fix: apply audio clip từ subtab về lại main session
This commit is contained in:
+87
-91
@@ -1825,91 +1825,87 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ── Sub Tab: apply effects & merge back (LOOP_EDITOR_2.md §1.2) ──
|
// ── Sub Tab: apply effects & merge back (LOOP_EDITOR_2.md §1.2) ──
|
||||||
const applySubTab = (tabId) => {
|
const applySubTab = (tabId) => {
|
||||||
const subTab = subTabs.find(s => s.id === tabId);
|
const subTab = subTabs.find(s => s.id === tabId);
|
||||||
if (!subTab || !subTab.buffer) return;
|
if (!subTab || !subTab.buffer) return;
|
||||||
const track = tracks.find(t => t.id === subTab.trackId);
|
const track = tracks.find(t => t.id === subTab.trackId);
|
||||||
if (!track || !track.buffer) return;
|
if (!track || !track.buffer) return;
|
||||||
|
|
||||||
const beforeSnap = captureTrackSnapshot(subTab.trackId);
|
const beforeSnap = captureTrackSnapshot(subTab.trackId);
|
||||||
|
|
||||||
// Clone buffer and apply effects
|
// Clone buffer and apply effects
|
||||||
const ctx = getAudioContext();
|
const ctx = getAudioContext();
|
||||||
const eff = subTab.buffer.getChannelData(0);
|
const sr = subTab.buffer.sampleRate;
|
||||||
const edBuffer = ctx.createBuffer(1, eff.length, subTab.buffer.sampleRate);
|
const eff = subTab.buffer.getChannelData(0);
|
||||||
const edData = edBuffer.getChannelData(0);
|
let resultBuffer = ctx.createBuffer(1, eff.length, sr);
|
||||||
edData.set(eff);
|
let resultData = resultBuffer.getChannelData(0);
|
||||||
|
resultData.set(eff);
|
||||||
|
|
||||||
// Apply effects inline
|
// Apply effects inline
|
||||||
const fx = subTab.effects || {};
|
const fx = subTab.effects || {};
|
||||||
// Reverse
|
|
||||||
if (fx.reverse) {
|
const applyResample = (data, ratio) => {
|
||||||
const reversed = new Float32Array(edData);
|
const newLen = Math.round(data.length * ratio);
|
||||||
for (let i = 0; i < edData.length; i++) reversed[i] = edData[edData.length - 1 - i];
|
const out = new Float32Array(newLen);
|
||||||
edBuffer.copyToChannel(reversed, 0);
|
for (let i = 0; i < newLen; i++) {
|
||||||
}
|
|
||||||
// Gain
|
|
||||||
if (fx.gainDb !== 0) {
|
|
||||||
const gain = Math.pow(10, fx.gainDb / 20);
|
|
||||||
for (let i = 0; i < edData.length; i++) edData[i] = Math.max(-1, Math.min(1, edData[i] * gain));
|
|
||||||
}
|
|
||||||
// Fade in
|
|
||||||
if (fx.fadeInMs > 0) {
|
|
||||||
const sr = edBuffer.sampleRate;
|
|
||||||
const fadeSamples = Math.min(edData.length, Math.floor(fx.fadeInMs / 1000 * sr));
|
|
||||||
for (let i = 0; i < fadeSamples; i++) edData[i] = edData[i] * (i / fadeSamples);
|
|
||||||
}
|
|
||||||
// Fade out
|
|
||||||
if (fx.fadeOutMs > 0) {
|
|
||||||
const sr = edBuffer.sampleRate;
|
|
||||||
const fadeSamples = Math.min(edData.length, Math.floor(fx.fadeOutMs / 1000 * sr));
|
|
||||||
for (let i = edData.length - fadeSamples; i < edData.length; i++) {
|
|
||||||
edData[i] = edData[i] * ((edData.length - 1 - i) / fadeSamples);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Normalize (apply to selection or entire buffer)
|
|
||||||
if (fx.normalizeDb !== 0) {
|
|
||||||
const targetDb = fx.normalizeDb;
|
|
||||||
let maxVal = 0;
|
|
||||||
for (let i = 0; i < edData.length; i++) {
|
|
||||||
const abs = Math.abs(edData[i]);
|
|
||||||
if (abs > maxVal) maxVal = abs;
|
|
||||||
}
|
|
||||||
if (maxVal > 0) {
|
|
||||||
const targetAmp = Math.pow(10, targetDb / 20);
|
|
||||||
const scale = targetAmp / maxVal;
|
|
||||||
for (let i = 0; i < edData.length; i++) {
|
|
||||||
edData[i] = Math.max(-1, Math.min(1, edData[i] * scale));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Pitch shift (simple resample)
|
|
||||||
if (fx.pitch !== 0) {
|
|
||||||
const ratio = Math.pow(2, fx.pitch / 12);
|
|
||||||
const newLength = Math.round(edData.length / ratio);
|
|
||||||
const newData = new Float32Array(newLength);
|
|
||||||
for (let i = 0; i < newLength; i++) {
|
|
||||||
const srcIdx = i * ratio;
|
|
||||||
const idx0 = Math.floor(srcIdx);
|
|
||||||
const idx1 = Math.min(idx0 + 1, edData.length - 1);
|
|
||||||
const frac = srcIdx - idx0;
|
|
||||||
newData[i] = edData[idx0] * (1 - frac) + edData[idx1] * frac;
|
|
||||||
}
|
|
||||||
edData.set(newData);
|
|
||||||
}
|
|
||||||
// Speed stretch
|
|
||||||
if (fx.speedStretch !== 100) {
|
|
||||||
const ratio = fx.speedStretch / 100;
|
|
||||||
const newLength = Math.round(edData.length * ratio);
|
|
||||||
const newData = new Float32Array(newLength);
|
|
||||||
for (let i = 0; i < newLength; i++) {
|
|
||||||
const srcIdx = i / ratio;
|
const srcIdx = i / ratio;
|
||||||
const idx0 = Math.floor(srcIdx);
|
const idx0 = Math.floor(srcIdx);
|
||||||
const idx1 = Math.min(idx0 + 1, edData.length - 1);
|
const idx1 = Math.min(idx0 + 1, data.length - 1);
|
||||||
const frac = srcIdx - idx0;
|
const frac = srcIdx - idx0;
|
||||||
newData[i] = edData[idx0] * (1 - frac) + edData[idx1] * frac;
|
out[i] = data[idx0] * (1 - frac) + data[idx1] * frac;
|
||||||
}
|
}
|
||||||
edData.set(newData);
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fx.reverse) {
|
||||||
|
const rev = new Float32Array(resultData);
|
||||||
|
for (let i = 0; i < resultData.length; i++) rev[i] = resultData[resultData.length - 1 - i];
|
||||||
|
resultData.set(rev);
|
||||||
|
}
|
||||||
|
if (fx.gainDb !== 0) {
|
||||||
|
const gain = Math.pow(10, fx.gainDb / 20);
|
||||||
|
for (let i = 0; i < resultData.length; i++) resultData[i] = Math.max(-1, Math.min(1, resultData[i] * gain));
|
||||||
|
}
|
||||||
|
if (fx.fadeInMs > 0) {
|
||||||
|
const fadeSamples = Math.min(resultData.length, Math.floor(fx.fadeInMs / 1000 * sr));
|
||||||
|
for (let i = 0; i < fadeSamples; i++) resultData[i] *= i / fadeSamples;
|
||||||
|
}
|
||||||
|
if (fx.fadeOutMs > 0) {
|
||||||
|
const fadeSamples = Math.min(resultData.length, Math.floor(fx.fadeOutMs / 1000 * sr));
|
||||||
|
for (let i = resultData.length - fadeSamples; i < resultData.length; i++) {
|
||||||
|
resultData[i] *= (resultData.length - 1 - i) / fadeSamples;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fx.normalizeDb !== 0) {
|
||||||
|
let maxVal = 0;
|
||||||
|
for (let i = 0; i < resultData.length; i++) { const abs = Math.abs(resultData[i]); if (abs > maxVal) maxVal = abs; }
|
||||||
|
if (maxVal > 0) {
|
||||||
|
const targetAmp = Math.pow(10, fx.normalizeDb / 20);
|
||||||
|
const scale = targetAmp / maxVal;
|
||||||
|
for (let i = 0; i < resultData.length; i++) resultData[i] = Math.max(-1, Math.min(1, resultData[i] * scale));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fx.pitch !== 0) {
|
||||||
|
const ratio = Math.pow(2, fx.pitch / 12);
|
||||||
|
const newData = applyResample(resultData, 1 / ratio);
|
||||||
|
resultBuffer = ctx.createBuffer(1, newData.length, sr);
|
||||||
|
resultData = resultBuffer.getChannelData(0);
|
||||||
|
resultData.set(newData);
|
||||||
|
}
|
||||||
|
const stretchSpeed = subTab.speed || 1.0;
|
||||||
|
if (stretchSpeed !== 1.0) {
|
||||||
|
const ratio = 1.0 / stretchSpeed;
|
||||||
|
const newData = applyResample(resultData, ratio);
|
||||||
|
resultBuffer = ctx.createBuffer(1, newData.length, sr);
|
||||||
|
resultData = resultBuffer.getChannelData(0);
|
||||||
|
resultData.set(newData);
|
||||||
|
}
|
||||||
|
if (fx.speedStretch !== 100) {
|
||||||
|
const ratio = fx.speedStretch / 100;
|
||||||
|
const newData = applyResample(resultData, ratio);
|
||||||
|
resultBuffer = ctx.createBuffer(1, newData.length, sr);
|
||||||
|
resultData = resultBuffer.getChannelData(0);
|
||||||
|
resultData.set(newData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subTab.clipId) {
|
if (subTab.clipId) {
|
||||||
@@ -1920,7 +1916,7 @@
|
|||||||
if (c.id === subTab.clipId) {
|
if (c.id === subTab.clipId) {
|
||||||
return {
|
return {
|
||||||
...c,
|
...c,
|
||||||
buffer: edBuffer,
|
buffer: resultBuffer,
|
||||||
name: c.name.endsWith('(edited)') ? c.name : c.name + ' (edited)'
|
name: c.name.endsWith('(edited)') ? c.name : c.name + ' (edited)'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1948,18 +1944,18 @@
|
|||||||
for (let i = 0; i < startSample; i++) mergedData[i] = origData[i];
|
for (let i = 0; i < startSample; i++) mergedData[i] = origData[i];
|
||||||
for (let i = endSample; i < track.buffer.length; i++) mergedData[i] = origData[i];
|
for (let i = endSample; i < track.buffer.length; i++) mergedData[i] = origData[i];
|
||||||
|
|
||||||
for (let i = 0; i < edData.length; i++) {
|
for (let i = 0; i < resultData.length; i++) {
|
||||||
const globalIdx = startSample + i;
|
const globalIdx = startSample + i;
|
||||||
let val = edData[i];
|
let val = resultData[i];
|
||||||
if (i < crossfadeLen) {
|
if (i < crossfadeLen) {
|
||||||
const alpha = i / crossfadeLen;
|
const alpha = i / crossfadeLen;
|
||||||
val = (1 - alpha) * (origData[globalIdx] || 0) + alpha * edData[i];
|
val = (1 - alpha) * (origData[globalIdx] || 0) + alpha * resultData[i];
|
||||||
} else if (i > edData.length - crossfadeLen) {
|
} else if (i > resultData.length - crossfadeLen) {
|
||||||
const distFromEnd = edData.length - 1 - i;
|
const distFromEnd = resultData.length - 1 - i;
|
||||||
const alpha = distFromEnd / crossfadeLen;
|
const alpha = distFromEnd / crossfadeLen;
|
||||||
const origEndIdx = endSample - (edData.length - i);
|
const origEndIdx = endSample - (resultData.length - i);
|
||||||
val = alpha * (origEndIdx >= 0 ? origData[origEndIdx] : 0) + (1 - alpha) * edData[i];
|
val = alpha * (origEndIdx >= 0 ? origData[origEndIdx] : 0) + (1 - alpha) * resultData[i];
|
||||||
}
|
}
|
||||||
mergedData[globalIdx] = val;
|
mergedData[globalIdx] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user