Files
diodiogod-TTS-Audio-Suite/web/audio_analyzer_node_integration.js
T
diodiogodandClaude fa00361e84 🎉 COMPLETE FIX: All connected audio scenarios now working perfectly
✅ All Scenarios Working:
- Scenario 1 (connected audio only): Perfect waveform + audio playback
- Scenario 2 (file path only): Perfect waveform + audio playback
- Scenario 3 (both inputs): Uses connected audio correctly (proper priority)

🔧 Key Fixes Applied:
1. **Method Context Fix**: Changed `this.setupAudioPlayback()` to `this.core.node.setupAudioPlayback()`
   - Fixed the core issue where filename was being transformed incorrectly
   - Method was being called on wrong object context

2. **Priority Logic**: Connected audio properly takes priority over file paths
   - Python backend respects priority: connected audio → file audio
   - JavaScript respects same priority in playback setup

3. **File Path Handling**: Fixed filename vs full path issues
   - Python provides correct filenames for web access
   - JavaScript receives and uses correct paths for ComfyUI API

4. **Duplicate Prevention**: Added audio setup deduplication
   - Prevents multiple audio loading causing playback issues
   - Clears setup state when new analysis starts

🎵 Final Working State:
- Real waveform analysis data (no more fake test data)
- Proper audio playback for all input types
- Connected audio saved as temporary WAV files for web access
- Priority handling matches tooltip description
- No more "unstoppable audio" or duplicate loading bugs

The connected audio functionality is now fully implemented and working\!

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-11 20:09:33 -03:00

375 lines
14 KiB
JavaScript

