Skip to main content

Notice

Please note that most of the software linked on this forum is likely to be safe to use. If you are unsure, feel free to ask in the relevant topics, or send a private message to an administrator or moderator. To help curb the problems of false positives, or in the event that you do find actual malware, you can contribute through the article linked here.
Topic: JScript Panel script discussion/help (Read 524045 times) previous topic - next topic
0 Members and 1 Guest are viewing this topic.

Re: JScript Panel script discussion/help

Reply #1700
Quote
Quote
Quote from: ilovefb2k on 2024-08-02 05:28:14
FFmpeg is a good fundamental media processing, foobar2000 is based on that platform among others.
Small clarification. foobar2000 is in no way based on FFmpeg. At some point a few decoders were switched from other libraries to decoders found in FFmpeg because they were faster and better. And some of these decoders have been reverted back away from FFmpeg because they turned out to be too buggy.

Quote
Quote from: ilovefb2k on 2024-08-03 06:23:14
P/S. Can anyone shed me a light on how to stop this site from keeping me banning without reason. It is annoying that i have to go to some cafe to get access.
How do you connect to the site? I believe some anonymizing VPN services are banned/blocked because of abuse. I also fear that when a spammer is banned the IP-address that was used is banned with the account. Personally I don't believe in IP-banning as the problematic spammers are not stuck with a static IP.

I'm not sure VPN bans will be lifted, but if your normal connection's IP is blocked, you can for example PM the IP info to me and I'll try to get an admin to remove the ban.

Hi @Case,

