jmtk 0.0.3.3-java
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.
- data/.yardopts +10 -0
- data/DEVELOPMENT_NOTES.md +115 -0
- data/INTRO.md +129 -0
- data/LICENSE.txt +27 -0
- data/README.md +50 -0
- data/Rakefile +102 -0
- data/bin/jmtk +250 -0
- data/bin/mtk +250 -0
- data/examples/crescendo.rb +20 -0
- data/examples/drum_pattern.rb +23 -0
- data/examples/dynamic_pattern.rb +36 -0
- data/examples/gets_and_play.rb +27 -0
- data/examples/notation.rb +22 -0
- data/examples/play_midi.rb +17 -0
- data/examples/print_midi.rb +13 -0
- data/examples/random_tone_row.rb +18 -0
- data/examples/syntax_to_midi.rb +28 -0
- data/examples/test_output.rb +7 -0
- data/examples/tone_row_melody.rb +23 -0
- data/lib/mtk.rb +76 -0
- data/lib/mtk/core/duration.rb +213 -0
- data/lib/mtk/core/intensity.rb +158 -0
- data/lib/mtk/core/interval.rb +157 -0
- data/lib/mtk/core/pitch.rb +154 -0
- data/lib/mtk/core/pitch_class.rb +194 -0
- data/lib/mtk/events/event.rb +119 -0
- data/lib/mtk/events/note.rb +112 -0
- data/lib/mtk/events/parameter.rb +54 -0
- data/lib/mtk/events/timeline.rb +232 -0
- data/lib/mtk/groups/chord.rb +56 -0
- data/lib/mtk/groups/collection.rb +196 -0
- data/lib/mtk/groups/melody.rb +96 -0
- data/lib/mtk/groups/pitch_class_set.rb +163 -0
- data/lib/mtk/groups/pitch_collection.rb +23 -0
- data/lib/mtk/io/dls_synth_device.rb +146 -0
- data/lib/mtk/io/dls_synth_output.rb +62 -0
- data/lib/mtk/io/jsound_input.rb +87 -0
- data/lib/mtk/io/jsound_output.rb +82 -0
- data/lib/mtk/io/midi_file.rb +209 -0
- data/lib/mtk/io/midi_input.rb +97 -0
- data/lib/mtk/io/midi_output.rb +195 -0
- data/lib/mtk/io/notation.rb +162 -0
- data/lib/mtk/io/unimidi_input.rb +117 -0
- data/lib/mtk/io/unimidi_output.rb +140 -0
- data/lib/mtk/lang/durations.rb +57 -0
- data/lib/mtk/lang/intensities.rb +61 -0
- data/lib/mtk/lang/intervals.rb +73 -0
- data/lib/mtk/lang/mtk_grammar.citrus +237 -0
- data/lib/mtk/lang/parser.rb +29 -0
- data/lib/mtk/lang/pitch_classes.rb +29 -0
- data/lib/mtk/lang/pitches.rb +52 -0
- data/lib/mtk/lang/pseudo_constants.rb +26 -0
- data/lib/mtk/lang/variable.rb +32 -0
- data/lib/mtk/numeric_extensions.rb +66 -0
- data/lib/mtk/patterns/chain.rb +49 -0
- data/lib/mtk/patterns/choice.rb +43 -0
- data/lib/mtk/patterns/cycle.rb +18 -0
- data/lib/mtk/patterns/for_each.rb +71 -0
- data/lib/mtk/patterns/function.rb +39 -0
- data/lib/mtk/patterns/lines.rb +54 -0
- data/lib/mtk/patterns/palindrome.rb +45 -0
- data/lib/mtk/patterns/pattern.rb +171 -0
- data/lib/mtk/patterns/sequence.rb +20 -0
- data/lib/mtk/sequencers/event_builder.rb +132 -0
- data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
- data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
- data/lib/mtk/sequencers/sequencer.rb +111 -0
- data/lib/mtk/sequencers/step_sequencer.rb +26 -0
- data/spec/mtk/core/duration_spec.rb +372 -0
- data/spec/mtk/core/intensity_spec.rb +289 -0
- data/spec/mtk/core/interval_spec.rb +265 -0
- data/spec/mtk/core/pitch_class_spec.rb +343 -0
- data/spec/mtk/core/pitch_spec.rb +297 -0
- data/spec/mtk/events/event_spec.rb +234 -0
- data/spec/mtk/events/note_spec.rb +174 -0
- data/spec/mtk/events/parameter_spec.rb +220 -0
- data/spec/mtk/events/timeline_spec.rb +430 -0
- data/spec/mtk/groups/chord_spec.rb +85 -0
- data/spec/mtk/groups/collection_spec.rb +374 -0
- data/spec/mtk/groups/melody_spec.rb +225 -0
- data/spec/mtk/groups/pitch_class_set_spec.rb +340 -0
- data/spec/mtk/io/midi_file_spec.rb +243 -0
- data/spec/mtk/io/midi_output_spec.rb +102 -0
- data/spec/mtk/lang/durations_spec.rb +89 -0
- data/spec/mtk/lang/intensities_spec.rb +101 -0
- data/spec/mtk/lang/intervals_spec.rb +143 -0
- data/spec/mtk/lang/parser_spec.rb +603 -0
- data/spec/mtk/lang/pitch_classes_spec.rb +62 -0
- data/spec/mtk/lang/pitches_spec.rb +56 -0
- data/spec/mtk/lang/pseudo_constants_spec.rb +20 -0
- data/spec/mtk/lang/variable_spec.rb +52 -0
- data/spec/mtk/numeric_extensions_spec.rb +83 -0
- data/spec/mtk/patterns/chain_spec.rb +110 -0
- data/spec/mtk/patterns/choice_spec.rb +97 -0
- data/spec/mtk/patterns/cycle_spec.rb +123 -0
- data/spec/mtk/patterns/for_each_spec.rb +136 -0
- data/spec/mtk/patterns/function_spec.rb +120 -0
- data/spec/mtk/patterns/lines_spec.rb +77 -0
- data/spec/mtk/patterns/palindrome_spec.rb +108 -0
- data/spec/mtk/patterns/pattern_spec.rb +132 -0
- data/spec/mtk/patterns/sequence_spec.rb +203 -0
- data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
- data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
- data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
- data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
- data/spec/mtk/sequencers/step_sequencer_spec.rb +93 -0
- data/spec/spec_coverage.rb +2 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/test.mid +0 -0
- metadata +226 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mtk/io/midi_output'
|
3
|
+
|
4
|
+
describe MTK::IO::MIDIOutput do
|
5
|
+
|
6
|
+
class MockOuput < MTK::IO::MIDIOutput
|
7
|
+
public_class_method :new
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:subject) { MockOuput.new(mock_device) }
|
11
|
+
|
12
|
+
let(:mock_device) do
|
13
|
+
mock_device = mock(:device)
|
14
|
+
mock_device.stub(:open)
|
15
|
+
mock_device
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:scheduler) do
|
19
|
+
scheduler = mock(:scheduler)
|
20
|
+
Gamelan::Scheduler.stub(:new).and_return scheduler
|
21
|
+
scheduler
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def timeline_with_param_event(event_type, event_options={})
|
26
|
+
event = MTK::Events::Parameter.new event_type, event_options
|
27
|
+
MTK::Events::Timeline.from_h 0 => event
|
28
|
+
end
|
29
|
+
|
30
|
+
def should_be_scheduled timed_data
|
31
|
+
timed_data.each do |time,data|
|
32
|
+
scheduler.should_receive(:at) do |scheduled_time,&callback|
|
33
|
+
scheduled_time.should == time
|
34
|
+
callback.yield.should == data
|
35
|
+
end
|
36
|
+
end
|
37
|
+
scheduler.should_receive(:at) # end time, don't care about this here...
|
38
|
+
scheduler.should_receive(:run).and_return mock(:thread,:join=>nil)
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
describe ".new" do
|
43
|
+
it "opens the given device" do
|
44
|
+
mock_device.should_receive(:open)
|
45
|
+
subject
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#play" do
|
50
|
+
|
51
|
+
it "handles note events" do
|
52
|
+
should_be_scheduled 0 => [:note_on, 60, 127, 0],
|
53
|
+
1 => [:note_off, 60, 127, 0]
|
54
|
+
subject.play MTK::Events::Timeline.from_h( 0 => Note(C4,fff,1) )
|
55
|
+
end
|
56
|
+
|
57
|
+
it "handles control events" do
|
58
|
+
should_be_scheduled 0 => [:control, 5, 32, 3]
|
59
|
+
subject.play timeline_with_param_event(:control, number:5, value:0.25, channel:3)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "handles channel pressure events" do
|
63
|
+
should_be_scheduled 0 => [:channel_pressure, 64, 0]
|
64
|
+
subject.play timeline_with_param_event(:pressure, value:0.5)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "handles poly pressure events" do
|
68
|
+
should_be_scheduled 0 => [:poly_pressure, 60, 127, 0]
|
69
|
+
subject.play timeline_with_param_event(:pressure, number:60, value:1)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "handles bend events" do
|
73
|
+
should_be_scheduled 0 => [:bend, 0, 0]
|
74
|
+
subject.play timeline_with_param_event(:bend, value: -1)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "handles program events" do
|
78
|
+
should_be_scheduled 0 => [:program, 7, 9]
|
79
|
+
subject.play timeline_with_param_event(:program, number:7, channel:9)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "handles simultaneous events" do
|
83
|
+
should_be_scheduled [
|
84
|
+
[0, [:note_on, 60, 127, 0]],
|
85
|
+
[1, [:note_off, 60, 127, 0]],
|
86
|
+
[0, [:note_on, 67, 127, 0]],
|
87
|
+
[1, [:note_off, 67, 127, 0]]
|
88
|
+
]
|
89
|
+
subject.play [Note(C4,fff,1),Note(G4,fff,1)]
|
90
|
+
end
|
91
|
+
|
92
|
+
it "handles a list of timelines" do
|
93
|
+
should_be_scheduled 0 => [:note_on, 60, 127, 0],
|
94
|
+
1 => [:note_off, 60, 127, 0],
|
95
|
+
2 => [:note_on, 67, 127, 0],
|
96
|
+
3 => [:note_off, 67, 127, 0]
|
97
|
+
subject.play [ MTK::Events::Timeline.from_h( 0 => Note(C4,fff,1) ), MTK::Events::Timeline.from_h( 2 => Note(G4,fff,1) )]
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Lang::Durations do
|
4
|
+
|
5
|
+
describe 'w' do
|
6
|
+
it 'is 4 beats' do
|
7
|
+
w.value.should == 4
|
8
|
+
end
|
9
|
+
it 'is available via a module property and via mixin' do
|
10
|
+
Durations::w.should == w
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'h' do
|
15
|
+
it 'is 2 beats' do
|
16
|
+
h.value.should == 2
|
17
|
+
end
|
18
|
+
it 'is available via a module property and via mixin' do
|
19
|
+
Durations::h.should == h
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'q' do
|
24
|
+
it 'is 1 beat' do
|
25
|
+
q.value.should == 1
|
26
|
+
end
|
27
|
+
it 'is available via a module property and via mixin' do
|
28
|
+
Durations::q.should == q
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'i' do
|
33
|
+
it 'is 1/2 of a beat' do
|
34
|
+
i.value.should == 1.0/2
|
35
|
+
end
|
36
|
+
it 'is available via a module property and via mixin' do
|
37
|
+
Durations::i.should == i
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 's' do
|
42
|
+
it 'is 1/4 of a beat' do
|
43
|
+
s.value.should == 1.0/4
|
44
|
+
end
|
45
|
+
it 'is available via a module property and via mixin' do
|
46
|
+
Durations::s.should == s
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'r' do
|
51
|
+
it 'is 1/8 of a beat' do
|
52
|
+
r.value.should == 1.0/8
|
53
|
+
end
|
54
|
+
it 'is available via a module property and via mixin' do
|
55
|
+
Durations::r.should == r
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'x' do
|
60
|
+
it 'is 1/16 of a beat' do
|
61
|
+
x.value.should == 1.0/16
|
62
|
+
end
|
63
|
+
it 'is available via a module property and via mixin' do
|
64
|
+
Durations::x.should == x
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "DURATIONS" do
|
69
|
+
it "contains all Durations pseudo-constants" do
|
70
|
+
Durations::DURATIONS.should =~ [w, h, q, i, s, r, x]
|
71
|
+
end
|
72
|
+
|
73
|
+
it "is immutable" do
|
74
|
+
lambda{ Durations::DURATIONS << :something }.should raise_error
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "DURATION_NAMES" do
|
79
|
+
it "contains all Durations pseudo-constants names as strings" do
|
80
|
+
Durations::DURATION_NAMES.should =~ ['w', 'h', 'q', 'i', 's', 'r', 'x']
|
81
|
+
end
|
82
|
+
|
83
|
+
it "is immutable" do
|
84
|
+
lambda{ Durations::DURATION_NAMES << :something }.should raise_error
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Lang::Intensities do
|
4
|
+
|
5
|
+
describe 'ppp' do
|
6
|
+
it 'is equivalent to MIDI velocity 16' do
|
7
|
+
(ppp.value * 127).round.should == 16
|
8
|
+
end
|
9
|
+
it 'is available via a module property and via mixin' do
|
10
|
+
Intensities::ppp.should == ppp
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'pp' do
|
15
|
+
it 'is equivalent to MIDI velocity 32' do
|
16
|
+
(pp.value * 127).round.should == 32
|
17
|
+
end
|
18
|
+
it 'is available via a module property and via mixin' do
|
19
|
+
Intensities::pp.should == pp
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'p' do
|
24
|
+
it 'is equivalent to MIDI velocity 48' do
|
25
|
+
(p.value * 127).round.should == 48
|
26
|
+
end
|
27
|
+
it 'is available via a module property and via mixin' do
|
28
|
+
Intensities::p.should == p
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'mp' do
|
33
|
+
it 'is equivalent to MIDI velocity 64' do
|
34
|
+
(mp.value * 127).round.should == 64
|
35
|
+
end
|
36
|
+
it 'is available via a module property and via mixin' do
|
37
|
+
Intensities::mp.should == mp
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'mf' do
|
42
|
+
it 'is equivalent to MIDI velocity 79' do
|
43
|
+
(mf.value * 127).round.should == 79
|
44
|
+
end
|
45
|
+
it 'is available via a module property and via mixin' do
|
46
|
+
Intensities::mf.should == mf
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'o' do # AKA forte
|
51
|
+
it 'is equivalent to MIDI velocity 95' do
|
52
|
+
(o.value * 127).round.should == 95
|
53
|
+
end
|
54
|
+
it 'is available via a module property and via mixin' do
|
55
|
+
Intensities::o.should == o
|
56
|
+
end
|
57
|
+
it "does not overwrite the PitchClass constant 'F'" do
|
58
|
+
F.should be_a PitchClass
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'ff' do
|
63
|
+
it 'is equivalent to MIDI velocity 111' do
|
64
|
+
(ff.value * 127).round.should == 111
|
65
|
+
end
|
66
|
+
it 'is available via a module property and via mixin' do
|
67
|
+
Intensities::ff.should == ff
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'fff' do
|
72
|
+
it 'is equivalent to MIDI velocity 127' do
|
73
|
+
(fff.value * 127).round.should == 127
|
74
|
+
end
|
75
|
+
it 'is available via a module property and via mixin' do
|
76
|
+
Intensities::fff.should == fff
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "INTENSITIES" do
|
81
|
+
it "contains all Intensities pseudo-constants" do
|
82
|
+
Intensities::INTENSITIES.should =~ [ppp, pp, p, mp, mf, o, ff, fff]
|
83
|
+
end
|
84
|
+
|
85
|
+
it "is immutable" do
|
86
|
+
lambda{ Intensities::INTENSITIES << :something }.should raise_error
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "INTENSITY_NAMES" do
|
91
|
+
it "contains all Intensities pseudo-constants names as strings" do
|
92
|
+
Intensities::INTENSITY_NAMES.should =~ ['ppp', 'pp', 'p', 'mp', 'mf', 'o', 'ff', 'fff']
|
93
|
+
end
|
94
|
+
|
95
|
+
it "is immutable" do
|
96
|
+
lambda{ Intensities::INTENSITY_NAMES << :something }.should raise_error
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Lang::Intervals do
|
4
|
+
|
5
|
+
describe 'P1' do
|
6
|
+
it 'is 0 semitones' do
|
7
|
+
P1.should == Interval[0]
|
8
|
+
end
|
9
|
+
it 'is available via a module property and via mixin' do
|
10
|
+
Intervals::P1.should == P1
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'm2' do
|
15
|
+
it 'is 1 semitone' do
|
16
|
+
m2.should == Interval[1]
|
17
|
+
end
|
18
|
+
it 'is available via a module property and via mixin' do
|
19
|
+
Intervals::P1.should == P1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'M2' do
|
24
|
+
it 'is 2 semitones' do
|
25
|
+
M2.should == Interval[2]
|
26
|
+
end
|
27
|
+
it 'is available via a module property and via mixin' do
|
28
|
+
Intervals::P1.should == P1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'm3' do
|
33
|
+
it 'is 3 semitones' do
|
34
|
+
m3.should == Interval[3]
|
35
|
+
end
|
36
|
+
it 'is available via a module property and via mixin' do
|
37
|
+
Intervals::P1.should == P1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'M3' do
|
42
|
+
it 'is 4 semitones' do
|
43
|
+
M3.should == Interval[4]
|
44
|
+
end
|
45
|
+
it 'is available via a module property and via mixin' do
|
46
|
+
Intervals::P1.should == P1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'P4' do
|
51
|
+
it 'is 5 semitones' do
|
52
|
+
P4.should == Interval[5]
|
53
|
+
end
|
54
|
+
it 'is available via a module property and via mixin' do
|
55
|
+
Intervals::P1.should == P1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'TT' do
|
60
|
+
it 'is 6 semitones' do
|
61
|
+
TT.should == Interval[6]
|
62
|
+
end
|
63
|
+
it 'is available via a module property and via mixin' do
|
64
|
+
Intervals::P1.should == P1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'P5' do
|
69
|
+
it 'is 7 semitones' do
|
70
|
+
P5.should == Interval[7]
|
71
|
+
end
|
72
|
+
it 'is available via a module property and via mixin' do
|
73
|
+
Intervals::P1.should == P1
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'm6' do
|
78
|
+
it 'is 8 semitones' do
|
79
|
+
m6.should == Interval[8]
|
80
|
+
end
|
81
|
+
it 'is available via a module property and via mixin' do
|
82
|
+
Intervals::P1.should == P1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'M6' do
|
87
|
+
it 'is 9 semitones' do
|
88
|
+
M6.should == Interval[9]
|
89
|
+
end
|
90
|
+
it 'is available via a module property and via mixin' do
|
91
|
+
Intervals::P1.should == P1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe 'm7' do
|
96
|
+
it 'is 10 semitones' do
|
97
|
+
m7.should == Interval[10]
|
98
|
+
end
|
99
|
+
it 'is available via a module property and via mixin' do
|
100
|
+
Intervals::P1.should == P1
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'M7' do
|
105
|
+
it 'is 11 semitones' do
|
106
|
+
M7.should == Interval[11]
|
107
|
+
end
|
108
|
+
it 'is available via a module property and via mixin' do
|
109
|
+
Intervals::P1.should == P1
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'P8' do
|
114
|
+
it 'is 12 semitones' do
|
115
|
+
P8.should == Interval[12]
|
116
|
+
end
|
117
|
+
it 'is available via a module property and via mixin' do
|
118
|
+
Intervals::P1.should == P1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
describe "INTERVALS" do
|
124
|
+
it "contains all intervals constants/pseudo-constants" do
|
125
|
+
Intervals::INTERVALS.should =~ [P1, m2, M2, m3, M3, P4, TT, P5, m6, M6, m7, M7, P8]
|
126
|
+
end
|
127
|
+
|
128
|
+
it "is immutable" do
|
129
|
+
lambda{ Intervals::INTERVALS << :something }.should raise_error
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "INTERVAL_NAMES" do
|
134
|
+
it "contains all intervals constants/pseudo-constant names" do
|
135
|
+
Intervals::INTERVAL_NAMES.should =~ ['P1', 'm2', 'M2', 'm3', 'M3', 'P4', 'TT', 'P5', 'm6', 'M6', 'm7', 'M7', 'P8']
|
136
|
+
end
|
137
|
+
|
138
|
+
it "is immutable" do
|
139
|
+
lambda{ Intervals::INTERVAL_NAMES << :something }.should raise_error
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
@@ -0,0 +1,603 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Lang::Parser do
|
4
|
+
|
5
|
+
def chain *args
|
6
|
+
Patterns.Chain *args
|
7
|
+
end
|
8
|
+
|
9
|
+
def seq *args
|
10
|
+
Patterns.Sequence *args
|
11
|
+
end
|
12
|
+
|
13
|
+
def cycle *args
|
14
|
+
Patterns.Cycle *args
|
15
|
+
end
|
16
|
+
|
17
|
+
def choice *args
|
18
|
+
Patterns.Choice *args
|
19
|
+
end
|
20
|
+
|
21
|
+
def foreach *args
|
22
|
+
Patterns.ForEach *args
|
23
|
+
end
|
24
|
+
|
25
|
+
def var(name)
|
26
|
+
::MTK::Lang::Variable.new(name)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def parse(*args)
|
31
|
+
MTK::Lang::Parser.parse(*args)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
describe ".parse" do
|
36
|
+
it "can parse a single pitch class and play it" do
|
37
|
+
sequencer = MTK::Lang::Parser.parse('c')
|
38
|
+
timeline = sequencer.to_timeline
|
39
|
+
timeline.should == MTK::Events::Timeline.from_h({0 => MTK.Note(C4)})
|
40
|
+
end
|
41
|
+
|
42
|
+
context "default (root rule) behavior" do
|
43
|
+
it "parses a bare_sequencer" do
|
44
|
+
sequencer = parse('C:q:mp D4:ff A i:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp')
|
45
|
+
sequencer.should be_a Sequencers::Sequencer
|
46
|
+
sequencer.patterns.should == [seq( chain(C,q,mp), chain(D4,ff), A, chain(i,p), chain(Eb,pp), Bb7, chain(F2,h+q), chain(Gb4,mf,s), q, ppp )]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "parses a sequencer" do
|
50
|
+
sequencer = parse('( C:q:mp D4:ff A i:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp )')
|
51
|
+
sequencer.should be_a Sequencers::Sequencer
|
52
|
+
sequencer.patterns.should == [seq( chain(C,q,mp), chain(D4,ff), A, chain(i,p), chain(Eb,pp), Bb7, chain(F2,h+q), chain(Gb4,mf,s), q, ppp )]
|
53
|
+
end
|
54
|
+
|
55
|
+
it "parses a timeline" do
|
56
|
+
parse("
|
57
|
+
{
|
58
|
+
0 => C4:mp:q
|
59
|
+
1 => D4:o:h
|
60
|
+
}
|
61
|
+
").should == MTK::Events::Timeline.from_h({0 => chain(C4,mp,q), 1 => chain(D4,o,h)})
|
62
|
+
end
|
63
|
+
|
64
|
+
it "parses a chain of sequences" do
|
65
|
+
sequencer = parse("(C D E F G):(mp mf ff):(q h w)")
|
66
|
+
sequencer.should be_a Sequencers::Sequencer
|
67
|
+
sequencer.patterns.should == [ chain( seq(C,D,E,F,G), seq(mp,mf,ff), seq(q,h,w) ) ]
|
68
|
+
end
|
69
|
+
|
70
|
+
it "parses a chain of choices" do
|
71
|
+
sequencer = parse("<i|s>:<c|d|e>")
|
72
|
+
sequencer.patterns.should == [ chain( choice(i,s), choice(C,D,E) ) ]
|
73
|
+
end
|
74
|
+
|
75
|
+
it "parses a chain of choices" do
|
76
|
+
sequencer = parse("(<i|s>:<c|d|e>)&8")
|
77
|
+
sequencer.patterns.should == [ seq( chain( choice(i,s), choice(C,D,E) ), min_elements:8, max_elements:8 ) ]
|
78
|
+
end
|
79
|
+
|
80
|
+
it "parses the repetition of a basic note property" do
|
81
|
+
sequencer = parse("c*4")
|
82
|
+
sequencer.patterns.should == [ seq(C, max_cycles:4) ]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
context 'bare_sequencer rule (no curly braces)' do
|
88
|
+
it "parses a sequence of pitch classes" do
|
89
|
+
sequencer = parse('C D E F G', :bare_sequencer)
|
90
|
+
sequencer.should be_a Sequencers::Sequencer
|
91
|
+
sequencer.patterns.should == [seq(C, D, E, F, G)]
|
92
|
+
end
|
93
|
+
|
94
|
+
it "parses a sequence of pitches" do
|
95
|
+
sequencer = parse('C4 D4 E4 F4 G3', :bare_sequencer)
|
96
|
+
sequencer.should be_a Sequencers::Sequencer
|
97
|
+
sequencer.patterns.should == [seq(C4, D4, E4, F4, G3)]
|
98
|
+
end
|
99
|
+
|
100
|
+
it "parses a sequence of pitch classes + pitches" do
|
101
|
+
sequencer = parse('C4 D E3 F G5', :bare_sequencer)
|
102
|
+
sequencer.should be_a Sequencers::Sequencer
|
103
|
+
sequencer.patterns.should == [seq(C4, D, E3, F, G5)]
|
104
|
+
end
|
105
|
+
|
106
|
+
it "parses pitchclass:duration chains" do
|
107
|
+
sequencer = parse('C:q D:q E:i F:i G:h', :bare_sequencer)
|
108
|
+
sequencer.should be_a Sequencers::Sequencer
|
109
|
+
sequencer.patterns.should == [seq(chain(C,q), chain(D,q), chain(E,i), chain(F,i), chain(G,h))]
|
110
|
+
end
|
111
|
+
|
112
|
+
it "parses a mix of chained and unchained pitches, pitch classes, durations, and intensities" do
|
113
|
+
sequencer = parse('C:q:mp D4:ff A i:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp', :bare_sequencer)
|
114
|
+
sequencer.should be_a Sequencers::Sequencer
|
115
|
+
sequencer.patterns.should == [seq( chain(C,q,mp), chain(D4,ff), A, chain(i,p), chain(Eb,pp), Bb7, chain(F2,h+q), chain(Gb4,mf,s), q, ppp )]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
context 'sequencer rule' do
|
121
|
+
it "parses a sequence of pitch classes" do
|
122
|
+
sequencer = parse('{C D E F G}', :sequencer)
|
123
|
+
sequencer.should be_a Sequencers::Sequencer
|
124
|
+
sequencer.patterns.should == [seq(C, D, E, F, G)]
|
125
|
+
end
|
126
|
+
|
127
|
+
it "parses a sequence of pitches" do
|
128
|
+
sequencer = parse('{ C4 D4 E4 F4 G3}', :sequencer)
|
129
|
+
sequencer.should be_a Sequencers::Sequencer
|
130
|
+
sequencer.patterns.should == [seq(C4, D4, E4, F4, G3)]
|
131
|
+
end
|
132
|
+
|
133
|
+
it "parses a sequence of pitch classes + pitches" do
|
134
|
+
sequencer = parse('{C4 D E3 F G5 }', :sequencer)
|
135
|
+
sequencer.should be_a Sequencers::Sequencer
|
136
|
+
sequencer.patterns.should == [seq(C4, D, E3, F, G5)]
|
137
|
+
end
|
138
|
+
|
139
|
+
it "parses pitchclass:duration chains" do
|
140
|
+
sequencer = parse('{ C:q D:q E:i F:i G:h }', :sequencer)
|
141
|
+
sequencer.should be_a Sequencers::Sequencer
|
142
|
+
sequencer.patterns.should == [seq(chain(C,q), chain(D,q), chain(E,i), chain(F,i), chain(G,h))]
|
143
|
+
end
|
144
|
+
|
145
|
+
it "parses a mix of chained and unchained pitches, pitch classes, durations, and intensities" do
|
146
|
+
sequencer = parse('{ C:q:mp D4:ff A i:p Eb:pp Bb7 F2:h. F#4:mf:s q ppp }', :sequencer)
|
147
|
+
sequencer.should be_a Sequencers::Sequencer
|
148
|
+
sequencer.patterns.should == [seq( chain(C,q,mp), chain(D4,ff), A, chain(i,p), chain(Eb,pp), Bb7, chain(F2,h+q), chain(Gb4,mf,s), q, ppp )]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
context "timeline rule" do
|
154
|
+
it "parses a very simple Timeline" do
|
155
|
+
parse("{0 => C}", :timeline).should == MTK::Events::Timeline.from_h({0 => seq(C)})
|
156
|
+
end
|
157
|
+
|
158
|
+
it "parses a Timeline with one entry" do
|
159
|
+
parse("
|
160
|
+
{
|
161
|
+
0 => C4:mp:q
|
162
|
+
}
|
163
|
+
", :timeline).should == MTK::Events::Timeline.from_h({0 => chain(C4,mp,q)})
|
164
|
+
end
|
165
|
+
|
166
|
+
it "parses a Timeline with multiple entries" do
|
167
|
+
parse("
|
168
|
+
{
|
169
|
+
0 => C4:mp:q
|
170
|
+
1 => D4:o:h
|
171
|
+
}
|
172
|
+
", :timeline).should == MTK::Events::Timeline.from_h({0 => chain(C4,mp,q), 1 => chain(D4,o,h)})
|
173
|
+
end
|
174
|
+
|
175
|
+
#it "parses a Timeline containing a chord" do
|
176
|
+
# parse("
|
177
|
+
# {
|
178
|
+
# 0 => [C4 E4 G4]:fff:w
|
179
|
+
# }
|
180
|
+
# ", :timeline).should == Timeline.from_h({0 => chain(Chord(C4,E4,G4),fff,w)})
|
181
|
+
#end
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
context 'timepoint rule' do
|
186
|
+
it 'should evaluate a number followed by "=>" as the numerical value' do
|
187
|
+
parse('42 => ', :timepoint).should == 42
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
context "pattern rule" do
|
193
|
+
it 'parses bare sequences' do
|
194
|
+
parse("C4 D4 E4", :pattern).should == seq(C4, D4, E4)
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'parses sequences' do
|
198
|
+
parse("(C4 D4 E4)", :pattern).should == seq(C4, D4, E4)
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'parses chains and wraps them in a Sequence' do
|
202
|
+
# The wrapping behavior isn't completely desirable,
|
203
|
+
# but I'm having trouble making the grammar do exactly what I want in all scenarios, so this is a compromise
|
204
|
+
parse("C4:q:ff", :pattern).should == chain(C4, q, ff)
|
205
|
+
end
|
206
|
+
|
207
|
+
it "parses chains of sequencers" do
|
208
|
+
parse("(C D E F G):(mp mf ff):(q h w)", :pattern).should == chain( seq(C,D,E,F,G), seq(mp,mf,ff), seq(q,h,w) )
|
209
|
+
end
|
210
|
+
|
211
|
+
it "parses chains of sequencers with modifiers" do
|
212
|
+
pattern = parse('(C D E F)*3:(q h w)&2', :pattern)
|
213
|
+
pattern.should == chain( cycle(C,D,E,F, max_cycles:3), seq(q,h,w, min_elements:2, max_elements:2))
|
214
|
+
pattern.next
|
215
|
+
pattern.next
|
216
|
+
lambda{ pattern.next }.should raise_error StopIteration
|
217
|
+
end
|
218
|
+
|
219
|
+
it "parses chains of sequencers with modifiers" do
|
220
|
+
pattern = parse('(C D E F G):(q h w)&3', :pattern)
|
221
|
+
pattern.should == chain( seq(C,D,E,F,G), seq(q,h,w, min_elements:3, max_elements:3))
|
222
|
+
pattern.next
|
223
|
+
pattern.next
|
224
|
+
pattern.next
|
225
|
+
lambda{ pattern.next }.should raise_error StopIteration
|
226
|
+
end
|
227
|
+
|
228
|
+
it "ensures a single element is wrapped in a Pattern" do
|
229
|
+
parse("C", :pattern).should be_a ::MTK::Patterns::Pattern
|
230
|
+
end
|
231
|
+
|
232
|
+
it "parse 'recursively' nested constructs" do
|
233
|
+
parse('C <D|E <F|G F>> | A B:(q h <q|h:mp h:<mf|ff fff>>)', :pattern).should ==
|
234
|
+
choice(
|
235
|
+
seq(C, choice(D, seq(E, choice(F,seq(G,F))))),
|
236
|
+
seq(A, chain(B,seq(q, h, choice(q,seq(chain(h,mp),chain(h,choice(mf,seq(ff,fff))))))))
|
237
|
+
)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
context 'bare_choice rule (no parentheses)' do
|
243
|
+
it "parses a choice of simple elements" do
|
244
|
+
parse('C | D | E', :bare_choice).should == choice(C,D,E)
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'parses a choice of sequences (parentheses implied)' do
|
248
|
+
parse('C D E | D E F | E F', :bare_choice).should == choice(seq(C,D,E), seq(D,E,F), seq(E,F))
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'parses a choice of sequences and simple elements' do
|
252
|
+
parse('C D E | G | E F | A', :bare_choice).should == choice(seq(C,D,E), G, seq(E,F), A)
|
253
|
+
end
|
254
|
+
|
255
|
+
it "doesn't require spaces around the pipe characters"do
|
256
|
+
parse('C D E|G|E F', :bare_choice).should == choice(seq(C,D,E), G, seq(E,F))
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'parses choices containing chains' do
|
260
|
+
parse('C D:q | G | A:mp|E:fff:h F:s', :bare_choice).should ==
|
261
|
+
choice( seq(C,chain(D,q)), G, chain(A,mp), seq(chain(E,fff,h),chain(F,s)) )
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'parses choices containing nested choices' do
|
265
|
+
parse('C <D|E> | F | G', :bare_choice).should == choice(seq(C,choice(D,E)), F, G)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
context 'choice rule' do
|
271
|
+
it "parses a choice of simple elements" do
|
272
|
+
parse('<C | D | E>', :bare_choice).should == choice(C,D,E)
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'parses a choice of sequences (parentheses implied)' do
|
276
|
+
parse('< C D E | D E F | E F>', :bare_choice).should == choice(seq(C,D,E), seq(D,E,F), seq(E,F))
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'parses a choice of sequences and simple elements' do
|
280
|
+
parse('<C D E | G | E F | A >', :bare_choice).should == choice(seq(C,D,E), G, seq(E,F), A)
|
281
|
+
end
|
282
|
+
|
283
|
+
it "doesn't require spaces around the pipe characters"do
|
284
|
+
parse('< C D E|G|E F >', :bare_choice).should == choice(seq(C,D,E), G, seq(E,F))
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'parses choices containing chains' do
|
288
|
+
parse('<C D:q | G | A:mp|E:fff:h F:s>', :bare_choice).should ==
|
289
|
+
choice( seq(C,chain(D,q)), G, chain(A,mp), seq(chain(E,fff,h),chain(F,s)) )
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'parses choices containing nested choices' do
|
293
|
+
parse('< C <D|E> | F | G >', :bare_choice).should == choice(seq(C,choice(D,E)), F, G)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
context 'bare_sequence rule (no parentheses)' do
|
299
|
+
it "parses pitch sequences" do
|
300
|
+
parse("C4 D4 E4", :bare_sequence).should == seq(C4, D4, E4)
|
301
|
+
end
|
302
|
+
|
303
|
+
#it "parses pitch sequences with chords" do
|
304
|
+
# parse("C4 [D4 E4]", :bare_sequence).should == seq( C4, Chord(D4,E4) )
|
305
|
+
#end
|
306
|
+
|
307
|
+
it "parses pitch sequences with pitch classes" do
|
308
|
+
parse("C4 D E4", :bare_sequence).should == seq( C4, D, E4 )
|
309
|
+
end
|
310
|
+
|
311
|
+
it "parses pitch sequences with intervals" do
|
312
|
+
parse("C4 m2", :bare_sequence).should == seq( C4, m2 )
|
313
|
+
end
|
314
|
+
|
315
|
+
it "parses intensity sequences" do
|
316
|
+
parse("ppp mf mp ff", :bare_sequence).should == seq(ppp, mf, mp, ff)
|
317
|
+
end
|
318
|
+
|
319
|
+
it "parses duration sequences" do
|
320
|
+
parse("q i q. ht", :bare_sequence).should == seq(q, i, q*Rational(1.5), h*Rational(2,3))
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
|
325
|
+
context "sequence rule" do
|
326
|
+
it "parses pitch sequences" do
|
327
|
+
parse("(C4 D4 E4)", :sequence).should == seq(C4, D4, E4)
|
328
|
+
end
|
329
|
+
|
330
|
+
#it "parses pitch sequences with chords" do
|
331
|
+
# parse("( C4 [D4 E4])", :sequence).should == seq( C4, Chord(D4,E4) )
|
332
|
+
#end
|
333
|
+
|
334
|
+
it "parses pitch sequences with pitch classes" do
|
335
|
+
parse("(C4 D E4 )", :sequence).should == seq( C4, D, E4 )
|
336
|
+
end
|
337
|
+
|
338
|
+
it "parses pitch sequences with intervals" do
|
339
|
+
parse("( C4 m2 )", :sequence).should == seq( C4, m2 )
|
340
|
+
end
|
341
|
+
|
342
|
+
it "parses intensity sequences" do
|
343
|
+
parse("( ppp mf mp ff )", :sequence).should == seq(ppp, mf, mp, ff)
|
344
|
+
end
|
345
|
+
|
346
|
+
it "parses duration sequences" do
|
347
|
+
parse("(q i q. ht)", :sequence).should == seq(q, i, q*Rational(1.5), h*Rational(2,3))
|
348
|
+
end
|
349
|
+
|
350
|
+
it "parses sequences with a max_cycles modifier" do
|
351
|
+
sequence = parse("(C D)*2", :sequence)
|
352
|
+
sequence.should == Patterns.Cycle(C,D, max_cycles: 2)
|
353
|
+
end
|
354
|
+
|
355
|
+
it "parses sequences with a min+max_elements modifier" do
|
356
|
+
sequence = parse("(C D E)&2", :sequence)
|
357
|
+
sequence.should == Patterns.Cycle(C,D,E, min_elements: 2, max_elements: 2)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
|
362
|
+
context "foreach rule" do
|
363
|
+
it "parses a for each pattern with 2 subpatterns" do
|
364
|
+
foreach = parse('(C D)@(E F)', :foreach)
|
365
|
+
foreach.should == Patterns.ForEach(seq(C,D),seq(E,F))
|
366
|
+
end
|
367
|
+
|
368
|
+
it "parses a for each pattern with 3 subpatterns" do
|
369
|
+
foreach = parse('(C D)@(E F)@(G A B)', :foreach)
|
370
|
+
foreach.should == Patterns.ForEach(seq(C,D),seq(E,F),seq(G,A,B))
|
371
|
+
end
|
372
|
+
|
373
|
+
it "parses a for each pattern with '$' variables" do
|
374
|
+
foreach = parse('(C D)@(E F)@($ $$)', :foreach)
|
375
|
+
foreach.should == Patterns.ForEach(seq(C,D),seq(E,F),seq(var('$'),var('$$')))
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
|
380
|
+
context "chain rule" do
|
381
|
+
it "parses a basic chain of note properties" do
|
382
|
+
parse("G4:h.:ff", :chain).should == Patterns.Chain(G4,h+q,ff)
|
383
|
+
end
|
384
|
+
|
385
|
+
it "parses a chain of for each patterns" do
|
386
|
+
parse('(C D)@(E F):(G A)@(B C)', :chain).should == chain( foreach(seq(C,D),seq(E,F)), foreach(seq(G,A),seq(B,C)) )
|
387
|
+
end
|
388
|
+
|
389
|
+
it "parses chains of elements with max_cycles" do
|
390
|
+
parse('C*3:mp*4:q*5', :chain).should == chain( seq(C,max_cycles:3), seq(mp,max_cycles:4), seq(q,max_cycles:5))
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
|
395
|
+
context "chainable rule" do
|
396
|
+
it "parses a pitch" do
|
397
|
+
parse("C4", :chainable).should == C4
|
398
|
+
end
|
399
|
+
|
400
|
+
#it "parses a chord" do
|
401
|
+
# parse("[C4 D4]", :chainable).should == Chord(C4,D4)
|
402
|
+
#end
|
403
|
+
|
404
|
+
it "parses a pitch class" do
|
405
|
+
parse("C", :chainable).should == C
|
406
|
+
end
|
407
|
+
|
408
|
+
it "parses intervals" do
|
409
|
+
parse("m2", :chainable).should == m2
|
410
|
+
end
|
411
|
+
|
412
|
+
it "parses durations" do
|
413
|
+
parse("h", :chainable).should == h
|
414
|
+
end
|
415
|
+
|
416
|
+
it "parses rests" do
|
417
|
+
parse("-h", :chainable).should == -h
|
418
|
+
end
|
419
|
+
|
420
|
+
it "parses intensities" do
|
421
|
+
parse("ff", :chainable).should == ff
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
context 'element rule' do
|
426
|
+
it "parses the repetition of a basic note property as a sequence with a max_cycles option" do
|
427
|
+
sequence = parse("c*4", :element)
|
428
|
+
sequence.elements.should == [ C ]
|
429
|
+
sequence.max_cycles.should == 4
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
|
434
|
+
#context 'chord rule' do
|
435
|
+
# it "parses chords" do
|
436
|
+
# parse("[C4 E4 G4]", :chord).should == Chord(C4,E4,G4)
|
437
|
+
# end
|
438
|
+
#end
|
439
|
+
|
440
|
+
|
441
|
+
context 'pitch rule' do
|
442
|
+
it "parses pitches" do
|
443
|
+
for pitch_class_name in PitchClass::VALID_NAMES
|
444
|
+
pc = PitchClass[pitch_class_name]
|
445
|
+
for octave in -1..9
|
446
|
+
parse("#{pitch_class_name}#{octave}", :pitch).should == Pitch[pc,octave]
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
|
453
|
+
context 'pitch_class rule' do
|
454
|
+
it "parses pitch classes" do
|
455
|
+
for pitch_class_name in PitchClass::VALID_NAMES
|
456
|
+
parse(pitch_class_name, :pitch_class).should == PitchClass[pitch_class_name]
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
|
462
|
+
context 'interval rule' do
|
463
|
+
it "parses intervals" do
|
464
|
+
for interval_name in Interval::ALL_NAMES
|
465
|
+
parse(interval_name, :interval).should == Interval(interval_name)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
|
471
|
+
context 'intensity rule' do
|
472
|
+
it "parses intensities" do
|
473
|
+
for intensity_name in Intensity::NAMES
|
474
|
+
parse(intensity_name, :intensity).should == Intensity(intensity_name)
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
it "parses intensities with + and - modifiers" do
|
479
|
+
for intensity_name in Intensity::NAMES
|
480
|
+
name = "#{intensity_name}+"
|
481
|
+
parse(name, :intensity).should == Intensity(name)
|
482
|
+
name = "#{intensity_name}-"
|
483
|
+
parse(name, :intensity).should == Intensity(name)
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
|
489
|
+
context 'duration rule' do
|
490
|
+
it "parses durations" do
|
491
|
+
for duration in Durations::DURATION_NAMES
|
492
|
+
parse(duration, :duration).should == Duration(duration)
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
it "parses durations with . and t modifiers" do
|
497
|
+
for duration in Durations::DURATION_NAMES
|
498
|
+
name = "#{duration}."
|
499
|
+
parse(name, :duration).should == Duration(name)
|
500
|
+
name = "#{duration}t"
|
501
|
+
parse(name, :duration).should == Duration(name)
|
502
|
+
name = "#{duration}..t.t"
|
503
|
+
parse(name, :duration).should == Duration(name)
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
it "parses durations with - modifier (it parses rests)" do
|
508
|
+
for duration in Durations::DURATION_NAMES
|
509
|
+
parse("-#{duration}", :duration).should == -1 * Duration(duration)
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
it "parses durations with integer multipliers" do
|
514
|
+
Durations::DURATION_NAMES.each_with_index do |duration, index|
|
515
|
+
multiplier = index+5
|
516
|
+
parse("#{multiplier}#{duration}", :duration).should == multiplier * Duration(duration)
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
it "parses durations with float multipliers" do
|
521
|
+
Durations::DURATION_NAMES.each_with_index do |duration, index|
|
522
|
+
multiplier = (index+1)*1.123
|
523
|
+
parse("#{multiplier}#{duration}", :duration).should == multiplier * Duration(duration)
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
it "parses durations with float multipliers" do
|
528
|
+
Durations::DURATION_NAMES.each_with_index do |duration, index|
|
529
|
+
multiplier = Rational(index+1, index+2)
|
530
|
+
parse("#{multiplier}#{duration}", :duration).should == multiplier * Duration(duration)
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
end
|
535
|
+
|
536
|
+
|
537
|
+
context 'variable rule' do
|
538
|
+
it "parses the '$' variable" do
|
539
|
+
parse('$', :variable).should == var('$')
|
540
|
+
end
|
541
|
+
|
542
|
+
it "parses the '$$', '$$$', etc variables" do
|
543
|
+
parse('$$', :variable).should == var('$$')
|
544
|
+
parse('$$$', :variable).should == var('$$$')
|
545
|
+
parse('$$$$', :variable).should == var('$$$$')
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
|
550
|
+
context 'number rule' do
|
551
|
+
it "parses floats as numbers" do
|
552
|
+
parse("1.23", :number).should == 1.23
|
553
|
+
end
|
554
|
+
|
555
|
+
it "parses rationals as numbers" do
|
556
|
+
parse("12/34", :number).should == Rational(12,34)
|
557
|
+
end
|
558
|
+
|
559
|
+
it "parses ints as numbers" do
|
560
|
+
parse("123", :number).should == 123
|
561
|
+
end
|
562
|
+
|
563
|
+
end
|
564
|
+
|
565
|
+
context 'float rule' do
|
566
|
+
it "parses floats" do
|
567
|
+
parse("1.23", :float).should == 1.23
|
568
|
+
end
|
569
|
+
|
570
|
+
it "parses negative floats" do
|
571
|
+
parse("-1.23", :float).should == -1.23
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
context 'rational rule' do
|
576
|
+
it 'parses rationals' do
|
577
|
+
parse('12/13', :rational).should == Rational(12,13)
|
578
|
+
end
|
579
|
+
|
580
|
+
it 'parses negative rationals' do
|
581
|
+
parse('-12/13', :rational).should == -Rational(12,13)
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
|
586
|
+
context 'int rule' do
|
587
|
+
it "parses ints" do
|
588
|
+
parse("123", :int).should == 123
|
589
|
+
end
|
590
|
+
|
591
|
+
it "parses negative ints" do
|
592
|
+
parse("-123", :int).should == -123
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
|
597
|
+
context 'space rule' do
|
598
|
+
it "returns nil as the value for whitespace" do
|
599
|
+
parse(" \t\n", :space).should == nil
|
600
|
+
end
|
601
|
+
end
|
602
|
+
end
|
603
|
+
end
|