#include #include #include #include #include #include #include #include "compressor.h" #include "processor_router.h" #include "random_lfo.h" #include "sound_engine.h" #include "synth_base.h" #include "synth_filter.h" #include "synth_lfo.h" #include "synth_oscillator.h" #include "value.h" #include "voice_handler.h" #include "wave_frame.h" namespace nb = nanobind; using namespace vital; class ModulationDestinationListCache { private: static std::vector cached_list; static bool initialized; static void initialize() { if (!initialized) { SoundEngine engine; vital::input_map &mono_destination_map = engine.getMonoModulationDestinations(); for (auto &destination_iter : mono_destination_map) { cached_list.push_back(destination_iter.first); } initialized = true; } } public: static const std::vector &get() { initialize(); return cached_list; } }; class ModulationSourceListCache { private: static std::vector cached_list; static bool initialized; static void initialize() { if (!initialized) { SoundEngine engine; vital::output_map &source_map = engine.getModulationSources(); for (auto &source_iter : source_map) { cached_list.push_back(source_iter.first); } initialized = true; } } public: static const std::vector &get() { initialize(); return cached_list; } }; // Initialize static members std::vector ModulationDestinationListCache::cached_list; bool ModulationDestinationListCache::initialized = false; std::vector ModulationSourceListCache::cached_list; bool ModulationSourceListCache::initialized = false; nb::list get_modulation_destinations() { const auto &cpp_list = ModulationDestinationListCache::get(); nb::list result; for (const auto &str : cpp_list) { result.append(str); } return result; } nb::list get_modulation_sources() { const auto &cpp_list = ModulationSourceListCache::get(); nb::list result; for (const auto &str : cpp_list) { result.append(str); } return result; } NB_MODULE(vita, m) { m.def("get_modulation_sources", &get_modulation_sources, "Returns a list of allowed modulation sources."); m.def("get_modulation_destinations", &get_modulation_destinations, "Returns a list of allowed modulation destinations"); auto m_constants = m.def_submodule("constants", "Submodule containing constants and enums"); // Expose Enums nb::enum_(m_constants, "SourceDestination") .value("Filter1", constants::SourceDestination::kFilter1) .value("Filter2", constants::SourceDestination::kFilter2) .value("DualFilters", constants::SourceDestination::kDualFilters) .value("Effects", constants::SourceDestination::kEffects) .value("DirectOut", constants::SourceDestination::kDirectOut) .def("__int__", [](constants::SourceDestination self) { return static_cast(self); }); nb::enum_(m_constants, "Effect") .value("Chorus", constants::Effect::kChorus) .value("Compressor", constants::Effect::kCompressor) .value("Delay", constants::Effect::kDelay) .value("Distortion", constants::Effect::kDistortion) .value("Eq", constants::Effect::kEq) .value("FilterFx", constants::Effect::kFilterFx) .value("Flanger", constants::Effect::kFlanger) .value("Phaser", constants::Effect::kPhaser) .value("Reverb", constants::Effect::kReverb) .def("__int__", [](constants::Effect self) { return static_cast(self); }); nb::enum_(m_constants, "FilterModel") .value("Analog", constants::FilterModel::kAnalog) .value("Dirty", constants::FilterModel::kDirty) .value("Ladder", constants::FilterModel::kLadder) .value("Digital", constants::FilterModel::kDigital) .value("Diode", constants::FilterModel::kDiode) .value("Formant", constants::FilterModel::kFormant) .value("Comb", constants::FilterModel::kComb) .value("Phase", constants::FilterModel::kPhase) .def("__int__", [](constants::FilterModel self) { return static_cast(self); }); nb::enum_(m_constants, "RetriggerStyle") .value("Free", constants::RetriggerStyle::kFree) .value("Retrigger", constants::RetriggerStyle::kRetrigger) .value("SyncToPlayHead", constants::RetriggerStyle::kSyncToPlayHead) .def("__int__", [](constants::RetriggerStyle self) { return static_cast(self); }); nb::enum_(m_constants, "SpectralMorph") .value("NoSpectralMorph", SynthOscillator::SpectralMorph::kNoSpectralMorph) .value("Vocode", SynthOscillator::SpectralMorph::kVocode) .value("FormScale", SynthOscillator::SpectralMorph::kFormScale) .value("HarmonicScale", SynthOscillator::SpectralMorph::kHarmonicScale) .value("InharmonicScale", SynthOscillator::SpectralMorph::kInharmonicScale) .value("Smear", SynthOscillator::SpectralMorph::kSmear) .value("RandomAmplitudes", SynthOscillator::SpectralMorph::kRandomAmplitudes) .value("LowPass", SynthOscillator::SpectralMorph::kLowPass) .value("HighPass", SynthOscillator::SpectralMorph::kHighPass) .value("PhaseDisperse", SynthOscillator::SpectralMorph::kPhaseDisperse) .value("ShepardTone", SynthOscillator::SpectralMorph::kShepardTone) .value("Skew", SynthOscillator::SpectralMorph::kSkew) .def("__int__", [](SynthOscillator::SpectralMorph self) { return static_cast(self); }); nb::enum_(m_constants, "DistortionType") .value("None", SynthOscillator::DistortionType::kNone) .value("Sync", SynthOscillator::DistortionType::kSync) .value("Formant", SynthOscillator::DistortionType::kFormant) .value("Quantize", SynthOscillator::DistortionType::kQuantize) .value("Bend", SynthOscillator::DistortionType::kBend) .value("Squeeze", SynthOscillator::DistortionType::kSqueeze) .value("PulseWidth", SynthOscillator::DistortionType::kPulseWidth) .value("FmOscillatorA", SynthOscillator::DistortionType::kFmOscillatorA) .value("FmOscillatorB", SynthOscillator::DistortionType::kFmOscillatorB) .value("FmSample", SynthOscillator::DistortionType::kFmSample) .value("RmOscillatorA", SynthOscillator::DistortionType::kRmOscillatorA) .value("RmOscillatorB", SynthOscillator::DistortionType::kRmOscillatorB) .value("RmSample", SynthOscillator::DistortionType::kRmSample) .def("__int__", [](SynthOscillator::DistortionType self) { return static_cast(self); }); nb::enum_(m_constants, "UnisonStackType") .value("Normal", SynthOscillator::UnisonStackType::kNormal) .value("CenterDropOctave", SynthOscillator::UnisonStackType::kCenterDropOctave) .value("CenterDropOctave2", SynthOscillator::UnisonStackType::kCenterDropOctave2) .value("Octave", SynthOscillator::UnisonStackType::kOctave) .value("Octave2", SynthOscillator::UnisonStackType::kOctave2) .value("PowerChord", SynthOscillator::UnisonStackType::kPowerChord) .value("PowerChord2", SynthOscillator::UnisonStackType::kPowerChord2) .value("MajorChord", SynthOscillator::UnisonStackType::kMajorChord) .value("MinorChord", SynthOscillator::UnisonStackType::kMinorChord) .value("HarmonicSeries", SynthOscillator::UnisonStackType::kHarmonicSeries) .value("OddHarmonicSeries", SynthOscillator::UnisonStackType::kOddHarmonicSeries) .def("__int__", [](SynthOscillator::UnisonStackType self) { return static_cast(self); }); nb::enum_(m_constants, "RandomLFOStyle") .value("Perlin", RandomLfo::RandomType::kPerlin) .value("SampleAndHold", RandomLfo::RandomType::kSampleAndHold) .value("SinInterpolate", RandomLfo::RandomType::kSinInterpolate) .value("LorenzAttractor", RandomLfo::RandomType::kLorenzAttractor) .def("__int__", [](RandomLfo::RandomType self) { return static_cast(self); }); nb::enum_(m_constants, "VoicePriority") .value("Newest", VoiceHandler::VoicePriority::kNewest) .value("Oldest", VoiceHandler::VoicePriority::kOldest) .value("Highest", VoiceHandler::VoicePriority::kHighest) .value("Lowest", VoiceHandler::VoicePriority::kLowest) .value("RoundRobin", VoiceHandler::VoicePriority::kRoundRobin) .def("__int__", [](VoiceHandler::VoicePriority self) { return static_cast(self); }); nb::enum_(m_constants, "VoiceOverride") .value("Kill",VoiceHandler::VoiceOverride::kKill) .value("Steal", VoiceHandler::VoiceOverride::kSteal) .def("__int__", [](VoiceHandler::VoiceOverride self) { return static_cast(self); }); nb::enum_(m_constants, "WaveShape") .value("Sin", PredefinedWaveFrames::kSin) .value("SaturatedSin", PredefinedWaveFrames::kSaturatedSin) .value("Triangle", PredefinedWaveFrames::kTriangle) .value("Square", PredefinedWaveFrames::kSquare) .value("Pulse", PredefinedWaveFrames::kPulse) .value("Saw", PredefinedWaveFrames::kSaw) .def("__int__", [](PredefinedWaveFrames::Shape self) { return static_cast(self); }); nb::enum_(m_constants, "SynthLFOSyncType") .value("Trigger", SynthLfo::SyncType::kTrigger) .value("Sync", SynthLfo::SyncType::kSync) .value("Envelope", SynthLfo::SyncType::kEnvelope) .value("SustainEnvelope", SynthLfo::SyncType::kSustainEnvelope) .value("LoopPoint", SynthLfo::SyncType::kLoopPoint) .value("LoopHold", SynthLfo::SyncType::kLoopHold) .def("__int__", [](SynthLfo::SyncType self) { return static_cast(self); }); nb::enum_(m_constants, "CompressorBandOption") .value("Multiband", MultibandCompressor::BandOptions::kMultiband) .value("LowBand", MultibandCompressor::BandOptions::kLowBand) .value("HighBand", MultibandCompressor::BandOptions::kHighBand) .value("SingleBand", MultibandCompressor::BandOptions::kSingleBand) .def("__int__", [](MultibandCompressor::BandOptions self) { return static_cast(self); }); nb::enum_(m_constants, "SynthFilterStyle") .value("k12Db", SynthFilter::Style::k12Db) .value("k24Db", SynthFilter::Style::k24Db) .value("NotchPassSwap", SynthFilter::Style::kNotchPassSwap) .value("DualNotchBand", SynthFilter::Style::kDualNotchBand) .value("BandPeakNotch", SynthFilter::Style::kBandPeakNotch) .value("Shelving", SynthFilter::Style::kShelving) .def("__int__", [](SynthFilter::Style self) { return static_cast(self); }); // https://github.com/mtytel/vital/blob/636ca0ef517a4db087a6a08a6a8a5e704e21f836/src/interface/look_and_feel/synth_strings.h#L174 enum SyncedFrequencyName { k32_1, k16_1, k8_1, k4_1, k2_1, k1_1, k1_2, k1_4, k1_8, k1_16, k1_32, k1_64 }; nb::enum_(m_constants, "SyncedFrequency") .value("k32_1", SyncedFrequencyName::k32_1) .value("k16_1", SyncedFrequencyName::k16_1) .value("k8_1", SyncedFrequencyName::k8_1) .value("k4_1", SyncedFrequencyName::k4_1) .value("k2_1", SyncedFrequencyName::k2_1) .value("k1_1", SyncedFrequencyName::k1_1) .value("k1_2", SyncedFrequencyName::k1_2) .value("k1_4", SyncedFrequencyName::k1_4) .value("k1_8", SyncedFrequencyName::k1_8) .value("k1_16", SyncedFrequencyName::k1_16) .value("k1_32", SyncedFrequencyName::k1_32) .value("k1_64", SyncedFrequencyName::k1_64) .def("__int__", [](SyncedFrequencyName self) { return static_cast(self); }); // https://github.com/mtytel/vital/blob/636ca0ef517a4db087a6a08a6a8a5e704e21f836/src/synthesis/modulators/synth_lfo.h#L58C1-L65C9 enum SyncOption { kTime, kTempo, kDottedTempo, kTripletTempo, kKeytrack, }; nb::enum_(m_constants, "SynthLFOSyncOption") .value("Time", SyncOption::kTime) .value("Tempo", SyncOption::kTempo) .value("DottedTempo", SyncOption::kDottedTempo) .value("TripletTempo", SyncOption::kTripletTempo) .value("Keytrack", SyncOption::kKeytrack) .def("__int__", [](SyncOption self) { return static_cast(self); }); // Binding for poly_float nb::class_(m, "poly_float") .def(nb::init_implicit()); // Expose the ProcessorRouter class nb::class_(m, "ProcessorRouter") .def(nb::init(), nb::arg("num_inputs") = 0, nb::arg("num_outputs") = 0, nb::arg("control_rate") = false); nb::class_(m, "Value") .def(nb::init(), nb::arg("value") = 0.0f, nb::arg("control_rate") = false) .def("process", &vital::Value::process, nb::arg("num_samples")) .def("set_oversample_amount", &vital::Value::setOversampleAmount, nb::arg("oversample")) .def("value", &vital::Value::value) // Handle floating point values (both float and double) .def("set", [](vital::Value &self, double value) { self.set(poly_float(static_cast(value))); }, nb::arg("value")) // Handle enum values .def("set", [](vital::Value &self, SyncedFrequencyName value) { self.set(poly_float(static_cast(static_cast(value)))); }, nb::arg("value")) // Handle integer values .def("set", [](vital::Value &self, int value) { self.set(poly_float(static_cast(value))); }, nb::arg("value")) .def("__repr__", [](const vital::Value &v) { return ""; }); nb::class_(m, "CRValue") .def(nb::init(), nb::arg("value") = 0.0f) .def("process", &vital::cr::Value::process, nb::arg("num_samples")) // Add the same method overloads for CRValue .def("set", [](vital::cr::Value &self, double value) { self.set(poly_float(static_cast(value))); }, nb::arg("value")) .def("set", [](vital::cr::Value &self, SyncedFrequencyName value) { self.set(poly_float(static_cast(static_cast(value)))); }, nb::arg("value")) .def("set", [](vital::cr::Value &self, int value) { self.set(poly_float(static_cast(value))); }, nb::arg("value")) .def("__repr__", [](const vital::cr::Value &v) { return ""; }); // Expose the SynthBase class, specifying ProcessorRouter as its base nb::class_(m, "Synth") .def(nb::init<>()) // Ensure there's a default constructor or adjust // accordingly // Bind the first overload of connectModulation .def("connect_modulation", nb::overload_cast( &HeadlessSynth::pyConnectModulation), nb::arg("source"), nb::arg("destination"), "Connects a modulation source to a destination by name.") .def("disconnect_modulation", nb::overload_cast( &HeadlessSynth::disconnectModulation), nb::arg("source"), nb::arg("destination"), "Disconnects a modulation source from a destination by name.") .def("set_bpm", &HeadlessSynth::pySetBPM, nb::arg("bpm")) .def("render_file", &HeadlessSynth::renderAudioToFile2, nb::arg("output_path"), nb::arg("midi_note"), nb::arg("midi_velocity"), nb::arg("note_dur"), nb::arg("render_dur"), "Renders audio to a file.\n\n" "Parameters:\n" " output_path (str): Path to the output audio file.\n" " midi_note (int): MIDI note to render.\n" " midi_velocity (float): Velocity of the note [0-1].\n" " note_dur (float): Length of the note sustain in seconds.\n" " render_dur (float): Length of the audio render in seconds.\n" "\n" "Returns:\n" " bool: True if rendering was successful, False otherwise.") .def("render", &HeadlessSynth::renderAudioToNumpy, nb::arg("midi_note"), nb::arg("midi_velocity"), nb::arg("note_dur"), nb::arg("render_dur"), "Renders audio to a file.\n\n" "Parameters:\n" " midi_note (int): MIDI note to render.\n" " midi_velocity (float): Velocity of the note [0-1].\n" " note_dur (float): Length of the note sustain in seconds.\n" " render_dur (float): Length of the audio render in seconds.\n" "\n" "Returns:\n" " bool: True if rendering was successful, False otherwise.") .def("load_json", &HeadlessSynth::loadFromString, nb::arg("json")) .def("to_json", &HeadlessSynth::pyToJson) .def("load_preset", &HeadlessSynth::pyLoadFromFile, nb::arg("filepath")) .def("load_init_preset", &HeadlessSynth::loadInitPreset, "Load the initial preset.") .def("clear_modulations", &HeadlessSynth::clearModulations) .def("get_controls", &HeadlessSynth::getControls, nb::rv_policy::reference); }