musicality 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +30 -0
  3. data/lib/musicality/errors.rb +1 -0
  4. data/lib/musicality/notation/conversion/change_conversion.rb +63 -3
  5. data/lib/musicality/notation/conversion/note_time_converter.rb +23 -5
  6. data/lib/musicality/notation/conversion/score_conversion.rb +60 -0
  7. data/lib/musicality/notation/conversion/score_converter.rb +105 -0
  8. data/lib/musicality/notation/model/change.rb +98 -28
  9. data/lib/musicality/notation/model/part.rb +1 -1
  10. data/lib/musicality/notation/model/score.rb +4 -4
  11. data/lib/musicality/notation/packing/change_packing.rb +35 -25
  12. data/lib/musicality/notation/packing/score_packing.rb +2 -2
  13. data/lib/musicality/notation/util/function.rb +99 -0
  14. data/lib/musicality/notation/util/piecewise_function.rb +79 -99
  15. data/lib/musicality/notation/util/transition.rb +12 -0
  16. data/lib/musicality/notation/util/value_computer.rb +12 -152
  17. data/lib/musicality/performance/conversion/score_collator.rb +35 -20
  18. data/lib/musicality/performance/midi/part_sequencer.rb +2 -5
  19. data/lib/musicality/validatable.rb +6 -1
  20. data/lib/musicality/version.rb +1 -1
  21. data/lib/musicality.rb +4 -4
  22. data/musicality.gemspec +1 -0
  23. data/spec/notation/conversion/change_conversion_spec.rb +216 -9
  24. data/spec/notation/conversion/measure_note_map_spec.rb +2 -2
  25. data/spec/notation/conversion/note_time_converter_spec.rb +91 -9
  26. data/spec/notation/conversion/{measured_score_conversion_spec.rb → score_conversion_spec.rb} +44 -9
  27. data/spec/notation/conversion/score_converter_spec.rb +246 -0
  28. data/spec/notation/model/change_spec.rb +139 -36
  29. data/spec/notation/model/part_spec.rb +3 -3
  30. data/spec/notation/model/score_spec.rb +4 -4
  31. data/spec/notation/packing/change_packing_spec.rb +222 -71
  32. data/spec/notation/packing/part_packing_spec.rb +1 -1
  33. data/spec/notation/packing/score_packing_spec.rb +3 -2
  34. data/spec/notation/util/function_spec.rb +43 -0
  35. data/spec/notation/util/transition_spec.rb +51 -0
  36. data/spec/notation/util/value_computer_spec.rb +43 -87
  37. data/spec/performance/conversion/score_collator_spec.rb +46 -7
  38. data/spec/performance/midi/part_sequencer_spec.rb +2 -1
  39. metadata +29 -14
  40. data/lib/musicality/notation/conversion/measured_score_conversion.rb +0 -70
  41. data/lib/musicality/notation/conversion/measured_score_converter.rb +0 -95
  42. data/lib/musicality/notation/conversion/unmeasured_score_conversion.rb +0 -47
  43. data/lib/musicality/notation/conversion/unmeasured_score_converter.rb +0 -64
  44. data/spec/notation/conversion/measured_score_converter_spec.rb +0 -329
  45. data/spec/notation/conversion/unmeasured_score_conversion_spec.rb +0 -71
  46. data/spec/notation/conversion/unmeasured_score_converter_spec.rb +0 -116
@@ -83,6 +83,43 @@ describe ScoreCollator do
83
83
  end
84
84
  end
85
85
 
86
+ context 'part contains trimmed gradual changes' do
87
+ it 'should exclude the change when it is not at all in a program segment' do
88
+ score = Score::Measured.new(FOUR_FOUR, 120,
89
+ parts: { 1 => Part.new(Dynamics::FF, dynamic_changes: {
90
+ 2 => Change::Gradual.linear(Dynamics::PP,5).trim(1,0)
91
+ }) },
92
+ program: Program.new([7...9])
93
+ )
94
+ collator = ScoreCollator.new(score)
95
+ parts = collator.collate_parts
96
+ dcs = parts[1].dynamic_changes
97
+ dcs.size.should eq(1)
98
+ dcs[0.to_r].should eq(Change::Immediate.new(Dynamics::PP))
99
+
100
+ score.program = Program.new([0...1])
101
+ collator = ScoreCollator.new(score)
102
+ parts = collator.collate_parts
103
+ dcs = parts[1].dynamic_changes
104
+ dcs.size.should eq(1)
105
+ dcs[0.to_r].should eq(Change::Immediate.new(Dynamics::FF))
106
+ end
107
+
108
+ it 'should trim the change further if needed' do
109
+ score = Score::Measured.new(FOUR_FOUR, 120,
110
+ parts: { 1 => Part.new(Dynamics::FF, dynamic_changes: {
111
+ 2 => Change::Gradual.linear(Dynamics::PP,5).trim(1,1)
112
+ }) },
113
+ program: Program.new([3...4])
114
+ )
115
+ collator = ScoreCollator.new(score)
116
+ parts = collator.collate_parts
117
+ dcs = parts[1].dynamic_changes
118
+ dcs.size.should eq(1)
119
+ dcs[0.to_r].should eq(Change::Gradual.linear(Dynamics::PP,5).trim(2,2))
120
+ end
121
+ end
122
+
86
123
  it 'should preserve links' do
87
124
  notes = Note.split_parse("1Db4~Bb4")
