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) ⇒ 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.

Parameters:

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

    MIDI channel number (0-15).

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

    human friendly identifier.



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/musa-dsl/midi/midi-voices.rb', line 207

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.



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

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).



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

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.



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

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.



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

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.



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

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:



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

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.



199
200
201
# File 'lib/musa-dsl/midi/midi-voices.rb', line 199

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 immediate note-off messages for all active pitches and an all-notes-off message on this channel and resets internal state.



295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/musa-dsl/midi/midi-voices.rb', line 295

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

  @active_pitches.each_with_index do |pitch_status, pitch|
    next if @fast_forward || pitch_status[:note_controls].empty?

    msg = MIDIEvents::NoteOff.new(@channel, pitch, 0)
    @output&.puts msg
  end

  @active_pitches.clear
  fill_active_pitches @active_pitches
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.



276
277
278
# File 'lib/musa-dsl/midi/midi-voices.rb', line 276

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.



235
236
237
238
239
240
241
242
243
# File 'lib/musa-dsl/midi/midi-voices.rb', line 235

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).



246
247
248
# File 'lib/musa-dsl/midi/midi-voices.rb', line 246

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.



313
314
315
# File 'lib/musa-dsl/midi/midi-voices.rb', line 313

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.



260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/musa-dsl/midi/midi-voices.rb', line 260

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.



288
289
290
# File 'lib/musa-dsl/midi/midi-voices.rb', line 288

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).



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

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.



318
319
320
# File 'lib/musa-dsl/midi/midi-voices.rb', line 318

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