musicality 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +7 -1
  3. data/lib/musicality.rb +0 -1
  4. data/lib/musicality/composition/dsl/score_methods.rb +5 -6
  5. data/lib/musicality/notation/conversion/score_conversion.rb +3 -34
  6. data/lib/musicality/notation/conversion/score_converter.rb +6 -17
  7. data/lib/musicality/notation/model/mark.rb +0 -12
  8. data/lib/musicality/notation/model/marks.rb +0 -3
  9. data/lib/musicality/notation/model/note.rb +0 -8
  10. data/lib/musicality/notation/model/score.rb +14 -35
  11. data/lib/musicality/notation/model/symbols.rb +0 -2
  12. data/lib/musicality/notation/parsing/mark_parsing.rb +0 -58
  13. data/lib/musicality/notation/parsing/mark_parsing.treetop +0 -12
  14. data/lib/musicality/notation/parsing/note_node.rb +22 -27
  15. data/lib/musicality/notation/parsing/note_parsing.rb +59 -189
  16. data/lib/musicality/notation/parsing/note_parsing.treetop +2 -9
  17. data/lib/musicality/performance/conversion/note_sequence_extractor.rb +1 -20
  18. data/lib/musicality/performance/model/note_sequence.rb +1 -1
  19. data/lib/musicality/printing/lilypond/note_engraving.rb +3 -3
  20. data/lib/musicality/printing/lilypond/part_engraver.rb +16 -12
  21. data/lib/musicality/project/create_tasks.rb +1 -1
  22. data/lib/musicality/project/load_config.rb +16 -1
  23. data/lib/musicality/project/project.rb +2 -2
  24. data/lib/musicality/version.rb +1 -1
  25. data/musicality.gemspec +0 -1
  26. data/spec/composition/dsl/score_methods_spec.rb +73 -0
  27. data/spec/notation/conversion/score_conversion_spec.rb +0 -100
  28. data/spec/notation/conversion/score_converter_spec.rb +33 -33
  29. data/spec/notation/model/note_spec.rb +2 -2
  30. data/spec/notation/model/score_spec.rb +17 -87
  31. data/spec/notation/parsing/note_node_spec.rb +2 -2
  32. data/spec/notation/parsing/note_parsing_spec.rb +3 -3
  33. data/spec/performance/conversion/note_sequence_extractor_spec.rb +23 -0
  34. data/spec/performance/model/note_sequence_spec.rb +50 -6
  35. metadata +4 -19
  36. data/lib/musicality/notation/conversion/measure_note_map.rb +0 -40
  37. data/spec/notation/conversion/measure_note_map_spec.rb +0 -73
@@ -2,7 +2,7 @@ module Musicality
2
2
 
3
3
  class Project
4
4
  def self.create_tasks config
5
- score_files = Rake::FileList[File.join(config[:scores_dir],"**/*.score")]
5
+ score_files = Rake::FileList[config[:scores]]
6
6
 
7
7
  yaml_task = Tasks::FileRaker::YAML.new(score_files)
8
8
 
@@ -2,9 +2,10 @@ module Musicality
2
2
 
3
3
  class Project
4
4
  CONFIG_FILE_NAME = "config.yml"
5
+ BASE_SCORES_DIR = "scores"
5
6
 
