Class: Musa::MIDIVoices::MIDIVoice Private
- 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.
Instance Attribute Summary collapse
-
#active_pitches ⇒ Array<Hash>
readonly
private
Metadata for each of the 128 MIDI pitches.
-
#channel ⇒ Integer
readonly
private
MIDI channel number (0-15).
-
#do_log ⇒ Boolean
private
Whether this voice logs every emitted message.
-
#name ⇒ String?
private
Optional name used in log messages.
-
#output ⇒ #puts?
readonly
private
MIDI destination.
-
#sequencer ⇒ Musa::Sequencer::Sequencer
readonly
private
Sequencer driving this voice.
-
#tick_duration ⇒ Rational
readonly
private
Duration (in bars) of a sequencer tick; used to schedule note offs.
Instance Method Summary collapse
-
#all_notes_off ⇒ Object
private
sequencer = Musa::Sequencer::BaseSequencer.new(4, 24) voices = Musa::MIDIVoices::MIDIVoices.new(sequencer: sequencer, output: output, channels: [0]) voice = voices.voices.first.
-
#controller ⇒ ControllersControl
private
MIDI CC manager for this voice.
-
#fast_forward=(enabled) ⇒ void
private
Turns fast-forward on/off for this voice.
-
#fast_forward? ⇒ Boolean
private
True when in fast-forward mode (notes registered but not emitted).
- #initialize(sequencer:, output:, channel:, name: nil, do_log: nil) ⇒ void constructor private
-
#log(msg) ⇒ void
private
Logs a message tagging the current voice.
-
#note(pitchvalue = nil, pitch: nil, velocity: nil, duration: nil, duration_offset: nil, note_duration: nil, velocity_off: nil) ⇒ NoteControl?
private
Plays one or several MIDI notes.
-
#sustain_pedal ⇒ Integer?
private
Current sustain pedal value.
-
#sustain_pedal=(value) ⇒ Object
private
Sets the sustain pedal state.
-
#to_s ⇒ String
private
Human-readable voice description.
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.
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 290 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.) @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_pitches ⇒ Array<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.
279 280 281 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 279 def active_pitches @active_pitches end |
#channel ⇒ Integer (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).
276 277 278 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 276 def channel @channel end |
#do_log ⇒ 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 whether this voice logs every emitted message.
267 268 269 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 267 def do_log @do_log end |
#name ⇒ String?
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.
264 265 266 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 264 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.
273 274 275 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 273 def output @output end |
#sequencer ⇒ Musa::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.
270 271 272 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 270 def sequencer @sequencer end |
#tick_duration ⇒ Rational (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.
282 283 284 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 282 def tick_duration @tick_duration end |
Instance Method Details
#all_notes_off ⇒ 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.
sequencer = Musa::Sequencer::BaseSequencer.new(4, 24) voices = Musa::MIDIVoices::MIDIVoices.new(sequencer: sequencer, output: output, channels: [0]) voice = voices.voices.first
voice.note(pitch: 60, velocity: 100, duration: 1r/4)
voice.all_notes_off
output.sent # => [[144, 60, 100], [176, 123, 0], [128, 60, 0]]
# The CC 123 AND an explicit note-off for what was sounding. The
# controller message is not honoured by every synth, so the explicit
# offs are what make this reliable.
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 411 def all_notes_off @output.puts MIDIEvents::ChannelMessage.new(0xb, @channel, 0x7b, 0) @active_pitches.each_with_index do |pitch_status, pitch| # Everything goes quiet here, including the notes that had let go of a # pitch someone else was holding and were waiting for it to fall to # zero. Clearing the table without telling them would leave them # sounding for ever as far as they know, and their callbacks unrun. waiting = pitch_status[:awaiting_silence] pitch_status[:awaiting_silence] = [] waiting.each(&:_pitch_silenced) 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 |
#controller ⇒ ControllersControl
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.
372 373 374 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 372 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.
318 319 320 321 322 323 324 325 326 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 318 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).
329 330 331 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 329 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.
437 438 439 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 437 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.
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 348 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 # Only when there is a duration to compute it from. Computing it # unconditionally raised on the nil that means "no end of its own", # which NoteControl one layer down goes out of its way to accept, so # the indefinite note could be implemented but not asked for # (issue #81). The keyword's own default value was unusable. note_duration ||= [0, duration + duration_offset].max if duration velocity_off ||= 63 log "note #{pitch} has no duration: it will sound until note_off" unless note_duration NoteControl.new(self, pitch: pitch, velocity: velocity, duration: note_duration, velocity_off: velocity_off).note_on end end |
#sustain_pedal ⇒ Integer?
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.
384 385 386 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 384 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.
379 380 381 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 379 def sustain_pedal=(value) @controllers_control[:sustain_pedal] = value end |
#to_s ⇒ String
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.
442 443 444 |
# File 'lib/musa-dsl/midi/midi-voices.rb', line 442 def to_s "voice #{@name} output: #{@output} channel: #{@channel}" end |