module Music module Performance SlurredElement = Struct.new(:duration, :pitch, :accented) do def slurred?; true; end def articulation; Music::Transcription::Articulations::NORMAL; end def accented?; accented; end end LegatoElement = Struct.new(:duration, :pitch, :accented) do def slurred?; false; end def articulation; Music::Transcription::Articulations::NORMAL; end def accented?; accented; end end FinalElement = Struct.new(:duration, :pitch, :accented, :articulation) do def slurred?; false; end def accented?; accented; end end class NoteSequence def self.adjust_duration duration, articulation x = duration y = Math.log2(x) case articulation when Music::Transcription::Articulations::TENUTO x when Music::Transcription::Articulations::PORTATO x / (1 + 2**(y-1)) when Music::Transcription::Articulations::STACCATO x / (1 + 2**(y)) when Music::Transcription::Articulations::STACCATISSIMO x / (1 + 2**(y+1)) else x - (1/16.0)*(1/(1+2**(-y))) end end attr_reader :start, :stop, :pitches, :attacks def initialize start, stop, pitches, attacks if start >= stop raise ArgumentError, "start #{start} is not less than stop #{stop}" end if pitches.empty? raise ArgumentError, "no pitches given (at least one pitch is required at start offset)" end unless pitches.has_key?(start) raise ArgumentError, "no start pitch given" end pitches.keys.each do |offset| unless offset.between?(start,stop) raise ArgumentError, "pitch offset #{offset} is not between start #{start} and stop #{stop}" end end if attacks.empty? raise ArgumentError, "no attacks given (at least one is required at start offset)" end unless attacks.has_key?(start) raise ArgumentError, "no start attack given" end attacks.keys.each do |offset| unless offset.between?(start,stop) raise ArgumentError, "attack offset #{offset} is not between start #{start} and stop #{stop}" end end @start, @stop = start, stop @pitches, @attacks = pitches, attacks end def self.from_elements offset, elements pitches = {} attacks = {} start = offset if elements.empty? raise ArgumentError, "no elements given" end last = elements.last skip_attack = false elements.each do |el| if skip_attack unless pitches.max[1] == el.pitch pitches[offset] = el.pitch end else pitches[offset] = el.pitch attacks[offset] = el.accented ? ACCENTED : UNACCENTED end skip_attack = el.slurred? unless el.equal?(last) offset += el.duration end end stop = offset + NoteSequence.adjust_duration(last.duration, last.articulation) new(start, stop, pitches, attacks) end def duration; @stop - @start; end end end end