Class: Musa::MIDIRecorder::MIDIRecorder
- Defined in:
- lib/musa-dsl/midi/midi-recorder.rb
Overview
Collects raw MIDI bytes alongside the sequencer position and transforms them into note events. It is especially useful when capturing phrases from an external controller that is clocked by the sequencer timeline.
The recorder uses the midi-parser gem to parse raw MIDI bytes into MIDIEvents objects, then pairs note-on/note-off events to calculate durations and detect silences.
Instance Method Summary collapse
-
#clear ⇒ void
Clears all stored events.
- #initialize(sequencer) ⇒ void constructor
-
#raw ⇒ Array<Message>
Unprocessed recorded messages.
-
#record(midi_bytes) ⇒ void
Records one MIDI packet.
-
#transcription ⇒ Array<Hash>
Converts the message buffer into a list of note hashes.
Constructor Details
#initialize(sequencer) ⇒ void
84 85 86 87 88 89 |
# File 'lib/musa-dsl/midi/midi-recorder.rb', line 84 def initialize(sequencer) @sequencer = sequencer @midi_parser = MIDIParser.new clear end |
Instance Method Details
#clear ⇒ void
This method returns an undefined value.
Clears all stored events.
94 95 96 |
# File 'lib/musa-dsl/midi/midi-recorder.rb', line 94 def clear @messages = [] end |
#raw ⇒ Array<Message>
Returns unprocessed recorded messages.
112 113 114 |
# File 'lib/musa-dsl/midi/midi-recorder.rb', line 112 def raw @messages end |
#record(midi_bytes) ⇒ void
This method returns an undefined value.
Records one MIDI packet.
102 103 104 105 106 107 108 109 |
# File 'lib/musa-dsl/midi/midi-recorder.rb', line 102 def record(midi_bytes) m = @midi_parser.parse midi_bytes m = [m] unless m.is_a? Array m.each do |mm| @messages << Message.new(@sequencer.position, mm) end end |
#transcription ⇒ Array<Hash>
Converts the message buffer into a list of note hashes.
Each note hash contains the keys :position, :channel, :pitch, :velocity
and, when appropriate, :duration and :velocity_off. Silences (gaps between
notes on the same channel) are expressed as pitch: :silence.
Both ways of releasing a note are read as a release: an explicit NoteOff and a NoteOn of velocity 0, which is the running-status form and what most keyboards and sequencers emit.
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/musa-dsl/midi/midi-recorder.rb', line 133 def transcription note_on = {} last_note = {} notes = [] release = lambda do |mm, position| note_on[mm.channel] ||= {} note = note_on[mm.channel][mm.note] if note note_on[mm.channel].delete mm.note note[:duration] = position - note[:position] note[:velocity_off] = mm.velocity end last_note[mm.channel] = position end @messages.each do |m| mm = m. case mm when MIDIEvents::NoteOn # A NoteOn of velocity 0 IS a NoteOff -- the running-status form, and # what most keyboards and sequencers actually emit. Taking it as a # note recorded a phantom note nobody played AND left the note it was # releasing without its :duration, since only a NoteOff closed one # (issue #89). Its velocity, 0, is the release velocity, which is all # the message says about how the note ended. if mm.velocity.zero? release.call(mm, m.position) next end if last_note[mm.channel] notes << { position: last_note[mm.channel], channel: mm.channel, pitch: :silence, duration: m.position - last_note[mm.channel] } last_note.delete mm.channel end note = { position: m.position, channel: mm.channel, pitch: mm.note, velocity: mm.velocity } note_on[mm.channel] ||= {} note_on[mm.channel][mm.note] = note notes << note when MIDIEvents::NoteOff release.call(mm, m.position) end end notes end |