test: qualify internal devices across DSP, persistence and GUI workflows

This commit is contained in:
Johannes Findeisen 2026-11-05 23:44:48 +01:00
commit e76552e8dd
7 changed files with 389 additions and 8 deletions

View file

@ -527,3 +527,10 @@ $(BUILD_DIR)/test_builtin: tests/unit/test_builtin.c $(BUILD_DIR)/libxtc.a
test: test-builtins
test-builtins: $(BUILD_DIR)/test_builtin
./$(BUILD_DIR)/test_builtin
$(BUILD_DIR)/builtin-preview: $(BUILD_DIR)/examples/builtin_preview.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: builtin-preview
builtin-preview: $(BUILD_DIR)/builtin-preview
./$(BUILD_DIR)/builtin-preview "$(abspath $(BUILD_DIR))/builtin"
-include $(BUILD_DIR)/examples/builtin_preview.d

View file

@ -39,3 +39,21 @@ Validation: `make BUILD_DIR=build/phase38 all gui test test-gui-instruments
test-gui-modern-plugins test-gui-plugins analyze` (join onto one line).
`test-builtins` checks impulse tails, dry mix, feedback bounds, block partition
consistency, note release/panic and instrument/effect project round trips.
## Phase 40 acceptance checks
`make BUILD_DIR=build/phase40 all gui test test-reliability test-gui builtin-preview`
checks all four device identities and GUI paths without external plugin binaries.
The reliability runner fails each internal preparation allocation in turn and
forbids allocation/free, direct file I/O and blocking locks during processing.
Core tests compare persisted rendering for both instruments and both automated
effects, corrupt an internal module identity transactionally, and cover voice
exhaustion, sustain, repeated notes, partitioning and transport reset/seek/panic.
The existing portability suite verifies older project migrations.
For repeatable manual listening, run `make builtin-preview` and audition
`build/builtin-{0,1,2,3}.wav` at a conservative level: 0 is Soft Keys, 1 Sub Bass,
2 Delay and 3 Reverb. Each six-second file includes held tones or two impulses
followed by silence for tails. Check onset/release, unwanted discontinuities,
finite decay and the documented mono character. Generation is verified;
human listening and physical-device qualification remain pending.

View file

