Skip to content

Instantly share code, notes, and snippets.

@rmt

rmt/midi_msg.nim Secret

Created March 22, 2020 06:13
Show Gist options
  • Save rmt/ccaffc4e44fa52485c103756974fe16e to your computer and use it in GitHub Desktop.
Save rmt/ccaffc4e44fa52485c103756974fe16e to your computer and use it in GitHub Desktop.
Parsing MIDI in nim lang
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]))
@rmt
Copy link
Author

rmt commented Mar 22, 2020

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment