#ifndef XTC_MIXER_WIDGET_HPP #define XTC_MIXER_WIDGET_HPP #include "xtc/xtc_mixer.h" #include "xtc/xtc_backend.h" #include #include #include class Fl_Choice; class Fl_Button; class Fl_Value_Slider; class Fl_Check_Button; class Fl_Progress; class Fl_Value_Input; class AutomationLane; class MixerStrip; class Fl_Scroll; class Fl_Box; class MixerWidget : public Fl_Group { 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 BeforeEdit = xtc_result (*)(void *); /** * @brief Creates the mixer panel and its child controls. * * Builds the track chooser, channel strips, pan knobs, mute/solo buttons, output * meters, automation controls, and lane, then displays the given project. * * @param x Left edge in parent coordinates. * @param y Top edge in parent coordinates. * @param w Widget width in pixels. * @param h Widget height in pixels. * @param p Project to display; borrowed and must outlive its use. * @param before Callback invoked before each edit; may be nullptr. * @param context Opaque pointer forwarded to before. */ MixerWidget(int x, int y, int w, int h, xtc_project * p, BeforeEdit before, void * context); /** * @brief Displays a different project and refreshes the controls. * * The current track selection is preserved only when the same project * pointer is passed again. The project is borrowed and must outlive use. * * @param p Project to display; must remain valid while shown. */ void set_project(xtc_project * p); /** * @brief Updates the left and right output meters. * * Meter values are clamped to 1.0, while the over-range color is chosen * from the unclamped level. * * @param left Left-channel peak level. * @param right Right-channel peak level. */ void peaks(float left, float right); /** * @brief Returns the ID of the track selected in the track chooser. * * @return Selected track ID, or UINT64_MAX when the chooser selection does * not resolve to a project track. */ uint64_t selected_track() const; /** * @brief Returns the project currently displayed by the widget. * * @return Borrowed project pointer, possibly nullptr; the widget does not * own it. */ xtc_project *project() const { return project_; } /** * @brief Returns the automation parameter selected in the chooser. * * @return XTC_AUTOMATION_PAN when the chooser selects "Pan", otherwise * XTC_AUTOMATION_GAIN. */ xtc_automation_parameter parameter() const; /** * @brief Returns the first tick shown by the automation lane. * * @return Clamped first tick in project ticks, or 0 when the input value is * not finite. */ uint64_t lane_start() const; /** * @brief Adds, replaces, or removes an automation point. * * The constructor's before-edit callback runs first; the edit is skipped * if it returns an error. On success the widget callback is invoked and * the controls are refreshed. * * @param tick Automation tick to modify; must be within the project range. * @param value New value; ignored when remove is true. * @param remove When true, removes the point instead of setting it. * @return XTC_OK on success, or the before-edit or project mutation error. */ xtc_result point(uint64_t tick, float value, bool remove = false); /** * @brief Applies gain, pan, mute, and solo to the selected track. * * The constructor's before-edit callback runs first; the edit is skipped * if it returns an error. On success the widget callback is invoked and * the controls are refreshed. * * @param gain Linear gain in the range 0..4. * @param pan Pan position in the range -1 (left)..1 (right). * @param mute Mute state: 0 or 1. * @param solo Solo state: 0 or 1. * @return XTC_OK on success, or the before-edit or project mutation error. */ xtc_result add_bus(); xtc_result route(uint64_t bus); bool select_track(uint64_t id); void track_peaks(float left, float right); /* One control-thread snapshot updates all strips; missing taps decay to silence. */ void meters(const xtc_meter_snapshot &snapshot); Fl_Group *strip(uint64_t id) const; size_t strip_count() const { return strips_.size(); } void resize(int x, int y, int w, int h) override; void reset_meters(); bool master_clipped() const { return master_[0].clipped || master_[1].clipped; } xtc_result mix(float gain, float pan, int mute, int solo); /** * @brief Handles keyboard shortcuts and forwards other events. * * M and S toggle mute and solo, Left/Right adjust gain (Shift adjusts pan), * and Up/Down change the selected track. Modified key presses fall through. * * @param event FLTK event code. * @return 1 when the event was consumed, otherwise the Fl_Group result. */ int handle(int event) override; private: xtc_project *project_; BeforeEdit before_; void *context_; Fl_Choice *tracks_, *parameter_, *routes_, *mode_; std::vector route_ids_; Fl_Progress *left_, *right_; Fl_Scroll *bank_; Fl_Group *master_panel_; Fl_Button *bus_, *reset_; Fl_Box *hint_, *master_readout_; std::vector strips_; void sync_strips(bool reset = false); void layout(); xtc_peak_meter master_[2]{}; uint64_t selected_id_ = UINT64_MAX; std::chrono::steady_clock::time_point peak_time_ = std::chrono::steady_clock::now(); double meter_elapsed_ = 0.1; void display_peaks(Fl_Progress *left, Fl_Progress *right, xtc_peak_meter *meters, const char *prefix); Fl_Value_Input *start_; AutomationLane *lane_; /** * @brief Reports an edit result and refreshes the controls. * * A successful result invokes the widget callback so the owner can mark * the project dirty; a failure shows a modal alert. The controls are * resynchronized in both cases. * * @param r Result of the attempted edit. * @return The same result passed in. */ xtc_result changed(xtc_result r); /** * @brief Synchronizes the child controls with the selected track. * * Reconcile strip controls with stable model IDs and the selected * automation target. */ void selection(); /** * @brief FLTK callback for the track, parameter, and first-tick controls. * * @param widget Track chooser or automation control that changed. * @param c Opaque MixerWidget pointer supplied as callback context. */ static void select(Fl_Widget * widget, void * c); }; #endif