Author Topic: SYNCPROC chaining  (Read 344 times)

gicci

  • Posts: 105
SYNCPROC chaining
« on: 31 Jan '23 - 13:48 »
Hi Ian,

I need to chain SYNCPROCs triggered at specific tick positions on a MIDI file playback (on Android).

At the beginning I set the position of the first one and let the playback start using something like:
Code: [Select]
BASS.BASS_ChannelSetSync(chanId, BASSMIDI.BASS_SYNC_MIDI_TICK | BASS.BASS_SYNC_ONETIME, tick, proc, user)

Then inside the proc I schedule the next one and similarly when seeking I clear the scheduled one and set the next one. Most of the time everything is ok.

The problem is that with debug prints, slow devices and very close tick positions, when I set the new one in the SYNCPROCs in some cases the position is already past and everything stops.

The only solution I could think of is checking the tick position after setting the new SYNCPROC and in this case clear it and retry with the next one. This will not anyway solve seeking, as this happens on another thread and I cannot (easily) be sure that the SYNCPROC has been triggered or not.
I could check for a minimum number of ticks from the current position before setting the SYNCPROC, maybe temporarily pause the playback when seeking, but it would be nice to have instead some flag that aborts the BASS_ChannelSetSync when specifying positions in the past, so that all this handling could be more easy and robust.

Do you think that this could be possible?
Thanks.

Ian @ un4seen

  • Administrator
  • Posts: 24936
Re: SYNCPROC chaining
« Reply #1 on: 31 Jan '23 - 14:44 »
The root of the issue in this case is that the sync is called asynchronously, delayed until its position is actually heard (which is later due to buffering). BASSMIDI will continue generating output data in the meantime, and if your SYNCPROC sets a new sync within that data then that sync won't be triggered because the position has already been processed. You can prevent that happening by using "mixtime" syncs via the BASS_SYNC_MIXTIME flag, but note that the sync will then be called before it's heard, which may cause other issues for you, eg. if your SYNCPROC also displays a visual cue. In that case, you can minimize such issues by disabling playback buffering (setting BASS_ATTRIB_BUFFER to 0).

Zenxia

  • Posts: 172
Re: SYNCPROC chaining
« Reply #2 on: 1 Feb '23 - 22:16 »
Ian you can improve the precision in less that 5 ms?

Ian @ un4seen

  • Administrator
  • Posts: 24936