88
125
  score = Score::Measured.new(
@@ -106,7 +143,7 @@ describe ScoreCollator do
106
143
  before :all do
107
144
  @change0 = Change::Immediate.new(120)
108
145
  @change1 = Change::Immediate.new(200)
109
- @change2 = Change::Gradual.new(100,1)
146
+ @change2 = Change::Gradual.linear(100,1)
110
147
  end
111
148
 
112
149
  context 'tempo change starts at end of program segment' do
@@ -134,8 +171,8 @@ describe ScoreCollator do
134
171
  @tcs[0.to_r].should be_a Change::Immediate
135
172
  end
136
173
 
137
- it 'should be used as starting tempo change value' do
138
- @tcs[0.to_r].value.should eq @change2.value
174
+ it 'should be used as starting tempo change end_value' do
175
+ @tcs[0.to_r].end_value.should eq @change2.end_value
139
176
  end
140
177
  end
141
178
 
@@ -146,8 +183,9 @@ describe ScoreCollator do
146
183
  collator = ScoreCollator.new(score)
147
184
  tcs = collator.collate_tempo_changes
148
185
  tcs.size.should eq 1
149
- tcs[0.to_r].value.should eq @change2.value
150
- tcs[0.to_r].duration.should eq(0.5)
186
+ tcs[0.to_r].should be_a Change::Gradual::Trimmed
187
+ tcs[0.to_r].end_value.should eq @change2.end_value
188
+ tcs[0.to_r].remaining.should eq(0.5)
151
189
  end
152
190
  end
153
191
 
@@ -160,8 +198,9 @@ describe ScoreCollator do
160
198
  tcs.size.should eq 3
161
199
  tcs[0.to_r].should eq @change0
162
200
  tcs[1.to_r].should eq @change1
163
- tcs[2.to_r].value.should eq @change2.value
164
- tcs[2.to_r].duration.should eq(0.5)
201
+ tcs[2.to_r].should be_a Change::Gradual::Trimmed
202
+ tcs[2.to_r].end_value.should eq @change2.end_value
203
+ tcs[2.to_r].remaining.should eq(0.5)
165
204
  end
166
205
  end
167
206
  end
@@ -3,7 +3,8 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
3
3
  describe PartSequencer do
4
4
  describe '#make_midi_track' do
5
5
  before :all do
6
- part = Part.new(Dynamics::PP, notes: "/4C4 /4D4 /8 /8D4 /8E4 /2C4".to_notes * 2)
6
+ part = Part.new(Dynamics::PP, notes: "/4C4 /4D4 /8 /8D4 /8E4 /2C4".to_notes * 2,
7
+ dynamic_changes: { 1 => Change::Gradual.linear(Dynamics::FF,2) })
7
8
  @midi_seq = MIDI::Sequence.new
8
9
  @part_name = "mypart"
9
10
  @channel = 2
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: musicality
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Tunnell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-24 00:00:00.000000000 Z
11
+ date: 2014-12-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ~>
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: treetop
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -109,6 +123,7 @@ files:
109
123
  - .gitignore
110
124
  - .rspec
111
125
  - .ruby-version
126
+ - ChangeLog.md
112
127
  - Gemfile
113
128
  - LICENSE.txt
114
129
  - README.md
@@ -122,12 +137,10 @@ files:
122
137
  - lib/musicality/errors.rb
123
138
  - lib/musicality/notation/conversion/change_conversion.rb
124
139
  - lib/musicality/notation/conversion/measure_note_map.rb
125
- - lib/musicality/notation/conversion/measured_score_conversion.rb
126
- - lib/musicality/notation/conversion/measured_score_converter.rb
127
140
  - lib/musicality/notation/conversion/note_time_converter.rb
141
+ - lib/musicality/notation/conversion/score_conversion.rb
142
+ - lib/musicality/notation/conversion/score_converter.rb
128
143
  - lib/musicality/notation/conversion/tempo_conversion.rb
129
- - lib/musicality/notation/conversion/unmeasured_score_conversion.rb
130
- - lib/musicality/notation/conversion/unmeasured_score_converter.rb
131
144
  - lib/musicality/notation/model/articulations.rb
132
145
  - lib/musicality/notation/model/change.rb
133
146
  - lib/musicality/notation/model/dynamics.rb
@@ -176,8 +189,10 @@ files:
176
189
  - lib/musicality/notation/parsing/pitch_parsing.treetop
177
190
  - lib/musicality/notation/parsing/segment_parsing.rb
178
191
  - lib/musicality/notation/parsing/segment_parsing.treetop
192
+ - lib/musicality/notation/util/function.rb
179
193
  - lib/musicality/notation/util/interpolation.rb
180
194
  - lib/musicality/notation/util/piecewise_function.rb
195
+ - lib/musicality/notation/util/transition.rb
181
196
  - lib/musicality/notation/util/value_computer.rb
182
197
  - lib/musicality/performance/conversion/glissando_converter.rb
183
198
  - lib/musicality/performance/conversion/note_sequence_extractor.rb
@@ -197,12 +212,10 @@ files:
197
212
  - spec/musicality_spec.rb
198
213
  - spec/notation/conversion/change_conversion_spec.rb
199
214
  - spec/notation/conversion/measure_note_map_spec.rb
200
- - spec/notation/conversion/measured_score_conversion_spec.rb
201
- - spec/notation/conversion/measured_score_converter_spec.rb
202
215
  - spec/notation/conversion/note_time_converter_spec.rb
216
+ - spec/notation/conversion/score_conversion_spec.rb
217
+ - spec/notation/conversion/score_converter_spec.rb
203
218
  - spec/notation/conversion/tempo_conversion_spec.rb
204
- - spec/notation/conversion/unmeasured_score_conversion_spec.rb
205
- - spec/notation/conversion/unmeasured_score_converter_spec.rb
206
219
  - spec/notation/model/change_spec.rb
207
220
  - spec/notation/model/link_spec.rb
208
221
  - spec/notation/model/meter_spec.rb
@@ -233,6 +246,8 @@ files:
233
246
  - spec/notation/parsing/pitch_node_spec.rb
234
247
  - spec/notation/parsing/pitch_parsing_spec.rb
235
248
  - spec/notation/parsing/segment_parsing_spec.rb
249
+ - spec/notation/util/function_spec.rb
250
+ - spec/notation/util/transition_spec.rb
236
251
  - spec/notation/util/value_computer_spec.rb
237
252
  - spec/performance/conversion/glissando_converter_spec.rb
238
253
  - spec/performance/conversion/note_sequence_extractor_spec.rb
@@ -273,12 +288,10 @@ test_files:
273
288
  - spec/musicality_spec.rb
274
289
  - spec/notation/conversion/change_conversion_spec.rb
275
290
  - spec/notation/conversion/measure_note_map_spec.rb
276
- - spec/notation/conversion/measured_score_conversion_spec.rb
277
- - spec/notation/conversion/measured_score_converter_spec.rb
278
291
  - spec/notation/conversion/note_time_converter_spec.rb
292
+ - spec/notation/conversion/score_conversion_spec.rb
293
+ - spec/notation/conversion/score_converter_spec.rb
279
294
  - spec/notation/conversion/tempo_conversion_spec.rb
280
- - spec/notation/conversion/unmeasured_score_conversion_spec.rb
281
- - spec/notation/conversion/unmeasured_score_converter_spec.rb
282
295
  - spec/notation/model/change_spec.rb
283
296
  - spec/notation/model/link_spec.rb
284
297
  - spec/notation/model/meter_spec.rb
@@ -309,6 +322,8 @@ test_files:
309
322
  - spec/notation/parsing/pitch_node_spec.rb
310
323
  - spec/notation/parsing/pitch_parsing_spec.rb
311
324
  - spec/notation/parsing/segment_parsing_spec.rb
325
+ - spec/notation/util/function_spec.rb
326
+ - spec/notation/util/transition_spec.rb
312
327
  - spec/notation/util/value_computer_spec.rb
313
328
  - spec/performance/conversion/glissando_converter_spec.rb
314
329
  - spec/performance/conversion/note_sequence_extractor_spec.rb
@@ -1,70 +0,0 @@
1
- module Musicality
2
-
3
- class Score
4
- class Measured < TempoBased
5
- # Convert to unmeasured score by converting measure-based offsets to
6
- # note-based offsets, and eliminating the use of meters. Also, tempo is
7
- # coverted from beats-per-minute to quarter-notes per minute.
8
- def to_unmeasured
9
- MeasuredScoreConverter.new(self).convert_score
10
- end
11
-
12
- def to_timed tempo_sample_rate
13
- unmeasured = MeasuredScoreConverter.new(self).convert_score
14
- unmeasured.to_timed(tempo_sample_rate)
15
- end
16
-
17
- def measure_note_map
18
- Conversion::measure_note_map(measure_offsets,measure_durations)
19
- end
20
-
21
- def measure_offsets
22
- moffs = Set.new([0.to_r])
23
-
24
- @tempo_changes.each do |moff,change|
25
- moffs += change.offsets(moff)
26
- end
27
-
28
- @meter_changes.keys.each {|moff| moffs.add(moff) }
29
-
30
- @parts.values.each do |part|
31
- part.dynamic_changes.each do |moff,change|
32
- moffs += change.offsets(moff)
33
- end
34
- end
35
-
36
- @program.segments.each do |seg|
37
- moffs.add(seg.first)
38
- moffs.add(seg.last)
39
- end
40
-
41
- return moffs.sort
42
- end
43
-
44
- def beat_durations
45
- bdurs = @meter_changes.map do |offset,change|
46
- [ offset, change.value.beat_duration ]
47
- end.sort
48
-
49
- if bdurs.empty? || bdurs[0][0] != 0
50
- bdurs.unshift([0,@start_meter.beat_duration])
51
- end
52
-
53
- return bdurs
54
- end
55
-
56
- def measure_durations
57
- mdurs = @meter_changes.map do |offset,change|
58
- [ offset, change.value.measure_duration ]
59
- end.sort
60
-
61
- if mdurs.empty? || mdurs[0][0] != 0
62
- mdurs.unshift([0,@start_meter.measure_duration])
63
- end
64
-
65
- return Hash[ mdurs ]
66
- end
67
- end
68
- end
69
-
70
- end
@@ -1,95 +0,0 @@
1
- module Musicality
2
-
3
- # Converts measured score to unmeasured by converting measure-based offsets
4
- # and durations to note-based offsets, and eliminating the use of meters.
5
- # Also, tempo is coverted from beats-per-minute to quarter-notes per minute.
6
- class MeasuredScoreConverter
7
- def initialize score
8
- unless score.valid?
9
- raise NotValidError, "The given score can not be converted because \
10
- it is invalid, with these errors: #{score.errors}"
11
- end
12
-
13
- @score = score
14
- @mnoff_map = score.measure_note_map
15
- end
16
-
17
- def convert_score
18
- Score::Unmeasured.new(convert_start_tempo,
19
- parts: convert_parts, program: convert_program,
20
- tempo_changes: convert_tempo_changes)
21
- end
22
-
23
- def convert_parts
24
- Hash[ @score.parts.map do |name,part|
25
- new_dcs = Hash[ part.dynamic_changes.map do |moff,change|
26
- case change
27
- when Change::Immediate
28
- [@mnoff_map[moff],change.clone]
29
- when Change::Gradual
30
- noff1, noff2, noff3, noff4 = change.offsets(moff).map {|x| @mnoff_map[x] }
31
- [noff2, Change::Gradual.new(change.value,
32
- noff3-noff2, noff2-noff1, noff4-noff3)]
33
- end
34
- end ]
35
- new_notes = part.notes.map {|n| n.clone }
36
- [name, Part.new(part.start_dynamic,
37
- notes: new_notes, dynamic_changes: new_dcs)]
38
- end ]
39
- end
40
-
41
- def convert_program
42
- Program.new(
43
- @score.program.segments.map do |seg|
44
- @mnoff_map[seg.first]...@mnoff_map[seg.last]
45
- end
46
- )
47
- end
48
-
49
- def convert_start_tempo
50
- Tempo::BPM.to_qnpm(@score.start_tempo, @score.start_meter.beat_duration)
51
- end
52
-
53
- def convert_tempo_changes
54
- tcs = {}
55
- bdurs = @score.beat_durations
56
-
57
- @score.tempo_changes.each do |moff,change|
58
- bdur = bdurs.select {|x,y| x <= moff}.max[1]
59
- tempo = change.value
60
-
61
- case change
62
- when Change::Immediate
63
- tcs[@mnoff_map[moff]] = Change::Immediate.new(Tempo::BPM.to_qnpm(tempo,bdur))
64
- when Change::Gradual
65
- start_moff, end_moff = moff, moff + change.duration
66
- start_noff, end_noff = @mnoff_map[start_moff], @mnoff_map[end_moff]
67
-
68
- initial_moff, final_moff = start_moff - change.elapsed, end_moff + change.remaining
69
- initial_noff, final_noff = @mnoff_map[initial_moff], @mnoff_map[final_moff]
70
-
71
- more_bdurs = bdurs.select {|x,y| x > start_moff && x < end_moff }
72
- cur_noff, cur_bdur = start_noff, bdur
73
-
74
- more_bdurs.each do |next_moff, next_bdur|
75
- next_noff = @mnoff_map[next_moff]
76
- elapsed = cur_noff - initial_noff
77
- impending = next_noff - cur_noff
78
- remaining = final_noff - next_noff
79
- tempo2 = Tempo::BPM.to_qnpm(tempo, cur_bdur)
80
- tcs[cur_noff] = Change::Gradual.new(tempo2, impending, elapsed, remaining)
81
- cur_noff, cur_bdur = next_noff, next_bdur
82
- end
83
- elapsed = cur_noff - initial_noff
84
- impending = end_noff - cur_noff
85
- remaining = final_noff - end_noff
86
- tempo2 = Tempo::BPM.to_qnpm(tempo, cur_bdur)
87
- tcs[cur_noff] = Change::Gradual.new(tempo2, impending, elapsed, remaining)
88
- end
89
- end
90
-
91
- return tcs
92
- end
93
- end
94
-
95
- end
@@ -1,47 +0,0 @@
1
- module Musicality
2
-
3
- class Score
4
- class Unmeasured < TempoBased
5
- # Convert to unmeasured score by converting measure-based offsets to
6
- # note-based offsets, and eliminating the use of meters. Also, tempo is
7
- # coverted from beats-per-minute to quarter-notes per minute.
8
- def to_timed tempo_sample_rate
9
- UnmeasuredScoreConverter.new(self,tempo_sample_rate).convert_score
10
- end
11
-
12
- def note_time_map tempo_sample_rate
13
- tempo_computer = ValueComputer.new(@start_tempo, @tempo_changes)
14
- ntc = NoteTimeConverter.new(tempo_computer, tempo_sample_rate)
15
- ntc.note_time_map(note_offsets)
16
- end
17
-
18
- def note_offsets
19
- noffs = Set.new([0.to_r])
20
-
21
- @tempo_changes.each do |noff,change|
22
- noffs += change.offsets(noff)
23
- end
24
-
25
- @parts.values.each do |part|
26
- noff = 0.to_r
27
- part.notes.each do |note|
28
- noff += note.duration
29
- noffs.add(noff)
30
- end
31
-
32
- part.dynamic_changes.each do |noff,change|
33
- noffs += change.offsets(noff)
34
- end
35
- end
36
-
37
- @program.segments.each do |seg|
38
- noffs.add(seg.first)
39
- noffs.add(seg.last)
40
- end
41
-
42
- return noffs.sort
43
- end
44
- end
45
- end
46
-
47
- end
@@ -1,64 +0,0 @@
1
- require 'set'
2
-
3
- module Musicality
4
-
5
- # Converts unmeasured score to timed score, by converting note-based offsets
6
- # and durations to time-based, and eliminating the use of tempo.
7
- class UnmeasuredScoreConverter
8
-
9
- def initialize score, tempo_sample_rate
10
- unless score.valid?
11
- raise NotValidError, "The given score can not be converted because \
12
- it is invalid, with these errors: #{score.errors}"
13
- end
14
-
15
- @score = score
16
- @note_time_map = score.note_time_map(tempo_sample_rate)
17
- end
18
-
19
- def convert_score
20
- Score::Timed.new(parts: convert_parts, program: convert_program)
21
- end
22
-
23
- # Convert note-based offsets & durations to time-based.
24
- def convert_parts
25
- Hash[ @score.parts.map do |name,part|
26
- offset = 0.to_r
27
-
28
- new_notes = part.notes.map do |note|
29
- starttime = @note_time_map[offset]
30
- endtime = @note_time_map[offset + note.duration]
31
- offset += note.duration
32
- newnote = note.clone
33
- newnote.duration = endtime - starttime
34
- newnote
35
- end
36
-
37
- new_dcs = Hash[ part.dynamic_changes.map do |noff,change|
38
- case change
39
- when Change::Immediate
40
- [@note_time_map[noff],change.clone]
41
- when Change::Gradual
42
- toff1, toff2, toff3, toff4 = change.offsets(noff).map {|x| @note_time_map[x] }
43
- [toff2, Change::Gradual.new(change.value,
44
- toff3-toff2, toff2-toff1, toff4-toff3)]
45
- end
46
- end ]
47
-
48
- [name, Part.new(part.start_dynamic,
49
- notes: new_notes, dynamic_changes: new_dcs)]
50
- end]
51
- end
52
-
53
- # Convert note-based offsets & durations to time-based.
54
- def convert_program
55
- newsegments = @score.program.segments.map do |segment|
56
- first = @note_time_map[segment.first]
57
- last = @note_time_map[segment.last]
58
- first...last
59
- end
60
- Program.new(newsegments)
61
- end
62
- end
63
-
64
- end