xtc/src/gui/sequencer_widget.hpp

226 lines
7.9 KiB
C++
Raw Normal View History

#ifndef XTC_SEQUENCER_WIDGET_HPP
#define XTC_SEQUENCER_WIDGET_HPP
#include <FL/Fl_Group.H>
#include <FL/Fl_Choice.H>
#include "xtc/xtc_plugin.h"
#include "xtc/xtc_editing.h"
#include <array>
#include "xtc/xtc_audio.h"
#include <memory>
#include <string>
#include <vector>
2026-10-27 01:37:21 +01:00
class SequencerWidget : public Fl_Group {
2026-10-27 01:37:21 +01:00
public:
/**
* @brief Prepares a model edit using the widget's opaque callback context.
*
* @return XTC_OK to allow the edit, or an error to cancel it.
*/
using LoopSelect = xtc_result (*)(void *,uint64_t,uint64_t);
LoopSelect loop_select = nullptr;
uint64_t new_clip_ticks = 3840;
int header_width() const {return header_width_;}
void header_width(int width);
int header_left() const;
int zoom_level() const { return scale_; }
xtc_result set_zoom(int scale);
void loop_region(uint64_t start,uint64_t end,bool enabled) { loop_begin_=start; loop_end_=end; loop_enabled_=enabled; redraw(); }
using OpenClip = void (*)(void *);
using InstrumentSelect = void (*)(void *, uint64_t, const xtc_plugin_spec *, bool);
OpenClip open_clip = nullptr;
InstrumentSelect instrument_select = nullptr;
Fl_Choice *instrument_choice(uint64_t track) const;
void refresh_instruments();
bool select(uint64_t track, uint64_t clip = UINT64_MAX);
xtc_result merge_midi();
xtc_result duplicate_track(const char *name);
void duplicate_track_dialog();
xtc_result ripple_time(uint64_t length,bool remove);
void ripple_dialog(bool remove);
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;
std::vector<uint64_t> selected_clips() const;
xtc_result transform_selection(xtc_clip_operation operation,int64_t amount);
xtc_result transform_midi(const xtc_midi_transform &edit);
void swing_dialog();
void gate_dialog();
void scale_dialog();
void velocity_dialog();
void humanize_dialog();
void fades_dialog();
xtc_result repeat_selection(unsigned int copies);
void repeat_dialog();
xtc_result copy_selection();
xtc_result paste_selection();
xtc_result nudge_selection(int direction);
xtc_result duplicate_selection();
xtc_result split_selection();
xtc_result audio_properties(float gain, uint64_t fade_in, uint64_t fade_out);
void audio_properties_dialog();
void refresh_waveforms();
size_t waveform_count() const { return waves_.size(); }
xtc_result rename_selection(const char *name);
xtc_result move_track(int direction);
xtc_result grid(uint64_t ticks);
uint64_t grid() const { return grid_; }
using BeforeEdit = xtc_result (*)(void *);
BeforeEdit before_edit = nullptr;
2026-10-27 01:37:21 +01:00
/**
* @brief Returns the selected track identifier, or UINT64_MAX when none is selected.
* @return Stable track identifier, or UINT64_MAX if no track is selected.
*/
2026-10-27 01:37:21 +01:00
uint64_t selected_track() const
{
return track_;
}
/**
* @brief Returns the selected clip identifier, or UINT64_MAX when none is selected.
* @return Stable clip identifier, or UINT64_MAX if no clip is selected.
*/
2026-10-27 01:37:21 +01:00
uint64_t selected_clip() const
{
return clip_;
}
/**
* @brief Requests a transport seek using an opaque context and musical tick.
*
* @return XTC_OK on success, or a transport error.
*/
using Seek = xtc_result (*)(void *, uint64_t);
/**
* @brief Constructs the timeline widget with a borrowed project and seek callback.
*
* @param x Left coordinate in pixels.
* @param y Top coordinate in pixels.
* @param project Borrowed project; must outlive the widget's use of it.
* @param seek Callback used to request a transport seek.
* @param context Opaque callback context.
*/
SequencerWidget(int x, int y, xtc_project *project, Seek seek, void *context);
/**
* @brief Replaces the borrowed project and refreshes the widget's displayed state.
*
* @param project Borrowed project; must outlive the widget's use of it.
* @param reset Whether to clear selection, cursor, and drag state.
*/
void set_project(xtc_project *project, bool reset = true);
2026-10-27 01:37:21 +01:00
/**
* @brief Returns the timeline cursor position in musical ticks.
* @return Cursor position in musical ticks.
*/
2026-10-27 01:37:21 +01:00
uint64_t position() const
{
return cursor_;
}
/**
* @brief Moves the timeline cursor to the supplied musical tick and requests a redraw.
*
* @param tick Musical position in ticks.
*/
2026-10-27 01:37:21 +01:00
void position(uint64_t tick)
{
cursor_ = tick;
redraw();
}
/**
* @brief Creates and selects a track through the arrangement edit hook.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result add_track();
/**
* @brief Creates and selects a one-bar silent region at the timeline cursor.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result add_region();
/**
* @brief Creates and selects a one-bar MIDI clip at the timeline cursor.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result add_midi_clip();
/**
* @brief Removes the selected clip, or the selected track when no clip is selected.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result delete_selection();
/**
* @brief Adjusts timeline zoom within its supported bounds and updates the scroll extent.
*
* @param direction Zoom direction; positive zooms in, nonpositive zooms out.
*/
void zoom(int direction);
2026-10-27 01:37:21 +01:00
/**
* @brief Returns the borrowed project currently displayed by the widget.
* @return Borrowed project pointer.
*/
2026-10-27 01:37:21 +01:00
xtc_project *project() const
{
return project_;
}
/**
* @brief Handles FLTK input events and delegates unhandled events to the base widget.
*
* @param event FLTK event code.
* @return Nonzero if the event was handled, zero otherwise.
*/
int handle(int event) override;
2026-10-27 01:37:21 +01:00
protected:
/**
* @brief Draws the widget's current model, selection, and interaction feedback.
*/
void draw() override;
2026-10-27 01:37:21 +01:00
private:
struct TrackChoice { Fl_Choice *widget = nullptr; uint64_t track = UINT64_MAX; };
std::array<TrackChoice,XTC_PROJECT_MAX_TRACKS> track_choices_{};
std::vector<xtc_plugin_spec> catalog_;
static void instrument_callback(Fl_Widget *, void *);
struct Wave {
std::string path;
std::unique_ptr<xtc_waveform, decltype(&xtc_waveform_destroy)> data{nullptr, xtc_waveform_destroy};
};
std::vector<Wave> waves_;
std::vector<uint64_t> selection_;
std::unique_ptr<xtc_clipboard,decltype(&xtc_clipboard_destroy)> clipboard_{nullptr,xtc_clipboard_destroy};
void draw_waveform(const xtc_clip_info &clip, int left, int top);
xtc_project *project_;
Seek seek_;
void *context_;
uint64_t track_ = UINT64_MAX, clip_ = UINT64_MAX, cursor_ = 0, grid_ = 240;
uint64_t loop_begin_=0,loop_end_=3840,loop_anchor_=0,loop_preview_=0;
bool loop_enabled_=false;
int scale_ = 48, drag_x_ = 0, mode_ = 0;
int header_width_=180,header_drag_x_=0,header_drag_original_=180;
xtc_clip_info original_{};
/**
* @brief Converts a screen x coordinate to a bounded timeline tick snapped to the selected grid.
*
* @param px Screen x coordinate in pixels.
* @return Snapped tick within the supported project range.
*/
uint64_t tick(int px) const;
/**
* @brief Reports an edit result, refreshes the widget, and notifies listeners on success.
*
* @param result Result of the attempted operation.
*/
void changed(xtc_result result);
/**
* @brief Resizes the timeline's scrollable area to fit its tracks and clip ends.
*/
void extent();
};
#endif