DJ turntable platter physics simulation: Link sound pitch to platter rotation speed?

UPDATED WITH VIDEO AND LATEST BLEND:


LATEST: Updated with proper README, CHANGELOG, TODO - http://files.djbarney.org/dj_turntable_platter_physics_simulation_97.blend UPDATED DOWNLOAD LOCATION: http://files.michaelzfreeman.org/dj_turntable_platter_physics_simulation_97.blend

Previous:
Forward and reverse now working but position is not synched yet - http://files.djbarney.org/dj_turntable_platter_physics_simulation_77_FORWARD_AND_REVERSE_WORKING.blendOriginal post below.


Hi,

Iā€™ve included the Blender file. Simple simulation of a vinyl turntable platter. Thatā€™s working fine. Left and right mouse buttons spin the platter left/right and then you can see it slowly come to a standstill. But I canā€™t work out how to link the rotation speed to the pitch of a sound. This is why Iā€™m doing this to implement DJ deck effects for music tracks when either A. the record in vinyl is not available, B. Serato time code record not available. There is no application (that I can find) that replicates the various turntable effects. Not even Ableton can do it although that was originally designed as a live looper so thatā€™s hardly surprising. Iā€™m talking about when a DJ mixes out the track by switching off the deck so the current track slowly comes to a standstill (with the audio still mixed in) as the next track comes in. Spin backs. Changes in pitch between 45 and 33. That can all be modelled using the BGE physics engine, but of course it has to be linked to the sound somehow and thatā€™s where I got stuck. However there is some rudimentary object position reporting in python.

dj_turntable_platter_physics_simulation.blend (415 KB)

1 Like

Grab the speed of rotation with:


rot_speed = platter.worldAngularVelocity.z

And write it to the pitch by doing something like:


sound_actuator.pitch = rot_speed

You can get an object by name:


platter = bge.logic.getCurrentScene().objects['platter_name']

I may have a look at that blend in an hour or so, sounds like an interesting project.

Thanks :), that makes a lot more sense now. After checking Important threads and FAQs ā€¦ https://blenderartists.org/forum/showthread.php?241891-Important-threads-and-FAQs

ā€¦ I got a better understanding of the main loop and engine tick cycles, hence I had not put True Level Triggering (pulse mode) on the ā€œalwaysā€ python sensor as it was only running once ! But now I have a nice stream of numbers as the platter rotates left and right. I also turned the python into a module (recommended apparently). Now to hitch that up to the pitch control.

dj_turntable_platter_physics_simulation_9_python_as_module.blend (412 KB)

I like to see someone using the audio side of Blender for a utility !! Neat!

Thought so myself :cool:

So I managed to do it.

dj_turntable_platter_physics_simulation_11_WORKING_PITCH.blend (414 KB)

http://files.djbarney.org/04%20-%20renato%20cohen%20-%20pontape%20(trevor%20rockcliffe%20remix)00000334_break_loop.wav

The codes a bit messy at the moment, and it wonā€™t play backwards (not unexpected). Be gentle with the mouse controls as they are probably a bit over sensitive. But starting the turntable/platter and then letting it slow down gives that classic dj turntable/Technics 1210 slow down sound. This is physics simulation connected to the sound. It escapes me, unless Iā€™m missing some stuff out there (always a possibility) why this well established music/mixing technique has not entered the digital realm. I had to analyse what that smooth dj turntable/technics 1210 sound is exactly, when it comes to playing music at different speeds/slowing down/reversing. Then it came to me that its the physics of that heavy platter with the powerful servo connected to it. Then the brain wave of using the BGE physics simulation to replicate this. BTW playing records at the wrong speed has a long history ā€¦

DJs have long experimented with the speeds of their records to add a little bit of personality to their sets. Patrick Ryder follows in the footsteps of the great dancefloor innovators to pick out ten records which shine when played at the wrong speed.

ā€¦following the same path as a host of avant garde musicians (Pierre Schaeffer), adventurous DJs (Levan, Baldelli, Loda) and gloriously unprofessional radio hosts (John Peel), whose experiments with speed, whether deliberate or accidental were creating weird and wonderful results all over the globe.

Quotes from: https://web.archive.org/web/20151027020415/http://www.thevinylfactory.com/vinyl-factory-releases/pitch-shift-10-records-that-sound-incredible-at-the-wrong-speed/[https://en.wikipedia.org/wiki/Larry_Levan](https://en.wikipedia.org/wiki/Larry_Levan) https://en.wikipedia.org/wiki/Afro/Cosmic_music

