Author Topic: Implementing vinyl scratching sound  (Read 314 times)

kafffee

  • Posts: 155
Implementing vinyl scratching sound
« on: 1 Dec '22 - 21:10 »
Hi everybody  :)

I found this demo code in this forum for creating a vinyl scratching sound:

Code: [Select]
if (scratchVelocity < 100 && scratchVelocity > -100000)
    {
        BASS_ChannelStop([bassPointer getChannel]);
        QWORD pos=BASS_ChannelGetPosition([bassPointer getChannel], BASS_POS_BYTE); // get current position
        BASS_ChannelSetPosition([bassPointer getChannel], pos, BASS_POS_BYTE); // "seek" there to clear output buffer (avoid latency)
        BASS_ChannelSetAttribute([bassPointer getRevChannel], BASS_ATTRIB_REVERSE_DIR, BASS_FX_RVS_REVERSE);
        BASS_ChannelPlay([bassPointer getChannel], false);
        NSLog(@"scratchVelocity %f", -scratchVelocity);
        BASS_ChannelSlideAttribute([bassPointer getChannel], BASS_ATTRIB_FREQ, -scratchVelocity, 100);
       
    }
    else if (scratchVelocity > 100 && scratchVelocity < 100000)
    {
        BASS_ChannelStop([bassPointer getChannel]);
        QWORD pos=BASS_ChannelGetPosition([bassPointer getChannel], BASS_POS_BYTE);
        BASS_ChannelSetPosition([bassPointer getChannel], pos, BASS_POS_BYTE);
        BASS_ChannelSetAttribute([bassPointer getRevChannel], BASS_ATTRIB_REVERSE_DIR, BASS_FX_RVS_FORWARD);
        BASS_ChannelPlay([bassPointer getChannel], false);
        NSLog(@"scratchVelocity %f", scratchVelocity);
        BASS_ChannelSlideAttribute([bassPointer getChannel], BASS_ATTRIB_FREQ, scratchVelocity, 100);
    }

Seems to be pretty easy, but what is this  NSLog(@"scratchVelocity %f", scratchVelocity); line? What does it do? Is there anything else I should think of?

Chris

  • Posts: 2105
Re: Implementing vinyl scratching sound
« Reply #1 on: 2 Dec '22 - 07:47 »
NsLog I think it's  a Logfile

kafffee

  • Posts: 155
Re: Implementing vinyl scratching sound
« Reply #2 on: 2 Dec '22 - 08:15 »
Ah okay so this must be only for debugging purposes?

Ian @ un4seen

  • Administrator
  • Posts: 24802
Re: Implementing vinyl scratching sound
« Reply #3 on: 2 Dec '22 - 18:01 »
Yes, the NSLog function is for logging/debugging on macOS/iOS platforms:

   https://developer.apple.com/documentation/foundation/1395275-nslog

Regarding the subject of a vinyl scratching effect, here's an old thread on the subject:

   www.un4seen.com/forum/?topic=9754.msg68053#msg68053

Unfortunately, the download links appear to be dead now, but there's still some code/discussion that you may find helpful.

kafffee

  • Posts: 155
Re: Implementing vinyl scratching sound
« Reply #4 on: 3 Dec '22 - 09:13 »
Yes I've already seen this. I dont really get along with the code so I want to stick with what I have:

I translated to c# and now it looks like this:

