-
-
Save rmt/ccaffc4e44fa52485c103756974fe16e to your computer and use it in GitHub Desktop.
Parsing MIDI in nim lang
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import strformat | |
import strutils | |
import events | |
import mylogger | |
type | |
MidiMsg* = object of RootObj | |
d*: uint32 | |
NoteOn* = object of MidiMsg # 0x9? | |
channel*: uint8 | |
note*: uint8 | |
velocity*: uint8 | |
NoteOff* = object of MidiMsg # 0x8? | |
channel*: uint8 | |
note*: uint8 | |
velocity*: uint8 | |
AfterTouch* = object of MidiMsg # 0xa? | |
channel*: uint8 | |
note*: uint8 | |
pressure*: uint8 | |
ControlChange* = object of MidiMsg # 0xb? | |
channel*: uint8 | |
number*: uint8 # ChannelMode if 120-127 | |
value*: uint8 | |
ProgramChange* = object of MidiMsg # 0xc0 | |
channel*: uint8 | |
value*: uint8 | |
ChannelPressure* = object of MidiMsg # 0xd0 | |
channel*: uint8 | |
pressure*: uint8 | |
PitchBend* = object of MidiMsg | |
channel*: uint8 | |
value*: uint16 | |
SysEx* = object of MidiMsg | |
data*: seq[uint8] | |
# TODO: RPNs? This would require storing state.. | |
method `$`*(e: MidiMsg): string {.base.} = | |
return "Unclassified MidiMsg" | |
method `$`*(e: NoteOn): string = | |
return fmt"NoteOn(channel={e.channel}, note={e.note}, velocity={e.velocity})" | |
method `$`*(e: NoteOff): string = | |
return fmt"NoteOff(channel={e.channel}, note={e.note}, velocity={e.velocity})" | |
method `$`*(e: Aftertouch): string = | |
return fmt"Aftertouch(channel={e.channel}, note={e.note}, pressure={e.pressure})" | |
method `$`*(e: ControlChange): string = | |
return fmt"ControlChange(channel={e.channel}, number={e.number}, value={e.value})" | |
method `$`*(e: ProgramChange): string = | |
return fmt"ProgramChange(channel={e.channel}, value={e.value})" | |
method `$`*(e: ChannelPressure): string = | |
return fmt"ChannelPressure(channel={e.channel}, pressure={e.pressure})" | |
method `$`*(e: PitchBend): string = | |
return fmt"PitchBend(channel={e.channel}, value={e.value})" | |
method `$`*(e: SysEx): string = | |
return fmt"SysEx({e.data})" | |
proc parse_midi_bytes*(bytes: seq[uint8]): MidiMsg = | |
if len(bytes) < 1: | |
error("Received a short midi message, which we'll ignore") | |
if bytes[0] < 0xf0: | |
if bytes.len < 2: | |
error("Received a short channel midi message, which we'll ignore") | |
return | |
let channel = (bytes[0] and 0x0f'u8) | |
let mtype = (bytes[0] and 0xf0'u8) | |
case mtype | |
of 0x80'u8: | |
if bytes.len < 3: | |
error("Received a short note_off message, which we'll ignore") | |
return | |
return NoteOff(channel: channel, note: bytes[1], velocity: bytes[2]) | |
of 0x90'u8: | |
if bytes.len < 3: | |
error("Received a short note_on message, which we'll ignore") | |
return | |
return NoteOn(channel: channel, note: bytes[1], velocity: bytes[2]) | |
of 0xa0'u8: | |
if bytes.len < 3: | |
error("Received a short aftertouch message, which we'll ignore") | |
return | |
return AfterTouch(channel: channel, note: bytes[1], pressure: bytes[2]) | |
of 0xb0'u8: | |
if bytes.len < 3: | |
error("Received a short controlchange message, which we'll ignore") | |
return | |
return ControlChange(channel: channel, number: bytes[1], value: bytes[2]) | |
of 0xc0'u8: | |
return ProgramChange(channel: channel, value: bytes[1]) | |
of 0xd0'u8: | |
return ChannelPressure(channel: channel, pressure: bytes[1]) | |
of 0xe0'u8: | |
if bytes.len < 3: | |
error("Received a short pitchbend message, which we'll ignore") | |
return | |
# PitchBend has a 14 bit value of uint7 LSB + uint7 MSB | |
let value = bytes[1] + bytes[2] * 127 | |
return Pitchbend(channel: channel, value: value) | |
else: | |
error("Received an unknown midi message type, which we'll ignore") | |
elif bytes[0] >= 0xf0'u8: | |
if bytes[0] == 0xf0'u8: | |
return SysEx(data: bytes) | |
# TODO: MIDI TIME | |
# TODO: Song Position | |
# TODO: Song Select | |
# TODO: Tune Request | |
# TODO: all the RealTime messages | |
when isMainModule: | |
proc show_midi_msg(e: MidiMsg) = | |
echo "Received event: ", e | |
show_midi_msg(parse_midi_bytes(@[0x92'u8, 60'u8, 64'u8])) | |
show_midi_msg(parse_midi_bytes(@[0xa2'u8, 60'u8, 60'u8])) | |
show_midi_msg(parse_midi_bytes(@[0xa2'u8, 60'u8, 50'u8])) | |
show_midi_msg(parse_midi_bytes(@[0xa2'u8, 60'u8, 40'u8])) | |
show_midi_msg(parse_midi_bytes(@[0xa2'u8, 60'u8, 30'u8])) | |
show_midi_msg(parse_midi_bytes(@[0xa2'u8, 60'u8, 15'u8])) | |
show_midi_msg(parse_midi_bytes(@[0x82'u8, 60'u8, 0'u8])) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note: Changing all of these to be ref object's fixes the issue ... my guess is that MidiMsg is pre-created on the stack in the calling function, but unfortunately the compiler doesn't complain if you try to return a different/larger object... it will just try to copy the returned value onto the stack, and blows up when it's larger than the original.