Regarding foobar2000 and FFmpeg, I may have misinterpreted the statement “foobar2000 uses FFmpeg under the GNU LGPL v2.1,” which I quoted from the foobar2000 site [https://www.foobar2000.org/ffmpeg]. Thank you for pointing out the correction.

In terms of accessing the Hydrogenaud.io site [https://hydrogenaud.io/], I initially thought my username or IP might be blacklisted. However, even after refreshing my history cache, I encountered the same problem. It appears that this issue is related to region-specific policies, as I was able to access a German site [https://www.mathcs.uni-leipzig.de/]. Interestingly, configuring a VPN at a café’s internet connection resolved the issue for me.

Lastly, thank you for your very useful components as well as your efforts in persuading @Marc2003 to provide us with real-time audio data chunks.

Best regards,
Ilovefb2k

Re: JScript Panel script discussion/help

Reply #1701
Hi @all,
//Ref: JSP3 64-bit VU Meter (script).

It’s me, the lazy man, again.
My sincere apologies for the delayed response.

As we can see, the ‘JSP3 64-bit VU Meter’ script allows us to customize colors and shape the meter in various ways. Another unique feature is the ability to position it anywhere within the panel by specifying its x and y coordinates. This targeted positioning also enables us to repaint only that specific area, rather than the entire panel. By this way, we can construct our own skin using this flexibility.

I’ve attached an example of the ‘TechnicsRSBX’ skin created by @iprad [https://foobar2000.ru/forum/viewtopic.php?t=5081] around 10 years ago. Originally, this skin employed the ingenious ‘VU Meter’ 32bit foo_vis_vumeter to control the skin’s VU meter. However, we’re now experimenting it with the ‘JSP3 64-bit VU Meter’ instead. Let’s see how this lazy  ‘JSP3 64-bit VU Meter’ performs.

/*
P/S.
It’s very fascinating that @Marc2003 is reconsidering providing us with JSP3 real-time audio data .
By that, we can imagine the possibilities:
- one day, @pqyt might redirect audio path from foo_uie_webview through foobar2000, allowing us to control audio from platforms like YouTube.
- one day, we can listen to music streaming platforms (Spotify, Tidal, . .) from within foobar2000.

The potential for fun and creativity is boundless.
*/
Wish you all the best.
Cheers,
Ilovefb2k

// We need JScript Panel 3 (64bit) installed and ffprobe.exe [ffmpeg.org] copied into folder {foobar2000 profile folded}\skins\external_helper\ffprobe.exe

Re: JScript Panel script discussion/help

Reply #1702
@ilovefb2k :  thanks for your posts and for this new script!
To be perfectly honest with you though I don't want part of Foobar to look like a Technics tape deck.  The folks over at the AIMP forum for skins love this stuff, they have skins to make AIMP look like/"react" like dozens of different cassette decks and reel-to-reel tape players from days gone by.  It's interesting but I was happy to get rid of that stuff, haha..

What would be incredibly useful would be if someone could come up with a guide, perhaps based on this script, on how users could use image files in a similar way to create their own meter "look" minus the huge, confusing code investment this one makes in an animated body, etc.  Maybe including "sample" templates for a more paint-by-numbers approach for novice script editors like myself--light, and easy to experiment with.  Thanks for your interest in Foobar, glad to see your forum access issue has been resolved.

P.S.  You added,  "We need JScript Panel 3 (64bit) installed and ffprobe.exe [ffmpeg.org] copied into folder {foobar2000 profile folded}\skins\external_helper\ffprobe.exe."  Actually folks should know it's easy to change the script line that defines the ffprobe.exe path to anywhere on your PC just using Notepad, like
Code: [Select]
var binaries = {
    ffprobe: 'C:\\ffmpeg\\ffprobe.exe',
I know YOU know that, but many beginners faced with adding another 150mb to their Foobar Portable backup folder might be put off altogether, especially with the availability of pqyt's Peak Meter.

Re: JScript Panel script discussion/help

Reply #1703
Here's a super-quick conversion of the VU-meter script changed to use the new audio interface in JScript Panel 3.6.6.
Code: [Select]
// ==PREPROCESSOR==
// @name "JSP3 VU Meter"
// ==/PREPROCESSOR==

var timer_SetInterval = 30; //ms
var VUMeterFb2k = false; // true: foobar2000 VU Meter style; false: free style

var font,font_t = "";
var g_text = "";
var ww = 0, wh = 0;
var colours = {
  text : 0,
  background : 0,
  highlight : 0,
}

var color_1;
var color_2;
var color = [];
var step = 20; // more is less space between bars (cpu powers consumed)  and vice versa.
var minDB = -60; //dB as Foobar2000' s built - in VU Meter
var db = [];

var ColourTypeCUI = {
  text: 0,
  selection_text: 1,
  inactive_selection_text: 2,
  background: 3,
  selection_background: 4,
  inactive_selection_background: 5,
  active_item_frame: 6
};


var ColourTypeDUI = {
  text: 0,
  background: 1,
  highlight: 2,
  selection: 3
};

var FontTypeCUI = {
  items : 0,
  labels : 1,
};

var FontTypeDUI = {
  defaults : 0,
  tabs : 1,
  lists : 2,
  playlists : 3,
  statusbar : 4,
  console : 5,
};


function update_colours() {
  if (window.IsDefaultUI) {
    colours.text = window.GetColourDUI(ColourTypeDUI.text);
    colours.highlight = window.GetColourDUI(ColourTypeDUI.highlight);
    colours.background = window.GetColourDUI(ColourTypeDUI.background);
  } else {
    colours.text = window.GetColourCUI(ColourTypeCUI.text);
    colours.background = window.GetColourCUI(ColourTypeCUI.background);
    colours.highlight = blendColours(colours.text, colours.background, 0.4);
  }

  //custom colours override
  //  colours.text = RGB(255,255,255);
  //  colours.highlight = RGB(0,0,250);
  //  colours.background = RGB(0,0,0);

  color_1 = colours.text; //  or any color  RGB(250,250,250);
  color_2 = colours.highlight; // or any color RGB(0,0,0);

  color = [];

  for (var i = 0; i <= step; i++) {
    color.push(blendColours(color_1, color_2, (i / step)));
    db.push((minDB / step) * (step - i))
  }

}

function update_font() {
  if (window.IsDefaultUI) {
    font = window.GetFontDUI(FontTypeDUI.defaults);
  } else {
    font = window.GetFontCUI(FontTypeCUI.items);
  }

  // adjust size
  font_t = font;
  var obj = JSON.parse(font_t);
  obj.Size -= 4;
  font_t = JSON.stringify(obj);
}

update_colours();
update_font();

function on_colours_changed() {
  update_colours();
  window.Repaint();
}

function on_mouse_lbtn_up(x, y) {
  window.ShowConfigure();
}

function on_size() {
  ww = window.Width;
  wh = window.Height;
}

//------------- just an implementation /////////////

function RGB(r,g,b) { return (0xff000000|(r<<16)|(g<<8)|(b)); }
function RGBA(r, g, b, a) { return ((a << 24) | (r << 16) | (g << 8) | (b)); }
function toRGB(col) {
  var a = col - 0xFF000000;
  return [a >> 16, a >> 8 & 0xFF, a & 0xFF];
}

function blendColours(c1, c2, factor) { //@Marc2003
  var c1 = toRGB(c1);
  var c2 = toRGB(c2);
  var r = Math.round(c1[0] + factor * (c2[0] - c1[0]));
  var g = Math.round(c1[1] + factor * (c2[1] - c1[1]));
  var b = Math.round(c1[2] + factor * (c2[2] - c1[2]));
  return (0xff000000 | (r << 16) | (g << 8) | (b));
}

function on_paint(gr) {
  gr.FillRectangle(0, 0, ww, wh, colours.background);
    //
    LM = RMS_level1; // ~ VUMeter.LeftLevel;
    RM = RMS_level2; // ~ VUMeter.RightLevel;
    L = Peak_level1; // ~ VUMeter.LeftPeak;
    R = Peak_level2; // ~ VUMeter.RightPeak;

    var h = 20; // VUh_bar;
    var VUh_bar = 20; //
    var VU_x = 20; //VUx;
    var VUy = 5;
    var VUh_off = h + 5;
    var VUw = ww - 20;

    var yL = (VUy + h);
    var yLM = (VUy + h) + VUh_bar;
    var yRM = (VUy + h) + VUh_bar + VUh_off;
    var yR = (VUy + h) + VUh_bar + VUh_off + VUh_bar;

    var dbLM = PctToDB(LM),
    dbRM = PctToDB(RM),
    dbL = PctToDB(L),
    dbR = PctToDB(R);

    if (VUMeterFb2k) {// foobar2000 VUMeter style
        if (!(dbLM == 0 || dbLM == -Infinity)) {
            // 65 : -60db- 0dB (+ 5dB offset at the end beyond 0dB ) scale

            var wLM = Math.round(ww * ((100 + Number(dbLM)) / 65 - 40 / 65));
            var wRM = Math.round(ww * ((100 + Number(dbRM)) / 65 - 40 / 65));
            var wL = Math.round(ww * ((100 + Number(dbL)) / 65 - 40 / 65));
            var wR = Math.round(ww * ((100 + Number(dbR)) / 65 - 40 / 65));

            FillGradientRectangle(gr, VU_x, yLM, wLM, h - 1, 1, colours.text, colours.highlight);
            FillGradientRectangle(gr, VU_x, yRM, wRM, h - 1, 1, colours.text, colours.highlight);
            FillGradientRectangle(gr, VU_x, yL, wL, h - 1, 1, colours.text, colours.highlight);
            FillGradientRectangle(gr, VU_x, yR, wR, h - 1, 1, colours.text, colours.highlight);
        }
    } else { // free style
        h = 15;
        var offset = Math.floor((VUw - VU_x) / (step + 1)); //step+1 : number of bars and we need 1 more bar at the end
        var w = Math.floor((VUw - VU_x) / (step + 4)); // step+4 : width bar. we need some spaces between bars.

        for (var i = 0; i <= step; i++) {
            if (i > 0) {
                dbLM = PctToDB(LM),
                dbRM = PctToDB(RM),
                dbL = PctToDB(L),
                dbR = PctToDB(R);
            }

            gr.FillRectangle(VU_x + i * offset, yLM, w, h, dbLM >= db[i] ? color[i] : RGBA(0, 0, 0, 0));
            gr.FillRectangle(VU_x + i * offset, yRM, w, h, dbRM >= db[i] ? color[i] : RGBA(0, 0, 0, 0));

            if ((dbL > db[i] && dbL < db[i + 1])) {
                var wL = i * offset + offset / Math.abs(db[i + 1] - db[i]) * Math.abs(dbL - db[i]);
                gr.FillRectangle(VU_x, yL, wL, h, color[i]);
            }

            if ((dbR > db[i] && dbR < db[i + 1])) {
                var wR = i * offset + offset / Math.abs(db[i + 1] - db[i]) * Math.abs(dbR - db[i]);
                gr.FillRectangle(VU_x, yR, wR, h, color[i]);
            }
        }
    }

    // X-,Y-axis
    for (var i = (65 - 5) / 5, j = 0; i >= 0; i--, j++)
        gr.WriteText("-" + 5 * i + "dB", font_t, colours.text, VU_x / 2 + j * (ww - VU_x) / 13, yR + h + 10, ww, wh);

    gr.WriteText("FL", font_t, colours.text, 0, yLM, ww, wh);
    gr.WriteText("FL", font_t, colours.text, 0, yL, ww, wh);
    gr.WriteText("FR", font_t, colours.text, 0, yRM, ww, wh);
    gr.WriteText("FR", font_t, colours.text, 0, yR, ww, wh);

    FillGradientRectangle(gr, VU_x, yR + h + 5, ww, 1, 1, colours.text, colours.highlight);
}

function clear_meter() {
  RMS_level1 = 0;
  RMS_level2 = 0;
  Peak_level1 = 0;
  Peak_level2 = 0;
  prev_time = 0;
  window.Repaint();
}

function on_playback_new_track(handler) {
  VUMeter();
}

function on_playback_stop(reason) {
  if (reason != 2) { // not starting another track
    offVUMeterTimer();
    clear_meter();
  }
}

function on_playback_pause(state) {
  state ? offVUMeterTimer() : onVUMeterTimer();
}

var RMS_level1 = RMS_level2 = Peak_level1 = Peak_level2 = 0; // RMS and Peak level of each 2 channels.
var timer_SetInterval_id = null;
var prev_time = 0;

function on_script_unload() {
}

function PctToDB(num) {
    // input : num [0-1]
    // output: dB [-100,0]
    return ((2000 * Math.log(num) / Math.LN10) / 100).toFixed(2);
}

function onVUMeterTimer() {
  if (!fb.IsPlaying || fb.IsPaused) return;

  if (timer_SetInterval_id === null) {
    timer_SetInterval_id = window.SetInterval(function() {
      if (fb.PlaybackTime) {
        var cur_time = fb.PlaybackTime;
        var chunk = fb.GetAudioChunk(cur_time - prev_time);
        if (chunk) {
          prev_time = cur_time;
          var data = chunk.Data.toArray();
          if (data) {
            var ch_count = chunk.ChannelCount;
            var frame_len = chunk.SampleCount;
            var ch2_idx = 1;
            if (ch_count < 2) ch2_idx = 0;
            var sum1 = 0;
            var sum2 = 0;
            var peak1 = 0;
            var peak2 = 0;
            for (var i = 0; i < data.length; i += ch_count) {
              var l = data[i];
              var r = data[i + ch2_idx];
              if (l > peak1) peak1 = l;
              if (r > peak2) peak2 = r;
              sum1 += l * l;
              sum2 += r * r;
            }
            RMS_level1 = Math.sqrt(sum1/frame_len);
            RMS_level2 = Math.sqrt(sum2/frame_len);
            Peak_level1 = peak1;
            Peak_level2 = peak2;
          }
        }
      }

      //(window.RepaintRect(x, y, w, h);
      window.Repaint();
    }, timer_SetInterval);
  }
}

function offVUMeterTimer() {
  if (timer_SetInterval_id) window.ClearInterval(timer_SetInterval_id);
  timer_SetInterval_id = null;
}

onVUMeterTimer();

function VUMeter() {
  offVUMeterTimer(); //turn off timer
  var handle = fb.IsPlaying ? fb.GetNowPlaying() : false;
  if (!handle) return;

  // set VUMeter timer tiktok
  onVUMeterTimer();
}

function FillGradientRectangle(gr, x, y, w, h, direction, colour1, colour2) { //@Marc2003
  var stops = [[0, colour1], [1, colour2]];
  var brush = {
      Start: [0, 0],
      Stops: stops
  };
  if (direction == 0)
      brush.End = [0, h];
  else
      brush.End = [w, 0];
  gr.FillRectangle(x, y, w, h, JSON.stringify(brush));
}

//EOF

Edit: tiny bit cleaned up version.

Re: JScript Panel script discussion/help

Reply #1704
Sorry for being off-topic:

Quote
Quote
Quote from: ilovefb2k on 2024-08-02 05:28:14
FFmpeg is a good fundamental media processing, foobar2000 is based on that platform among others.
Small clarification. foobar2000 is in no way based on FFmpeg. At some point a few decoders were switched from other libraries to decoders found in FFmpeg because they were faster and better. And some of these decoders have been reverted back away from FFmpeg because they turned out to be too buggy.

Quote
Quote from: ilovefb2k on 2024-08-03 06:23:14
P/S. Can anyone shed me a light on how to stop this site from keeping me banning without reason. It is annoying that i have to go to some cafe to get access.
How do you connect to the site? I believe some anonymizing VPN services are banned/blocked because of abuse. I also fear that when a spammer is banned the IP-address that was used is banned with the account. Personally I don't believe in IP-banning as the problematic spammers are not stuck with a static IP.

I'm not sure VPN bans will be lifted, but if your normal connection's IP is blocked, you can for example PM the IP info to me and I'll try to get an admin to remove the ban.

Hi @Case,

Regarding foobar2000 and FFmpeg, I may have misinterpreted the statement “foobar2000 uses FFmpeg under the GNU LGPL v2.1,” which I quoted from the foobar2000 site [https://www.foobar2000.org/ffmpeg]. Thank you for pointing out the correction.

In terms of accessing the Hydrogenaud.io site [https://hydrogenaud.io/], I initially thought my username or IP might be blacklisted. However, even after refreshing my history cache, I encountered the same problem. It appears that this issue is related to region-specific policies, as I was able to access a German site [https://www.mathcs.uni-leipzig.de/]. Interestingly, configuring a VPN at a café’s internet connection resolved the issue for me.

Lastly, thank you for your very useful components as well as your efforts in persuading @Marc2003 to provide us with real-time audio data chunks.

Best regards,
Ilovefb2k

Then I ask myself why you are not using a browser with VPN at home like Opera, or a browser-plugin providing a VPN service, there are enough free solutions available at least for Firefox (for instance Browsec, Urban VPN, among other free solutions), but I guess you'll find such plugins for every browser that can handle plugins.

Michelist

Re: JScript Panel script discussion/help

Reply #1705
Here's a super-quick conversion of the VU-meter script changed to use the new audio interface in JScript Panel 3.6.6.

Thanks. All very much over my head as expected.  ;D

Here's an even tidier version with lots of rubbish removed. There is some unexpected flickering which I can't figure out but it seems to be present in the original too.

Code: [Select]
// ==PREPROCESSOR==
// @name "JSP3 VU Meter"
// @import "%fb2k_component_path%helpers.txt"
// ==/PREPROCESSOR==

var timer_interval = 30; //ms
var timer_id = 0;

var font_t = CreateFontString("Segoe UI", 8);
var ww = 0, wh = 0;

var minDB = -60;
var RMS_level1 = RMS_level2 = Peak_level1 = Peak_level2 = 0;
var prev_time = 0;

var stops = [
[ 1.0, RGB(227, 9, 64) ],
[ 0.66, RGB(231, 215, 2) ],
[ 0.33, RGB(15, 168, 149) ],
[ 0.0, RGB(19, 115, 232) ]
];

var brush = {
Stops : stops,
Start : [0, 0], // x and y values
End : [0, 0], // x and y values
};
var brush_str = "";

var colours = {
text : 0,
background : 0,
};

var ColourTypeCUI = {
text: 0,
selection_text: 1,
inactive_selection_text: 2,
background: 3,
selection_background: 4,
inactive_selection_background: 5,
active_item_frame: 6
};

var ColourTypeDUI = {
text: 0,
background: 1,
highlight: 2,
selection: 3
};

update_colours();
if (fb.IsPlaying) start_timer();

function update_colours() {
if (window.IsDefaultUI) {
colours.text = window.GetColourDUI(ColourTypeDUI.text);
colours.highlight = window.GetColourDUI(ColourTypeDUI.highlight);
colours.background = window.GetColourDUI(ColourTypeDUI.background);
} else {
colours.text = window.GetColourCUI(ColourTypeCUI.text);
colours.highlight = window.GetColourCUI(ColourTypeCUI.text);
colours.background = window.GetColourCUI(ColourTypeCUI.background);
}
}

function clear_meter() {
RMS_level1 = 0;
RMS_level2 = 0;
Peak_level1 = 0;
Peak_level2 = 0;
prev_time = 0;
window.Repaint();
}

function to_db(num) {
var ret = (2000 * Math.log(num) / Math.LN10 / 100).toFixed(2);
return clamp(ret, minDB, 0);
}

function clamp(value, min, max) {
if (value < min) return min;
if (value > max) return max;
return value;
}

function start_timer() {
if (!timer_id) {
timer_id = window.SetInterval(function() {
var cur_time = fb.PlaybackTime;
if (cur_time) {
var chunk = fb.GetAudioChunk(cur_time - prev_time);
if (chunk) {
prev_time = cur_time;
var data = chunk.Data.toArray();
if (data) {
var ch_count = chunk.ChannelCount;
var frame_len = chunk.SampleCount;
var ch2_idx = 1;
if (ch_count < 2) ch2_idx = 0;
var sum1 = 0;
var sum2 = 0;
var peak1 = 0;
var peak2 = 0;
for (var i = 0; i < data.length; i += ch_count) {
  var l = data[i];
  var r = data[i + ch2_idx];
  if (l > peak1) peak1 = l;
  if (r > peak2) peak2 = r;
  sum1 += l * l;
  sum2 += r * r;
}
RMS_level1 = Math.sqrt(sum1/frame_len);
RMS_level2 = Math.sqrt(sum2/frame_len);
Peak_level1 = peak1;
Peak_level2 = peak2;
}
}
}
window.Repaint();
}, timer_interval);
}
}

function stop_timer() {
if (timer_id) {
window.ClearInterval(timer_id);
}

timer_id = 0;
}

function on_colours_changed() {
update_colours();
window.Repaint();
}

function on_paint(gr) {
gr.FillRectangle(0, 0, ww, wh, colours.background);

var h = 20;
var VU_x = 20;;
var VUy = 5;
var VUw = ww - 20;

var yL = VUy + h;
var yLM = VUy + (h * 2);
var yRM = VUy + (h * 3);
var yR = VUy + (h * 4);

// labels
for (var i = (65 - 5) / 5, j = 0; i >= 0; i--, j++) {
gr.WriteText("-" + 5 * i + "dB", font_t, colours.text, VU_x / 2 + j * (ww - VU_x) / 13, yR + h + 10, ww, wh);
}

gr.WriteText("FL", font_t, colours.text, 4, yLM, ww, wh);
gr.WriteText("FL", font_t, colours.text, 4, yL, ww, wh);
gr.WriteText("FR", font_t, colours.text, 4, yRM, ww, wh);
gr.WriteText("FR", font_t, colours.text, 4, yR, ww, wh);

gr.FillRectangle(VU_x, yR + h + 5, ww, 1, colours.text);

// bars
var dbLM = to_db(RMS_level1),
dbRM = to_db(RMS_level2),
dbL = to_db(Peak_level1),
dbR = to_db(Peak_level2);

var wLM = Math.round(ww * ((100 + Number(dbLM)) / 65 - 40 / 65));
var wRM = Math.round(ww * ((100 + Number(dbRM)) / 65 - 40 / 65));
var wL = Math.round(ww * ((100 + Number(dbL)) / 65 - 40 / 65));
var wR = Math.round(ww * ((100 + Number(dbR)) / 65 - 40 / 65));

gr.FillRectangle(VU_x, yLM, wLM, h - 1, brush_str);
gr.FillRectangle(VU_x, yRM, wRM, h - 1, brush_str);
gr.FillRectangle(VU_x, yL, wL, h - 1, brush_str);
gr.FillRectangle(VU_x, yR, wR, h - 1, brush_str);
}

function on_playback_new_track(handle) {
start_timer();
}

function on_playback_pause(state) {
state ? stop_timer() : start_timer();
}

function on_playback_stop(reason) {
if (reason != 2) {
stop_timer();
clear_meter();
}
}

function on_size() {
ww = window.Width;
wh = window.Height;

brush.End = [ww, 0];
brush_str = JSON.stringify(brush);
}


Re: JScript Panel script discussion/help

Reply #1706
@marc2k3
Nice! Thanks.
SHURE SRH1840, SENNHEISER HD660S2, SENNHEISER HD620S, SENNHEISER HD 490 Pro Plus, beyerdynamic DT 1990 PRO, HiFiMAN Edition XS, HIFIMAN ANANDA, Bowers & Wilkins P7, FiiO FT5, FiiO FT1 Pro, 水月雨 (MOONDROP) 空鳴 - VOID, SONY WH1000XM5 (made a Upgrade/Balanced Cable by myself)

Re: JScript Panel script discussion/help

Reply #1707
Here's a super-quick conversion of the VU-meter script changed to use the new audio interface in JScript Panel 3.6.6.

Thanks. All very much over my head as expected.  ;D

Here's an even tidier version with lots of rubbish removed. There is some unexpected flickering which I can't figure out but it seems to be present in the original too.

Code: [Select]
// ==PREPROCESSOR==
// @name "JSP3 VU Meter"
// @import "%fb2k_component_path%helpers.txt"
// ==/PREPROCESSOR==

var timer_interval = 30; //ms
var timer_id = 0;

var font_t = CreateFontString("Segoe UI", 8);
var ww = 0, wh = 0;

var minDB = -60;
var RMS_level1 = RMS_level2 = Peak_level1 = Peak_level2 = 0;
var prev_time = 0;

var stops = [
[ 1.0, RGB(227, 9, 64) ],
[ 0.66, RGB(231, 215, 2) ],
[ 0.33, RGB(15, 168, 149) ],
[ 0.0, RGB(19, 115, 232) ]
];

var brush = {
Stops : stops,
Start : [0, 0], // x and y values
End : [0, 0], // x and y values
};
var brush_str = "";

var colours = {
text : 0,
background : 0,
};

var ColourTypeCUI = {
text: 0,
selection_text: 1,
inactive_selection_text: 2,
background: 3,
selection_background: 4,
inactive_selection_background: 5,
active_item_frame: 6
};

var ColourTypeDUI = {
text: 0,
background: 1,
highlight: 2,
selection: 3
};

update_colours();
if (fb.IsPlaying) start_timer();

function update_colours() {
if (window.IsDefaultUI) {
colours.text = window.GetColourDUI(ColourTypeDUI.text);
colours.highlight = window.GetColourDUI(ColourTypeDUI.highlight);
colours.background = window.GetColourDUI(ColourTypeDUI.background);
} else {
colours.text = window.GetColourCUI(ColourTypeCUI.text);
colours.highlight = window.GetColourCUI(ColourTypeCUI.text);
colours.background = window.GetColourCUI(ColourTypeCUI.background);
}
}

function clear_meter() {
RMS_level1 = 0;
RMS_level2 = 0;
Peak_level1 = 0;
Peak_level2 = 0;
prev_time = 0;
window.Repaint();
}

function to_db(num) {
var ret = (2000 * Math.log(num) / Math.LN10 / 100).toFixed(2);
return clamp(ret, minDB, 0);
}

function clamp(value, min, max) {
if (value < min) return min;
if (value > max) return max;
return value;
}

function start_timer() {
if (!timer_id) {
timer_id = window.SetInterval(function() {
var cur_time = fb.PlaybackTime;
if (cur_time) {
var chunk = fb.GetAudioChunk(cur_time - prev_time);
if (chunk) {
prev_time = cur_time;
var data = chunk.Data.toArray();
if (data) {
var ch_count = chunk.ChannelCount;
var frame_len = chunk.SampleCount;
var ch2_idx = 1;
if (ch_count < 2) ch2_idx = 0;
var sum1 = 0;
var sum2 = 0;
var peak1 = 0;
var peak2 = 0;
for (var i = 0; i < data.length; i += ch_count) {
  var l = data[i];
  var r = data[i + ch2_idx];
  if (l > peak1) peak1 = l;
  if (r > peak2) peak2 = r;
  sum1 += l * l;
  sum2 += r * r;
}
RMS_level1 = Math.sqrt(sum1/frame_len);
RMS_level2 = Math.sqrt(sum2/frame_len);
Peak_level1 = peak1;
Peak_level2 = peak2;
}
}
}
window.Repaint();
}, timer_interval);
}
}

function stop_timer() {
if (timer_id) {
window.ClearInterval(timer_id);
}

timer_id = 0;
}

function on_colours_changed() {
update_colours();
window.Repaint();
}

function on_paint(gr) {
gr.FillRectangle(0, 0, ww, wh, colours.background);

var h = 20;
var VU_x = 20;;
var VUy = 5;
var VUw = ww - 20;

var yL = VUy + h;
var yLM = VUy + (h * 2);
var yRM = VUy + (h * 3);
var yR = VUy + (h * 4);

// labels
for (var i = (65 - 5) / 5, j = 0; i >= 0; i--, j++) {
gr.WriteText("-" + 5 * i + "dB", font_t, colours.text, VU_x / 2 + j * (ww - VU_x) / 13, yR + h + 10, ww, wh);
}

gr.WriteText("FL", font_t, colours.text, 4, yLM, ww, wh);
gr.WriteText("FL", font_t, colours.text, 4, yL, ww, wh);
gr.WriteText("FR", font_t, colours.text, 4, yRM, ww, wh);
gr.WriteText("FR", font_t, colours.text, 4, yR, ww, wh);

gr.FillRectangle(VU_x, yR + h + 5, ww, 1, colours.text);

// bars
var dbLM = to_db(RMS_level1),
dbRM = to_db(RMS_level2),
dbL = to_db(Peak_level1),
dbR = to_db(Peak_level2);

var wLM = Math.round(ww * ((100 + Number(dbLM)) / 65 - 40 / 65));
var wRM = Math.round(ww * ((100 + Number(dbRM)) / 65 - 40 / 65));
var wL = Math.round(ww * ((100 + Number(dbL)) / 65 - 40 / 65));
var wR = Math.round(ww * ((100 + Number(dbR)) / 65 - 40 / 65));

gr.FillRectangle(VU_x, yLM, wLM, h - 1, brush_str);
gr.FillRectangle(VU_x, yRM, wRM, h - 1, brush_str);
gr.FillRectangle(VU_x, yL, wL, h - 1, brush_str);
gr.FillRectangle(VU_x, yR, wR, h - 1, brush_str);
}

function on_playback_new_track(handle) {
start_timer();
}

function on_playback_pause(state) {
state ? stop_timer() : start_timer();
}

function on_playback_stop(reason) {
if (reason != 2) {
stop_timer();
clear_meter();
}
}

function on_size() {
ww = window.Width;
wh = window.Height;

brush.End = [ww, 0];
brush_str = JSON.stringify(brush);
}
Hi @Case,
Frankly, I had no idea until I saw your post. Again, thank you for persuading Marc2003 to give JSP3 a nice feature.

Hi @marc2k3,
It seems you have just pushed the JSP3 to the edge of ES5.
Your script is very beautiful and tidy.
Wishing you both happiness.

Ilovefb2k
Cheers.

Re: JScript Panel script discussion/help

Reply #1708
Sorry for being off-topic:
Spoiler (click to show/hide)
Then I ask myself why you are not using a browser with VPN at home like Opera, or a browser-plugin providing a VPN service, there are enough free solutions available at least for Firefox (for instance Browsec, Urban VPN, among other free solutions), but I guess you'll find such plugins for every browser that can handle plugins.

Michelist
Hi @Michelist
I did a google search and followed the instruction here https://www.reddit.com/r/foobar2000/comments/9eb8ua/why_i_banned_on_hydrogenaudioio/ (." . .It does work on Brave if I select 'Open Private Window with Tor' though . . ").
That saves my life sometimes.
But , why do we have to play around with VPN ? and my case may not the only one.

Regards,
Ilovefb2k.

Re: JScript Panel script discussion/help

Reply #1709
But , why do we have to play around with VPN ? and my case may not the only one.
You are not supposed to play around with VPN, and actually VPN services are most likely to be blocked. Especially free ones.

The forums have no geo blocking going on.

But you don't tell any information about the connection you have trouble accessing the site with. How do you expect people to help you if there is zero data?

Re: JScript Panel script discussion/help

Reply #1710
Here's a super-quick conversion of the VU-meter script changed to use the new audio interface in JScript Panel 3.6.6.

Thanks. All very much over my head as expected.  ;D

Here's an even tidier version with lots of rubbish removed. There is some unexpected flickering which I can't figure out but it seems to be present in the original too.

Thanks marc2k3 and Case!  Great job marc2k3 on the JSP3 3.6.6 revision that makes this possible, man like overnight!  Case thanks for getting it started, yours kept the original segmented bars for median which I liked but as you said the RMS peak bars would flicker and often disappear completely on high-level material.  Loved marc2k3's revision with the colors and smooth operation.  Cool to be able to use the meter on radio streams now with a nice accuracy level and no more caching.

Re: JScript Panel script discussion/help

Reply #1711
@eurekagliese
Hi :)
Thank you for the wonderful script as always

Crash when wheeling on panel (JSP3 panel only).
I know I won't be using a mouse on this panel.

Text Display + Album Art + Custom SVG and PNG Buttons (2024-07-18).txt
https://hydrogenaud.io/index.php/topic,110516.msg1048037.html#msg1048037

Sorry, this was it.
@marc2k3
Reply #1531
https://hydrogenaud.io/index.php/topic,110516.msg1048043.html#msg1048043


SHURE SRH1840, SENNHEISER HD660S2, SENNHEISER HD620S, SENNHEISER HD 490 Pro Plus, beyerdynamic DT 1990 PRO, HiFiMAN Edition XS, HIFIMAN ANANDA, Bowers & Wilkins P7, FiiO FT5, FiiO FT1 Pro, 水月雨 (MOONDROP) 空鳴 - VOID, SONY WH1000XM5 (made a Upgrade/Balanced Cable by myself)

Re: JScript Panel script discussion/help

Reply #1712
Line 113, replace

Code: [Select]
function on_mouse_wheel(s) {
if (seekbar.wheel(s)) {
return;
}
text.wheel(s);
}

with

Code: [Select]
function on_mouse_wheel(s) {
text.wheel(s);
}

Re: JScript Panel script discussion/help

Reply #1713
But , why do we have to play around with VPN ? and my case may not the only one.
You are not supposed to play around with VPN, and actually VPN services are most likely to be blocked. Especially free ones.

The forums have no geo blocking going on.

But you don't tell any information about the connection you have trouble accessing the site with. How do you expect people to help you if there is zero data?
Hi @Case
Thank you for your attention,
I made this noise not just for my shake.

If this is a public forum, then : Why make it easy accessing as https://wiki.hydrogenaud.io ? some people just look around for  information from this knowledge base. Do they walk away from this site [hydrogenaud.io] right away or keep trying.
 
I made this noise for others and for this forum.

Regards,
Ilovefb2k

Re: JScript Panel script discussion/help

Reply #1714
@marc2k3
Perfect.
Thanks.
SHURE SRH1840, SENNHEISER HD660S2, SENNHEISER HD620S, SENNHEISER HD 490 Pro Plus, beyerdynamic DT 1990 PRO, HiFiMAN Edition XS, HIFIMAN ANANDA, Bowers & Wilkins P7, FiiO FT5, FiiO FT1 Pro, 水月雨 (MOONDROP) 空鳴 - VOID, SONY WH1000XM5 (made a Upgrade/Balanced Cable by myself)

 

Re: JScript Panel script discussion/help

Reply #1715
I noticed is that if the tag contains a number in the streaming information, it is not displayed correctly.

In Custom title I use:

$font(Twemoji Mozilla,10)$country_flag($meta(country,$sub($meta_num(country),1))) [%artist%] - [%title%]


Re: JScript Panel script discussion/help

Reply #1716
You're telling it to use the Twemoji Mozilla font for the whole text which is wrong. You need to use $font() which tells it to revert to the default font after the country flag...

Code: [Select]
$font(Twemoji Mozilla,10)FLAG STUFF$font() %artist% - %title%

Re: JScript Panel script discussion/help

Reply #1717
You're telling it to use the Twemoji Mozilla font for the whole text which is wrong. You need to use $font() which tells it to revert to the default font after the country flag...

Code: [Select]
$font(Twemoji Mozilla,10)FLAG STUFF$font() %artist% - %title%

Perfect, thanks.


Re: JScript Panel script discussion/help

Reply #1718
Here's another tweak on the VU Meter that ilovefb2k/Case provided and I tidied up. It will be in the next release.

https://raw.githubusercontent.com/jscript-panel/component/main/samples/VU%20Meter.txt

I changed the timer interval to 50ms which seems to have solved my unexpected flickering and I prefer the less frenetic updates. I'm guessing it was monitor refresh rate related but I really have no idea what I'm talking about. Just the first line needs to be edited if people want to experiment with it.

Re: JScript Panel script discussion/help

Reply #1719
Here's another tweak on the VU Meter that ilovefb2k/Case provided and I tidied up. It will be in the next release.

https://raw.githubusercontent.com/jscript-panel/component/main/samples/VU%20Meter.txt

I changed the timer interval to 50ms which seems to have solved my unexpected flickering and I prefer the less frenetic updates. I'm guessing it was monitor refresh rate related but I really have no idea what I'm talking about. Just the first line needs to be edited if people want to experiment with it.
Thanks marc2k3 for the timer fix, the beautiful color blend, and for your intention to add the VU Meter to your own samples!

I tried making the same timer change to 50ms on Case's original trial version, but it doesn't make the same change as it did on yours, only on very quiet passages.  The LR Peak value bars still end up vanishing off the screen when the amplitude increases, while the segmented RMS meters behave normally.  If I can make a request, would you consent to doing a version of your own meter that maintains the blue/white color scheme and the segmented RMS bars of his original?  It would be great to have that design working as well as yours does now.  Thanks for any consideration.

Re: JScript Panel script discussion/help

Reply #1720
I have worked on some improvements to the VU meter too, below you can find current work-in-progress version. It uses marc2k3's cleaned up version as the base but with some fixes and I think improvements.

This shows meters for all channels, bars scale according to panel size, channels are labeled correctly from channel mask. Range is configurable, RMS window is configurable. Optional "AES +3dB" mode. Peak is now shown as a bar at the end of the VU meter and it falls off at configurable speed.
All user-configurable settings are at the top easily editable.

If this is something usable it can be modified to use a block mode and original RMS meter colors.

Code: [Select]
// ==PREPROCESSOR==
// @name "JSP3 VU Meter"
// @import "%fb2k_component_path%helpers.txt"
// ==/PREPROCESSOR==

// User adjustable settings:
var timer_interval = 1000 / 60; // in ms (default: 60 fps update rate)
var rms_window = 50 / 1000; // in seconds (default: 50 ms)
var peak_hold = 20; // in frames
var peak_fall_mul = 0.99;
var peak_bar_width = 3; // in pixels
var minDB = -60; // minimum dB on the meter (meter range)
var maxDB = 5;   // maximum dB on the meter (meter range)
var rms_3db = false; // use AES +3dB RMS mode

// End of user adjustable settings
// -------------------------------

var font_t = CreateFontString("Segoe UI", 8);

var RMS_levels = [], Peak_levels = [], Peak_falldown = [];
var ch_count = 0;
var ch_config = 0;
var ww = 0, wh = 0;
var timer_id = 0;
var rms_db_offset = 0;
if (rms_3db) rms_db_offset = 20 * Math.log(Math.sqrt(2)) / Math.LN10; // 3.01029995663981 dB

var stops = [
[ 1.0, RGB(227, 9, 64) ],
[ 0.66, RGB(231, 215, 2) ],
[ 0.33, RGB(15, 168, 149) ],
[ 0.0, RGB(19, 115, 232) ]
];

var brush = {
Stops : stops,
Start : [0, 0], // x and y values
End : [0, 0], // x and y values
};
var brush_str = "";

var colours = {
text : 0,
background : 0,
};

var ColourTypeCUI = {
text: 0,
selection_text: 1,
inactive_selection_text: 2,
background: 3,
selection_background: 4,
inactive_selection_background: 5,
active_item_frame: 6
};

var ColourTypeDUI = {
text: 0,
background: 1,
highlight: 2,
selection: 3
};

var ChannelNames = [ "FL", "FR", "FC", "LFE", "BL", "BR", "FCL", "FCR", "BC", "SL", "SR", "TC", "TFL", "TFC", "TFR", "TBL", "TBC", "TBR" ];

function update_colours() {
if (window.IsDefaultUI) {
colours.text = window.GetColourDUI(ColourTypeDUI.text);
colours.highlight = window.GetColourDUI(ColourTypeDUI.highlight);
colours.background = window.GetColourDUI(ColourTypeDUI.background);
} else {
colours.text = window.GetColourCUI(ColourTypeCUI.text);
colours.highlight = window.GetColourCUI(ColourTypeCUI.text);
colours.background = window.GetColourCUI(ColourTypeCUI.background);
}
}

function clear_graph() {
  for (var c = 0; c < ch_count; ++c) {
    RMS_levels[c] = 0;
    Peak_levels[c] = 0;
    Peak_falldown[c] = 0;
  }
window.Repaint();
}

function update_graph() {
  var cur_time = fb.PlaybackTime;
  if (cur_time > rms_window) {
    var chunk = fb.GetAudioChunk(rms_window);
    if (chunk) {
      ch_count = chunk.ChannelCount;
      ch_config = chunk.ChannelConfig;
      var data = chunk.Data.toArray();
      var frame_len = chunk.SampleCount;
      if (data && ch_count > 0 && frame_len > 0) {
        var old_count = Peak_levels.length;
        RMS_levels.length = ch_count;
        Peak_levels.length = ch_count;
        Peak_falldown.length = ch_count;
        if (old_count < ch_count) {
          for (var c = old_count; c < ch_count; ++c) {
            Peak_levels[c] = 0;
            Peak_falldown[c] = 0;
          }
        }
        for (var c = 0; c < ch_count; ++c) {
          var sum = 0, peak = 0;
          for (var i = c; i < data.length; i += ch_count) {
            var s = Math.abs(data[i]);
            if (s > peak) peak = s;
            sum += s * s;
          }
          RMS_levels[c] = Math.sqrt(sum/frame_len);
          if (peak >= Peak_levels[c]) {
            Peak_levels[c] = peak;
            Peak_falldown[c] = 0;
          } else {
            if (++Peak_falldown[c] > peak_hold) Peak_levels[c] *= peak_fall_mul;
          }
        }
        window.Repaint();
      }
    }
  }
}

function to_db(num) {
return 20 * Math.log(num) / Math.LN10;
}

function clamp(value, min, max) {
if (value < min) return min;
if (value > max) return max;
return value;
}

function start_timer() {
if (!timer_id) {
timer_id = window.SetInterval(update_graph, timer_interval);
}
}

function stop_timer() {
if (timer_id) {
window.ClearInterval(timer_id);
}

timer_id = 0;
}

function on_colours_changed() {
update_colours();
window.Repaint();
}

var dBrange = maxDB - minDB;

function channel_name(ch) {
  if (ch < ChannelNames.length) {
    if (ch_config) {
      for (var i = 0, idx = 0; i < ChannelNames.length; ++i) {
        if (ch_config & (1 << i)) {
          if (idx == ch) return ChannelNames[i];
          idx++;
        }
      }
    } else {
      return ChannelNames[ch];
    }
  }
  var name = "Ch" + (ch+1).toString();
  return name;
}

function on_paint(gr) {
gr.Clear(colours.background);
  if (wh < 1 || ww < 1) return;

  var hide_ch_labels = false;
  var hide_db_labels = false;
  var bar_pad_left = 3*8;
  var bar_pad_right = 3*8;
  var bar_pad_top = 5;
  var bar_pad_bottom = 30;
  var bar_height = 0;
  var bar_width = 0;

bar_height = Math.floor((wh - bar_pad_top - bar_pad_bottom) / ch_count);

  if (bar_height < 8) { // bars are too thin for channel labels, hide them
    hide_ch_labels = true;
  }
  if (bar_height < 4) { // bars are too thin for dB scale too
    hide_ch_labels = true;
    hide_db_labels = true;
    bar_pad_top = 0;
    bar_pad_bottom = 0;
    bar_pad_left = 0;
    bar_pad_right = 0;
    bar_height = Math.floor(wh / ch_count);
  }
  if (bar_height < 2 || bar_height * ch_count > wh) { // bars won't fit in the window, downmix to mono
    var rms_sum = 0;
    var peak_sum = 0;
    for (var c = 0; c < ch_count; ++c) {
      rms_sum += RMS_levels[c] * RMS_levels[c];
      peak_sum += Peak_levels[c];
    }
    RMS_levels[0] = Math.sqrt(rms_sum/ch_count);
    Peak_levels[0] = peak_sum/ch_count;
    ch_count = 1;
    hide_ch_labels = true;
    hide_db_labels = true;
    bar_pad_top = 0;
    bar_pad_bottom = 0;
    bar_pad_left = 0;
    bar_pad_right = 0;
    bar_height = Math.floor(wh / ch_count);
  }

  bar_width = ww - bar_pad_left - bar_pad_right;

// labels
  if (!hide_db_labels) {
    var db_spacing = 5;
    if (dBrange < db_spacing) db_spacing = 1;
    if (ww * db_spacing / dBrange < 10*8) {
      db_spacing = ((10*8 * dBrange) / ww);
      db_spacing -= (db_spacing % 5);
    }

    gr.FillRectangle(bar_pad_left, bar_pad_top + (bar_height * ch_count) + 5, bar_width, 1, colours.text);

    for (var i = minDB, j = 0; i <= maxDB; i += db_spacing, j++) {
      gr.WriteText(i + "dB", font_t, colours.text, bar_pad_left / 2 + bar_width * j / (dBrange/db_spacing), wh - 20, ww, wh);
    }
  }

  if (!hide_ch_labels) {
    for (var c = 0; c < ch_count; ++c) {
      gr.WriteText(channel_name(c), font_t, colours.text, 4, bar_pad_top + (bar_height * c) + bar_height / 2 - 8, ww, wh);
    }
  }

// bars
  for (var c = 0; c < ch_count; ++c) {
    if (RMS_levels[c]) {
      var rms_db = clamp(to_db(RMS_levels[c]) + rms_db_offset, minDB, maxDB);
      var width = Math.round(bar_width * (rms_db - minDB) / dBrange);
      gr.FillRectangle(bar_pad_left, bar_pad_top + (bar_height * c), width, bar_height - 1, brush_str);
    }
    if (peak_bar_width > 0 && Peak_levels[c] > 0) {
      var peak_db = clamp(to_db(Peak_levels[c]), minDB, maxDB);
      if (peak_db > minDB) {
        var peak_pos = Math.round(bar_width * (peak_db - minDB) / dBrange);
        gr.FillRectangle(bar_pad_left + peak_pos - peak_bar_width / 2, bar_pad_top + (bar_height * c), peak_bar_width, bar_height - 1, brush_str);
      }
    }
  }
}

function on_playback_new_track(handle) {
start_timer();
}

function on_playback_pause(state) {
state ? stop_timer() : start_timer();
}

function on_playback_stop(reason) {
if (reason != 2) {
stop_timer();
}
  clear_graph();
}

function on_size() {
ww = window.Width;
wh = window.Height;

brush.End = [ww, 0];
brush_str = JSON.stringify(brush);
}

function get_initial_track_info() {
  if (ch_count > 0) return;
  var handle = fb.GetNowPlaying();
  if (!handle) handle = fb.GetFocusItem();
  if (!handle) return;
 
  var info = handle.GetFileInfo();
  if (info) {
    var idx_ch_count = info.InfoFind("channels");
    var idx_ch_config = info.InfoFind("WAVEFORMATEXTENSIBLE_CHANNEL_MASK");
    if (idx_ch_count >= 0) {
      ch_count = Number(info.InfoValue(idx_ch_count));
    }
    if (idx_ch_config >= 0) {
      ch_config = Number(info.InfoValue(idx_ch_config));
    }
    info.Dispose();
  }
 
  handle.Dispose();
}

update_colours();
if (fb.IsPlaying) start_timer();
get_initial_track_info();

Re: JScript Panel script discussion/help

Reply #1721
Wow, thanks Case for deciding to get back in with your own VU meter scripts!  I'm running this one right now and it's working beautifully.  Thanks for the user-adjustable settings; the only change I made was to raise the max db to 10.

It was a good idea making the RMS level bars and the max levels share the same horizontal space.  The peaks are easier to spot with this design and adjustable fallback, while the RMS levels are very accurate and responsive without being too "hyper."

You sort of "asked for it" when you mentioned "If this is something usable it can be modified to use a block mode and original RMS meter colors," because OF COURSE it usable, and of course I'd love to see a second version with the original meter colors and block mode!!

Thanks for your work on this, and to marc2k3's new JSP3 3.6.6 version that made all this possible.

Re: JScript Panel script discussion/help

Reply #1722
Here's a quick and dirty mod with right click menu for toggling bar colours between UI and Rainbow. CUI is single colour only because there is no "highlight" like DUI.

Code: [Select]
// ==PREPROCESSOR==
// @name "JSP3 VU Meter"
// @import "%fb2k_component_path%helpers.txt"
// ==/PREPROCESSOR==

// User adjustable settings:
var timer_interval = 1000 / 60; // in ms (default: 60 fps update rate)
var rms_window = 50 / 1000; // in seconds (default: 50 ms)
var peak_hold = 20; // in frames
var peak_fall_mul = 0.99;
var peak_bar_width = 3; // in pixels
var minDB = -60; // minimum dB on the meter (meter range)
var maxDB = 5;   // maximum dB on the meter (meter range)
var rms_3db = false; // use AES +3dB RMS mode

// End of user adjustable settings
// -------------------------------

var font_t = CreateFontString("Segoe UI", 8);
var colour_mode = window.GetProperty("2K3.METER.COLOUR.MODE", 1); // 0 UI, 1 Rainbow
var solid_colour = false;
var RMS_levels = [], Peak_levels = [], Peak_falldown = [];
var ch_count = 0;
var ch_config = 0;
var ww = 0, wh = 0;
var timer_id = 0;
var rms_db_offset = 0;
if (rms_3db) rms_db_offset = 20 * Math.log(Math.sqrt(2)) / Math.LN10; // 3.01029995663981 dB

var rainbow_stops = [
[ 1.0, RGB(227, 9, 64) ],
[ 0.66, RGB(231, 215, 2) ],
[ 0.33, RGB(15, 168, 149) ],
[ 0.0, RGB(19, 115, 232) ]
];

var brush = {
Stops : [],
Start : [0, 0], // x and y values
End : [0, 0], // x and y values
};
var brush_str = "";

var colours = {
text : 0,
highlight : 0,
background : 0,
};

var ChannelNames = [ "FL", "FR", "FC", "LFE", "BL", "BR", "FCL", "FCR", "BC", "SL", "SR", "TC", "TFL", "TFC", "TFR", "TBL", "TBC", "TBR" ];

function update_colours() {
if (window.IsDefaultUI) {
colours.text = window.GetColourDUI(ColourTypeDUI.text);
colours.highlight = window.GetColourDUI(ColourTypeDUI.highlight);
colours.background = window.GetColourDUI(ColourTypeDUI.background);
} else {
colours.text = window.GetColourCUI(ColourTypeCUI.text);
colours.highlight = window.GetColourCUI(ColourTypeCUI.text);
colours.background = window.GetColourCUI(ColourTypeCUI.background);
}

if (colour_mode == 0) {
solid_colour = colours.text == colours.highlight;

if (!solid_colour) {
brush.Stops = [
[0.0, colours.text],
[1.0, colours.highlight],
]
brush_str = JSON.stringify(brush);
}
} else {
solid_colour = false;
brush.Stops = rainbow_stops;
brush_str = JSON.stringify(brush);
}
}

function clear_graph() {
  for (var c = 0; c < ch_count; ++c) {
    RMS_levels[c] = 0;
    Peak_levels[c] = 0;
    Peak_falldown[c] = 0;
  }
window.Repaint();
}

function update_graph() {
  var cur_time = fb.PlaybackTime;
  if (cur_time > rms_window) {
    var chunk = fb.GetAudioChunk(rms_window);
    if (chunk) {
      ch_count = chunk.ChannelCount;
      ch_config = chunk.ChannelConfig;
      var data = chunk.Data.toArray();
      var frame_len = chunk.SampleCount;
      if (data && ch_count > 0 && frame_len > 0) {
        var old_count = Peak_levels.length;
        RMS_levels.length = ch_count;
        Peak_levels.length = ch_count;
        Peak_falldown.length = ch_count;
        if (old_count < ch_count) {
          for (var c = old_count; c < ch_count; ++c) {
            Peak_levels[c] = 0;
            Peak_falldown[c] = 0;
          }
        }
        for (var c = 0; c < ch_count; ++c) {
          var sum = 0, peak = 0;
          for (var i = c; i < data.length; i += ch_count) {
            var s = Math.abs(data[i]);
            if (s > peak) peak = s;
            sum += s * s;
          }
          RMS_levels[c] = Math.sqrt(sum/frame_len);
          if (peak >= Peak_levels[c]) {
            Peak_levels[c] = peak;
            Peak_falldown[c] = 0;
          } else {
            if (++Peak_falldown[c] > peak_hold) Peak_levels[c] *= peak_fall_mul;
          }
        }
        window.Repaint();
      }
    }
  }
}

function to_db(num) {
return 20 * Math.log(num) / Math.LN10;
}

function clamp(value, min, max) {
if (value < min) return min;
if (value > max) return max;
return value;
}

function start_timer() {
if (!timer_id) {
timer_id = window.SetInterval(update_graph, timer_interval);
}
}

function stop_timer() {
if (timer_id) {
window.ClearInterval(timer_id);
}

timer_id = 0;
}

function on_colours_changed() {
update_colours();
window.Repaint();
}

var dBrange = maxDB - minDB;

function channel_name(ch) {
  if (ch < ChannelNames.length) {
    if (ch_config) {
      for (var i = 0, idx = 0; i < ChannelNames.length; ++i) {
        if (ch_config & (1 << i)) {
          if (idx == ch) return ChannelNames[i];
          idx++;
        }
      }
    } else {
      return ChannelNames[ch];
    }
  }
  var name = "Ch" + (ch+1).toString();
  return name;
}

function on_mouse_rbtn_up(x, y) {
var menu = window.CreatePopupMenu();
var colour_menu = window.CreatePopupMenu();

colour_menu.AppendMenuItem(MF_STRING, 1, 'UI');
colour_menu.AppendMenuItem(MF_STRING, 2, 'Rainbow');
colour_menu.CheckMenuRadioItem(1, 2, colour_mode +1);
colour_menu.AppendTo(menu, MF_STRING, 'Bar Colours');
menu.AppendMenuSeparator();
menu.AppendMenuItem(MF_STRING, 10, 'Configure...');

var idx = menu.TrackPopupMenu(x, y);
menu.Dispose();

switch (idx) {
case 0:
break;
case 1:
case 2:
colour_mode = idx -1;
window.SetProperty("2K3.METER.COLOUR.MODE", colour_mode);
update_colours();
window.Repaint();
break;
case 10:
window.ShowConfigure();
break;
}

return true;
}

function on_paint(gr) {
gr.Clear(colours.background);
  if (wh < 1 || ww < 1) return;

  var hide_ch_labels = false;
  var hide_db_labels = false;
  var bar_pad_left = 3*8;
  var bar_pad_right = 3*8;
  var bar_pad_top = 5;
  var bar_pad_bottom = 30;
  var bar_height = 0;
  var bar_width = 0;

bar_height = Math.floor((wh - bar_pad_top - bar_pad_bottom) / ch_count);

  if (bar_height < 8) { // bars are too thin for channel labels, hide them
    hide_ch_labels = true;
  }
  if (bar_height < 4) { // bars are too thin for dB scale too
    hide_ch_labels = true;
    hide_db_labels = true;
    bar_pad_top = 0;
    bar_pad_bottom = 0;
    bar_pad_left = 0;
    bar_pad_right = 0;
    bar_height = Math.floor(wh / ch_count);
  }
  if (bar_height < 2 || bar_height * ch_count > wh) { // bars won't fit in the window, downmix to mono
    var rms_sum = 0;
    var peak_sum = 0;
    for (var c = 0; c < ch_count; ++c) {
      rms_sum += RMS_levels[c] * RMS_levels[c];
      peak_sum += Peak_levels[c];
    }
    RMS_levels[0] = Math.sqrt(rms_sum/ch_count);
    Peak_levels[0] = peak_sum/ch_count;
    ch_count = 1;
    hide_ch_labels = true;
    hide_db_labels = true;
    bar_pad_top = 0;
    bar_pad_bottom = 0;
    bar_pad_left = 0;
    bar_pad_right = 0;
    bar_height = Math.floor(wh / ch_count);
  }

  bar_width = ww - bar_pad_left - bar_pad_right;

// labels
  if (!hide_db_labels) {
    var db_spacing = 5;
    if (dBrange < db_spacing) db_spacing = 1;
    if (ww * db_spacing / dBrange < 10*8) {
      db_spacing = ((10*8 * dBrange) / ww);
      db_spacing -= (db_spacing % 5);
    }

    gr.FillRectangle(bar_pad_left, bar_pad_top + (bar_height * ch_count) + 5, bar_width, 1, colours.text);

    for (var i = minDB, j = 0; i <= maxDB; i += db_spacing, j++) {
      gr.WriteText(i + "dB", font_t, colours.text, bar_pad_left / 2 + bar_width * j / (dBrange/db_spacing), wh - 20, ww, wh);
    }
  }

  if (!hide_ch_labels) {
    for (var c = 0; c < ch_count; ++c) {
      gr.WriteText(channel_name(c), font_t, colours.text, 4, bar_pad_top + (bar_height * c) + bar_height / 2 - 8, ww, wh);
    }
  }

// bars
var bar_colour = solid_colour ? colours.text : brush_str;
  for (var c = 0; c < ch_count; ++c) {
    if (RMS_levels[c]) {
      var rms_db = clamp(to_db(RMS_levels[c]) + rms_db_offset, minDB, maxDB);
      var width = Math.round(bar_width * (rms_db - minDB) / dBrange);
      gr.FillRectangle(bar_pad_left, bar_pad_top + (bar_height * c), width, bar_height - 1, bar_colour);
    }
    if (peak_bar_width > 0 && Peak_levels[c] > 0) {
      var peak_db = clamp(to_db(Peak_levels[c]), minDB, maxDB);
      if (peak_db > minDB) {
        var peak_pos = Math.round(bar_width * (peak_db - minDB) / dBrange);
        gr.FillRectangle(bar_pad_left + peak_pos - peak_bar_width / 2, bar_pad_top + (bar_height * c), peak_bar_width, bar_height - 1, bar_colour);
      }
    }
  }
}

function on_playback_new_track(handle) {
start_timer();
}

function on_playback_pause(state) {
state ? stop_timer() : start_timer();
}

function on_playback_stop(reason) {
if (reason != 2) {
stop_timer();
}
  clear_graph();
}

function on_size() {
ww = window.Width;
wh = window.Height;

brush.End = [ww, 0];
brush_str = JSON.stringify(brush);
}

function get_initial_track_info() {
  if (ch_count > 0) return;
  var handle = fb.GetNowPlaying();
  if (!handle) handle = fb.GetFocusItem();
  if (!handle) return;
 
  var info = handle.GetFileInfo();
  if (info) {
    var idx_ch_count = info.InfoFind("channels");
    var idx_ch_config = info.InfoFind("WAVEFORMATEXTENSIBLE_CHANNEL_MASK");
    if (idx_ch_count >= 0) {
      ch_count = Number(info.InfoValue(idx_ch_count));
    }
    if (idx_ch_config >= 0) {
      ch_config = Number(info.InfoValue(idx_ch_config));
    }
    info.Dispose();
  }
 
  handle.Dispose();
}

update_colours();
if (fb.IsPlaying) start_timer();
get_initial_track_info();

edit: obviously this superior version will be included in the next component release. Thanks Case.

Re: JScript Panel script discussion/help

Reply #1723
Here's first attempt of added block mode. I used marc2k3's menu code and added the option there.

The first "simple" block mode tries to mimic the original code, it has a static number of blocks. I'm not a fan of that mode as the indicators don't align nicely with the dB scale. There's also a second block mode that aligns the blocks steadily on the dB axel.

The smooth peak indicator feels a bit weird in this mode.

Here's the code:
Code: [Select]
// ==PREPROCESSOR==
// @name "JSP3 VU Meter"
// @import "%fb2k_component_path%helpers.txt"
// ==/PREPROCESSOR==

// User adjustable settings:
var timer_interval = 1000 / 60; // in ms (default: 60 fps update rate)
var rms_window = 50 / 1000; // in seconds (default: 50 ms)
var peak_hold = 20; // in frames
var peak_fall_mul = 0.99;
var peak_bar_width = 3; // in pixels
var minDB = -60; // minimum dB on the meter (meter range)
var maxDB = 5;   // maximum dB on the meter (meter range)
var rms_block_count = 20; // number of blocks in meter style 1
var rms_block_db = 2.5;   // dBs per block in meter style 2
var rms_3db = false; // use AES +3dB RMS mode

// End of user adjustable settings
// -------------------------------

var font_t = CreateFontString("Segoe UI", 8);
var colour_mode = window.GetProperty("2K3.METER.COLOUR.MODE", 1); // 0 UI, 1 Rainbow
var meter_style = window.GetProperty("2K3.METER.STYLE", 0); // 0: smooth, 1: blocks-by-count, 2: blocks-by-dB
var solid_colour = false;
var RMS_levels = [], Peak_levels = [], Peak_falldown = [];
var ch_count = 0;
var ch_config = 0;
var ww = 0, wh = 0;
var timer_id = 0;
var rms_db_offset = 0;
if (rms_3db) rms_db_offset = 20 * Math.log(Math.sqrt(2)) / Math.LN10; // 3.01029995663981 dB
var dBrange = maxDB - minDB;

var rainbow_stops = [
  [ 1.0, RGB(227, 9, 64) ],
  [ 0.66, RGB(231, 215, 2) ],
  [ 0.33, RGB(15, 168, 149) ],
  [ 0.0, RGB(19, 115, 232) ]
];

var brush = {
  Stops : [],
  Start : [0, 0], // x and y values
  End : [0, 0], // x and y values
};
var brush_str = "";

var colours = {
  text : 0,
  highlight : 0,
  background : 0,
};

var ChannelNames = [ "FL", "FR", "FC", "LFE", "BL", "BR", "FCL", "FCR", "BC", "SL", "SR", "TC", "TFL", "TFC", "TFR", "TBL", "TBC", "TBR" ];

function get_colour_for_db(db) {
  if (colour_mode != 0) {
    var f = db / dBrange;
    for (var i = 0; i < rainbow_stops.length-1; ++i) {
      var a = rainbow_stops[i][0];
      var b = rainbow_stops[i+1][0];
      var ca = rainbow_stops[i][1];
      var cb = rainbow_stops[i+1][1];
      if (b > a) {
        var t = a; a = b; b = t;
        var tc = ca; ca = cb; cb = tc;
      }
      if (a >= f && f >= b) {
        return blendColours(cb, ca, (f - b) / (a - b));
      }
    }
  }
  if (!solid_colour) {
    return blendColours(colours.text, colours.highlight, db / dBrange);
  } else {
    return colours.text;
  }
}

function update_colours() {
  if (window.IsDefaultUI) {
    colours.text = window.GetColourDUI(ColourTypeDUI.text);
    colours.highlight = window.GetColourDUI(ColourTypeDUI.highlight);
    colours.background = window.GetColourDUI(ColourTypeDUI.background);
  } else {
    colours.text = window.GetColourCUI(ColourTypeCUI.text);
    colours.highlight = window.GetColourCUI(ColourTypeCUI.text);
    colours.background = window.GetColourCUI(ColourTypeCUI.background);
  }

  if (colour_mode == 0) {
    solid_colour = colours.text == colours.highlight;

    if (!solid_colour) {
      brush.Stops = [
         [0.0, colours.text],
         [1.0, colours.highlight],
      ]
      brush_str = JSON.stringify(brush);
    }
  } else {
    solid_colour = false;
    brush.Stops = rainbow_stops;
    brush_str = JSON.stringify(brush);
  }
}

function clear_graph() {
  for (var c = 0; c < ch_count; ++c) {
    RMS_levels[c] = 0;
    Peak_levels[c] = 0;
    Peak_falldown[c] = 0;
  }
  window.Repaint();
}

function update_graph() {
  var cur_time = fb.PlaybackTime;
  if (cur_time > rms_window) {
    var chunk = fb.GetAudioChunk(rms_window);
    if (chunk) {
      ch_count = chunk.ChannelCount;
      ch_config = chunk.ChannelConfig;
      var data = chunk.Data.toArray();
      var frame_len = chunk.SampleCount;
      if (data && ch_count > 0 && frame_len > 0) {
        var old_count = Peak_levels.length;
        RMS_levels.length = ch_count;
        Peak_levels.length = ch_count;
        Peak_falldown.length = ch_count;
        if (old_count < ch_count) {
          for (var c = old_count; c < ch_count; ++c) {
            Peak_levels[c] = 0;
            Peak_falldown[c] = 0;
          }
        }
        for (var c = 0; c < ch_count; ++c) {
          var sum = 0, peak = 0;
          for (var i = c; i < data.length; i += ch_count) {
            var s = Math.abs(data[i]);
            if (s > peak) peak = s;
            sum += s * s;
          }
          RMS_levels[c] = Math.sqrt(sum/frame_len);
          if (peak >= Peak_levels[c]) {
            Peak_levels[c] = peak;
            Peak_falldown[c] = 0;
          } else {
            if (++Peak_falldown[c] > peak_hold) Peak_levels[c] *= peak_fall_mul;
          }
        }
        window.Repaint();
      }
    }
  }
}

function to_db(num) {
  return 20 * Math.log(num) / Math.LN10;
}

function clamp(value, min, max) {
  if (value < min) return min;
  if (value > max) return max;
  return value;
}

function start_timer() {
  if (!timer_id) {
    timer_id = window.SetInterval(update_graph, timer_interval);
  }
}

function stop_timer() {
  if (timer_id) {
    window.ClearInterval(timer_id);
  }

  timer_id = 0;
}

function on_colours_changed() {
  update_colours();
  window.Repaint();
}

function channel_name(ch) {
  if (ch < ChannelNames.length) {
    if (ch_config) {
      for (var i = 0, idx = 0; i < ChannelNames.length; ++i) {
        if (ch_config & (1 << i)) {
          if (idx == ch) return ChannelNames[i];
          idx++;
        }
      }
    } else {
      return ChannelNames[ch];
    }
  }
  var name = "Ch" + (ch+1).toString();
  return name;
}

function on_mouse_rbtn_up(x, y) {
  var menu = window.CreatePopupMenu();
  var colour_menu = window.CreatePopupMenu();
  var style_menu = window.CreatePopupMenu();
  if (!menu || !colour_menu || !style_menu) {
    if (menu) menu.Dispose();
    if (colour_menu) colour_menu.Dispose();
    if (style_menu) style_menu.Dispose();
    return false;
  }

  colour_menu.AppendMenuItem(MF_STRING, 1, 'UI');
  colour_menu.AppendMenuItem(MF_STRING, 2, 'Rainbow');
  colour_menu.CheckMenuRadioItem(1, 2, colour_mode + 1);
  colour_menu.AppendTo(menu, MF_STRING, 'Bar Colours');
  style_menu.AppendMenuItem(MF_STRING, 3, 'Smooth');
  style_menu.AppendMenuItem(MF_STRING, 4, 'Blocks (simple)');
  style_menu.AppendMenuItem(MF_STRING, 5, 'Blocks (per dB range)');
  style_menu.CheckMenuRadioItem(3, 5, meter_style + 3);
  style_menu.AppendTo(menu, MF_STRING, 'Meter style');

  menu.AppendMenuSeparator();
  menu.AppendMenuItem(MF_STRING, 10, 'Configure...');

  var idx = menu.TrackPopupMenu(x, y);
  menu.Dispose();
  colour_menu.Dispose();
  style_menu.Dispose();

  switch (idx) {
  case 0:
    break;
  case 1:
  case 2:
    colour_mode = idx -1;
    window.SetProperty("2K3.METER.COLOUR.MODE", colour_mode);
    update_colours();
    window.Repaint();
    break;
  case 3:
  case 4:
  case 5:
    meter_style = idx -3;
    window.SetProperty("2K3.METER.STYLE", meter_style);
    window.Repaint();
    break;
  case 10:
    window.ShowConfigure();
    break;
  }

  return true;
}

function on_paint(gr) {
  gr.Clear(colours.background);
  if (wh < 1 || ww < 1) return;

  var hide_ch_labels = false;
  var hide_db_labels = false;
  var bar_pad_left = 3*8;
  var bar_pad_right = 3*8;
  var bar_pad_top = 5;
  var bar_pad_bottom = 30;
  var bar_height = 0;
  var bar_width = 0;

  bar_height = Math.floor((wh - bar_pad_top - bar_pad_bottom) / ch_count);

  if (bar_height < 8) { // bars are too thin for channel labels, hide them
    hide_ch_labels = true;
  }
  if (bar_height < 4) { // bars are too thin for dB scale too
    hide_ch_labels = true;
    hide_db_labels = true;
    bar_pad_top = 0;
    bar_pad_bottom = 0;
    bar_pad_left = 0;
    bar_pad_right = 0;
    bar_height = Math.floor(wh / ch_count);
  }
  if (bar_height < 2 || bar_height * ch_count > wh) { // bars won't fit in the window, downmix to mono
    var rms_sum = 0;
    var peak_sum = 0;
    for (var c = 0; c < ch_count; ++c) {
      rms_sum += RMS_levels[c] * RMS_levels[c];
      peak_sum += Peak_levels[c];
    }
    RMS_levels[0] = Math.sqrt(rms_sum/ch_count);
    Peak_levels[0] = peak_sum/ch_count;
    ch_count = 1;
    hide_ch_labels = true;
    hide_db_labels = true;
    bar_pad_top = 0;
    bar_pad_bottom = 0;
    bar_pad_left = 0;
    bar_pad_right = 0;
    bar_height = Math.floor(wh / ch_count);
  }

  bar_width = ww - bar_pad_left - bar_pad_right;

  // labels
  if (!hide_db_labels) {
    var db_spacing = 5;
    if (dBrange < db_spacing) db_spacing = 1;
    if (ww * db_spacing / dBrange < 10*8) {
      db_spacing = ((10*8 * dBrange) / ww);
      db_spacing -= (db_spacing % 5);
    }

    var y = bar_pad_top + (bar_height * ch_count) + 5;
    gr.FillRectangle(bar_pad_left, y, bar_width, 1, colours.text);

    for (var i = minDB, j = 0; i <= maxDB; i += db_spacing, j++) {
      var x = bar_pad_left + (bar_width * j / (dBrange / db_spacing));
      gr.WriteText(i + "dB", font_t, colours.text, x - (bar_pad_left / 2), wh - 20, ww, wh);
      gr.DrawLine(x, y-2, x, y+2, 1, colours.text);
    }
  }

  if (!hide_ch_labels) {
    for (var c = 0; c < ch_count; ++c) {
      gr.WriteText(channel_name(c), font_t, colours.text, 4, bar_pad_top + (bar_height * c) + bar_height / 2 - 8, ww, wh);
    }
  }

  // bars
  var bar_colour = solid_colour ? colours.text : brush_str;
  var block_count = rms_block_count;
  if (meter_style == 2 && rms_block_db > 0) block_count = Math.floor(dBrange / rms_block_db);
  if (block_count < 1) block_count = 1;
  var block_width = bar_width / block_count;
  var block_pad = Math.ceil(block_width * 0.03);
  if (block_pad < 1) block_pad = 1;

  for (var c = 0; c < ch_count; ++c) {
    if (RMS_levels[c]) {
      var rms_db = clamp(to_db(RMS_levels[c]) + rms_db_offset, minDB, maxDB);

      if (meter_style == 0) { // smooth mode
        var width = Math.round(bar_width * (rms_db - minDB) / dBrange);
        gr.FillRectangle(bar_pad_left, bar_pad_top + (bar_height * c), width, bar_height - 1, bar_colour);
      } else { // block mode
        var blocks = Math.round(block_count * (rms_db - minDB) / dBrange);
        for (var i = 0; i < blocks; ++i) {
          gr.FillRectangle(bar_pad_left + (i * block_width) + block_pad, bar_pad_top + (bar_height * c), block_width - block_pad*2, bar_height - 1, get_colour_for_db(i * dBrange / block_count));
        }
      }
    }
    if (peak_bar_width > 0 && Peak_levels[c] > 0) {
      var peak_db = clamp(to_db(Peak_levels[c]), minDB, maxDB);
      if (peak_db > minDB) {
        var peak_pos = Math.round(bar_width * (peak_db - minDB) / dBrange);
        gr.FillRectangle(bar_pad_left + peak_pos - peak_bar_width / 2, bar_pad_top + (bar_height * c), peak_bar_width, bar_height - 1, bar_colour);
      }
    }
  }
}

function on_playback_new_track(handle) {
  start_timer();
}

function on_playback_pause(state) {
  state ? stop_timer() : start_timer();
}

function on_playback_stop(reason) {
  if (reason != 2) {
    stop_timer();
  }
  clear_graph();
}

function on_size() {
  ww = window.Width;
  wh = window.Height;

  brush.End = [ww, 0];
  brush_str = JSON.stringify(brush);
}

function get_initial_track_info() {
  if (ch_count > 0) return;
  var handle = fb.GetNowPlaying();
  if (!handle) handle = fb.GetFocusItem();
  if (!handle) return;

  var info = handle.GetFileInfo();
  if (info) {
    var idx_ch_count = info.InfoFind("channels");
    var idx_ch_config = info.InfoFind("WAVEFORMATEXTENSIBLE_CHANNEL_MASK");
    if (idx_ch_count >= 0) {
      ch_count = Number(info.InfoValue(idx_ch_count));
    }
    if (idx_ch_config >= 0) {
      ch_config = Number(info.InfoValue(idx_ch_config));
    }
    info.Dispose();
  }

  handle.Dispose();
}

update_colours();
if (fb.IsPlaying) start_timer();
get_initial_track_info();

Edit: added a fallback to get_colour_for_db in case rainbow range blend fails for some reason. Changed default block mode 2 to use 2.5 dB blocks.
Edit 2: restored block mode to allow fractional pixel sizes. Added small notches to dB axle. Centered the blocks.

Re: JScript Panel script discussion/help

Reply #1724
Thx Case, but I can't find how to change the background color