xtc/src/gui/sequencer_widget.cpp

292 lines
9.4 KiB
C++
Raw Normal View History

#include "sequencer_widget.hpp"
#include "xtc/xtc_midi.h"
#include <FL/Fl.H>
#include <FL/Fl_Scroll.H>
#include <FL/fl_draw.H>
#include <FL/fl_ask.H>
#include <algorithm>
#include <cstdio>
2026-10-27 01:37:21 +01:00
SequencerWidget::SequencerWidget(int left, int top, xtc_project *project, Seek seek, void *context)
2026-10-27 01:37:21 +01:00
: Fl_Widget(left, top, 6300, 3700), project_(project), seek_(seek), context_(context)
{
extent();
}
void SequencerWidget::set_project(xtc_project *project, bool reset)
{
project_ = project;
2026-10-27 01:37:21 +01:00
if (reset) {
track_ = clip_ = UINT64_MAX;
cursor_ = 0;
mode_ = 0;
}
extent();
redraw();
}
2026-10-27 01:37:21 +01:00
uint64_t SequencerWidget::tick(int px) const
{
return std::min(XTC_PROJECT_MAX_TICK, static_cast<uint64_t>(std::max(0, px - x() - 120)) * 960U /
2026-10-27 01:37:21 +01:00
static_cast<unsigned int>(scale_) / 240U * 240U);
}
2026-10-27 01:37:21 +01:00
void SequencerWidget::extent()
{
size_t tracks, clips;
xtc_project_get_counts(project_, &tracks, &clips);
uint64_t end = 32U * 3840U;
for (size_t i = 0; i < clips; ++i) {
2026-10-27 01:37:21 +01:00
xtc_clip_info c{};
xtc_project_get_clip(project_, i, &c);
end = std::max(end, c.start_tick + c.length_tick + 3840U);
}
size(static_cast<int>(end * static_cast<unsigned int>(scale_) / 960U + 140U),
std::max(400, static_cast<int>(tracks) * 56 + 100));
2026-10-27 01:37:21 +01:00
if (parent())
parent()->redraw();
}
2026-10-27 01:37:21 +01:00
void SequencerWidget::changed(xtc_result result)
{
2026-10-27 01:37:21 +01:00
if (result != XTC_OK)
fl_alert("%s", xtc_result_string(result));
else {
extent();
do_callback();
}
redraw();
}
2026-10-27 01:37:21 +01:00
xtc_result SequencerWidget::add_track()
{
2026-10-27 01:37:21 +01:00
if (before_edit) {
xtc_result r = before_edit(context_);
if (r != XTC_OK) {
changed(r);
return r;
}
}
uint64_t id;
xtc_result r = xtc_project_add_track(project_, "Track", &id);
if (r == XTC_OK) {
track_ = id;
clip_ = UINT64_MAX;
}
changed(r);
return r;
}
2026-10-27 01:37:21 +01:00
xtc_result SequencerWidget::add_region()
{
2026-10-27 01:37:21 +01:00
if (before_edit) {
xtc_result r = before_edit(context_);
if (r != XTC_OK) {
changed(r);
return r;
}
}
size_t tracks, clips;
xtc_project_get_counts(project_, &tracks, &clips);
if (track_ == UINT64_MAX && tracks) {
2026-10-27 01:37:21 +01:00
xtc_track_info t{};
xtc_project_get_track(project_, 0, &t);
track_ = t.id;
}
uint64_t id;
xtc_result r = xtc_project_add_clip(project_, track_, "Region (silent)", cursor_, 3840U, &id);
2026-10-27 01:37:21 +01:00
if (r == XTC_OK)
clip_ = id;
changed(r);
return r;
}
2026-10-27 01:37:21 +01:00
xtc_result SequencerWidget::add_midi_clip()
{
xtc_result r = before_edit ? before_edit(context_) : XTC_OK;
uint64_t id;
if (r == XTC_OK) {
if (track_ == UINT64_MAX) {
xtc_track_info t{};
2026-10-27 01:37:21 +01:00
r = xtc_project_get_track(project_, 0, &t);
track_ = r == XTC_OK ? t.id : UINT64_MAX;
}
2026-10-27 01:37:21 +01:00
if (r == XTC_OK)
r = xtc_project_add_midi_clip(project_, track_, cursor_, 3840, &id);
if (r == XTC_OK)
clip_ = id;
}
2026-10-27 01:37:21 +01:00
changed(r);
return r;
}
2026-10-27 01:37:21 +01:00
xtc_result SequencerWidget::delete_selection()
{
2026-10-27 01:37:21 +01:00
if (before_edit) {
xtc_result r = before_edit(context_);
if (r != XTC_OK) {
changed(r);
return r;
}
}
xtc_result r =
clip_ != UINT64_MAX ? xtc_project_remove_clip(project_, clip_) : xtc_project_remove_track(project_, track_);
if (r == XTC_OK)
clip_ = track_ = UINT64_MAX;
changed(r);
return r;
}
2026-10-27 01:37:21 +01:00
void SequencerWidget::zoom(int direction)
2026-10-27 01:37:21 +01:00
{
scale_ = std::clamp(direction > 0 ? scale_ * 2 : scale_ / 2, 24, 384);
extent();
redraw();
}
void SequencerWidget::draw()
{
fl_push_clip(x(), y(), w(), h());
2026-10-27 01:37:21 +01:00
fl_color(fl_rgb_color(35, 38, 43));
fl_rectf(x(), y(), w(), h());
int cx, cy, cw, ch;
fl_clip_box(x(), y(), w(), h(), cx, cy, cw, ch);
fl_font(FL_HELVETICA, 12);
int first = std::max(0, (cx - x() - 120) / (scale_ * 4));
int last = (cx + cw - x() - 120) / (scale_ * 4) + 1;
for (int bar = first; bar <= last; ++bar) {
int px = x() + 120 + bar * scale_ * 4;
2026-10-27 01:37:21 +01:00
fl_color(fl_rgb_color(65, 68, 74));
fl_line(px, y(), px, y() + h());
char label[32];
std::snprintf(label, sizeof(label), "%d", bar + 1);
fl_color(FL_WHITE);
fl_draw(label, px + 4, y() + 19);
}
char tempo[32];
std::snprintf(tempo, sizeof(tempo), "%.3g BPM | 4/4", xtc_project_tempo(project_) / 1000.0);
2026-10-27 01:37:21 +01:00
fl_color(FL_WHITE);
fl_draw(tempo, x() + 4, y() + 19);
size_t tracks, clips;
xtc_project_get_counts(project_, &tracks, &clips);
for (size_t i = 0; i < tracks; ++i) {
2026-10-27 01:37:21 +01:00
xtc_track_info t{};
xtc_project_get_track(project_, i, &t);
int py = y() + 28 + static_cast<int>(i) * 56;
fl_color(t.id == track_ ? fl_rgb_color(70, 90, 110) : fl_rgb_color(48, 51, 57));
fl_rectf(x(), py, 118, 55);
2026-10-27 01:37:21 +01:00
fl_color(FL_WHITE);
fl_draw(t.name, x() + 6, py + 32);
for (size_t j = 0; j < clips; ++j) {
2026-10-27 01:37:21 +01:00
xtc_clip_info c{};
xtc_project_get_clip(project_, j, &c);
if (c.track_id != t.id)
continue;
int left = x() + 120 + static_cast<int>(c.start_tick * static_cast<unsigned int>(scale_) / 960U);
int right = left + std::max(4, static_cast<int>(c.length_tick * static_cast<unsigned int>(scale_) / 960U));
int a = std::max(left, cx), b = std::min(right, cx + cw);
2026-10-27 01:37:21 +01:00
if (b <= a || py + 50 < cy || py > cy + ch)
continue;
fl_color(c.id == clip_ ? fl_rgb_color(100, 170, 205) : fl_rgb_color(65, 120, 150));
fl_rectf(a, py + 5, b - a, 44);
2026-10-27 01:37:21 +01:00
fl_push_clip(a, py + 5, b - a, 44);
fl_color(FL_WHITE);
fl_draw(c.midi ? "MIDI" : c.asset_path[0] ? "Audio" : c.name, std::max(a, left + 6), py + 31);
fl_pop_clip();
}
}
int playhead = x() + 120 + static_cast<int>(cursor_ * static_cast<unsigned int>(scale_) / 960U);
if (playhead >= cx && playhead <= cx + cw) {
2026-10-27 01:37:21 +01:00
fl_color(FL_RED);
fl_line(playhead, y(), playhead, y() + h());
}
fl_pop_clip();
}
2026-10-27 01:37:21 +01:00
int SequencerWidget::handle(int event)
{
if (event == FL_PUSH && Fl::event_button() == FL_LEFT_MOUSE) {
2026-10-27 01:37:21 +01:00
take_focus();
mode_ = 0;
if (Fl::event_y() < y() + 28) {
uint64_t target = tick(Fl::event_x());
xtc_result r = seek_(context_, target);
2026-10-27 01:37:21 +01:00
if (r == XTC_OK)
cursor_ = target;
else
fl_alert("Stop playback before seeking: %s", xtc_result_string(r));
redraw();
return 1;
}
2026-10-27 01:37:21 +01:00
size_t tracks, clips;
xtc_project_get_counts(project_, &tracks, &clips);
int row = (Fl::event_y() - y() - 28) / 56;
2026-10-27 01:37:21 +01:00
if (row < 0 || static_cast<size_t>(row) >= tracks)
return 1;
xtc_track_info t{};
xtc_project_get_track(project_, static_cast<size_t>(row), &t);
track_ = t.id;
clip_ = UINT64_MAX;
for (size_t j = clips; j > 0; --j) {
2026-10-27 01:37:21 +01:00
xtc_clip_info c{};
xtc_project_get_clip(project_, j - 1, &c);
int left = x() + 120 + static_cast<int>(c.start_tick * static_cast<unsigned int>(scale_) / 960U);
int right = left + static_cast<int>(c.length_tick * static_cast<unsigned int>(scale_) / 960U);
if (c.track_id == track_ && Fl::event_x() >= left && Fl::event_x() < right) {
2026-10-27 01:37:21 +01:00
clip_ = c.id;
original_ = c;
drag_x_ = Fl::event_x();
mode_ = Fl::event_x() - left < 7 ? 2 : right - Fl::event_x() < 7 ? 3 : 1;
break;
}
}
2026-10-27 01:37:21 +01:00
redraw();
return 1;
}
2026-10-27 01:37:21 +01:00
if (event == FL_DRAG)
return mode_ != 0;
if (event == FL_RELEASE && mode_) {
2026-10-27 01:37:21 +01:00
int mode = mode_;
mode_ = 0;
int64_t delta = static_cast<int64_t>(Fl::event_x() - drag_x_) * 960 / scale_ / 240 * 240;
int64_t start = static_cast<int64_t>(original_.start_tick);
int64_t length = static_cast<int64_t>(original_.length_tick);
int64_t offset = static_cast<int64_t>(original_.offset_tick);
uint64_t target = original_.track_id;
if (mode == 1) {
start = std::max<int64_t>(0, start + delta);
int row = (Fl::event_y() - y() - 28) / 56;
xtc_track_info t{};
2026-10-27 01:37:21 +01:00
if (row >= 0 && xtc_project_get_track(project_, static_cast<size_t>(row), &t) == XTC_OK)
target = t.id;
} else if (mode == 2) {
start += delta;
offset += delta;
length -= delta;
} else
length += delta;
if (start < 0 || offset < 0 || length <= 0)
return 1;
if (before_edit) {
xtc_result r = before_edit(context_);
if (r != XTC_OK) {
changed(r);
return 1;
}
}
changed(xtc_project_edit_clip(project_, clip_, target, static_cast<uint64_t>(start),
2026-10-27 01:37:21 +01:00
static_cast<uint64_t>(length), static_cast<uint64_t>(offset)));
return 1;
}
2026-10-27 01:37:21 +01:00
if (event == FL_KEYDOWN && Fl::event_key() == FL_Escape) {
mode_ = 0;
return 1;
}
if (event == FL_KEYDOWN && Fl::event_key() == FL_Delete) {
delete_selection();
return 1;
}
if (event == FL_FOCUS || event == FL_UNFOCUS)
return 1;
return Fl_Widget::handle(event);
}