Class: Musa::MIDIRecorder::MIDIRecorder

Inherits:
Object
  • Object
show all
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.

Examples:

Complete recording and transcription workflow

require 'musa-dsl'
require 'midi-communications'

sequencer = Musa::Sequencer::Sequencer.new(4, 24)
recorder = Musa::MIDIRecorder::MIDIRecorder.new(sequencer)

# Connect to MIDI input
input = MIDICommunications::Input.gets
input.on_message { |bytes| recorder.record(bytes) }

# Start sequencer and record...
# (play notes on MIDI controller)

# After recording, get transcription:
notes = recorder.transcription
# => [
#   { position: 1r, channel: 0, pitch: 60, velocity: 100, duration: 1/4r, velocity_off: 64 },
#   { position: 5/4r, channel: 0, pitch: :silence, duration: 1/8r },
#   { position: 11/8r, channel: 0, pitch: 62, velocity: 90, duration: 1/4r, velocity_off: 64 }
# ]

notes.each do |note|
  if note[:pitch] == :silence
    puts "Rest at #{note[:position]} for #{note[:duration]} bars"
  else
    puts "Note #{note[:pitch]} at #{note[:position]} for #{note[:duration]} bars"
  end
end

# Clear for next recording:
recorder.clear

See Also:

Instance Method Summary collapse

Constructor Details

#initialize(sequencer) ⇒ void

Parameters:



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

#clearvoid

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

#rawArray<Message>

Returns unprocessed recorded messages.

Returns:

  • (Array<Message>)

    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.

Parameters:

  • midi_bytes (String, Array<Integer>)

    bytes as provided by the MIDI driver.



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

#transcriptionArray<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.

Examples:

Output format

[
  { position: Rational(0,1), channel: 0, pitch: 60, velocity: 100, duration: Rational(1,4), velocity_off: 64 },
  { position: Rational(1,4), channel: 0, pitch: :silence, duration: Rational(1,8) },
  { position: Rational(3,8), channel: 0, pitch: 62, velocity: 90, duration: Rational(1,4), velocity_off: 64 }
]

Returns:

  • (Array<Hash>)

    list of events suitable for Musa transcription pipelines.



129
130
131
132
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
# File 'lib/musa-dsl/midi/midi-recorder.rb', line 129

def transcription
  note_on = {}
  last_note = {}

  notes = []

  @messages.each do |m|
    mm = m.message

    case mm
    when MIDIEvents::NoteOn
      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
      note_on[mm.channel] ||= {}

      note = note_on[mm.channel][mm.note]

      if note
        note_on[mm.channel].delete mm.note

        note[:duration] = m.position - note[:position]
        note[:velocity_off] = mm.velocity
      end

      last_note[mm.channel] = m.position
    end
  end

  notes
end