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