musicality 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +47 -0
  8. data/Rakefile +65 -0
  9. data/bin/midify +78 -0
  10. data/examples/hip.rb +32 -0
  11. data/examples/missed_connection.rb +26 -0
  12. data/examples/song1.rb +33 -0
  13. data/examples/song2.rb +32 -0
  14. data/lib/musicality/errors.rb +9 -0
  15. data/lib/musicality/notation/conversion/change_conversion.rb +19 -0
  16. data/lib/musicality/notation/conversion/measure_note_map.rb +40 -0
  17. data/lib/musicality/notation/conversion/measured_score_conversion.rb +70 -0
  18. data/lib/musicality/notation/conversion/measured_score_converter.rb +95 -0
  19. data/lib/musicality/notation/conversion/note_time_converter.rb +68 -0
  20. data/lib/musicality/notation/conversion/tempo_conversion.rb +25 -0
  21. data/lib/musicality/notation/conversion/unmeasured_score_conversion.rb +47 -0
  22. data/lib/musicality/notation/conversion/unmeasured_score_converter.rb +64 -0
  23. data/lib/musicality/notation/model/articulations.rb +13 -0
  24. data/lib/musicality/notation/model/change.rb +62 -0
  25. data/lib/musicality/notation/model/dynamics.rb +12 -0
  26. data/lib/musicality/notation/model/link.rb +73 -0
  27. data/lib/musicality/notation/model/meter.rb +54 -0
  28. data/lib/musicality/notation/model/meters.rb +9 -0
  29. data/lib/musicality/notation/model/note.rb +120 -0
  30. data/lib/musicality/notation/model/part.rb +54 -0
  31. data/lib/musicality/notation/model/pitch.rb +163 -0
  32. data/lib/musicality/notation/model/pitches.rb +21 -0
  33. data/lib/musicality/notation/model/program.rb +53 -0
  34. data/lib/musicality/notation/model/score.rb +132 -0
  35. data/lib/musicality/notation/packing/change_packing.rb +46 -0
  36. data/lib/musicality/notation/packing/part_packing.rb +31 -0
  37. data/lib/musicality/notation/packing/program_packing.rb +16 -0
  38. data/lib/musicality/notation/packing/score_packing.rb +108 -0
  39. data/lib/musicality/notation/parsing/articulation_parsing.rb +264 -0
  40. data/lib/musicality/notation/parsing/articulation_parsing.treetop +59 -0
  41. data/lib/musicality/notation/parsing/convenience_methods.rb +74 -0
  42. data/lib/musicality/notation/parsing/duration_nodes.rb +21 -0
  43. data/lib/musicality/notation/parsing/duration_parsing.rb +205 -0
  44. data/lib/musicality/notation/parsing/duration_parsing.treetop +25 -0
  45. data/lib/musicality/notation/parsing/link_nodes.rb +35 -0
  46. data/lib/musicality/notation/parsing/link_parsing.rb +270 -0
  47. data/lib/musicality/notation/parsing/link_parsing.treetop +33 -0
  48. data/lib/musicality/notation/parsing/meter_parsing.rb +190 -0
  49. data/lib/musicality/notation/parsing/meter_parsing.treetop +29 -0
  50. data/lib/musicality/notation/parsing/note_node.rb +40 -0
  51. data/lib/musicality/notation/parsing/note_parsing.rb +229 -0
  52. data/lib/musicality/notation/parsing/note_parsing.treetop +28 -0
  53. data/lib/musicality/notation/parsing/numbers/nonnegative_float_parsing.rb +289 -0
  54. data/lib/musicality/notation/parsing/numbers/nonnegative_float_parsing.treetop +29 -0
  55. data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.rb +64 -0
  56. data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.treetop +17 -0
  57. data/lib/musicality/notation/parsing/numbers/nonnegative_rational_parsing.rb +86 -0
  58. data/lib/musicality/notation/parsing/numbers/nonnegative_rational_parsing.treetop +20 -0
  59. data/lib/musicality/notation/parsing/numbers/positive_float_parsing.rb +503 -0
  60. data/lib/musicality/notation/parsing/numbers/positive_float_parsing.treetop +33 -0
  61. data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.rb +95 -0
  62. data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.treetop +19 -0
  63. data/lib/musicality/notation/parsing/numbers/positive_rational_parsing.rb +84 -0
  64. data/lib/musicality/notation/parsing/numbers/positive_rational_parsing.treetop +19 -0
  65. data/lib/musicality/notation/parsing/parseable.rb +30 -0
  66. data/lib/musicality/notation/parsing/pitch_node.rb +23 -0
  67. data/lib/musicality/notation/parsing/pitch_parsing.rb +448 -0
  68. data/lib/musicality/notation/parsing/pitch_parsing.treetop +52 -0
  69. data/lib/musicality/notation/parsing/segment_parsing.rb +141 -0
  70. data/lib/musicality/notation/parsing/segment_parsing.treetop +23 -0
  71. data/lib/musicality/notation/util/interpolation.rb +16 -0
  72. data/lib/musicality/notation/util/piecewise_function.rb +122 -0
  73. data/lib/musicality/notation/util/value_computer.rb +170 -0
  74. data/lib/musicality/performance/conversion/glissando_converter.rb +34 -0
  75. data/lib/musicality/performance/conversion/note_sequence_extractor.rb +98 -0
  76. data/lib/musicality/performance/conversion/portamento_converter.rb +24 -0
  77. data/lib/musicality/performance/conversion/score_collator.rb +126 -0
  78. data/lib/musicality/performance/midi/midi_events.rb +34 -0
  79. data/lib/musicality/performance/midi/midi_util.rb +31 -0
  80. data/lib/musicality/performance/midi/part_sequencer.rb +123 -0
  81. data/lib/musicality/performance/midi/score_sequencer.rb +45 -0
  82. data/lib/musicality/performance/model/note_attacks.rb +19 -0
  83. data/lib/musicality/performance/model/note_sequence.rb +111 -0
  84. data/lib/musicality/performance/util/note_linker.rb +28 -0
  85. data/lib/musicality/performance/util/optimization.rb +31 -0
  86. data/lib/musicality/validatable.rb +38 -0
  87. data/lib/musicality/version.rb +3 -0
  88. data/lib/musicality.rb +81 -0
  89. data/musicality.gemspec +30 -0
  90. data/spec/musicality_spec.rb +7 -0
  91. data/spec/notation/conversion/change_conversion_spec.rb +40 -0
  92. data/spec/notation/conversion/measure_note_map_spec.rb +73 -0
  93. data/spec/notation/conversion/measured_score_conversion_spec.rb +141 -0
  94. data/spec/notation/conversion/measured_score_converter_spec.rb +329 -0
  95. data/spec/notation/conversion/note_time_converter_spec.rb +81 -0
  96. data/spec/notation/conversion/tempo_conversion_spec.rb +40 -0
  97. data/spec/notation/conversion/unmeasured_score_conversion_spec.rb +71 -0
  98. data/spec/notation/conversion/unmeasured_score_converter_spec.rb +116 -0
  99. data/spec/notation/model/change_spec.rb +90 -0
  100. data/spec/notation/model/link_spec.rb +83 -0
  101. data/spec/notation/model/meter_spec.rb +97 -0
  102. data/spec/notation/model/note_spec.rb +183 -0
  103. data/spec/notation/model/part_spec.rb +69 -0
  104. data/spec/notation/model/pitch_spec.rb +180 -0
  105. data/spec/notation/model/program_spec.rb +50 -0
  106. data/spec/notation/model/score_spec.rb +211 -0
  107. data/spec/notation/packing/change_packing_spec.rb +153 -0
  108. data/spec/notation/packing/part_packing_spec.rb +66 -0
  109. data/spec/notation/packing/program_packing_spec.rb +33 -0
  110. data/spec/notation/packing/score_packing_spec.rb +301 -0
  111. data/spec/notation/parsing/articulation_parsing_spec.rb +23 -0
  112. data/spec/notation/parsing/convenience_methods_spec.rb +99 -0
  113. data/spec/notation/parsing/duration_nodes_spec.rb +83 -0
  114. data/spec/notation/parsing/duration_parsing_spec.rb +70 -0
  115. data/spec/notation/parsing/link_nodes_spec.rb +30 -0
  116. data/spec/notation/parsing/link_parsing_spec.rb +13 -0
  117. data/spec/notation/parsing/meter_parsing_spec.rb +23 -0
  118. data/spec/notation/parsing/note_node_spec.rb +87 -0
  119. data/spec/notation/parsing/note_parsing_spec.rb +46 -0
  120. data/spec/notation/parsing/numbers/nonnegative_float_spec.rb +28 -0
  121. data/spec/notation/parsing/numbers/nonnegative_integer_spec.rb +11 -0
  122. data/spec/notation/parsing/numbers/nonnegative_rational_spec.rb +11 -0
  123. data/spec/notation/parsing/numbers/positive_float_spec.rb +28 -0
  124. data/spec/notation/parsing/numbers/positive_integer_spec.rb +28 -0
  125. data/spec/notation/parsing/numbers/positive_rational_spec.rb +28 -0
  126. data/spec/notation/parsing/pitch_node_spec.rb +38 -0
  127. data/spec/notation/parsing/pitch_parsing_spec.rb +14 -0
  128. data/spec/notation/parsing/segment_parsing_spec.rb +27 -0
  129. data/spec/notation/util/value_computer_spec.rb +146 -0
  130. data/spec/performance/conversion/glissando_converter_spec.rb +93 -0
  131. data/spec/performance/conversion/note_sequence_extractor_spec.rb +230 -0
  132. data/spec/performance/conversion/portamento_converter_spec.rb +91 -0
  133. data/spec/performance/conversion/score_collator_spec.rb +183 -0
  134. data/spec/performance/midi/midi_util_spec.rb +110 -0
  135. data/spec/performance/midi/part_sequencer_spec.rb +40 -0
  136. data/spec/performance/midi/score_sequencer_spec.rb +50 -0
  137. data/spec/performance/model/note_sequence_spec.rb +147 -0
  138. data/spec/performance/util/note_linker_spec.rb +68 -0
  139. data/spec/performance/util/optimization_spec.rb +73 -0
  140. data/spec/spec_helper.rb +43 -0
  141. metadata +323 -0
