Files
JaySynth/tools/testhost/src/TestScenario.h
T
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

86 lines
2.2 KiB
C++

#ifndef TESTHOST_TESTSCENARIO_H_INCLUDED
#define TESTHOST_TESTSCENARIO_H_INCLUDED
#include <JuceHeader.h>
#include "AudioInputSource.h"
/** A timed sequence of actions (MIDI events, parameter changes, patch/bank
load & save) plus render/I-O configuration, parsed from a JSON file.
This is what makes test runs repeatable and batchable instead of ad-hoc
CLI invocations - e.g. one file per regression case, run unattended.
Nothing in here is JaySynth-specific: events that don't apply to a given
plugin (MIDI events sent to a plugin with no MIDI input, say) are simply
harmless, since JUCE's MidiBuffer/AudioProcessor API already tolerates
that generically.
*/
class TestScenario
{
public:
TestScenario() {}
struct Event
{
enum Type
{
noteOn = 0,
noteOff,
controller,
param,
loadPatch,
loadBank,
savePatch,
saveBank
};
int64 sample = 0;
Type type = noteOn;
// MIDI note/controller fields
int channel = 1;
int note = 60;
int velocity = 100;
int controllerNumber = 0;
int controllerValue = 0;
// Parameter-change fields
int paramIndex = 0;
float paramValue = 0.0f;
// Patch/bank load & save fields
String file;
};
/** Parses jsonFile. Relative file paths named inside it (input.wavFile,
loadPatch/loadBank, and any event's "file") are resolved relative to
the JSON file's own directory, not the process's working directory,
so a scenario file is portable regardless of where it's run from.
*/
bool loadFromFile (const File &jsonFile);
double sampleRate = 44100.0;
int blockSize = 512;
int64 durationSamples = 0;
AudioInputSource::Type inputType = AudioInputSource::silence;
double inputFrequencyHz = 440.0;
float inputAmplitude = 0.5f;
String initialLoadPatchFile;
String initialLoadBankFile;
String recordWavFile;
/** Sorted ascending by .sample. */
Array<Event> events;
private:
bool parseTopLevel (const var &root, const File &baseDir);
bool parseInput (const var &inputVar);
bool parseEvents (const var &eventsVar, const File &baseDir);
static bool parseEventType (const String &typeName, Event::Type &outType);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TestScenario)
};
#endif // TESTHOST_TESTSCENARIO_H_INCLUDED