feat(project): persist bounded per-track effect chains (phase 81)
This commit is contained in:
parent
e9d783146f
commit
7489f86351
15 changed files with 159 additions and 12 deletions
8
Makefile
8
Makefile
|
|
@ -574,3 +574,11 @@ test: test-sample
|
|||
test-gui-sample: $(BUILD_DIR)/test_gui
|
||||
XTC_TEST_SAMPLE_EDITOR_ONLY=1 xvfb-run -a -s "-screen 0 1280x900x24" ./$(BUILD_DIR)/test_gui
|
||||
-include $(BUILD_DIR)/src/gui/sample_editor.d $(BUILD_DIR)/tests/unit/test_sample.d
|
||||
|
||||
$(BUILD_DIR)/test_track_fx: $(BUILD_DIR)/tests/unit/test_track_fx.o $(BUILD_DIR)/libxtc.a
|
||||
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
|
||||
.PHONY: test-track-fx
|
||||
test-track-fx: $(BUILD_DIR)/test_track_fx
|
||||
./$(BUILD_DIR)/test_track_fx
|
||||
test: test-track-fx
|
||||
-include $(BUILD_DIR)/tests/unit/test_track_fx.d
|
||||
|
|
|
|||
|
|
@ -1895,12 +1895,12 @@ Implemented: sequencer Create menu retains empty/audio/MIDI track creation, WAV
|
|||
Add a bounded C99 per-track insert model with validation, clone/history/duplicate support and backward-compatible project persistence. Loading metadata must not load plugin binaries.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [ ] Implement the described workflow with validated errors and safe ownership.
|
||||
- [ ] Pass relevant core and/or GUI tests.
|
||||
- [ ] Document behavior and limitations; commit this phase separately.
|
||||
- [x] Implement the described workflow with validated errors and safe ownership.
|
||||
- [x] Pass relevant core and/or GUI tests.
|
||||
- [x] Document behavior and limitations; commit this phase separately.
|
||||
|
||||
### Implementation status
|
||||
Planned.
|
||||
Implemented: eight validated metadata-only insert slots per non-bus track, including enabled/wet/output values, format 17 persistence and reads of formats 1–16 with empty chains. Clone/history use copied state; duplicate/move/remove retain track association. Full warning-enabled core suite and dedicated metadata, persistence and invalid-value tests passed. See TRACK_INSERTS.md. Playback integration follows in phase 82.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
16
docs/TRACK_INSERTS.md
Normal file
16
docs/TRACK_INSERTS.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Track inserts
|
||||
|
||||
The sequencer's **Create** dropdown groups track creation, WAV import and
|
||||
instrument loading. **View → Track inspector** opens the optional inspector;
|
||||
it is hidden when the application starts to leave more room for editing.
|
||||
|
||||
Project format 17 stores up to eight effect slots per track, their order,
|
||||
bypass, wet amount and chain output gain. Formats 1–16 read with empty chains.
|
||||
Metadata load, clone and history never open executable plugin modules. Track
|
||||
move, duplicate and removal retain the association with stable track IDs.
|
||||
Nonempty bus chains are rejected. The model accepts mono effect specifications
|
||||
for LADSPA, VST2, VST3, CLAP and internal effects; this alone does not establish
|
||||
that a third-party binary is compatible with the host.
|
||||
|
||||
The chain API is declared in `xtc_plugin.h`. Setter errors preserve the old
|
||||
chain; plugin identity and layout are checked when preparing playback.
|
||||
|
|
@ -31,6 +31,14 @@ typedef struct {
|
|||
unsigned int tempo, point_count; /* milli-BPM, 20000..300000 */
|
||||
xtc_plugin_point points[XTC_PLUGIN_POINTS];
|
||||
} xtc_plugin_spec;
|
||||
#define XTC_TRACK_INSERTS 8U
|
||||
typedef struct { xtc_plugin_spec plugin; unsigned int enabled; float wet; } xtc_track_insert;
|
||||
typedef struct { size_t count; float output_gain; xtc_track_insert slots[XTC_TRACK_INSERTS]; } xtc_track_chain;
|
||||
/* Metadata only; control thread. Empty chain has unity output. No module is
|
||||
* opened here. Nonempty chains are mono effects on non-bus tracks. */
|
||||
xtc_result xtc_track_chain_validate(const xtc_track_chain *chain);
|
||||
xtc_result xtc_project_get_chain(const xtc_project *project,uint64_t track,xtc_track_chain *out);
|
||||
xtc_result xtc_project_set_chain(xtc_project *project,uint64_t track,const xtc_track_chain *chain);
|
||||
/* Copied discovery metadata, never descriptor/module pointers. Modern adapters
|
||||
* currently expose the name only (zero controls). LADSPA provides
|
||||
* no standardized units; unit is empty. Flags describe input control hints. */
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define XTC_PROJECT_FORMAT_VERSION 16U
|
||||
#define XTC_PROJECT_FORMAT_VERSION 17U
|
||||
#define XTC_PROJECT_MAX_TRACKS 64U
|
||||
#define XTC_PROJECT_MAX_CLIPS 1024U
|
||||
#define XTC_PROJECT_NAME_BYTES 64U
|
||||
|
|
|
|||
|
|
@ -502,6 +502,40 @@ static xtc_result read_project(reader *input, xtc_project *project, const char *
|
|||
if(!decode_name(input->tokens[2],project->markers[i].name))return XTC_ERR_FORMAT;
|
||||
}
|
||||
}
|
||||
for(i=0;i<project->track_count;++i)project->chains[i].output_gain=1;
|
||||
if(format_version>=17U) {
|
||||
READ("CHAINS",2U);NUM(1U,value);if(value!=project->track_count)return XTC_ERR_FORMAT;
|
||||
for(i=0;i<project->track_count;++i) {
|
||||
xtc_track_chain *c=project->chains+i;size_t k;
|
||||
READ("CHAIN",4U);NUM(1U,value);if(value!=project->tracks[i].id)return XTC_ERR_FORMAT;
|
||||
NUM(2U,value);if(value>XTC_TRACK_INSERTS)return XTC_ERR_FORMAT;c->count=(size_t)value;
|
||||
if(!decode_float(input->tokens[3],&c->output_gain))return XTC_ERR_FORMAT;
|
||||
for(j=0;j<c->count;++j) {
|
||||
xtc_track_insert *slot=c->slots+j;xtc_plugin_spec *p=&slot->plugin;
|
||||
READ("INSERT",9U);NUM(1U,value);if(value>1)return XTC_ERR_FORMAT;slot->enabled=(unsigned int)value;
|
||||
if(!decode_float(input->tokens[2],&slot->wet))return XTC_ERR_FORMAT;
|
||||
NUM(3U,value);if(value>XTC_PLUGIN_BUILTIN)return XTC_ERR_FORMAT;p->format=(xtc_plugin_format)value;
|
||||
NUM(4U,value);if(value>=1024)return XTC_ERR_FORMAT;p->index=(unsigned int)value;
|
||||
NUM(5U,value);if(value>UINT32_MAX)return XTC_ERR_FORMAT;p->unique_id=(unsigned int)value;
|
||||
NUM(6U,value);if(value!=1)return XTC_ERR_FORMAT;p->inputs=1;
|
||||
NUM(7U,value);if(value<20000 || value>300000)return XTC_ERR_FORMAT;p->tempo=(unsigned int)value;
|
||||
if(!decode_name(input->tokens[8],p->label))return XTC_ERR_FORMAT;
|
||||
READ("MODULE",2U);if(!decode_bytes(input->tokens[1],p->path,sizeof(p->path)))return XTC_ERR_FORMAT;
|
||||
READ("IDENTITY",2U);if(strcmp(input->tokens[1],"-") && !decode_bytes(input->tokens[1],p->identifier,sizeof(p->identifier)))return XTC_ERR_FORMAT;
|
||||
READ("CONTROLS",2U);NUM(1U,value);if(value>XTC_PLUGIN_CONTROLS)return XTC_ERR_FORMAT;p->control_count=(unsigned int)value;
|
||||
for(k=0;k<p->control_count;++k) {
|
||||
READ("CONTROL",3U);NUM(1U,value);if(value>=64)return XTC_ERR_FORMAT;p->ports[k]=(unsigned int)value;
|
||||
if(!decode_float(input->tokens[2],p->values+k))return XTC_ERR_FORMAT;
|
||||
}
|
||||
READ("PARAMETERS",2U);NUM(1U,value);if(value>XTC_PLUGIN_POINTS)return XTC_ERR_FORMAT;p->point_count=(unsigned int)value;
|
||||
for(k=0;k<p->point_count;++k) {
|
||||
xtc_plugin_point *a=p->points+k;READ("PARAM",5U);NUM(1U,a->tick);NUM(2U,value);if(value>=64)return XTC_ERR_FORMAT;a->port=(unsigned int)value;
|
||||
NUM(3U,value);if(value>1)return XTC_ERR_FORMAT;a->step=(unsigned int)value;
|
||||
if(!decode_float(input->tokens[4],&a->value))return XTC_ERR_FORMAT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
READ("END", 1U);
|
||||
if (fgetc(input->file) != EOF)
|
||||
return XTC_ERR_FORMAT;
|
||||
|
|
@ -681,6 +715,22 @@ static xtc_result write_project(FILE *file, const xtc_project *project, const ch
|
|||
}
|
||||
if(fprintf(file,"MARKERS %zu\n",project->marker_count)<0)return XTC_ERR_IO;
|
||||
for(i=0;i<project->marker_count;++i)if(fprintf(file,"MARKER %" PRIu64 " ",project->markers[i].tick)<0 || !write_name(file,project->markers[i].name))return XTC_ERR_IO;
|
||||
if(fprintf(file,"CHAINS %zu\n",project->track_count)<0)return XTC_ERR_IO;
|
||||
for(i=0;i<project->track_count;++i) {
|
||||
const xtc_track_chain *c=project->chains+i;size_t j,k;uint32_t bits;
|
||||
memcpy(&bits,&c->output_gain,4);
|
||||
if(fprintf(file,"CHAIN %" PRIu64 " %zu %08" PRIx32 "\n",project->tracks[i].id,c->count,bits)<0)return XTC_ERR_IO;
|
||||
for(j=0;j<c->count;++j) {
|
||||
const xtc_track_insert *slot=c->slots+j;const xtc_plugin_spec *p=&slot->plugin;memcpy(&bits,&slot->wet,4);
|
||||
if(fprintf(file,"INSERT %u %08" PRIx32 " %u %u %u %u %u ",slot->enabled,bits,(unsigned int)p->format,p->index,p->unique_id,p->inputs,p->tempo)<0 || !write_name(file,p->label))return XTC_ERR_IO;
|
||||
if(fputs("MODULE ",file)==EOF || !write_name(file,p->path) || fputs("IDENTITY ",file)==EOF)return XTC_ERR_IO;
|
||||
if(p->identifier[0]) {if(!write_name(file,p->identifier))return XTC_ERR_IO;}else if(fputs("-\n",file)==EOF)return XTC_ERR_IO;
|
||||
if(fprintf(file,"CONTROLS %u\n",p->control_count)<0)return XTC_ERR_IO;
|
||||
for(k=0;k<p->control_count;++k) {memcpy(&bits,p->values+k,4);if(fprintf(file,"CONTROL %u %08" PRIx32 "\n",p->ports[k],bits)<0)return XTC_ERR_IO;}
|
||||
if(fprintf(file,"PARAMETERS %u\n",p->point_count)<0)return XTC_ERR_IO;
|
||||
for(k=0;k<p->point_count;++k) {const xtc_plugin_point *a=p->points+k;memcpy(&bits,&a->value,4);if(fprintf(file,"PARAM %" PRIu64 " %u %u %08" PRIx32 "\n",a->tick,a->port,a->step,bits)<0)return XTC_ERR_IO;}
|
||||
}
|
||||
}
|
||||
return fputs("END\n", file) == EOF ? XTC_ERR_IO : XTC_OK;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ xtc_result xtc_project_duplicate_track(xtc_project *p,uint64_t track,const char
|
|||
if(p->tracks[index].is_bus)return XTC_ERR_INVALID_STATE;
|
||||
r=xtc_project_clone(p,©);if(r!=XTC_OK)return r;
|
||||
r=xtc_project_add_track(copy,name,&id);
|
||||
if(r==XTC_OK){xtc_track_info *target=copy->tracks+copy->track_count-1;*target=p->tracks[index];target->id=id;memcpy(target->name,name,strlen(name)+1);copy->instruments[copy->track_count-1]=p->instruments[index];}
|
||||
if(r==XTC_OK){xtc_track_info *target=copy->tracks+copy->track_count-1;*target=p->tracks[index];target->id=id;memcpy(target->name,name,strlen(name)+1);copy->instruments[copy->track_count-1]=p->instruments[index];copy->chains[copy->track_count-1]=p->chains[index];}
|
||||
for(i=0;r==XTC_OK && i<p->clip_count;++i)if(p->clips[i].track_id==track){uint64_t clip;r=xtc_project_duplicate_clip(copy,p->clips[i].id,id,p->clips[i].start_tick,&clip);}
|
||||
for(i=0;r==XTC_OK && i<p->automation_count;++i)if(p->automation[i].track_id==track){xtc_automation_point point=p->automation[i];point.track_id=id;r=xtc_project_set_automation(copy,&point);}
|
||||
if(r==XTC_OK)r=xtc_project_move_track(copy,id,index+1);
|
||||
|
|
|
|||
|
|
@ -306,6 +306,8 @@ xtc_result xtc_project_add_track(xtc_project *project, const char *name, uint64_
|
|||
track.gain = 1.0f;
|
||||
track.bus_id = UINT64_MAX;
|
||||
memcpy(track.name, name, strlen(name) + 1U);
|
||||
memset(project->chains+project->track_count,0,sizeof(*project->chains));
|
||||
project->chains[project->track_count].output_gain=1;
|
||||
project->tracks[project->track_count++] = track;
|
||||
*id = track.id;
|
||||
return XTC_OK;
|
||||
|
|
@ -333,6 +335,9 @@ xtc_result xtc_project_remove_track(xtc_project *project, uint64_t id)
|
|||
for (i = 0; i < project->track_count; ++i)
|
||||
if (project->tracks[i].bus_id == id) project->tracks[i].bus_id = UINT64_MAX;
|
||||
if (project->live_instrument == id) project->live_instrument = UINT64_MAX;
|
||||
memmove(project->chains+index,project->chains+index+1,
|
||||
(project->track_count-index-1)*sizeof(*project->chains));
|
||||
memset(project->chains+project->track_count-1,0,sizeof(*project->chains));
|
||||
memmove(project->instruments+index,project->instruments+index+1,
|
||||
(project->track_count-index-1)*sizeof(*project->instruments));
|
||||
memset(project->instruments+project->track_count-1,0,sizeof(*project->instruments));
|
||||
|
|
@ -544,6 +549,8 @@ xtc_result xtc_project_validate(const xtc_project *project)
|
|||
if (target == SIZE_MAX || !project->instruments[target].instrument) return XTC_ERR_FORMAT;
|
||||
}
|
||||
for (i = 0U; i < project->track_count; ++i) {
|
||||
if(xtc_track_chain_validate(project->chains+i)!=XTC_OK ||
|
||||
(project->tracks[i].is_bus && project->chains[i].count))return XTC_ERR_FORMAT;
|
||||
if (project->instruments[i].instrument && (project->tracks[i].is_bus ||
|
||||
xtc_plugin_spec_validate(project->instruments+i) != XTC_OK)) return XTC_ERR_FORMAT;
|
||||
if (project->tracks[i].id >= project->next_track_id || !xtc_project_valid_name(project->tracks[i].name))
|
||||
|
|
@ -689,6 +696,10 @@ xtc_result xtc_project_move_track(xtc_project *p, uint64_t id, size_t destinatio
|
|||
size_t i; xtc_track_info track;
|
||||
if (!p || destination >= p->track_count) return XTC_ERR_INVALID_ARGUMENT;
|
||||
i = track_index(p,id); if (i == SIZE_MAX) return XTC_ERR_NOT_FOUND;
|
||||
{ xtc_track_chain chain=p->chains[i];
|
||||
if(i<destination)memmove(p->chains+i,p->chains+i+1,(destination-i)*sizeof(chain));
|
||||
else if(i>destination)memmove(p->chains+destination+1,p->chains+destination,(i-destination)*sizeof(chain));
|
||||
p->chains[destination]=chain; }
|
||||
{ xtc_plugin_spec instrument = p->instruments[i];
|
||||
if (i < destination) memmove(p->instruments+i,p->instruments+i+1,(destination-i)*sizeof(instrument));
|
||||
else if (i > destination) memmove(p->instruments+destination+1,p->instruments+destination,(i-destination)*sizeof(instrument));
|
||||
|
|
@ -772,3 +783,28 @@ xtc_result xtc_project_set_live_instrument(xtc_project *p, uint64_t track)
|
|||
}
|
||||
xtc_result xtc_project_get_live_instrument(const xtc_project *p, uint64_t *track)
|
||||
{ if (!p || !track) return XTC_ERR_INVALID_ARGUMENT; *track = p->live_instrument; return XTC_OK; }
|
||||
|
||||
xtc_result xtc_track_chain_validate(const xtc_track_chain *c)
|
||||
{
|
||||
size_t i;
|
||||
if(!c || c->count>XTC_TRACK_INSERTS || !isfinite(c->output_gain) || c->output_gain<0 || c->output_gain>4)return XTC_ERR_INVALID_ARGUMENT;
|
||||
for(i=0;i<c->count;++i) {
|
||||
const xtc_track_insert *s=c->slots+i;
|
||||
if(s->enabled>1 || !isfinite(s->wet) || s->wet<0 || s->wet>1 || s->plugin.instrument ||
|
||||
s->plugin.inputs!=1 || s->plugin.sample_path[0] || xtc_plugin_spec_validate(&s->plugin)!=XTC_OK)return XTC_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
return XTC_OK;
|
||||
}
|
||||
xtc_result xtc_project_get_chain(const xtc_project *p,uint64_t track,xtc_track_chain *out)
|
||||
{
|
||||
size_t i;if(!p || !out)return XTC_ERR_INVALID_ARGUMENT;
|
||||
i=track_index(p,track);if(i==SIZE_MAX)return XTC_ERR_NOT_FOUND;
|
||||
*out=p->chains[i];return XTC_OK;
|
||||
}
|
||||
xtc_result xtc_project_set_chain(xtc_project *p,uint64_t track,const xtc_track_chain *c)
|
||||
{
|
||||
size_t i;if(!p || xtc_track_chain_validate(c)!=XTC_OK)return XTC_ERR_INVALID_ARGUMENT;
|
||||
i=track_index(p,track);if(i==SIZE_MAX)return XTC_ERR_NOT_FOUND;
|
||||
if(p->tracks[i].is_bus && c->count)return XTC_ERR_UNSUPPORTED;
|
||||
p->chains[i]=*c;return XTC_OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ struct xtc_project {
|
|||
xtc_marker markers[XTC_PROJECT_MAX_MARKERS];
|
||||
uint64_t live_instrument;
|
||||
xtc_plugin_spec instruments[XTC_PROJECT_MAX_TRACKS];
|
||||
xtc_track_chain chains[XTC_PROJECT_MAX_TRACKS];
|
||||
uint64_t next_track_id, next_clip_id;
|
||||
unsigned int tempo;
|
||||
unsigned char control_map[16][3];
|
||||
|
|
|
|||
|
|
@ -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..16' in diagnose and 'JACK compiled:' in diagnose
|
||||
assert 'Project formats: 1..17' 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..16")),
|
||||
(base.replace("XTC_PROJECT 10", "XTC_PROJECT 999"), ("line 1", "999", "1..17")),
|
||||
("XTC_PROJECT 10\n", ("line 2", "APP")),
|
||||
(base.replace("TEMPO 120000", "TEMPO 0"), ("line 3", "TEMPO")),
|
||||
(base + "OPTIONAL_FUTURE 1\n", ("Unknown fields",)),
|
||||
|
|
|
|||
|
|
@ -167,8 +167,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,"MARKERS ",8) || !strncmp(line,"SAMPLE ",7) || !strncmp(line,"INSTRUMENT_CONTROLS",19) || !strncmp(line,"INSTRUMENT_AUTOMATION",21))continue;
|
||||
if(!strncmp(line,"XTC_PROJECT 16",14))strcpy(line,"XTC_PROJECT 13\n");
|
||||
if(!strncmp(line,"CHAINS ",7) || !strncmp(line,"CHAIN ",6) || !strncmp(line,"MARKERS ",8) || !strncmp(line,"SAMPLE ",7) || !strncmp(line,"INSTRUMENT_CONTROLS",19) || !strncmp(line,"INSTRUMENT_AUTOMATION",21))continue;
|
||||
if(!strncmp(line,"XTC_PROJECT 17",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);
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ int main(void)
|
|||
fputc('\n',legacy); continue;
|
||||
}
|
||||
if (strncmp(line, "MIX ", 4) && strncmp(line, "AUTOMATION ", 11) &&
|
||||
strncmp(line, "POINT ", 6) && strncmp(line, "AUDIO ", 6) && strncmp(line, "ROUTE ", 6) && strncmp(line,"CONTROL_MAPS ",13) && strncmp(line,"MAP ",4) && strncmp(line,"TRACK_INSTRUMENTS ",18) && strncmp(line,"LIVE_INSTRUMENT ",16) && strncmp(line,"MARKERS ",8) && strncmp(line,"MARKER ",7))
|
||||
strncmp(line, "POINT ", 6) && strncmp(line, "AUDIO ", 6) && strncmp(line, "ROUTE ", 6) && strncmp(line,"CONTROL_MAPS ",13) && strncmp(line,"MAP ",4) && strncmp(line,"TRACK_INSTRUMENTS ",18) && strncmp(line,"LIVE_INSTRUMENT ",16) && strncmp(line,"CHAINS ",7) && strncmp(line,"CHAIN ",6) && strncmp(line,"MARKERS ",8) && strncmp(line,"MARKER ",7))
|
||||
assert(fputs(line, legacy) >= 0);
|
||||
}
|
||||
assert(fclose(f) == 0 && fclose(legacy) == 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..16"));
|
||||
diagnostic.format_version == 999 && !strcmp(diagnostic.record,"XTC_PROJECT") && strstr(diagnostic.message,"1..17"));
|
||||
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"));
|
||||
|
|
|
|||
28
tests/unit/test_track_fx.c
Normal file
28
tests/unit/test_track_fx.c
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "xtc/xtc_plugin.h"
|
||||
#include "xtc/xtc_editing.h"
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
int main(void)
|
||||
{
|
||||
xtc_project *p=NULL,*q=NULL;uint64_t a,b,duplicate;xtc_track_chain c={0},got={0};
|
||||
char file[]="/tmp/xtc-chain-XXXXXX";int fd=mkstemp(file);assert(fd>=0);close(fd);
|
||||
assert(xtc_project_create(&p)==XTC_OK && xtc_project_add_track(p,"Audio",&a)==XTC_OK && xtc_project_add_track(p,"Other",&b)==XTC_OK);
|
||||
assert(xtc_project_get_chain(p,a,&got)==XTC_OK && got.count==0 && got.output_gain==1);
|
||||
assert(xtc_plugin_probe_format(XTC_PLUGIN_BUILTIN,XTC_BUILTIN_PATH,2,48000,&c.slots[0].plugin)==XTC_OK);
|
||||
c.count=1;c.output_gain=.75f;c.slots[0].enabled=1;c.slots[0].wet=.5f;
|
||||
assert(xtc_project_set_chain(p,a,&c)==XTC_OK);
|
||||
assert(xtc_project_save(p,file)==XTC_OK && xtc_project_create(&q)==XTC_OK && xtc_project_load(q,file)==XTC_OK);
|
||||
assert(xtc_project_get_chain(q,a,&got)==XTC_OK && got.count==1 && got.output_gain==.75f && got.slots[0].wet==.5f && !strcmp(got.slots[0].plugin.label,c.slots[0].plugin.label));
|
||||
assert(xtc_project_duplicate_track(q,a,"Copy",&duplicate)==XTC_OK && xtc_project_get_chain(q,duplicate,&got)==XTC_OK && got.count==1);
|
||||
assert(xtc_project_move_track(q,a,2)==XTC_OK && xtc_project_remove_track(q,b)==XTC_OK && xtc_project_get_chain(q,a,&got)==XTC_OK && got.count==1);
|
||||
c.output_gain=NAN;assert(xtc_project_set_chain(q,a,&c)==XTC_ERR_INVALID_ARGUMENT);
|
||||
assert(xtc_project_get_chain(q,a,&got)==XTC_OK && got.output_gain==.75f);
|
||||
c.output_gain=1;c.slots[0].wet=2;assert(xtc_project_set_chain(q,a,&c)==XTC_ERR_INVALID_ARGUMENT);
|
||||
c.slots[0].wet=1;c.count=XTC_TRACK_INSERTS+1;assert(xtc_track_chain_validate(&c)==XTC_ERR_INVALID_ARGUMENT);
|
||||
xtc_project_destroy(p);xtc_project_destroy(q);unlink(file);puts("Track insert metadata/persistence passed.");return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue