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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +34 -0
  3. data/lib/music-transcription/change.rb +2 -2
  4. data/lib/music-transcription/meter.rb +3 -4
  5. data/lib/music-transcription/note.rb +1 -2
  6. data/lib/music-transcription/parsing/articulation_parsing.rb +266 -0
  7. data/lib/music-transcription/parsing/articulation_parsing.treetop +61 -0
  8. data/lib/music-transcription/parsing/convenience_methods.rb +83 -0
  9. data/lib/music-transcription/parsing/duration_nodes.rb +23 -0
  10. data/lib/music-transcription/parsing/duration_parsing.rb +207 -0
  11. data/lib/music-transcription/parsing/duration_parsing.treetop +27 -0
  12. data/lib/music-transcription/parsing/link_nodes.rb +37 -0
  13. data/lib/music-transcription/parsing/link_parsing.rb +272 -0
  14. data/lib/music-transcription/parsing/link_parsing.treetop +35 -0
  15. data/lib/music-transcription/parsing/nonnegative_integer_parsing.rb +57 -0
  16. data/lib/music-transcription/parsing/nonnegative_integer_parsing.treetop +13 -0
  17. data/lib/music-transcription/parsing/note_nodes.rb +64 -0
  18. data/lib/music-transcription/parsing/note_parsing.rb +354 -0
  19. data/lib/music-transcription/parsing/note_parsing.treetop +47 -0
  20. data/lib/music-transcription/parsing/pitch_node.rb +20 -0
  21. data/lib/music-transcription/parsing/pitch_parsing.rb +355 -0
  22. data/lib/music-transcription/parsing/pitch_parsing.treetop +45 -0
  23. data/lib/music-transcription/parsing/positive_integer_parsing.rb +95 -0
  24. data/lib/music-transcription/parsing/positive_integer_parsing.treetop +19 -0
  25. data/lib/music-transcription/part.rb +1 -1
  26. data/lib/music-transcription/program.rb +1 -4
  27. data/lib/music-transcription/score.rb +1 -1
  28. data/lib/music-transcription/validatable.rb +16 -1
  29. data/lib/music-transcription/version.rb +1 -1
  30. data/lib/music-transcription.rb +14 -0
  31. data/music-transcription.gemspec +2 -0
  32. data/spec/parsing/articulation_parsing_spec.rb +23 -0
  33. data/spec/parsing/convenience_methods_spec.rb +89 -0
  34. data/spec/parsing/duration_nodes_spec.rb +83 -0
  35. data/spec/parsing/duration_parsing_spec.rb +70 -0
  36. data/spec/parsing/link_nodes_spec.rb +30 -0
  37. data/spec/parsing/link_parsing_spec.rb +23 -0
  38. data/spec/parsing/nonnegative_integer_spec.rb +11 -0
  39. data/spec/parsing/note_nodes_spec.rb +84 -0
  40. data/spec/parsing/note_parsing_spec.rb +43 -0
  41. data/spec/parsing/pitch_node_spec.rb +32 -0
  42. data/spec/parsing/pitch_parsing_spec.rb +23 -0
  43. data/spec/parsing/positive_integer_spec.rb +17 -0
  44. data/spec/spec_helper.rb +12 -0
  45. 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
- # A new instance of Program.
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
- @check_methods.each do |check_method|
23
+
24
+ check_methods.each do |check_method|
10
25
  begin
11
26
  send(check_method)
12
27
  rescue StandardError => e
@@ -2,6 +2,6 @@
2
2
  module Music
3
3
  module Transcription
4
4
  # music-transcription version
5
- VERSION = "0.9.2"
5
+ VERSION = "0.10.0"
6
6
  end
7
7
  end
@@ -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'
@@ -29,4 +29,6 @@ DESCRIPTION
29
29
  gem.add_development_dependency 'pry'
30
30
  gem.add_development_dependency 'pry-nav'
31
31
  gem.add_development_dependency 'pry-stack_explorer'
32
+
33
+ gem.add_dependency 'treetop'
32
34
  end
@@ -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