music-transcription 0.11.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/examples/{make_hip.rb → hip.rb} +8 -2
  4. data/examples/hip_packed.yml +22 -0
  5. data/examples/{make_missed_connection.rb → missed_connection.rb} +8 -2
  6. data/examples/missed_connection.yml +2 -2
  7. data/examples/missed_connection_packed.yml +14 -0
  8. data/examples/{make_song1.rb → song1.rb} +7 -1
  9. data/examples/song1_packed.yml +19 -0
  10. data/examples/{make_song2.rb → song2.rb} +6 -1
  11. data/examples/song2_packed.yml +21 -0
  12. data/lib/music-transcription.rb +11 -3
  13. data/lib/music-transcription/model/link.rb +24 -6
  14. data/lib/music-transcription/model/meter.rb +10 -0
  15. data/lib/music-transcription/model/meters.rb +1 -1
  16. data/lib/music-transcription/model/note.rb +32 -0
  17. data/lib/music-transcription/model/pitch.rb +19 -0
  18. data/lib/music-transcription/packing/change_packing.rb +27 -0
  19. data/lib/music-transcription/packing/part_packing.rb +33 -0
  20. data/lib/music-transcription/packing/program_packing.rb +18 -0
  21. data/lib/music-transcription/packing/score_packing.rb +59 -0
  22. data/lib/music-transcription/parsing/convenience_methods.rb +10 -0
  23. data/lib/music-transcription/parsing/meter_parsing.rb +117 -9
  24. data/lib/music-transcription/parsing/meter_parsing.treetop +12 -0
  25. data/lib/music-transcription/parsing/note_node.rb +42 -0
  26. data/lib/music-transcription/parsing/note_parsing.rb +57 -180
  27. data/lib/music-transcription/parsing/note_parsing.treetop +2 -19
  28. data/lib/music-transcription/parsing/numbers/nonnegative_float_parsing.rb +178 -0
  29. data/lib/music-transcription/parsing/numbers/nonnegative_float_parsing.treetop +19 -0
  30. data/lib/music-transcription/parsing/{nonnegative_integer_parsing.rb → numbers/nonnegative_integer_parsing.rb} +9 -0
  31. data/lib/music-transcription/parsing/{nonnegative_integer_parsing.treetop → numbers/nonnegative_integer_parsing.treetop} +7 -1
  32. data/lib/music-transcription/parsing/numbers/nonnegative_rational_parsing.rb +88 -0
  33. data/lib/music-transcription/parsing/numbers/nonnegative_rational_parsing.treetop +22 -0
  34. data/lib/music-transcription/parsing/{positive_integer_parsing.rb → numbers/positive_integer_parsing.rb} +0 -0
  35. data/lib/music-transcription/parsing/{positive_integer_parsing.treetop → numbers/positive_integer_parsing.treetop} +0 -0
  36. data/lib/music-transcription/parsing/segment_parsing.rb +143 -0
  37. data/lib/music-transcription/parsing/segment_parsing.treetop +25 -0
  38. data/lib/music-transcription/version.rb +1 -1
  39. data/spec/model/link_spec.rb +44 -60
  40. data/spec/model/meter_spec.rb +18 -0
  41. data/spec/model/note_spec.rb +39 -0
  42. data/spec/model/pitch_spec.rb +32 -0
  43. data/spec/packing/change_packing_spec.rb +91 -0
  44. data/spec/packing/part_packing_spec.rb +66 -0
  45. data/spec/packing/program_packing_spec.rb +33 -0
  46. data/spec/packing/score_packing_spec.rb +122 -0
  47. data/spec/parsing/meter_parsing_spec.rb +2 -2
  48. data/spec/parsing/note_node_spec.rb +87 -0
  49. data/spec/parsing/note_parsing_spec.rb +3 -0
  50. data/spec/parsing/numbers/nonnegative_float_spec.rb +11 -0
  51. data/spec/parsing/{nonnegative_integer_spec.rb → numbers/nonnegative_integer_spec.rb} +1 -1
  52. data/spec/parsing/numbers/nonnegative_rational_spec.rb +11 -0
  53. data/spec/parsing/{positive_integer_spec.rb → numbers/positive_integer_spec.rb} +1 -1
  54. data/spec/parsing/segment_parsing_spec.rb +27 -0
  55. metadata +45 -17
  56. data/lib/music-transcription/parsing/note_nodes.rb +0 -64
  57. data/spec/parsing/note_nodes_spec.rb +0 -84
@@ -0,0 +1,25 @@
1
+ module Music
2
+ module Transcription
3
+ module Parsing
4
+
5
+ grammar Segment
6
+ include NonnegativeInteger
7
+ include NonnegativeFloat
8
+ include NonnegativeRational
9
+
10
+ rule range
11
+ first:nonnegative_number ([.] 2..3) last:nonnegative_number {
12
+ def to_range
13
+ first.to_num...last.to_num
14
+ end
15
+ }
16
+ end
17
+
18
+ rule nonnegative_number
19
+ nonnegative_float / nonnegative_rational / nonnegative_integer
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -2,6 +2,6 @@
2
2
  module Music
3
3
  module Transcription
4
4
  # music-transcription version
5
- VERSION = "0.11.0"
5
+ VERSION = "0.13.0"
6
6
  end
7
7
  end
@@ -1,73 +1,51 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
- describe Link::Glissando do
4
- describe '#initialize' do
5
- it 'should assign the given pitch to :target_pitch' do
6
- Link::Glissando.new(C2).target_pitch.should eq(C2)
7
- end
8
- end
9
-
10
- describe '#==' do
11
- it 'should return true if two links have the same target pitch' do
12
- Link::Glissando.new(C2).should eq(Link::Glissando.new(C2))
3
+ {
4
+ Link::Glissando => Link::Portamento,
5
+ Link::Portamento => Link::Glissando,
6
+ Link::Slur => Link::Legato,
7
+ Link::Legato => Link::Slur
8
+ }.each do |klass,klass2|
9
+ describe klass do
10
+ describe '#initialize' do
11
+ it 'should assign the given pitch to :target_pitch' do
12
+ klass.new(C2).target_pitch.should eq(C2)
13
+ end
13
14
  end
14
15
 
15
- it 'should return false if two links do not have the same target pitch' do
16
- Link::Glissando.new(C2).should_not eq(Link::Glissando.new(F5))
16
+ describe '#==' do
17
+ it 'should return true if two links have the same target pitch' do
18
+ klass.new(C2).should eq(klass.new(C2))
19
+ end
20
+
21
+ it 'should return false if two links do not have the same target pitch' do
22
+ klass.new(C2).should_not eq(klass.new(F5))
23
+ end
24
+
25
+ it 'should return false if the link type is different' do
26
+ klass.new(C2).should_not eq(klass2.new(C2))
27
+ end
17
28
  end
18
29
 
19
- it 'should return false if the link type is different' do
20
- Link::Glissando.new(C2).should_not eq(Link::Portamento.new(D2))
21
- end
22
- end
23
-
24
- describe '#clone' do
25
- it 'should return a link equal to original' do
26
- l = Link::Glissando.new(C4)
27
- l.clone.should eq l
28
- end
29
- end
30
-
31
- describe '#to_yaml' do
32
- it 'should produce YAML that can be loaded' do
33
- l = Link::Glissando.new(C5)
34
- YAML.load(l.to_yaml).should eq l
35
- end
36
- end
37
- end
38
-
39
- describe Link::Portamento do
40
- describe '#initialize' do
41
- it 'should assign the given pitch to :target_pitch' do
42
- Link::Portamento.new(C2).target_pitch.should eq(C2)
43
- end
44
- end
45
-
46
- describe '#==' do
47
- it 'should return true if two links have the same target pitch' do
48
- Link::Portamento.new(C2).should eq(Link::Portamento.new(C2))
30
+ describe '#clone' do
31
+ it 'should return a link equal to original' do
32
+ l = klass.new(C4)
33
+ l.clone.should eq l
34
+ end
49
35
  end