Next problem. How to include playing backwards ?

Some more progress.

dj_turntable_platter_physics_simulation_32.blend (436 KB)

Track: http://files.djbarney.org/SEEDS%20MASTER%203.wav (originally a free download from Nick Lewis).

Really getting into this now. The ā€œdeckā€ now has, just like a real Technics 1210, a start/stop button (SPACE), and an on/off button (ALT). The audio keeps playing when its turned off until the turntable comes to a standstill. The ALT control sometimes needs a couple of presses, prob a bug in my logic somewhere. The deck now spins the correct direction. Please not everything is completely (or should be) completely interoperable. So while in play mode you can still nudge with left and right mouse buttons and the sound responds appropriately.

Iā€™ve started looking at tracking the audio position using audspace which should allow both playing backwards and being able to select the play position (like moving the stylus to another position in the record).

Iā€™m going to jump in here and make the suggestion that you convert most (make that all) of the logic bricks to python. Maintinance of python code is a lot easier than maintaining logic bricks.

In python, to get keypresses you can use:


import bge

def get_key(key_name):
    key_id = bge.events.__dict__[key_name]
    return bge.logic.keyboard.events[key_id]


if get_key('WKEY') != 0:
    print("Key was pressed")

if get_key('TABKEY') == bge.events.bge.logic.KX_INPUT_JUST_RELEASED:
    print("Key was released")

(Untested).

You can also make the python script external to the blend file, allowing things like version control (eg git), as well as using your favourite text-editor/IDE.

If youā€™re using aud, youā€™ll end up having significant python components anyway, so why not go the whole hog?

Python solving all your problems? Actually, very often it will. The things you can do with logic bricks are a minuscule subset of what you can do with python. Debugging python is far easier than debugging a stack of logic bricks. When youā€™ve tried to manage more than 20-30 bricks on an object youā€™ll discover what I mean. Maintaining 2000 lines of code is easier than maintaining 100 logic bricks in my opinion.

Yes, use module mode. I donā€™t think Iā€™ve touched script mode in a very long time. I just use the word ā€œscriptiingā€ to imply python use, not script mode specifically.

ā€œThe logic bricks are fasterā€ is perhaps one of Monsters most misunderstood lines. Imagine writing a python ā€œAndā€ controller:


def and(cont):
    for sens in cont.sensors:
        if not sens.positive:
            return
    for act in cont.actuators:
        cont.activate(act)

Is that slower than an And logic brick? Of course. Weā€™re working in a higher-level language and thereā€™s the overhead from running python to begin with.

But on other cases, such as raycasting for several different objects:


def rawCastTest(cont):
    obj_name = cont.owner.rayCastTo('target')
    if obj_name = "enemy":
        do_something
    elif obj_name = "wall":
        do_something_else
   ....

To do this in logic bricks would take multiple ray sensors, and each ray sensor casts itā€™s own ray. Casting a ray is rather expensive, so here, the python is much faster because we can perform multiple tests on a single raycast. For lower cost sensors such as keyboard and mouse? Iā€™ve got no idea where the crossover is.

Another consideration is that bricks run iteratively. If I change the game property in one logic brick, it will only change for the other bricks on the next logic tick. This will mean that some things will run at a fraction of the speed of a python program as it has to do a full frame each time you want to change the value of a variable.

I have two theorems:

  • Python will end up faster in any sizable project because you will want to do more complex tasks which will negate the speed benefit of using bricks.
  • Only worry about performance when it becomes an issue. In 99% of the projects I have worked on I did not hit performance issues on my PC or other peoples. In my latest game (CaveX16), I did have performance issues. The most serious was the result of too many objects and draw calls. This was solved by using python to reduce the number of objects. It cost a lot in terms of logic time, but saved far more in terms of render time. The other performance issue was a custom pathfinding (because of dynamic maps), when the target was unreachable it was running upwards of 10,000 iterations per frame. So, well, donā€™t do complex things 10,000 times per frame. That said, I do thinking in advance about the structure as the structure more often determines the performance than the implementation.

Clarity and readability: I find wading through logic bricks far less clear than reading lines of code.
This is the problem (not my image):


Iā€™ve already stated why Iā€™m doing this the way I am. Thanks.

So, hereā€™s my next problem (BTW Iā€™m PMā€™ing admin to move this thread to WIP forum section).

