music-transcription 0.17.1 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/examples/hip.rb +4 -4
  3. data/examples/missed_connection.rb +3 -3
  4. data/examples/song1.rb +6 -6
  5. data/examples/song2.rb +3 -3
  6. data/lib/music-transcription.rb +8 -2
  7. data/lib/music-transcription/model/change.rb +9 -3
  8. data/lib/music-transcription/model/measure_score.rb +62 -0
  9. data/lib/music-transcription/model/meter.rb +5 -1
  10. data/lib/music-transcription/model/note.rb +4 -1
  11. data/lib/music-transcription/model/note_score.rb +60 -0
  12. data/lib/music-transcription/model/part.rb +4 -1
  13. data/lib/music-transcription/model/program.rb +5 -2
  14. data/lib/music-transcription/model/tempo.rb +13 -23
  15. data/lib/music-transcription/packing/measure_score_packing.rb +34 -0
  16. data/lib/music-transcription/packing/{score_packing.rb → note_score_packing.rb} +12 -21
  17. data/lib/music-transcription/packing/part_packing.rb +1 -1
  18. data/lib/music-transcription/packing/program_packing.rb +1 -1
  19. data/lib/music-transcription/parsing/convenience_methods.rb +37 -58
  20. data/lib/music-transcription/parsing/numbers/nonnegative_float_parsing.rb +172 -59
  21. data/lib/music-transcription/parsing/numbers/nonnegative_float_parsing.treetop +13 -1
  22. data/lib/music-transcription/parsing/numbers/positive_float_parsing.rb +505 -0
  23. data/lib/music-transcription/parsing/numbers/positive_float_parsing.treetop +35 -0
  24. data/lib/music-transcription/parsing/numbers/positive_integer_parsing.rb +2 -0
  25. data/lib/music-transcription/parsing/numbers/positive_integer_parsing.treetop +2 -0
  26. data/lib/music-transcription/parsing/numbers/positive_rational_parsing.rb +86 -0
  27. data/lib/music-transcription/parsing/numbers/positive_rational_parsing.treetop +21 -0
  28. data/lib/music-transcription/parsing/parseable.rb +32 -0
  29. data/lib/music-transcription/parsing/tempo_parsing.rb +396 -0
  30. data/lib/music-transcription/parsing/tempo_parsing.treetop +49 -0
  31. data/lib/music-transcription/validatable.rb +5 -18
  32. data/lib/music-transcription/version.rb +1 -1
  33. data/spec/model/measure_score_spec.rb +85 -0
  34. data/spec/model/note_score_spec.rb +68 -0
  35. data/spec/model/tempo_spec.rb +15 -15
  36. data/spec/packing/{score_packing_spec.rb → measure_score_packing_spec.rb} +13 -7
  37. data/spec/packing/note_score_packing_spec.rb +100 -0
  38. data/spec/packing/part_packing_spec.rb +1 -1
  39. data/spec/parsing/convenience_methods_spec.rb +80 -81
  40. data/spec/parsing/numbers/nonnegative_float_spec.rb +19 -2
  41. data/spec/parsing/numbers/positive_float_spec.rb +28 -0
  42. data/spec/parsing/numbers/positive_integer_spec.rb +13 -2
  43. data/spec/parsing/numbers/positive_rational_spec.rb +28 -0
  44. data/spec/parsing/tempo_parsing_spec.rb +21 -0
  45. metadata +27 -8
  46. data/lib/music-transcription/model/score.rb +0 -68
  47. data/spec/model/score_spec.rb +0 -69
@@ -0,0 +1,49 @@
1
+ module Music
2
+ module Transcription
3
+ module Parsing
4
+
5
+ grammar Tempo
6
+ include PositiveInteger
7
+ include PositiveFloat
8
+ include PositiveRational
9
+
10
+ rule tempo
11
+ tempo_bpm / tempo_qnpm / tempo_npm / tempo_nps
12
+ end
13
+
14
+ rule tempo_bpm
15
+ val:positive_number ("bpm" / "BPM") {
16
+ def to_tempo
17
+ Music::Transcription::Tempo::BPM.new(val.to_num)
18
+ end }
19
+ end
20
+
21
+ rule tempo_qnpm
22
+ val:positive_number ("qnpm" / "QNPM") {
23
+ def to_tempo
24
+ Music::Transcription::Tempo::QNPM.new(val.to_num)
25
+ end }
26
+ end
27
+
28
+ rule tempo_npm
29
+ val:positive_number ("npm" / "NPM") {
30
+ def to_tempo
31
+ Music::Transcription::Tempo::NPM.new(val.to_num)
32
+ end }
33
+ end
34
+
35
+ rule tempo_nps
36
+ val:positive_number ("nps" / "NPS") {
37
+ def to_tempo
38
+ Music::Transcription::Tempo::NPS.new(val.to_num)
39
+ end }
40
+ end
41
+
42
+ rule positive_number
43
+ positive_float / positive_rational / positive_integer
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -3,19 +3,8 @@
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
6
+ def check_methods; []; end
7
+ def validatables; []; end
19
8
 
