Phase 18: preserve portable project media paths and verify format migrations
This commit is contained in:
parent
440e98f2b5
commit
1369e4d463
15 changed files with 329 additions and 13 deletions
8
Makefile
8
Makefile
|
|
@ -336,3 +336,11 @@ test-editing: $(BUILD_DIR)/test_editing
|
|||
./$(BUILD_DIR)/test_editing
|
||||
test: test-editing
|
||||
-include $(BUILD_DIR)/tests/unit/test_editing.d
|
||||
|
||||
$(BUILD_DIR)/test_portability: $(BUILD_DIR)/tests/integration/test_portability.o $(BUILD_DIR)/libxtc.a
|
||||
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
|
||||
.PHONY: test-portability
|
||||
test-portability: $(BUILD_DIR)/test_portability
|
||||
./$(BUILD_DIR)/test_portability
|
||||
test: test-portability
|
||||
-include $(BUILD_DIR)/tests/integration/test_portability.d
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Project format v5 (reads v1/v2/v3/v4)
|
||||
# Project format v7 (reads v1–v6)
|
||||
|
||||
Phases 5–15 use a bounded text format implemented in C99 without a parser library.
|
||||
The extension is `.xtc`. File operations run exclusively on the control thread.
|
||||
|
|
@ -12,7 +12,7 @@ Fields use ASCII spaces. Integers are unsigned decimal, without signs.
|
|||
Counts determine the number of subsequent records:
|
||||
|
||||
```text
|
||||
XTC_PROJECT 5
|
||||
XTC_PROJECT 7
|
||||
APP 0 1 0
|
||||
TEMPO 120000
|
||||
IDS 1 1
|
||||
|
|
@ -82,8 +82,8 @@ 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 v5. Unknown versions are rejected without replacing the destination; older
|
||||
applications cannot read v5. Migration and maximum-path roundtrips are tested.
|
||||
Saving always writes v7. Unknown versions are rejected without replacing the destination; older
|
||||
applications cannot read v7. Migration and maximum-path roundtrips are tested.
|
||||
|
||||
## Ownership and failures
|
||||
|
||||
|
|
@ -148,3 +148,43 @@ port/tick pairs, nonfinite values, unknown control ports and ticks beyond the
|
|||
project limit are rejected. Loaded plugin tempo comes from TEMPO. Range and
|
||||
discrete-mode compatibility are checked on control-thread compilation. Versions
|
||||
1–5 migrate with empty plugin lanes. See CONTROL.md for timing/ordering.
|
||||
|
||||
## Phase 18: version 7 and project folders
|
||||
|
||||
Current writer emits v7; readers migrate v1–v6. Record ordering and field bounds
|
||||
are otherwise unchanged from v6. v7 ASSET may contain a relative path, interpreted
|
||||
against the actual containing directory of the named project file (resolved on
|
||||
the control thread), never the launch working directory. After loading, all core
|
||||
asset references are absolute, including missing media. Media inside the save
|
||||
folder are written relative; references outside remain absolute. Save As resolves
|
||||
against its new folder and preserves media identity rather than rebasing it by
|
||||
accident. Copy/move a project folder together with its referenced media to retain
|
||||
portability. Saving does not copy assets: import/record into that folder first.
|
||||
Plugin binaries remain trusted absolute module references, not bundled resources.
|
||||
|
||||
Empty assets still use `-`. Paths exclude embedded NUL/control bytes and must fit
|
||||
511 bytes after resolution; overly long resolved references fail transactionally.
|
||||
Relative references can name outside the folder using ordinary POSIX semantics;
|
||||
this is not a sandbox. Symlinks follow filesystem behavior, so portability requires
|
||||
moving their targets too. Old formats continue to require absolute media paths.
|
||||
No unknown version/node/record is silently discarded; unsupported versions/types
|
||||
or malformed/trailing data reject the candidate and retain the current project.
|
||||
Parsing never opens media/plugins or constructs an audio engine.
|
||||
|
||||
Save validates the complete model, writes/fsyncs a same-folder temporary, closes
|
||||
it, atomically renames it, then fsyncs the containing directory. Failures before
|
||||
rename retain the old destination. A directory sync failure *after* rename reports
|
||||
I/O error but the new complete file may already be present: inspect/reopen it;
|
||||
there is no rollback across that durability boundary. Filesystem/hardware power
|
||||
loss guarantees still depend on the underlying storage. Recording destinations
|
||||
have their separate no-clobber policy; ordinary project Save intentionally replaces
|
||||
the selected document. In-memory models and the existing destination are preserved
|
||||
on load failure. No automatic unknown-field stripping or media deletion occurs.
|
||||
|
||||
`make test-portability test test-plugins test-recovery test-recording` covers checked-in
|
||||
v1–v6 fixtures (including missing v5/v6 plugins), v7 exact roundtrips, moved-folder
|
||||
playback from a different cwd, missing media retention, unknown records, every
|
||||
truncation and deterministic byte mutations, malformed plugin metadata, interrupted
|
||||
writers and write-limit failures. ASan/UBSan/static analysis additionally audit the
|
||||
bounded parser. This is regression/fault-injection coverage, not exhaustive fuzzing
|
||||
or a claim of power-cut qualification.
|
||||
|
|
|
|||
|
|
@ -385,6 +385,14 @@ screen-reader/accessibility qualification remain explicit limits.
|
|||
- Missing media and plugins are reported clearly.
|
||||
- Save/load round trips and format migrations have automated tests.
|
||||
|
||||
Implemented: v7 folder-relative media persistence with absolute runtime references,
|
||||
transactional resolution independent of launch cwd, Save As identity preservation,
|
||||
and parent-directory fsync after atomic publication. Checked-in v1–v6 migrations
|
||||
(including missing plugins), moved-folder exact playback, missing-media retention,
|
||||
truncation/mutation and unknown-record/error tests pass. See PROJECT_FORMAT.md.
|
||||
No media is copied/deleted automatically; absolute external/plugin dependencies,
|
||||
path bounds, post-rename sync errors and physical power-loss limits are explicit.
|
||||
|
||||
## Phase 19 — Performance, Scalability, and Stress Testing
|
||||
|
||||
**Goal:** Measure performance and establish predictable behavior as projects grow.
|
||||
|
|
|
|||
|
|
@ -208,7 +208,9 @@ xtc_result xtc_project_sample_to_tick(const xtc_project *project, uint64_t sampl
|
|||
uint64_t *out_tick);
|
||||
/* Control-thread I/O. Versioned, bounded format; see docs/PROJECT_FORMAT.md.
|
||||
* Load is transactional. Save fsyncs a same-directory temporary and renames it;
|
||||
* failures before rename preserve the previous destination. No asset I/O. */
|
||||
* failures before rename preserve the previous destination. No asset I/O. v7 saves local media paths relative to the project folder; load
|
||||
* resolves them to absolute references. Post-rename directory-sync failure can
|
||||
* report IO with the complete new document already published. */
|
||||
/**
|
||||
* @brief Saves a versioned project through a synced temporary file and atomic rename.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#define _XOPEN_SOURCE 700
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "../project/project_internal.h"
|
||||
#include "../graph/graph_internal.h"
|
||||
|
|
@ -10,6 +11,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
typedef struct {
|
||||
FILE *file;
|
||||
|
|
@ -184,14 +186,14 @@ static int decode_float(const char *text, float *value)
|
|||
* @param project Project model; borrowed for this call.
|
||||
* @return XTC_OK on success, or an error code describing the failure.
|
||||
*/
|
||||
static xtc_result read_project(reader *input, xtc_project *project)
|
||||
static xtc_result read_project(reader *input, xtc_project *project, const char *directory)
|
||||
{
|
||||
uint64_t version, value, next_id, node_count, edge_count, format_version;
|
||||
size_t i, j;
|
||||
xtc_graph *graph = project->graph;
|
||||
READ("XTC_PROJECT", 2U);
|
||||
NUM(1U, version);
|
||||
if (version < 1U || version > 6U)
|
||||
if (version < 1U || version > 7U)
|
||||
return XTC_ERR_UNSUPPORTED;
|
||||
format_version = version;
|
||||
READ("APP", 4U);
|
||||
|
|
@ -402,6 +404,15 @@ static xtc_result read_project(reader *input, xtc_project *project)
|
|||
return XTC_ERR_FORMAT;
|
||||
if (ferror(input->file))
|
||||
return XTC_ERR_IO;
|
||||
if (format_version >= 7) for (i = 0; i < project->clip_count; ++i) {
|
||||
char *asset = project->clips[i].asset_path;
|
||||
if (asset[0] && asset[0] != '/') {
|
||||
size_t root = strlen(directory), length = strlen(asset);
|
||||
if (root+length+2 > XTC_ASSET_PATH_BYTES) return XTC_ERR_CAPACITY;
|
||||
memmove(asset+root+1,asset,length+1);
|
||||
memcpy(asset,directory,root); asset[root] = '/';
|
||||
}
|
||||
}
|
||||
return xtc_project_validate(project);
|
||||
}
|
||||
|
||||
|
|
@ -428,7 +439,7 @@ static int write_name(FILE *file, const char *name)
|
|||
* @param project Project model; borrowed for this call.
|
||||
* @return XTC_OK on success, or an error code describing the failure.
|
||||
*/
|
||||
static xtc_result write_project(FILE *file, const xtc_project *project)
|
||||
static xtc_result write_project(FILE *file, const xtc_project *project, const char *directory)
|
||||
{
|
||||
const xtc_graph *graph = project->graph;
|
||||
size_t i;
|
||||
|
|
@ -437,7 +448,7 @@ static xtc_result write_project(FILE *file, const xtc_project *project)
|
|||
return XTC_ERR_UNSUPPORTED;
|
||||
if (sizeof(float) != sizeof(uint32_t) || FLT_RADIX != 2 || FLT_MANT_DIG != 24 || FLT_MAX_EXP != 128)
|
||||
return XTC_ERR_UNSUPPORTED;
|
||||
if (fprintf(file, "XTC_PROJECT 6\nAPP %d %d %d\nTEMPO %u\nIDS %" PRIu64 " %" PRIu64 "\nGRAPH %d %zu %zu %zu\n",
|
||||
if (fprintf(file, "XTC_PROJECT 7\nAPP %d %d %d\nTEMPO %u\nIDS %" PRIu64 " %" PRIu64 "\nGRAPH %d %zu %zu %zu\n",
|
||||
XTC_VERSION_MAJOR, XTC_VERSION_MINOR, XTC_VERSION_PATCH, project->tempo, project->next_track_id,
|
||||
project->next_clip_id, graph->require_output_nodes, graph->next_id, graph->node_count,
|
||||
graph->edge_count) < 0)
|
||||
|
|
@ -493,7 +504,10 @@ static xtc_result write_project(FILE *file, const xtc_project *project)
|
|||
if (fputs("ASSET ", file) == EOF)
|
||||
return XTC_ERR_IO;
|
||||
if (clip->asset_path[0]) {
|
||||
if (!write_name(file, clip->asset_path))
|
||||
const char *asset = clip->asset_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;
|
||||
|
|
@ -537,6 +551,19 @@ static size_t path_length(const char *path)
|
|||
return size == 4096U ? 0U : size;
|
||||
}
|
||||
|
||||
/* Resolve only the containing folder: media need not exist. Save replaces the
|
||||
* named entry (including a symlink), so do not resolve the final component. */
|
||||
static char *project_folder(const char *path)
|
||||
{
|
||||
char *copy = malloc(strlen(path)+2), *slash, *folder;
|
||||
if (!copy) return NULL;
|
||||
strcpy(copy,path); slash = strrchr(copy,'/');
|
||||
if (!slash) strcpy(copy,".");
|
||||
else if (slash == copy) slash[1] = 0;
|
||||
else *slash = 0;
|
||||
folder = realpath(copy,NULL); free(copy); return folder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loads and validates a project before transactionally replacing the current state.
|
||||
*
|
||||
|
|
@ -548,15 +575,19 @@ xtc_result xtc_project_load(xtc_project *project, const char *path)
|
|||
{
|
||||
xtc_project *candidate = NULL;
|
||||
reader input = {0};
|
||||
char *directory;
|
||||
xtc_result result;
|
||||
if (project == NULL || path_length(path) == 0U)
|
||||
return XTC_ERR_INVALID_ARGUMENT;
|
||||
input.file = fopen(path, "rb");
|
||||
if (input.file == NULL)
|
||||
return XTC_ERR_IO;
|
||||
directory = project_folder(path);
|
||||
if (!directory) { fclose(input.file); return XTC_ERR_IO; }
|
||||
result = xtc_project_create(&candidate);
|
||||
if (result == XTC_OK)
|
||||
result = read_project(&input, candidate);
|
||||
result = read_project(&input, candidate, directory);
|
||||
free(directory);
|
||||
if (fclose(input.file) != 0 && result == XTC_OK)
|
||||
result = XTC_ERR_IO;
|
||||
if (result == XTC_OK) {
|
||||
|
|
@ -578,7 +609,7 @@ xtc_result xtc_project_load(xtc_project *project, const char *path)
|
|||
xtc_result xtc_project_save(const xtc_project *project, const char *path)
|
||||
{
|
||||
size_t length = path_length(path);
|
||||
char *temporary;
|
||||
char *temporary, *directory;
|
||||
FILE *file;
|
||||
int descriptor;
|
||||
xtc_result result;
|
||||
|
|
@ -604,7 +635,8 @@ xtc_result xtc_project_save(const xtc_project *project, const char *path)
|
|||
free(temporary);
|
||||
return XTC_ERR_IO;
|
||||
}
|
||||
result = write_project(file, project);
|
||||
directory = project_folder(path);
|
||||
result = directory ? write_project(file, project, directory) : XTC_ERR_IO;
|
||||
if (result == XTC_OK && fflush(file) != 0)
|
||||
result = XTC_ERR_IO;
|
||||
if (result == XTC_OK && fsync(descriptor) != 0)
|
||||
|
|
@ -615,6 +647,12 @@ xtc_result xtc_project_save(const xtc_project *project, const char *path)
|
|||
result = XTC_ERR_IO;
|
||||
if (result != XTC_OK)
|
||||
(void)unlink(temporary);
|
||||
if (result == XTC_OK) {
|
||||
int folder = open(directory,O_RDONLY);
|
||||
if (folder < 0) result = XTC_ERR_IO;
|
||||
else { if (fsync(folder)) result = XTC_ERR_IO; if (close(folder)) result = XTC_ERR_IO; }
|
||||
}
|
||||
free(directory);
|
||||
free(temporary);
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
14
tests/fixtures/project-plugin-v5.xtc
vendored
Normal file
14
tests/fixtures/project-plugin-v5.xtc
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
XTC_PROJECT 5
|
||||
APP 0 1 0
|
||||
TEMPO 120000
|
||||
IDS 0 0
|
||||
GRAPH 1 1 1 0
|
||||
NODE 0 8 00000000
|
||||
PLUGIN 900000 0 1 1 7874635f746573745f6761696e
|
||||
MODULE 2f6d697373696e672f746573742e736f
|
||||
CONTROL 2 3f800000
|
||||
TRACKS 0
|
||||
CLIPS 0
|
||||
NOTES 0 0
|
||||
AUTOMATION 0
|
||||
END
|
||||
16
tests/fixtures/project-plugin-v6.xtc
vendored
Normal file
16
tests/fixtures/project-plugin-v6.xtc
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
XTC_PROJECT 6
|
||||
APP 0 1 0
|
||||
TEMPO 120000
|
||||
IDS 0 0
|
||||
GRAPH 1 1 1 0
|
||||
NODE 0 8 00000000
|
||||
PLUGIN 900000 0 1 1 7874635f746573745f6761696e
|
||||
MODULE 2f6d697373696e672f746573742e736f
|
||||
CONTROL 2 3f800000
|
||||
PLUGIN_AUTOMATION 1
|
||||
PARAM 960 2 0 3f000000
|
||||
TRACKS 0
|
||||
CLIPS 0
|
||||
NOTES 0 0
|
||||
AUTOMATION 0
|
||||
END
|
||||
13
tests/fixtures/project-v1.xtc
vendored
Normal file
13
tests/fixtures/project-v1.xtc
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
XTC_PROJECT 1
|
||||
APP 0 1 0
|
||||
TEMPO 120000
|
||||
IDS 1 1
|
||||
GRAPH 1 2 2 1
|
||||
NODE 0 1 3f000000
|
||||
NODE 1 4 00000000
|
||||
EDGE 0 1
|
||||
TRACKS 1
|
||||
TRACK 0 4b657973
|
||||
CLIPS 1
|
||||
CLIP 0 0 0 960 0 960 5665727365
|
||||
END
|
||||
14
tests/fixtures/project-v2.xtc
vendored
Normal file
14
tests/fixtures/project-v2.xtc
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
XTC_PROJECT 2
|
||||
APP 0 1 0
|
||||
TEMPO 120000
|
||||
IDS 1 1
|
||||
GRAPH 1 2 2 1
|
||||
NODE 0 1 3f000000
|
||||
NODE 1 4 00000000
|
||||
EDGE 0 1
|
||||
TRACKS 1
|
||||
TRACK 0 4b657973
|
||||
CLIPS 1
|
||||
CLIP 0 0 0 960 0 960 5665727365
|
||||
ASSET -
|
||||
END
|
||||
17
tests/fixtures/project-v3.xtc
vendored
Normal file
17
tests/fixtures/project-v3.xtc
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
XTC_PROJECT 3
|
||||
APP 0 1 0
|
||||
TEMPO 120000
|
||||
IDS 1 1
|
||||
GRAPH 1 2 2 1
|
||||
NODE 0 1 3f000000
|
||||
NODE 1 4 00000000
|
||||
EDGE 0 1
|
||||
TRACKS 1
|
||||
TRACK 0 4b657973
|
||||
CLIPS 1
|
||||
CLIP 0 0 0 960 0 960 5665727365
|
||||
ASSET -
|
||||
MIDI 1
|
||||
NOTES 1 1
|
||||
NOTE 0 0 0 480 69 100 0
|
||||
END
|
||||
20
tests/fixtures/project-v4.xtc
vendored
Normal file
20
tests/fixtures/project-v4.xtc
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
XTC_PROJECT 4
|
||||
APP 0 1 0
|
||||
TEMPO 120000
|
||||
IDS 1 1
|
||||
GRAPH 1 2 2 1
|
||||
NODE 0 1 3f000000
|
||||
NODE 1 4 00000000
|
||||
EDGE 0 1
|
||||
TRACKS 1
|
||||
TRACK 0 4b657973
|
||||
MIX 3f800000 00000000 0 0
|
||||
CLIPS 1
|
||||
CLIP 0 0 0 960 0 960 5665727365
|
||||
ASSET -
|
||||
MIDI 1
|
||||
NOTES 1 1
|
||||
NOTE 0 0 0 480 69 100 0
|
||||
AUTOMATION 1
|
||||
POINT 0 0 0 3f800000
|
||||
END
|
||||
20
tests/fixtures/project-v5.xtc
vendored
Normal file
20
tests/fixtures/project-v5.xtc
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
XTC_PROJECT 5
|
||||
APP 0 1 0
|
||||
TEMPO 120000
|
||||
IDS 1 1
|
||||
GRAPH 1 2 2 1
|
||||
NODE 0 1 3f000000
|
||||
NODE 1 4 00000000
|
||||
EDGE 0 1
|
||||
TRACKS 1
|
||||
TRACK 0 4b657973
|
||||
MIX 3f800000 00000000 0 0
|
||||
CLIPS 1
|
||||
CLIP 0 0 0 960 0 960 5665727365
|
||||
ASSET -
|
||||
MIDI 1
|
||||
NOTES 1 1
|
||||
NOTE 0 0 0 480 69 100 0
|
||||
AUTOMATION 1
|
||||
POINT 0 0 0 3f800000
|
||||
END
|
||||
20
tests/fixtures/project-v6.xtc
vendored
Normal file
20
tests/fixtures/project-v6.xtc
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
XTC_PROJECT 6
|
||||
APP 0 1 0
|
||||
TEMPO 120000
|
||||
IDS 1 1
|
||||
GRAPH 1 2 2 1
|
||||
NODE 0 1 3f000000
|
||||
NODE 1 4 00000000
|
||||
EDGE 0 1
|
||||
TRACKS 1
|
||||
TRACK 0 4b657973
|
||||
MIX 3f800000 00000000 0 0
|
||||
CLIPS 1
|
||||
CLIP 0 0 0 960 0 960 5665727365
|
||||
ASSET -
|
||||
MIDI 1
|
||||
NOTES 1 1
|
||||
NOTE 0 0 0 480 69 100 0
|
||||
AUTOMATION 1
|
||||
POINT 0 0 0 3f800000
|
||||
END
|
||||
80
tests/integration/test_portability.c
Normal file
80
tests/integration/test_portability.c
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#define _XOPEN_SOURCE 700
|
||||
#include "xtc/xtc_audio.h"
|
||||
#include "xtc/xtc_mixer.h"
|
||||
#include "xtc/xtc_midi.h"
|
||||
#include "xtc/xtc_plugin.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
int main(void)
|
||||
{
|
||||
char root[] = "/tmp/xtc-portable-XXXXXX", moved[512], file[512], media[512], old[512];
|
||||
xtc_project *p = NULL; unsigned version; uint64_t track, clip;
|
||||
float in[64], expected[64], actual[64]; size_t i;
|
||||
xtc_recorder *rec = NULL; xtc_audio_plan *audio = NULL;
|
||||
assert(mkdtemp(root));
|
||||
assert(xtc_project_create(&p) == XTC_OK);
|
||||
snprintf(file,sizeof(file),"%s/migration.xtc",root);
|
||||
for (version = 1; version <= 6; ++version) {
|
||||
xtc_track_info t; xtc_clip_info c; size_t notes, points;
|
||||
snprintf(old,sizeof(old),"tests/fixtures/project-v%u.xtc",version);
|
||||
assert(xtc_project_load(p,old) == XTC_OK);
|
||||
assert(xtc_project_get_track(p,0,&t) == XTC_OK && !strcmp(t.name,"Keys") && t.gain == 1);
|
||||
assert(xtc_project_get_clip(p,0,&c) == XTC_OK && c.midi == (version >= 3));
|
||||
assert(xtc_project_note_count(p,¬es) == XTC_OK && notes == (size_t)(version >= 3));
|
||||
assert(xtc_project_automation_count(p,&points) == XTC_OK && points == (size_t)(version >= 4));
|
||||
assert(xtc_project_save(p,file) == XTC_OK && xtc_project_load(p,file) == XTC_OK);
|
||||
}
|
||||
for (version = 5; version <= 6; ++version) {
|
||||
xtc_plugin_spec spec; xtc_plan *plan = NULL;
|
||||
snprintf(old,sizeof(old),"tests/fixtures/project-plugin-v%u.xtc",version);
|
||||
assert(xtc_project_load(p,old) == XTC_OK);
|
||||
assert(xtc_graph_get_plugin(xtc_project_graph(p),0,&spec) == XTC_OK && spec.tempo == 120000 && spec.point_count == version-5);
|
||||
assert(xtc_project_save(p,file) == XTC_OK && xtc_project_load(p,file) == XTC_OK);
|
||||
{ xtc_result r = xtc_graph_compile(xtc_project_graph(p),48000,64,&plan);
|
||||
assert((r == XTC_ERR_NOT_FOUND || r == XTC_ERR_UNSUPPORTED) && !plan); }
|
||||
}
|
||||
xtc_project_destroy(p); p = NULL;
|
||||
assert(xtc_project_create(&p) == XTC_OK && xtc_project_add_track(p,"Audio",&track) == XTC_OK);
|
||||
snprintf(media,sizeof(media),"%s/media.wav",root);
|
||||
for (i = 0; i < 64; ++i) in[i] = (float)i / 100;
|
||||
assert(xtc_recorder_create(media,48000,64,&rec) == XTC_OK);
|
||||
xtc_recorder_push(rec,in,64); assert(xtc_recorder_finish(rec) == XTC_OK); xtc_recorder_destroy(rec);
|
||||
assert(xtc_project_import_audio(p,track,media,0,&clip) == XTC_OK);
|
||||
assert(xtc_audio_plan_create(p,48000,&audio) == XTC_OK);
|
||||
assert(xtc_audio_plan_render(audio,0,expected,64) == XTC_OK); xtc_audio_plan_destroy(audio);
|
||||
assert(xtc_project_save(p,file) == XTC_OK);
|
||||
{ FILE *f = fopen(file,"r"); char text[4096]; size_t n; assert(f);
|
||||
n = fread(text,1,sizeof(text)-1,f); text[n] = 0; fclose(f);
|
||||
assert(strstr(text,"ASSET 6d656469612e776176\n")); }
|
||||
{
|
||||
char sub[512], copy[512]; xtc_clip_info c;
|
||||
snprintf(sub,sizeof(sub),"%s/elsewhere",root); assert(mkdir(sub,0700) == 0);
|
||||
snprintf(copy,sizeof(copy),"%s/elsewhere/copy.xtc",root);
|
||||
assert(xtc_project_save(p,copy) == XTC_OK && xtc_project_load(p,copy) == XTC_OK);
|
||||
assert(xtc_project_find_clip(p,clip,&c) == XTC_OK && !strcmp(c.asset_path,media));
|
||||
unlink(copy); rmdir(sub);
|
||||
}
|
||||
snprintf(moved,sizeof(moved),"%s-moved",root); assert(rename(root,moved) == 0);
|
||||
snprintf(file,sizeof(file),"%s-moved/migration.xtc",root);
|
||||
assert(xtc_project_load(p,file) == XTC_OK);
|
||||
{ char *cwd = getcwd(NULL,0); assert(cwd && chdir("/") == 0);
|
||||
assert(xtc_audio_plan_create(p,48000,&audio) == XTC_OK && xtc_audio_plan_issues(audio,NULL) == 0);
|
||||
assert(xtc_audio_plan_render(audio,0,actual,64) == XTC_OK);
|
||||
for (i = 0; i < 64; ++i) assert(expected[i] == actual[i]);
|
||||
xtc_audio_plan_destroy(audio); assert(chdir(cwd) == 0); free(cwd); }
|
||||
snprintf(media,sizeof(media),"%s-moved/media.wav",root); assert(unlink(media) == 0);
|
||||
assert(xtc_project_save(p,file) == XTC_OK && xtc_project_load(p,file) == XTC_OK);
|
||||
assert(xtc_audio_plan_create(p,48000,&audio) == XTC_OK && xtc_audio_plan_issues(audio,NULL) == 1);
|
||||
xtc_audio_plan_destroy(audio);
|
||||
/* Unknown records/versions fail without discarding the retained clip. */
|
||||
{ FILE *f = fopen(file,"a"); assert(f); fputs("FUTURE 1\n",f); fclose(f);
|
||||
assert(xtc_project_load(p,file) == XTC_ERR_FORMAT);
|
||||
{ xtc_clip_info c; assert(xtc_project_find_clip(p,clip,&c) == XTC_OK && c.asset_path[0]); } }
|
||||
xtc_project_destroy(p); unlink(file); rmdir(moved);
|
||||
puts("v1-v6 migration, v7 folder relocation, cwd independence and missing-media preservation passed.");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -190,6 +190,12 @@ int main(void)
|
|||
assert(fgets(line, sizeof(line), f));
|
||||
assert(fputs("XTC_PROJECT 3\n", legacy) >= 0);
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
/* Old formats use absolute assets, unlike the v7 writer. */
|
||||
if (!strncmp(line,"ASSET ",6) && line[6] != '-') {
|
||||
size_t k; fputs("ASSET ",legacy);
|
||||
for (k = 0; wave[k]; ++k) fprintf(legacy,"%02x",(unsigned char)wave[k]);
|
||||
fputc('\n',legacy); continue;
|
||||
}
|
||||
if (strncmp(line, "MIX ", 4) && strncmp(line, "AUTOMATION ", 11) &&
|
||||
strncmp(line, "POINT ", 6))
|
||||
assert(fputs(line, legacy) >= 0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue