I am trying to optimize my oscilloscope script while still keeping it visually interesting. I recall having a discussion a few months ago about this but I can't find the original post.
Anyways here is an oscilloscope script for drawing the analyzer to a path in a panel. There are a lot of adjustable variables for optimizing it (buffer size, frame rate, frame count, etc.) There is also optional opacity settings (glow effect) and ghost trails (though these consume a lot more CPU). Hope you guys like it and let me know if you can think of any optimizations or better visual performance for it!
Here is an A/B comparison of it with the floating tile display
HiseSnippet 3084.3oc2as0baabEFTRH0Vw4Vmz59HFMsy.YISSRcupNQzTjRbh3kRRY0LdznYIvJx0BDfE.zxJdzL8WRm9SnO1eF8w9X+Ij+AsmcWbYW.PKZEeIN7AEuWNm8rmy24xtKRaWGCrmmiqRtE6c0XrRt6o18Ja+gUFhH1J02WI2mp53Y7PSb+ICTdxUiQddXSkb4l+.5Dxc2ETX+9wu8IHKjsANtKEkm5PLvGQFQ7i6s8deGwxpFxD2iLRX1quWcCG6JNVNS.gYd0BJiQFWfFfahnSaNUkCQdCUx8.09EMJfWquAB0GsCd6s2n+5au0NHbQyhaswl6rQgc5u4FasogRtOopIw2wsqOxG6ojagm3XdU2gNWZyWfmR7H8svzFEU5BqLu6ZNVlzsHsWkJCIVlsC0RdJ.SaGqylmqy9Z0FDSRT+w5tujMfVLEhJvbyIKdyKIdEEEuBBhWFhTNAQZAtH8UpcMbIi8iGgaKqa6icOGA1IQQgOWk4962WshCLCa+7iPWfq4BMhnPe8BEVUC9yx6tnMXT7.CDVqkmAXOAHhyX7huZQM3GXG870dAxUqrMx5JOraQsGqwfU4Gf8qd94XCe8khFbIfgxz8jiqUqZGQh1m3M1Bc0Sl.D61EvHf7bCb3rVcq.rfyqT7PuPJZFaacFrc.ZBUB.QUbFM1wFZnuTv3YrZ9.T1Enqp8.hMNugKFLoT7saq9OmtYSKeS78crKN00JX7vkRfZ5N6npMOn2g.wqWXmM2US6QORqC1bhA1TqOa2o4Q9Ar14NtZ8w9f8SqR6iiYjKd.iMs6zpc0N8pWsKvpWsDWwbD1df+vk9iBKzpZK0bxH.Vaais7fgJdMWrBzx48v9cH1C3L.fbiwt9Drmt7hrbJhJa3SdAV22cBN8FsW0FsOKBIv4cfpUOV1VdWIh1uS4SlIhRtZ05TtQ0ypz53l8.BWa2HE04t.VmyHOXjmcZ7PFSbcAqUM5LpaaheILdAg8AXVpfLFBVkwH+gZNLrfWD4ifvDsoCDCB3RJsyPHCch9tHh0MNS1enlbcBSNzHZ+IwsEzwJqrLaVb+T17E1c4GOwan9T0YAKy0x3nQnA1D+IlX5ZleCQbdixGzrdui2u5YcazpECuVH+NRyndyyhlEa3BaHq+1GaPFg7IN1hF4pUp2nbupmUqbkdsnF50JsaJTP0mVsy2eVyyXp.JBuXBiZEmIzXaILZAdYkq7cGzATa6CJuiZcLcQJ7xBA+D2CmT9oUq0pSCw4UtPsZkqNk408vx625D171Dl21Sie85Tt9QroslD6BUMG4bIH7u.YMA6I5pCNePqQzjwBr8fiZApDvunBEeS83jLDs5UuUS9R1MiwqzpSypchsiqkWRlaWt2gm06v5U9tlU6RouT9DBaiHXxjwl.vh0Ow1BBWpc9DaCpENXnnopmDrZ4XfrzrwWBSAVDAecZzSA51MhDQ3Y7+9AowlqDx2GnoWLeAsGlZJSgqM.WPHa4K0k.yqFOmz9Mf9fEyPyCR+Ck3.Klis0Ufxb7UZlbDOMngCwNHbQRMEkPbEgvOozUICDD6HSauxiS5DsbDkICK7rTg4N8YjSk0+PGwZmn+QVwG0S24JZEWV6OHEpJCM19tnKgbLQ5fL0KlvjDqHQevpPrWHqEydjTGMH+4vLKaYomxce4L1Nb32kDSVfXFao3tSncHh43yaHlLXnu3DOj0S5YNhX98v7BH3QZkl5Z6A+AadXHmCn3AAXPhME4x2oIWCphgtHr05gRL5QkRN49PfQSVxNfaLJWkusWUhvrL4AFJdJKNLxSibtF1FA0xZFMOnKcofNw3uWIiDo3XFepaxyupE0JQ5sv9CyxkMCCW8Xd93z.0kSQRPvNehcXjQweo5fqHo91YB4eX7lXEwMQl9Ao4qCT5Mw+JVFys.loSWnGIynG.is4LHoQEWj2vBibEgmI.enQisjh38PFbSF7IE4HbSd5yJb5xYv3301yG452DeY2I8Y0zPgwAPU1xt7LrShB4kHz1sI.X13lXkAKTFAz4BEHApblWxtYR2OI0GIS0mrJjFIrmi9KuYM20uInBWZvfJNt1fDoKUGvrXSFPKymepdc9+wK+kD+gksFODoKWqypQ3ZJ1cir1wCxSCGwPHQR3pAwqVENBSugDiKrgC7BGRIQQILddcBdFqItd5wxn0oypfWH+Oux8ztLbzgnCOXwugroSOb+LAYDYXB+rH4bFbuV7ssajr6yapaysXum1IIZ+ey9FYX+indlbADgMGX4boF+dNjx3ITD9zzSynCC+PDqlQl+j5.AWlvMzL6w.kwe8qWGIJtINIj.g2ZY3QktNc4yA2CCckaCbyuiyDHsLVOrJP8AKq8pXQLqZB8GR7jJQmO8qWV9TKrqvQqVX0kAmMlUmNTJ5xYVBpiMipJHKq9HiKRUXd30ePYRGLxL35nDtsir.U+zJmWtRcfKBqV1ktm5nXwyIiydjsWP2KHiCKAz2QiLZrqyKvoNeZz4MBNO9Jqr6hoJTK7r5eyiy5v8SyYJDo3hGSAJIqsI6aAHFscs.ZfcEeTHmr0Mg0VD+jFXDbodzqvw0wR2H759VkeJ9jHE5lmM.sD0hu1Bjuwa7Y50zvn+4b5edBj0ymo5jRcfQxoO64mxuNmoktMUwBA5WZdJlFUesBYF4Aa4gyjPmwb5BiWHXIBz67PULcuf8S1n.TesfEqoiOtkM3C+pEu6hfPjbnyOOywBXlEUbxX3.Ac5DpaOYTeraDtHXhJ4VP9ICTm9SFH9hFF76LTXhN10AG6Viw1S6cNTBtnQ5yKDHUvT8YOtvmG73BbUmBwTI2cUCziJLQV7ggTNt99HeTHi.dFeCwPe6ieAw.ye0h6ptO16BvTBKUzkgqj6S3K6mE9lFzCTyV0EUCuSdkWJ9vSWI1fUbQbGWsG+Xyw8P1SQXwtmeva5vWrZVNHH6xfdDPuRWyuPUrqouvj8lgENPgv28egZ4IlDmvW0Po+fvmC6dpqWZ6har9FaTZMEhOdjz.aUbss1oz1BCTJXjRat45EJrthO9k9hOsFydj6HUZzfkhr50Mg7uBOpxpzQYknRuxeVqZ.n14xSbbuf85OP+minNjWqHpB+Uy.LYVsW+s+bJ012rmI9bzDK+mxgZL.b5m.6KUa3XNwB4K+hbz2sLX.vmS5YvnO0ksGb.DQ36asmoaVEwuRsMw2XX1x3bYHifm06BYL3wM+LUdgrwB3Bp09KuidIy4DV+6D8fzDZs5GDt72SsAxCxXy5X5uHshx9XKjjZ5DYGxueuLdy551u.BM01wB4lTIO+LuKK952kKLyHA5VR6mBZcgYyT75ey6YE49qUYp7OlDXFj3iGA9KUgje+LPb4G4Z.b5SWBDIWs4jQ7GkO7Qhg9xMGsJBd6Bz1TApK11j03+A+BFrHsctfAKFNnnJQkqRVTMJ2H+qYHJWkjdH3iDf7CBN1+y8Z6BEZfuLnPiftuydu07peeqS9b9N4Sgvi1X12IASo7aYs0NAAmz5.rM1kBfJNs3j68u9r+yr9k6Ldl+xcZY3CKeOWjs2XGOIF2EOhzCJSvSryi8v0bw+0NTHsX+UbPtYNzO9s0fMYlzzE4Owk4aTdD8jcutH3KLa15a3CBZg2UePPumir7AqvHoueo6FJi7r9UgzwVvoEhxuvJ7KrWYbcCGamwCcrIFhF8NXvebv.rqnrm4FpruObjvnd9ww60AagQB.3l+i8NB.dHWPOguk5hhuweKWYZu9cpbwkcGUe7j.60Vb67yTwsu0k2O.E599NYw8CSVPyKDmr32vZSuMtojqXN4Zp+2uyyUTLyjER8lfhRYRQI4CB3OwVl07tjlEUUTCTEEEKOHryRhcB0fIwLnsDmZPDNc6+8aqxd66tXXmaxuHZpdVzkr8D3PzrRQKJRXb2kD69PjqIXCMdKjZSNluv7+n3Ce8Wz41J8tL2lxWmJ2lxu+Wf419vdYMy+K5KqY189f3g+71JdeUHtbIMloT6Cis78QAAuOViQHCWmyL3uX.0o+Nrdf8sM6+QNtqZCZasTOTfhxHHkwYFFxrJEgktsDt1skv0usDtwskvMusDt0skvsuYBoYzKOw2g+4XSKyocU9CIjiWgCyMQ4+iYkPuv
Content.makeFrontInterface(400, 400);
namespace Oscilloscope
{
const var Analyser1 = Synth.getEffect("Analyser1");
const var BUFFER = Synth.getDisplayBufferSource("Analyser1");
const var BUF_OSC = BUFFER.getDisplayBuffer(0);
const var pnl_Osc = Content.getComponent("pnl_Osc1");
const var timer = Engine.createTimerObject();
const var Button1 = Content.getComponent("Button1");
const BUF_LENGTH = 4096; // Reduced buffer size for better CPU
reg BUF_PROPERTIES = {"BufferLength": BUF_LENGTH, "NumChannels": 1};
BUF_OSC.setRingBufferProperties(BUF_PROPERTIES);
BUF_OSC.setActive(true);
const TEMP_BUFFER = Buffer.create(BUF_LENGTH);
const DRAW_BUFFER = Buffer.create(BUF_LENGTH);
const FRAME_COUNT = 3;
reg frameBuffers = [];
reg currentFrameIndex = 0;
// Cached path objects
reg mainPath = Content.createPath();
reg trailPath = Content.createPath();
for (i = 0; i < FRAME_COUNT; i++)
{
frameBuffers.push(Buffer.create(BUF_LENGTH));
}
reg magnitude = 0.5;
const MAGNITUDE_SMOOTH = 0.9;
const MIN_MAGNITUDE = 0.05;
// Decimation
const DECIMATE_FACTOR = 32;
const DRAW_EVERY_N_FRAMES = 1;
reg frameCounter = 0;
const BACKGROUND_COLOUR = 0x00000000;
const WAVEFORM_COLOUR = 0xA0FFAE00;
const WAVEFORM_SHADOW = 0x60FF8000;
const WAVEFORM_TRAIL = 0x30FFAE00;
// Lower values for better performance
const GLOW_EFFECT = true;
const MOTION_TRAILS = true;
const CORNER_SMOOTH = 3.0;
const PATH_THICKNESS = 2.0;
// Magnitude update
inline function updateMagnitude()
{
local newMag = DRAW_BUFFER.getMagnitude();
magnitude = magnitude * MAGNITUDE_SMOOTH + newMag * (1.0 - MAGNITUDE_SMOOTH);
magnitude = Math.max(MIN_MAGNITUDE, magnitude);
}
// Frame storage - only copy decimated points
inline function storeCurrentFrame()
{
for (i = 0; i < BUF_LENGTH; i += DECIMATE_FACTOR)
frameBuffers[currentFrameIndex][i] = DRAW_BUFFER[i];
currentFrameIndex = (currentFrameIndex + 1) % FRAME_COUNT;
}
// Drawing function
inline function drawOscilloscope(g, panel, mag)
{
g.fillAll(BACKGROUND_COLOUR);
local width = panel.getWidth();
local height = panel.getHeight();
local midY = height / 2;
local scaledHeight = height * Math.min(1.0, mag);
local drawY = midY - scaledHeight/2;
local bounds = [0, drawY, width, scaledHeight];
// Draw trail frames if enabled
if (MOTION_TRAILS)
{
for (frameIdx = 0; frameIdx < FRAME_COUNT; frameIdx++)
{
if (frameIdx == currentFrameIndex)
continue;
local age = (currentFrameIndex - frameIdx + FRAME_COUNT) % FRAME_COUNT;
local opacity = 0.7 - (age / FRAME_COUNT) * 0.6;
trailPath.clear();
local sample = Math.max(-1.0, Math.min(1.0, frameBuffers[frameIdx][0]));
trailPath.startNewSubPath(0, midY - sample);
for (i = DECIMATE_FACTOR; i < BUF_LENGTH; i += DECIMATE_FACTOR)
{
local x = (i / BUF_LENGTH) * width;
sample = Math.max(-1.0, Math.min(1.0, frameBuffers[frameIdx][i]));
trailPath.lineTo(x, midY - sample);
}
trailPath.roundCorners(CORNER_SMOOTH);
g.setColour(Colours.withAlpha(WAVEFORM_TRAIL, opacity * 0.5));
g.drawPath(trailPath, bounds, {"Thickness": PATH_THICKNESS * 0.5});
}
}
// Draw main path
mainPath.clear();
local currentFrame = frameBuffers[currentFrameIndex];
local sample = Math.max(-1.0, Math.min(1.0, currentFrame[0]));
mainPath.startNewSubPath(0, midY - sample);
for (i = DECIMATE_FACTOR; i < BUF_LENGTH; i += DECIMATE_FACTOR)
{
local x = (i / BUF_LENGTH) * width;
sample = Math.max(-1.0, Math.min(1.0, currentFrame[i]));
mainPath.lineTo(x, midY - sample);
}
mainPath.roundCorners(CORNER_SMOOTH);
// Glow Effect
if (GLOW_EFFECT)
{
g.setColour(Colours.withAlpha(WAVEFORM_SHADOW, Math.min(1.0, mag)));
g.drawPath(mainPath, bounds, {"Thickness": PATH_THICKNESS * 2.0});
}
g.setColour(WAVEFORM_COLOUR);
g.drawPath(mainPath, bounds, {"Thickness": PATH_THICKNESS/2});
}
pnl_Osc.setPaintRoutine(function(g) {
drawOscilloscope(g, this, magnitude);
});
// Timer Function (Buffer copying)
inline function onTimerCallback()
{
BUF_OSC.copyReadBuffer(TEMP_BUFFER);
for (i = 0; i < BUF_LENGTH; i += DECIMATE_FACTOR)
DRAW_BUFFER[i] = TEMP_BUFFER[i];
updateMagnitude();
storeCurrentFrame();
// Skip frames to improve performance
frameCounter++;
if (frameCounter >= DRAW_EVERY_N_FRAMES)
{
pnl_Osc.repaint();
frameCounter = 0;
}
};
timer.setTimerCallback(onTimerCallback);
inline function onButton1Control(component, value)
{
if (value == 1)
{
for (i = 0; i < FRAME_COUNT; i++)
{
for (j = 0; j < BUF_LENGTH; j += DECIMATE_FACTOR)
frameBuffers[i][j] = 0.0;
}
timer.startTimer(30);
}
else
timer.stopTimer();
};
Button1.setControlCallback(onButton1Control);
}