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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -1
- data/Gemfile +4 -0
- data/README.md +2 -2
- data/Rakefile +15 -6
- data/bin/setup +1 -1
- data/examples/bwv846_prelude.rb +1 -1
- data/lib/alda-rb.rb +8 -1074
- data/lib/alda-rb/commandline.rb +139 -0
- data/lib/alda-rb/error.rb +80 -0
- data/lib/alda-rb/event.rb +983 -0
- data/lib/alda-rb/event_list.rb +313 -0
- data/lib/alda-rb/patches.rb +168 -0
- data/lib/alda-rb/repl.rb +245 -0
- data/lib/alda-rb/version.rb +7 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dda95434d70128902d3d1ab96628c079ea8716123768d2e93b1ed429df5571b2
|
4
|
+
data.tar.gz: 8886cd7c861469aa980c18b56142e939c1727086c4d14e15b55fad3eb9fad1b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4cc9a4cd860643fdd69d152d77b13dac9c9d730d119b55d2799c0ffef328ccbeb91c9e618bf706652306210bdd298d1055f3f0a88ead557cc5f550d9f644823f
|
7
|
+
data.tar.gz: 0c3654c453bf4d66fc6eed1bdcda53abe712ad625799d6687d64d29222c77d6bb7fc86e9190cb96d09052f7191eb43bfbcafc8dea8a0e26bf11a2a1bbb99dfee
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
data/Rakefile
CHANGED
@@ -1,10 +1,19 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rdoc/task'
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
RDoc::Task.new do |t|
|
6
|
+
t.main = 'README.md'
|
7
|
+
t.rdoc_files.include 'README.md', 'CODE_OF_CONDUCT.md',
|
8
|
+
'LICENSE.txt', 'lib/**/*.rb'
|
9
|
+
t.options << '--tab-width'
|
10
|
+
t.options << '2'
|
11
|
+
end
|
12
|
+
|
13
|
+
Rake::TestTask.new :test do |t|
|
14
|
+
t.libs << 'test'
|
15
|
+
t.libs << 'lib'
|
16
|
+
t.test_files = FileList['test/**/*_test.rb']
|
8
17
|
end
|
9
18
|
|
10
19
|
task :default => :test
|
data/bin/setup
CHANGED
data/examples/bwv846_prelude.rb
CHANGED
@@ -15,7 +15,7 @@ class Alda::Sequence
|
|
15
15
|
/(?<letter>[a-g][-+_]*)(?<octave>\d*)/ =~ pitch
|
16
16
|
octave = @@last_octave ||= '4' if octave.empty?
|
17
17
|
result = Alda::Note.new "o#{@@last_octave = octave} #{letter}", duration
|
18
|
-
|
18
|
+
@events.push result
|
19
19
|
result
|
20
20
|
end
|
21
21
|
end
|
data/lib/alda-rb.rb
CHANGED
@@ -1,1075 +1,9 @@
|
|
1
|
-
|
2
|
-
require 'set'
|
3
|
-
require 'stringio'
|
4
|
-
require 'irb/ruby-lex'
|
5
|
-
require 'alda-rb/version'
|
6
|
-
require 'colorize'
|
7
|
-
|
8
|
-
{
|
9
|
-
Array => -> { "[#{map(&:to_alda_code).join ' '}]" },
|
10
|
-
Hash => -> { "{#{to_a.reduce(:+).map(&:to_alda_code).join ' '}}" },
|
11
|
-
String => -> { dump },
|
12
|
-
Symbol => -> { ?: + to_s },
|
13
|
-
Numeric => -> { inspect },
|
14
|
-
Range => -> { "#{first}-#{last}" },
|
15
|
-
TrueClass => -> { 'true' },
|
16
|
-
FalseClass => -> { 'false' },
|
17
|
-
NilClass => -> { 'nil' }
|
18
|
-
}.each { |klass, block| klass.define_method :to_alda_code, &block }
|
19
|
-
|
20
|
-
class Proc
|
21
|
-
# Runs +self+ for +n+ times.
|
22
|
-
def * n
|
23
|
-
if !lambda? || arity == 1
|
24
|
-
n.times &self
|
25
|
-
else
|
26
|
-
n.times { self.() }
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
1
|
+
# frozen_string_literal: true
|
30
2
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
module Kernel
|
39
|
-
# Runs the alda command.
|
40
|
-
# Does not capture output.
|
41
|
-
# @example
|
42
|
-
# alda 'version'
|
43
|
-
# alda 'play', '-c', 'piano: a'
|
44
|
-
# alda 'repl'
|
45
|
-
def alda *args
|
46
|
-
system Alda.executable, *args
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
# The module serving as a namespace.
|
51
|
-
module Alda
|
52
|
-
|
53
|
-
# The array of available subcommands of alda executable.
|
54
|
-
#
|
55
|
-
# Alda# is able to invoke +alda+ at the command line.
|
56
|
-
# The subcommand is the name of the method invoked upon Alda#.
|
57
|
-
#
|
58
|
-
# The first argument (a hash) is interpreted as the options.
|
59
|
-
# The keyword arguments are interpreted as the subcommand options.
|
60
|
-
#
|
61
|
-
# The return value is the string output by the command in STDOUT.
|
62
|
-
#
|
63
|
-
# If the exit code is nonzero, a CommandLineError# is raised.
|
64
|
-
# @example
|
65
|
-
# Alda.version
|
66
|
-
# # => "Client version: 1.4.0\nServer version: [27713] 1.4.0\n"
|
67
|
-
# Alda.parse code: 'bassoon: o3 c'
|
68
|
-
# # => "{\"chord-mode\":false,\"current-instruments\":...}\n"
|
69
|
-
COMMANDS = %i[
|
70
|
-
help update repl up start_server init down stop_server
|
71
|
-
downup restart_server list status version play stop parse
|
72
|
-
instruments export
|
73
|
-
].freeze
|
74
|
-
|
75
|
-
COMMANDS.each do |command|
|
76
|
-
define_method command do |*args, **opts|
|
77
|
-
block = ->key, val do
|
78
|
-
next unless val
|
79
|
-
args.push "--#{key.to_s.tr ?_, ?-}"
|
80
|
-
args.push val.to_s unless val == true
|
81
|
-
end
|
82
|
-
# executable
|
83
|
-
args.unshift Alda.executable
|
84
|
-
args.map! &:to_s
|
85
|
-
# options
|
86
|
-
Alda.options.each &block
|
87
|
-
# subcommand
|
88
|
-
args.push command.to_s
|
89
|
-
# subcommand options
|
90
|
-
opts.each &block
|
91
|
-
# subprocess
|
92
|
-
IO.popen(args, &:read).tap do
|
93
|
-
raise CommandLineError.new $?, _1 if $?.exitstatus.nonzero?
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# The path to the +alda+ executable.
|
99
|
-
#
|
100
|
-
# The default value is <tt>"alda"</tt>,
|
101
|
-
# which will depend on your PATH.
|
102
|
-
singleton_class.attr_accessor :executable
|
103
|
-
@executable = 'alda'
|
104
|
-
|
105
|
-
singleton_class.attr_reader :options
|
106
|
-
@options = {}
|
107
|
-
|
108
|
-
# @return Whether the alda server is up.
|
109
|
-
def up?
|
110
|
-
status.include? 'up'
|
111
|
-
end
|
112
|
-
|
113
|
-
# @return Whether the alda server is down.
|
114
|
-
def down?
|
115
|
-
status.include? 'down'
|
116
|
-
end
|
117
|
-
|
118
|
-
module_function :up?, :down?, *COMMANDS
|
119
|
-
|
120
|
-
# Start a REPL session.
|
121
|
-
def self.repl
|
122
|
-
REPL.new.run
|
123
|
-
end
|
124
|
-
|
125
|
-
# Sets the options of alda command.
|
126
|
-
# Not the subcommand options.
|
127
|
-
def self.[] **opts
|
128
|
-
@options.merge! opts
|
129
|
-
self
|
130
|
-
end
|
131
|
-
|
132
|
-
# Clears the command line options.
|
133
|
-
def self.clear_options
|
134
|
-
@options.clear
|
135
|
-
end
|
136
|
-
|
137
|
-
# Including this module can make your class have the ability
|
138
|
-
# to have an event list.
|
139
|
-
# See docs below to get an overview of its functions.
|
140
|
-
module EventList
|
141
|
-
|
142
|
-
# The array containing the events (Event# objects),
|
143
|
-
# most of which are EventContainer# objects.
|
144
|
-
attr_accessor :events
|
145
|
-
|
146
|
-
# The set containing the available variable names.
|
147
|
-
attr_accessor :variables
|
148
|
-
|
149
|
-
def on_contained
|
150
|
-
instance_eval &@block if @block
|
151
|
-
end
|
152
|
-
|
153
|
-
# Make the object have the ability to appending its +events+
|
154
|
-
# conveniently.
|
155
|
-
#
|
156
|
-
# Here is a list of sugar. When the name of a method meets certain
|
157
|
-
# condition, the method is regarded as an event appended to +events+.
|
158
|
-
#
|
159
|
-
# 1. Ending with 2 underlines: set variable. See SetVariable#.
|
160
|
-
#
|
161
|
-
# 2. Starting with 2 lowercase letters and
|
162
|
-
# ending with underline character: instrument. See Part#.
|
163
|
-
#
|
164
|
-
# 3. Starting with 2 lowercase letters: inline lisp code,
|
165
|
-
# set variable, or get variable.
|
166
|
-
# One of the above three is chosen intelligently.
|
167
|
-
# See InlineLisp#, SetVariable#, GetVariable#.
|
168
|
-
#
|
169
|
-
# 4. Starting with "t": CRAM. See Cram#.
|
170
|
-
#
|
171
|
-
# 5. Starting with one of "a", "b", ..., "g": note. See Note#.
|
172
|
-
#
|
173
|
-
# 6. Starting with "r": rest. See Rest#.
|
174
|
-
#
|
175
|
-
# 7. "x": chord. See Chord#.
|
176
|
-
#
|
177
|
-
# 8. "s": sequence. See Sequence#.
|
178
|
-
#
|
179
|
-
# 9. Starting with "o": octave. See Octave#.
|
180
|
-
#
|
181
|
-
# 10. Starting with "v": voice. See Voice#.
|
182
|
-
#
|
183
|
-
# 11. Starting with "__" (2 underlines): at marker. See AtMarker#.
|
184
|
-
#
|
185
|
-
# 12. Starting with "_" (underline): marker. See Marker#.
|
186
|
-
#
|
187
|
-
# Notes cannot have dots.
|
188
|
-
# To tie multiple durations, +_+ is used instead of +~+.
|
189
|
-
#
|
190
|
-
# All the appended events are contained in an EventContainer# object,
|
191
|
-
# which is to be returned.
|
192
|
-
#
|
193
|
-
# These sugars forms a DSL.
|
194
|
-
# @see #initialize.
|
195
|
-
# @return an EventContainer# object.
|
196
|
-
def method_missing name, *args, &block
|
197
|
-
if @parent&.respond_to? name, true
|
198
|
-
return @parent.__send__ name, *args, &block
|
199
|
-
end
|
200
|
-
sequence_sugar = ->event do
|
201
|
-
if args.size == 1
|
202
|
-
joined = args.first
|
203
|
-
unless (got = @events.pop) == (expected = joined)
|
204
|
-
raise OrderError.new expected, got
|
205
|
-
end
|
206
|
-
Sequence.join event, joined
|
207
|
-
else
|
208
|
-
event
|
209
|
-
end
|
210
|
-
end
|
211
|
-
case
|
212
|
-
when /\A(?<head>[a-z][a-z].*)__\z/ =~ name
|
213
|
-
SetVariable.new head, *args, &block
|
214
|
-
when /\A(?<part>[a-z][a-z].*)_\z/ =~ name
|
215
|
-
if args.first.is_a? String
|
216
|
-
Part.new [part], args.first
|
217
|
-
else
|
218
|
-
sequence_sugar.(Part.new [part])
|
219
|
-
end
|
220
|
-
when /\A[a-z][a-z].*\z/ =~ name
|
221
|
-
if block
|
222
|
-
SetVariable.new name, *args, &block
|
223
|
-
elsif has_variable?(name) && (args.empty? || args.size == 1 && args.first.is_a?(Event))
|
224
|
-
sequence_sugar.(GetVariable.new name)
|
225
|
-
else
|
226
|
-
InlineLisp.new name, *args
|
227
|
-
end
|
228
|
-
when /\At(?<duration>.*)\z/ =~ name
|
229
|
-
Cram.new duration, &block
|
230
|
-
when /\A(?<pitch>[a-g])(?<duration>.*)\z/ =~ name
|
231
|
-
sequence_sugar.(Note.new pitch, duration)
|
232
|
-
when /\Ar(?<duration>.*)\z/ =~ name
|
233
|
-
sequence_sugar.(Rest.new duration)
|
234
|
-
when /\Ax\z/ =~ name
|
235
|
-
Chord.new &block
|
236
|
-
when /\As\z/ =~ name
|
237
|
-
Sequence.new *args, &block
|
238
|
-
when /\Ao!\z/ =~ name
|
239
|
-
sequence_sugar.(Octave.new('').tap { _1.up_or_down = 1})
|
240
|
-
when /\Ao\?\z/ =~ name
|
241
|
-
sequence_sugar.(Octave.new('').tap { _1.up_or_down = -1})
|
242
|
-
when /\Ao(?<num>\d*)\z/ =~ name
|
243
|
-
sequence_sugar.(Octave.new num)
|
244
|
-
when /\Av(?<num>\d+)\z/ =~ name
|
245
|
-
sequence_sugar.(Voice.new num)
|
246
|
-
when /\A__(?<head>.+)\z/ =~ name
|
247
|
-
sequence_sugar.(AtMarker.new head)
|
248
|
-
when /\A_(?<head>.+)\z/ =~ name
|
249
|
-
sequence_sugar.(Marker.new head)
|
250
|
-
else
|
251
|
-
super
|
252
|
-
end.then do |event|
|
253
|
-
EventContainer.new event, self
|
254
|
-
end.tap &@events.method(:push)
|
255
|
-
end
|
256
|
-
|
257
|
-
def has_variable? name
|
258
|
-
@variables.include?(name) || !!@parent&.has_variable?(name)
|
259
|
-
end
|
260
|
-
|
261
|
-
# Append the events of another EventList# object here.
|
262
|
-
# This method covers the disadvantage of alda's being unable to
|
263
|
-
# import scores from other files.
|
264
|
-
# See https://github.com/alda-lang/alda-core/issues/8.
|
265
|
-
def import event_list
|
266
|
-
@events.concat event_list.events
|
267
|
-
end
|
268
|
-
|
269
|
-
# @param block to be passed with the EventList# object as +self+.
|
270
|
-
# @example
|
271
|
-
# Alda::Score.new do
|
272
|
-
# tempo! 108 # inline lisp
|
273
|
-
# piano_ # piano part
|
274
|
-
# o4 # octave 4
|
275
|
-
# c8; d; e; f # notes
|
276
|
-
# g4 g a f g e f d e c # a sequence
|
277
|
-
# d4_8 # cannot have '~', use '_' instead
|
278
|
-
# o3 b8 o4 c2 # a sequence
|
279
|
-
# end
|
280
|
-
# # => #<Alda::Score:0x... @events=[...]>
|
281
|
-
def initialize &block
|
282
|
-
@events ||= []
|
283
|
-
@variables ||= Set.new
|
284
|
-
@block ||= block
|
285
|
-
end
|
286
|
-
|
287
|
-
# Same as #events
|
288
|
-
def to_a
|
289
|
-
@events
|
290
|
-
end
|
291
|
-
|
292
|
-
# Join the alda codes of #events with a specified delimiter.
|
293
|
-
# Returns a string representing the result.
|
294
|
-
def events_alda_codes delimiter = ' '
|
295
|
-
@events.map(&:to_alda_code).join delimiter
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
# The class mixes in EventList# and provides methods to play or parse.
|
300
|
-
class Score
|
301
|
-
include EventList
|
302
|
-
|
303
|
-
# Plays the score.
|
304
|
-
# @return The command line output of the +alda+ command.
|
305
|
-
# @example
|
306
|
-
# Alda::Score.new { piano_; c; d; e }.play
|
307
|
-
# # => "[27713] Parsing/evaluating...\n[27713] Playing...\n"
|
308
|
-
# # (and plays the sound)
|
309
|
-
# Alda::Score.new { piano_; c; d; e }.play from: 1
|
310
|
-
# # (plays only an E note)
|
311
|
-
def play **opts
|
312
|
-
Alda.play code: self, **opts
|
313
|
-
end
|
314
|
-
|
315
|
-
# Parses the score.
|
316
|
-
# @return The JSON string of the parse result.
|
317
|
-
# @example
|
318
|
-
# Alda::Score.new { piano_; c }.parse output: :events
|
319
|
-
# # => "[{\"event-type\":...}]\n"
|
320
|
-
def parse **opts
|
321
|
-
Alda.parse code: self, **opts
|
322
|
-
end
|
323
|
-
|
324
|
-
# Exports the score.
|
325
|
-
# @return The command line output of the +alda+ command.
|
326
|
-
# @example
|
327
|
-
# Alda::Score.new { piano_; c }.export output: 'temp.mid'
|
328
|
-
# # (outputs a midi file called temp.mid)
|
329
|
-
def export **opts
|
330
|
-
Alda.export code: self, **opts
|
331
|
-
end
|
332
|
-
|
333
|
-
# Saves the alda codes into a file.
|
334
|
-
def save filename
|
335
|
-
File.open(filename, 'w') { _1.puts to_s }
|
336
|
-
end
|
337
|
-
|
338
|
-
# Loads alda codes from a file.
|
339
|
-
def load filename
|
340
|
-
event = InlineLisp.new :alda_code, File.read(filename)
|
341
|
-
@events.push event
|
342
|
-
event
|
343
|
-
end
|
344
|
-
|
345
|
-
# @return Alda codes.
|
346
|
-
def to_s
|
347
|
-
events_alda_codes
|
348
|
-
end
|
349
|
-
|
350
|
-
# The initialization.
|
351
|
-
def initialize(...)
|
352
|
-
super
|
353
|
-
on_contained
|
354
|
-
end
|
355
|
-
|
356
|
-
# Clears all the events and variables.
|
357
|
-
def clear
|
358
|
-
@events.clear
|
359
|
-
@variables.clear
|
360
|
-
self
|
361
|
-
end
|
362
|
-
end
|
363
|
-
|
364
|
-
# An encapsulation for the REPL session for alda-rb.
|
365
|
-
class REPL
|
366
|
-
|
367
|
-
# The score object used in REPL.
|
368
|
-
# Includes Alda#, so it can refer to alda commandline.
|
369
|
-
class TempScore < Score
|
370
|
-
include Alda
|
371
|
-
|
372
|
-
Score.instance_methods(false).each do |meth|
|
373
|
-
define_method meth, Score.instance_method(meth)
|
374
|
-
end
|
375
|
-
|
376
|
-
def initialize session
|
377
|
-
super()
|
378
|
-
@session = session
|
379
|
-
end
|
380
|
-
|
381
|
-
def to_s
|
382
|
-
history
|
383
|
-
end
|
384
|
-
|
385
|
-
def history
|
386
|
-
@session.history.to_s
|
387
|
-
end
|
388
|
-
|
389
|
-
def clear_history
|
390
|
-
@session.clear_history
|
391
|
-
end
|
392
|
-
|
393
|
-
def get_binding
|
394
|
-
binding
|
395
|
-
end
|
396
|
-
|
397
|
-
alias quit exit
|
398
|
-
end
|
399
|
-
|
400
|
-
# The history.
|
401
|
-
attr_reader :history
|
402
|
-
|
403
|
-
# Initialization.
|
404
|
-
def initialize
|
405
|
-
@score = TempScore.new self
|
406
|
-
@binding = @score.get_binding
|
407
|
-
@lex = RubyLex.new
|
408
|
-
@history = StringIO.new
|
409
|
-
end
|
410
|
-
|
411
|
-
# Runs the session. Includes the start, the main loop, and the termination.
|
412
|
-
def run
|
413
|
-
start
|
414
|
-
while code = rb_code
|
415
|
-
break unless process_rb_code code
|
416
|
-
end
|
417
|
-
terminate
|
418
|
-
end
|
419
|
-
|
420
|
-
# Starts the session.
|
421
|
-
def start
|
422
|
-
end
|
423
|
-
|
424
|
-
# Reads the next Ruby codes input in the REPL session.
|
425
|
-
# It can intelligently continue reading if the code is not complete yet.
|
426
|
-
def rb_code
|
427
|
-
result = ''
|
428
|
-
begin
|
429
|
-
buf = Readline.readline '> '.green, true
|
430
|
-
return unless buf
|
431
|
-
result.concat buf, ?\n
|
432
|
-
ltype, indent, continue, block_open = @lex.check_state result
|
433
|
-
rescue Interrupt
|
434
|
-
$stdout.puts
|
435
|
-
retry
|
436
|
-
end while ltype || indent.nonzero? || continue || block_open
|
437
|
-
result
|
438
|
-
end
|
439
|
-
|
440
|
-
# Processes the Ruby codes read.
|
441
|
-
# Sending it to a score and sending the result to alda.
|
442
|
-
# @return +true+ for continue looping, +false+ for breaking the loop.
|
443
|
-
def process_rb_code code
|
444
|
-
@score.clear
|
445
|
-
begin
|
446
|
-
@binding.eval code
|
447
|
-
rescue StandardError, ScriptError => e
|
448
|
-
$stderr.print e.full_message
|
449
|
-
return true
|
450
|
-
rescue Interrupt
|
451
|
-
return true
|
452
|
-
rescue SystemExit
|
453
|
-
return false
|
454
|
-
end
|
455
|
-
code = @score.events_alda_codes
|
456
|
-
unless code.empty?
|
457
|
-
$stdout.puts code.yellow
|
458
|
-
play_score code
|
459
|
-
end
|
460
|
-
true
|
461
|
-
end
|
462
|
-
|
463
|
-
# Tries to run the block and rescue CommandLineError#.
|
464
|
-
def try_command # :block:
|
465
|
-
begin
|
466
|
-
yield
|
467
|
-
rescue CommandLineError => e
|
468
|
-
puts e.message.red
|
469
|
-
end
|
470
|
-
end
|
471
|
-
|
472
|
-
# Plays the score.
|
473
|
-
def play_score code
|
474
|
-
try_command do
|
475
|
-
Alda.play code: code, history: @history
|
476
|
-
@history.puts code
|
477
|
-
end
|
478
|
-
end
|
479
|
-
|
480
|
-
# Terminates the REPL session.
|
481
|
-
def terminate
|
482
|
-
clear_history
|
483
|
-
end
|
484
|
-
|
485
|
-
# Clears the history.
|
486
|
-
def clear_history
|
487
|
-
@history = StringIO.new
|
488
|
-
end
|
489
|
-
end
|
490
|
-
|
491
|
-
# The error is raised when one tries to
|
492
|
-
# run a non-existing subcommand of +alda+.
|
493
|
-
class CommandLineError < StandardError
|
494
|
-
|
495
|
-
# The <tt>Process::Status</tt> object representing the status of
|
496
|
-
# the process that runs +alda+ command.
|
497
|
-
attr_reader :status
|
498
|
-
|
499
|
-
# The port on which the problematic Alda server runs.
|
500
|
-
# @example
|
501
|
-
# begin
|
502
|
-
# Alda.play({port: 1108}, code: "y")
|
503
|
-
# rescue CommandLineError => e
|
504
|
-
# e.port # => 1108
|
505
|
-
# end
|
506
|
-
attr_reader :port
|
507
|
-
|
508
|
-
# Create a CommandLineError# object.
|
509
|
-
# @param status The status of the process running +alda+ command.
|
510
|
-
# @param msg The exception message.
|
511
|
-
def initialize status, msg = nil
|
512
|
-
if match = msg&.match(/^\[(?<port>\d+)\]\sERROR\s(?<message>.*)$/)
|
513
|
-
super match[:message]
|
514
|
-
@port = match[:port].to_i
|
515
|
-
else
|
516
|
-
super msg
|
517
|
-
@port = nil
|
518
|
-
end
|
519
|
-
@status = status
|
520
|
-
end
|
521
|
-
end
|
522
|
-
|
523
|
-
# This error is raised when one tries to
|
524
|
-
# append events in an EventList# in a wrong order.
|
525
|
-
# @example
|
526
|
-
# Alda::Score.new do
|
527
|
-
# motif = f4 f e e d d c2
|
528
|
-
# g4 f e d c2 # It commented out, error will not occur
|
529
|
-
# c4 c g g a a g2 motif # (OrderError)
|
530
|
-
# end
|
531
|
-
class OrderError < StandardError
|
532
|
-
|
533
|
-
# The expected element gotten if it is of the correct order.
|
534
|
-
# @see #got
|
535
|
-
# @example
|
536
|
-
# Alda::Score.new do
|
537
|
-
# motif = f4 f e e d d c2
|
538
|
-
# g4 f e d c2
|
539
|
-
# p @events.size # => 2
|
540
|
-
# c4 c g g a a g2 motif
|
541
|
-
# rescue OrderError => e
|
542
|
-
# p @events.size # => 1
|
543
|
-
# p e.expected # => #<Alda::EventContainer:...>
|
544
|
-
# p e.got # => #<Alda::EventContainer:...>
|
545
|
-
# end
|
546
|
-
attr_reader :expected
|
547
|
-
|
548
|
-
# The actually gotten element.
|
549
|
-
# For an example, see #expected.
|
550
|
-
# @see #expected
|
551
|
-
attr_reader :got
|
552
|
-
|
553
|
-
def initialize expected, got
|
554
|
-
super 'events are out of order'
|
555
|
-
@expected = expected
|
556
|
-
@got = got
|
557
|
-
end
|
558
|
-
end
|
559
|
-
|
560
|
-
# The class of elements of EventList#events.
|
561
|
-
class Event
|
562
|
-
|
563
|
-
# The EventList# object that contains it.
|
564
|
-
# Note that it may not be directly contained, but with an EventContainer#
|
565
|
-
# object in the middle.
|
566
|
-
attr_accessor :parent
|
567
|
-
|
568
|
-
# The EventContainer# object that contains it.
|
569
|
-
# It may be +nil+, especially probably when
|
570
|
-
# it itself is an EventContainer#.
|
571
|
-
attr_accessor :container
|
572
|
-
|
573
|
-
# The callback invoked when it is contained in an EventContainer#.
|
574
|
-
# It is overridden in InlineLisp# and EventList#.
|
575
|
-
# @example
|
576
|
-
# class Alda::Note
|
577
|
-
# def on_contained
|
578
|
-
# puts 'a note contained'
|
579
|
-
# end
|
580
|
-
# end
|
581
|
-
# Alda::Score.new { c } # => outputs "a note contained"
|
582
|
-
def on_contained
|
583
|
-
end
|
584
|
-
|
585
|
-
# Converts to alda code. To be overridden.
|
586
|
-
def to_alda_code
|
587
|
-
''
|
588
|
-
end
|
589
|
-
end
|
590
|
-
|
591
|
-
# The class for objects containing an event.
|
592
|
-
class EventContainer < Event
|
593
|
-
|
594
|
-
# The contained Event# object.
|
595
|
-
attr_accessor :event
|
596
|
-
|
597
|
-
# The repetition counts. +nil+ if none.
|
598
|
-
attr_accessor :count
|
599
|
-
|
600
|
-
# The repetition labels. Empty if none.
|
601
|
-
attr_accessor :labels
|
602
|
-
|
603
|
-
# @param event The Event# object to be contained.
|
604
|
-
# @param parent The EventList# object containing the event.
|
605
|
-
def initialize event, parent
|
606
|
-
@event = event
|
607
|
-
@parent = parent
|
608
|
-
@labels = []
|
609
|
-
on_containing
|
610
|
-
end
|
611
|
-
|
612
|
-
# Make #event a Chord# object.
|
613
|
-
# @example
|
614
|
-
# Alda::Score.new { piano_; c/-e/g }.play
|
615
|
-
# # (plays the chord Cm)
|
616
|
-
#
|
617
|
-
# If the contained event is a Part# object,
|
618
|
-
# make #event a new Part# object.
|
619
|
-
# @example
|
620
|
-
# Alda::Score.new { violin_/viola_/cello_; e; f; g}.play
|
621
|
-
# # (plays notes E, F, G with three instruments simultaneously)
|
622
|
-
def / other
|
623
|
-
unless (expected = other) == (got = @parent.events.pop)
|
624
|
-
raise OrderError.new expected, got
|
625
|
-
end
|
626
|
-
@event =
|
627
|
-
if @event.is_a? Part
|
628
|
-
Part.new @event.names + other.event.names, other.event.arg
|
629
|
-
else
|
630
|
-
Chord.new @event, other.event
|
631
|
-
end
|
632
|
-
self
|
633
|
-
end
|
634
|
-
|
635
|
-
def to_alda_code
|
636
|
-
result = @event.to_alda_code
|
637
|
-
unless @labels.empty?
|
638
|
-
result.concat ?', @labels.map(&:to_alda_code).join(?,)
|
639
|
-
end
|
640
|
-
result.concat ?*, @count.to_alda_code if @count
|
641
|
-
result
|
642
|
-
end
|
643
|
-
|
644
|
-
# Marks repetition.
|
645
|
-
def * num
|
646
|
-
@count = (@count || 1) * num
|
647
|
-
self
|
648
|
-
end
|
649
|
-
|
650
|
-
# Marks alternative repetition.
|
651
|
-
def % labels
|
652
|
-
labels = [labels] unless labels.is_a? Array
|
653
|
-
@labels.replace labels.to_a
|
654
|
-
self
|
655
|
-
end
|
656
|
-
|
657
|
-
def event= event
|
658
|
-
@event = event
|
659
|
-
on_containing
|
660
|
-
@event
|
661
|
-
end
|
662
|
-
|
663
|
-
def on_containing
|
664
|
-
if @event
|
665
|
-
@event.container = self
|
666
|
-
@event.parent = @parent
|
667
|
-
@event.on_contained
|
668
|
-
end
|
669
|
-
end
|
670
|
-
|
671
|
-
def method_missing name, *args
|
672
|
-
result = @event.__send__ name, *args
|
673
|
-
result = self if result == @event
|
674
|
-
result
|
675
|
-
end
|
676
|
-
end
|
677
|
-
|
678
|
-
# Inline lisp event.
|
679
|
-
class InlineLisp < Event
|
680
|
-
|
681
|
-
# The function name of the lisp function
|
682
|
-
attr_accessor :head
|
683
|
-
|
684
|
-
# The arguments passed to the lisp function.
|
685
|
-
# Its elements can be
|
686
|
-
# Array#, Hash#, Numeric#, String#, Symbol#, or Event#.
|
687
|
-
attr_accessor :args
|
688
|
-
|
689
|
-
# The underlines in +head+ will be converted to hyphens.
|
690
|
-
def initialize head, *args
|
691
|
-
@head = head.to_s.gsub ?_, ?-
|
692
|
-
@args = args
|
693
|
-
end
|
694
|
-
|
695
|
-
def to_alda_code
|
696
|
-
"(#{head} #{args.map(&:to_alda_code).join ' '})"
|
697
|
-
end
|
698
|
-
|
699
|
-
def on_contained
|
700
|
-
super
|
701
|
-
@args.reverse_each do |event|
|
702
|
-
if event.is_a?(Event) && (expected = event) != (got = @parent.events.pop)
|
703
|
-
raise OrderError.new expected, got
|
704
|
-
end
|
705
|
-
end
|
706
|
-
end
|
707
|
-
end
|
708
|
-
|
709
|
-
# A note event.
|
710
|
-
class Note < Event
|
711
|
-
|
712
|
-
# The string representing the pitch
|
713
|
-
attr_accessor :pitch
|
714
|
-
|
715
|
-
# The string representing the duration.
|
716
|
-
# It ends with +~+ if the note slurs.
|
717
|
-
attr_accessor :duration
|
718
|
-
|
719
|
-
# The underlines in +duration+ will be converted to +~+.
|
720
|
-
# Exclamation mark and question mark in +duration+
|
721
|
-
# will be interpreted as accidentals in #pitch.
|
722
|
-
#
|
723
|
-
# The number of underlines at the end of +duration+ means:
|
724
|
-
# neither natural nor slur if 0,
|
725
|
-
# natural if 1,
|
726
|
-
# slur if 2,
|
727
|
-
# both natural and slur if 3.
|
728
|
-
def initialize pitch, duration
|
729
|
-
@pitch = pitch.to_s
|
730
|
-
@duration = duration.to_s.tr ?_, ?~
|
731
|
-
case @duration[-1]
|
732
|
-
when ?! # sharp
|
733
|
-
@pitch.concat ?+
|
734
|
-
@duration[-1] = ''
|
735
|
-
when ?? # flat
|
736
|
-
@pitch.concat ?-
|
737
|
-
@duration[-1] = ''
|
738
|
-
end
|
739
|
-
waves = /(?<str>~+)\z/ =~ @duration ? str.size : return
|
740
|
-
@duration[@duration.length - waves..] = ''
|
741
|
-
if waves >= 2
|
742
|
-
waves -= 2
|
743
|
-
@duration.concat ?~
|
744
|
-
end
|
745
|
-
@pitch.concat ?_ * waves
|
746
|
-
end
|
747
|
-
|
748
|
-
# Append a sharp sign after #pitch.
|
749
|
-
# @example
|
750
|
-
# Alda::Score.new { piano_; +c }.play
|
751
|
-
# # (plays a C\# note)
|
752
|
-
def +@
|
753
|
-
@pitch.concat ?+
|
754
|
-
self
|
755
|
-
end
|
756
|
-
|
757
|
-
# Append a flat sign after #pitch.
|
758
|
-
# @example
|
759
|
-
# Alda::Score.new { piano_; -d }.play
|
760
|
-
# # (plays a Db note)
|
761
|
-
def -@
|
762
|
-
@pitch.concat ?-
|
763
|
-
self
|
764
|
-
end
|
765
|
-
|
766
|
-
# Append a natural sign after #pitch
|
767
|
-
# @example
|
768
|
-
# Alda::Score.new { piano_; key_sig 'f+'; ~f }.play
|
769
|
-
# # (plays a F note)
|
770
|
-
def ~
|
771
|
-
@pitch.concat ?_
|
772
|
-
self
|
773
|
-
end
|
774
|
-
|
775
|
-
def to_alda_code
|
776
|
-
result = @pitch + @duration
|
777
|
-
result.concat ?*, @count.to_alda_code if @count
|
778
|
-
result
|
779
|
-
end
|
780
|
-
end
|
781
|
-
|
782
|
-
# A rest event.
|
783
|
-
class Rest < Event
|
784
|
-
|
785
|
-
# The string representing a duration.
|
786
|
-
attr_accessor :duration
|
787
|
-
|
788
|
-
# Underlines in +duration+ will be converted to +~+.
|
789
|
-
def initialize duration
|
790
|
-
@duration = duration.to_s.tr ?_, ?~
|
791
|
-
end
|
792
|
-
|
793
|
-
def to_alda_code
|
794
|
-
?r + @duration
|
795
|
-
end
|
796
|
-
end
|
797
|
-
|
798
|
-
# An octave event.
|
799
|
-
class Octave < Event
|
800
|
-
|
801
|
-
# The string representing the octave's number.
|
802
|
-
# It can be empty, serving for #+@ and #-@.
|
803
|
-
attr_accessor :num
|
804
|
-
|
805
|
-
# Positive for up, negative for down, and 0 as default.
|
806
|
-
attr_accessor :up_or_down
|
807
|
-
|
808
|
-
def initialize num
|
809
|
-
@num = num.to_s
|
810
|
-
@up_or_down = 0
|
811
|
-
end
|
812
|
-
|
813
|
-
# Octave up.
|
814
|
-
# @example
|
815
|
-
# Alda::Score.new { piano_; c; +o; c }.play
|
816
|
-
# # (plays C4, then C5)
|
817
|
-
# @see #-@
|
818
|
-
def +@
|
819
|
-
@up_or_down += 1
|
820
|
-
self
|
821
|
-
end
|
822
|
-
|
823
|
-
# Octave down.
|
824
|
-
# @see #+@.
|
825
|
-
def -@
|
826
|
-
@up_or_down -= 1
|
827
|
-
self
|
828
|
-
end
|
829
|
-
|
830
|
-
def to_alda_code
|
831
|
-
case @up_or_down <=> 0
|
832
|
-
when 0
|
833
|
-
?o + @num
|
834
|
-
when 1
|
835
|
-
?> * @up_or_down
|
836
|
-
when -1
|
837
|
-
?< * -@up_or_down
|
838
|
-
end
|
839
|
-
end
|
840
|
-
end
|
841
|
-
|
842
|
-
# A chord event.
|
843
|
-
# Includes EventList#.
|
844
|
-
class Chord < Event
|
845
|
-
include EventList
|
846
|
-
|
847
|
-
# EventList#x invokes this method.
|
848
|
-
# @see EventList#method_missing
|
849
|
-
# @param events In most cases, should not be used.
|
850
|
-
# @param block To be passed with the Chord# object as +self+.
|
851
|
-
# @example
|
852
|
-
# Alda::Score.new { piano_; x { c; -e; g } }.play
|
853
|
-
# # (plays chord Cm)
|
854
|
-
def initialize *events, &block
|
855
|
-
@events = events
|
856
|
-
super &block
|
857
|
-
end
|
858
|
-
|
859
|
-
def to_alda_code
|
860
|
-
events_alda_codes ?/
|
861
|
-
end
|
862
|
-
end
|
863
|
-
|
864
|
-
# A part event.
|
865
|
-
class Part < Event
|
866
|
-
|
867
|
-
# The names of the part. To be joined with +/+ as delimiter.
|
868
|
-
attr_accessor :names
|
869
|
-
|
870
|
-
# The nickname of the part. +nil+ if none.
|
871
|
-
attr_accessor :arg
|
872
|
-
|
873
|
-
def initialize names, arg = nil
|
874
|
-
@names = names.map { |name| name.to_s.tr ?_, ?- }
|
875
|
-
@arg = arg
|
876
|
-
end
|
877
|
-
|
878
|
-
def to_alda_code
|
879
|
-
result = @names.join ?/
|
880
|
-
result.concat " \"#{@arg}\"" if @arg
|
881
|
-
result.concat ?:
|
882
|
-
end
|
883
|
-
|
884
|
-
# Enables dot accessor.
|
885
|
-
# @example
|
886
|
-
# Alda::Score.new do
|
887
|
-
# violin_/viola_/cello_('strings'); g1_1_1
|
888
|
-
# strings_.cello_; -o; c1_1_1
|
889
|
-
# end.play
|
890
|
-
def method_missing name, *args
|
891
|
-
str = name.to_s
|
892
|
-
return super unless str[-1] == ?_
|
893
|
-
str[-1] = ''
|
894
|
-
@names.last.concat ?., str
|
895
|
-
if args.size == 1
|
896
|
-
joined = args.first
|
897
|
-
unless (got = @parent.events.pop) == (expected = joined)
|
898
|
-
raise OrderError.new expected, got
|
899
|
-
end
|
900
|
-
unless @container
|
901
|
-
@container = EventContainer.new nil, @parent
|
902
|
-
@parent.events.delete self
|
903
|
-
@parent.push @container
|
904
|
-
end
|
905
|
-
@container.event = Sequence.join self, joined
|
906
|
-
end
|
907
|
-
end
|
908
|
-
end
|
909
|
-
|
910
|
-
# A voice event.
|
911
|
-
class Voice < Event
|
912
|
-
|
913
|
-
# The string representing the voice's number.
|
914
|
-
attr_accessor :num
|
915
|
-
|
916
|
-
def initialize num
|
917
|
-
@num = num
|
918
|
-
end
|
919
|
-
|
920
|
-
def to_alda_code
|
921
|
-
?V + num + ?:
|
922
|
-
end
|
923
|
-
end
|
924
|
-
|
925
|
-
# A CRAM event. Includes EventList#.
|
926
|
-
class Cram < Event
|
927
|
-
include EventList
|
928
|
-
|
929
|
-
# The string representing the duration of the CRAM.
|
930
|
-
attr_accessor :duration
|
931
|
-
|
932
|
-
# EventList#t invokes this method.
|
933
|
-
# @see EventList#method_missing
|
934
|
-
# @param block To be passed with the CRAM as +self+.
|
935
|
-
# @example
|
936
|
-
# Alda::Score.new { piano_; t8 { x; y; }}
|
937
|
-
def initialize duration, &block
|
938
|
-
@duration = duration
|
939
|
-
super &block
|
940
|
-
end
|
941
|
-
|
942
|
-
def to_alda_code
|
943
|
-
"{#{events_alda_codes}}#@duration"
|
944
|
-
end
|
945
|
-
end
|
946
|
-
|
947
|
-
# A marker event.
|
948
|
-
# @see AtMarker#
|
949
|
-
class Marker < Event
|
950
|
-
|
951
|
-
# The marker's name
|
952
|
-
attr_accessor :name
|
953
|
-
|
954
|
-
# Underlines in +name+ is converted to hyphens.
|
955
|
-
def initialize name
|
956
|
-
@name = name.to_s.tr ?_, ?-
|
957
|
-
end
|
958
|
-
|
959
|
-
def to_alda_code
|
960
|
-
?% + @name
|
961
|
-
end
|
962
|
-
end
|
963
|
-
|
964
|
-
# An at-marker event.
|
965
|
-
# @see Marker#
|
966
|
-
class AtMarker < Event
|
967
|
-
|
968
|
-
# The corresponding marker's name
|
969
|
-
attr_accessor :name
|
970
|
-
|
971
|
-
# Underlines in +name+ is converted to hyphens.
|
972
|
-
def initialize name
|
973
|
-
@name = name.to_s.tr ?_, ?-
|
974
|
-
end
|
975
|
-
|
976
|
-
def to_alda_code
|
977
|
-
?@ + @name
|
978
|
-
end
|
979
|
-
end
|
980
|
-
|
981
|
-
# A sequence event. Includes EventList#.
|
982
|
-
class Sequence < Event
|
983
|
-
include EventList
|
984
|
-
|
985
|
-
# Using this module can fix a bug of Array#flatten.
|
986
|
-
# @example
|
987
|
-
# def (a = Object.new).method_missing(...)
|
988
|
-
# Object.new
|
989
|
-
# end
|
990
|
-
# [a].flatten rescue $! # => #<TypeError:...>
|
991
|
-
# using Alda::Sequence::RefineFlatten
|
992
|
-
# [a].flatten # => [#<Object:...>]
|
993
|
-
module RefineFlatten
|
994
|
-
refine Array do
|
995
|
-
def flatten
|
996
|
-
each_with_object [] do |element, result|
|
997
|
-
if element.is_a? Array
|
998
|
-
result.push *element.flatten
|
999
|
-
else
|
1000
|
-
result.push element
|
1001
|
-
end
|
1002
|
-
end
|
1003
|
-
end
|
1004
|
-
end
|
1005
|
-
end
|
1006
|
-
using RefineFlatten
|
1007
|
-
|
1008
|
-
def to_alda_code
|
1009
|
-
@events.to_alda_code
|
1010
|
-
end
|
1011
|
-
|
1012
|
-
# Creates a Sequence# object by joining +events+.
|
1013
|
-
# The EventContainer# objects are extracted,
|
1014
|
-
# and the Sequence# objects are flattened.
|
1015
|
-
def self.join *events
|
1016
|
-
new do
|
1017
|
-
@events = events.map do |event|
|
1018
|
-
while event.is_a?(EventContainer) && !event.count && event.labels.empty?
|
1019
|
-
event = event.event
|
1020
|
-
end
|
1021
|
-
event.is_a?(Sequence) ? event.events : event
|
1022
|
-
end.flatten
|
1023
|
-
end
|
1024
|
-
end
|
1025
|
-
end
|
1026
|
-
|
1027
|
-
# A set-variable event.
|
1028
|
-
# Includes EventList#.
|
1029
|
-
class SetVariable < Event
|
1030
|
-
include EventList
|
1031
|
-
|
1032
|
-
# The name of the variable.
|
1033
|
-
attr_accessor :name
|
1034
|
-
|
1035
|
-
# The events passed to it using arguments instead of a block.
|
1036
|
-
attr_reader :original_events
|
1037
|
-
|
1038
|
-
def initialize name, *events, &block
|
1039
|
-
@name = name.to_sym
|
1040
|
-
@original_events = events
|
1041
|
-
@events = events.clone
|
1042
|
-
super &block
|
1043
|
-
end
|
1044
|
-
|
1045
|
-
# Specially, the result ends with a newline.
|
1046
|
-
def to_alda_code
|
1047
|
-
"#@name = #{events_alda_codes}\n"
|
1048
|
-
end
|
1049
|
-
|
1050
|
-
def on_contained
|
1051
|
-
super
|
1052
|
-
@parent.variables.add @name
|
1053
|
-
@original_events.reverse_each do |event|
|
1054
|
-
unless (expected = event) == (got = @parent.events.pop)
|
1055
|
-
raise OrderError.new expected, got
|
1056
|
-
end
|
1057
|
-
end
|
1058
|
-
end
|
1059
|
-
end
|
1060
|
-
|
1061
|
-
# A get-variable event
|
1062
|
-
class GetVariable < Event
|
1063
|
-
|
1064
|
-
# The name of the variable
|
1065
|
-
attr_accessor :name
|
1066
|
-
|
1067
|
-
def initialize name
|
1068
|
-
@name = name
|
1069
|
-
end
|
1070
|
-
|
1071
|
-
def to_alda_code
|
1072
|
-
@name.to_s
|
1073
|
-
end
|
1074
|
-
end
|
1075
|
-
end
|
3
|
+
require 'alda-rb/version'
|
4
|
+
require 'alda-rb/patches'
|
5
|
+
require 'alda-rb/error'
|
6
|
+
require 'alda-rb/commandline'
|
7
|
+
require 'alda-rb/event_list'
|
8
|
+
require 'alda-rb/repl'
|
9
|
+
require 'alda-rb/event'
|