musicality 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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