-
Notifications
You must be signed in to change notification settings - Fork 21
colorful_spectrum remastered #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
786316c
0f549d2
52449db
05a1ab8
a3c28ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,63 +1,310 @@ | ||
| /* | ||
| * Plugin: colorify the spectrum analyzer. | ||
| * | ||
| * Plugin: Spectravue Style Spectrum Analyzer (Stable & Uncorrupted) | ||
| * License: MIT | ||
| * Copyright (c) 2023 Stanislav Lechev [0xAF], LZ2SLL | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why removing the copyright? |
||
| */ | ||
|
|
||
| // do not load CSS for this plugin | ||
| Plugins.colorful_spectrum.no_css = true; | ||
|
|
||
| Plugins.colorful_spectrum.init = async function () { | ||
|
|
||
| // Check if utils plugin is loaded | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why removing the comments everywhere? |
||
| if (!Plugins.isLoaded('utils', 0.4)) { | ||
| // try to load the utils plugin | ||
| await Plugins.load('https://0xaf.github.io/openwebrxplus-plugins/receiver/utils/utils.js'); | ||
|
|
||
| // check again if it was loaded successfully | ||
| if (!Plugins.isLoaded('utils', 0.4)) { | ||
| console.error('colorful_spectrum plugin depends on "utils >= 0.4".'); | ||
| return false; | ||
| } else { | ||
| Plugins._debug('Plugin "utils" has been loaded as dependency.'); | ||
| } | ||
| } | ||
|
|
||
| // wait for OWRX to initialize | ||
| // --- CONFIGURATION --- | ||
| const defaultColor = window.SpectrumDefaultColor || 'blue'; | ||
|
|
||
| const defaultOpacity = window.SpectrumBackgroundOpacity !== undefined ? window.SpectrumBackgroundOpacity : 0.0; | ||
|
|
||
| // Define custom Spectravue palettes. | ||
| const colorPalettes = { | ||
| 'waterfall': { dynamic: true }, | ||
| 'blue': { | ||
| stroke: "rgba(0, 255, 255, 1.0)", | ||
| top: "rgba(0, 255, 255, 0.7)", | ||
| bottom: "rgba(0, 0, 150, 0.5)" | ||
| }, | ||
| 'red': { | ||
| stroke: "rgba(255, 30, 30, 1.0)", | ||
| top: "rgba(255, 30, 30, 0.9)", | ||
| bottom: "rgba(100, 0, 0, 0.5)" | ||
| }, | ||
| 'green': { | ||
| stroke: "rgba(0, 255, 0, 1.0)", | ||
| top: "rgba(0, 255, 0, 0.9)", | ||
| bottom: "rgba(0, 80, 0, 0.5)" | ||
| }, | ||
| 'yellow': { | ||
| stroke: "rgba(255, 255, 0, 1.0)", | ||
| top: "rgba(255, 255, 0, 0.9)", | ||
| bottom: "rgba(120, 120, 0, 0.5)" | ||
| }, | ||
| 'pink': { | ||
| stroke: "rgba(255, 0, 150, 1.0)", | ||
| top: "rgba(255, 0, 150, 0.9)", | ||
| bottom: "rgba(100, 0, 50, 0.5)" | ||
| }, | ||
| 'purple': { | ||
| stroke: "rgba(180, 50, 255, 1.0)", | ||
| top: "rgba(180, 50, 255, 0.9)", | ||
| bottom: "rgba(60, 0, 100, 0.5)" | ||
| }, | ||
| 'grey': { | ||
| stroke: "rgba(220, 220, 220, 1.0)", | ||
| top: "rgba(180, 180, 180, 0.8)", | ||
| bottom: "rgba(50, 50, 50, 0.5)" | ||
| } | ||
| }; | ||
|
|
||
| Plugins.utils.on_ready(function () { | ||
| Plugins.utils.wrap_func( | ||
| 'draw', | ||
| function (orig, thisArg, args) { | ||
| return true; | ||
| }, | ||
| function (res, thisArg, args) { | ||
| var vis_freq = get_visible_freq_range(); | ||
| var vis_start = 0.5 - (center_freq - vis_freq.start) / bandwidth; | ||
| var vis_end = 0.5 - (center_freq - vis_freq.end) / bandwidth; | ||
| var data_start = Math.round(fft_size * vis_start); | ||
| var data_end = Math.round(fft_size * vis_end); | ||
| var data_width = data_end - data_start; | ||
| var data_height = Math.abs(thisArg.max - thisArg.min); | ||
| var spec_width = thisArg.el.offsetWidth; | ||
| var spec_height = thisArg.el.offsetHeight; | ||
| if (spec_width <= data_width) { | ||
| var x_ratio = data_width / spec_width; | ||
| var y_ratio = spec_height / data_height; | ||
| for (var x = 0; x < spec_width; x++) { | ||
| var data = (thisArg.data[data_start + ((x * x_ratio) | 0)]); | ||
| var y = (data - thisArg.min) * y_ratio; | ||
| thisArg.ctx.fillRect(x, spec_height, 1, -y); | ||
| if (data) { | ||
| var c = Waterfall.makeColor(data); | ||
| thisArg.ctx.fillStyle = "rgba(" + | ||
| c[0] + ", " + c[1] + ", " + c[2] + ", " + | ||
| (25 + y * 2) + "%)"; | ||
|
|
||
| // --- 1. PRECISE UI INJECTION --- | ||
| function injectUI() { | ||
| if (document.getElementById('webrx-spectrum-color')) return; | ||
|
|
||
| let waterfallSelect = null; | ||
| let allSelects = document.querySelectorAll('select'); | ||
|
|
||
| for (let i = 0; i < allSelects.length; i++) { | ||
| let text = allSelects[i].textContent || allSelects[i].innerText; | ||
| if (text.includes('Turbo') && text.includes('Ocean') && text.includes('Eclipse')) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why checking for specific theme names? |
||
| waterfallSelect = allSelects[i]; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (!waterfallSelect) { | ||
| setTimeout(injectUI, 500); | ||
| return; | ||
| } | ||
|
|
||
| let wrapper = waterfallSelect.parentNode; | ||
| let newWrapper = wrapper.cloneNode(true); | ||
|
|
||
| wrapper.style.display = 'inline-flex'; | ||
| wrapper.style.verticalAlign = 'middle'; | ||
|
|
||
| newWrapper.style.display = 'inline-flex'; | ||
| newWrapper.style.verticalAlign = 'middle'; | ||
| newWrapper.style.marginLeft = '8px'; | ||
| newWrapper.style.width = '115px'; | ||
| newWrapper.style.position = 'relative'; | ||
|
|
||
| let clonedSelects = newWrapper.querySelectorAll('select'); | ||
| for (let j = 1; j < clonedSelects.length; j++) { | ||
| clonedSelects[j].remove(); | ||
| } | ||
|
|
||
| let spectrumSelect = clonedSelects[0]; | ||
| spectrumSelect.id = 'webrx-spectrum-color'; | ||
| spectrumSelect.title = 'Spectrum Fill Color'; | ||
| spectrumSelect.innerHTML = ''; | ||
| spectrumSelect.style.width = '100%'; | ||
| spectrumSelect.style.flex = '1'; | ||
| spectrumSelect.style.display = 'inline-block'; | ||
| spectrumSelect.style.marginLeft = '3px'; | ||
|
|
||
| try { | ||
| let origStyle = window.getComputedStyle(waterfallSelect); | ||
| const stylesToCopy = [ | ||
| 'height', 'minHeight', 'maxHeight', | ||
| 'fontSize', 'fontFamily', 'fontWeight', | ||
| 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', | ||
| 'lineHeight', 'boxSizing', 'border', 'borderRadius' | ||
| ]; | ||
| stylesToCopy.forEach(prop => { | ||
| if(origStyle[prop]) spectrumSelect.style[prop] = origStyle[prop]; | ||
| }); | ||
| } catch(e) { | ||
| console.warn("Colorful Spectrum: Could not copy styles perfectly", e); | ||
| } | ||
|
|
||
| for (const key in colorPalettes) { | ||
| let opt = document.createElement('option'); | ||
| opt.value = key; | ||
| opt.innerText = key.charAt(0).toUpperCase() + key.slice(1); | ||
| spectrumSelect.appendChild(opt); | ||
| } | ||
|
|
||
| let savedColor = localStorage.getItem('owrx-spectrum-color') || defaultColor; | ||
| spectrumSelect.value = savedColor; | ||
|
|
||
| spectrumSelect.addEventListener('change', function(e) { | ||
| localStorage.setItem('owrx-spectrum-color', e.target.value); | ||
| }); | ||
|
|
||
| let opacitySlider = document.createElement('input'); | ||
| opacitySlider.type = 'range'; | ||
| opacitySlider.id = 'webrx-spectrum-opacity'; | ||
| opacitySlider.min = '0'; | ||
| opacitySlider.max = '1'; | ||
| opacitySlider.step = '0.05'; | ||
| opacitySlider.title = 'Background Darkness'; | ||
| opacitySlider.style.width = '100%'; | ||
| opacitySlider.style.flex = '1'; | ||
| opacitySlider.style.display = 'none'; | ||
| opacitySlider.style.verticalAlign = 'middle'; | ||
| opacitySlider.style.marginLeft = '3px'; | ||
|
|
||
| let savedOpacity = localStorage.getItem('owrx-spectrum-opacity'); | ||
| if (savedOpacity !== null) { | ||
| window.SpectrumBackgroundOpacity = parseFloat(savedOpacity); | ||
| } else { | ||
| window.SpectrumBackgroundOpacity = defaultOpacity; | ||
| } | ||
| opacitySlider.value = window.SpectrumBackgroundOpacity; | ||
|
|
||
| opacitySlider.addEventListener('input', function(e) { | ||
| let val = parseFloat(e.target.value); | ||
| window.SpectrumBackgroundOpacity = val; | ||
| localStorage.setItem('owrx-spectrum-opacity', val); | ||
| }); | ||
|
|
||
| newWrapper.appendChild(opacitySlider); | ||
|
|
||
| let isSliderVisible = false; | ||
| Array.from(newWrapper.children).forEach(child => { | ||
| if (child.tagName !== 'SELECT' && child.tagName !== 'INPUT') { | ||
| child.style.transform = 'scaleX(-1)'; | ||
| child.style.cursor = 'pointer'; | ||
| child.title = 'Toggle Opacity Slider'; | ||
|
|
||
| child.addEventListener('click', function() { | ||
| isSliderVisible = !isSliderVisible; | ||
| if (isSliderVisible) { | ||
| spectrumSelect.style.display = 'none'; | ||
| opacitySlider.style.display = 'inline-block'; | ||
| child.title = 'Back to Color Menu'; | ||
| } else { | ||
| opacitySlider.style.display = 'none'; | ||
| spectrumSelect.style.display = 'inline-block'; | ||
| child.title = 'Toggle Opacity Slider'; | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| }, | ||
| spectrum | ||
| }); | ||
|
|
||
| wrapper.parentNode.insertBefore(newWrapper, wrapper.nextSibling); | ||
| } | ||
|
|
||
| injectUI(); | ||
|
|
||
| // --- 2. SPECTRAVUE-STYLE DRAWING LOGIC --- | ||
| Plugins.utils.wrap_func( | ||
| 'draw', | ||
| function (orig, thisArg, args) { return true; }, | ||
| function (res, thisArg, args) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. indent is not ok |
||
| if (!thisArg.data) return; | ||
|
|
||
| var vis_freq = get_visible_freq_range(); | ||
| var vis_start = 0.5 - (center_freq - vis_freq.start) / bandwidth; | ||
| var vis_end = 0.5 - (center_freq - vis_freq.end) / bandwidth; | ||
| var data_start = Math.round(fft_size * vis_start); | ||
| var data_end = Math.round(fft_size * vis_end); | ||
| var data_width = data_end - data_start; | ||
| var data_height = Math.abs(thisArg.max - thisArg.min); | ||
| var spec_width = thisArg.el.offsetWidth; | ||
| var spec_height = thisArg.el.offsetHeight; | ||
|
|
||
| var x_ratio = data_width / spec_width; | ||
| var y_ratio = spec_height / data_height; | ||
|
|
||
| let spectrumSelectUI = document.getElementById('webrx-spectrum-color'); | ||
| let selectedColorMode = spectrumSelectUI ? spectrumSelectUI.value : defaultColor; | ||
|
|
||
| thisArg.ctx.clearRect(0, 0, spec_width, spec_height); | ||
|
|
||
| const bgOpacity = window.SpectrumBackgroundOpacity !== undefined ? window.SpectrumBackgroundOpacity : 0.0; | ||
| if (bgOpacity > 0) { | ||
| thisArg.ctx.fillStyle = `rgba(0, 0, 0, ${bgOpacity})`; | ||
| thisArg.ctx.fillRect(0, 0, spec_width, spec_height); | ||
| } | ||
|
|
||
| thisArg.ctx.save(); | ||
| thisArg.ctx.beginPath(); | ||
|
|
||
| for (var x = 0; x < spec_width; x++) { | ||
| var data_idx = data_start + ((x * x_ratio) | 0); | ||
|
|
||
| if (data_idx < 0) data_idx = 0; | ||
| if (data_idx >= thisArg.data.length) data_idx = thisArg.data.length - 1; | ||
|
|
||
| var data = thisArg.data[data_idx]; | ||
| var y = (data - thisArg.min) * y_ratio; | ||
|
|
||
| if (x === 0) { | ||
| thisArg.ctx.moveTo(x, spec_height - y); | ||
| } else { | ||
| thisArg.ctx.lineTo(x, spec_height - y); | ||
| } | ||
| } | ||
|
|
||
| if (selectedColorMode === 'waterfall') { | ||
|
|
||
| var fillGradient = thisArg.ctx.createLinearGradient(0, 0, 0, spec_height); | ||
| var strokeGradient = thisArg.ctx.createLinearGradient(0, 0, 0, spec_height); | ||
|
|
||
| for (var i = 0; i <= 10; i++) { | ||
| var step = i / 10; | ||
|
|
||
| var signal = thisArg.max - (step * (thisArg.max - thisArg.min)); | ||
| var originalC = Waterfall.makeColor(signal); | ||
| var c = [originalC[0], originalC[1], originalC[2]]; | ||
|
|
||
| var fillAlpha = 0.8; | ||
|
|
||
| var blend = Math.max(0, (step - 0.6) / 0.4); | ||
| if (blend > 0) { | ||
| // Pushed these numbers down for a much darker, deeper navy blue | ||
| c[0] = Math.round(c[0] * (1 - blend) + 20 * blend); | ||
| c[1] = Math.round(c[1] * (1 - blend) + 70 * blend); | ||
| c[2] = Math.round(c[2] * (1 - blend) + 160 * blend); | ||
| fillAlpha = 0.8 + (0.15 * blend); | ||
| } | ||
|
|
||
| fillGradient.addColorStop(step, "rgba(" + c[0] + ", " + c[1] + ", " + c[2] + ", " + fillAlpha + ")"); | ||
| strokeGradient.addColorStop(step, "rgba(" + c[0] + ", " + c[1] + ", " + c[2] + ", 1.0)"); | ||
| } | ||
|
|
||
| thisArg.ctx.lineWidth = 1.5; | ||
| thisArg.ctx.strokeStyle = strokeGradient; | ||
| thisArg.ctx.stroke(); | ||
|
|
||
| thisArg.ctx.lineTo(spec_width, spec_height); | ||
| thisArg.ctx.lineTo(0, spec_height); | ||
| thisArg.ctx.closePath(); | ||
|
|
||
| thisArg.ctx.fillStyle = fillGradient; | ||
| thisArg.ctx.fill(); | ||
|
|
||
| } else { | ||
|
|
||
| let palette = colorPalettes[selectedColorMode] || colorPalettes['blue']; | ||
|
|
||
| thisArg.ctx.lineWidth = 1.5; | ||
| thisArg.ctx.strokeStyle = palette.stroke; | ||
| thisArg.ctx.stroke(); | ||
|
|
||
| thisArg.ctx.lineTo(spec_width, spec_height); | ||
| thisArg.ctx.lineTo(0, spec_height); | ||
| thisArg.ctx.closePath(); | ||
|
|
||
| var gradient = thisArg.ctx.createLinearGradient(0, 0, 0, spec_height); | ||
| gradient.addColorStop(0, palette.top); | ||
| gradient.addColorStop(1, palette.bottom); | ||
|
|
||
| thisArg.ctx.fillStyle = gradient; | ||
| thisArg.ctx.fill(); | ||
| } | ||
|
|
||
| thisArg.ctx.restore(); | ||
| }, | ||
| spectrum | ||
| ); | ||
| }); | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why putting new description to the plugin?