20
9
  def validate
21
10
  @errors = []
@@ -30,16 +19,14 @@ module Validatable
30
19
  end
31
20
 
32
21
  validatables.each do |v|
33
- @errors += v.validate
22
+ if v.respond_to?(:validate)
23
+ @errors += v.validate
24
+ end
34
25
  end
35
26
 
36
27
  return @errors
37
28
  end
38
29
 
39
- def validatables
40
- []
41
- end
42
-
43
30
  def valid?
44
31
  self.validate
45
32
  @errors.empty?
@@ -2,6 +2,6 @@
2
2
  module Music
3
3
  module Transcription
4
4
  # music-transcription version
5
- VERSION = "0.17.1"
5
+ VERSION = "0.19.0"
6
6
  end
7
7
  end
@@ -0,0 +1,85 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe MeasureScore do
4
+ describe '#initialize' do
5
+ it 'should use empty containers for parameters not given' do
6
+ s = MeasureScore.new(FOUR_FOUR,120)
7
+ s.parts.should be_empty
8
+ s.program.segments.should be_empty
9
+ end
10
+
11
+ it 'should assign given parameters' do
12
+ m = FOUR_FOUR
13
+ s = MeasureScore.new(m,120)
14
+ s.start_meter.should eq m
15
+ s.start_tempo.should eq 120
16
+
17
+ parts = { "piano (LH)" => Samples::SAMPLE_PART }
18
+ program = Program.new [0...0.75, 0...0.75]
19
+ mcs = { 1 => Change::Immediate.new(THREE_FOUR) }
20
+ tcs = { 1 => Change::Immediate.new(100) }
21
+
22
+ s = MeasureScore.new(m,120,
23
+ parts: parts,
24
+ program: program,
25
+ meter_changes: mcs,
26
+ tempo_changes: tcs
27
+ )
28
+ s.parts.should eq parts
29
+ s.program.should eq program
30
+ s.meter_changes.should eq mcs
31
+ s.tempo_changes.should eq tcs
32
+ end
33
+ end
34
+
35
+ describe '#valid?' do
36
+ {
37
+ 'QNPM start tempo' => [ FOUR_FOUR, Tempo::QNPM.new(40) ],
38
+ 'NPM start tempo' => [ FOUR_FOUR, Tempo::NPM.new(40) ],
39
+ 'NPS start tempo' => [ FOUR_FOUR, Tempo::NPS.new(40) ],
40
+ 'BPM start tempo' => [ FOUR_FOUR, Tempo::BPM.new(40) ],
41
+ 'QNPM tempo changes' => [ FOUR_FOUR, Tempo::BPM.new(30),
42
+ :tempo_changes => { 1 => Change::Gradual.new(Tempo::QNPM.new(40), 2), 2 => Change::Immediate.new(Tempo::QNPM.new(50)) } ],
43
+ 'NPM tempo changes' => [ FOUR_FOUR, Tempo::BPM.new(30),
44
+ :tempo_changes => { 1 => Change::Gradual.new(Tempo::NPM.new(40), 2), 2 => Change::Immediate.new(Tempo::QNPM.new(50)) } ],
45
+ 'NPS tempo changes' => [ FOUR_FOUR, Tempo::BPM.new(30),
46
+ :tempo_changes => { 1 => Change::Gradual.new(Tempo::NPS.new(40), 2), 2 => Change::Immediate.new(Tempo::QNPM.new(50)) } ],
47
+ 'BPM tempo changes' => [ FOUR_FOUR, Tempo::BPM.new(30),
48
+ :tempo_changes => { 1 => Change::Gradual.new(Tempo::BPM.new(40), 2), 2 => Change::Immediate.new(Tempo::QNPM.new(50)) } ],
49
+ 'valid meter changes' => [ FOUR_FOUR, Tempo::BPM.new(120),
50
+ :meter_changes => { 1 => Change::Immediate.new(TWO_FOUR) } ],
51
+ 'valid tempo changes' => [ FOUR_FOUR, Tempo::BPM.new(120),
52
+ :tempo_changes => { 1 => Change::Gradual.new(Tempo::BPM.new(200), 2), 2 => Change::Immediate.new(Tempo::BPM.new(300)) } ],
53
+ 'valid part' => [ FOUR_FOUR, Tempo::BPM.new(120), :parts => { "piano" => Samples::SAMPLE_PART }],
54
+ 'valid program' => [ FOUR_FOUR, Tempo::BPM.new(120), :program => Program.new([0..2,0..2]) ]
55
+ }.each do |context_str,args|
56
+ context context_str do
57
+ it 'should return true' do
58
+ MeasureScore.new(*args).should be_valid
59
+ end
60
+ end
61
+ end
62
+
63
+ {
64
+ 'start tempo object is not a Tempo object' => [ FOUR_FOUR, 120],
65
+ 'invalid start meter' => [ Meter.new(-1,"1/4".to_r), Tempo::BPM.new(120)],
66
+ 'non-meter start meter' => [ 1, Tempo::BPM.new(120)],
67
+ 'invalid meter in change' => [ FOUR_FOUR, Tempo::BPM.new(120),
68
+ :meter_changes => { 1 => Change::Immediate.new(Meter.new(-2,"1/4".to_r)) } ],
69
+ 'non-meter values in meter changes' => [ FOUR_FOUR, Tempo::BPM.new(120),
70
+ :meter_changes => { 1 => Change::Immediate.new(5) } ],
71
+ 'non-immediate meter change' => [ FOUR_FOUR, Tempo::BPM.new(120),
72
+ :meter_changes => { 1 => Change::Gradual.new(TWO_FOUR,1) } ],
73
+ 'tempo change value is not a Tempo object' => [ FOUR_FOUR, Tempo::QNPM.new(120),
74
+ :tempo_changes => { 1 => Change::Gradual.new(140,1) } ],
75
+ 'invalid part' => [ FOUR_FOUR, 120, :parts => { "piano" => Part.new(-0.1) }],
76
+ 'invalid program' => [ FOUR_FOUR, 120, :program => Program.new([2..0]) ],
77
+ }.each do |context_str,args|
78
+ context context_str do
79
+ it 'should return false' do
80
+ MeasureScore.new(*args).should be_invalid
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,68 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe NoteScore do
4
+ describe '#initialize' do
5
+ it 'should use empty containers for parameters not given' do
6
+ s = NoteScore.new(Tempo::QNPM.new(30))
7
+ s.parts.should be_empty
8
+ s.program.segments.should be_empty
9
+ end
10
+
11
+ it 'should assign given parameters' do
12
+ s = NoteScore.new(Tempo::QNPM.new(30))
13
+ s.start_tempo.should eq Tempo::QNPM.new(30)
14
+
15
+ parts = { "piano (LH)" => Samples::SAMPLE_PART }
16
+ program = Program.new [0...0.75, 0...0.75]
17
+ tcs = { 1 => Change::Immediate.new(Tempo::QNPM.new(40)) }
18
+
19
+ s = NoteScore.new(Tempo::QNPM.new(30),
20
+ parts: parts,
21
+ program: program,
22
+ tempo_changes: tcs
23
+ )
24
+ s.parts.should eq parts
25
+ s.program.should eq program
26
+ s.tempo_changes.should eq tcs
27
+ end
28
+ end
29
+
30
+ describe '#valid?' do
31
+ {
32
+ 'QNPM start tempo' => [ Tempo::QNPM.new(40) ],
33
+ 'NPM start tempo' => [ Tempo::NPM.new(40) ],
34
+ 'NPS start tempo' => [ Tempo::NPS.new(40) ],
35
+ 'QNPM tempo changes' => [ Tempo::QNPM.new(30),
36
+ :tempo_changes => { 1 => Change::Gradual.new(Tempo::QNPM.new(40), 2), 2 => Change::Immediate.new(Tempo::QNPM.new(50)) } ],
37
+ 'NPM tempo changes' => [ Tempo::NPM.new(30),
38
+ :tempo_changes => { 1 => Change::Gradual.new(Tempo::NPM.new(40), 2), 2 => Change::Immediate.new(Tempo::QNPM.new(50)) } ],
39
+ 'NPS tempo changes' => [ Tempo::NPS.new(30),
40
+ :tempo_changes => { 1 => Change::Gradual.new(Tempo::NPS.new(40), 2), 2 => Change::Immediate.new(Tempo::QNPM.new(50)) } ],
41
+ 'valid part' => [ Tempo::QNPM.new(30), :parts => { "piano" => Samples::SAMPLE_PART }],
42
+ 'valid program' => [ Tempo::QNPM.new(30), :program => Program.new([0..2,0..2]) ]
43
+ }.each do |context_str,args|
44
+ context context_str do
45
+ it 'should return true' do
46
+ NoteScore.new(*args).should be_valid
47
+ end
48
+ end
49
+ end
50
+
51
+ {
52
+ 'start tempo object is not a Tempo object' => [ 30],
53
+ 'start tempo object is not a valid Tempo type' => [ Tempo::BPM.new(120)],
54
+ 'tempo change value is not a Tempo object' => [ Tempo::QNPM.new(30),
55
+ :tempo_changes => { 1 => Change::Gradual.new(30,1) } ],
56
+ 'tempo change value is not a valid Tempo type' => [ Tempo::QNPM.new(30),
57
+ :tempo_changes => { 1 => Change::Gradual.new(Tempo::BPM.new(120),1) } ],
58
+ 'invalid part' => [ Tempo::QNPM.new(30), :parts => { "piano" => Part.new(-0.1) }],
59
+ 'invalid program' => [ Tempo::QNPM.new(30), :program => Program.new([2..0]) ],
60
+ }.each do |context_str,args|
61
+ context context_str do
62
+ it 'should return false' do
63
+ NoteScore.new(*args).should be_invalid
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,23 +1,23 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
3
  describe Tempo do