Code: [Select]
private void Scratching(TimeSpan ts)
        {
            if (ts.Milliseconds < 100 && ts.Milliseconds > -100000)
            {
                Bass.BASS_ChannelStop(streamforward);
                long pos = Bass.BASS_ChannelGetPosition(streamforward, BASSMode.BASS_POS_BYTE); // get current position
                Bass.BASS_ChannelSetPosition(streamforward, pos, BASSMode.BASS_POS_BYTE); // "seek" there to clear output buffer (avoid latency)
                Bass.BASS_ChannelSetAttribute(streamreverse, BASSAttribute.BASS_ATTRIB_REVERSE_DIR, -1);
                Bass.BASS_ChannelPlay(streamforward, false);
                //NSLog(@"scratchVelocity %f", -scratchVelocity);
                Bass.BASS_ChannelSlideAttribute(streamforward, BASSAttribute.BASS_ATTRIB_FREQ, -ts.Milliseconds, 100);

            }
            else if (ts.Milliseconds > 100 && ts.Milliseconds < 100000)
            {
                Bass.BASS_ChannelStop(streamforward);
                long pos = Bass.BASS_ChannelGetPosition(streamforward, BASSMode.BASS_POS_BYTE);
                Bass.BASS_ChannelSetPosition(streamforward, pos, BASSMode.BASS_POS_BYTE);
                Bass.BASS_ChannelSetAttribute(streamreverse, BASSAttribute.BASS_ATTRIB_REVERSE_DIR, 1);
                Bass.BASS_ChannelPlay(streamforward, false);
                //NSLog(@"scratchVelocity %f", scratchVelocity);
                Bass.BASS_ChannelSlideAttribute(streamforward, BASSAttribute.BASS_ATTRIB_FREQ, ts.Milliseconds, 100);
            }
        }

Looks like something is wrong, because when I scratch, all I hear is a buzzing.

Edit: When I spin the turntable for either just two or three pixels or When I spin as fast as I can do with my mouse I can hear something like a scratching sound for like half a second.

I guess it has to do with the values for ts. Anybody know an approach on how to calculate this argument? Should I  better pass a TimeSpan or a velocity?

Edit: Ah okay I found out about it. Must be the sampling rate and I have to leave out everything but ChannelSetAttribute and ChannelSlideAttribute...

Further questions may come...  :)
« Last Edit: 4 Dec '22 - 07:27 by kafffee »

Ian @ un4seen

  • Administrator
  • Posts: 24802
Re: Implementing vinyl scratching sound
« Reply #5 on: 5 Dec '22 - 13:49 »
Those old threads and code are from before BASS had the option of disabling playback buffering. So seeking was used to reset the playback buffer and avoid latency when switching direction. These days you can disable playback buffering instead (by setting BASS_ATTRIB_BUFFER to 0 via BASS_ChannelSetAttribute), which allows your function to be reduced to something like this:

Code: [Select]
private void Scratching(TimeSpan ts)
        {
            if (ts.Milliseconds < 100 && ts.Milliseconds > -100000)
            {
                Bass.BASS_ChannelSetAttribute(streamreverse, BASSAttribute.BASS_ATTRIB_REVERSE_DIR, -1);
                Bass.BASS_ChannelSlideAttribute(streamforward, BASSAttribute.BASS_ATTRIB_FREQ, -ts.Milliseconds, 100);

            }
            else if (ts.Milliseconds > 100 && ts.Milliseconds < 100000)
            {
                Bass.BASS_ChannelSetAttribute(streamreverse, BASSAttribute.BASS_ATTRIB_REVERSE_DIR, 1);
                Bass.BASS_ChannelSlideAttribute(streamforward, BASSAttribute.BASS_ATTRIB_FREQ, ts.Milliseconds, 100);
            }
        }

kafffee

  • Posts: 155
Re: Implementing vinyl scratching sound
« Reply #6 on: 19 Jan '23 - 12:53 »
Ok I can hear something like a scratching when I do this. But the tones coming out seem to be too low, just as if you would turn down the pitch.

I have been trying with all types of values for SamplingRate. Is there a better way to do it?

Code: [Select]
Public Sub New()
Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_DEFAULT, Nothing)
MainModule.forwardstream = Bass.BASS_StreamCreateFile("C:\Test.mp3", -1, -1, BASSFlag.BASS_STREAM_PRESCAN)
MainModule.reversestream = BassFx.BASS_FX_ReverseCreate(MainModule.forwardstream, 2.0F, BASSFlag.BASS_FX_FREESOURCE)
Bass.BASS_ChannelGetAttribute(MainModule.forwardstream, BASSAttribute.BASS_ATTRIB_FREQ, SampleRate)
Bass.BASS_ChannelPlay(MainModule.forwardstream, False)
Bass.BASS_ChannelPlay(MainModule.reversestream, False)
End Sub