Iā€™d had the Sound actuator changing the pitch perfectly, but only in one direction as there appears to be no way to make the actuator reverse the sound.

I thought Audaspace was going to be a lot more flexible. But it seems to have very limited pitch settings abilities.

If Iā€™ve got this right, bizarrely, it can only set pitch as an integer. The rotation from the object (the platter) is float. I do the conversion to int in Python but the results are confusing. Donā€™t know if Iā€™m right about this but at the moment it looks like Audaspace canā€™t set the pitch along with the rotation speed of the object as the pitch setting is not fine enough. Anyone ?
http://files.djbarney.org/dj_turntable_platter_physics_simulation_47.blend

EDIT:

I could have a possible solution. Timecode vinyl works by producing a sine wave on each channel which are slightly out of phase (to determine if the record is going forward or back). The location information is more difficult but must be known otherwise Mixxx would not be able to use various timecode records. SO I wonder if Audaspace could produce that timecode signal to create a sort of virtual timecode vinyl turntable driven by the physics simulation ?

This approach is not unheard of. For example see: Tonetable + Scratch Track: The Turntablist VST

Moved at OP request from ā€œGame Engine Support and Discussionā€ to ā€œWorks in Progress and Game Demosā€

Hi, thanks. About time this became WIP :slight_smile:

Here is the latest blend:

http://files.djbarney.org/dj_turntable_platter_physics_simulation_49.blend (with platter rotating in correct direction on forward play).

http://files.djbarney.org/dj_turntable_platter_physics_simulation_48_AUDSPACE_SLOW_DOWN_WORKING_SPIN_IN_REVERSE_SLOWLY.blend
Actually Audaspace IS now slowing down the sound with the speed of rotation of the platter :ba: ! At the moment you have to rotate the platter counter clockwise to make a funky Technics like slow down (now fixed above).

Almost completely missed this due to the limited Audaspace documentation (should I be looking at the Blender docs or the main Audaspace docs ?). The note on the entry about pitch appears to be wrong ā€¦

Note
This is done by changing the sample rate of the underlying factory, which has to be an integer, so the factor value rounded and the factor may not be 100 % accurate.

https://www.blender.org/api/blender_python_api_2_62_0/aud.html

I might have misunderstood it and thought that it needs an ā€œintā€ when it can actually accept a float.

Anyway this is good news, and I now have the same functionality in Audaspace/Python that I had using logic bricks alone :evilgrin: and it looks like I can handle slow downs / speeds ups in Audaspace. The tricky part is going to be reversing the track AND keeping a pointer to where that happens so it starts playing back in the right direction at the right place, although Audaspace might handle that itself in some way.

Does anyone have an example of playing a sound backwards (reverse) with Audaspace ? Whatever I try I get silence.

I have not tested it, but their is the factory.reverse() method which returns a new factory.
Expected (untested) example:


forward = some_aud_factory
forward_buffered = forward.buffer()      #To ensure audio seekable (and reversable), store it in ram.
reverse = forward_buffered.reverse()
audio_length = ?? Not sure where to get this from

forward_handle = device.play(forward)
forward_handle.keep = True                   #To allow you to stop the audio without losing the handle. If you change track, make sure to change this to false to allow AUD to remove the audio from ram
forward_handle.pause()

reverse_handle = device.play(reverse)
reverse_handle.keep = True
reverse_handle.pause()

def play(time, speed):
    #Seeks to a location and plays at the specified speed
    if speed > 0:
        forward_handle.resume()
        forward_handle.pitch = speed
        forward_handle.position = time
    else:
        reverse_handle.resume()
        reverse_handle.pitch = abs(speed)
        reverse_handle.position = audio_length - time
        
        



testedā€¦


import bge
import aud


device = aud.device()
# load sound file (it can be a video file with audio)


forward = aud.Factory(bge.logic.expandPath("//music.wav"))
forward_buffered = forward.buffer()      #To ensure audio seekable (and reversable), store it in ram.
reverse = forward_buffered.reverse()




forward_handle = device.play(forward)
forward_handle.keep = True                   #To allow you to stop the audio without losing the handle. If you change track, make sure to change this to false to allow AUD to remove the audio from ram
forward_handle.pause()


reverse_handle = device.play(reverse)
reverse_handle.keep = True
reverse_handle.pause()




def get_hl(h):
    # not ever work, sometimes crashes
    h.position = 0.0
    end = 0.0
    while h.position >= 0.0:
        end += 0.05
        h.position = end
        print(h.position)
    h.position = 0.0
    return end
        
audio_length = get_hl(forward_handle)  # ?? Not sure where to get this from
speed = 1.0






def play(time, speed):
    #Seeks to a location and plays at the specified speed
    if speed > 0:
        forward_handle.resume()
        forward_handle.pitch = speed
        forward_handle.position = time
        reverse_handle.pause()
        
    elif speed < 0:
        reverse_handle.resume()
        reverse_handle.pitch = abs(speed)
        reverse_handle.position = audio_length - time
        forward_handle.pause()
        
    else:
        forward_handle.pause()
        reverse_handle.pause()


# play the audio, this return a handle to control play/pause
#handle = device.play(factory)
# if the audio is not too big and will be used often you can buffer it
#factory_buffered = aud.Factory.buffer(factory)
#handle_buffered = device.play(factory_buffered)


# stop the sounds (otherwise they play until their ends)
#handle.stop()
#handle_buffered.stop()




#print(dir(handle_buffered))




bge.logic.mouse.position = 0.5, 0.5


def update():
    global speed
    
    #move the mouse up and down to change speed
    sp = (0.5 - bge.logic.mouse.position[1]) * 4
    
    if sp != speed:
        if speed > 0.0:
            h = forward_handle
            time = h.position
        else:
            h = reverse_handle
            time = audio_length - h.position
        speed = sp
        play(time, speed)
        
    bge.logic.getCurrentController().owner["prop"] = sp
    

it ā€œseemā€ that work (im not sure if when switch restart exactly where should) :slight_smile:

another similar way that come in mind is keep fix the handle forward , while creating the handle reverse on the fly when need:
if speed is reversed:(from forward to backward)
handle_forward.pause()
fac_rev = forward_buffered.limit(0.0, handle_forward.position).reverse()
handle_reverse = device.play(fac_rev)

just an idea

Thanks ! I was wondering about buffering and thatā€™s what was needed. Iā€™m not that familiar with Python which is sometimes a bit confusing but I seem to be picking it up as I go along. So are you writing that code from a Python perspective, or things unique to Audaspace ? Unless Iā€™m missing something the documentation on Audaspace is very poor. For example I donā€™t think it mentions anything about HAVING to buffer for reverse() which seems to be the case.

Latest blend added to top post. At the moment its basically the same functionality but with the track reversed, so you can still start/stop with SPACE and turn the deck on/off with ALT (ā€œoffā€ should really be ā€œplatter drive offā€ as record players in general, donā€™t think its just Technics 1210ā€™s, still play the audio even when ā€œoffā€). You can still spin the platter left with left mouse button, try doing it that while stopped or off. Spining right with right mouse button just gives a ā€œcannot set pitchā€ error as that part is not setup yet.

Please note it takes a while to buffer the track so wait a few seconds after starting.

Thatā€™s a niftey trick. Nice.

Thanks ! I was wondering about buffering and thatā€™s what was needed. Iā€™m not that familiar with Python which is sometimes a bit confusing but I seem to be picking it up as I go along. So are you writing that code from a Python perspective, or things unique to Audaspace ? Unless Iā€™m missing something the documentation on Audaspace is very poor. For example I donā€™t think it mentions anything about HAVING to buffer for reverse() which seems to be the case.

Iā€™ve never reversed audio with the python API, and it c omes down to a mix of experience, reading the API, and knowing how things work at a level slightly lower than blender.

First off, if you are confused about the API, I wrote a resource on it several years back. It didnā€™t mention the aud module, but it may help. WIth the AUD documentation, the classes (Eg handle and factory) are on the same page. What youā€™ll find is that all APIā€™s seem pretty poor until you are fluent with the system being documented (in my experience).

So how did I write that code snippet. I recalled seeing that factories could be reversed, but that a handle could not be, so I decided the best approach was to have one playing forwards and another playing backwards. I then had to figure out how to reverse it. The API says:

reverse()

Plays a factory reversed.
[TABLE=ā€œclass: docutils field-listā€]
[TR=ā€œclass: field-odd fieldā€]
[TH=ā€œclass: field-nameā€]Returns:[/TH]
The created Factory object.
[/TR]
[TR=ā€œclass: field-even fieldā€]
[TH=ā€œclass: field-nameā€]Return type:[/TH]
Factory
[/TR]
[/TABLE]
Note
The factory has to have a finite length and has to be seekable. Itā€™s recommended to use this only with factories with fast and accurate seeking, which is not true for encoded audio files, such ones should be buffered using buffer() before being played reversed.

Warning

If seeking is not accurate in the underlying factory youā€™ll likely hear skips/jumps/cracks.

Youā€™ll notice that it says that facroties have to be finite length and seekable, and even suggests buffering it.
So then it was simply a case of taking the factory, buffering it and then reversing it.
Now, Iā€™ve found that unless you buffer audio, the first time you play it it will lag slightly, so I also buffered the reversed factory. Technically this shouldnā€™t be necessary, but for a smooth experience it probably is.
Then it was a case of looking for the seek method (handle.position, which Iā€™ve also never used), and then playing/stopping (which I have used) it when required .
So there you go, that is how I wrote it.

Please note it takes a while to buffer the track so wait a few seconds after starting.

It may be worth creating a loading screen if this takes more than a few seconds, however this is often quite an involved process.
If you add some sort of music selection, this would also be nice. Youā€™ve got me somewhat enthusiastic about this project, and Iā€™ve always wanted to make a file selection screen, so I may do this this weekendā€¦

Spining right with right mouse button just gives a ā€œcannot set pitchā€ error as that part is not setup yet.

There are several issues I can see:

  1. You areā€™t using ā€œhandle.keep = trueā€ so when the track reaches itā€™s end, the handle becomes invalid
  2. A pitch of zero is invalid (and also wouldnā€™t be playing anything), so maybe run a check against this and make all pitches of less than, say, 0.001 simply pause the handle.
  3. Youā€™re also tarting the reverse track twice. (line 23 and line 35). The reason you start it twice is because you run handle.stop() rather than handle.pause(). handle.stop() completely invalidates the handle.

But yes, it is playing backwards.

Thanks people. Forward and reverse now working. See latest blend file top of post. Different approach but I could not have done it without your script examples ! Now Iā€™m just getting my head around syncing the track position so that the forward buffer and reverse buffer sync properly when the platter switches between forward and reverse.

OK, damn ! :cool: This was the most difficult part. Forward AND reverse working WITH the correct position maintained. Blend uploaded, see top. Next:

  • Control to send deck into reverse (ā€œplay in reverseā€).
  • Mouse up and down to control forward back rather than mouse left/right buttons as it is at the moment. That could allow scratching.
  • Mouse click on platter determines play position in track depending on position across radius.
  • Fix deck off control sometimes needing two key presses.
  • Add proper changelog.

This things gonna rock :evilgrin:

I had some fun with this today:
https://drive.google.com/file/d/0B27awtGNGuMPQ0lHX1BpQ0VMR1U/view?usp=sharing

By default the motor is engaged. To disengage, hold down middle mouse button.
Left and right button apply a bit of torque.
To change the track, use the game property attached to the platter and specify file path either relative to the blend (prefixing with ā€˜//ā€™) or global (prefixing with ā€˜/home/$user/ā€™ or ā€˜C://ā€™).

I have included about 30 seconds of Sleeping Child Remix You can download the full version for free as part of the Space Holidays volume 7 from www.spacesoundrecords.com/ if you so desire (I am not affiliated with them in any way, but I do like their music).

  • I rewrote a lot of stuff, making the code pylint compliant and separating out the audio boilerplate from the conversion from rotation to pitch. Interfaces exist to set the pitch, seek the track, set the volume and a few other things. Just use cont.owner[ā€˜trackā€™].set_volume(), cont.owner[ā€˜trackā€™].seek(time_in_seconds).
  • It turns out that your conversion to length doesnā€™t exactly match what aud thinks it is (and only works with .wav files) Iā€™ll have a bit more of a think about this.
  • There is a known bug: when the track ends, rewinding doesnā€™t work, but if you rewind and then go forwards it will continue from the start. This was the result of trying to fix the same problem at the other end and is related to the problem above.
  • I am not sure why the text object on the platter is being duplicated. I havenā€™t come across this before and it looks like a bug with linked groups.

If you have questions or issues let me know.
Tomorrow evening I hope to add a track chooser of some sort.

A couple of suggestions:

  • Split things out of the blend into smaller parts. It really does help when things start getting complex. If youā€™re looking for a good script editor, I can recommend Geany (I think you are a linux user?).
  • Instead of using long file names and lots of copies, use version control (such as git). My version of this is on gitlab and available over here. If you want developer access to that project, just ask.