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) ──
|
||||
const applySubTab = (tabId) => {
|
||||
const subTab = subTabs.find(s => s.id === tabId);
|
||||
if (!subTab || !subTab.buffer) return;
|
||||
const track = tracks.find(t => t.id === subTab.trackId);
|
||||
if (!track || !track.buffer) return;
|
||||
const applySubTab = (tabId) => {
|
||||
const subTab = subTabs.find(s => s.id === tabId);
|
||||
if (!subTab || !subTab.buffer) return;
|
||||
const track = tracks.find(t => t.id === subTab.trackId);
|
||||
if (!track || !track.buffer) return;
|
||||
|
||||
const beforeSnap = captureTrackSnapshot(subTab.trackId);
|
||||
const beforeSnap = captureTrackSnapshot(subTab.trackId);
|
||||
|
||||
// Clone buffer and apply effects
|
||||
const ctx = getAudioContext();
|
||||
const eff = subTab.buffer.getChannelData(0);
|
||||
const edBuffer = ctx.createBuffer(1, eff.length, subTab.buffer.sampleRate);
|
||||
const edData = edBuffer.getChannelData(0);
|
||||
edData.set(eff);
|
||||
// Clone buffer and apply effects
|
||||
const ctx = getAudioContext();
|
||||
const sr = subTab.buffer.sampleRate;
|
||||
const eff = subTab.buffer.getChannelData(0);
|
||||
let resultBuffer = ctx.createBuffer(1, eff.length, sr);
|
||||
let resultData = resultBuffer.getChannelData(0);
|
||||
resultData.set(eff);
|
||||
|
||||
// Apply effects inline
|
||||
const fx = subTab.effects || {};
|
||||
// Reverse
|
||||
if (fx.reverse) {
|
||||
const reversed = new Float32Array(edData);
|
||||
for (let i = 0; i < edData.length; i++) reversed[i] = edData[edData.length - 1 - i];
|
||||
edBuffer.copyToChannel(reversed, 0);
|
||||
}
|
||||
// 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++) {
|
||||
// Apply effects inline
|
||||
const fx = subTab.effects || {};
|
||||
|
||||
const applyResample = (data, ratio) => {
|
||||
const newLen = Math.round(data.length * ratio);
|
||||
const out = new Float32Array(newLen);
|
||||
for (let i = 0; i < newLen; i++) {
|
||||
const srcIdx = i / ratio;
|
||||
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;
|
||||
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) {
|
||||
@@ -1920,7 +1916,7 @@
|
||||
if (c.id === subTab.clipId) {
|
||||
return {
|
||||
...c,
|
||||
buffer: edBuffer,
|
||||
buffer: resultBuffer,
|
||||
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 = endSample; i < track.buffer.length; i++) mergedData[i] = origData[i];
|
||||
|
||||
for (let i = 0; i < edData.length; i++) {
|
||||
const globalIdx = startSample + i;
|
||||
let val = edData[i];
|
||||
if (i < crossfadeLen) {
|
||||
const alpha = i / crossfadeLen;
|
||||
val = (1 - alpha) * (origData[globalIdx] || 0) + alpha * edData[i];
|
||||
} else if (i > edData.length - crossfadeLen) {
|
||||
const distFromEnd = edData.length - 1 - i;
|
||||
const alpha = distFromEnd / crossfadeLen;
|
||||
const origEndIdx = endSample - (edData.length - i);
|
||||
val = alpha * (origEndIdx >= 0 ? origData[origEndIdx] : 0) + (1 - alpha) * edData[i];
|
||||
}
|
||||
for (let i = 0; i < resultData.length; i++) {
|
||||
const globalIdx = startSample + i;
|
||||
let val = resultData[i];
|
||||
if (i < crossfadeLen) {
|
||||
const alpha = i / crossfadeLen;
|
||||
val = (1 - alpha) * (origData[globalIdx] || 0) + alpha * resultData[i];
|
||||
} else if (i > resultData.length - crossfadeLen) {
|
||||
const distFromEnd = resultData.length - 1 - i;
|
||||
const alpha = distFromEnd / crossfadeLen;
|
||||
const origEndIdx = endSample - (resultData.length - i);
|
||||
val = alpha * (origEndIdx >= 0 ? origData[origEndIdx] : 0) + (1 - alpha) * resultData[i];
|
||||
}
|
||||
mergedData[globalIdx] = val;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user