Files
audio-ui/lib/audio/buffer-utils.ts
Sebastian Krüger 74879a42cf feat: implement multi-track waveform selection and editing with undo/redo
Added comprehensive selection and editing capabilities to multi-track editor:
- Visual selection overlay with Shift+drag interaction on waveforms
- Multi-track edit commands (cut, copy, paste, delete, duplicate)
- Full keyboard shortcut support (Ctrl+X/C/V/D, Delete, Ctrl+Z/Y)
- Complete undo/redo integration via command pattern
- Per-track selection state with localStorage persistence
- Audio buffer manipulation utilities (extract, insert, delete, duplicate segments)
- Toast notifications for all edit operations
- Red playhead to distinguish from blue selection overlay

All edit operations are fully undoable and integrated with the existing
history manager system.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 13:05:05 +01:00

180 lines
4.5 KiB
TypeScript

/**
* AudioBuffer manipulation utilities
*/
import { getAudioContext } from './context';
/**
* Extract a portion of an AudioBuffer
*/
export function extractBufferSegment(
buffer: AudioBuffer,
startTime: number,
endTime: number
): AudioBuffer {
const audioContext = getAudioContext();
const startSample = Math.floor(startTime * buffer.sampleRate);
const endSample = Math.floor(endTime * buffer.sampleRate);
const length = endSample - startSample;
const segment = audioContext.createBuffer(
buffer.numberOfChannels,
length,
buffer.sampleRate
);
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
const sourceData = buffer.getChannelData(channel);
const targetData = segment.getChannelData(channel);
for (let i = 0; i < length; i++) {
targetData[i] = sourceData[startSample + i];
}
}
return segment;
}
/**
* Delete a portion of an AudioBuffer
*/
export function deleteBufferSegment(
buffer: AudioBuffer,
startTime: number,
endTime: number
): AudioBuffer {
const audioContext = getAudioContext();
const startSample = Math.floor(startTime * buffer.sampleRate);
const endSample = Math.floor(endTime * buffer.sampleRate);
const beforeLength = startSample;
const afterLength = buffer.length - endSample;
const newLength = beforeLength + afterLength;
const newBuffer = audioContext.createBuffer(
buffer.numberOfChannels,
newLength,
buffer.sampleRate
);
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
const sourceData = buffer.getChannelData(channel);
const targetData = newBuffer.getChannelData(channel);
// Copy before segment
for (let i = 0; i < beforeLength; i++) {
targetData[i] = sourceData[i];
}
// Copy after segment
for (let i = 0; i < afterLength; i++) {
targetData[beforeLength + i] = sourceData[endSample + i];
}
}
return newBuffer;
}
/**
* Insert an AudioBuffer at a specific position
*/
export function insertBufferSegment(
buffer: AudioBuffer,
insertBuffer: AudioBuffer,
insertTime: number
): AudioBuffer {
const audioContext = getAudioContext();
const insertSample = Math.floor(insertTime * buffer.sampleRate);
const newLength = buffer.length + insertBuffer.length;
const newBuffer = audioContext.createBuffer(
buffer.numberOfChannels,
newLength,
buffer.sampleRate
);
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
const sourceData = buffer.getChannelData(channel);
const insertData = insertBuffer.getChannelData(
Math.min(channel, insertBuffer.numberOfChannels - 1)
);
const targetData = newBuffer.getChannelData(channel);
// Copy before insert point
for (let i = 0; i < insertSample; i++) {
targetData[i] = sourceData[i];
}
// Copy insert buffer
for (let i = 0; i < insertBuffer.length; i++) {
targetData[insertSample + i] = insertData[i];
}
// Copy after insert point
for (let i = insertSample; i < buffer.length; i++) {
targetData[insertSample + insertBuffer.length + (i - insertSample)] = sourceData[i];
}
}
return newBuffer;
}
/**
* Trim buffer to selection
*/
export function trimBuffer(
buffer: AudioBuffer,
startTime: number,
endTime: number
): AudioBuffer {
return extractBufferSegment(buffer, startTime, endTime);
}
/**
* Concatenate two audio buffers
*/
export function concatenateBuffers(
buffer1: AudioBuffer,
buffer2: AudioBuffer
): AudioBuffer {
const audioContext = getAudioContext();
const newLength = buffer1.length + buffer2.length;
const channels = Math.max(buffer1.numberOfChannels, buffer2.numberOfChannels);
const newBuffer = audioContext.createBuffer(
channels,
newLength,
buffer1.sampleRate
);
for (let channel = 0; channel < channels; channel++) {
const targetData = newBuffer.getChannelData(channel);
// Copy first buffer
if (channel < buffer1.numberOfChannels) {
const data1 = buffer1.getChannelData(channel);
targetData.set(data1, 0);
}
// Copy second buffer
if (channel < buffer2.numberOfChannels) {
const data2 = buffer2.getChannelData(channel);
targetData.set(data2, buffer1.length);
}
}
return newBuffer;
}
/**
* Duplicate a segment of audio buffer (extract and insert it after the selection)
*/
export function duplicateBufferSegment(
buffer: AudioBuffer,
startTime: number,
endTime: number
): AudioBuffer {
const segment = extractBufferSegment(buffer, startTime, endTime);
return insertBufferSegment(buffer, segment, endTime);
}