alda-rb 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,313 @@
1
+ require 'set'
2
+
3
+ ##
4
+ # Including this module can make your class have the ability
5
+ # to have an event list.
6
+ # See docs below to get an overview of its functions.
7
+ module Alda::EventList
8
+
9
+ ##
10
+ # The array containing the events (Alda::Event objects),
11
+ # most of which are Alda::EventContainer objects.
12
+ attr_accessor :events
13
+
14
+ ##
15
+ # The set containing the available variable names.
16
+ attr_accessor :variables
17
+
18
+ def on_contained
19
+ super if defined? super
20
+ instance_eval &@block if @block
21
+ end
22
+
23
+ ##
24
+ # :call-seq:
25
+ # (some sugar) -> Alda::EventContainer
26
+ #
27
+ # Make the object have the ability to append its #events conveniently.
28
+ #
29
+ # Here is a list of sugar. When the name of a method meets certain
30
+ # condition, the method is regarded as an event appended to #events.
31
+ #
32
+ # 1. Ending with 2 underlines: set variable. See Alda::SetVariable.
33
+ #
34
+ # 2. Starting with 2 lowercase letters and
35
+ # ending with underline character: instrument. See Alda::Part.
36
+ #
37
+ # 3. Starting with 2 lowercase letters: inline lisp code,
38
+ # set variable, or get variable.
39
+ # One of the above three is chosen intelligently.
40
+ # See Alda::InlineLisp, Alda::SetVariable, Alda::GetVariable.
41
+ #
42
+ # 4. Starting with "t": CRAM. See Alda::Cram.
43
+ #
44
+ # 5. Starting with one of "a", "b", ..., "g": note. See Alda::Note.
45
+ #
46
+ # 6. Starting with "r": rest. See Alda::Rest.
47
+ #
48
+ # 7. "x": chord. See Alda::Chord.
49
+ #
50
+ # 8. "s": sequence. See Alda::Sequence.
51
+ #
52
+ # 9. Starting with "o": octave. See Alda::Octave.
53
+ #
54
+ # 10. Starting with "v": voice. See Alda::Voice.
55
+ #
56
+ # 11. Starting with "__" (2 underlines): at marker. See Alda::AtMarker.
57
+ #
58
+ # 12. Starting with "_" (underline) and ending with "_" (underline):
59
+ # lisp identifier. See Alda::LispIdentifier.
60
+ #
61
+ # 13. Starting with "_" (underline): marker. See Alda::Marker.
62
+ #
63
+ # All the appended events are contained in an Alda::EventContainer object,
64
+ # which is to be returned.
65
+ #
66
+ # These sugars forms a DSL. See ::new for examples.
67
+ def method_missing name, *args, &block
68
+ if @parent&.respond_to? name, true
69
+ return @parent.__send__ name, *args, &block
70
+ end
71
+ sequence_sugar = ->event do
72
+ if args.size == 1
73
+ Alda::Sequence.join event, args.first.tap(&:detach_from_parent)
74
+ else
75
+ event
76
+ end
77
+ end
78
+ case
79
+ when /\A(?<head>[a-z][a-z].*)__\z/ =~ name
80
+ Alda::SetVariable.new head, *args, &block
81
+ when /\A(?<part>[a-z][a-z].*)_\z/ =~ name
82
+ if args.first.is_a? String
83
+ Alda::Part.new [part], args.first
84
+ else
85
+ sequence_sugar.(Alda::Part.new [part])
86
+ end
87
+ when /\A[a-z][a-z].*\z/ =~ name
88
+ arg = args.first
89
+ if block || !has_variable?(name) && args.size == 1 && arg.is_a?(Alda::Event) &&
90
+ !arg.is_a?(Alda::InlineLisp) && !arg.is_a?(Alda::LispIdentifier) &&
91
+ !(arg.is_a?(Alda::EventContainer) &&
92
+ (arg.event.is_a?(Alda::InlineLisp) || arg.event.is_a?(Alda::LispIdentifier)))
93
+ Alda::SetVariable.new name, *args, &block
94
+ elsif has_variable?(name) && (args.empty? || args.size == 1 && arg.is_a?(Alda::Event))
95
+ sequence_sugar.(Alda::GetVariable.new name)
96
+ else
97
+ Alda::InlineLisp.new name, *args
98
+ end
99
+ when /\At(?<duration>.*)\z/ =~ name
100
+ Alda::Cram.new duration, &block
101
+ when /\A(?<pitch>[a-g])(?<duration>.*)\z/ =~ name
102
+ sequence_sugar.(Alda::Note.new pitch, duration)
103
+ when /\Ar(?<duration>.*)\z/ =~ name
104
+ sequence_sugar.(Alda::Rest.new duration)
105
+ when /\Ax\z/ =~ name
106
+ Alda::Chord.new &block
107
+ when /\As\z/ =~ name
108
+ Alda::Sequence.new *args, &block
109
+ when /\Ao!\z/ =~ name
110
+ sequence_sugar.(Alda::Octave.new('').tap { _1.up_or_down = 1})
111
+ when /\Ao\?\z/ =~ name
112
+ sequence_sugar.(Alda::Octave.new('').tap { _1.up_or_down = -1})
113
+ when /\Ao(?<num>\d*)\z/ =~ name
114
+ sequence_sugar.(Alda::Octave.new num)
115
+ when /\Av(?<num>\d+)\z/ =~ name
116
+ sequence_sugar.(Alda::Voice.new num)
117
+ when /\A__(?<head>.+)\z/ =~ name
118
+ sequence_sugar.(Alda::AtMarker.new head)
119
+ when /\A_(?<head>.+)_\z/ =~ name
120
+ sequence_sugar.(Alda::LispIdentifier.new head)
121
+ when /\A_(?<head>.+)\z/ =~ name
122
+ sequence_sugar.(Alda::Marker.new head)
123
+ else
124
+ super
125
+ end.then do |event|
126
+ Alda::EventContainer.new event, self
127
+ end.tap { @events.push _1 }
128
+ end
129
+
130
+ ##
131
+ # :call-seq:
132
+ # has_variable?(name) -> true or false
133
+ #
134
+ # Whether there is a previously declared alda variable
135
+ # whose name is specified by +name+.
136
+ #
137
+ # Searches variables in #parent.
138
+ def has_variable? name
139
+ @variables.include?(name) || !!@parent&.has_variable?(name)
140
+ end
141
+
142
+ ##
143
+ # :call-seq:
144
+ # import(event_list) -> nil
145
+ #
146
+ # Append the events of another Alda::EventList object here.
147
+ # This method covers the disadvantage of alda's being unable to
148
+ # import scores from other files.
149
+ # See https://github.com/alda-lang/alda-core/issues/8.
150
+ def import event_list
151
+ @events.concat event_list.events
152
+ nil
153
+ end
154
+
155
+ ##
156
+ # :call-seq:
157
+ # new(&block) -> Alda::EventList
158
+ #
159
+ # +block+ is to be passed with the Alda::EventList object as +self+.
160
+ #
161
+ # Note that +block+ is not called immediately.
162
+ # It is instead called in #on_contained.
163
+ # Specially, Alda::Score::new calls #on_contained.
164
+ #
165
+ # Alda::Score.new do
166
+ # tempo! 108 # inline lisp
167
+ # piano_ # piano part
168
+ # o4 # octave 4
169
+ # c8; d; e; f # notes
170
+ # g4 g a f g e f d e c # a sequence
171
+ # d4_8 # cannot have '~', use '_' instead
172
+ # o3 b8 o4 c2 # a sequence
173
+ # end
174
+ # # => #<Alda::Score:0x... @events=[...]>
175
+ #
176
+ # For a list of sugars, see #method_missing.
177
+ def initialize &block
178
+ @events ||= []
179
+ @variables ||= Set.new
180
+ @block ||= block
181
+ end
182
+
183
+ ##
184
+ # :call-seq:
185
+ # to_a -> Array
186
+ #
187
+ # Same as #events.
188
+ def to_a
189
+ @events
190
+ end
191
+
192
+ ##
193
+ # :call-seq:
194
+ # events_alda_codes(delimiter=" ") -> String
195
+ #
196
+ # Join the alda codes of #events with a specified delimiter.
197
+ # Returns a string representing the result.
198
+ def events_alda_codes delimiter = ' '
199
+ @events.map(&:to_alda_code).join delimiter
200
+ end
201
+ end
202
+
203
+ ##
204
+ # Includes Alda::EventList and provides methods to #play, #parse, or #export.
205
+ class Alda::Score
206
+ include Alda::EventList
207
+
208
+ ##
209
+ # :call-seq:
210
+ # play(**opts) -> String
211
+ #
212
+ # Plays the score.
213
+ #
214
+ # Returns the command line output of the +alda+ command.
215
+ #
216
+ # Run command <tt>alda help</tt> to see available options
217
+ # that can be specified in +opts+.
218
+ #
219
+ # Alda::Score.new { piano_; c; d; e }.play
220
+ # # => "[27713] Parsing/evaluating...\n[27713] Playing...\n"
221
+ # # (and plays the sound)
222
+ # Alda::Score.new { piano_; c; d; e }.play from: 1
223
+ # # (plays only an E note)
224
+ def play **opts
225
+ Alda.play code: self, **opts
226
+ end
227
+
228
+ ##
229
+ # :call-seq:
230
+ # parse(**opts) -> String
231
+ #
232
+ # Parses the score.
233
+ #
234
+ # Returns the JSON string of the parse result.
235
+ #
236
+ # Run command <tt>alda help</tt> to see available options
237
+ # that can be specified in +opts+.
238
+ #
239
+ # Alda::Score.new { piano_; c }.parse output: :events
240
+ # # => "[{\"event-type\":...}]\n"
241
+ def parse **opts
242
+ Alda.parse code: self, **opts
243
+ end
244
+
245
+ ##
246
+ # :call-seq:
247
+ # export(**opts) -> String
248
+ #
249
+ # Exports the score.
250
+ #
251
+ # Returns the command line output of the +alda+ command.
252
+ #
253
+ # Run command <tt>alda help</tt> to see available options
254
+ # that can be specified in +opts+.
255
+ #
256
+ # Alda::Score.new { piano_; c }.export output: 'temp.mid'
257
+ # # (outputs a midi file called temp.mid)
258
+ def export **opts
259
+ Alda.export code: self, **opts
260
+ end
261
+
262
+ ##
263
+ # :call-seq:
264
+ # save(filename) -> nil
265
+ #
266
+ # Saves the alda codes into a file.
267
+ def save filename
268
+ File.open(filename, 'w') { _1.puts to_s }
269
+ end
270
+
271
+ ##
272
+ # :call-seq:
273
+ # load(filename) -> Alda::InlineLisp
274
+ #
275
+ # Loads alda codes from a file.
276
+ #
277
+ # Actually appends a Alda::InlineLisp event of +alda-code+ lisp call.
278
+ def load filename
279
+ event = Alda::InlineLisp.new :alda_code, File.read(filename)
280
+ @events.push event
281
+ event
282
+ end
283
+
284
+ ##
285
+ # :call-seq:
286
+ # to_s() -> String
287
+ #
288
+ # Returns a String containing the alda codes representing the score.
289
+ def to_s
290
+ events_alda_codes
291
+ end
292
+
293
+ ##
294
+ # :call-seq:
295
+ # new(&block) -> Alda::Score
296
+ #
297
+ # Creates an Alda::Score.
298
+ def initialize(...)
299
+ super
300
+ on_contained
301
+ end
302
+
303
+ ##
304
+ # :call-seq:
305
+ # clear() -> nil
306
+ #
307
+ # Clears all the events and variables.
308
+ def clear
309
+ @events.clear
310
+ @variables.clear
311
+ nil
312
+ end
313
+ end
@@ -0,0 +1,168 @@
1
+ require 'stringio'
2
+
3
+ ##
4
+ class Array
5
+
6
+ ##
7
+ # See Alda::Event#to_alda_code.
8
+ def to_alda_code
9
+ "[#{map(&:to_alda_code).join ' '}]"
10
+ end
11
+
12
+ ##
13
+ # See Alda::Event#detach_from_parent.
14
+ def detach_from_parent
15
+ reverse_each &:detach_from_parent
16
+ end
17
+ end
18
+
19
+ ##
20
+ class Hash
21
+
22
+ ##
23
+ # See Alda::Event#to_alda_code.
24
+ def to_alda_code
25
+ "[#{map(&:to_alda_code).join ' '}]"
26
+ end
27
+
28
+ ##
29
+ # See Alda::Event#detach_from_parent.
30
+ def detach_from_parent
31
+ each.reverse_each &:detach_from_parent
32
+ end
33
+ end
34
+
35
+ ##
36
+ class String
37
+
38
+ ##
39
+ # See Alda::Event#to_alda_code.
40
+ def to_alda_code
41
+ dump
42
+ end
43
+
44
+ ##
45
+ # See Alda::Event#detach_from_parent.
46
+ def detach_from_parent
47
+ end
48
+ end
49
+
50
+ ##
51
+ class Symbol
52
+
53
+ ##
54
+ # See Alda::Event#to_alda_code.
55
+ def to_alda_code
56
+ ?: + to_s
57
+ end
58
+
59
+ ##
60
+ # See Alda::Event#detach_from_parent.
61
+ def detach_from_parent
62
+ end
63
+ end
64
+
65
+ ##
66
+ class Numeric
67
+
68
+ ##
69
+ # See Alda::Event#to_alda_code.
70
+ def to_alda_code
71
+ inspect
72
+ end
73
+
74
+ ##
75
+ # See Alda::Event#detach_from_parent.
76
+ def detach_from_parent
77
+ end
78
+ end
79
+
80
+ ##
81
+ class Range
82
+
83
+ ##
84
+ # See Alda::Event#to_alda_code.
85
+ def to_alda_code
86
+ "#{first}-#{last}"
87
+ end
88
+
89
+ ##
90
+ # See Alda::Event#detach_from_parent.
91
+ def detach_from_parent
92
+ end
93
+ end
94
+
95
+ ##
96
+ class TrueClass
97
+
98
+ ##
99
+ # See Alda::Event#to_alda_code.
100
+ def to_alda_code
101
+ 'true'
102
+ end
103
+
104
+ ##
105
+ # See Alda::Event#detach_from_parent.
106
+ def detach_from_parent
107
+ end
108
+ end
109
+
110
+ ##
111
+ class FalseClass
112
+
113
+ ##
114
+ # See Alda::Event#to_alda_code.
115
+ def to_alda_code
116
+ 'false'
117
+ end
118
+
119
+ ##
120
+ # See Alda::Event#detach_from_parent.
121
+ def detach_from_parent
122
+ end
123
+ end
124
+
125
+ ##
126
+ class NilClass
127
+
128
+ ##
129
+ # See Alda::Event#to_alda_code.
130
+ def to_alda_code
131
+ 'nil'
132
+ end
133
+
134
+ ##
135
+ # See Alda::Event#detach_from_parent.
136
+ def detach_from_parent
137
+ end
138
+ end
139
+
140
+ ##
141
+ class Proc
142
+
143
+ ##
144
+ # :call-seq:
145
+ # proc * n -> n
146
+ #
147
+ # Runs +self+ for +n+ times.
148
+ def * n
149
+ if !lambda? || arity == 1
150
+ n.times &self
151
+ else
152
+ n.times { self.() }
153
+ end
154
+ end
155
+ end
156
+
157
+ ##
158
+ class StringIO
159
+
160
+ ##
161
+ # :call-seq:
162
+ # to_s() -> String
163
+ #
164
+ # Equivalent to <tt>string</tt>.
165
+ def to_s
166
+ string
167
+ end
168
+ end