Phase 7: Add MIDI clips, instrument and piano roll

This commit is contained in:
Johannes Findeisen 2026-10-27 00:53:41 +01:00
commit cacc086805
38 changed files with 1373 additions and 74 deletions

View file

@ -1,6 +1,7 @@
#include "../../src/gui/main_window.hpp"
#include "../../src/gui/graph_widget.hpp"
#include "../../src/gui/sequencer_widget.hpp"
#include "../../src/gui/piano_roll.hpp"
#include <unistd.h>
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
@ -16,7 +17,7 @@
/* Fake only the public C backend boundary. Real FLTK widgets, callbacks and
* timers run under Xvfb; no server/device is required or touched. */
struct xtc_backend { int running = 0; xtc_plan *plan = nullptr; xtc_audio_plan *audio = nullptr; xtc_recorder *recorder = nullptr; };
struct xtc_backend { int running = 0; xtc_plan *plan = nullptr; xtc_audio_plan *audio = nullptr; xtc_midi_plan *midi = nullptr; xtc_recorder *recorder = nullptr; };
namespace {
xtc_backend handle;
xtc_result open_result = XTC_OK, start_result = XTC_OK, stop_result = XTC_OK, close_result = XTC_OK;
@ -59,7 +60,7 @@ SequencerWidget *timeline(Fl_Group *group)
}
return nullptr;
}
void gesture(SequencerWidget *s, int left, int top, int dx, int dy)
void gesture(Fl_Widget *s, int left, int top, int dx, int dy)
{
int old_x = Fl::e_x, old_y = Fl::e_y, old_key = Fl::e_keysym;
Fl::e_x = s->x() + left; Fl::e_y = s->y() + top; Fl::e_keysym = FL_Button + FL_LEFT_MOUSE;
@ -98,7 +99,7 @@ xtc_result xtc_backend_close(xtc_backend *backend)
{
if (backend == nullptr) return XTC_OK;
++closes;
if (close_result == XTC_OK) { CHECK(alive == 1U); --alive; handle.running = 0; xtc_plan_destroy(handle.plan); handle.plan = nullptr; xtc_audio_plan_destroy(handle.audio); handle.audio = nullptr; }
if (close_result == XTC_OK) { CHECK(alive == 1U); --alive; handle.running = 0; xtc_plan_destroy(handle.plan); handle.plan = nullptr; xtc_audio_plan_destroy(handle.audio); handle.audio = nullptr; xtc_midi_plan_destroy(handle.midi); handle.midi = nullptr; }
return close_result;
}
xtc_result xtc_backend_start(xtc_backend *backend)
@ -126,13 +127,17 @@ xtc_result xtc_backend_submit(xtc_backend *backend, const xtc_graph *graph)
++engine_status.submitted_revision;
return XTC_OK;
}
xtc_result xtc_backend_panic(xtc_backend *) { xtc_midi_plan_panic(handle.midi, engine_status.sample_position); return XTC_OK; }
xtc_result xtc_backend_prepare_project(xtc_backend *backend, const xtc_project *project)
{
xtc_audio_plan *plan = nullptr;
xtc_midi_plan *midi = nullptr;
xtc_result r = xtc_audio_plan_create(project, 48000, &plan);
if (r == XTC_OK) r = xtc_midi_plan_create(project, 48000, &midi);
if (r == XTC_OK) r = xtc_backend_submit(backend, xtc_project_graph(project));
if (r != XTC_OK) { xtc_audio_plan_destroy(plan); return r; }
if (r != XTC_OK) { xtc_audio_plan_destroy(plan); xtc_midi_plan_destroy(midi); return r; }
xtc_audio_plan_destroy(handle.audio); handle.audio = plan;
xtc_midi_plan_destroy(handle.midi); handle.midi = midi;
engine_status.asset_issues = static_cast<unsigned int>(xtc_audio_plan_issues(plan, nullptr));
return XTC_OK;
}
@ -300,6 +305,29 @@ int main(int argc, char **argv)
CHECK(unlink(take.c_str()) == 0);
CHECK(window.open_project(filename.c_str()) == XTC_OK && contains(&window, "Missing/unreadable audio"));
CHECK(window.import_audio("/missing/xtc-audio.wav") == XTC_ERR_IO);
press(find(&window, "Add MIDI clip"));
CHECK(window.open_piano_roll() == XTC_OK);
auto *roll = window.piano_roll(); CHECK(roll != nullptr);
int saved_x = Fl::e_x, saved_y = Fl::e_y, saved_key = Fl::e_keysym;
Fl::e_x = roll->x() + 112; Fl::e_y = roll->y() + 1110; Fl::e_keysym = FL_Button + FL_LEFT_MOUSE;
CHECK(roll->handle(FL_PUSH) == 1); (void)roll->handle(FL_RELEASE);
Fl::e_x = saved_x; Fl::e_y = saved_y; Fl::e_keysym = saved_key;
gesture(roll, 130, 1110, 24, -16); // Move one sixteenth and a semitone.
gesture(roll, 230, 1094, 48, 0); // Extend right edge by half a beat.
xtc_note_info drawn{}; size_t note_count;
CHECK(xtc_project_note_count(sequence->project(), &note_count) == XTC_OK && note_count == 1);
CHECK(xtc_project_get_note(sequence->project(), 0, &drawn) == XTC_OK &&
drawn.start_tick == 720 && drawn.length_tick == 1440 && drawn.pitch == 61);
CHECK(window.save_project(filename.c_str()) == XTC_OK);
CHECK(window.open_project(filename.c_str()) == XTC_OK);
CHECK(xtc_project_get_note(sequence->project(), 0, &drawn) == XTC_OK && drawn.pitch == 61 && drawn.start_tick == 720);
float instrument[32], heard[32];
CHECK(xtc_midi_plan_render(handle.midi, 18000, nullptr, 0, instrument, 32) == XTC_OK && instrument[1] > 0);
CHECK(xtc_plan_process_sources(handle.plan, nullptr, nullptr, instrument, heard, 32) == XTC_OK && heard[1] == instrument[1]);
engine_status.sample_position = 18032;
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;
CHECK(unlink(filename.c_str()) == 0 && rmdir(directory) == 0);
engine_status.xruns = 3U;
engine_status.processed_blocks = 123U;