6
7
  DEFAULT_CONFIG = {
7
- :scores_dir => "scores",
8
+ :scores => File.join(BASE_SCORES_DIR, "**", "*.score"),
8
9
  :tempo_sample_rate => 200,
9
10
  :audio_sample_rate => 44100,
10
11
  :audio_sample_format => "int16"
@@ -35,6 +36,20 @@ class Project
35
36
  else
36
37
  DEFAULT_CONFIG
37
38
  end
39
+
40
+ # overrides from ENV
41
+ config.keys.each do |k|
42
+ k_str = k.to_s
43
+ if ENV.has_key? k_str
44
+ case k
45
+ when :tempo_sample_rate, :audio_sample_rate
46
+ config[k] = ENV[k_str].to_i
47
+ else
48
+ config[k] = ENV[k_str]
49
+ end
50
+ end
51
+ end
52
+
38
53
  check_config config
39
54
  return config
40
55
  end
@@ -24,7 +24,7 @@ class Project
24
24
  unless Dir.exists?(dest_dir)
25
25
  raise "directory #{dest_dir} could not be created"
26
26
  end
27
- end
27
+ end
28
28
  end
29
29
 
30
30
  def create_gemfile
@@ -56,7 +56,7 @@ class Project
56
56
  end
57
57
 
58
58
  def create_scores_dir
59
- scores_dir = File.join(dest_dir, Project::DEFAULT_CONFIG[:scores_dir])
59
+ scores_dir = File.join(dest_dir, Project::BASE_SCORES_DIR)
60
60
  Dir.mkdir(scores_dir)
61
61
  end
62
62
  end
@@ -1,3 +1,3 @@
1
1
  module Musicality
2
- VERSION = "0.9.0"
2
+ VERSION = "0.10.0"
3
3
  end
data/musicality.gemspec CHANGED
@@ -28,7 +28,6 @@ Gem::Specification.new do |spec|
28
28
  spec.add_dependency "treetop", "~> 1.5"
29
29
  spec.add_dependency 'midilib', '~> 2.0'
30
30
  spec.add_dependency 'docopt', '~> 0.5'
31
- spec.add_dependency 'ruby-osc', '~> 0.4'
32
31
 
33
32
  spec.required_ruby_version = '>= 2.0'
34
33
  end
@@ -0,0 +1,73 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Score do
4
+ describe '#notes' do
5
+ before :all do
6
+ @equal_duration_note_sets = [
7
+ [ Note.quarter, Note.half ],
8
+ [ Note.eighth, Note.half, Note.eighth ],
9
+ "/2 /12 /12 /12".to_notes,
10
+ ]
11
+ end
12
+
13
+ context 'given two parts' do
14
+ before :all do
15
+ @notes_a = "/4C2 /4Bb2 /2D4".to_notes
16
+ @notes_b = "/8 /8F3 3/4D2".to_notes
17
+ end
18
+
19
+ context 'neither part in score already' do
20
+ it 'should create both parts with given notes and add to score' do
21
+ s = Score::Timed.new
22
+ s.notes("A" => @notes_a, "B" => @notes_b)
23
+ expect(s.parts).to include("A")
24
+ expect(s.parts).to include("B")
25
+ expect(s.parts["A"].notes).to eq(@notes_a)
26
+ expect(s.parts["B"].notes).to eq(@notes_b)
27
+ end
28
+ end
29
+
30
+ context 'both parts in score already' do
31
+ it 'should add onto part notes' do
32
+ s = Score::Timed.new(parts: {
33
+ "A" => Part.new(Dynamics::MP),
34
+ "B" => Part.new(Dynamics::MF)
35
+ })
36
+ s.notes("A" => @notes_a, "B" => @notes_b)
37
+ expect(s.parts["A"].notes).to eq(@notes_a)
38
+ expect(s.parts["B"].notes).to eq(@notes_b)
39
+ end
40
+ end
41
+
42
+ context 'one part in score already' do
43
+ it 'should create the other part with an initial rest and add to score' do
44
+ first_note_a = Note.whole(C2)
45
+ s = Score::Timed.new(parts: {
46
+ "A" => Part.new(Dynamics::MP, notes: [ first_note_a ]),
47
+ })
48
+ s.notes("A" => @notes_a, "B" => @notes_b)
49
+ expect(s.parts["A"].notes).to eq([first_note_a] + @notes_a)
50
+ expect(s.parts["B"].notes).to eq([Note.whole] + @notes_b)
51
+ end
52
+ end
53
+
54
+ context 'given equal duration note sets' do
55
+ it 'should not raise DurationMismatchError' do
56
+ @equal_duration_note_sets.combination(2).each do |note_sets|
57
+ s = Score::Timed.new
58
+ expect { s.notes("A" => note_sets[0], "B" => note_sets[1]) }.to_not raise_error
59
+ end
60
+ end
61
+ end
62
+
63
+ context 'given different duration note sets' do
64
+ it 'should raise DurationMismatchError' do
65
+ note_set_a = "/4 /4 /8".to_notes
66
+ note_set_b = "/4 /4 /8 /16".to_notes
67
+ s = Score::Timed.new
68
+ expect { s.notes("A" => note_set_a, "B" => note_set_b) }.to raise_error(DurationMismatchError)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -31,106 +31,6 @@ describe Score::Tempo do
31
31
  )
32
32
  end
33
33
 
34
- describe '#measure_offsets' do
35
- before(:all){ @moffs = @score.measure_offsets }
36
-
37
- it 'should return an already-sorted array' do
38
- @moffs.should eq @moffs.sort
39
- end
40
-
41
- it 'should start with offset from start tempo/meter/dynamic' do
42
- @moffs[0].should eq(0)
43
- end
44
-
45
- it 'should include offsets from tempo changes' do
46
- @score.tempo_changes.each do |moff,change|
47
- @moffs.should include(moff)
48
- @moffs.should include(moff + change.duration)
49
- end
50
- end
51
-
52
- it 'should include offsets from meter changes' do
53
- @score.meter_changes.keys.each {|moff| @moffs.should include(moff) }
54
- end
55
-
56
- it "should include offsets from each part's dynamic changes" do
57
- @score.parts.values.each do |part|
58
- part.dynamic_changes.each do |moff,change|
59
- @moffs.should include(moff)
60
- change.offsets(moff).each {|offset| @moffs.should include(offset) }
61
- end
62
- end
63
- end
64
-
65
- it 'should include offsets from program segments' do
66
- @score.program.each do |seg|
67
- @moffs.should include(seg.first)
68
- @moffs.should include(seg.last)
69
- end
70
- end
71
- end
72
-
73
- describe '#measure_durations' do
74
- before(:all){ @mdurs = @score.measure_durations }
75
-
76
- it 'should return a Hash' do
77
- @mdurs.should be_a Hash
78
- end
79
-
80
- context 'no meter change at offset 0' do
81
- it 'should have size of meter_changes.size + 1' do
82
- @mdurs.size.should eq(@score.meter_changes.size + 1)
83
- end
84
-
85
- it 'should begin with offset 0' do
86
- @mdurs.keys.min.should eq(0)
87
- end
88
-
89
- it 'should map start meter to offset 0' do
90
- @mdurs[0.to_r].should eq(@score.start_meter.measure_duration)
91
- end
92
- end
93
-
94
- context 'meter change at offset 0' do
95
- before :all do
96
- @change = Change::Immediate.new(THREE_FOUR)
97
- @score2 = Score::Tempo.new(FOUR_FOUR, 120, meter_changes: { 0 => @change })
98
- @mdurs2 = @score2.measure_durations
99
- end
100
-
101
- it 'should have same size as meter changes' do
102
- @mdurs2.size.should eq(@score2.meter_changes.size)
103
- end
104
-
105
- it 'should begin with offset 0' do
106
- @mdurs2.keys.min.should eq(0)
107
- end
108
-
109
- it 'should begin with meter change at offset 0, instead of start meter' do
110
- @mdurs2[0].should eq(@change.end_value.measure_duration)
111
- end
112
- end
113
-
114
- context 'no meter changes' do
115
- before :all do
116
- @score3 = Score::Tempo.new(FOUR_FOUR, 120)
117
- @mdurs3 = @score3.measure_durations
118
- end
119
-
120
- it 'should have size 1' do
121
- @mdurs3.size.should eq(1)
122
- end
123
-
124
- it 'should begin with offset 0' do
125
- @mdurs3.keys.min.should eq(0)
126
- end
127
-
128
- it 'should begin with start meter' do
129
- @mdurs3[0.to_r].should eq(@score3.start_meter.measure_duration)
130
- end
131
- end
132
- end
133
-
134
34
  describe '#to_timed' do
135
35
  it 'should use ScoreConverter#convert_score' do
136
36
  nscore1 = @score.to_timed(200)
@@ -25,7 +25,7 @@ describe ScoreConverter do
25
25
  parts.keys.sort.should eq(@score.parts.keys.sort)
26
26
  end
27
27
 