Private Sub ScratchingCommand_Execute(_Rate As Integer)

        Dim SamplingRate As Integer = _Rate

        If ((SamplingRate1 < 100) AndAlso (SamplingRate1 > -100000)) Then
            Bass.BASS_ChannelSetAttribute(MainModule.reversestream, BASSAttribute.BASS_ATTRIB_REVERSE_DIR, -1)
            Bass.BASS_ChannelSlideAttribute(MainModule.forwardstream, BASSAttribute.BASS_ATTRIB_FREQ, CSng(-SamplingRate), 100)
        ElseIf ((SamplingRate1 > 100) AndAlso (SamplingRate < 100000)) Then
 

            Bass.BASS_ChannelSetAttribute(MainModule.reversestream, BASSAttribute.BASS_ATTRIB_REVERSE_DIR, 1)
            Bass.BASS_ChannelSlideAttribute(MainModule.forwardstream, BASSAttribute.BASS_ATTRIB_FREQ, CSng(SamplingRate), 100)

        End If
    End Sub

Ian @ un4seen

  • Administrator
  • Posts: 24802
Re: Implementing vinyl scratching sound
« Reply #7 on: 19 Jan '23 - 16:01 »
Ok I can hear something like a scratching when I do this. But the tones coming out seem to be too low, just as if you would turn down the pitch.

That sounds like the BASS_ATTRIB_FREQ setting is lower than you want. How are you calculating the ScratchingCommand_Execute call parameter? I notice that function references "SamplingRate" and "SamplingRate1" variables. Is "SamplingRate1" just a typo in the post and not in the code?

kafffee

  • Posts: 155
Re: Implementing vinyl scratching sound
« Reply #8 on: 19 Jan '23 - 21:40 »
Oh yes thats a typo sorry :o

I have a jog wheel on a hardware mixing desk, whose commands I can receive via MIDI. It will send a value 1 when moved forwards and 127 in reverse. This command is being sent 128 times for each rotation.

This is how I am trying to calculate it:

Code: [Select]
Private oldTime As DateTime = DateTime.Now            'get current system time when initialized
Private newTime As DateTime                                   'declare variable to measure time in between two events

And this is being called when a MIDI-event happens, respectively the jog wheel is being moved:

Code: [Select]
Public Sub NewMIDI-Event()

newTime = DateTime.Now                                     'get current system time when jog wheel is being moved
Dim timeDifference As TimeSpan = newTime - oldTime          'get timespan in between two moving-events
oldTime = newTime                                                             'update old time for the next call

If MIDIEvent.Value = 127 Then                                              'If wheel has been moved in reverse then
   ScratchingCommand_Execute(-100000) 'with this value it sounds like the tones are too low
Else                                                                                     ' íf wheel has been moved forward
   ScratchingCommand_Execute(100000) 'with this value it sounds like the tones are too low
End Sub

Now what I need to get is a correct value instead of 100000 respectively -100000...

I am not exactly a genius at mathematics and I 've been staring at my screen and contemplating for like two hours now, but I need to somehow get the correct sampling rate out of  the varibale timeDifference, which should return the timespan in between two moving-events (which occur 128 times at each rotation) in milliseconds when I do:

timeDifference.TotalMilliSeconds

When I assume, that a record is spinning with 45 rpm and the event happens 128 times per rotation I should somehow be able to get this...?

jpf

  • Posts: 159
Re: Implementing vinyl scratching sound
« Reply #9 on: 20 Jan '23 - 03:45 »
I never did vinyl scratching in my life but I did a lot of cueing when I had to cover for an absent desk operator in the radio station I worked for 43 years, so I can figure out how scratching must sound.

For simplicity let's consider just the forward scratching.
Let's assume one turn of the jog wheel would move the 45 rpm vinyl exactly one turn.
The nominal rotational speed of the record is 45/60 turn/sec.
At that speed and 128 Midi events per turn, we have one Midi event each 45/60/128 sec. or approx. 5.86 ms.
At that jog speed the samplerate must be 1x the native samplerate of the file being played.
For shorter timeDifference the samplerate must rise, and viceversa.
So the scratching samplerate should be: (native file samplerate) * 5.86 / timeDifference.
(Please check my math. I did it all in my mind.)

