Basic sample sequencer - keeping in time

Blitz3D Forums/Blitz3D Programming/Basic sample sequencer - keeping in time

LamptonWorm(Posted 2013) [#1]
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.


Bobysait(Posted 2013) [#2]
Instead of increasing a counter, check the current time to launch the sounds

Graphics 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.


LamptonWorm(Posted 2013) [#3]
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.


Rroff(Posted 2013) [#4]
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.


Bobysait(Posted 2013) [#5]
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)


Midimaster(Posted 2013) [#6]
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....


LamptonWorm(Posted 2013) [#7]
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.


Midimaster(Posted 2013) [#8]
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.


LamptonWorm(Posted 2013) [#9]
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.


LamptonWorm(Posted 2013) [#10]
@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.