28
- it 'should convert part dynamic change offsets from measure-based to note-based' do
28
+ it 'should convert part dynamic change offsets from note-based to time-based' do
29
29
  parts = ScoreConverter.new(@score,200).convert_parts
30
30
  parts.should have_key("simple")
31
31
  part = parts["simple"]
@@ -36,36 +36,36 @@ describe ScoreConverter do
36
36
  change.end_value.should eq(@changeB.end_value)
37
37
  change.duration.should eq(4)
38
38
 
39
- #@score.start_meter = THREE_FOUR
40
- #parts = ScoreConverter.new(@score,200).convert_parts
41
- #parts.should have_key("simple")
42
- #part = parts["simple"]
43
- #part.dynamic_changes.keys.sort.should eq([Rational(3,4),Rational(9,4)])
44
- #change = part.dynamic_changes[Rational(3,4)]
45
- #change.end_value.should eq(@changeA.end_value)
46
- #change.duration.should eq(0)
47
- #change = part.dynamic_changes[Rational(9,4)]
48
- #change.end_value.should eq(@changeB.end_value)
49
- #change.duration.should eq(1.5)
39
+ @score.start_meter = THREE_FOUR
40
+ parts = ScoreConverter.new(@score,200).convert_parts
41
+ parts.should have_key("simple")
42
+ part = parts["simple"]
43
+ part.dynamic_changes.keys.sort.should eq([2,6])
44
+ change = part.dynamic_changes[2.0]
45
+ change.end_value.should eq(@changeA.end_value)
46
+ change.duration.should eq(0)
47
+ change = part.dynamic_changes[6.0]
48
+ change.end_value.should eq(@changeB.end_value)
49
+ change.duration.should eq(4)
50
50
  end
51
51
 
52
- #context 'gradual changes with positive elapsed and/or remaining' do
53
- # it 'should change elapsed and remaining so they reflect note-based offsets' do
54
- # score = Score::Tempo.new(THREE_FOUR,120, parts: {
55
- # "abc" => Part.new(Dynamics::P, dynamic_changes: {
56
- # 2 => Change::Gradual.linear(Dynamics::F,2,1,3),
57
- # 7 => Change::Gradual.linear(Dynamics::F,1,4,5)
58
- # })
59
- # })
60
- # converter = ScoreConverter.new(score)
61
- # parts = converter.convert_parts
62
- # dcs = parts["abc"].dynamic_changes
63
- #
64
- # dcs.keys.should eq([Rational(6,4), Rational(21,4)])
65
- # dcs[Rational(3,2)].should eq(Change::Gradual.linear(Dynamics::F,Rational(6,4),Rational(3,4),Rational(9,4)))
66
- # dcs[Rational(21,4)].should eq(Change::Gradual.linear(Dynamics::F,Rational(3,4),Rational(12,4),Rational(15,4)))
67
- # end
68
- #end
52
+ context 'gradual changes with positive elapsed and/or remaining' do
53
+ it 'should change elapsed and remaining so they reflect time-based offsets' do
54
+ score = Score::Tempo.new(THREE_FOUR,120, parts: {
55
+ "abc" => Part.new(Dynamics::P, dynamic_changes: {
56
+ 2 => Change::Gradual.linear(Dynamics::F,2).to_trimmed(1, 3),
57
+ 7 => Change::Gradual.linear(Dynamics::F,1).to_trimmed(4, 5)
58
+ })
59
+ })
60
+ converter = ScoreConverter.new(score, 200)
61
+ parts = converter.convert_parts
62
+ dcs = parts["abc"].dynamic_changes
63
+
64
+ dcs.keys.should eq([4, 14])
65
+ dcs[4.0].should eq(Change::Gradual.linear(Dynamics::F,4).to_trimmed(2,6))
66
+ dcs[14.0].should eq(Change::Gradual.linear(Dynamics::F,2).to_trimmed(8,10))
67
+ end
68
+ end
69
69
  end
70
70
 
71
71
  describe '#convert_program' do
@@ -81,7 +81,7 @@ describe ScoreConverter do
81
81
  prog.size.should eq(@score.program.size)
82
82
  end
83
83
 
