Class: Musa::MIDIVoices::MIDIVoice Private

Inherits:
Object
  • Object
show all
Defined in:
lib/musa-dsl/midi/midi-voices.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Individual MIDI channel voice with sequencer-synchronized note management.

Manages the state of a single MIDI channel including active notes, controller values, and sustain pedal. All note scheduling is tied to the sequencer clock, ensuring proper timing in fast-forward mode or during quantized playback.

Supports indefinite notes (manual note-off), automatic note-off scheduling, callbacks on note stop, and fast-forward mode for silent state updates.

Examples:

Playing notes

voice = voices.voices.first
voice.note pitch: 60, velocity: 90, duration: 1r/4
voice.note pitch: [60, 64, 67], velocity: 100, duration: 1r  # chord

Indefinite notes with manual control

note_ctrl = voice.note pitch: 60, duration: nil
note_ctrl.on_stop { puts "Note ended!" }
# ... later:
note_ctrl.note_off

Controller and sustain pedal

voice.controller[:mod_wheel] = 64
voice.sustain_pedal = 127
voice.controller[:expression] = 100

See Also:

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(sequencer:, output:, channel:, name: nil, do_log: nil) ⇒ MIDIVoice

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of MIDIVoice.

Parameters:

  • sequencer (Musa::Sequencer::Sequencer)
  • output (#puts, nil)
  • channel (Integer)

    MIDI channel number (0-15).

  • name (String, nil) (defaults to: nil)

    human friendly identifier.



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/musa-dsl/midi/midi-voices.rb', line 203

def initialize(sequencer:, output:, channel:, name: nil, do_log: nil)
  do_log ||= false

  @sequencer = sequencer
  @output = output
  @channel = channel
  @name = name
  @do_log = do_log

  @tick_duration = Rational(1, @sequencer.ticks_per_bar)

  @controllers_control = ControllersControl.new(@output, @channel)

  @active_pitches = []
  fill_active_pitches @active_pitches

  @sequencer.logger.warn 'voice without output' unless @output

  self
end

Instance Attribute Details

#active_pitchesArray<Hash> (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns metadata for each of the 128 MIDI pitches. Mainly used internally.

Returns:

  • (Array<Hash>)

    metadata for each of the 128 MIDI pitches. Mainly used internally.



194
195
196
# File 'lib/musa-dsl/midi/midi-voices.rb', line 194

def active_pitches
  @active_pitches
end

#channelInteger (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns MIDI channel number (0-15).

Returns:

  • (Integer)

    MIDI channel number (0-15).



191
192
193
# File 'lib/musa-dsl/midi/midi-voices.rb', line 191

def channel
  @channel
end

#do_logBoolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns whether this voice logs every emitted message.

Returns:

  • (Boolean)

    whether this voice logs every emitted message.



182
183
184
# File 'lib/musa-dsl/midi/midi-voices.rb', line 182

def do_log
  @do_log
end

#nameString?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns optional name used in log messages.

Returns:

  • (String, nil)

    optional name used in log messages.



179
180
181
# File 'lib/musa-dsl/midi/midi-voices.rb', line 179

def name
  @name
end

#output#puts? (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns MIDI destination. When nil the voice becomes silent.

Returns:

  • (#puts, nil)

    MIDI destination. When nil the voice becomes silent.



188
189
190
# File 'lib/musa-dsl/midi/midi-voices.rb', line 188

def output
  @output
end

#sequencerMusa::Sequencer::Sequencer (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns sequencer driving this voice.

Returns:



185
186
187
# File 'lib/musa-dsl/midi/midi-voices.rb', line 185

def sequencer
  @sequencer
end

#tick_durationRational (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns duration (in bars) of a sequencer tick; used to schedule note offs.

Returns:

  • (Rational)

    duration (in bars) of a sequencer tick; used to schedule note offs.



197
198
199
# File 'lib/musa-dsl/midi/midi-voices.rb', line 197

def tick_duration
  @tick_duration
end

Instance Method Details

#all_notes_offvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Sends an immediate all-notes-off message on this channel and resets internal state.



291
292
293
294
295
296
# File 'lib/musa-dsl/midi/midi-voices.rb', line 291

def all_notes_off
  @active_pitches.clear
  fill_active_pitches @active_pitches

  @output.puts MIDIEvents::ChannelMessage.new(0xb, @channel, 0x7b, 0)
end

#controllerControllersControl

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns MIDI CC manager for this voice.

Returns:

  • (ControllersControl)

    MIDI CC manager for this voice.



272
273
274
# File 'lib/musa-dsl/midi/midi-voices.rb', line 272

def controller
  @controllers_control
end

#fast_forward=(enabled) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Turns fast-forward on/off for this voice.

When disabling it, pending notes that were held silently are sent again so the synth is in sync with the sequencer state.

Parameters:

  • enabled (Boolean)

    true to enable fast-forward, false to disable.



231
232
233
234
235
236
237
238
239
# File 'lib/musa-dsl/midi/midi-voices.rb', line 231

def fast_forward=(enabled)
  if @fast_forward && !enabled
    (0..127).each do |pitch|
      @output.puts MIDIEvents::NoteOn.new(@channel, pitch, @active_pitches[pitch][:velocity]) unless @active_pitches[pitch][:note_controls].empty?
    end
  end

  @fast_forward = enabled
end

#fast_forward?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns true when in fast-forward mode (notes registered but not emitted).

Returns:

  • (Boolean)

    true when in fast-forward mode (notes registered but not emitted).



242
243
244
# File 'lib/musa-dsl/midi/midi-voices.rb', line 242

def fast_forward?
  @fast_forward
end

#log(msg) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Logs a message tagging the current voice.

Parameters:

  • msg (String)

    the message to log.



302
303
304
# File 'lib/musa-dsl/midi/midi-voices.rb', line 302

def log(msg)
  @sequencer.logger.info('MIDIVoice') { "voice #{name || @channel}: #{msg}" } if @do_log
end

#note(pitchvalue = nil, pitch: nil, velocity: nil, duration: nil, duration_offset: nil, note_duration: nil, velocity_off: nil) ⇒ NoteControl?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Plays one or several MIDI notes.

Parameters:

  • pitchvalue (Numeric, Array<Numeric>, nil) (defaults to: nil)

    optional shorthand for +pitch+.

  • pitch (Numeric, Symbol, Array<Numeric, Symbol>) (defaults to: nil)

    MIDI note numbers or :silence. Arrays/ranges expand to multiple notes.

  • velocity (Numeric, Array<Numeric>) (defaults to: nil)

    raw velocity (0-127). Defaults to 63.

  • duration (Numeric, nil) (defaults to: nil)

    musical duration in bars. When nil the note stays on until Musa::MIDIVoices::MIDIVoice::NoteControl#note_off is called manually.

  • duration_offset (Numeric) (defaults to: nil)

    offset applied when scheduling the note-off inside the sequencer.

  • note_duration (Numeric, nil) (defaults to: nil)

    alternative duration in bars for legato control.

  • velocity_off (Numeric, Array<Numeric>) (defaults to: nil)

    release velocity (defaults to 63).

Returns:

  • (NoteControl, nil)

    handler that can be used to attach callbacks.



256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/musa-dsl/midi/midi-voices.rb', line 256

def note(pitchvalue = nil, pitch: nil, velocity: nil, duration: nil, duration_offset: nil, note_duration: nil, velocity_off: nil)
  pitch ||= pitchvalue

  if pitch
    velocity ||= 63

    duration_offset ||= -@tick_duration
    note_duration ||= [0, duration + duration_offset].max

    velocity_off ||= 63

    NoteControl.new(self, pitch: pitch, velocity: velocity, duration: note_duration, velocity_off: velocity_off).note_on
  end
end

#sustain_pedalInteger?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns current sustain pedal value.

Returns:

  • (Integer, nil)

    current sustain pedal value.



284
285
286
# File 'lib/musa-dsl/midi/midi-voices.rb', line 284

def sustain_pedal
  @controllers_control[:sustain_pedal]
end

#sustain_pedal=(value) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Sets the sustain pedal state.

Parameters:

  • value (Integer)

    pedal value (0-127, typically 0 or 127).



279
280
281
# File 'lib/musa-dsl/midi/midi-voices.rb', line 279

def sustain_pedal=(value)
  @controllers_control[:sustain_pedal] = value
end

#to_sString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns human-readable voice description.

Returns:

  • (String)

    human-readable voice description.



307
308
309
# File 'lib/musa-dsl/midi/midi-voices.rb', line 307

def to_s
  "voice #{@name} output: #{@output} channel: #{@channel}"
end