feat(metering): show live stereo peaks beside track inserts (phase 96)
This commit is contained in:
parent
924821738d
commit
7ad85b6144
6 changed files with 75 additions and 6 deletions
|
|
@ -52,3 +52,12 @@ one undoable edit. Enabling all intentionally enables every slot, including
|
|||
previously bypassed effects; Undo restores the previous mixed bypass state.
|
||||
Clear removes slots while preserving chain output gain. No plugin resources
|
||||
are released in the audio callback. Empty racks return a no-result error.
|
||||
|
||||
## Meter while editing effects
|
||||
|
||||
The insert workspace shows the selected track's stereo sample peaks after its
|
||||
fader and before its bus, including the effects' output. Green bars release at
|
||||
20 dB/s. A sticky red CLIP appears at unity or above; click the display to clear
|
||||
it. Stopped, offline or missing-track readings show the -90 dBFS floor. Changing
|
||||
tracks clears the indicator. These are sample peaks, not true-peak or loudness
|
||||
measurements. The display shares the mixer's existing backend snapshot read.
|
||||
|
|
|
|||
|
|
@ -2148,12 +2148,12 @@ Implemented: bounded C99 enable-all, bypass-all and clear transforms preserve ou
|
|||
Display selected-track stereo sample peaks and a resettable clip indicator from the existing single backend meter reader, with clear post-fader labeling and offline/stopped handling.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [ ] Implement the workflow with bounded validation, safe preparation and documented limits.
|
||||
- [ ] Pass meaningful core and/or GUI checks, including error and undo behavior.
|
||||
- [ ] Document results and commit this phase separately.
|
||||
- [x] Implement the workflow with bounded validation, safe preparation and documented limits.
|
||||
- [x] Pass meaningful core and/or GUI checks, including error and undo behavior.
|
||||
- [x] Document results and commit this phase separately.
|
||||
|
||||
### Implementation status
|
||||
Planned.
|
||||
Implemented: UI-only stereo peak display in the insert workspace shares the existing backend meter snapshot, with post-fader/pre-bus labels, 20 dB/s release and click-to-clear sticky clipping. Stopped/missing/offline readings show the floor and track changes reset history. GUI dB conversion, clipping, stopped/reset and selection checks passed; no extra backend reader was added.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
45
src/gui/insert_meter.hpp
Normal file
45
src/gui/insert_meter.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#ifndef XTC_INSERT_METER_HPP
|
||||
#define XTC_INSERT_METER_HPP
|
||||
#include "xtc/xtc_backend.h"
|
||||
#include "xtc/xtc_mixer.h"
|
||||
#include "theme.hpp"
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
/* UI-only display fed by the existing single backend snapshot reader. */
|
||||
class InsertMeter : public Fl_Button {
|
||||
xtc_peak_meter left_{-90,0},right_{-90,0};
|
||||
uint64_t track_=UINT64_MAX;
|
||||
public:
|
||||
InsertMeter(int x,int y,int w,int h):Fl_Button(x,y,w,h) {
|
||||
tooltip("Post-track-fader, pre-bus stereo sample peaks. Click to clear the sticky clip indicator.");
|
||||
callback([](Fl_Widget *w,void *){static_cast<InsertMeter *>(w)->clear_clips();});
|
||||
}
|
||||
void clear_clips(){left_.clipped=right_.clipped=0;redraw();}
|
||||
float left_db() const {return left_.db;}
|
||||
float right_db() const {return right_.db;}
|
||||
bool clipped() const {return left_.clipped || right_.clipped;}
|
||||
void feed(const xtc_meter_snapshot &snapshot,uint64_t track,bool active,double seconds) {
|
||||
if(track_!=track){track_=track;xtc_peak_meter_reset(&left_);xtc_peak_meter_reset(&right_);}
|
||||
bool found=false;float l=0,r=0;
|
||||
for(size_t i=0;i<snapshot.count && i<XTC_PROJECT_MAX_TRACKS;++i)if(snapshot.tracks[i].id==track){l=snapshot.tracks[i].left;r=snapshot.tracks[i].right;found=true;break;}
|
||||
if(!active || !found){left_.db=right_.db=-90;}
|
||||
else {
|
||||
(void)xtc_peak_meter_update(&left_,std::isfinite(l) && l>=0 ? l : 0,seconds);
|
||||
(void)xtc_peak_meter_update(&right_,std::isfinite(r) && r>=0 ? r : 0,seconds);
|
||||
}
|
||||
redraw();
|
||||
}
|
||||
void draw() override {
|
||||
draw_box();char text[120];std::snprintf(text,sizeof(text),"Post-fader L %.1f R %.1f dBFS%s",static_cast<double>(left_.db),static_cast<double>(right_.db),clipped() ? " CLIP" : "");
|
||||
fl_font(FL_HELVETICA,11);fl_color(clipped() ? fl_rgb_color(245,132,142) : xtc_ui::muted());fl_draw(text,x()+8,y()+12);
|
||||
int width=std::max(0,(w()-24)/2);
|
||||
for(unsigned int side=0;side<2;++side) {
|
||||
int left=x()+8+static_cast<int>(side)*(width+8);float db=side ? right_.db : left_.db;
|
||||
fl_color(xtc_ui::background());fl_rectf(left,y()+h()-7,width,4);
|
||||
fl_color(clipped() ? fl_rgb_color(245,132,142) : xtc_ui::accent());
|
||||
fl_rectf(left,y()+h()-7,static_cast<int>(static_cast<float>(width)*std::min(1.f,std::max(0.f,(db+90)/90))),4);
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
|
@ -1012,6 +1012,7 @@ void MainWindow::refresh_status()
|
|||
refresh_workspace();
|
||||
update_history_controls();
|
||||
if (mixer_ && !ready_) mixer_->meters(xtc_meter_snapshot{});
|
||||
if(track_inserts_ && !ready_)track_inserts_->meters(xtc_meter_snapshot{},false);
|
||||
bool can_play = false, can_record = false;
|
||||
if (!ready_) {
|
||||
set_label(indicator_, "Audio: disconnected");
|
||||
|
|
@ -1024,6 +1025,7 @@ void MainWindow::refresh_status()
|
|||
xtc_result result = xtc_backend_get_status(backend_, &status);
|
||||
if (result != XTC_OK) {
|
||||
report_error("Read engine status failed", result);
|
||||
if(track_inserts_)track_inserts_->meters(xtc_meter_snapshot{},false);
|
||||
set_label(indicator_, "Audio: status unavailable");
|
||||
set_label(graph_state_, "Graph: engine status unavailable");
|
||||
set_label(status_, "Unable to read JACK status — use Reconnect");
|
||||
|
|
@ -1050,6 +1052,7 @@ void MainWindow::refresh_status()
|
|||
xtc_meter_snapshot meters{};
|
||||
if (xtc_backend_read_meters(backend_, &meters) == XTC_OK) mixer_->meters(meters);
|
||||
else { meters.master_left=status.peak_left;meters.master_right=status.peak_right;mixer_->meters(meters); }
|
||||
if(track_inserts_)track_inserts_->meters(meters,running_ && !fault,static_cast<double>(settings_.refresh_ms)/1000);
|
||||
char text[256];
|
||||
std::snprintf(
|
||||
text, sizeof(text),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "plugin_library.hpp"
|
||||
#include "plugin_dialog.hpp"
|
||||
#include "sequencer_widget.hpp"
|
||||
#include "insert_meter.hpp"
|
||||
#include <FL/Fl_Choice.H>
|
||||
#include <FL/Fl_Menu_Button.H>
|
||||
#include <FL/Fl_Hold_Browser.H>
|
||||
|
|
@ -18,13 +19,14 @@ class TrackInsertsPanel : public Fl_Group {
|
|||
MainWindow *owner_;
|
||||
Fl_Choice *tracks_;
|
||||
Fl_Menu_Button *actions_;
|
||||
InsertMeter *meter_;
|
||||
Fl_Hold_Browser *slots_;
|
||||
Fl_Box *message_;
|
||||
Fl_Value_Input *wet_,*gain_;
|
||||
GraphWidget *graph_;
|
||||
Fl_Scroll *scroll_;
|
||||
xtc_graph *model_=nullptr;
|
||||
uint64_t track_=UINT64_MAX;
|
||||
uint64_t track_=UINT64_MAX,meter_track_=UINT64_MAX;
|
||||
xtc_track_chain cached_{};
|
||||
xtc_track_chain clipboard_{};
|
||||
bool clipboard_ready_=false;
|
||||
|
|
@ -68,6 +70,7 @@ class TrackInsertsPanel : public Fl_Group {
|
|||
actions_->add("Copy chain",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->copy_chain());},this);
|
||||
actions_->add("Paste chain (replace)",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->paste_chain());},this,FL_MENU_INACTIVE);
|
||||
actions_->add("Duplicate effect",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->duplicate_slot());},this);
|
||||
meter_=new InsertMeter(x+650,y+52,w-666,26);
|
||||
message_=new Fl_Box(x+16,y+84,w-32,30,"Track input > inserts > output gain > track fader > bus/master");message_->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE);
|
||||
slots_=new Fl_Hold_Browser(x+16,y+126,230,h-142);slots_->format_char(0);slots_->callback([](Fl_Widget *,void *c){static_cast<TrackInsertsPanel *>(c)->update_mix();},this);
|
||||
scroll_=new Fl_Scroll(x+260,y+126,w-276,h-142);
|
||||
|
|
@ -91,7 +94,9 @@ class TrackInsertsPanel : public Fl_Group {
|
|||
if(r==XTC_OK)r=owner_->set_track_chain(track_,chain);
|
||||
return r;
|
||||
}
|
||||
void clear_session() {clipboard_={};clipboard_ready_=false;for(auto &slot:comparisons_)slot=Comparison{};update_actions();}
|
||||
InsertMeter *meter() const {return meter_;}
|
||||
void meters(const xtc_meter_snapshot &snapshot,bool active,double seconds=.02) {meter_->feed(snapshot,track_,active,seconds);}
|
||||
void clear_session() {meter_->feed(xtc_meter_snapshot{},UINT64_MAX,false,.02);clipboard_={};clipboard_ready_=false;for(auto &slot:comparisons_)slot=Comparison{};update_actions();}
|
||||
bool clipboard_ready() const {return clipboard_ready_;}
|
||||
bool comparison_ready(unsigned int slot) const {return slot<2 && comparisons_[slot].ready && comparisons_[slot].track==track_;}
|
||||
xtc_result comparison(unsigned int slot,bool capture) {
|
||||
|
|
@ -184,6 +189,7 @@ class TrackInsertsPanel : public Fl_Group {
|
|||
if(std::find(ids_.begin(),ids_.end(),track_)==ids_.end())track_=ids_.empty() ? UINT64_MAX : ids_[0];
|
||||
for(size_t i=0;i<ids_.size();++i)if(ids_[i]==track_)tracks_->value(static_cast<int>(i));
|
||||
update_actions();
|
||||
if(track_!=meter_track_){meter_track_=track_;meters(xtc_meter_snapshot{},false);}
|
||||
xtc_track_chain chain{};chain.output_gain=1;
|
||||
if(track_!=UINT64_MAX)xtc_project_get_chain(project,track_,&chain);
|
||||
if(!rebuild && !std::memcmp(&cached_,&chain,sizeof(chain)))return;
|
||||
|
|
|
|||
|
|
@ -256,6 +256,12 @@ int creative_workflow_tests()
|
|||
CHECK(panel->bulk(XTC_CHAIN_ENABLE_ALL)==XTC_OK && panel->bulk(XTC_CHAIN_CLEAR)==XTC_OK);
|
||||
CHECK(xtc_project_get_chain(s->project(),second_track,&chain)==XTC_OK && chain.count==0 && chain.output_gain==.8f);
|
||||
CHECK(window.undo()==XTC_OK);panel->refresh(true);CHECK(xtc_project_get_chain(s->project(),second_track,&chain)==XTC_OK && chain.count==2);
|
||||
xtc_meter_snapshot peaks{};peaks.count=1;peaks.tracks[0]={second_track,.5f,.25f};panel->meters(peaks,true);
|
||||
CHECK(std::fabs(panel->meter()->left_db()+6.0206f)<.001f && std::fabs(panel->meter()->right_db()+12.0412f)<.001f);
|
||||
peaks.tracks[0].left=1.2f;panel->meters(peaks,true);CHECK(panel->meter()->clipped());
|
||||
panel->meters(peaks,false);CHECK(panel->meter()->left_db()==-90 && panel->meter()->clipped());
|
||||
panel->meter()->do_callback();CHECK(!panel->meter()->clipped());
|
||||
panel->select_track(first_track);CHECK(panel->meter()->left_db()==-90 && !panel->meter()->clipped());
|
||||
CHECK(window.new_project()==XTC_OK && !panel->clipboard_ready() && panel->paste_chain()==XTC_ERR_NOT_FOUND);
|
||||
CHECK(!panel->comparison_ready(0) && !panel->comparison_ready(1));
|
||||
window.do_callback();puts("Creative workflow GUI checks passed.");return failures ? 1 : 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue