music-transcription 0.9.2 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +34 -0
- data/lib/music-transcription/change.rb +2 -2
- data/lib/music-transcription/meter.rb +3 -4
- data/lib/music-transcription/note.rb +1 -2
- data/lib/music-transcription/parsing/articulation_parsing.rb +266 -0
- data/lib/music-transcription/parsing/articulation_parsing.treetop +61 -0
- data/lib/music-transcription/parsing/convenience_methods.rb +83 -0
- data/lib/music-transcription/parsing/duration_nodes.rb +23 -0
- data/lib/music-transcription/parsing/duration_parsing.rb +207 -0
- data/lib/music-transcription/parsing/duration_parsing.treetop +27 -0
- data/lib/music-transcription/parsing/link_nodes.rb +37 -0
- data/lib/music-transcription/parsing/link_parsing.rb +272 -0
- data/lib/music-transcription/parsing/link_parsing.treetop +35 -0
- data/lib/music-transcription/parsing/nonnegative_integer_parsing.rb +57 -0
- data/lib/music-transcription/parsing/nonnegative_integer_parsing.treetop +13 -0
- data/lib/music-transcription/parsing/note_nodes.rb +64 -0
- data/lib/music-transcription/parsing/note_parsing.rb +354 -0
- data/lib/music-transcription/parsing/note_parsing.treetop +47 -0
- data/lib/music-transcription/parsing/pitch_node.rb +20 -0
- data/lib/music-transcription/parsing/pitch_parsing.rb +355 -0
- data/lib/music-transcription/parsing/pitch_parsing.treetop +45 -0
- data/lib/music-transcription/parsing/positive_integer_parsing.rb +95 -0
- data/lib/music-transcription/parsing/positive_integer_parsing.treetop +19 -0
- data/lib/music-transcription/part.rb +1 -1
- data/lib/music-transcription/program.rb +1 -4
- data/lib/music-transcription/score.rb +1 -1
- data/lib/music-transcription/validatable.rb +16 -1
- data/lib/music-transcription/version.rb +1 -1
- data/lib/music-transcription.rb +14 -0
- data/music-transcription.gemspec +2 -0
- data/spec/parsing/articulation_parsing_spec.rb +23 -0
- data/spec/parsing/convenience_methods_spec.rb +89 -0
- data/spec/parsing/duration_nodes_spec.rb +83 -0
- data/spec/parsing/duration_parsing_spec.rb +70 -0
- data/spec/parsing/link_nodes_spec.rb +30 -0
- data/spec/parsing/link_parsing_spec.rb +23 -0
- data/spec/parsing/nonnegative_integer_spec.rb +11 -0
- data/spec/parsing/note_nodes_spec.rb +84 -0
- data/spec/parsing/note_parsing_spec.rb +43 -0
- data/spec/parsing/pitch_node_spec.rb +32 -0
- data/spec/parsing/pitch_parsing_spec.rb +23 -0
- data/spec/parsing/positive_integer_spec.rb +17 -0
- data/spec/spec_helper.rb +12 -0
- metadata +59 -2
@@ -0,0 +1,95 @@
|
|
1
|
+
# Autogenerated from a Treetop grammar. Edits may be lost.
|
2
|
+
|
3
|
+
|
4
|
+
module Music
|
5
|
+
module Transcription
|
6
|
+
module Parsing
|
7
|
+
|
8
|
+
module PositiveInteger
|
9
|
+
include Treetop::Runtime
|
10
|
+
|
11
|
+
def root
|
12
|
+
@root ||= :positive_integer
|
13
|
+
end
|
14
|
+
|
15
|
+
include NonnegativeInteger
|
16
|
+
|
17
|
+
module PositiveInteger0
|
18
|
+
def nonnegative_integer
|
19
|
+
elements[2]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module PositiveInteger1
|
24
|
+
def to_i
|
25
|
+
text_value.to_i
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def _nt_positive_integer
|
30
|
+
start_index = index
|
31
|
+
if node_cache[:positive_integer].has_key?(index)
|
32
|
+
cached = node_cache[:positive_integer][index]
|
33
|
+
if cached
|
34
|
+
node_cache[:positive_integer][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
35
|
+
@index = cached.interval.end
|
36
|
+
end
|
37
|
+
return cached
|
38
|
+
end
|
39
|
+
|
40
|
+
i0, s0 = index, []
|
41
|
+
s1, i1 = [], index
|
42
|
+
loop do
|
43
|
+
if has_terminal?(@regexps[gr = '\A[0]'] ||= Regexp.new(gr), :regexp, index)
|
44
|
+
r2 = true
|
45
|
+
@index += 1
|
46
|
+
else
|
47
|
+
terminal_parse_failure('[0]')
|
48
|
+
r2 = nil
|
49
|
+
end
|
50
|
+
if r2
|
51
|
+
s1 << r2
|
52
|
+
else
|
53
|
+
break
|
54
|
+
end
|
55
|
+
end
|
56
|
+
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
|
57
|
+
s0 << r1
|
58
|
+
if r1
|
59
|
+
if has_terminal?(@regexps[gr = '\A[1-9]'] ||= Regexp.new(gr), :regexp, index)
|
60
|
+
r3 = true
|
61
|
+
@index += 1
|
62
|
+
else
|
63
|
+
terminal_parse_failure('[1-9]')
|
64
|
+
r3 = nil
|
65
|
+
end
|
66
|
+
s0 << r3
|
67
|
+
if r3
|
68
|
+
r4 = _nt_nonnegative_integer
|
69
|
+
s0 << r4
|
70
|
+
end
|
71
|
+
end
|
72
|
+
if s0.last
|
73
|
+
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
|
74
|
+
r0.extend(PositiveInteger0)
|
75
|
+
r0.extend(PositiveInteger1)
|
76
|
+
else
|
77
|
+
@index = i0
|
78
|
+
r0 = nil
|
79
|
+
end
|
80
|
+
|
81
|
+
node_cache[:positive_integer][start_index] = r0
|
82
|
+
|
83
|
+
r0
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
class PositiveIntegerParser < Treetop::Runtime::CompiledParser
|
89
|
+
include PositiveInteger
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Music
|
2
|
+
module Transcription
|
3
|
+
module Parsing
|
4
|
+
|
5
|
+
grammar PositiveInteger
|
6
|
+
include NonnegativeInteger
|
7
|
+
|
8
|
+
rule positive_integer
|
9
|
+
[0]* [1-9] nonnegative_integer {
|
10
|
+
def to_i
|
11
|
+
text_value.to_i
|
12
|
+
end
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -8,11 +8,11 @@ class Part
|
|
8
8
|
|
9
9
|
attr_reader :start_dynamic, :dynamic_changes, :notes
|
10
10
|
|
11
|
+
@@check_methods = [:ensure_start_dynamic, :ensure_dynamic_change_values_range ]
|
11
12
|
def initialize start_dynamic, notes: [], dynamic_changes: {}
|
12
13
|
@notes = notes
|
13
14
|
@start_dynamic = start_dynamic
|
14
15
|
@dynamic_changes = dynamic_changes
|
15
|
-
@check_methods = [:ensure_start_dynamic, :ensure_dynamic_change_values_range ]
|
16
16
|
|
17
17
|
yield(self) if block_given?
|
18
18
|
end
|
@@ -10,11 +10,9 @@ class Program
|
|
10
10
|
|
11
11
|
attr_accessor :segments
|
12
12
|
|
13
|
-
|
14
|
-
# @param [Hash] args Hashed arguments. Required key is :segments.
|
13
|
+
@@check_methods = [:ensure_increasing_segments, :ensure_nonnegative_segments]
|
15
14
|
def initialize segments = []
|
16
15
|
@segments = segments
|
17
|
-
@check_methods = [:ensure_increasing_segments, :ensure_nonnegative_segments]
|
18
16
|
end
|
19
17
|
|
20
18
|
# @return [Float] the sum of all program segment lengths
|
@@ -22,7 +20,6 @@ class Program
|
|
22
20
|
segments.inject(0.0) { |length, segment| length + (segment.last - segment.first) }
|
23
21
|
end
|
24
22
|
|
25
|
-
# compare to another Program
|
26
23
|
def == other
|
27
24
|
return other.respond_to?(:segments) && @segments == other.segments
|
28
25
|
end
|
@@ -6,6 +6,7 @@ class Score
|
|
6
6
|
|
7
7
|
attr_reader :start_meter, :start_tempo, :parts, :program, :meter_changes, :tempo_changes
|
8
8
|
|
9
|
+
@@check_methods = [ :check_start_tempo, :check_tempo_changes, :check_meter_changes ]
|
9
10
|
def initialize start_meter, start_tempo, meter_changes: {}, tempo_changes: {}, parts: {}, program: Program.new
|
10
11
|
@start_meter = start_meter
|
11
12
|
@start_tempo = start_tempo
|
@@ -13,7 +14,6 @@ class Score
|
|
13
14
|
@tempo_changes = tempo_changes
|
14
15
|
@parts = parts
|
15
16
|
@program = program
|
16
|
-
@check_methods = [ :check_start_tempo, :check_tempo_changes, :check_meter_changes ]
|
17
17
|
|
18
18
|
yield(self) if block_given?
|
19
19
|
end
|
@@ -3,10 +3,25 @@
|
|
3
3
|
module Validatable
|
4
4
|
attr_reader :errors
|
5
5
|
|
6
|
+
def check_methods
|
7
|
+
if instance_variable_defined?(:@check_methods)
|
8
|
+
methods = instance_variable_get(:@check_methods)
|
9
|
+
else
|
10
|
+
methods = []
|
11
|
+
end
|
12
|
+
|
13
|
+
if self.class.class_variable_defined?(:@@check_methods)
|
14
|
+
methods += self.class.class_variable_get(:@@check_methods)
|
15
|
+
end
|
16
|
+
|
17
|
+
return methods
|
18
|
+
end
|
19
|
+
|
6
20
|
def validate
|
7
21
|
@errors = []
|
8
22
|
|
9
|
-
|
23
|
+
|
24
|
+
check_methods.each do |check_method|
|
10
25
|
begin
|
11
26
|
send(check_method)
|
12
27
|
rescue StandardError => e
|
data/lib/music-transcription.rb
CHANGED
@@ -17,3 +17,17 @@ require 'music-transcription/tempo'
|
|
17
17
|
require 'music-transcription/meter'
|
18
18
|
require 'music-transcription/meters'
|
19
19
|
require 'music-transcription/score'
|
20
|
+
|
21
|
+
require 'treetop'
|
22
|
+
require 'music-transcription/parsing/nonnegative_integer_parsing'
|
23
|
+
require 'music-transcription/parsing/positive_integer_parsing'
|
24
|
+
require 'music-transcription/parsing/pitch_parsing'
|
25
|
+
require 'music-transcription/parsing/pitch_node'
|
26
|
+
require 'music-transcription/parsing/duration_parsing'
|
27
|
+
require 'music-transcription/parsing/duration_nodes'
|
28
|
+
require 'music-transcription/parsing/articulation_parsing'
|
29
|
+
require 'music-transcription/parsing/link_parsing'
|
30
|
+
require 'music-transcription/parsing/link_nodes'
|
31
|
+
require 'music-transcription/parsing/note_parsing'
|
32
|
+
require 'music-transcription/parsing/note_nodes'
|
33
|
+
require 'music-transcription/parsing/convenience_methods'
|
data/music-transcription.gemspec
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Parsing::ArticulationParser do
|
4
|
+
parser = Parsing::ArticulationParser.new
|
5
|
+
|
6
|
+
{
|
7
|
+
'=' => SLUR,
|
8
|
+
'-' => LEGATO,
|
9
|
+
'_' => TENUTO,
|
10
|
+
'%' => PORTATO,
|
11
|
+
'.' => STACCATO,
|
12
|
+
"'" => STACCATISSIMO
|
13
|
+
}.each do |str,art|
|
14
|
+
res = parser.parse(str)
|
15
|
+
it "should parse '#{str}'" do
|
16
|
+
res.should_not be nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should return a node to responds to :to_articulation correctly' do
|
20
|
+
res.to_articulation.should eq art
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
dur_stuff = ['should parse as single duration',
|
4
|
+
{
|
5
|
+
'/2' => "1/2".to_r,
|
6
|
+
'5/29' => "5/29".to_r,
|
7
|
+
'200/' => "200/1".to_r,
|
8
|
+
'66' => "66/1".to_r
|
9
|
+
}]
|
10
|
+
|
11
|
+
durs_stuff = ['should parse as whitespace-separated durations',
|
12
|
+
{
|
13
|
+
'/2 5/29 200/ 66' => ["1/2".to_r,"5/29".to_r,"200/1".to_r,"66/1".to_r],
|
14
|
+
"/2\t5/29\n200/ \t\n 66" => ["1/2".to_r,"5/29".to_r,"200/1".to_r,"66/1".to_r],
|
15
|
+
}]
|
16
|
+
|
17
|
+
pitch_stuff = ['should parse as single pitch',
|
18
|
+
{
|
19
|
+
'C2' => C2,
|
20
|
+
'Db4' => Db4,
|
21
|
+
'A#9' => Bb9
|
22
|
+
}]
|
23
|
+
|
24
|
+
pitches_stuff = ['should parse as whitespace-separated pitches',
|
25
|
+
{
|
26
|
+
'C2 C2 D2 C2' => [C2,C2,D2,C2],
|
27
|
+
"Bb2\tF5 \n Gb7" => [Bb2,F5,Gb7],
|
28
|
+
}]
|
29
|
+
|
30
|
+
note_stuff = ['should parse as a single note',
|
31
|
+
{
|
32
|
+
'/2' => Note::half,
|
33
|
+
'99/10C2' => Note.new('99/10'.to_r, [C2]),
|
34
|
+
'5/2.Db4,Eb5' => Note.new('5/2'.to_r, [Db4,Eb5], articulation:STACCATO)
|
35
|
+
}]
|
36
|
+
|
37
|
+
notes_stuff = ['should parse as whitespace-separated notes',
|
38
|
+
{
|
39
|
+
'/2 /2 /4' => [Note::half,Note::half,Note::quarter],
|
40
|
+
"/4C4 \t /4D4" => [Note::quarter([C4]),Note::quarter([D4])],
|
41
|
+
"/2Db2\t/2C2 \n /2C2" => [Note::half([Db2]), Note::half([C2]), Note::half([C2])]
|
42
|
+
}]
|
43
|
+
|
44
|
+
{
|
45
|
+
:duration => dur_stuff,
|
46
|
+
:dur => dur_stuff,
|
47
|
+
:durations => durs_stuff,
|
48
|
+
:durs => durs_stuff,
|
49
|
+
:pitch => pitch_stuff,
|
50
|
+
:pitches => pitches_stuff,
|
51
|
+
:note => note_stuff,
|
52
|
+
:notes => notes_stuff
|
53
|
+
}.each do |mod_fun,descr_cases|
|
54
|
+
describe("Parsing::" + mod_fun.to_s) do
|
55
|
+
descr, cases = descr_cases
|
56
|
+
it descr do
|
57
|
+
cases.each do |s,tgt|
|
58
|
+
Parsing.send(mod_fun,s).should eq tgt
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
{
|
65
|
+
:to_d => dur_stuff,
|
66
|
+
:to_dur => dur_stuff,
|
67
|
+
:to_duration => dur_stuff,
|
68
|
+
:to_ds => durs_stuff,
|
69
|
+
:to_durs => durs_stuff,
|
70
|
+
:to_durations => durs_stuff,
|
71
|
+
:to_p => pitch_stuff,
|
72
|
+
:to_pitch => pitch_stuff,
|
73
|
+
:to_ps=> pitches_stuff,
|
74
|
+
:to_pitches => pitches_stuff,
|
75
|
+
:to_n => note_stuff,
|
76
|
+
:to_note => note_stuff,
|
77
|
+
:to_ns => notes_stuff,
|
78
|
+
:to_notes => notes_stuff
|
79
|
+
}.each do |inst_meth,descr_cases|
|
80
|
+
describe("String#" + inst_meth.to_s) do
|
81
|
+
descr, cases = descr_cases
|
82
|
+
it descr do
|
83
|
+
cases.each do |s,tgt|
|
84
|
+
s.send(inst_meth).should eq tgt
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Parsing::NumDenNode do
|
4
|
+
dur_parser = Parsing::DurationParser.new
|
5
|
+
|
6
|
+
{
|
7
|
+
'1/2' => Rational(1,2),
|
8
|
+
'5/100' => Rational(5,100),
|
9
|
+
'007/777' => Rational(7,777)
|
10
|
+
}.each do |str,tgt|
|
11
|
+
res = dur_parser.parse(str)
|
12
|
+
context str do
|
13
|
+
it 'should parse as NumDenNode' do
|
14
|
+
res.should be_a Parsing::NumDenNode
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#to_r' do
|
18
|
+
r = res.to_r
|
19
|
+
it 'should produce a Rational' do
|
20
|
+
r.should be_a Rational
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should produce value matching input str' do
|
24
|
+
r.should eq tgt
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe Parsing::NumOnlyNode do
|
32
|
+
dur_parser = Parsing::DurationParser.new
|
33
|
+
{
|
34
|
+
'1/' => Rational(1,1),
|
35
|
+
'5' => Rational(5,1),
|
36
|
+
'007/' => Rational(7,1)
|
37
|
+
}.each do |str,tgt|
|
38
|
+
res = dur_parser.parse(str)
|
39
|
+
context str do
|
40
|
+
it 'should parse as NumOnlyNode' do
|
41
|
+
res.should be_a Parsing::NumOnlyNode
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#to_r' do
|
45
|
+
r = res.to_r
|
46
|
+
it 'should produce a Rational' do
|
47
|
+
r.should be_a Rational
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should produce value matching input str' do
|
51
|
+
r.should eq tgt
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe Parsing::DenOnlyNode do
|
59
|
+
dur_parser = Parsing::DurationParser.new
|
60
|
+
{
|
61
|
+
'/2' => Rational(1,2),
|
62
|
+
'/100' => Rational(1,100),
|
63
|
+
'/777' => Rational(1,777)
|
64
|
+
}.each do |str,tgt|
|
65
|
+
res = dur_parser.parse(str)
|
66
|
+
context str do
|
67
|
+
it 'should parse as DenOnlyNode' do
|
68
|
+
res.should be_a Parsing::DenOnlyNode
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#to_r' do
|
72
|
+
r = res.to_r
|
73
|
+
it 'should produce a Rational' do
|
74
|
+
r.should be_a Rational
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should produce value matching input str' do
|
78
|
+
r.should eq tgt
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Parsing::DurationParser do
|
4
|
+
dur_parser = Parsing::DurationParser.new
|
5
|
+
before :all do
|
6
|
+
@valid = {
|
7
|
+
:numbers => [1,5,50,3999,01,0010,0000005050],
|
8
|
+
}
|
9
|
+
|
10
|
+
@invalid = {
|
11
|
+
:numbers => [0,00],
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'valid (non-zero) numerator and denominator' do
|
16
|
+
["n","n/","n/d","/d"].each do |expr|
|
17
|
+
it "should parse durations of the form #{expr}" do
|
18
|
+
@valid[:numbers].each do |n|
|
19
|
+
@valid[:numbers].each do |d|
|
20
|
+
str = expr.gsub('n',"#{n}")
|
21
|
+
str = str.gsub('d',"#{d}")
|
22
|
+
dur_parser.parse(str).should_not be nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'invalid (zero) numerator and valid denominator' do
|
30
|
+
["n","n/","n/d"].each do |expr|
|
31
|
+
it "should parse durations of the form #{expr}" do
|
32
|
+
@invalid[:numbers].each do |n|
|
33
|
+
@valid[:numbers].each do |d|
|
34
|
+
str = expr.gsub('n',"#{n}")
|
35
|
+
str = str.gsub('d',"#{d}")
|
36
|
+
dur_parser.parse(str).should be nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'valid numerator and invalid (zero) denominator' do
|
44
|
+
["n/d","/d"].each do |expr|
|
45
|
+
it "should parse durations of the form #{expr}" do
|
46
|
+
@valid[:numbers].each do |n|
|
47
|
+
@invalid[:numbers].each do |d|
|
48
|
+
str = expr.gsub('n',"#{n}")
|
49
|
+
str = str.gsub('d',"#{d}")
|
50
|
+
dur_parser.parse(str).should be nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'invalid numerator and invalid denominator' do
|
58
|
+
["n","n/","n/d","/d"].each do |expr|
|
59
|
+
it "should parse durations of the form #{expr}" do
|
60
|
+
@invalid[:numbers].each do |n|
|
61
|
+
@invalid[:numbers].each do |d|
|
62
|
+
str = expr.gsub('n',"#{n}")
|
63
|
+
str = str.gsub('d',"#{d}")
|
64
|
+
dur_parser.parse(str).should be nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|