Phase 35: add persistent preferences and sequencer loop playback

This commit is contained in:
Johannes Findeisen 2026-10-28 10:37:01 +01:00
commit 47b7d38a66
21 changed files with 852 additions and 108 deletions

View file

@ -4,6 +4,8 @@
#include "../../src/gui/piano_roll.hpp"
#include "../../src/gui/mixer_widget.hpp"
#include "../../src/gui/plugin_dialog.hpp"
#include "../../src/gui/preferences_dialog.hpp"
#include "../../src/gui/plugin_library.hpp"
#include <FL/Fl_Hold_Browser.H>
#include <FL/Fl_Choice.H>
#include <FL/Fl_Value_Slider.H>
@ -126,6 +128,8 @@ xtc_result xtc_backend_close(xtc_backend *backend)
}
xtc_result xtc_backend_start(xtc_backend *backend)
{ CHECK(backend == &handle); ++starts; if (start_result == XTC_OK) { handle.running = 1; engine_status.active_revision = engine_status.submitted_revision; } return start_result; }
xtc_result xtc_backend_set_loop(xtc_backend *,uint64_t start,uint64_t end,int enabled)
{ return enabled && (end<=start || end-start<64) ? XTC_ERR_INVALID_ARGUMENT : XTC_OK; }
xtc_result xtc_backend_set_transition_frames(xtc_backend *, size_t frames)
{ CHECK(frames == 240); return XTC_OK; }
xtc_result xtc_backend_request_stop(xtc_backend *backend)
@ -183,7 +187,7 @@ int main(int argc, char **argv)
{
reset();
{
MainWindow window(false);
MainWindow window(false,"");
window.show(argc, argv);
auto *play = find(&window, "Play");
auto *stop = find(&window, "Stop");
@ -535,7 +539,7 @@ int main(int argc, char **argv)
for (xtc_result failure : {XTC_ERR_IO, XTC_ERR_UNSUPPORTED}) {
reset();
open_result = failure;
MainWindow window(false);
MainWindow window(false,"");
window.show(argc, argv);
CHECK(contains(&window, "Open JACK failed") && !find(&window, "Play")->active());
open_result = XTC_OK;
@ -549,21 +553,21 @@ int main(int argc, char **argv)
partial_open = true;
open_result = XTC_ERR_IO;
{
MainWindow window(false);
MainWindow window(false,"");
CHECK(alive == 1U && !find(&window, "Play")->active());
window.do_callback(); // Partially initialized handles are closed only.
CHECK(alive == 0U);
}
reset();
for (int i = 0; i < 10; ++i) {
MainWindow window(false);
MainWindow window(false,"");
press(find(&window, "Play"));
window.do_callback();
}
CHECK(alive == 0U);
reset();
{
MainWindow window(false); auto *s = timeline(&window);
MainWindow window(false,""); auto *s = timeline(&window);
CHECK(s->add_track() == XTC_OK && s->rename_selection("Keys") == XTC_OK);
CHECK(s->add_region() == XTC_OK && s->rename_selection("Verse") == XTC_OK);
CHECK(s->grid(120) == XTC_OK && s->grid() == 120 && s->grid(0) == XTC_ERR_INVALID_ARGUMENT);
@ -585,7 +589,7 @@ int main(int argc, char **argv)
}
{
reset();
MainWindow window(false);
MainWindow window(false,"");
auto *canvas = graph_canvas(&window);
CHECK(canvas != nullptr);
CHECK(canvas->add_node(XTC_NODE_OSCILLATOR) == XTC_OK);
@ -628,7 +632,7 @@ int main(int argc, char **argv)
window.do_callback();
}
{
reset(); MainWindow window(false);
reset(); MainWindow window(false,"");
auto *sequence = timeline(&window); auto *mixer = window.mixer();
CHECK(sequence->add_track() == XTC_OK);
CHECK(mixer->add_bus() == XTC_OK);
@ -649,7 +653,7 @@ int main(int argc, char **argv)
window.do_callback();
}
{
reset(); MainWindow window(false);
reset(); MainWindow window(false,"");
auto *sequence = timeline(&window);
unsigned int bindings[3] = {1,74,11}, actual[3];
CHECK(window.set_control_map(2,bindings) == XTC_OK);
@ -674,7 +678,7 @@ int main(int argc, char **argv)
unlink("/tmp/xtc-gui-controls-test.xtc"); window.do_callback();
}
{
reset(); MainWindow window(false);
reset(); MainWindow window(false,"");
auto *sequence = timeline(&window); auto *canvas = graph_canvas(&window);
CHECK(sequence->add_track() == XTC_OK && sequence->add_midi_clip() == XTC_OK);
const uint64_t track = sequence->selected_track(), clip = sequence->selected_clip();
@ -705,7 +709,7 @@ int main(int argc, char **argv)
}
if (const char *module = std::getenv("XTC_TEST_PLUGIN")) {
reset();
MainWindow window(false);
MainWindow window(false,"");
auto *sequence = timeline(&window);
size_t id = SIZE_MAX;
CHECK(window.add_plugin(module,0,&id) == XTC_OK);
@ -807,7 +811,7 @@ int main(int argc, char **argv)
}
for (const char *variable : {"XTC_TEST_CLAP","XTC_TEST_VST2","XTC_TEST_VST3"}) {
if (const char *module = std::getenv(variable)) {
reset(); MainWindow window(false); auto *sequence = timeline(&window);
reset(); MainWindow window(false,""); auto *sequence = timeline(&window);
size_t id = SIZE_MAX; xtc_plugin_spec spec{};
CHECK(window.add_plugin(module,0,&id) == XTC_OK);
CHECK(xtc_graph_get_plugin(xtc_project_graph(sequence->project()),id,&spec) == XTC_OK);
@ -831,7 +835,7 @@ int main(int argc, char **argv)
}
for (const char *variable : {"XTC_TEST_SYNTH_CLAP","XTC_TEST_SYNTH_VST2","XTC_TEST_SYNTH_VST3"}) {
if (const char *module = std::getenv(variable)) {
reset(); MainWindow window(false); auto *sequence = timeline(&window);
reset(); MainWindow window(false,""); auto *sequence = timeline(&window);
CHECK(sequence->add_track() == XTC_OK && sequence->add_midi_clip() == XTC_OK);
uint64_t track = sequence->selected_track(); xtc_plugin_spec spec{};
CHECK(window.assign_instrument(track,module,0) == XTC_OK);
@ -867,7 +871,7 @@ int main(int argc, char **argv)
// the current model. Exercise real sidebar buttons rather than mock actions.
reset();
{
MainWindow window(false); window.show();
MainWindow window(false,""); window.show();
auto *sequence = timeline(&window);
CHECK(sequence != nullptr);
auto *midi = find(&window,"New MIDI track");
@ -908,7 +912,7 @@ int main(int argc, char **argv)
}
reset();
{
MainWindow window(false); window.show(); auto *sequence = timeline(&window);
MainWindow window(false,""); window.show(); auto *sequence = timeline(&window);
CHECK(window.create_track(true) == XTC_OK);
uint64_t clip = sequence->selected_clip(), track = sequence->selected_track();
auto *snap = dynamic_cast<Fl_Choice *>(find(&window,"Snap")); CHECK(snap && snap->value() == 3);
@ -964,6 +968,44 @@ int main(int argc, char **argv)
xtc_project_get_note(sequence->project(),0,&note); CHECK(note.velocity > 29 && note.pitch == 60 && note.channel == 2);
unlink("/tmp/xtc-grid-velocity.xtc"); window.do_callback();
}
{
char dir[]="/tmp/xtc-preferences-XXXXXX"; CHECK(mkdtemp(dir));
std::string path=std::string(dir)+"/.xtc.conf";
reset();
{
MainWindow window(false,path.c_str());window.show();
xtc_settings settings=window.settings();settings.note_velocity=73;settings.sequencer_snap=480;
CHECK(window.apply_preferences(settings)==XTC_OK);
CHECK(timeline(&window)->grid()==480);
int old_state=Fl::e_state;Fl::e_state=FL_SHIFT;
gesture(timeline(&window),228,12,96,0);Fl::e_state=old_state;
CHECK(window.settings().loop_start==960 && window.settings().loop_end==2880);
CHECK(window.set_loop(960,3840,true)==XTC_OK);
CHECK(window.settings().loop_enabled==1 && window.settings().loop_start==960);
CHECK(window.set_loop(4000,2000,true)==XTC_ERR_INVALID_ARGUMENT);
settings=window.settings();CHECK(window.apply_preferences(settings)==XTC_OK);
settings.note_velocity=128;CHECK(window.apply_preferences(settings)==XTC_ERR_INVALID_ARGUMENT);
PreferencesDialog dialog(window.settings(),path,[&](const xtc_settings &v){return window.apply_preferences(v);});
auto *input=dynamic_cast<Fl_Input *>(find(&dialog,"New note velocity"));CHECK(input);
if(input)input->value("81");
press(find(&dialog,"Apply"));CHECK(window.settings().note_velocity==81);
if(input)input->value("82");
press(find(&dialog,"Close"));CHECK(window.settings().note_velocity==81);
window.do_callback();
}
reset();
{
MainWindow window(false,path.c_str());CHECK(window.settings().note_velocity==81);
CHECK(window.settings().loop_start==960 && window.settings().loop_enabled==1);
xtc_settings paths=window.settings();
std::string binary=std::string(dir)+"/test.clap";FILE *f=fopen(binary.c_str(),"wb");CHECK(f);if(f)fclose(f);
std::snprintf(paths.clap_paths,sizeof(paths.clap_paths),"%s:%s",dir,dir);
paths.vst2_paths[0]=paths.vst3_paths[0]=paths.ladspa_paths[0]=0;
auto files=xtc_plugin_files(paths);CHECK(files.size()==1 && files[0]==binary);
unlink(binary.c_str());window.do_callback();
}
unlink(path.c_str());rmdir(dir);
}
if (failures != 0) return 1;
std::printf("GUI controls/errors/views/timers/cleanup passed: %u opens, %u closes.\n", opens, closes);
return 0;