feat: plan composition phases and persist named arrangement markers
This commit is contained in:
parent
1b668068de
commit
64e5ab170f
19 changed files with 323 additions and 9 deletions
11
Makefile
11
Makefile
|
|
@ -27,7 +27,7 @@ CXXFLAGS ?= -O2 -g
|
|||
CXXFLAGS += -std=c++17 -Wall -Wextra -Wpedantic -Iinclude
|
||||
LDFLAGS ?=
|
||||
|
||||
CORE_SOURCES := src/project/editing.c src/audio/export.c src/plugin/builtin.c src/settings/settings.c src/control/model.c src/audio/waveform.c src/plugin/ladspa.c src/plugin/host.c src/plugin/modern.c src/native/processor.c src/audio/recovery.c src/dsp/ramp.c src/native/native.c src/mixer/model.c src/mixer/render.c src/project/history.c src/midi/model.c src/midi/instrument.c src/audio/wave.c src/audio/arrangement.c src/audio/recorder.c src/core/xtc.c src/project/project.c src/persistence/project_file.c src/graph/graph.c src/dsp/gain.c src/engine/offline_engine.c src/engine/realtime.c src/backend/jack_backend.c
|
||||
CORE_SOURCES := src/project/sections.c src/project/editing.c src/audio/export.c src/plugin/builtin.c src/settings/settings.c src/control/model.c src/audio/waveform.c src/plugin/ladspa.c src/plugin/host.c src/plugin/modern.c src/native/processor.c src/audio/recovery.c src/dsp/ramp.c src/native/native.c src/mixer/model.c src/mixer/render.c src/project/history.c src/midi/model.c src/midi/instrument.c src/audio/wave.c src/audio/arrangement.c src/audio/recorder.c src/core/xtc.c src/project/project.c src/persistence/project_file.c src/graph/graph.c src/dsp/gain.c src/engine/offline_engine.c src/engine/realtime.c src/backend/jack_backend.c
|
||||
JACK_AVAILABLE := $(shell $(PKG_CONFIG) --exists jack 2>/dev/null && echo yes)
|
||||
ifeq ($(JACK_AVAILABLE),yes)
|
||||
JACK_CFLAGS := $(shell $(PKG_CONFIG) --cflags jack)
|
||||
|
|
@ -545,3 +545,12 @@ test-modern-editing: $(BUILD_DIR)/test_modern_editing
|
|||
.PHONY: test-gui-modern-editing
|
||||
test-gui-modern-editing: $(BUILD_DIR)/test_gui
|
||||
XTC_TEST_MODERN_EDITING_ONLY=1 xvfb-run -a -s "-screen 0 1280x800x24" ./$(BUILD_DIR)/test_gui
|
||||
|
||||
$(BUILD_DIR)/test_next_daw: tests/unit/test_next_daw.c $(BUILD_DIR)/libxtc.a
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $< $(BUILD_DIR)/libxtc.a -lm -ldl
|
||||
.PHONY: test-next-daw test-gui-next-daw
|
||||
test-next-daw: $(BUILD_DIR)/test_next_daw
|
||||
./$(BUILD_DIR)/test_next_daw
|
||||
test: test-next-daw
|
||||
test-gui-next-daw: $(BUILD_DIR)/test_gui
|
||||
XTC_TEST_MODERN_EDITING_ONLY=1 XTC_TEST_NEXT_DAW_ONLY=1 xvfb-run -a -s "-screen 0 1280x800x24" ./$(BUILD_DIR)/test_gui
|
||||
|
|
|
|||
8
docs/COMPOSITION_WORKFLOWS.md
Normal file
8
docs/COMPOSITION_WORKFLOWS.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Arrangement and composition workflows
|
||||
|
||||
All mutations run on the control thread through the existing stop, history and
|
||||
prepared-plan workflow. No new dependencies are required.
|
||||
|
||||
## Phase 58 — Persistent Arrangement Markers
|
||||
|
||||
Edit/Markers/Set at cursor adds or renames a marker; Remove at cursor removes the exact tick key. Markers draw names and vertical guides in the arrangement and survive project save/load and undo. Up to 128 markers are supported. Names may overlap visually when markers are close; markers do not change playback by themselves.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Project format v15 (reads v1–v14)
|
||||
# Project format v16 (reads v1–v15)
|
||||
|
||||
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.
|
||||
|
|
@ -418,3 +418,11 @@ 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`.
|
||||
|
||||
## Version 16: arrangement markers
|
||||
|
||||
After `LIVE_INSTRUMENT` and before `END`, version 16 writes `MARKERS count`
|
||||
followed by `MARKER tick hex_name` records. Count is 0..128; ticks must be strictly
|
||||
increasing and at most MAX_TICK. Names use the existing bounded UTF-8 hex encoding.
|
||||
Versions 1–15 migrate to an empty marker list. Older builds reject version 16.
|
||||
Markers are copied by snapshots and history; the tick is their stable key.
|
||||
|
|
|
|||
165
docs/ROADMAP.md
165
docs/ROADMAP.md
|
|
@ -1491,6 +1491,171 @@ remain unqualified.
|
|||
|
||||
---
|
||||
|
||||
## Phase 58 — Persistent Arrangement Markers
|
||||
|
||||
### Goal
|
||||
Store up to 128 named, tick-keyed markers with versioned migration, undoable GUI editing and ruler display.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [x] Implement bounded C99 model/control APIs and accessible GUI commands.
|
||||
- [x] Preserve transactional errors, history, compatibility and callback safety.
|
||||
- [x] Verify the feature with automated model/GUI tests and document limitations.
|
||||
|
||||
### Implementation status
|
||||
Implemented: sorted tick-keyed markers, bounded UTF-8 names, undoable GUI editing and ruler display. Format 16 appends marker records and reads versions 1–15. Core, legacy migration, malformed-load rollback and focused GUI history/docking tests pass.
|
||||
|
||||
---
|
||||
|
||||
## Phase 59 — Section Navigation and Looping
|
||||
|
||||
### Goal
|
||||
Navigate previous/next markers and loop the section between a marker and its successor or the song end.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [ ] Implement bounded C99 model/control APIs and accessible GUI commands.
|
||||
- [ ] Preserve transactional errors, history, compatibility and callback safety.
|
||||
- [ ] Verify the feature with automated model/GUI tests and document limitations.
|
||||
|
||||
### Implementation status
|
||||
Planned.
|
||||
|
||||
---
|
||||
|
||||
## Phase 60 — Ripple Time Editing
|
||||
|
||||
### Goal
|
||||
Insert or remove bounded timeline ranges atomically, shifting clips, markers and automation; reject clips crossing edit boundaries explicitly.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [ ] Implement bounded C99 model/control APIs and accessible GUI commands.
|
||||
- [ ] Preserve transactional errors, history, compatibility and callback safety.
|
||||
- [ ] Verify the feature with automated model/GUI tests and document limitations.
|
||||
|
||||
### Implementation status
|
||||
Planned.
|
||||
|
||||
---
|
||||
|
||||
## Phase 61 — Complete Track Duplication
|
||||
|
||||
### Goal
|
||||
Duplicate a non-bus track with fresh IDs, clips, notes, mixer controls, routing, instrument settings and track automation in one transaction.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [ ] Implement bounded C99 model/control APIs and accessible GUI commands.
|
||||
- [ ] Preserve transactional errors, history, compatibility and callback safety.
|
||||
- [ ] Verify the feature with automated model/GUI tests and document limitations.
|
||||
|
||||
### Implementation status
|
||||
Planned.
|
||||
|
||||
---
|
||||
|
||||
## Phase 62 — Nondestructive MIDI Clip Splitting
|
||||
|
||||
### Goal
|
||||
Split trimmed MIDI clips at the cursor while sharing source-relative note content and preserving exact rendered boundaries.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [ ] Implement bounded C99 model/control APIs and accessible GUI commands.
|
||||
- [ ] Preserve transactional errors, history, compatibility and callback safety.
|
||||
- [ ] Verify the feature with automated model/GUI tests and document limitations.
|
||||
|
||||
### Implementation status
|
||||
Planned.
|
||||
|
||||
---
|
||||
|
||||
## Phase 63 — MIDI Clip Consolidation
|
||||
|
||||
### Goal
|
||||
Merge selected MIDI clips on one track into a fresh source, retaining audible notes and gaps with explicit boundary clipping and rollback.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [ ] Implement bounded C99 model/control APIs and accessible GUI commands.
|
||||
- [ ] Preserve transactional errors, history, compatibility and callback safety.
|
||||
- [ ] Verify the feature with automated model/GUI tests and document limitations.
|
||||
|
||||
### Implementation status
|
||||
Planned.
|
||||
|
||||
---
|
||||
|
||||
## Phase 64 — Deterministic MIDI Humanization
|
||||
|
||||
### Goal
|
||||
Apply seeded timing and velocity variation to MIDI clip sources with reproducible results, bounds checks and undo.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [ ] Implement bounded C99 model/control APIs and accessible GUI commands.
|
||||
- [ ] Preserve transactional errors, history, compatibility and callback safety.
|
||||
- [ ] Verify the feature with automated model/GUI tests and document limitations.
|
||||
|
||||
### Implementation status
|
||||
Planned.
|
||||
|
||||
---
|
||||
|
||||
## Phase 65 — Chord Construction
|
||||
|
||||
### Goal
|
||||
Generate bounded custom chord intervals from source notes with fresh IDs, preserved articulation and transactional pitch/capacity checks.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [ ] Implement bounded C99 model/control APIs and accessible GUI commands.
|
||||
- [ ] Preserve transactional errors, history, compatibility and callback safety.
|
||||
- [ ] Verify the feature with automated model/GUI tests and document limitations.
|
||||
|
||||
### Implementation status
|
||||
Planned.
|
||||
|
||||
---
|
||||
|
||||
## Phase 66 — Offline Arpeggio Generation
|
||||
|
||||
### Goal
|
||||
Convert source note chords into bounded ascending/descending stepped patterns with gate controls, deterministic ordering and undo.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [ ] Implement bounded C99 model/control APIs and accessible GUI commands.
|
||||
- [ ] Preserve transactional errors, history, compatibility and callback safety.
|
||||
- [ ] Verify the feature with automated model/GUI tests and document limitations.
|
||||
|
||||
### Implementation status
|
||||
Planned.
|
||||
|
||||
---
|
||||
|
||||
## Phase 67 — Audio Peak Normalization
|
||||
|
||||
### Goal
|
||||
Analyze the audible source slice outside callbacks and adjust nondestructive clip gain to a requested sample-peak target; validate silence, missing media and gain limits.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [ ] Implement bounded C99 model/control APIs and accessible GUI commands.
|
||||
- [ ] Preserve transactional errors, history, compatibility and callback safety.
|
||||
- [ ] Verify the feature with automated model/GUI tests and document limitations.
|
||||
|
||||
### Implementation status
|
||||
Planned.
|
||||
|
||||
---
|
||||
|
||||
## Phase 68 — Portable Project Collection
|
||||
|
||||
### Goal
|
||||
Collect referenced audio and sampler media into a new self-contained project folder, deduplicate references, preserve original files and reject existing destinations or unsupported references safely.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [ ] Implement bounded C99 model/control APIs and accessible GUI commands.
|
||||
- [ ] Preserve transactional errors, history, compatibility and callback safety.
|
||||
- [ ] Verify the feature with automated model/GUI tests and document limitations.
|
||||
|
||||
### Implementation status
|
||||
Planned.
|
||||
|
||||
---
|
||||
|
||||
## Recommended Execution Order
|
||||
|
||||
Reorder the phases when the audit reveals a blocking dependency. Do not mark a phase complete merely because code exists: its acceptance criteria must be demonstrated by builds, tests, or documented manual verification.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define XTC_PROJECT_FORMAT_VERSION 15U
|
||||
#define XTC_PROJECT_FORMAT_VERSION 16U
|
||||
#define XTC_PROJECT_MAX_TRACKS 64U
|
||||
#define XTC_PROJECT_MAX_CLIPS 1024U
|
||||
#define XTC_PROJECT_NAME_BYTES 64U
|
||||
|
|
@ -15,6 +15,15 @@ extern "C" {
|
|||
#define XTC_TICKS_PER_QUARTER 960U
|
||||
#define XTC_PROJECT_MAX_TICK UINT64_C(38400000)
|
||||
typedef struct xtc_project xtc_project;
|
||||
#define XTC_PROJECT_MAX_MARKERS 128U
|
||||
/* Control-thread, sorted unique tick keys. Setting an existing tick renames it;
|
||||
* no allocations, playback implications or external resources. */
|
||||
typedef struct {uint64_t tick;char name[XTC_PROJECT_NAME_BYTES];} xtc_marker;
|
||||
xtc_result xtc_project_marker_count(const xtc_project *project,size_t *out);
|
||||
xtc_result xtc_project_get_marker(const xtc_project *project,size_t index,xtc_marker *out);
|
||||
xtc_result xtc_project_set_marker(xtc_project *project,uint64_t tick,const char *name);
|
||||
xtc_result xtc_project_remove_marker(xtc_project *project,uint64_t tick);
|
||||
|
||||
/* Control-thread edits, transactional validation; duplicate retains complete
|
||||
* source notes/trim/media references, allocates fresh IDs, copies no audio. */
|
||||
xtc_result xtc_project_duplicate_clip(xtc_project *project, uint64_t clip, uint64_t track, uint64_t start, uint64_t *out);
|
||||
|
|
|
|||
|
|
@ -339,6 +339,8 @@ MainWindow::MainWindow(bool prompt_unsaved,const char *configuration)
|
|||
menu->add("Edit/Repeat selected phrase...",0,[](Fl_Widget *,void *c){static_cast<SequencerWidget *>(c)->repeat_dialog();},sequencer_);
|
||||
menu->add("Edit/Copy clips",0,[](Fl_Widget *,void *c){static_cast<SequencerWidget *>(c)->copy_selection();},sequencer_);
|
||||
menu->add("Edit/Paste clips at cursor",0,[](Fl_Widget *,void *c){static_cast<SequencerWidget *>(c)->paste_selection();},sequencer_);
|
||||
menu->add("Edit/Markers/Set at cursor...",0,[](Fl_Widget *,void *c){static_cast<SequencerWidget *>(c)->marker_dialog();},sequencer_);
|
||||
menu->add("Edit/Markers/Remove at cursor",0,[](Fl_Widget *,void *c){static_cast<SequencerWidget *>(c)->marker(nullptr);},sequencer_);
|
||||
menu->add("Edit/Select all clips",0,[](Fl_Widget *,void *c){static_cast<SequencerWidget *>(c)->select_all_clips();},sequencer_);
|
||||
menu->add("Edit/Duplicate clip",FL_CTRL+'d',[](Fl_Widget *, void *c) { (void)static_cast<SequencerWidget *>(c)->duplicate_selection(); },sequencer_);
|
||||
menu->add("Edit/Split audio at cursor", 0, [](Fl_Widget *, void *c) { (void)static_cast<SequencerWidget *>(c)->split_selection(); }, sequencer_);
|
||||
|
|
|
|||
|
|
@ -319,6 +319,14 @@ void SequencerWidget::draw()
|
|||
fl_color(loop_enabled_ ? xtc_ui::accent() : xtc_ui::muted());
|
||||
fl_rectf(left,y()+23,std::max(0,right-left),4);
|
||||
}
|
||||
size_t markers=0;xtc_project_marker_count(project_,&markers);
|
||||
for(size_t i=0;i<markers;++i) {
|
||||
xtc_marker marker{};xtc_project_get_marker(project_,i,&marker);
|
||||
int px=x()+header_width_+static_cast<int>(marker.tick*static_cast<unsigned int>(scale_)/960U);
|
||||
if(px<cx || px>cx+cw)continue;
|
||||
fl_color(xtc_ui::accent());fl_line(px,y()+24,px,y()+h());
|
||||
fl_push_clip(px,y(),160,12);fl_font(FL_HELVETICA,10);fl_draw(marker.name,px+3,y()+10);fl_pop_clip();
|
||||
}
|
||||
int playhead = x()+header_width_+static_cast<int>(cursor_*static_cast<unsigned int>(scale_)/960U);
|
||||
if (playhead >= cx && playhead <= cx+cw) {
|
||||
fl_color(xtc_ui::accent()); fl_line(playhead,y(),playhead,y()+h());
|
||||
|
|
@ -511,6 +519,16 @@ int SequencerWidget::handle(int event)
|
|||
return Fl_Group::handle(event);
|
||||
}
|
||||
|
||||
xtc_result SequencerWidget::marker(const char *name)
|
||||
{
|
||||
const uint64_t at=cursor_;xtc_result r=before_edit ? before_edit(context_) : XTC_OK;
|
||||
if(r==XTC_OK)r=name ? xtc_project_set_marker(project_,at,name) : xtc_project_remove_marker(project_,at);
|
||||
changed(r);return r;
|
||||
}
|
||||
void SequencerWidget::marker_dialog()
|
||||
{
|
||||
const char *name=fl_input("Marker at cursor (1..63 UTF-8 bytes)","Section");if(name)marker(name);
|
||||
}
|
||||
std::vector<uint64_t> SequencerWidget::selected_clips() const
|
||||
{
|
||||
std::vector<uint64_t> ids;xtc_clip_info c{};
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ class SequencerWidget : public Fl_Group {
|
|||
Fl_Choice *instrument_choice(uint64_t track) const;
|
||||
void refresh_instruments();
|
||||
bool select(uint64_t track, uint64_t clip = UINT64_MAX);
|
||||
xtc_result marker(const char *name); /* NULL removes at cursor. */
|
||||
void marker_dialog();
|
||||
bool toggle_clip(uint64_t clip);
|
||||
void select_all_clips();
|
||||
bool clip_selected(uint64_t clip) const;
|
||||
|
|
|
|||
|
|
@ -495,6 +495,13 @@ static xtc_result read_project(reader *input, xtc_project *project, const char *
|
|||
}
|
||||
READ("LIVE_INSTRUMENT",2U); NUM(1U,project->live_instrument);
|
||||
}
|
||||
if(format_version>=16U) {
|
||||
READ("MARKERS",2U);NUM(1U,value);if(value>XTC_PROJECT_MAX_MARKERS)return XTC_ERR_FORMAT;
|
||||
project->marker_count=(size_t)value;
|
||||
for(i=0;i<project->marker_count;++i){READ("MARKER",3U);NUM(1U,project->markers[i].tick);
|
||||
if(!decode_name(input->tokens[2],project->markers[i].name))return XTC_ERR_FORMAT;
|
||||
}
|
||||
}
|
||||
READ("END", 1U);
|
||||
if (fgetc(input->file) != EOF)
|
||||
return XTC_ERR_FORMAT;
|
||||
|
|
@ -672,6 +679,8 @@ static xtc_result write_project(FILE *file, const xtc_project *project, const ch
|
|||
}
|
||||
if (fprintf(file,"LIVE_INSTRUMENT %" PRIu64 "\n",project->live_instrument) < 0) return XTC_ERR_IO;
|
||||
}
|
||||
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;
|
||||
return fputs("END\n", file) == EOF ? XTC_ERR_IO : XTC_OK;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -529,6 +529,9 @@ xtc_result xtc_project_validate(const xtc_project *project)
|
|||
if (project->tempo < 20000U || project->tempo > 300000U || project->track_count > XTC_PROJECT_MAX_TRACKS ||
|
||||
project->clip_count > XTC_PROJECT_MAX_CLIPS || project->note_count > XTC_PROJECT_MAX_NOTES)
|
||||
return XTC_ERR_FORMAT;
|
||||
if(project->marker_count>XTC_PROJECT_MAX_MARKERS)return XTC_ERR_FORMAT;
|
||||
for(i=0;i<project->marker_count;++i)if(project->markers[i].tick>XTC_PROJECT_MAX_TICK ||
|
||||
!xtc_project_valid_name(project->markers[i].name) || (i && project->markers[i-1].tick>=project->markers[i].tick))return XTC_ERR_FORMAT;
|
||||
for (i = 0; i < 16; ++i) for (j = 0; j < 3; ++j) {
|
||||
unsigned int cc = project->control_map[i][j];
|
||||
size_t k;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
struct xtc_project {
|
||||
xtc_graph *graph;
|
||||
size_t marker_count;
|
||||
xtc_marker markers[XTC_PROJECT_MAX_MARKERS];
|
||||
uint64_t live_instrument;
|
||||
xtc_plugin_spec instruments[XTC_PROJECT_MAX_TRACKS];
|
||||
uint64_t next_track_id, next_clip_id;
|
||||
|
|
|
|||
33
src/project/sections.c
Normal file
33
src/project/sections.c
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#include "project_internal.h"
|
||||
#include <string.h>
|
||||
|
||||
xtc_result xtc_project_marker_count(const xtc_project *p,size_t *out)
|
||||
{
|
||||
if(!p || !out)return XTC_ERR_INVALID_ARGUMENT;
|
||||
*out=p->marker_count;return XTC_OK;
|
||||
}
|
||||
xtc_result xtc_project_get_marker(const xtc_project *p,size_t index,xtc_marker *out)
|
||||
{
|
||||
if(!p || !out)return XTC_ERR_INVALID_ARGUMENT;
|
||||
if(index>=p->marker_count)return XTC_ERR_NOT_FOUND;
|
||||
*out=p->markers[index];return XTC_OK;
|
||||
}
|
||||
xtc_result xtc_project_set_marker(xtc_project *p,uint64_t tick,const char *name)
|
||||
{
|
||||
size_t i;xtc_marker marker;
|
||||
if(!p || tick>XTC_PROJECT_MAX_TICK || !xtc_project_valid_name(name))return XTC_ERR_INVALID_ARGUMENT;
|
||||
memset(&marker,0,sizeof(marker));marker.tick=tick;memcpy(marker.name,name,strlen(name)+1);
|
||||
for(i=0;i<p->marker_count && p->markers[i].tick<tick;++i){}
|
||||
if(i<p->marker_count && p->markers[i].tick==tick){p->markers[i]=marker;return XTC_OK;}
|
||||
if(p->marker_count==XTC_PROJECT_MAX_MARKERS)return XTC_ERR_CAPACITY;
|
||||
memmove(p->markers+i+1,p->markers+i,(p->marker_count-i)*sizeof(marker));
|
||||
p->markers[i]=marker;++p->marker_count;return XTC_OK;
|
||||
}
|
||||
xtc_result xtc_project_remove_marker(xtc_project *p,uint64_t tick)
|
||||
{
|
||||
size_t i;if(!p)return XTC_ERR_INVALID_ARGUMENT;
|
||||
for(i=0;i<p->marker_count;++i)if(p->markers[i].tick==tick)break;
|
||||
if(i==p->marker_count)return XTC_ERR_NOT_FOUND;
|
||||
memmove(p->markers+i,p->markers+i+1,(p->marker_count-i-1)*sizeof(*p->markers));
|
||||
--p->marker_count;return XTC_OK;
|
||||
}
|
||||
|
|
@ -1309,6 +1309,7 @@ int main(int argc, char **argv)
|
|||
unlink(path.c_str());
|
||||
}
|
||||
}
|
||||
if(!std::getenv("XTC_TEST_NEXT_DAW_ONLY")) {
|
||||
reset();
|
||||
{
|
||||
MainWindow window(false,"");window.show();CHECK(window.create_track(true)==XTC_OK);
|
||||
|
|
@ -1359,6 +1360,17 @@ int main(int argc, char **argv)
|
|||
CHECK(window.new_project()==XTC_OK && window.mix_snapshot(0,false)==XTC_ERR_NOT_FOUND);
|
||||
window.do_callback();
|
||||
}
|
||||
}
|
||||
reset();
|
||||
{
|
||||
MainWindow window(false,"");window.show();CHECK(window.create_track(true)==XTC_OK);
|
||||
auto *s=timeline(&window);s->position(960);CHECK(s->marker("Verse")==XTC_OK);
|
||||
size_t markers=0;xtc_project_marker_count(s->project(),&markers);CHECK(markers==1);
|
||||
CHECK(window.undo()==XTC_OK);xtc_project_marker_count(s->project(),&markers);CHECK(markers==0);
|
||||
CHECK(window.undo(true)==XTC_OK);xtc_project_marker_count(s->project(),&markers);CHECK(markers==1);
|
||||
window.detach_editor(0,true);pump();window.detach_editor(0,false);
|
||||
window.do_callback();
|
||||
}
|
||||
if (failures != 0) return 1;
|
||||
std::printf("GUI controls/errors/views/timers/cleanup passed: %u opens, %u closes.\n", opens, closes);
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -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..15' in diagnose and 'JACK compiled:' in diagnose
|
||||
assert 'Project formats: 1..16' 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..15")),
|
||||
(base.replace("XTC_PROJECT 10", "XTC_PROJECT 999"), ("line 1", "999", "1..16")),
|
||||
("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,"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");
|
||||
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");
|
||||
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, "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))
|
||||
assert(fputs(line, legacy) >= 0);
|
||||
}
|
||||
assert(fclose(f) == 0 && fclose(legacy) == 0);
|
||||
|
|
|
|||
34
tests/unit/test_next_daw.c
Normal file
34
tests/unit/test_next_daw.c
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "xtc/xtc_editing.h"
|
||||
#include "xtc/xtc_mixer.h"
|
||||
#include "xtc/xtc_audio.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void markers(void)
|
||||
{
|
||||
xtc_project *p=NULL,*q=NULL;xtc_marker m;size_t count;char path[]="/tmp/xtc-sections-XXXXXX";
|
||||
int fd=mkstemp(path);assert(fd>=0 && close(fd)==0);
|
||||
assert(xtc_project_create(&p)==XTC_OK && xtc_project_create(&q)==XTC_OK);
|
||||
assert(xtc_project_set_marker(p,3840,"Chorus")==XTC_OK);
|
||||
assert(xtc_project_set_marker(p,0,"Intro")==XTC_OK);
|
||||
assert(xtc_project_set_marker(p,0,"Intro ä")==XTC_OK);
|
||||
assert(xtc_project_get_marker(p,0,&m)==XTC_OK && !strcmp(m.name,"Intro ä"));
|
||||
assert(xtc_project_set_marker(p,1,"Bad\n")==XTC_ERR_INVALID_ARGUMENT);
|
||||
assert(xtc_project_save(p,path)==XTC_OK && xtc_project_load(q,path)==XTC_OK);
|
||||
assert(xtc_project_marker_count(q,&count)==XTC_OK && count==2);
|
||||
assert(xtc_project_get_marker(q,1,&m)==XTC_OK && m.tick==3840 && !strcmp(m.name,"Chorus"));
|
||||
assert(xtc_project_remove_marker(q,0)==XTC_OK && xtc_project_remove_marker(q,0)==XTC_ERR_NOT_FOUND);
|
||||
for(size_t i=1;i<127;++i)assert(xtc_project_set_marker(p,i,"M")==XTC_OK);
|
||||
assert(xtc_project_set_marker(p,999,"Full")==XTC_ERR_CAPACITY);
|
||||
/* Malformed marker ordering rejects transactionally. */
|
||||
{FILE *f=fopen(path,"r");char data[32768];size_t n;assert(f);n=fread(data,1,sizeof(data)-1,f);data[n]=0;fclose(f);
|
||||
char *at=strstr(data,"MARKER 3840");assert(at);memcpy(at+7,"0000",4);
|
||||
f=fopen(path,"w");assert(f && fwrite(data,1,n,f)==n && fclose(f)==0);
|
||||
assert(xtc_project_load(q,path)==XTC_ERR_FORMAT);assert(xtc_project_marker_count(q,&count)==XTC_OK && count==1);}
|
||||
xtc_project_destroy(p);xtc_project_destroy(q);assert(unlink(path)==0);
|
||||
}
|
||||
int main(void){markers();puts("Next DAW workflow tests 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..15"));
|
||||
diagnostic.format_version == 999 && !strcmp(diagnostic.record,"XTC_PROJECT") && strstr(diagnostic.message,"1..16"));
|
||||
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