Play looped with a delay

Started by QuentinC,

QuentinC

Hello,

Is there a simple way to loop a sample, but with a delay between loops ?

Let's say I have a beep sound with a duration of 0.2 seconds. I want to hear a beep beep loop, but with a delay between bips, let's say a delay of 1.0 second.

From T=0 to T=0.2, I hear the beep
From T=0.2 to T=1.2, I don't hear the beep, because I'm in delay phase
From T=1.2 to T=1.4, I again hear the beep
From T=1.4 to T=2.4, I don't hear it
From T=2.4 to T=2.6, I hear it again
And so on.

If I use the standard way, the beep sounds continuously, and that's not what I want:

DWORD beep = BASS_SampleLoad(FALSE, "beep.ogg", 0, 0, 4, BASS_SAMPLE_LOOP);
DWORD chan = BASS_SampleGetChannel(beep, BASS_SAMPLE_LOOP);
BASS_ChannelPlay(chan, FALSE);

Of course, I could handle timing myself, by repeatedely creating and playing channels at right times, but it would be obviously easier if it was automatic. It would also probably be a little more CPU and memory efficient if a single channel was used in this case.

A very simple workaround is to myself append the 1 second of silence directly in the file. It's very easy with an audio editor like Audacity.

Another possibility is to append silence programmatically:

BASS_SAMPLE info;
BASS_SampleGetInfo(beep, &info);
info.length += (info.freq * info.chans * LOWORD(info.origres) * delay / 8);
auto buffer = std::make_unique<char[]>(info.length);
memset(&buffer[0], 0, info.length);
BASS_SampleGetData(beep, &buffer[0]);
BASS_SampleSetInfo(beep, &info);
BASS_SampleSetData(beep, &buffer[0]);

However, without mentioning the (small, but still) waste of memory, none of these workaround solutions are very practical. Especially if the delay should change over time depending on in-game events.
In my case, I want the delay to change depending on the distance with the player: as you are getting nearer, the delay gets shorter, from 1.5 or 2 seconds (when being far away, at the hearable limit) down to 0 with a continuous beep (when you are exactly at the right place).

I was hopping the relatively newly added attribute BASS_ATTRIB_TAIL could solve my problem. But documentation clearly says it doesn't:

QuoteThis attribute allows some silence to be added to the end a channel, which can be useful for hearing the tail of DSP/FX processing on the channel, eg. from reverb or echo effects. The silence is not added when looping is enabled.


So, is there a simpler way to achieve what I want ? Did I miss something obvious ?

Otherwise I think it might be a good addition for the next BASS version.
I don't know if reusing / changing the behavior of BASS_ATTRIB_TAIL would be good or bad, since both cases (with, or without delay) have their own usefulness.

Thank you very much for your answers !

Ian @ un4seen

One way you could do it is to set BASS_ATTRIB_TAIL to the wanted delay and use a mixtime BASS_SYNC_END sync (instead of BASS_SAMPLE_LOOP) for looping. That way the "tail" would be played every loop. Note that the sync will be triggered twice: once at the normal end (data=0) and once at the tail end (data=3); you would want to ignore the former:

void CALLBACK TailLoopSyncProc(HSYNC handle, DWORD channel, DWORD data, void *user)
{
if (data == 3) // at end of tail
BASS_ChannelSetPosition(channel, 0, BASS_POS_BYTE); // restart
}

Sample channels (HCHANNEL) don't support syncs or BASS_ATTRIB_TAIL, so you would need to create a stream instead by using the BASS_SAMCHAN_STREAM flag in the BASS_SampleGetChannel call. Or just use BASS_StreamCreateFile instead of BASS_SampleLoad.

QuentinC

Hello,

Your solution works very well! That's just what I needed. Thank you very much!