4
- describe '#==' do
5
- context 'same bpm and beat duration' do
6
- it 'should return true' do
7
- [
8
- [120,nil],
9
- [120,0.25.to_r],
10
- [300,"3/8".to_r]
11
- ].each do |bpm,bd|
12
- Tempo.new(bpm,bd).should eq(Tempo.new(bpm,bd))
13
- end
14
- end
4
+ describe '#initialize' do
5
+ it 'should assign given value' do
6
+ Tempo.new(3).value.should eq(3)
15
7
  end
16
8
 
17
- context 'different bpm or beat duration' do
18
- it 'should return false' do
19
- Tempo.new(120,nil).should_not eq(Tempo.new(120,"1/4".to_r))
20
- Tempo.new(200,2).should_not eq(Tempo.new(200,2.1))
9
+ context 'given negative value' do
10
+ it 'should raise NonPositiveError' do
11
+ expect { Tempo.new(-3) }.to raise_error(NonPositiveError)
12
+ end
13
+ end
14
+ end
15
+
16
+ [ :qnpm, :bpm, :npm, :nps ].each do |sym|
17
+ describe "Tempo::#{sym}" do
18
+ it "should print tempo value + '#{sym}'" do
19
+ klass = Tempo.const_get(sym.upcase)
20
+ klass.new(20).to_s.should eq("20#{sym}")
21
21
  end
