feat: add a bounded WAV sampler with looping, relinking and saved presets
This commit is contained in:
parent
159fa939a1
commit
35bf6a00a1
24 changed files with 213 additions and 32 deletions
|
|
@ -90,3 +90,34 @@ Format 14 adds instrument controls and automation. Readers still accept v1–v13
|
|||
old fixed internal assignments use their original defaults. Older builds reject
|
||||
v14. Tests cover legacy-default sound equality, v13 migration, parameter/preset
|
||||
round trips, pitch-bend offsets, automated block partitions, undo and GUI edits.
|
||||
|
||||
## Phase 42 sample instrument
|
||||
|
||||
**File → Load sampler WAV** assigns a Sample Player to the selected track or
|
||||
relinks its existing sample while retaining its controls. Use the instrument
|
||||
controls editor for root MIDI key (0..127), whole-sample loop (0/1), attack,
|
||||
release and gain (0..4). One root mapping covers every MIDI key using linear
|
||||
rate conversion, including source/output sample-rate differences. Loop and root
|
||||
are integer controls and require hold automation. Live bend spans ±2 semitones.
|
||||
This initial sampler has no multisample zones, loop-point editor or time stretch.
|
||||
Extreme transposition can alias; use appropriate source material.
|
||||
|
||||
The existing WAV reader supports PCM16/24/32 and float32, mono/stereo downmixed
|
||||
to mono. Each sample is at most 16M mono frames; the combined instrument plan
|
||||
budget is 64M frames (256 MiB). Files are at most 256 MiB, with 4096 chunks.
|
||||
There are 32 preallocated voices per track. Loading and freeing happen only on
|
||||
the control thread; playback interpolates cached samples without I/O.
|
||||
|
||||
Missing/invalid samples retain their references and play silence, reported in
|
||||
backend asset-issue counts, GUI preparation warnings and `xtc-check --inspect`.
|
||||
Relink with Load sampler WAV; it is one undoable prepared edit. Allocation and
|
||||
capacity failures reject preparation. Format 15 adds a SAMPLE reference after
|
||||
each instrument's controls/automation; in-project samples save relatively and
|
||||
resolve relative to the project file. Other samples retain absolute paths.
|
||||
Preset files include the reference but do not embed/copy audio.
|
||||
|
||||
`examples/projects/original-pluck.wav` is an original synthesized example with
|
||||
license/provenance in SAMPLE_LICENSE.md. Both are installed with the examples.
|
||||
Tests cover source-rate conversion, one-shot endpoint, looping, release,
|
||||
partitioning, preset references, missing assets, GUI relinking/undo/reload and
|
||||
preparation-allocation/callback guards.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Project format v14 (reads v1–v13)
|
||||
# Project format v15 (reads v1–v14)
|
||||
|
||||
Phases 5–31 use a bounded text format implemented in C99 without a parser library.
|
||||
The extension is `.xtc`. File operations run exclusively on the control thread.
|
||||
|
|
@ -104,7 +104,7 @@ node types 1..4, migrating their clips to empty asset paths. Version 2 accepts
|
|||
node types 1..5 and ASSET records. Both migrate to MIDI=0 and no notes.
|
||||
Version 3 accepts instrument nodes and MIDI/NOTES records. Versions 1–3 migrate
|
||||
all tracks to unity gain, centered pan, mute/solo off and no automation.
|
||||
Saving always writes v14. Unknown versions are rejected without replacing the destination; older
|
||||
Saving always writes v15. Unknown versions are rejected without replacing the destination; older
|
||||
applications without v12 support cannot read current saves. Migration and maximum-path roundtrips are tested.
|
||||
|
||||
## Ownership and failures
|
||||
|
|
@ -274,7 +274,7 @@ must rebuild and initialize the appended `xtc_automation_point.step` field.
|
|||
|
||||
## Phase 28 compatibility and diagnostic contract
|
||||
|
||||
The current reader accepts versions 1–14 and the writer always emits version 14.
|
||||
The current reader accepts versions 1–15 and the writer always emits version 15.
|
||||
Phase 28 made no format change; Phase 31 adds plugin format identity in v11. Saving an older document upgrades it;
|
||||
retain an original backup if an older application must still open it. APP is
|
||||
informational; compatibility depends on the format version, not the producer's
|
||||
|
|
@ -407,3 +407,14 @@ Earlier bindings have no controls/automation; v13 internal instruments retain
|
|||
fixed default sounds. Modern external instruments still require zero controls.
|
||||
Instrument tempo follows project tempo. Instrument preset files use this same
|
||||
schema with exactly one instrument track and no clips; they are metadata only.
|
||||
|
||||
## Version 15 sample references
|
||||
|
||||
Each instrument parameter block ends with `SAMPLE hex_path_or_dash`. Only the
|
||||
internal Sample Player (index 4) may carry a nonempty sample path. Paths within
|
||||
the project directory are relative on disk and resolve against the project
|
||||
file's directory; other paths are absolute. Metadata loading does not read WAVs.
|
||||
Missing/invalid samples remain references and become reported silence during
|
||||
preparation. Controls describe root key, whole-file loop, attack, release and
|
||||
gain. Earlier projects have empty sample paths. Rebuild public API consumers
|
||||
for the additional copied `sample_path` field in `xtc_plugin_spec`.
|
||||
|
|
|
|||
|
|
@ -1195,14 +1195,19 @@ pitch-bend offset regressions. See BUILTIN_DEVICES.md and PROJECT_FORMAT.md.
|
|||
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.
|
||||
- [x] Define supported sample formats, mapping, looping, envelopes, voice limits and memory budgets.
|
||||
- [x] Load/prepare samples outside callbacks and expose an instrument editor with undo.
|
||||
- [x] Persist sample references and mappings with missing-media reporting and relinking.
|
||||
- [x] Provide original or appropriately licensed example sounds and document their licenses.
|
||||
- [x] Test playback rate conversion, boundaries, voice release, missing assets and project reload.
|
||||
|
||||
### Implementation status
|
||||
Planned. Phase 38 supplies no sample instrument or sample library.
|
||||
Implemented: a 32-voice, single-root WAV sampler with whole-file looping,
|
||||
envelopes, gain, cached linear rate conversion and an undoable editor/relink
|
||||
workflow. Format 15 persists relocatable sample references and reads v1–v14.
|
||||
Missing/invalid media becomes reported silence. Original licensed example and
|
||||
core, GUI, persistence, release/loop and allocation/callback tests are included.
|
||||
See BUILTIN_DEVICES.md for memory/format and transposition limits.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
7
examples/projects/SAMPLE_LICENSE.md
Normal file
7
examples/projects/SAMPLE_LICENSE.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Original sampler example
|
||||
|
||||
`original-pluck.wav` was synthesized specifically for xtc using an original
|
||||
exponentially decaying sine/second-harmonic formula. It contains no recordings
|
||||
or third-party samples. It is distributed under the repository LICENSE.
|
||||
|
||||
Load it with File / Load sampler WAV, then play MIDI at root key 60.
|
||||
BIN
examples/projects/original-pluck.wav
Normal file
BIN
examples/projects/original-pluck.wav
Normal file
Binary file not shown.
|
|
@ -156,6 +156,7 @@ xtc_result xtc_midi_plan_create(const xtc_project *project, unsigned int sample_
|
|||
*
|
||||
* @param plan Instrument plan and its single-owner runtime state.
|
||||
*/
|
||||
size_t xtc_midi_plan_asset_issues(const xtc_midi_plan *plan);
|
||||
void xtc_midi_plan_destroy(xtc_midi_plan *plan);
|
||||
/* Set before publication, while quiescent. Zero preserves raw scheduled notes.
|
||||
* Linear boundary envelopes do not delay note scheduling; live note-offs and
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ typedef struct { uint64_t tick; unsigned int port, step; float value; } xtc_plug
|
|||
typedef struct {
|
||||
char path[512], label[64];
|
||||
xtc_plugin_format format;
|
||||
char sample_path[512]; /* Internal sampler WAV reference; empty for other devices. */
|
||||
char identifier[128]; /* CLAP stable ID / VST3 class hex; empty for LADSPA/VST2. */
|
||||
unsigned int index, unique_id, inputs, control_count;
|
||||
unsigned int instrument; /* Zero-input note instrument; track binding only. */
|
||||
|
|
@ -68,6 +69,8 @@ xtc_result xtc_plugin_parameters(const xtc_plugin_spec *spec, double rate, xtc_p
|
|||
/* Metadata-only validation: never opens libraries. */
|
||||
xtc_result xtc_plugin_spec_validate(const xtc_plugin_spec *spec);
|
||||
xtc_result xtc_plugin_create(const xtc_plugin_spec *spec, double rate, size_t frames, xtc_plugin **out);
|
||||
size_t xtc_plugin_sample_frames(const xtc_plugin *plugin);
|
||||
size_t xtc_plugin_asset_issues(const xtc_plugin *plugin);
|
||||
void xtc_plugin_destroy(xtc_plugin *plugin); /* control, after quiescence */
|
||||
/* Single audio owner. No loading/allocation/I/O in host process; trusted run()
|
||||
* must obey realtime rules. No MIDI; max one mono input and one mono output.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define XTC_PROJECT_FORMAT_VERSION 14U
|
||||
#define XTC_PROJECT_FORMAT_VERSION 15U
|
||||
#define XTC_PROJECT_MAX_TRACKS 64U
|
||||
#define XTC_PROJECT_MAX_CLIPS 1024U
|
||||
#define XTC_PROJECT_NAME_BYTES 64U
|
||||
|
|
|
|||
|
|
@ -584,6 +584,7 @@ const char *xtc_backend_asset_issue(const xtc_backend *backend)
|
|||
const char *path = "";
|
||||
if (backend)
|
||||
xtc_audio_plan_issues(backend->audio, &path);
|
||||
if(backend && !path[0] && xtc_midi_plan_asset_issues(backend->midi))return "Sampler WAV missing/invalid (File/Load sampler WAV to relink)";
|
||||
return path;
|
||||
}
|
||||
|
||||
|
|
@ -705,7 +706,7 @@ xtc_result xtc_backend_get_status(const xtc_backend *backend, xtc_backend_status
|
|||
backend->running ? (float)__atomic_load_n(&backend->peak_right, __ATOMIC_RELAXED) / 1000000.0f : 0;
|
||||
out_status->stopping = (int)__atomic_load_n(&backend->stop_requested, __ATOMIC_ACQUIRE);
|
||||
out_status->midi_dropped = __atomic_load_n(&backend->midi_dropped, __ATOMIC_RELAXED);
|
||||
out_status->asset_issues = (unsigned int)xtc_audio_plan_issues(backend->audio, NULL);
|
||||
out_status->asset_issues = (unsigned int)(xtc_audio_plan_issues(backend->audio, NULL)+xtc_midi_plan_asset_issues(backend->midi));
|
||||
out_status->sample_rate = __atomic_load_n(&backend->rate, __ATOMIC_RELAXED);
|
||||
out_status->buffer_frames = __atomic_load_n(&backend->frames, __ATOMIC_RELAXED);
|
||||
out_status->xruns = __atomic_load_n(&backend->xruns, __ATOMIC_RELAXED);
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ MainWindow::MainWindow(bool prompt_unsaved,const char *configuration)
|
|||
menu->add("Track/Live MIDI to built-in sine", 0, instrument_callback, this);
|
||||
menu->add("Edit/Selected instrument controls",0,instrument_callback,this);
|
||||
menu->add("Edit/Selected instrument automation",0,instrument_callback,this);
|
||||
menu->add("File/Load sampler WAV",0,instrument_callback,this);
|
||||
menu->add("File/Save instrument preset",0,instrument_callback,this);
|
||||
menu->add("File/Load instrument preset",0,instrument_callback,this);
|
||||
menu->add("Edit/Insert internal delay on wire", 0, plugin_callback, this);
|
||||
|
|
@ -717,7 +718,7 @@ void MainWindow::start_engine()
|
|||
if (issue[0]) {
|
||||
char message[640];
|
||||
std::snprintf(message, sizeof(message),
|
||||
"Missing/unreadable audio (silent): %s. Select clip and Relink to repair.", issue);
|
||||
"Missing/unreadable audio (silent): %s. Use File/Relink selected audio or Load sampler WAV to repair.", issue);
|
||||
set_label(error_, message);
|
||||
}
|
||||
}
|
||||
|
|
@ -1177,6 +1178,7 @@ xtc_result MainWindow::install_project(xtc_project *candidate, bool reset_histor
|
|||
xtc_midi_plan *offline_midi = nullptr;
|
||||
if (r == XTC_OK && !ready_)
|
||||
r = xtc_midi_plan_create(candidate, static_cast<unsigned int>(settings_.offline_rate), &offline_midi);
|
||||
size_t sample_issues=xtc_midi_plan_asset_issues(offline_midi);
|
||||
xtc_midi_plan_destroy(offline_midi);
|
||||
if (r == XTC_OK && !ready_) {
|
||||
xtc_plan *graph = nullptr;
|
||||
|
|
@ -1195,6 +1197,7 @@ xtc_result MainWindow::install_project(xtc_project *candidate, bool reset_histor
|
|||
issue = path;
|
||||
xtc_audio_plan_destroy(offline);
|
||||
}
|
||||
if(sample_issues)issue="Sampler WAV missing/invalid; File/Load sampler WAV relinks the selected track";
|
||||
if (piano_window_)
|
||||
piano_window_->hide();
|
||||
if (piano_)
|
||||
|
|
@ -1211,7 +1214,7 @@ xtc_result MainWindow::install_project(xtc_project *candidate, bool reset_histor
|
|||
dirty_ = false;
|
||||
if (!issue.empty()) {
|
||||
char text[640];
|
||||
std::snprintf(text, sizeof(text), "Missing/unreadable audio (silent): %s. Select clip and Relink to repair.",
|
||||
std::snprintf(text, sizeof(text), "Missing/unreadable audio (silent): %s. Use File/Relink selected audio or Load sampler WAV to repair.",
|
||||
issue.c_str());
|
||||
set_label(error_, text);
|
||||
} else
|
||||
|
|
@ -1854,6 +1857,14 @@ xtc_result MainWindow::assign_instrument(uint64_t track, const char *path, unsig
|
|||
if (r != XTC_OK) xtc_project_destroy(candidate); else { sequencer_->select(track,clip); mixer_->select_track(track); mark_dirty(); }
|
||||
return r;
|
||||
}
|
||||
xtc_result MainWindow::load_sampler(uint64_t track,const char *path)
|
||||
{
|
||||
if(!path || path[0]!='/' || std::strlen(path)>=sizeof(xtc_plugin_spec{}.sample_path))return XTC_ERR_INVALID_ARGUMENT;
|
||||
xtc_plugin_spec spec{};xtc_result r=xtc_project_get_instrument(project_.get(),track,&spec);
|
||||
if(r!=XTC_OK || spec.format!=XTC_PLUGIN_BUILTIN || spec.index!=4)r=xtc_plugin_probe(XTC_BUILTIN_PATH,4,sample_rate(),&spec);
|
||||
if(r!=XTC_OK)return r;
|
||||
std::strcpy(spec.sample_path,path);return set_instrument_spec(track,spec);
|
||||
}
|
||||
xtc_result MainWindow::set_instrument_spec(uint64_t track,const xtc_plugin_spec &spec)
|
||||
{
|
||||
if(recorder_)return XTC_ERR_INVALID_STATE;
|
||||
|
|
@ -1885,6 +1896,11 @@ void MainWindow::instrument_callback(Fl_Widget *widget, void *context)
|
|||
const char *label = menu ? menu->mvalue()->label() : "Load instrument plugin";
|
||||
uint64_t track = w->sequencer_->selected_track();
|
||||
xtc_result r;
|
||||
if(std::strcmp(label,"Load sampler WAV")==0) {
|
||||
const char *path=fl_input("Sampler WAV path (absolute; replaces/relinks selected track sample)","");
|
||||
if(path) {r=w->load_sampler(track,path);if(r!=XTC_OK)w->report_error("Sampler preparation failed",r);}
|
||||
return;
|
||||
}
|
||||
if(std::strcmp(label,"Selected instrument controls")==0 || std::strcmp(label,"Selected instrument automation")==0 ||
|
||||
std::strcmp(label,"Save instrument preset")==0 || std::strcmp(label,"Load instrument preset")==0) {
|
||||
xtc_plugin_spec spec{};
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ class MainWindow : public Fl_Double_Window {
|
|||
Fl_Widget *velocity_lane() const { return velocity_lane_; }
|
||||
/* Control-thread plugin commands, transactional model adoption. */
|
||||
xtc_result assign_instrument(uint64_t track, const char *path, unsigned int index);
|
||||
xtc_result load_sampler(uint64_t track, const char *path);
|
||||
xtc_result set_instrument_spec(uint64_t track, const xtc_plugin_spec &spec);
|
||||
xtc_result select_live_instrument(uint64_t track);
|
||||
static void instrument_callback(Fl_Widget *, void *);
|
||||
|
|
|
|||
|
|
@ -71,6 +71,11 @@ static int inspect(const xtc_project *project, uint64_t version)
|
|||
quoted(spec.identifier); putchar(' '); quoted(spec.label);
|
||||
printf(": %s ",available ? "available" : "missing/unreadable"); quoted(spec.path); putchar('\n');
|
||||
if (!available) ++missing;
|
||||
if(spec.sample_path[0]) {
|
||||
int sample_available=readable_file(spec.sample_path);
|
||||
printf("Sampler WAV track %" PRIu64 ": %s ",track.id,sample_available ? "available" : "missing/unreadable");
|
||||
quoted(spec.sample_path);putchar('\n');if(!sample_available)++missing;
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("Dependency references missing/unreadable: %zu.\n",missing);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ struct xtc_midi_plan {
|
|||
xtc_plugin *plugins[XTC_PROJECT_MAX_TRACKS];
|
||||
size_t live_track;
|
||||
unsigned int plugin_reset;
|
||||
size_t sample_frames,asset_issues;
|
||||
float scratch[256];
|
||||
xtc_midi_event queue[256];
|
||||
size_t count;
|
||||
|
|
@ -202,6 +203,9 @@ xtc_result xtc_midi_plan_create(const xtc_project *project, unsigned int rate, x
|
|||
if (xtc_project_get_instrument(project,p->mix.tracks[i].id,&spec) == XTC_OK) {
|
||||
xtc_result r = xtc_plugin_create(&spec,rate,256,&p->plugins[i]);
|
||||
if (r != XTC_OK) { xtc_midi_plan_destroy(p); return r; }
|
||||
p->sample_frames+=xtc_plugin_sample_frames(p->plugins[i]);
|
||||
p->asset_issues+=xtc_plugin_asset_issues(p->plugins[i]);
|
||||
if(p->sample_frames>64U*1024U*1024U) {xtc_midi_plan_destroy(p);return XTC_ERR_CAPACITY;}
|
||||
}
|
||||
if (project->live_instrument == p->mix.tracks[i].id) p->live_track = i;
|
||||
}
|
||||
|
|
@ -218,6 +222,7 @@ xtc_result xtc_midi_plan_create(const xtc_project *project, unsigned int rate, x
|
|||
*
|
||||
* @param p Instrument plan and its single-owner runtime state.
|
||||
*/
|
||||
size_t xtc_midi_plan_asset_issues(const xtc_midi_plan *p) {return p ? p->asset_issues : 0;}
|
||||
void xtc_midi_plan_destroy(xtc_midi_plan *p)
|
||||
{
|
||||
size_t i;
|
||||
|
|
|
|||
|
|
@ -482,6 +482,15 @@ static xtc_result read_project(reader *input, xtc_project *project, const char *
|
|||
a->step=(unsigned int)value;if(!decode_float(input->tokens[4],&a->value))return XTC_ERR_FORMAT;
|
||||
}
|
||||
}
|
||||
if(format_version>=15U) {
|
||||
READ("SAMPLE",2U);
|
||||
if(strcmp(input->tokens[1],"-") && !decode_bytes(input->tokens[1],s->sample_path,sizeof(s->sample_path)))return XTC_ERR_FORMAT;
|
||||
if(s->sample_path[0] && s->sample_path[0]!='/') {
|
||||
size_t root=strlen(directory),len=strlen(s->sample_path);
|
||||
if(root+len+2>sizeof(s->sample_path))return XTC_ERR_CAPACITY;
|
||||
memmove(s->sample_path+root+1,s->sample_path,len+1);memcpy(s->sample_path,directory,root);s->sample_path[root]='/';
|
||||
}
|
||||
}
|
||||
if (xtc_plugin_spec_validate(s) != XTC_OK) return XTC_ERR_FORMAT;
|
||||
}
|
||||
READ("LIVE_INSTRUMENT",2U); NUM(1U,project->live_instrument);
|
||||
|
|
@ -654,6 +663,12 @@ static xtc_result write_project(FILE *file, const xtc_project *project, const ch
|
|||
if(fprintf(file,"PARAM %" PRIu64 " %u %u %08" PRIx32 "\n",s->points[k].tick,s->points[k].port,s->points[k].step,bits)<0)return XTC_ERR_IO;
|
||||
}
|
||||
}
|
||||
if(fputs("SAMPLE ",file)==EOF)return XTC_ERR_IO;
|
||||
if(s->sample_path[0]) {
|
||||
const char *asset=s->sample_path;size_t root=strlen(directory);
|
||||
if(!strncmp(asset,directory,root) && asset[root]=='/' && asset[root+1])asset+=root+1;
|
||||
if(!write_name(file,asset))return XTC_ERR_IO;
|
||||
} else if(fputs("-\n",file)==EOF)return XTC_ERR_IO;
|
||||
}
|
||||
if (fprintf(file,"LIVE_INSTRUMENT %" PRIu64 "\n",project->live_instrument) < 0) return XTC_ERR_IO;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
/* Original, dependency-free mono devices. All storage is prepared off-thread. */
|
||||
#include "builtin.h"
|
||||
#include "../audio/wave_internal.h"
|
||||
#include "xtc/xtc_midi.h"
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#define TAU 6.2831853071795864769
|
||||
static const char *names[] = {"Soft Keys", "Sub Bass", "Delay", "Reverb"};
|
||||
static const char *names[] = {"Soft Keys", "Sub Bass", "Delay", "Reverb", "Sample Player"};
|
||||
static const char *controls[2][3] = {{"Time", "Feedback", "Mix"}, {"Decay", "Damping", "Mix"}};
|
||||
static const float defaults[2][3] = {{350, .35f, .25f}, {.7f, .45f, .25f}};
|
||||
static const float lows[2][3] = {{1,0,0},{0,0,0}};
|
||||
static const float highs[2][3] = {{2000,.95f,1},{.95f,.95f,1}};
|
||||
static const char *sample_names[]={"Root key", "Loop", "Attack", "Release", "Gain"};
|
||||
static const float sample_low[]={0,0,.1f,1,0},sample_high[]={127,1,2000,5000,4},sample_defaults[]={60,0,8,80,1};
|
||||
static const char *synth_names[]={"Harmonic", "Attack", "Release", "Cutoff", "Bend range"};
|
||||
static const float synth_low[]={0, .1f, 1, 20, 0};
|
||||
static const float synth_high[]={1, 2000, 5000, 24000, 24};
|
||||
|
|
@ -28,17 +31,19 @@ struct xtc_builtin {
|
|||
double frequencies[128];
|
||||
int sustain[16];
|
||||
float bend[16];
|
||||
xtc_wave sample;
|
||||
size_t sample_issue;
|
||||
};
|
||||
xtc_result xtc_builtin_probe(unsigned int index, xtc_plugin_spec *out)
|
||||
{
|
||||
unsigned int i; xtc_plugin_spec s = {0};
|
||||
if (!out) return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (index >= 4) return XTC_ERR_NOT_FOUND;
|
||||
if (index >= 5) return XTC_ERR_NOT_FOUND;
|
||||
strcpy(s.path,XTC_BUILTIN_PATH); strcpy(s.label,names[index]);
|
||||
s.format = XTC_PLUGIN_BUILTIN; s.index = index; s.unique_id = 0x58544300U+index;
|
||||
s.instrument = index < 2; s.inputs = index < 2 ? 0U : 1U; s.tempo = 120000;
|
||||
s.control_count = index < 2 ? 5U : 3U;
|
||||
for (i=0;i<s.control_count;++i) { s.ports[i]=i; s.values[i]=index<2 ? synth_default(index,i) : defaults[index-2][i]; }
|
||||
s.instrument = index < 2 || index==4; s.inputs = s.instrument ? 0U : 1U; s.tempo = 120000;
|
||||
s.control_count = s.instrument ? 5U : 3U;
|
||||
for (i=0;i<s.control_count;++i) { s.ports[i]=i; s.values[i]=index==4 ? sample_defaults[i] : index<2 ? synth_default(index,i) : defaults[index-2][i]; }
|
||||
*out=s; return XTC_OK;
|
||||
}
|
||||
xtc_result xtc_builtin_validate(const xtc_plugin_spec *s)
|
||||
|
|
@ -48,10 +53,12 @@ xtc_result xtc_builtin_validate(const xtc_plugin_spec *s)
|
|||
s->identifier[0] || s->unique_id!=a.unique_id || s->instrument!=a.instrument || s->inputs!=a.inputs ||
|
||||
(s->control_count!=a.control_count && !(s->index<2 && !s->control_count))) return XTC_ERR_INVALID_ARGUMENT;
|
||||
for(i=0;i<s->control_count;++i) if(s->ports[i]!=i || !isfinite(s->values[i]) ||
|
||||
s->values[i]<(s->index<2 ? synth_low[i] : lows[s->index-2][i]) || s->values[i]>(s->index<2 ? synth_high[i] : highs[s->index-2][i])) return XTC_ERR_INVALID_ARGUMENT;
|
||||
s->values[i]<(s->index==4 ? sample_low[i] : s->index<2 ? synth_low[i] : lows[s->index-2][i]) || s->values[i]>(s->index==4 ? sample_high[i] : s->index<2 ? synth_high[i] : highs[s->index-2][i])) return XTC_ERR_INVALID_ARGUMENT;
|
||||
for(i=0;i<s->point_count;++i) if(s->points[i].port>=s->control_count || !isfinite(s->points[i].value) ||
|
||||
s->points[i].value<(s->index<2 ? synth_low[s->points[i].port] : lows[s->index-2][s->points[i].port]) ||
|
||||
s->points[i].value>(s->index<2 ? synth_high[s->points[i].port] : highs[s->index-2][s->points[i].port])) return XTC_ERR_INVALID_ARGUMENT;
|
||||
s->points[i].value<(s->index==4 ? sample_low[s->points[i].port] : s->index<2 ? synth_low[s->points[i].port] : lows[s->index-2][s->points[i].port]) ||
|
||||
s->points[i].value>(s->index==4 ? sample_high[s->points[i].port] : s->index<2 ? synth_high[s->points[i].port] : highs[s->index-2][s->points[i].port])) return XTC_ERR_INVALID_ARGUMENT;
|
||||
if(s->index==4) {if(s->values[0]!=floorf(s->values[0]) || s->values[1]!=floorf(s->values[1]))return XTC_ERR_INVALID_ARGUMENT;
|
||||
for(i=0;i<s->point_count;++i)if(s->points[i].port<2 && (!s->points[i].step || s->points[i].value!=floorf(s->points[i].value)))return XTC_ERR_INVALID_ARGUMENT;}
|
||||
return XTC_OK;
|
||||
}
|
||||
xtc_result xtc_builtin_describe(const xtc_plugin_spec *s, xtc_plugin_details *out)
|
||||
|
|
@ -62,10 +69,12 @@ xtc_result xtc_builtin_describe(const xtc_plugin_spec *s, xtc_plugin_details *ou
|
|||
strcpy(out->maker,"xtc"); strcpy(out->copyright,"See xtc LICENSE"); out->hard_realtime=1;
|
||||
for(i=0;i<s->control_count;++i) {
|
||||
xtc_parameter_info *p=out->parameters+i; p->id=i;
|
||||
strcpy(p->name,s->index<2 ? synth_names[i] : controls[s->index-2][i]);
|
||||
strcpy(p->name,s->index==4 ? sample_names[i] : s->index<2 ? synth_names[i] : controls[s->index-2][i]);
|
||||
if(s->index<2) {strcpy(p->unit,i==1 || i==2 ? "ms" : i==3 ? "Hz" : i==4 ? "st" : "");} if(s->index==2 && i==0) strcpy(p->unit,"ms");
|
||||
p->minimum=s->index<2 ? synth_low[i] : lows[s->index-2][i]; p->maximum=s->index<2 ? synth_high[i] : highs[s->index-2][i]; p->default_value=s->index<2 ? synth_default(s->index,i) : defaults[s->index-2][i];
|
||||
p->minimum=s->index==4 ? sample_low[i] : s->index<2 ? synth_low[i] : lows[s->index-2][i]; p->maximum=s->index==4 ? sample_high[i] : s->index<2 ? synth_high[i] : highs[s->index-2][i]; p->default_value=s->index==4 ? sample_defaults[i] : s->index<2 ? synth_default(s->index,i) : defaults[s->index-2][i];
|
||||
out->flags[i]=XTC_PLUGIN_BOUNDED_BELOW|XTC_PLUGIN_BOUNDED_ABOVE;
|
||||
if(s->index==4 && i<2)out->flags[i]|=XTC_PLUGIN_INTEGER;
|
||||
if(s->index==4 && (i==2 || i==3))strcpy(p->unit,"ms");
|
||||
}
|
||||
return XTC_OK;
|
||||
}
|
||||
|
|
@ -78,9 +87,16 @@ xtc_result xtc_builtin_create(const xtc_plugin_spec *s, double rate, size_t fram
|
|||
if(!s->instrument) { p->line=calloc(p->length*4,sizeof(float)); if(!p->line) {free(p);return XTC_ERR_OUT_OF_MEMORY;} }
|
||||
for(i=0;i<128;++i) p->frequencies[i]=440*pow(2,((double)i-69)/12)/rate;
|
||||
for(i=0;i<4;++i) { static const double times[]={.0297,.0371,.0411,.0437}; p->comb[i]=(size_t)(rate*times[i])+1; }
|
||||
if(s->index==4) {
|
||||
xtc_result r=s->sample_path[0] ? xtc_wave_load(s->sample_path,&p->sample) : XTC_ERR_NOT_FOUND;
|
||||
if(r==XTC_ERR_OUT_OF_MEMORY || r==XTC_ERR_CAPACITY) {xtc_builtin_destroy(p);return r;}
|
||||
p->sample_issue=r!=XTC_OK;
|
||||
}
|
||||
*out=p; return XTC_OK;
|
||||
}
|
||||
void xtc_builtin_destroy(xtc_builtin *p) { if(p) {free(p->line);free(p);} }
|
||||
size_t xtc_builtin_sample_frames(const xtc_builtin *p) {return p ? p->sample.frames : 0;}
|
||||
size_t xtc_builtin_asset_issues(const xtc_builtin *p) {return p ? p->sample_issue : 0;}
|
||||
void xtc_builtin_destroy(xtc_builtin *p) { if(p) {free(p->sample.samples);free(p->line);free(p);} }
|
||||
static float parameter(const xtc_builtin *p, unsigned int port, uint64_t position)
|
||||
{
|
||||
const xtc_plugin_point *a=NULL,*b=NULL; unsigned int i;
|
||||
|
|
@ -117,6 +133,19 @@ xtc_result xtc_builtin_process(xtc_builtin *p, const float *in, float *out, size
|
|||
}
|
||||
return XTC_OK;
|
||||
}
|
||||
static float sample_voice(xtc_builtin *p,voice *v,uint64_t position)
|
||||
{
|
||||
size_t a,b;float value;double advance;
|
||||
if(!p->sample.frames)return 0;
|
||||
if(v->phase>=(double)p->sample.frames) {v->active=0;return 0;}
|
||||
a=(size_t)v->phase;b=a+1;
|
||||
if(b>=p->sample.frames)b=parameter(p,1,position)>=.5f ? 0 : a;
|
||||
value=p->sample.samples[a]+(p->sample.samples[b]-p->sample.samples[a])*(float)(v->phase-(double)a);
|
||||
advance=(double)p->sample.rate/p->rate*pow(2,((double)v->note-parameter(p,0,position)+p->bend[v->channel]*2)/12);
|
||||
v->phase+=advance;
|
||||
if(parameter(p,1,position)>=.5f)v->phase=fmod(v->phase,(double)p->sample.frames);
|
||||
return value*v->level*v->velocity*parameter(p,4,position);
|
||||
}
|
||||
static void midi_event(xtc_builtin *p, const xtc_midi_event *e)
|
||||
{
|
||||
unsigned int j,slot=0;
|
||||
|
|
@ -145,6 +174,7 @@ xtc_result xtc_builtin_midi(xtc_builtin *p, const xtc_midi_event *events, size_t
|
|||
for(i=0;i<frames;++i) {
|
||||
float sum=0; float harmonic=parameter(p,0,position+i), attack=parameter(p,1,position+i), release=parameter(p,2,position+i);
|
||||
float cutoff=parameter(p,3,position+i), range=parameter(p,4,position+i);
|
||||
if(p->spec.index==4) {attack=parameter(p,2,position+i);release=parameter(p,3,position+i);}
|
||||
float attack_step=attack==8 ? p->attack : (float)(1-exp(-1/(attack*.001*p->rate)));
|
||||
float release_step=release==80 ? p->release : (float)exp(-1/(release*.001*p->rate));
|
||||
float filter_step=(float)(1-exp(-TAU*cutoff/p->rate));
|
||||
|
|
@ -154,6 +184,7 @@ xtc_result xtc_builtin_midi(xtc_builtin *p, const xtc_midi_event *events, size_t
|
|||
if(v->released) v->level *= release_step;
|
||||
else v->level += (1-v->level)*attack_step;
|
||||
if(v->released && v->level<.00001f) {v->active=0;continue;}
|
||||
if(p->spec.index==4) {sum+=sample_voice(p,v,position+i);continue;}
|
||||
step=v->step*pow(2,p->bend[v->channel]*range/12);
|
||||
wave=step<.49 ? sin(TAU*v->phase) : 0;
|
||||
if(step<.16) wave+=(harmonic==.3f ? .3 : (double)harmonic)*sin(3*TAU*v->phase);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ xtc_result xtc_builtin_probe(unsigned int index, xtc_plugin_spec *out);
|
|||
xtc_result xtc_builtin_validate(const xtc_plugin_spec *s);
|
||||
xtc_result xtc_builtin_describe(const xtc_plugin_spec *s, xtc_plugin_details *out);
|
||||
xtc_result xtc_builtin_create(const xtc_plugin_spec *s, double rate, size_t frames, xtc_builtin **out);
|
||||
size_t xtc_builtin_sample_frames(const xtc_builtin *p);
|
||||
size_t xtc_builtin_asset_issues(const xtc_builtin *p);
|
||||
void xtc_builtin_destroy(xtc_builtin *p);
|
||||
xtc_result xtc_builtin_process(xtc_builtin *p, const float *in, float *out, size_t frames, uint64_t position);
|
||||
xtc_result xtc_builtin_midi(xtc_builtin *p, const xtc_midi_event *events, size_t count, float *out, size_t frames, uint64_t position);
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ xtc_result xtc_plugin_describe(const xtc_plugin_spec *s, double rate, xtc_plugin
|
|||
snprintf(out->copyright,sizeof(out->copyright),"See plugin license");
|
||||
return XTC_OK;
|
||||
}
|
||||
size_t xtc_plugin_sample_frames(const xtc_plugin *p) {return p ? xtc_builtin_sample_frames(p->builtin) : 0;}
|
||||
size_t xtc_plugin_asset_issues(const xtc_plugin *p) {return p ? xtc_builtin_asset_issues(p->builtin) : 0;}
|
||||
void xtc_plugin_destroy(xtc_plugin *p)
|
||||
{ if (p) { xtc_builtin_destroy(p->builtin); xtc_ladspa_destroy(p->ladspa); xtc_modern_destroy(p->modern); free(p); } }
|
||||
xtc_result xtc_plugin_create(const xtc_plugin_spec *s, double rate, size_t frames, xtc_plugin **out)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ xtc_result xtc_plugin_spec_validate(const xtc_plugin_spec *s)
|
|||
s->tempo < 20000 || s->tempo > 300000 || s->point_count > XTC_PLUGIN_POINTS) return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (s->format < XTC_PLUGIN_LADSPA || s->format > XTC_PLUGIN_BUILTIN ||
|
||||
!terminated(s->identifier,sizeof(s->identifier))) return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (!terminated(s->sample_path,sizeof(s->sample_path)) || (s->sample_path[0] && (s->sample_path[0]!='/' || s->format!=XTC_PLUGIN_BUILTIN || s->index!=4)))return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (s->format == XTC_PLUGIN_BUILTIN) {
|
||||
if (xtc_builtin_validate(s) != XTC_OK) return XTC_ERR_INVALID_ARGUMENT;
|
||||
} else if (s->format != XTC_PLUGIN_LADSPA) {
|
||||
|
|
|
|||
|
|
@ -748,6 +748,14 @@ int main(int argc, char **argv)
|
|||
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);
|
||||
char sample[1024];CHECK(getcwd(sample,sizeof(sample))!=nullptr);std::strcat(sample,"/examples/projects/original-pluck.wav");
|
||||
CHECK(window.load_sampler(track,sample)==XTC_OK);
|
||||
CHECK(xtc_project_get_instrument(sequence->project(),track,&spec)==XTC_OK && spec.index==4 && !std::strcmp(spec.sample_path,sample));
|
||||
CHECK(window.save_project("/tmp/xtc-sampler-gui.xtc")==XTC_OK);
|
||||
CHECK(window.new_project()==XTC_OK && window.open_project("/tmp/xtc-sampler-gui.xtc")==XTC_OK);
|
||||
CHECK(window.load_sampler(track,"/absent/sample.wav")==XTC_OK);
|
||||
CHECK(window.load_sampler(track,sample)==XTC_OK); // Relink is one prepared history edit.
|
||||
CHECK(window.undo()==XTC_OK && window.undo(true)==XTC_OK);unlink("/tmp/xtc-sampler-gui.xtc");
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ def main():
|
|||
run([str(binaries / 'xtc-check'), '--inspect', str(resources / 'xtc/examples/modern-session.xtc')])
|
||||
assert (docs / 'WORKSPACE.md').is_file()
|
||||
diagnose = subprocess.check_output([str(binaries / 'xtc-check'), '--diagnose'], text=True)
|
||||
assert 'Project formats: 1..14' in diagnose and 'JACK compiled:' in diagnose
|
||||
assert 'Project formats: 1..15' in diagnose and 'JACK compiled:' in diagnose
|
||||
# Replacing a file symlink must not overwrite an unrelated target.
|
||||
protected = stage / 'unrelated-target'
|
||||
protected.write_text('keep unrelated target\n')
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ with tempfile.TemporaryDirectory(prefix="xtc-inspect-") as directory:
|
|||
project.write_text(malformed)
|
||||
check("--validate", project, 1)
|
||||
for text, messages in [
|
||||
(base.replace("XTC_PROJECT 10", "XTC_PROJECT 999"), ("line 1", "999", "1..14")),
|
||||
(base.replace("XTC_PROJECT 10", "XTC_PROJECT 999"), ("line 1", "999", "1..15")),
|
||||
("XTC_PROJECT 10\n", ("line 2", "APP")),
|
||||
(base.replace("TEMPO 120000", "TEMPO 0"), ("line 3", "TEMPO")),
|
||||
(base + "OPTIONAL_FUTURE 1\n", ("Unknown fields",)),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
void *__real_malloc(size_t size);
|
||||
void *__real_calloc(size_t count, size_t size);
|
||||
|
|
@ -156,15 +157,16 @@ static void history_reservations(void)
|
|||
static void builtin_failures(void)
|
||||
{
|
||||
unsigned int id;
|
||||
for(id=0;id<4;++id) {
|
||||
for(id=0;id<5;++id) {
|
||||
int failure, succeeded=0; xtc_plugin_spec spec;
|
||||
assert(xtc_plugin_probe(XTC_BUILTIN_PATH,id,48000,&spec)==XTC_OK);
|
||||
if(id==4) {assert(getcwd(spec.sample_path,sizeof(spec.sample_path)));strcat(spec.sample_path,"/examples/projects/original-pluck.wav");}
|
||||
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};
|
||||
if(spec.instrument) {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;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "xtc/xtc_plugin.h"
|
||||
#include "../../src/audio/wave_internal.h"
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include "xtc/xtc_midi.h"
|
||||
|
|
@ -165,8 +166,8 @@ static void editable(void)
|
|||
assert(xtc_plugin_preset_save(&legacy,path)==XTC_OK);
|
||||
{FILE *f=fopen(path,"r");char lines[32768],line[2048];size_t used=0;assert(f);
|
||||
while(fgets(line,sizeof(line),f)) {
|
||||
if(!strncmp(line,"INSTRUMENT_CONTROLS",19) || !strncmp(line,"INSTRUMENT_AUTOMATION",21))continue;
|
||||
if(!strncmp(line,"XTC_PROJECT 14",14))strcpy(line,"XTC_PROJECT 13\n");
|
||||
if(!strncmp(line,"SAMPLE ",7) || !strncmp(line,"INSTRUMENT_CONTROLS",19) || !strncmp(line,"INSTRUMENT_AUTOMATION",21))continue;
|
||||
if(!strncmp(line,"XTC_PROJECT 15",14))strcpy(line,"XTC_PROJECT 13\n");
|
||||
assert(used+strlen(line)<sizeof(lines));strcpy(lines+used,line);used+=strlen(line);
|
||||
}fclose(f);f=fopen(path,"w");assert(f && fwrite(lines,1,used,f)==used);fclose(f);}
|
||||
assert(xtc_plugin_preset_load(path,&t)==XTC_OK && !t.control_count && t.index==0);
|
||||
|
|
@ -187,7 +188,40 @@ static void editable(void)
|
|||
assert(xtc_midi_decode(bend,3,7,&e)==XTC_OK && e.type==XTC_MIDI_BEND && e.channel==3 && e.velocity==64);}
|
||||
unlink(path);
|
||||
}
|
||||
static void sampler(void)
|
||||
{
|
||||
xtc_plugin_spec s,t;xtc_plugin *a=NULL,*b=NULL;float x[256],y[256],data[]={0,.25f,.5f,.75f};
|
||||
xtc_midi_event e={0,XTC_MIDI_NOTE_ON,0,60,127};size_t i;double envelope=0;
|
||||
char path[]="/tmp/xtc-sample-XXXXXX",preset[]="/tmp/xtc-sample-preset-XXXXXX";
|
||||
int fd=mkstemp(path),pd=mkstemp(preset);FILE *file;assert(fd>=0 && pd>=0);close(fd);close(pd);
|
||||
file=fopen(path,"wb");assert(file && xtc_wave_header(file,24000,4) && xtc_wave_write(file,data,4));fclose(file);
|
||||
assert(xtc_plugin_probe(XTC_BUILTIN_PATH,4,48000,&s)==XTC_OK && s.instrument);
|
||||
strcpy(s.sample_path,path);s.values[2]=.1f;
|
||||
assert(xtc_plugin_create(&s,48000,256,&a)==XTC_OK && !xtc_plugin_asset_issues(a) && xtc_plugin_sample_frames(a)==4);
|
||||
assert(xtc_plugin_process_midi(a,&e,1,x,256)==XTC_OK);
|
||||
for(i=0;i<8;++i) {
|
||||
double expected=i<6 ? (double)i*.125 : .75;
|
||||
envelope+=(1-envelope)*(1-exp(-1/(.1*.001*48000)));
|
||||
assert(fabs(x[i]-expected*envelope)<1e-5);
|
||||
}
|
||||
for(i=8;i<256;++i)assert(x[i]==0);
|
||||
xtc_plugin_destroy(a);s.values[1]=1;
|
||||
assert(xtc_plugin_preset_save(&s,preset)==XTC_OK && xtc_plugin_preset_load(preset,&t)==XTC_OK);
|
||||
assert(!strcmp(t.sample_path,path) && t.values[1]==1);
|
||||
assert(xtc_plugin_create(&s,48000,256,&a)==XTC_OK && xtc_plugin_create(&t,48000,256,&b)==XTC_OK);
|
||||
assert(xtc_plugin_process_midi(a,&e,1,x,256)==XTC_OK && xtc_plugin_process_midi(b,&e,1,y,128)==XTC_OK);
|
||||
assert(xtc_plugin_process_midi(b,NULL,0,y+128,128)==XTC_OK && !memcmp(x,y,sizeof(x)));
|
||||
e.type=XTC_MIDI_NOTE_OFF;assert(xtc_plugin_process_midi(a,&e,1,x,256)==XTC_OK);
|
||||
for(i=0;i<200;++i)assert(xtc_plugin_process_midi(a,NULL,0,x,256)==XTC_OK);
|
||||
for(i=0;i<256;++i)assert(x[i]==0);
|
||||
xtc_plugin_destroy(a);xtc_plugin_destroy(b);unlink(path);
|
||||
assert(xtc_plugin_preset_load(preset,&t)==XTC_OK && !strcmp(t.sample_path,path));
|
||||
assert(xtc_plugin_create(&t,48000,256,&a)==XTC_OK && xtc_plugin_asset_issues(a)==1);
|
||||
e.type=XTC_MIDI_NOTE_ON;assert(xtc_plugin_process_midi(a,&e,1,x,256)==XTC_OK);
|
||||
for(i=0;i<256;++i)assert(x[i]==0);
|
||||
xtc_plugin_destroy(a);unlink(preset);
|
||||
}
|
||||
int main(void) {
|
||||
editable();transport(0);transport(1);automation();roundtrip(0,2);roundtrip(1,3);effects(2);effects(3);synth(0);synth(1);
|
||||
sampler();editable();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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ int main(void)
|
|||
}
|
||||
write_text(bad,"XTC_PROJECT 999\n");
|
||||
assert(xtc_project_load_diagnostic(loaded,bad,&diagnostic) == XTC_ERR_UNSUPPORTED && diagnostic.line == 1 &&
|
||||
diagnostic.format_version == 999 && !strcmp(diagnostic.record,"XTC_PROJECT") && strstr(diagnostic.message,"1..14"));
|
||||
diagnostic.format_version == 999 && !strcmp(diagnostic.record,"XTC_PROJECT") && strstr(diagnostic.message,"1..15"));
|
||||
write_text(bad,"XTC_PROJECT 10\nAPP 0 1 0\nTEMPO nope\n");
|
||||
assert(xtc_project_load_diagnostic(loaded,bad,&diagnostic) == XTC_ERR_FORMAT && diagnostic.line == 3 &&
|
||||
!strcmp(diagnostic.record,"TEMPO"));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue