| import { app } from '../../../scripts/app.js' |
| import { api } from '../../../scripts/api.js' |
| import { ComfyWidgets } from '../../../scripts/widgets.js' |
| import { $el } from '../../../scripts/ui.js' |
|
|
| import WaveSurfer from 'https://cdn.jsdelivr.net/npm/wavesurfer.js@7/dist/wavesurfer.esm.js' |
|
|
| function get_position_style (ctx, widget_width, y, node_height) { |
| const MARGIN = 4 |
|
|
| |
| const elRect = ctx.canvas.getBoundingClientRect() |
| const transform = new DOMMatrix() |
| .scaleSelf( |
| elRect.width / ctx.canvas.width, |
| elRect.height / ctx.canvas.height |
| ) |
| .multiplySelf(ctx.getTransform()) |
| .translateSelf(MARGIN, MARGIN + y) |
|
|
| return { |
| transformOrigin: '0 0', |
| transform: transform, |
| left: `0`, |
| top: '0', |
| cursor: 'pointer', |
| position: 'absolute', |
| maxWidth: `${widget_width - MARGIN * 2}px`, |
| |
| width: `${widget_width - MARGIN * 2}px`, |
| |
| |
| display: 'flex', |
| flexDirection: 'column', |
| |
| justifyContent: 'space-around' |
| } |
| } |
|
|
| |
| const parseUrl = data => { |
| let { filename, subfolder, type, prompt } = data |
| return { |
| url: api.apiURL( |
| `/view?filename=${encodeURIComponent( |
| filename |
| )}&type=${type}&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}` |
| ), |
| prompt |
| } |
| } |
|
|
| const createWaveSurfer = (wavesurfer, id,url) => { |
| |
| if (wavesurfer) { |
| wavesurfer.destroy() |
| } |
| wavesurfer = WaveSurfer.create({ |
| container: '#' + id, |
| waveColor: 'rgb(200, 0, 200)', |
| progressColor: 'rgb(100, 0, 100)', |
| |
| barWidth: 10, |
| |
| barGap: 2, |
| |
| barRadius: 6, |
| url |
| }) |
|
|
| wavesurfer._auto = true |
|
|
| |
| wavesurfer.on('finish', function () { |
| |
| if (wavesurfer._auto) wavesurfer.play() |
| }) |
|
|
| wavesurfer.on('interaction', () => { |
| wavesurfer._auto = false |
| if (!wavesurfer.isPlaying()) wavesurfer.play() |
| }) |
|
|
| |
| wavesurfer.on('audioprocess', () => { |
| if (wavesurfer.isPlaying()&&wavesurfer.getDecodedData()) { |
| const channelData = wavesurfer.getDecodedData().getChannelData(0); |
| const currentTime = wavesurfer.getCurrentTime() |
| |
| const sampleRate = wavesurfer.getDecodedData().sampleRate |
|
|
| |
| const windowSize = 1 |
| const startSample = Math.floor(currentTime * sampleRate) |
| const endSample = Math.min( |
| startSample + windowSize * sampleRate, |
| channelData.length |
| ) |
|
|
| let peak = 0 |
| for (let i = startSample; i < endSample; i++) { |
| const value = Math.abs(channelData[i]) |
| if (value > peak) { |
| peak = value |
| } |
| } |
| |
| } |
| }) |
|
|
| return wavesurfer |
| } |
|
|
| |
| function updateWaveWidgetValue (widgets, id, url, prompt, wavesurfer) { |
| let widget = widgets.filter(w => w.name == 'AudioPlay')[0] |
| |
| widget.value = [url, prompt] |
|
|
| if (widget.div) { |
| widget.div.querySelector('.wave').id = `AudioPlay_${id}` |
| } |
|
|
| wavesurfer = createWaveSurfer(wavesurfer, `AudioPlay_${id}`,url) |
|
|
| wavesurfer.on('ready', duration => { |
| console.log('Audio duration: ' + duration + ' seconds') |
| if (widget.div) { |
| widget.div.setAttribute('data-url', url) |
| widget.div.querySelector('.link').setAttribute('href', url) |
| widget.div.querySelector( |
| '.info' |
| ).innerHTML = `<span style="font-size: 12px; |
| margin: 8px;">${duration.toFixed( |
| 2 |
| )} seconds</span> <br><span style="font-size: 14px;">${prompt||''}</span> <br>` |
| } |
| }) |
| |
| |
| wavesurfer.load(url) |
| |
| return wavesurfer |
| } |
|
|
| app.registerExtension({ |
| name: 'SoundLab.AudioPlay', |
| async beforeRegisterNodeDef (nodeType, nodeData, app) { |
| if (nodeType.comfyClass == 'AudioPlay') { |
| let that = this |
| |
|
|
| const orig_nodeCreated = nodeType.prototype.onNodeCreated |
| nodeType.prototype.onNodeCreated = function () { |
| orig_nodeCreated?.apply(this, arguments) |
|
|
| const widget = { |
| type: 'div', |
| name: 'AudioPlay', |
| draw (ctx, node, widget_width, y, widget_height) { |
| Object.assign( |
| this.div.style, |
| get_position_style(ctx, widget_width, y, node.size[1]) |
| ) |
| } |
| } |
|
|
| |
| widget.div = $el('div', {}) |
|
|
| document.body.appendChild(widget.div) |
|
|
| |
| const waveDiv = document.createElement('div') |
| waveDiv.className = 'wave' |
| waveDiv.style.minHeight = '172px' |
| widget.div.appendChild(waveDiv) |
|
|
| |
| const infoDiv = document.createElement('div') |
| infoDiv.className = 'info' |
| infoDiv.style.marginBottom = '20px' |
| widget.div.appendChild(infoDiv) |
|
|
| |
| let btns = document.createElement('div') |
| btns.className = 'btns' |
| btns.style = `display: flex; |
| width: 100%; |
| justify-content: space-between;` |
| widget.div.appendChild(btns) |
|
|
| |
| const playBtn = document.createElement('a') |
| playBtn.innerText = 'Play/Pause' |
|
|
| playBtn.style = ` |
| display: flex; |
| padding: 4px 15px; |
| background-color: var(--comfy-input-bg); |
| border-radius: 8px; |
| border-color: var(--border-color); |
| border-style: solid; |
| color: var(--descrip-text); |
| text-decoration: none; |
| border-radius: 5px; |
| transition: background-color 0.3s ease 0s; |
| ` |
|
|
| playBtn.addEventListener('click', e => { |
| e.preventDefault() |
| if (that[`wavesurfer_${this.id}`]) { |
| that[`wavesurfer_${this.id}`]?.playPause() |
| that[`wavesurfer_${this.id}`]._auto = true |
| } |
| }) |
| btns.appendChild(playBtn) |
|
|
| const urlLink = document.createElement('a') |
| urlLink.className = 'link' |
| urlLink.innerText = 'URL' |
| urlLink.setAttribute('target', '_blank') |
| urlLink.style = `display: flex; |
| padding: 4px 15px; |
| background-color: var(--comfy-input-bg); |
| border-radius: 8px; |
| border-color: var(--border-color); |
| border-style: solid; |
| color: var(--descrip-text); |
| text-decoration: none; |
| border-radius: 5px; |
| transition: background-color 0.3s ease 0s;` |
| |
| btns.appendChild(urlLink) |
|
|
|
|
| |
| |
|
|
|
|
| this.addCustomWidget(widget) |
|
|
| const onRemoved = this.onRemoved |
| this.onRemoved = () => { |
| widget.div.remove() |
| return onRemoved?.() |
| } |
|
|
| this.size = [this.size[0], 280] |
| this.serialize_widgets = true |
| } |
|
|
| const onExecuted = nodeType.prototype.onExecuted |
| nodeType.prototype.onExecuted = function (message) { |
| onExecuted?.apply(this, arguments) |
| const audio = message.audio |
| console.log('#onExecuted', `AudioPlay_${this.id}`, message,audio) |
| try { |
| let { url, prompt } = parseUrl(audio[0]) |
|
|
| that[`wavesurfer_${this.id}`] = updateWaveWidgetValue( |
| this.widgets, |
| this.id, |
| url, |
| prompt, |
| that[`wavesurfer_${this.id}`] |
| ) |
|
|
| that[`wavesurfer_${this.id}`]?.playPause() |
| } catch (error) { |
| console.log(error) |
| } |
| } |
| } |
| }, |
| async loadedGraphNode (node, app) { |
| if (node.type === 'AudioPlay') { |
| let widget = node.widgets.filter(w => w.name == 'AudioPlay')[0] |
|
|
| if (widget.value) { |
| let [url, prompt] = widget.value |
|
|
| this[`wavesurfer_${node.id}`] = updateWaveWidgetValue( |
| node.widgets, |
| node.id, |
| url, |
| prompt, |
| this[`wavesurfer_${node.id}`] |
| ) |
| } |
|
|
| console.log('#loadedGraphNode', node) |
| } |
| } |
| }) |
|
|