|
norbert
Posts: 33
|
 |
« on: 3 Mar '12 - 10:55 » |
Quote
|
what would be the best way to play back a large number of midi events (= a sequence) that are stored in an array of BASS_MIDI_EVENT (i also want to enable looping and changing the tempo while playing)?
When using a real time midi stream created with BASS_MIDI_StreamCreate then BASS_SYNC_MIDI_TICK is not possible, which is somehow logical because no tempo and ppqn is specified ? Perhaps one could provide these two values at the beginning, ie sending a tempo change event, but the ppqn?
If not , the only chance could be to use BASS_SYNC_POS ? --> then you have to convert midi ticks to bytes...
1. Create an "one tick" sync clock: With BASS_SYNC_POS and BASS_SYNC_ONETIME you could trigger a sync every BytesPerTick bytes and then fire a StreamEvent of the events that match the current tick pos =>Easy, but what about performance if there is a sync every one tick = BytesPerTick which it is set on the fly? And when a TEMPO Event occures, you would have to calculate the BytesPerTick again. ALso, there could be rounding errors when calculating the byte sync pos because BytesPerTick will be a floating point number...
or 2. Setting the syncs for all midievents in advance: want to avoid this...you would also have to "chase" any tempo events that occur to adjust the BytesPerTick value
Any other ideas ?
|
|
|
|
« Last Edit: 3 Mar '12 - 17:22 by norbert »
|
Logged
|
|
|
|
|
Ian @ un4seen
Administrator
Posts: 15259
|
 |