Re: SYNCPROC chaining
« Reply #3 on: 2 Feb '23 - 13:11 »
Are you referring to syncs or something else? A "mixtime" sync will be called at the exact position that the event (tick in this thread's case) is processed. A non-mixtime sync should be called at the moment that the event is heard, but it is possible for it to be slightly off (eg. if the system is under heavy load).

Zenxia

  • Posts: 172
Re: SYNCPROC chaining
« Reply #4 on: 5 Feb '23 - 03:11 »
Currently Async mode ON, seems bad precision on midi event under 5 ms

gicci

  • Posts: 105
Re: SYNCPROC chaining
« Reply #5 on: 10 Feb '23 - 13:24 »
The root of the issue in this case is that the sync is called asynchronously, delayed until its position is actually heard (which is later due to buffering). BASSMIDI will continue generating output data in the meantime, and if your SYNCPROC sets a new sync within that data then that sync won't be triggered because the position has already been processed. You can prevent that happening by using "mixtime" syncs via the BASS_SYNC_MIXTIME flag, but note that the sync will then be called before it's heard, which may cause other issues for you, eg. if your SYNCPROC also displays a visual cue. In that case, you can minimize such issues by disabling playback buffering (setting BASS_ATTRIB_BUFFER to 0).

Following your advice I switched to mixtime syncing:
Code: [Select]
BASS.BASS_ChannelSetSync(chanId, BASSMIDI.BASS_SYNC_MIDI_TICK | BASS.BASS_SYNC_ONETIME | BASS.BASS_SYNC_MIXTIME, tick, proc, user)

but I am experiencing other issues.

Just to give more context, the sync proc should trigger a metronome sound on a MIDI file (on which I set the sync proc) that is feed in a Mixer (currently configured with BASS_ATTRIB_BUFFER = 0.5).
On a sample file I should trigger the metronome every 384 ticks, and this is how I configure the syncproc, but this is how it gets called:
Code: [Select]
Delta: 0 ms, 0 ticks
Delta: 118 ms, 76 ticks
Delta: 402 ms, 319 ticks
Delta: 495 ms, 393 ticks
Delta: 400 ms, 318 ticks
Delta: 500 ms, 397 ticks
Delta: 501 ms, 398 ticks
Delta: 500 ms, 396 ticks
Delta: 501 ms, 397 ticks
Delta: 499 ms, 397 ticks
Delta: 400 ms, 317 ticks
Delta: 500 ms, 397 ticks
Delta: 501 ms, 397 ticks
Delta: 499 ms, 397 ticks
Delta: 501 ms, 397 ticks
Delta: 498 ms, 395 ticks
Delta: 502 ms, 399 ticks
Delta: 401 ms, 318 ticks
Delta: 498 ms, 395 ticks

I understand that this is due to the fact that the MIDI channel is decoded and buffered into the Mixer, but how should I then have a sync in this configuration (changing the BASS_ATTRIB_BUFFER  is not an option as I am running also Fx effects on the Mixer)?

I am thinking about using a mixtime syncproc for the scheduling and a play time syncproc for the actual event, but maybe there is some other way that I am not aware of.

Thank you.
« Last Edit: 10 Feb '23 - 13:50 by gicci »

Ian @ un4seen

  • Administrator
  • Posts: 24936
Re: SYNCPROC chaining
« Reply #6 on: 10 Feb '23 - 14:45 »
On a sample file I should trigger the metronome every 384 ticks, and this is how I configure the syncproc, but this is how it gets called:
Code: [Select]
Delta: 0 ms, 0 ticks
Delta: 118 ms, 76 ticks
Delta: 402 ms, 319 ticks
Delta: 495 ms, 393 ticks
...

How are you getting the "ticks" value? Make sure you're using the BASS_POS_DECODE flag with BASS_ChannelGetPosition to get the decoder position, not the playback/heard position.

If the metronome timing doesn't sound right, is it played in the MIDI stream (eg. using a MIDI_EVENT_NOTE event) or is it played separately? The former should be heard exactly on the tick (when triggered within the mixtime SYNCPROC), but the latter probably wouldn't. So if you are currently playing the metronome sound separately, please try playing it in the MIDI stream instead. You can use the BASS_ATTRIB_MIDI_CHANS option to add a dedicated MIDI channel for it.

gicci

  • Posts: 105
Re: SYNCPROC chaining
« Reply #7 on: 10 Feb '23 - 15:43 »
Yes, those are the playback tick positions. The issue is that I cannot trigger the metronome playback at the sync proc call (configured exactly every 384 ticks in that case), because it is not actually triggered every 384 ticks but when the decoding of the MIDI stream into the Mixer happens. In this case I am not yet to the part where I need to sync the metronome sound with the MIDI playback, making it to have the same output delay.

I had another situation like that in the past for another feature, and how I said I solved using 2 syncprocs. The first one is MIXTIME and is used for scheduling, the second is configured without MIXTIME and is used to report the event. In that case it was a visual event, in this case I hope that I will be able to start the playback of the metronome sound channel in time to avoid delays.

Ian @ un4seen

  • Administrator
  • Posts: 24936
Re: SYNCPROC chaining
« Reply #8 on: 10 Feb '23 - 16:21 »
Yes, those are the playback tick positions. The issue is that I cannot trigger the metronome playback at the sync proc call (configured exactly every 384 ticks in that case), because it is not actually triggered every 384 ticks but when the decoding of the MIDI stream into the Mixer happens.

When a BASS_SYNC_MIDI_TICK sync is mixtime, it is triggered when decoding/rendering (not playback) reaches the tick position. If you want a metronome to be heard at that exact position then that is when you need to start it (in the same MIDI stream). Waiting until playback reaches the position (eg. with a non-mixtime sync) will be too late, due to buffering.

gicci

  • Posts: 105
Re: SYNCPROC chaining
« Reply #9 on: 10 Feb '23 - 16:28 »
When a BASS_SYNC_MIDI_TICK sync is mixtime, it is triggered when decoding/rendering (not playback) reaches the tick position. If you want a metronome to be heard at that exact position then that is when you need to start it (in the same MIDI stream). Waiting until playback reaches the position (eg. with a non-mixtime sync) will be too late, due to buffering.

I am planning to use another channel on a WAV file that is not feed in the Mixer, but goes directly to the output device. I guess that this should work, unless the delay in the call of the sync proc will not be too high.

Ian @ un4seen

  • Administrator
  • Posts: 24936
Re: SYNCPROC chaining
« Reply #10 on: 10 Feb '23 - 17:59 »
The problem with that is there will be some delay before the WAV file is heard, due to the device output buffering (see BASS_CONFIG_DEV_BUFFER). You can get the average playback delay from BASS_GetInfo (see the "latency" value). You could try to start playing the WAV file slightly earlier to compensate, but if you want to hear the metronome exactly at the tick positions then the most reliable way is to play it in the MIDI stream instead.

Note you can use your existing WAV file in a MIDI stream via an SFZ file, containing something like this:

Code: [Select]
<region>
key=60
sample=metronome.wav
loop_mode=one_shot

gicci

  • Posts: 105
Re: SYNCPROC chaining
« Reply #11 on: 10 Feb '23 - 22:45 »
The problem with that is there will be some delay before the WAV file is heard, due to the device output buffering (see BASS_CONFIG_DEV_BUFFER). You can get the average playback delay from BASS_GetInfo (see the "latency" value). You could try to start playing the WAV file slightly earlier to compensate, but if you want to hear the metronome exactly at the tick positions then the most reliable way is to play it in the MIDI stream instead.

So when a syncproc is scheduled to be executed at playback time, will it take into account also the latency of the output device and not only the latency due to internal buffering?
To anticipate the playback of the WAV file I should then use a scheduling method external to Bass.

Note you can use your existing WAV file in a MIDI stream via an SFZ file, containing something like this:

This could be an option, even though I will need to reserve a MIDI channel for this to avoid interference with the playback.


Ian @ un4seen

  • Administrator
  • Posts: 24936
Re: SYNCPROC chaining
« Reply #12 on: 13 Feb '23 - 12:57 »
So when a syncproc is scheduled to be executed at playback time, will it take into account also the latency of the output device and not only the latency due to internal buffering?

Yes, whenever possible, BASS uses the output device's position/clock to time a SYNCPROC call at the exact moment that the sync's event (tick in this case) is heard.

This could be an option, even though I will need to reserve a MIDI channel for this to avoid interference with the playback.

You can use the BASS_ATTRIB_MIDI_CHANS option to add a channel for the metronome.

gicci

  • Posts: 105
Re: SYNCPROC chaining
« Reply #13 on: 13 Feb '23 - 22:48 »
After some testing capturing and visualizing the audio output, I verified that at least on my device I am able to start the metronome wav audio channel in sync with the MIDI events if I anticipate the syncproc by 60 ms. I still need to check if this is result will be the same on other devices, I will anyway provide the configurability so that user could adjust if needed (not nice, I know).

What I can say for sure is that changing the device buffer from the default of 40ms to say 200ms will not delay the syncproc triggering. I already verified this in the past as I had to delay visual effects when users changed that parameter in the app. I have verified this also in relation with the metronome triggering.

In a next release of the app I could check the option of playing the metronome within the MIDI file.

It would be very nice and useful anyway to have the possibility to sync automatically different channels, maybe when connected to the same Mixer. This will make much more easier to handle other use cases, like mixing MIDI files with audio samples or other MIDI files. This can be done within the single MIDI file as you already pointed out, but with more complexity and less flexibility.

Ian @ un4seen

  • Administrator
  • Posts: 24936
Re: SYNCPROC chaining
« Reply #14 on: 14 Feb '23 - 15:32 »
It would be very nice and useful anyway to have the possibility to sync automatically different channels, maybe when connected to the same Mixer. This will make much more easier to handle other use cases, like mixing MIDI files with audio samples or other MIDI files. This can be done within the single MIDI file as you already pointed out, but with more complexity and less flexibility.

It should be possible now to have one MIDI stream trigger events at specific positions in another, using BASS_MIDI_StreamEvents with BASS_MIDI_EVENTS_ABSTIME. You will need the triggering stream to be processed before the triggered one if there might not be much time until the event, to make sure the position hasn't already passed. When using BASSmix, a mixer's sources are processed in the order that they were added, ie. the order of the BASS_Mixer_StreamAddChannel calls. Except when a source has the BASS_MIXER_CHAN_LIMIT flag set, in which case it's always processed first.

gicci

  • Posts: 105
Re: SYNCPROC chaining
« Reply #15 on: 15 Feb '23 - 00:41 »
Note you can use your existing WAV file in a MIDI stream via an SFZ file, containing something like this:

Code: [Select]
<region>
key=60
sample=metronome.wav
loop_mode=one_shot

Is there a way to use BASS_MIDI_FontInitUser() with a BASS_FILEPROCS able to read the audio files and not only the SFZ file contents? I currently use this to dynamically create the SFZ file pointing to the files I want without writing it to the storage, but it would be useful to do the same also with the samples.

Ian @ un4seen

  • Administrator
  • Posts: 24936
Re: SYNCPROC chaining
« Reply #16 on: 15 Feb '23 - 16:08 »
It isn't currently possible to provide sample files via the same BASS_FILEPROCS as an SFZ file (I'll look into that), but you can load samples from memory by setting the "sample" opcode to "mem:<address>:<length>" (address and length are in hexadecimal). Note the memory should remain valid until the SFZ is freed via BASS_MIDI_FontFree.

gicci

  • Posts: 105
Re: SYNCPROC chaining
« Reply #17 on: 15 Feb '23 - 21:56 »
It isn't currently possible to provide sample files via the same BASS_FILEPROCS as an SFZ file (I'll look into that), but you can load samples from memory by setting the "sample" opcode to "mem:<address>:<length>" (address and length are in hexadecimal). Note the memory should remain valid until the SFZ is freed via BASS_MIDI_FontFree.

I guess this will not work from Java...

Ian @ un4seen

  • Administrator
  • Posts: 24936
Re: SYNCPROC chaining
« Reply #18 on: 16 Feb '23 - 15:24 »
Yeah, unfortunately I don't think it'll be possible to use the memory option directly from Java. You would probably need a little JNI library function that takes a "direct" ByteBuffer (containing the sample file) and returns the native memory address of it. You would also need to keep a reference to the ByteBuffer to prevent it being garbage-collected while in use.

gicci

  • Posts: 105
Re: SYNCPROC chaining
« Reply #19 on: 1 Mar '23 - 21:31 »
I am starting to use an additional channel using BASS_ATTRIB_MIDI_CHANS with drum kit for metronome, but I have an issue at MIDI file start for the first beat: if I set it at tick 0 on most of the files it is not reproduced, setting at 1 seems ok for most, I need to move it even further in other cases. As I understood this is due to system reset events at the beginning of the file.
Looking at the docs I have seen that a MIDI_EVENT_SYSTEMEX will affect any additional channels allocated to a MIDI file/sequence stream via the BASS_ATTRIB_MIDI_CHANS attribute, but if I search for those events in the MIDI file I cannot find them as I get only MIDI_EVENT_SYSTEM events.
Does this mean that MIDI_EVENT_SYSTEM will not change the configuration of additional channels but will anyway reset their playback? Is this intended?

Ian @ un4seen

  • Administrator
  • Posts: 24936
Re: SYNCPROC chaining
« Reply #20 on: 2 Mar '23 - 18:06 »
MIDI_EVENT_SYSTEMEX is BASSMIDI-specific, so you will never find it used in a MIDI file. It will reset things in all channels, while MIDI_EVENT_SYSTEM (which can be found in MIDI files) shouldn't reset things in any extra channels from BASS_ATTRIB_MIDI_CHANS. But it looks like there's a bug in the MIDI_EVENT_SYSTEM handling, resulting in notes being killed in the extra channels, and it sounds like you may be encountering that bug. I'll get an update for you to try tomorrow.

Ian @ un4seen

  • Administrator
  • Posts: 24936
Re: SYNCPROC chaining
« Reply #21 on: 3 Mar '23 - 12:38 »
Here's the update for you to try:

   www.un4seen.com/stuff/bassmidi-android.zip

Let me know if you still have the problem with that.

gicci

  • Posts: 105
Re: SYNCPROC chaining
« Reply #22 on: 3 Mar '23 - 23:54 »
Hi Ian, thank you, that version solved the issue.