Sound samples back to back - gapless
BlitzMax Forums/BlitzMax Programming/Sound samples back to back - gapless
I'm trying to play music loops back to back - using ChannelPlaying() to check when previous sample is complete.
Problem - usually a few ms gap inbetween (due to sound finishing a few ms before I get back to check or other delays.
I was thinking of using elapsed time compared to sample length.
Get MilliSecs() at start of playback.
Compare to current MilliSecs() every time I check (approx every 16ms)
Start next sample when the time left is less than 32ms.
Do you think that will work?
Any other ideas?
Errrr, whats wrong with a few millisecs?
I would setup an event that gets posted every so often (At The max delay you accept), then check when I catch it.
But even then you have no garrentee that it would work, cos the events could be stuck in a big que.
Maybe yours is a better way. But honestly a "few millisecs" of no music?
it can be annoying to have a few ms of no sound if what he wants to achieve is this (What i'm trying to do):
My song is seperated into 8 beats wavs i have 20 normal wavs, 10 omg i'm going to die wavs and 10 i should hurry up wavs. Each of them can fit with any other wav. Now when the 'mood' changes, i want to switch to the correct bank of wavs, in which i play a random wav everytime the previous wav is over. Now with that in mind, i cant afford gaps between playing my wavs.
Smurftra - that's it exactly
Right now - I cue up the next snippet to play and when ChannelPlaying comes back false - I play it.
What I need is a 'ChannelAlmostDone()' function! :)
I haven't messed with the sound stuff so maybe this can't be done but...
Can you test to see how much data is left in a channel and if it is below a certain theshold start playing the new sound?
Scott:
I was thinking of something like that, exept i'd have to manually add some nonoise time at the start of the next wave and play it on a different channel.
Havent tried that yet as i decided not to work on sound/music until i decide on a free library (fmod, bass, etc)
But basicaly, i think it would have to be like
If TimeTillEndOfSong < 20 ms Then
Add TimeTillEndOfSong ms of nosound to next wave
play next wave
End if
When you start the channal get the music lenght, post an event hook to be called after that time. When you catch the hook, start the channel with the next music.
But honestly both of you. Just check each frame. The most delay even at 60Htz would be 17MilliSecs
I was only checking once every 33 ms.
I've reduce it to much smaller now by checking several times per frame.
1 right after segment number could have been updated.
1 after updating all objects
1 after drawing all object
(Any time that is left in the 33ms frame was used as Delay time)
now - I Delay time/4, check channel, Delay time/4 etc.
The gaps are almost unnoticeable now!
The gaps are almost unnoticeable now!
----
i'm a perfectionist and im looking for a solution without the 'almost' :)
Hmm, I'd like to take a crack at this problem. One question though, how do you determine just how much time is left for a sample? I couldn't find anything in the docs (surprise, surprise :)
'==============================================================================
'
Type TBeat'An atuo call Timer Container
'=======================================================================
Field length:Int ' Length of Beat in Milli seconds
Field Tikker:TTimer ' The actual Timer
Field MyEvent:TEvent = New TEvent ' The Event the Timer is tied to
Field BeatFunction(Caller:TBeat,event:TEvent) ' The Function to be called on Beat
'=======================================================================
Method Create:TBeat (length:Int = 1000,BeatFunction(Caller:TBeat,event:TEvent) = Null)
Self.length = length ' Let the New Beat length = length
Local hertz:Float = 1000.0 / length ' Stupid Bmx Hertz
AddHook EmitEventHook,eventhook,Self ' Start the Hook
Self.Tikker = CreateTimer (hertz) ' Create the timer
Self.BeatFunction = BeatFunction ' Set the Function to call
Return Self 'Return the New TBeat
End Method
'-----------------------------------------------
Function eventhook:Object(id:Int,data:Object,context:Object)
If TBeat(context) TBeat(context).Ev TEvent(data);Return data
EndFunction
'======================================================================
Method Free()
RemoveHook EmitEventHook,eventhook ' Release the Hook
GCCollect() ' Do an Garbage Collection
End Method
'----------------------------------------------
Method Ev(event:TEvent)
If event.id = EVENT_TIMERTICK and event.source =Tikker ' See if the Event was A Beat
BeatFunction(Self,event) ' Pass control to
EndIf ' the right Function
End Method
'---------------------------------------------
Method HowManyTicks:Int ()
Return TimerTicks(Tikker) ' Return the number of Ticks
EndMethod
'---------------------------------------------
Method GetFrequancy:Float ()
Return (1000.0 / length) ' Return the Hertz#
End Method
'======================================================================
End Type
'
'==============================================================================
' Fields: Length :Int -Length in MilliSeconds Before Beat
' Tikker :TTimer -The actual timer
' MyEvent :TEvent -Event Object to handle the tick
' BeatFunction :(TBeat,TEvent) -Pointer to function taking the event
'
' Methods Create :TBeat (Length, Ptr BeatFunction(TBeat,TEvent=Null))
' EventHook :Object (Int,Odject,Object)
' Free() -Release the eventhook
' HowManyTicks :Int -Returns the number of Ticks
' Ev(TEvent) -Catch any beats and call correct catch program
'==============================================================================
' Function BeatFunction (Caller:TBeat , event:TEvent)
'==============================================================================
If you place this Type in your code, then invoke it with
Global SoundCheckBeat:TBeat = New TBeat.Create(1 ,CatchSoundCheckBeat)
Then every milli second (Well not every milli sec, cos Its multi tasking, but yes let say ever milli) it will call
Function CatchSoundCheckBeat()
Where you have a looksee if the sound has finished, and if it has start the new one.
But really guys, at 100Hz thats a max gap of only 10 milli
H&K that will end up flooding the event list
This is VERY easy to do with openal, but you would need to que at least 3 sound buffers, then when the first buffer has finished playing you release it and just add another one to the end. so it creates an endless stream of audio.
Is there any internal method or field to get the length of a TSound object?
@Redi
It does not "Flood" the event list, it (At most) will put 1000 events on the list in a second.
Each one then calls that one function. And as long as you are quick , no problem.
Even if you get like 200 of them in a row, its still just a quick looksee if the sound has finished.
If you want to know to the exacpt millisecond if the sound has finished, then looksee every millisecond.
@RediAgian,
I personaly wouldnt bother, As I think they could looksee every 5milli (say), and then they only have 200 events a second. Or just look every frame, but they wanted "To the Milli second"
sorry i was thinking more about the delay from Flipping, if you have "flip false" it will work ok (tick every millisecond), but if you had "flip true" (i always do for games) it would fire the events in chuncks every 10 or whatever millisecs.
i think even with flip false, any major game logic/rendering would stop it from hooking the events every millisec, you would probably end up with a flood (10+) of checks every 10+ millisecs.
Oh I agree on the major locic/rendering, but for this, were they simply want to call channelplaying(), I think a millisecond is well doable.