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