22
22
  end
23
23
  end
@@ -1,8 +1,8 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
- describe Score do
3
+ describe MeasureScore do
4
4
  before :all do
5
- @score = Score.new(FOUR_FOUR,120) do |s|
5
+ @score = MeasureScore.new(FOUR_FOUR,Tempo::BPM.new(120)) do |s|
6
6
  s.program = Program.new([0...2, 0...2,2...4,0...2])
7
7
  s.parts["lead"] = Part.new(Dynamics::MF) do |p|
8
8
  riff = "/6Bb3 /4 /12Db4= /6Db4= /36Db4 /36Eb4 /36Db4 /6Ab3 /12Db4 \
@@ -52,8 +52,8 @@ describe Score do
52
52
  end
53
53
  end
54
54
 
55
- it 'should pack start tempo as plain numeric value' do
56
- @h['start_tempo'].should be_a Numeric
55
+ it 'should pack start tempo as string' do
56
+ @h['start_tempo'].should be_a String
57
57
  end
58
58
 
59
59
  it 'should pack tempo changes as whatver type Change#pack returns' do
@@ -64,6 +64,12 @@ describe Score do
64
64
  end
65
65
  end
66
66
 
67
+ it 'should pack tempo change values as strings' do
68
+ @h['tempo_changes'].each do |offset,packed_v|
69
+ packed_v[0].should be_a String
70
+ end
71
+ end
72
+
67
73
  it 'should pack program as whatever type Program#pack returns' do
68
74
  t = @score.program.pack.class
69
75
  @h['program'].should be_a t
@@ -87,11 +93,11 @@ describe Score do
87
93
 
88
94
  describe '.unpack' do
89
95
  before :all do
90
- @score2 = Score.unpack @h
96
+ @score2 = MeasureScore.unpack @h
91
97
  end
92
98
 
