Hardening pass: critical/high/memory/patch-bank fixes + VCF buffer overflow #2
@@ -0,0 +1,56 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## What this is
|
||||
|
||||
JaySynth is a subtractive/wavetable software synthesizer built as a **VST2 audio plugin** using JUCE (bundled at `sdk/juce/JUCE-3.1.1`) and the Steinberg VST2.4 SDK (`sdk/vst/vstsdk2.4`). It targets Linux (`.so`) and Windows (`.dll`) hosts. Development/testing happens against the Carla plugin host (`~/jaysynth+tal.carxp`) and Reaper.
|
||||
|
||||
## Build
|
||||
|
||||
Building is plain GNU Make, driven by two submodule-provided include files (`submodule/make/defaults.mk`, `compile.mk`, `link.mk`, pulled in via `MAKE_HOME`). There is no CMake/autotools.
|
||||
|
||||
```sh
|
||||
make # build release .so into build/linux/release/
|
||||
make CONFIG=debug # debug build
|
||||
make install # copies waves.bin + JaySynth.so into ~/.vst/jaysynth/
|
||||
make run # install, then launch Carla with the test project (~/jaysynth+tal.carxp)
|
||||
make bear # distclean + rebuild via `bear` to regenerate compile_commands.json (used by clangd)
|
||||
```
|
||||
|
||||
Windows build uses `Makefile.win` with the same `PACKAGES` structure but different tool names (`Source`, `Synth`, `Fir` targets instead of lowercase).
|
||||
|
||||
Submodules must be checked out (`git submodule update --init`) before building:
|
||||
- `submodule/make` — shared Makefile fragments (compiler flags, link rules)
|
||||
- `submodule/fir` — FIR/resampling C library used by the synth core
|
||||
|
||||
`sdk/juce` and `sdk/vst` are vendored SDKs, not submodules — they're expected to already exist at those paths.
|
||||
|
||||
There are no automated tests in this repo; verification is manual, by building, installing, and playing the plugin in a host (Carla/Reaper — see `.vscode/launch.json` for debug configs).
|
||||
|
||||
## Architecture
|
||||
|
||||
The codebase has a hard split between a **portable C DSP core** and a **C++/JUCE plugin shell**, built as separate sub-Makefiles and linked together:
|
||||
|
||||
- `src/synth/` — pure C DSP engine (no JUCE, no C++). Oscillators (`vco.c`, BLIT-based band-limited synthesis in `blit.c`), wavetables (`wavetable.c`, `mw_wave_data.c` — Waldorf Microwave/PPG-style wavetables), filter (`vcf.c`), envelopes (`env.c`), LFOs (`lfo.c`), noise (`noise.c`), and parameter scaling (`param_scale.c`). `synth_float_t` is a build-time typedef (`double` on Linux, `float` on Windows — set via `-Dsynth_float_t=...` in the Makefiles).
|
||||
- `voice.h`/`voice.c` is the center of this layer: a `voice_t` (per-voice state: 2 VCOs, 4 LFOs, 4 envelopes, 1 VCF) driven by a shared `voice_common_t` (global LFO/env/VCF/noise tables and shared per-block buffers, to avoid recomputing identical modulation sources per voice). All per-voice parameters are addressed by the flat `VOICE_PARAM_*` enum in `voice.h`; modulation routing (FM/AM/PWM sources for oscillators, filter cutoff/Q sources) is expressed as source/amount/op triples pointing into `VOICE_MOD_SRC_*`.
|
||||
- Voice audio rendering can run multi-threaded: `JaySynthThread` (in `src/plug/JaySynth.h`) splits the voice array across CPU cores, each thread calling `VoiceProcessDataV`.
|
||||
- `src/plug/` — the JUCE `AudioProcessor`/`Synthesiser` plugin implementation, C++11.
|
||||
- `PluginProcessor.{h,cpp}` (`JaySynthAudioProcessor`) is the VST entry point: parameter get/set, program (patch) management, and all patch/bank XML and VST `.fxp`/`.fxb` import/export.
|
||||
- `JaySynth.{h,cpp}` (`class JaySynth : public Synthesiser`) owns the array of `voice_t` (via `JaySynthVoice`), MIDI note/CC/NRPN handling, humanize/unison/monophonic modes, and MIDI clock sync (`JK_MidiClock`).
|
||||
- `JaySynthSound` is a patch: a flat `parameter[SYNTH_NUM_PARAMS]` array plus a `JaySynthMidiCC` (per-parameter MIDI CC/NRPN assignment table). `JaySynthAudioProcessor` holds `NUM_PROGRAMS` (64) of these plus a separate "current patch" edit buffer, and tracks a modified/"[edited]" state independent of the stored program.
|
||||
- `JaySynthMidiCC`/`MidiNrpn`/`MidiCC_PopUp` implement per-parameter MIDI-learn: any synth parameter can be bound to a CC or NRPN controller, with absolute/relative modes.
|
||||
- `JaySynthAudioProcessorEditor` + `graph_window`/`ButtonLED`/`VelKey_PopUp` are the custom GUI (no Projucer-managed component files beyond the `.jucer`'s single editor group — most UI is hand-written).
|
||||
- Notifications from the audio/synth layer to the GUI go through `JaySynthActionListener` (a `JUCE::ActionBroadcaster` wrapping typed events like `JAYSYNTH_CHANGED_PARAM`, `JAYSYNTH_CHANGED_MIDICC`, `JAYSYNTH_CHANGED_PROGRAM` as encoded strings), not direct callbacks — needed because it crosses the audio-thread/message-thread boundary.
|
||||
- `extras/waves/` — binary wavetable sets (Microwave/PPG format, 8/12-bit) that must be installed as `waves.bin` next to the plugin for the wavetable oscillator feature to activate (see `extras/waves.readme`).
|
||||
- `extras/sounds/` — example patches/banks in both VST2 native format (`.fxp`/`.fxb`) and a custom XML format (`.xmp`/`.xmb`, patch/bank respectively) — these are the two import/export paths implemented in `PluginProcessor.cpp` (`patchImportXml`/`bankImportXml` vs. `setCurrentProgramStateInformation`/`setStateInformation`).
|
||||
- `matlab/` — MATLAB/Octave prototyping scripts for the DSP algorithms (oscillator BLEP/BLIT, filter, envelope, LFO, limiter, param curves) — reference models for the C implementation, not part of the build.
|
||||
- `doc/papers/` — background papers on the DSP techniques used (band-limited synthesis, wavetables).
|
||||
- `history.txt` — the changelog (German/English mixed); check here before assuming a described bug is still open.
|
||||
- `todo.txt` — open design/feature notes, also mixed German/English.
|
||||
|
||||
## Conventions to be aware of
|
||||
|
||||
- Per-voice DSP parameters are plain enums (`VOICE_PARAM_*`), not named struct fields — when adding a synth parameter, it must be threaded through the enum in `voice.h`, `param_scale.c`'s scaling table, and the plugin-side parameter name/XML (de)serialization in `PluginProcessor.cpp`.
|
||||
- Modulation source enums are duplicated per destination (`VOICE_MOD_SRC_0_*` vs `VOICE_MOD_SRC_1_*` vs the general `VOICE_MOD_SRC_*`) because not all destinations can be modulated by all sources (e.g. VCO0 can't modulate itself) — keep this distinction when touching mod routing.
|
||||
- Comments and log/history text are a German/English mix; don't assume English-only when grepping for TODOs or history entries.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Hardening todo
|
||||
|
||||
Findings from a code-review pass over `src/plug` and `src/synth` (2026-07-27, branch `hardening`).
|
||||
|
||||
## Critical — crashes, memory corruption (fixed, commit `a65b71a`)
|
||||
|
||||
- [x] **NRPN controller ID caused an out-of-bounds write on the audio thread.** `JaySynth::handleController` (`src/plug/JaySynth.cpp:899`) now bounds-checks `midiCC_info.ID` against `NUM_MIDI_CONTROLLERS` before indexing `last_midiCC_info[]` (NRPN IDs can reach 16383, array is sized 1024).
|
||||
- [x] **Negative controller IDs from patch files could corrupt memory.** `JaySynthMidiCC::add`/`remove` (`src/plug/JaySynthMidiCC.cpp:433-460`) now also reject `< 0`, not just out-of-range-high.
|
||||
- [x] **Null-pointer dereference on malformed/unparsable patch or bank files.** `bankImportXml` and the `.xmp` branch of `loadPatchFromFile` (`src/plug/PluginProcessor.cpp:860-885, 949-975`) now null-check XML elements after `getFirstChildElement()`/`XmlDocument::parse()` before dereferencing.
|
||||
- [x] **No size validation before casting raw file bytes to `fxBank`/`fxProgram` structs.** `loadBankFromFile`/`loadPatchFromFile` (`src/plug/PluginProcessor.cpp:834-858, 930-957`) now check the loaded file is at least header-sized and that the embedded chunk size fits within it before reading through the raw pointer.
|
||||
- [x] **Host block sizes larger than `SYNTH_MAX_BUFSIZE` (8192) caused a stack buffer overflow or a permanent deadlock.** `processBlock` (`src/plug/PluginProcessor.cpp:547`) now chunks rendering into `SYNTH_MAX_BUFSIZE`-sized passes instead of writing the host's full block size into a fixed 8192-sample stack buffer; `JaySynth::renderNextBlock` (`src/plug/JaySynth.cpp:977`) clamps the same way and correctly holds a pending MIDI event over between passes.
|
||||
- [x] **Unchecked file stream could be null, crashing on plugin construction.** `JaySynth::JaySynth` (`src/plug/JaySynth.cpp:55-57`) now guards the wavetable file read against `createInputStream()` returning null.
|
||||
- [x] **Parameter-changing calls raced with real-time audio rendering — no lock protected them.** `JaySynth::setParameter`/`setControl`/`setPerVoiceControl`/`setParam`/`ClearControls` (`src/plug/JaySynth.cpp`) now take `ScopedLock sl(lock)`, matching `renderNextBlock` and the note/controller handlers.
|
||||
- [x] **Found while building `tools/testhost`'s regression suite for this list: the block-size fix above wasn't sufficient on its own.** `VCF_CalcCoeff_LPF`/`_HPF`/`_BPF` (`src/synth/vcf.c`) advance the coefficient pointer once per *(sample, filter-section)* pair - a 4th-order filter uses `FILTER_MAX_ORDER/2` = 2 cascaded sections per sample - but `VCF_SetBufsize` only allocated `pCoef` with `bufsize` entries, not `bufsize * sections`. Any host block between 4097 and 8192 samples (inclusive of the now-correctly-chunked range from the fix above) with a 4th-order filter selected wrote past the end of that buffer, corrupting adjacent heap memory (confirmed with AddressSanitizer: `heap-buffer-overflow`, `WRITE of size 8`, exactly 8 bytes past a 393216-byte/8192-`vcf_coef_t` allocation). Fixed by sizing the allocation for the worst case, `bufsize*(FILTER_MAX_ORDER/2)`. Verified with ASan across the full range (4097, 8192, 16384, 20000 samples) after the fix - zero errors.
|
||||
|
||||
## High — real-time audio-thread safety (fixed)
|
||||
|
||||
- [x] **Every note/CC/voice-count change allocated Strings and posted a message from the audio thread.** `synthChanged()` (`src/plug/PluginProcessor.cpp`) no longer calls `JaySynthActionListener::call*` directly from the audio thread. It now pushes a small POD event into a lock-free single-producer/single-consumer queue (JUCE's `AbstractFifo`, added to `PluginProcessor.h`), and the existing 10ms UI `Timer` (`timerCallback`/`dispatchUiEvents`) drains it on the message thread, where the String-building and `sendActionMessage` now safely happen.
|
||||
- [x] **~450KB–900KB of stack arrays allocated per voice per audio block.** `VoiceProcessDataV`'s 11 `SYNTH_MAX_BUFSIZE`-sized locals (`src/synth/voice.c`) are now persistent per-voice buffers (`pBuf_VCO_fmout`, `pBuf_vco_pwm`, etc., declared in `voice.h`), allocated once in `VoiceSetBufsize`/freed in `VoiceFree` — same lifecycle/pattern already used by `pBuf_Q` and by `vcf.c`/`env.c`/`lfo.c`.
|
||||
- [x] **`SynthDebug` used unbounded `vsprintf` into a fixed 1024-byte buffer.** `src/synth/synth_debug.c` now uses `vsnprintf` with the buffer size. (Left the blocking `puts()`/`OutputDebugString` I/O as-is — it's compiled out entirely unless `SYNTH_DEBUG` is defined, so release builds are unaffected, and a lock-free logging redesign felt like overreach for a debug-only path.)
|
||||
|
||||
## Memory management (fixed)
|
||||
|
||||
- [x] **`new[]`/`delete` mismatches (UB) in `JaySynth`'s destructor** — `m_pVoices`, `pPer_voice_controls`, `pCurrNoteInfos`, `humanize_voice_param[i]`, `ppHumanizedSliders[i]`, `m_ppAudioThread`, `m_ppEventAudioThreadRdy` (`src/plug/JaySynth.cpp`) all now freed with `delete[]` to match their `new T[n]` allocations. Also found and fixed the same pattern via `ScopedPointer` in `PluginProcessor.cpp` (`setStateInformation`/`setCurrentProgramStateInformation`) — `ScopedPointer` always calls scalar `delete` (per JUCE's own doc comment), so `ScopedPointer<char> pUncompressedData = new char[...]` was the same bug; replaced with `HeapBlock<char>`, JUCE's array-owning smart pointer.
|
||||
- [x] **`malloc` was never null-checked** across the C DSP core. Added a shared `SynthCheckAlloc()` helper (`synth_defs.h`/`synth_debug.c`) that aborts with a clear diagnostic instead of returning NULL, and wrapped all 24 `malloc` call sites across `env.c`, `lfo.c`, `vcf.c`, `vco.c`, `blit.c`, `wavetable.c`, `voice.c`, `param_scale.c`. All are one-time init/bufsize-change calls, never in the per-block render path, so this adds no real-time overhead.
|
||||
|
||||
## Patch/Bank import-export (fixed)
|
||||
|
||||
- [x] **Legacy patch import decoded the wrong XML node.** `patchImportXml` now handles the legacy single-patch format (`JSYNTH_PATCH` root) as its own top-level branch instead of incorrectly nested inside the modern per-child loop, which checked/decoded the outer `xml` on every iteration instead of the loop variable. Also switched its CC-table import to `import_midi_cc` (matching the actually-exercised convention already used by `bankDecodeXml_legacy`, since this branch was previously unreachable dead code). `loadPatchFromFile`'s `.xmp` branch now also accepts legacy-rooted files, so single legacy patches can actually be loaded. Also fixed a related bug found in passing: `setCurrentProgramStateInformation` (VST2 "copy plugin state to another track") was passing `patchImportXml` an XML node one level too deep, making it a complete no-op.
|
||||
- [x] **Found a much bigger bug while investigating "regular .fxb/.fxp silently ignored": the magic-number check itself was completely broken.** It compared `String((const char*)&bank->chunkMagic)` (raw file bytes as text, e.g. `"CcnK"`) against `String(cMagic)` (JUCE's `int`-to-*decimal-text* constructor, e.g. `"1130589771"`) — these can never be equal, so **the entire opaque `.fxb`/`.fxp` load path was dead code**, regardless of format. Fixed using `ByteOrder::bigEndianInt`, the correct byte-order-aware comparison (matches the pattern JUCE's own VST3 wrapper uses for the identical struct). "Regular" (non-chunk) format is still not supported for load, but now surfaces an `AlertWindow` explaining why instead of silently doing nothing. Verified with a standalone round-trip test of the binary format logic (magic detection, size/payload round-trip, safe rejection of truncated/bogus files).
|
||||
- [x] **`.fxp`/`.fxb` saving was unimplemented.** Now implemented using the same opaque-chunk format the (now-fixed) load path expects, reusing `getCurrentProgramStateInformation`/`getStateInformation` for the payload.
|
||||
- [x] **`patchDecodeXml`/`patchDecodeXml_legacy` duplication** — `_legacy` now just delegates to `patchDecodeXml`.
|
||||
|
||||
## Design
|
||||
|
||||
- [ ] **`JaySynth` is a god object** (`src/plug/JaySynth.h`) — owns parameters, MIDI CC/NRPN, MIDI clock, 3 humanize subsystems, unison, monophonic orchestration, voice stealing, and the thread pool, all via direct field access with no internal boundaries. This is very plausibly why the parameter-locking bug (now fixed) was missed in the first place — no seam enforces consistent locking discipline. Consider splitting into a handful of focused collaborators (e.g. MIDI dispatch, voice allocation, humanize/unison modes) that `JaySynth` composes.
|
||||
- [ ] **`void *the_editor` (`src/plug/PluginProcessor.h:394`) duplicates JUCE's own editor tracking**, is never cleared when the editor is destroyed, and every other call site correctly uses `getEditor(this)` instead — currently harmless but a dangling-pointer trap. Fix: remove the field and route `createEditor()` through `getEditor(this)` like everywhere else.
|
||||
+44
-15
@@ -53,7 +53,8 @@ JaySynth::JaySynth (int num_voices, String pathToWaves)
|
||||
{
|
||||
File wavesFile = File(pathToWaves);
|
||||
ScopedPointer<FileInputStream>pIn = wavesFile.createInputStream();
|
||||
pIn->read(synth_common.vco.wt.wave_rawdata, sizeof(synth_common.vco.wt.wave_rawdata));
|
||||
if (pIn != NULL)
|
||||
pIn->read(synth_common.vco.wt.wave_rawdata, sizeof(synth_common.vco.wt.wave_rawdata));
|
||||
}
|
||||
|
||||
max_num_voices = num_voices;
|
||||
@@ -157,7 +158,7 @@ JaySynth::~JaySynth()
|
||||
{
|
||||
delete(m_ppAudioThread[i]);
|
||||
}
|
||||
delete(m_ppAudioThread);
|
||||
delete[] m_ppAudioThread;
|
||||
|
||||
SynthDebug("delete params\n");
|
||||
paramInfoFree(&pb_range[0]);
|
||||
@@ -170,25 +171,25 @@ JaySynth::~JaySynth()
|
||||
{
|
||||
paramInfoFree(&humanize_voice_param[i][j]);
|
||||
}
|
||||
delete (humanize_voice_param[i]);
|
||||
delete (ppHumanizedSliders[i]);
|
||||
delete[] humanize_voice_param[i];
|
||||
delete[] ppHumanizedSliders[i];
|
||||
}
|
||||
delete (humanize_voice_param);
|
||||
delete (ppHumanizedSliders);
|
||||
delete[] humanize_voice_param;
|
||||
delete[] ppHumanizedSliders;
|
||||
|
||||
for (i = max_num_voices; --i >= 0;)
|
||||
{
|
||||
removeVoice(i);
|
||||
}
|
||||
|
||||
delete (pPer_voice_controls);
|
||||
delete (pCurrNoteInfos);
|
||||
delete (m_pVoices);
|
||||
delete[] pPer_voice_controls;
|
||||
delete[] pCurrNoteInfos;
|
||||
delete[] m_pVoices;
|
||||
for (i=0; i < m_num_audiothreads; i++)
|
||||
{
|
||||
delete (m_ppEventAudioThreadRdy[i]);
|
||||
}
|
||||
delete(m_ppEventAudioThreadRdy);
|
||||
delete[] m_ppEventAudioThreadRdy;
|
||||
|
||||
|
||||
}
|
||||
@@ -379,6 +380,8 @@ void JaySynth::humanizeVarianceChanged_ENV(void)
|
||||
//==============================================================================
|
||||
void JaySynth::ClearControls(void)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
memset(controls, 0, SYNTH_NUM_PARAMS*sizeof(synth_float_t));
|
||||
}
|
||||
|
||||
@@ -428,6 +431,8 @@ synth_float_t JaySynth::getParameter(int paramID)
|
||||
|
||||
void JaySynth::setParameter(int paramID, synth_float_t param)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
params[paramID] = param;
|
||||
setParam(paramID, -1);
|
||||
|
||||
@@ -437,6 +442,8 @@ void JaySynth::setParameter(int paramID, synth_float_t param)
|
||||
|
||||
void JaySynth::setControl(int paramID, synth_float_t param)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
controls[paramID] = param;
|
||||
setParam(paramID, -1);
|
||||
|
||||
@@ -446,12 +453,16 @@ void JaySynth::setControl(int paramID, synth_float_t param)
|
||||
|
||||
void JaySynth::setPerVoiceControl(int voice, int channel, int paramID, synth_float_t param)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
pPer_voice_controls[voice].ctrl[channel][paramID] = param;
|
||||
setParam(paramID, voice);
|
||||
}
|
||||
|
||||
void JaySynth::setParam(int paramID, int voice)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
int i, j, voice_param_type, voice_min, voice_max;
|
||||
synth_float_t param;
|
||||
|
||||
@@ -898,6 +909,9 @@ void JaySynth::handlePitchWheel (const int midiChannel, const int wheelValue)
|
||||
|
||||
void JaySynth::handleController (const midiCC_info_t &midiCC_info)
|
||||
{
|
||||
if (midiCC_info.ID < 0 || midiCC_info.ID >= NUM_MIDI_CONTROLLERS)
|
||||
return;
|
||||
|
||||
last_midiCC_info[midiCC_info.ID] = midiCC_info;
|
||||
listeners.call (&JaySynthListener::synthChanged, SYNTH_CHANGED_MIDICC, (midiCC_info_t*)&midiCC_info);
|
||||
|
||||
@@ -990,14 +1004,26 @@ void JaySynth::renderNextBlock (AudioSampleBuffer& outputBuffer,
|
||||
midiIterator.setNextSamplePosition (startSample);
|
||||
MidiMessage m (0xf4, 0.0);
|
||||
|
||||
bool havePendingEvent = false;
|
||||
int midiEventPos = 0;
|
||||
|
||||
while (numSamples > 0)
|
||||
{
|
||||
int midiEventPos;
|
||||
const bool useEvent = midiIterator.getNextEvent (m, midiEventPos)
|
||||
if (!havePendingEvent)
|
||||
havePendingEvent = midiIterator.getNextEvent (m, midiEventPos);
|
||||
|
||||
const bool useEvent = havePendingEvent
|
||||
&& midiEventPos < startSample + numSamples;
|
||||
|
||||
const int numThisTime = useEvent ? midiEventPos - startSample
|
||||
: numSamples;
|
||||
int numThisTime = useEvent ? midiEventPos - startSample
|
||||
: numSamples;
|
||||
|
||||
// The DSP core's per-block work buffers are fixed at SYNTH_MAX_BUFSIZE
|
||||
// samples, so render oversized ranges in multiple passes rather than
|
||||
// overrunning them. The pending midi event (if any) stays queued until
|
||||
// we actually reach its sample position.
|
||||
if (numThisTime > SYNTH_MAX_BUFSIZE)
|
||||
numThisTime = SYNTH_MAX_BUFSIZE;
|
||||
|
||||
if (numThisTime > 0)
|
||||
{
|
||||
@@ -1035,9 +1061,12 @@ void JaySynth::renderNextBlock (AudioSampleBuffer& outputBuffer,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (useEvent)
|
||||
// Only fire the pending event once we've actually rendered up to its
|
||||
// sample position (numThisTime may have been clamped short of it above).
|
||||
if (useEvent && numThisTime == midiEventPos - startSample)
|
||||
{
|
||||
handleMidiEvent (m);
|
||||
havePendingEvent = false;
|
||||
}
|
||||
|
||||
startSample += numThisTime;
|
||||
|
||||
@@ -432,7 +432,7 @@ int JaySynthMidiCC::import_midi_cc_legacy(XmlElement const *pXML_MidiCc)
|
||||
|
||||
void JaySynthMidiCC::add(midiCC_container_t *pObj, int controllerID)
|
||||
{
|
||||
if (controllerID >= NUM_MIDI_CONTROLLERS)
|
||||
if (controllerID < 0 || controllerID >= NUM_MIDI_CONTROLLERS)
|
||||
return;
|
||||
|
||||
remove(pObj, pObj->controllerID);
|
||||
@@ -448,7 +448,7 @@ void JaySynthMidiCC::add(midiCC_container_t *pObj, int controllerID)
|
||||
|
||||
void JaySynthMidiCC::remove(midiCC_container_t *pObj, int controllerID)
|
||||
{
|
||||
if (controllerID >= NUM_MIDI_CONTROLLERS)
|
||||
if (controllerID < 0 || controllerID >= NUM_MIDI_CONTROLLERS)
|
||||
return;
|
||||
|
||||
pObj->isAssigned = 0;
|
||||
|
||||
+271
-108
@@ -8,6 +8,8 @@
|
||||
==============================================================================
|
||||
*/
|
||||
#include <math.h>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "PluginProcessor.h"
|
||||
#include "JaySynthAudioProcessorEditor.h"
|
||||
@@ -23,6 +25,7 @@ JaySynthAudioProcessorEditor* getEditor(JaySynthAudioProcessor *pThis)
|
||||
|
||||
//==============================================================================
|
||||
JaySynthAudioProcessor::JaySynthAudioProcessor()
|
||||
: m_uiEventFifo(uiEventQueueCapacity)
|
||||
{
|
||||
currProgram = 0;
|
||||
currpatch = &patches[currProgram];
|
||||
@@ -56,10 +59,77 @@ JaySynthAudioProcessor::~JaySynthAudioProcessor()
|
||||
|
||||
void JaySynthAudioProcessor::timerCallback()
|
||||
{
|
||||
dispatchUiEvents();
|
||||
|
||||
if (getEditor(this))
|
||||
actionListener.callLimiterChanged(m_limiter_active);
|
||||
}
|
||||
|
||||
void JaySynthAudioProcessor::pushUiEvent(int type, int i1, int i2, float f1)
|
||||
{
|
||||
int start1, size1, start2, size2;
|
||||
m_uiEventFifo.prepareToWrite(1, start1, size1, start2, size2);
|
||||
|
||||
if (size1 > 0)
|
||||
{
|
||||
SynthUiEvent &e = m_uiEventBuffer[start1];
|
||||
e.type = type;
|
||||
e.i1 = i1;
|
||||
e.i2 = i2;
|
||||
e.f1 = f1;
|
||||
m_uiEventFifo.finishedWrite(1);
|
||||
}
|
||||
// else: queue is full, drop the event rather than blocking the audio thread.
|
||||
}
|
||||
|
||||
void JaySynthAudioProcessor::dispatchUiEvent(const SynthUiEvent &e)
|
||||
{
|
||||
switch (e.type)
|
||||
{
|
||||
case JaySynth::SYNTH_CHANGED_PARAM:
|
||||
actionListener.callParamChanged(e.i1);
|
||||
break;
|
||||
|
||||
case JaySynth::SYNTH_CHANGED_MIDICC:
|
||||
actionListener.callMidiControllerChanged(e.i1, e.i2, e.f1);
|
||||
break;
|
||||
|
||||
case JaySynth::SYNTH_CHANGED_NOTE_PRESSED:
|
||||
actionListener.callMidiNotePressed(e.i1, e.f1);
|
||||
break;
|
||||
|
||||
case JaySynth::SYNTH_CHANGED_NOTE_RELEASED:
|
||||
actionListener.callMidiNoteReleased(e.i1, e.f1);
|
||||
break;
|
||||
|
||||
case JaySynth::SYNTH_CHANGED_NUM_VOICES_PLAYING:
|
||||
actionListener.callNumVoicesPlaying(e.i1);
|
||||
break;
|
||||
|
||||
case JaySynth::SYNTH_CHANGED_MIDICLOCK:
|
||||
actionListener.callMidiClock((uint32_t)e.i1, (uint32_t)e.i2);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JaySynthAudioProcessor::dispatchUiEvents(void)
|
||||
{
|
||||
int start1, size1, start2, size2;
|
||||
m_uiEventFifo.prepareToRead(m_uiEventFifo.getNumReady(), start1, size1, start2, size2);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < size1; i++)
|
||||
dispatchUiEvent(m_uiEventBuffer[start1 + i]);
|
||||
|
||||
for (i = 0; i < size2; i++)
|
||||
dispatchUiEvent(m_uiEventBuffer[start2 + i]);
|
||||
|
||||
m_uiEventFifo.finishedRead(size1 + size2);
|
||||
}
|
||||
|
||||
String JaySynthAudioProcessor::wavesGetPath()
|
||||
{
|
||||
bool isExistent, isValid;
|
||||
@@ -148,37 +218,37 @@ void JaySynthAudioProcessor::synthChanged(int type, void *pData)
|
||||
{
|
||||
case JaySynth::SYNTH_CHANGED_PARAM:
|
||||
if (getEditor(this) != NULL)
|
||||
actionListener.callParamChanged(*((int*)pData));
|
||||
pushUiEvent(type, *((int*)pData), 0, 0.0f);
|
||||
break;
|
||||
|
||||
case JaySynth::SYNTH_CHANGED_MIDICC:
|
||||
pMidiCC_info = (JaySynth::midiCC_info_t*)pData;
|
||||
applyMidiController(pMidiCC_info, true);
|
||||
if (getEditor(this) != NULL)
|
||||
actionListener.callMidiControllerChanged(pMidiCC_info->channel, pMidiCC_info->ID, (float)pMidiCC_info->value);
|
||||
pushUiEvent(type, pMidiCC_info->channel, pMidiCC_info->ID, (float)pMidiCC_info->value);
|
||||
break;
|
||||
|
||||
case JaySynth::SYNTH_CHANGED_NOTE_PRESSED:
|
||||
pMidi_note_info = (JaySynth::midi_note_info_t*)pData;
|
||||
if (getEditor(this) != NULL)
|
||||
actionListener.callMidiNotePressed(pMidi_note_info->note, (float)pMidi_note_info->velocity);
|
||||
pushUiEvent(type, pMidi_note_info->note, 0, (float)pMidi_note_info->velocity);
|
||||
break;
|
||||
|
||||
case JaySynth::SYNTH_CHANGED_NOTE_RELEASED:
|
||||
pMidi_note_info = (JaySynth::midi_note_info_t*)pData;
|
||||
if (getEditor(this) != NULL)
|
||||
actionListener.callMidiNoteReleased(pMidi_note_info->note, (float)pMidi_note_info->velocity);
|
||||
pushUiEvent(type, pMidi_note_info->note, 0, (float)pMidi_note_info->velocity);
|
||||
break;
|
||||
|
||||
case JaySynth::SYNTH_CHANGED_NUM_VOICES_PLAYING:
|
||||
if (getEditor(this) != NULL)
|
||||
actionListener.callNumVoicesPlaying(*((int*)pData));
|
||||
pushUiEvent(type, *((int*)pData), 0, 0.0f);
|
||||
break;
|
||||
|
||||
case JaySynth::SYNTH_CHANGED_MIDICLOCK:
|
||||
pMidi_quarter_info = (JaySynth::midi_quarter_clock_info_t*)pData;
|
||||
if (getEditor(this) != NULL)
|
||||
actionListener.callMidiClock(pMidi_quarter_info->bpm, pMidi_quarter_info->type);
|
||||
pushUiEvent(type, (int)pMidi_quarter_info->bpm, (int)pMidi_quarter_info->type, 0.0f);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -546,18 +616,16 @@ void JaySynthAudioProcessor::releaseResources()
|
||||
void JaySynthAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
|
||||
{
|
||||
const int numSamples = buffer.getNumSamples();
|
||||
int i;
|
||||
float *buf1 = buffer.getWritePointer (0, 0);
|
||||
float *buf2 = buffer.getWritePointer (1, 0);
|
||||
synth_float_t buf[2][SYNTH_MAX_BUFSIZE];
|
||||
float synth_gain;
|
||||
|
||||
AudioPlayHead::CurrentPositionInfo posInfo;
|
||||
getPlayHead()->getCurrentPosition(posInfo);
|
||||
|
||||
|
||||
// Parse midi buffer and add midi clock messages
|
||||
m_JK_MidiClock.generateMidiclock(posInfo, &midiMessages, numSamples, getSampleRate());
|
||||
|
||||
|
||||
// Now pass any incoming midi messages to our keyboard state object, and let it
|
||||
// add messages to the buffer if the user is clicking on the on-screen keys
|
||||
keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true);
|
||||
@@ -566,78 +634,88 @@ void JaySynthAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer
|
||||
buffer.clear (0, 0, numSamples);
|
||||
buffer.clear (1, 0, numSamples);
|
||||
|
||||
// and now get the synth to process these midi events and generate its output.
|
||||
// Render individual voices
|
||||
pSynth->renderNextBlock (buffer, midiMessages, 0, numSamples);
|
||||
|
||||
// apply master volume
|
||||
synth_gain = (float)pSynth->getVolume();
|
||||
|
||||
for (i=0; i <numSamples; i++)
|
||||
buf[0][i] = (synth_float_t)buf1[i] * synth_gain;
|
||||
|
||||
for (i=0; i <numSamples; i++)
|
||||
buf[1][i] = (synth_float_t)buf2[i] * synth_gain;
|
||||
|
||||
m_limiter_active = 0;
|
||||
|
||||
synth_float_t master_gain = 0.5;
|
||||
|
||||
#ifdef WITH_LIMITER
|
||||
synth_float_t limiter_env[SYNTH_MAX_BUFSIZE];
|
||||
synth_float_t limiter_gain[SYNTH_MAX_BUFSIZE];
|
||||
synth_float_t x, limiter_ar, limiter_af, limiter_threshold;
|
||||
|
||||
// Limiter
|
||||
limiter_ar = 5.0/(SYNTH_LIMITER_RISE_TIME*m_fs);
|
||||
limiter_af = 5.0/(SYNTH_LIMITER_FALL_TIME*m_fs);
|
||||
limiter_threshold = pow((synth_float_t)10.0, (synth_float_t)SYNTH_LIMITER_THRESHOLD/20);
|
||||
|
||||
for (i=0; i <numSamples; i++)
|
||||
// The DSP core (and this function's own work buffers) can only render up to
|
||||
// SYNTH_MAX_BUFSIZE samples per call, so process oversized host blocks in
|
||||
// multiple chunks instead of overflowing the fixed-size stack buffers below.
|
||||
for (int chunkStart = 0; chunkStart < numSamples; chunkStart += SYNTH_MAX_BUFSIZE)
|
||||
{
|
||||
x = jsy_max(fabs(buf[0][i]), fabs(buf[1][i]));
|
||||
if (x > m_limiter_env)
|
||||
const int chunkLen = jsy_min(numSamples - chunkStart, (int)SYNTH_MAX_BUFSIZE);
|
||||
synth_float_t buf[2][SYNTH_MAX_BUFSIZE];
|
||||
int i;
|
||||
|
||||
// and now get the synth to process these midi events and generate its output.
|
||||
// Render individual voices
|
||||
pSynth->renderNextBlock (buffer, midiMessages, chunkStart, chunkLen);
|
||||
|
||||
for (i=0; i <chunkLen; i++)
|
||||
buf[0][i] = (synth_float_t)buf1[chunkStart + i] * synth_gain;
|
||||
|
||||
for (i=0; i <chunkLen; i++)
|
||||
buf[1][i] = (synth_float_t)buf2[chunkStart + i] * synth_gain;
|
||||
|
||||
m_limiter_active = 0;
|
||||
|
||||
#ifdef WITH_LIMITER
|
||||
synth_float_t limiter_env[SYNTH_MAX_BUFSIZE];
|
||||
synth_float_t limiter_gain[SYNTH_MAX_BUFSIZE];
|
||||
synth_float_t x, limiter_ar, limiter_af, limiter_threshold;
|
||||
|
||||
// Limiter
|
||||
limiter_ar = 5.0/(SYNTH_LIMITER_RISE_TIME*m_fs);
|
||||
limiter_af = 5.0/(SYNTH_LIMITER_FALL_TIME*m_fs);
|
||||
limiter_threshold = pow((synth_float_t)10.0, (synth_float_t)SYNTH_LIMITER_THRESHOLD/20);
|
||||
|
||||
for (i=0; i <chunkLen; i++)
|
||||
{
|
||||
m_limiter_env = (1.0-limiter_ar)*m_limiter_env + limiter_ar*x;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_limiter_env = (1.0-limiter_af)*m_limiter_env;
|
||||
x = jsy_max(fabs(buf[0][i]), fabs(buf[1][i]));
|
||||
if (x > m_limiter_env)
|
||||
{
|
||||
m_limiter_env = (1.0-limiter_ar)*m_limiter_env + limiter_ar*x;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_limiter_env = (1.0-limiter_af)*m_limiter_env;
|
||||
}
|
||||
|
||||
limiter_env[i] = m_limiter_env;
|
||||
}
|
||||
|
||||
limiter_env[i] = m_limiter_env;
|
||||
}
|
||||
|
||||
for (i=0; i <numSamples; i++)
|
||||
{
|
||||
limiter_gain[i] = jsy_min(1.0, 1.0/(limiter_env[i] + 1.0e-6));
|
||||
if (limiter_gain[i] < 1.0)
|
||||
for (i=0; i <chunkLen; i++)
|
||||
{
|
||||
m_limiter_active = 1;
|
||||
break;
|
||||
limiter_gain[i] = jsy_min(1.0, 1.0/(limiter_env[i] + 1.0e-6));
|
||||
if (limiter_gain[i] < 1.0)
|
||||
{
|
||||
m_limiter_active = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i=0; i <numSamples; i++)
|
||||
{
|
||||
buf1[i] = (float)(master_gain*buf[0][i]*limiter_gain[i]);
|
||||
}
|
||||
for (i=0; i <chunkLen; i++)
|
||||
{
|
||||
buf1[chunkStart + i] = (float)(master_gain*buf[0][i]*limiter_gain[i]);
|
||||
}
|
||||
|
||||
for (i=0; i <numSamples; i++)
|
||||
{
|
||||
buf2[i] = (float)(master_gain*buf[1][i]*limiter_gain[i]);
|
||||
}
|
||||
for (i=0; i <chunkLen; i++)
|
||||
{
|
||||
buf2[chunkStart + i] = (float)(master_gain*buf[1][i]*limiter_gain[i]);
|
||||
}
|
||||
#else
|
||||
for (i=0; i <numSamples; i++)
|
||||
{
|
||||
buf1[i] = (float)(master_gain*buf[0][i]);
|
||||
}
|
||||
for (i=0; i <chunkLen; i++)
|
||||
{
|
||||
buf1[chunkStart + i] = (float)(master_gain*buf[0][i]);
|
||||
}
|
||||
|
||||
for (i=0; i <numSamples; i++)
|
||||
{
|
||||
buf2[i] = (float)(master_gain*buf[1][i]);
|
||||
}
|
||||
for (i=0; i <chunkLen; i++)
|
||||
{
|
||||
buf2[chunkStart + i] = (float)(master_gain*buf[1][i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// In case we have more outputs than inputs, we'll clear any output
|
||||
// channels that didn't contain input data, (because these aren't
|
||||
@@ -766,20 +844,9 @@ void JaySynthAudioProcessor::patchDecodeXml(const XmlElement *xml, JaySynthSound
|
||||
|
||||
void JaySynthAudioProcessor::patchDecodeXml_legacy(const XmlElement& xml, JaySynthSound *patch)
|
||||
{
|
||||
// ok, now pull out our parameters..
|
||||
patch->setName(xml.getStringAttribute("NAME"));
|
||||
for (int i=0; i < SYNTH_NUM_PARAMS; i++)
|
||||
{
|
||||
synth_float_t value = (synth_float_t) xml.getDoubleAttribute (getParameterNameXML(i), patch->getParameter(i));
|
||||
if (i==SYNTH_PARAM_TUNE)
|
||||
{
|
||||
if (value > 400.0)
|
||||
{
|
||||
value = 1200*log(value/440)/log(2.0);
|
||||
}
|
||||
}
|
||||
patch->setParameter(i, value);
|
||||
}
|
||||
// Legacy format uses the same per-parameter attribute layout as the
|
||||
// current format, just addressed via a reference instead of a pointer.
|
||||
patchDecodeXml(&xml, patch);
|
||||
}
|
||||
|
||||
void JaySynthAudioProcessor::bankEncodeXml(XmlElement *xml)
|
||||
@@ -838,16 +905,34 @@ void JaySynthAudioProcessor::loadBankFromFile (const File &file)
|
||||
{
|
||||
MemoryBlock mem;
|
||||
file.loadFileAsData(mem);
|
||||
|
||||
const size_t headerSize = offsetof(fxBank, content) + sizeof(VstInt32);
|
||||
if (mem.getSize() < headerSize)
|
||||
return;
|
||||
|
||||
fxBank *bank = (fxBank*)mem.getData();
|
||||
if (String((const char*)&bank->chunkMagic) == String(cMagic))
|
||||
// fxb/fxp magic numbers and sizes are stored big-endian on disk (see
|
||||
// vstfxstore.h); bigEndianInt() reads them correctly regardless of
|
||||
// host byte order.
|
||||
if (ByteOrder::bigEndianInt(&bank->chunkMagic) == (uint32_t)cMagic)
|
||||
{
|
||||
bool is_regular = String((const char*)&bank->fxMagic) == String(bankMagic);
|
||||
bool is_opaque = String((const char*)&bank->fxMagic) == String(chunkBankMagic);
|
||||
bool is_regular = ByteOrder::bigEndianInt(&bank->fxMagic) == (uint32_t)bankMagic;
|
||||
bool is_opaque = ByteOrder::bigEndianInt(&bank->fxMagic) == (uint32_t)chunkBankMagic;
|
||||
if (is_opaque)
|
||||
{
|
||||
uint32_t size = ByteOrder::swap((uint32_t)bank->content.data.size);
|
||||
uint32_t size = ByteOrder::bigEndianInt(&bank->content.data.size);
|
||||
const size_t chunkOffset = offsetof(fxBank, content.data.chunk);
|
||||
if (chunkOffset > mem.getSize() || (size_t)size > mem.getSize() - chunkOffset)
|
||||
return;
|
||||
setStateInformation(bank->content.data.chunk, (int)size);
|
||||
}
|
||||
else if (is_regular)
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync(AlertWindow::WarningIcon, "Load bank",
|
||||
"This .fxb file uses the \"regular\" (non-chunk) VST2 bank format, "
|
||||
"which JaySynth doesn't support. Only chunk-based .fxb files "
|
||||
"(as saved by JaySynth or other JUCE-based plugins) can be loaded.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (String(".xmb") == ext)
|
||||
@@ -864,7 +949,7 @@ void JaySynthAudioProcessor::bankImportXml (const XmlElement *xml)
|
||||
if (xml->hasTagName ("JSynth"))
|
||||
{
|
||||
XmlElement *xml2 = xml->getFirstChildElement();
|
||||
if (xml2->hasTagName ("Bank"))
|
||||
if (xml2 != 0 && xml2->hasTagName ("Bank"))
|
||||
{
|
||||
XmlElement *xml3 = xml2->getFirstChildElement();
|
||||
bankDecodeXml(xml3);
|
||||
@@ -876,8 +961,9 @@ void JaySynthAudioProcessor::bankImportXml (const XmlElement *xml)
|
||||
if (xml->hasTagName ("JSYNTH_BANK"))
|
||||
{
|
||||
XmlElement *xml2 = xml->getFirstChildElement();
|
||||
bankDecodeXml_legacy(*xml2);
|
||||
|
||||
if (xml2 != 0)
|
||||
bankDecodeXml_legacy(*xml2);
|
||||
|
||||
}
|
||||
}
|
||||
setCurrentProgram(getCurrentProgram());
|
||||
@@ -886,7 +972,7 @@ void JaySynthAudioProcessor::bankImportXml (const XmlElement *xml)
|
||||
|
||||
void JaySynthAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
|
||||
{
|
||||
ScopedPointer<char> pUncompressedData = new char[8192*1024];
|
||||
HeapBlock<char> pUncompressedData (8192*1024);
|
||||
|
||||
MemoryInputStream inputStream(data, sizeInBytes, false);
|
||||
GZIPDecompressorInputStream GZIPDec(inputStream);
|
||||
@@ -934,22 +1020,40 @@ void JaySynthAudioProcessor::loadPatchFromFile (const File &file, int index)
|
||||
{
|
||||
MemoryBlock mem;
|
||||
file.loadFileAsData(mem);
|
||||
|
||||
const size_t headerSize = offsetof(fxProgram, content) + sizeof(VstInt32);
|
||||
if (mem.getSize() < headerSize)
|
||||
return;
|
||||
|
||||
fxProgram *program = (fxProgram*)mem.getData();
|
||||
if (String((const char*)&program->chunkMagic) == String(cMagic))
|
||||
// fxb/fxp magic numbers and sizes are stored big-endian on disk (see
|
||||
// vstfxstore.h); bigEndianInt() reads them correctly regardless of
|
||||
// host byte order.
|
||||
if (ByteOrder::bigEndianInt(&program->chunkMagic) == (uint32_t)cMagic)
|
||||
{
|
||||
bool is_regular = String((const char*)&program->fxMagic) == String(fMagic);
|
||||
bool is_opaque = String((const char*)&program->fxMagic) == String(chunkPresetMagic);
|
||||
bool is_regular = ByteOrder::bigEndianInt(&program->fxMagic) == (uint32_t)fMagic;
|
||||
bool is_opaque = ByteOrder::bigEndianInt(&program->fxMagic) == (uint32_t)chunkPresetMagic;
|
||||
if (is_opaque)
|
||||
{
|
||||
uint32_t size = ByteOrder::swap((uint32_t)program->content.data.size);
|
||||
uint32_t size = ByteOrder::bigEndianInt(&program->content.data.size);
|
||||
const size_t chunkOffset = offsetof(fxProgram, content.data.chunk);
|
||||
if (chunkOffset > mem.getSize() || (size_t)size > mem.getSize() - chunkOffset)
|
||||
return;
|
||||
setCurrentProgramStateInformation(program->content.data.chunk, (int)size);
|
||||
}
|
||||
else if (is_regular)
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync(AlertWindow::WarningIcon, "Load patch",
|
||||
"This .fxp file uses the \"regular\" (non-chunk) VST2 patch format, "
|
||||
"which JaySynth doesn't support. Only chunk-based .fxp files "
|
||||
"(as saved by JaySynth or other JUCE-based plugins) can be loaded.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (String(".xmp") == ext)
|
||||
{
|
||||
ScopedPointer<XmlElement> xml = XmlDocument::parse(file);
|
||||
if (xml->hasTagName ("JSynth"))
|
||||
if (xml != 0 && (xml->hasTagName ("JSynth") || xml->hasTagName ("JSYNTH_PATCH")))
|
||||
{
|
||||
patchImportXml(xml, currpatch);
|
||||
}
|
||||
@@ -958,6 +1062,21 @@ void JaySynthAudioProcessor::loadPatchFromFile (const File &file, int index)
|
||||
|
||||
void JaySynthAudioProcessor::patchImportXml (const XmlElement *xml, JaySynthSound *patch)
|
||||
{
|
||||
// Legacy single-patch format: parameters are attributes directly on the
|
||||
// root element, not nested under a "Patch" child - handle it separately
|
||||
// rather than inside the per-child loop below (xml has no children with
|
||||
// tag "Patch"/"VelKey"/"MidiCC" in this format, so it doesn't fit that
|
||||
// loop's shape). Mirrors how bankDecodeXml_legacy decodes each legacy
|
||||
// bank entry.
|
||||
if (xml->hasTagName ("JSYNTH_PATCH"))
|
||||
{
|
||||
patchDecodeXml_legacy(*xml, patch);
|
||||
XmlElement *pXML_MidiCc = xml->getChildByName ("JSYNTH_MIDICONTROLLER_TABLE");
|
||||
((JaySynthMidiCC*)patch->getMidiCC())->import_midi_cc(pXML_MidiCc);
|
||||
setCurrentProgram(getCurrentProgram());
|
||||
return;
|
||||
}
|
||||
|
||||
XmlElement *xml2 = xml->getFirstChildElement();
|
||||
while(xml2)
|
||||
{
|
||||
@@ -970,20 +1089,13 @@ void JaySynthAudioProcessor::patchImportXml (const XmlElement *xml, JaySynthSoun
|
||||
((JaySynthMidiCC*)patch->getMidiCC())->import_midi_cc_legacy(pXML_MidiCc);
|
||||
}
|
||||
}
|
||||
else if (xml->hasTagName ("JSYNTH_PATCH"))
|
||||
{
|
||||
patchDecodeXml_legacy(*xml, patch);
|
||||
XmlElement *pXML_MidiCc = xml->getChildByName ("JSYNTH_MIDICONTROLLER_TABLE");
|
||||
((JaySynthMidiCC*)patch->getMidiCC())->import_midi_cc_legacy(pXML_MidiCc);
|
||||
|
||||
}
|
||||
else if (xml2->hasTagName ("VelKey"))
|
||||
{
|
||||
((JaySynthMidiCC*)patch->getMidiCC())->import_midi_velkey(xml->getChildByName ("VelKey"));
|
||||
((JaySynthMidiCC*)patch->getMidiCC())->import_midi_velkey(xml2);
|
||||
}
|
||||
else if (xml2->hasTagName ("MidiCC"))
|
||||
{
|
||||
((JaySynthMidiCC*)patch->getMidiCC())->import_midi_cc(xml->getChildByName ("MidiCC"));
|
||||
((JaySynthMidiCC*)patch->getMidiCC())->import_midi_cc(xml2);
|
||||
}
|
||||
xml2 = xml2->getNextElement();
|
||||
};
|
||||
@@ -999,8 +1111,7 @@ void JaySynthAudioProcessor::setCurrentProgramStateInformation (const void* data
|
||||
void* pData;
|
||||
int size;
|
||||
|
||||
ScopedPointer<char> pUncompressedData;
|
||||
pUncompressedData = new char[2*65536];
|
||||
HeapBlock<char> pUncompressedData (2*65536);
|
||||
|
||||
MemoryInputStream inputStream(data, sizeInBytes, false);
|
||||
GZIPDecompressorInputStream GZIPDec(inputStream);
|
||||
@@ -1018,8 +1129,10 @@ void JaySynthAudioProcessor::setCurrentProgramStateInformation (const void* data
|
||||
{
|
||||
if (xml->hasTagName ("JSynth"))
|
||||
{
|
||||
XmlElement *xml2 = xml->getFirstChildElement();
|
||||
patchImportXml(xml2, currpatch);
|
||||
// xml is already the "JSynth" container (Patch/VelKey/MidiCC
|
||||
// children) - patchImportXml expects that container, not one of
|
||||
// its children.
|
||||
patchImportXml(xml, currpatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1029,7 +1142,34 @@ void JaySynthAudioProcessor::savePatchToFile (const File &file, int index)
|
||||
String ext = file.getFileExtension();
|
||||
if (String(".fxp") == ext)
|
||||
{
|
||||
// ToDo: implement fxp save
|
||||
commitEditBuffer();
|
||||
|
||||
// Reuse the same opaque chunk format used for the host's own VST2
|
||||
// "current program" state (and expected back by the .fxp opaque-chunk
|
||||
// branch of loadPatchFromFile).
|
||||
MemoryBlock chunk;
|
||||
getCurrentProgramStateInformation(chunk);
|
||||
|
||||
const size_t headerSize = offsetof(fxProgram, content) + sizeof(VstInt32);
|
||||
MemoryBlock mem(headerSize + chunk.getSize(), true);
|
||||
fxProgram *program = (fxProgram*)mem.getData();
|
||||
|
||||
program->chunkMagic = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)cMagic);
|
||||
program->byteSize = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)(mem.getSize() - 8));
|
||||
program->fxMagic = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)chunkPresetMagic);
|
||||
program->version = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)1);
|
||||
program->fxID = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)JucePlugin_VSTUniqueID);
|
||||
program->fxVersion = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)JucePlugin_VersionCode);
|
||||
program->numParams = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)SYNTH_NUM_PARAMS);
|
||||
|
||||
const String name = currpatch->getName();
|
||||
memcpy(program->prgName, name.toRawUTF8(),
|
||||
jmin(sizeof(program->prgName) - 1, (size_t)name.getNumBytesAsUTF8()));
|
||||
|
||||
program->content.data.size = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)chunk.getSize());
|
||||
memcpy(program->content.data.chunk, chunk.getData(), chunk.getSize());
|
||||
|
||||
file.replaceWithData(mem.getData(), mem.getSize());
|
||||
}
|
||||
else if (String(".xmp") == ext)
|
||||
{
|
||||
@@ -1051,7 +1191,30 @@ void JaySynthAudioProcessor::saveBankToFile (const File &file)
|
||||
String ext = file.getFileExtension();
|
||||
if (String(".fxb") == ext)
|
||||
{
|
||||
// ToDo: implement fxp save
|
||||
commitEditBuffer();
|
||||
|
||||
// Reuse the same opaque chunk format used for the host's own VST2
|
||||
// bank state (and expected back by the .fxb opaque-chunk branch of
|
||||
// loadBankFromFile).
|
||||
MemoryBlock chunk;
|
||||
getStateInformation(chunk);
|
||||
|
||||
const size_t headerSize = offsetof(fxBank, content) + sizeof(VstInt32);
|
||||
MemoryBlock mem(headerSize + chunk.getSize(), true);
|
||||
fxBank *bank = (fxBank*)mem.getData();
|
||||
|
||||
bank->chunkMagic = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)cMagic);
|
||||
bank->byteSize = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)(mem.getSize() - 8));
|
||||
bank->fxMagic = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)chunkBankMagic);
|
||||
bank->version = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)1);
|
||||
bank->fxID = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)JucePlugin_VSTUniqueID);
|
||||
bank->fxVersion = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)JucePlugin_VersionCode);
|
||||
bank->numPrograms = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)NUM_PROGRAMS);
|
||||
|
||||
bank->content.data.size = (VstInt32)ByteOrder::swapIfLittleEndian((uint32_t)chunk.getSize());
|
||||
memcpy(bank->content.data.chunk, chunk.getData(), chunk.getSize());
|
||||
|
||||
file.replaceWithData(mem.getData(), mem.getSize());
|
||||
}
|
||||
else if (String(".xmb") == ext)
|
||||
{
|
||||
|
||||
@@ -396,7 +396,29 @@ private:
|
||||
int m_limiter_active;
|
||||
synth_float_t m_limiter_env;
|
||||
JK_MidiClock m_JK_MidiClock;
|
||||
|
||||
|
||||
// synthChanged() runs on the audio thread. Building the JaySynthActionListener
|
||||
// Strings and posting them there (as JAYSYNTH_CHANGED_* used to do directly)
|
||||
// allocates and locks on the audio thread. Instead, synthChanged() pushes a
|
||||
// small POD event into this lock-free single-producer (audio thread) /
|
||||
// single-consumer (message-thread timer) queue, and timerCallback() drains it
|
||||
// and does the actual String-building/sendActionMessage on the message thread.
|
||||
struct SynthUiEvent
|
||||
{
|
||||
int type;
|
||||
int i1;
|
||||
int i2;
|
||||
float f1;
|
||||
};
|
||||
|
||||
enum { uiEventQueueCapacity = 256 };
|
||||
SynthUiEvent m_uiEventBuffer[uiEventQueueCapacity];
|
||||
AbstractFifo m_uiEventFifo;
|
||||
|
||||
void pushUiEvent(int type, int i1, int i2, float f1);
|
||||
void dispatchUiEvent(const SynthUiEvent &e);
|
||||
void dispatchUiEvents(void);
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JaySynthAudioProcessor);
|
||||
};
|
||||
|
||||
+5
-5
@@ -139,7 +139,7 @@ void BLIT_ModInit(blit_common_t *pCom)
|
||||
synth_float_t x, dx, a, b, blit, blep;
|
||||
|
||||
// Calculate table sizes
|
||||
pCom->pTableSizes = (UINT32*)malloc(BLIT_NUM_HARM_MAX*sizeof(UINT32));
|
||||
pCom->pTableSizes = (UINT32*)SynthCheckAlloc(malloc(BLIT_NUM_HARM_MAX*sizeof(UINT32)));
|
||||
for (m=0; m < BLIT_NUM_HARM_MAX; m++)
|
||||
{
|
||||
pCom->pTableSizes[m] = m*BLIT_TABLE_OVERSAMPLING;
|
||||
@@ -148,18 +148,18 @@ void BLIT_ModInit(blit_common_t *pCom)
|
||||
}
|
||||
|
||||
// Generate Kaiser window
|
||||
ppKaiser = (synth_float_t**)malloc(BLIT_NUM_HARM_MAX*sizeof(synth_float_t*));
|
||||
ppKaiser = (synth_float_t**)SynthCheckAlloc(malloc(BLIT_NUM_HARM_MAX*sizeof(synth_float_t*)));
|
||||
for (m=0; m < BLIT_NUM_HARM_MAX; m++)
|
||||
{
|
||||
ppKaiser[m] = (synth_float_t*)malloc(pCom->pTableSizes[m]*sizeof(synth_float_t));
|
||||
ppKaiser[m] = (synth_float_t*)SynthCheckAlloc(malloc(pCom->pTableSizes[m]*sizeof(synth_float_t)));
|
||||
CalcKaiser(NULL, ppKaiser[m], 8.f, pCom->pTableSizes[m]);
|
||||
}
|
||||
|
||||
// Allocate BLIT table
|
||||
pCom->ppBLEP = (synth_float_t**)malloc(BLIT_NUM_HARM_MAX*sizeof(synth_float_t*));
|
||||
pCom->ppBLEP = (synth_float_t**)SynthCheckAlloc(malloc(BLIT_NUM_HARM_MAX*sizeof(synth_float_t*)));
|
||||
for (m=0; m < BLIT_NUM_HARM_MAX; m++)
|
||||
{
|
||||
pCom->ppBLEP[m] = (synth_float_t*)malloc(pCom->pTableSizes[m]*sizeof(synth_float_t));
|
||||
pCom->ppBLEP[m] = (synth_float_t*)SynthCheckAlloc(malloc(pCom->pTableSizes[m]*sizeof(synth_float_t)));
|
||||
}
|
||||
|
||||
// Generate BLIT
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ void ENV_SetBufsize(envgen_t *pObj, UINT32 size)
|
||||
if (!size)
|
||||
return;
|
||||
|
||||
pObj->pOut = (synth_float_t*)malloc(pObj->bufsize*sizeof(synth_float_t));
|
||||
pObj->pOut = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
}
|
||||
|
||||
void ENV_SetFS(envgen_t *pObj, synth_float_t fs)
|
||||
|
||||
+3
-3
@@ -276,9 +276,9 @@ void LFO_SetBufsize(lfo_t *pObj, UINT32 size)
|
||||
if (!size)
|
||||
return;
|
||||
|
||||
pObj->pOut = (synth_float_t*)malloc(pObj->bufsize*sizeof(synth_float_t));
|
||||
pObj->pPhase = (sync_result_t*)malloc(pObj->bufsize*sizeof(sync_result_t));
|
||||
pObj->pWave = (synth_float_t*)malloc(pObj->bufsize*sizeof(synth_float_t));
|
||||
pObj->pOut = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
pObj->pPhase = (sync_result_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(sync_result_t)));
|
||||
pObj->pWave = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -986,7 +986,7 @@ void paramInfoInit(param_info_t *pObj, UINT32 id, const char *pName)
|
||||
pObj->pName = NULL;
|
||||
if (pName)
|
||||
{
|
||||
pObj->pName = (char*)malloc(strlen(pName)+1);
|
||||
pObj->pName = (char*)SynthCheckAlloc(malloc(strlen(pName)+1));
|
||||
memcpy(pObj->pName, pName, strlen(pName)+1);
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -6,6 +6,7 @@
|
||||
#endif
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// internal funcs
|
||||
@@ -14,6 +15,17 @@
|
||||
// Exported functions
|
||||
// --------------------------------------------------------------
|
||||
|
||||
void *SynthCheckAlloc(void *ptr)
|
||||
{
|
||||
if (!ptr)
|
||||
{
|
||||
fprintf(stderr, "JaySynth: out of memory, aborting.\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void SynthDebug(const char *fmtstr, ...)
|
||||
{
|
||||
#ifndef SYNTH_DEBUG
|
||||
@@ -27,7 +39,7 @@ void SynthDebug(const char *fmtstr, ...)
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmtstr);
|
||||
vsprintf(buf, fmtstr, args);
|
||||
vsnprintf(buf, sizeof(buf), fmtstr, args);
|
||||
va_end(args);
|
||||
#ifdef WIN32
|
||||
OutputDebugString(buf);
|
||||
|
||||
@@ -55,6 +55,11 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
void SynthDebug(const char *fmtstr, ...);
|
||||
|
||||
// Checks the result of a malloc() call. Aborts with a diagnostic instead of
|
||||
// returning NULL, so an allocation failure never turns into a silent
|
||||
// null-pointer dereference deep inside DSP processing.
|
||||
void *SynthCheckAlloc(void *ptr);
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
+8
-4
@@ -55,8 +55,8 @@ void VCF_ModInit(vcf_t *pObj)
|
||||
{
|
||||
vcf_common_t *pCom = pObj->pCom;
|
||||
|
||||
pCom->pLUT_cos = (synth_float_t*)malloc(FILTER_TABLE_SIZE*sizeof(synth_float_t));
|
||||
pCom->pLUT_sin = (synth_float_t*)malloc(FILTER_TABLE_SIZE*sizeof(synth_float_t));
|
||||
pCom->pLUT_cos = (synth_float_t*)SynthCheckAlloc(malloc(FILTER_TABLE_SIZE*sizeof(synth_float_t)));
|
||||
pCom->pLUT_sin = (synth_float_t*)SynthCheckAlloc(malloc(FILTER_TABLE_SIZE*sizeof(synth_float_t)));
|
||||
}
|
||||
|
||||
void VCF_ModFree(vcf_t *pObj)
|
||||
@@ -141,8 +141,12 @@ void VCF_SetBufsize(vcf_t *pObj, UINT32 size)
|
||||
if (!size)
|
||||
return;
|
||||
|
||||
pObj->pOut = (synth_float_t*)malloc(pObj->bufsize*sizeof(synth_float_t));
|
||||
pObj->pCoef = (vcf_coef_t*)malloc(pObj->bufsize*sizeof(vcf_coef_t));
|
||||
pObj->pOut = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
// VCF_CalcCoeff_*() advances the coefficient pointer once per
|
||||
// (sample, filter-section) pair - a 4th-order filter uses
|
||||
// FILTER_MAX_ORDER/2 cascaded 2nd-order sections per sample, so the
|
||||
// buffer needs that many times bufsize entries, not just bufsize.
|
||||
pObj->pCoef = (vcf_coef_t*)SynthCheckAlloc(malloc(pObj->bufsize*(FILTER_MAX_ORDER/2)*sizeof(vcf_coef_t)));
|
||||
pObj->pCoeff_last = pObj->pCoef;
|
||||
VCF_Coeff_update(pObj);
|
||||
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ void VCO_SetBufsize(osc_t *pObj, UINT32 size)
|
||||
if (!size)
|
||||
return;
|
||||
|
||||
pObj->pOut = (synth_float_t*)malloc(pObj->bufsize*sizeof(synth_float_t));
|
||||
pObj->pOut = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
|
||||
}
|
||||
|
||||
|
||||
+91
-13
@@ -121,6 +121,21 @@ void VoiceInit(voice_t *pObj, voice_common_t *pCom, UINT32 id, synth_float_t fs)
|
||||
|
||||
pObj->pBuf_Q = NULL;
|
||||
|
||||
for (i = 0; i < (INT32)VOICE_NUM_OSC; i++)
|
||||
{
|
||||
pObj->pBuf_VCO_fmout[i] = NULL;
|
||||
pObj->pBuf_vco_pitch[i] = NULL;
|
||||
pObj->pBuf_osc_out_smoothed[i] = NULL;
|
||||
}
|
||||
pObj->pBuf_vco_pwm = NULL;
|
||||
pObj->pBuf_vco_fm = NULL;
|
||||
pObj->pBuf_vco_am = NULL;
|
||||
pObj->pBuf_vcf_fm = NULL;
|
||||
pObj->pBuf_vcf_qm = NULL;
|
||||
pObj->pBuf_vca_am = NULL;
|
||||
pObj->pBuf_vca_pan = NULL;
|
||||
pObj->pBuf_osc_out = NULL;
|
||||
|
||||
VoiceSetBufsize(pObj, SYNTH_MAX_BUFSIZE);
|
||||
}
|
||||
|
||||
@@ -249,11 +264,65 @@ void VoiceSetBufsize(voice_t *pObj, UINT32 size)
|
||||
|
||||
pObj->pBuf_Q = NULL;
|
||||
|
||||
for (i = 0; i < (INT32)VOICE_NUM_OSC; i++)
|
||||
{
|
||||
if (pObj->pBuf_VCO_fmout[i])
|
||||
free(pObj->pBuf_VCO_fmout[i]);
|
||||
pObj->pBuf_VCO_fmout[i] = NULL;
|
||||
|
||||
if (pObj->pBuf_vco_pitch[i])
|
||||
free(pObj->pBuf_vco_pitch[i]);
|
||||
pObj->pBuf_vco_pitch[i] = NULL;
|
||||
|
||||
if (pObj->pBuf_osc_out_smoothed[i])
|
||||
free(pObj->pBuf_osc_out_smoothed[i]);
|
||||
pObj->pBuf_osc_out_smoothed[i] = NULL;
|
||||
}
|
||||
|
||||
if (pObj->pBuf_vco_pwm) free(pObj->pBuf_vco_pwm);
|
||||
pObj->pBuf_vco_pwm = NULL;
|
||||
|
||||
if (pObj->pBuf_vco_fm) free(pObj->pBuf_vco_fm);
|
||||
pObj->pBuf_vco_fm = NULL;
|
||||
|
||||
if (pObj->pBuf_vco_am) free(pObj->pBuf_vco_am);
|
||||
pObj->pBuf_vco_am = NULL;
|
||||
|
||||
if (pObj->pBuf_vcf_fm) free(pObj->pBuf_vcf_fm);
|
||||
pObj->pBuf_vcf_fm = NULL;
|
||||
|
||||
if (pObj->pBuf_vcf_qm) free(pObj->pBuf_vcf_qm);
|
||||
pObj->pBuf_vcf_qm = NULL;
|
||||
|
||||
if (pObj->pBuf_vca_am) free(pObj->pBuf_vca_am);
|
||||
pObj->pBuf_vca_am = NULL;
|
||||
|
||||
if (pObj->pBuf_vca_pan) free(pObj->pBuf_vca_pan);
|
||||
pObj->pBuf_vca_pan = NULL;
|
||||
|
||||
if (pObj->pBuf_osc_out) free(pObj->pBuf_osc_out);
|
||||
pObj->pBuf_osc_out = NULL;
|
||||
|
||||
if (!size)
|
||||
return;
|
||||
|
||||
pObj->pBuf_Q = (synth_float_t*)malloc(pObj->bufsize*sizeof(synth_float_t));
|
||||
pObj->pBuf_Q = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
|
||||
for (i = 0; i < (INT32)VOICE_NUM_OSC; i++)
|
||||
{
|
||||
pObj->pBuf_VCO_fmout[i] = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
pObj->pBuf_vco_pitch[i] = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
pObj->pBuf_osc_out_smoothed[i] = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
}
|
||||
|
||||
pObj->pBuf_vco_pwm = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
pObj->pBuf_vco_fm = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
pObj->pBuf_vco_am = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
pObj->pBuf_vcf_fm = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
pObj->pBuf_vcf_qm = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
pObj->pBuf_vca_am = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
pObj->pBuf_vca_pan = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
pObj->pBuf_osc_out = (synth_float_t*)SynthCheckAlloc(malloc(pObj->bufsize*sizeof(synth_float_t)));
|
||||
}
|
||||
|
||||
void VoiceEvent(voice_t *pObj, UINT32 type)
|
||||
@@ -844,18 +913,27 @@ void VoiceProcessDataV(voice_t *pObj, float *pOut1, float *pOut2, UINT32 len)
|
||||
synth_float_t *pModSrc[VOICE_NUM_MOD_SOURCES];
|
||||
voice_common_t *pCom = pObj->pCom;
|
||||
|
||||
// Temp Variables
|
||||
synth_float_t VCO_fmout[VOICE_NUM_OSC][SYNTH_MAX_BUFSIZE];
|
||||
synth_float_t vco_pwm[SYNTH_MAX_BUFSIZE];
|
||||
synth_float_t vco_fm[SYNTH_MAX_BUFSIZE];
|
||||
synth_float_t vco_am[SYNTH_MAX_BUFSIZE];
|
||||
synth_float_t vcf_fm[SYNTH_MAX_BUFSIZE];
|
||||
synth_float_t vcf_qm[SYNTH_MAX_BUFSIZE];
|
||||
synth_float_t vca_am[SYNTH_MAX_BUFSIZE];
|
||||
synth_float_t vca_pan[SYNTH_MAX_BUFSIZE];
|
||||
synth_float_t vco_pitch[VOICE_NUM_OSC][SYNTH_MAX_BUFSIZE];
|
||||
synth_float_t osc_out[SYNTH_MAX_BUFSIZE];
|
||||
synth_float_t osc_out_smoothed[VOICE_NUM_OSC][SYNTH_MAX_BUFSIZE];
|
||||
// Temp Variables - these point at persistent per-voice buffers (allocated
|
||||
// in VoiceSetBufsize) rather than being call-local arrays, to avoid
|
||||
// ~450KB-900KB of stack allocation on every audio block.
|
||||
synth_float_t *VCO_fmout[VOICE_NUM_OSC];
|
||||
synth_float_t *vco_pwm = pObj->pBuf_vco_pwm;
|
||||
synth_float_t *vco_fm = pObj->pBuf_vco_fm;
|
||||
synth_float_t *vco_am = pObj->pBuf_vco_am;
|
||||
synth_float_t *vcf_fm = pObj->pBuf_vcf_fm;
|
||||
synth_float_t *vcf_qm = pObj->pBuf_vcf_qm;
|
||||
synth_float_t *vca_am = pObj->pBuf_vca_am;
|
||||
synth_float_t *vca_pan = pObj->pBuf_vca_pan;
|
||||
synth_float_t *vco_pitch[VOICE_NUM_OSC];
|
||||
synth_float_t *osc_out = pObj->pBuf_osc_out;
|
||||
synth_float_t *osc_out_smoothed[VOICE_NUM_OSC];
|
||||
|
||||
for (i = 0; i < VOICE_NUM_OSC; i++)
|
||||
{
|
||||
VCO_fmout[i] = pObj->pBuf_VCO_fmout[i];
|
||||
vco_pitch[i] = pObj->pBuf_vco_pitch[i];
|
||||
osc_out_smoothed[i] = pObj->pBuf_osc_out_smoothed[i];
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Process parameter smoothing
|
||||
|
||||
@@ -305,6 +305,20 @@ typedef struct _svoice_t
|
||||
smooth_t smooth_vco_enable[VOICE_NUM_OSC];
|
||||
smooth_t smooth_vco_portamento[VOICE_NUM_OSC];
|
||||
synth_float_t vco_pitch[VOICE_NUM_OSC];
|
||||
// Scratch buffers used only within VoiceProcessDataV (voice.c), allocated
|
||||
// once per voice (see VoiceSetBufsize) instead of as call-local arrays, to
|
||||
// avoid ~450KB-900KB of stack allocation on every audio block.
|
||||
synth_float_t *pBuf_VCO_fmout[VOICE_NUM_OSC];
|
||||
synth_float_t *pBuf_vco_pwm;
|
||||
synth_float_t *pBuf_vco_fm;
|
||||
synth_float_t *pBuf_vco_am;
|
||||
synth_float_t *pBuf_vcf_fm;
|
||||
synth_float_t *pBuf_vcf_qm;
|
||||
synth_float_t *pBuf_vca_am;
|
||||
synth_float_t *pBuf_vca_pan;
|
||||
synth_float_t *pBuf_vco_pitch[VOICE_NUM_OSC];
|
||||
synth_float_t *pBuf_osc_out;
|
||||
synth_float_t *pBuf_osc_out_smoothed[VOICE_NUM_OSC];
|
||||
// smooth_t *pParam_smoother[VOICE_NUM_PARAMS];
|
||||
// synth_float_t param_smoothed[VOICE_NUM_PARAMS][SYNTH_MAX_BUFSIZE];
|
||||
} voice_t;
|
||||
|
||||
@@ -173,10 +173,10 @@ void WT_ModInit(wt_common_t *pCom)
|
||||
// Allocate memory
|
||||
for (i=0; i < WT_NUM_WAVETABLES; i++)
|
||||
{
|
||||
pCom->ppWavetables[i] = (synth_float_t**)malloc(WT_WAVETABLE_NUM_ENTRIES*sizeof(synth_float_t*));
|
||||
pCom->ppWavetables[i] = (synth_float_t**)SynthCheckAlloc(malloc(WT_WAVETABLE_NUM_ENTRIES*sizeof(synth_float_t*)));
|
||||
for (j=0; j < WT_WAVETABLE_NUM_ENTRIES; j++)
|
||||
{
|
||||
pCom->ppWavetables[i][j] = (synth_float_t*)malloc(WT_WAVE_SIZE*sizeof(synth_float_t));
|
||||
pCom->ppWavetables[i][j] = (synth_float_t*)SynthCheckAlloc(malloc(WT_WAVE_SIZE*sizeof(synth_float_t)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user