50
36
 
51
- it 'should return false if two links do not have the same target pitch' do
52
- Link::Portamento.new(C2).should_not eq(Link::Portamento.new(F5))
37
+ describe '#to_yaml' do
38
+ it 'should produce YAML that can be loaded' do
39
+ l = klass.new(C5)
40
+ YAML.load(l.to_yaml).should eq l
41
+ end
53
42
  end
54
43
 
55
- it 'should return false if the link type is different' do
56
- Link::Portamento.new(C2).should_not eq(Link::Glissando.new(D2))
57
- end
58
- end
59
-
60
- describe '#clone' do
61
- it 'should return a link equal to original' do
62
- l = Link::Portamento.new(C4)
63
- l.clone.should eq l
64
- end
65
- end
66
-
67
- describe '#to_yaml' do
68
- it 'should produce YAML that can be loaded' do
69
- l = Link::Portamento.new(C5)
70
- YAML.load(l.to_yaml).should eq l
44
+ describe '#to_s' do
45
+ it 'should produce string that include link char and target pitch str' do
46
+ l = klass.new(C3)
47
+ l.to_s.should eq(l.link_char + "C3")
48
+ end
71
49
  end
72
50
  end
73
51
  end
@@ -96,4 +74,10 @@ describe Link::Tie do
96
74
  YAML.load(l.to_yaml).should eq l
97
75
  end
98
76
  end
77
+
78
+ describe '#to_s' do
79
+ it 'should return =' do
80
+ Link::Tie.new.to_s.should eq("=")
81
+ end
82
+ end
99
83
  end
@@ -48,6 +48,24 @@ describe Meter do
48
48
  end
49
49
  end
50
50
 
51
+ describe '#to_s' do
52
+ context 'beat duration with 1 in denominator' do
53
+ it 'should return string of fraction: beats_per_measure / beat_duration.denom' do
54
+ FOUR_FOUR.to_s.should eq("4/4")
55
+ TWO_FOUR.to_s.should eq("2/4")
56
+ THREE_FOUR.to_s.should eq("3/4")
57
+ TWO_TWO.to_s.should eq("2/2")
58
+ end
59
+ end
60
+
61
+ context 'beat duration with >1 in denominator' do
62
+ it 'should return beats_per_measure * beat_dur fraction' do
63
+ SIX_EIGHT.to_s.should eq("2*3/8")
64
+ Meter.new(3,"3/8".to_r).to_s.should eq("3*3/8")
65
+ end
66
+ end
67
+ end
68
+
51
69
  describe '#valid?' do
52
70
  {
53
71
  '4/4 meter' => [4,'1/4'.to_r],
@@ -122,6 +122,45 @@ describe Note do
122
122
  end
123
123
  end
124
124
 
125
+ describe '#to_s' do
126
+ before :all do
127
+ @note_parser = Parsing::NoteParser.new
128
+ end
129
+
130
+ context
131
+ it 'should produce string that when parsed produces an equal note' do
132
+ durations = ["1/8".to_r,"1".to_r,"5/3".to_r]
133
+ include Articulations
134
+ articulations = [NORMAL, SLUR, LEGATO, TENUTO, PORTATO, STACCATO, STACCATISSIMO ]
135
+ pitches_links_sets = [
136
+ [[],{}],
137
+ [[C2],{}],
138
+ [[A5,D6],{ A5 => Link::Tie.new }],
139
+ [[C5,E6,Gb2],{ C5 => Link::Glissando.new(D5) }],
140
+ [[A5,D6],{ A5 => Link::Legato.new(B5), D6 => Link::Slur.new(E6) }],
141
+ [[C5,E6,Gb2],{ C5 => Link::Portamento.new(D5), Gb2 => Link::Tie.new }],
142
+ ]
143
+
144
+ notes = []
145
+ durations.each do |d|
146
+ articulations.each do |art|
147
+ pitches_links_sets.each do |pitches_links_set|
148
+ pitches,links = pitches_links_set
149
+ [true,false].each do |acc|
150
+ notes.push Note.new(d, pitches, articulation: art, links: links, accented: acc)
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ notes.each do |note|
157
+ str = note.to_s
158
+ note2 = @note_parser.parse(str).to_note
159
+ note2.should eq(note)
160
+ end
161
+ end
162
+ end
163
+
125
164
  describe '#to_yaml' do
126
165
  it 'should produce YAML that can be loaded' do
127
166
  n = Note.new(1,[C2])
@@ -108,6 +108,38 @@ describe Pitch do
108
108
  end
109
109
  end
110
110
 
111
+ describe '#to_s' do
112
+ context 'on-letter semitones' do
113
+ it 'should return the semitone letter + octave number' do
114
+ { C0 => "C0", D1 => "D1", E7 => "E7",
115
+ F8 => "F8", G3 => "G3", A4 => "A4",
116
+ B5 => "B5", C2 => "C2" }.each do |p,s|
117
+ p.to_s.should eq s
118
+ end
119
+ end
120
+ end
121
+
122
+ context 'off-letter semitones' do
123
+ context 'sharpit set false' do
124
+ it 'should return semitone letter + "b" + octave number' do
125
+ { Db0 => "Db0", Eb1 => "Eb1", Gb7 => "Gb7",
126
+ Ab4 => "Ab4", Bb1 => "Bb1" }.each do |p,s|
127
+ p.to_s(false).should eq s
128
+ end
129
+ end
130
+ end
131
+
132
+ context 'sharpit set true' do
133
+ it 'should return semitone letter + "#" + octave number' do
134
+ { Db0 => "C#0", Eb1 => "D#1", Gb7 => "F#7",
135
+ Ab4 => "G#4", Bb1 => "A#1" }.each do |p,s|
136
+ p.to_s(true).should eq s
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+
111
143
  describe '.make_from_freq' do
112
144
  it 'should make a pitch whose freq is approximately the given freq' do
113
145
  [16.35, 440.0, 987.77].each do |given_freq|
@@ -0,0 +1,91 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Change::Immediate do
4
+ describe '#pack' do
5
+ before :all do
6
+ @c = Change::Immediate.new(0.5)
7
+ @a = @c.pack
8
+ end
9
+
10
+ it 'should return an Array' do
11
+ @a.should be_a Array
12
+ end
13
+
14
+ it 'should return an Array of size 1' do
15
+ @a.size.should eq 1
16
+ end
17
+
18
+ it 'should put the change value at index 0' do
19
+ @a[0].should eq @c.value
20
+ end
21
+ end
22
+ end
23
+
24
+ describe Change::Gradual do
25
+ describe '#pack' do
26
+ before :all do
27
+ @c = Change::Gradual.new(0.3,1.5)
28
+ @a = @c.pack
29
+ end
30
+
31
+ it 'should return an Array' do
32
+ @a.should be_a Array
33
+ end
34
+
35
+ it 'should return an Array of size 1' do
36
+ @a.size.should eq 2
37
+ end
38
+
39
+ it 'should put the change value at index 0' do
40
+ @a[0].should eq @c.value
41
+ end
42
+
43
+ it 'should put the duration at index 1' do
44
+ @a[1].should eq @c.duration
45
+ end
46
+ end
47
+ end
48
+
49
+ describe Change do
50
+ describe '.unpack' do
51
+ context 'given a packed immediate change' do
52
+ before :all do
53
+ @c = Change::Immediate.new(0.5)
54
+ @a = @c.pack
55
+ @c2 = Change.unpack(@a)
56
+ end
57
+
58
+ it 'should return a Change::Immediate' do
59
+ @c2.should be_a Change::Immediate
60
+ end
61
+
62
+ it 'should successfully unpack the change value' do
63
+ @c2.value.should eq @c.value
64
+ end
65
+
66
+ it 'should successfully unpack the change duration' do
67
+ @c2.duration.should eq @c.duration
68
+ end
69
+ end
70
+
71
+ context 'given a packed gradual change' do
72
+ before :all do
73
+ @c = Change::Gradual.new(0.3,1.5)
74
+ @a = @c.pack
75
+ @c2 = Change.unpack(@a)
76
+ end
77
+
78
+ it 'should return a Change::Gradual' do
79
+ @c2.should be_a Change::Gradual
80
+ end
81
+
82
+ it 'should successfully unpack the change value' do
83
+ @c2.value.should eq @c.value
84
+ end
85
+
86
+ it 'should successfully unpack the change duration' do
87
+ @c2.duration.should eq @c.duration
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,66 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Part do
4
+ before :all do
5
+ @p = Part.new(
6
+ Dynamics::MP,
7
+ notes: Parsing::notes("/4Bb2 /8 /8F3= /2F3 /4Bb2 /8 /8F3= /2F3"),
8
+ dynamic_changes: {
9
+ 1 => Change::Immediate.new(Dynamics::PP),
10
+ 2 => Change::Gradual.new(Dynamics::FF, 2.0)
11
+ }
12
+ )
13
+
14
+ @h = @p.pack
15
+ end
16
+
17
+ describe '#pack' do
18
+ it 'should produce a hash' do
19
+ @h.should be_a Hash
20
+ end
21
+
22
+ it 'should return a hash with keys: "notes", "start_dynamic", and "dynamic_changes"' do
23
+ @h.keys.should include("notes")
24
+ @h.keys.should include("start_dynamic")
25
+ @h.keys.should include("dynamic_changes")
26
+ end
27
+
28
+ it 'should pack notes into a string' do
29
+ @h['notes'].should be_a String
30
+ end
31
+
32
+ it 'should pack start dynamic as plain numeric value' do
33
+ @h['start_dynamic'].should be_a Numeric
34
+ end
35
+
36
+ it 'should pack dynamic changes as whatver type Change#pack returns' do
37
+ @h['dynamic_changes'].each do |offset,packed_v|
38
+ change_v = @p.dynamic_changes[offset]
39
+ t = change_v.pack.class
40
+ packed_v.should be_a t
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '.unpack' do
46
+ before :all do
47
+ @p2 = Part.unpack @h
48
+ end
49
+
50
+ it 'should return a Part' do
51
+ @p2.should be_a Part
52
+ end
53
+
54
+ it 'should successfuly unpack the notes' do
55
+ @p2.notes.should eq @p.notes
56
+ end
57
+
58
+ it 'should successfuly unpack the start dynamic' do
59
+ @p2.start_dynamic.should eq @p.start_dynamic
60
+ end
61
+
62
+ it 'should successfuly unpack the dynamic changes' do
63
+ @p2.dynamic_changes.should eq @p.dynamic_changes
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,33 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Program do
4
+ before :all do
5
+ @p = Program.new([0...5,3.0...6.5,("1/2".to_r)...("3/2".to_r)])
6
+ @a = @p.pack
7
+ @p2 = Program.unpack(@a)
8
+ end
9
+
10
+ describe '#pack' do
11
+ it 'should return an Array' do
12
+ @a.should be_a Array
13
+ end
14
+
15
+ it 'should return an array with same size as # of segments' do
16
+ @a.size.should eq @p.segments.size
17
+ end
18
+
19
+ it 'should return an array of strings' do
20
+ @a.each {|x| x.should be_a String }
21
+ end
22
+ end
23
+
24
+ describe '#unpack' do
25
+ it 'should return a Program' do
26
+ @p2.should be_a Program
27
+ end
28
+
29
+ it 'should successfully unpack program segments' do
30
+ @p2.segments.should eq @p.segments
31
+ end
32
+ end
33
+ end