Basic sample sequencer - keeping in time
Blitz3D Forums/Blitz3D Programming/Basic sample sequencer - keeping in time
| ||
Hi all, I've been playing with a basic sequencer, essentially loading samples, putting note data into a string, then during playback playing the right sample depending on the string. However, the timing just doesn't feel right 100% of the time. Sometimes it's ok, but every now and then there are tiny but noticeable changes in tempo, it doesn't feel solid/consistent. I wondered if anyone else had already looked a way to have solid sample playback, in terms of timing, or even if this is do-able with Blitz3D/MilliSecs() out of the box. TBH I'm happy for it to run at a forced FPS, just needs to be super accurate, i.e. in-time, all the time. What do guys think? Current method is using a delay minus elapsed, but I've also tried fixed logic and waittimers, all seem to have the same "ok 80% of the time" result. Example code - Graphics 640,480,16,2 SetBuffer BackBuffer() ; sounds Global ga = LoadSound("audiofiles/a/a.wav") Global gb = LoadSound("audiofiles/a/b.wav") Global gc = LoadSound("audiofiles/a/c.wav") ; track data Dim trackdata$(1) Global track_1=1 trackdata$(track_1)="acccbcccaccabccc" Global gDataPos = 0 Global Tick = 0 Repeat s=MilliSecs() Update() Draw() e=MilliSecs() diff=e-s Delay 20-diff Until KeyHit(1) Function Update() Tick = Tick + 1 If Tick < 7 Then Return gDataPos = gDataPos +1 If gDataPos > 8 Then gDataPos = 1 End If note$=Mid$(trackdata(1),gDataPos,1) If note$="a" Then PlaySound ga If note$="b" Then PlaySound gb If note$="c" Then PlaySound gc Tick = 0 End Function Function Draw() Cls Text 0,0,"Hello World " + diff Text 0,10,gDataPos Flip False End Function Cheers, LW. |
| ||
Instead of increasing a counter, check the current time to launch the soundsGraphics 640,480,0,2 SetBuffer BackBuffer() ; sounds Global ga = LoadSound("audiofiles/a/a.wav") Global gb = LoadSound("audiofiles/a/b.wav") Global gc = LoadSound("audiofiles/a/c.wav") ; track data Dim trackdata$(1) Global track_1=1 trackdata$(track_1)="acccbcccaccabccc" Global gDataPos = 0 ; ---------------------------------- ; application time Global STARTTIME = MilliSecs() ; current time Global MT = 0 Global DelayPlayTime = 250 ; play a sound each 250 ms ; declare and initialize the "play time" Global PlayTime = MT+DelayPlayTime ; ---------------------------------- Repeat ; update current time (remove application time to store lower values) MT=MilliSecs()-STARTTIME Update() Draw() Delay 15 Until KeyHit(1) Function Update() ; if current time reached the Play timer > emit a sound If MT>=PlayTime ; increment datapos gDataPos = gDataPos Mod(Len(trackdata(1))) + 1 ; get the current char (at DataPos+1) Local note$=Mid$(trackdata(1),gDataPos+1,1) If note$="a" Then PlaySound ga If note$="b" Then PlaySound gb If note$="c" Then PlaySound gc ; up PlayTime to the next Time PlayTime=PlayTime+DelayPlayTime End If End Function Function Draw() Cls Text 0,0,"Hello World " Text 0,10,gDataPos Flip False End Function ps : there is a smart library (BASS) to deal with sounds in an efficient manner. |
| ||
Hi, Thanks for the quick reply! I still find the timing isn't tight enough, wondering if it's not the timer code, but "PlaySound" maybe. I'll check out the bass library also. Cheers, LW. |
| ||
Both PlaySound and the millisecs timer functions will be pretty poor latency and precision wise for dealing with this. You might find BlitzAL http://tools.mirage-lab.com/ works better than using PlaySound but the timer will be a lot more tricky to replace as even using APIs its hard to get a "multimedia" timer working properly with B3D. |
| ||
you can use the HD timer from windows, with a small library, it can be usefull (I've made a wrapper for this long time ago) >> http://filax.celeonet.fr/_fofo/index.php?topic=4418.0 It allows to deal with µsecs instead of msecs. (But it's only available for machines that support HD_Timer technology, which I think is available on almost the whole last century machines) |
| ||
from my pratice in music education software I can tell you, there is no need of shorter latency below 30msec in a music arrangment. You can handle this with a normal msec timer without hearing any problems. If you hear latency problems they must be caused by something else.... |
| ||
Hi, Thanks for the replies. @midimaster, good to hear you've written music software using the standard timer. Would you be able to provide a tiny piece of code that has stable timing which run ok on your system, then I can run the same here, and see if I still have issues? Adapting the code above might be a good start as it's similar to what I'm trying already, but I appreciate everyone is busy so no worries if not. Cheers, LW. |
| ||
you need a language to descripe when what should happen. Normaly all musical time events are notated as relativ "steps", where a quarter note is represented by "24". A eights is 12 and a half note is 48. A triblet eights is 24:3=8 Ok... the positions of a notes can now be defined in steps: 0=A;48=B;60=A will say: at the beginning there is sound A and two quarters later there is sound B, and a eights later there is again A With this system you can also descripe chords or simultanous drum events: 0=A;48=B;48=A;48=C will say: 3 sounds at the time timestep "48" Now to change from relativ to absolut timing you have to fix a "factor of speed". factor#=60*1000/tempo/24 with a tempo of 120 factor would be 20.83, with a tempo of 240 factor would be 10.42 If you now multiply factor with the step postions you will get the real time, when you have to fire the sound our script: 0=A;48=B;60=A Tempo=120 will sound: A at 0 msec B at 1000 msec A at 1250 msec You need a loop which checks the millisecs()-StartTime%. All your events, where POS*factor are smaller then this, should be fired immediately thereotical sample code: factor#=20.84 B_time#=48*factor StartTime%=Millisecs() Repeat If B_time>0 If B_time< (Millisecs()-StartTime) B_time=0 PlaySound.... B_Sound Endif Endif Flip 1 Until KeyHit(1) Of course you have to translate your script language into TYPES with FIELDS Pos and Sound, Length, Volume etc.. Then you can check all lements of the type list in the main loop, fire the sounds when necessary and remove the element from the list. |
| ||
Hi, Thanks for your help on the theory - I'll read through this in detail tonight and see how I get on. I've used trackers many times through the years, and it's fun starting to see how these things work underneath. Cheers, LW. |
| ||
@Rroff, btw I tried BlitzAL, looks neat - only thing is no docs, so I'm not sure how you play more than one sample at the same time (they currently cut each other out, even with restart as false and sourcestop commented out), have you used the library before? Ps. Also on bass.dll, the download in the toolbox isn't working but I did grab a copy from the German site so add that to the list of DLL's to explore! Cheers, LW. |