I anticipate an ill extreme case of this approach due to the scratching samplerate being applied after each Midi event and not at a fixed interval.
Suppose you're turning the jog wheel at a somehow high speed, producing a high pitched scratch sound. If you then suddenly stop turning it, the next Midi event will never arrive and the pitch will stay high forever.
A possible workaround could be to have a timer set on each Midi event that will call ScratchingCommand_Execute(native file samplerate) if the next Midi event doesn't arrive within a specified time.
That will sound like you releasing your hand from the vinyl and letting the turntable spin it at normal speed.
But if you instead wanted to hold the vinyl steady waiting for the right moment to mix it in by not moving the jog wheel, then the timer must pause the playback instead.
I think the pausing and unpausing could be implemented inside the ScratchingCommand_Execute sub for simplicity. If called with an argument < 100 then it should pause, else it should unpause.

kafffee

  • Posts: 155
Re: Implementing vinyl scratching sound
« Reply #10 on: 20 Jan '23 - 06:05 »
Wow cool I can use that  :D

I'll let you know how it worked out, thank you


>>>>>>>>>Edit:

So far so good. Now I got this:

Code: [Select]
Bass.BASS_ChannelGetAttribute(MainModule.forwardstream, BASSAttribute.BASS_ATTRIB_FREQ, SampleRate)

Public Sub NewMIDI-Event()

newTime = DateTime.Now
Dim timeDifference As TimeSpan = newTime - oldTime
oldTime = newTime

If (timeDifference.TotalMilliseconds > 80) AndAlso (IsPaused = False) Then
            Bass.BASS_ChannelPause(MainModule.forwardstream)
            Bass.BASS_ChannelPause(MainModule.reversestream)
            IsPaused = True
Else
            If IsPaused = True Then
                Bass.BASS_ChannelPlay(MainModule.forwardstream, False)
                Bass.BASS_ChannelPlay(MainModule.reversestream, False)
                IsPaused = False
            End If

            If MyMIDIHandler.MIDIEventParser.ControlID = 51 Then
                If MyMIDIHandler.MIDIEventParser.ControlValue = 127 Then
                    ScratchingCommand_Execute(SampleRate * 33 / 60 * 1000 / 128 / timeDifference.TotalMilliseconds)
                Else
                    ScratchingCommand_Execute(-SampleRate * 33 / 60 * 1000 / 128 / timeDifference.TotalMilliseconds)
                End If
            ElseIf MyMIDIHandler.MIDIEventParser.ControlID = 52 AndAlso MyMIDIHandler.MIDIEventParser.ControlValue = 0 Then     'if jog wheel is released
                ScratchingCommand_Execute(SampleRate)   'reset to native sample rate
            End If
        End If
End Sub

Luckily, my mixing desk also sends a MIDI command, when the jog wheel is releaed, so I don't have to worry about whether I want to pause the music or play it at native sample rate...

It's actually supposed to work I guess, but it doesn't... It sounds more like a quick pitch bending, but not like a scratching...

Any ideas?
« Last Edit: 20 Jan '23 - 08:10 by kafffee »

jpf

  • Posts: 159
Re: Implementing vinyl scratching sound
« Reply #11 on: 20 Jan '23 - 15:54 »
Your code seems fine. Maybe test small fragments of it separatelly to see if they behave as expected?

1-Does the ScratchingCommand_Execute sub produce the expected pitch? You can try calling the sub with let's say the native samplerate, 1/2 of that, twice of that, etc. and check that you get the expected pitch. Also check those values *-1 for reverse rotation. Be sure to check out of bounds values too.
2-Is the ScratchingCommand_Execute sub able to cope with the expected rate of arrival of Midi events? I'm not sure how to check this. Maybe you can set a timer to send fake Sub NewMIDI-Event calls at a high enough rate and check that timeDifference is always = the timer interval.
3-Is timeDifference really roughly inverse proportional to the jog wheel rotation speed? Since it's not ease to rotate the wheel at a constant speed by hand you may want to use some motorized gadget to do it (for instance a mirror ball motor).
4-Is the argument sent to ScratchingCommand_Execute proportional to the rotational speed of the jog wheel?

Even if ScratchingCommand_Execute passes the test it's not a perfect emulation of rotating the vinyl at very low and very high speeds. You should also mind that a real moving magnet pick-up cartridge output voltage is proportional to the rotational speed of the vinyl. This is roughly compensated by the RIAA frecuency responce curve of the preamplifier, but just roughly.

