diff --git a/CMakeLists.txt b/CMakeLists.txt index e96e93d..a6aeaed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,9 +20,9 @@ endif() add_subdirectory("${JUCE_ROOT}" juce_build) if(APPLE) - set(SM26_FORMATS AU VST3) + set(SM26_FORMATS Standalone AU VST3) elseif(UNIX) - set(SM26_FORMATS VST3) + set(SM26_FORMATS Standalone VST3) else() set(SM26_FORMATS VST3) endif() @@ -68,6 +68,8 @@ target_compile_definitions(SM-26 PRIVATE juce_generate_juce_header(SM-26) +target_link_libraries (SM-26 PRIVATE juce::juce_audio_utils juce::juce_audio_devices) + add_custom_target(gen_buildinfo ALL COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tools/gen_buildinfo.sh" "${CMAKE_CURRENT_SOURCE_DIR}" diff --git a/Makefile b/Makefile index ddd6490..a840148 100644 --- a/Makefile +++ b/Makefile @@ -83,6 +83,10 @@ install: build if [ -n "$$VST3" ]; then rm -rf "$(DEST_VST3)"; cp -R "$$VST3" "$(HOME)/.vst3/"; else echo "!! VST3 not found in build dir"; exit 1; fi; \ fi @echo ">> SM-26 installed." + @echo "" + @echo ">> To make newly-installed AUs visible to macOS immediately, run:" + @echo ">> sudo killall -HUP coreaudiod" + @echo ">> (or simply restart your audio host / DAW)" uninstall: @if [ "$(UNAME_S)" = "Darwin" ]; then \ diff --git a/README.md b/README.md new file mode 100644 index 0000000..c6a8896 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# SM-26 + +A simple subtractive synthesizer plugin built with JUCE / C++. + +* **VST3** + **Audio Unit** (macOS arm64), **VST3** (Linux) +* 4 rotary knobs on the front panel whose meaning follows the selected preset +* 52 factory presets (Bass / Lead / Pad / Keys / Pluck / FX / Stab / Arp) +* tempo-synced delay (follows the host transport / BPM) +* 4x80 HD44780-style pixel display with a cascaded on-screen menu +* left/right/up/down navigation diamond + ENTER / ESC controls the menu +* status footer with the git revision and local build time + +## Build + +Requires a C++17 compiler, CMake >= 3.22 and (on Linux) X11/ALSA dev headers. + +```sh +make # clones JUCE (once) into deps/ and builds +make install # installs + ad-hoc signs + un-quarantines +make uninstall # removes the installed plugins +make distclean # wipes build/ +``` + +* macOS installs to `~/Library/Audio/Plug-Ins/` (AU + VST3). +* Linux installs to `~/.vst3/`. + +If a freshly installed AU is not visible to `auval` / your DAW: + +```sh +sudo killall -HUP coreaudiod +``` + +## Usage + +* Ship MIDI into the plugin; the 4 front-panel knobs drive whichever + parameters the current preset maps them to (e.g. cutoff/resonance/ + envelope/delay, but many other routings exist). +* **Nav diamond + ENTER/ESC** drive the on-screen menu: + * root menu -> PRESETS / OSCILLATORS / FILTER / ENVELOPE / DELAY / SYSTEM + * up/down = move, left/right = adjust value, ENTER = open/confirm, ESC = back +* The LCD shows the current preset, the four knob mappings with live values, + host BPM and voice count. The delay can be set to a rhythmic subdivision + (SYNC) and locks to the host tempo. + +## Layout + +``` +Build: Makefile -> CMake -> JUCE (deps/juce) +Source/ + PluginProcessor.* audio processor + DSP shell + SynthEngine.* osc/filter/ADSR/delay voice engine + Presets.* 52 preset definitions + knob routings + SynthParams.* parameter model + formatting + PluginEditor.* panel, knobs, nav diamond, footer + LcdDisplay.* 4x80 HD44780-style display renderer + MenuSystem.* cascaded menu state machine +tools/gen_buildinfo.sh generates git-version + build-time header +``` \ No newline at end of file diff --git a/Source/SynthEngine.cpp b/Source/SynthEngine.cpp index 91f83ee..0755e64 100644 --- a/Source/SynthEngine.cpp +++ b/Source/SynthEngine.cpp @@ -42,6 +42,7 @@ void SynthEngine::reset() void SynthEngine::loadPreset (const Preset& p) { + juce::SpinLock::ScopedLockType lock (paramLock); for (int i = 0; i < kNumParams; ++i) params[i] = p.base[i]; for (int i = 0; i < kNumKnobs; ++i) @@ -63,6 +64,7 @@ void SynthEngine::setPresetIndex (int i) void SynthEngine::setParam (int id, float n01) { if (id < 0 || id >= kNumParams) return; + juce::SpinLock::ScopedLockType lock (paramLock); params[id] = juce::jlimit (0.0f, 1.0f, n01); } @@ -79,6 +81,8 @@ SynthEngine::KnobView SynthEngine::knobView (int knob) const k.name = knobNames[knob]; k.param = knobParams[knob]; k.value01 = getParam (k.param); + k.lo = knobLows[knob]; + k.hi = knobHighs[knob]; return k; } @@ -192,6 +196,14 @@ void SynthEngine::process (AudioBuffer& buffer, MidiBuffer& midi, wasPlaying = transportPlaying; lastBpmValue = (bpm > 10.0 && bpm < 300.0) ? bpm : 120.0; + std::array snap; + { + juce::SpinLock::ScopedLockType lock (paramLock); + for (int i = 0; i < kNumParams; ++i) + snap[(size_t) i] = params[i]; + } + const float* const params = snap.data(); + const float cutN01 = params[PCUTOFF]; const float resN01 = params[PRES]; const float fenvN01 = params[PFENV];