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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +65 -0
- data/bin/midify +78 -0
- data/examples/hip.rb +32 -0
- data/examples/missed_connection.rb +26 -0
- data/examples/song1.rb +33 -0
- data/examples/song2.rb +32 -0
- data/lib/musicality/errors.rb +9 -0
- data/lib/musicality/notation/conversion/change_conversion.rb +19 -0
- data/lib/musicality/notation/conversion/measure_note_map.rb +40 -0
- data/lib/musicality/notation/conversion/measured_score_conversion.rb +70 -0
- data/lib/musicality/notation/conversion/measured_score_converter.rb +95 -0
- data/lib/musicality/notation/conversion/note_time_converter.rb +68 -0
- data/lib/musicality/notation/conversion/tempo_conversion.rb +25 -0
- data/lib/musicality/notation/conversion/unmeasured_score_conversion.rb +47 -0
- data/lib/musicality/notation/conversion/unmeasured_score_converter.rb +64 -0
- data/lib/musicality/notation/model/articulations.rb +13 -0
- data/lib/musicality/notation/model/change.rb +62 -0
- data/lib/musicality/notation/model/dynamics.rb +12 -0
- data/lib/musicality/notation/model/link.rb +73 -0
- data/lib/musicality/notation/model/meter.rb +54 -0
- data/lib/musicality/notation/model/meters.rb +9 -0
- data/lib/musicality/notation/model/note.rb +120 -0
- data/lib/musicality/notation/model/part.rb +54 -0
- data/lib/musicality/notation/model/pitch.rb +163 -0
- data/lib/musicality/notation/model/pitches.rb +21 -0
- data/lib/musicality/notation/model/program.rb +53 -0
- data/lib/musicality/notation/model/score.rb +132 -0
- data/lib/musicality/notation/packing/change_packing.rb +46 -0
- data/lib/musicality/notation/packing/part_packing.rb +31 -0
- data/lib/musicality/notation/packing/program_packing.rb +16 -0
- data/lib/musicality/notation/packing/score_packing.rb +108 -0
- data/lib/musicality/notation/parsing/articulation_parsing.rb +264 -0
- data/lib/musicality/notation/parsing/articulation_parsing.treetop +59 -0
- data/lib/musicality/notation/parsing/convenience_methods.rb +74 -0
- data/lib/musicality/notation/parsing/duration_nodes.rb +21 -0
- data/lib/musicality/notation/parsing/duration_parsing.rb +205 -0
- data/lib/musicality/notation/parsing/duration_parsing.treetop +25 -0
- data/lib/musicality/notation/parsing/link_nodes.rb +35 -0
- data/lib/musicality/notation/parsing/link_parsing.rb +270 -0
- data/lib/musicality/notation/parsing/link_parsing.treetop +33 -0
- data/lib/musicality/notation/parsing/meter_parsing.rb +190 -0
- data/lib/musicality/notation/parsing/meter_parsing.treetop +29 -0
- data/lib/musicality/notation/parsing/note_node.rb +40 -0
- data/lib/musicality/notation/parsing/note_parsing.rb +229 -0
- data/lib/musicality/notation/parsing/note_parsing.treetop +28 -0
- data/lib/musicality/notation/parsing/numbers/nonnegative_float_parsing.rb +289 -0
- data/lib/musicality/notation/parsing/numbers/nonnegative_float_parsing.treetop +29 -0
- data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.rb +64 -0
- data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.treetop +17 -0
- data/lib/musicality/notation/parsing/numbers/nonnegative_rational_parsing.rb +86 -0
- data/lib/musicality/notation/parsing/numbers/nonnegative_rational_parsing.treetop +20 -0
- data/lib/musicality/notation/parsing/numbers/positive_float_parsing.rb +503 -0
- data/lib/musicality/notation/parsing/numbers/positive_float_parsing.treetop +33 -0
- data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.rb +95 -0
- data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.treetop +19 -0
- data/lib/musicality/notation/parsing/numbers/positive_rational_parsing.rb +84 -0
- data/lib/musicality/notation/parsing/numbers/positive_rational_parsing.treetop +19 -0
- data/lib/musicality/notation/parsing/parseable.rb +30 -0
- data/lib/musicality/notation/parsing/pitch_node.rb +23 -0
- data/lib/musicality/notation/parsing/pitch_parsing.rb +448 -0
- data/lib/musicality/notation/parsing/pitch_parsing.treetop +52 -0
- data/lib/musicality/notation/parsing/segment_parsing.rb +141 -0
- data/lib/musicality/notation/parsing/segment_parsing.treetop +23 -0
- data/lib/musicality/notation/util/interpolation.rb +16 -0
- data/lib/musicality/notation/util/piecewise_function.rb +122 -0
- data/lib/musicality/notation/util/value_computer.rb +170 -0
- data/lib/musicality/performance/conversion/glissando_converter.rb +34 -0
- data/lib/musicality/performance/conversion/note_sequence_extractor.rb +98 -0
- data/lib/musicality/performance/conversion/portamento_converter.rb +24 -0
- data/lib/musicality/performance/conversion/score_collator.rb +126 -0
- data/lib/musicality/performance/midi/midi_events.rb +34 -0
- data/lib/musicality/performance/midi/midi_util.rb +31 -0
- data/lib/musicality/performance/midi/part_sequencer.rb +123 -0
- data/lib/musicality/performance/midi/score_sequencer.rb +45 -0
- data/lib/musicality/performance/model/note_attacks.rb +19 -0
- data/lib/musicality/performance/model/note_sequence.rb +111 -0
- data/lib/musicality/performance/util/note_linker.rb +28 -0
- data/lib/musicality/performance/util/optimization.rb +31 -0
- data/lib/musicality/validatable.rb +38 -0
- data/lib/musicality/version.rb +3 -0
- data/lib/musicality.rb +81 -0
- data/musicality.gemspec +30 -0
- data/spec/musicality_spec.rb +7 -0
- data/spec/notation/conversion/change_conversion_spec.rb +40 -0
- data/spec/notation/conversion/measure_note_map_spec.rb +73 -0
- data/spec/notation/conversion/measured_score_conversion_spec.rb +141 -0
- data/spec/notation/conversion/measured_score_converter_spec.rb +329 -0
- data/spec/notation/conversion/note_time_converter_spec.rb +81 -0
- data/spec/notation/conversion/tempo_conversion_spec.rb +40 -0
- data/spec/notation/conversion/unmeasured_score_conversion_spec.rb +71 -0
- data/spec/notation/conversion/unmeasured_score_converter_spec.rb +116 -0
- data/spec/notation/model/change_spec.rb +90 -0
- data/spec/notation/model/link_spec.rb +83 -0
- data/spec/notation/model/meter_spec.rb +97 -0
- data/spec/notation/model/note_spec.rb +183 -0
- data/spec/notation/model/part_spec.rb +69 -0
- data/spec/notation/model/pitch_spec.rb +180 -0
- data/spec/notation/model/program_spec.rb +50 -0
- data/spec/notation/model/score_spec.rb +211 -0
- data/spec/notation/packing/change_packing_spec.rb +153 -0
- data/spec/notation/packing/part_packing_spec.rb +66 -0
- data/spec/notation/packing/program_packing_spec.rb +33 -0
- data/spec/notation/packing/score_packing_spec.rb +301 -0
- data/spec/notation/parsing/articulation_parsing_spec.rb +23 -0
- data/spec/notation/parsing/convenience_methods_spec.rb +99 -0
- data/spec/notation/parsing/duration_nodes_spec.rb +83 -0
- data/spec/notation/parsing/duration_parsing_spec.rb +70 -0
- data/spec/notation/parsing/link_nodes_spec.rb +30 -0
- data/spec/notation/parsing/link_parsing_spec.rb +13 -0
- data/spec/notation/parsing/meter_parsing_spec.rb +23 -0
- data/spec/notation/parsing/note_node_spec.rb +87 -0
- data/spec/notation/parsing/note_parsing_spec.rb +46 -0
- data/spec/notation/parsing/numbers/nonnegative_float_spec.rb +28 -0
- data/spec/notation/parsing/numbers/nonnegative_integer_spec.rb +11 -0
- data/spec/notation/parsing/numbers/nonnegative_rational_spec.rb +11 -0
- data/spec/notation/parsing/numbers/positive_float_spec.rb +28 -0
- data/spec/notation/parsing/numbers/positive_integer_spec.rb +28 -0
- data/spec/notation/parsing/numbers/positive_rational_spec.rb +28 -0
- data/spec/notation/parsing/pitch_node_spec.rb +38 -0
- data/spec/notation/parsing/pitch_parsing_spec.rb +14 -0
- data/spec/notation/parsing/segment_parsing_spec.rb +27 -0
- data/spec/notation/util/value_computer_spec.rb +146 -0
- data/spec/performance/conversion/glissando_converter_spec.rb +93 -0
- data/spec/performance/conversion/note_sequence_extractor_spec.rb +230 -0
- data/spec/performance/conversion/portamento_converter_spec.rb +91 -0
- data/spec/performance/conversion/score_collator_spec.rb +183 -0
- data/spec/performance/midi/midi_util_spec.rb +110 -0
- data/spec/performance/midi/part_sequencer_spec.rb +40 -0
- data/spec/performance/midi/score_sequencer_spec.rb +50 -0
- data/spec/performance/model/note_sequence_spec.rb +147 -0
- data/spec/performance/util/note_linker_spec.rb +68 -0
- data/spec/performance/util/optimization_spec.rb +73 -0
- data/spec/spec_helper.rb +43 -0
- 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
|