musicality 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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