From 6795bce6d4fbbbd4753418b90cc44b2a15b10a54 Mon Sep 17 00:00:00 2001 From: Haokun Tian <72003580+tiianhk@users.noreply.github.com> Date: Wed, 10 Sep 2025 20:56:21 +0100 Subject: [PATCH] make sample rate configurable (#6) --- Makefile | 8 +++++--- README.md | 6 +++--- src/common/synth_base.cpp | 16 ++++++++++------ src/common/synth_base.h | 1 + src/headless/bindings.cpp | 1 + tests/test_render.py | 8 ++++---- 6 files changed, 24 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index f11159e..04140a9 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ -PYTHONINCLUDEPATH := $(shell python3.10 -c "import sysconfig; print(sysconfig.get_path('include'))") +PYTHON := $(shell which python) -PYTHONLIBPATH := $(shell python3.10 -c "import sysconfig; print(sysconfig.get_config_var('LIBDIR'))") +PYTHONINCLUDEPATH := $(shell $(PYTHON) -c "import sysconfig; print(sysconfig.get_path('include'))") + +PYTHONLIBPATH := $(shell $(PYTHON) -c "import sysconfig; print(sysconfig.get_config_var('LIBDIR'))") ifndef CONFIG CONFIG=Release @@ -8,7 +10,7 @@ endif ifndef LIBDIR # LIBDIR=/usr/lib/ - LIBDIR=$(shell python3.10 -c "import sysconfig; print(sysconfig.get_config_var('LIBDIR'))") + LIBDIR=$(shell $(PYTHON) -c "import sysconfig; print(sysconfig.get_config_var('LIBDIR'))") endif BUILD_DATE="$(shell date +'%Y %m %d %H %M')" diff --git a/README.md b/README.md index 1a28d88..0437edb 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,7 @@ pip install vita from scipy.io import wavfile import vita -SAMPLE_RATE = 44_100 - +sample_rate = 44100 bpm = 120.0 note_dur = 1.0 render_dur = 3.0 @@ -27,6 +26,7 @@ velocity = 0.7 # [0.0 to 1.0] synth = vita.Synth() # The initial preset is loaded by default. +synth.set_sample_rate(sample_rate) synth.set_bpm(bpm) # Let's make a custom modulation using @@ -57,7 +57,7 @@ print(f"Current: {synth.get_control_text('delay_style')}") # e.g., "Stereo" # Render audio to numpy array shaped (2, NUM_SAMPLES) audio = synth.render(pitch, velocity, note_dur, render_dur) -wavfile.write("generated_preset.wav", SAMPLE_RATE, audio.T) +wavfile.write("generated_preset.wav", sample_rate, audio.T) # Dump current state to JSON text preset_path = "generated_preset.vital" diff --git a/src/common/synth_base.cpp b/src/common/synth_base.cpp index 60d1c62..a48e233 100644 --- a/src/common/synth_base.cpp +++ b/src/common/synth_base.cpp @@ -448,8 +448,13 @@ void SynthBase::pySetBPM(float bpm) { engine_->setBpm(bpm); }; +// src/plugin/synth_plugin.cpp Line 129-133 prepareToPlay +void SynthBase::setSampleRate(double sample_rate) { + engine_->setSampleRate(sample_rate); + midi_manager_->setSampleRate(sample_rate); +} + void SynthBase::renderAudioToFile(File file, std::vector notes, float velocity, float note_dur, float render_dur, bool render_images) { - static constexpr int kSampleRate = 44100; static constexpr int kPreProcessSamples = 44100; static constexpr int kFadeSamples = 200; static constexpr int kBufferSize = 64; @@ -464,11 +469,11 @@ void SynthBase::renderAudioToFile(File file, std::vector notes, float veloc engine_->allSoundsOff(); // note: dbraun added this processModulationChanges(); - engine_->setSampleRate(kSampleRate); // engine_->setBpm(bpm); engine_->updateAllModulationSwitches(); + int kSampleRate = getSampleRate(); - double sample_time = 1.0 / getSampleRate(); + double sample_time = 1.0 / kSampleRate; double current_time = -kPreProcessSamples * sample_time; for (int samples = 0; samples < kPreProcessSamples; samples += kBufferSize) { @@ -575,7 +580,6 @@ void SynthBase::renderAudioToFile(File file, std::vector notes, float veloc } nb::ndarray, nb::numpy> SynthBase::renderAudioToNumpy(const int& midi_note, float velocity, float note_dur, float render_dur) { - static constexpr int kSampleRate = 44100; static constexpr int kFadeSamples = 200; static constexpr int kBufferSize = 64; static constexpr int kPreProcessSamples = 256; // note: dbraun decreased this from 44100. @@ -588,11 +592,11 @@ nb::ndarray, nb::numpy> SynthBase::renderAudioToNumpy(co engine_->allSoundsOff(); // note: dbraun added this processModulationChanges(); - engine_->setSampleRate(kSampleRate); engine_->updateAllModulationSwitches(); + int kSampleRate = getSampleRate(); // Preprocess modulation - double sample_time = 1.0 / getSampleRate(); + double sample_time = 1.0 / kSampleRate; double current_time = -kPreProcessSamples * sample_time; for (int samples = 0; samples < kPreProcessSamples; samples += kBufferSize) { diff --git a/src/common/synth_base.h b/src/common/synth_base.h index 5c4fe83..bf5f7eb 100644 --- a/src/common/synth_base.h +++ b/src/common/synth_base.h @@ -139,6 +139,7 @@ class SynthBase : public MidiManager::Listener { Tuning* getTuning() { return &tuning_; } void pySetBPM(float bpm); + void setSampleRate(double sample_rate); struct ValueChangedCallback : public CallbackMessage { ValueChangedCallback(std::shared_ptr listener, std::string name, vital::mono_float val) : diff --git a/src/headless/bindings.cpp b/src/headless/bindings.cpp index 2bb599c..019bf55 100644 --- a/src/headless/bindings.cpp +++ b/src/headless/bindings.cpp @@ -572,6 +572,7 @@ NB_MODULE(vita, m) { "Disconnects a modulation source from a destination by name.") .def("set_bpm", &HeadlessSynth::pySetBPM, nb::arg("bpm")) + .def("set_sample_rate", &HeadlessSynth::setSampleRate, nb::arg("sample_rate")) .def("render_file", &HeadlessSynth::renderAudioToFile2, nb::arg("output_path"), nb::arg("midi_note"), diff --git a/tests/test_render.py b/tests/test_render.py index 4ad59aa..d75096c 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -20,15 +20,14 @@ from vita.constants import ( SyncedFrequency, ) -SAMPLE_RATE = 44_100 - -def test_render(bpm=120.0, note_dur=1.0, render_dur=3.0, pitch=36, velocity=0.7): +def test_render(bpm=120.0, sample_rate=48000, note_dur=1.0, render_dur=3.0, pitch=36, velocity=0.7): synth = vita.Synth() # The initial preset is laoded by default. synth.set_bpm(bpm) + synth.set_sample_rate(sample_rate) assert vita.get_modulation_sources() assert vita.get_modulation_destinations() @@ -43,8 +42,9 @@ def test_render(bpm=120.0, note_dur=1.0, render_dur=3.0, pitch=36, velocity=0.7) # Render audio to numpy array shaped (2, NUM_SAMPLES) audio = synth.render(pitch, velocity, note_dur, render_dur) + assert sample_rate == int(audio.shape[1] / render_dur) # assume int - wavfile.write("generated_preset.wav", SAMPLE_RATE, audio.T) + wavfile.write("generated_preset.wav", sample_rate, audio.T) # Dump current state to json text json_text = synth.to_json()