File size: 16,074 Bytes
d91d3dc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
document.addEventListener('DOMContentLoaded', function() {
    // Global variables
    let vocalAudioBuffer = null;
    let musicAudioBuffer = null;
    let audioContext = null;
    let vocalSource = null;
    let musicSource = null;
    let vocalGainNode = null;
    let musicGainNode = null;
    let isPlaying = false;
    let startTime = 0;
    let playStartTime = 0;
    let animationFrameId = null;
    let vocalStartTime = 0;
    let vocalEndTime = 0;
    let musicStartTime = 0;
    let musicEndTime = 0;
    let vocalOffsetX = 0;
    let musicOffsetX = 0;
    let isDragging = false;
    let currentDraggingElement = null;
    let dragStartX = 0;
    
    // DOM Elements
    const vocalUpload = document.getElementById('vocalUpload');
    const musicUpload = document.getElementById('musicUpload');
    const vocalFileName = document.getElementById('vocalFileName');
    const musicFileName = document.getElementById('musicFileName');
    const timelineEditor = document.getElementById('timelineEditor');
    const playBtn = document.getElementById('playBtn');
    const exportBtn = document.getElementById('exportBtn');
    const exportSection = document.getElementById('exportSection');
    const vocalVolume = document.getElementById('vocalVolume');
    const musicVolume = document.getElementById('musicVolume');
    const vocalTrack = document.getElementById('vocalTrack');
    const musicTrack = document.getElementById('musicTrack');
    const vocalStartHandle = document.getElementById('vocalStartHandle');
    const vocalEndHandle = document.getElementById('vocalEndHandle');
    const musicStartHandle = document.getElementById('musicStartHandle');
    const musicEndHandle = document.getElementById('musicEndHandle');
    const vocalProgress = document.getElementById('vocalProgress');
    const musicProgress = document.getElementById('musicProgress');
    const timelineMarkers = document.getElementById('timelineMarkers');
    const progressContainer = document.getElementById('progressContainer');
    const exportProgress = document.getElementById('exportProgress');
    const progressText = document.getElementById('progressText');

    // Initialize Audio Context
    function initAudioContext() {
        if (!audioContext) {
            audioContext = new (window.AudioContext || window.webkitAudioContext)();
            vocalGainNode = audioContext.createGain();
            musicGainNode = audioContext.createGain();
            vocalGainNode.gain.value = vocalVolume.value;
            musicGainNode.gain.value = musicVolume.value;
            vocalGainNode.connect(audioContext.destination);
            musicGainNode.connect(audioContext.destination);
        }
    }

    // File Upload Handlers
    vocalUpload.addEventListener('change', function(e) {
        handleFileUpload(e.target.files[0], 'vocal');
    });

    musicUpload.addEventListener('change', function(e) {
        handleFileUpload(e.target.files[0], 'music');
    });

    function handleFileUpload(file, type) {
        if (!file) return;
        
        const fileNameElement = type === 'vocal' ? vocalFileName : musicFileName;
        fileNameElement.textContent = file.name;
        
        const reader = new FileReader();
        reader.onload = function(e) {
            initAudioContext();
            audioContext.decodeAudioData(e.target.result)
                .then(buffer => {
                    if (type === 'vocal') {
                        vocalAudioBuffer = buffer;
                        vocalStartTime = 0;
                        vocalEndTime = buffer.duration;
                        updateTrackUI('vocal');
                    } else {
                        musicAudioBuffer = buffer;
                        musicStartTime = 0;
                        musicEndTime = buffer.duration;
                        updateTrackUI('music');
                    }
                    
                    // Show timeline editor if both files are loaded
                    if (vocalAudioBuffer && musicAudioBuffer) {
                        timelineEditor.classList.remove('hidden');
                        exportSection.classList.remove('hidden');
                        createTimelineMarkers();
                    }
                })
                .catch(error => {
                    console.error('Error decoding audio data', error);
                    fileNameElement.textContent += ' (Error loading)';
                });
        };
        reader.readAsArrayBuffer(file);
    }

    // Update track UI with waveform and handles
    function updateTrackUI(type) {
        const buffer = type === 'vocal' ? vocalAudioBuffer : musicAudioBuffer;
        const trackElement = type === 'vocal' ? vocalTrack : musicTrack;
        const startHandle = type === 'vocal' ? vocalStartHandle : musicStartHandle;
        const endHandle = type === 'voral' ? vocalEndHandle : musicEndHandle;
        
        if (!buffer) return;
        
        // Update handle positions
        const duration = buffer.duration;
        const containerWidth = trackElement.parentElement.offsetWidth;
        const startPosition = (type === 'vocal' ? vocalStartTime : musicStartTime) / duration * containerWidth;
        const endPosition = (type === 'voral' ? vocalEndTime : musicEndTime) / duration * containerWidth;
        
        startHandle.style.left = `${startPosition}px`;
        endHandle.style.left = `${endPosition}px`;
        
        // For simplicity, we're not drawing actual waveforms here
        // In a real app, you would use a library like wavesurfer.js
    }

    // Create timeline markers
    function createTimelineMarkers() {
        timelineMarkers.innerHTML = '';
        const duration = Math.max(vocalAudioBuffer.duration, musicAudioBuffer.duration);
        const markerCount = Math.ceil(duration);
        
        for (let i = 0; i <= markerCount; i++) {
            const marker = document.createElement('div');
            marker.className = 'relative h-6 w-px bg-gray-400';
            const label = document.createElement('div');
            label.className = 'absolute top-full text-xs text-gray-500';
            label.textContent = `${i}s`;
            marker.appendChild(label);
            timelineMarkers.appendChild(marker);
        }
    }

    // Play/Pause functionality
    playBtn.addEventListener('click', function() {
        if (isPlaying) {
            stopPlayback();
        } else {
            startPlayback();
        }
    });

    function startPlayback() {
        if (!vocalAudioBuffer || !musicAudioBuffer) return;
        
        stopPlayback(); // Stop any existing playback
        
        initAudioContext();
        
        // Create sources
        vocalSource = audioContext.createBufferSource();
        musicSource = audioContext.createBufferSource();
        
        vocalSource.buffer = vocalAudioBuffer;
        musicSource.buffer = musicAudioBuffer;
        
        vocalSource.connect(vocalGainNode);
        musicSource.connect(musicGainNode);
        
        // Calculate offsets and durations
        const vocalDuration = vocalEndTime - vocalStartTime;
        const musicDuration = musicEndTime - musicStartTime;
        
        // Start playback with offsets
        vocalSource.start(0, vocalStartTime, vocalDuration);
        musicSource.start(0, musicStartTime, musicDuration);
        
        startTime = audioContext.currentTime;
        playStartTime = audioContext.currentTime;
        isPlaying = true;
        
        // Update play button
        const playIcon = playBtn.querySelector('i');
        playIcon.setAttribute('data-feather', 'pause');
        feather.replace();
        
        // Start progress animation
        updatePlaybackProgress();
    }

    function stopPlayback() {
        if (vocalSource) {
            vocalSource.stop();
            vocalSource = null;
        }
        if (musicSource) {
            musicSource.stop();
            musicSource = null;
        }
        
        if (animationFrameId) {
            cancelAnimationFrame(animationFrameId);
            animationFrameId = null;
        }
        
        isPlaying = false;
        
        // Update play button
        const playIcon = playBtn.querySelector('i');
        playIcon.setAttribute('data-feather', 'play');
        feather.replace();
        
        // Reset progress bars
        vocalProgress.style.width = '0%';
        musicProgress.style.width = '0%';
    }

    function updatePlaybackProgress() {
        if (!isPlaying) return;
        
        const currentTime = audioContext.currentTime - playStartTime;
        const vocalDuration = vocalEndTime - vocalStartTime;
        const musicDuration = musicEndTime - musicStartTime;
        
        // Update progress bars
        const vocalProgressPercent = Math.min(100, (currentTime / vocalDuration) * 100);
        const musicProgressPercent = Math.min(100, (currentTime / musicDuration) * 100);
        
        vocalProgress.style.width = `${vocalProgressPercent}%`;
        musicProgress.style.width = `${musicProgressPercent}%`;
        
        // Continue animation
        animationFrameId = requestAnimationFrame(updatePlaybackProgress);
        
        // Stop when both tracks are finished
        if (currentTime >= vocalDuration && currentTime >= musicDuration) {
            stopPlayback();
        }
    }

    // Volume controls
    vocalVolume.addEventListener('input', function() {
        if (vocalGainNode) {
            vocalGainNode.gain.value = this.value;
        }
    });

    musicVolume.addEventListener('input', function() {
        if (musicGainNode) {
            musicGainNode.gain.value = this.value;
        }
    });

    // Drag and drop for track positioning
    [vocalTrack, musicTrack].forEach(track => {
        track.addEventListener('mousedown', startDrag);
    });

    function startDrag(e) {
        if (e.button !== 0) return; // Only left mouse button
        
        isDragging = true;
        currentDraggingElement = e.target;
        dragStartX = e.clientX;
        
        if (currentDraggingElement === vocalTrack) {
            vocalOffsetX = parseFloat(currentDraggingElement.style.transform?.replace('translateX(', '')?.replace('px)', '') || 0);
        } else {
            musicOffsetX = parseFloat(currentDraggingElement.style.transform?.replace('translateX(', '')?.replace('px)', '') || 0);
        }
        
        currentDraggingElement.classList.add('dragging');
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', stopDrag);
    }

    function drag(e) {
        if (!isDragging) return;
        
        const deltaX = e.clientX - dragStartX;
        
        if (currentDraggingElement === vocalTrack) {
            const newOffset = vocalOffsetX + deltaX;
            currentDraggingElement.style.transform = `translateX(${newOffset}px)`;
            
            // Calculate new start time based on position
            const containerWidth = currentDraggingElement.parentElement.offsetWidth;
            const duration = vocalAudioBuffer.duration;
            vocalStartTime = Math.max(0, (deltaX / containerWidth) * duration);
            vocalEndTime = Math.min(duration, vocalEndTime + (deltaX / containerWidth) * duration);
        } else {
            const newOffset = musicOffsetX + deltaX;
            currentDraggingElement.style.transform = `translateX(${newOffset}px)`;
            
            // Calculate new start time based on position
            const containerWidth = currentDraggingElement.parentElement.offsetWidth;
            const duration = musicAudioBuffer.duration;
            musicStartTime = Math.max(0, (deltaX / containerWidth) * duration);
            musicEndTime = Math.min(duration, musicEndTime + (deltaX / containerWidth) * duration);
        }
    }

    function stopDrag() {
        if (!isDragging) return;
        
        isDragging = false;
        currentDraggingElement.classList.remove('dragging');
        document.removeEventListener('mousemove', drag);
        document.removeEventListener('mouseup', stopDrag);
    }

    // Trim buttons functionality
    document.querySelectorAll('.trim-start-btn').forEach(btn => {
        btn.addEventListener('click', function() {
            const trackType = this.getAttribute('data-track');
            if (trackType === 'vocal' && vocalAudioBuffer) {
                vocalStartTime = Math.min(vocalStartTime + 0.1, vocalEndTime - 0.1);
                updateTrackUI('vocal');
            } else if (trackType === 'music' && musicAudioBuffer) {
                musicStartTime = Math.min(musicStartTime + 0.1, musicEndTime - 0.1);
                updateTrackUI('music');
            }
        });
    });

    document.querySelectorAll('.trim-end-btn').forEach(btn => {
        btn.addEventListener('click', function() {
            const trackType = this.getAttribute('data-track');
            if (trackType === 'vocal' && vocalAudioBuffer) {
                vocalEndTime = Math.max(vocalEndTime - 0.1, vocalStartTime + 0.1);
                updateTrackUI('vocal');
            } else if (trackType === 'music' && musicAudioBuffer) {
                musicEndTime = Math.max(musicEndTime - 0.1, musicStartTime + 0.1);
                updateTrackUI('music');
            }
        });
    });

    // Handle resize
    window.addEventListener('resize', function() {
        if (vocalAudioBuffer) updateTrackUI('vocal');
        if (musicAudioBuffer) updateTrackUI('music');
    });

    // Export functionality
    exportBtn.addEventListener('click', function() {
        if (!vocalAudioBuffer || !musicAudioBuffer) return;
        
        progressContainer.classList.remove('hidden');
        exportProgress.style.width = '0%';
        progressText.textContent = 'Processing...';
        
        // Simulate export progress
        let progress = 0;
        const interval = setInterval(() => {
            progress += 5;
            exportProgress.style.width = `${progress}%`;
            
            if (progress >= 100) {
                clearInterval(interval);
                progressText.textContent = 'Export complete!';
                
                // Create a download link for the mixed audio (simplified)
                setTimeout(() => {
                    const a = document.createElement('a');
                    a.href = '#';
                    a.download = 'mixed-audio.wav';
                    a.textContent = 'Download Mixed Audio';
                    a.className = 'text-indigo-600 hover:underline';
                    progressText.innerHTML = '';
                    progressText.appendChild(a);
                }, 500);
            }
        }, 100);
    });

    // Drag and drop file handling
    const uploadContainers = document.querySelectorAll('.border-dashed');
    
    uploadContainers.forEach(container => {
        container.addEventListener('dragover', function(e) {
            e.preventDefault();
            this.classList.add('dragover');
        });
        
        container.addEventListener('dragleave', function() {
            this.classList.remove('dragover');
        });
        
        container.addEventListener('drop', function(e) {
            e.preventDefault();
            this.classList.remove('dragover');
            
            const files = e.dataTransfer.files;
            if (files.length > 0) {
                const inputId = this.querySelector('input[type="file"]').id;
                const fileInput = document.getElementById(inputId);
                
                // Create a new FileList (since we can't modify the existing one)
                const dataTransfer = new DataTransfer();
                dataTransfer.items.add(files[0]);
                fileInput.files = dataTransfer.files;
                
                // Trigger change event
                const event = new Event('change');
                fileInput.dispatchEvent(event);
            }
        });
    });
});