From 473c47c009e5037ee256c9a29ce9afd5f01f102a Mon Sep 17 00:00:00 2001 From: kagersophia <159161703+kagersophia@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:39:48 -0600 Subject: [PATCH] Render file & Updated config & Updated Libraries Created render file to allow for time scaling and is able to preserve pitch. Updated CLI to account for time scaling changes. Updated libraries to add pydub, librosa, and soundfile (pyproject.toml) --- src/amplify/cli.py | 4 ++- src/amplify/pyproject.toml | 25 ++++++++++++++++ src/amplify/render.py | 60 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/amplify/pyproject.toml create mode 100644 src/amplify/render.py diff --git a/src/amplify/cli.py b/src/amplify/cli.py index cd572bc..7517a36 100644 --- a/src/amplify/cli.py +++ b/src/amplify/cli.py @@ -1,6 +1,7 @@ import typer from pathlib import Path from amplify.config import load_cfg, save_cfg, ensure_cfg +from amplify.render import export as render_export app = typer.Typer(add_completion=False) @@ -85,7 +86,8 @@ def export(cfg: str, out: str): data = load_cfg(cfg) data["export"]["path"] = out save_cfg(cfg, data) - typer.echo(f"Would export to {out} (render engine not connected).") + render_export(data) + typer.echo(f"Exported to {out}") def main(): app() diff --git a/src/amplify/pyproject.toml b/src/amplify/pyproject.toml new file mode 100644 index 0000000..612c032 --- /dev/null +++ b/src/amplify/pyproject.toml @@ -0,0 +1,25 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "amplify" +version = "0.1.0" +description = "CLI audio manipulation tool" +requires-python = ">=3.10" +dependencies = [ + "typer>=0.12", + "pyyaml>=6.0", + "pydub>=0.25.1", + "librosa>=0.10.1", + "soundfile>=0.12.1" +] + +[project.scripts] +amplify = "amplify.cli:main" + +[tool.setuptools] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] diff --git a/src/amplify/render.py b/src/amplify/render.py new file mode 100644 index 0000000..3346a8c --- /dev/null +++ b/src/amplify/render.py @@ -0,0 +1,60 @@ +import numpy as np +import librosa +import soundfile as sf +from pathlib import Path + +def apply_scale(audio, sr, factor, preserve_pitch): + """ + Time-scale audio + Scale factor > 1.0 = faster audio + Scale factor < 1.0 = slower audio + """ + + # if preserving pitch, change duration but keep pitch the same + if preserve_pitch: + return librosa.effects.time_stretch(audio, rate=factor) + + # if not preserving pitch, change sample rate by a factor + else: + new_sr = int(sr * factor) + return librosa.resample(audio, orig_sr = sr, target_sr = new_sr) + +def render(cfg_data): + + # stores tracks + mix_buffer = [] + + # stores sample rate, set with first audio file in track + sample_rate = None + + for item in cfg_data["timeline"]: + # get asset id of current item + asset_id = item["asset"] + + # finds next asset matching id + asset = next(a for a in cfg_data["assets"] if a["id"] == asset_id) + audio, file_sr = librosa.load(asset["path"], sr = None) + + if sample_rate is None: + sample_rate = file_sr + + for op in item["ops"]: + if op["type"] == "scale": + audio = apply_scale(audio, file_sr, op["factor"], op["preserve_pitch"]) + elif op["type"] == "loop": + if op["count"]: + audio = np.tile(audio, op["count"]) + mix_buffer.append(audio) + output = np.sum(mix_buffer, axis = 0) + + if cfg_data["mix"]["normalize"]: + peak = np.max(np.abs(output)) + if peak > 0: + output = output / peak + + return output, sample_rate + +def export(cfg_data): + audio, sr = render(cfg_data) + out_path = Path(cfg_data["export"]["path"]) + sf.write(out_path, audio, sr) \ No newline at end of file