@ -1073,6 +1073,8 @@ They require separate model/layout designs and acceptance tests.
## Phase 38 — Internal instruments and effects
### Tasks and acceptance criteria
- [x] Add always-available Soft Keys and Sub Bass MIDI instrument presets.
- [x] Add Delay and Reverb with bounded controls and effect automation.
- [x] Integrate track selection, graph wire insertion, undo and saved projects.
@ -1080,19 +1082,196 @@ They require separate model/layout designs and acceptance tests.
- [x] Verify DSP tails, block partition consistency, note release/panic and persistence.
- [x] Refresh application screenshots and add internal device editor views.
See [BUILTIN_DEVICES.md](BUILTIN_DEVICES.md) for controls and explicit limitations.
### Implementation status
Implemented for the bounded initial device set. Audited 2026-11-05 against
`src/plugin/builtin.c`, the shared plugin host, track instrument selector,
graph commands and v13 persistence. Soft Keys and Sub Bass are fixed 32-voice
presets; Delay and Reverb each expose three bounded, automatable controls.
Preparation owns buffer allocation; processing uses preallocated storage.
Internal devices use virtual identities rather than loading external modules;
the reader retains v1–v12 compatibility. Actual device control screenshots are
listed in [screenshots/README.md](../screenshots/README.md).
`tests/unit/test_builtin.c` covers both effect tails and block partitions, dry
mix, invalid feedback, both instruments' release/panic, mix automation and a
keys/delay project round trip. Shared GUI tests cover instrument selection,
history and plugin editing, but do not constitute a dedicated end-to-end matrix
for all four internal devices. Additional qualification is tracked in Phase 40.
Missing extensions: editable instrument parameters/presets, pitch bend, sample
instruments, click-free/tempo-synced delay, stereo reverb/diffusion, explicit seek
history policy and automatic tail rendering. These are planned in Phases 41–44;
they are not part of the checked initial subset. See
[BUILTIN_DEVICES.md](BUILTIN_DEVICES.md) for controls and explicit limitations.
---
## Phase 39 — Detachable transport and mixer
### Tasks and acceptance criteria
- [x] Float/dock the existing transport and mixer without duplicating state.
- [x] Keep BPM and loop settings in the shared transport controls.
- [x] Recover arrangement space and retain resizable mixer channels.
- [x] Support close-to-dock, Dock all, floating playback shortcuts and safe shutdown.
- [x] Exercise lifecycle, settings and resizing in GUI tests and capture floating views.
See [WORKSPACE.md](WORKSPACE.md) for usage and session-only layout limitations.
### Implementation status
Implemented for the transport and mixer. Audited 2026-11-05 against
`src/gui/main_window.cpp` and `tests/integration/test_gui.cpp`. Detaching
reparents the existing widgets on the GUI thread; BPM, loop settings and mixer
state remain shared. Docking restores ownership and arrangement space, and the
destructor docks panels before destroying their windows. Tests exercise repeated
detach/dock, close-to-dock, shared BPM/loop state, floating playback shortcuts,
main/floating resizing and failed/successful shutdown. Floating screenshots are
listed in [screenshots/README.md](../screenshots/README.md).
Layout and window positions are session-only. The floating transport requires
1220 pixels of width; mixer minimum size is 600 × 420. Other editors cannot be
detached, and general dock layouts are absent. Persistence, compact transport
and additional panels are tracked in Phases 45–46. See
[WORKSPACE.md](WORKSPACE.md) for usage and limits.
### Audit validation (Phases 38–39)
Warning-enabled `make BUILD_DIR=build/roadmap-audit all gui test` passed,
including `test-builtins`. Sequential GUI runs with the same build passed:
`test-gui`, `test-gui-instruments`, `test-gui-modern-plugins` and
`test-gui-plugins`. The initial parallel GUI invocation reported passing test
assertions but failed Xvfb cleanup; sequential reruns exited successfully.
Historical sanitizer/live-JACK evidence in WORKSPACE.md is separate from this
audit; sanitizers, live JACK and physical listening were not rerun here.
---
## Phase 40 — Internal Device Acceptance Coverage
### Goal
Close the dedicated validation gaps in the initial Phase 38 device set.
### Tasks and acceptance criteria
- [x] Exercise all four internal devices through GUI selection/addition, wire insertion where applicable, control editing, undo/redo and save/reopen.
- [x] Round-trip both instruments and both effects, including automated controls, and compare rendered output.
- [x] Verify older-format migration alongside v13 identities and reject malformed internal identities transactionally.
- [x] Test sustain, repeated notes, voice exhaustion, seek/stop/panic and instrument block partition consistency.
- [x] Cover preparation/allocation failures and callback allocation/free guards for internal devices.
- [x] Document repeatable manual listening checks for both presets and representative effect settings.
### Implementation status
Implemented: dedicated all-device GUI/history/persistence tests, exact persisted
render comparisons, malformed-identity rollback, voice/transport regressions and
allocation-failure/callback guards. Warning-enabled core tests, reliability, GUI
and listening-preview generation pass. See BUILTIN_DEVICES.md for repeatable
manual checks; human listening and physical hardware remain unverified.
---
## Phase 41 — Editable Internal Instruments and Presets
### Goal
Extend the fixed Soft Keys/Sub Bass presets into editable instruments.
### Tasks and acceptance criteria
- [ ] Define bounded oscillator, envelope and filter controls with metadata and defaults that preserve existing preset sounds.
- [ ] Provide instrument parameter editing, automation and reusable preset save/load through public C APIs.
- [ ] Support channel pitch bend with a documented range and sustain/note lifecycle behavior.
- [ ] Persist parameters and presets with explicit version migration, undo and failed-preparation rollback.
- [ ] Verify sample timing, block partition consistency, bounds, aliasing behavior and callback safety.
### Implementation status
Planned. The current instruments expose no editable parameters or pitch bend.
---
## Phase 42 — Internal Sample Instrument
### Goal
Provide sample-based sounds beyond the current oscillator presets.
### Tasks and acceptance criteria
- [ ] Define supported sample formats, mapping, looping, envelopes, voice limits and memory budgets.
- [ ] Load/prepare samples outside callbacks and expose an instrument editor with undo.
- [ ] Persist sample references and mappings with missing-media reporting and relinking.
- [ ] Provide original or appropriately licensed example sounds and document their licenses.
- [ ] Test playback rate conversion, boundaries, voice release, missing assets and project reload.
### Implementation status
Planned. Phase 38 supplies no sample instrument or sample library.
---
## Phase 43 — Smooth Delay and Stereo Reverb
### Goal
Address the documented sound-design and transition limits of the basic effects.
### Tasks and acceptance criteria
- [ ] Smooth or crossfade delay-time changes and define automation behavior without abrupt read-head jumps.
- [ ] Add tempo-synced delay divisions with deterministic tempo/seek behavior.
- [ ] Add true stereo processing and reverb diffusion/room controls rather than independent mono instances alone.
- [ ] Preserve old projects' effect identities, parameters and sound, or provide an explicit compatibility mode.
- [ ] Test stability at parameter extremes, stereo response, automation partitions and transition artifacts; document listening results.
### Implementation status
Planned. Delay time can click; reverb uses four damped mono combs without a room
model or stereo diffusion. Current decay is a feedback coefficient, not seconds.
---
## Phase 44 — Effect History and Tail Rendering
### Goal
Make discontinuities and effect tails explicit in playback and export.
### Tasks and acceptance criteria
- [ ] Define retained/reset effect history for seek, stop, loop wrap and project/plan replacement.
- [ ] Implement the chosen history policy without callback allocation or unsafe state sharing.
- [ ] Add bounded automatic or user-configurable tail rendering after the arrangement/export endpoint.
- [ ] Expose tail limits and cancellation, including silent input and non-decaying/high-feedback cases.
- [ ] Test seek/loop reproducibility and complete delay/reverb tails across processing block sizes.
### Implementation status
Planned. Seeks currently retain effect history; tails need continued rendering
and do not automatically extend playback/export.
---
## Phase 45 — Persistent Panel Layouts and Compact Transport
### Goal
Retain useful floating layouts across sessions and support narrower displays.
### Tasks and acceptance criteria
- [ ] Persist docked/floating state and panel geometry through the application preferences workflow.
- [ ] Restore layouts safely when monitors or display sizes change; keep all panels reachable.
- [ ] Provide a compact/responsive floating transport while retaining BPM, loop and playback controls.
- [ ] Preserve close-to-dock, Dock all, shortcuts, editing focus protection and safe shutdown.
- [ ] Test restart, invalid settings, off-screen geometry, narrow sizes and repeated dock/resize cycles.
### Implementation status
Planned. Phase 39 starts docked each session and its transport minimum is 1220 pixels.
---
## Phase 46 — Additional Dockable Editors
### Goal
Extend Phase 39's two-panel arrangement to a general editor workspace.
### Tasks and acceptance criteria
- [ ] Define supported dock locations and layout ownership for sequencer, piano roll and modular graph panels.
- [ ] Detach/dock each supported editor without duplicating project, selection or history state.
- [ ] Support resizable pinned track headers and preserve editing coordinates, scrolling and focus after reparenting.
- [ ] Integrate with Phase 45 layout persistence and provide a safe reset-layout action.
- [ ] Verify minimum/large layouts, cross-panel editing, repeated reparenting and shutdown/error paths.
### Implementation status
Planned. Phase 39 only detaches transport and mixer; broader docking and
resizable pinned headers from Phase 37 remain outstanding. These follow-up
phases cover gaps found in Phases 38–39 and their documented limitations;
unrelated outstanding features in earlier phases retain their existing status.
---