@@ -0,0 +1,87 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ NOTE_PARSER = Parsing::NoteParser.new
4
+
5
+ describe Parsing::NoteNode do
6
+ context 'rest note' do
7
+ {
8
+ '/2' => Note.new(Rational(1,2)),
9
+ '4/2' => Note.new(Rational(4,2)),
10
+ '28' => Note.new(Rational(28,1)),
11
+ '56/33' => Note.new(Rational(56,33)),
12
+ }.each do |str,tgt|
13
+ res = NOTE_PARSER.parse(str)
14
+ context str do
15
+ it 'should parse as NoteNode' do
16
+ res.should be_a Parsing::NoteNode
17
+ end
18
+
19
+ describe '#to_note' do
20
+ n = res.to_note
21
+ it 'should produce a Note' do
22
+ n.should be_a Note
23
+ end
24
+
25
+ it 'should produce value matching input str' do
26
+ n.should eq tgt
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ context 'monophonic note' do
34
+ {
35
+ '/2=C2=C2' => Note.new(Rational(1,2),[C2],articulation:SLUR, links:{C2=>Link::Slur.new(C2)}),
36
+ '4/2.D#6' => Note.new(Rational(4,2),[Eb6],articulation:STACCATO),
37
+ '28%Eb7!' => Note.new(Rational(28,1),[Eb7],articulation:PORTATO, accented: true),
38
+ "56/33'B1" => Note.new(Rational(56,33),[B1],articulation:STACCATISSIMO),
39
+ }.each do |str,tgt|
40
+ res = NOTE_PARSER.parse(str)
41
+
42
+ context str do
43
+ it 'should parse as MonophonicNoteNode' do
44
+ res.should be_a Parsing::NoteNode
45
+ end
46
+
47
+ describe '#to_note' do
48
+ n = res.to_note
49
+ it 'should produce a Note' do
50
+ n.should be_a Note
51
+ end
52
+
53
+ it 'should produce value matching input str' do
54
+ n.should eq tgt
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ context 'polyphonic note' do
62
+ {
63
+ '/2C2,D2,E2|F2' => Note.new(Rational(1,2),[C2,D2,E2],links:{E2=>Link::Legato.new(F2)}),
64
+ '4/2.D#6,G4' => Note.new(Rational(4,2),[Eb6,G4], articulation:STACCATO),
65
+ '28_Eb7,D7,G7' => Note.new(Rational(28,1),[Eb7,D7,G7],articulation:TENUTO),
66
+ '56/33B1,B2,B3,B4,B5!' => Note.new(Rational(56,33),[B1,B2,B3,B4,B5], accented: true),
67
+ }.each do |str,tgt|
68
+ res = NOTE_PARSER.parse(str)
69
+ context str do
70
+ it 'should parse as PolyphonicNoteNode' do
71
+ res.should be_a Parsing::NoteNode
72
+ end
73
+
74
+ describe '#to_note' do
75
+ n = res.to_note
76
+ it 'should produce a Note' do
77
+ n.should be_a Note
78
+ end
79
+
80
+ it 'should produce value matching input str' do
81
+ n.should eq tgt
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,46 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Parsing::NoteParser do
4
+ before :all do
5
+ @parser = Parsing::NoteParser.new
6
+ end
7
+
8
+ valid_cases = {
9
+ 'duration only' => ['1/4','/2','1','55/33'],
10
+ 'duration + accent' => ['1/4!','/2!'],
11
+ 'duration + articulation' => ['1/4.','/2%','2/3='],
12
+ 'duration + articulation + accent' => ['1/4.!','/2%!','2/3=!'],
13
+ 'single pitch' => ['/4C2','5/3Db3','/33E#8'],
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|B2','/2_D3,F4'],
16
+ 'with accent' => ['/4C2!','3/2Db3,Bb4!'],
17
+ 'with links' => ['/2C2=','/2C2=D2','/4D4|E4,G4~A5'],
18
+ 'with single pitch + articulation + link + accent' => [
19
+ '3/4.D2=!','5/8=F2=!','/8Db4|Db5!','/3_G4~B4!'],
20
+ 'with multiple pitches + articulation + links + accent' => [
21
+ '5/4.D2=,G4|A4,C3~D3!','5/8|F2=D4,B4/A4!'],
22
+ }
23
+ invalid_cases = {
24
+ 'single pitch' => ['5/3Hb|3','/33E|2'],
25
+ 'multiple pitches' => ['5/3Db3,Bb|1,E5','/33H8,F1,B9'],
26
+ 'with articulation' => ['/4[C2',"5/3>Db3"],
27
+ 'with accent' => ['/4C2['],
28
+ 'with links' => ['/2C2)'],
29
+ }
30
+
31
+ valid_cases.each do |descr, strs|
32
+ context(descr + ' (valid)') do
33
+ it 'should parse' do
34
+ strs.each {|s| @parser.should parse(s) }
35
+ end
36
+ end
37
+ end
38
+
39
+ invalid_cases.each do |descr, strs|
40
+ context(descr + ' (invalid)') do
41
+ it 'should not parse' do
42
+ strs.each {|s| @parser.should_not parse(s) }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,28 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
2
+
3
+ describe Parsing::NonnegativeFloatParser do
4
+ parser = Parsing::NonnegativeFloatParser .new
5
+
6
+ ["0.0","0e1","2e2","1.0","0.50","05.003e-10","1.555e+2","3.443214","0.001","0000.0030000"].each do |str|
7
+ res = parser.parse(str)
8
+ f = str.to_f
9
+
10
+ it "should parse '#{str}'" do
11
+ res.should_not be nil
12
+ end
13
+
14
+ it 'should return node that is convertible to float using #to_f method' do
15
+ res.to_f.should eq(f)
16
+ end
17
+
18
+ it 'should return node that is convertible to float using #to_num method' do
19
+ res.to_num.should eq(f)
20
+ end
21
+ end
22
+
23
+ ["-1.0","-0e1"].each do |str|
24
+ it "should not parse '#{str}'" do
25
+ parser.should_not parse(str)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
2
+
3
+ describe Parsing::NonnegativeIntegerParser do
4
+ parser = Parsing::NonnegativeIntegerParser.new
5
+
6
+ ["1","50","05","502530","0"].each do |str|
7
+ it "should parse '#{str}'" do
8
+ parser.parse(str).should_not be nil
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
2
+
3
+ describe Parsing::NonnegativeRationalParser do
4
+ parser = Parsing::NonnegativeRationalParser.new
5
+
6
+ ["1/2","0/50","05/003","502530/1","0/1"].each do |str|
7
+ it "should parse '#{str}'" do
8
+ parser.parse(str).should_not be nil
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
2
+
3
+ describe Parsing::PositiveFloatParser do
4
+ parser = Parsing::PositiveFloatParser.new
5
+
6
+ ["2e2","1.0","0.50","05.003e-10","1.555e+2","3.443214","0.001","0000.0030000"].each do |str|
7
+ res = parser.parse(str)
8
+ f = str.to_f
9
+
10
+ it "should parse '#{str}'" do
11
+ res.should_not be nil
12
+ end
13
+
14
+ it 'should return node that is convertible to float using #to_f method' do
15
+ res.to_f.should eq(f)
16
+ end
17
+
18
+ it 'should return node that is convertible to float using #to_num method' do
19
+ res.to_num.should eq(f)
20
+ end
21
+ end
22
+
23
+ ["-2.0","-1.55e-2","0.0","0e1"].each do |str|
24
+ it "should not parse '#{str}'" do
25
+ parser.should_not parse(str)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
2
+
3
+ describe Parsing::PositiveIntegerParser do
4
+ parser = Parsing::PositiveIntegerParser.new
5
+
6
+ ["1","50","05","502530"].each do |str|
7
+ res = parser.parse(str)
8
+ i = str.to_i
9
+
10
+ it "should parse '#{str}'" do
11
+ res.should_not be nil
12
+ end
13
+
14
+ it 'should return node that is convertible to integer using #to_i method' do
15
+ res.to_i.should eq(i)
16
+ end
17
+
18
+ it 'should return node that is convertible to integer using #to_num method' do
19
+ res.to_num.should eq(i)
20
+ end
21
+ end
22
+
23
+ ["0"].each do |str|
24
+ it "should not parse '#{str}'" do
25
+ parser.should_not parse(str)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
2
+
3
+ describe Parsing::PositiveRationalParser do
4
+ parser = Parsing::PositiveRationalParser.new
5
+
6
+ ["1/2","50/50","050/003","502530/1","01/1"].each do |str|
7
+ res = parser.parse(str)
8
+ r = str.to_r
9
+
10
+ it "should parse '#{str}'" do
11
+ res.should_not be nil
12
+ end
13
+
14
+ it 'should return node that is convertible to rational using #to_r method' do
15
+ res.to_r.should eq(r)
16
+ end
17
+
18
+ it 'should return node that is convertible to rational using #to_num method' do
19
+ res.to_num.should eq(r)
20
+ end
21
+ end
22
+
23
+ ["0/0","0/10","0000/1"].each do |str|
24
+ it "should not parse '#{str}'" do
25
+ parser.should_not parse(str)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,38 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Parsing::PitchNode do
4
+ parser = Parsing::PitchParser.new
5
+
6
+ {
7
+ 'C4' => C4,
8
+ 'Db2' => Db2,
9
+ 'C#2' => Db2,
10
+ 'Db2' => Db2,
11
+ 'F7' => F7,
12
+ 'B1' => B1,
13
+ "Bb22" => Pitch.new(octave: 22, semitone: 10),
14
+ "G2235" => Pitch.new(octave: 2235, semitone: 7),
15
+ "G2+11" => G2.transpose(0.11),
16
+ "G2-11" => G2.transpose(-0.11),
17
+ "A2-11301" => A2.transpose(-113.01),
18
+ "B5+881" => B5.transpose(8.81),
19
+ }.each do |str,tgt|
20
+ res = parser.parse(str)
21
+ context str do
22
+ it 'should parse as PitchNode' do
23
+ res.should be_a Parsing::PitchNode
24
+ end
25
+
26
+ describe '#to_pitch' do
27
+ p = res.to_pitch
28
+ it 'should produce a Pitch object' do
29
+ p.should be_a Pitch
30
+ end
31
+
32
+ it 'should produce pitch matching input str' do
33
+ p.should eq tgt
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,14 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Parsing::PitchParser do
4
+ before :all do
5
+ @parser = Parsing::PitchParser.new
6
+ end
7
+
8
+ ["C4","C#9","Ab0","G#2","E2+22","Cb5-99","G200","Bb9951+3920"
9
+ ].each do |str|
10
+ it "should parse #{str}" do
11
+ @parser.should parse(str)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,27 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Parsing::SegmentParser do
4
+ parser = Parsing::SegmentParser.new
5
+
6
+ cases = {
7
+ "ints" => ["0...4",0...4],
8
+ "plain floats" => ["0.0..4.0",0.0...4.0],
9
+ "sci floats" => ["1.3e-10...5",1.3e-10...5],
10
+ "int/float" => ["45..46.5",45...46.5],
11
+ "float/int" => ["4.5...5",4.5...5],
12
+ "rationals" => ["2/3..3/2",Rational(2,3)...Rational(3,2)],
13
+ "float/rational" => ["3.5..10/6",3.5...Rational(10,6)]
14
+ }.each do |descr,str_tgt|
15
+ context descr do
16
+ str,tgt = str_tgt
17
+ res = parser.parse(str)
18
+ it 'should parse' do
19
+ res.should_not be nil
20
+ end
21
+
22
+ it 'should return node that converts to exclusive range via #to_range' do
23
+ res.to_range.should eq tgt
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,146 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe ValueComputer do
4
+ describe '#value_at' do
5
+ before :all do
6
+ @value_change1 = Change::Immediate.new(0.6)
7
+ @value_change2 = Change::Gradual.new(0.6, 1.0)
8
+ end
9
+
10
+ context "constant value" do
11
+ before :each do
12
+ @comp = ValueComputer.new 0.5
13
+ end
14
+
15
+ it "should always return default value if no changes are given" do
16
+ [ValueComputer.domain_min, -1000, 0, 1, 5, 100, 10000, ValueComputer.domain_max].each do |offset|
17
+ @comp.value_at(offset).should eq(0.5)
18
+ end
19
+ end
20
+ end
21
+
22
+ context "one change, no transition" do
23
+ before :each do
24
+ @comp = ValueComputer.new 0.5, 1.0 => @value_change1
25
+ end
26
+
27
+ it "should be the default value just before the first change" do
28
+ @comp.value_at(0.999).should eq(0.5)
29
+ end
30
+
31
+ it "should transition to the second value immediately" do
32
+ @comp.value_at(1.0).should eq(0.6)
33
+ end
34
+
35
+ it "should be the first value for all time before" do
36
+ @comp.value_at(ValueComputer.domain_min).should eq(0.5)
37
+ end
38
+
39
+ it "should be at the second value for all time after" do
40
+ @comp.value_at(ValueComputer.domain_max).should eq(0.6)
41
+ end
42
+ end
43
+
44
+ context "one change, linear transition" do
45
+ before :each do
46
+ @comp = ValueComputer.new 0.2, 1.0 => @value_change2
47
+ end
48
+
49
+ it "should be the first (starting) value just before the second value" do
50
+ @comp.value_at(0.999).should eq(0.2)
51
+ end
52
+
53
+ it "should be the first (starting) value exactly at the second value" do
54
+ @comp.value_at(1.0).should eq(0.2)
55
+ end
56
+
57
+ it "should be 1/4 to the second value after 1/4 transition duration has elapsed" do
58
+ @comp.value_at(Rational(5,4).to_f).should eq(0.3)
59
+ end
60
+
61
+ it "should be 1/2 to the second value after 1/2 transition duration has elapsed" do
62
+ @comp.value_at(Rational(6,4).to_f).should eq(0.4)
63
+ end
64
+
65
+ it "should be 3/4 to the second value after 3/4 transition duration has elapsed" do
66
+ @comp.value_at(Rational(7,4).to_f).should eq(0.5)
67
+ end
68
+
69
+ it "should be at the second value after transition duration has elapsed" do
70
+ @comp.value_at(Rational(8,4).to_f).should eq(0.6)
71
+ end
72
+ end
73
+
74
+ # context "one change, sigmoid transition" do
75
+ # before :all do
76
+ # @cases = [
77
+ # { :start_value => 0, :end_value => 1, :offset => 0, :duration => 1.0, :abruptness => 0.75 },
78
+ # { :start_value => 0.25, :end_value => 0.75, :offset => 1.0, :duration => 2.0, :abruptness => 0.75 },
79
+ # { :start_value => -1.0, :end_value => 5.5, :offset => 2.4, :duration => 20, :abruptness => 0.75 },
80
+ # { :start_value => 10, :end_value => 100, :offset => 20, :duration => 4, :abruptness => 0.75 },
81
+ # ]
82
+ #
83
+ # @computers = {}
84
+ # @cases.each do |case_hash|
85
+ # start_value = case_hash[:start_value]
86
+ # offset = case_hash[:offset]
87
+ # end_value = case_hash[:end_value]
88
+ # duration = case_hash[:duration]
89
+ # #abruptness = case_hash[:abruptness]
90
+ #
91
+ # change = Change::Gradual.new(end_value, duration)
92
+ # @computers[case_hash] = ValueComputer.new start_value, offset => change
93
+ # # @computers[case_hash].plot_range(offset..(offset + duration), 0.01)
94
+ # end
95
+ # end
96
+ #
97
+ # it "should be the first (starting) value just before the value change offset" do
98
+ # @computers.each do |case_hash, comp|
99
+ # comp.value_at(case_hash[:offset] - 0.0001).should eq(case_hash[:start_value])
100
+ # end
101
+ # end
102
+ #
103
+ # it "should be very nearly the first (starting) value exactly at the value change offset" do
104
+ # @computers.each do |case_hash, comp|
105
+ # comp.value_at(case_hash[:offset]).should be_within(0.0001).of(case_hash[:start_value])
106
+ # end
107
+ # end
108
+ #
109
+ # it "should be within 1% of start/end difference away from the start value after 1/4 transition duration has elapsed" do
110
+ # @computers.each do |case_hash, comp|
111
+ # test_offset = case_hash[:offset] + (case_hash[:duration] * 0.25)
112
+ # start_value = case_hash[:start_value]
113
+ # end_value = case_hash[:end_value]
114
+ # tolerance = 0.01 * (end_value - start_value)
115
+ # comp.value_at(test_offset).should be_within(tolerance).of(start_value)
116
+ # end
117
+ # end
118
+ #
119
+ # it "should be half way to the end value after half way through transition" do
120
+ # @computers.each do |case_hash, comp|
121
+ # test_offset = case_hash[:offset] + (case_hash[:duration] * 0.5)
122
+ # start_value = case_hash[:start_value]
123
+ # expected_value = start_value + (case_hash[:end_value] - start_value) * 0.5
124
+ # comp.value_at(test_offset).should be_within(0.0001).of(expected_value)
125
+ # end
126
+ # end
127
+ #
128
+ # it "should be within 1% of start/end difference away from the end value after 3/4 transition duration has elapsed" do
129
+ # @computers.each do |case_hash, comp|
130
+ # test_offset = case_hash[:offset] + (case_hash[:duration] * 0.75)
131
+ # start_value = case_hash[:start_value]
132
+ # end_value = case_hash[:end_value]
133
+ # tolerance = 0.01 * (end_value - start_value)
134
+ # comp.value_at(test_offset).should be_within(tolerance).of(end_value)
135
+ # end
136
+ # end
137
+ #
138
+ # it "should be at the second value after transition duration has elapsed" do
139
+ # @computers.each do |case_hash, comp|
140
+ # comp.value_at(case_hash[:offset] + case_hash[:duration]).should eq(case_hash[:end_value])
141
+ # end
142
+ # end
143
+ # end
144
+ end
145
+ end
146
+
@@ -0,0 +1,93 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe GlissandoConverter do
4
+ describe '.glissando_pitches' do
5
+ context 'start pitch <= target pitch' do
6
+ [
7
+ [F3,B3],
8
+ [C4,Gb5],
9
+ [D2,C5],
10
+ [F2,F2],
11
+ [C4.transpose(0.5),D4],
12
+ [C4.transpose(0.5),D4.transpose(0.5)],
13
+ [C4.transpose(0.01),F5],
14
+ [C4.transpose(-0.01),F5.transpose(-0.01)],
15
+ ].each do |start,finish|
16
+ context "start at #{start.to_s}, target #{finish.to_s}" do
17
+ pitches = GlissandoConverter.glissando_pitches(start,finish)
18
+
19
+ it 'should begin with start pitch' do
20
+ pitches.first.should eq(start)
21
+ end
22
+
23
+ it 'should move up to next whole (zero-cent) pitches' do
24
+ (1...pitches.size).each do |i|
25
+ pitches[i].cent.should eq(0)
26
+ pitches[i].diff(pitches[i-1]).should be <= 1
27
+ end
28
+ end
29
+
30
+ it 'should end on the whole (zero-cent) pitch below target pitch' do
31
+ pitches.last.cent.should eq(0)
32
+ diff = finish.total_cents - pitches.last.total_cents
33
+ diff.should be <= 100
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ context 'start pitch > target pitch' do
40
+ [
41
+ [B3,F3],
42
+ [Gb5,C4],
43
+ [C5,D2],
44
+ [D4,C4.transpose(0.5)],
45
+ [D4.transpose(0.5),C4.transpose(0.5)],
46
+ [F5,C4.transpose(0.01)],
47
+ [F5.transpose(-0.01),C4.transpose(-0.01)],
48
+ ].each do |start,finish|
49
+ context "start at #{start.to_s}, target #{finish.to_s}" do
50
+ pitches = GlissandoConverter.glissando_pitches(start,finish)
51
+
52
+ it 'should move down to next whole (zero-cent) pitches' do
53
+ (1...pitches.size).each do |i|
54
+ pitches[i].cent.should eq(0)
55
+ pitches[i-1].diff(pitches[i]).should be <= 1
56
+ end
57
+ end
58
+
59
+ it 'should end on the whole (zero-cent) pitch above target pitch' do
60
+ pitches.last.cent.should eq(0)
61
+ diff = pitches.last.total_cents - finish.total_cents
62
+ diff.should be <= 100
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ describe '.glissando_elements' do
71
+ before :all do
72
+ @dur = Rational(3,2)
73
+ @acc = false
74
+ @els = GlissandoConverter.glissando_elements(C4,A4,@dur,@acc)
75
+ end
76
+
77
+ it 'should return an array of LegatoElement objects' do
78
+ @els.each {|el| el.should be_a LegatoElement }
79
+ end
80
+
81
+ it 'should split up duration among elements' do
82
+ sum = @els.map {|el| el.duration }.inject(0,:+)
83
+ sum.should eq(@dur)
84
+ end
85
+
86
+ it 'should set accented as given' do
87
+ els = GlissandoConverter.glissando_elements(C4,A4,1,false)
88
+ els.each {|el| el.accented.should eq(false) }
89
+ els = GlissandoConverter.glissando_elements(C4,A4,1,true)
90
+ els.each {|el| el.accented.should eq(true) }
91
+ end
92
+ end
93
+ end