Class: Music::Transcription::MeasureScore

Inherits:
NoteScore
  • Object
show all
Defined in:
lib/music-transcription/model/measure_score.rb,
lib/music-transcription/packing/measure_score_packing.rb,
lib/music-transcription/conversion/measure_score_conversion.rb

Instance Attribute Summary collapse

Attributes inherited from NoteScore

#parts, #program, #start_tempo, #tempo_changes

Attributes included from Validatable

#errors

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from NoteScore

#check_start_tempo_type, #check_tempo_change_types, #clone, #duration

Methods included from Validatable

#invalid?, #valid?, #validate

Constructor Details

#initialize(start_meter, start_tempo, meter_changes: {}, tempo_changes: {}, parts: {}, program: Program.new) {|_self| ... } ⇒ MeasureScore

Returns a new instance of MeasureScore.

Yields:

  • (_self)

Yield Parameters:



7
8
9
10
11
12
13
14
# File 'lib/music-transcription/model/measure_score.rb', line 7

def initialize start_meter, start_tempo, meter_changes: {}, tempo_changes: {}, parts: {}, program: Program.new
  @start_meter = start_meter
  @meter_changes = meter_changes
  
  super(start_tempo, tempo_changes: tempo_changes,
        program: program, parts: parts)
  yield(self) if block_given?
end

Instance Attribute Details

#meter_changesObject

Returns the value of attribute meter_changes.



5
6
7
# File 'lib/music-transcription/model/measure_score.rb', line 5

def meter_changes
  @meter_changes
end

#start_meterObject

Returns the value of attribute start_meter.



5
6
7
# File 'lib/music-transcription/model/measure_score.rb', line 5

def start_meter
  @start_meter
end

Class Method Details

.unpack(packing) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/music-transcription/packing/measure_score_packing.rb', line 16

def self.unpack packing
  unpacked_start_meter = Meter.parse(packing["start_meter"])
  unpacked_mcs = Hash[ packing["meter_changes"].map do |k,v|
    v = v.clone
    v[0] = Meter.parse(v[0])
    [k, Change.from_ary(v) ]
  end ]
  
  note_score = NoteScore.unpack(packing)
  
  new(unpacked_start_meter, note_score.start_tempo,
    meter_changes: unpacked_mcs, tempo_changes: note_score.tempo_changes,
    program: note_score.program, parts: note_score.parts
  )
end

.valid_tempo_typesObject



25
26
27
# File 'lib/music-transcription/model/measure_score.rb', line 25

def self.valid_tempo_types
  NoteScore.valid_tempo_types + [ Tempo::BPM ]
end

Instance Method Details

#==(other) ⇒ Object



56
57
58
59
# File 'lib/music-transcription/model/measure_score.rb', line 56

def ==(other)
  return super(other) && @start_meter == other.start_meter &&
    @meter_changes == other.meter_changes
end

#beat_duration_at(moff) ⇒ Object



93
94
95
# File 'lib/music-transcription/conversion/measure_score_conversion.rb', line 93

def beat_duration_at moff
  beat_durations.select {|k,v| k <= moff }.max[1]
end

#beat_durationsObject



126
127
128
129
130
131
132
133
134
135
136
# File 'lib/music-transcription/conversion/measure_score_conversion.rb', line 126

def beat_durations
  bdurs = @meter_changes.map do |offset,change|
    [ offset, change.value.beat_duration ]
  end.sort
  
  if bdurs.empty? || bdurs[0][0] != 0
    bdurs.unshift([0,@start_meter.beat_duration])
  end

  return bdurs
end

#check_meterchange_dursObject



49
50
51
52
53
54
# File 'lib/music-transcription/model/measure_score.rb', line 49

def check_meterchange_durs
  nonzero_duration = @meter_changes.select {|k,v| !v.is_a?(Change::Immediate) }
  if nonzero_duration.any?
    raise NonZeroError, "meter changes #{nonzero_duration} are not immediate"
  end
end

#check_meterchange_offsetsObject



42
43
44
45
46
47
# File 'lib/music-transcription/model/measure_score.rb', line 42

def check_meterchange_offsets
  badoffsets = @meter_changes.select {|k,v| k != k.to_i }
  if badoffsets.any?
    raise NonIntegerError, "meter changes #{badoffsets} have non-integer offsets"
  end
end

#check_meterchange_typesObject



35
36
37
38
39
40
# File 'lib/music-transcription/model/measure_score.rb', line 35

def check_meterchange_types
  badtypes = @meter_changes.select {|k,v| !v.value.is_a?(Meter) }
  if badtypes.any?
    raise TypeError, "meter change values #{nonmeter_values} are not Meter objects"
  end
end

#check_methodsObject



16
17
18
19
# File 'lib/music-transcription/model/measure_score.rb', line 16

def check_methods
  super() + [:check_startmeter_type, :check_meterchange_types,
             :check_meterchange_durs, :check_meterchange_offsets]
end

#check_startmeter_typeObject



29
30
31
32
33
# File 'lib/music-transcription/model/measure_score.rb', line 29

def check_startmeter_type
  unless @start_meter.is_a? Meter
    raise TypeError, "start meter #{@start_meter} is not a Meter object"
  end
end

#convert_parts(mnoff_map = self.measure_note_map) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/music-transcription/conversion/measure_score_conversion.rb', line 31

