Karplus strong generator
This commit is contained in:
@ -504,6 +504,10 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.engine-button:hover {
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.engine-button.active {
|
||||
opacity: 1;
|
||||
border-color: #646cff;
|
||||
|
||||
432
src/lib/audio/engines/KarplusStrong.ts
Normal file
432
src/lib/audio/engines/KarplusStrong.ts
Normal file
@ -0,0 +1,432 @@
|
||||
import type { SynthEngine } from './SynthEngine';
|
||||
|
||||
type HarmonicMode =
|
||||
| 'single' // Just fundamental
|
||||
| 'octave' // 2:1
|
||||
| 'fifth' // 3:2
|
||||
| 'fourth' // 4:3
|
||||
| 'majorThird' // 5:4
|
||||
| 'majorSixth' // 5:3
|
||||
| 'octaveFifth' // 3:1
|
||||
| 'doubleOctave'; // 4:1
|
||||
|
||||
interface KarplusStrongParams {
|
||||
frequency: number; // Hz (50-2000)
|
||||
damping: number; // 0-1 (higher = longer decay)
|
||||
brightness: number; // 0-1 (higher = brighter tone)
|
||||
decayCharacter: number; // -1 to 1 (negative=brighter over time, positive=darker over time)
|
||||
pluckPosition: number; // 0-1 (where to pluck the string)
|
||||
pluckHardness: number; // 0-1 (0=soft/warm, 1=hard/bright)
|
||||
bodyResonance: number; // 0-1 (amount of body resonance)
|
||||
bodyFrequency: number; // 100-800 Hz (body resonance frequency)
|
||||
stereoDetune: number; // 0-1 (stereo detuning amount)
|
||||
outputGain: number; // 0.5-2.0 (output level boost)
|
||||
harmonicMode: HarmonicMode; // Which harmonics to layer
|
||||
harmonicDetune: number; // Amount of detuning from perfect ratio (cents)
|
||||
harmonicMix: number; // 0-1 (how loud the harmonic is vs fundamental)
|
||||
}
|
||||
|
||||
export class KarplusStrong implements SynthEngine<KarplusStrongParams> {
|
||||
getName(): string {
|
||||
return 'Karplus-Strong';
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
return 'Plucked string synthesis using a feedback delay line';
|
||||
}
|
||||
|
||||
getType() {
|
||||
return 'generative' as const;
|
||||
}
|
||||
|
||||
generate(params: KarplusStrongParams, sampleRate: number, duration: number): [Float32Array, Float32Array] {
|
||||
const numSamples = Math.floor(sampleRate * duration);
|
||||
const leftBuffer = new Float32Array(numSamples);
|
||||
const rightBuffer = new Float32Array(numSamples);
|
||||
|
||||
// Generate fundamental frequency
|
||||
const fundamentalLeft = new Float32Array(numSamples);
|
||||
const fundamentalRight = new Float32Array(numSamples);
|
||||
|
||||
this.generateChannel(fundamentalLeft, params, sampleRate, 0);
|
||||
|
||||
const rightParams = {
|
||||
...params,
|
||||
frequency: params.frequency * (1 + params.stereoDetune * 0.005),
|
||||
};
|
||||
this.generateChannel(fundamentalRight, rightParams, sampleRate, params.stereoDetune);
|
||||
|
||||
// Copy fundamental to output
|
||||
for (let i = 0; i < numSamples; i++) {
|
||||
leftBuffer[i] = fundamentalLeft[i];
|
||||
rightBuffer[i] = fundamentalRight[i];
|
||||
}
|
||||
|
||||
// Add harmonic if not single mode
|
||||
if (params.harmonicMode !== 'single') {
|
||||
// Map harmonic mode to frequency ratio
|
||||
const harmonicRatios: Record<HarmonicMode, number> = {
|
||||
single: 1.0,
|
||||
octave: 2.0,
|
||||
fifth: 1.5,
|
||||
fourth: 4 / 3,
|
||||
majorThird: 1.25,
|
||||
majorSixth: 5 / 3,
|
||||
octaveFifth: 3.0,
|
||||
doubleOctave: 4.0,
|
||||
};
|
||||
|
||||
const harmonicRatio = harmonicRatios[params.harmonicMode];
|
||||
|
||||
// Apply slight detuning (convert cents to ratio)
|
||||
// cents = 1200 * log2(ratio), so ratio = 2^(cents/1200)
|
||||
const detuneRatio = Math.pow(2, params.harmonicDetune / 1200);
|
||||
const detunedRatio = harmonicRatio * detuneRatio;
|
||||
|
||||
const harmonicLeft = new Float32Array(numSamples);
|
||||
const harmonicRight = new Float32Array(numSamples);
|
||||
|
||||
const harmonicParams = {
|
||||
...params,
|
||||
frequency: params.frequency * detunedRatio,
|
||||
};
|
||||
|
||||
const harmonicParamsRight = {
|
||||
...params,
|
||||
frequency: params.frequency * detunedRatio * (1 + params.stereoDetune * 0.005),
|
||||
};
|
||||
|
||||
this.generateChannel(harmonicLeft, harmonicParams, sampleRate, 0.5);
|
||||
this.generateChannel(harmonicRight, harmonicParamsRight, sampleRate, params.stereoDetune + 0.5);
|
||||
|
||||
// Mix harmonic with fundamental
|
||||
const harmonicLevel = params.harmonicMix;
|
||||
for (let i = 0; i < numSamples; i++) {
|
||||
leftBuffer[i] = leftBuffer[i] * (1 - harmonicLevel * 0.5) + harmonicLeft[i] * harmonicLevel;
|
||||
rightBuffer[i] = rightBuffer[i] * (1 - harmonicLevel * 0.5) + harmonicRight[i] * harmonicLevel;
|
||||
}
|
||||
}
|
||||
|
||||
return [leftBuffer, rightBuffer];
|
||||
}
|
||||
|
||||
private generateChannel(
|
||||
buffer: Float32Array,
|
||||
params: KarplusStrongParams,
|
||||
sampleRate: number,
|
||||
seed: number
|
||||
): void {
|
||||
// Calculate delay line length from frequency
|
||||
const delayLength = Math.round(sampleRate / params.frequency);
|
||||
const delayLine = new Float32Array(delayLength);
|
||||
|
||||
// Initialize delay line with noise burst
|
||||
this.fillDelayLine(delayLine, params.pluckPosition, params.pluckHardness, seed);
|
||||
|
||||
// Loop gain controls decay rate
|
||||
// Must be < 1 for stability!
|
||||
const loopGain = 0.99 + params.damping * 0.0099; // Range: 0.99 to 0.9999
|
||||
|
||||
// Previous sample for filtering
|
||||
let prevSample = 0;
|
||||
|
||||
// Delay line read position
|
||||
let position = 0;
|
||||
|
||||
// Generate samples
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
// Calculate progress through the sound (0 to 1)
|
||||
const progress = i / buffer.length;
|
||||
|
||||
// Modulate brightness over time based on decay character
|
||||
// Negative decay character = gets brighter over time
|
||||
// Positive decay character = gets darker over time (natural)
|
||||
const brightnessModulation = 1 + params.decayCharacter * progress * 0.8;
|
||||
const currentBrightness = Math.max(0, Math.min(1, params.brightness * brightnessModulation));
|
||||
|
||||
// Low-pass filter coefficient for brightness
|
||||
// 0 = dark/muted, 1 = bright/sustaining
|
||||
const filterCoeff = 0.5 + currentBrightness * 0.49; // Range: 0.5 to 0.99
|
||||
|
||||
// Read current sample from delay line
|
||||
const current = delayLine[position];
|
||||
|
||||
// Apply simple low-pass filter
|
||||
// Classic KS uses: filtered = (current + prev) * 0.5
|
||||
// We add brightness control
|
||||
const filtered = current * filterCoeff + prevSample * (1 - filterCoeff);
|
||||
|
||||
// Apply loop gain for decay
|
||||
const damped = filtered * loopGain;
|
||||
|
||||
// Write back to delay line
|
||||
delayLine[position] = damped;
|
||||
|
||||
// Store for next iteration
|
||||
prevSample = current;
|
||||
|
||||
// Output
|
||||
buffer[i] = current;
|
||||
|
||||
// Advance position
|
||||
position = (position + 1) % delayLength;
|
||||
}
|
||||
|
||||
// Apply final envelope to smooth start/end
|
||||
this.applyEnvelope(buffer, sampleRate);
|
||||
|
||||
// Apply body resonance if enabled
|
||||
if (params.bodyResonance > 0.01) {
|
||||
this.applyBodyResonance(buffer, params.bodyResonance, params.bodyFrequency, sampleRate);
|
||||
}
|
||||
|
||||
// Normalize to ensure consistent output level
|
||||
let maxVal = 0;
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
maxVal = Math.max(maxVal, Math.abs(buffer[i]));
|
||||
}
|
||||
|
||||
if (maxVal > 0.01) {
|
||||
// Normalize to 0.7 and then apply output gain
|
||||
const normalizeGain = (0.7 / maxVal) * params.outputGain;
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
buffer[i] *= normalizeGain;
|
||||
}
|
||||
} else {
|
||||
// Sound is too quiet, just apply output gain
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
buffer[i] *= params.outputGain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fillDelayLine(delayLine: Float32Array, pluckPosition: number, pluckHardness: number, seed: number): void {
|
||||
// Create a burst of noise to excite the string
|
||||
// The pluck position affects the harmonic content
|
||||
|
||||
const burstLength = Math.floor(delayLine.length * 0.1); // 10% of delay line
|
||||
const pluckIndex = Math.floor(pluckPosition * delayLine.length);
|
||||
|
||||
// Simple seeded random for stereo variation
|
||||
let random = seed * 9301 + 49297;
|
||||
const nextRandom = () => {
|
||||
random = (random * 9301 + 49297) % 233280;
|
||||
return (random / 233280) * 2 - 1;
|
||||
};
|
||||
|
||||
// Fill entire delay line with noise burst centered at pluck position
|
||||
for (let i = 0; i < delayLine.length; i++) {
|
||||
const distanceFromPluck = Math.abs(i - pluckIndex);
|
||||
|
||||
// Noise burst that decays away from pluck position
|
||||
if (distanceFromPluck < burstLength) {
|
||||
const window = 1 - (distanceFromPluck / burstLength);
|
||||
delayLine[i] = (seed > 0 ? nextRandom() : Math.random() * 2 - 1) * window;
|
||||
} else {
|
||||
delayLine[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply pluck hardness filter
|
||||
// Low hardness = soft/warm (heavy filtering), high hardness = bright/hard (minimal filtering)
|
||||
// One-pole lowpass filter: y[n] = y[n-1] + alpha * (x[n] - y[n-1])
|
||||
const filterAmount = 1 - pluckHardness; // 0 = no filter, 1 = heavy filter
|
||||
const alpha = 0.1 + pluckHardness * 0.9; // 0.1 (soft) to 1.0 (hard)
|
||||
|
||||
if (filterAmount > 0.01) {
|
||||
let prevSample = 0;
|
||||
for (let i = 0; i < delayLine.length; i++) {
|
||||
const input = delayLine[i];
|
||||
const output = prevSample + alpha * (input - prevSample);
|
||||
delayLine[i] = output;
|
||||
prevSample = output;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize to prevent instability
|
||||
let maxVal = 0;
|
||||
for (let i = 0; i < delayLine.length; i++) {
|
||||
maxVal = Math.max(maxVal, Math.abs(delayLine[i]));
|
||||
}
|
||||
|
||||
if (maxVal > 0) {
|
||||
const normalizeGain = 1.0 / maxVal; // Normalize to full amplitude
|
||||
for (let i = 0; i < delayLine.length; i++) {
|
||||
delayLine[i] *= normalizeGain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private applyBodyResonance(
|
||||
buffer: Float32Array,
|
||||
resonance: number,
|
||||
frequency: number,
|
||||
sampleRate: number
|
||||
): void {
|
||||
// Resonant bandpass filter to simulate acoustic body
|
||||
// Higher resonance = more pronounced body effect
|
||||
|
||||
const Q = 2 + resonance * 8; // Quality factor: 2 to 10
|
||||
const w0 = (2 * Math.PI * frequency) / sampleRate;
|
||||
const alpha = Math.sin(w0) / (2 * Q);
|
||||
|
||||
// Bandpass biquad coefficients
|
||||
const b0 = alpha;
|
||||
const b1 = 0;
|
||||
const b2 = -alpha;
|
||||
const a0 = 1 + alpha;
|
||||
const a1 = -2 * Math.cos(w0);
|
||||
const a2 = 1 - alpha;
|
||||
|
||||
// Normalize coefficients
|
||||
const b0n = b0 / a0;
|
||||
const b1n = b1 / a0;
|
||||
const b2n = b2 / a0;
|
||||
const a1n = a1 / a0;
|
||||
const a2n = a2 / a0;
|
||||
|
||||
// Filter state
|
||||
let x1 = 0, x2 = 0, y1 = 0, y2 = 0;
|
||||
|
||||
// Apply filter with dry/wet mix
|
||||
const wet = resonance;
|
||||
const dry = 1 - wet * 0.7; // Don't fully remove dry signal
|
||||
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
const x0 = buffer[i];
|
||||
const y0 = b0n * x0 + b1n * x1 + b2n * x2 - a1n * y1 - a2n * y2;
|
||||
|
||||
// Mix dry and wet
|
||||
buffer[i] = dry * x0 + wet * y0 * 3; // Boost wet signal
|
||||
|
||||
// Update state
|
||||
x2 = x1;
|
||||
x1 = x0;
|
||||
y2 = y1;
|
||||
y1 = y0;
|
||||
}
|
||||
}
|
||||
|
||||
private applyEnvelope(buffer: Float32Array, sampleRate: number): void {
|
||||
// Short fade in to avoid click
|
||||
const fadeInSamples = Math.floor(sampleRate * 0.005); // 5ms
|
||||
for (let i = 0; i < fadeInSamples && i < buffer.length; i++) {
|
||||
buffer[i] *= i / fadeInSamples;
|
||||
}
|
||||
|
||||
// Fade out at end
|
||||
const fadeOutSamples = Math.floor(sampleRate * 0.05); // 50ms
|
||||
const fadeOutStart = buffer.length - fadeOutSamples;
|
||||
for (let i = fadeOutStart; i < buffer.length; i++) {
|
||||
const fade = (buffer.length - i) / fadeOutSamples;
|
||||
buffer[i] *= fade;
|
||||
}
|
||||
}
|
||||
|
||||
randomParams(): KarplusStrongParams {
|
||||
// Musical frequencies (notes from E2 to E5)
|
||||
const frequencies = [
|
||||
82.41, 87.31, 92.50, 98.00, 103.83, 110.00, 116.54, 123.47, 130.81, 138.59,
|
||||
146.83, 155.56, 164.81, 174.61, 185.00, 196.00, 207.65, 220.00, 233.08, 246.94,
|
||||
261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99, 392.00, 415.30, 440.00,
|
||||
466.16, 493.88, 523.25, 554.37, 587.33, 622.25, 659.25, 698.46, 739.99, 783.99,
|
||||
830.61, 880.00, 932.33, 987.77, 1046.50, 1108.73, 1174.66, 1244.51, 1318.51
|
||||
];
|
||||
|
||||
// Randomly choose harmonic mode
|
||||
// Weighted selection: favor more consonant intervals
|
||||
const modes: HarmonicMode[] = [
|
||||
'single',
|
||||
'single', // More likely to have no harmonic
|
||||
'octave',
|
||||
'octave', // Octaves are very consonant
|
||||
'fifth',
|
||||
'fifth', // Fifths are very consonant
|
||||
'fourth',
|
||||
'majorThird',
|
||||
'majorSixth',
|
||||
'octaveFifth',
|
||||
'doubleOctave',
|
||||
];
|
||||
const harmonicMode = modes[Math.floor(Math.random() * modes.length)];
|
||||
|
||||
// Small detuning for more natural sound (-5 to +5 cents)
|
||||
const harmonicDetune = (Math.random() * 2 - 1) * 5;
|
||||
|
||||
// Body resonance frequencies for different instrument characters
|
||||
const bodyFreqs = [
|
||||
120, 150, 180, // Deep guitar/bass bodies
|
||||
200, 220, 250, 280, // Classical guitar range
|
||||
300, 350, 400, // Mandolin/small guitar
|
||||
450, 500, 600, 700, // Banjo/ukulele/bright
|
||||
];
|
||||
|
||||
return {
|
||||
frequency: frequencies[Math.floor(Math.random() * frequencies.length)],
|
||||
damping: 0.7 + Math.random() * 0.29, // 0.7 to 0.99
|
||||
brightness: Math.random(), // 0 to 1
|
||||
decayCharacter: (Math.random() * 2 - 1) * 0.8, // -0.8 to 0.8 (mostly natural darkening)
|
||||
pluckPosition: 0.2 + Math.random() * 0.6, // 0.2 to 0.8
|
||||
pluckHardness: Math.random(), // 0 to 1 (full range for variety)
|
||||
bodyResonance: Math.random() * 0.7, // 0 to 0.7 (not too extreme)
|
||||
bodyFrequency: bodyFreqs[Math.floor(Math.random() * bodyFreqs.length)],
|
||||
stereoDetune: 0.3 + Math.random() * 0.7, // 0.3 to 1.0
|
||||
outputGain: 1.2 + Math.random() * 0.8, // 1.2 to 2.0 for good volume
|
||||
harmonicMode,
|
||||
harmonicDetune,
|
||||
harmonicMix: harmonicMode === 'single' ? 0 : 0.3 + Math.random() * 0.4, // 0.3 to 0.7
|
||||
};
|
||||
}
|
||||
|
||||
mutateParams(params: KarplusStrongParams, mutationAmount: number = 0.15): KarplusStrongParams {
|
||||
const mutate = (value: number, range: number, min: number, max: number) => {
|
||||
const delta = (Math.random() * 2 - 1) * range * mutationAmount;
|
||||
return Math.max(min, Math.min(max, value + delta));
|
||||
};
|
||||
|
||||
// Occasionally jump to harmonic/subharmonic
|
||||
let newFreq = params.frequency;
|
||||
if (Math.random() < 0.15) {
|
||||
const multipliers = [0.5, 2, 1.5, 3];
|
||||
newFreq = params.frequency * multipliers[Math.floor(Math.random() * multipliers.length)];
|
||||
newFreq = Math.max(50, Math.min(2000, newFreq));
|
||||
} else {
|
||||
newFreq = mutate(params.frequency, 100, 50, 2000);
|
||||
}
|
||||
|
||||
// Occasionally change harmonic mode
|
||||
let newHarmonicMode = params.harmonicMode;
|
||||
if (Math.random() < 0.1) {
|
||||
const modes: HarmonicMode[] = [
|
||||
'single',
|
||||
'single',
|
||||
'octave',
|
||||
'octave',
|
||||
'fifth',
|
||||
'fifth',
|
||||
'fourth',
|
||||
'majorThird',
|
||||
'majorSixth',
|
||||
'octaveFifth',
|
||||
'doubleOctave',
|
||||
];
|
||||
newHarmonicMode = modes[Math.floor(Math.random() * modes.length)];
|
||||
}
|
||||
|
||||
return {
|
||||
frequency: newFreq,
|
||||
damping: mutate(params.damping, 0.2, 0.5, 0.99),
|
||||
brightness: mutate(params.brightness, 0.3, 0, 1),
|
||||
decayCharacter: mutate(params.decayCharacter, 0.5, -1, 1),
|
||||
pluckPosition: mutate(params.pluckPosition, 0.4, 0.1, 0.9),
|
||||
pluckHardness: mutate(params.pluckHardness, 0.4, 0, 1),
|
||||
bodyResonance: mutate(params.bodyResonance, 0.3, 0, 0.9),
|
||||
bodyFrequency: mutate(params.bodyFrequency, 100, 100, 800),
|
||||
stereoDetune: mutate(params.stereoDetune, 0.5, 0, 1),
|
||||
outputGain: mutate(params.outputGain, 0.5, 1.0, 2.0),
|
||||
harmonicMode: newHarmonicMode,
|
||||
harmonicDetune: mutate(params.harmonicDetune, 3, -5, 5),
|
||||
harmonicMix: newHarmonicMode === 'single' ? 0 : mutate(params.harmonicMix, 0.3, 0.2, 0.8),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@ import { NoiseDrum } from './NoiseDrum';
|
||||
import { Ring } from './Ring';
|
||||
import { Sample } from './Sample';
|
||||
import { Input } from './Input';
|
||||
import { KarplusStrong } from './KarplusStrong';
|
||||
|
||||
export const engines: SynthEngine[] = [
|
||||
new Sample(),
|
||||
@ -17,4 +18,5 @@ export const engines: SynthEngine[] = [
|
||||
new ZzfxEngine(),
|
||||
new NoiseDrum(),
|
||||
new Ring(),
|
||||
new KarplusStrong(),
|
||||
];
|
||||
|
||||
37
src/lib/audio/processors/DCOffsetRemover.ts
Normal file
37
src/lib/audio/processors/DCOffsetRemover.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import type { AudioProcessor } from "./AudioProcessor";
|
||||
|
||||
export class DCOffsetRemover implements AudioProcessor {
|
||||
getName(): string {
|
||||
return "DC Offset Remover";
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
return "Removes DC offset bias from the audio signal";
|
||||
}
|
||||
|
||||
async process(
|
||||
leftChannel: Float32Array,
|
||||
rightChannel: Float32Array
|
||||
): Promise<[Float32Array, Float32Array]> {
|
||||
const leftOffset = this.calculateDCOffset(leftChannel);
|
||||
const rightOffset = this.calculateDCOffset(rightChannel);
|
||||
|
||||
const newLeft = new Float32Array(leftChannel.length);
|
||||
const newRight = new Float32Array(rightChannel.length);
|
||||
|
||||
for (let i = 0; i < leftChannel.length; i++) {
|
||||
newLeft[i] = leftChannel[i] - leftOffset;
|
||||
newRight[i] = rightChannel[i] - rightOffset;
|
||||
}
|
||||
|
||||
return [newLeft, newRight];
|
||||
}
|
||||
|
||||
private calculateDCOffset(channel: Float32Array): number {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < channel.length; i++) {
|
||||
sum += channel[i];
|
||||
}
|
||||
return sum / channel.length;
|
||||
}
|
||||
}
|
||||
62
src/lib/audio/processors/TrimSilence.ts
Normal file
62
src/lib/audio/processors/TrimSilence.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import type { AudioProcessor } from "./AudioProcessor";
|
||||
|
||||
export class TrimSilence implements AudioProcessor {
|
||||
getName(): string {
|
||||
return "Trim Silence";
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
return "Removes leading and trailing silence from audio";
|
||||
}
|
||||
|
||||
async process(
|
||||
leftChannel: Float32Array,
|
||||
rightChannel: Float32Array
|
||||
): Promise<[Float32Array, Float32Array]> {
|
||||
const threshold = 0.001;
|
||||
|
||||
const startIndex = this.findSoundStart(leftChannel, rightChannel, threshold);
|
||||
const endIndex = this.findSoundEnd(leftChannel, rightChannel, threshold);
|
||||
|
||||
if (startIndex >= endIndex) {
|
||||
return [new Float32Array(0), new Float32Array(0)];
|
||||
}
|
||||
|
||||
const trimmedLength = endIndex - startIndex + 1;
|
||||
const newLeft = new Float32Array(trimmedLength);
|
||||
const newRight = new Float32Array(trimmedLength);
|
||||
|
||||
for (let i = 0; i < trimmedLength; i++) {
|
||||
newLeft[i] = leftChannel[startIndex + i];
|
||||
newRight[i] = rightChannel[startIndex + i];
|
||||
}
|
||||
|
||||
return [newLeft, newRight];
|
||||
}
|
||||
|
||||
private findSoundStart(
|
||||
leftChannel: Float32Array,
|
||||
rightChannel: Float32Array,
|
||||
threshold: number
|
||||
): number {
|
||||
for (let i = 0; i < leftChannel.length; i++) {
|
||||
if (Math.abs(leftChannel[i]) > threshold || Math.abs(rightChannel[i]) > threshold) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return leftChannel.length - 1;
|
||||
}
|
||||
|
||||
private findSoundEnd(
|
||||
leftChannel: Float32Array,
|
||||
rightChannel: Float32Array,
|
||||
threshold: number
|
||||
): number {
|
||||
for (let i = leftChannel.length - 1; i >= 0; i--) {
|
||||
if (Math.abs(leftChannel[i]) > threshold || Math.abs(rightChannel[i]) > threshold) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -22,6 +22,8 @@ import { PhaseInverter } from './PhaseInverter';
|
||||
import { Compressor } from './Compressor';
|
||||
import { RingModulator } from './RingModulator';
|
||||
import { Waveshaper } from './Waveshaper';
|
||||
import { DCOffsetRemover } from './DCOffsetRemover';
|
||||
import { TrimSilence } from './TrimSilence';
|
||||
|
||||
const processors: AudioProcessor[] = [
|
||||
new SegmentShuffler(),
|
||||
@ -47,6 +49,8 @@ const processors: AudioProcessor[] = [
|
||||
new Compressor(),
|
||||
new RingModulator(),
|
||||
new Waveshaper(),
|
||||
new DCOffsetRemover(),
|
||||
new TrimSilence(),
|
||||
];
|
||||
|
||||
export function getRandomProcessor(): AudioProcessor {
|
||||
|
||||
Reference in New Issue
Block a user