/**
* Audio Analyzer Node Integration Module
* Handles communication with ComfyUI AudioAnalyzerNode
*/
export class AudioAnalyzerNodeIntegration {
constructor(core) {
this.core = core;
this.lastAnalysisParams = null;
this.isAnalyzing = false;
}
// Handle visualization data updates from node execution
updateVisualization(data) {
console.log('🎵 Audio Analyzer: Received visualization data from node execution');
if (data.error) {
console.error('❌ Audio Analyzer: Analysis error:', data.error);
this.core.showMessage(`Analysis error: ${data.error}`);
this.core.ui.updateStatus('Analysis failed');
return;
}
// Check if we have waveform data
if (!data.waveform || !data.waveform.samples || data.waveform.samples.length === 0) {
console.warn('⚠️ Audio Analyzer: No waveform data received');
this.core.showMessage('No audio data received - check if audio file loaded correctly');
this.core.ui.updateStatus('No audio data');
return;
}
console.log(`✅ Audio Analyzer: Loaded ${data.waveform.samples.length} audio samples, duration: ${data.duration}s`);
// Update waveform data - handle the correct data structure from Python
this.core.waveformData = {
samples: data.waveform?.samples || [],
time: data.waveform?.time || [],
rms: data.rms?.values || [],
rmsTime: data.rms?.time || [],
peaks: data.peaks || [],
duration: data.duration || 0,
sampleRate: data.sample_rate || 44100,
regions: data.regions || [],
analysisResults: data.analysis_results || {}
};
// Create audio element for playback if we have file path or web audio filename
if (data.file_path) {
this.core.node.setupAudioPlayback(data.file_path);
} else if (data.web_audio_filename) {
this.core.node.setupAudioPlayback(data.web_audio_filename);
}
// Update UI status
this.core.ui.updateStatus(`Analyzed: ${this.core.formatTime(this.core.waveformData.duration)}`);
// Show analysis results summary
this.showAnalysisResults(data.analysis_results);
// Make hasConnectedAudio available to core for playback checks
this.core.hasConnectedAudio = () => this.hasConnectedAudio();
// Redraw visualization
this.core.visualization.redraw();
// Reset analysis state
this.isAnalyzing = false;
this.core.showMessage('Analysis complete');
}
// Handle audio file selection
onAudioFileSelected(filePath) {
console.log('Audio file selected:', filePath);
if (!filePath || filePath.trim() === '') {
this.core.showMessage('No audio file selected');
return;
}
// Clear previous data
this.core.waveformData = null;
this.core.selectedRegions = [];
this.core.clearSelection();
// Update UI
this.core.ui.updateStatus('Loading audio file...');
this.core.visualization.redraw();
// Trigger node execution
this.triggerNodeExecution();
this.core.showMessage(`Loading: ${filePath}`);
}
// Handle parameter changes (now works like manual refresh)
onParametersChanged() {
console.log('Analysis requested');
if (!this.hasAudioSource()) {
this.core.showMessage('No audio source available for analysis');
return;
}
// Always perform analysis when user clicks Analyze button
// No need to check if parameters changed - user explicitly requested analysis
// Clear current audio setup to allow new setup
if (this.core.node) {
this.core.node.currentAudioFile = null;
}
// Update current params for future comparisons
this.lastAnalysisParams = this.getCurrentAnalysisParams();
// Clear previous analysis results
if (this.core.waveformData) {
this.core.waveformData.analysisResults = {};
}
// Update UI
this.core.ui.updateStatus('Analyzing audio...');
this.core.visualization.redraw();
// Trigger node execution (same as manual refresh)
this.core.node.lastExecutionTime = Date.now();
// Queue execution and check for results
window.app.queuePrompt();
// Check for results after execution
setTimeout(() => this.core.node.checkForResults(), 3000);
setTimeout(() => this.core.node.checkForResults(), 6000);
this.core.showMessage('Analyzing audio...');
}
// Handle audio connection
onAudioConnected() {
console.log('Audio input connected');
// Clear file-based data
this.core.waveformData = null;
this.core.selectedRegions = [];
this.core.clearSelection();
// Update UI
this.core.ui.updateStatus('Audio connected, analyzing...');
this.core.visualization.redraw();
// Trigger node execution
this.triggerNodeExecution();
this.core.showMessage('Audio connected, analyzing...');
}
// Trigger node execution
triggerNodeExecution() {
if (this.isAnalyzing) {
console.log('Analysis already in progress');
return;
}
this.isAnalyzing = true;
try {
// Queue the node for execution
if (this.core.node.graph && this.core.node.graph.runStep) {
this.core.node.graph.runStep([this.core.node]);
} else {
// Fallback: manually trigger execution
console.log('Triggering manual node execution');
this.core.node.doExecute?.();
}
} catch (error) {
console.error('Failed to trigger node execution:', error);
this.core.showMessage(`Execution error: ${error.message}`);
this.isAnalyzing = false;
}
}
// Check if we have an audio source
hasAudioSource() {
// Check for file input
const audioFileWidget = this.core.node.widgets?.find(w => w.name === 'audio_file');
if (audioFileWidget && audioFileWidget.value && audioFileWidget.value.trim()) {
return true;
}
// Check for audio input connection by name (not hardcoded slot index)
if (this.core.node.inputs) {
const audioInput = this.core.node.inputs.find(input => input.name === 'audio');
if (audioInput && audioInput.link) {
return true;
}
}
return false;
}
// Check if we have connected audio input (no file path)
hasConnectedAudio() {
const audioFileWidget = this.core.node.widgets?.find(w => w.name === 'audio_file');
const hasFile = audioFileWidget && audioFileWidget.value && audioFileWidget.value.trim();
if (this.core.node.inputs) {
const audioInput = this.core.node.inputs.find(input => input.name === 'audio');
const hasConnection = audioInput && audioInput.link;
return hasConnection && !hasFile;
}
return false;
}
// Get current analysis parameters
getCurrentAnalysisParams() {
const params = {};
// Get widget values
this.core.node.widgets?.forEach(widget => {
if (widget.name === 'analysis_method' ||
widget.name === 'silence_threshold' ||
widget.name === 'silence_min_duration' ||
widget.name === 'energy_sensitivity') {
params[widget.name] = widget.value;
}
});
return params;
}
// Compare parameter objects
paramsEqual(params1, params2) {
if (!params1 || !params2) return false;
const keys1 = Object.keys(params1);
const keys2 = Object.keys(params2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (params1[key] !== params2[key]) return false;
}
return true;
}
// Setup audio playback
setupAudioPlayback(filePath) {
try {
// Remove existing audio element
if (this.core.audioElement) {
this.core.audioElement.removeEventListener('ended', this.handleAudioEnded);
this.core.audioElement.removeEventListener('timeupdate', this.handleTimeUpdate);
}
// Create new audio element
this.core.audioElement = new Audio();
this.core.audioElement.src = filePath;
this.core.audioElement.preload = 'metadata';
// Audio event listeners
this.core.audioElement.addEventListener('ended', () => {
console.log('🔊 Audio ended - stopping all animations');
this.core.isPlaying = false;
this.core.ui.playButton.textContent = '▶️ Play';
this.core.currentTime = 0;
this.core.ui.updateTimeDisplay();
this.core.stopPlayheadAnimation(); // Stop the core playhead animation loop
this.core.visualization.stopAnimation(); // Stop the visualization animation loop
this.core.visualization.redraw(); // Final redraw
console.log('🔊 All animations stopped, isPlaying:', this.core.isPlaying);
});
this.core.audioElement.addEventListener('timeupdate', () => {
if (this.core.isPlaying) {
this.core.currentTime = this.core.audioElement.currentTime;
this.core.ui.updateTimeDisplay();
}
});
this.core.audioElement.addEventListener('loadedmetadata', () => {
console.log('Audio metadata loaded');
this.core.ui.updateStatus('Audio loaded and ready for playback');
});
this.core.audioElement.addEventListener('error', (e) => {
console.error('Audio loading error:', e);
this.core.showMessage('Failed to load audio for playback');
});
} catch (error) {
console.error('Failed to setup audio playback:', error);
this.core.showMessage('Audio playback unavailable');
}
}
// Show analysis results summary
showAnalysisResults(results) {
if (!results) return;
const messages = [];
if (results.silence_regions) {
messages.push(`Found ${results.silence_regions.length} silence regions`);
}
if (results.speech_regions) {
messages.push(`Found ${results.speech_regions.length} speech regions`);
}
if (results.energy_peaks) {
messages.push(`Found ${results.energy_peaks.length} energy peaks`);
}
if (results.timing_markers) {
messages.push(`Found ${results.timing_markers.length} timing markers`);
}
if (messages.length > 0) {
this.core.showMessage(messages.join(', '));
}
}
// Export analysis results
exportAnalysisResults() {
if (!this.core.waveformData || !this.core.waveformData.analysisResults) {
this.core.showMessage('No analysis results to export');
return null;
}
const results = {
duration: this.core.waveformData.duration,
sample_rate: this.core.waveformData.sampleRate,
analysis_results: this.core.waveformData.analysisResults,
selected_regions: this.core.selectedRegions,
analysis_params: this.getCurrentAnalysisParams(),
export_timestamp: new Date().toISOString()
};
return JSON.stringify(results, null, 2);
}
// Import analysis results
importAnalysisResults(jsonData) {
try {
const results = JSON.parse(jsonData);
if (results.analysis_results) {
this.core.waveformData.analysisResults = results.analysis_results;
}
if (results.selected_regions) {
this.core.selectedRegions = results.selected_regions;
this.core.updateManualRegions();
}
this.core.visualization.redraw();
this.core.showMessage('Analysis results imported successfully');
} catch (error) {
console.error('Failed to import analysis results:', error);
this.core.showMessage('Failed to import analysis results');
}
}
// Get node execution status
getExecutionStatus() {
return {
isAnalyzing: this.isAnalyzing,
hasAudioSource: this.hasAudioSource(),
hasResults: !!(this.core.waveformData && this.core.waveformData.analysisResults),
currentParams: this.getCurrentAnalysisParams()
};
}
}