kafffee

  • Posts: 155
Re: Implementing vinyl scratching sound
« Reply #12 on: 7 Feb '23 - 11:26 »
Sry for the late response, I did not really get to work on this earlier.

I did some testing, here go the results (I think I found whats wrong with it):

(1) ScratchingCommand_Execute produces the right pitch, but when I pass a negative value as an argument (obj), it will not play backwards. This must be the main issue. Any ideas how to fix this? I do not really see through this piece of code, so any help is appreciated:

Code: [Select]
Private Sub ScratchingCommand_Execute(obj As Object)
        Dim Samplingrate1 As Integer = CInt(obj)   'turn object into integer
       
        If ((Samplingrate1 < 100) AndAlso (Samplingrate1 > -Limit)) Then         

            Bass.BASS_ChannelSetAttribute(MainModule.reversestream, BASSAttribute.BASS_ATTRIB_REVERSE_DIR, -1)         
            Bass.BASS_ChannelSlideAttribute(MainModule.forwardstream, BASSAttribute.BASS_ATTRIB_FREQ, CSng(-Samplingrate1), 100)
           
        ElseIf ((Samplingrate1 > 100) AndAlso (Samplingrate1 < Limit)) Then
           
            Bass.BASS_ChannelSetAttribute(MainModule.reversestream, BASSAttribute.BASS_ATTRIB_REVERSE_DIR, 1)           
            Bass.BASS_ChannelSlideAttribute(MainModule.forwardstream, BASSAttribute.BASS_ATTRIB_FREQ, CSng(Samplingrate1), 100)

           Else

            Bass.BASS_ChannelSetAttribute(MainModule.reversestream, BASSAttribute.BASS_ATTRIB_REVERSE_DIR, 1)
            Bass.BASS_ChannelSlideAttribute(MainModule.forwardstream, BASSAttribute.BASS_ATTRIB_FREQ, CSng(0), 100)

        End If

    End Sub

(2) As a matter of fact, the calculated interval is different from the actual interval in between the calls. I logged this, and this was returned:
0measured time: (+)3208,7395
0actual interval: (+)100

1measured time: (+)631,2004
1actual interval: (+)600

2measured time: (+)1128,5063
2actual interval: (+)1100

3measured time: (+)1628,1082
3actual interval: (+)1600

4measured time: (+)2107,17
4actual interval: (+)2100

5measured time: (+)2621,6564
5actual interval: (+)2600

6measured time: (+)3112,9302
6actual interval: (+)3100

7measured time: (+)3626,2407
7actual interval: (+)3600

8measured time: (+)4118,6537
8actual interval: (+)4100

9measured time: (+)4614,8074
9actual interval: (+)4600

10measured time: (+)5123,1657
10actual interval: (+)5100

I don't know if this is negligible, I guess I'd have to fix issue no. (1) first to find out...

Ian @ un4seen

  • Administrator
  • Posts: 24802
Re: Implementing vinyl scratching sound
« Reply #13 on: 7 Feb '23 - 16:03 »
I notice you have "reversestream" and "forwardstream" handles. Referring back to your earlier post, that code looks problematic. Firstly, BASS_FX_ReverseCreate requires a decoding channel (with BASS_STREAM_DECODE flag set). Secondly, you should only be playing the reverse stream (which also supports forward playback via BASS_ATTRIB_REVERSE_DIR), not the original file stream too. Try changing that code like this:

Code: [Select]
Public Sub New()
Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_DEFAULT, Nothing)
MainModule.decoder = Bass.BASS_StreamCreateFile("C:\Test.mp3", 0, 0, BASSFlag.BASS_STREAM_PRESCAN or BASSFlag.BASS_STREAM_DECODE)
MainModule.reversestream = BassFx.BASS_FX_ReverseCreate(MainModule.decoder, 2.0F, BASSFlag.BASS_FX_FREESOURCE)
Bass.BASS_ChannelGetAttribute(MainModule.reversestream, BASSAttribute.BASS_ATTRIB_FREQ, SampleRate)
Bass.BASS_ChannelPlay(MainModule.reversestream, False)
End Sub

And then change the ScratchingCommand_Execute function like this:

