Author Topic: Length of a MIDI-Note  (Read 390 times)

Treata

  • Posts: 2
Length of a MIDI-Note
« on: 10 Jun '24 - 12:41 »
Greetings,

I'm trying to visualize the note-events of a SMF in SwiftUI on a basic Canvas...
```swift
private(set) var notes: [BASS_MIDI_EVENT] = []
    private(set) var notesCount: UInt32 = .zero
   
    /**
     Retrieves the entire Note-Events of the current `stream`.
     */
    @MainActor
    func getNotes() {
        // Determine the number of events
        let numEvents = BASS_MIDI_StreamGetEvents(self.stream, -1, DWORD(MIDI_EVENT_NOTE.highByte), nil)
        self.notesCount = numEvents
        // Allocate memory for the events
        self.notes = [BASS_MIDI_EVENT](repeating: BASS_MIDI_EVENT(), count: Int(numEvents))
 ...
    }
```

I also was able to read the parameters of an event:
```swift
            model.notes.forEach { note in
                let parameter = note.param

                // LOBYTE = key number (0-127, 60=middle C)
                let keyNumber = parameter.lowByte
                // HIBYTE = velocity (0=release, 1-127=press, 255=stop)
                let velocity = parameter.highByte
                /// The position of the note **in ticks**
                let tick = CGFloat(note.tick)
                /// Position of the note **in bytes**
                let position = note.pos
...
```

All of the variables above would come handy, but there's one important one missing ...
How am I able to retrieve the length of a note (either in ticks or bytes ... it wouldn't matter)

Falcosoft

  • Posts: 200
Re: Length of a MIDI-Note
« Reply #1 on: 10 Jun '24 - 14:21 »
Hi,
Even in the Midi protocol a Note event has no inherent length property. A Note starts when a Note On event is found and ends when the corresponding Note Off event is found. In this context corresponding means a Note Off event on the same channel and at the same key(pitch) as the Note On event. In paractice the situation is more complicated since:
1. A Note On event with 0 velocity value (3rd byte) actually also means a Note Off.
2. A Note Off can mean different kind of end in case of different instruments. A Note off actually just means that the release phase of the sound should start. E.g. in case of percussion instruments usually a Note Off event has no effect.
3. When sustain controller (CC#64) is in the on state on a channel then all the Note Off events on that same channel have no effect.

BassMidi abstracts Note On/Off events somewhat so when you call BASS_MIDI_StreamGetEvents() with MIDI_EVENT_NOTE parameter you get back both Note On and Note Off events.
AFAIK there is no difference in BassMidi between Note Off events and Note On events with 0 velocity. Both are represented in BassMidi as MIDI_EVENT_NOTE events with zero velocity.

According to documentation you can get back only Note On events by calling BASS_MIDI_StreamGetEvents() with MIDI_EVENT_NOTES parameter.
https://www.un4seen.com/doc/#bassmidi/BASS_MIDI_StreamGetEvents.html

Treata

  • Posts: 2
Re: Length of a MIDI-Note
« Reply #2 on: 10 Jun '24 - 16:03 »
Hi,
Even in the Midi protocol a Note event has no inherent length property. A Note starts when a Note On event is found and ends when the corresponding Note Off event is found. In this context corresponding means a Note Off event on the same channel and at the same key(pitch) as the Note On event. In paractice the situation is more complicated since:
1. A Note On event with 0 velocity value (3rd byte) actually also means a Note Off.
2. A Note Off can mean different kind of end in case of different instruments. A Note off actually just means that the release phase of the sound should start. E.g. in case of percussion instruments usually a Note Off event has no effect.
3. When sustain controller (CC#64) is in the on state on a channel then all the Note Off events on that same channel have no effect.

BassMidi abstracts Note On/Off events somewhat so when you call BASS_MIDI_StreamGetEvents() with MIDI_EVENT_NOTE parameter you get back both Note On and Note Off events.
AFAIK there is no difference in BassMidi between Note Off events and Note On events with 0 velocity. Both are represented in BassMidi as MIDI_EVENT_NOTE events with zero velocity.

According to documentation you can get back only Note On events by calling BASS_MIDI_StreamGetEvents() with MIDI_EVENT_NOTES parameter.
https://www.un4seen.com/doc/#bassmidi/BASS_MIDI_StreamGetEvents.html

I appreciate your descriptive response.
Based on the info, it seems that I have to find the note-off event of every single note-on event that I've retrieved using Bass APIs in order to get the length of a Note...
As I've mentioned, I need the length property for visualization purposes & I cannot think of any better approaches.

Ian @ un4seen

  • Administrator
  • Posts: 26028
Re: Length of a MIDI-Note
« Reply #3 on: 12 Jun '24 - 15:10 »
Yes, you would need to find the corresponding note-off for each note-on in the MIDI_EVENT_NOTE events to get the note lengths. For example, something like this:

Code: [Select]
DWORD eventc = BASS_MIDI_StreamGetEvents(handle, -1, MIDI_EVENT_NOTE, NULL); // get number of note events
BASS_MIDI_EVENT *events = new BASS_MIDI_EVENT[eventc]; // allocate event array
BASS_MIDI_StreamGetEvents(handle, -1, MIDI_EVENT_NOTE, events); // get the events
for (int a = 0; a < eventc; a++) {
int vel = HIBYTE(events[a].param); // the velocity
if (vel) { // a note-on
int key = LOBYTE(events[a].param); // the key
int chan = events[a].chan; // the channel
int len = -1;
// look for the note-off
for (int b = a + 1; b < eventc; b++)
if (events[b].param == key && events[b].chan == chan) { // found it
len = events[b].tick - events[a].tick; // the length in ticks
break;
}
printf("pos:%d chan:%d key:%d vel:%d len:%d\n", events[a].tick, chan, key, vel, len);
}
}
delete[] events;

If you have BASS_MIDI_NOTEOFF1 enabled then you can account for that by invalidating the used note-offs so that they aren't used again:

Code: [Select]
if (events[b].param == key && events[b].chan == chan) { // found it
len = events[b].tick - events[a].tick; // the length in ticks
events[b].chan = -1; // invalidate this note-off
break;
}