@@ -726,14 +726,31 @@ bool QuantumMixerSlider::onMotion(const MotionEvent& ev)
726726
727727// --------------------------------------------------------------------------------------------------------------------
728728
729+ static constexpr const char * kQuantumLabelLvlGain = " Lvl Gain" ;
730+
729731QuantumGainReductionMeter::QuantumGainReductionMeter (NanoSubWidget* const parent, const QuantumTheme& t)
730732 : NanoSubWidget(parent),
731- theme(t)
733+ theme(t),
734+ label(const_cast <char *>(kQuantumLabelLvlGain ))
732735{
733736 loadSharedResources ();
734737 setSize (QuantumMetrics (t).gainReductionMeter );
735738}
736739
740+ QuantumGainReductionMeter::~QuantumGainReductionMeter ()
741+ {
742+ if (label != nullptr && label != kQuantumLabelLvlGain )
743+ std::free (label);
744+ }
745+
746+ void QuantumGainReductionMeter::setLabel (const char * const label2)
747+ {
748+ if (label != nullptr && label != kQuantumLabelLvlGain )
749+ std::free (label);
750+
751+ label = label2 != nullptr ? strdup (label2) : nullptr ;
752+ }
753+
737754void QuantumGainReductionMeter::setValue (const float value2)
738755{
739756 if (d_isEqual (value, value2))
@@ -832,8 +849,8 @@ void QuantumGainReductionMeter::onNanoDisplay()
832849 text (width * 0 .5f , height - theme.textHeight * 0 .5f + theme.borderSize , valuestr, nullptr );
833850
834851 // top label
835- fontSize (theme.fontSize * 2 /3 );
836- text (width * 0 .5f , verticalReservedHeight, " Lvl Gain " , nullptr );
852+ fontSize (theme.fontSize * 2 / 3 );
853+ text (width * 0 .5f , verticalReservedHeight, label , nullptr );
837854}
838855
839856// --------------------------------------------------------------------------------------------------------------------
@@ -1153,6 +1170,261 @@ void QuantumLevelMeter::onNanoDisplay()
11531170
11541171// --------------------------------------------------------------------------------------------------------------------
11551172
1173+ QuantumStereoLevelMeter::QuantumStereoLevelMeter (NanoTopLevelWidget* const parent, const QuantumTheme& t)
1174+ : NanoSubWidget(parent),
1175+ app(parent->getApp ()),
1176+ theme(t)
1177+ {
1178+ loadSharedResources ();
1179+ setSize (QuantumMetrics (t).stereoLevelMeter );
1180+ app.addIdleCallback (this );
1181+ }
1182+
1183+ QuantumStereoLevelMeter::QuantumStereoLevelMeter (NanoSubWidget* const parent, const QuantumTheme& t)
1184+ : NanoSubWidget(parent),
1185+ app(parent->getApp ()),
1186+ theme(t)
1187+ {
1188+ loadSharedResources ();
1189+ setSize (QuantumMetrics (t).stereoLevelMeter );
1190+ app.addIdleCallback (this );
1191+ }
1192+
1193+ void QuantumStereoLevelMeter::setRange (const float min, const float max)
1194+ {
1195+ minimum = min;
1196+ maximum = max;
1197+ repaint ();
1198+ }
1199+
1200+ void QuantumStereoLevelMeter::setValueL (const float value)
1201+ {
1202+ if (value >= falloffL)
1203+ {
1204+ falloffL = value;
1205+ lastTimeL = timeL = app.getTime ();
1206+ }
1207+
1208+ if (d_isEqual (valueL, value))
1209+ return ;
1210+
1211+ valueL = value;
1212+
1213+ repaint ();
1214+ }
1215+
1216+ void QuantumStereoLevelMeter::setValueR (const float value)
1217+ {
1218+ if (value >= falloffR)
1219+ {
1220+ falloffR = value;
1221+ lastTimeR = timeR = app.getTime ();
1222+ }
1223+
1224+ if (d_isEqual (valueR, value))
1225+ return ;
1226+
1227+ valueR = value;
1228+
1229+ repaint ();
1230+ }
1231+
1232+ void QuantumStereoLevelMeter::setValues (const float l, const float r)
1233+ {
1234+ falloffL = valueL = l;
1235+ falloffR = valueR = r;
1236+ lastTimeL = timeL = lastTimeR = timeR = 0 ;
1237+ repaint ();
1238+ }
1239+
1240+ void QuantumStereoLevelMeter::onNanoDisplay ()
1241+ {
1242+ const float verticalReservedHeight = theme.textHeight ;
1243+ const float usableMeterHeight = getHeight () - verticalReservedHeight;
1244+ const float centerX = static_cast <float >(getWidth ()) / 2 ;
1245+
1246+ beginPath ();
1247+ rect (0 , verticalReservedHeight, getWidth (), usableMeterHeight);
1248+ fillColor (theme.widgetBackgroundColor );
1249+ fill ();
1250+
1251+ float value;
1252+ char valuestr[32 ] = {};
1253+
1254+ const float meterChannelWidth = theme.textHeight - theme.borderSize * 2 ;
1255+ const float meterChannelHeight = usableMeterHeight - theme.borderSize * 2 ;
1256+
1257+ const float pxl = theme.borderSize ;
1258+ const float pxr = theme.borderSize * 5 + meterChannelWidth;
1259+
1260+ // alternate background
1261+ fillColor (Color (theme.windowBackgroundColor , theme.widgetBackgroundColor , 0 .75f ));
1262+
1263+ beginPath ();
1264+ rect (pxl,
1265+ theme.borderSize + verticalReservedHeight,
1266+ meterChannelWidth, meterChannelHeight);
1267+ fill ();
1268+
1269+ beginPath ();
1270+ rect (pxr,
1271+ theme.borderSize + verticalReservedHeight,
1272+ meterChannelWidth, meterChannelHeight);
1273+ fill ();
1274+
1275+ // fake spacer
1276+ fillColor (Color (theme.widgetBackgroundColor , theme.windowBackgroundColor , 0 .5f ));
1277+
1278+ beginPath ();
1279+ rect (pxr - theme.borderSize * 3 , verticalReservedHeight,
1280+ theme.borderSize * 2 , meterChannelHeight + theme.borderSize * 2 );
1281+ fill ();
1282+
1283+ // left channel
1284+ value = normalizedLevelMeterValue (valueL);
1285+
1286+ if (d_isNotZero (value))
1287+ {
1288+ beginPath ();
1289+ rect (pxl,
1290+ theme.borderSize + verticalReservedHeight + meterChannelHeight * (1 .f - value),
1291+ meterChannelWidth, meterChannelHeight * value);
1292+ fillColor (theme.levelMeterColor );
1293+ fill ();
1294+
1295+ std::snprintf (valuestr, sizeof (valuestr)-1 , " %.0f" , valueL);
1296+ }
1297+ else
1298+ {
1299+ std::strncpy (valuestr, " -inf" , sizeof (valuestr)-1 );
1300+ }
1301+
1302+ fillColor (theme.textLightColor );
1303+ fontSize (theme.fontSize * 2 / 3 );
1304+ textAlign (ALIGN_CENTER|ALIGN_BOTTOM);
1305+ text (pxl + meterChannelWidth / 2 ,
1306+ verticalReservedHeight, valuestr, nullptr );
1307+
1308+ if (d_isNotEqual (valueL, falloffL))
1309+ {
1310+ value = normalizedLevelMeterValue (falloffL);
1311+ const float y = theme.borderSize + verticalReservedHeight + meterChannelHeight * (1 .f - value);
1312+
1313+ beginPath ();
1314+ moveTo (pxl, y);
1315+ lineTo (pxl + meterChannelWidth, y);
1316+ strokeColor (theme.levelMeterColor );
1317+ strokeWidth (theme.borderSize );
1318+ stroke ();
1319+ }
1320+
1321+ // right channel
1322+ value = normalizedLevelMeterValue (valueR);
1323+
1324+ if (d_isNotZero (value))
1325+ {
1326+ beginPath ();
1327+ rect (pxr,
1328+ theme.borderSize + verticalReservedHeight + meterChannelHeight * (1 .f - value),
1329+ meterChannelWidth, meterChannelHeight * value);
1330+ fillColor (theme.levelMeterColor );
1331+ fill ();
1332+
1333+ std::snprintf (valuestr, sizeof (valuestr)-1 , " %.0f" , valueR);
1334+ }
1335+ else
1336+ {
1337+ std::strncpy (valuestr, " -inf" , sizeof (valuestr)-1 );
1338+ }
1339+
1340+ fillColor (theme.textLightColor );
1341+ fontSize (theme.fontSize * 2 / 3 );
1342+ textAlign (ALIGN_CENTER|ALIGN_BOTTOM);
1343+ text (pxr + meterChannelWidth / 2 ,
1344+ verticalReservedHeight, valuestr, nullptr );
1345+
1346+ if (d_isNotEqual (valueR, falloffR))
1347+ {
1348+ value = normalizedLevelMeterValue (falloffR);
1349+ const float y = theme.borderSize + verticalReservedHeight + meterChannelHeight * (1 .f - value);
1350+
1351+ beginPath ();
1352+ moveTo (pxr, y);
1353+ lineTo (pxr + meterChannelWidth, y);
1354+ strokeColor (theme.levelMeterColor );
1355+ strokeWidth (theme.borderSize );
1356+ stroke ();
1357+ }
1358+
1359+ // helper lines with labels
1360+ constexpr const float db2 = 1 .f - normalizedLevelMeterValue (-2 );
1361+ constexpr const float db5 = 1 .f - normalizedLevelMeterValue (-5 );
1362+ constexpr const float db10 = 1 .f - normalizedLevelMeterValue (-10 );
1363+ constexpr const float db20 = 1 .f - normalizedLevelMeterValue (-20 );
1364+ constexpr const float db30 = 1 .f - normalizedLevelMeterValue (-30 );
1365+ constexpr const float db40 = 1 .f - normalizedLevelMeterValue (-40 );
1366+ constexpr const float db50 = 1 .f - normalizedLevelMeterValue (-50 );
1367+ fillColor (theme.textLightColor );
1368+ fontSize (theme.fontSize );
1369+ textAlign (ALIGN_CENTER|ALIGN_MIDDLE);
1370+ const float yOffset = theme.borderSize + verticalReservedHeight;
1371+ text (centerX, yOffset + usableMeterHeight * db2, " - 2 -" , nullptr );
1372+ text (centerX, yOffset + usableMeterHeight * db5, " - 5 -" , nullptr );
1373+ text (centerX, yOffset + usableMeterHeight * db10, " - 10 -" , nullptr );
1374+ text (centerX, yOffset + usableMeterHeight * db20, " - 20 -" , nullptr );
1375+ text (centerX, yOffset + usableMeterHeight * db30, " - 30 -" , nullptr );
1376+ text (centerX, yOffset + usableMeterHeight * db40, " - 40 -" , nullptr );
1377+ text (centerX, yOffset + usableMeterHeight * db50, " - 50 -" , nullptr );
1378+ }
1379+
1380+ void QuantumStereoLevelMeter::idleCallback ()
1381+ {
1382+ const double time = app.getTime (); // in seconds
1383+
1384+ // TESTING
1385+ DISTRHO_SAFE_ASSERT_RETURN (falloffL >= valueL,);
1386+ DISTRHO_SAFE_ASSERT_RETURN (falloffR >= valueR,);
1387+
1388+ constexpr const double secondsToWaitForFalloffStart = 2 ;
1389+ constexpr const double falloffDbPerSecond = 8.6 ;
1390+
1391+ if (d_isEqual (valueL, falloffL))
1392+ {
1393+ lastTimeL = timeL = time;
1394+ }
1395+ else
1396+ {
1397+ const double diffSinceValueSet = time - timeL;
1398+ const double diffSinceLastIdle = time - lastTimeL;
1399+ lastTimeL = time;
1400+
1401+ if (diffSinceValueSet >= secondsToWaitForFalloffStart)
1402+ {
1403+ falloffL = std::max (valueL, static_cast <float >(falloffL - falloffDbPerSecond * diffSinceLastIdle));
1404+ repaint ();
1405+ }
1406+ }
1407+
1408+ if (d_isEqual (valueR, falloffR))
1409+ {
1410+ lastTimeR = timeR = time;
1411+ }
1412+ else
1413+ {
1414+ const double diffSinceValueSet = time - timeR;
1415+ const double diffSinceLastIdle = time - lastTimeR;
1416+ lastTimeR = time;
1417+
1418+ if (diffSinceValueSet >= secondsToWaitForFalloffStart)
1419+ {
1420+ falloffR = std::max (valueR, static_cast <float >(falloffR - falloffDbPerSecond * diffSinceLastIdle));
1421+ repaint ();
1422+ }
1423+ }
1424+ }
1425+
1426+ // --------------------------------------------------------------------------------------------------------------------
1427+
11561428QuantumStereoLevelMeterWithLUFS::QuantumStereoLevelMeterWithLUFS (NanoTopLevelWidget* const parent, const QuantumTheme& t)
11571429 : NanoSubWidget(parent),
11581430 app(parent->getApp ()),
0 commit comments