jmtk 0.0.3.3-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/.yardopts +10 -0
  2. data/DEVELOPMENT_NOTES.md +115 -0
  3. data/INTRO.md +129 -0
  4. data/LICENSE.txt +27 -0
  5. data/README.md +50 -0
  6. data/Rakefile +102 -0
  7. data/bin/jmtk +250 -0
  8. data/bin/mtk +250 -0
  9. data/examples/crescendo.rb +20 -0
  10. data/examples/drum_pattern.rb +23 -0
  11. data/examples/dynamic_pattern.rb +36 -0
  12. data/examples/gets_and_play.rb +27 -0
  13. data/examples/notation.rb +22 -0
  14. data/examples/play_midi.rb +17 -0
  15. data/examples/print_midi.rb +13 -0
  16. data/examples/random_tone_row.rb +18 -0
  17. data/examples/syntax_to_midi.rb +28 -0
  18. data/examples/test_output.rb +7 -0
  19. data/examples/tone_row_melody.rb +23 -0
  20. data/lib/mtk.rb +76 -0
  21. data/lib/mtk/core/duration.rb +213 -0
  22. data/lib/mtk/core/intensity.rb +158 -0
  23. data/lib/mtk/core/interval.rb +157 -0
  24. data/lib/mtk/core/pitch.rb +154 -0
  25. data/lib/mtk/core/pitch_class.rb +194 -0
  26. data/lib/mtk/events/event.rb +119 -0
  27. data/lib/mtk/events/note.rb +112 -0
  28. data/lib/mtk/events/parameter.rb +54 -0
  29. data/lib/mtk/events/timeline.rb +232 -0
  30. data/lib/mtk/groups/chord.rb +56 -0
  31. data/lib/mtk/groups/collection.rb +196 -0
  32. data/lib/mtk/groups/melody.rb +96 -0
  33. data/lib/mtk/groups/pitch_class_set.rb +163 -0
  34. data/lib/mtk/groups/pitch_collection.rb +23 -0
  35. data/lib/mtk/io/dls_synth_device.rb +146 -0
  36. data/lib/mtk/io/dls_synth_output.rb +62 -0
  37. data/lib/mtk/io/jsound_input.rb +87 -0
  38. data/lib/mtk/io/jsound_output.rb +82 -0
  39. data/lib/mtk/io/midi_file.rb +209 -0
  40. data/lib/mtk/io/midi_input.rb +97 -0
  41. data/lib/mtk/io/midi_output.rb +195 -0
  42. data/lib/mtk/io/notation.rb +162 -0
  43. data/lib/mtk/io/unimidi_input.rb +117 -0
  44. data/lib/mtk/io/unimidi_output.rb +140 -0
  45. data/lib/mtk/lang/durations.rb +57 -0
  46. data/lib/mtk/lang/intensities.rb +61 -0
  47. data/lib/mtk/lang/intervals.rb +73 -0
  48. data/lib/mtk/lang/mtk_grammar.citrus +237 -0
  49. data/lib/mtk/lang/parser.rb +29 -0
  50. data/lib/mtk/lang/pitch_classes.rb +29 -0
  51. data/lib/mtk/lang/pitches.rb +52 -0
  52. data/lib/mtk/lang/pseudo_constants.rb +26 -0
  53. data/lib/mtk/lang/variable.rb +32 -0
  54. data/lib/mtk/numeric_extensions.rb +66 -0
  55. data/lib/mtk/patterns/chain.rb +49 -0
  56. data/lib/mtk/patterns/choice.rb +43 -0
  57. data/lib/mtk/patterns/cycle.rb +18 -0
  58. data/lib/mtk/patterns/for_each.rb +71 -0
  59. data/lib/mtk/patterns/function.rb +39 -0
  60. data/lib/mtk/patterns/lines.rb +54 -0
  61. data/lib/mtk/patterns/palindrome.rb +45 -0
  62. data/lib/mtk/patterns/pattern.rb +171 -0
  63. data/lib/mtk/patterns/sequence.rb +20 -0
  64. data/lib/mtk/sequencers/event_builder.rb +132 -0
  65. data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
  66. data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
  67. data/lib/mtk/sequencers/sequencer.rb +111 -0
  68. data/lib/mtk/sequencers/step_sequencer.rb +26 -0
  69. data/spec/mtk/core/duration_spec.rb +372 -0
  70. data/spec/mtk/core/intensity_spec.rb +289 -0
  71. data/spec/mtk/core/interval_spec.rb +265 -0
  72. data/spec/mtk/core/pitch_class_spec.rb +343 -0
  73. data/spec/mtk/core/pitch_spec.rb +297 -0
  74. data/spec/mtk/events/event_spec.rb +234 -0
  75. data/spec/mtk/events/note_spec.rb +174 -0
  76. data/spec/mtk/events/parameter_spec.rb +220 -0
  77. data/spec/mtk/events/timeline_spec.rb +430 -0
  78. data/spec/mtk/groups/chord_spec.rb +85 -0
  79. data/spec/mtk/groups/collection_spec.rb +374 -0
  80. data/spec/mtk/groups/melody_spec.rb +225 -0
  81. data/spec/mtk/groups/pitch_class_set_spec.rb +340 -0
  82. data/spec/mtk/io/midi_file_spec.rb +243 -0
  83. data/spec/mtk/io/midi_output_spec.rb +102 -0
  84. data/spec/mtk/lang/durations_spec.rb +89 -0
  85. data/spec/mtk/lang/intensities_spec.rb +101 -0
  86. data/spec/mtk/lang/intervals_spec.rb +143 -0
  87. data/spec/mtk/lang/parser_spec.rb +603 -0
  88. data/spec/mtk/lang/pitch_classes_spec.rb +62 -0
  89. data/spec/mtk/lang/pitches_spec.rb +56 -0
  90. data/spec/mtk/lang/pseudo_constants_spec.rb +20 -0
  91. data/spec/mtk/lang/variable_spec.rb +52 -0
  92. data/spec/mtk/numeric_extensions_spec.rb +83 -0
  93. data/spec/mtk/patterns/chain_spec.rb +110 -0
  94. data/spec/mtk/patterns/choice_spec.rb +97 -0
  95. data/spec/mtk/patterns/cycle_spec.rb +123 -0
  96. data/spec/mtk/patterns/for_each_spec.rb +136 -0
  97. data/spec/mtk/patterns/function_spec.rb +120 -0
  98. data/spec/mtk/patterns/lines_spec.rb +77 -0
  99. data/spec/mtk/patterns/palindrome_spec.rb +108 -0
  100. data/spec/mtk/patterns/pattern_spec.rb +132 -0
  101. data/spec/mtk/patterns/sequence_spec.rb +203 -0
  102. data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
  103. data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
  104. data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
  105. data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
  106. data/spec/mtk/sequencers/step_sequencer_spec.rb +93 -0
  107. data/spec/spec_coverage.rb +2 -0
  108. data/spec/spec_helper.rb +12 -0
  109. data/spec/test.mid +0 -0
  110. 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