Class: Musa::Transcriptors::FromGDV::ToMIDI::Trill

Inherits:
Musa::Transcription::FeatureTranscriptor show all
Defined in:
lib/musa-dsl/transcription/from-gdv-to-midi.rb

Overview

Trill transcriptor for MIDI playback.

Expands trill ornaments into a rapid alternation between the main note and its upper neighbor. The trill fills the entire note duration with alternating notes, with sophisticated duration management including acceleration.

Trill Options

  • .tr or .tr(true) - Standard trill starting with upper neighbor
  • .tr(:low) - Start with lower neighbor first (2 notes)
  • .tr(:low2) - Start with upper but include lower neighbor (4 notes)
  • .tr(:same) - Start with main note
  • .tr(factor) - Custom duration factor (e.g., .tr(1/8r))

Duration Algorithm

The trill uses a sophisticated multi-phase duration algorithm:

  1. Initial pattern: Based on trill options (:low, :low2, :same)
  2. Regular pattern: Two cycles at full note_duration
  3. Accelerando: Cycles at 2/3 note_duration (faster)
  4. Final notes: Distribute remaining duration

Processing

Given .tr on a note:

{ grade: 0, duration: 1r, tr: true }

Expands to alternating sequence:

[
  { grade: 1, duration: 1/16r },    # Upper (initial)
  { grade: 0, duration: 1/16r },    # Main
  { grade: 1, duration: 1/16r },    # Upper (regular)
  { grade: 0, duration: 1/16r },    # Main
  { grade: 1, duration: ~1/24r },   # Upper (accelerando)
  { grade: 0, duration: ~1/24r },   # Main
  ...
]

Process: .tr

Examples:

Standard trill

trill = Trill.new(duration_factor: 1/4r)
gdv = { grade: 0, duration: 1r, tr: true }
result = trill.transcript(gdv, base_duration: 1/4r, tick_duration: 1/96r)
# Generates alternating upper/main notes filling duration

Trill starting low

gdv = { grade: 0, duration: 1r, tr: :low }
# Starts with lower neighbor, then alternates upper/main

Custom duration factor

gdv = { grade: 0, duration: 1r, tr: 1/8r }
# Faster trill with shorter note durations

Instance Method Summary collapse

Constructor Details

#initialize(duration_factor: nil) ⇒ Trill

Creates trill transcriptor.

Parameters:

  • duration_factor (Rational) (defaults to: nil)

    factor for trill note duration relative to base_duration (default: 1/4)



447
448
449
# File 'lib/musa-dsl/transcription/from-gdv-to-midi.rb', line 447

def initialize(duration_factor: nil)
  @duration_factor = duration_factor || 1/4r
end

Instance Method Details

#transcript(gdv, base_duration:, tick_duration:) ⇒ Array<Hash>, Hash

Transcribes trill to alternating note sequence.

Parameters:

  • gdv (Hash)

    GDV event possibly containing :tr

  • base_duration (Rational)

    base duration unit

  • tick_duration (Rational)

    minimum tick duration

Returns:

  • (Array<Hash>, Hash)

    array with trill notes, or unchanged event



460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
# File 'lib/musa-dsl/transcription/from-gdv-to-midi.rb', line 460

def transcript(gdv, base_duration:, tick_duration:)
  tr = gdv.delete :tr

  if tr
    note_duration = base_duration * @duration_factor

    check(tr) do |tr|
      case tr
      when Numeric # duration factor
        note_duration *= base_duration * tr.to_r
      end
    end

    used_duration = 0r
    last = nil

    gdvs = []

    check(tr) do |tr|
      case tr
      when :low # start with lower note
        gdvs << gdv.clone.tap { |gdv| gdv[:grade] += (last = -1); gdv[:duration] = note_duration }
        gdvs << gdv.clone.tap { |gdv| gdv[:grade] += (last = 0); gdv[:duration] = note_duration }
        used_duration += 2 * note_duration

      when :low2 # start with upper note but go to lower note once
        gdvs << gdv.clone.tap { |gdv| gdv[:grade] += (last = 1); gdv[:duration] = note_duration }
        gdvs << gdv.clone.tap { |gdv| gdv[:grade] += (last = 0); gdv[:duration] = note_duration }
        gdvs << gdv.clone.tap { |gdv| gdv[:grade] += (last = -1); gdv[:duration] = note_duration }
        gdvs << gdv.clone.tap { |gdv| gdv[:grade] += (last = 0); gdv[:duration] = note_duration }
        used_duration += 4 * note_duration

      when :same # start with the same note
        gdvs << gdv.clone.tap { |gdv| gdv[:grade] += (last = 0); gdv[:duration] = note_duration }
        used_duration += note_duration
      end
    end

    2.times do
      if used_duration + 2 * note_duration <= gdv[:duration]
        gdvs << gdv.clone.tap { |gdv| gdv[:grade] += (last = 1); gdv[:duration] = note_duration }
        gdvs << gdv.clone.tap { |gdv| gdv[:grade] += (last = 0); gdv[:duration] = note_duration }

        used_duration += 2 * note_duration
      end
    end

    while used_duration + 2 * note_duration * 2/3r <= gdv[:duration]
      gdvs << gdv.clone.tap { |gdv| gdv[:grade] += (last = 1); gdv[:duration] = note_duration * 2/3r }
      gdvs << gdv.clone.tap { |gdv| gdv[:grade] += (last = 0); gdv[:duration] = note_duration * 2/3r }

      used_duration += 2 * note_duration * 2/3r
    end

    duration_diff = gdv[:duration] - used_duration
    if duration_diff >= note_duration
      gdvs << gdv.clone.tap { |gdv| gdv[:grade] += (last = 1); gdv[:duration] = duration_diff / 2 }
      gdvs << gdv.clone.tap { |gdv| gdv[:grade] += (last = 0); gdv[:duration] = duration_diff / 2 }

    elsif duration_diff > 0
      gdvs[-1][:duration] += duration_diff / 2
      gdvs[-2][:duration] += duration_diff / 2
    end

    super gdvs, base_duration: base_duration, tick_duration: tick_duration
  else
    super
  end
end