Class: Musa::Transcriptors::FromGDV::ToMIDI::Trill
- Inherits:
-
Musa::Transcription::FeatureTranscriptor
- Object
- Musa::Transcription::FeatureTranscriptor
- Musa::Transcriptors::FromGDV::ToMIDI::Trill
- 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
.tror.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:
- Initial pattern: Based on trill options (:low, :low2, :same)
- Regular pattern: Two cycles at full
note_duration - Accelerando: Cycles at 2/3
note_duration(faster) - 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
Instance Method Summary collapse
-
#initialize(duration_factor: nil, logger: nil) ⇒ Trill
constructor
Creates trill transcriptor.
-
#transcript(gdv, base_duration:, tick_duration:) ⇒ Array<Hash>, Hash
Transcribes trill to alternating note sequence.
Constructor Details
#initialize(duration_factor: nil, logger: nil) ⇒ Trill
Creates trill transcriptor.
471 472 473 474 |
# File 'lib/musa-dsl/transcription/from-gdv-to-midi.rb', line 471 def initialize(duration_factor: nil, logger: nil) @duration_factor = duration_factor || 1/4r @logger = logger || Musa::Logger::Logger.new end |
Instance Method Details
#transcript(gdv, base_duration:, tick_duration:) ⇒ Array<Hash>, Hash
Transcribes trill to alternating note sequence.
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 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 |
# File 'lib/musa-dsl/transcription/from-gdv-to-midi.rb', line 485 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 # A factor over this transcriptor's own trill speed, not an # absolute duration: `tr(1/2)` is twice as fast as this # performer usually trills, whatever that is. So one knob in # main.rb governs every ornament of the piece and they all move # together when it changes. # # Speed is a realisation matter and not a notation one, which is # what the MusicXML side shows: it has no trill transcriptor at # all, reads :tr as "is there a trill" and writes <trill-mark/>, # discarding the value. A number meaning an absolute duration # would be an assertion the engraved score silently loses. # # It used to be `*= base_duration * tr`, applying the base # duration a second time and keeping the default factor the # caller was replacing: `tr(1/8)` gave 766 notes of 1/512 in the # space of a quarter (issue #83). note_duration *= tr.to_r end end # As Mordent already does with the same parameter, three hundred # lines up: a trill finer than the grid is not a fast trill, it is # an unplayable one, and several of its notes would land on the same # tick. Asking for one is a legitimate way to say "as fast as you # can"; it just should not pass unnoticed. # # The floor is a tick and a HALF, not a tick, because the middle of # a trill accelerates to 2/3 of its note duration -- measured, that # is the smallest thing it ever emits. Clamping to one tick would # have made the promise nominal and let the fast section through # below the grid anyway. minimum_duration = tick_duration * 3/2r if note_duration < minimum_duration # Once per transcriptor: a piece full of trills should say this # once, not once per note. unless @warned_about_tick @warned_about_tick = true @logger.warn('Trill') do "a trill note of #{note_duration} would go below the tick #{tick_duration} " \ "when it accelerates; trilling at #{minimum_duration} instead" end end note_duration = minimum_duration 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 |