Vinyl Scratch Emulation On iPhone (updated)

Currently, we are working on an iPhone game that will fundamentally feature an audio track scratching. We are using the BASS audio library (it seems that we should be able to use FMOD, too, since both libraries have almost identical functions) and a combination of Objective-C and C++.

Without looking at the shoulders of giants, I implemented a simple scratching demo. Basically, whenever the scratching is initiated, I pause the source stream and start playing a stream which has a “fill-audio-buffer” callback attached to it. That is, the audio is generated dynamically by filling a small playback buffer in the callback function. In the callback, a simple linear interpolation between the previous position in the scratch buffer and the current position is performed.

for (int i = 0; i < buffer_length; ++i) output[i] = input[F(i)];

where F is a piecewise linear function interpolating between [prev_sample_index, C * prev_pos_screen] and [curr_sample_index, C * curr_pos_screen]. The C multiplier corresponds to a relationship between a finger position (whether it’s a translational or a rotational motion) and the position in the source buffer. It turns out that the sound this method produces is too “digital”. It is far from a smooth vinyl scratching emulation. Consulting with our audio (and graphics) designer revealed that it is caused by abrupt changes in the playback frequency (= dF/dt). Thus started a few days of figuring out/tweaking parameters of various interpolation methods that would ensure smooth frequency slides during scratching.

At that point I started looking for other people trying to do the same. It turns out that although a lot of people claim/show they know how to achieve the effect, only a few share some code. Since my result seems to be satisfactory to our team, let me describe my approach and share the full source code.

There are two basic problems to deal with. The first one is how to interpolate the source wave to find intermediate values when F is a general function. The other one is to have smooth frequency changes.

1. Sound wave interpolation

This was the easier problem. A quick search revealed a Stack Overflow discussion and a useful article by Olli Niemitalo. Using the “6-point, 5th-order optimal 32x z-form implementation” interpolator seemed to have done the trick (maybe even a simpler interpolator would suffice).

2. Frequency interpolation

I was mainly looking at two features of the interpolation function (see curve fitting, smoothing) which seem to be on the opposite sides of a balance scale. One is smoothness of the function, the other one is fitness, that is, how close the function is to the recorded (interpolated) values.

Our approach is to get a nice smooth function without trying to achieve precision first and then apply tricks to push it closer to the recorded values. We discovered that a simple moving average of the scratching velocity (averaged over 3000 audio samples) and integrating the result works well. As expected, it quickly drifts away from the target position C * curr_pos_screen. To remedy this problem we chose to adjust the velocity to point in the direction towards the target position. And the further the target is (that is, the bigger the error), the larger the adjustment is. (Note: another approach would be to base the adjustment magnitude on the current scratching speed or acceleration rather than error.)

Feel free to look at the source code, use your own music, play around with the interpolation functions or change the input method.


Now using mmap to allow for scratching the whole length of the track. Download the source code.


