/
score.rb
350 lines (283 loc) · 8.04 KB
/
score.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
require 'forwardable'
require 'music/attributes'
require 'music/temporal'
require 'music/timeline'
require 'music/pretty_printer'
module Music
module Score
class Base
include Temporal
# Return the empty Score.
def self.none; rest(0) end
def none; self.class.none end
# Sequential composition.
def &(other)
Seq.new(self, other)
end
# Parallel (concurrent) composition.
def |(other)
Par.new(self, other)
end
# Parallel composition. The duration of the longer sequence is truncated
# to match the duration of the shorter one.
def /(other)
d = [duration, other.duration].min
take(d) | other.take(d)
end
# Sequentially compose a sequence with itself n times.
def repeat(n)
unless n.kind_of?(Integer)
raise TypeError, "Expected Integer, got #{n.class}."
end
unless n >= 0
raise ArgumentError, "Expected non-negative Integer, got #{n}."
end
if n.zero? then none
else
(1..(n-1)).inject(self) { |mus,rep| mus & self }
end
end
alias * repeat
# Delay a sequence by composing it with a Rest.
def delay(dur)
rest(dur) & self
end
def transpose(hs)
map { |a| a.transpose(hs) }
end
# Test for equivalence. Two Scores are _equivalent_ if they
# produce idential Timelines when interpreted.
def ===(mus)
self.to_timeline == mus.to_timeline
end
def to_timeline
TimelineInterpreter.eval(self)
end
def inspect
PrettyPrinter.eval(self)
end
alias to_s inspect
end
class Seq < Base
attr_reader :left, :right, :duration, :final_onset
def initialize(left, right)
@left, @right = left, right
@duration = @left.duration + @right.duration
@final_onset = @left.duration + @right.final_onset
end
def ==(other)
case other
when Seq
left == other.left && right == other.right
else false
end
end
def map(&block)
self.class.new(left.map(&block), right.map(&block))
end
def eval(interpreter, c0)
l = left.eval(interpreter, c0)
c1 = c0.advance(left.duration)
r = right.eval(interpreter, c1)
interpreter.eval_seq(l, r, c0)
end
def take(d)
dl = left.duration
if d <= dl
left.take(d)
else
left & right.take(d - dl)
end
end
def drop(d)
dl = left.duration
if d <= dl
left.drop(d) & right
else
right.drop(d-dl)
end
end
def reverse
self.class.new(right.reverse, left.reverse)
end
end
class Par < Base
attr_reader :top, :bottom, :duration, :final_onset
def initialize(top, bottom)
dt = top.duration
db = bottom.duration
if dt == db
@top, @bottom = top, bottom
elsif dt > db
@top = top
@bottom = bottom & rest(dt-db)
elsif db > dt
@top = top & rest(db-dt)
@bottom = bottom
end
@duration = [@top.duration, @bottom.duration].max
@final_onset = [@top.final_onset, @bottom.final_onset].max
end
def ==(other)
case other
when Par
top == other.top && bottom == other.bottom
else false
end
end
def map(&block)
self.class.new(top.map(&block), bottom.map(&block))
end
def eval(interpreter, c0)
t = interpreter.eval(top, c0)
b = interpreter.eval(bottom, c0)
interpreter.eval_par(t, b, c0)
end
def take(d)
top.take(d) | bottom.take(d)
end
def drop(d)
top.drop(d) | bottom.drop(d)
end
def reverse
self.class.new(top.reverse, bottom.reverse)
end
end
class Group < Base
extend Forwardable
attr_reader :score, :attributes
def_delegators :@score, :duration, :final_onset
def initialize(score, attributes = {})
@score, @attributes = score, attributes
end
def ==(other)
case other
when Group: score == other.score
else false
end
end
def map(&block)
self.class.new(score.map(&block), attributes)
end
def take(d)
self.class.new(score.take(d), attributes)
end
def drop(d)
self.class.new(score.drop(d), attributes)
end
def reverse
self.class.new(score.reverse, attributes)
end
def eval(interpreter, c0)
c1 = c0.push(Scope.new(c0.time, final_onset, attributes))
m = score.eval(interpreter, c1)
interpreter.eval_group(m, c0)
end
end
class ScoreObject < Base
include Attributes
include Temporal
def final_onset; 0 end
def map; yield self end
def reverse; self end
def inspect
PrettyPrinter.eval(self)
end
alias to_s inspect
def to_timeline
TimelineInterpreter.eval(self)
end
def read(name)
attributes[name]
end
def take(time)
if time <= 0 then none
else
update(:duration, [time, duration].min)
end
end
def drop(time)
if time >= duration then none
else
update(:duration, (duration - time).clip(0..duration))
end
end
# Default implementation
def transpose(interval) self end
end
# Remain silent for the duration.
class Rest < ScoreObject
attr_reader :attributes
def initialize(duration, attributes = {})
@attributes = attributes.merge(:duration => duration)
end
def ==(other)
case other
when Rest: duration == other.duration
else false
end
end
def update(name, val)
a = attributes.merge(name => val)
d = a.delete(:duration)
self.class.new(d, a)
end
def eval(interpreter, context)
interpreter.eval_rest(self, context)
end
end
# A note has a pitch and a duration.
class Note < ScoreObject
attr_reader :attributes
def initialize(pitch, duration, attrs = {})
@attributes = attrs.merge(:pitch => pitch, :duration => duration)
end
def ==(other)
case other
when Note
[pitch, duration] == [other.pitch, other.duration]
else false
end
end
def transpose(interval)
update(:pitch, pitch + interval)
end
def eval(interpreter, context)
n1, c1 = inherit(context)
interpreter.eval_note(n1, c1)
end
def update(name, val)
a = attributes.merge(name => val)
p, d = a.values_at(:pitch, :duration)
self.class.new(p, d, a)
end
private
def inherit(context)
c1 = context.accept(attributes)
p1, d1 = c1.attributes.values_at(:pitch, :duration)
[self.class.new(p1, d1, c1.attributes), c1]
end
end
class Controller < ScoreObject
include Instant
attr_reader :attributes
def initialize(name, as = {})
@attributes = as.merge(:name => name)
end
def ==(other)
case other
when Controller
[name, value] == [other.name, other.value]
else false
end
end
def update(key, val)
a = attributes.merge(key => val)
self.class.new(name, a)
end
def eval(interpreter, context)
interpreter.eval_controller(self, context)
end
end
end
end