lib/music-transcription/note.rb in music-transcription-0.3.0 vs lib/music-transcription/note.rb in music-transcription-0.4.0

- old
+ new

@@ -1,156 +1,112 @@ module Music module Transcription -# Abstraction of a musical note. The note can contain multiple intervals -# (at different pitches). Each interval can also contain a link to an interval -# in a following note. Contains values for attack, sustain, and separation, -# which will be used to form the envelope profile for the note. +require 'set' + +# Abstraction of a musical note. The note can contain zero or more pitches, +# with links to a pitches in a following note. The note also has an accent, +# which must be one of Note::ACCENTS. # # @author James Tunnell # # @!attribute [rw] duration # @return [Numeric] The duration (in, say note length or time), greater than 0.0. # -# @!attribute [rw] intervals -# @return [Numeric] The intervals that define which pitches are part of the -# note and can link to intervals in a following note. +# @!attribute [r] pitches +# @return [Set] The pitches that are part of the note and can link to +# pitches in a following note. +# +# @!attribute [r] links +# @return [Hash] Maps pitches in the current note to pitches in the following +# note, by some link class, like Link::Slur. # -# @!attribute [rw] attack -# @return [Numeric] The amount of attack, from 0.0 (less) to 1.0 (more). -# Attack controls how quickly a note's loudness increases -# at the start. +# @!attribute [rw] accent +# @return [Accent] The accent type, which must be one of Note::ACCENTS. # -# @!attribute [rw] sustain -# @return [Numeric] The amount of sustain, from 0.0 (less) to 1.0 (more). -# Sustain controls how much the note's loudness is -# sustained after the attack. -# -# @!attribute [rw] separation -# @return [Numeric] Shift the note release towards or away the beginning -# of the note. From 0.0 (towards end of the note) to -# 1.0 (towards beginning of the note). -# class Note - include Hashmake::HashMakeable - attr_reader :duration, :intervals, :sustain, :attack, :separation + attr_reader :duration, :pitches, :links + attr_accessor :accent - # hashed-arg specs (for hash-makeable idiom) - ARG_SPECS = { - :duration => arg_spec(:type => Numeric, :reqd => true, :validator => ->(a){ a > 0 } ), - :intervals => arg_spec_array(:type => Interval, :reqd => false), - :sustain => arg_spec(:type => Numeric, :reqd => false, :validator => ->(a){ a.between?(0.0,1.0)}, :default => 0.5), - :attack => arg_spec(:type => Numeric, :reqd => false, :validator => ->(a){ a.between?(0.0,1.0)}, :default => 0.5), - :separation => arg_spec(:type => Numeric, :reqd => false, :validator => ->(a){ a.between?(0.0,1.0)}, :default => 0.5), - } - # A new instance of Note. - # @param [Hash] args Hashed arguments. See Note::ARG_SPECS for details. - def initialize args={} - hash_make args + def initialize duration, pitches = [], links: {}, accent: nil + self.duration = duration + @pitches = Set.new(pitches) + @links = links + self.accent = accent end # Compare the equality of another Note object. def == other return (@duration == other.duration) && - (@intervals == other.intervals) && - (@sustain == other.sustain) && - (@attack == other.attack) && - (@separation == other.separation) + (self.pitches == other.pitches) && + (@links == other.links) && + (@accent == other.accent) end # Set the note duration. # @param [Numeric] duration The duration to use. # @raise [ArgumentError] if duration is not greater than 0. def duration= duration - ARG_SPECS[:duration].validate_value duration + raise ValueNotPositiveError if duration <= 0 @duration = duration - end - - # Set the note sustain. - # @param [Numeric] sustain The sustain of the note. - # @raise [ArgumentError] if sustain is not a Numeric. - # @raise [RangeError] if sustain is outside the range 0.0..1.0. - def sustain= sustain - ARG_SPECS[:sustain].validate_value sustain - @sustain = sustain end - - # Set the note attack. - # @param [Numeric] attack The attack of the note. - # @raise [ArgumentError] if attack is not a Numeric. - # @raise [RangeError] if attack is outside the range 0.0..1.0. - def attack= attack - ARG_SPECS[:attack].validate_value attack - @attack = attack - end - - # Set the note separation. - # @param [Numeric] separation The separation of the note. - # @raise [ArgumentError] if separation is not a Numeric. - # @raise [RangeError] if separation is outside the range 0.0..1.0. - def separation= separation - ARG_SPECS[:separation].validate_value separation - @separation = separation - end # Produce an identical Note object. def clone Marshal.load(Marshal.dump(self)) end - - # Remove any duplicate intervals (occuring on the same pitch), removing - # all but the last occurance. Remove any duplicate links (links to the - # same interval), removing all but the last occurance. - def remove_duplicates - # in case of duplicate notes - intervals_to_remove = Set.new - for i in (0...@intervals.count).entries.reverse - @intervals.each_index do |j| - if j < i - if @intervals[i].pitch == @intervals[j].pitch - intervals_to_remove.add @intervals[j] - end - end - end - end - @intervals.delete_if { |interval| intervals_to_remove.include? interval} - - # in case of duplicate links - for i in (0...@intervals.count).entries.reverse - @intervals.each_index do |j| - if j < i - if @intervals[i].linked? && @intervals[j].linked? && @intervals[i].link.target_pitch == @intervals[j].link.target_pitch - @intervals[j].link = Link.new - end - end - end - end + + def transpose_pitches_only pitch_diff + self.clone.transpose_pitches! pitch_diff, transpose_link end - def transpose pitch_diff - self.clone.transpose! pitch_diff + def transpose_pitches_only! pitch_diff + @pitches = @pitches.map {|pitch| pitch + pitch_diff} + new_links = {} + @links.each_pair do |k,v| + new_links[k + pitch_diff] = v + end + @links = new_links + return self end + + def transpose_pitches_and_links pitch_diff + self.clone.transpose_pitches_and_links! pitch_diff + end - def transpose! pitch_diff - @intervals.each do |interval| - interval.pitch += pitch_diff - interval.link.target_pitch += pitch_diff + def transpose_pitches_and_links! pitch_diff + @pitches = @pitches.map {|pitch| pitch + pitch_diff} + new_links = {} + @links.each_pair do |k,v| + v.target_pitch += pitch_diff + new_links[k + pitch_diff] = v end + @links = new_links return self end def to_s - "#{duration}:#{intervals.map{|i| i.pitch}.inspect}" + output = @duration.to_s + if @pitches.any? + output += "@" + @pitches[0...-1].each do |pitch| + output += pitch.to_s + if @links.has_key? pitch + output += @links[pitch].to_s + end + output += "," + end + + last_pitch = @pitches[-1] + output += last_pitch.to_s + if @links.has_key? last_pitch + output += @links[last_pitch].to_s + end + end + + return output end -end - -module_function - -def note duration, intervals = [], other_args = {} - Note.new( - { :duration => duration, :intervals => intervals }.merge other_args - ) end end end