84
- it 'should convert program segments offsets from measure-based to note-based' do
84
+ it 'should convert program segments offsets from note-based to time-based' do
85
85
  prog = ScoreConverter.new(@score,200).convert_program
86
86
  prog.size.should eq(2)
87
87
  prog[0].first.should eq(0)
@@ -93,9 +93,9 @@ describe ScoreConverter do
93
93
  prog = ScoreConverter.new(@score,200).convert_program
94
94
  prog.size.should eq(2)
95
95
  prog[0].first.should eq(0)
96
- prog[0].last.should eq(6)
97
- prog[1].first.should eq(3)
98
- prog[1].last.should eq(7.5)
96
+ prog[0].last.should eq(8)
97
+ prog[1].first.should eq(4)
98
+ prog[1].last.should eq(10)
99
99
  end
100
100
  end
101
101
 
@@ -25,7 +25,7 @@ describe Note do
25
25
 
26
26
  it 'should assign :marks if given' do
27
27
  [
28
- [], [BEGIN_SLUR, BEGIN_TRIPLET]
28
+ [], [BEGIN_SLUR], [END_SLUR]
29
29
  ].each do |marks|
30
30
  Note.quarter(marks: marks).marks.should eq(marks)
31
31
  end
@@ -131,7 +131,7 @@ describe Note do
131
131
  pitches,links = pitches_links_set
132
132
  if pitches.any?
133
133
  articulations.each do |art|
134
- [[],[BEGIN_SLUR],[END_SLUR, BEGIN_TRIPLET]].each do |marks|
134
+ [[],[BEGIN_SLUR],[END_SLUR]].each do |marks|
135
135
  notes.push Note.new(d, pitches, articulation: art, links: links, marks: marks)
136
136
  end
137
137
  end
@@ -64,32 +64,7 @@ describe Score do
64
64
  end
65
65
  end
66
66
  end
67
-
68
- describe '#max_part_duration' do
69
- context 'no parts' do
70
- it 'should return 0' do
71
- Score.new.max_part_duration.should eq(0)
72
- end
73
- end
74
67
 
75
- context 'one part' do
76
- it 'should return the part duration' do
77
- Score.new(parts: {"part1" => Part.new(Dynamics::PP,
78
- notes: "/4 /4 /2 1".to_notes)
79
- }).max_part_duration.should eq(2)
80
- end
81
- end
82
-
83
- context 'two parts' do
84
- it 'should return the part duration of the longer part' do
85
- Score.new(parts: {"part1" => Part.new(Dynamics::PP,
86
- notes: "/4 /4 /2 1".to_notes), "part2" => Part.new(Dynamics::MP,
87
- notes: "4".to_notes)
88
- }).max_part_duration.should eq(4)
89
- end
90
- end
91
- end
92
-
93
68
  describe '#valid?' do
94
69
  context 'non-Range objects' do
95
70
  it 'should return false' do
@@ -163,69 +138,26 @@ describe Score::Tempo do
163
138
  end
164
139
  end
165
140
 
166
- describe '#measures_long' do
167
- context 'with no meter changes' do
168
- context 'with no parts' do
169
- it 'should return 0' do
170
- Score::Tempo.new(TWO_FOUR, 120).measures_long.should eq(0)
171
- end
172
- end
173
-
174
- context 'with one part' do
175
- it 'should return the duration of the part, in measures' do
176
- Score::Tempo.new(TWO_FOUR, 120, parts: {
177
- "abc" => Part.new(Dynamics::MF, notes: "/4 /4 /2 3/4".to_notes)
178
- }).measures_long.should eq(3.5)
179
- end
180
- end
181
-
182
- context 'with two parts' do
183
- it 'should return the duration of the longest part, in measures' do
184
- Score::Tempo.new(TWO_FOUR, 120, parts: {
185
- "abc" => Part.new(Dynamics::MF, notes: "/4 /4 /2 3/4".to_notes),
186
- "def" => Part.new(Dynamics::MF, notes: "/4 /4 /2 1".to_notes)
187
- }).measures_long.should eq(4)
188
- end
141
+ describe '#duration' do
142
+ context 'with no parts' do
143
+ it 'should return 0' do
144
+ Score::Tempo.new(TWO_FOUR, 120).duration.should eq(0)
189
145
  end