An awesome port to ActionScript by Devon O. Wolfgang with source available here!

  • K

    Many many thanks to you for this! Absolutely awesome.

  • Daniel

    Great article and very well explained. I’m trying to implement the same effect in XNA for Windows Phone 7.

    Initially set the time to send the audio buffers to the device eg 100 ms and took the difference in position of the finger on the screen. With the delta calculated the offset position in the currently playing music, taking the array of samples to play and then applied a Catmull-Rom interpolation using an algorithm very similar to the one shown here showcode/3.php.

    As you have mentioned, the result produces very abrupt changes in frequency and does not reproduce the desired effect.

    I’ve been looking at your code and I lost a bit when once get smooth speed, calculate the position of the sample to be interpolated (from line 133 to 170 in Scratcher.cpp), if you could explain that part more fully appreciate what you .

  • Jan Kalis

    @Daniel – Thanks for the comment. Between lines 133 and 170 what step specifically would you want me to explain in more detail?

  • patrice

    Many thanks for this awesome job. The sound is very good. But I’ve a question : How can we increase the range of the scratch. I mean, we can scratch the sound already heard but how pre load the audio, for example : 10 seconds before and 10 seconds after, and use the both parts for increase the range.

    Many thank for advance

  • Daniel

    Hello again Jan.

    I finally got it working in XNA implementing a circular buffer and using moving average.
    But I’m still not satisfied with the result, I am trying to implement the RIAA equalization ( )using digital filters to see if I get the proper saturation.

  • Jan Kalis

    @patrice – You could definitely do that, it’s just that the mechanics of our game limit the scratching to the sound already heard.

  • Jan Kalis

    @daniel – Glad to hear that. Let me know how it goes!

  • patrice

    Thank for you response Jan. unfortunately I’m new with IOS and I’ve to admit since a week, I’ve got some trouble to find the part of your code where I have to modify, to remove the limit.

    If you have a small clue where or how I’ve to begin that’ll be very appreciate, because I’m gonna freak out :-))

  • Jan Kalis

    @partice – Currently, I am only allowing to scratch what has been played using a buffer that records the BASS’s playback internal buffer. But it doesn’t contain more samples then it needs for a smooth playback (only a couple of ms). You would have to force BASS to decode more bytes. From the top of my head, you could use the BASS_STREAM_DECODE flag in the BASS_StreamCreateFile function. That should enable you to record the stream to your custom buffer (without actually playing it back) which can be used for playback and scratching.

  • Jan Kalis

    @patrice – Check out the new code. It should do exactly what you need :)

  • rich

    awesome, this is precisely what i need for the dj softare im making for touchscreens. its a shame i dont have time to implement it in fmod before i demonstrate my work next friday, dont suppose anyone has done this already?

  • Jan Kalis

    @rich – I haven’t worked on an FMOD version, but as you can see from the source code, there’s only a very limited amount of BASS functions and all of them are easily converted to FMOD.

    Warning: the code is a proof of concept only and is far far from a release quality code! Use on your own risk!

  • suzi

    I’ve been working on a FMOD port for this but I’m currently stuck on this line within the Scratcher.cpp class:

    soundTrackScratchStreamHandle_ = BASS_StreamCreate(BASE_PLAYBACK_FREQUENCY, 2, BASS_SAMPLE_FLOAT, Scratcher::WriteScratchStream, this);

    Which corresponding FMOD function should be used? Any hints would be very much appreciated!

  • Jan Kalis

    @suzi – looking at the FMOD documentation, it seems that FMOD_System_CreateSound with mode FMOD_OPENUSER and defined FMOD_FILE_READCALLBACK callback in exinfo should do the trick. (I haven’t done a FMOD port yet, so I may be wrong, but hopefully it will point you in the right direction.)

  • suzi

    @Jan – Thanks! That will definitely help me out. I’ll try what you suggested today and see how it goes.

  • suzi

    @Jan I have another question, where exactly does the playback occur once the user initiates the scratching?

    I tried to follow the code and only only found one playback instruction in the Scratcher class which plays back the normal song (forward direction, normal speed):

    BASS_ChannelPlay(soundTrackScratchStreamHandle_, false);


  • Jan Kalis

    @suzi – BASS_ChannelPlay(soundTrackScratchStreamHandle_, false) starts a stream on the app start which, instead of reading from a file or memory, is using the WriteScratchStream callback to write to a buffer that BASS asks the callback to fill. This buffer will be played back within a couple of milliseconds (based on the BASS settings).

    There is no switching between the normal playback and the scratching playback, because you can think of the normal playback as scratching at *exactly* 44.1kHz, or the speed of a record.

  • suzi

    @Jan, Thanks for the kind explanation. I now understand how that callback function works. Since BASS plays back using buffer data in the callback, would it still be possible to apply further effects such as pitch change ?

  • Jan Kalis

    @suzi – You can still attach a dsp effect to the channel without any problems or do any additional processing in the callback function.

  • john

    Hi Jan, I’m currently also working with BASS but cannot seem to figure out how to create a sort of SEEK function..would you happen to have any information on how to get that started? All I need is a nudge in the right direction and I think I can figure the rest out..

  • Jan Kalis

    @john – if you’re using a write-to-buffer callback like I am, that should fairly straightforward, just read from the position that you’re seeking to. Otherwise, there is BASS_ChannelSetPosition function for seeking. You can also ask on the BASS forums, the admin seems to be really responsive.

  • John Lee

    @ Jan – Thank you so much for your quick reply! This is a great start..I was just wondering though, how can I add seek (BASS_ChannelSetPosition and BASS_ChannelGetPosition equivalents) to your example write-to-buffer callback function? I will also start using the Bass forums as well…

  • Jan Kalis

    @John – I believe it should as simple as resetting positionOffset_ and scratchingPositionOffset_ to the position you are seeking to.

  • John

    @ Jan – I’ve been able to get the SEEK function working as you suggested. Thanks! However, I’ve been struggling a lot on getting an understanding of the Scratcher::WriteScratchStream function, and still having trouble… would it be possible to provide a little more explanation on how it works … such as the purpose of the variables and perhaps some explanation on the logic flow?

  • Jan

    @John – sorry for late reply. We’ve been finishing the game ( that’s using the algorithm lately. I modified the function a couple of times so I may have a new version soon. Meanwhile, do you more specific questions?

  • John

    @Jan – Thanks for your consistent interest in trying to help me out. My specific questions are regarding the Scratcher::WriteScratchStream function.

    1) Why do we create another Scratcher object here – Scratcher* soundTrackScratcher = static_cast(user);

    2) What is the relationship between smoothBufferPosition, scratchingPositionOffset and positionOffset

    3) What is the difference between scratchingPositionSmoothedVelocity and scratchingPositionVelocity?

  • Trent

    Hi Jan,

    Looks like a cool game ^^ . Is there any chance for an update on the code?


  • sagar bhavsar

    When i used this code to load multiple songs using this code then it gives error on .

    mappedMemory_ = mmap(
    NULL, /* No preferred address. */
    mappedMemorySize_, /* Size of mapped space. */
    PROT_READ | PROT_WRITE, /* Read/write access. */
    MAP_FILE | MAP_SHARED, /* Map from file (default) and map as shared (see above.) */
    fd, /* The file descriptor. */
    0 /* Offset from start of file. */

    if (mappedMemory_ == MAP_FAILED)

    I got Map_failed on mmap() when use it frequently for loading songs using your code.

    Any help will be appreciated !!

    Thanks in advance..

  • Jan

    In the end, I decided to move away from mmap and using intermediate buffers for uncompressed audio. It solved a couple of issues – mainly that the mapped file took a lot of space on the disk. I’ll try to get an updated version out – but we are super-busy with new projects.

  • onebyoneblog » Towards a Better Scratch

    [...] could find any ready made examples I could just copy. The most comprehensive thing I could find was this C++ / Obj-C example from Jan Kalis. So I downloaded the source and made a half assed attempt at just a straight port to Actionscript. [...]

  • Patrice Peau

    Hi again Jan, I will be very interrested to see, how you’ve replaced the mmap with your intermediate buffers because I tried it, but each time I have got some trouble with the position that I have to set in the circular buffer. Thank for all Patrice

  • Jan

    Hi Patrice, There probably won’t be any source code update any time soon, since I’ve recently left Glow. I can’t give you any specific pointers now, but briefly, I used 5 buffers (not circular!) which are discarded and loaded on-demand as the track position moves through the track. You can think of it as the track being completely covered by fixed-size windows, which are loaded in the buffers as needed. E.g. the windows are

    [0-2seconds of the track, window(0)][2-4s, window(1)][4-6s, window(2)][6-8s, window(3)]…

    Before the track starts, windows window(-2), window(-1), window(0), window(1) and window(2) are loaded in buffers b0, b1, b2, b3, b4 respectively and the track position starts in window(0). As the track position progresses and switches from window(0) to window(1) (or buffer b2 to b3), buffer b0 is discarded and window(3) loaded into it. At this point, window(-1), window(0), window(1), window(2) and window(3) are loaded into buffers b1, b2, b3, b4, b0 (in this order). If it progress further, window(0), … window(4) are loaded into buffers b2, b3, b4, b0, b1. Etc.

    A couple of technical difficulties: when to do the load exactly, how big the buffers are and how many, what to do at the end of the track when the buffer cannot be fully filled, etc. Bear in mind, that this is only one of many solutions – this one was relatively easy to understand and implement to keep the maintainability high.

    This approach was very advantageous in our situation, because it gave us fine-tune control over the caching behavior.

  • Max

    I don’t fully understand line 157 (and following) in Scratcher.cpp. It ensures that scratchingPositionOffset is never greater than 0. So, the fBufferPosition in line 160 will never be greater than the bufferBasePosition. Am I missing something?