rosabeats 0.1.3__tar.gz → 0.2.0__tar.gz

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.
Files changed (31) hide show
  1. {rosabeats-0.1.3/rosabeats.egg-info → rosabeats-0.2.0}/PKG-INFO +8 -30
  2. {rosabeats-0.1.3 → rosabeats-0.2.0}/README.md +3 -21
  3. {rosabeats-0.1.3 → rosabeats-0.2.0}/pyproject.toml +8 -5
  4. {rosabeats-0.1.3 → rosabeats-0.2.0}/rosabeats/__init__.py +1 -1
  5. rosabeats-0.2.0/rosabeats/__main__.py +59 -0
  6. {rosabeats-0.1.3 → rosabeats-0.2.0}/rosabeats/beatrecipe_processor.py +63 -46
  7. {rosabeats-0.1.3 → rosabeats-0.2.0}/rosabeats/beatswitch.py +29 -13
  8. rosabeats-0.2.0/rosabeats/downbeat.py +207 -0
  9. {rosabeats-0.1.3 → rosabeats-0.2.0}/rosabeats/rosabeats.py +575 -543
  10. rosabeats-0.2.0/rosabeats/rosabeats_shell.py +494 -0
  11. {rosabeats-0.1.3 → rosabeats-0.2.0}/rosabeats/segment_song.py +100 -31
  12. {rosabeats-0.1.3 → rosabeats-0.2.0/rosabeats.egg-info}/PKG-INFO +8 -30
  13. {rosabeats-0.1.3 → rosabeats-0.2.0}/rosabeats.egg-info/SOURCES.txt +9 -4
  14. {rosabeats-0.1.3 → rosabeats-0.2.0}/rosabeats.egg-info/requires.txt +3 -6
  15. {rosabeats-0.1.3 → rosabeats-0.2.0}/rosabeats.egg-info/top_level.txt +1 -1
  16. rosabeats-0.2.0/tests/__init__.py +1 -0
  17. rosabeats-0.2.0/tests/conftest.py +131 -0
  18. rosabeats-0.2.0/tests/test_beatrecipe_processor.py +193 -0
  19. rosabeats-0.2.0/tests/test_downbeat.py +149 -0
  20. rosabeats-0.2.0/tests/test_rosabeats.py +234 -0
  21. rosabeats-0.2.0/tests/test_segment_song.py +120 -0
  22. rosabeats-0.2.0/tests/test_shell.py +305 -0
  23. rosabeats-0.1.3/docs/beatrecipe_docs.txt +0 -80
  24. rosabeats-0.1.3/rosabeats/rosabeats_shell.py +0 -387
  25. rosabeats-0.1.3/scripts/reverse_beats_in_bars_rosa.py +0 -48
  26. rosabeats-0.1.3/scripts/shuffle_bars_rosa.py +0 -35
  27. rosabeats-0.1.3/scripts/shuffle_beats_rosa.py +0 -29
  28. {rosabeats-0.1.3 → rosabeats-0.2.0}/LICENSE.md +0 -0
  29. {rosabeats-0.1.3 → rosabeats-0.2.0}/rosabeats.egg-info/dependency_links.txt +0 -0
  30. {rosabeats-0.1.3 → rosabeats-0.2.0}/rosabeats.egg-info/entry_points.txt +0 -0
  31. {rosabeats-0.1.3 → rosabeats-0.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosabeats
3
- Version: 0.1.3
3
+ Version: 0.2.0
4
4
  Summary: Audio beat detection, segmentation, and remixing library using librosa
5
5
  Author: John Fleming
6
6
  License-Expression: ISC
@@ -8,15 +8,13 @@ Project-URL: Homepage, https://github.com/jbff/rosabeats
8
8
  Project-URL: Repository, https://github.com/jbff/rosabeats
9
9
  Keywords: audio,beat,detection,segmentation,remixing,librosa,music
10
10
  Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.9
12
- Classifier: Programming Language :: Python :: 3.10
13
11
  Classifier: Programming Language :: Python :: 3.11
14
12
  Classifier: Programming Language :: Python :: 3.12
15
13
  Classifier: Programming Language :: Python :: 3.13
16
14
  Classifier: Operating System :: OS Independent
17
15
  Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
18
16
  Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
19
- Requires-Python: >=3.9
17
+ Requires-Python: >=3.11
20
18
  Description-Content-Type: text/markdown
21
19
  License-File: LICENSE.md
22
20
  Requires-Dist: librosa<1.0,>=0.11.0
@@ -27,11 +25,9 @@ Requires-Dist: scipy<2.0,>=1.15
27
25
  Requires-Dist: scikit-learn<2.0,>=1.5
28
26
  Provides-Extra: ffms2
29
27
  Requires-Dist: ffms2; extra == "ffms2"
30
- Provides-Extra: vamp
31
- Requires-Dist: vamp; extra == "vamp"
32
- Provides-Extra: all
33
- Requires-Dist: ffms2; extra == "all"
34
- Requires-Dist: vamp; extra == "all"
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8.0; extra == "dev"
30
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
35
31
  Dynamic: license-file
36
32
 
37
33
  # rosabeats
@@ -48,9 +44,7 @@ I wrote most of this code in 2018-2019 and made some additions through 2021. I c
48
44
 
49
45
  - **Beat Detection**: Automatically detect beats and tempo in audio files
50
46
  - **Bar Analysis**: Group beats into bars with configurable beats-per-bar
51
- - **Audio Segmentation**: Segment songs into structural parts using:
52
- - Laplacian segmentation (powered by librosa)
53
- - Segmentino (using Vamp plugins)
47
+ - **Audio Segmentation**: Segment songs into structural parts using Laplacian spectral clustering
54
48
  - **Audio Remixing**: Create remixes by manipulating beats and bars:
55
49
  - Play individual beats or ranges of beats
56
50
  - Play entire bars or ranges of bars
@@ -66,7 +60,7 @@ I wrote most of this code in 2018-2019 and made some additions through 2021. I c
66
60
 
67
61
  ### Prerequisites
68
62
 
69
- - Python 3.9+ (Tested most recently with Python 3.11.12 and 3.13.2)
63
+ - Python 3.11+ (Tested most recently with Python 3.11.12 and 3.13.2)
70
64
  - ffms2 libraries (On Fedora: `dnf install ffms2`; tested most recently with ffms-5.0)
71
65
 
72
66
  ### Installing from PyPI
@@ -105,32 +99,17 @@ pip install -e .
105
99
 
106
100
  3. Installing optional dependencies:
107
101
 
108
- rosabeats has optional dependencies that can be installed based on your needs:
109
-
110
102
  ```bash
111
- # Install with Vamp plugin support
112
- pip install .[vamp]
113
-
114
103
  # Install with ffms2 support for additional audio formats
115
104
  pip install .[ffms2]
116
-
117
- # Install with all optional dependencies
118
- pip install .[all]
119
105
  ```
120
106
 
121
107
  This will install the package and its dependencies, and make the following command-line tools available:
122
108
  - `beatrecipe-processor`: Process beat recipes
123
- - `segment-song`: Segment songs using librosa's Laplacian segmentation
109
+ - `segment-song`: Segment songs and track beats
124
110
  - `beatswitch`: Generate beat recipes with alternating patterns
125
111
  - `rosabeats-shell`: Interactive shell for beat manipulation
126
112
 
127
- ### Optional: Vamp Plugins for Segmentino
128
-
129
- If you want to use the Segmentino-based segmentation:
130
-
131
- 1. Download and install the Vamp plugin SDK
132
- 2. Install the Segmentino plugin
133
-
134
113
  ### Using rosabeats
135
114
 
136
115
  The package can be imported in Python as:
@@ -153,6 +132,5 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH RE
153
132
  ## Acknowledgments
154
133
 
155
134
  - [librosa](https://librosa.org/) for audio analysis
156
- - [Vamp plugins](https://www.vamp-plugins.org/) for audio segmentation
157
135
  - [Amen](https://github.com/algorithmic-music-exploration/amen) was the original inspiration
158
136
 
@@ -12,9 +12,7 @@ I wrote most of this code in 2018-2019 and made some additions through 2021. I c
12
12
 
13
13
  - **Beat Detection**: Automatically detect beats and tempo in audio files
14
14
  - **Bar Analysis**: Group beats into bars with configurable beats-per-bar
15
- - **Audio Segmentation**: Segment songs into structural parts using:
16
- - Laplacian segmentation (powered by librosa)
17
- - Segmentino (using Vamp plugins)
15
+ - **Audio Segmentation**: Segment songs into structural parts using Laplacian spectral clustering
18
16
  - **Audio Remixing**: Create remixes by manipulating beats and bars:
19
17
  - Play individual beats or ranges of beats
20
18
  - Play entire bars or ranges of bars
@@ -30,7 +28,7 @@ I wrote most of this code in 2018-2019 and made some additions through 2021. I c
30
28
 
31
29
  ### Prerequisites
32
30
 
33
- - Python 3.9+ (Tested most recently with Python 3.11.12 and 3.13.2)
31
+ - Python 3.11+ (Tested most recently with Python 3.11.12 and 3.13.2)
34
32
  - ffms2 libraries (On Fedora: `dnf install ffms2`; tested most recently with ffms-5.0)
35
33
 
36
34
  ### Installing from PyPI
@@ -69,32 +67,17 @@ pip install -e .
69
67
 
70
68
  3. Installing optional dependencies:
71
69
 
72
- rosabeats has optional dependencies that can be installed based on your needs:
73
-
74
70
  ```bash
75
- # Install with Vamp plugin support
76
- pip install .[vamp]
77
-
78
71
  # Install with ffms2 support for additional audio formats
79
72
  pip install .[ffms2]
80
-
81
- # Install with all optional dependencies
82
- pip install .[all]
83
73
  ```
84
74
 
85
75
  This will install the package and its dependencies, and make the following command-line tools available:
86
76
  - `beatrecipe-processor`: Process beat recipes
87
- - `segment-song`: Segment songs using librosa's Laplacian segmentation
77
+ - `segment-song`: Segment songs and track beats
88
78
  - `beatswitch`: Generate beat recipes with alternating patterns
89
79
  - `rosabeats-shell`: Interactive shell for beat manipulation
90
80
 
91
- ### Optional: Vamp Plugins for Segmentino
92
-
93
- If you want to use the Segmentino-based segmentation:
94
-
95
- 1. Download and install the Vamp plugin SDK
96
- 2. Install the Segmentino plugin
97
-
98
81
  ### Using rosabeats
99
82
 
100
83
  The package can be imported in Python as:
@@ -117,6 +100,5 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH RE
117
100
  ## Acknowledgments
118
101
 
119
102
  - [librosa](https://librosa.org/) for audio analysis
120
- - [Vamp plugins](https://www.vamp-plugins.org/) for audio segmentation
121
103
  - [Amen](https://github.com/algorithmic-music-exploration/amen) was the original inspiration
122
104
 
@@ -8,15 +8,13 @@ dynamic = ["version"]
8
8
  description = "Audio beat detection, segmentation, and remixing library using librosa"
9
9
  readme = "README.md"
10
10
  license = "ISC"
11
- requires-python = ">=3.9"
11
+ requires-python = ">=3.11"
12
12
  authors = [
13
13
  { name = "John Fleming" }
14
14
  ]
15
15
  keywords = ["audio", "beat", "detection", "segmentation", "remixing", "librosa", "music"]
16
16
  classifiers = [
17
17
  "Programming Language :: Python :: 3",
18
- "Programming Language :: Python :: 3.9",
19
- "Programming Language :: Python :: 3.10",
20
18
  "Programming Language :: Python :: 3.11",
21
19
  "Programming Language :: Python :: 3.12",
22
20
  "Programming Language :: Python :: 3.13",
@@ -35,8 +33,7 @@ dependencies = [
35
33
 
36
34
  [project.optional-dependencies]
37
35
  ffms2 = ["ffms2"]
38
- vamp = ["vamp"]
39
- all = ["ffms2", "vamp"]
36
+ dev = ["pytest>=8.0", "pytest-cov>=4.0"]
40
37
 
41
38
  [project.scripts]
42
39
  beatrecipe-processor = "rosabeats.beatrecipe_processor:main"
@@ -56,3 +53,9 @@ where = ["."]
56
53
 
57
54
  [tool.setuptools.package-data]
58
55
  rosabeats = ["docs/*"]
56
+
57
+ [tool.pytest.ini_options]
58
+ testpaths = ["tests"]
59
+ python_files = ["test_*.py"]
60
+ python_functions = ["test_*"]
61
+ addopts = "-v --tb=short"
@@ -13,7 +13,7 @@ Main features:
13
13
  - Beat Recipe Files: Save and load beat patterns as reusable recipes
14
14
  """
15
15
 
16
- __version__ = "0.1.3"
16
+ __version__ = "0.2.0"
17
17
 
18
18
  # Import main classes and functions from the module
19
19
  from .rosabeats import rosabeats
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Entry point for running rosabeats as a module.
4
+
5
+ Usage:
6
+ python -m rosabeats [command]
7
+
8
+ Commands:
9
+ shell Launch interactive shell (default)
10
+ segment Segment audio file and track beats
11
+ beatswitch Generate beat recipe with alternating patterns
12
+ recipe Process beat recipe files
13
+
14
+ Examples:
15
+ python -m rosabeats # Launch interactive shell
16
+ python -m rosabeats shell # Same as above
17
+ python -m rosabeats segment song.wav # Segment audio file
18
+ python -m rosabeats beatswitch song.wav # Generate beat recipe
19
+ python -m rosabeats recipe file.br # Process beat recipe
20
+ """
21
+
22
+ import sys
23
+
24
+
25
+ def main():
26
+ """Main entry point for python -m rosabeats."""
27
+ if len(sys.argv) < 2:
28
+ # Default to shell
29
+ from rosabeats.rosabeats_shell import main as shell_main
30
+ sys.exit(shell_main())
31
+
32
+ command = sys.argv[1]
33
+
34
+ # Remove the command from argv so subcommands see correct args
35
+ sys.argv = [f"rosabeats {command}"] + sys.argv[2:]
36
+
37
+ if command in ("shell", "sh"):
38
+ from rosabeats.rosabeats_shell import main as shell_main
39
+ sys.exit(shell_main())
40
+ elif command in ("segment", "seg"):
41
+ from rosabeats.segment_song import main as segment_main
42
+ sys.exit(segment_main() or 0)
43
+ elif command in ("beatswitch", "bs"):
44
+ from rosabeats.beatswitch import main as beatswitch_main
45
+ sys.exit(beatswitch_main() or 0)
46
+ elif command in ("recipe", "br"):
47
+ from rosabeats.beatrecipe_processor import main as recipe_main
48
+ sys.exit(recipe_main() or 0)
49
+ elif command in ("-h", "--help", "help"):
50
+ print(__doc__)
51
+ sys.exit(0)
52
+ else:
53
+ print(f"Unknown command: {command}")
54
+ print("Use 'python -m rosabeats --help' for usage information.")
55
+ sys.exit(1)
56
+
57
+
58
+ if __name__ == "__main__":
59
+ main()
@@ -4,6 +4,7 @@ import re, sys, os.path, random, logging
4
4
 
5
5
  from rosabeats import rosabeats
6
6
 
7
+
7
8
  class beatrecipe_processor(rosabeats):
8
9
  @staticmethod
9
10
  def ilist(indices):
@@ -65,6 +66,13 @@ class beatrecipe_processor(rosabeats):
65
66
  def parse_error(self, error_info):
66
67
  self.log.error("%s" % error_info)
67
68
 
69
+ def parse_command(self, cmd):
70
+ """Split a command line into (verb, args). args is a list of remaining tokens."""
71
+ parts = cmd.split()
72
+ if not parts:
73
+ return None, []
74
+ return parts[0], parts[1:]
75
+
68
76
  def read_beatrecipe(self):
69
77
  self.recipe_lines = list()
70
78
  with open(self.recipe, "r") as f:
@@ -75,11 +83,18 @@ class beatrecipe_processor(rosabeats):
75
83
 
76
84
  for cmd in lines:
77
85
  self.recipe_lines.append(cmd)
86
+ # Load macro definitions at read time so macros are available after init
87
+ if cmd.strip().startswith("def "):
88
+ try:
89
+ _, rest = cmd.split(maxsplit=1)
90
+ name, value = rest.split(maxsplit=1)
91
+ self.define_macro(name, value)
92
+ except Exception:
93
+ pass
78
94
 
79
95
  def define_macro(self, name, value):
80
96
  if self.macros.get(name) is not None:
81
- self.parse_error("macro %s already defined" % name)
82
- return False
97
+ self.log.debug("macro %s redefined" % name)
83
98
  self.macros[name] = value
84
99
  return True
85
100
 
@@ -114,7 +129,7 @@ class beatrecipe_processor(rosabeats):
114
129
  return None
115
130
 
116
131
  # skip lines that start with hash
117
- if line.startswith('#'):
132
+ if line.startswith("#"):
118
133
  return None
119
134
 
120
135
  # remove same line comments but process rest of line
@@ -127,7 +142,7 @@ class beatrecipe_processor(rosabeats):
127
142
 
128
143
  # split multiple commands separated by semicolons
129
144
  lines = []
130
- for l in line.split(';'):
145
+ for l in line.split(";"):
131
146
  lines.append(l.strip())
132
147
 
133
148
  return lines
@@ -339,9 +354,7 @@ class beatrecipe_processor(rosabeats):
339
354
  return True
340
355
  else:
341
356
  raise e
342
- print(
343
- "[*] (def seg: %s = %s)" % (name, value)
344
- )
357
+ print("[*] (def seg: %s = %s)" % (name, value))
345
358
 
346
359
  self.define_macro(name, value)
347
360
  print("defined %s => %s" % (name, value))
@@ -357,7 +370,6 @@ class beatrecipe_processor(rosabeats):
357
370
  print("%15s %15s" % (name, value))
358
371
 
359
372
  elif self.interactive and verb == "ls":
360
-
361
373
  print(", ".join(list(self.macros.keys())))
362
374
 
363
375
  elif self.interactive and verb == "quit":
@@ -392,12 +404,11 @@ class beatrecipe_processor(rosabeats):
392
404
  def process_interactive(self):
393
405
  KeepProcessing = True
394
406
  while KeepProcessing:
395
-
396
407
  print("")
397
408
  print("enter beatrecipe command => ", end="", flush=True)
398
409
 
399
410
  lines = self.preprocess(input())
400
-
411
+
401
412
  for cmd in lines:
402
413
  print(cmd)
403
414
  KeepProcessing = self.execute_command(cmd)
@@ -413,8 +424,8 @@ class beatrecipe_processor(rosabeats):
413
424
  except Exception:
414
425
  verb = line
415
426
  args = ""
416
- # self.parse_error("could not split line into verb and args")
417
- # sys.exit(1)
427
+ # self.parse_error("could not split line into verb and args")
428
+ # sys.exit(1)
418
429
 
419
430
  if verb == "file":
420
431
  filename = str(args)
@@ -433,10 +444,12 @@ class beatrecipe_processor(rosabeats):
433
444
  # if we know our audio source, load it, analyze it, and init outputs
434
445
  if self.sourcefile:
435
446
  print("One moment as we track your beats for you, madame...")
436
- self.track_beats(beatsper=per, firstfull=first)
447
+ self.track_beats(beatsper=per, downbeat=first)
437
448
  self.init_outputs()
438
449
  else:
439
- self.parse_error("a file directive must come before a beats_bar directive")
450
+ self.parse_error(
451
+ "a file directive must come before a beats_bar directive"
452
+ )
440
453
  sys.exit(1)
441
454
 
442
455
  elif verb == "def":
@@ -457,46 +470,49 @@ class beatrecipe_processor(rosabeats):
457
470
 
458
471
 
459
472
  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)
473
+ import argparse
474
+
475
+ parser = argparse.ArgumentParser(
476
+ description='Process beat recipe files (.br) to create audio remixes',
477
+ formatter_class=argparse.RawDescriptionHelpFormatter,
478
+ epilog='''
479
+ Examples:
480
+ beatrecipe-processor -p recipe.br Play the recipe
481
+ beatrecipe-processor -s recipe.br Save output to WAV
482
+ beatrecipe-processor -p -s recipe.br Play and save
483
+ beatrecipe-processor -b recipe.br Write beats output file
484
+ '''
485
+ )
486
+ parser.add_argument('recipes', nargs='+', metavar='RECIPE',
487
+ help='Beat recipe files (.br) to process')
488
+ parser.add_argument('-p', '--play', action='store_true',
489
+ help='Enable audio playback')
490
+ parser.add_argument('-s', '--save', action='store_true',
491
+ help='Save output to WAV file')
492
+ parser.add_argument('-b', '--beats', action='store_true',
493
+ help='Write beats output file')
494
+ parser.add_argument('-d', '--debug', action='store_true',
495
+ help='Enable debug mode')
496
+
497
+ parsed = parser.parse_args(args)
498
+
499
+ output_play = parsed.play
500
+ output_save = parsed.save
501
+ output_beats = parsed.beats
502
+ loglevel = logging.DEBUG if parsed.debug else logging.INFO
503
+ recipes = parsed.recipes
489
504
 
490
505
  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)
506
+ parser.error("must specify one or more of --play, --save, or --beats for output")
493
507
 
494
508
  for recipe in recipes:
495
509
  print("processing %s..." % recipe, flush=True)
496
510
 
497
511
  stub, ext = os.path.splitext(os.path.basename(recipe))
498
512
 
499
- p = beatrecipe_processor(recipe, interactive=False, loglevel=loglevel, debug=True)
513
+ p = beatrecipe_processor(
514
+ recipe, interactive=False, loglevel=loglevel, debug=True
515
+ )
500
516
 
501
517
  if output_play:
502
518
  p.enable_output_play()
@@ -519,5 +535,6 @@ def main(args=None):
519
535
  print("error processing %s: %s" % (recipe, e), flush=True)
520
536
  raise e
521
537
 
538
+
522
539
  if __name__ == "__main__":
523
540
  main()
@@ -21,16 +21,16 @@ class BeatSwitcher(rosabeats.rosabeats):
21
21
  Attributes:
22
22
  infile (str): Path to input audio file
23
23
  outfile (str): Path to output beat recipe file (.br)
24
- firstfull (int): Index of first full bar
24
+ downbeat (int): Beat index of first downbeat
25
25
  fmin (int): Minimum number of forward beats
26
26
  fmax (int): Maximum number of forward beats
27
27
  bmin (int): Minimum number of backward beats
28
28
  bmax (int): Maximum number of backward beats
29
29
  """
30
-
30
+
31
31
  def __init__(self, infile, debug=False):
32
32
  """Initialize the BeatSwitcher.
33
-
33
+
34
34
  Args:
35
35
  infile (str): Path to input audio file
36
36
  debug (bool, optional): Enable debug mode
@@ -43,26 +43,26 @@ class BeatSwitcher(rosabeats.rosabeats):
43
43
 
44
44
  # Initialize instance variables
45
45
  self.outfile = None
46
- self.firstfull = None
46
+ self._downbeat_arg = None
47
47
  self.fmin = None
48
48
  self.fmax = None
49
49
  self.bmin = None
50
50
  self.bmax = None
51
51
 
52
- def setup(self, outfile, firstfull):
52
+ def setup(self, outfile, downbeat=None):
53
53
  """Set up the beat recipe configuration.
54
-
54
+
55
55
  Args:
56
56
  outfile (str): Path to output beat recipe file (.br)
57
- firstfull (int): Index of first full bar
57
+ downbeat (int, optional): Beat index of first downbeat (auto-detected if None)
58
58
  """
59
59
  # Configure output file
60
60
  self.outfile = outfile
61
61
  self.enable_output_beats(self.outfile)
62
62
 
63
63
  # Configure beat tracking
64
- self.firstfull = firstfull
65
- self.track_beats(firstfull=firstfull)
64
+ self._downbeat_arg = downbeat
65
+ self.track_beats(downbeat=downbeat)
66
66
 
67
67
  # Disable WAV output and playback
68
68
  self.disable_output_save()
@@ -90,7 +90,7 @@ class BeatSwitcher(rosabeats.rosabeats):
90
90
  self.gen_beat_samples()
91
91
 
92
92
  # Start from first full bar
93
- curr_beat = self.firstfullbar
93
+ curr_beat = self.downbeat
94
94
  song_over = False
95
95
  direction = "r" # Start with reverse direction
96
96
 
@@ -215,8 +215,12 @@ def parse_args():
215
215
  help="Maximum number of backward beats"
216
216
  )
217
217
  parser.add_argument(
218
- "--firstfull", type=int, default=0,
219
- help="First full bar index"
218
+ "--downbeat", type=int, default=None,
219
+ help="Beat index of first downbeat (default: 0)"
220
+ )
221
+ parser.add_argument(
222
+ "--auto-downbeat", action="store_true",
223
+ help="Auto-detect downbeat using DBN"
220
224
  )
221
225
  parser.add_argument(
222
226
  "--debug", action="store_true",
@@ -247,7 +251,19 @@ def main():
247
251
 
248
252
  # Create and run beat switcher
249
253
  bs = BeatSwitcher(args.input_file, debug=args.debug)
250
- bs.setup(output_file, args.firstfull)
254
+
255
+ # Determine downbeat value
256
+ if args.downbeat is not None:
257
+ downbeat = args.downbeat
258
+ elif args.auto_downbeat:
259
+ # Auto-detect using DBN approach
260
+ bs.track_beats(downbeat=0)
261
+ print("Auto-detecting downbeat using DBN...")
262
+ downbeat = bs.detect_downbeat_dbn(bs.beatsperbar)
263
+ else:
264
+ downbeat = 0
265
+
266
+ bs.setup(output_file, downbeat)
251
267
  bs.set_parameters(args.fmin, args.fmax, args.bmin, args.bmax)
252
268
  bs.run()
253
269