93
- it 'should return a Score' do
94
- @score2.should be_a Score
99
+ it 'should return a MeasureScore' do
100
+ @score2.should be_a MeasureScore
95
101
  end
96
102
 
97
103
  it 'should successfuly unpack the parts' do
@@ -0,0 +1,100 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe NoteScore do
4
+ before :all do
5
+ @score = NoteScore.new(Tempo::QNPM.new(30)) do |s|
6
+ s.program = Program.new([0...2, 0...2,2...4,0...2])
7
+ s.parts["lead"] = Part.new(Dynamics::MF) do |p|
8
+ riff = "/6Bb3 /4 /12Db4= /6Db4= /36Db4 /36Eb4 /36Db4 /6Ab3 /12Db4 \
9
+ /6Bb3 /4 /12Db4= /4Db4= /8=Db4 /8C4".to_notes
10
+ p.notes = riff + riff.map {|n| n.transpose(2) }
11
+ end
12
+
13
+ s.parts["bass"] = Part.new(Dynamics::MP) do |p|
14
+ riff = "/6Bb2 /4 /3Ab2 /6F2 /12Ab2 \
15
+ /6Bb2 /4 /3Ab2 /4Ab2".to_notes
16
+ p.notes = riff + riff.map {|n| n.transpose(2) }
17
+ end
18
+ end
19
+
20
+ @h = @score.pack
21
+ end
22
+
23
+ describe '#pack' do
24
+ it 'should return a hash' do
25
+ @h.should be_a Hash
26
+ end
27
+
28
+ it 'should return a hash with keys: "parts", "program", ...' do
29
+ @h.keys.should include("parts")
30
+ @h.keys.should include("start_tempo")
31
+ @h.keys.should include("tempo_changes")
32
+ @h.keys.should include("program")
33
+ end
34
+
35
+ it 'should pack start tempo as string' do
36
+ @h['start_tempo'].should be_a String
37
+ end
38
+
39
+ it 'should pack tempo changes as whatver type Change#pack returns' do
40
+ @h['tempo_changes'].each do |offset,packed_v|
41
+ change_v = @score.tempo_changes[offset]
42
+ t = change_v.pack.class
43
+ packed_v.should be_a t
44
+ end
45
+ end
46
+
47
+ it 'should pack tempo change values as strings' do
48
+ @h['tempo_changes'].each do |offset,packed_v|
49
+ packed_v[0].should be_a String
50
+ end
51
+ end
52
+
53
+ it 'should pack program as whatever type Program#pack returns' do
54
+ t = @score.program.pack.class
55
+ @h['program'].should be_a t
56
+ end
57
+
58
+ it 'should pack program segments as strings' do
59
+ @h['program'].each {|x| x.should be_a String }
60
+ end
61
+
62
+ it 'should pack parts as hash' do
63
+ @h['parts'].should be_a Hash
64
+ end
65
+
66
+ it 'should pack parts as whatever Part#pack returns' do
67
+ @score.parts.each do |name,part|
68
+ packing = part.pack
69
+ @h['parts'][name].should eq packing
70
+ end
71
+ end
72
+ end
73
+
74
+ describe '.unpack' do
75
+ before :all do
76
+ @score2 = NoteScore.unpack @h
77
+ end
78
+
79
+ it 'should return a NoteScore' do
80
+ @score2.should be_a NoteScore
81
+ end
82
+
83
+ it 'should successfuly unpack the parts' do
84
+ @score2.parts.should eq @score.parts
85
+ end
86
+
87
+ it 'should successfuly unpack the start tempo' do
88
+ @score2.start_tempo.should eq @score.start_tempo
89
+ end
90
+
91
+ it 'should successfuly unpack the tempo changes' do
92
+ @score2.tempo_changes.should eq @score.tempo_changes
93
+ end
94
+
95
+ it 'should successfuly unpack the program' do
96
+ @score2.program.should eq @score.program
97
+ end
98
+ end
99
+ end
100
+
@@ -4,7 +4,7 @@ describe Part do
4
4
  before :all do
5
5
  @p = Part.new(
6
6
  Dynamics::MP,
7
- notes: Parsing::notes("/4Bb2 /8 /8F3= /2F3 /4Bb2 /8 /8F3= /2F3"),
7
+ notes: Note.split_parse("/4Bb2 /8 /8F3= /2F3 /4Bb2 /8 /8F3= /2F3"),
8
8
  dynamic_changes: {
9
9
  1 => Change::Immediate.new(Dynamics::PP),
10
10
  2 => Change::Gradual.new(Dynamics::FF, 2.0)