190
146
  end
191
-
192
- context 'with meter changes' do
193
- it 'should return the duration of the longest part, in measures' do
194
- Score::Tempo.new(TWO_FOUR, 120,
195
- meter_changes: {
196
- 2 => Change::Immediate.new(FOUR_FOUR),
197
- },
198
- parts: {
199
- "abc" => Part.new(Dynamics::MF, notes: "/4 /4 /2 3/4".to_notes),
200
- "def" => Part.new(Dynamics::MF, notes: "/4 /4 /2 1".to_notes)
201
- }
202
- ).measures_long.should eq(3)
203
-
204
- Score::Tempo.new(TWO_FOUR, 120,
205
- meter_changes: {
206
- 2 => Change::Immediate.new(FOUR_FOUR),
207
- 4 => Change::Immediate.new(SIX_EIGHT),
208
- },
209
- parts: {
210
- "abc" => Part.new(Dynamics::MF, notes: "/4 /4 /2 3/4".to_notes),
211
- "def" => Part.new(Dynamics::MF, notes: "/4 /4 /2 1 /2".to_notes)
212
- }
213
- ).measures_long.should eq(3.5)
147
+ context 'with one part' do
148
+ it 'should return the duration of the part, in notes' do
149
+ Score::Tempo.new(TWO_FOUR, 120, parts: {
150
+ "abc" => Part.new(Dynamics::MF, notes: "/4 /4 /2 3/4".to_notes)
151
+ }).duration.should eq(1.75)
214
152
  end
215
153
  end
216
154
 
217
- context 'given specific note duration' do
218
- it 'should change the given note duration to measures' do
219
- score = Score::Tempo.new(TWO_FOUR, 120,
220
- meter_changes: {
221
- 2 => Change::Immediate.new(FOUR_FOUR),
222
- 4 => Change::Immediate.new(SIX_EIGHT)
223
- })
224
-
225
- { 1 => 2, 1.5 => 2.5, 2 => 3, 3 => 4, 3.75 => 5
226
- }.each do |note_dur, meas_dur|
227
- score.measures_long(note_dur).should eq(meas_dur)
228
- end
155
+ context 'with two parts' do
156
+ it 'should return the duration of the longest part, in notes' do
157
+ Score::Tempo.new(TWO_FOUR, 120, parts: {
158
+ "abc" => Part.new(Dynamics::MF, notes: "/4 /4 /2 3/4".to_notes),
159
+ "def" => Part.new(Dynamics::MF, notes: "/4 /4 /2 1".to_notes)
160
+ }).duration.should eq(2)
229
161
  end
230
162
  end
231
163
  end
@@ -258,8 +190,6 @@ describe Score::Tempo do
258
190
  :meter_changes => { 1 => Change::Immediate.new(5) } ],
259
191
  'non-immediate meter change' => [ FOUR_FOUR, 120,
260
192
  :meter_changes => { 1 => Change::Gradual.linear(TWO_FOUR,1) } ],
261
- 'non-integer meter change offset' => [ FOUR_FOUR, 120,
262
- :meter_changes => { 1.1 => Change::Immediate.new(TWO_FOUR) } ],
263
193
  'invalid part' => [ FOUR_FOUR, 120, :parts => { "piano" => Part.new(-0.1) }],
264
194
  'invalid program' => [ FOUR_FOUR, 120, :program => [2..0] ],
265
195
  }.each do |context_str,args|
@@ -314,12 +244,12 @@ describe Score::Timed do
314
244
  end
315
245
  end
316
246
 
317
- describe '#seconds_long' do
247
+ describe '#duration' do
318
248
  it 'should return the duration of the longest part' do
319
249
  Score::Timed.new(parts: {
320
250
  "abc" => Part.new(Dynamics::MF, notes: "/4 /4 /2 3/4".to_notes),
321
251
  "def" => Part.new(Dynamics::MF, notes: "/4 /4 /2 1".to_notes)
322
- }).seconds_long.should eq(2)
252
+ }).duration.should eq(2)
323
253
  end
324
254
  end
325
255