metronome-odd 1.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 +7 -0
- data/bin/metronome-odd +406 -0
- data/data/metronome-odd/beat_downbeat.aiff +0 -0
- data/data/metronome-odd/beat_upbeat.aiff +0 -0
- data/lib/metronome-odd.rb +261 -0
- data/lib/metronome-odd/interruptible_sleep.rb +14 -0
- data/lib/metronome-odd/keypress.rb +66 -0
- data/lib/metronome-odd/parse_line.rb +40 -0
- data/lib/metronome-odd/save.rb +57 -0
- data/tests/test_metronome-odd.rb +13 -0
- metadata +55 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3498f8b8652455ef065ce8876395adb9bfcb0e8139a5ce0938981224c51ddfb1
|
|
4
|
+
data.tar.gz: 67a174595dab000f0b193a51299f4bf0f73eb0b97f589633c00cee8f3c494713
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4773f9a3f5a6abd7ae901dc819f139bda1ecc0d5a132abe9287a6e7401d39b177382818b47eed57f29a0064f56bce5db1800f1f97e6a8de727771ba193a48a6e
|
|
7
|
+
data.tar.gz: c3843cd332f0fc9d077541b136980145beea2b759db6e2aeb41b7b5bc69dde7d2bab50e8b29e1fce7016c8107f034bb7f7b14fad4ff55381c59f8d54b12b53e7
|
data/bin/metronome-odd
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'metronome-odd'
|
|
4
|
+
|
|
5
|
+
ParseLine::Line.add_command(:play, /^play$/)
|
|
6
|
+
ParseLine::Line.add_command(:loop, /^loop$/)
|
|
7
|
+
ParseLine::Line.add_command(:loop_speed_up, /^loop:(?<first>\d+),(?<second>\d+)\((?<times>\d+)\)$/)
|
|
8
|
+
ParseLine::Line.add_command(:print_tempo, /^tempo$/)
|
|
9
|
+
ParseLine::Line.add_command(:next_tempo, /^next_tempo:(?<tempo>\d+)$/)
|
|
10
|
+
ParseLine::Line.add_command(:next_tempo_short, /^nt:(?<tempo>\d+)$/)
|
|
11
|
+
ParseLine::Line.add_command(:force_tempo, /^force_tempo:(?<tempo>\d+)$/)
|
|
12
|
+
ParseLine::Line.add_command(:force_tempo_short, /^ft:(?<tempo>\d+)$/)
|
|
13
|
+
ParseLine::Line.add_command(:signature, /^sign:(?<n>\d+)x(?<d>2|4|8|16)$/)
|
|
14
|
+
ParseLine::Line.add_command(:odd_signature, /^sign:(?<beats>(\d\.\d)(,(\d\.\d))*)$/)
|
|
15
|
+
ParseLine::Line.add_command(:add_bar, /^add:(\((?<tempo>\d+)\))?(?<n>\d+)x(?<d>2|4|8|16)(\((?<times>\d+)\))?$/)
|
|
16
|
+
ParseLine::Line.add_command(:odd_bar, /^add:(\((?<tempo>\d+)\))?(?<beats>(\d\.\d)(,(\d\.\d))*)(\((?<times>\d+)\))?$/)
|
|
17
|
+
ParseLine::Line.add_command(:add_1, /^add$/)
|
|
18
|
+
ParseLine::Line.add_command(:add, /^add:(?<times>\d+)$/)
|
|
19
|
+
ParseLine::Line.add_command(:save, /^save:(?<practice>\w+)?$/)
|
|
20
|
+
ParseLine::Line.add_command(:load, /^load:(?<practice>\w+)?$/)
|
|
21
|
+
ParseLine::Line.add_command(:list, /^list$/)
|
|
22
|
+
ParseLine::Line.add_command(:reset, /^reset$/)
|
|
23
|
+
ParseLine::Line.add_command(:help, /^help$/)
|
|
24
|
+
ParseLine::Line.add_command(:help_command, /^help:(?<command>\w+)$/)
|
|
25
|
+
ParseLine::Line.add_command(:print, /^print$/)
|
|
26
|
+
ParseLine::Line.add_command(:quit, /^quit$/)
|
|
27
|
+
ParseLine::Line.add_command(:empty, /^$/)
|
|
28
|
+
|
|
29
|
+
ParseLine::Line.add_command(:example, /^example$/)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
puts
|
|
33
|
+
puts "Welcome to the Programmable Metronome, by Billy Regodón"
|
|
34
|
+
|
|
35
|
+
def help
|
|
36
|
+
puts
|
|
37
|
+
puts "Use the following syntax: add:(t)nxd(T), to add a nxd bar of tempo t, T times"
|
|
38
|
+
puts "e.g.: add:(100)4x4(8) adds 8 bars of 4x4 with tempo 100"
|
|
39
|
+
puts
|
|
40
|
+
puts """Type
|
|
41
|
+
\"play\" to play the programmed bars
|
|
42
|
+
\"loop\" to play them repeatedly, press \'crtl\'+C to stop
|
|
43
|
+
\"loop:xf(N)\" to play the practice session repeatedly, speeding up xf in N repetitions
|
|
44
|
+
\"loop:t1,t2(N)\" to play the practice session, changing tempo from t1 to t2 in N repetitions
|
|
45
|
+
\"next_tempo:t\" to set the tempo of the next added bar, in beats per minute or bpm
|
|
46
|
+
you may use \"nt\" instead of \"next_tempo:t\"
|
|
47
|
+
\"force_tempo:t\" to change the tempo of the current practice session
|
|
48
|
+
you may use \"ft\" instead of \"force_tempo:t\"
|
|
49
|
+
\"sign:nxd\" to set a standard time signature, with a multiple of 2, up to 16
|
|
50
|
+
\"sign:f,f,f...\" to set an arbitrary odd bar, with f the duration of the beats
|
|
51
|
+
\"add:nxd\" to add a standard time signature with the set tempo
|
|
52
|
+
\"add:f,f,f...\" to add an arbitrary odd bar with the set tempo
|
|
53
|
+
\"add:(t)nxd(N)\" to add N bars of a standard time signature with tempo t (t and N optional)
|
|
54
|
+
\"add:(t)f,f,f...(N)\" to add N bars of an arbitrary odd bar with tempo t (t and N optional)
|
|
55
|
+
\"add\" to add one bar of the previously set type
|
|
56
|
+
\"add:N\" to add N bars of the previously set type
|
|
57
|
+
\"load:practice\" to load a previously saved practice session
|
|
58
|
+
\"save:practice\" to save the current practice session
|
|
59
|
+
\"list\" to list the available saved practice sessions
|
|
60
|
+
\"reset\" to reset to default and dismiss current practice session
|
|
61
|
+
\"help\" to print this help
|
|
62
|
+
\"help:command\" to print the help for another command
|
|
63
|
+
\"quit\" to exit"""
|
|
64
|
+
puts """\nIt is recommended that your create a practice folder to save your practice sessions
|
|
65
|
+
and that you run \"metronome-odd\" in that folder."""
|
|
66
|
+
puts """\nType \"example\" to print some example commands."""
|
|
67
|
+
puts
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def help_command(command)
|
|
71
|
+
puts
|
|
72
|
+
puts "Type"
|
|
73
|
+
case command
|
|
74
|
+
when "play"
|
|
75
|
+
puts " \"play\" to play the programmed bars"
|
|
76
|
+
when "loop"
|
|
77
|
+
puts """ \"loop\" to play the programmed bars repeatedly, press \'crtl\'+C to stop
|
|
78
|
+
\"loop:xf(N)\" to play the practice session repeatedly, speeding xf in N repetitions
|
|
79
|
+
\"loop:t(N)\" to play the practice session repeatedly, changing tempo to t in N repetitions"""
|
|
80
|
+
when "tempo"
|
|
81
|
+
puts " \"tempo:t\" to set the tempo of the next added bar, in beats per minute or bpm"
|
|
82
|
+
when "force_tempo"
|
|
83
|
+
puts " \"force_tempo:t\" to change the tempo of the current practice session"
|
|
84
|
+
when "sign"
|
|
85
|
+
puts """ \"sign:nxd\" to set a standard time signature, with a multiple of 2, up to 16
|
|
86
|
+
\"sign:f,f,f...\" to set an arbitrary odd bar, with f the duration of the beats"""
|
|
87
|
+
when "add"
|
|
88
|
+
puts """ \"add:nxd\" to add a standard time signature with the set tempo
|
|
89
|
+
\"add:f,f,f...\" to add an arbitrary odd bar with the set tempo
|
|
90
|
+
\"add:(t)nxd(N)\" to add N bars of a standard time signature with tempo t (t and N optional)
|
|
91
|
+
\"add:(t)f,f,f...(N)\" to add N bars of an arbitrary odd bar with tempo t (t and N optional)
|
|
92
|
+
\"add\" to add one bar of the previously set type
|
|
93
|
+
\"add:N\" to add N bars of the previously set type"""
|
|
94
|
+
when "print"
|
|
95
|
+
puts " \"print\" prints the current practice session on the screen"
|
|
96
|
+
when "help"
|
|
97
|
+
puts """ \"help\" to print this help
|
|
98
|
+
\"help:command\" to print the help for another command"""
|
|
99
|
+
when "quit"
|
|
100
|
+
puts " \"quit\" to exit"
|
|
101
|
+
else
|
|
102
|
+
puts " Unknown command"
|
|
103
|
+
end
|
|
104
|
+
puts
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def help_uninitialized
|
|
108
|
+
puts
|
|
109
|
+
puts " The practice session has not been initialized"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def help_example
|
|
113
|
+
puts """\nType:
|
|
114
|
+
\"add\" to add a default 4x4 bar
|
|
115
|
+
\"play\" to play it
|
|
116
|
+
\"loop\" to play it repeatedly until you press \'crtl\'+C
|
|
117
|
+
\"add:3x4\" to add a 3x4 bar after the previous one
|
|
118
|
+
\"loop\" to play them in loop
|
|
119
|
+
\"print\" to print the session on the screen without playing it
|
|
120
|
+
\"reset\" to go back to default empty session
|
|
121
|
+
\"play\" or \"loop\" to verify that we have an empty session
|
|
122
|
+
\"add:1.0,1.0,0.5,1.5,0.5(3)\" to add three non-standard 9x8 bars
|
|
123
|
+
\"add:4x4\" to add a standard 4x4 after them
|
|
124
|
+
\"play\" to play the practice session
|
|
125
|
+
\"print\" to print the session on the screen without playing it
|
|
126
|
+
\"tempo\" to check that the tempo is 100
|
|
127
|
+
\"force_tempo:150\" or \"ft:150\" to change the tempo of the session to 150
|
|
128
|
+
\"play\" to play the practice session
|
|
129
|
+
\"reset\" to go back to default empty session
|
|
130
|
+
\"add\" to add a default 4x4 bar at the default 100 tempo
|
|
131
|
+
\"next_tempo:150\" or \"nt:150\" to set the tempo for the next bar
|
|
132
|
+
\"add\" to add a default 4x4 bar at 150 tempo
|
|
133
|
+
\"loop\" to play them repeatedly until you press \'crtl\'+C"
|
|
134
|
+
puts
|
|
135
|
+
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
help
|
|
139
|
+
|
|
140
|
+
def reset
|
|
141
|
+
metronome = Metronome::Practice.new
|
|
142
|
+
metronome.push(Metronome::Silence.new(0.01))
|
|
143
|
+
tempo = 100
|
|
144
|
+
n = 4
|
|
145
|
+
d = 4
|
|
146
|
+
times = 1
|
|
147
|
+
beats = [1.0, 1.0, 1.0, 1.0]
|
|
148
|
+
prev_bar = :bar
|
|
149
|
+
non_initialized = true
|
|
150
|
+
[metronome, tempo, n, d, times, beats, prev_bar, non_initialized]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
metronome, tempo, n, d, times, beats, prev_bar, non_initialized = reset
|
|
154
|
+
|
|
155
|
+
loop do
|
|
156
|
+
print(">>>")
|
|
157
|
+
line = ParseLine::Line.new($stdin.gets.chomp)
|
|
158
|
+
symbol, match = line.parse
|
|
159
|
+
|
|
160
|
+
case symbol
|
|
161
|
+
when :quit
|
|
162
|
+
break
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
when :play
|
|
166
|
+
unless non_initialized
|
|
167
|
+
metronome.play
|
|
168
|
+
puts
|
|
169
|
+
else
|
|
170
|
+
puts help_uninitialized
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
when :loop
|
|
175
|
+
unless non_initialized
|
|
176
|
+
puts "\nPlaying loop, press \'crtl\'+C to stop\n"
|
|
177
|
+
loop do
|
|
178
|
+
if metronome.loop_stop?
|
|
179
|
+
break
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
puts
|
|
183
|
+
else
|
|
184
|
+
puts help_uninitialized
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
when :loop_speed_up
|
|
189
|
+
unless non_initialized
|
|
190
|
+
puts "\nPlaying loop, press \'crtl\'+C to stop\n"
|
|
191
|
+
first_tempo = Float(match[:first])
|
|
192
|
+
second_tempo = Float(match[:second])
|
|
193
|
+
tempo = first_tempo
|
|
194
|
+
tempo_inc = second_tempo - first_tempo
|
|
195
|
+
times = Integer(match[:times])
|
|
196
|
+
|
|
197
|
+
if tempo_inc < 0.0
|
|
198
|
+
first_tempo, second_tempo = second_tempo, first_tempo
|
|
199
|
+
tempo_inc *= -1
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
inc = tempo_inc/times
|
|
203
|
+
loop do
|
|
204
|
+
if (second_tempo > first_tempo) && (second_tempo > tempo)
|
|
205
|
+
metronome.force_tempo(Integer(tempo))
|
|
206
|
+
tempo += inc
|
|
207
|
+
end
|
|
208
|
+
if metronome.loop_stop?
|
|
209
|
+
break
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
puts
|
|
214
|
+
else
|
|
215
|
+
puts help_uninitialized
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
when :print_tempo
|
|
220
|
+
puts "\n Set tempo is #{tempo}"
|
|
221
|
+
puts
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
when :next_tempo, :next_tempo_short
|
|
225
|
+
tempo = Integer(match[:tempo])
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
when :force_tempo, :force_tempo_short
|
|
229
|
+
metronome.force_tempo(Float(match[:tempo]))
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
when :signature
|
|
233
|
+
n = Integer(match[:n])
|
|
234
|
+
d = Integer(match[:d])
|
|
235
|
+
prev_bar = :bar
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
when :odd_signature
|
|
239
|
+
beats = []
|
|
240
|
+
match[:beats].split(',').each do |d| beats << d.to_f end
|
|
241
|
+
|
|
242
|
+
prev_bar = :odd_bar
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
when :add_bar
|
|
246
|
+
tempo = match[:tempo] ? Integer(match[:tempo]) : tempo
|
|
247
|
+
n = Integer(match[:n])
|
|
248
|
+
d = Integer(match[:d])
|
|
249
|
+
times = match[:times] ? Integer(match[:times]) : 1
|
|
250
|
+
|
|
251
|
+
(1..times).each do
|
|
252
|
+
metronome.push(Metronome::Bar.new(tempo, n, d))
|
|
253
|
+
end
|
|
254
|
+
prev_bar = :bar
|
|
255
|
+
non_initialized = false
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
when :odd_bar
|
|
259
|
+
tempo = match[:tempo] ? Integer(match[:tempo]) : tempo
|
|
260
|
+
times = match[:times] ? Integer(match[:times]) : 1
|
|
261
|
+
beats = []
|
|
262
|
+
match[:beats].split(',').each do |d| beats << d.to_f end
|
|
263
|
+
|
|
264
|
+
(1..times).each do
|
|
265
|
+
metronome.push(Metronome::OddBar.new(tempo, beats))
|
|
266
|
+
end
|
|
267
|
+
prev_bar = :odd_bar
|
|
268
|
+
non_initialized = false
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
when :add_1
|
|
272
|
+
if prev_bar == :bar
|
|
273
|
+
metronome.push(Metronome::Bar.new(tempo, n, d))
|
|
274
|
+
elsif prev_bar == :odd_bar
|
|
275
|
+
metronome.push(Metronome::OddBar.new(tempo, beats))
|
|
276
|
+
else
|
|
277
|
+
puts 181283
|
|
278
|
+
return -1
|
|
279
|
+
end
|
|
280
|
+
non_initialized = false
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
when :add
|
|
284
|
+
times = Integer(match[:times])
|
|
285
|
+
(1..times).each do
|
|
286
|
+
if prev_bar == :bar
|
|
287
|
+
metronome.push(Metronome::Bar.new(tempo, n, d))
|
|
288
|
+
elsif prev_bar == :odd_bar
|
|
289
|
+
metronome.push(Metronome::OddBar.new(tempo, beats))
|
|
290
|
+
else
|
|
291
|
+
puts 181743
|
|
292
|
+
return -1
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
non_initialized = false
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
when :print
|
|
299
|
+
unless non_initialized
|
|
300
|
+
metronome.print_practice
|
|
301
|
+
puts
|
|
302
|
+
else
|
|
303
|
+
puts help_uninitialized
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
when :save
|
|
308
|
+
practice_name = match[:practice]
|
|
309
|
+
|
|
310
|
+
if metronome.length > 1
|
|
311
|
+
if Save.save_exist?(practice_name)
|
|
312
|
+
puts "\n\"#{practice_name}\" has been already used, press \"o\" to overwrite"
|
|
313
|
+
if Keypress.read_char == "o"
|
|
314
|
+
puts "\b\n Overwriting..."
|
|
315
|
+
else
|
|
316
|
+
practice_name = :no_overwrite
|
|
317
|
+
puts "\b\n Cancelling..."
|
|
318
|
+
end
|
|
319
|
+
else
|
|
320
|
+
puts "\n Saving..."
|
|
321
|
+
end
|
|
322
|
+
unless practice_name == :no_overwrite
|
|
323
|
+
save_obj = Array.new
|
|
324
|
+
save_obj << metronome
|
|
325
|
+
save_obj << tempo
|
|
326
|
+
save_obj << n
|
|
327
|
+
save_obj << d
|
|
328
|
+
save_obj << times
|
|
329
|
+
save_obj << beats
|
|
330
|
+
save_obj << prev_bar
|
|
331
|
+
save_obj << non_initialized
|
|
332
|
+
if Save.save_obj(practice_name, save_obj) == 0
|
|
333
|
+
puts " #{practice_name} successfully saved."
|
|
334
|
+
else
|
|
335
|
+
puts " Could no save #{practice_name}"
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
else
|
|
339
|
+
puts "\n There is no practice session to save."
|
|
340
|
+
end
|
|
341
|
+
puts
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
when :load
|
|
345
|
+
practice_name = match[:practice]
|
|
346
|
+
|
|
347
|
+
if Save.save_exist?(practice_name)
|
|
348
|
+
if metronome.length > 1
|
|
349
|
+
puts "\n Are you sure to load a new practice session? Press \"y\" to continue."
|
|
350
|
+
unless Keypress.read_char == "y"
|
|
351
|
+
practice_name = :no_overload
|
|
352
|
+
puts "\b Cancelling..."
|
|
353
|
+
end
|
|
354
|
+
else
|
|
355
|
+
puts
|
|
356
|
+
end
|
|
357
|
+
unless practice_name == :no_overload
|
|
358
|
+
puts " Loading #{practice_name}..."
|
|
359
|
+
metronome, tempo, n, d, times, beats, prev_bar, non_initialized = Save.load_obj(practice_name)
|
|
360
|
+
end
|
|
361
|
+
else
|
|
362
|
+
puts "\n File not found"
|
|
363
|
+
end
|
|
364
|
+
puts
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
when :list
|
|
368
|
+
puts "\nAvailable practice sessions:"
|
|
369
|
+
Save.list.each do |n|
|
|
370
|
+
puts " #{n}"
|
|
371
|
+
end
|
|
372
|
+
puts
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
when :reset
|
|
376
|
+
puts "\nAre you sure to continue? Press \"y\" to confirm"
|
|
377
|
+
if Keypress.read_char == "y"
|
|
378
|
+
puts "\n Resetting..."
|
|
379
|
+
metronome, tempo, n, d, times, beats, prev_bar, non_initialized = reset
|
|
380
|
+
end
|
|
381
|
+
puts
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
when :help
|
|
385
|
+
help
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
when :empty
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
when :help_command
|
|
392
|
+
help_command(match[:command])
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
when :example
|
|
396
|
+
help_example
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
else
|
|
400
|
+
puts
|
|
401
|
+
puts " Unknown command"
|
|
402
|
+
puts
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
puts "Leaving..."
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
require 'metronome-odd/interruptible_sleep'
|
|
2
|
+
require 'metronome-odd/parse_line.rb'
|
|
3
|
+
require 'metronome-odd/save.rb'
|
|
4
|
+
require 'metronome-odd/keypress.rb'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
module Metronome
|
|
8
|
+
class Sound
|
|
9
|
+
def initialize(sound_file)
|
|
10
|
+
@sound_file = sound_file
|
|
11
|
+
self
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def is_sound_file?
|
|
15
|
+
@sound_file.extension == "aiff"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def play
|
|
19
|
+
spawn("afplay #{@sound_file}")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def set_sound(sound_file)
|
|
23
|
+
@sound_file = sound_file
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
def extension
|
|
28
|
+
@sound_file.split('.').last
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Silence
|
|
33
|
+
include Stop
|
|
34
|
+
include InterruptibleSleep
|
|
35
|
+
attr_accessor :duration
|
|
36
|
+
|
|
37
|
+
def initialize(duration)
|
|
38
|
+
@duration = duration
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def wait
|
|
43
|
+
Signal.trap('SIGINT') do print "\b\bstopped"
|
|
44
|
+
@@stop = true
|
|
45
|
+
end
|
|
46
|
+
interruptible_sleep @duration
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def play
|
|
50
|
+
self.wait
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def print_sign
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class Beat
|
|
58
|
+
attr_accessor :silence
|
|
59
|
+
def initialize(tempo, sound_file)
|
|
60
|
+
@sound = Sound.new(sound_file)
|
|
61
|
+
@silence = Silence.new(60.0/tempo)
|
|
62
|
+
self
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def play
|
|
66
|
+
@sound.play
|
|
67
|
+
@silence.wait
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class Bar
|
|
72
|
+
attr_writer :upbeat_sound_file
|
|
73
|
+
attr_writer :downbeat_sound_file
|
|
74
|
+
attr_reader :n
|
|
75
|
+
attr_reader :d
|
|
76
|
+
attr_accessor :tempo
|
|
77
|
+
attr_accessor :beat_array
|
|
78
|
+
|
|
79
|
+
def initialize(tempo, n, d)
|
|
80
|
+
data_dir = Gem.datadir("metronome-odd")
|
|
81
|
+
data_dir = data_dir ? data_dir : ""
|
|
82
|
+
@upbeat_sound_file = data_dir + "/beat_upbeat.aiff"
|
|
83
|
+
@downbeat_sound_file = data_dir + "/beat_downbeat.aiff"
|
|
84
|
+
|
|
85
|
+
@beat_array = Array.new
|
|
86
|
+
@beat_array.push(Beat.new(tempo*d/4.0, @upbeat_sound_file))
|
|
87
|
+
|
|
88
|
+
@n = n
|
|
89
|
+
@d = d
|
|
90
|
+
|
|
91
|
+
@tempo = tempo
|
|
92
|
+
|
|
93
|
+
(2..n).each do
|
|
94
|
+
@beat_array.push(Beat.new(tempo*d/4.0, @downbeat_sound_file))
|
|
95
|
+
end
|
|
96
|
+
self
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def play
|
|
100
|
+
@beat_array.each {|b| b.play}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def print_sign
|
|
104
|
+
print "|#{@n}x#{@d}|"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def set_tempo(tempo)
|
|
108
|
+
old_tempo = Float(@tempo)
|
|
109
|
+
|
|
110
|
+
@beat_array.each do |beat|
|
|
111
|
+
beat.silence.duration *= old_tempo/tempo
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
@tempo = tempo
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class OddBar < Bar
|
|
120
|
+
attr_accessor :tempo
|
|
121
|
+
attr_accessor :beat_array
|
|
122
|
+
|
|
123
|
+
def initialize(tempo, in_beat)
|
|
124
|
+
data_dir = Gem.datadir("metronome-odd")
|
|
125
|
+
data_dir = data_dir ? data_dir : ""
|
|
126
|
+
@upbeat_sound_file = data_dir + "/beat_upbeat.aiff"
|
|
127
|
+
@downbeat_sound_file = data_dir + "/beat_downbeat.aiff"
|
|
128
|
+
|
|
129
|
+
@beat_array = Array.new
|
|
130
|
+
@in_beat = in_beat
|
|
131
|
+
@tempo = tempo
|
|
132
|
+
|
|
133
|
+
upbeat_b = true
|
|
134
|
+
time = 1
|
|
135
|
+
prev_beat = 1
|
|
136
|
+
in_beat.each do |t|
|
|
137
|
+
if upbeat_b
|
|
138
|
+
beat = Beat.new(tempo/t, @upbeat_sound_file)
|
|
139
|
+
upbeat_b = false
|
|
140
|
+
else
|
|
141
|
+
beat = Beat.new(tempo/t, @downbeat_sound_file)
|
|
142
|
+
end
|
|
143
|
+
@beat_array.push(beat)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def print_sign
|
|
149
|
+
# In order to print an odd bar, firstly we find the beat sound times
|
|
150
|
+
# in in_sum, starting from 1.
|
|
151
|
+
# Then we add the silences in the beats in in_clone
|
|
152
|
+
# Finally we print the bar, with numbers in the beats and
|
|
153
|
+
# semicolons in the silences that occur in a beat
|
|
154
|
+
# width may be used to change size
|
|
155
|
+
width = 8
|
|
156
|
+
max_beat = @in_beat.sum
|
|
157
|
+
in_sum = [1.0]
|
|
158
|
+
acum = 1.0
|
|
159
|
+
@in_beat.each do |e| acum=acum+e;in_sum.push(acum) end
|
|
160
|
+
in_clone = in_sum.clone
|
|
161
|
+
(1...max_beat.floor).each do |d|
|
|
162
|
+
if not(in_clone.include?(d))
|
|
163
|
+
in_clone.push(Float(d))
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
in_clone = in_clone.sort
|
|
167
|
+
|
|
168
|
+
print "|"
|
|
169
|
+
in_clone[0...-1].each_with_index do |d, i|
|
|
170
|
+
if in_sum.include?(d)
|
|
171
|
+
if d == Integer(d)
|
|
172
|
+
print(':')
|
|
173
|
+
end
|
|
174
|
+
print(d.floor)
|
|
175
|
+
else
|
|
176
|
+
print(':')
|
|
177
|
+
end
|
|
178
|
+
if d<in_clone.last
|
|
179
|
+
t = Integer((in_clone[i+1]-in_clone[i])*width-1)
|
|
180
|
+
t.times do print " " end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
print "|"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
class Practice < Array
|
|
188
|
+
include Stop
|
|
189
|
+
@next_bar_hash = nil
|
|
190
|
+
@prev_bar = nil
|
|
191
|
+
|
|
192
|
+
def initialize
|
|
193
|
+
@next_bar_hash = Hash.new(nil)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def sound(type)
|
|
197
|
+
@@stop = false
|
|
198
|
+
bar_counter = 0 # 0 accounts for the intial silence
|
|
199
|
+
|
|
200
|
+
self.each do |b|
|
|
201
|
+
unless b.class == Silence
|
|
202
|
+
print "\n#{bar_counter}:\t"
|
|
203
|
+
b.print_sign
|
|
204
|
+
if @next_bar_hash[b]
|
|
205
|
+
print " -> "
|
|
206
|
+
@next_bar_hash[b].print_sign
|
|
207
|
+
print "\t"
|
|
208
|
+
else
|
|
209
|
+
case type
|
|
210
|
+
when :play
|
|
211
|
+
print " - last bar! "
|
|
212
|
+
when :loop
|
|
213
|
+
print " - repeat! "
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
b.play
|
|
218
|
+
bar_counter += 1
|
|
219
|
+
if @@stop then break end
|
|
220
|
+
end
|
|
221
|
+
puts
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def play
|
|
225
|
+
sound(:play)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def loop_stop?
|
|
229
|
+
sound(:loop)
|
|
230
|
+
@@stop
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def force_tempo(tempo)
|
|
234
|
+
self.each do |bar|
|
|
235
|
+
unless bar.class == Silence
|
|
236
|
+
bar.set_tempo(tempo)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def push(b)
|
|
242
|
+
super(b)
|
|
243
|
+
if @prev_bar
|
|
244
|
+
@next_bar_hash[@prev_bar] = b
|
|
245
|
+
end
|
|
246
|
+
@prev_bar = b
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def print_practice
|
|
250
|
+
bar_counter = 0 # 0 accounts for the intial silence
|
|
251
|
+
self.each do |b|
|
|
252
|
+
unless b.class == Silence
|
|
253
|
+
print "\n#{bar_counter}:\t"
|
|
254
|
+
b.print_sign
|
|
255
|
+
end
|
|
256
|
+
bar_counter += 1
|
|
257
|
+
end
|
|
258
|
+
puts
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module InterruptibleSleep
|
|
2
|
+
def interruptible_sleep(seconds)
|
|
3
|
+
@_sleep_check, @_sleep_interrupt = IO.pipe
|
|
4
|
+
IO.select([@_sleep_check], nil, nil, seconds)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def interrupt_sleep
|
|
8
|
+
@_sleep_interrupt.close if @_sleep_interrupt
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module Stop
|
|
13
|
+
@@stop = false
|
|
14
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#This has since been folded into a much more robust gem called Remedy. https://rubygems.org/gems/remedy & https://github.com/acook/remedy
|
|
2
|
+
|
|
3
|
+
require 'io/console'
|
|
4
|
+
|
|
5
|
+
# Reads keypresses from the user including 2 and 3 escape character sequences.
|
|
6
|
+
module Keypress
|
|
7
|
+
def Keypress.read_char
|
|
8
|
+
STDIN.echo = false
|
|
9
|
+
STDIN.raw!
|
|
10
|
+
|
|
11
|
+
input = STDIN.getc.chr
|
|
12
|
+
if input == "\e" then
|
|
13
|
+
input << STDIN.read_nonblock(3) rescue nil
|
|
14
|
+
input << STDIN.read_nonblock(2) rescue nil
|
|
15
|
+
end
|
|
16
|
+
ensure
|
|
17
|
+
STDIN.echo = true
|
|
18
|
+
STDIN.cooked!
|
|
19
|
+
|
|
20
|
+
return input
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# oringal case statement from:
|
|
24
|
+
# http://www.alecjacobson.com/weblog/?p=75
|
|
25
|
+
def Keypress.show_single_key
|
|
26
|
+
c = read_char
|
|
27
|
+
|
|
28
|
+
case c
|
|
29
|
+
when " "
|
|
30
|
+
puts "SPACE"
|
|
31
|
+
when "\t"
|
|
32
|
+
puts "TAB"
|
|
33
|
+
when "\r"
|
|
34
|
+
puts "RETURN"
|
|
35
|
+
when "\n"
|
|
36
|
+
puts "LINE FEED"
|
|
37
|
+
when "\e"
|
|
38
|
+
puts "ESCAPE"
|
|
39
|
+
when "\e[A"
|
|
40
|
+
puts "UP ARROW"
|
|
41
|
+
when "\e[B"
|
|
42
|
+
puts "DOWN ARROW"
|
|
43
|
+
when "\e[C"
|
|
44
|
+
puts "RIGHT ARROW"
|
|
45
|
+
when "\e[D"
|
|
46
|
+
puts "LEFT ARROW"
|
|
47
|
+
when "\177"
|
|
48
|
+
puts "BACKSPACE"
|
|
49
|
+
when "\004"
|
|
50
|
+
puts "DELETE"
|
|
51
|
+
when "\e[3~"
|
|
52
|
+
puts "ALTERNATE DELETE"
|
|
53
|
+
when "\u0003"
|
|
54
|
+
puts "CONTROL-C"
|
|
55
|
+
exit 0
|
|
56
|
+
when /^.$/
|
|
57
|
+
puts "SINGLE CHAR HIT: #{c.inspect}"
|
|
58
|
+
else
|
|
59
|
+
puts "SOMETHING ELSE: #{c.inspect}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def Keypress.test
|
|
64
|
+
show_single_key while(true)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module ParseLine
|
|
2
|
+
class Line
|
|
3
|
+
@@line_regexp_hash = nil
|
|
4
|
+
@help = false
|
|
5
|
+
|
|
6
|
+
def remove_separators
|
|
7
|
+
@line = @line.gsub(/\s+/, "")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(line)
|
|
11
|
+
if @@line_regexp_hash.nil?
|
|
12
|
+
@@line_regexp_hash = Hash.new([/^$/,/^$/])
|
|
13
|
+
end
|
|
14
|
+
@line = line
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def Line.add_command(symbol, command)
|
|
18
|
+
if @@line_regexp_hash.nil?
|
|
19
|
+
@@line_regexp_hash = Hash.new([/^$/,[]])
|
|
20
|
+
end
|
|
21
|
+
@@line_regexp_hash[symbol] = command
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def parse
|
|
25
|
+
c = nil
|
|
26
|
+
s = nil
|
|
27
|
+
self.remove_separators
|
|
28
|
+
@@line_regexp_hash.each do |symbol, command_regex|
|
|
29
|
+
if @line.match(command_regex)
|
|
30
|
+
c = @line.match(command_regex)
|
|
31
|
+
s = symbol
|
|
32
|
+
break
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
[s, c]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Save
|
|
2
|
+
def self.file_name_format(short_name)
|
|
3
|
+
"./sp." + short_name + ".marshal"
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def self.save_exist?(short_name)
|
|
7
|
+
File.exist?(Save.file_name_format(short_name))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.save_obj(short_name, obj)
|
|
11
|
+
file_name = Save.file_name_format(short_name)
|
|
12
|
+
file_handler = File.open(file_name, "w") {|to_file| Marshal.dump(obj, to_file)}
|
|
13
|
+
file_handler.close
|
|
14
|
+
if File.exist?(file_name)
|
|
15
|
+
return 0
|
|
16
|
+
else
|
|
17
|
+
return -1
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.load_obj(short_name)
|
|
22
|
+
file_name = Save.file_name_format(short_name)
|
|
23
|
+
Save.load(file_name)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.delete_obj(short_name)
|
|
27
|
+
file_name = Save.file_name_format(short_name)
|
|
28
|
+
Save.delete(file_name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.list
|
|
32
|
+
list = []
|
|
33
|
+
ls = Dir.entries(".")
|
|
34
|
+
ls.each do |f|
|
|
35
|
+
m = f.match(/^sp\.(?<name>\w+)\.marshal$/)
|
|
36
|
+
if m
|
|
37
|
+
list.push(m[:name])
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
list
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.load(file_name)
|
|
44
|
+
if File.exist?(file_name)
|
|
45
|
+
begin
|
|
46
|
+
file_handler = File.open(file_name, "r")
|
|
47
|
+
obj = Marshal.load(file_handler)
|
|
48
|
+
file_handler.close
|
|
49
|
+
rescue
|
|
50
|
+
obj = false
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
obj = nil
|
|
54
|
+
end
|
|
55
|
+
obj
|
|
56
|
+
end
|
|
57
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: metronome-odd
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: '1.0'
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Guillermo Regodón
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2020-11-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Metronome that can be programmed to change de tempo or the time signature.
|
|
14
|
+
email:
|
|
15
|
+
- guillermoregodon@gmail.com
|
|
16
|
+
executables:
|
|
17
|
+
- metronome-odd
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- bin/metronome-odd
|
|
22
|
+
- data/metronome-odd/beat_downbeat.aiff
|
|
23
|
+
- data/metronome-odd/beat_upbeat.aiff
|
|
24
|
+
- lib/metronome-odd.rb
|
|
25
|
+
- lib/metronome-odd/interruptible_sleep.rb
|
|
26
|
+
- lib/metronome-odd/keypress.rb
|
|
27
|
+
- lib/metronome-odd/parse_line.rb
|
|
28
|
+
- lib/metronome-odd/save.rb
|
|
29
|
+
- tests/test_metronome-odd.rb
|
|
30
|
+
homepage: https://github.com/GuillermoRegodon/rubygem-metronome-odd
|
|
31
|
+
licenses:
|
|
32
|
+
- MIT
|
|
33
|
+
metadata: {}
|
|
34
|
+
post_install_message:
|
|
35
|
+
rdoc_options: []
|
|
36
|
+
require_paths:
|
|
37
|
+
- lib
|
|
38
|
+
- data
|
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '0'
|
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0'
|
|
49
|
+
requirements: []
|
|
50
|
+
rubygems_version: 3.0.3
|
|
51
|
+
signing_key:
|
|
52
|
+
specification_version: 4
|
|
53
|
+
summary: Programmable metronome
|
|
54
|
+
test_files:
|
|
55
|
+
- tests/test_metronome-odd.rb
|