ACTUALLY fix butterchurn
This commit is contained in:
parent
68b0d9dcdd
commit
c2fd81348a
4 changed files with 263 additions and 122 deletions
|
|
@ -86,10 +86,41 @@ class AudioContextManager {
|
||||||
this.currentGains = new Array(16).fill(0);
|
this.currentGains = new Array(16).fill(0);
|
||||||
this.audio = null;
|
this.audio = null;
|
||||||
|
|
||||||
|
// Callbacks for audio graph changes (for visualizers like Butterchurn)
|
||||||
|
this._graphChangeCallbacks = [];
|
||||||
|
|
||||||
// Load saved settings
|
// Load saved settings
|
||||||
this._loadSettings();
|
this._loadSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a callback to be called when audio graph is reconnected
|
||||||
|
* @param {Function} callback - Function to call when graph changes
|
||||||
|
* @returns {Function} - Unregister function
|
||||||
|
*/
|
||||||
|
onGraphChange(callback) {
|
||||||
|
this._graphChangeCallbacks.push(callback);
|
||||||
|
return () => {
|
||||||
|
const index = this._graphChangeCallbacks.indexOf(callback);
|
||||||
|
if (index > -1) {
|
||||||
|
this._graphChangeCallbacks.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify all registered callbacks that graph has changed
|
||||||
|
*/
|
||||||
|
_notifyGraphChange() {
|
||||||
|
this._graphChangeCallbacks.forEach((callback) => {
|
||||||
|
try {
|
||||||
|
callback(this.source);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[AudioContext] Graph change callback failed:', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the audio context and connect to the audio element
|
* Initialize the audio context and connect to the audio element
|
||||||
* This should be called when audio starts playing
|
* This should be called when audio starts playing
|
||||||
|
|
@ -183,6 +214,9 @@ class AudioContextManager {
|
||||||
this.analyser.connect(this.audioContext.destination);
|
this.analyser.connect(this.audioContext.destination);
|
||||||
console.log('[AudioContext] EQ bypassed');
|
console.log('[AudioContext] EQ bypassed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify visualizers that graph has been reconnected
|
||||||
|
this._notifyGraphChange();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[AudioContext] Failed to connect graph:', e);
|
console.warn('[AudioContext] Failed to connect graph:', e);
|
||||||
// Fallback: direct connection
|
// Fallback: direct connection
|
||||||
|
|
@ -234,6 +268,13 @@ class AudioContextManager {
|
||||||
return this.audioContext;
|
return this.audioContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the source node for visualizers
|
||||||
|
*/
|
||||||
|
getSourceNode() {
|
||||||
|
return this.source;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if initialized
|
* Check if initialized
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import {
|
||||||
fontSettings,
|
fontSettings,
|
||||||
} from './storage.js';
|
} from './storage.js';
|
||||||
import { audioContextManager, EQ_PRESETS } from './audio-context.js';
|
import { audioContextManager, EQ_PRESETS } from './audio-context.js';
|
||||||
|
import { getButterchurnPresets } from './visualizers/butterchurn.js';
|
||||||
import { db } from './db.js';
|
import { db } from './db.js';
|
||||||
import { authManager } from './accounts/auth.js';
|
import { authManager } from './accounts/auth.js';
|
||||||
import { syncManager } from './accounts/pocketbase.js';
|
import { syncManager } from './accounts/pocketbase.js';
|
||||||
|
|
@ -884,31 +885,37 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
if (butterchurnDurationSetting) butterchurnDurationSetting.style.display = showSubSettings ? 'flex' : 'none';
|
if (butterchurnDurationSetting) butterchurnDurationSetting.style.display = showSubSettings ? 'flex' : 'none';
|
||||||
if (butterchurnRandomizeSetting) butterchurnRandomizeSetting.style.display = showSubSettings ? 'flex' : 'none';
|
if (butterchurnRandomizeSetting) butterchurnRandomizeSetting.style.display = showSubSettings ? 'flex' : 'none';
|
||||||
|
|
||||||
// Populate preset list if visible
|
// Populate preset list using module-level cache (works even before visualizer initializes)
|
||||||
if (show && ui && ui.visualizer && ui.visualizer.presets['butterchurn']) {
|
const { keys: presetNames } = getButterchurnPresets();
|
||||||
const preset = ui.visualizer.presets['butterchurn'];
|
const select = butterchurnSpecificPresetSelect;
|
||||||
const select = butterchurnSpecificPresetSelect;
|
|
||||||
|
|
||||||
// Only populate if needed (to avoid resetting selection or heavy DOM ops)
|
if (select && presetNames.length > 0) {
|
||||||
if (select && select.options.length <= 1 && preset.getPresetNames && preset.getPresetNames().length > 0) {
|
const currentNames = Array.from(select.options).map((opt) => opt.value);
|
||||||
const names = preset.getPresetNames();
|
// Check if dropdown only has "Loading..." or needs full update
|
||||||
|
const hasOnlyLoadingOption = currentNames.length === 1 && currentNames[0] === '';
|
||||||
|
const needsUpdate =
|
||||||
|
hasOnlyLoadingOption ||
|
||||||
|
currentNames.length !== presetNames.length ||
|
||||||
|
!presetNames.every((name) => currentNames.includes(name));
|
||||||
|
|
||||||
|
if (needsUpdate) {
|
||||||
|
// Save current selection
|
||||||
|
const currentSelection = select.value;
|
||||||
|
|
||||||
|
// Clear and rebuild dropdown
|
||||||
select.innerHTML = '';
|
select.innerHTML = '';
|
||||||
names.forEach(name => {
|
presetNames.forEach((name) => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = name;
|
option.value = name;
|
||||||
option.textContent = name;
|
option.textContent = name;
|
||||||
select.appendChild(option);
|
select.appendChild(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Select current
|
// Restore selection if it still exists
|
||||||
if (preset.getCurrentPresetName) {
|
if (presetNames.includes(currentSelection)) {
|
||||||
select.value = preset.getCurrentPresetName();
|
select.value = currentSelection;
|
||||||
}
|
} else {
|
||||||
} else if (select && preset.getCurrentPresetName) {
|
select.selectedIndex = 0;
|
||||||
// Just update selection if list already populated
|
|
||||||
const current = preset.getCurrentPresetName();
|
|
||||||
if (select.value !== current) {
|
|
||||||
select.value = current;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -982,6 +989,7 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
|
|
||||||
if (butterchurnSpecificPresetSelect) {
|
if (butterchurnSpecificPresetSelect) {
|
||||||
butterchurnSpecificPresetSelect.addEventListener('change', (e) => {
|
butterchurnSpecificPresetSelect.addEventListener('change', (e) => {
|
||||||
|
// Try to load via visualizer if active, otherwise just store the selection
|
||||||
if (ui && ui.visualizer && ui.visualizer.presets['butterchurn']) {
|
if (ui && ui.visualizer && ui.visualizer.presets['butterchurn']) {
|
||||||
ui.visualizer.presets['butterchurn'].loadPreset(e.target.value);
|
ui.visualizer.presets['butterchurn'].loadPreset(e.target.value);
|
||||||
}
|
}
|
||||||
|
|
@ -990,9 +998,33 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
|
|
||||||
// Refresh settings when presets are loaded asynchronously
|
// Refresh settings when presets are loaded asynchronously
|
||||||
window.addEventListener('butterchurn-presets-loaded', () => {
|
window.addEventListener('butterchurn-presets-loaded', () => {
|
||||||
|
console.log('[Settings] Butterchurn presets loaded event received');
|
||||||
updateButterchurnSettingsVisibility();
|
updateButterchurnSettingsVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if presets already cached and update immediately
|
||||||
|
const { keys: cachedKeys } = getButterchurnPresets();
|
||||||
|
if (cachedKeys.length > 0) {
|
||||||
|
console.log('[Settings] Presets already cached, updating dropdown immediately');
|
||||||
|
updateButterchurnSettingsVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for audio tab becoming active and refresh presets
|
||||||
|
const audioTabContent = document.getElementById('settings-tab-audio');
|
||||||
|
if (audioTabContent) {
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
||||||
|
if (audioTabContent.classList.contains('active')) {
|
||||||
|
console.log('[Settings] Audio tab became active, refreshing presets');
|
||||||
|
updateButterchurnSettingsVisibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
observer.observe(audioTabContent, { attributes: true });
|
||||||
|
}
|
||||||
|
|
||||||
// Visualizer Mode Select
|
// Visualizer Mode Select
|
||||||
const visualizerModeSelect = document.getElementById('visualizer-mode-select');
|
const visualizerModeSelect = document.getElementById('visualizer-mode-select');
|
||||||
if (visualizerModeSelect) {
|
if (visualizerModeSelect) {
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ export class Visualizer {
|
||||||
* Get the source node
|
* Get the source node
|
||||||
*/
|
*/
|
||||||
getSourceNode() {
|
getSourceNode() {
|
||||||
return audioContextManager.source;
|
return audioContextManager.getSourceNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
initContext() {
|
initContext() {
|
||||||
|
|
@ -143,11 +143,8 @@ export class Visualizer {
|
||||||
|
|
||||||
// Initialize Butterchurn if it's the active preset
|
// Initialize Butterchurn if it's the active preset
|
||||||
if (this.activePresetKey === 'butterchurn' && this.activePreset.lazyInit) {
|
if (this.activePresetKey === 'butterchurn' && this.activePreset.lazyInit) {
|
||||||
this.activePreset.lazyInit(
|
const sourceNode = audioContextManager.getSourceNode();
|
||||||
this.canvas,
|
this.activePreset.lazyInit(this.canvas, this.audioContext, sourceNode);
|
||||||
this.audioContext,
|
|
||||||
this.analyser
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resize();
|
this.resize();
|
||||||
|
|
@ -272,11 +269,8 @@ export class Visualizer {
|
||||||
|
|
||||||
// Initialize Butterchurn if switching to it
|
// Initialize Butterchurn if switching to it
|
||||||
if (key === 'butterchurn' && this.presets[key].lazyInit && this.audioContext) {
|
if (key === 'butterchurn' && this.presets[key].lazyInit && this.audioContext) {
|
||||||
this.presets[key].lazyInit(
|
const sourceNode = audioContextManager.getSourceNode();
|
||||||
this.canvas,
|
this.presets[key].lazyInit(this.canvas, this.audioContext, sourceNode);
|
||||||
this.audioContext,
|
|
||||||
this.analyser
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,103 @@
|
||||||
/**
|
/**
|
||||||
* Butterchurn (Milkdrop) Visualizer Preset
|
* Butterchurn (Milkdrop) Visualizer Preset
|
||||||
* WebGL-based audio visualization using the Butterchurn library
|
* WebGL-based audio visualization using the Butterchurn library
|
||||||
|
* Uses same loading logic as bc-demo.html - loads presets as global scripts
|
||||||
*/
|
*/
|
||||||
import butterchurn from 'butterchurn';
|
import butterchurn from 'butterchurn';
|
||||||
import { visualizerSettings } from '../storage.js';
|
import { visualizerSettings } from '../storage.js';
|
||||||
|
import { audioContextManager } from '../audio-context.js';
|
||||||
|
|
||||||
|
// Module-level preset cache - loads immediately when this file is imported
|
||||||
|
let cachedPresets = null;
|
||||||
|
let cachedPresetKeys = [];
|
||||||
|
let isLoading = false;
|
||||||
|
let loadCallbacks = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load presets at module level so they're available immediately
|
||||||
|
*/
|
||||||
|
function loadPresetsModule() {
|
||||||
|
if (cachedPresets || isLoading) return;
|
||||||
|
isLoading = true;
|
||||||
|
|
||||||
|
// Check if already loaded in global
|
||||||
|
if (window.butterchurnPresets) {
|
||||||
|
processPresetsModule();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load presets script like bc-demo.html does
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = '/node_modules/butterchurn-presets/lib/butterchurnPresets.min.js';
|
||||||
|
script.onload = () => {
|
||||||
|
console.log('[Butterchurn] Presets script loaded');
|
||||||
|
processPresetsModule();
|
||||||
|
};
|
||||||
|
script.onerror = (e) => {
|
||||||
|
console.error('[Butterchurn] Failed to load presets script:', e);
|
||||||
|
isLoading = false;
|
||||||
|
};
|
||||||
|
document.head.appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process loaded presets at module level
|
||||||
|
*/
|
||||||
|
function processPresetsModule() {
|
||||||
|
try {
|
||||||
|
const presetsModule = window.butterchurnPresets;
|
||||||
|
if (!presetsModule) {
|
||||||
|
console.error('[Butterchurn] butterchurnPresets not found on window');
|
||||||
|
isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allPresets =
|
||||||
|
typeof presetsModule.getPresets === 'function'
|
||||||
|
? presetsModule.getPresets()
|
||||||
|
: presetsModule.default || presetsModule;
|
||||||
|
|
||||||
|
cachedPresets = allPresets || {};
|
||||||
|
cachedPresetKeys = Object.keys(cachedPresets);
|
||||||
|
cachedPresetKeys.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
||||||
|
|
||||||
|
console.log('[Butterchurn] Module-level presets loaded:', cachedPresetKeys.length);
|
||||||
|
|
||||||
|
// Notify all waiting callbacks
|
||||||
|
loadCallbacks.forEach((cb) => cb(cachedPresets, cachedPresetKeys));
|
||||||
|
loadCallbacks = [];
|
||||||
|
|
||||||
|
// Dispatch global event
|
||||||
|
window.dispatchEvent(new CustomEvent('butterchurn-presets-loaded'));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[Butterchurn] Failed to process presets:', e);
|
||||||
|
cachedPresets = {};
|
||||||
|
cachedPresetKeys = [];
|
||||||
|
} finally {
|
||||||
|
isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cached presets - available immediately after module loads
|
||||||
|
*/
|
||||||
|
export function getButterchurnPresets() {
|
||||||
|
return { presets: cachedPresets, keys: cachedPresetKeys };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register callback for when presets are loaded
|
||||||
|
*/
|
||||||
|
export function onButterchurnPresetsLoaded(callback) {
|
||||||
|
if (cachedPresets) {
|
||||||
|
callback(cachedPresets, cachedPresetKeys);
|
||||||
|
} else {
|
||||||
|
loadCallbacks.push(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start loading presets immediately when module is imported
|
||||||
|
loadPresetsModule();
|
||||||
|
|
||||||
export class ButterchurnPreset {
|
export class ButterchurnPreset {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -17,60 +111,30 @@ export class ButterchurnPreset {
|
||||||
this.lastPresetChange = 0;
|
this.lastPresetChange = 0;
|
||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
|
|
||||||
this.presets = {};
|
// Use cached presets if available
|
||||||
this.presetKeys = [];
|
this.presets = cachedPresets || {};
|
||||||
this.isLoadingPresets = false;
|
this.presetKeys = cachedPresetKeys || [];
|
||||||
|
this.isLoadingPresets = isLoading;
|
||||||
|
|
||||||
// Transition settings
|
// Transition settings
|
||||||
this.blendProgress = 0;
|
this.blendProgress = 0;
|
||||||
this.blendDuration = 2.7; // seconds for preset transitions
|
this.blendDuration = 2.7; // seconds for preset transitions
|
||||||
|
|
||||||
// Load presets asynchronously
|
// Listen for presets if not loaded yet
|
||||||
this.loadPresets();
|
if (!cachedPresets) {
|
||||||
}
|
onButterchurnPresetsLoaded((presets, keys) => {
|
||||||
|
this.presets = presets;
|
||||||
|
this.presetKeys = keys;
|
||||||
|
this.isLoadingPresets = false;
|
||||||
|
|
||||||
/**
|
// Notify system that presets are ready (for settings dropdown)
|
||||||
* Load presets dynamically to avoid blocking main bundle
|
window.dispatchEvent(new CustomEvent('butterchurn-presets-loaded'));
|
||||||
*/
|
|
||||||
async loadPresets() {
|
|
||||||
if (this.isLoadingPresets) return;
|
|
||||||
this.isLoadingPresets = true;
|
|
||||||
|
|
||||||
try {
|
// If visualizer already initialized, load a preset
|
||||||
const module = await import('butterchurn-presets');
|
if (this.isInitialized && this.visualizer) {
|
||||||
const presets = module.default.getPresets();
|
this.loadNextPreset();
|
||||||
|
}
|
||||||
this.presets = presets;
|
|
||||||
this.presetKeys = Object.keys(this.presets);
|
|
||||||
|
|
||||||
// Filter to get a good selection of presets
|
|
||||||
this.presetKeys = this.presetKeys.filter(key => {
|
|
||||||
const skipPatterns = ['flexi', 'empty', 'test', '_'];
|
|
||||||
return !skipPatterns.some(pattern => key.toLowerCase().includes(pattern));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.presetKeys.length === 0) {
|
|
||||||
this.presetKeys = Object.keys(this.presets);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shuffle presets for variety
|
|
||||||
this.shufflePresets();
|
|
||||||
|
|
||||||
console.log('[Butterchurn] Presets loaded:', this.presetKeys.length);
|
|
||||||
|
|
||||||
// Notify system that presets are ready
|
|
||||||
window.dispatchEvent(new CustomEvent('butterchurn-presets-loaded'));
|
|
||||||
|
|
||||||
// If initialized (visualizer ready), load a preset immediately
|
|
||||||
if (this.isInitialized && this.visualizer) {
|
|
||||||
this.loadNextPreset();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[Butterchurn] Failed to load presets:', e);
|
|
||||||
this.presets = {};
|
|
||||||
this.presetKeys = [];
|
|
||||||
} finally {
|
|
||||||
this.isLoadingPresets = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,7 +166,7 @@ export class ButterchurnPreset {
|
||||||
|
|
||||||
// Connect audio source
|
// Connect audio source
|
||||||
if (sourceNode) {
|
if (sourceNode) {
|
||||||
this.visualizer.connectAudio(sourceNode);
|
this.connectAudioWithDelay(sourceNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load initial preset
|
// Load initial preset
|
||||||
|
|
@ -111,6 +175,16 @@ export class ButterchurnPreset {
|
||||||
this.lastPresetChange = performance.now();
|
this.lastPresetChange = performance.now();
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
|
|
||||||
|
// Register for audio graph changes so we can reconnect when EQ is toggled
|
||||||
|
if (audioContextManager) {
|
||||||
|
this._unregisterGraphChange = audioContextManager.onGraphChange((sourceNode) => {
|
||||||
|
if (sourceNode && this.isInitialized) {
|
||||||
|
console.log('[Butterchurn] Audio graph changed, reconnecting...');
|
||||||
|
this.connectAudioWithDelay(sourceNode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.log('[Butterchurn] Initialized with', this.presetKeys.length, 'presets');
|
console.log('[Butterchurn] Initialized with', this.presetKeys.length, 'presets');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Butterchurn] Initialization failed:', error);
|
console.error('[Butterchurn] Initialization failed:', error);
|
||||||
|
|
@ -118,12 +192,27 @@ export class ButterchurnPreset {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shuffle the preset keys for random variety
|
* Connect audio source to the visualizer (public API)
|
||||||
*/
|
*/
|
||||||
shufflePresets() {
|
connectAudio(sourceNode) {
|
||||||
for (let i = this.presetKeys.length - 1; i > 0; i--) {
|
if (sourceNode) {
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
this.connectAudioWithDelay(sourceNode);
|
||||||
[this.presetKeys[i], this.presetKeys[j]] = [this.presetKeys[j], this.presetKeys[i]];
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect audio source with delay node for proper sync
|
||||||
|
* Like bc-demo.html: creates a delay node and connects visualizer to it
|
||||||
|
*/
|
||||||
|
connectAudioWithDelay(sourceNode) {
|
||||||
|
if (!this.audioContext || !this.visualizer) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Connect visualizer directly to the source node
|
||||||
|
this.visualizer.connectAudio(sourceNode);
|
||||||
|
console.log('[Butterchurn] Audio connected');
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[Butterchurn] Failed to connect audio:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,15 +222,6 @@ export class ButterchurnPreset {
|
||||||
loadNextPreset() {
|
loadNextPreset() {
|
||||||
if (!this.visualizer || this.presetKeys.length === 0) return;
|
if (!this.visualizer || this.presetKeys.length === 0) return;
|
||||||
|
|
||||||
// If cycle enabled is false, don't change preset automatically unless forced (e.g. init or manual next)
|
|
||||||
// But here we are just loading 'a' preset.
|
|
||||||
// The cycling logic is in draw().
|
|
||||||
|
|
||||||
// Wait, loadNextPreset is general.
|
|
||||||
// Let's check settings inside loadNextPreset?
|
|
||||||
// No, loadNextPreset is an action. It should just do it.
|
|
||||||
// The caller decides when.
|
|
||||||
|
|
||||||
const randomize = visualizerSettings.isButterchurnRandomizeEnabled();
|
const randomize = visualizerSettings.isButterchurnRandomizeEnabled();
|
||||||
|
|
||||||
if (randomize) {
|
if (randomize) {
|
||||||
|
|
@ -156,7 +236,6 @@ export class ButterchurnPreset {
|
||||||
if (preset) {
|
if (preset) {
|
||||||
try {
|
try {
|
||||||
this.visualizer.loadPreset(preset, this.blendDuration);
|
this.visualizer.loadPreset(preset, this.blendDuration);
|
||||||
// console.log('[Butterchurn] Loaded preset:', presetKey);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[Butterchurn] Failed to load preset:', presetKey, error);
|
console.warn('[Butterchurn] Failed to load preset:', presetKey, error);
|
||||||
// Try next preset
|
// Try next preset
|
||||||
|
|
@ -222,8 +301,6 @@ export class ButterchurnPreset {
|
||||||
*/
|
*/
|
||||||
draw(ctx, canvas, analyser, dataArray, params) {
|
draw(ctx, canvas, analyser, dataArray, params) {
|
||||||
if (!this.isInitialized) {
|
if (!this.isInitialized) {
|
||||||
// Lazy initialization - need audio context and source node
|
|
||||||
// This will be handled by the visualizer.js main class
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,11 +326,8 @@ export class ButterchurnPreset {
|
||||||
console.warn('[Butterchurn] Render error:', error);
|
console.warn('[Butterchurn] Render error:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle blended mode - we need to composite with cover art
|
// Handle blended mode
|
||||||
// Butterchurn renders directly to the canvas, so for blended mode
|
|
||||||
// we need to adjust the canvas opacity/blend
|
|
||||||
if (mode === 'blended') {
|
if (mode === 'blended') {
|
||||||
// The canvas will be composited by CSS in the parent
|
|
||||||
canvas.style.opacity = '0.85';
|
canvas.style.opacity = '0.85';
|
||||||
canvas.style.mixBlendMode = 'screen';
|
canvas.style.mixBlendMode = 'screen';
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -262,38 +336,34 @@ export class ButterchurnPreset {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect audio source to the visualizer
|
|
||||||
*/
|
|
||||||
connectAudio(sourceNode) {
|
|
||||||
if (this.visualizer && sourceNode) {
|
|
||||||
try {
|
|
||||||
this.visualizer.connectAudio(sourceNode);
|
|
||||||
console.log('[Butterchurn] Audio connected');
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[Butterchurn] Failed to connect audio:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lazy initialization helper for when audio context becomes available
|
* Lazy initialization helper for when audio context becomes available
|
||||||
*/
|
*/
|
||||||
lazyInit(canvas, audioContext, sourceNode) {
|
lazyInit(canvas, audioContext, sourceNode) {
|
||||||
if (!this.isInitialized && canvas && audioContext) {
|
if (!this.isInitialized && canvas && audioContext) {
|
||||||
const gl = canvas.getContext('webgl2', {
|
const gl =
|
||||||
alpha: true,
|
canvas.getContext('webgl2', {
|
||||||
antialias: true,
|
alpha: true,
|
||||||
preserveDrawingBuffer: true,
|
antialias: true,
|
||||||
}) || canvas.getContext('webgl', {
|
preserveDrawingBuffer: true,
|
||||||
alpha: true,
|
}) ||
|
||||||
antialias: true,
|
canvas.getContext('webgl', {
|
||||||
preserveDrawingBuffer: true,
|
alpha: true,
|
||||||
});
|
antialias: true,
|
||||||
|
preserveDrawingBuffer: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (gl) {
|
if (gl) {
|
||||||
this.init(canvas, gl, audioContext, sourceNode);
|
this.init(canvas, gl, audioContext, null);
|
||||||
|
|
||||||
|
// Connect audio if sourceNode is provided
|
||||||
|
if (sourceNode) {
|
||||||
|
this.connectAudioWithDelay(sourceNode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if (this.isInitialized && sourceNode) {
|
||||||
|
// Reconnect if source changed
|
||||||
|
this.connectAudioWithDelay(sourceNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -301,9 +371,13 @@ export class ButterchurnPreset {
|
||||||
* Cleanup resources
|
* Cleanup resources
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
|
// Unregister graph change listener
|
||||||
|
if (this._unregisterGraphChange) {
|
||||||
|
this._unregisterGraphChange();
|
||||||
|
this._unregisterGraphChange = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.visualizer) {
|
if (this.visualizer) {
|
||||||
// Butterchurn doesn't have an explicit cleanup method
|
|
||||||
// but we can null our references
|
|
||||||
this.visualizer = null;
|
this.visualizer = null;
|
||||||
}
|
}
|
||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue