Phase 8: Add stereo track mixer, automation and undo/redo

This commit is contained in:
Johannes Findeisen 2026-10-27 01:06:57 +01:00
commit 7db5fb7738
45 changed files with 1611 additions and 130 deletions

View file

@ -2,6 +2,7 @@
#include "../../src/gui/graph_widget.hpp"
#include "../../src/gui/sequencer_widget.hpp"
#include "../../src/gui/piano_roll.hpp"
#include "../../src/gui/mixer_widget.hpp"
#include <unistd.h>
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
@ -89,7 +90,7 @@ void reset()
extern "C" {
xtc_result xtc_backend_open(const xtc_backend_config *config, xtc_backend **out)
{
CHECK(config->kind == XTC_BACKEND_JACK && config->channel_count == 1U);
CHECK(config->kind == XTC_BACKEND_JACK && config->channel_count == 2U);
++opens;
*out = open_result == XTC_OK || partial_open ? &handle : nullptr;
if (*out != nullptr) { ++alive; handle.running = 0; CHECK(handle.plan == nullptr); engine_status.active_revision = engine_status.submitted_revision = 0U; }
@ -328,6 +329,41 @@ int main(int argc, char **argv)
press(find(&window, "Panic"));
CHECK(xtc_midi_plan_render(handle.midi, 18032, nullptr, 0, instrument, 32) == XTC_OK && instrument[0] == 0);
engine_status.sample_position = 0;
// Mixer edits and automation are undoable; failed backend install retains cursor.
auto *mixer_ui = window.mixer(); CHECK(mixer_ui != nullptr);
xtc_track_info mixed{};
CHECK(mixer_ui->mix(0.5f, -1, 0, 1) == XTC_OK);
CHECK(xtc_project_get_track(sequence->project(), 0, &mixed) == XTC_OK && mixed.gain == 0.5f && mixed.pan == -1 && mixed.solo);
CHECK(mixer_ui->point(0, 0) == XTC_OK && mixer_ui->point(960, 1) == XTC_OK);
size_t points = 0;
CHECK(xtc_project_automation_count(sequence->project(), &points) == XTC_OK && points == 2);
CHECK(window.undo() == XTC_OK);
CHECK(xtc_project_automation_count(sequence->project(), &points) == XTC_OK && points == 1);
submit_result = XTC_ERR_CAPACITY;
CHECK(window.undo(true) == XTC_ERR_CAPACITY);
CHECK(xtc_project_automation_count(sequence->project(), &points) == XTC_OK && points == 1);
submit_result = XTC_OK;
CHECK(window.undo(true) == XTC_OK);
CHECK(xtc_project_automation_count(sequence->project(), &points) == XTC_OK && points == 2);
CHECK(window.undo() == XTC_OK && mixer_ui->point(1920, 0.25f) == XTC_OK && window.undo(true) == XTC_ERR_NOT_FOUND);
CHECK(window.save_project(filename.c_str()) == XTC_OK && window.open_project(filename.c_str()) == XTC_OK);
CHECK(xtc_project_get_track(sequence->project(), 0, &mixed) == XTC_OK && mixed.gain == 0.5f && mixed.pan == -1);
CHECK(xtc_project_automation_count(sequence->project(), &points) == XTC_OK && points == 2);
CHECK(window.undo() == XTC_ERR_NOT_FOUND); // Open resets history.
auto *lane = mixer_ui->child(mixer_ui->children() - 1);
saved_x = Fl::e_x; saved_y = Fl::e_y; saved_key = Fl::e_keysym;
Fl::e_x = lane->x() + (lane->w() - 1) / 4; Fl::e_y = lane->y() + lane->h() / 2;
Fl::e_keysym = FL_Button + FL_LEFT_MOUSE;
CHECK(lane->handle(FL_PUSH) == 1);
CHECK(xtc_project_automation_count(sequence->project(), &points) == XTC_OK && points == 3);
Fl::e_keysym = FL_Button + FL_RIGHT_MOUSE;
CHECK(lane->handle(FL_PUSH) == 1);
CHECK(xtc_project_automation_count(sequence->project(), &points) == XTC_OK && points == 2);
Fl::e_keysym = 'm'; CHECK(mixer_ui->handle(FL_KEYDOWN) == 1);
CHECK(xtc_project_get_track(sequence->project(), 0, &mixed) == XTC_OK && mixed.mute);
CHECK(window.undo() == XTC_OK);
CHECK(xtc_project_get_track(sequence->project(), 0, &mixed) == XTC_OK && !mixed.mute);
Fl::e_x = saved_x; Fl::e_y = saved_y; Fl::e_keysym = saved_key;
CHECK(unlink(filename.c_str()) == 0 && rmdir(directory) == 0);
engine_status.xruns = 3U;
engine_status.processed_blocks = 123U;