« Reply #1 on: 5 Mar '12 - 16:16 » |
Quote
|
You would indeed need to use BASS_SYNC_POS syncs (with the BASS_SYNC_MIXTIME flag) for that. You can avoid rounding errors by keeping track of time in floating-point ("double"), and only convert it to integer when setting a sync. For example, the SYNCPROC could look something like this... double byteclock, bytespertick;
...
void CALLBACK SyncProc(HSYNC handle, DWORD channel, DWORD data, void *user) { // process this tick's events here byteclock+=bytespertick; // advance the clock to next tick BASS_ChannelSetSync(channel, BASS_SYNC_POS|BASS_SYNC_ONETIME|BASS_SYNC_MIXTIME, (QWORD)byteclock, SyncProc, user); // set a sync there (BASS will round down to nearest sample) }
Regarding performance, setting a sync every tick is likely to affect that a bit, but probably not too excessively. I guess you don't actually need to set a sync every tick though, ie. you could just set a sync at the next event(s) position.
|
|
|
|
|
Logged
|
|
|
|
|
norbert
Posts: 33
|
 |
« Reply #2 on: 6 Mar '12 - 18:20 » |
Quote
|
Is there something to keep in mind regarding the size of BytesPerTick, e.g does the BytesPerTick value always have to be smaller than the current buffer block size (BASS_CONFIG_BUFFER) ? Or does BASS recognize that there are new syncs set that would be apply in the same playback block (in which BASS is just sending the current syncs) ?
|
|
|
|
|
Logged
|
|
|
|
|
Ian @ un4seen
Administrator
Posts: 15259
|
 |
« Reply #3 on: 7 Mar '12 - 15:12 » |
Quote
|
The BASS_SYNC_MIXTIME flag means that a BASS_SYNC_POS sync will be triggered when the MIDI decoder/renderer reaches the specified position, so the new sync set in the SyncProc (above) just needs to be ahead of the current decoding position, ie. ahead of the current sync's position. It won't matter what the BASS_CONFIG_BUFFER setting or "BytesPerTick" value is (so long as it isn't 0).
|
|
|
|
|
Logged
|
|
|
|
|
norbert
Posts: 33
|
 |
« Reply #4 on: 13 Mar '12 - 09:03 » |
Quote
|
Thanks a lot for your help ! I got it working so far; When BASS loads a midi file into memory, does it (internally) already convert the raw bytes blocks into the BASS_MIDI_EVENT structure ie. an array of tracks whereas each track constists of an array events ? If so, then why not providing a possibility to pass a 2-dim. array of BASS_MIDI_EVENT (with tick information set) to BASS e.g.: BASS_MIDI_StreamCreateEvents(array of array of BASS_MIDI_EVENT (= array of tracks),tempo,ppqn,...) Or/Additional the possibility to modify to this array, maybe together with a function "BASS_RefreshStream" after having finished editing. As said, just an idea if BASS would already work with array structures...
|
|
|
|
|
Logged
|
|
|
|
|
Ian @ un4seen
Administrator
Posts: 15259
|
 |
« Reply #5 on: 13 Mar '12 - 17:26 » |
Quote
|
When BASS loads a midi file into memory, does it (internally) already convert the raw bytes blocks into the BASS_MIDI_EVENT structure ie. an array of tracks whereas each track constists of an array events ?
Yes, the MIDI data is converted as it is read from the file, and stored in an array (basically like the BASS_MIDI_EVENT structure but more compact). If so, then why not providing a possibility to pass a 2-dim. array of BASS_MIDI_EVENT (with tick information set) to BASS e.g.: BASS_MIDI_StreamCreateEvents(array of array of BASS_MIDI_EVENT (= array of tracks),tempo,ppqn,...) I think it would be possible to arrange something like that. I'll look into it.
|
|
|
|
|
Logged
|
|
|
|
|
norbert
Posts: 33
|
 |
« Reply #6 on: 13 Mar '12 - 19:14 » |
Quote
|
That would be great! It felt a bit like reinventing the wheel while implementing the above procedure... As an additional thought (in combination with the potential new function):
A callback method called before a midi event gets processed would make sense: There you would have the possibility to modify the event, which would be passed as parameter (something like the DSPPROC callback in the BASS audio world...) ->The modification should not alter the event in the source array, just the "copy" of it that will be processed after leaving the callback. And additional to this "live filtering" it could make sense to let the user decide that the current event should not be processed at all: maybe with a return value "-1" of the callback proc or similar...
|
|
|
|
« Last Edit: 13 Mar '12 - 19:23 by norbert »
|
Logged
|
|
|
|
|
Ian @ un4seen
Administrator
Posts: 15259
|
 |
« Reply #7 on: 15 Mar '12 - 17:08 » |
Quote
|
Here's something to try... www.un4seen.com/stuff/bassmidi.zipIt includes a BASS_MIDI_StreamCreateEvents function to create a MIDI stream from an array of BASS_MIDI_EVENT. The array should be terminated with a MIDI_EVENT_END event, and a MIDI_EVENT_END_TRACK event can be used to mark the end of a track (the next event will be in a new track). For example, to play a middle C note every 2 seconds (with tempo/controllers/etc left at defaults), you could do this... BASS_MIDI_EVENT events[]={ {MIDI_EVENT_NOTE, MAKEWORD(60, 100), 0, 0, 0}, // press the key immediately (after 0 ticks) {MIDI_EVENT_NOTE, 60, 0, 200, 0}, // release the key after 200 ticks {MIDI_EVENT_END, 0, 0, 400, 0} // end after 400 ticks }; midi=BASS_MIDI_StreamCreateEvents(events, 100, BASS_SAMPLE_LOOP, 0); // create a stream from the events BASS_ChannelPlay(midi, 0); // start playing it
There are a few things to note. Only the standard MIDI events can be used (plus the END events) and only on 16 channels; any other events (MIXLEVEL/TRANSPOSE/SYSTEMEX) and channels will be ignored. TEMPO events should only be used in the first track. The position of the events is always set in ticks (the "pos" BASS_MIDI_EVENT member is ignored) and they must be in chronological order within each track.
|
|
|
|
|
Logged
|
|
|
|
|
norbert
Posts: 33
|
 |
« Reply #8 on: 15 Mar '12 - 20:36 » |
Quote
|
Wow...this was really fast! I'll try it in the next days !
|
|
|
|
|
Logged
|
|
|
|
|
Brannon
Posts: 16
|
 |
« Reply #9 on: 1 Apr '12 - 06:02 » |
Quote
|
Can someone please add this new MIDI functionality to the .NET API? Thanks.
|
|
|
|
|
Logged
|
|
|
|
|
Brannon
Posts: 16
|
 |
« Reply #10 on: 21 Apr '12 - 05:34 » |
Quote
|
Please get us a 64bit build of bassmidi.dll with this BASS_MIDI_StreamCreateEvents method. Thanks.
|
|
|
|
|
Logged
|
|
|
|
|
|
|
Genervd
Posts: 9
|
 |
« Reply #12 on: 26 Apr '12 - 08:24 » |
Quote
|
BASS_MIDI_EVENT events[]={ {MIDI_EVENT_NOTE, MAKEWORD(60, 100), 0, 0, 0}, // press the key immediately (after 0 ticks) {MIDI_EVENT_NOTE, 60, 0, 200, 0}, // release the key after 200 ticks {MIDI_EVENT_END, 0, 0, 400, 0} // end after 400 ticks }; midi=BASS_MIDI_StreamCreateEvents(events, 100, BASS_SAMPLE_LOOP, 0); // create a stream from the events BASS_ChannelPlay(midi, 0); // start playing it
I tried to do this with Visual Basic 2010. I used bassmidi.dll and Bassnet, which I downloaded yesterday. First try Dim events(,) As Un4seen.Bass.AddOn.Midi.BASS_MIDI_EVENT events = { {BASSMIDIEvent.MIDI_EVENT_NOTE, MakeWord(60, 100), 0, 0, 0}, {BASSMIDIEvent.MIDI_EVENT_NOTE, 60, 0, 200, 0}, {BASSMIDIEvent.MIDI_EVENT_END, 0, 0, 400, 0} }
Dim midi = BASS_MIDI_StreamCreateEvents(events, 100, BASS_SAMPLE_LOOP, 0) BASS_ChannelPlay(midi, 0) Error: It is not able to convert BASSMIDIEvent to BASS_MIDI_EVENT Second try Dim events(,) As Un4seen.Bass.AddOn.Midi.BASSMIDIEvent events = { {BASSMIDIEvent.MIDI_EVENT_NOTE, MakeWord(60, 100), 0, 0, 0}, {BASSMIDIEvent.MIDI_EVENT_NOTE, 60, 0, 200, 0}, {BASSMIDIEvent.MIDI_EVENT_END, 0, 0, 400, 0} }
Dim midi = BASS_MIDI_StreamCreateEvents(events, 100, BASS_SAMPLE_LOOP, 0) BASS_ChannelPlay(midi, 0) Error: Events is an array with two dimensions, must be one with one dimension So I tried a different syntax Dim events(3) As BASS_MIDI_EVENT events(0).chan = 0 events(0).eventtype = BASSMIDIEvent.MIDI_EVENT_NOTE events(0).param = Un4seen.Bass.Utils.MakeWord(60, 127) With events(1) .chan = 1 .eventtype = BASSMIDIEvent.MIDI_EVENT_PROGRAM .param = 20 End With events(2).chan = 1 events(2).eventtype = BASSMIDIEvent.MIDI_EVENT_NOTE events(2).param = Un4seen.Bass.Utils.MakeWord(86, 126) events(2).tick = 30
Dim midi = BASS_MIDI_StreamCreateEvents(events, 100, BASS_SAMPLE_LOOP, 0) BASS_ChannelPlay(midi, 0)
No errors, but also no sound
|
|
|
|
|
Logged
|
|
|
|
|
Ian @ un4seen
Administrator
Posts: 15259
|
 |
« Reply #13 on: 26 Apr '12 - 15:41 » |
Quote
|
I'm not very familiar with VB.Net, so I'm not certain, but I think the translation would look something like this... Dim events() As BASS_MIDI_EVENT events={ New BASS_MIDI_EVENT(BASSMIDIEvent.MIDI_EVENT_NOTE, MakeWord(60, 100), 0, 0, 0), New BASS_MIDI_EVENT(BASSMIDIEvent.MIDI_EVENT_NOTE, 60, 0, 200, 0), New BASS_MIDI_EVENT(BASSMIDIEvent.MIDI_EVENT_END, 0, 0, 400, 0) } midi=BassMidi.BASS_MIDI_StreamCreateEvents(events, 100, BASS_SAMPLE_LOOP, 0) Bass.BASS_ChannelPlay(midi, 0)
Regarding the lack of sound, it might be that the MIDI stream doesn't have a soundfont to use. BASSMIDI will default to using a Creative soundfont if one is installed, but otherwise you will need to provide a soundfont. If you currently aren't providing a soundfont, the simplest way to do that is via BASS_SetConfigPtr's BASS_CONFIG_MIDI_DEFFONT option. Note BASSMIDI will need to be loaded before using that option; you can ensure that it is by making a BASSMIDI function call.
|
|
|
|
|
Logged
|
|
|
|
|
Genervd
Posts: 9
|
 |
« Reply #14 on: 29 Apr '12 - 08:28 » |
Quote
|
Dim soundFont = BassMidi.BASS_MIDI_FontInit("C:\Users\Stephan\Desktop\brauchbar+\ChoriumRevA.sf2") BASS_MIDI_FontLoad(soundFont, -1, -1) Dim soundFontInstance(2) As BASS_MIDI_FONT soundFontInstance(0) = New BASS_MIDI_FONT(soundFont, -1, 0)
Stream = BASS_MIDI_StreamCreate(4, BASS_DEFAULT, 44100) BASS_MIDI_StreamSetFonts(Stream, soundFontInstance, 1)
Dim events() As BASS_MIDI_EVENT events = {New BASS_MIDI_EVENT(BASSMIDIEvent.MIDI_EVENT_NOTE, MakeWord(60, 100), 0, 0, 0), _ New BASS_MIDI_EVENT(BASSMIDIEvent.MIDI_EVENT_NOTE, 60, 0, 200, 0), _ New BASS_MIDI_EVENT(BASSMIDIEvent.MIDI_EVENT_END, 0, 0, 400, 0)}
Dim Midi = BassMidi.BASS_MIDI_StreamCreateEvents(events, 100, BASS_SAMPLE_LOOP, 0) BASS_MIDI_StreamSetFonts(Midi, soundFontInstance, 1)
BASS_ChannelPlay(Midi, 0) Thank you. Your Code was nearly right. I have corrected it. To play the array one time, one have to use Dim Midi = BassMidi.BASS_MIDI_StreamCreateEvents(events, 100, BASS_MIDI_EVENTS_STRUCT, 0)
|
|
|
|
|
Logged
|
|
|
|
|
Ian @ un4seen
Administrator
Posts: 15259
|
 |
« Reply #15 on: 30 Apr '12 - 15:14 » |
Quote
|
Good to hear that you have got it working now. Note to play the event sequence once you just need to remove the BASS_SAMPLE_LOOP flag, eg. set the "flags" parameter to 0. No need to replace it with BASS_MIDI_EVENTS_STRUCT 
|
|
|
|
|
Logged
|
|
|
|
|
Genervd
Posts: 9
|
 |
« Reply #16 on: 5 May '12 - 16:57 » |
Quote
|
How I have to calculate the BMP-tempo of such an array? If I decide that one quater in the array is 100 ticks long and the second value is Midi(0) = BassMidi.BASS_MIDI_StreamCreateEvents(events, 100, 0, 0) 100. What is the bmp of a quarter?
|
|
|
|
« Last Edit: 6 May '12 - 17:16 by Genervd »
|
Logged
|
|
|
|
|
Ian @ un4seen
Administrator
Posts: 15259
|
 |
« Reply #17 on: 7 May '12 - 14:53 » |
Quote
|
In the absence of MIDI_EVENT_TEMPO events, the default is 0.5 seconds per quarter note (120 BPM). Here's an extended example snippet (from the upcoming BASS_MIDI_StreamCreateEvents documentation) that does include a tempo setting... BASS_MIDI_EVENT events[]={ {MIDI_EVENT_TEMPO, 500000, 0, 0}, // set the tempo to 0.5 seconds per quarter note {MIDI_EVENT_PROGRAM, 40, 0, 0}, // select the violin preset {MIDI_EVENT_NOTE, MAKEWORD(60, 100), 0, 0}, // press the key {MIDI_EVENT_NOTE, 60, 0, 200}, // release the key after 200 ticks {MIDI_EVENT_END, 0, 0, 400} // end after 400 ticks }; HSTREAM stream=BASS_MIDI_StreamCreateEvents(events, 100, BASS_SAMPLE_LOOP, 0); // create a looping stream from the events BASS_ChannelPlay(stream, 0); // start playing it
|
|
|
|
|
Logged
|
|
|
|
|