81 Commits
Author SHA1 Message Date
jensandClaude Sonnet 5 9f26df3dbe testhost: confirm multi-channel WAV output works via Pianoteq 8 (5 out)
Resolves the open question in TODO.md about whether WavAudioFormat's
"must be 1 or 2 channels" doc comment is an enforced limit - a
5-channel plugin recorded a valid, non-silent WAV with no clamping.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JAZXxJbHD7PzdKaNE6Nuiv
2026-07-27 22:40:46 +02:00
jensandClaude Sonnet 5 c4f48f6e6d bump version to 1.1.1
Drop the -pre suffix and finalize the version. Also corrects
APP_VERSION_HEX, which had drifted out of sync with APP_VERSION since the
1.1.0 bump (stayed at 0x010001 instead of following the major/minor/patch
per-byte encoding established at 1.0.0/0x010000 and 1.0.1/0x010001); now
0x010101 to match 1.1.1. Updates JuceLibraryCode/AppConfig.h's fallback
JucePlugin_Version*/VersionString defines to match (normally overridden by
the Makefile's -D flags, but were stale at 0.5.8).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
2026-07-27 22:21:28 +02:00
jensandClaude Sonnet 5 374fc151f0 testhost: trim resolved coverage-gap note in TODO.md
The chunk-advertisement misdiagnosis is already fully disproved and
documented in tests/README.md's "corrected finding" section; condense the
TODO.md entry to a one-line resolved note instead of duplicating the full
narrative, keeping only the genuinely still-open question (why specific
old external .fxp sample files fail to apply).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
2026-07-27 21:56:33 +02:00
jens d7cf5b7399 Merge pull request 'testhost: prove the actual chunk-based state path via raw VST2 dispatcher' (#3) from testhost-chunk-coverage into master
Reviewed-on: #3
2026-07-27 21:23:26 +02:00
jensandClaude Sonnet 5 8194bda9c8 testhost: prove the actual chunk-based state path via raw VST2 dispatcher
Add Vst2RawChunk, a raw effGetChunk/effSetChunk bypass around JUCE's
VSTPluginInstance::usesChunks() gate, plus --loadBankChunk/--saveBankChunk/
--loadPatchChunk/--savePatchChunk CLI flags and a new test_chunk_roundtrip
regression test asserting the saved file is genuinely zlib/XML (0x78 header,
>1000 bytes), not a per-parameter fallback.

Building and using this bypass disproved an earlier documented finding: the
compiled VST2 wrapper does set effFlagsProgramChunks and usesChunks() is
genuinely true, so the previous "chunk-advertisement gap" theory in TODO.md
and tests/README.md was wrong. Corrected in both places. The real cause of
old bundled .fxp sample files having no effect remains open and is now
narrowed to a JaySynth-side XML/schema parsing question, not a host/plugin
capability mismatch.

Also adds a PluginHost-method-to-VST2-wiring-to-test coverage table in
tests/README.md, and documents the remaining "exercised but not asserted"
gaps (channel/param counts, DSP correctness) in TODO.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
2026-07-27 21:22:24 +02:00
jens 2a0ea726f3 Merge pull request 'Add generic headless plugin test host' (#1) from testhost into master
Reviewed-on: #1
2026-07-27 20:44:47 +02:00
jens 161f57bc4e Merge pull request 'Hardening pass: critical/high/memory/patch-bank fixes + VCF buffer overflow' (#2) from hardening into master
Reviewed-on: #2
2026-07-27 20:44:33 +02:00
jensandClaude Sonnet 5 f54689f071 Add regression suite testing JaySynth's TODO.md findings via testhost
tools/testhost/tests/run_tests.sh dynamically exercises the tracked
hardening findings using testhost itself - no GUI, sub-second wall
time:

- oversized_blocksize (Critical #5): --blockSize 16384 under a
  timeout, catching either the overflow or the deadlock the original
  bug could cause.
- nrpn_out_of_range/nrpn_in_range (Critical #1): JSON scenarios
  sending the 5-message MIDI NRPN CC sequence, once targeting the
  max 14-bit ID (16383) and once a legitimate in-range ID (500), so
  a regression that made the bounds check too aggressive would also
  show up.
- patch_save_load_roundtrip / bank_save_load_roundtrip: save a
  distinctive continuous parameter value, reload in a fresh process,
  check it round-trips within tolerance - covers the
  setCurrentProgramStateInformation no-op bug and the
  patchDecodeXml/patchImportXml fixes.
- stability_sweep: loads every bundled .fxp under extras/sounds/ and
  renders a held note through each, as a broad crash/NaN net.

Added --printParams to main.cpp (prints every parameter's index and
value) to make the round-trip tests possible - this was also already
a noted nice-to-have in testhost/TODO.md.

While building the oversized_blocksize test, found and fixed (on the
hardening branch, commit d713ca5) a second, previously-unknown bug:
VCF_CalcCoeff_LPF/_HPF/_BPF advance the coefficient buffer pointer
once per (sample, filter-section) pair, but the buffer was only ever
allocated for bufsize samples, not bufsize*sections - a 4th-order
filter overflowed it for any block between 4097 and 8192 samples.
Found via bisecting the crashing block size then confirming with an
ASan build; see tests/README.md's debugging-notes section for exactly
how, including the ASan symbolizer hang encountered along the way and
the workaround.

Also discovered (documented in tools/testhost/TODO.md's Investigate
section, not fixed - out of scope): JaySynth's compiled VST2 wrapper
never advertises chunk support to the host, so real .fxp/.fxb sample
files have no effect when loaded through the generic AudioProcessor
state API. The round-trip tests work around this by using testhost's
own save output as the fixture rather than an external sample file.

All 10 checks pass against the current hardening-branch build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
2026-07-27 20:06:44 +02:00
jensandClaude Sonnet 5 d713ca50bb Fix VCF coefficient buffer undersized for 4th-order filters
Found while building tools/testhost's regression suite for the
Critical #5 block-size fix: VCF_CalcCoeff_LPF/_HPF/_BPF advance the
coefficient pointer once per (sample, filter-section) pair, but a
4th-order filter uses FILTER_MAX_ORDER/2 = 2 cascaded 2nd-order
sections per sample. VCF_SetBufsize only allocated pCoef with bufsize
entries, not bufsize*sections, so any host block between 4097 and
8192 samples with a 4th-order filter selected wrote past the
allocation.

Confirmed with AddressSanitizer before the fix: heap-buffer-overflow,
WRITE of size 8, exactly 8 bytes past a 393216-byte (8192 x
sizeof(vcf_coef_t)) allocation. Fixed by sizing pCoef for the worst
case. Re-verified with ASan across blockSize 4097/8192/16384/20000 -
zero errors after the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
2026-07-27 20:02:04 +02:00
jensandClaude Sonnet 5 ab29ad2820 Add README and TODO tracking for the testhost tool
- README.md: what the tool is and isn't (generic, not JaySynth-specific),
  build instructions, CLI quick-start, full JSON scenario schema/field
  reference, the patch/bank state-format caveat (this tool's load/save
  goes through JUCE's host-side AudioPluginInstance state calls, not
  JaySynth's own GUI-triggered loadPatchFromFile/patchImportXml - a
  different code path worth knowing about), and an architecture summary.
- TODO.md: tracks what's deferred by design (LV2/VST3/LADSPA format
  registration, Windows build), test coverage gaps (stereo effects
  untested, no regression-baseline tooling, no CI wiring, no JSON-parser
  unit tests, no polyphony testing), two things worth investigating
  (a non-fatal DPF plugin assertion seen on load, an unconfirmed
  WavAudioFormat channel-count limit), and one nice-to-have.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
2026-07-27 19:31:54 +02:00
jensandClaude Sonnet 5 39353751e6 Add JSON test scenarios, patch/bank scripting, and performance stats (Phases 2-4)
- TestScenario: parses a JSON file describing sample rate/block size/
  duration, an input signal config (silence/sine/noise/impulse), and a
  timed sequence of events (noteOn/noteOff/controller/param/loadPatch/
  loadBank/savePatch/saveBank), sorted by sample position. Relative
  file paths inside the JSON resolve against the scenario file's own
  directory, not the process cwd, so scenario files are portable.
  Nothing here is JaySynth-specific: an event type a given plugin
  doesn't care about (e.g. MIDI sent to a plugin with no MIDI input)
  is simply inert, since AudioProcessor/MidiBuffer already tolerate
  that generically.
- main.cpp: unified the ad-hoc CLI flags (--note/--param/--input/etc.)
  and --scenario JSON files through one render loop, by synthesizing
  a TestScenario from the CLI flags when no JSON file is given. Added
  --bank/--savePatch/--saveBank for the ad-hoc path (loadPatch/wav
  already existed). Non-MIDI events (param/patch/bank) scheduled
  inside a block apply at the start of that block; MIDI events keep
  full per-sample timing via MidiBuffer's own offset parameter.
- PerformanceStats: wraps each processBlock call with a high-resolution
  timer, reports min/mean/max render time and a real-time factor
  (audio seconds rendered / wall-clock seconds), plus optional
  per-block CSV export for tracking regressions over time. Never
  paces the loop to real-time - the point is to run faster than that.

Verified end-to-end:
- Ad-hoc CLI path against JaySynth.so still renders correctly and now
  reports performance (e.g. ~74x real-time on this machine).
- A JSON scenario driving JaySynth (timed noteOn/param/noteOff/
  savePatch) produces correct WAV output and a valid saved patch file.
- That saved patch round-trips back in via --patch and renders
  cleanly (proves the save/load path is consistent, not just
  one-directional).
- A JSON scenario driving a real third-party effect (ZamComp-vst.so,
  2 in/1 out - a genuinely asymmetric channel count, not just "0 in"
  or "stereo") with generated pink noise and a param event produces
  correct mono output at >1000x real-time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
2026-07-27 19:19:08 +02:00
jensandClaude Sonnet 5 97b1c34cee Add generic headless plugin test host (Phase 1: core + audio I/O)
tools/testhost/ is a standalone CLI executable, not a JaySynth-specific
tool: it drives any VST2 plugin offline (no audio device, no GUI event
loop) via JUCE's own format-agnostic AudioPluginFormatManager /
AudioPluginInstance hosting API, so adding another format later (VST3,
LADSPA, eventually LV2) is just another addFormat() call - none of the
host logic changes.

- PluginHost: format-agnostic load/prepare/process/release, parameter
  get/set, and patch/bank state I/O, all delegating straight to
  AudioProcessor. Always queries the plugin's real input/output channel
  counts rather than assuming "0 in" (instrument) or a fixed channel
  layout - the same code path drives JaySynth (0 in/2 out, MIDI-driven)
  and a stereo/mono effect identically.
- AudioInputSource: fills the plugin's input channels with silence
  (default, correct no-op for instruments), a sine tone, white/pink
  noise, or an impulse - what makes the host equally useful for
  effect plugins, which need a real input signal to do anything.
- WavRecorder: records the plugin's actual output channel count to a
  WAV file via WavAudioFormat/AudioFormatWriter.
- main.cpp: CLI (--plugin/--patch/--param/--note/--input/--duration/
  --wav/--sampleRate/--blockSize), with basic NaN/Inf output detection.

Has its own JuceLibraryCode-equivalent package that compiles only the
module amalgams needed for hosting (not juce_audio_plugin_client,
which is the plugin-side VST entry point and would collide with this
executable's own main()), reusing the already-vendored
sdk/juce/JUCE-3.1.1 rather than a second JUCE copy - the host and any
plugin it loads are decoupled at the VST2 ABI, not the JUCE source
level, so this doesn't need to track JaySynth's own future JUCE
version bump.

Verified: builds clean (no new warnings) via
`MAKE_HOME=... make -C tools/testhost`. Smoke-tested against two very
different real plugins:
- build/linux/release/JaySynth.so (0 in/2 out, MIDI-driven): --note
  60 100 --duration 2 --wav produces correct-length, non-silent,
  NaN-free stereo audio.
- /usr/lib/vst/ZamDelay-vst.so (1 in/1 out, real third-party VST2
  effect, no MIDI): --input pink --duration 2 --wav correctly sizes
  input/output to mono and produces non-silent, NaN-free audio -
  proof the host is generic, not JaySynth-shaped.
- /usr/lib/vst/ZamEQ2-vst.so: the host's own NaN/Inf detection caught
  a real NaN in this plugin's default state on first render - exactly
  the kind of regression this tool exists to catch without a GUI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
2026-07-27 19:09:06 +02:00
jensandClaude Sonnet 5 cee75c523b Fix Patch/Bank import-export issues from TODO.md
- patchImportXml: handle 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 xml2. Switched
  its CC-table import to import_midi_cc, matching the convention
  already used (and exercised) by bankDecodeXml_legacy. loadPatchFromFile's
  .xmp branch now also accepts legacy-rooted files.
- setCurrentProgramStateInformation: fix a related bug found in
  passing - it was passing patchImportXml an XML node one level too
  deep, making the VST2 "copy plugin state to another track" feature
  a complete no-op.
- Fix the fundamentally broken fxb/fxp magic-number check: it compared
  raw file bytes (as text, e.g. "CcnK") against JUCE's int-to-decimal-
  text String constructor (e.g. "1130589771"), which can never match.
  This meant the entire opaque .fxb/.fxp load path was dead code.
  Fixed using ByteOrder::bigEndianInt, matching the pattern JUCE's own
  VST3 wrapper uses for the identical struct. "Regular" (non-chunk)
  .fxb/.fxp is still unsupported, but now shows an AlertWindow
  explaining why instead of silently doing nothing.
- Implement .fxp/.fxb saving using the same opaque-chunk format the
  load path expects, reusing getCurrentProgramStateInformation/
  getStateInformation for the payload.
- Deduplicate patchDecodeXml/patchDecodeXml_legacy (the latter now
  just delegates to the former).

Verified with clean debug/release builds and a standalone round-trip
test of the binary format logic (magic detection, byte order,
size/payload round-trip, safe rejection of truncated/bogus files).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
2026-07-27 18:32:52 +02:00
jensandClaude Sonnet 5 54a67562db Fix Memory-management issues from TODO.md
- JaySynth's destructor now uses delete[] to match every new T[n]
  allocation (m_pVoices, pPer_voice_controls, pCurrNoteInfos,
  humanize_voice_param[i], ppHumanizedSliders[i], m_ppAudioThread,
  m_ppEventAudioThreadRdy), fixing undefined behavior from the
  mismatched scalar delete.
- Found and fixed the same new[]/delete mismatch pattern via
  ScopedPointer in PluginProcessor.cpp: ScopedPointer always calls
  scalar delete (per JUCE's own doc comment "do not give it an array
  to hold!"), so ScopedPointer<char> holding a new char[...] in
  setStateInformation/setCurrentProgramStateInformation had the same
  bug. Replaced both with HeapBlock<char>, JUCE's array-owning,
  malloc/free-backed smart pointer.
- Added a shared SynthCheckAlloc() helper (synth_defs.h/synth_debug.c)
  that aborts with a diagnostic instead of returning NULL, and wrapped
  all 24 malloc call sites across the C DSP core (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-thread overhead.

Verified with clean debug and release builds (no new warnings/errors)
and a full link.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
2026-07-27 18:19:56 +02:00
jensandClaude Sonnet 5 608ae003df Fix High-severity real-time audio-thread safety issues from TODO.md
- synthChanged() no longer builds JaySynthActionListener Strings and
  calls sendActionMessage directly on the audio thread. It now pushes
  a small POD event into a lock-free SPSC queue (JUCE's AbstractFifo),
  drained by the existing 10ms UI timer on the message thread, which
  does the String-building/posting there instead.
- VoiceProcessDataV's ~450KB-900KB of SYNTH_MAX_BUFSIZE-sized local
  arrays are now persistent per-voice buffers allocated once in
  VoiceSetBufsize (freed in VoiceFree), matching the pattern already
  used for pBuf_Q and by vcf.c/env.c/lfo.c, instead of being
  reallocated on the stack on every audio block.
- SynthDebug uses vsnprintf instead of vsprintf, bounding writes to
  its 1024-byte buffer.

Verified with clean debug and release builds (no new warnings) and a
live playback smoke test in Reaper.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
2026-07-27 18:08:35 +02:00
jensandClaude Sonnet 5 e151036963 Add TODO.md tracking hardening review findings
Records the critical fixes already applied (commit a65b71a) and the
remaining high/memory/patch-export/design findings from the code
review, for later reference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
2026-07-27 17:26:41 +02:00
jensandClaude Sonnet 5 a65b71ab86 Fix critical memory-safety and concurrency bugs found in code review
- handleController: bounds-check NRPN/CC controller ID before indexing
  last_midiCC_info[] (NRPN IDs can reach 16383, array is sized 1024)
- JaySynthMidiCC::add/remove: also reject negative controller IDs coming
  from patch/bank file data, not just out-of-range-high ones
- bankImportXml/loadPatchFromFile: null-check XML elements after
  getFirstChildElement()/XmlDocument::parse() before dereferencing, so
  malformed/truncated patch or bank files no longer crash on load
- loadBankFromFile/loadPatchFromFile: validate the loaded file is large
  enough for the fxBank/fxProgram header, and that the embedded chunk
  size fits within the file, before reading through the raw pointer
- processBlock/JaySynth::renderNextBlock: host blocks larger than
  SYNTH_MAX_BUFSIZE no longer overflow the fixed-size stack work buffers
  or deadlock the render worker threads; oversized blocks are now
  rendered in multiple SYNTH_MAX_BUFSIZE-sized passes (pending MIDI
  events are correctly held over between passes)
- JaySynth constructor: guard against createInputStream() returning null
  when the wavetable file can't be opened
- JaySynth::setParameter/setControl/setPerVoiceControl/setParam/
  ClearControls: take the synth's lock, matching renderNextBlock and the
  note/controller handlers, to close a data race between parameter
  changes (UI/host automation) and audio-thread rendering

Verified with a full rebuild (make) producing JaySynth.so with no new
warnings or errors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
2026-07-27 17:22:46 +02:00
jensandClaude Sonnet 5 468b4c36de Add CLAUDE.md with codebase architecture and build guidance
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
2026-07-27 17:14:24 +02:00
jens e9a5784ed1 refactored pacth import/export 2026-05-10 16:10:51 +02:00
jens f72a89ed61 no setCurrentProgram on getCurrentSatte 2026-05-10 13:55:47 +02:00
jens 17b12fe3f1 fix midi controller dialog 2026-05-10 13:37:38 +02:00
jens 255858cee8 - combo boxes have midi controller dialog
- reduce update of parameter sliders during Midi Controller Dialog
2026-05-10 13:08:35 +02:00
jens 15bcf87529 improved assignment of midi controllers 2026-05-10 12:11:34 +02:00
jens 31a0c4c009 fixed NRPN 2026-05-10 10:53:26 +02:00
jens 6e67193c3d - robusten and refactored NRPN
- increased number of midicontrollers from 128 to 1024
2026-05-10 10:36:24 +02:00
jens cbdb18e6ba handleController for CC and NPRN 2026-05-09 22:11:40 +02:00
jens bc2020e612 NRPN: normalized by max value 2026-05-09 22:04:21 +02:00
jens 8ce2db511b added Midi NRPN 2026-05-09 20:37:17 +02:00
jens 6be1b4a911 updated .gitignore 2026-05-09 08:37:16 +02:00
jens a8e6593eed used -pre suffix for version 2026-05-09 08:34:24 +02:00
jens c392104aec - refactored kml import/export
- added ability to import patch file with midicc defined without patch parameter
- fixed notes cut off by calling getStateInformation()
- disabled limiter
- increased version to v1.1.0-pre
2026-05-09 08:34:01 +02:00
jens 0de8c05f00 fixed behavior whenn unison and humanize is set over midi 2026-05-09 08:30:12 +02:00
jens 597c70bb53 fixed eval_blep 2026-05-09 08:29:00 +02:00
jens c7c9366dbb - refactored
- remove DC
2025-08-22 14:49:50 +02:00
jens 2ab8c68c9b m-files fixed wavwrite 2025-08-22 13:39:23 +02:00
jens 21411d2c56 m-files compile clean 2025-08-21 21:09:48 +02:00
jens 4f7803a8b4 added doc 2025-08-21 07:52:16 +02:00
jens 64c6745c76 added matlab 2025-08-21 07:27:52 +02:00
jens fd522e1b99 no SYNTH_DEBUG for release build 2025-08-18 21:55:31 +02:00
jens 7e11e3e72b fixed midi sync led 2025-08-18 21:42:19 +02:00
jens ed737f526f updated .gitignore 2025-08-18 18:35:13 +02:00
jens 83afc11612 - added run and install target 2025-08-18 18:32:08 +02:00
jens 8a81dd649e added web URL for later reference 2025-08-18 18:31:26 +02:00
jens 42e877c08d fixed includes 2025-08-18 18:30:55 +02:00
jens 1e0760e49b fixed includes 2025-08-18 18:29:25 +02:00
jens db4fdf68ff fixed MidiClock now working with ardour 2025-08-18 18:22:55 +02:00
jens 314b57b1ed fixing MidiClock (Zwischenstand #3) 2025-08-18 09:37:39 +02:00
jens a3ad0e989e fixing MidiClock (Zwischenstand #2) 2025-08-16 15:10:26 +02:00
jens 159fa60b08 fixing MidiClock (Zwischenstand) 2025-08-16 14:16:13 +02:00
jens e3fb2074f6 DSP: changed from float to double 2025-08-08 17:44:01 +02:00
jens 7fcbb9b509 Fixed code problems 2025-08-08 16:36:22 +02:00
jens 3a19db6a86 Fixed code problems 2025-08-08 16:36:09 +02:00
jens d73f172fc1 added bear target 2025-08-08 16:35:29 +02:00
jens f05e3be55f Fixed code problems 2025-08-08 16:35:04 +02:00
jens 2ec3cc9543 added run target 2025-08-08 13:54:33 +02:00
jens 0f70ea025c change submodule/make 2025-08-08 13:54:08 +02:00
jens 2ac71f33f1 added new waves 2025-08-06 22:08:46 +02:00
jens 73332a7292 refactored wavetable constants 2025-08-06 22:08:13 +02:00
jens 4bf46df821 - added 12bit wace for Microwave
- refactored
2025-08-06 21:15:12 +02:00
jens 072280c603 refactored waves 2025-08-06 07:51:29 +02:00
jens 4a4d67f5f1 - wavetable using Waldorf Microwave "wave.bin" (16bit) is broken
- wavetable uses "wave2.bin" (8bit) Waveforms
-
2025-08-05 19:36:59 +02:00
jens 12783fdfdb removed unused include 2025-08-03 20:51:34 +02:00
jens 63e1daa706 export waves to uild folder 2025-08-03 20:42:07 +02:00
jens bc8a2b5b20 cleaned up 2025-08-03 20:35:59 +02:00
jens 26c3ee4cb6 - fixed problems
- updated .gitignore
2025-08-03 17:04:57 +02:00
jens c0e651bf68 - refactored 2025-08-03 10:57:01 +02:00
jens d801ce21e8 added patch 2025-08-02 14:36:02 +02:00
jens 025818a042 [sdk/vstsdk2.4]
- refactored
- created patch
2025-08-02 14:34:53 +02:00
jens 3bda521d62 - fixed master volume
- added osc jitter to blep waveform saw, square and tri
2025-08-02 12:04:40 +02:00
jens ff6ba8a179 permit to override default make environment 2025-08-02 10:24:41 +02:00
jens 2afe0cfccc change submodule/make 2025-08-02 10:23:53 +02:00
jens e666247a45 change submodule/fir 2025-08-02 10:22:04 +02:00
jens dab1ed956f juce: use 2025-08-02 10:17:55 +02:00
jens 0dd21799f9 [VCO/Blit/Sawtooth]
- added slight random frequency deviation for more vivid sound
2025-07-31 18:15:08 +02:00
jens bcfe5e4edd fixed 2025-07-31 18:14:23 +02:00
jens 4d25a24e53 disabled limiter 2025-07-31 18:13:16 +02:00
jens 3e63bdd4ce added sounds 2025-07-31 18:01:49 +02:00
jens 465bc88e8f removed linefeed 2025-07-31 16:41:24 +02:00
jens 6e8b990391 -fixed includes 2025-07-31 16:39:38 +02:00
jens 6fc6b9f567 - added submodules 2025-07-29 20:43:25 +02:00
jens 7ef6975cb3 - adde exra sounds 2024-03-04 08:40:52 +01:00
183 changed files with 162036 additions and 791 deletions
+12
View File
@@ -0,0 +1,12 @@
build
.cache
c_cpp_properties.json
compile_commands.json
venv
.codechecker
.vscode
matlab/osc/blip/saw.wav
matlab/osc/blip/sqr.wav
matlab/osc/blip/tri.wav
octave-workspace
+6
View File
@@ -0,0 +1,6 @@
[submodule "submodule/make"]
path = submodule/make
url = http://192.168.22.90:3001/jayfield/make.git
[submodule "submodule/fir"]
path = submodule/fir
url = http://192.168.22.90:3001/jayfield/fir.git
+56
View File
@@ -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.
+3 -3
View File
@@ -236,13 +236,13 @@
#define JucePlugin_EditorRequiresKeyboardFocus 0
#endif
#ifndef JucePlugin_Version
#define JucePlugin_Version 0.5.8
#define JucePlugin_Version 1.1.1
#endif
#ifndef JucePlugin_VersionCode
#define JucePlugin_VersionCode 0x00050008
#define JucePlugin_VersionCode 0x010101
#endif
#ifndef JucePlugin_VersionString
#define JucePlugin_VersionString "0.5.8"
#define JucePlugin_VersionString "1.1.1"
#endif
#ifndef JucePlugin_VSTUniqueID
#define JucePlugin_VSTUniqueID JucePlugin_PluginCode
+31 -15
View File
@@ -1,15 +1,19 @@
include $(MAKE_HOME)/defaults.mk
include make/defaults.mk
CONFIG ?= release
TARGET ?= JaySynth.so
LIBSRC_PATH ?= $(realpath ../../libsrc)
APP_VERSION := 1.1.1
APP_VERSION_HEX := 0x010101
LIBSRC_PATH ?= $(realpath ./submodule)
JUCE_PATH := $(realpath ./sdk/juce/JUCE-3.1.1)
VST2_SDK_PATH ?= $(realpath ./sdk/vst/vstsdk2.4)
JUCE_LIBCODE_PATH ?= $(realpath ./JuceLibraryCode)
SRC_PATH := $(realpath ./src)
NAME =: JaySynth
DEFINES := -Dradio_float_t=float -Dfir_float_t=float -Dsynth_float_t=float
DEFINES := -Dradio_float_t=double -Dfir_float_t=double -Dsynth_float_t=double
DEFINES += -DHAVE_LROUND
CFLAGS += -fPIC
CXXFLAGS += -fPIC
@@ -19,20 +23,21 @@ LIBS := -lstdc++ -lm -lGL -lX11 -lXext -lXinerama -lasound -ldl -lfreetype -lpth
LDFLAGS += $(TARGET_ARCH) $(LIBDIR) -L/usr/X11R6/lib/ -shared
DEFINES += -D__cdecl=""
DEFINES += -DJUCE_GCC=1 -DLINUX=1 -DJUCE_APP_VERSION=1.0.0 -DJUCE_APP_VERSION_HEX=0x10000
DEFINES_debug += -DDEBUG=1 -D_DEBUG=1
DEFINES += -DJUCE_GCC=1 -DLINUX=1 -DJUCE_APP_VERSION=$(APP_VERSION) -DJUCE_APP_VERSION_HEX=$(APP_VERSION_HEX)
DEFINES_debug += -DDEBUG=1 -D_DEBUG=1 -DSYNTH_DEBUG
DEFINES_release += -DNDEBUG
DEFINES += -DJucePlugin_Version=1.0.0
DEFINES += -DJucePlugin_VersionCode=0x01000000
DEFINES += -DJucePlugin_VersionString=\"1.0.0\"
DEFINES += -DJucePlugin_Version=$(APP_VERSION)
DEFINES += -DJucePlugin_VersionCode=$(APP_VERSION_HEX)
DEFINES += -DJucePlugin_VersionString=\"$(APP_VERSION)\"
INCLUDES += -I $(JUCE_PATH) -I $(JUCE_PATH)/modules -I $(VST2_SDK_PATH) -I $(LIBSRC_PATH)
INCLUDES += -I $(JUCE_PATH) -I $(JUCE_PATH)/modules -I $(VST2_SDK_PATH) -I $(LIBSRC_PATH) -I $(JUCE_LIBCODE_PATH) -I $(SRC_PATH)
PACKAGES := JuceLibraryCode Source Synth Fir
PACKAGES := JuceLibraryCode plug synth fir
export
all: app
app: objects link
@@ -40,16 +45,27 @@ app: objects link
objects: ${PACKAGES}
Synth:
@$(MAKE) -C Source/synth
synth:
@$(MAKE) -C $(SRC_PATH)/synth
Source:
@$(MAKE) -C $@
plug:
@$(MAKE) -C $(SRC_PATH)/plug
JuceLibraryCode:
@$(MAKE) -C $@
Fir:
fir:
@$(MAKE) -C $(LIBSRC_PATH)/fir
bear:
make distclean
bear --output compile_commands.json -- $(MAKE)
run: install
carla ~/jaysynth+tal.carxp
install: app
cp extras/waves/waves_microwave_v2.0_8bit ~/.vst/jaysynth/waves.bin
cp build/linux/$(CONFIG)/JaySynth.so ~/.vst/jaysynth/JaySynth.so
include $(MAKE_HOME)/link.mk
-189
View File
@@ -1,189 +0,0 @@
/*
//#######################################################################################
//Class to insert Midiclock messages into a given MidiBuffer, suitable for block
//based audio callbacks. The class can act on position jumps e.g. "Loops" by sending a
//positioning message.
//NOTE: No ballistik came in to play so I wouldn't recommend using it to drive a mechanical
//tape deck!!!
//
//solar3d-software, April- 10- 2012
//#######################################################################################
*/
#include "JK_MidiClock.h"
JK_MidiClock::JK_MidiClock()
:wasPlaying(false),
syncPpqPosition(-999.0),
posChangeThreshold(0.001),
ppqToStartSyncAt(0.0),
followSongPosition(true),
syncFlag(0),
ppqOffset(0)
{
//Prepare Midi messages to avoid blocking the caller of generateMidiclock()!
continueMessage = new MidiMessage(MidiMessage::midiContinue());
stopMessage = new MidiMessage(MidiMessage::midiStop());
clockMessage = new MidiMessage(MidiMessage::midiClock());
songPositionMessage = new MidiMessage(MidiMessage::songPositionPointer(0));
}
//=================================================================================================
void JK_MidiClock::generateMidiclock(AudioPlayHead::CurrentPositionInfo &lastPosInfo,
MidiBuffer* midiBuffer, const int bufferSize, const double sampleRate)
{
//###################################Some explanation about musical tempo #################
//A Time Signature, is two numbers, one on top of the other. The numerator describes the #
//number of Beats in a Bar, while the denominator describes of what note value a Beat is. #
//So 4/4 would be four quarter-notes per Bar, while 4/2 would be four half-notes per Bar, #
//4/8 would be four eighth-notes per Bar, and 2/4 would be two quarter-notes per Bar. #
//#########################################################################################
if (midiBuffer != nullptr)
{
//PPQ value of one sample
const double ppqPerSample = (lastPosInfo.bpm / 60.0) / sampleRate;
//PPQ offset to compensate Midi interface latency
double hostPpqPosition = lastPosInfo.ppqPosition + ppqOffset * ppqPerSample;;
if (lastPosInfo.isPlaying || lastPosInfo.isRecording)
{
if (! wasPlaying)
{
//set the point where to start the slave
ppqToStartSyncAt = getNearestSixteenthInPPQ(hostPpqPosition);
//Special case: Master is set to always start playback from the previous start position...
if (positionJumped(syncPpqPosition, hostPpqPosition, sampleRate, ppqPerSample))
{
//Cue Midiclock slave to the nearest sixteenth note to new start position
//because the one calculated in stop mode isn't valid anymore.
sendSongPositionPointerMessage(ppqToStartSyncAt, 0, midiBuffer);
}
}
else
{
//Position jump (loop or manually position change while playing)
if (positionJumped(syncPpqPosition, hostPpqPosition, sampleRate, ppqPerSample))
{
//set the point where to start the slave
ppqToStartSyncAt = getNearestSixteenthInPPQ(hostPpqPosition);
//User has changed position manually while playing
if (syncFlag == 0)
{
midiBuffer->addEvent(*stopMessage, 0);
sendSongPositionPointerMessage(hostPpqPosition, 0, midiBuffer);
syncFlag = startSlave_;
}
else
{
if (followSongPosition)
{
sendSongPositionPointerMessage(hostPpqPosition, 0, midiBuffer);
syncFlag = startSlave_;
}
else
syncFlag = 0;
}
}
}
for (int posInBuffer = 0; posInBuffer < bufferSize; ++posInBuffer)
{
syncPpqPosition = hostPpqPosition + (posInBuffer * ppqPerSample);
const int clockDistanceInSamples = roundToInt((60.0 * sampleRate) / (lastPosInfo.bpm * 24.0));
const int64 hostSamplePos = roundToInt64((hostPpqPosition * (60.0 / lastPosInfo.bpm)) * sampleRate);
const int64 syncSamplePos = hostSamplePos + posInBuffer;
//Some hosts like Cubase come up with a wacky ppqPosition
//that could break the timing! Best is to "wait"
//here for the right ppqPosition to jump on.
if (syncPpqPosition >= ppqToStartSyncAt)
{
if ((syncFlag & startSlave_) == startSlave_)
{
midiBuffer->addEvent(*continueMessage, posInBuffer);
syncFlag &= cycleEnd_;
}
//Cycle mode on
if (lastPosInfo.isLooping && lastPosInfo.ppqLoopStart != lastPosInfo.ppqLoopEnd)
{
const double ppqToCycleEnd = fabs(lastPosInfo.ppqLoopEnd - syncPpqPosition);
const int64 samplesToCycleEnd = roundToInt64(ppqToCycleEnd * (60.0 / lastPosInfo.bpm) * sampleRate);
if ((syncFlag & cycleEnd_) == 0)
{
if (samplesToCycleEnd <= clockDistanceInSamples) //For fine tuning tweak here
{
//We have reached the cycle- end position
//and must stop the Midiclock slave here
if (followSongPosition)
midiBuffer->addEvent(*stopMessage, posInBuffer);
syncFlag |= cycleEnd_;
}
}
}
}
//For best timing we should never interupt Midiclock messages!
//Seems that some slaves constantly adjusting their internal clock
//to Midiclock even if they are in stop mode.
if (syncSamplePos % clockDistanceInSamples == 0)
midiBuffer->addEvent(*clockMessage, posInBuffer);
}
wasPlaying = true;
}
else
{
//Send positioning message if the user has stopped or if he changed the playhead position
//manually in stop mode! This will also initially cue slave after loading plugin instance.
if (wasPlaying || positionJumped(syncPpqPosition, hostPpqPosition, sampleRate, ppqPerSample))
{
midiBuffer->addEvent(*stopMessage, 0);
sendSongPositionPointerMessage(hostPpqPosition, 0, midiBuffer);
}
syncPpqPosition = hostPpqPosition;
syncFlag = startSlave_;
wasPlaying = false;
}
}
}
//=================================================================================================
bool JK_MidiClock::positionJumped(const double lastPosInPPQ, const double currentPosInPPQ,
const double sampleRate, const double ppqPerSample)
{
//This returns true if the user has changed the playhead position manually or if
//a jump has occured! The comperator's default threshold is lastPosInPPQ +- 10ms.
if (currentPosInPPQ < lastPosInPPQ - ((posChangeThreshold * sampleRate) * ppqPerSample) ||
currentPosInPPQ > lastPosInPPQ + ((posChangeThreshold * sampleRate) * ppqPerSample))
return true;
return false;
}
//=================================================================================================
void JK_MidiClock::sendSongPositionPointerMessage(const double ppqPosition, const int posInBuffer, MidiBuffer* buffer)
{
//This will cue the slave to the NEAREST
//16th note to the given ppqPosition.
int intBeat = int(ceil(ppqPosition * 4));
uint8* pSongPositionTime((uint8*)(songPositionMessage->getRawData()));
*(pSongPositionTime + 1) = (uint8)(intBeat & 0x7f);
*(pSongPositionTime + 2) = (uint8)((intBeat & 0x3f80)>>7);
buffer->addEvent(*songPositionMessage, posInBuffer);
}
-11
View File
@@ -1,11 +0,0 @@
include $(MAKE_HOME)/defaults.mk
LIBSRC_PATH ?= $(realpath ../../../libsrc)
JUCE_PATH ?= $(realpath ../sdk/juce/JUCE-3.1.1)
VST2_SDK_PATH ?= $(realpath ../sdk/vst/vstsdk2.4)
include config.mk
INCLUDES += -I $(LIBSRC_PATH) -I $(JUCE_PATH) -I $(VST2_SDK_PATH)
include $(MAKE_HOME)/compile.mk
-10
View File
@@ -1,10 +0,0 @@
include $(MAKE_HOME)/defaults.mk
LIBSRC_PATH ?= $(realpath ../../../../libsrc)
VST2_SDK_PATH ?= $(realpath ../../sdk/vst/vstsdk2.4)
include config.mk
INCLUDES ?= -I $(LIBSRC_PATH) -I $(VST2_SDK_PATH)
include $(MAKE_HOME)/compile.mk
+37
View File
@@ -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] **~450KB900KB 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.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
Waldorf MicroWave Wellenformen und WaveTables hinzugefgt. WaveTable-Scan ber PWM.
wave.bin: Original MicroWave Wellenformen mssen als Datei ("waves.bin", 16bit, Big-Endian) im Plugin-Verzeichnis vorliegen, damit Feature freigeschaltet wird.
wave2.bin: Original PPG EVU Wellenformen 8bit
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+7
View File
@@ -0,0 +1,7 @@
include $(MAKE_HOME)/defaults.mk
# Environment
CC=clang
CCC=clang++
CXX=clang++
Vendored Executable
+53
View File
@@ -0,0 +1,53 @@
function eval_adsr(Ta, Td, Vs, Tr, ks)
% eval_adsr(0.1, 0.2, 0.2, 0.2)
fs = 1000;
a_a = -log(1-ks)/(Ta*fs);
a_d = 5/(Td*fs);
a_r = 5/(Tr*fs);
state = 0;
s = 0;
tol = 1E-4;
n = 1;
p = 0;
k = 1/ks;
while (1),
if state == 0 % Attack
s = a_a + (1-a_a)*s;
y(n) = k*s;
if s >= (ks-tol)
state = 1;
s = 1-ks;
end;
end;
if state == 1 % Decay
s = (1-a_d)*s;
y(n) = s/(1-ks);
if s <= (Vs+tol)
state = 2;
p = 0;
end;
end;
if state == 2 % sustain
y(n) = Vs;
if p >= 1*fs
state = 3;
s = Vs;
end;
p = p + 1;
end;
if state == 3 % Release
s = (1-a_r)*s;
y(n) = s;
if s <= (tol)
break;
end;
end;
n = n + 1;
end;
plot((0:n-1)/fs, y); grid;
set(gca,'xtick',[0:0.1:(n-1)/fs])
Vendored Executable
+52
View File
@@ -0,0 +1,52 @@
function eval_adsr(Ta, Td, Vs, Tr, ks)
% eval_adsr(0.1, 0.2, 0.2, 0.2)
fs = 1000;
a_a = -log(1-ks)/(Ta*fs);
a_d = 5/(Td*fs);
a_r = 5/(Tr*fs);
state = 0;
s = 0;
tol = 1E-4;
n = 1;
p = 0;
k = 1/ks;
while (1),
if state == 0 % Attack
s = a_a + (1-a_a)*s;
y(n) = s/ks;
if s >= (ks-tol)
state = 1;
end;
end;
if state == 1 % Decay
s = (1-a_d)*s;
y(n) = s/(ks);
if s <= (Vs*ks+tol)
state = 2;
p = 0;
end;
end;
if state == 2 % sustain
y(n) = Vs;
if p >= 1*fs
state = 3;
s = Vs;
end;
p = p + 1;
end;
if state == 3 % Release
s = (1-a_r)*s;
y(n) = s;
if s <= (tol)
break;
end;
end;
n = n + 1;
end;
plot((0:n-1)/fs, y); grid;
set(gca,'xtick',[0:0.1:(n-1)/fs])
Vendored Executable
+56
View File
@@ -0,0 +1,56 @@
function eval_adsr(Ta, Td, Vs, Tr)
% eval_adsr(0.1, 0.2, 0.2, 0.2)
fs = 10000;
aa = 1/(Ta*fs)
ba = (1-aa)
a_d = 5/(Td*fs)
a_r = 5/(Tr*fs)
state = 0;
s = 0;
tol = 1E-4;
ke = 1-exp(-1)
n = 0;
p = 0;
k = 1/ke;
kk = 0;
s
while (1),
if state == 0
s = aa + ba*s;
if s >= (ke-tol)
state = 1;
k = (1-Vs)/ke;
kk = Vs;
end;
end;
if state == 1
s = (1-a_d)*s;
if s <= (tol)
state = 2;
s = 1;
k = Vs;
kk = 0;
p = 0;
end;
end;
if state == 2
if p >= 0.1*fs
state = 3;
end;
p = p + 1;
end;
if state == 3
s = (1-a_r)*s;
if s <= (tol)
break;
end;
end;
n = n + 1;
y(n) = k*s + kk;
end;
plot((0:n-1)/fs, 0.8-y); grid;
Vendored Executable
+52
View File
@@ -0,0 +1,52 @@
function eval_adsr(Ta, Td, Vs, Tr)
% eval_adsr(0.1, 0.2, 0.2, 0.2)
fs = 1000;
a_a = 1/(Ta*fs);
a_d = -1/(Td*fs/log(Vs))
a_d = 5/(Td*fs)
a_r = 5/(Tr*fs)
state = 0;
s = 0;
tol = 1E-4;
target = 1;
n = 0;
p = 0;
k = 1/0.63;
while (1),
if state == 0 % Attack
s = a_a + (1-a_a)*s;
if s >= (0.63-tol)
state = 1
target = Vs;
end;
end;
if state == 1 % Decay
s = (1-a_d)*s;
if s <= (0.63*Vs+tol)
state = 2
target = 0;
p = 0;
end;
end;
if state == 2 % sustain
s = Vs/k;
if p >= 1*fs
state = 3
end;
p = p + 1;
end;
if state == 3 % Release
s = (1-a_r)*s;
if s <= (target+tol)
break;
end;
end;
n = n + 1;
y(n) = k*s;
end;
plot((0:n-1)/fs, y); grid;
+18
View File
@@ -0,0 +1,18 @@
function eval_vcf_lut()
N = 1000;
fs = 48000;
fmin = 18;
fmax = 18000;
sweepbase = 10;
filterParam = struct('sweepbase', 10, 'omega_min', 2*fmin/fs, 'omega_max', 2*fmax/fs, 'num_octaves', log(fmax/fmin)/log(sweepbase));
filterParam
cv = (0:N-1)/N;
plot (c
function cv = omega2cv(filterParam, omega)
cv = log(omega/filterParam.omega_min)/(filterParam.num_octaves * log(filterParam.sweepbase));
cv = min(1, max(0, cv));
function omega = cv2omega(filterParam, cv)
omega = filterParam.omega_min*filterParam.sweepbase ^ (filterParam.num_octaves*min(1, max(0, cv)));
+23
View File
@@ -0,0 +1,23 @@
function eval_vcf_lut()
N = 1000;
fs = 48000;
fmin = 18;
fmax = 18000;
sweepbase = 10;
filterParam = struct('sweepbase', 10, 'omega_min', fmin/fs, 'omega_max', fmax/fs, 'num_octaves', log(fmax/fmin)/log(sweepbase));
filterParam
cv = (0:N-1)/N;
subplot(2, 1, 1)
plot (cv, fs*cv2omega(filterParam, cv)); grid; legend('F'); xlabel('cv');
subplot(2, 1, 2)
plot (cv, cos(2*pi*cv2omega(filterParam, cv)), cv, sin(2*pi*cv2omega(filterParam, cv))); grid; legend('kc', 'ks'); xlabel('cv');
function cv = omega2cv(filterParam, omega)
cv = log(omega/filterParam.omega_min)/(filterParam.num_octaves * log(filterParam.sweepbase));
cv = min(1, max(0, cv));
function omega = cv2omega(filterParam, cv)
omega = filterParam.omega_min*filterParam.sweepbase .^ (filterParam.num_octaves*min(1, max(0, cv)));
+34
View File
@@ -0,0 +1,34 @@
function eval_lfo()
L = 10000;
fs = 44100;
fstart = 40;
fend = 400;
df = (fend-fstart)/L;
ip = 0 % phase of the first output sample in radians
w = freq*pi / samplerate
b1 = 2.0 * cos(w)
% Init
y1=sin(ip-w)
y2=sin(ip-2*w)
% Loop
for n=1:L,
y0 = b1*y1 - y2
y2 = y1
y1 = y0
close all
figure;
plot(1:L, vsaw, 1:L, vblit); grid;
wavwrite(0.5*vtri, fs, 'tri.wav');
wavwrite(0.5*vsqr, fs, 'sqr.wav');
wavwrite(0.5*vsaw, fs, 'saw.wav');
wavwrite(0.5*vblit, fs, 'blit.wav');
+44
View File
@@ -0,0 +1,44 @@
function eval_lfo()
L = 10000;
fs = 44100;
fstart = 44;
fend = 400;
df = (fend-fstart)/L;
ip = 0 % phase of the first output sample in radians
% Init
% Loop
f = fstart;
fchg = 1;
y1=sin(ip-f*pi / fs);
y2=sin(ip-2*f*pi / fs);
for n=1:L,
if fchg == 1,
b1 = 2.0 * cos(f*pi / fs);
fchg = 1000;
end;
y0 = b1*y1 - y2;
y2 = y1;
y1 = y0;
fchg = fchg - 1;
lfo(n) = y0;
f = f + df;
close all
end;
figure;
plot(1:L, lfo); grid;
wavwrite(0.5*lfo, fs, 'lfo.wav');
+47
View File
@@ -0,0 +1,47 @@
function eval_lfo2()
L = 80000;
fs = 48000;
fstart = 10;
fend = 1000;
df = (fend-fstart)/L;
ip = 0.0 % phase of the first output sample in radians
% Init
% Loop
f = fstart;
fchg = 0;
ylast = 0;
a = 0.5;
b = 2.0 * sin(f*pi / fs);
y0 = a*cos(2*pi*ip);
y1 = a*sin(2*pi*ip);
for n=1:L,
y0 = y0 - b*y1;
y1 = y1 + b*y0;
fchg = (mod(n, 1000) == 0);
if fchg
b = 2.0 * sin(f*pi / fs);
end
ylast = y1;
lfo(n) = y0;
f = f + df;
close all
end;
figure;
plot(1:L, lfo); grid;
wavwrite(0.5*lfo, fs, 'lfo.wav');
+47
View File
@@ -0,0 +1,47 @@
function eval_lfo2()
L = 80000;
fs = 48000;
fstart = 10;
fend = 5000;
df = (fend-fstart)/L;
ip = 0.0 % phase of the first output sample in radians
% Init
% Loop
f = fstart;
fchg = 0;
ylast = 0;
a = 0.5;
b = 2.0 * sin(f*pi / fs);
y0 = a*cos(2*pi*ip);
y1 = a*sin(2*pi*ip);
for n=1:L,
y0 = y0 - b*y1;
y1 = y1 + b*y0;
fchg = (mod(n, 1000) == 0);
if fchg
b = 2.0 * sin(f*pi / fs);
end
ylast = y1;
lfo(n) = y0;
f = f + df;
close all
end;
figure;
plot(1:L, lfo); grid;
wavwrite(0.5*lfo, fs, 'lfo.wav');
+21
View File
@@ -0,0 +1,21 @@
function eval_smooth(Ta)
fs = 10000;
b = 1;
if (abs(Ta) > 0)
b = 1/(abs(Ta)*fs);
end;
a = 1 - b;
x = [ zeros(1, 1000) ones(1, 1000) zeros(1, 1000)];
y = 0;
for i=1:length(x)
if (Ta < 0)
y = b*x(i) + a*y;
yn(i) = y;
end;
plot(yn); grid;
+34
View File
@@ -0,0 +1,34 @@
function eval_smooth(Ta)
fs = 10000;
b = 1;
if (abs(Ta) > 0)
b = 1/(abs(Ta)*fs);
end;
a = 1 - b;
x = [ zeros(1, 1000) ones(1, 1000) zeros(1, 1000)];
N = length(x);
y = 0;
y1 = 0;
for i=1:length(x)
y1 = b*x(i) + a*y1;
% if (Ta > 0)
y1n(i) = y1;
% else
y2n(i) = 2*x(i) - y1;
% end
end;
subplot(3, 1, 1)
plot(1:N, x, 'r'); grid;
xlabel('Original LFO Wellenform (Square)')
subplot(3, 1, 2)
plot(1:N, y1n, 'r'); grid;
xlabel('Positive smoothed')
subplot(3, 1, 3)
plot(1:N, y2n, 'r'); grid;
xlabel('Negative smoothed')
BIN
View File
Binary file not shown.
+38
View File
@@ -0,0 +1,38 @@
function eval_limiter(name)
Tr = 1; % ms
Tf = 100; % ms
input_gain = 2;
output_gain = 0.5;
limit_thresh = 0.25;
[x, fs, nbits, opts] = wavread(name);
x = x.*input_gain;
N = fix(length(x)/);
a_r = 1000/(Tr*fs);
a_f = 1000/(Tf*fs);
y = 0;
for n=1:N
if (y < abs(x(n)))
y = y + a_r*(abs(x(n)) - y);
else
y = y + a_f*(abs(x(n)) - y);
end
env(n) = y;
end;
limiter_gain = limit_thresh./max(limit_thresh, env)';
x_limited = x(1:N).*limiter_gain.*output_gain;
wavwrite(x_limited,fs,'limiter_out.wav');
subplot(2, 1, 1)
plot(0:N-1, x(1:N), 0:N-1, env, 'r-'); grid;
subplot(2, 1, 2)
plot(0:N-1, x_limited, 0:N-1, limiter_gain, 'r-'); grid;
+38
View File
@@ -0,0 +1,38 @@
function eval_limiter(name)
Tr = 1; % ms
Tf = 100; % ms
input_gain = 2;
output_gain = 0.5;
limit_thresh = 0.25;
[x, fs, nbits, opts] = wavread(name);
x = x.*input_gain;
N = fix(length(x));
a_r = 1000/(Tr*fs);
a_f = 1000/(Tf*fs);
y = 0;
for n=1:N
if (y < abs(x(n)))
y = y + a_r*(abs(x(n)) - y);
else
y = y + a_f*(abs(x(n)) - y);
end
env(n) = y;
end;
limiter_gain = limit_thresh./max(limit_thresh, env)';
x_limited = x(1:N).*limiter_gain.*output_gain;
wavwrite(x_limited,fs,'limiter_out.wav');
subplot(2, 1, 1)
plot(0:N-1, x(1:N), 0:N-1, env, 'r-'); grid;
subplot(2, 1, 2)
plot(0:N-1, x_limited, 0:N-1, limiter_gain, 'r-'); grid;
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+77
View File
@@ -0,0 +1,77 @@
function eval_midi_sync(pe, fe)
N = 5000;
t = (0:N-1)/N;
x1 = mod(20*t, 1) >=0.5;
x2 = mod((20+fe)*t+pe, 1) >=0.5;
dx1 = 50/N
dx2 = (50+fe)/N
qi1 = 0;
qi2 = 0;
xi1 = 0;
xi2 = 0;
xx1 = 0;
xx2 = 0;
lead = 0;
lag = 0;
ddx = 0;
for i=1:N,
%[qi1, qi2, xi1, xi2] = pd(x1(i), x2(i), qi1, qi2, xi1, xi2);
[qi1, qi2, xi1, xi2] = pd(xx1 >=0.5, xx2 >=0.5, qi1, qi2, xi1, xi2);
err = (qi1-qi2);
lead = 0.*err;
lag = lag + 0.000001*err;
ddx = (lead+lag);
xx1 = mod(xx1 + dx1, 1);
xx2 = mod(xx2 + dx2 + ddx, 1);
q1(i) = qi1;
q2(i) = qi2;
end;
dx2 = dx2 + ddx
dx1 = dx1
subplot(3, 1, 1)
plot(t, x1, t, x2); grid; axis([0 1 -0.2 1.2])
subplot(3, 1, 2)
plot(t, q1); grid; axis([0 1 -0.2 1.2])
subplot(3, 1, 3)
plot(t, q2); grid; axis([0 1 -0.2 1.2])
function [Q1, Q2, xo1, xo2] = pd(x1, x2, qi1, qi2, xi1, xi2)
xo1 = xi1;
xo2 = xi2;
qo1 = qi1;
qo2 = qi2;
for i=1:length(x1)
% Q1
% Detect edge
if (x1(i) - xo1) > 0.5
qo1 = 1;
end;
xo1 = x1(i);
% Q2
% Detect edge
if (x2(i) - xo2) > 0.5
qo2 = 1;
end;
xo2 = x2(i);
% Asynchronous reset
if (qo1 * qo2) > 0.5
qo1 = 0;
qo2 = 0;
end;
Q1(i) = qo1;
Q2(i) = qo2;
end;
+75
View File
@@ -0,0 +1,75 @@
function eval_midi_sync(pe, fe)
N = 5000;
t = (0:N-1)/N;
dx1 = 40/N
dx2 = (40+fe)/N
qi1 = 0;
qi2 = 0;
xi1 = 0;
xi2 = 0;
xx1 = 0;
xx2 = pe;
lead = 0;
lag = 0;
ddx = 0;
for i=1:N,
[qi1, qi2, xi1, xi2] = pd(xx1 >=0.5, xx2 >=0.5, qi1, qi2, xi1, xi2);
err = (qi1-qi2);
lead = 0.002*err;
lag = lag + 0.000005*err;
ddx(i) = (lead+lag);
xx1 = mod(xx1 + dx1, 1);
xx2 = mod(xx2 + dx2 + ddx(i), 1);
q1(i) = qi1;
q2(i) = qi2;
x1(i) = xx1 >=0.5;
x2(i) = xx2 >=0.5;
end;
subplot(4, 1, 1)
plot(t, x1, t, x2); grid; axis([0 1 -0.2 1.2])
subplot(4, 1, 2)
plot(t, q1); grid; axis([0 1 -0.2 1.2])
subplot(4, 1, 3)
plot(t, q2); grid; axis([0 1 -0.2 1.2])
subplot(4, 1, 4)
plot(t, ddx); grid;
function [Q1, Q2, xo1, xo2] = pd(x1, x2, qi1, qi2, xi1, xi2)
xo1 = xi1;
xo2 = xi2;
qo1 = qi1;
qo2 = qi2;
for i=1:length(x1)
% Q1
% Detect edge
if (x1(i) - xo1) > 0.5
qo1 = 1;
end;
xo1 = x1(i);
% Q2
% Detect edge
if (x2(i) - xo2) > 0.5
qo2 = 1;
end;
xo2 = x2(i);
% Asynchronous reset
if (qo1 * qo2) > 0.5
qo1 = 0;
qo2 = 0;
end;
Q1(i) = qo1;
Q2(i) = qo2;
end;
+116
View File
@@ -0,0 +1,116 @@
function eval_minblep()
nsin = 4096;
nharm_max = 1800;
L = 48000/2;
fs = 48000;
fstart = 440;
fend = 440;
f2start = 200;
f2end = 600;
df = (fend-fstart)/L
df2 = (f2end-f2start)/L
saw = 0;
% Calc blep table
nharm = min(fix((fs/fstart/2.0)) + 1, nharm_max)
Hw = kaiser(nsin, 8);
xx = (0:nsin-1)/nsin - 0.5;
for mm=1:nharm
blitm(mm,:) = sin(xx*(mm-1)*pi)./sin(xx*pi).*Hw';
blitm(mm,(find(isnan(blitm(mm,:))))) = (mm-1);
end;
for mm=1:nharm
blepm(mm, :) = cumsum(blitm(mm, :))/nsin;
end;
w = [1; 2*ones(nsin/2-1,1); ones(1 - rem(nsin,2),1); zeros(nsin/2-1,1)]';
for mm=1:nharm
x_rc = real(ifft(log(abs(fft(blitm(mm, :))))));
y = real(ifft(exp(fft(w.*x_rc))));
minblepm(mm, :) = cumsum(y)/nsin;
end;
x = 0;
x2 = 0;
z = 0;
f = fstart;
f2 = f2start;
tri = 0;
NLG = 2;
h = lagrange(1, 0.35)
startup = 1;
blep = 1;
for n = 1:L,
if x >= 1 || startup,
x = 0;
p = fs/f;
dx = 1.0/p;
m = min(fix((p/2.0)) + 1, nharm_max);
blep_offset = 0;
blep_gain = 1;
z = ~z;
end;
if x2 >= 1 || startup,
x2 =0;
dx2 = f2/fs;
% blep_offset = -(1-x);
% blep_gain = x;
% x = 0;
% z = ~z;
end;
startup = 0;
nn = (x+0.0)*(nsin-1) + 1;
ni = fix(nn);
nf = nn - ni;
h = lagrange(NLG, nf);
blep = h(NLG+1)*minblepm(m, max(1, ni));
j = 1;
for i = NLG:-1:1
blep = blep + h(i)*minblepm(m, max(1, ni-j));
j = j + 1;
end;
saw = x - blep_gain*blep + blep_offset;
if (z == 0)
sqr = blep;
else
sqr = 1-blep;
end
tri = tri + 2*(sqr-0.5)*dx;
vsqr(n) = 0.5*(sqr-0.5);
vtri(n) = tri;
vsaw(n,1) = x;
vsaw(n,2) = saw+0.5;
vblep(n,1) = x;
vblep(n,2) = blep;
x = x + dx;
f = f + df;
x2 = x2 + dx2;
f2 = f2 + df2;
end;
close all
plot(1:nsin, blepm(fix(nharm/2), :), 1:nsin, minblepm(fix(nharm/2), :)); grid;
wavwrite(0.5*vtri, fs, 'tri.wav');
wavwrite(0.5*vsqr, fs, 'sqr.wav');
wavwrite(0.5*vsaw, fs, 'saw.wav');
wavwrite(0.5*vblep, fs, 'blep.wav');
function h = lagrange(N, delay)
%LAGRANGE h=lagrange(N,delay) returns order N FIR
% filter h which implements given delay
% (in samples). For best results,
% delay should be near N/2 +/- 1.
n = 0:N;
h = ones(1,N+1);
for k = 0:N
index = find(n ~= k);
h(index) = h(index) * (delay-k)./ (n(index)-k);
end
+116
View File
@@ -0,0 +1,116 @@
function eval_minblep()
nsin = 4096;
nharm_max = 1800;
L = 48000/2;
fs = 48000;
fstart = 440;
fend = 440;
df = (fend-fstart)/L
f2start = 200;
f2end = 600;
df2 = (f2end-f2start)/L
saw = 0;
% Calc blep table
nharm = min(fix((fs/fstart/2.0)) + 1, nharm_max)
Hw = kaiser(nsin, 8);
xx = (0:nsin-1)/nsin - 0.5;
for mm=1:nharm
blitm(mm,:) = sin(xx*(mm-1)*pi)./sin(xx*pi).*Hw';
blitm(mm,(find(isnan(blitm(mm,:))))) = (mm-1);
end;
for mm=1:nharm
blepm(mm, :) = cumsum(blitm(mm, :))/nsin;
end;
w = [1; 2*ones(nsin/2-1,1); ones(1 - rem(nsin,2),1); zeros(nsin/2-1,1)]';
for mm=1:nharm
x_rc = real(ifft(log(abs(fft(blitm(mm, :))))));
y = real(ifft(exp(fft(w.*x_rc))));
minblepm(mm, :) = cumsum(y)/nsin;
end;
x = 0;
x2 = 0;
z = 0;
f = fstart;
f2 = f2start;
tri = 0;
NLG = 2;
h = lagrange(1, 0.35)
startup = 1;
blep = 1;
for n = 1:L,
if x >= 1 || startup,
x = 0;
p = fs/f;
dx = 1.0/p;
m = min(fix((p/2.0)) + 1, nharm_max);
blep_offset = 0;
blep_gain = 1;
z = ~z;
end;
if x2 >= 1 || startup,
x2 =0;
dx2 = f2/fs;
% blep_offset = -(1-x);
% blep_gain = x;
% x = 0;
% z = ~z;
end;
startup = 0;
nn = (x+0.0)*(nsin-1) + 1;
ni = fix(nn);
nf = nn - ni;
h = lagrange(NLG, nf);
blep = h(NLG+1)*minblepm(m, max(1, ni));
j = 1;
for i = NLG:-1:1
blep = blep + h(i)*minblepm(m, max(1, ni-j));
j = j + 1;
end;
saw = x - blep_gain*blep + blep_offset;
if (z == 0)
sqr = blep;
else
sqr = 1-blep;
end
vsqr(n) = 0.5*(sqr-0.5);
tri = tri + 2*(sqr-0.5)*dx;
vtri(n) = tri;
vsaw(n,1) = x;
vsaw(n,2) = saw+0.5;
vblep(n,1) = x;
vblep(n,2) = blep;
x = x + dx;
f = f + df;
x2 = x2 + dx2;
f2 = f2 + df2;
end;
close all
plot(1:nsin, blepm(fix(nharm/2), :), 1:nsin, minblepm(fix(nharm/2), :)); grid;
wavwrite(0.5*vtri, fs, 'tri.wav');
wavwrite(0.5*vsqr, fs, 'sqr.wav');
wavwrite(0.5*vsaw, fs, 'saw.wav');
wavwrite(0.5*vblep, fs, 'blep.wav');
function h = lagrange(N, delay)
%LAGRANGE h=lagrange(N,delay) returns order N FIR
% filter h which implements given delay
% (in samples). For best results,
% delay should be near N/2 +/- 1.
n = 0:N;
h = ones(1,N+1);
for k = 0:N
index = find(n ~= k);
h(index) = h(index) * (delay-k)./ (n(index)-k);
end
+33
View File
@@ -0,0 +1,33 @@
function eval_minblep_pre()
f = 440;
N = 4000;
t = (-N/2:N/2-1)/N;
x = sin(t*f*pi)./sin(t*pi); .* kaiser(N, 8)';
x(find(isnan(x))) = f;
%x = x / f;
% Real cepstrum
x_rc = real(ifft(log(abs(fft(x)))));
w = [1; 2*ones(N/2-1,1); ones(1 - rem(N,2),1); zeros(N/2-1,1)]';
y = real(ifft(exp(fft(w.*x_rc))));
xi = cumsum(x)/N;
yi = cumsum(y)/N;
close all;
subplot(2, 1, 1)
plot(t, x/f, t, xi, 'r'); grid;
subplot(2, 1, 2)
plot(0:N-1, y/f, 0:N-1, yi, 'r'); grid;
figure;
plot(t, xi); grid;
figure;
plot(t, yi); grid;
figure;
plot(w); grid;
+33
View File
@@ -0,0 +1,33 @@
function eval_minblep_pre()
f = 440;
N = 4000;
t = (-N/2:N/2-1)/N;
x = sin(t*f*pi)./sin(t*pi);% .* kaiser(N, 8)';
x(find(isnan(x))) = f;
%x = x / f;
% Real cepstrum
x_rc = real(ifft(log(abs(fft(x)))));
w = [1; 2*ones(N/2-1,1); ones(1 - rem(N,2),1); zeros(N/2-1,1)]';
y = real(ifft(exp(fft(w.*x_rc))));
xi = cumsum(x)/N;
yi = cumsum(y)/N;
close all;
subplot(2, 1, 1)
plot(t, x/f, t, xi, 'r'); grid;
subplot(2, 1, 2)
plot(0:N-1, y/f, 0:N-1, yi, 'r'); grid;
figure;
plot(t, xi); grid;
figure;
plot(t, yi); grid;
figure;
plot(w); grid;
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+96
View File
@@ -0,0 +1,96 @@
function eval_blep()
nsin = 2048;
nharm_max = 1800;
L = 48000;
fs = 48000;
fstart = 110;
fend = 110;
df = (fend-fstart)/L
% Calc blep table
nharm = min(fix((fs/fstart/2.0)) + 1, nharm_max)
Hw = kaiser(nsin, 8);
xx = (0:nsin-1)/nsin - 0.5;
for mm=1:nharm,
bb(mm, :) = sin((mm-1)*xx*pi);
end;
aa = sin(xx*pi);
for mm=1:nharm
for nn=1:length(xx)
if (aa(nn) == 0)
blitm(mm,nn) = (mm-1);
else
blitm(mm,nn) = bb(mm, nn)/aa(nn).*Hw(nn);
end
end
end;
for mm=1:nharm
blepm(mm, :) = cumsum(blitm(mm, :))/nsin;
end;
%blep2 = blepm(nharm_max/2+1, :)'
%aa2 = aa(:)'
%bb2 = bb(nharm_max/2+1, :)'
x = 0.5;
z = 0;
f = fstart;
tri = 0;
for n = 1:L,
if x >= 0.5,
x = x - 1;
p = fs/f;
dx = 1.0/p;
m = min(fix((p/2.0)) + 1, nharm_max);
z = ~z;
end;
nn = (x+0.5)*(nsin-1) + 1;
ni = fix(nn);
nf = nn - ni;
h = lagrange(1, nf);
% blep = (blepm(m, min(ni+1, nsin)) - blepm(m, ni))*nf + blepm(m, ni);
[blep, Zi] = filter(h, 1, blepm(m
blep = h(1)*blepm(m, max(1, ni-1)) + h(2)*blepm(m, ni);
saw = x - blep;
if (z == 0)
sqr = blep;
else
sqr = 1-blep;
end
tri = tri + 2*(sqr-0.5)*dx;
vtri(n) = tri;
vsaw(n) = saw+0.5;
vsqr(n) = 0.5*(sqr-0.5);
vblep(n) = blep;
x = x + dx;
f = f + df;
end;
close all
plot(1:nsin, blepm(fix(nharm/2), :)); grid;
wavwrite(0.5*vtri, fs, 'tri.wav');
wavwrite(0.5*vsqr, fs, 'sqr.wav');
wavwrite(0.5*vsaw, fs, 'saw.wav');
wavwrite(0.5*vblep, fs, 'blep.wav');
function h = lagrange(N, delay)
%LAGRANGE h=lagrange(N,delay) returns order N FIR
% filter h which implements given delay
% (in samples). For best results,
% delay should be near N/2 +/- 1.
n = 0:N;
h = ones(1,N+1);
for k = 0:N
index = find(n ~= k);
h(index) = h(index) * (delay-k)./ (n(index)-k);
end
+90
View File
@@ -0,0 +1,90 @@
function eval_blep()
nsin = 4096;
nharm_max = 1800;
L = 48000/2;
fs = 48000;
fstart = 440;
fend = 440;
df = (fend-fstart)/L
% Calc blep table
nharm = min(fix((fs/fstart/2.0)) + 1, nharm_max)
Hw = kaiser(nsin, 8);
xx = (0:nsin-1)/nsin - 0.5;
for mm=1:nharm
blitm(mm,:) = sin(xx*(mm-1)*pi)./sin(xx*pi).*Hw';
blitm(mm,(find(isnan(blitm(mm,:))))) = (mm-1);
end;
for mm=1:nharm
blepm(mm, :) = cumsum(blitm(mm, :))/nsin;
end;
x = 0.0;
z = 0;
f = fstart;
tri = 0;
NLG = 2;
h = lagrange(1, 0.35)
startup = 1;
for n = 1:L,
if x >= 0.5 || startup;
x = x - 1;
p = fs/f;
dx = 1.0/p;
m = min(fix((p/2.0)) + 1, nharm_max);
z = ~z;
end;
startup = 0;
nn = (x+0.5)*(nsin-1) + 1;
ni = fix(nn);
nf = nn - ni;
h = lagrange(NLG, nf);
blep = h(NLG+1)*blepm(m, max(1, ni));
j = 1;
for i = NLG:-1:1
blep = blep + h(i)*blepm(m, max(1, ni-j));
j = j + 1;
end;
saw = x - blep;
if (z == 0)
sqr = blep;
else
sqr = 1-blep;
end
vsqr(n) = 0.5*(sqr-0.5);
tri = tri + 2*(sqr-0.5)*dx;
vtri(n) = tri;
vsaw(n,1) = x;
vsaw(n,2) = saw+0.5;
vblep(n,1) = x;
vblep(n,2) = blep;
x = x + dx;
f = f + df;
end;
close all
plot(1:nsin, blepm(fix(nharm/2), :)); grid;
wavwrite(0.5*vtri, fs, 'tri.wav');
wavwrite(0.5*vsqr, fs, 'sqr.wav');
wavwrite(0.5*vsaw, fs, 'saw.wav');
wavwrite(0.5*vblep, fs, 'blep.wav');
function h = lagrange(N, delay)
%LAGRANGE h=lagrange(N,delay) returns order N FIR
% filter h which implements given delay
% (in samples). For best results,
% delay should be near N/2 +/- 1.
n = 0:N;
h = ones(1,N+1);
for k = 0:N
index = find(n ~= k);
h(index) = h(index) * (delay-k)./ (n(index)-k);
end
+105
View File
@@ -0,0 +1,105 @@
function eval_blep_hs()
nsin = 1024;
nharm_max = 1800;
L = 48000;
fs = 48000;
fstart = 44;
fend = 44;
df = (fend-fstart)/L
% Calc blep table
nharm = min(fix((fs/fstart/2.0)) + 1, nharm_max)
Hw = kaiser(nsin, 8);
xx = (0:nsin-1)/nsin - 0.5;
for mm=1:nharm,
bb(mm, :) = sin((mm-1)*xx*pi);
end;
aa = sin(xx*pi);
for mm=1:nharm
for nn=1:length(xx)
if (aa(nn) == 0)
blitm(mm,nn) = (mm-1);
else
blitm(mm,nn) = bb(mm, nn)/aa(nn).*Hw(nn);
end
end
end;
for mm=1:nharm
blepm(mm, :) = cumsum(blitm(mm, :))/nsin;
end;
%blep2 = blepm(nharm_max/2+1, :)'
%aa2 = aa(:)'
%bb2 = bb(nharm_max/2+1, :)'
x = 0.5;
x2 = 0.5;
z = 0;
f = fstart;
tri = 0;
NLG = 2;
h = lagrange(1, 0.35)
for n = 1:L,
if x >= 0.5,
x = x - 1;
p = fs/f;
dx = 1.0/p;
m = min(fix((p/2.0)) + 1, nharm_max);
z = ~z;
end;
nn = (x+0.5)*(nsin-1) + 1;
ni = fix(nn);
nf = nn - ni;
h = lagrange(NLG, nf);
% blep = (blepm(m, min(ni+1, nsin)) - blepm(m, ni))*nf + blepm(m, ni);
% blep = h(1)*blepm(m, max(1, ni-3)) + h(2)*blepm(m, max(1, ni-2)) + h(3)*blepm(m, max(1, ni-1)) + h(4)*blepm(m, max(1, ni));
blep = h(NLG+1)*blepm(m, max(1, ni));
j = 1;
for i = NLG:-1:1
blep = blep + h(i)*blepm(m, max(1, ni-j));
j = j + 1;
end;
saw = x - blep;
if (z == 0)
sqr = blep;
else
sqr = 1-blep;
end
tri = tri + 2*(sqr-0.5)*dx;
vtri(n) = tri;
vsaw(n) = saw+0.5;
vsqr(n) = 0.5*(sqr-0.5);
vblep(n) = blep;
x = x + dx;
f = f + df;
end;
close all
plot(1:nsin, blepm(fix(nharm/2), :)); grid;
wavwrite(0.5*vtri, fs, 'tri.wav');
wavwrite(0.5*vsqr, fs, 'sqr.wav');
wavwrite(0.5*vsaw, fs, 'saw.wav');
wavwrite(0.5*vblep, fs, 'blep.wav');
function h = lagrange(N, delay)
%LAGRANGE h=lagrange(N,delay) returns order N FIR
% filter h which implements given delay
% (in samples). For best results,
% delay should be near N/2 +/- 1.
n = 0:N;
h = ones(1,N+1);
for k = 0:N
index = find(n ~= k);
h(index) = h(index) * (delay-k)./ (n(index)-k);
end
+145
View File
@@ -0,0 +1,145 @@
function eval_blep_hs()
nsin = 1024;
nharm_max = 256;
L = 4800;
fs = 48000;
fstart = 44;
fend = 44;
df = (fend-fstart)/L
% Calc blep table
nharm = min(fix((fs/fstart/2.0)) + 1, nharm_max)
Hw = kaiser(nsin, 8);
xx = (0:nsin-1)/nsin - 0.5;
for mm=1:nharm,
bb(mm, :) = sin((mm-1)*xx*pi);
end;
aa = sin(xx*pi);
for mm=1:nharm
for nn=1:length(xx)
if (aa(nn) == 0)
blitm(mm,nn) = (mm-1);
else
blitm(mm,nn) = bb(mm, nn)/aa(nn).*Hw(nn);
end
end
end;
for mm=1:nharm
blepm(mm, :) = cumsum(blitm(mm, :))/nsin;
end;
%blep2 = blepm(nharm_max/2+1, :)'
%aa2 = aa(:)'
%bb2 = bb(nharm_max/2+1, :)'
x = 0.0;
x2 = -0.5;
z = 0;
f = fstart;
f2 = 0.6*f;
dx2 = f2/fs;
tri = 0;
NLG = 2;
h = lagrange(1, 0.35)
sync = 0;
restart = 0;
startup = 1;
minblep_active = 0;
minblep_count = 0;
minblep_length = 200;
minblep = 1-(0:minblep_length-1)/minblep_length;
for n = 1:L,
sync = 0;
if startup,
x = 0.0;
startup = 0;
p = fs/f;
dx = 1.0/p;
m = min(fix((p/2.0)) + 1, nharm_max);
z = 0;
end;
if (x >= 0.5),
x = x - 1;
p = fs/f;
dx = 1.0/p;
m = min(fix((p/2.0)) + 1, nharm_max);
z = ~z;
end;
if (x2 >= 0.5),
x2 = x2 - 1;
sync = 1;
startup = 1;
minblep_count = minblep_length;
end;
nn = (x+0.5)*(nsin-1) + 1;
ni = fix(nn);
nf = nn - ni;
h = lagrange(NLG, nf);
blep = h(NLG+1)*blepm(m, max(1, ni));
j = 1;
for i = NLG:-1:1
blep = blep + h(i)*blepm(m, max(1, ni-j));
j = j + 1;
end;
if (minblep_count > 0)
saw = saw_last - minblep(minblep_count)/(1-2*saw_last+minblep_length*dx);
minblep_count = minblep_count - 1;
minblep_active = 1;
else
minblep_active = 0;
saw = x - blep;
saw_last = saw;
end
if (z == 0)
sqr = blep;
else
sqr = 1-blep;
end
tri = tri + 2*(sqr-0.5)*dx;
vtri(n) = tri;
vsaw(n) = 2*(saw+0.5);
vsqr(n) = 0.5*(sqr-0.5);
vblep(n) = blep;
vx(n) = x;
vsync(n) = sync;
vminblep_active(n) = minblep_active;
x2 = x2 + dx2;
x = x + dx;
f = f + df;
end;
close all
plot(1:nsin, blepm(fix(nharm/2), :)); grid;
figure;
subplot(3, 1, 1)
plot(1:L, vx, 1:L, vsync, 'r'); grid;
subplot(3, 1, 2)
plot(1:L, vblep); grid;
subplot(3, 1, 3)
plot(1:L, vsaw, 1:L, vminblep_active); grid;
wavwrite(0.5*vtri, fs, 'tri.wav');
wavwrite(0.5*vsqr, fs, 'sqr.wav');
wavwrite(0.5*vsaw, fs, 'saw.wav');
wavwrite(0.5*vblep, fs, 'blep.wav');
function h = lagrange(N, delay)
%LAGRANGE h=lagrange(N,delay) returns order N FIR
% filter h which implements given delay
% (in samples). For best results,
% delay should be near N/2 +/- 1.
n = 0:N;
h = ones(1,N+1);
for k = 0:N
index = find(n ~= k);
h(index) = h(index) * (delay-k)./ (n(index)-k);
end
+103
View File
@@ -0,0 +1,103 @@
function eval_blit()
sinc_use_lut = 1
L = 40000;
fs = 48000;
fstart = 110;
fend = 1100;
df = (fend-fstart)/L;
nhw = 4096;
Hw = kaiser(nhw, 8)';
if sinc_use_lut == 1
nharm = 512;
nsin = 4096;
xx = (0:nsin)/nsin - 0.5;
mmm=0;
for mm=1:nharm,
mmm=mmm+1;
bb(mm, :) = sin(mmm*xx*pi);
end;
aa = sin(xx*pi);
end;
sinc_m(mm, find(aa == 0)) = 1;
x = 0.5;
z = -1;
f = fstart;
sqr = 0.0;
c4 = 0;
a = 1;
b = 1;
for n = 1:L,
if x >= 0.5,
x = x - 1;
p = fs/f;
fraq = 1.0/p;
m = fix((p/2.0)) + 1;
saw = 0.0;
c3 = 0.0;
z = -z;
end;
if sinc_use_lut == 1
nn = (x+0.5)*nsin + 1;
ni = fix(nn);
nf = nn - ni;
bbi = (bb(m, ni+1) - bb(m, ni))*nf + bb(m, ni); % Fractional delay (linear interpolation)
aai = (aa(ni+1) - aa(ni))*nf + aa(ni); % Fractional delay (linear interpolation)
if (aai ~= 0)
blit = fraq* bbi / aai * Hw(fix((x+0.5)*nhw)+1);
else
blit = fraq;
end
else
b = sin(m*x*pi);
a = sin(x*pi);
if (a ~= 0)
blit = fraq * b/a * Hw(fix((x+0.5)*nhw)+1);
else
blit = fraq;
end
end
saw = saw + blit;
sqr = sqr + z*blit;
vsaw(n) = 2*(saw - c3);
vsqr(n) = 2*(sqr - 0.5);
vblit(n) = blit;
vc3(n) = b;
x = x + fraq;
c3 = c3 + fraq;
f = f + df;
c4 = c4 + fraq - blit;
vc4(n) = a;
end;
close all
figure;
plot(1:L, vsaw, 1:L, vc4, 1:L, vblit); grid;
figure;
plot(vc4); grid;
figure;
plot(vc3); grid;
if sinc_use_lut == 1
figure;
plot(sinc_m(nharm, :)); grid;
end;
wavwrite(0.5*vc4, fs, 'c4.wav');
wavwrite(0.5*vc3, fs, 'c3.wav');
wavwrite(0.5*vsqr, fs, 'sqr.wav');
wavwrite(0.5*vsaw, fs, 'saw.wav');
wavwrite(0.5*vblit, fs, 'blit.wav');
+116
View File
@@ -0,0 +1,116 @@
function eval_blit()
Nb = 33;
Nos = 1000;
fs = 48000;
f = 440;
fcut = 16000;
P = fs/f
Pi = floor(P)
Pf = fix(Nos*(fs/f - Pi))
x = (0:Nb*Nos-1) - (Nb*Nos-1)/2;
H = sinc(2*fcut/fs.*x/Nos).*Kaiser(Nos*Nb, 8)';
for ii=0:Nos-1,
for jj=0:Nb-1
Hs(ii+1,jj+1) = H(Nos-ii+Nos*jj);
end
end;
%Hs = init_steps(Nb, Nos);
k = 1./sum(Hs');
%k = ones(1, Nb);
y0 = 0;
y1 = 0;
y2 = .5;
ks = 0.002*f/55
ii = 0;
ff = 0;
nn = 1;
jj = Nb;
kk = 1;
for i = 1:32000,
if ii == 0
jj = 1;
kk = ff + 1;
ii = Pi;
ff = ff + Pf;
if ff > (Nos-1)
ff = ff - Nos;
ii = ii + 1;
end
end;
ii = ii - 1;
if jj <= Nb
y0 = y0 + k(kk)*Hs(kk,jj); % - ks*y0;
jj = jj + 1;
end;
y0 = y0 - ks*y0;
y1 = 1 - y0;
y2 = 0.001*y1 + 0.999*y2;
blit(nn) = y1-y2;
nn = nn + 1;
end;
wavwrite(0.7*blit, fs, 'blit.wav');
for ii = 1:Nos
step(ii,:) = k(ii).*filter(Hs(ii,:), 1, [ones(1,Nb)]);
end
close all
plot(1:length(blit), blit, '-o'); grid;
figure
plot(abs(fft(blit))); grid;
figure
plot(step', '-'); grid;
figure
plot(Hs(1,:)', '-+'); grid;
function steps = init_steps(step_width, phase_count)
low_pass = 0.999; % lower values filter more high frequency
high_pass = 0.990; % lower values filter more low frequency
%phase_count = 32; % number of phase offsets to sample band-limited step at
%step_width = 16; % number of samples in each final band-limited step
%steps [phase_count] [step_width]; // would use short for speed in a real program
% Generate master band-limited step by adding sine components of a square wave
master_size = step_width * phase_count;
% master [master_size]; // large; might want to malloc() instead
for i = 0:master_size-1
master(i+1) = 0.5;
end;
gain = 0.5 / 0.777; % adjust normal square wave's amplitude of ~0.777 to 0.5
sine_size = 256 * phase_count + 2;
max_harmonic = sine_size / 2 / phase_count;
for h = 1:2:max_harmonic
amplitude = gain / h;
to_angle = 3.14159265358979323846 * 2 / sine_size * h;
for i = 0:master_size-1
master(i+1) = master(i+1) + sin( (i - master_size / 2) * to_angle ) * amplitude;
end
gain = gain * low_pass;
end
% Sample master step at several phases
for phase = 0:phase_count-1
error = 1.0;
prev = 0.0;
for i = 0:step_width-1
cur = master (i * phase_count + (phase_count - 1 - phase)+1);
delta = cur - prev;
error = error - delta;
prev = cur;
steps (phase+1, i+1) = delta;
end
% each delta should total 1.0
steps (phase+1, step_width / 2) = steps (phase+1, step_width / 2) + error * 0.5;
steps (phase+1, step_width / 2 + 1) = steps (phase+1, step_width / 2 + 1) + error * 0.5;
end
+90
View File
@@ -0,0 +1,90 @@
function eval_blit()
sinc_use_lut = 1
L = 96000;
fs = 48000;
fstart = 220;
fend = 880;
df = (fend-fstart)/L
# for saw
rho = 1.0;
nhw = 4096;
Hw = kaiser(nhw, 8)';
max_m = fix((fs/fstart/2.0)) + 1
if sinc_use_lut == 1
nharm = max_m
nsin = 1024;
xx = (0:nsin)/nsin - 0.5;
mmm=0;
for mm=1:nharm,
mmm=mmm+1;
bb(mm, :) = sin(mmm*xx*pi);
end;
aa = sin(xx*pi);
end;
x = 0.5;
z = -1;
f = fstart;
sqr = 0.0;
saw = 0.0;
tri = 0.0;
a = 1;
b = 1;
for n = 1:L,
if x >= 0.5,
x = x - 1;
p = fs/f;
fraq = 1.0/p;
m = fix((p/2.0)) + 1;
z = -z;
end;
if sinc_use_lut == 1
nn = (x+0.5)*nsin + 1;
ni = fix(nn);
nf = nn - ni;
b = (bb(m, ni+1) - bb(m, ni))*nf + bb(m, ni); % Fractional delay (linear interpolation)
a = (aa(ni+1) - aa(ni))*nf + aa(ni); % Fractional delay (linear interpolation)
else
b = sin(m*x*pi);
a = sin(x*pi);
end
if (a ~= 0)
blit = fraq * b/a * Hw(fix((x+0.5)*nhw)+1);
else
blit = fraq;
end
saw = rho*saw + (fraq - blit);
sqr = sqr + z*blit;
tri = tri + 2*(sqr-0.5)*fraq;
vtri(n) = tri;
vsaw(n) = saw;
vsqr(n) = 2*(sqr - 0.5);
vblit(n) = blit;
x = x + fraq;
f = f + df;
end;
close all
figure;
plot(1:L, vsaw, 1:L, vblit); grid;
N_UNDC = fs/100;
a, b = undcc(N_UNDC);
wavwrite(0.5*filter(b, a, vtri), fs, 'tri.wav');
wavwrite(0.5*filter(b, a, vsqr), fs, 'sqr.wav');
wavwrite(0.5*filter(b, a, vsaw), fs, 'saw.wav');
wavwrite(0.5*vblit, fs, 'blit.wav');
+128
View File
@@ -0,0 +1,128 @@
function eval_blit2()
L = 64000;
Nb = 65;
Nos = 8;
fs = 48000;
fstart = 440;
fend = 1760;
df = (fend-fstart)/L
fcut = 16000;
x = (0:Nb*Nos-1) - (Nb*Nos-1)/2;
H = sinc(2*fcut/fs.*x/Nos).*kaiser(Nos*Nb, 8)';
for ii=0:Nos-1,
for jj=0:Nb-1
Hs(ii+1,jj+1) = H(Nos-ii+Nos*jj);
end
end;
%Hs = init_steps(Nb, Nos);
f = fstart;
k = 1./sum(Hs')
%k = ones(1, Nb);
y0 = 0;
y1 = 0;
y2 = .5;
ks = 0.002*f/55
ii = 0;
ff = 0;
nn = 1;
jj = Nb;
kk = 1;
for i = 1:L,
P = fs/f;
Pi = floor(P);
Pf = fix(Nos*(fs/f - Pi));
if ii == 0
jj = 1;
kk = ff + 1;
ii = Pi;
ff = ff + Pf;
if ff > (Nos-1)
ff = ff - Nos;
ii = ii + 1;
end
end;
ii = ii - 1;
if jj <= Nb
y0 = k(kk)*Hs(kk,jj); % - ks*y0;
jj = jj + 1;
end;
y0 = y0 - ks*y0;
y1 = 1 - y0;
y2 = 0.001*y1 + 0.999*y2;
blit(nn) = y1-y2; % DC removal
nn = nn + 1;
f = f + df;
end;
wavwrite(0.7*blit, fs, 'blit.wav');
for ii = 1:Nos
step(ii,:) = k(ii).*filter(Hs(ii,:), 1, [ones(1,Nb)]);
end
DC = y2
close all
plot(H); grid;
figure
plot(1:length(blit), blit, '-o'); grid;
figure
plot(abs(fft(blit))); grid;
figure
plot(step', '-'); grid;
figure
plot(Hs(1,:)', '-+'); grid;
function steps = init_steps(step_width, phase_count)
low_pass = 0.999; % lower values filter more high frequency
high_pass = 0.990; % lower values filter more low frequency
%phase_count = 32; % number of phase offsets to sample band-limited step at
%step_width = 16; % number of samples in each final band-limited step
%steps [phase_count] [step_width]; // would use short for speed in a real program
% Generate master band-limited step by adding sine components of a square wave
master_size = step_width * phase_count;
% master [master_size]; // large; might want to malloc() instead
for i = 0:master_size-1
master(i+1) = 0.5;
end;
gain = 0.5 / 0.777; % adjust normal square wave's amplitude of ~0.777 to 0.5
sine_size = 256 * phase_count + 2;
max_harmonic = sine_size / 2 / phase_count;
for h = 1:2:max_harmonic
amplitude = gain / h;
to_angle = 3.14159265358979323846 * 2 / sine_size * h;
for i = 0:master_size-1
master(i+1) = master(i+1) + sin( (i - master_size / 2) * to_angle ) * amplitude;
end
gain = gain * low_pass;
end
% Sample master step at several phases
for phase = 0:phase_count-1
error = 1.0;
prev = 0.0;
for i = 0:step_width-1
cur = master (i * phase_count + (phase_count - 1 - phase)+1);
delta = cur - prev;
error = error - delta;
prev = cur;
steps (phase+1, i+1) = delta;
end
% each delta should total 1.0
steps (phase+1, step_width / 2) = steps (phase+1, step_width / 2) + error * 0.5;
steps (phase+1, step_width / 2 + 1) = steps (phase+1, step_width / 2 + 1) + error * 0.5;
end
+68
View File
@@ -0,0 +1,68 @@
function eval_blit3()
L = 64000;
Nb = 137;
Nphases = 256;
fs = 48000;
fstart = 110*4;
fend = 110*4;
df = (fend-fstart)/L;
fcut = 16000;
x = (0:Nb*Nphases-1) - (Nb*Nphases-1)/2;
H = sinc(2*fcut/fs.*x/Nphases);%.*kaiser(Nphases*Nb, 8)';
Hw = kaiser(Nb, 18)';
for ii=0:Nphases-1,
for jj=0:Nb-1
Hs(ii+1,jj+1) = H(Nphases-ii+Nphases*jj);
end
end;
f = fstart;
Pic = 1;
blit(1:L) = zeros(1, L);
blit_bp(1:L) = zeros(1, L);
pol = 1;
for n = Nb:L,
P = fs/f;
Pi = floor(P);
Pf = 1+fix(Nphases*(P - Pi));
if Pic >= Pi,
blit(n-Nb+1:n) = blit(n-Nb+1:n) + Hs(Pf,:).*Hw;
blit_bp(n-Nb+1:n) = blit_bp(n-Nb+1:n) + pol*Hs(Pf,:).*Hw;
Pic = 0;
pol = -pol;
end;
Pic = Pic + 1;
f = f + df;
end;
mean_blit = 0.0034;
mean_saw = 0.00;
saw(1:L) = zeros(1, L);
sqr(1:L) = zeros(1, L);
y_saw = 0;
y_sqr = 0;
for n = 2:L,
y_saw = y_saw + blit(n)-mean_blit;
y_sqr = y_sqr + blit_bp(n);
saw(n) = -y_saw -1;
sqr(n) = y_sqr;
end;
saw_nodc = filter([1 -1], [1 -0.995], saw);
sqr_nodc = filter([1 -1], [1 -0.995], sqr);
close all;
plot(saw); grid;
figure;
plot(saw_nodc); grid;
wavwrite(0.8*blit, fs, 'blit.wav');
wavwrite(0.7*saw_nodc, fs, 'saw.wav');
wavwrite(0.5*sqr_nodc, fs, 'sqr.wav');
+92
View File
@@ -0,0 +1,92 @@
function eval_blit()
sinc_use_lut = 0
L = 40000;
fs = 48000;
fstart = 110;
fend = 1550;
df = (fend-fstart)/L;
nhw = 4096;
Hw = kaiser(nhw, 8)';
ya = zeros(3,1);
yb = zeros(3,1);
x = 0.5;
z = -1;
f = fstart;
sqr = 0.0;
c4 = 0;
for n = 1:L,
if x >= 0.5,
x = x - 1;
p = fs/f;
fraq = 1.0/p;
m = fix((p/2.0)) + 1;
saw = 0.0;
c3 = 0.0;
z = -z;
ip = 0; % phase of the first output sample in radians
w = f*pi / fs
ba1 = 2.0 * cos(w)
bb1 = 2.0 * cos(w)
end;
if sinc_use_lut == 1
xi = fix((x+0.5)*nsin)+1
if aa(xi) ~= 0
blit = fraq*sinc_m(m, xi) * Hw(fix((x+0.5)*nhw)+1);;
else
blit = fraq;
end
else
b = fraq*sin(m*x*pi);
a = sin(x*pi);
if (a ~= 0)
blit = b/a * Hw(fix((x+0.5)*nhw)+1);
else
blit = fraq;
end
end
saw = saw + blit;
sqr = sqr + z*blit;
vsaw(n) = 2*(saw - c3);
vsqr(n) = 2*(sqr - 0.5);
vblit(n) = blit;
vc3(n) = c3;
x = x + fraq;
c3 = c3 + fraq;
f = f + df;
c4 = c4 + fraq - blit;
vc4(n) = c4;
end;
close all
figure;
plot(1:L, vsaw, 1:L, vc4, 1:L, vblit); grid;
figure;
plot(vblit); grid;
figure;
plot(vc3); grid;
if sinc_use_lut == 1
figure;
plot(sinc_m(nharm, :)); grid;
end;
wavwrite(0.5*vc4, fs, 'c4.wav');
wavwrite(0.5*vc3, fs, 'c3.wav');
wavwrite(0.5*vsqr, fs, 'sqr.wav');
wavwrite(0.5*vsaw, fs, 'saw.wav');
wavwrite(0.5*vblit, fs, 'blit.wav');
+88
View File
@@ -0,0 +1,88 @@
function eval_blit4()
L = 4000;
fs = 48000;
fstart = 110;
fend = 110;
df = (fend-fstart)/L;
nhw = 4096;
Hw = kaiser(nhw, 8)';
ya = zeros(3,1);
yb = zeros(3,1);
x = 0.5;
z = -1;
f = fstart;
sqr = 0.0;
c4 = 0;
for n = 1:L,
if x >= 0.5,
x = x - 1;
p = fs/f;
fraq = 1.0/p;
m = fix((p/2.0)) + 1
saw = 0.0;
c3 = 0.0;
z = -z;
ip = -pi/2; % phase of the first output sample in radians
w = f*pi / fs;
ba1 = 2.0 * cos(w);
bb1 = 2.0 * cos(m*w);
ya(2)=sin(ip-w);
ya(3)=sin(ip-2*w);
yb(2)=sin(ip-m*w);
yb(3)=sin(ip-2*m*w);
end;
ya(1) = ba1*ya(2) - ya(3);
ya(3) = ya(2);
ya(2) = ya(1);
yb(1) = bb1*yb(2) - yb(3);
yb(3) = yb(2);
yb(2) = yb(1);
b = fraq*yb(1);
a = ya(1);
if (a ~= 0)
blit = b/a * Hw(fix((x+0.5)*nhw)+1);
else
blit = fraq;
end
saw = saw + blit;
sqr = sqr + z*blit;
vsaw(n) = 2*(saw - c3);
vsqr(n) = 2*(sqr - 0.5);
vblit(n) = blit;
vc3(n) = b;
x = x + fraq;
c3 = c3 + fraq;
f = f + df;
c4 = c4 + fraq - blit;
vc4(n) = a;
end;
close all
figure;
plot(1:L, vsaw, 1:L, vc4, 1:L, vblit); grid;
figure;
plot(vc4); grid;
figure;
plot(vc3); grid;
wavwrite(0.5*vc4, fs, 'c4.wav');
wavwrite(0.5*vc3, fs, 'c3.wav');
wavwrite(0.5*vsqr, fs, 'sqr.wav');
wavwrite(0.5*vsaw, fs, 'saw.wav');
wavwrite(0.5*vblit, fs, 'blit.wav');
+125
View File
@@ -0,0 +1,125 @@
function eval_hardsync()
nsin = 4096;
nharm_max = 1800;
L = 48000/2;
fs = 48000;
fstart = 440;
fend = 440;
df = (fend-fstart)/L
f2start = 400;
f2end = 400;
df2 = (f2end-f2start)/L
% Calc blep table
nharm = min(fix((fs/min(fstart, f2start)/2.0)) + 1, nharm_max)
Hw = kaiser(nsin, 8);
xx = (0:nsin-1)/nsin - 0.5;
for mm=1:nharm
blitm(mm,:) = sin(xx*(mm-1)*pi)./sin(xx*pi).*Hw';
blitm(mm,(find(isnan(blitm(mm,:))))) = (mm-1);
end;
for mm=1:nharm
blepm(mm, :) = cumsum(blitm(mm, :))/nsin;
end;
x = 0.0;
x2 = 0.0;
z = 0;
f = fstart;
f2 = f2start;
tri = 0;
saw = 0;
NLG = 2;
h = lagrange(1, 0.35)
startup = 1;
for n = 1:L,
if x >= 1 || startup;
x = 0;
p = fs/f;
dx = 1.0/p;
m = min(fix((p/2.0)) + 1, nharm_max);
blep_offset = 0;
blep_gain = 1;
z = ~z;
end;
if x2 >= 1 || startup,
x2 =0;
p2 = fs/f2;
dx2 = 1.0/p2;
m2 = min(fix((p2/2.0)) + 1, nharm_max);
% blep_offset = -(1-(x+0.5));
% blep_gain = x+0.5;
% x = 0;
% z = ~z;
end;
startup = 0;
% BLEP 1
nn = (x+0.0)*(nsin-1) + 1;
ni = fix(nn);
nf = nn - ni;
h = lagrange(NLG, nf);
blep = h(NLG+1)*blepm(m, max(1, ni));
j = 1;
for i = NLG:-1:1
blep = blep + h(i)*blepm(m, max(1, ni-j));
j = j + 1;
end;
% BLEP 2
nn = (x2+0.0)*(nsin-1) + 1;
ni = fix(nn);
nf = nn - ni;
h = lagrange(NLG, nf);
blep2 = h(NLG+1)*blepm(m2, max(1, ni));
j = 1;
for i = NLG:-1:1
blep2 = blep2 + h(i)*blepm(m2, max(1, ni-j));
j = j + 1;
end;
saw = x - blep;
if (z == 0)
sqr = blep;
else
sqr = 1-blep;
end
vsqr(n) = 0.5*(sqr-0.5);
tri = tri + 2*(sqr-0.5)*dx;
vtri(n,1) = sqr;
vtri(n,2) = tri;
vsaw(n,1) = x;
vsaw(n,2) = tri;
vblep(n,1) = x;
vblep(n,2) = blep;
x = x + dx;
f = f + df;
x2 = x2 + dx2;
f2 = f2 + df2;
end;
close all
plot(1:nsin, blepm(fix(nharm/2), :)); grid;
wavwrite(0.5*vtri, fs, 'tri.wav');
wavwrite(0.5*vsqr, fs, 'sqr.wav');
wavwrite(0.5*vsaw, fs, 'saw.wav');
wavwrite(0.5*vblep, fs, 'blep.wav');
function h = lagrange(N, delay)
%LAGRANGE h=lagrange(N,delay) returns order N FIR
% filter h which implements given delay
% (in samples). For best results,
% delay should be near N/2 +/- 1.
n = 0:N;
h = ones(1,N+1);
for k = 0:N
index = find(n ~= k);
h(index) = h(index) * (delay-k)./ (n(index)-k);
end
+107
View File
@@ -0,0 +1,107 @@
function eval_hardsync()
nsin = 4096;
nharm_max = 1800;
L = 48000/2;
fs = 48000;
fstart = 440;
fend = 440;
df = (fend-fstart)/L
f2start = 200;
f2end = 600;
df2 = (f2end-f2start)/L
% Calc blep table
nharm = min(fix((fs/fstart/2.0)) + 1, nharm_max)
Hw = kaiser(nsin, 8);
xx = (0:nsin-1)/nsin - 0.5;
for mm=1:nharm
blitm(mm,:) = sin(xx*(mm-1)*pi)./sin(xx*pi).*Hw';
blitm(mm,(find(isnan(blitm(mm,:))))) = (mm-1);
end;
for mm=1:nharm
blepm(mm, :) = cumsum(blitm(mm, :))/nsin;
end;
x = 0.0;
x2 = 0.0;
z = 0;
f = fstart;
f2 = f2start;
tri = 0;
NLG = 2;
h = lagrange(1, 0.35)
startup = 1;
for n = 1:L,
if x >= 1 || startup;
x = x - 1;
p = fs/f;
dx = 1.0/p;
m = min(fix((p/2.0)) + 1, nharm_max);
z = ~z;
end;
if x2 >= 1 || startup,
x2 =0;
p2 = fs/f2;
dx2 = 1.0/p2;
m2 = min(fix((p2/2.0)) + 1, nharm_max);
% blep_offset = -(1-x);
% blep_gain = x;
% x = 0;
% z = ~z;
end;
startup = 0;
nn = (x+0.0)*(nsin-1) + 1;
ni = fix(nn);
nf = nn - ni;
h = lagrange(NLG, nf);
blep = h(NLG+1)*blepm(m, max(1, ni));
j = 1;
for i = NLG:-1:1
blep = blep + h(i)*blepm(m, max(1, ni-j));
j = j + 1;
end;
saw = x - blep;
if (z == 0)
sqr = blep;
else
sqr = 1-blep;
end
vsqr(n) = 0.5*(sqr-0.5);
tri = tri + 2*(sqr-0.5)*dx;
vtri(n) = tri;
vsaw(n,1) = x;
vsaw(n,2) = saw+0.5;
vblep(n,1) = x;
vblep(n,2) = blep;
x = x + dx;
f = f + df;
x2 = x2 + dx2;
f2 = f2 + df2;
end;
close all
plot(1:nsin, blepm(fix(nharm/2), :)); grid;
wavwrite(0.5*vtri, fs, 'tri.wav');
wavwrite(0.5*vsqr, fs, 'sqr.wav');
wavwrite(0.5*vsaw, fs, 'saw.wav');
wavwrite(0.5*vblep, fs, 'blep.wav');
function h = lagrange(N, delay)
%LAGRANGE h=lagrange(N,delay) returns order N FIR
% filter h which implements given delay
% (in samples). For best results,
% delay should be near N/2 +/- 1.
n = 0:N;
h = ones(1,N+1);
for k = 0:N
index = find(n ~= k);
h(index) = h(index) * (delay-k)./ (n(index)-k);
end
+11
View File
@@ -0,0 +1,11 @@
function eval_harmonics(name)
base = exp(1);
ke = 10;
[x, fs, nbits] = wavread(name);
y = (1 - base.^(-ke*fabs(x))) * sign(x);
wavrite(y, fs, nbits,
+11
View File
@@ -0,0 +1,11 @@
function eval_harmonics(name)
base = exp(1);
ke = 2;
[x, fs, nbits] = wavread(name);
y = (1 - base.^(-ke*abs(x))) .* sign(x);
yname = sprintf('out.wav', name);
wavwrite(y*0.8, fs, nbits, yname);
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+66
View File
@@ -0,0 +1,66 @@
function paramscale(base, kexp, scenter, pcenter, pmin, pmax)
N = 1000;
slider = (0:N)/N;
p = toParam(base, kexp, scenter, pcenter, pmin, pmax, slider);
s = toSlider(base, kexp, scenter, pcenter, pmin, pmax, p);
subplot (2, 1, 1)
plot (0:N, p); grid; xlabel('param');
subplot (2, 1, 2)
plot (0:N, s); grid; xlabel('slider');
function param = toParam(base, kexp, scenter, pcenter, pmin, pmax, slider)
% pcenter = pmax*scenter + pmin*(1-scenter)
for i=1:length(slider),
s = min(1, max(0, slider(i)));
if (s < scenter)
if (base == 1)
p = pcenter-(pcenter-pmin)*(scenter-s)/scenter;
% p = pcenter-(pcenter-pmin)*(scenter-s)/scenter;
else
p = pcenter-(pcenter-pmin)*(base^(kexp*(scenter-s)/scenter)-1)/(base^kexp-1);
% p = pcenter-(pcenter-pmin)*(pow(base,kexp*(scenter-s)/scenter)-1)/(pow(base,kexp)-1);
end
else
if (base == 1)
p = pcenter+(pmax-pcenter)*(-scenter+s)/(1-scenter);
% p = pcenter+(pmax-pcenter)*(-scenter+s)/(1-scenter);
else
p = pcenter+(pmax-pcenter)*(base^(kexp*(-scenter+s)/(1-scenter))-1)/(base^kexp-1);
% p = pcenter+(pmax-pcenter)*(pow(base,kexp*(-scenter+s)/(1-scenter))-1)/(pow(base,kexp)-1);
end;
end
param(i) = p;
end
function slider = toSlider(base, kexp, scenter, pcenter, pmin, pmax, param)
%pcenter = pmax*scenter + pmin*(1-scenter);
for i=1:length(param),
p = param(i);
if (p < pcenter)
if (base == 1)
s = (-pmin+p)*scenter/(pcenter-pmin);
% s = (-pmin+p)*scenter/(pcenter-pmin);
else
s = scenter*(log(base)*kexp-log(-(-pcenter*base^kexp+pmin+p*base^kexp-p)/(pcenter-pmin)))/log(base)/kexp;
% s = scenter*(log(base)*kexp-log(-(-pcenter*pow(base,kexp)+pmin+p*pow(base,kexp)-p)/(pcenter-pmin)))/log(base)/kexp;
end
else
if (base == 1)
s = (pcenter-scenter*pmax-p+scenter*p)/(-pmax+pcenter);
% s = (pcenter-scenter*pmax-p+scenter*p)/(-pmax+pcenter);
else
s = (kexp*log(base)*scenter+log(-(-pcenter*base^kexp+pmax+p*base^kexp-p)/(-pmax+pcenter))-log(-(-pcenter*base^kexp+pmax+p*base^kexp-p)/(-pmax+pcenter))*scenter)/log(base)/kexp;
% s = (kexp*log(base)*scenter+log(-(-pcenter*pow(base,kexp)+pmax+p*pow(base,kexp)-p)/(-pmax+pcenter))-log(-(-pcenter*pow(base,kexp)+pmax+p*pow(base,kexp)-p)/(-pmax+pcenter))*scenter)/log(base)/kexp;
end
end
slider(i) = s;
end
+13
View File
@@ -0,0 +1,13 @@
function paramscale(base, kexp, scenter, pcenter, pmin, pmax)
N = 1000;
slider = (0:N)/N;
p = toParam(base, kexp, scenter, pcenter, pmin, pmax, slider);
s = toSlider(base, kexp, scenter, pcenter, pmin, pmax, p);
subplot (2, 1, 1)
plot (slider, p); grid; xlabel('toParam(s)');
subplot (2, 1, 2)
plot (slider, s); grid; xlabel('toSlider(p)');
+28
View File
@@ -0,0 +1,28 @@
function param = toParam(base, kexp, scenter, pcenter, pmin, pmax, slider)
if ((pcenter < pmin) || (pcenter > pmax))
pcenter = pmax*scenter + pmin*(1-scenter);
end
for i=1:length(slider),
s = min(1, max(0, slider(i)));
if (s < scenter)
if (base == 1)
p = pcenter-(pcenter-pmin)*(scenter-s)/scenter;
% p = pcenter-(pcenter-pmin)*(scenter-s)/scenter;
else
p = pcenter-(pcenter-pmin)*(base^(kexp*(scenter-s)/scenter)-1)/(base^kexp-1);
% p = pcenter-(pcenter-pmin)*(pow(base,kexp*(scenter-s)/scenter)-1)/(pow(base,kexp)-1);
end
else
if (base == 1)
p = pcenter+(pmax-pcenter)*(-scenter+s)/(1-scenter);
% p = pcenter+(pmax-pcenter)*(-scenter+s)/(1-scenter);
else
p = pcenter+(pmax-pcenter)*(base^(kexp*(-scenter+s)/(1-scenter))-1)/(base^kexp-1);
% p = pcenter+(pmax-pcenter)*(pow(base,kexp*(-scenter+s)/(1-scenter))-1)/(pow(base,kexp)-1);
end;
end
param(i) = p;
end
+28
View File
@@ -0,0 +1,28 @@
function slider = toSlider(base, kexp, scenter, pcenter, pmin, pmax, param)
if ((pcenter < pmin) || (pcenter > pmax))
pcenter = pmax*scenter + pmin*(1-scenter);
end
for i=1:length(param),
p = param(i);
if (p < pcenter)
if (base == 1)
s = (-pmin+p)*scenter/(pcenter-pmin);
% s = (-pmin+p)*scenter/(pcenter-pmin);
else
s = scenter*(log(base)*kexp-log(-(-pcenter*base^kexp+pmin+p*base^kexp-p)/(pcenter-pmin)))/log(base)/kexp;
% s = scenter*(log(base)*kexp-log(-(-pcenter*pow(base,kexp)+pmin+p*pow(base,kexp)-p)/(pcenter-pmin)))/log(base)/kexp;
end
else
if (base == 1)
s = (pcenter-scenter*pmax-p+scenter*p)/(-pmax+pcenter);
% s = (pcenter-scenter*pmax-p+scenter*p)/(-pmax+pcenter);
else
s = (kexp*log(base)*scenter+log(-(-pcenter*base^kexp+pmax+p*base^kexp-p)/(-pmax+pcenter))-log(-(-pcenter*base^kexp+pmax+p*base^kexp-p)/(-pmax+pcenter))*scenter)/log(base)/kexp;
% s = (kexp*log(base)*scenter+log(-(-pcenter*pow(base,kexp)+pmax+p*pow(base,kexp)-p)/(-pmax+pcenter))-log(-(-pcenter*pow(base,kexp)+pmax+p*pow(base,kexp)-p)/(-pmax+pcenter))*scenter)/log(base)/kexp;
end
end
slider(i) = s;
end
+3
View File
@@ -0,0 +1,3 @@
build
JUCE-3.1.1
+1 -1
View File
@@ -23,7 +23,7 @@ DEFINES += -D${TARGET}
${OBJS}: $(BUILD_DIR)/%.o : ${PKG_ROOT}/%.cpp
mkdir -p $(dir $@)
g++ ${CXXFLAGS_${CONFIG}} ${DEFINES} ${INCLUDES} -o $@ -c $<
$(CXX) ${CXXFLAGS_${CONFIG}} ${DEFINES} ${INCLUDES} -o $@ -c $<
${BUILD_DIR}/${BUILD_TARGET}: ${OBJS}
ar -q $@ ${OBJS}
+2
View File
@@ -0,0 +1,2 @@
vstsdk2.4
+1 -1
View File
@@ -1,5 +1,5 @@
CONFIG ?= release
PKG_VER := vst_sdk2_4_rev2
PKG_VER := vstsdk2.4
include pkg.mk
+24
View File
@@ -0,0 +1,24 @@
diff -ruN temp/vstsdk2.4/public.sdk/source/vst2.x/audioeffect.cpp ./vstsdk2.4/public.sdk/source/vst2.x/audioeffect.cpp
--- temp/vstsdk2.4/public.sdk/source/vst2.x/audioeffect.cpp 2006-06-07 09:22:02.000000000 +0200
+++ ./vstsdk2.4/public.sdk/source/vst2.x/audioeffect.cpp 2025-08-02 10:01:34.082302630 +0200
@@ -7,7 +7,7 @@
// Created by : Steinberg Media Technologies
// Description : Class AudioEffect (VST 1.0)
//
-// © 2006, Steinberg Media Technologies, All Rights Reserved
+// � 2006, Steinberg Media Technologies, All Rights Reserved
//-------------------------------------------------------------------------------------------------------
#include "audioeffect.h"
@@ -507,9 +507,10 @@
{
VstInt32 digit = value / div;
value -= digit * div;
+ const char digit8 = (char)(digit & 0xFF) + '0';
if (state || digit > 0)
{
- char temp[2] = {'0' + (char)digit, '\0'};
+ char temp[2] = {digit8, '\0'};
vst_strncat (text, temp, maxLen);
state = true;
}
+1 -1
View File
@@ -23,7 +23,7 @@
#define __JUCER_HEADER_BUTTONLED_BUTTONLED_B8510B75__
//[Headers] -- You can add your own extra header files here --
#include "../JuceLibraryCode/JuceHeader.h"
#include <JuceHeader.h>
//[/Headers]
+241
View File
@@ -0,0 +1,241 @@
/*
//#######################################################################################
//Class to insert Midiclock messages into a given MidiBuffer, suitable for block
//based audio callbacks. The class can act on position jumps e.g. "Loops" by sending a
//positioning message.
//NOTE: No ballistik came in to play so I wouldn't recommend using it to drive a mechanical
//tape deck!!!
//
//solar3d-software, April- 10- 2012
//#######################################################################################
*/
#include "JK_MidiClock.h"
#include "juce_core/maths/juce_MathsFunctions.h"
#include <cmath>
#include <synth/synth_defs.h>
JK_MidiClock::JK_MidiClock()
:m_wasPlaying(false),
m_syncPpqPosition(-999.0),
m_posChangeThreshold(0.001),
m_ppqToStartSyncAt(0.0),
m_followSongPosition(true),
m_syncFlag(0),
m_ppqOffset(0)
{
//Prepare Midi messages to avoid blocking the caller of generateMidiclock()!
m_continueMessage = new MidiMessage(MidiMessage::midiContinue());
m_stopMessage = new MidiMessage(MidiMessage::midiStop());
m_clockMessage = new MidiMessage(MidiMessage::midiClock());
m_songPositionMessage = new MidiMessage(MidiMessage::songPositionPointer(0));
m_sample_remain = 0;
}
//=================================================================================================
void JK_MidiClock::generateMidiclock(const AudioPlayHead::CurrentPositionInfo &posInfo,
MidiBuffer* midiBuffer, const int bufferSize, const double sampleRate)
{
//###################################Some explanation about musical tempo #################
//A Time Signature, is two numbers, one on top of the other. The numerator describes the #
//number of Beats in a Bar, while the denominator describes of what note value a Beat is. #
//So 4/4 would be four quarter-notes per Bar, while 4/2 would be four half-notes per Bar, #
//4/8 would be four eighth-notes per Bar, and 2/4 would be two quarter-notes per Bar. #
//#########################################################################################
if (midiBuffer == nullptr)
{
return;
}
//PPQ value of one sample
const double ppqPerSample = (posInfo.bpm / 60.0) / sampleRate;
//PPQ offset to compensate Midi interface latency
double hostPpqPosition = posInfo.ppqPosition + m_ppqOffset * ppqPerSample;
const double clockDistanceInSamples_f = (60.0 * sampleRate) / (posInfo.bpm * 24.0);
const int clockDistanceInSamples = roundToInt(clockDistanceInSamples_f);
const double quant = (60.0 / posInfo.bpm) * sampleRate;
if (posInfo.isPlaying || posInfo.isRecording)
{
if (! m_wasPlaying)
{
//set the point where to start the slave
m_ppqToStartSyncAt = getNearestSixteenthInPPQ(hostPpqPosition);
SynthDebug("m_ppqToStartSyncAt %f", m_ppqToStartSyncAt);
//Special case: Master is set to always start playback from the previous start position...
if (positionJumped(m_syncPpqPosition, hostPpqPosition, sampleRate, ppqPerSample))
{
//Cue Midiclock slave to the nearest sixteenth note to new start position
//because the one calculated in stop mode isn't valid anymore.
sendSongPositionPointerMessage(m_ppqToStartSyncAt, 0, midiBuffer);
SynthDebug("m_ppqToStartSyncAt %f", m_ppqToStartSyncAt);
}
m_sample_remain = clockDistanceInSamples;
}
else
{
//Position jump (loop or manually position change while playing)
if (positionJumped(m_syncPpqPosition, hostPpqPosition, sampleRate, ppqPerSample))
{
SynthDebug("positionJumped");
//set the point where to start the slave
m_ppqToStartSyncAt = getNearestSixteenthInPPQ(hostPpqPosition);
//User has changed position manually while playing
if (m_syncFlag == 0)
{
SynthDebug("Sync=0");
midiBuffer->addEvent(*m_stopMessage, 0);
sendSongPositionPointerMessage(hostPpqPosition, 0, midiBuffer);
m_syncFlag = startSlave_;
}
else
{
SynthDebug("Sync=1");
if (m_followSongPosition)
{
sendSongPositionPointerMessage(hostPpqPosition, 0, midiBuffer);
m_syncFlag = startSlave_;
}
else
{
m_syncFlag = 0;
}
}
m_sample_remain = clockDistanceInSamples;
}
}
for (int posInBuffer = 0; posInBuffer < bufferSize; ++posInBuffer)
{
m_syncPpqPosition = hostPpqPosition + (posInBuffer * ppqPerSample);
//Some hosts like Cubase come up with a wacky ppqPosition
//that could break the timing! Best is to "wait"
//here for the right ppqPosition to jump on.
if (m_syncPpqPosition >= m_ppqToStartSyncAt)
{
if ((m_syncFlag & startSlave_) == startSlave_)
{
midiBuffer->addEvent(*m_continueMessage, posInBuffer);
m_syncFlag &= cycleEnd_;
}
//Cycle mode on
if (posInfo.isLooping && posInfo.ppqLoopStart != posInfo.ppqLoopEnd)
{
const double ppqToCycleEnd = fabs(posInfo.ppqLoopEnd - m_syncPpqPosition);
const int64 samplesToCycleEnd = roundToInt64(ppqToCycleEnd * (60.0 / posInfo.bpm) * sampleRate);
if ((m_syncFlag & cycleEnd_) == 0)
{
if (samplesToCycleEnd <= clockDistanceInSamples) //For fine tuning tweak here
{
//We have reached the cycle- end position
//and must stop the Midiclock slave here
if (m_followSongPosition)
midiBuffer->addEvent(*m_stopMessage, posInBuffer);
m_syncFlag |= cycleEnd_;
}
}
}
}
}
//For best timing we should never interupt Midiclock messages!
//Seems that some slaves constantly adjusting their internal clock
//to Midiclock even if they are in stop mode.
int buffer_remain = bufferSize;
int posInBuffer = 0;
int buffer_consume = 0;
while(buffer_remain)
{
if (m_sample_remain > buffer_remain)
{
m_sample_remain -= buffer_remain;
buffer_consume = buffer_remain;
}
else
{
buffer_consume = m_sample_remain;
m_sample_remain = 0;
}
buffer_remain -= buffer_consume;
posInBuffer += buffer_consume;
if (buffer_remain == 0)
{
break;
}
if (m_sample_remain == 0)
{
m_sample_remain = clockDistanceInSamples;
#if 0
SynthDebug("posInBuffer : %d", posInBuffer);
SynthDebug("clockDistanceInSamples_f (soll) : %f", clockDistanceInSamples_f);
SynthDebug("hostPpqPosition : %f", hostPpqPosition);
SynthDebug("hostPpqPosition (Q) : %f", getNearestInPPQ(hostPpqPosition, 96));
SynthDebug("hostSamplePos (Q) : %f", getNearestInPPQ(hostPpqPosition, 96) * quant);
#endif
midiBuffer->addEvent(*m_clockMessage, roundToInt(posInBuffer));
}
}
m_wasPlaying = true;
}
else
{
//Send positioning message if the user has stopped or if he changed the playhead position
//manually in stop mode! This will also initially cue slave after loading plugin instance.
if (m_wasPlaying || positionJumped(m_syncPpqPosition, hostPpqPosition, sampleRate, ppqPerSample))
{
midiBuffer->addEvent(*m_stopMessage, 0);
sendSongPositionPointerMessage(hostPpqPosition, 0, midiBuffer);
}
m_syncPpqPosition = hostPpqPosition;
m_syncFlag = startSlave_;
m_wasPlaying = false;
}
}
//=================================================================================================
bool JK_MidiClock::positionJumped(const double lastPosInPPQ, const double currentPosInPPQ,
const double sampleRate, const double ppqPerSample)
{
//This returns true if the user has changed the playhead position manually or if
//a jump has occured! The comperator's default threshold is lastPosInPPQ +- 10ms.
if (currentPosInPPQ < lastPosInPPQ - ((m_posChangeThreshold * sampleRate) * ppqPerSample) ||
currentPosInPPQ > lastPosInPPQ + ((m_posChangeThreshold * sampleRate) * ppqPerSample))
return true;
return false;
}
//=================================================================================================
void JK_MidiClock::sendSongPositionPointerMessage(const double ppqPosition, const int posInBuffer, MidiBuffer* buffer)
{
//This will cue the slave to the NEAREST
//16th note to the given ppqPosition.
int intBeat = int(ceil(ppqPosition * 4));
uint8* pSongPositionTime((uint8*)(m_songPositionMessage->getRawData()));
*(pSongPositionTime + 1) = (uint8)(intBeat & 0x7f);
*(pSongPositionTime + 2) = (uint8)((intBeat & 0x3f80)>>7);
buffer->addEvent(*m_songPositionMessage, posInBuffer);
}
@@ -14,7 +14,7 @@
#ifndef __JK_MIDICLOCK
#define __JK_MIDICLOCK
#include "../JuceLibraryCode/JuceHeader.h"
#include <JuceHeader.h>
//#include "Includes.h"
@@ -23,14 +23,14 @@ class JK_MidiClock
public:
JK_MidiClock();
void setPositionJumpThreshold(const double ms) {posChangeThreshold = ms / 1000.0;}
double getPositionJumpThreshold() {return posChangeThreshold;}
void setFollowSongPosition(const bool shouldFollow) {followSongPosition = shouldFollow;}
bool getFollowSongPosition() {return followSongPosition;}
void setOffset(const int offset) {ppqOffset = offset;}
int getOffset() {return ppqOffset;}
void setPositionJumpThreshold(const double ms) {m_posChangeThreshold = ms / 1000.0;}
double getPositionJumpThreshold() {return m_posChangeThreshold;}
void setFollowSongPosition(const bool shouldFollow) {m_followSongPosition = shouldFollow;}
bool getFollowSongPosition() {return m_followSongPosition;}
void setOffset(const int offset) {m_ppqOffset = offset;}
int getOffset() {return m_ppqOffset;}
void generateMidiclock(AudioPlayHead::CurrentPositionInfo &lastPosInfo,
void generateMidiclock(const AudioPlayHead::CurrentPositionInfo &lastPosInfo,
MidiBuffer* midiBuffer,
const int bufferSize,
const double sampleRate);
@@ -45,21 +45,21 @@ private:
}
bool wasPlaying;
double syncPpqPosition;
double posChangeThreshold;
double ppqToStartSyncAt;
bool followSongPosition;
uint8 syncFlag;
int ppqOffset;
bool m_wasPlaying;
double m_syncPpqPosition;
double m_posChangeThreshold;
double m_ppqToStartSyncAt;
bool m_followSongPosition;
uint8 m_syncFlag;
int m_ppqOffset;
int m_sample_remain;
static const int cycleEnd_ = 1;
static const int startSlave_ = 2;
ScopedPointer <MidiMessage> clockMessage;
ScopedPointer <MidiMessage> continueMessage;
ScopedPointer <MidiMessage> stopMessage;
ScopedPointer <MidiMessage> songPositionMessage;
ScopedPointer <MidiMessage> m_clockMessage;
ScopedPointer <MidiMessage> m_continueMessage;
ScopedPointer <MidiMessage> m_stopMessage;
ScopedPointer <MidiMessage> m_songPositionMessage;
void sendSongPositionPointerMessage(const double ppqPosition, const int posInBuffer, MidiBuffer* buffer);
@@ -67,6 +67,7 @@ private:
const double sampleRate, const double ppqPerSample);
double getNearestSixteenthInPPQ(const double ppqPosition) {return ceil(ppqPosition * 4.0) / 4.0;}
double getNearestInPPQ(const double ppqPosition, double quantizer) {return ceil(ppqPosition * quantizer / 4) / (quantizer / 4);}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(JK_MidiClock);
};
+95 -33
View File
@@ -9,7 +9,16 @@
#include <math.h>
#include <stdio.h>
#include "synth/synth_defs.h"
#include <synth/synth_defs.h>
#include <synth/voice.h>
#include <synth/vco.h>
#include <synth/noise.h>
#include <synth/smooth.h>
#include <synth/vcf.h>
#include <synth/lfo.h>
#include <synth/env.h>
#include <synth/param_scale.h>
#include "JaySynthSound.h"
#include "JaySynthVoice.h"
#include "JaySynthMonophonicMGR.h"
@@ -44,6 +53,7 @@ JaySynth::JaySynth (int num_voices, String pathToWaves)
{
File wavesFile = File(pathToWaves);
ScopedPointer<FileInputStream>pIn = wavesFile.createInputStream();
if (pIn != NULL)
pIn->read(synth_common.vco.wt.wave_rawdata, sizeof(synth_common.vco.wt.wave_rawdata));
}
@@ -148,7 +158,7 @@ JaySynth::~JaySynth()
{
delete(m_ppAudioThread[i]);
}
delete(m_ppAudioThread);
delete[] m_ppAudioThread;
SynthDebug("delete params\n");
paramInfoFree(&pb_range[0]);
@@ -161,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;
}
@@ -370,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));
}
@@ -419,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);
@@ -428,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);
@@ -437,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;
@@ -887,22 +907,19 @@ void JaySynth::handlePitchWheel (const int midiChannel, const int wheelValue)
}
void JaySynth::handleController (const int midiChannel,
const int controllerNumber,
const int controllerValue)
void JaySynth::handleController (const midiCC_info_t &midiCC_info)
{
midiCC_info.channel = midiChannel;
midiCC_info.ID = controllerNumber;
midiCC_info.value = (synth_float_t)controllerValue/127;
if (midiCC_info.ID < 0 || midiCC_info.ID >= NUM_MIDI_CONTROLLERS)
return;
last_midiCC_info[controllerNumber] = midiCC_info;
listeners.call (&JaySynthListener::synthChanged, SYNTH_CHANGED_MIDICC, &midiCC_info);
last_midiCC_info[midiCC_info.ID] = midiCC_info;
listeners.call (&JaySynthListener::synthChanged, SYNTH_CHANGED_MIDICC, (midiCC_info_t*)&midiCC_info);
switch (controllerNumber)
switch (midiCC_info.ID)
{
case 0x40: handleSustainPedal (midiChannel, controllerValue >= 64); break;
case 0x42: handleSostenutoPedal (midiChannel, controllerValue >= 64); break;
case 0x43: handleSoftPedal (midiChannel, controllerValue >= 64); break;
case 0x40: handleSustainPedal (midiCC_info.channel, midiCC_info.value_raw >= 64); break;
case 0x42: handleSostenutoPedal (midiCC_info.channel, midiCC_info.value_raw >= 64); break;
case 0x43: handleSoftPedal (midiCC_info.channel, midiCC_info.value_raw >= 64); break;
default: break;
}
@@ -912,8 +929,8 @@ void JaySynth::handleController (const int midiChannel,
{
JaySynthVoice* const voice = voices.getUnchecked (i);
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
voice->controllerMoved (controllerNumber, controllerValue);
if (midiCC_info.channel <= 0 || voice->isPlayingChannel (midiCC_info.channel))
voice->controllerMoved (midiCC_info.ID, midiCC_info.value_raw);
}
}
@@ -987,15 +1004,27 @@ 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
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)
{
@@ -1032,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;
@@ -1087,9 +1119,30 @@ void JaySynth::handleMidiEvent (const MidiMessage& m)
}
if (m.isController())
{
handleController (m.getChannel(),
m.getControllerNumber(),
m.getControllerValue());
midiCC_info_t midiCC_info;
midiCC_info.channel = m.getChannel();
midiCC_info.ID = m.getControllerNumber();
midiCC_info.type = MIDI_CONTROLLER_TYPE_CC;
midiCC_info.value_raw = m.getControllerValue();
midiCC_info.value = (synth_float_t)midiCC_info.value_raw/127;
SynthDebug("CC: controllerNumber=%d, controllerValue=%d", midiCC_info.ID, midiCC_info.value_raw);
bool isNrpnProcessing = mMidiNrpn.process(midiCC_info.ID, midiCC_info.value_raw);
if (mMidiNrpn.isValid())
{
midiCC_info.ID = mMidiNrpn.getId();
midiCC_info.type = MIDI_CONTROLLER_TYPE_NRPN;
midiCC_info.value_raw = mMidiNrpn.getValue();
midiCC_info.value = (synth_float_t)midiCC_info.value_raw/mMidiNrpn.getMaxValue();
SynthDebug("NRPN: controllerNumber=%d, controllerValue=%d", midiCC_info.ID, midiCC_info.value_raw);
}
if (!isNrpnProcessing)
{
handleController (midiCC_info);
}
}
if (m.isMidiMachineControlMessage())
{
@@ -1128,8 +1181,10 @@ void JaySynth::handleMidiEvent (const MidiMessage& m)
if (m.isSongPositionPointer())
{
SynthDebug("SongPositionPointer\n");
m_clock_cnt_bar = (6 * m.getSongPositionPointerMidiBeat()) % m_numMidiClocksPerBar;
SynthDebug("getSongPositionPointerMidiBeat(): %u -> %u clocks\n", m.getSongPositionPointerMidiBeat(), m_clock_cnt_bar);
double bar_16 = 1 + ((double)m.getSongPositionPointerMidiBeat()/4);
double bar_4 = (bar_16 - (int)bar_16);
m_clock_cnt_bar = (int)(bar_4*4);
SynthDebug("getSongPositionPointerMidiBeat(): %f -> %u clocks", bar_4, m_clock_cnt_bar);
}
if (m.isQuarterFrame())
{
@@ -1162,10 +1217,17 @@ void JaySynth::handleMidiEvent (const MidiMessage& m)
info.bpm = (uint32_t)(bpm + 0.5);
info.type = 1;
if (m_clock_cnt_bar == 0)
{
info.type = 2;
SynthDebug("1111111111111111111111111");
}
else
{
SynthDebug("-------------------------");
}
listeners.call (&JaySynthListener::synthChanged, SYNTH_CHANGED_MIDICLOCK, &info);
}
}
+85 -70
View File
@@ -9,11 +9,14 @@
#ifndef _JAYSYNTH_H_
#define _JAYSYNTH_H_
#include "../JuceLibraryCode/JuceHeader.h"
#include "synth/synth_defs.h"
#include "synth/param_scale.h"
#include <synth/synth_defs.h>
#include <synth/param_scale.h>
#include <JuceHeader.h>
#include "JaySynthVoice.h"
#include "JaySynthMonophonicMGR.h"
#include "JaySynthMidiCC.h"
#include "MidiNrpn.h"
#define GET_NUM_VOICES max_num_voices
@@ -44,6 +47,82 @@ private:
class JaySynth : public Synthesiser
{
public:
enum
{
SYNTH_PER_VOICE_CONTROL_VELKEY = 0,
SYNTH_PER_VOICE_CONTROL_SIZE
};
enum
{
SYNTH_CHANGED_PARAM = 0,
SYNTH_CHANGED_MIDICC,
SYNTH_CHANGED_NUM_VOICES_PLAYING,
SYNTH_CHANGED_NOTE_PRESSED,
SYNTH_CHANGED_NOTE_RELEASED,
SYNTH_CHANGED_MIDICLOCK,
SYNTH_CHANGED_SIZE
};
enum
{
MIDI_CONTROLLER_TYPE_CC = 0,
MIDI_CONTROLLER_TYPE_NRPN,
MIDI_CONTROLLER_TYPE_RPN,
MIDI_CONTROLLER_TYPE_SIZE
};
typedef struct _smidiCC_info_t
{
int type;
int channel;
int ID;
int value_raw;
synth_float_t value;
} midiCC_info_t;
typedef struct _smidi_note_info_t
{
int note;
synth_float_t velocity;
} midi_note_info_t;
typedef struct _smidi_quarter_clock_info_t
{
uint32_t type;
uint32_t bpm;
} midi_quarter_clock_info_t;
midiCC_info_t* getLastMidiCC_infos(void)
{
return last_midiCC_info;
}
midiCC_info_t* getLastMidiCC_info(int controllerID)
{
if (controllerID < NUM_MIDI_CONTROLLERS)
return &last_midiCC_info[controllerID];
return NULL;
}
midi_note_info_t* getLastMidiNote_infos()
{
return &last_midi_note_info;
}
void setMidiCC_editBuffer(JaySynthMidiCC *pMidiCC)
{
pMidCC_editBuffer = pMidiCC;
}
typedef struct _sper_voice_control_t
{
synth_float_t ctrl[SYNTH_PER_VOICE_CONTROL_SIZE][SYNTH_NUM_PARAMS];
} per_voice_control_t;
JaySynth(int num_voices, String pathToWaves);
~JaySynth();
void setSampleRate (synth_float_t sampleRate);
@@ -57,7 +136,8 @@ public:
synth_float_t getVolume(void);
int lastPitchWheelValue, lastmidiChannel;
void handlePitchWheel (int midiChannel, int wheelValue);
void handleController (int midiChannel, int controllerNumber, int controllerValue);
void handleController (int midiChannel, int controllerNumber, int controllerValue) {}
void handleController (const midiCC_info_t &midiCC_info);
void updateParameters(void);
void updateParameter(int paramID);
void humanizeModeChanged(void);
@@ -145,72 +225,6 @@ public:
listeners.add (listener);
}
enum
{
SYNTH_PER_VOICE_CONTROL_VELKEY = 0,
SYNTH_PER_VOICE_CONTROL_SIZE
};
enum
{
SYNTH_CHANGED_PARAM = 0,
SYNTH_CHANGED_MIDICC,
SYNTH_CHANGED_NUM_VOICES_PLAYING,
SYNTH_CHANGED_NOTE_PRESSED,
SYNTH_CHANGED_NOTE_RELEASED,
SYNTH_CHANGED_MIDICLOCK,
SYNTH_CHANGED_SIZE
};
typedef struct _smidiCC_info_t
{
int channel;
int ID;
synth_float_t value;
} midiCC_info_t;
typedef struct _smidi_note_info_t
{
int note;
synth_float_t velocity;
} midi_note_info_t;
typedef struct _smidi_quarter_clock_info_t
{
uint32_t type;
uint32_t bpm;
} midi_quarter_clock_info_t;
midiCC_info_t* getLastMidiCC_infos(void)
{
return last_midiCC_info;
}
midiCC_info_t* getLastMidiCC_info(int controllerID)
{
if (controllerID < NUM_MIDI_CONTROLLERS)
return &last_midiCC_info[controllerID];
return NULL;
}
midi_note_info_t* getLastMidiNote_infos()
{
return &last_midi_note_info;
}
void setMidiCC_editBuffer(JaySynthMidiCC *pMidiCC)
{
pMidCC_editBuffer = pMidiCC;
}
typedef struct _sper_voice_control_t
{
synth_float_t ctrl[SYNTH_PER_VOICE_CONTROL_SIZE][SYNTH_NUM_PARAMS];
} per_voice_control_t;
//==============================================================================
private:
@@ -261,6 +275,7 @@ private:
bool m_isMidiClockStarted;
void updateMidiTiming(const MidiMessage& m);
MidiNrpn mMidiNrpn;
};
@@ -726,24 +726,6 @@ JaySynthAudioProcessorEditor::JaySynthAudioProcessorEditor (JaySynthAudioProcess
m_comboBox_vcf_type->addItem (TRANS("BPF"), 3);
m_comboBox_vcf_type->addListener (this);
addAndMakeVisible (m_slider_lfo1_smooth = new Slider ("LFO 2 smooth"));
m_slider_lfo1_smooth->setTooltip (TRANS("LFO 2 smooth [%]"));
m_slider_lfo1_smooth->setRange (0, 1, 0);
m_slider_lfo1_smooth->setSliderStyle (Slider::RotaryVerticalDrag);
m_slider_lfo1_smooth->setTextBoxStyle (Slider::NoTextBox, false, 80, 20);
m_slider_lfo1_smooth->setColour (Slider::rotarySliderFillColourId, Colour (0x7fffffff));
m_slider_lfo1_smooth->setColour (Slider::rotarySliderOutlineColourId, Colour (0x66ffffff));
m_slider_lfo1_smooth->addListener (this);
addAndMakeVisible (m_label_lfo1_smooth = new Label ("LFO 2 smooth",
TRANS("value")));
m_label_lfo1_smooth->setFont (Font (14.00f, Font::plain));
m_label_lfo1_smooth->setJustificationType (Justification::centred);
m_label_lfo1_smooth->setEditable (true, true, false);
m_label_lfo1_smooth->setColour (TextEditor::textColourId, Colours::black);
m_label_lfo1_smooth->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
m_label_lfo1_smooth->addListener (this);
addAndMakeVisible (m_slider_lfo0_smooth = new Slider ("LFO 1 smooth"));
m_slider_lfo0_smooth->setTooltip (TRANS("LFO 1 smooth [%]"));
m_slider_lfo0_smooth->setRange (0, 1, 0);
@@ -762,6 +744,24 @@ JaySynthAudioProcessorEditor::JaySynthAudioProcessorEditor (JaySynthAudioProcess
m_label_lfo0_smooth->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
m_label_lfo0_smooth->addListener (this);
addAndMakeVisible (m_slider_lfo1_smooth = new Slider ("LFO 2 smooth"));
m_slider_lfo1_smooth->setTooltip (TRANS("LFO 2 smooth [%]"));
m_slider_lfo1_smooth->setRange (0, 1, 0);
m_slider_lfo1_smooth->setSliderStyle (Slider::RotaryVerticalDrag);
m_slider_lfo1_smooth->setTextBoxStyle (Slider::NoTextBox, false, 80, 20);
m_slider_lfo1_smooth->setColour (Slider::rotarySliderFillColourId, Colour (0x7fffffff));
m_slider_lfo1_smooth->setColour (Slider::rotarySliderOutlineColourId, Colour (0x66ffffff));
m_slider_lfo1_smooth->addListener (this);
addAndMakeVisible (m_label_lfo1_smooth = new Label ("LFO 2 smooth",
TRANS("value")));
m_label_lfo1_smooth->setFont (Font (14.00f, Font::plain));
m_label_lfo1_smooth->setJustificationType (Justification::centred);
m_label_lfo1_smooth->setEditable (true, true, false);
m_label_lfo1_smooth->setColour (TextEditor::textColourId, Colours::black);
m_label_lfo1_smooth->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
m_label_lfo1_smooth->addListener (this);
addAndMakeVisible (m_slider_lfo2_smooth = new Slider ("LFO 3 smooth"));
m_slider_lfo2_smooth->setTooltip (TRANS("LFO 3 smooth [%]"));
m_slider_lfo2_smooth->setRange (0, 1, 0);
@@ -2497,10 +2497,10 @@ JaySynthAudioProcessorEditor::~JaySynthAudioProcessorEditor()
m_label_synth_name = nullptr;
m_label_synth_version = nullptr;
m_comboBox_vcf_type = nullptr;
m_slider_lfo1_smooth = nullptr;
m_label_lfo1_smooth = nullptr;
m_slider_lfo0_smooth = nullptr;
m_label_lfo0_smooth = nullptr;
m_slider_lfo1_smooth = nullptr;
m_label_lfo1_smooth = nullptr;
m_slider_lfo2_smooth = nullptr;
m_label_lfo2_smooth = nullptr;
m_slider_lfo3_smooth = nullptr;
@@ -3535,10 +3535,10 @@ void JaySynthAudioProcessorEditor::resized()
m_label_synth_name->setBounds (12, 662, 92, 24);
m_label_synth_version->setBounds (978, 662, 132, 24);
m_comboBox_vcf_type->setBounds (880, 74, 68, 16);
m_slider_lfo1_smooth->setBounds (506, 88, 36, 36);
m_label_lfo1_smooth->setBounds (498, 70, 52, 20);
m_slider_lfo0_smooth->setBounds (392, 224, 36, 36);
m_label_lfo0_smooth->setBounds (384, 206, 52, 20);
m_slider_lfo1_smooth->setBounds (506, 88, 36, 36);
m_label_lfo1_smooth->setBounds (498, 70, 52, 20);
m_slider_lfo2_smooth->setBounds (392, 402, 36, 36);
m_label_lfo2_smooth->setBounds (384, 384, 52, 20);
m_slider_lfo3_smooth->setBounds (506, 402, 36, 36);
@@ -5045,10 +5045,6 @@ void JaySynthAudioProcessorEditor::mouseDown (const MouseEvent &m)
if (m.mods.isRightButtonDown() && m.mods.isShiftDown())
{
// Comboboxes don't support midicontroller assignments
if (componentThatWasClicked->getComponentID().contains(String("PARAM_COMBOBOX")))
{
return;
}
MidiCC_PopUp_show(componentThatWasClicked);
}
}
@@ -5164,10 +5160,6 @@ void JaySynthAudioProcessorEditor::MidiCC_PopUp_event(int event)
switch (event)
{
case MidiCC_PopUp::POPUP_OK:
paramID = m_MidiCC_PopUp->getParamID();
getProcessor()->setParam(paramID, m_MidiCC_PopUp->getCurrentParamValue(), false, false);
getProcessor()->controlParam(paramID, m_MidiCC_PopUp->getCurrentControlValue());
m_label_patch_name->setText(getProcessor()->getCurrentProgramName(), juce::NotificationType::dontSendNotification);
deleteAndZero (m_MidiCC_PopUp);
break;
@@ -5366,6 +5358,16 @@ void JaySynthAudioProcessorEditor::paramUpdate(int paramID, bool doHostUpdate)
if (pButton)
{
pButton->setToggleState(getProcessor()->getParameter(paramID) > 0.5, juce::NotificationType::dontSendNotification);
if (pButton == m_button_humanize_enable)
{
getProcessor()->humanizeModeChanged();
}
else if (pButton == m_button_unisono_enable)
{
getProcessor()->unisonoModeChanged();
}
}
}
@@ -21,7 +21,7 @@
#define __JUCE_HEADER_AD3FF14AD0EF4DBE__
//[Headers] -- You can add your own extra header files here --
#include "../JuceLibraryCode/JuceHeader.h"
#include <JuceHeader.h>
#include "PluginProcessor.h"
#include "ButtonLED.h"
#include "MidiCC_PopUp.h"
@@ -177,10 +177,10 @@ private:
ScopedPointer<Label> m_label_synth_name;
ScopedPointer<Label> m_label_synth_version;
ScopedPointer<ComboBox> m_comboBox_vcf_type;
ScopedPointer<Slider> m_slider_lfo1_smooth;
ScopedPointer<Label> m_label_lfo1_smooth;
ScopedPointer<Slider> m_slider_lfo0_smooth;
ScopedPointer<Label> m_label_lfo0_smooth;
ScopedPointer<Slider> m_slider_lfo1_smooth;
ScopedPointer<Label> m_label_lfo1_smooth;
ScopedPointer<Slider> m_slider_lfo2_smooth;
ScopedPointer<Label> m_label_lfo2_smooth;
ScopedPointer<Slider> m_slider_lfo3_smooth;
@@ -7,8 +7,9 @@
==============================================================================
*/
#include "../JuceLibraryCode/JuceHeader.h"
#include "synth/synth_defs.h"
#include <synth/synth_defs.h>
#include <JuceHeader.h>
#include "JaySynthMidiCC.h"
//==============================================================================
@@ -54,11 +55,21 @@ JaySynthMidiCC::JaySynthMidiCC()
/*pmax*/pConstraints->pmax,
/*pinterval*/pConstraints->pinterval);
midiCC_containers[i].doChangeParameter = false;
midiCC_containers[i].controllerID = 0;
midiCC_containers[i].controllerID = 128 + i;
midiCC_containers[i].isAssigned = false;
midiCC_containers[i].doChangeParameter = false;
midiCC_containers[i].doApplyOnPatchInit = false;
if (pConstraints->midiControllerId >= 0)
{
if (pConstraints->midiControllerId > 0)
{
midiCC_containers[i].controllerID = pConstraints->midiControllerId;
}
midiCC_containers[i].doChangeParameter = true;
midiCC_containers[i].isAssigned = true;
}
// Velocity
paramInfoInit(&midiCC_containers[i].curve_vel, i, pConstraints->pName);
paramInfoSet(&midiCC_containers[i].curve_vel,
@@ -173,26 +184,13 @@ int JaySynthMidiCC::findParamID_byName(String name)
return (int)paramNamedValueSet.getWithDefault (toParameterNameXML(name), var(-1));
}
void JaySynthMidiCC::exportXML(String name)
{
File file(name);
XmlElement xml ("JSYNTH_MIDICC_TABLE_FILE");
exportXML(&xml);
file.create();
xml.writeToFile (/*const File &destinationFile*/file, /*const String &dtdToUse*/"JSYNTH_MIDICC_TABLE_FILE" /*, const String &encodingType="UTF-8", int lineWrapLength=60*/);
}
void JaySynthMidiCC::exportXML(XmlElement *pXML_doc) const
void JaySynthMidiCC::export_midi_cc(XmlElement *pXML) const
{
int paramID;
midiCC_container_t const *pMidiContainer;
XmlElement *pXML, *pXML_root, *pXML_PARAM, *pXML_PARAM_INFO;
XmlElement *pXML_PARAM, *pXML_PARAM_INFO;
// Store controller and slider infos
pXML_root = pXML_doc->createNewChildElement (String("JSYNTH_MIDICONTROLLER_TABLE"));
pXML = pXML_root->createNewChildElement (String("TARGETS"));
for (paramID=0; paramID < SYNTH_NUM_PARAMS; paramID++)
{
pMidiContainer = &midiCC_containers[paramID];
@@ -202,29 +200,34 @@ void JaySynthMidiCC::exportXML(XmlElement *pXML_doc) const
pXML_PARAM->setAttribute ("IS_ABSOLUTE", pMidiContainer->doChangeParameter);
pXML_PARAM->setAttribute ("DO_APPLY_ON_PATCH_INIT", pMidiContainer->doApplyOnPatchInit);
pXML_PARAM->setAttribute ("CONTROLLER_ID", pMidiContainer->controllerID);
}
}
void JaySynthMidiCC::export_midi_velkey(XmlElement *pXML) const
{
int paramID;
midiCC_container_t const *pMidiContainer;
XmlElement *pXML_PARAM, *pXML_PARAM_INFO;
// Store controller and slider infos
for (paramID=0; paramID < SYNTH_NUM_PARAMS; paramID++)
{
pMidiContainer = &midiCC_containers[paramID];
pXML_PARAM = pXML->createNewChildElement (String("PARAM_") + String(paramID));
pXML_PARAM->setAttribute ("NAME", toParameterNameXML(pMidiContainer->constraints.pName));
pXML_PARAM->setAttribute ("IS_VELOCITY_ASSIGNED", pMidiContainer->isVelAssigned);
pXML_PARAM->setAttribute ("IS_KEYFOLLOW_ASSIGNED", pMidiContainer->isKeyAssigned);
pXML_PARAM->setAttribute ("VELKEY_COMBINE_OP", pMidiContainer->velKeyCombineOP);
pXML_PARAM_INFO = pXML_PARAM->createNewChildElement (String("RELATIVE"));
// pXML_PARAM_INFO->setAttribute ("BASE", String(pMidiContainer->control.base, 10));
// pXML_PARAM_INFO->setAttribute ("KEXP", String(pMidiContainer->control.kexp, 10));
// pXML_PARAM_INFO->setAttribute ("SCENTER", String(pMidiContainer->control.scenter, 10));
pXML_PARAM_INFO->setAttribute ("PMIN", String(pMidiContainer->control.pmin, 10));
pXML_PARAM_INFO->setAttribute ("PMAX", String(pMidiContainer->control.pmax, 10));
// pXML_PARAM_INFO->setAttribute ("PINTERVAL", String(pMidiContainer->control.pinterval, 10));
pXML_PARAM_INFO = pXML_PARAM->createNewChildElement (String("ABSOLUTE"));
// pXML_PARAM_INFO->setAttribute ("BASE", String(pMidiContainer->param.base, 10));
// pXML_PARAM_INFO->setAttribute ("KEXP", String(pMidiContainer->param.kexp, 10));
// pXML_PARAM_INFO->setAttribute ("SCENTER", String(pMidiContainer->param.scenter, 10));
pXML_PARAM_INFO->setAttribute ("PMIN", String(pMidiContainer->param.pmin, 10));
pXML_PARAM_INFO->setAttribute ("PMAX", String(pMidiContainer->param.pmax, 10));
// pXML_PARAM_INFO->setAttribute ("PINTERVAL", String(pMidiContainer->param.pinterval, 10));
pXML_PARAM_INFO = pXML_PARAM->createNewChildElement (String("VELOCITY_CURVE"));
// pXML_PARAM_INFO->setAttribute ("BASE", String(pMidiContainer->curve_vel.base, 10));
pXML_PARAM_INFO->setAttribute ("KEXP", String(pMidiContainer->curve_vel.kexp, 10));
pXML_PARAM_INFO->setAttribute ("SCENTER", String(pMidiContainer->curve_vel.scenter, 10));
pXML_PARAM_INFO->setAttribute ("PMIN", String(pMidiContainer->curve_vel.pmin, 10));
@@ -232,65 +235,137 @@ void JaySynthMidiCC::exportXML(XmlElement *pXML_doc) const
pXML_PARAM_INFO->setAttribute ("PINTERVAL", String(pMidiContainer->curve_vel.pinterval, 10));
pXML_PARAM_INFO = pXML_PARAM->createNewChildElement (String("KEYFOLLOW_CURVE"));
// pXML_PARAM_INFO->setAttribute ("BASE", String(pMidiContainer->curve_key.base, 10));
pXML_PARAM_INFO->setAttribute ("KEXP", String(pMidiContainer->curve_key.kexp, 10));
pXML_PARAM_INFO->setAttribute ("SCENTER", String(pMidiContainer->curve_key.scenter, 10));
pXML_PARAM_INFO->setAttribute ("PMIN", String(pMidiContainer->curve_key.pmin, 10));
pXML_PARAM_INFO->setAttribute ("PMAX", String(pMidiContainer->curve_key.pmax, 10));
pXML_PARAM_INFO->setAttribute ("PINTERVAL", String(pMidiContainer->curve_key.pinterval, 10));
}
/*
{
int i, controllerID, numDst;
XmlElement *pXML_dst, *pXML_MIDICC;
// todo: romove that in future versions
pXML = pXML_root->createNewChildElement (String("CONTROLLERS"));
// Store MIDI controller assignments
for (controllerID=0; controllerID < NUM_MIDI_CONTROLLERS; controllerID++)
{
pXML_MIDICC = pXML->createNewChildElement (String("MIDICC_") + String(controllerID));
pXML_MIDICC->setAttribute ("ID", controllerID);
pXML_dst = pXML_MIDICC->createNewChildElement (String("TARGETS"));
numDst = getNumDestinations(controllerID);
for (i=0; i < numDst; i++)
{
pMidiContainer = getDestination(controllerID, i);
pXML_dst->setAttribute (String("PARAM") + String(pMidiContainer->constraints.id), pMidiContainer->constraints.pName);
}
}
}
// todo: romove that in future versions
*/
}
int JaySynthMidiCC::importXML(String name)
{
File file(name);
XmlDocument xml(file);
ScopedPointer<XmlElement> pXML_root (xml.getDocumentElement());
return importXML(pXML_root);
}
int JaySynthMidiCC::importXML(XmlElement const *pXML_doc)
int JaySynthMidiCC::import_midi_velkey(XmlElement const *pXML)
{
int i, paramID;
midiCC_container_t *pMidiContainer;
XmlElement *pXML, *pXML_root, *pXML_PARAM, *pXML_PARAM_INFO;
XmlElement *pXML_PARAM, *pXML_PARAM_INFO;
for (i=0; i < NUM_MIDI_CONTROLLERS; i++)
removeDestinations(i);
pXML_root = pXML_doc->getChildByName ("JSYNTH_MIDICONTROLLER_TABLE");
// make sure that it's actually our type of XML object..
if (pXML_root)
if (pXML)
{
pXML = pXML_root->getFirstChildElement();
while(pXML)
pXML_PARAM = pXML->getFirstChildElement();
while(pXML_PARAM)
{
paramID = findParamID_byName(pXML_PARAM->getStringAttribute("NAME"));
if (paramID < 0)
{
pXML_PARAM = pXML_PARAM->getNextElement();
continue;
}
pMidiContainer = &midiCC_containers[paramID];
pMidiContainer->isVelAssigned = pXML_PARAM->getIntAttribute("IS_VELOCITY_ASSIGNED", 0) != 0;
pMidiContainer->isKeyAssigned = pXML_PARAM->getIntAttribute("IS_KEYFOLLOW_ASSIGNED", 0) != 0;
pMidiContainer->velKeyCombineOP = pXML_PARAM->getIntAttribute("VELKEY_COMBINE_OP", VELKEY_OP_ADD);
pXML_PARAM_INFO = pXML_PARAM->getChildByName(String("RELATIVE"));
if (pXML_PARAM_INFO)
{
pMidiContainer->control.pmin = pXML_PARAM_INFO->getDoubleAttribute("PMIN", pMidiContainer->constraints.pmin);
pMidiContainer->control.pmax = pXML_PARAM_INFO->getDoubleAttribute("PMAX", pMidiContainer->constraints.pmax);
}
pXML_PARAM_INFO = pXML_PARAM->getChildByName(String("ABSOLUTE"));
if (pXML_PARAM_INFO)
{
pMidiContainer->param.pmin = pXML_PARAM_INFO->getDoubleAttribute("PMIN", pMidiContainer->constraints.pmin);
pMidiContainer->param.pmax = pXML_PARAM_INFO->getDoubleAttribute("PMAX", pMidiContainer->constraints.pmax);
}
pXML_PARAM_INFO = pXML_PARAM->getChildByName(String("VELOCITY_CURVE"));
if (pXML_PARAM_INFO)
{
pMidiContainer->curve_vel.kexp = pXML_PARAM_INFO->getDoubleAttribute("KEXP", 0);
pMidiContainer->curve_vel.scenter = pXML_PARAM_INFO->getDoubleAttribute("SCENTER", 0.5);
pMidiContainer->curve_vel.pmin = pXML_PARAM_INFO->getDoubleAttribute("PMIN", 0);
pMidiContainer->curve_vel.pmax = pXML_PARAM_INFO->getDoubleAttribute("PMAX", 1);
}
pXML_PARAM_INFO = pXML_PARAM->getChildByName(String("KEYFOLLOW_CURVE"));
if (pXML_PARAM_INFO)
{
pMidiContainer->curve_key.kexp = pXML_PARAM_INFO->getDoubleAttribute("KEXP", 0);
pMidiContainer->curve_key.scenter = pXML_PARAM_INFO->getDoubleAttribute("SCENTER", 0.5);
pMidiContainer->curve_key.pmin = pXML_PARAM_INFO->getDoubleAttribute("PMIN", 0);
pMidiContainer->curve_key.pmax = pXML_PARAM_INFO->getDoubleAttribute("PMAX", 1);
}
if (pXML_PARAM->getIntAttribute("IS_ASSIGNED", 0))
add(pMidiContainer, pXML_PARAM->getIntAttribute("CONTROLLER_ID", 0));
else
remove(pMidiContainer, pXML_PARAM->getIntAttribute("CONTROLLER_ID", 0));
pXML_PARAM = pXML_PARAM->getNextElement();
}
}
isModified = false;
return 0;
}
int JaySynthMidiCC::import_midi_cc(XmlElement const *pXML)
{
int i, paramID;
midiCC_container_t *pMidiContainer;
XmlElement *pXML_PARAM, *pXML_PARAM_INFO;
for (i=0; i < NUM_MIDI_CONTROLLERS; i++)
removeDestinations(i);
// make sure that it's actually our type of XML object..
if (pXML)
{
pXML_PARAM = pXML->getFirstChildElement();
while(pXML_PARAM)
{
paramID = findParamID_byName(pXML_PARAM->getStringAttribute("NAME"));
if (paramID < 0)
{
pXML_PARAM = pXML_PARAM->getNextElement();
continue;
}
pMidiContainer = &midiCC_containers[paramID];
pMidiContainer->doChangeParameter = pXML_PARAM->getIntAttribute("IS_ABSOLUTE", pXML_PARAM->getIntAttribute("DO_CHANGE_PARAMETER", 0)) != 0;
pMidiContainer->doApplyOnPatchInit = pXML_PARAM->getIntAttribute("DO_APPLY_ON_PATCH_INIT", 0) != 0;
if (pXML_PARAM->getIntAttribute("IS_ASSIGNED", 0))
add(pMidiContainer, pXML_PARAM->getIntAttribute("CONTROLLER_ID", 0));
else
remove(pMidiContainer, pXML_PARAM->getIntAttribute("CONTROLLER_ID", 0));
pXML_PARAM = pXML_PARAM->getNextElement();
}
}
isModified = false;
return 0;
}
int JaySynthMidiCC::import_midi_cc_legacy(XmlElement const *pXML_MidiCc)
{
int i, paramID;
midiCC_container_t *pMidiContainer;
XmlElement *pXML, *pXML_PARAM, *pXML_PARAM_INFO;
for (i=0; i < NUM_MIDI_CONTROLLERS; i++)
removeDestinations(i);
// make sure that it's actually our type of XML object..
if (pXML_MidiCc)
{
pXML = pXML_MidiCc->getFirstChildElement();
if(pXML)
{
pXML_PARAM = pXML->getFirstChildElement();
while(pXML_PARAM)
@@ -313,83 +388,42 @@ int JaySynthMidiCC::importXML(XmlElement const *pXML_doc)
pXML_PARAM_INFO = pXML_PARAM->getChildByName(String("RELATIVE"));
if (pXML_PARAM_INFO)
{
// pMidiContainer->control.base = pXML_PARAM_INFO->getDoubleAttribute("BASE", pMidiContainer->constraints.base);
// pMidiContainer->control.kexp = pXML_PARAM_INFO->getDoubleAttribute("KEXP", pMidiContainer->constraints.kexp);
// pMidiContainer->control.scenter = pXML_PARAM_INFO->getDoubleAttribute("SCENTER", pMidiContainer->constraints.scenter);
pMidiContainer->control.pmin = pXML_PARAM_INFO->getDoubleAttribute("PMIN", pMidiContainer->constraints.pmin);
pMidiContainer->control.pmax = pXML_PARAM_INFO->getDoubleAttribute("PMAX", pMidiContainer->constraints.pmax);
// pMidiContainer->control.pinterval = pXML_PARAM_INFO->getDoubleAttribute("PINTERVAL", pMidiContainer->constraints.pinterval);
}
pXML_PARAM_INFO = pXML_PARAM->getChildByName(String("ABSOLUTE"));
if (pXML_PARAM_INFO)
{
// pMidiContainer->param.base = pXML_PARAM_INFO->getDoubleAttribute("BASE", pMidiContainer->constraints.base);
// pMidiContainer->param.kexp = pXML_PARAM_INFO->getDoubleAttribute("KEXP", pMidiContainer->constraints.kexp);
// pMidiContainer->param.scenter = pXML_PARAM_INFO->getDoubleAttribute("SCENTER", pMidiContainer->constraints.scenter);
pMidiContainer->param.pmin = pXML_PARAM_INFO->getDoubleAttribute("PMIN", pMidiContainer->constraints.pmin);
pMidiContainer->param.pmax = pXML_PARAM_INFO->getDoubleAttribute("PMAX", pMidiContainer->constraints.pmax);
// pMidiContainer->param.pinterval = pXML_PARAM_INFO->getDoubleAttribute("PINTERVAL", pMidiContainer->constraints.pinterval);
}
pXML_PARAM_INFO = pXML_PARAM->getChildByName(String("VELOCITY_CURVE"));
if (pXML_PARAM_INFO)
{
// pMidiContainer->curve_vel.base = pXML_PARAM_INFO->getDoubleAttribute("BASE", exp(1.0));
pMidiContainer->curve_vel.kexp = pXML_PARAM_INFO->getDoubleAttribute("KEXP", 0);
pMidiContainer->curve_vel.scenter = pXML_PARAM_INFO->getDoubleAttribute("SCENTER", 0.5);
pMidiContainer->curve_vel.pmin = pXML_PARAM_INFO->getDoubleAttribute("PMIN", 0);
pMidiContainer->curve_vel.pmax = pXML_PARAM_INFO->getDoubleAttribute("PMAX", 1);
// pMidiContainer->curve_vel.pinterval = pXML_PARAM_INFO->getDoubleAttribute("PINTERVAL", 0);
}
pXML_PARAM_INFO = pXML_PARAM->getChildByName(String("KEYFOLLOW_CURVE"));
if (pXML_PARAM_INFO)
{
// pMidiContainer->curve_key.base = pXML_PARAM_INFO->getDoubleAttribute("BASE", exp(1.0));
pMidiContainer->curve_key.kexp = pXML_PARAM_INFO->getDoubleAttribute("KEXP", 0);
pMidiContainer->curve_key.scenter = pXML_PARAM_INFO->getDoubleAttribute("SCENTER", 0.5);
pMidiContainer->curve_key.pmin = pXML_PARAM_INFO->getDoubleAttribute("PMIN", 0);
pMidiContainer->curve_key.pmax = pXML_PARAM_INFO->getDoubleAttribute("PMAX", 1);
// pMidiContainer->curve_key.pinterval = pXML_PARAM_INFO->getDoubleAttribute("PINTERVAL", 0);
}
if (pXML_PARAM->getIntAttribute("IS_ASSIGNED", 0))
add(pMidiContainer, pXML_PARAM->getIntAttribute("CONTROLLER_ID", 0));
else
remove(pMidiContainer, pXML_PARAM->getIntAttribute("CONTROLLER_ID", 0));
pXML_PARAM = pXML_PARAM->getNextElement();
}
// todo: romove that in future versions
{
int controllerID;
XmlElement *pXML_MIDICC, *pXML_DST;
pXML = pXML->getNextElement();
while(pXML)
{
pXML_MIDICC = pXML->getFirstChildElement();
while(pXML_MIDICC)
{
controllerID = pXML_MIDICC->getIntAttribute("ID");
pXML_DST = pXML_MIDICC->getFirstChildElement();
if (pXML_DST)
{
for (i=0; i < pXML_DST->getNumAttributes(); i++)
{
paramID = findParamID_byName(pXML_DST->getAttributeValue(i));
if (paramID < 0)
break;
pMidiContainer = &midiCC_containers[paramID];
add(pMidiContainer, controllerID);
}
}
pXML_MIDICC = pXML_MIDICC->getNextElement();
}
pXML = pXML->getNextElement();
}
}
// todo: romove that in future versions
}
}
isModified = false;
@@ -398,7 +432,7 @@ int JaySynthMidiCC::importXML(XmlElement const *pXML_doc)
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);
@@ -414,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;
@@ -11,8 +11,17 @@
#ifndef _JAYSYNTH_MIDICC_
#define _JAYSYNTH_MIDICC_
#include "../JuceLibraryCode/JuceHeader.h"
#include "synth/synth_defs.h"
#include <JuceHeader.h>
#include <synth/synth_defs.h>
#include <synth/voice.h>
#include <synth/vco.h>
#include <synth/noise.h>
#include <synth/smooth.h>
#include <synth/vcf.h>
#include <synth/lfo.h>
#include <synth/env.h>
#include <synth/param_scale.h>
//==============================================================================
#define MIDICONTROLLER_TYPE_VELKEY 0x00000080
@@ -46,10 +55,11 @@ public:
bool isMidiCCmodified(void) const;
String toParameterNameXML (String const &name) const;
int findParamID_byName(String name);
void exportXML(String name);
void exportXML(XmlElement *pXML_doc) const;
int importXML(String name);
int importXML(XmlElement const *pXML_doc);
void export_midi_cc(XmlElement *pXML_doc) const;
int import_midi_cc(XmlElement const *pXML_doc);
void export_midi_velkey(XmlElement *pXML_doc) const;
int import_midi_velkey(XmlElement const *pXML_doc);
int import_midi_cc_legacy(XmlElement const *pXML_doc);
void add(midiCC_container_t *pObj, int controllerID);
void remove(midiCC_container_t *pObj, int controllerID);
struct midiCC_container_t* getAtParamID(int paramID);
@@ -11,7 +11,7 @@
#include <stdlib.h>
#include <math.h>
#include "synth/synth_defs.h"
#include <synth/synth_defs.h>
#include "JaySynthMonophonicMGR.h"
/** A JaySynth voice that just plays incredible sounds.. */

Some files were not shown because too many files have changed in this diff Show More