Code: [Select]
Private Sub ScratchingCommand_Execute(Samplingrate1 As Integer)
       
        If (Samplingrate1 < 0) Then         

            Bass.BASS_ChannelSetAttribute(MainModule.reversestream, BASSAttribute.BASS_ATTRIB_REVERSE_DIR, -1)         
            Bass.BASS_ChannelSlideAttribute(MainModule.reversestream, BASSAttribute.BASS_ATTRIB_FREQ, CSng(-Samplingrate1), 100)
           
        Else
           
            Bass.BASS_ChannelSetAttribute(MainModule.reversestream, BASSAttribute.BASS_ATTRIB_REVERSE_DIR, 1)           
            Bass.BASS_ChannelSlideAttribute(MainModule.reversestream, BASSAttribute.BASS_ATTRIB_FREQ, CSng(Samplingrate1), 100)

        End If

    End Sub

If it still isn't sounding right, please try logging the "Samplingrate1" values in there when you move your jog wheel back and forward to see what they are.

kafffee

  • Posts: 155
Re: Implementing vinyl scratching sound
« Reply #14 on: 8 Feb '23 - 10:02 »
Yes it still sounds about the same  :(

Here is a part of the logs:

Samplingrate1: -10733
Event triggered!!
Samplingrate1: -26398
Event triggered!!
Samplingrate1: -14817
Event triggered!!
Samplingrate1: -29839
Event triggered!!
Samplingrate1: -17106
Event triggered!!
Samplingrate1: -22012
Event triggered!!
Event triggered!!
Samplingrate1: 44100
Event triggered!!
Event triggered!!
Event triggered!!
Samplingrate1: 1955
Event triggered!!
Samplingrate1: 32314
Event triggered!!
Samplingrate1: 16491
Event triggered!!
Samplingrate1: 28728

Notice that the change of driection seems to work right. but some of the events seem to get "swallowed up". At some points the midi event gets triggered but without changing SamplingRate1, as you can see...
« Last Edit: 8 Feb '23 - 10:47 by kafffee »

jpf

  • Posts: 159
Re: Implementing vinyl scratching sound
« Reply #15 on: 8 Feb '23 - 14:34 »
Those logs seem wrong. Too few events and the values are too far appart from next/previous.

(For a 1/4 turn Backspin you should get 128/4=32 events triggered about 5 ms appart from each other (that would translate into a -44100 samplerate). I'd say, if you're not very handy values from 3 to 7 ms could still do even if they'll sound too low / high pitched, but they should be about constant or smoothly varying across the whole Backspin except at the very beginning. Anything else won't sound like a Backspin.)

To see if you're skipping events because of the ScratchingCommand_Execute taking too much time you could skip it and just log the events.

I expect you're handy on real (physical) vinyl scratching and/or Serato and are comparing the sound of the same scratch effect (you didn't mention what that effect was: Baby scratch? Chirp? Scribble? etc.?).

I would still advice on breaking down the code and testing each part independently, so to identify which part isn't working properly.

Ian @ un4seen

  • Administrator
  • Posts: 24802
Re: Implementing vinyl scratching sound
« Reply #16 on: 8 Feb '23 - 15:26 »
I forgot to disable playback buffering in my last post. Try adding that:

Code: [Select]
Public Sub New()
Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_DEFAULT, Nothing)
MainModule.decoder = Bass.BASS_StreamCreateFile("C:\Test.mp3", 0, 0, BASSFlag.BASS_STREAM_PRESCAN or BASSFlag.BASS_STREAM_DECODE)
MainModule.reversestream = BassFx.BASS_FX_ReverseCreate(MainModule.decoder, 2.0F, BASSFlag.BASS_FX_FREESOURCE)
Bass.BASS_ChannelSetAttribute(MainModule.reversestream, BASSAttribute.BASS_ATTRIB_BUFFER, 0) ' disable playback buffering
Bass.BASS_ChannelGetAttribute(MainModule.reversestream, BASSAttribute.BASS_ATTRIB_FREQ, SampleRate)
Bass.BASS_ChannelPlay(MainModule.reversestream, False)
End Sub

Regarding the "swallowed up" events, please show where exactly you are writing the "Event triggered" and "Samplingrate1" log entries to confirm whether the latter should always follow the former (and why it might not).