diff --git a/README.md b/README.md
index e819285..e7284ac 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
-# BeatIt
-
[](LICENSE)
[](#build)
[](#tests)
+

BeatIt
+
Machine Learning assisted Beat and Downbeat Tracking Framework for macOS.
## Core Values
@@ -44,6 +44,30 @@ Several canonical files are currently exact or perceptually exact in tempo, drif
phase after BeatIt postprocessing, while the same material was visibly worse with the model
output alone.
+### Flexibility
+
+BeatIt is intentionally not locked to one inference stack. The project currently supports two
+entirely different backend families:
+
+- CoreML for the native macOS path
+- Torch for the PyTorch model path
+
+Those backends are loaded through plugins rather than hard-linked into one monolithic binary.
+That matters in practice:
+
+- the default CoreML path stays lightweight
+- the heavy Torch runtime closure, easily well above `200 MB`, remains optional
+- the project can ship one product while still tracking multiple model ecosystems
+
+This is not just a packaging detail. It is a deliberate architectural choice:
+
+- BeatIt can stay fast and native on the default macOS path
+- while still being able to adopt new model exports, new research code, or new runtime stacks
+ when machine-learning approaches to tempo, beat, and downbeat detection move forward
+
+That flexibility is what lets BeatIt combine a strongly optimized native path with the ability
+to follow the latest model trends instead of freezing the project around one runtime forever.
+
## Features
### Sparse Selection
@@ -214,16 +238,6 @@ available as an optional backend.
### CLI
-#### Defaults
-
-If you run the CLI without model switches, BeatIt uses:
-
-- backend: CoreML
-- preset: `beatthis`
-- BPM range clamp: `70..180`
-- DBN: enabled (calmdad mode)
-- sparse probe mode: enabled
-
Basic:
```bash
@@ -250,6 +264,7 @@ Important options:
- `--preset `
- `--device `
- `--model `
+- `--dump-events`
- `--min-bpm ` / `--max-bpm ` (validated in `[70,180]`)
- `--dbn` / `--no-dbn`
- `--log-level `
diff --git a/scripts/package_macos_pkg.sh b/scripts/package_macos_pkg.sh
index bcdb506..44cf2e8 100755
--- a/scripts/package_macos_pkg.sh
+++ b/scripts/package_macos_pkg.sh
@@ -20,42 +20,70 @@ INSTALLER_SIGN_KEYCHAIN="${INSTALLER_SIGN_KEYCHAIN:-}"
UNSIGNED_PKG_PATH="${DIST_DIR}/unsigned-${PKG_NAME}"
RUNTIME_PAYLOAD_ROOT="$DIST_DIR/pkgroot-runtime"
+COREML_PAYLOAD_ROOT="$DIST_DIR/pkgroot-coreml"
+TORCH_PAYLOAD_ROOT="$DIST_DIR/pkgroot-torch"
COMPONENT_DIR="$DIST_DIR/pkg-components"
-RUNTIME_PKG_PATH="$COMPONENT_DIR/beatit-runtime.pkg"
+RUNTIME_PKG_PATH="$COMPONENT_DIR/beatit-cli.pkg"
+COREML_PKG_PATH="$COMPONENT_DIR/beatit-coreml-runtime.pkg"
+TORCH_PKG_PATH="$COMPONENT_DIR/beatit-torch-runtime.pkg"
FRAMEWORK_PKG_PATH="$COMPONENT_DIR/beatit-framework.pkg"
DISTRIBUTION_XML="$DIST_DIR/distribution.xml"
RESOURCE_DIR="$DIST_DIR/pkg-resources"
mkdir -p "$DIST_DIR"
-rm -rf "$RUNTIME_PAYLOAD_ROOT" "$COMPONENT_DIR" "$RESOURCE_DIR"
+rm -rf "$RUNTIME_PAYLOAD_ROOT" "$COREML_PAYLOAD_ROOT" "$TORCH_PAYLOAD_ROOT" "$COMPONENT_DIR" "$RESOURCE_DIR"
rm -f "$PKG_PATH" "$UNSIGNED_PKG_PATH" "$DISTRIBUTION_XML"
mkdir -p "$COMPONENT_DIR" "$RESOURCE_DIR"
mkdir -p "$RUNTIME_PAYLOAD_ROOT/usr/local/bin"
-mkdir -p "$RUNTIME_PAYLOAD_ROOT/usr/local/lib/beatit"
mkdir -p "$RUNTIME_PAYLOAD_ROOT/usr/local/share/beatit/models"
+mkdir -p "$COREML_PAYLOAD_ROOT/usr/local/lib/beatit"
+mkdir -p "$COREML_PAYLOAD_ROOT/usr/local/share/beatit/models"
+mkdir -p "$TORCH_PAYLOAD_ROOT/usr/local/lib/beatit"
+mkdir -p "$TORCH_PAYLOAD_ROOT/usr/local/share/beatit/models"
cp "$BUILD_DIR/beatit" "$RUNTIME_PAYLOAD_ROOT/usr/local/bin/beatit"
cp "$ROOT/README.md" "$RUNTIME_PAYLOAD_ROOT/usr/local/share/beatit/README.md"
cp "$ROOT/LICENSE" "$RUNTIME_PAYLOAD_ROOT/usr/local/share/beatit/LICENSE"
-if [[ -d "$BUILD_DIR/plugins" ]]; then
- find "$BUILD_DIR/plugins" -maxdepth 1 -type f -name '*.dylib' -print0 | while IFS= read -r -d '' dylib; do
- cp "$dylib" "$RUNTIME_PAYLOAD_ROOT/usr/local/lib/beatit/"
- done
+COREML_PLUGIN_PATH="$BUILD_DIR/plugins/libbeatit_backend_coreml.dylib"
+TORCH_PLUGIN_PATH="$BUILD_DIR/plugins/libbeatit_backend_torch.dylib"
+
+if [[ -f "$COREML_PLUGIN_PATH" ]]; then
+ cp "$COREML_PLUGIN_PATH" "$COREML_PAYLOAD_ROOT/usr/local/lib/beatit/"
fi
cp -R "$ROOT/models/BeatThis_small0.mlpackage" \
- "$RUNTIME_PAYLOAD_ROOT/usr/local/share/beatit/models/BeatThis_small0.mlpackage"
+ "$COREML_PAYLOAD_ROOT/usr/local/share/beatit/models/BeatThis_small0.mlpackage"
+
+if [[ -f "$TORCH_PLUGIN_PATH" ]]; then
+ find "$BUILD_DIR/plugins" -maxdepth 1 -type f -name '*.dylib' ! -name 'libbeatit_backend_coreml.dylib' -print0 | while IFS= read -r -d '' dylib; do
+ cp "$dylib" "$TORCH_PAYLOAD_ROOT/usr/local/lib/beatit/"
+ done
+fi
if [[ -f "$ROOT/models/BeatThis_small0.pt" ]]; then
cp "$ROOT/models/BeatThis_small0.pt" \
- "$RUNTIME_PAYLOAD_ROOT/usr/local/share/beatit/models/BeatThis_small0.pt"
+ "$TORCH_PAYLOAD_ROOT/usr/local/share/beatit/models/BeatThis_small0.pt"
fi
RUNTIME_PKGBUILD_ARGS=(
--root "$RUNTIME_PAYLOAD_ROOT"
- --identifier "com.tilltoenshoff.beatit.runtime"
+ --identifier "com.tilltoenshoff.beatit.cli"
+ --version "$VERSION"
+ --install-location "/"
+)
+
+COREML_PKGBUILD_ARGS=(
+ --root "$COREML_PAYLOAD_ROOT"
+ --identifier "com.tilltoenshoff.beatit.coreml"
+ --version "$VERSION"
+ --install-location "/"
+)
+
+TORCH_PKGBUILD_ARGS=(
+ --root "$TORCH_PAYLOAD_ROOT"
+ --identifier "com.tilltoenshoff.beatit.torch"
--version "$VERSION"
--install-location "/"
)
@@ -67,38 +95,102 @@ FRAMEWORK_PKGBUILD_ARGS=(
--install-location "/Library/Frameworks"
)
-echo "Building runtime component package: $RUNTIME_PKG_PATH"
+echo "Building CLI component package: $RUNTIME_PKG_PATH"
pkgbuild "${RUNTIME_PKGBUILD_ARGS[@]}" "$RUNTIME_PKG_PATH"
+echo "Building CoreML runtime package: $COREML_PKG_PATH"
+pkgbuild "${COREML_PKGBUILD_ARGS[@]}" "$COREML_PKG_PATH"
+
+HAVE_TORCH_RUNTIME=0
+if [[ -f "$TORCH_PLUGIN_PATH" && -f "$ROOT/models/BeatThis_small0.pt" ]]; then
+ echo "Building Torch runtime package: $TORCH_PKG_PATH"
+ pkgbuild "${TORCH_PKGBUILD_ARGS[@]}" "$TORCH_PKG_PATH"
+ HAVE_TORCH_RUNTIME=1
+fi
+
echo "Building framework component package: $FRAMEWORK_PKG_PATH"
pkgbuild "${FRAMEWORK_PKGBUILD_ARGS[@]}" "$FRAMEWORK_PKG_PATH"
-cp "$ROOT/README.md" "$RESOURCE_DIR/README.txt"
cp "$ROOT/LICENSE" "$RESOURCE_DIR/LICENSE.txt"
+cat > "$RESOURCE_DIR/WELCOME.txt" <<'TEXT'
+Installs the BeatIt command-line tool, optional CoreML and Torch runtime
+support for the CLI, and the BeatIt framework.
+
+Install locations:
+- /usr/local/bin/beatit
+- /usr/local/lib/beatit
+- /usr/local/share/beatit
+- /Library/Frameworks/BeatIt.framework
+
+The overall space requirement of the installation is around a crazy 500mb due to bundled model assets and runtime.
+
+Use the installer to deploy the runtime and framework on this Mac.
+TEXT
+cat > "$RESOURCE_DIR/README.txt" <<'TEXT'
+BeatIt
+
+After installation, you can run BeatIt from Terminal:
+
+ beatit -i /path/to/song.mp3
-cat > "$DISTRIBUTION_XML" <<'XML'
+Installer choices:
+
+- BeatIt CLI
+- BeatIt CoreML Runtime Support
+- BeatIt Torch Runtime Support
+- BeatIt Framework
+
+The framework is self-contained. The CLI needs at least one runtime support
+package to run actual model inference.
+
+The full project documentation and license are installed here:
+
+- /usr/local/share/beatit/README.md
+- /usr/local/share/beatit/LICENSE
+TEXT
+
+cat > "$DISTRIBUTION_XML" <
BeatIt
-
+
-
+
-
+
+
+$(if [[ $HAVE_TORCH_RUNTIME -eq 1 ]]; then cat <<'CHOICE'
+
+CHOICE
+fi)
-
-
-
+
+
+
+
+
+
+
+$(if [[ $HAVE_TORCH_RUNTIME -eq 1 ]]; then cat <<'CHOICE'
+
+
-
+CHOICE
+fi)
+
- beatit-runtime.pkg
+ beatit-cli.pkg
+ beatit-coreml-runtime.pkg
+$(if [[ $HAVE_TORCH_RUNTIME -eq 1 ]]; then cat <<'CHOICE'
+ beatit-torch-runtime.pkg
+CHOICE
+fi)
beatit-framework.pkg
XML
diff --git a/site/images/BeatIt_Logo.png b/site/images/BeatIt_Logo.png
new file mode 100644
index 0000000..bc6e0a9
Binary files /dev/null and b/site/images/BeatIt_Logo.png differ
diff --git a/src/cli/main.mm b/src/cli/main.mm
index a36f81d..8dd6df3 100644
--- a/src/cli/main.mm
+++ b/src/cli/main.mm
@@ -921,6 +921,15 @@ int main(int argc, char** argv) {
stream << std::fixed << std::setprecision(precision) << value;
return stream.str();
};
+ const auto append_event_position = [&](std::ostream& stream,
+ unsigned long long sample_frame) {
+ double seconds = 0.0;
+ if (audio.sample_rate > 0.0) {
+ seconds = static_cast(sample_frame) / audio.sample_rate;
+ }
+ stream << "time=" << format_fixed(seconds, 3)
+ << " sec sample_frame=" << sample_frame;
+ };
if (!std::isfinite(result.estimated_bpm) ||
result.estimated_bpm <= 0.0 ||
@@ -997,20 +1006,26 @@ int main(int argc, char** argv) {
}
if (options.dump_events) {
- std::cout << "Events (first 64):\n";
- const std::size_t max_beats = std::min(64, beat_feature_frames.size());
- for (std::size_t i = 0; i < max_beats; ++i) {
+ std::cout << "type,time_sec,sample_frame,activation\n";
+ for (std::size_t i = 0; i < beat_feature_frames.size(); ++i) {
const bool is_downbeat =
std::find(downbeat_feature_frames.begin(),
downbeat_feature_frames.end(),
beat_feature_frames[i]) != downbeat_feature_frames.end();
- std::cout << (is_downbeat ? "* " : " ")
- << "feature_frame=" << beat_feature_frames[i];
+ std::cout << (is_downbeat ? "downbeat" : "beat") << ",";
if (i < beat_sample_frames.size()) {
- std::cout << " sample_frame=" << beat_sample_frames[i];
+ double seconds = 0.0;
+ if (audio.sample_rate > 0.0) {
+ seconds = static_cast(beat_sample_frames[i]) / audio.sample_rate;
+ }
+ std::cout << format_fixed(seconds, 3) << "," << beat_sample_frames[i];
+ } else {
+ std::cout << "n/a,n/a";
}
if (i < result.coreml_beat_strengths.size()) {
- std::cout << " strength=" << result.coreml_beat_strengths[i];
+ std::cout << "," << result.coreml_beat_strengths[i];
+ } else {
+ std::cout << ",";
}
std::cout << "\n";
}
@@ -1198,11 +1213,9 @@ int main(int argc, char** argv) {
std::find(refined.downbeat_feature_frames.begin(),
refined.downbeat_feature_frames.end(),
refined.beat_feature_frames[i]) != refined.downbeat_feature_frames.end();
- std::cout << (is_downbeat ? "* " : " ")
- << "feature_frame=" << refined.beat_feature_frames[i]
- << " sample_frame=" << refined.beat_sample_frames[i]
- << " strength=" << refined.beat_strengths[i]
- << "\n";
+ std::cout << (is_downbeat ? "* " : " ");
+ append_event_position(std::cout, refined.beat_sample_frames[i]);
+ std::cout << " strength=" << refined.beat_strengths[i] << "\n";
}
if (refined.beat_sample_frames.size() > 1) {
print_bpm_stats(refined.beat_sample_frames, "Constant");