24 May '13 - 04:43 *
Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length
 
   Home   Help Search Login Register  
Pages: [1]
  Reply  |  Print  
Author Topic: playing an array of midi events  (Read 1510 times)
norbert
Posts: 33


« on: 3 Mar '12 - 10:55 »
Reply with quoteQuote

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: 15270


« Reply #1 on: 5 Mar '12 - 16:16 »
Reply with quoteQuote

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 »
Reply with quoteQuote

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: 15270


« Reply #3 on: 7 Mar '12 - 15:12 »
Reply with quoteQuote

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 »
Reply with quoteQuote

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: 15270


« Reply #5 on: 13 Mar '12 - 17:26 »
Reply with quoteQuote

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 »
Reply with quoteQuote

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: 15270


« Reply #7 on: 15 Mar '12 - 17:08 »
Reply with quoteQuote

Here's something to try...

   www.un4seen.com/stuff/bassmidi.zip

It 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 »
Reply with quoteQuote

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 »
Reply with quoteQuote

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 »
Reply with quoteQuote

Please get us a 64bit build of bassmidi.dll with this BASS_MIDI_StreamCreateEvents method. Thanks.
Logged
Ian @ un4seen
Administrator
Posts: 15270


« Reply #11 on: 24 Apr '12 - 17:11 »
Reply with quoteQuote

Here's the latest stuff in both Win32 and Win64 flavour...

   www.un4seen.com/stuff/bassmidi.zip
Logged
Genervd
Posts: 9


« Reply #12 on: 26 Apr '12 - 08:24 »
Reply with quoteQuote

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: 15270


« Reply #13 on: 26 Apr '12 - 15:41 »
Reply with quoteQuote

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 »
Reply with quoteQuote

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: 15270


« Reply #15 on: 30 Apr '12 - 15:14 »
Reply with quoteQuote

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 Smiley
Logged
Genervd
Posts: 9


« Reply #16 on: 5 May '12 - 16:57 »
Reply with quoteQuote

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: 15270


« Reply #17 on: 7 May '12 - 14:53 »
Reply with quoteQuote

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
Pages: [1]
  Reply  |  Print  
 
Jump to:  

Powered by SMF 1.1.18 | SMF © 2013, Simple Machines