music-transcription 0.15.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/examples/hip.yml +88 -0
- data/examples/missed_connection.yml +34 -0
- data/examples/song1.yml +66 -0
- data/examples/song2.yml +22 -0
- data/lib/music-transcription/model/link.rb +8 -8
- data/lib/music-transcription/model/note.rb +1 -1
- data/lib/music-transcription/model/pitch.rb +66 -46
- data/lib/music-transcription/parsing/articulation_parsing.rb +2 -2
- data/lib/music-transcription/parsing/articulation_parsing.treetop +1 -1
- data/lib/music-transcription/parsing/link_parsing.rb +2 -2
- data/lib/music-transcription/parsing/link_parsing.treetop +1 -1
- data/lib/music-transcription/parsing/pitch_node.rb +7 -2
- data/lib/music-transcription/parsing/pitch_parsing.rb +103 -8
- data/lib/music-transcription/parsing/pitch_parsing.treetop +10 -1
- data/lib/music-transcription/version.rb +1 -1
- data/spec/model/note_spec.rb +2 -1
- data/spec/model/pitch_spec.rb +41 -34
- data/spec/parsing/articulation_parsing_spec.rb +1 -1
- data/spec/parsing/link_nodes_spec.rb +1 -1
- data/spec/parsing/link_parsing_spec.rb +4 -14
- data/spec/parsing/note_node_spec.rb +1 -1
- data/spec/parsing/note_parsing_spec.rb +6 -6
- data/spec/parsing/pitch_node_spec.rb +6 -0
- data/spec/parsing/pitch_parsing_spec.rb +5 -14
- metadata +3 -3
@@ -116,12 +116,12 @@ module Articulation
|
|
116
116
|
return cached
|
117
117
|
end
|
118
118
|
|
119
|
-
if (match_len = has_terminal?("
|
119
|
+
if (match_len = has_terminal?("|", false, index))
|
120
120
|
r0 = instantiate_node(SyntaxNode,input, index...(index + match_len))
|
121
121
|
r0.extend(Legato0)
|
122
122
|
@index += match_len
|
123
123
|
else
|
124
|
-
terminal_parse_failure("
|
124
|
+
terminal_parse_failure("|")
|
125
125
|
r0 = nil
|
126
126
|
end
|
127
127
|
|
@@ -149,11 +149,11 @@ module Link
|
|
149
149
|
end
|
150
150
|
|
151
151
|
i0, s0 = index, []
|
152
|
-
if (match_len = has_terminal?("
|
152
|
+
if (match_len = has_terminal?("|", false, index))
|
153
153
|
r1 = true
|
154
154
|
@index += match_len
|
155
155
|
else
|
156
|
-
terminal_parse_failure("
|
156
|
+
terminal_parse_failure("|")
|
157
157
|
r1 = nil
|
158
158
|
end
|
159
159
|
s0 << r1
|
@@ -11,8 +11,13 @@ module Parsing
|
|
11
11
|
when "b" then -1
|
12
12
|
end
|
13
13
|
end
|
14
|
-
oct =
|
15
|
-
|
14
|
+
oct = octave.to_i
|
15
|
+
ncents = 0
|
16
|
+
unless cents.empty?
|
17
|
+
ncents = cents.to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
Music::Transcription::Pitch.new(semitone: sem, octave: oct, cent: ncents)
|
16
21
|
end
|
17
22
|
end
|
18
23
|
end
|
@@ -12,6 +12,8 @@ module Pitch
|
|
12
12
|
@root ||= :pitch
|
13
13
|
end
|
14
14
|
|
15
|
+
include NonnegativeInteger
|
16
|
+
|
15
17
|
module Pitch0
|
16
18
|
def pitch_letter
|
17
19
|
elements[0]
|
@@ -21,9 +23,13 @@ module Pitch
|
|
21
23
|
elements[1]
|
22
24
|
end
|
23
25
|
|
24
|
-
def
|
26
|
+
def octave
|
25
27
|
elements[2]
|
26
28
|
end
|
29
|
+
|
30
|
+
def cents
|
31
|
+
elements[3]
|
32
|
+
end
|
27
33
|
end
|
28
34
|
|
29
35
|
def _nt_pitch
|
@@ -55,14 +61,17 @@ module Pitch
|
|
55
61
|
end
|
56
62
|
s0 << r2
|
57
63
|
if r2
|
58
|
-
|
59
|
-
r4 = true
|
60
|
-
@index += 1
|
61
|
-
else
|
62
|
-
terminal_parse_failure('[0-9]')
|
63
|
-
r4 = nil
|
64
|
-
end
|
64
|
+
r4 = _nt_octave
|
65
65
|
s0 << r4
|
66
|
+
if r4
|
67
|
+
r6 = _nt_cent
|
68
|
+
if r6
|
69
|
+
r5 = r6
|
70
|
+
else
|
71
|
+
r5 = instantiate_node(SyntaxNode,input, index...index)
|
72
|
+
end
|
73
|
+
s0 << r5
|
74
|
+
end
|
66
75
|
end
|
67
76
|
end
|
68
77
|
if s0.last
|
@@ -78,6 +87,92 @@ module Pitch
|
|
78
87
|
r0
|
79
88
|
end
|
80
89
|
|
90
|
+
module Octave0
|
91
|
+
def n
|
92
|
+
elements[0]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
module Octave1
|
97
|
+
def to_i; n.to_i; end
|
98
|
+
end
|
99
|
+
|
100
|
+
def _nt_octave
|
101
|
+
start_index = index
|
102
|
+
if node_cache[:octave].has_key?(index)
|
103
|
+
cached = node_cache[:octave][index]
|
104
|
+
if cached
|
105
|
+
node_cache[:octave][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
106
|
+
@index = cached.interval.end
|
107
|
+
end
|
108
|
+
return cached
|
109
|
+
end
|
110
|
+
|
111
|
+
i0, s0 = index, []
|
112
|
+
r1 = _nt_nonnegative_integer
|
113
|
+
s0 << r1
|
114
|
+
if s0.last
|
115
|
+
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
|
116
|
+
r0.extend(Octave0)
|
117
|
+
r0.extend(Octave1)
|
118
|
+
else
|
119
|
+
@index = i0
|
120
|
+
r0 = nil
|
121
|
+
end
|
122
|
+
|
123
|
+
node_cache[:octave][start_index] = r0
|
124
|
+
|
125
|
+
r0
|
126
|
+
end
|
127
|
+
|
128
|
+
module Cent0
|
129
|
+
def n
|
130
|
+
elements[1]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
module Cent1
|
135
|
+
def to_i; text_value.to_i; end
|
136
|
+
end
|
137
|
+
|
138
|
+
def _nt_cent
|
139
|
+
start_index = index
|
140
|
+
if node_cache[:cent].has_key?(index)
|
141
|
+
cached = node_cache[:cent][index]
|
142
|
+
if cached
|
143
|
+
node_cache[:cent][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
144
|
+
@index = cached.interval.end
|
145
|
+
end
|
146
|
+
return cached
|
147
|
+
end
|
148
|
+
|
149
|
+
i0, s0 = index, []
|
150
|
+
if has_terminal?(@regexps[gr = '\A[+-]'] ||= Regexp.new(gr), :regexp, index)
|
151
|
+
r1 = true
|
152
|
+
@index += 1
|
153
|
+
else
|
154
|
+
terminal_parse_failure('[+-]')
|
155
|
+
r1 = nil
|
156
|
+
end
|
157
|
+
s0 << r1
|
158
|
+
if r1
|
159
|
+
r2 = _nt_nonnegative_integer
|
160
|
+
s0 << r2
|
161
|
+
end
|
162
|
+
if s0.last
|
163
|
+
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
|
164
|
+
r0.extend(Cent0)
|
165
|
+
r0.extend(Cent1)
|
166
|
+
else
|
167
|
+
@index = i0
|
168
|
+
r0 = nil
|
169
|
+
end
|
170
|
+
|
171
|
+
node_cache[:cent][start_index] = r0
|
172
|
+
|
173
|
+
r0
|
174
|
+
end
|
175
|
+
|
81
176
|
def _nt_pitch_letter
|
82
177
|
start_index = index
|
83
178
|
if node_cache[:pitch_letter].has_key?(index)
|
@@ -3,8 +3,17 @@ module Transcription
|
|
3
3
|
module Parsing
|
4
4
|
|
5
5
|
grammar Pitch
|
6
|
+
include NonnegativeInteger
|
6
7
|
rule pitch
|
7
|
-
pitch_letter mod:[#b]?
|
8
|
+
pitch_letter mod:[#b]? octave cents:cent? <PitchNode>
|
9
|
+
end
|
10
|
+
|
11
|
+
rule octave
|
12
|
+
n:nonnegative_integer { def to_i; n.to_i; end }
|
13
|
+
end
|
14
|
+
|
15
|
+
rule cent
|
16
|
+
[+-] n:nonnegative_integer { def to_i; text_value.to_i; end }
|
8
17
|
end
|
9
18
|
|
10
19
|
rule pitch_letter
|
data/spec/model/note_spec.rb
CHANGED
data/spec/model/pitch_spec.rb
CHANGED
@@ -5,15 +5,19 @@ describe Pitch do
|
|
5
5
|
before :each do
|
6
6
|
@cases =
|
7
7
|
[
|
8
|
-
{ octave
|
9
|
-
{ octave
|
10
|
-
{ octave
|
11
|
-
{ octave
|
12
|
-
{ octave
|
13
|
-
{ octave
|
14
|
-
{ octave
|
15
|
-
{ octave
|
16
|
-
{ octave
|
8
|
+
{ :octave => 1, :semitone => 0, :cent => 0, :ratio => 2.0, :total_cents => 1200 },
|
9
|
+
{ :octave => 2, :semitone => 0, :cent => 0, :ratio => 4.0, :total_cents => 2400 },
|
10
|
+
{ :octave => 1, :semitone => 6, :cent => 0, :ratio => 2.828, :total_cents => 1800 },
|
11
|
+
{ :octave => 2, :semitone => 6, :cent => 0, :ratio => 5.657, :total_cents => 3000 },
|
12
|
+
{ :octave => 3, :semitone => 7, :cent => 0, :ratio => 11.986, :total_cents => 4300 },
|
13
|
+
{ :octave => -1, :semitone => 0, :cent => 0, :ratio => 0.5, :total_cents => -1200 },
|
14
|
+
{ :octave => -2, :semitone => 0, :cent => 0, :ratio => 0.25, :total_cents => -2400 },
|
15
|
+
{ :octave => -2, :semitone => 7, :cent => 0, :ratio => 0.37458, :total_cents => -1700 },
|
16
|
+
{ :octave => -1, :semitone => 9, :cent => 0, :ratio => 0.841, :total_cents => -300 },
|
17
|
+
{ :octave => 5, :semitone => 0, :cent => 20, :ratio => 32.372, :total_cents => 6020 },
|
18
|
+
{ :octave => 3, :semitone => 3, :cent => 95, :ratio => 10.0503, :total_cents => 3995 },
|
19
|
+
{ :octave => -3, :semitone => 2, :cent => -20, :ratio => 0.1387, :total_cents => -3420 },
|
20
|
+
{ :octave => -5, :semitone => -2, :cent => -77, :ratio => 0.02663, :total_cents => -6277 }
|
17
21
|
]
|
18
22
|
end
|
19
23
|
|
@@ -22,30 +26,38 @@ describe Pitch do
|
|
22
26
|
end
|
23
27
|
|
24
28
|
it "should take keyword args" do
|
25
|
-
obj = Pitch.new octave: 4, semitone: 3
|
29
|
+
obj = Pitch.new octave: 4, semitone: 3, cent: 5
|
26
30
|
obj.octave.should eq(4)
|
27
31
|
obj.semitone.should eq(3)
|
32
|
+
obj.cent.should eq(5)
|
28
33
|
end
|
29
34
|
|
30
|
-
it "should use default octave and
|
35
|
+
it "should use default octave, semitone, and cent if none is given" do
|
31
36
|
p = Pitch.new
|
32
37
|
p.ratio.should be_within(0.01).of(1.0)
|
33
|
-
p.
|
38
|
+
p.total_cents.should eq(0)
|
34
39
|
end
|
35
40
|
|
36
41
|
it "should use the octave and semitone given during construction" do
|
37
42
|
@cases.each do |case_data|
|
38
|
-
p = Pitch.new octave: case_data[:octave], semitone: case_data[:semitone]
|
43
|
+
p = Pitch.new octave: case_data[:octave], semitone: case_data[:semitone], cent: case_data[:cent]
|
39
44
|
p.ratio.should be_within(0.01).of case_data[:ratio]
|
40
|
-
p.
|
45
|
+
p.total_cents.should be case_data[:total_cents]
|
41
46
|
end
|
42
47
|
end
|
43
48
|
|
44
49
|
describe '#diff' do
|
45
50
|
it 'should return the difference between the given pitch, in semitones' do
|
46
|
-
|
47
|
-
|
48
|
-
|
51
|
+
[
|
52
|
+
[C5,C4,12],
|
53
|
+
[C5,D5,-2],
|
54
|
+
[D5,C5,2],
|
55
|
+
[C5,Pitch.new(octave:5, cent:-5),5/100.0],
|
56
|
+
[A5,Pitch.new(octave:5, semitone: 5, cent: 22),378/100.0],
|
57
|
+
[A5,Pitch.new(octave:5, semitone: 11, cent: 85),-285/100.0],
|
58
|
+
].each do |a,b,c|
|
59
|
+
a.diff(b).should eq(c)
|
60
|
+
end
|
49
61
|
end
|
50
62
|
end
|
51
63
|
|
@@ -62,22 +74,7 @@ describe Pitch do
|
|
62
74
|
it 'should return a Pitch with given ratio' do
|
63
75
|
@cases.each do |case_data|
|
64
76
|
p = Pitch.from_ratio case_data[:ratio]
|
65
|
-
|
66
|
-
p.octave.should eq case_data[:octave]
|
67
|
-
p.semitone.should eq case_data[:semitone]
|
68
|
-
p.total_semitone.should eq case_data[:total_semitone]
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
describe '.from_semitones' do
|
74
|
-
it 'should return a Pitch with given total semitones' do
|
75
|
-
@cases.each do |case_data|
|
76
|
-
p = Pitch.from_semitones case_data[:total_semitone]
|
77
|
-
|
78
|
-
p.octave.should eq case_data[:octave]
|
79
|
-
p.semitone.should eq case_data[:semitone]
|
80
|
-
p.total_semitone.should eq case_data[:total_semitone]
|
77
|
+
p.total_cents.should eq case_data[:total_cents]
|
81
78
|
end
|
82
79
|
end
|
83
80
|
end
|
@@ -146,9 +143,19 @@ describe Pitch do
|
|
146
143
|
{ Db0 => "C#0", Eb1 => "D#1", Gb7 => "F#7",
|
147
144
|
Ab4 => "G#4", Bb1 => "A#1" }.each do |p,s|
|
148
145
|
p.to_s(true).should eq s
|
149
|
-
end
|
146
|
+
end
|
150
147
|
end
|
151
148
|
end
|
152
149
|
end
|
150
|
+
|
151
|
+
context 'non-zero cent value' do
|
152
|
+
it 'should append +n (n = cent value)' do
|
153
|
+
{ C0.transpose(0.01) => "C0+1", E1.transpose(0.15) => "E1+15",
|
154
|
+
G5.transpose(-0.55) => "Gb5+45"
|
155
|
+
}.each do |p,s|
|
156
|
+
p.to_s.should eq s
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
153
160
|
end
|
154
161
|
end
|
@@ -7,7 +7,7 @@ describe Parsing::LinkNode do
|
|
7
7
|
'=C4' => Link::Slur.new(C4),
|
8
8
|
'/Db2' => Link::Portamento.new(Db2),
|
9
9
|
'~C#2' => Link::Glissando.new(Db2),
|
10
|
-
'
|
10
|
+
'|Db2' => Link::Legato.new(Db2),
|
11
11
|
}.each do |str,tgt|
|
12
12
|
res = parser.parse(str)
|
13
13
|
context str do
|
@@ -5,19 +5,9 @@ describe Parsing::LinkParser do
|
|
5
5
|
@parser = Parsing::LinkParser.new
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
it 'should parse a "-C2"' do
|
13
|
-
@parser.parse("-C2").should_not be nil
|
14
|
-
end
|
15
|
-
|
16
|
-
it 'should parse a "~C2"' do
|
17
|
-
@parser.parse("~C2").should_not be nil
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'should parse a "/C2"' do
|
21
|
-
@parser.parse("/C2").should_not be nil
|
8
|
+
["=C2","|C2","~C2","/C2"].each do |str|
|
9
|
+
it "should parse #{str}" do
|
10
|
+
@parser.should parse(str)
|
11
|
+
end
|
22
12
|
end
|
23
13
|
end
|
@@ -60,7 +60,7 @@ describe Parsing::NoteNode do
|
|
60
60
|
|
61
61
|
context 'polyphonic note' do
|
62
62
|
{
|
63
|
-
'/2C2,D2,E2
|
63
|
+
'/2C2,D2,E2|F2' => Note.new(Rational(1,2),[C2,D2,E2],links:{E2=>Link::Legato.new(F2)}),
|
64
64
|
'4/2.D#6,G4' => Note.new(Rational(4,2),[Eb6,G4], articulation:STACCATO),
|
65
65
|
'28_Eb7,D7,G7' => Note.new(Rational(28,1),[Eb7,D7,G7],articulation:TENUTO),
|
66
66
|
'56/33B1,B2,B3,B4,B5!' => Note.new(Rational(56,33),[B1,B2,B3,B4,B5], accented: true),
|
@@ -12,17 +12,17 @@ describe Parsing::NoteParser do
|
|
12
12
|
'duration + articulation + accent' => ['1/4.!','/2%!','2/3=!'],
|
13
13
|
'single pitch' => ['/4C2','5/3Db3','/33E#8'],
|
14
14
|
'multiple pitches' => ['/4C2,C3,c5','5/3Db3,Bb2,E5','/33E#8,F1,B9'],
|
15
|
-
'with articulation' => ['/4.C2',"5/3'Db3,Bb2,E5",'/33=F3','5
|
15
|
+
'with articulation' => ['/4.C2',"5/3'Db3,Bb2,E5",'/33=F3','5|B2','/2_D3,F4'],
|
16
16
|
'with accent' => ['/4C2!','3/2Db3,Bb4!'],
|
17
|
-
'with links' => ['/2C2=','/2C2=D2','/4D4
|
17
|
+
'with links' => ['/2C2=','/2C2=D2','/4D4|E4,G4~A5'],
|
18
18
|
'with single pitch + articulation + link + accent' => [
|
19
|
-
'3/4.D2=!','5/8=F2=!','/8Db4
|
19
|
+
'3/4.D2=!','5/8=F2=!','/8Db4|Db5!','/3_G4~B4!'],
|
20
20
|
'with multiple pitches + articulation + links + accent' => [
|
21
|
-
'5/4.D2=,G4
|
21
|
+
'5/4.D2=,G4|A4,C3~D3!','5/8|F2=D4,B4/A4!'],
|
22
22
|
}
|
23
23
|
invalid_cases = {
|
24
|
-
'single pitch' => ['
|
25
|
-
'multiple pitches' => ['
|
24
|
+
'single pitch' => ['5/3Hb|3','/33E|2'],
|
25
|
+
'multiple pitches' => ['5/3Db3,Bb|1,E5','/33H8,F1,B9'],
|
26
26
|
'with articulation' => ['/4[C2',"5/3>Db3"],
|
27
27
|
'with accent' => ['/4C2['],
|
28
28
|
'with links' => ['/2C2)'],
|