def convert_parts mnoff_map = self.measure_note_map
  Hash[ @parts.map do |name,part|
    new_dcs = Hash[ part.dynamic_changes.map do |moff,change|
      noff = mnoff_map[moff]
      noff2 = mnoff_map[moff + change.duration]
      [noff, change.resize(noff2-noff)]
    end ]
    new_notes = part.notes.map {|n| n.clone }
    [name, Part.new(part.start_dynamic,
      notes: new_notes, dynamic_changes: new_dcs)]
  end ]
end

#convert_program(mnoff_map = self.measure_note_map) ⇒ Object



44
45
46
47
48
49
50
# File 'lib/music-transcription/conversion/measure_score_conversion.rb', line 44

def convert_program mnoff_map = self.measure_note_map
  Program.new(
    @program.segments.map do |seg|
      mnoff_map[seg.first]...mnoff_map[seg.last]
    end
  )
end

#convert_tempo_changes(tempo_class, mnoff_map = self.measure_note_map) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/music-transcription/conversion/measure_score_conversion.rb', line 52

def convert_tempo_changes tempo_class, mnoff_map = self.measure_note_map
  tcs = {}
  bdurs = beat_durations
  
  @tempo_changes.each do |moff,change|
    bdur = bdurs.select {|x,y| x <= moff}.max[1]
    tempo = change.value
    
    case change
    when Change::Immediate
      tcs[mnoff_map[moff]] = Change::Immediate.new(tempo.convert(tempo_class,bdur))
    when Change::Gradual
      start_moff, end_moff = moff, moff + change.duration
      start_noff, end_noff = mnoff_map[start_moff], mnoff_map[end_moff]
      dur = end_noff - start_noff
      cur_noff, cur_bdur = start_noff, bdur

      more_bdurs = bdurs.select {|x,y| x > moff && x < end_moff }
      if more_bdurs.any?
        more_bdurs.each do |next_moff, next_bdur|
          next_noff = mnoff_map[next_moff]
          tcs[cur_noff] = Change::Partial.new(
            tempo.convert(tempo_class, cur_bdur), dur,
            cur_noff - start_noff, next_noff - start_noff)
          cur_noff, cur_bdur = next_noff, next_bdur
        end
        tcs[cur_noff] = Change::Partial.new(
          tempo.convert(tempo_class, cur_bdur), dur,
          cur_noff - start_noff, dur)
      else
        tcs[start_noff] = Change::Gradual.new(
          tempo.convert(tempo_class, cur_bdur), end_noff - start_noff)
      end
    when Change::Partial
      raise NotImplementedError, "No support yet for converting partial tempo changes."
    end
  end
  
  return tcs
end

#measure_durationsObject



138
139
140
141
142
143
144
145
146
147
148
# File 'lib/music-transcription/conversion/measure_score_conversion.rb', line 138

def measure_durations
  mdurs = @meter_changes.map do |offset,change|
    [ offset, change.value.measure_duration ]
  end.sort
  
  if mdurs.empty? || mdurs[0][0] != 0
    mdurs.unshift([0,@start_meter.measure_duration])
  end

  return Hash[ mdurs ]
end

#measure_note_mapObject



27
28
29
# File 'lib/music-transcription/conversion/measure_score_conversion.rb', line 27

def measure_note_map
  Conversion::measure_note_map(measure_offsets,measure_durations)
end

#measure_offsetsObject



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/music-transcription/conversion/measure_score_conversion.rb', line 97

def measure_offsets
  moffs = Set.new([0.to_r])
  
  @tempo_changes.each do |moff,change|
    moffs.add(moff)
    if change.duration > 0
      moffs.add(moff + change.duration)
    end
  end
  
  @meter_changes.keys.each {|moff| moffs.add(moff) }
  
  @parts.values.each do |part|
    part.dynamic_changes.each do |moff,change|
      moffs.add(moff)
      if change.duration > 0
        moffs.add(moff + change.duration)
      end
    end
  end
  
  @program.segments.each do |seg|
    moffs.add(seg.first)
    moffs.add(seg.last)
  end
  
  return moffs.sort
end

#packObject



5
6
7
8
9
10
11
12
13
14
# File 'lib/music-transcription/packing/measure_score_packing.rb', line 5

def pack
  hash = super()
  hash["start_meter"] = start_meter.to_s
  hash["meter_changes"] = Hash[ meter_changes.map do |offset,change|
    a = change.pack
    a[0] = a[0].to_s
    [offset,a]
  end ]
  return hash
end

#to_note_score(tempo_class = Tempo::QNPM) ⇒ Object

Convert to NoteScore object by first converting measure-based offsets to note-based offsets, and eliminating the use of meters. Also, tempo is to non-BPM tempo.



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/music-transcription/conversion/measure_score_conversion.rb', line 8

def to_note_score tempo_class = Tempo::QNPM
  unless valid?
    raise NotValidError, "Current MeasureScore is invalid, so it can not be \
                          converted to a NoteScore. Validation errors: #{self.errors}"
  end
  
  unless NoteScore.valid_tempo_types.include? tempo_class
    raise TypeError, "The desired tempo class #{tempo_class} is not valid for a NoteScore."
  end
  
  mnoff_map = self.measure_note_map
  parts = convert_parts(mnoff_map)
  prog = convert_program(mnoff_map)
  tcs = convert_tempo_changes(tempo_class, mnoff_map)
  start_tempo = @start_tempo.convert(tempo_class,@start_meter.beat_duration)
  
  NoteScore.new(start_tempo, parts: parts, program: prog, tempo_changes: tcs)
end

#validatablesObject



21
22
23
# File 'lib/music-transcription/model/measure_score.rb', line 21

def validatables
  super() + [ @start_meter ] + @meter_changes.values.map {|v| v.value}
end