rosabeats 0.1.3__py3-none-any.whl
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.
- docs/beatrecipe_docs.txt +80 -0
- rosabeats/__init__.py +23 -0
- rosabeats/beatrecipe_processor.py +523 -0
- rosabeats/beatswitch.py +256 -0
- rosabeats/rosabeats.py +996 -0
- rosabeats/rosabeats_shell.py +387 -0
- rosabeats/segment_song.py +181 -0
- rosabeats-0.1.3.dist-info/METADATA +158 -0
- rosabeats-0.1.3.dist-info/RECORD +16 -0
- rosabeats-0.1.3.dist-info/WHEEL +5 -0
- rosabeats-0.1.3.dist-info/entry_points.txt +5 -0
- rosabeats-0.1.3.dist-info/licenses/LICENSE.md +7 -0
- rosabeats-0.1.3.dist-info/top_level.txt +3 -0
- scripts/reverse_beats_in_bars_rosa.py +48 -0
- scripts/shuffle_bars_rosa.py +35 -0
- scripts/shuffle_beats_rosa.py +29 -0
docs/beatrecipe_docs.txt
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
every beat recipe must contain these two lines, at the top:
|
|
2
|
+
file <filename.wav>
|
|
3
|
+
beats_bar # #
|
|
4
|
+
|
|
5
|
+
the two arguments to beats_bar specify the number of beats pe bar, and the absolute beat
|
|
6
|
+
number in the file that starts the first bar (this is often 0, first beat = first bar,
|
|
7
|
+
but is also often not, depending on track and file characteristics; using control_beats.py
|
|
8
|
+
to experiment is best way to discover if this should be non-zero for a given track)
|
|
9
|
+
|
|
10
|
+
These two lines are necessary to specify source and in order to calculate where bars are
|
|
11
|
+
in the file. After them, everything else is optional and can occur as many or few times as
|
|
12
|
+
you like, in any order
|
|
13
|
+
|
|
14
|
+
beats #
|
|
15
|
+
bars #
|
|
16
|
+
beats #-#
|
|
17
|
+
bars #-#
|
|
18
|
+
|
|
19
|
+
These are the simplest. You can play one or more beats, or one or more bars, by specifying
|
|
20
|
+
the beats or bars you wish to play. All of these are valid syntax:
|
|
21
|
+
|
|
22
|
+
beats 3
|
|
23
|
+
beats 3-3
|
|
24
|
+
beats 3-8
|
|
25
|
+
beats 8-3
|
|
26
|
+
bars 4
|
|
27
|
+
bars 4-4
|
|
28
|
+
bars 4-12
|
|
29
|
+
bars 12-4
|
|
30
|
+
|
|
31
|
+
If the first and last # specified are the same, it will be like specifying a single
|
|
32
|
+
beat/bar. If the first # specified is greater than the last, the beats/bars will be
|
|
33
|
+
played in reverse order, i.e. bars 4-1 will play bar 4, bar 3, bar 2, then bar 1.
|
|
34
|
+
|
|
35
|
+
rest #
|
|
36
|
+
|
|
37
|
+
Will insert silence. The argument specifies number of beats of silence. "rest 1" will
|
|
38
|
+
insert 1 beat of silence. Argument is floating point, so you can add fractional beats
|
|
39
|
+
of silence, e.g. "rest 0.5" will add half a beat of silence.
|
|
40
|
+
|
|
41
|
+
beats_shuf #
|
|
42
|
+
beats_shuf #-#
|
|
43
|
+
bars_shuf #
|
|
44
|
+
bars_shuf #-#
|
|
45
|
+
|
|
46
|
+
These two act much like beats and bars covered previously, except instead of playing
|
|
47
|
+
the specified beats or bars in the sequence specified, they will be randomly shuffled
|
|
48
|
+
before being played. If only a single beat or bar is given after these specifiers,
|
|
49
|
+
it will be the same as using "beats" or "bars" as shuffling a single item will always
|
|
50
|
+
result in the output of that one item (i.e. "bars_shuf 8" is same as "bars 8").
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
bars_rev #
|
|
54
|
+
bars_rev #-#
|
|
55
|
+
|
|
56
|
+
This is like bars, except it will play the beats in the bar in reverse order. That is,
|
|
57
|
+
if "bars 0" played beats 0, 1, 2, 3, 4, 5, 6, and 7, "bars_rev 0" would play beats
|
|
58
|
+
7, 6, 5, 4, 3, 2, 1, and 0.
|
|
59
|
+
|
|
60
|
+
beat_div # # #
|
|
61
|
+
This will divide a beat into equal segments and play a certain number of them. The
|
|
62
|
+
first argument is the beat #. The second argument is how many pieces to divide the beat
|
|
63
|
+
into. THe third argument is how many times to play that subdivided beat. For example,
|
|
64
|
+
"beat_div 540 2 2" will play the first half of beat 540 twice; "beat_div 300 3 12"
|
|
65
|
+
will play the first third of beat 300 12 times (this will sound like 4 beats of triplets).
|
|
66
|
+
|
|
67
|
+
def name bars #
|
|
68
|
+
def name bars #-#
|
|
69
|
+
def name beats #
|
|
70
|
+
def name beats #-#
|
|
71
|
+
|
|
72
|
+
This defines a segment that can later be played with the play command (below). Right now
|
|
73
|
+
it can only handle beats and bars specifications (per above) but the plan is to expand it
|
|
74
|
+
to represent any valid command.
|
|
75
|
+
|
|
76
|
+
play name
|
|
77
|
+
|
|
78
|
+
This will play any defined segment defined with "def" (above). If previously you had said
|
|
79
|
+
"def seg1 bars 0-7" later when you say "play seg1" it will actually play bars 0-7.
|
|
80
|
+
1
|
rosabeats/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
rosabeats: Audio beat detection, segmentation, and remixing library
|
|
3
|
+
|
|
4
|
+
This package provides tools for audio beat detection, segmentation, and remixing.
|
|
5
|
+
It leverages the power of librosa and other audio processing libraries to enable
|
|
6
|
+
precise beat tracking, song segmentation, and creative audio manipulation.
|
|
7
|
+
|
|
8
|
+
Main features:
|
|
9
|
+
- Beat Detection: Automatically detect beats and tempo in audio files
|
|
10
|
+
- Bar Analysis: Group beats into bars with configurable beats-per-bar
|
|
11
|
+
- Audio Segmentation: Segment songs into structural parts
|
|
12
|
+
- Audio Remixing: Create remixes by manipulating beats and bars
|
|
13
|
+
- Beat Recipe Files: Save and load beat patterns as reusable recipes
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.3"
|
|
17
|
+
|
|
18
|
+
# Import main classes and functions from the module
|
|
19
|
+
from .rosabeats import rosabeats
|
|
20
|
+
|
|
21
|
+
# Expose key functionality at package level
|
|
22
|
+
__all__ = ['rosabeats']
|
|
23
|
+
|
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import re, sys, os.path, random, logging
|
|
4
|
+
|
|
5
|
+
from rosabeats import rosabeats
|
|
6
|
+
|
|
7
|
+
class beatrecipe_processor(rosabeats):
|
|
8
|
+
@staticmethod
|
|
9
|
+
def ilist(indices):
|
|
10
|
+
start, stop, step = indices
|
|
11
|
+
|
|
12
|
+
r = range(start, (stop + 1) if step >= 0 else (stop - 1), step)
|
|
13
|
+
return [x for x in r]
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def parse_first_last(args):
|
|
17
|
+
first = None
|
|
18
|
+
last = None
|
|
19
|
+
step = None
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
if re.match(r"[0-9]+\-[0-9]+", args):
|
|
23
|
+
beatrecipe_processor.d_print("parsed target range")
|
|
24
|
+
first, last = args.split("-", maxsplit=1)
|
|
25
|
+
first = int(first)
|
|
26
|
+
last = int(last)
|
|
27
|
+
|
|
28
|
+
elif re.match(r"^[0-9]+$", args):
|
|
29
|
+
beatrecipe_processor.d_print("parsed single target")
|
|
30
|
+
first = int(args)
|
|
31
|
+
last = first
|
|
32
|
+
except Exception as e:
|
|
33
|
+
raise e
|
|
34
|
+
|
|
35
|
+
if first > last:
|
|
36
|
+
step = -1
|
|
37
|
+
else:
|
|
38
|
+
step = 1
|
|
39
|
+
|
|
40
|
+
return (first, last, step)
|
|
41
|
+
|
|
42
|
+
#### instance methods ####
|
|
43
|
+
|
|
44
|
+
def __init__(self, recipe, interactive=False, loglevel=logging.INFO, debug=False):
|
|
45
|
+
super().__init__(debug=debug)
|
|
46
|
+
|
|
47
|
+
self.recipe = recipe
|
|
48
|
+
self.interactive = interactive
|
|
49
|
+
self.recipe_lines = None
|
|
50
|
+
self.macros = dict()
|
|
51
|
+
|
|
52
|
+
logging.basicConfig(level=loglevel)
|
|
53
|
+
self.log = logging.getLogger(__name__)
|
|
54
|
+
self.log.setLevel(loglevel)
|
|
55
|
+
self.log.debug("logging activated")
|
|
56
|
+
|
|
57
|
+
if interactive:
|
|
58
|
+
# set up our outputs
|
|
59
|
+
self.disable_output_beats()
|
|
60
|
+
self.disable_output_save()
|
|
61
|
+
self.enable_output_play()
|
|
62
|
+
|
|
63
|
+
self.read_beatrecipe()
|
|
64
|
+
|
|
65
|
+
def parse_error(self, error_info):
|
|
66
|
+
self.log.error("%s" % error_info)
|
|
67
|
+
|
|
68
|
+
def read_beatrecipe(self):
|
|
69
|
+
self.recipe_lines = list()
|
|
70
|
+
with open(self.recipe, "r") as f:
|
|
71
|
+
for line in f:
|
|
72
|
+
lines = self.preprocess(line)
|
|
73
|
+
if lines is None:
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
for cmd in lines:
|
|
77
|
+
self.recipe_lines.append(cmd)
|
|
78
|
+
|
|
79
|
+
def define_macro(self, name, value):
|
|
80
|
+
if self.macros.get(name) is not None:
|
|
81
|
+
self.parse_error("macro %s already defined" % name)
|
|
82
|
+
return False
|
|
83
|
+
self.macros[name] = value
|
|
84
|
+
return True
|
|
85
|
+
|
|
86
|
+
def play_macro(self, name, times):
|
|
87
|
+
macro = self.macros.get(name)
|
|
88
|
+
if macro is None:
|
|
89
|
+
self.parse_error("macro %s not defined" % name)
|
|
90
|
+
|
|
91
|
+
self.log.info("[*] %d * %s [%s]" % (times, name, macro))
|
|
92
|
+
|
|
93
|
+
for x in range(times):
|
|
94
|
+
if self.output_beats:
|
|
95
|
+
self.write_out("# === macro %s ===" % name)
|
|
96
|
+
self.execute_command(macro)
|
|
97
|
+
|
|
98
|
+
def is_defined_macro(self, name):
|
|
99
|
+
for key in self.macros:
|
|
100
|
+
if name == key:
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
def preprocess(self, line):
|
|
106
|
+
line = line.strip()
|
|
107
|
+
|
|
108
|
+
# remove leading and trailing whitespace
|
|
109
|
+
line = re.sub(r"^\s+", "", line)
|
|
110
|
+
line = re.sub(r"\s+$", "", line)
|
|
111
|
+
|
|
112
|
+
# skip blank lines
|
|
113
|
+
if re.match(r"^$", line):
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
# skip lines that start with hash
|
|
117
|
+
if line.startswith('#'):
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
# remove same line comments but process rest of line
|
|
121
|
+
try:
|
|
122
|
+
line, comment = line.split("#", maxsplit=2)
|
|
123
|
+
line = line.rstrip()
|
|
124
|
+
except:
|
|
125
|
+
# do nothing if no comments to strip
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
# split multiple commands separated by semicolons
|
|
129
|
+
lines = []
|
|
130
|
+
for l in line.split(';'):
|
|
131
|
+
lines.append(l.strip())
|
|
132
|
+
|
|
133
|
+
return lines
|
|
134
|
+
|
|
135
|
+
def execute_command(self, cmd):
|
|
136
|
+
cmd_elements = cmd.split()
|
|
137
|
+
|
|
138
|
+
verb = cmd_elements[0]
|
|
139
|
+
cmd_elements = cmd_elements[1:]
|
|
140
|
+
|
|
141
|
+
if verb == "rest":
|
|
142
|
+
try:
|
|
143
|
+
rest = float(cmd_elements[0])
|
|
144
|
+
cmd_elements = cmd_elements[1:]
|
|
145
|
+
except:
|
|
146
|
+
self.parse_error("could not determine decimal value for rest")
|
|
147
|
+
if self.interactive:
|
|
148
|
+
return True
|
|
149
|
+
else:
|
|
150
|
+
raise e
|
|
151
|
+
try:
|
|
152
|
+
times = int(cmd_elements[0])
|
|
153
|
+
except:
|
|
154
|
+
times = 1
|
|
155
|
+
|
|
156
|
+
print("[*] %d * %g beat(s) of rest" % (times, rest), flush=True)
|
|
157
|
+
|
|
158
|
+
for x in range(times):
|
|
159
|
+
self.rest(rest)
|
|
160
|
+
|
|
161
|
+
elif verb == "beats":
|
|
162
|
+
try:
|
|
163
|
+
args = cmd_elements[0]
|
|
164
|
+
cmd_elements = cmd_elements[1:]
|
|
165
|
+
|
|
166
|
+
beats = beatrecipe_processor.ilist(
|
|
167
|
+
beatrecipe_processor.parse_first_last(args)
|
|
168
|
+
)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
self.parse_error("could not derive first/last from %s" % args)
|
|
171
|
+
if self.interactive:
|
|
172
|
+
return True
|
|
173
|
+
else:
|
|
174
|
+
raise e
|
|
175
|
+
try:
|
|
176
|
+
times = int(cmd_elements[0])
|
|
177
|
+
except:
|
|
178
|
+
times = 1
|
|
179
|
+
|
|
180
|
+
print("[*] %d * " % (times), end="", flush=True)
|
|
181
|
+
|
|
182
|
+
if len(beats) > 1:
|
|
183
|
+
print("(beats %d-%d) " % (beats[0], beats[-1]), flush=True)
|
|
184
|
+
else:
|
|
185
|
+
print("(beat %d) " % beats[0], flush=True)
|
|
186
|
+
|
|
187
|
+
for x in range(times):
|
|
188
|
+
self.play_beats(beats)
|
|
189
|
+
|
|
190
|
+
elif verb == "beat_div":
|
|
191
|
+
try:
|
|
192
|
+
beat = int(cmd_elements[0])
|
|
193
|
+
divisor = int(cmd_elements[1])
|
|
194
|
+
cmd_elements = cmd_elements[2:]
|
|
195
|
+
except:
|
|
196
|
+
self.parse_error("invalid beat/divisor")
|
|
197
|
+
try:
|
|
198
|
+
times = int(cmd_elements[0])
|
|
199
|
+
except:
|
|
200
|
+
times = 1
|
|
201
|
+
|
|
202
|
+
print("[*] %d * (1/%d beats) " % (times, divisor), flush=True)
|
|
203
|
+
|
|
204
|
+
for x in range(times):
|
|
205
|
+
print("%d/%d " % (beat, divisor), end="", flush=True)
|
|
206
|
+
self.play_beat(beat, divisor=divisor, silent=True)
|
|
207
|
+
|
|
208
|
+
print(flush=True)
|
|
209
|
+
|
|
210
|
+
elif verb == "beats_shuf":
|
|
211
|
+
try:
|
|
212
|
+
args = cmd_elements[0]
|
|
213
|
+
cmd_elements = cmd_elements[1:]
|
|
214
|
+
|
|
215
|
+
beats = beatrecipe_processor.ilist(
|
|
216
|
+
beatrecipe_processor.parse_first_last(args)
|
|
217
|
+
)
|
|
218
|
+
except Exception as e:
|
|
219
|
+
self.parse_error("could not derive first/last from %s" % args)
|
|
220
|
+
if self.interactive:
|
|
221
|
+
return True
|
|
222
|
+
else:
|
|
223
|
+
raise e
|
|
224
|
+
try:
|
|
225
|
+
times = int(cmd_elements[0])
|
|
226
|
+
except:
|
|
227
|
+
times = 1
|
|
228
|
+
|
|
229
|
+
print("[*] %d * " % times, end="", flush=True)
|
|
230
|
+
print("(shuffled beats %d-%d] " % (beats[0], beats[-1]), flush=True)
|
|
231
|
+
|
|
232
|
+
for x in range(times):
|
|
233
|
+
random.shuffle(beats)
|
|
234
|
+
self.play_beats(beats)
|
|
235
|
+
|
|
236
|
+
elif verb == "bars":
|
|
237
|
+
try:
|
|
238
|
+
args = cmd_elements[0]
|
|
239
|
+
cmd_elements = cmd_elements[1:]
|
|
240
|
+
|
|
241
|
+
bars = beatrecipe_processor.ilist(
|
|
242
|
+
beatrecipe_processor.parse_first_last(args)
|
|
243
|
+
)
|
|
244
|
+
except Exception as e:
|
|
245
|
+
self.parse_error("could not derive first/last from %s" % args)
|
|
246
|
+
if self.interactive:
|
|
247
|
+
return True
|
|
248
|
+
else:
|
|
249
|
+
raise e
|
|
250
|
+
try:
|
|
251
|
+
times = int(cmd_elements[0])
|
|
252
|
+
except:
|
|
253
|
+
times = 1
|
|
254
|
+
|
|
255
|
+
print("[*] %d * " % times, end="", flush=True)
|
|
256
|
+
if len(bars) > 1:
|
|
257
|
+
print("(bars %d-%d) " % (bars[0], bars[-1]), flush=True)
|
|
258
|
+
else:
|
|
259
|
+
print("(bar %d) " % bars[0], flush=True)
|
|
260
|
+
|
|
261
|
+
for x in range(times):
|
|
262
|
+
self.play_bars(bars)
|
|
263
|
+
|
|
264
|
+
elif verb == "bars_rev":
|
|
265
|
+
try:
|
|
266
|
+
args = cmd_elements[0]
|
|
267
|
+
cmd_elements = cmd_elements[1:]
|
|
268
|
+
|
|
269
|
+
bars = beatrecipe_processor.ilist(
|
|
270
|
+
beatrecipe_processor.parse_first_last(args)
|
|
271
|
+
)
|
|
272
|
+
except Exception as e:
|
|
273
|
+
self.parse_error("could not derive first/last from %s" % args)
|
|
274
|
+
if self.interactive:
|
|
275
|
+
return True
|
|
276
|
+
else:
|
|
277
|
+
raise e
|
|
278
|
+
try:
|
|
279
|
+
times = int(cmd_elements[0])
|
|
280
|
+
except:
|
|
281
|
+
times = 1
|
|
282
|
+
|
|
283
|
+
print("[*] %d * " % times, end="", flush=True)
|
|
284
|
+
if len(bars) > 1:
|
|
285
|
+
print("(rev bars %d-%d) " % (bars[0], bars[-1]), flush=True)
|
|
286
|
+
else:
|
|
287
|
+
print("(rev bar %d) " % bars[0], flush=True)
|
|
288
|
+
|
|
289
|
+
for x in range(times):
|
|
290
|
+
self.play_bars(bars, reverse=True)
|
|
291
|
+
|
|
292
|
+
elif verb == "bars_shuf":
|
|
293
|
+
try:
|
|
294
|
+
args = cmd_elements[0]
|
|
295
|
+
cmd_elements = cmd_elements[1:]
|
|
296
|
+
|
|
297
|
+
bars = beatrecipe_processor.ilist(
|
|
298
|
+
beatrecipe_processor.parse_first_last(args)
|
|
299
|
+
)
|
|
300
|
+
except Exception:
|
|
301
|
+
self.parse_error("could not derive first/last from %s" % args)
|
|
302
|
+
try:
|
|
303
|
+
times = int(cmd_elements[0])
|
|
304
|
+
except:
|
|
305
|
+
times = 1
|
|
306
|
+
|
|
307
|
+
print("[*] %d * " % times, end="", flush=True)
|
|
308
|
+
print("(shuffled bars %d-%d) " % (bars[0], bars[-1]), flush=True)
|
|
309
|
+
|
|
310
|
+
for x in range(times):
|
|
311
|
+
random.shuffle(bars)
|
|
312
|
+
self.play_bars(bars)
|
|
313
|
+
|
|
314
|
+
elif verb == "play":
|
|
315
|
+
try:
|
|
316
|
+
name = str(cmd_elements[0])
|
|
317
|
+
cmd_elements = cmd_elements[1:]
|
|
318
|
+
except Exception:
|
|
319
|
+
self.parse_error("could not parse name of macro to play")
|
|
320
|
+
if self.interactive:
|
|
321
|
+
return True
|
|
322
|
+
else:
|
|
323
|
+
raise e
|
|
324
|
+
try:
|
|
325
|
+
times = int(cmd_elements[0])
|
|
326
|
+
except:
|
|
327
|
+
times = 1
|
|
328
|
+
|
|
329
|
+
self.play_macro(name, times)
|
|
330
|
+
|
|
331
|
+
elif verb == "def":
|
|
332
|
+
try:
|
|
333
|
+
name = cmd_elements[0]
|
|
334
|
+
value = " ".join(cmd_elements[1:])
|
|
335
|
+
cmd_elements = []
|
|
336
|
+
except:
|
|
337
|
+
self.parse_error("unable to parse macro definition")
|
|
338
|
+
if self.interactive:
|
|
339
|
+
return True
|
|
340
|
+
else:
|
|
341
|
+
raise e
|
|
342
|
+
print(
|
|
343
|
+
"[*] (def seg: %s = %s)" % (name, value)
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
self.define_macro(name, value)
|
|
347
|
+
print("defined %s => %s" % (name, value))
|
|
348
|
+
|
|
349
|
+
elif self.interactive and verb == "help":
|
|
350
|
+
print("valid commands are:")
|
|
351
|
+
print(" beats, bars, beats_shuf, bars_shuf, beat_div, bars_rev, rest")
|
|
352
|
+
print(" for predefined macros: ls, lsdef, play, or just type the def name")
|
|
353
|
+
print(" and finally: help, quit, next")
|
|
354
|
+
|
|
355
|
+
elif self.interactive and verb == "lsdef":
|
|
356
|
+
for name, value in self.macros.items():
|
|
357
|
+
print("%15s %15s" % (name, value))
|
|
358
|
+
|
|
359
|
+
elif self.interactive and verb == "ls":
|
|
360
|
+
|
|
361
|
+
print(", ".join(list(self.macros.keys())))
|
|
362
|
+
|
|
363
|
+
elif self.interactive and verb == "quit":
|
|
364
|
+
self.shutdown()
|
|
365
|
+
sys.exit(0)
|
|
366
|
+
|
|
367
|
+
elif self.interactive and verb == "next":
|
|
368
|
+
self.shutdown()
|
|
369
|
+
return False
|
|
370
|
+
|
|
371
|
+
elif self.is_defined_macro(verb):
|
|
372
|
+
print("[*] cmd '%s' resolves to defined macro" % verb)
|
|
373
|
+
try:
|
|
374
|
+
times = int(cmd_elements[0])
|
|
375
|
+
except:
|
|
376
|
+
times = 1
|
|
377
|
+
self.play_macro(verb, times)
|
|
378
|
+
|
|
379
|
+
else:
|
|
380
|
+
self.parse_error("verb %s not recognized" % verb)
|
|
381
|
+
|
|
382
|
+
if self.interactive:
|
|
383
|
+
return True
|
|
384
|
+
|
|
385
|
+
def process(self):
|
|
386
|
+
for cmd in self.recipe_lines:
|
|
387
|
+
self.execute_command(cmd)
|
|
388
|
+
|
|
389
|
+
print("[the end - shutting down outputs]", flush=True)
|
|
390
|
+
self.shutdown()
|
|
391
|
+
|
|
392
|
+
def process_interactive(self):
|
|
393
|
+
KeepProcessing = True
|
|
394
|
+
while KeepProcessing:
|
|
395
|
+
|
|
396
|
+
print("")
|
|
397
|
+
print("enter beatrecipe command => ", end="", flush=True)
|
|
398
|
+
|
|
399
|
+
lines = self.preprocess(input())
|
|
400
|
+
|
|
401
|
+
for cmd in lines:
|
|
402
|
+
print(cmd)
|
|
403
|
+
KeepProcessing = self.execute_command(cmd)
|
|
404
|
+
|
|
405
|
+
def prepare_file_and_macros(self):
|
|
406
|
+
# this goes through the preprocessd/stored recipe that was loaded
|
|
407
|
+
# and only processes file, beats_bar, and macro deinitions lines
|
|
408
|
+
# all else is ignored
|
|
409
|
+
unprocessed_lines = []
|
|
410
|
+
for line in self.recipe_lines:
|
|
411
|
+
try:
|
|
412
|
+
verb, args = line.split(maxsplit=1)
|
|
413
|
+
except Exception:
|
|
414
|
+
verb = line
|
|
415
|
+
args = ""
|
|
416
|
+
# self.parse_error("could not split line into verb and args")
|
|
417
|
+
# sys.exit(1)
|
|
418
|
+
|
|
419
|
+
if verb == "file":
|
|
420
|
+
filename = str(args)
|
|
421
|
+
self.setfile(filename)
|
|
422
|
+
# don't actually load until we have beats/bar info
|
|
423
|
+
|
|
424
|
+
elif verb == "beats_bar":
|
|
425
|
+
try:
|
|
426
|
+
per, first = args.split(maxsplit=1)
|
|
427
|
+
per = int(per)
|
|
428
|
+
first = int(first)
|
|
429
|
+
except:
|
|
430
|
+
self.parse_error("could not parse two arguments for beats_bar")
|
|
431
|
+
sys.exit(1)
|
|
432
|
+
|
|
433
|
+
# if we know our audio source, load it, analyze it, and init outputs
|
|
434
|
+
if self.sourcefile:
|
|
435
|
+
print("One moment as we track your beats for you, madame...")
|
|
436
|
+
self.track_beats(beatsper=per, firstfull=first)
|
|
437
|
+
self.init_outputs()
|
|
438
|
+
else:
|
|
439
|
+
self.parse_error("a file directive must come before a beats_bar directive")
|
|
440
|
+
sys.exit(1)
|
|
441
|
+
|
|
442
|
+
elif verb == "def":
|
|
443
|
+
try:
|
|
444
|
+
name, value = args.split(maxsplit=1)
|
|
445
|
+
except:
|
|
446
|
+
self.parse_error("unable to parse macro definition")
|
|
447
|
+
sys.exit(1)
|
|
448
|
+
|
|
449
|
+
self.define_macro(name, value)
|
|
450
|
+
print("defined macro %s -=> %s" % (name, value))
|
|
451
|
+
|
|
452
|
+
else:
|
|
453
|
+
# these are the only commands we process noninteractively
|
|
454
|
+
unprocessed_lines.append(line)
|
|
455
|
+
|
|
456
|
+
self.recipe_lines = unprocessed_lines
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def main(args=None):
|
|
460
|
+
output_play = False
|
|
461
|
+
output_save = False
|
|
462
|
+
output_beats = False
|
|
463
|
+
loglevel = logging.INFO
|
|
464
|
+
recipes = []
|
|
465
|
+
|
|
466
|
+
# If no args provided, use sys.argv (skipping the script name at index 0)
|
|
467
|
+
if args is None:
|
|
468
|
+
args = sys.argv[1:]
|
|
469
|
+
|
|
470
|
+
for arg in args:
|
|
471
|
+
if arg == "-p" or arg == "--play":
|
|
472
|
+
output_play = True
|
|
473
|
+
|
|
474
|
+
elif arg == "-s" or arg == "--save":
|
|
475
|
+
output_save = True
|
|
476
|
+
|
|
477
|
+
elif arg == "-b" or arg == "--beats":
|
|
478
|
+
output_beats = True
|
|
479
|
+
|
|
480
|
+
elif arg == "-d" or arg == "--debug":
|
|
481
|
+
loglevel = logging.DEBUG
|
|
482
|
+
|
|
483
|
+
else:
|
|
484
|
+
recipes.append(arg)
|
|
485
|
+
|
|
486
|
+
if len(recipes) < 1:
|
|
487
|
+
print("no recipes to process were found on command line")
|
|
488
|
+
sys.exit(1)
|
|
489
|
+
|
|
490
|
+
if not (output_play or output_save or output_beats):
|
|
491
|
+
print("must specify one or more of --play, --save, or --beats for output")
|
|
492
|
+
sys.exit(1)
|
|
493
|
+
|
|
494
|
+
for recipe in recipes:
|
|
495
|
+
print("processing %s..." % recipe, flush=True)
|
|
496
|
+
|
|
497
|
+
stub, ext = os.path.splitext(os.path.basename(recipe))
|
|
498
|
+
|
|
499
|
+
p = beatrecipe_processor(recipe, interactive=False, loglevel=loglevel, debug=True)
|
|
500
|
+
|
|
501
|
+
if output_play:
|
|
502
|
+
p.enable_output_play()
|
|
503
|
+
|
|
504
|
+
if output_save:
|
|
505
|
+
# /path/to/thisfile.br -> $PWD/thisfile.wav
|
|
506
|
+
wavfile = stub + ".wav"
|
|
507
|
+
p.enable_output_save(wavfile)
|
|
508
|
+
|
|
509
|
+
if output_beats:
|
|
510
|
+
# /path/to/thisfile.br -> $PWD/thisfile_beats.br
|
|
511
|
+
beatsfile = stub + "_beats.br"
|
|
512
|
+
p.enable_output_beats(beatsfile)
|
|
513
|
+
|
|
514
|
+
# will skip ahead to next one if problem arises
|
|
515
|
+
try:
|
|
516
|
+
p.prepare_file_and_macros()
|
|
517
|
+
p.process()
|
|
518
|
+
except Exception as e:
|
|
519
|
+
print("error processing %s: %s" % (recipe, e), flush=True)
|
|
520
|
+
raise e
|
|
521
|
+
|
|
522
|
+
if __name__ == "__main__":
|
|
523
|
+
main()
|