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.
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