From e76552e8dde690d76cbe430ee005d21f4095b83b Mon Sep 17 00:00:00 2001 From: hanez Date: Thu, 5 Nov 2026 23:44:48 +0100 Subject: [PATCH] test: qualify internal devices across DSP, persistence and GUI workflows --- Makefile | 7 + docs/BUILTIN_DEVICES.md | 18 +++ docs/ROADMAP.md | 183 ++++++++++++++++++++++++++- examples/builtin_preview.c | 40 ++++++ tests/integration/test_gui.cpp | 35 +++++ tests/integration/test_reliability.c | 22 ++++ tests/unit/test_builtin.c | 92 +++++++++++++- 7 files changed, 389 insertions(+), 8 deletions(-) create mode 100644 examples/builtin_preview.c diff --git a/Makefile b/Makefile index acfff1a..4668c62 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/docs/BUILTIN_DEVICES.md b/docs/BUILTIN_DEVICES.md index 80fd865..f97322a 100644 --- a/docs/BUILTIN_DEVICES.md +++ b/docs/BUILTIN_DEVICES.md @@ -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. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 839a5b4..b9f95b6 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -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. --- diff --git a/examples/builtin_preview.c b/examples/builtin_preview.c new file mode 100644 index 0000000..4796cfc --- /dev/null +++ b/examples/builtin_preview.c @@ -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 +#include +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; +} diff --git a/tests/integration/test_gui.cpp b/tests/integration/test_gui.cpp index 1b43c08..f352a73 100644 --- a/tests/integration/test_gui.cpp +++ b/tests/integration/test_gui.cpp @@ -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(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,""); diff --git a/tests/integration/test_reliability.c b/tests/integration/test_reliability.c index b42d0b7..ebe1523 100644 --- a/tests/integration/test_reliability.c +++ b/tests/integration/test_reliability.c @@ -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 #include #include @@ -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), diff --git a/tests/unit/test_builtin.c b/tests/unit/test_builtin.c index 778ea8f..480ab5c 100644 --- a/tests/unit/test_builtin.c +++ b/tests/unit/test_builtin.c @@ -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,¬e)==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; +}