alda-rb 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,139 @@
1
+ module Kernel
2
+ ##
3
+ # :call-seq:
4
+ # alda(*args) -> true or false
5
+ #
6
+ # Runs the alda command.
7
+ # Does not capture output.
8
+ #
9
+ # alda 'version'
10
+ # alda 'play', '-c', 'piano: a'
11
+ # alda 'repl'
12
+ #
13
+ # Returns whether the exit status is +0+.
14
+ def alda *args
15
+ system Alda.executable, *args
16
+ end
17
+ end
18
+
19
+ module Alda
20
+
21
+ ##
22
+ # The array of available subcommands of alda executable.
23
+ #
24
+ # Alda is able to invoke +alda+ at the command line.
25
+ # The subcommand is the name of the method invoked upon Alda.
26
+ #
27
+ # The keyword arguments are interpreted as the subcommand options.
28
+ # To specify the command options, use ::[].
29
+ #
30
+ # The return value is the string output by the command in STDOUT.
31
+ #
32
+ # If the exit code is nonzero, an Alda::CommandLineError is raised.
33
+ #
34
+ # Alda.version
35
+ # # => "Client version: 1.4.0\nServer version: [27713] 1.4.0\n"
36
+ # Alda.parse code: 'bassoon: o3 c'
37
+ # # => "{\"chord-mode\":false,\"current-instruments\":...}\n"
38
+ #
39
+ # The available commands are: +help+, +update+, +repl+, +up+,
40
+ # +start_server+, +init+, +down+, +stop_server+, +downup+, +restart_server+,
41
+ # +list+, +status+, +version+, +play+, +stop+, +parse+, +instruments+, and
42
+ # +export+.
43
+ COMMANDS = %i[
44
+ help update repl up start_server init down stop_server
45
+ downup restart_server list status version play stop parse
46
+ instruments export
47
+ ].freeze
48
+
49
+ COMMANDS.each do |command|
50
+ define_method command do |*args, **opts|
51
+ block = ->key, val do
52
+ next unless val
53
+ args.push "--#{key.to_s.tr ?_, ?-}"
54
+ args.push val.to_s unless val == true
55
+ end
56
+ # executable
57
+ args.unshift Alda.executable
58
+ args.map! &:to_s
59
+ # options
60
+ Alda.options.each &block
61
+ # subcommand
62
+ args.push command.to_s
63
+ # subcommand options
64
+ opts.each &block
65
+ # subprocess
66
+ IO.popen(args, &:read).tap do
67
+ raise CommandLineError.new $?, _1 if $?.exitstatus.nonzero?
68
+ end
69
+ end
70
+ end
71
+
72
+ class << self
73
+
74
+ ##
75
+ # The path to the +alda+ executable.
76
+ #
77
+ # The default value is <tt>"alda"</tt>,
78
+ # which will depend on your +PATH+.
79
+ attr_accessor :executable
80
+
81
+ ##
82
+ # The commandline options set using ::[].
83
+ # Not the subcommand options.
84
+ # Clear it using ::clear_options.
85
+ attr_reader :options
86
+
87
+ ##
88
+ # :call-seq:
89
+ # Alda[**opts] -> self
90
+ #
91
+ # Sets the options of alda command.
92
+ # Not the subcommand options.
93
+ #
94
+ # Alda[port: 1108].up # => "[1108] ..."
95
+ # Alda.status # => "[1108] ..."
96
+ #
97
+ # Further set options will be merged.
98
+ # The options can be seen by ::options.
99
+ # To clear them, use ::clear_options.
100
+ def [] **opts
101
+ @options.merge! opts
102
+ self
103
+ end
104
+
105
+ ##
106
+ # :call-seq:
107
+ # clear_options() -> nil
108
+ #
109
+ # Clears the command line options.
110
+ # Makes ::options an empty Array.
111
+ def clear_options
112
+ @options.clear
113
+ end
114
+ end
115
+
116
+ @executable = 'alda'
117
+ @options = {}
118
+
119
+ ##
120
+ # :call-seq:
121
+ # up?() -> true or false
122
+ #
123
+ # Whether the alda server is up.
124
+ def up?
125
+ status.include? 'up'
126
+ end
127
+
128
+ ##
129
+ # :call-seq:
130
+ # down? -> true or false
131
+ #
132
+ # Whether the alda server is down.
133
+ def down?
134
+ status.include? 'down'
135
+ end
136
+
137
+ module_function :up?, :down?, *COMMANDS
138
+
139
+ end
@@ -0,0 +1,80 @@
1
+ ##
2
+ # The error is raised when +alda+ command exits with nonzero status.
3
+ class Alda::CommandLineError < StandardError
4
+
5
+ ##
6
+ # The <tt>Process::Status</tt> object representing the status of
7
+ # the process that runs +alda+ command.
8
+ attr_reader :status
9
+
10
+ ##
11
+ # The port on which the problematic alda server runs.
12
+ #
13
+ # begin
14
+ # Alda[port: 1108].play code: 'y'
15
+ # rescue CommandLineError => e
16
+ # e.port # => 1108
17
+ # end
18
+ attr_reader :port
19
+
20
+ ##
21
+ # :call-seq:
22
+ # new(status, msg=nil) -> Alda::CommandLineError
23
+ #
24
+ # Create a Alda::CommandLineError object.
25
+ # +status+ is the status of the process running +alda+ command.
26
+ # +msg+ is output of +alda+ command. port# info is extracted from +msg+.
27
+ def initialize status, msg = nil
28
+ if match = msg&.match(/^\[(?<port>\d+)\]\sERROR\s(?<message>.*)$/)
29
+ super match[:message]
30
+ @port = match[:port].to_i
31
+ else
32
+ super msg
33
+ @port = nil
34
+ end
35
+ @status = status
36
+ end
37
+ end
38
+
39
+ ##
40
+ # This error is raised when one tries to
41
+ # append events in an Alda::EventList in a wrong order.
42
+ #
43
+ # Alda::Score.new do
44
+ # motif = f4 f e e d d c2
45
+ # g4 f e d c2 # It commented out, error will not occur
46
+ # c4 c g g a a g2 motif # (OrderError)
47
+ # end
48
+ class Alda::OrderError < StandardError
49
+
50
+ ##
51
+ # The expected element gotten if it is of the correct order.
52
+ #
53
+ # See #got
54
+ #
55
+ # Alda::Score.new do
56
+ # motif = f4 f e e d d c2
57
+ # g4 f e d c2
58
+ # p @events.size # => 2
59
+ # c4 c g g a a g2 motif
60
+ # rescue OrderError => e
61
+ # p @events.size # => 1
62
+ # p e.expected # => #<Alda::EventContainer:...>
63
+ # p e.got # => #<Alda::EventContainer:...>
64
+ # end
65
+ attr_reader :expected
66
+
67
+ ##
68
+ # The actually gotten element.
69
+ # For an example, see #expected.
70
+ attr_reader :got
71
+
72
+ ##
73
+ # :call-seq:
74
+ # new(expected, got) -> Alda::OrderError
75
+ def initialize expected, got
76
+ super 'events are out of order'
77
+ @expected = expected
78
+ @got = got
79
+ end
80
+ end
@@ -0,0 +1,983 @@
1
+ ##
2
+ # The class of elements of Alda::EventList#events.
3
+ class Alda::Event
4
+
5
+ ##
6
+ # The Alda::EventList object that contains it.
7
+ #
8
+ # Note that it may not be directly contained, but with an Alda::EventContainer
9
+ # object in the middle.
10
+ attr_accessor :parent
11
+
12
+ ##
13
+ # The Alda::EventContainer object that contains it.
14
+ # It may be +nil+ if there is not a container containing it,
15
+ # especially probably when it itself is an Alda::EventContainer.
16
+ attr_accessor :container
17
+
18
+ ##
19
+ # The callback invoked when it is contained in an Alda::EventContainer.
20
+ # It is overridden in Alda::InlineLisp and Alda::EventList.
21
+ # It is called in Alda::EventContainer#on_containing.
22
+ #
23
+ # class Alda::Note
24
+ # def on_contained
25
+ # super
26
+ # puts 'a note contained'
27
+ # end
28
+ # end
29
+ # Alda::Score.new { c } # => outputs "a note contained"
30
+ def on_contained
31
+ end
32
+
33
+ ##
34
+ # :call-seq:
35
+ # to_alda_code() -> String
36
+ #
37
+ # Converts to alda code. To be overridden in subclasses.
38
+ def to_alda_code
39
+ ''
40
+ end
41
+
42
+ ##
43
+ # Delete itself from its #parent.
44
+ # If it is not at its #parent's end, raises Alda::OrderError.
45
+ #
46
+ # Here is a list of cases where the method is invoked:
47
+ #
48
+ # 1. Using the sequence sugar when operating an Alda::EventList.
49
+ #
50
+ # 2. Using Alda::EventContainer#/ to create chords or
51
+ # parts of multiple instruments.
52
+ #
53
+ # 3. Using dot accessor of Alda::Part. See Alda::Part#method_missing.
54
+ #
55
+ # 4. Using the inline lisp sugar. See Alda::InlineLisp.
56
+ #
57
+ # This method needs invoking in these cases because
58
+ # if an event is created using Alda::EventList sugars
59
+ # (see Alda::EventList#method_missing), it is automatically
60
+ # pushed to its #parent.
61
+ # However, the cases above requires the event be contained
62
+ # in another object.
63
+ def detach_from_parent
64
+ if @parent && self != (got = @parent.events.pop)
65
+ raise Alda::OrderError.new self, got
66
+ end
67
+ end
68
+ end
69
+
70
+ ##
71
+ # The class for objects containing an event.
72
+ #
73
+ # Alda::EventContainer objects are literally everywhere
74
+ # if you are a heavy user of event list sugars.
75
+ # See Alda::EventList#method_missing.
76
+ class Alda::EventContainer < Alda::Event
77
+
78
+ ##
79
+ # The contained Alda::Event object.
80
+ #
81
+ # Alda::Score.new do
82
+ # p c.event.class # => Alda::Note
83
+ # p((e/g).event.class) # => Alda::Chord
84
+ # p((a b).event.class) # => Alda::Sequence
85
+ # end
86
+ attr_accessor :event
87
+
88
+ ##
89
+ # The repetition counts. +nil+ if none.
90
+ #
91
+ # Alda::Score.new do
92
+ # p((c*2).count) # => 2
93
+ # p((d*3*5).count) # => 15
94
+ # end
95
+ attr_accessor :count
96
+
97
+ ##
98
+ # The repetition labels. Empty if none.
99
+ #
100
+ # Alda::Score.new do
101
+ # p((c%2).labels) # => [2]
102
+ # p((c%[2,4..6]).labels) # => [2, 4..6]
103
+ # end
104
+ attr_accessor :labels
105
+
106
+ ##
107
+ # :call-seq:
108
+ # new(event, parent) -> Alda::EventContainer
109
+ #
110
+ # Creates a new Alda::EventContainer.
111
+ # Invokes #on_containing.
112
+ #
113
+ # +event+ is the Alda::Event object to be contained.
114
+ #
115
+ # +parent+ is the Alda::EventList object containing the event.
116
+ def initialize event, parent
117
+ @event = event
118
+ @parent = parent
119
+ @labels = []
120
+ on_containing
121
+ end
122
+
123
+ ##
124
+ # :call-seq:
125
+ # container / other -> container
126
+ #
127
+ # Makes #event an Alda::Chord object.
128
+ #
129
+ # Alda::Score.new { piano_; c/-e/g }.play
130
+ # # (plays the chord Cm)
131
+ #
132
+ # If the contained event is an Alda::Part object,
133
+ # makes #event a new Alda::Part object.
134
+ #
135
+ # Alda::Score.new { violin_/viola_/cello_; e; f; g}.play
136
+ # # (plays notes E, F, G with three instruments simultaneously)
137
+ def / other
138
+ other.detach_from_parent
139
+ @event =
140
+ if @event.is_a? Alda::Part
141
+ Alda::Part.new @event.names + other.event.names, other.event.arg
142
+ else
143
+ Alda::Chord.new @event, other.event
144
+ end
145
+ self
146
+ end
147
+
148
+ def to_alda_code
149
+ result = @event.to_alda_code
150
+ unless @labels.empty?
151
+ result.concat ?', @labels.map(&:to_alda_code).join(?,)
152
+ end
153
+ result.concat ?*, @count.to_alda_code if @count
154
+ result
155
+ end
156
+
157
+ ##
158
+ # :call-seq:
159
+ # container * num -> container
160
+ #
161
+ # Marks repetition.
162
+ #
163
+ # For examples, see #%.
164
+ def * num
165
+ @count = (@count || 1) * num
166
+ self
167
+ end
168
+
169
+ ##
170
+ # :call-seq:
171
+ # container % labels -> container
172
+ #
173
+ # Marks alternative endings.
174
+ #
175
+ # Alda::Score.new { (b a%1)*2 }.to_s
176
+ # # => "[b a'1]*2"
177
+ def % labels
178
+ labels = [labels] unless labels.is_a? Array
179
+ @labels.replace labels.to_a
180
+ self
181
+ end
182
+
183
+ ##
184
+ # :call-seq:
185
+ # event=(event) -> event
186
+ #
187
+ # Sets #event and invokes #on_containing.
188
+ def event= event
189
+ @event = event
190
+ on_containing
191
+ @event
192
+ end
193
+
194
+ ##
195
+ # A callback invoked in #event= and ::new.
196
+ def on_containing
197
+ if @event
198
+ @event.container = self
199
+ @event.parent = @parent
200
+ @event.on_contained
201
+ end
202
+ end
203
+
204
+ ##
205
+ # :call-seq:
206
+ # (missing method) -> obj
207
+ #
208
+ # Calls method on #event.
209
+ #
210
+ # Note that if the method of #event returns #event itself,
211
+ # the method here returns the container itself.
212
+ #
213
+ # Alda::Score.new do
214
+ # container = c
215
+ # p container.class # => Alda::EventContainer
216
+ # p container.respond_to? :pitch # => false
217
+ # p container.pitch # => "c"
218
+ # p container.respond_to? :+@ # => false
219
+ # p((+container).class) # => Alda::EventContainer
220
+ # p to_s # => "c+"
221
+ # end
222
+ def method_missing(...)
223
+ result = @event.__send__(...)
224
+ result = self if result == @event
225
+ result
226
+ end
227
+ end
228
+
229
+ ##
230
+ # An inline lisp event. An Alda::EventContainer containing
231
+ # an Alda::InlineLisp can be derived using event list
232
+ # sugar. See Alda::EventList#method_missing.
233
+ #
234
+ # Sometimes you need help from Alda::LispIdentifier.
235
+ #
236
+ # It serves as attributes in alda codes.
237
+ #
238
+ # Alda::Score.new do
239
+ # tempo! 108
240
+ # quant! 200
241
+ # piano_ c e g violin_ g2 e4
242
+ # end
243
+ #
244
+ # Here, <tt>tempo! 108</tt> and <tt>quant! 200</tt>
245
+ # are inline lisp events and serves for alda attributes.
246
+ #
247
+ # It can participate in the sequence sugar if it is
248
+ # at the end of the sequence.
249
+ #
250
+ # Alda::Score.new do
251
+ # piano_ c d e quant 200
252
+ # g o! c o? c2
253
+ # end
254
+ #
255
+ # You can operate a score by purely using inline lisp events.
256
+ #
257
+ # Alda::Score.new do
258
+ # part 'piano'
259
+ # key_sig [:d, :major]
260
+ # note pitch :d
261
+ # note pitch :e
262
+ # note pitch(:f), duration(note_length 2)
263
+ # end
264
+ #
265
+ # When using event list sugar to create inline lisp events,
266
+ # note that it is not previously defined as a variable.
267
+ # See Alda::SetVariable and Alda::GetVariable.
268
+ #
269
+ # Alda::Score.new do
270
+ # piano_
271
+ # p barline.event.class # => Alda::InlineLisp
272
+ # barline__ c d e f
273
+ # p barline.event.class # => Alda::GetVariable
274
+ # end
275
+ #
276
+ # Whether it is an Alda::SetVariable, Alda::InlineLisp,
277
+ # or Alda::GetVariable is intelligently determined.
278
+ #
279
+ # Alda::Score.new do
280
+ # piano_
281
+ # p((tempo 108).event.class) # => Alda::InlineLisp
282
+ # p tempo { c d }.event.class # => Alda::SetVariable
283
+ # p tempo.event.class # => Alda::GetVariable
284
+ # p((tempo 60).event.class) # => Alda::InlineLisp
285
+ # p to_s
286
+ # # => "piano: (tempo 108) tempo = [c d]\n tempo (tempo 60)"
287
+ # end
288
+ #
289
+ # If you want, you can generate lisp codes using ruby.
290
+ #
291
+ # Alda::Score.new do
292
+ # println reduce _into_, {}, [{dog: 'food'}, {cat: 'chow'}]
293
+ # end.save 'temp.clj'
294
+ # `clj temp.clj` # => "[[:dog food] [:cat chow]]\n"
295
+ class Alda::InlineLisp < Alda::Event
296
+
297
+ ##
298
+ # The function name of the lisp function
299
+ attr_accessor :head
300
+
301
+ ##
302
+ # The arguments passed to the lisp function.
303
+ #
304
+ # Its elements can be any object that responds to
305
+ # +to_alda_code+ and +detach_from_parent+.
306
+ attr_accessor :args
307
+
308
+ ##
309
+ # :call-seq:
310
+ # new(head, *args) -> Alda::InlineLisp
311
+ #
312
+ # Creates a new Alda::InlineLisp.
313
+ #
314
+ # The underlines "_" in +head+ will be converted to hyphens "-".
315
+ def initialize head, *args
316
+ @head = head.to_s.gsub ?_, ?-
317
+ @args = args
318
+ end
319
+
320
+ def to_alda_code
321
+ "(#{head} #{args.map(&:to_alda_code).join ' '})"
322
+ end
323
+
324
+ def on_contained
325
+ super
326
+ @args.detach_from_parent
327
+ end
328
+ end
329
+
330
+ ##
331
+ # A note event. An Alda::EventContainer containing
332
+ # an Alda::Note can be derived using Alda::EventList sugar.
333
+ # See Alda::EventList#method_missing.
334
+ #
335
+ # There cannot be tildes and dots in (usual) ruby method names,
336
+ # so use underlines instead.
337
+ #
338
+ # The accidentals can be added using #+@, #-@, and #~, or by
339
+ # using exclamation mark, question mark or underline.
340
+ #
341
+ # Alda::Score.new do
342
+ # key_sig! [:d, :major]
343
+ # c4_2 d1108ms e2s
344
+ # f2! # F sharp
345
+ # g20ms_4? # G flat
346
+ # a6_ # A natural
347
+ # c__ # C (slur)
348
+ # f___ # D natural (slur)
349
+ # end
350
+ class Alda::Note < Alda::Event
351
+
352
+ ##
353
+ # The string representing the pitch
354
+ attr_accessor :pitch
355
+
356
+ ##
357
+ # The string representing the duration.
358
+ #
359
+ # It ends with a tilde "~" if the note slurs.
360
+ attr_accessor :duration
361
+
362
+ ##
363
+ # :call-seq:
364
+ # new(pitch, duration) -> Alda::Note
365
+ #
366
+ # The underlines in +duration+ will be converted to tildes "~".
367
+ # Exclamation mark and question mark in +duration+
368
+ # will be interpreted as accidentals in #pitch.
369
+ #
370
+ # The number of underlines at the end of +duration+ means:
371
+ # neither natural nor slur if 0,
372
+ # natural if 1,
373
+ # slur if 2,
374
+ # both natural and slur if 3.
375
+ def initialize pitch, duration
376
+ @pitch = pitch.to_s
377
+ @duration = duration.to_s.tr ?_, ?~
378
+ case @duration[-1]
379
+ when ?! # sharp
380
+ @pitch.concat ?+
381
+ @duration[-1] = ''
382
+ when ?? # flat
383
+ @pitch.concat ?-
384
+ @duration[-1] = ''
385
+ end
386
+ waves = /(?<str>~+)\z/ =~ @duration ? str.size : return
387
+ @duration[@duration.length - waves..] = ''
388
+ if waves >= 2
389
+ waves -= 2
390
+ @duration.concat ?~
391
+ end
392
+ @pitch.concat ?_ * waves
393
+ end
394
+
395
+ ##
396
+ # :call-seq:
397
+ # +note -> note
398
+ #
399
+ # Append a sharp sign after #pitch.
400
+ #
401
+ # Alda::Score.new { piano_; +c }.play
402
+ # # (plays a C sharp note)
403
+ def +@
404
+ @pitch.concat ?+
405
+ self
406
+ end
407
+
408
+ ##
409
+ # :call-seq:
410
+ # -note -> note
411
+ #
412
+ # Append a flat sign after #pitch.
413
+ #
414
+ # Alda::Score.new { piano_; -d }.play
415
+ # # (plays a D flat note)
416
+ def -@
417
+ @pitch.concat ?-
418
+ self
419
+ end
420
+
421
+ ##
422
+ # :call-seq:
423
+ # ~note -> note
424
+ #
425
+ # Append a natural sign after #pitch.
426
+ #
427
+ # Alda::Score.new { piano_; key_sig 'f+'; ~f }.play
428
+ # # (plays an F note)
429
+ def ~
430
+ @pitch.concat ?_
431
+ self
432
+ end
433
+
434
+ def to_alda_code
435
+ result = @pitch + @duration
436
+ result.concat ?*, @count.to_alda_code if @count
437
+ result
438
+ end
439
+ end
440
+
441
+ ##
442
+ # A rest event. An Alda::EventContainer containing an
443
+ # Alda::Rest can be created using event list sugar.
444
+ # See Alda::EventList#method_missing.
445
+ #
446
+ # When using event list sugar, its duration can be specified
447
+ # just like that of Alda::Note.
448
+ #
449
+ # Alda::Score.new do
450
+ # piano_ c8 r4 c8 r4 c4
451
+ # end
452
+ class Alda::Rest < Alda::Event
453
+
454
+ ##
455
+ # The string representing a duration.
456
+ attr_accessor :duration
457
+
458
+ ##
459
+ # :call-seq:
460
+ # new(duration) -> Alda::Rest
461
+ #
462
+ # Creates an Alda::Rest.
463
+ #
464
+ # Underlines "_" in +duration+ will be converted to tildes "~".
465
+ def initialize duration
466
+ @duration = duration.to_s.tr ?_, ?~
467
+ end
468
+
469
+ def to_alda_code
470
+ ?r + @duration
471
+ end
472
+ end
473
+
474
+ ##
475
+ # An octave event. An Alda::EventContainer containing
476
+ # an Alda::Octave can be derived using event list sugar.
477
+ # See Alda::EventList#method_missing.
478
+ #
479
+ # +o!+ means octave up, and +o?+ means octave down.
480
+ # You can also use #+@ and #-@ to denote octave up and down.
481
+ class Alda::Octave < Alda::Event
482
+
483
+ ##
484
+ # The string representing the octave's number.
485
+ #
486
+ # It can be empty, in which case
487
+ # it is purely serving for #+@ and #-@.
488
+ attr_accessor :num
489
+
490
+ ##
491
+ # Positive for up, negative for down, and +0+ as default.
492
+ #
493
+ # Alda::Score.new do
494
+ # p((++++o).event.up_or_down) # => 4
495
+ # end
496
+ attr_accessor :up_or_down
497
+
498
+ ##
499
+ # :call-seq:
500
+ # new(num) -> Alda::Octave
501
+ #
502
+ # Creates an Alda::Octave.
503
+ def initialize num
504
+ @num = num.to_s
505
+ @up_or_down = 0
506
+ end
507
+
508
+ ##
509
+ # :call-seq:
510
+ # +octave -> octave
511
+ #
512
+ # \Octave up.
513
+ #
514
+ # Alda::Score.new { piano_; c; +o; c }.play
515
+ # # (plays C4, then C5)
516
+ #
517
+ # See #-@.
518
+ def +@
519
+ @up_or_down += 1
520
+ self
521
+ end
522
+
523
+ ##
524
+ # :call-seq:
525
+ # -octave -> octave
526
+ #
527
+ # \Octave down.
528
+ # See #+@.
529
+ def -@
530
+ @up_or_down -= 1
531
+ self
532
+ end
533
+
534
+ def to_alda_code
535
+ case @up_or_down <=> 0
536
+ when 0
537
+ ?o + @num
538
+ when 1
539
+ ?> * @up_or_down
540
+ when -1
541
+ ?< * -@up_or_down
542
+ end
543
+ end
544
+ end
545
+
546
+ ##
547
+ # A chord event.
548
+ # Includes Alda::EventList.
549
+ #
550
+ # An Alda::EventContainer containing an Alda::Chord
551
+ # can be created using event list sugar.
552
+ # See Alda::EventList#method_missing.
553
+ #
554
+ # Alda::Score.new do
555
+ # p x{ c; e; g }.event.class # => Alda::Chord
556
+ # end
557
+ #
558
+ # The event contained by an Alda::EventContainer
559
+ # can become an Alda::Chord by using Alda::EventContainer#/.
560
+ class Alda::Chord < Alda::Event
561
+ include Alda::EventList
562
+
563
+ ##
564
+ # :call-seq:
565
+ # new(*events, &block) -> Alda::Chord
566
+ #
567
+ # There is an event list sugar invoking this method.
568
+ # See Alda::EventList#method_missing.
569
+ #
570
+ # In most cases, +events+ should be empty.
571
+ # Note that +events+ cannot be specified using the sugar.
572
+ # +block+ is to be passed with the chord object as +self+.
573
+ #
574
+ # Alda::Score.new { piano_; x { c; -e; g } }.play
575
+ # # (plays chord Cm)
576
+ def initialize *events, &block
577
+ @events = events
578
+ super &block
579
+ end
580
+
581
+ def to_alda_code
582
+ events_alda_codes ?/
583
+ end
584
+ end
585
+
586
+ ##
587
+ # A part event. An Alda::EventContainer containing an
588
+ # Alda::Part can be derived using event list sugar.
589
+ # See Alda::EventList#method_missing.
590
+ #
591
+ # A part can have nickname.
592
+ #
593
+ # Alda::Score.new do
594
+ # piano_ 'player1'
595
+ # c4 d e e e1
596
+ # piano_ 'player2'
597
+ # e4 d g g g1
598
+ # end
599
+ #
600
+ # You can use Alda::EventContainer#/ to have a set of
601
+ # instruments.
602
+ #
603
+ # Alda::Score.new do
604
+ # violin_/viola_
605
+ # c2 d4 e2_4
606
+ # end
607
+ #
608
+ # A set of instruments can also have nickname.
609
+ # You can also access an instruments from a set.
610
+ # See #method_missing.
611
+ #
612
+ # Alda::Score.new do
613
+ # violin_/viola_/cello_('strings')
614
+ # g1_1_1
615
+ # strings_.cello_
616
+ # c1_1_1
617
+ # end
618
+ class Alda::Part < Alda::Event
619
+
620
+ ##
621
+ # The names of the part. To be joined with +/+ as delimiter.
622
+ attr_accessor :names
623
+
624
+ ##
625
+ # The nickname of the part. +nil+ if none.
626
+ attr_accessor :arg
627
+
628
+ ##
629
+ # :call-seq:
630
+ # new(names, arg=nil) -> Alda::Part
631
+ #
632
+ # Creates an Alda::Part.
633
+ def initialize names, arg = nil
634
+ @names = names.map { |name| name.to_s.tr ?_, ?- }
635
+ @arg = arg
636
+ end
637
+
638
+ def to_alda_code
639
+ result = @names.join ?/
640
+ result.concat " \"#{@arg}\"" if @arg
641
+ result.concat ?:
642
+ end
643
+
644
+ ##
645
+ # :call-seq:
646
+ # part.(component)_() -> Alda::EventContainer or Alda::Part
647
+ #
648
+ # Enables dot accessor.
649
+ #
650
+ # Alda::Score.new do
651
+ # violin_/viola_/cello_('strings'); g1_1_1
652
+ # strings_.cello_; -o; c1_1_1
653
+ # end.play
654
+ def method_missing name, *args
655
+ str = name.to_s
656
+ return super unless str[-1] == ?_
657
+ str[-1] = ''
658
+ @names.last.concat ?., str
659
+ if args.size == 1
660
+ unless @container
661
+ @container = Alda::EventContainer.new nil, @parent
662
+ @parent.events.delete self
663
+ @parent.push @container
664
+ end
665
+ @container.event = Alda::Sequence.join self, args.first.tap(&:detach_from_parent)
666
+ @container
667
+ else
668
+ @container || self
669
+ end
670
+ end
671
+ end
672
+
673
+ ##
674
+ # A voice event. An Alda::EventContainer containing an
675
+ # Alda::Voice can be created using event list sugar.
676
+ # See Alda::EventList#method_missing.
677
+ #
678
+ # Alda::Score.new do
679
+ # piano_ v1 c d e f v2 e f g a
680
+ # end
681
+ class Alda::Voice < Alda::Event
682
+
683
+ ##
684
+ # The string representing the voice's number.
685
+ attr_accessor :num
686
+
687
+ ##
688
+ # :call-seq:
689
+ # new(num) -> Alda::Voice
690
+ #
691
+ # Creates an Alda::Voice.
692
+ def initialize num
693
+ @num = num
694
+ end
695
+
696
+ def to_alda_code
697
+ ?V + num + ?:
698
+ end
699
+ end
700
+
701
+ ##
702
+ # A CRAM event.
703
+ # Includes Alda::EventList.
704
+ #
705
+ # An Alda::EventContainer containing an Alda::Cram
706
+ # can be created using event list sugar.
707
+ # See Alda::EventList#method_missing.
708
+ #
709
+ # The duration of a cram event can be specified
710
+ # just like that of an Alda::Note.
711
+ #
712
+ # Alda::Score.new do
713
+ # piano_ c3 t4 { c2 d4 e f }; g2
714
+ # end
715
+ class Alda::Cram < Alda::Event
716
+ include Alda::EventList
717
+
718
+ ##
719
+ # The string representing the duration of the CRAM.
720
+ attr_accessor :duration
721
+
722
+ ##
723
+ # There is an event list sugar invoking this method,
724
+ # see Alda::EventList#method_missing.
725
+ #
726
+ # +block+ is to be passed with the CRAM object as +self+.
727
+ #
728
+ # Alda::Score.new { piano_; t8 { a; b }}
729
+ def initialize duration, &block
730
+ @duration = duration
731
+ super &block
732
+ end
733
+
734
+ def to_alda_code
735
+ "{#{events_alda_codes}}#@duration"
736
+ end
737
+ end
738
+
739
+ ##
740
+ # A marker event. An Alda::EventContainer containing an
741
+ # Alda::Marker can be created using event list sugar.
742
+ # See Alda::EventList#method_missing.
743
+ #
744
+ # It should be used together with Alda::AtMarker.
745
+ #
746
+ # Alda::Score.new do
747
+ # piano_ v1 c d _here e2 v2 __here c4 d e2
748
+ # end
749
+ class Alda::Marker < Alda::Event
750
+
751
+ ##
752
+ # The marker's name
753
+ attr_accessor :name
754
+
755
+ ##
756
+ # :call-seq:
757
+ # new(name) -> Alda::Marker
758
+ #
759
+ # Creates an Alda::Marker.
760
+ #
761
+ # Underlines in +name+ is converted to hyphens.
762
+ def initialize name
763
+ @name = name.to_s.tr ?_, ?-
764
+ end
765
+
766
+ def to_alda_code
767
+ ?% + @name
768
+ end
769
+ end
770
+
771
+ ##
772
+ # An at-marker event. An Alda::EventContainer containing
773
+ # an Alda::AtMarker can be created using event list sugar.
774
+ # See Alda::EventList#method_missing.
775
+ #
776
+ # It should be used together with Alda::Marer.
777
+ # For examples, see Alda::Marker.
778
+ class Alda::AtMarker < Alda::Event
779
+
780
+ ##
781
+ # The corresponding marker's name
782
+ attr_accessor :name
783
+
784
+ ##
785
+ # :call-seq:
786
+ # new(name) -> Alda::AtMarker
787
+ #
788
+ # Creates an Alda::AtMarker.
789
+ #
790
+ # Underlines "_" in +name+ is converted to hyphens "-".
791
+ def initialize name
792
+ @name = name.to_s.tr ?_, ?-
793
+ end
794
+
795
+ def to_alda_code
796
+ ?@ + @name
797
+ end
798
+ end
799
+
800
+ ##
801
+ # A sequence event. Includes Alda::EventList.
802
+ #
803
+ # An Alda::EventContainer containing
804
+ # an Alda::Sequence can be created using event list sugar.
805
+ # See Alda::EventList#method_missing.
806
+ #
807
+ # Alda::Score.new do
808
+ # p s{ c; d; e; f }.event.class # => Alda::Sequence
809
+ # end
810
+ #
811
+ # There is also a special sequence sugar.
812
+ #
813
+ # Alda::Score.new do
814
+ # p((c d e f).event.class) # => Alda::Sequence
815
+ # end
816
+ #
817
+ # The effects of the two examples above are the same.
818
+ class Alda::Sequence < Alda::Event
819
+ include Alda::EventList
820
+
821
+ ##
822
+ # Using this module can fix a bug of <tt>Array#flatten</tt>.
823
+ #
824
+ # def (a = Object.new).method_missing(...)
825
+ # Object.new
826
+ # end
827
+ # [a].flatten rescue $! # => #<TypeError:...>
828
+ # using Alda::Sequence::RefineFlatten
829
+ # [a].flatten # => [#<Object:...>]
830
+ module RefineFlatten
831
+ refine Array do
832
+ def flatten
833
+ each_with_object [] do |element, result|
834
+ if element.is_a? Array
835
+ result.push *element.flatten
836
+ else
837
+ result.push element
838
+ end
839
+ end
840
+ end
841
+ end
842
+ end
843
+ using RefineFlatten
844
+
845
+ def to_alda_code
846
+ @events.to_alda_code
847
+ end
848
+
849
+ ##
850
+ # :call-seq:
851
+ # join(*events) -> Alda::Sequence
852
+ #
853
+ # Creates an Alda::Sequence object by joining +events+.
854
+ #
855
+ # The Alda::EventContainer objects are extracted,
856
+ # and the Alda::Sequence objects are flattened.
857
+ def self.join *events
858
+ new do
859
+ @events = events.map do |event|
860
+ while event.is_a?(Alda::EventContainer) && !event.count && event.labels.empty?
861
+ event = event.event
862
+ end
863
+ event.is_a?(Alda::Sequence) ? event.events : event
864
+ end.flatten
865
+ end
866
+ end
867
+ end
868
+
869
+ ##
870
+ # A set-variable event. Includes Alda::EventList.
871
+ #
872
+ # An Alda::EventContainer containing an Alda::SetVariable
873
+ # can be derived using event list sugar.
874
+ # See Alda::EventList#method_missing.
875
+ #
876
+ # There are several equivalent means of setting variable.
877
+ # Some of them can be ambiguous with Alda::InlineLisp or
878
+ # Alda::GetVariable, but it is intelligently chosen.
879
+ #
880
+ # Alda::Score.new do
881
+ # p var.event.class # => Alda::InlineLisp
882
+ # p((var c d e f).event.class) # => Alda::SetVariable
883
+ # p var { c d e f }.event.class # => Alda::SetVariable
884
+ # p((var__ c d e f).event.class) # => Alda::SetVariable
885
+ # p var__ { c d e f }.event.class # => Alda::SetVariable
886
+ # p((var c d e f).event.class) # => Alda::Sequence
887
+ # p var.event.class # => Alda::GetVariable
888
+ # p var(1).event.class # => Alda::InlineLisp
889
+ # end
890
+ class Alda::SetVariable < Alda::Event
891
+ include Alda::EventList
892
+
893
+ ##
894
+ # The name of the variable.
895
+ attr_accessor :name
896
+
897
+ ##
898
+ # The events passed to it using arguments instead of a block.
899
+ attr_reader :original_events
900
+
901
+ ##
902
+ # :call-seq:
903
+ # new(name, *events, &block) -> Alda::SetVariable
904
+ #
905
+ # Creates an Alda::SetVariable.
906
+ def initialize name, *events, &block
907
+ @name = name.to_sym
908
+ @original_events = events
909
+ @events = events.clone
910
+ super &block
911
+ end
912
+
913
+ ##
914
+ # Specially, the returned value ends with a newline "\\n".
915
+ def to_alda_code
916
+ "#@name = #{events_alda_codes}\n"
917
+ end
918
+
919
+ def on_contained
920
+ super
921
+ @parent.variables.add @name
922
+ @original_events.detach_from_parent
923
+ end
924
+ end
925
+
926
+ ##
927
+ # A get-variable event. An Alda::EventContainer containing
928
+ # an Alda::GetVariable can be derived using event list sugar.
929
+ # See Alda::EventList#method_missing.
930
+ #
931
+ # This can be ambiguous with Alda::SetVariable and
932
+ # Alda::InlineLisp. For examples, see Alda::SetVariable.
933
+ class Alda::GetVariable < Alda::Event
934
+
935
+ ##
936
+ # The name of the variable.
937
+ attr_accessor :name
938
+
939
+ ##
940
+ # :call-seq:
941
+ # new(name) -> Alda::GetVariable
942
+ #
943
+ # Creates an Alda::GetVariable.
944
+ def initialize name
945
+ @name = name
946
+ end
947
+
948
+ def to_alda_code
949
+ @name.to_s
950
+ end
951
+ end
952
+
953
+ ##
954
+ # A lisp identifier event. An Alda::EventContainer containing
955
+ # an Alda::Lisp
956
+ #
957
+ # It is in fact not a kind of event in alda.
958
+ # However, such a thing is needed when writing some
959
+ # lisp codes in alda.
960
+ #
961
+ # A standalone lisp identifier is useless.
962
+ # Use it together with Alda::InlineLisp.
963
+ class Alda::LispIdentifier < Alda::Event
964
+
965
+ ##
966
+ # The name of the lisp identifier.
967
+ attr_accessor :name
968
+
969
+ ##
970
+ # :call-seq:
971
+ # new(name) -> Alda::LispIdentifier
972
+ #
973
+ # Creates an Alda::LispIdentifier.
974
+ #
975
+ # Underlines "_" in +name+ is converted to hyphens "-".
976
+ def initialize name
977
+ @name = name.tr ?_, ?-
978
+ end
979
+
980
+ def to_alda_code
981
+ @name
982
+ end
983
+ end