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.
@@ -0,0 +1,387 @@
1
+ #!/usr/bin/env python
2
+
3
+ import re, sys, os.path, random
4
+ import cmd, rosabeats
5
+
6
+
7
+ class rosabeats_shell(cmd.Cmd, rosabeats.rosabeats):
8
+ intro = "welcome to the rosabeats shell"
9
+ prompt = "R@; "
10
+
11
+ def define_macro(self, name, value):
12
+ if self.is_defined_macro(name):
13
+ print("redefining macro %s" % name)
14
+ self.macros[name] = value
15
+ return True
16
+
17
+ def play_macro(self, name, times):
18
+ if not self.is_defined_macro(name):
19
+ print("macro %s not defined" % name)
20
+ return False
21
+
22
+ macro = self.macros[name]
23
+
24
+ print("[*] %d * %s [%s]" % (times, name, macro))
25
+
26
+ for x in range(times):
27
+ print("-> %s" % macro)
28
+ self.onecmd(self.precmd(macro))
29
+
30
+ def is_defined_macro(self, name):
31
+ if self.macros.get(name, None) is not None:
32
+ return True
33
+ else:
34
+ return False
35
+
36
+ def arg1_parse_range(self):
37
+ start = None
38
+ stop = None
39
+ step = None
40
+
41
+ try:
42
+ if "-" in self.cmd_args[0]:
43
+ start, stop = self.cmd_args[0].split("-", maxsplit=1)
44
+ start = int(start)
45
+ stop = int(stop)
46
+
47
+ else:
48
+ start = int(self.cmd_args[0])
49
+ stop = start
50
+ except:
51
+ print("first argument must be valid range, e.g. '8' or '8-10'")
52
+ return None
53
+
54
+ if start > stop:
55
+ step = -1
56
+ else:
57
+ step = 1
58
+
59
+ return [x for x in range(start, (stop + 1) if step >= 0 else (stop - 1), step)]
60
+
61
+ # constructor
62
+ def __init__(self):
63
+ cmd.Cmd.__init__(self)
64
+ rosabeats.rosabeats.__init__(self)
65
+ # super(cmd.Cmd, self)
66
+ # super(rosabeats.rosabeats, self)
67
+ # super().__init__()
68
+ self.macros = dict()
69
+ self.cmd_args = None
70
+
71
+ # pre & post hooks
72
+ def precmd(self, line):
73
+ if line.strip() != '':
74
+ print("precmd: saving cmd as %s" % line)
75
+ self.prev_cmd = line
76
+ self.cmd_args = line.split()[1:]
77
+ return line
78
+
79
+ def preloop(self):
80
+ self.enable_output_play()
81
+ self.disable_output_beats()
82
+ self.disable_output_save()
83
+
84
+ def arg1_float(self):
85
+ try:
86
+ arg1 = float(self.cmd_args[0])
87
+ except:
88
+ print("arg1 must be float")
89
+ return None
90
+ return arg1
91
+
92
+ def arg1_string(self):
93
+ try:
94
+ arg1 = str(self.cmd_args[0])
95
+ except:
96
+ print("arg1 must be string")
97
+ return None
98
+ return arg1
99
+
100
+ def arg1_int(self):
101
+ try:
102
+ arg1 = int(self.cmd_args[0])
103
+ except:
104
+ print("arg1 must be int")
105
+ return None
106
+ return arg1
107
+
108
+ def arg2_int(self):
109
+ try:
110
+ arg1 = int(self.cmd_args[1])
111
+ except:
112
+ print("arg2 must be int")
113
+ return None
114
+ return arg1
115
+
116
+ def arg3_int(self):
117
+ try:
118
+ arg3 = int(self.cmd_args[2])
119
+ except:
120
+ print("arg3 must be int")
121
+ return None
122
+ return arg3
123
+
124
+ def arg2_valid_repeat(self):
125
+ # no second arg so 1 by default
126
+ if len(self.cmd_args) < 2:
127
+ return 1
128
+
129
+ return self.arg2_int()
130
+
131
+ def arg3_valid_repeat(self):
132
+ # no third arg so 1 by default
133
+ if len(self.cmd_args) < 3:
134
+ return 1
135
+
136
+ return self.arg3_int()
137
+
138
+ # command handlers
139
+ def do_rest(self, arg):
140
+ rest = self.arg1_float()
141
+ times = self.arg2_valid_repeat()
142
+
143
+ if rest is None or times is None:
144
+ return False
145
+
146
+ print("[*] %d * %g beat(s) of rest" % (times, rest))
147
+
148
+ for x in range(times):
149
+ self.rest(rest)
150
+
151
+ def do_beats(self, arg):
152
+ beats = self.arg1_parse_range()
153
+ times = self.arg2_valid_repeat()
154
+
155
+ if beats is None or times is None:
156
+ return False
157
+
158
+ print("[*] %d * " % times, end="", flush=True)
159
+
160
+ if len(beats) > 1:
161
+ print("(beats %d-%d) " % (beats[0], beats[-1]))
162
+ else:
163
+ print("(beat %d) " % beats[0])
164
+
165
+ for x in range(times):
166
+ self.play_beats(beats)
167
+
168
+ def do_beat_div(self, arg):
169
+ beat = self.arg1_int()
170
+ divisor = self.arg2_int()
171
+ times = self.arg3_valid_repeat()
172
+
173
+ if beats is None or divisor is None or times is None:
174
+ return False
175
+
176
+ print("[*] %d * (1/%d beats) " % (times, divisor), flush=True)
177
+
178
+ for x in range(times):
179
+ print("%d/%d " % (beat, divisor), end="", flush=True)
180
+ self.play_beat(beat, divisor=divisor, silent=True)
181
+
182
+ print(flush=True)
183
+
184
+ def do_beats_shuf(self, arg):
185
+ beats = self.arg1_parse_range()
186
+ times = self.arg2_valid_repeat()
187
+
188
+ if beats is None or times is None:
189
+ return False
190
+
191
+ print("[*] %d * " % times, end="", flush=True)
192
+ print("(shuffled beats %d-%d] " % (beats[0], beats[-1]), flush=True)
193
+
194
+ for x in range(times):
195
+ random.shuffle(beats)
196
+ self.play_beats(beats)
197
+
198
+ def do_bars(self, arg):
199
+ bars = self.arg1_parse_range()
200
+ times = self.arg2_valid_repeat()
201
+
202
+ if bars is None or times is None:
203
+ return False
204
+
205
+ print("[*] %d * " % times, end="", flush=True)
206
+ if len(bars) > 1:
207
+ print("(bars %d-%d) " % (bars[0], bars[-1]), flush=True)
208
+ else:
209
+ print("(bar %d) " % bars[0], flush=True)
210
+
211
+ for x in range(times):
212
+ self.play_bars(bars)
213
+
214
+ def do_bars_rev(self, arg):
215
+ bars = self.arg1_parse_range()
216
+ times = self.arg2_valid_repeat()
217
+
218
+ if bars is None or times is None:
219
+ return False
220
+
221
+ print("[*] %d * " % times, end="", flush=True)
222
+ if len(bars) > 1:
223
+ print("(rev bars %d-%d) " % (bars[0], bars[-1]), flush=True)
224
+ else:
225
+ print("(rev bar %d) " % bars[0], flush=True)
226
+
227
+ for x in range(times):
228
+ self.play_bars(bars, reverse=True)
229
+
230
+ def do_bars_shuf(self, arg):
231
+ bars = self.arg1_parse_range()
232
+ times = self.arg2_valid_repeat()
233
+
234
+ if bars is None or times is None:
235
+ return False
236
+
237
+ print("[*] %d * " % times, end="", flush=True)
238
+ print("(shuffled bars %d-%d) " % (bars[0], bars[-1]), flush=True)
239
+
240
+ for x in range(times):
241
+ random.shuffle(bars)
242
+ self.play_bars(bars)
243
+
244
+ def do_play(self, arg):
245
+ name = self.arg1_string()
246
+ times = self.arg2_valid_repeat()
247
+
248
+ if name is None or times is None:
249
+ return False
250
+
251
+ self.play_macro(name, times)
252
+
253
+ def args_valid_value(self):
254
+ try:
255
+ args = " ".join(self.cmd_args[1:])
256
+ except:
257
+ print("this cmd requires the value to assign")
258
+ return None
259
+ return args
260
+
261
+ def do_def(self, arg):
262
+ name = self.arg1_string()
263
+ value = self.args_valid_value()
264
+
265
+ if name is None or value is None:
266
+ return False
267
+
268
+ print("[*] (def seg: %s = %s)" % (name, value))
269
+
270
+ self.define_macro(name, value)
271
+ print("defined %s => %s" % (name, value))
272
+
273
+ def do_help(self, arg):
274
+ print("valid commands are:")
275
+ print(" beats, bars, beats_shuf, bars_shuf, beat_div, bars_rev, rest")
276
+ print(" for predefined macros: ls, lsdef, play, or just type the def name")
277
+ print(" and finally: save, help, quit")
278
+
279
+ def do_lsdef(self, arg):
280
+ for name, value in self.macros.items():
281
+ print("%15s %15s" % (name, value))
282
+
283
+ def do_ls(self, arg):
284
+ print(", ".join(list(self.macros.keys())))
285
+
286
+ def do_quit(self, arg):
287
+ self.shutdown()
288
+ sys.exit(0)
289
+
290
+ def do_file(self, arg):
291
+ filename = self.arg1_string()
292
+
293
+ if filename is None:
294
+ return False
295
+
296
+ print("setting filename to %s" % filename)
297
+ self.setfile(filename)
298
+
299
+ def do_beats_bar(self, arg):
300
+ per = self.arg1_int()
301
+ first = self.arg2_int()
302
+
303
+ if per is None or first is None:
304
+ return False
305
+
306
+ # if we know our audio source, load it, analyze it, and init outputs
307
+ if self.sourcefile:
308
+ print("One moment as we track your beats for you, madame...")
309
+ self.track_beats(beatsper=per, firstfull=first)
310
+ print("%d beats, %d bars" % (self.total_beats, self.total_bars))
311
+ self.init_outputs()
312
+ else:
313
+ print("specify file before beats_bar")
314
+ return False
315
+
316
+ def do_save(self, arg):
317
+ filename = self.arg1_string()
318
+ print("saving %s" % filename)
319
+ with open(filename, "w") as f:
320
+ print("file %s" % self.sourcefile, file=f)
321
+ print("beats_bar %d %d" % (self.beatsperbar, self.firstfullbar), file=f)
322
+ for name, value in self.macros.items():
323
+ print("def %s %s" % (name, value), file=f)
324
+
325
+ def default(self, line):
326
+ self.cmd_args = line.split()
327
+ self.do_play(line)
328
+
329
+
330
+ # def emptyline(self):
331
+ # print("repeating: %s" % self.prev_cmd)
332
+ # self.onecmd(self.precmd(self.prev_cmd))
333
+
334
+
335
+ def main():
336
+ """
337
+ Main function for rosabeats-shell command-line tool.
338
+
339
+ Usage: rosabeats-shell [audio_file [beats_per_bar first_full_bar]]
340
+
341
+ Args:
342
+ audio_file: Optional audio file to analyze
343
+ beats_per_bar: Number of beats per bar (if audio_file is provided)
344
+ first_full_bar: First full bar number (if audio_file is provided)
345
+
346
+ Returns:
347
+ Exit code (0 for success, non-zero for errors)
348
+ """
349
+ try:
350
+ load_file = False
351
+ try:
352
+ filename = sys.argv[1]
353
+ beats_bar = " ".join(sys.argv[2:])
354
+ load_file = True
355
+ print(f"Audio file: {filename}")
356
+ print(f"Beats/bar settings: {beats_bar}")
357
+ except IndexError:
358
+ # No command line arguments is valid
359
+ pass
360
+
361
+ s = rosabeats_shell()
362
+ if load_file:
363
+ s.preloop()
364
+ s.onecmd(s.precmd(f"file {filename}"))
365
+ s.onecmd(s.precmd(f"beats_bar {beats_bar}"))
366
+
367
+ s.cmdloop()
368
+ return 0
369
+ except KeyboardInterrupt:
370
+ print("\nShutting down...")
371
+ if 's' in locals() and hasattr(s, 'shutdown'):
372
+ s.shutdown()
373
+ return 0
374
+ except Exception as e:
375
+ print(f"Error: {str(e)}")
376
+ return 1
377
+ finally:
378
+ # Ensure we clean up resources even if there's an error
379
+ if 's' in locals() and hasattr(s, 'shutdown'):
380
+ try:
381
+ s.shutdown()
382
+ except:
383
+ pass
384
+
385
+
386
+ if __name__ == "__main__":
387
+ sys.exit(main())
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env python
2
+
3
+ import sys
4
+ import os.path
5
+ import argparse
6
+ from rosabeats import rosabeats
7
+
8
+ # Check if terminal supports colors
9
+ def supports_color():
10
+ """Check if the terminal supports color output"""
11
+ if sys.platform == 'win32':
12
+ return False
13
+ if not sys.stdout.isatty():
14
+ return False
15
+ try:
16
+ import curses
17
+ curses.setupterm()
18
+ return curses.tigetnum("colors") > 0
19
+ except:
20
+ return False
21
+
22
+ # Check if terminal supports emojis
23
+ def supports_emojis():
24
+ """Check if the terminal supports emoji output"""
25
+ if sys.platform == 'win32':
26
+ return False
27
+ if not sys.stdout.isatty():
28
+ return False
29
+ try:
30
+ # Try to print an emoji and check if it's displayed correctly
31
+ print("\U0001F44D", end='', flush=True)
32
+ return True
33
+ except:
34
+ return False
35
+
36
+ # ANSI color codes for terminal output
37
+ class Colors:
38
+ if supports_color():
39
+ HEADER = '\033[95m'
40
+ BLUE = '\033[94m'
41
+ CYAN = '\033[96m'
42
+ GREEN = '\033[92m'
43
+ YELLOW = '\033[93m'
44
+ RED = '\033[91m'
45
+ ENDC = '\033[0m'
46
+ BOLD = '\033[1m'
47
+ UNDERLINE = '\033[4m'
48
+ else:
49
+ HEADER = BLUE = CYAN = GREEN = YELLOW = RED = ENDC = BOLD = UNDERLINE = ''
50
+
51
+ # Emoji symbols
52
+ class Symbols:
53
+ if supports_emojis():
54
+ CHECK = '✓'
55
+ BULLET = '•'
56
+ WARNING = '⚠'
57
+ else:
58
+ CHECK = '[OK]'
59
+ BULLET = '*'
60
+ WARNING = '[!]'
61
+
62
+ def print_header(text):
63
+ """Print a formatted header message"""
64
+ print(f"\n{Colors.BOLD}{Colors.BLUE}== {text} =={Colors.ENDC}\n")
65
+
66
+ def print_step(text):
67
+ """Print a formatted step message"""
68
+ print(f"{Colors.CYAN}{Symbols.BULLET} {text}...{Colors.ENDC}")
69
+
70
+ def print_success(text):
71
+ """Print a formatted success message"""
72
+ print(f"{Colors.GREEN}{Symbols.CHECK} {text}{Colors.ENDC}")
73
+
74
+ def print_warning(text):
75
+ """Print a formatted warning message"""
76
+ print(f"{Colors.YELLOW}{Symbols.WARNING} {text}{Colors.ENDC}")
77
+
78
+ def main(args=None):
79
+ # Set up command line argument parsing
80
+ parser = argparse.ArgumentParser(
81
+ description='Segment audio file and track beats using laplacian, segmentino, or backtrack methods',
82
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter
83
+ )
84
+ parser.add_argument('audiofile', help='Audio file to process')
85
+ parser.add_argument('--method', choices=['laplacian', 'segmentino', 'backtrack'],
86
+ default='backtrack', help='Segmentation method to use')
87
+ parser.add_argument('--max-clusters', type=int, default=None,
88
+ help='Maximum number of clusters to use for laplacian segmentation')
89
+ parser.add_argument('--beatsper', type=int, default=8,
90
+ help='Number of beats per bar')
91
+ parser.add_argument('--firstfull', type=int, default=0,
92
+ help='First full bar number')
93
+ parser.add_argument('--output', help='Output file path (default: input filename with .bri extension)')
94
+ parser.add_argument('--debug', action='store_true',
95
+ help='Enable debug mode for detailed processing information')
96
+
97
+ args = parser.parse_args()
98
+
99
+ # Set output file if not specified
100
+ if args.output is None:
101
+ basename = os.path.basename(args.audiofile)
102
+ stub, _ = os.path.splitext(basename)
103
+ output = stub + ".bri"
104
+ else:
105
+ output = args.output
106
+
107
+ if args.method == "laplacian" and args.max_clusters is None:
108
+ print_warning("max-clusters is not specified for laplacian segmentation, using default of 48")
109
+ args.max_clusters = 48
110
+
111
+ if args.max_clusters is not None and args.method != "laplacian":
112
+ print_warning("max-clusters is specified for non-laplacian segmentation, this will be ignored")
113
+
114
+ # Print processing information
115
+ print_header("Audio Segmentation and Beat Tracking")
116
+ print(f"{Colors.BOLD}Input:{Colors.ENDC}")
117
+ print(f" Audio file: {args.audiofile}")
118
+ print(f" Method: {args.method}")
119
+ print(f" Beats per bar: {args.beatsper}")
120
+ print(f" First full bar: {args.firstfull}")
121
+ print(f" Output file: {output}")
122
+ if args.debug:
123
+ print_warning("Debug mode enabled - detailed processing information will be shown")
124
+
125
+ # Process the audio file
126
+ print_step("Loading audio file")
127
+ s = rosabeats(args.audiofile, debug=args.debug)
128
+ print_success(f"Loaded {os.path.basename(args.audiofile)}")
129
+
130
+ print_step("Tracking beats")
131
+ s.track_beats(args.beatsper, args.firstfull)
132
+ print_success(f"Found {s.total_beats} beats in {s.total_bars} bars")
133
+
134
+ print_step(f"Segmenting with {args.method} method")
135
+ s.segment(method=args.method, max_clusters=args.max_clusters)
136
+ print_success(f"Identified {s.total_segments} segments")
137
+
138
+ print_step("Assigning beats and bars to segments")
139
+ s.segmentize_beats()
140
+ print_success("Completed beat and bar assignment")
141
+
142
+ # Write results to output file
143
+ print_step(f"Writing results to {output}")
144
+ with open(output, "w") as f:
145
+ # Write header information
146
+ f.write("##BEATS## This was segmented and tracked using librosa's beat tracker\n\n")
147
+ f.write(f"file {s.sourcefile}\n")
148
+ f.write(f"beats_bar {s.beatsperbar} {s.firstfullbar}\n")
149
+ f.write(f"# total beats = {s.total_beats}\n")
150
+ f.write(f"# total bars = {s.total_bars} (beats {s.firstfullbar}-{s.firstfullbar + (s.beatsperbar * s.total_bars)})\n")
151
+
152
+ # Process and write segment information
153
+ bars_defs = []
154
+ beats_defs = []
155
+ for idx, seg in enumerate(s.segments):
156
+ # Print segment information to console
157
+ print(f"\n{Colors.BOLD}Segment {idx}:{Colors.ENDC}")
158
+ print(f" Label: {seg['label']}")
159
+ print(f" Duration: {seg['duration']:.2f} seconds")
160
+
161
+ if len(seg["bars"]) >= 1:
162
+ print(f" Bars: {seg['bars'][0]}-{seg['bars'][-1]}")
163
+ bars_defs.append(f"def {seg['label']}_{idx} bars {seg['bars'][0]}-{seg['bars'][-1]}")
164
+
165
+ if len(seg["beats"]) >= 1:
166
+ print(f" Beats: {seg['beats'][0]}-{seg['beats'][-1]}")
167
+ beats_defs.append(f"def {seg['label']}_{idx}_beats beats {seg['beats'][0]}-{seg['beats'][-1]}\t# dur = {seg['duration']:.2f}s, beats {seg['beats'][0]}-{seg['beats'][-1]}")
168
+
169
+ # Write segment definitions to file
170
+ for d in bars_defs:
171
+ f.write(f"{d}\n")
172
+ f.write("\n")
173
+ for d in beats_defs:
174
+ f.write(f"{d}\n")
175
+ f.write("\n")
176
+
177
+ print_success(f"Results written to {output}")
178
+ print_header("Processing Complete")
179
+
180
+ if __name__ == "__main__":
181
+ main()