View file

@ -0,0 +1,40 @@
/* Repeatable internal-device listening material; never opens audio hardware. */
#include "xtc/xtc_plugin.h"
#include "xtc/xtc_audio.h"
#include "xtc/xtc_midi.h"
#include <stdio.h>
#include <stdlib.h>
static xtc_result preview(const char *prefix, unsigned int id)
{
xtc_plugin_spec spec; xtc_plugin *plugin=NULL;xtc_recorder *recorder=NULL;
char path[1024];size_t block;xtc_result result;
int n=snprintf(path,sizeof(path),"%s-%u.wav",prefix,id);
if(n<0 || (size_t)n>=sizeof(path))return XTC_ERR_CAPACITY;
result=xtc_plugin_probe(XTC_BUILTIN_PATH,id,48000,&spec);
if(result==XTC_OK)result=xtc_plugin_create(&spec,48000,256,&plugin);
if(result==XTC_OK)result=xtc_recorder_create_replace(path,48000,256,1,&recorder);
for(block=0;result==XTC_OK && block<1125;++block) {
float input[256]={0},output[256];xtc_midi_event event={0,XTC_MIDI_NOTE_ON,0,60,100};
if(id<2) {
size_t count=0;
if(block==0 || block==94 || block==188) {event.pitch=block==0 ? 48U : block==94 ? 60U : 67U;count=1;}
if(block==282) {event.type=XTC_MIDI_CONTROL;event.pitch=123;event.velocity=0;count=1;}
result=xtc_plugin_process_midi(plugin,&event,count,output,256);
} else {
if(block==0 || block==94) input[0]=.5f;
result=xtc_plugin_process(plugin,input,output,256);
}
if(result==XTC_OK) {xtc_recorder_push(recorder,output,256);result=xtc_recorder_drain(recorder);}
}
if(result==XTC_OK)result=xtc_recorder_finish(recorder);
xtc_recorder_destroy(recorder);xtc_plugin_destroy(plugin);
if(result==XTC_OK)printf("Wrote %s\n",path);
return result;
}
int main(int argc,char **argv)
{
unsigned int id;
if(argc!=2) {fprintf(stderr,"Usage: %s OUTPUT_PREFIX\n",argv[0]);return EXIT_FAILURE;}
for(id=0;id<4;++id) if(preview(argv[1],id)!=XTC_OK)return EXIT_FAILURE;
return EXIT_SUCCESS;
}

View file

@ -712,6 +712,41 @@ int main(int argc, char **argv)
CHECK(!canvas->select_at(-20,-20) && canvas->selected_node() == SIZE_MAX);
window.do_callback();
}
// Internal devices must work without plugin fixture environment variables.
for (unsigned int instrument=0;instrument<2;++instrument) {
reset(); MainWindow window(false,""); window.show();
CHECK(window.create_track(true)==XTC_OK);
auto *sequence=timeline(&window); auto *canvas=graph_canvas(&window);
uint64_t track=sequence->selected_track();
auto *selector=sequence->instrument_choice(track); CHECK(selector);
selector->value(static_cast<int>(instrument+1));selector->do_callback();
xtc_plugin_spec spec{};
CHECK(xtc_project_get_instrument(sequence->project(),track,&spec)==XTC_OK && spec.index==instrument);
CHECK(window.undo()==XTC_OK && window.undo(true)==XTC_OK);
xtc_connection_info wire{};
CHECK(xtc_graph_get_connection(xtc_project_graph(sequence->project()),0,&wire)==XTC_OK);
unsigned int effect=instrument+2;size_t id=SIZE_MAX;
CHECK(window.insert_plugin(wire,XTC_BUILTIN_PATH,effect,&id)==XTC_OK);
CHECK(canvas->select_node(id));
CHECK(window.set_plugin_control(id,2,.75f)==XTC_OK);
CHECK(window.set_plugin_automation(id,{960,2,0,.4f},false)==XTC_OK);
CHECK(window.undo()==XTC_OK && window.undo(true)==XTC_OK);
CHECK(xtc_graph_get_plugin(xtc_project_graph(sequence->project()),id,&spec)==XTC_OK);
CHECK(spec.index==effect && spec.values[2]==.75f && spec.point_count==1);
xtc_plugin_details details{};xtc_plugin_spec edited{};
CHECK(xtc_plugin_describe(&spec,48000,&details)==XTC_OK);
Fl::add_timeout(.05,[](void *) {press(find(Fl::first_window(),"Apply"));});
CHECK(xtc_edit_plugin(spec,details,edited) && edited.index==effect);
CHECK(window.save_project("/tmp/xtc-internal-gui.xtc")==XTC_OK);
CHECK(window.new_project()==XTC_OK && window.open_project("/tmp/xtc-internal-gui.xtc")==XTC_OK);
CHECK(xtc_project_get_instrument(sequence->project(),track,&spec)==XTC_OK && spec.index==instrument);
CHECK(xtc_graph_get_plugin(xtc_project_graph(sequence->project()),id,&spec)==XTC_OK && spec.index==effect && spec.point_count==1);
size_t added=SIZE_MAX;CHECK(window.add_plugin(XTC_BUILTIN_PATH,effect,&added)==XTC_OK);
CHECK(added!=id);CHECK(window.undo()==XTC_OK);
const xtc_project *before=sequence->project();reserve_result=XTC_ERR_OUT_OF_MEMORY;
CHECK(window.add_plugin(XTC_BUILTIN_PATH,effect)==XTC_ERR_OUT_OF_MEMORY && sequence->project()==before);
reserve_result=XTC_OK;unlink("/tmp/xtc-internal-gui.xtc");window.do_callback();
}
if (const char *module = std::getenv("XTC_TEST_PLUGIN")) {
reset();
MainWindow window(false,"");

View file

@ -3,6 +3,7 @@
#include "xtc/xtc_realtime.h"
#include "xtc/xtc_native.h"
#include "xtc/xtc_control.h"
#include "xtc/xtc_plugin.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
@ -152,8 +153,29 @@ static void history_reservations(void)
xtc_project_destroy(snapshot); xtc_project_destroy(project); xtc_history_destroy(history);
}
static void builtin_failures(void)
{
unsigned int id;
for(id=0;id<4;++id) {
int failure, succeeded=0; xtc_plugin_spec spec;
assert(xtc_plugin_probe(XTC_BUILTIN_PATH,id,48000,&spec)==XTC_OK);
for(failure=0;failure<16;++failure) {
xtc_plugin *plugin=NULL; xtc_result result; float in[256]={0},out[256];
fail_after=failure;result=xtc_plugin_create(&spec,48000,256,&plugin);fail_after=-1;
if(result!=XTC_OK) {assert(result==XTC_ERR_OUT_OF_MEMORY && !plugin);continue;}
processing=1;
if(id<2) {xtc_midi_event event={0,XTC_MIDI_NOTE_ON,0,60,100};
assert(xtc_plugin_process_midi(plugin,&event,1,out,256)==XTC_OK);
} else assert(xtc_plugin_process(plugin,in,out,256)==XTC_OK);
processing=0;xtc_plugin_destroy(plugin);succeeded=1;break;
}
assert(succeeded);
}
}
int main(void)
{
builtin_failures();
graph_limits();
history_reservations();
xtc_native_descriptor descriptor = {XTC_NATIVE_ABI_VERSION, sizeof(descriptor),

View file

@ -53,21 +53,101 @@ static void automation(void)
for(i=0;i<128;++i) assert(fabsf(out[i]-(i<100 ? 1-(float)i/100 : 0))<1e-6f);
xtc_plugin_destroy(p);
}
static void roundtrip(void)
static void roundtrip(unsigned int instrument, unsigned int effect)
{
xtc_project *p=NULL,*q=NULL; xtc_plugin_spec s,t; uint64_t track; size_t id; xtc_graph *g=NULL;
xtc_plugin *a=NULL,*b=NULL; float input[256]={0},x[256],y[256]; size_t k;
char path[]="/tmp/xtc-builtin-XXXXXX"; int fd=mkstemp(path); assert(fd>=0); close(fd);
assert(xtc_project_create(&p)==XTC_OK); assert(xtc_project_create(&q)==XTC_OK);
assert(xtc_project_add_track(p,"Keys",&track)==XTC_OK);
assert(xtc_plugin_probe(XTC_BUILTIN_PATH,0,48000,&s)==XTC_OK);
assert(xtc_plugin_probe(XTC_BUILTIN_PATH,instrument,48000,&s)==XTC_OK);
assert(xtc_project_set_instrument(p,track,&s)==XTC_OK);
assert(xtc_plugin_probe(XTC_BUILTIN_PATH,2,48000,&s)==XTC_OK); s.values[0]=250;
assert(xtc_plugin_probe(XTC_BUILTIN_PATH,effect,48000,&s)==XTC_OK);
s.point_count=2; s.points[0]=(xtc_plugin_point){0,2,0,.1f}; s.points[1]=(xtc_plugin_point){960,2,0,.6f};
assert(xtc_graph_clone(xtc_project_graph(p),&g)==XTC_OK);
assert(xtc_graph_add_plugin(g,&s,&id)==XTC_OK); assert(xtc_project_set_graph(p,g)==XTC_OK);xtc_graph_destroy(g);
assert(xtc_project_save(p,path)==XTC_OK); assert(xtc_project_load(q,path)==XTC_OK);
assert(xtc_graph_get_plugin(xtc_project_graph(q),id,&t)==XTC_OK); assert(t.values[0]==250 && t.point_count==2);
assert(xtc_project_get_instrument(q,track,&t)==XTC_OK && t.index==0 && t.format==XTC_PLUGIN_BUILTIN);
assert(xtc_graph_get_plugin(xtc_project_graph(q),id,&t)==XTC_OK); assert(!memcmp(&s,&t,sizeof(s)));
assert(xtc_plugin_create(&s,48000,256,&a)==XTC_OK && xtc_plugin_create(&t,48000,256,&b)==XTC_OK);
for(k=0;k<256;++k) {
input[0]=k==0 ? 1 : 0;
assert(xtc_plugin_process(a,input,x,256)==XTC_OK && xtc_plugin_process(b,input,y,256)==XTC_OK);
assert(!memcmp(x,y,sizeof(x)));
}
xtc_plugin_destroy(a);xtc_plugin_destroy(b);
assert(xtc_project_get_instrument(q,track,&t)==XTC_OK && t.index==instrument && t.format==XTC_PLUGIN_BUILTIN);
assert(xtc_project_get_instrument(p,track,&s)==XTC_OK);
assert(xtc_plugin_create(&s,48000,256,&a)==XTC_OK && xtc_plugin_create(&t,48000,256,&b)==XTC_OK);
{ xtc_midi_event e={19,XTC_MIDI_NOTE_ON,0,60,100};
assert(xtc_plugin_process_midi(a,&e,1,x,256)==XTC_OK && xtc_plugin_process_midi(b,&e,1,y,256)==XTC_OK);
assert(!memcmp(x,y,sizeof(x))); }
xtc_plugin_destroy(a);xtc_plugin_destroy(b);
/* Corrupt a serialized identity, retaining a valid destination project. */
{ FILE *file=fopen(path,"r+"); char data[32768]; size_t n; char *label;
assert(file);n=fread(data,1,sizeof(data)-1,file);data[n]=0;
label=strstr(data,effect==2 ? "44656c6179" : "526576657262");
/* Names are hex encoded; module corruption is independent of name codec. */
(void)label; label=strstr(data,"2f7874632f696e7465726e616c");
assert(label);label[0]='3';rewind(file);assert(fwrite(data,1,n,file)==n);fclose(file);
assert(xtc_project_load(q,path)!=XTC_OK);
assert(xtc_project_get_instrument(q,track,&t)==XTC_OK && t.index==instrument); }
xtc_project_destroy(p);xtc_project_destroy(q);unlink(path);
}
int main(void) {automation();roundtrip();effects(2);effects(3);synth(0);synth(1);puts("Built-in devices passed.");return 0;}
static void synth_partition(unsigned int id)
{
xtc_plugin_spec s; xtc_plugin *a=NULL,*b=NULL; float x[256],y[256]; size_t i,k;
xtc_midi_event events[40];
assert(xtc_plugin_probe(XTC_BUILTIN_PATH,id,48000,&s)==XTC_OK);
assert(xtc_plugin_create(&s,48000,256,&a)==XTC_OK && xtc_plugin_create(&s,48000,256,&b)==XTC_OK);
/* Exhaust voices, repeat a key, sustain its note-off, then release pedal. */
for(i=0;i<36;++i) events[i]=(xtc_midi_event){(unsigned int)i,XTC_MIDI_NOTE_ON,0,40+(unsigned int)i,100};
events[36]=(xtc_midi_event){40,XTC_MIDI_NOTE_ON,0,60,80};
events[37]=(xtc_midi_event){50,XTC_MIDI_CONTROL,0,64,127};
events[38]=(xtc_midi_event){60,XTC_MIDI_NOTE_OFF,0,60,0};
events[39]=(xtc_midi_event){80,XTC_MIDI_CONTROL,0,64,0};
assert(xtc_plugin_process_midi(a,events,40,x,256)==XTC_OK);
assert(xtc_plugin_process_midi(b,events,40,y,128)==XTC_OK);
assert(xtc_plugin_process_midi(b,NULL,0,y+128,128)==XTC_OK);
assert(!memcmp(x,y,sizeof(x)));
for(k=0;k<100;++k) {
assert(xtc_plugin_process_midi(a,NULL,0,x,256)==XTC_OK);
assert(xtc_plugin_process_midi(b,NULL,0,y,128)==XTC_OK);
assert(xtc_plugin_process_midi(b,NULL,0,y+128,128)==XTC_OK);
for(i=0;i<256;++i) assert(isfinite(x[i]) && x[i]==y[i]);
}
events[0]=(xtc_midi_event){0,XTC_MIDI_ALL_OFF,0,0,0};
assert(xtc_plugin_process_midi(a,events,1,x,256)==XTC_OK);
for(i=0;i<256;++i) assert(x[i]==0);
xtc_plugin_destroy(a);xtc_plugin_destroy(b);
}
static void transport(unsigned int id)
{
xtc_project *project=NULL;xtc_midi_plan *plan=NULL,*fresh=NULL;xtc_plugin_spec spec;
uint64_t track,clip,note;float x[256],y[256];size_t k;
assert(xtc_project_create(&project)==XTC_OK);
assert(xtc_project_add_track(project,"Transport",&track)==XTC_OK);
assert(xtc_project_add_midi_clip(project,track,0,3840,&clip)==XTC_OK);
assert(xtc_project_add_note(project,clip,0,1920,60,100,0,&note)==XTC_OK);
assert(xtc_plugin_probe(XTC_BUILTIN_PATH,id,48000,&spec)==XTC_OK);
assert(xtc_project_set_instrument(project,track,&spec)==XTC_OK);
assert(xtc_midi_plan_create(project,48000,&plan)==XTC_OK);
assert(xtc_midi_plan_create(project,48000,&fresh)==XTC_OK);
assert(xtc_midi_plan_render(plan,0,NULL,0,x,256)==XTC_OK);
/* Discontinuity chases with a fresh envelope, identical to a fresh plan. */
assert(xtc_midi_plan_render(plan,1000,NULL,0,x,256)==XTC_OK);
assert(xtc_midi_plan_render(fresh,1000,NULL,0,y,256)==XTC_OK);
assert(!memcmp(x,y,sizeof(x)));
xtc_midi_plan_panic(plan,1256);
for(k=0;k<200;++k)assert(xtc_midi_plan_render(plan,1256+k*256,NULL,0,x,256)==XTC_OK);
for(k=0;k<256;++k)assert(fabsf(x[k])<1e-5f);
xtc_midi_plan_reset(plan);
assert(xtc_midi_plan_render(plan,0,NULL,0,x,256)==XTC_OK);
xtc_midi_plan_reset(fresh);
assert(xtc_midi_plan_render(fresh,0,NULL,0,y,256)==XTC_OK);
assert(!memcmp(x,y,sizeof(x)));
xtc_midi_plan_destroy(plan);xtc_midi_plan_destroy(fresh);xtc_project_destroy(project);
}
int main(void) {
transport(0);transport(1);automation();roundtrip(0,2);roundtrip(1,3);effects(2);effects(3);synth(0);synth(1);
synth_partition(0);synth_partition(1);puts("Built-in devices passed.");return 0;
}