mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 13:48:40 +00:00
fixed offset time
This commit is contained in:
parent
065b24a2ee
commit
9f958abb08
@ -62,7 +62,6 @@
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"events": "^3.3.0",
|
||||
"fft-js": "^0.0.12",
|
||||
"jsdoc": "^4.0.2",
|
||||
"jsdoc-json": "^2.0.2",
|
||||
"jsdoc-to-markdown": "^8.0.0",
|
||||
|
||||
@ -1390,9 +1390,7 @@ export const { speed } = registerControl('speed');
|
||||
* @name stretch
|
||||
* @param {number | Pattern} factor -inf to inf, negative numbers play the sample backwards.
|
||||
* @example
|
||||
* s("bd*6").speed("1 2 4 1 -2 -4")
|
||||
* @example
|
||||
* speed("1 1.5*2 [2 1.1]").s("piano").clip(1)
|
||||
* s("gm_flute").stretch("1 2 .5")
|
||||
*
|
||||
*/
|
||||
export const { stretch } = registerControl('stretch');
|
||||
@ -1407,7 +1405,6 @@ export const { stretch } = registerControl('stretch');
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
export const { unit } = registerControl('unit');
|
||||
/**
|
||||
* Made by Calum Gunn. Reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter. The SuperCollider manual defines Squiz as:
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
numeralArgs,
|
||||
parseNumeral,
|
||||
pairs,
|
||||
clamp,
|
||||
} from './util.mjs';
|
||||
import drawLine from './drawLine.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
@ -1970,6 +1971,7 @@ export const late = register(
|
||||
true,
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Plays a portion of a pattern, specified by the beginning and end of a time span. The new resulting pattern is played over the time period of the original pattern:
|
||||
*
|
||||
|
||||
@ -18,7 +18,7 @@ export default class FFT {
|
||||
// NOTE: Use of `var` is intentional for old V8 versions
|
||||
var table = new Array(this.size * 2);
|
||||
for (var i = 0; i < table.length; i += 2) {
|
||||
const angle = Math.PI * i / this.size;
|
||||
const angle = (Math.PI * i) / this.size;
|
||||
table[i] = Math.cos(angle);
|
||||
table[i + 1] = -Math.sin(angle);
|
||||
}
|
||||
@ -26,8 +26,7 @@ export default class FFT {
|
||||
|
||||
// Find size's power of two
|
||||
var power = 0;
|
||||
for (var t = 1; this.size > t; t <<= 1)
|
||||
power++;
|
||||
for (var t = 1; this.size > t; t <<= 1) power++;
|
||||
|
||||
// Calculate initial step's width:
|
||||
// * If we are full radix-4 - it is 2x smaller to give inital len=8
|
||||
@ -50,14 +49,12 @@ export default class FFT {
|
||||
}
|
||||
fromComplexArray(complex, storage) {
|
||||
var res = storage || new Array(complex.length >>> 1);
|
||||
for (var i = 0; i < complex.length; i += 2)
|
||||
res[i >>> 1] = complex[i];
|
||||
for (var i = 0; i < complex.length; i += 2) res[i >>> 1] = complex[i];
|
||||
return res;
|
||||
}
|
||||
createComplexArray() {
|
||||
const res = new Array(this._csize);
|
||||
for (var i = 0; i < res.length; i++)
|
||||
res[i] = 0;
|
||||
for (var i = 0; i < res.length; i++) res[i] = 0;
|
||||
return res;
|
||||
}
|
||||
toComplexArray(input, storage) {
|
||||
@ -77,8 +74,7 @@ export default class FFT {
|
||||
}
|
||||
}
|
||||
transform(out, data) {
|
||||
if (out === data)
|
||||
throw new Error('Input and output buffers must be different');
|
||||
if (out === data) throw new Error('Input and output buffers must be different');
|
||||
|
||||
this._out = out;
|
||||
this._data = data;
|
||||
@ -88,8 +84,7 @@ export default class FFT {
|
||||
this._data = null;
|
||||
}
|
||||
realTransform(out, data) {
|
||||
if (out === data)
|
||||
throw new Error('Input and output buffers must be different');
|
||||
if (out === data) throw new Error('Input and output buffers must be different');
|
||||
|
||||
this._out = out;
|
||||
this._data = data;
|
||||
@ -99,15 +94,13 @@ export default class FFT {
|
||||
this._data = null;
|
||||
}
|
||||
inverseTransform(out, data) {
|
||||
if (out === data)
|
||||
throw new Error('Input and output buffers must be different');
|
||||
if (out === data) throw new Error('Input and output buffers must be different');
|
||||
|
||||
this._out = out;
|
||||
this._data = data;
|
||||
this._inv = 1;
|
||||
this._transform4();
|
||||
for (var i = 0; i < out.length; i++)
|
||||
out[i] /= this.size;
|
||||
for (var i = 0; i < out.length; i++) out[i] /= this.size;
|
||||
this._out = null;
|
||||
this._data = null;
|
||||
}
|
||||
@ -224,8 +217,7 @@ export default class FFT {
|
||||
// radix-2 implementation
|
||||
//
|
||||
// NOTE: Only called for len=4
|
||||
_singleTransform2(outOff, off,
|
||||
step) {
|
||||
_singleTransform2(outOff, off, step) {
|
||||
const out = this._out;
|
||||
const data = this._data;
|
||||
|
||||
@ -247,8 +239,7 @@ export default class FFT {
|
||||
// radix-4
|
||||
//
|
||||
// NOTE: Only called for len=8
|
||||
_singleTransform4(outOff, off,
|
||||
step) {
|
||||
_singleTransform4(outOff, off, step) {
|
||||
const out = this._out;
|
||||
const data = this._data;
|
||||
const inv = this._inv ? -1 : 1;
|
||||
@ -401,8 +392,7 @@ export default class FFT {
|
||||
}
|
||||
|
||||
// Do not overwrite ourselves
|
||||
if (i === hquarterLen)
|
||||
continue;
|
||||
if (i === hquarterLen) continue;
|
||||
|
||||
// In the flipped case:
|
||||
// MAi = -MAi
|
||||
@ -438,9 +428,7 @@ export default class FFT {
|
||||
// radix-2 implementation
|
||||
//
|
||||
// NOTE: Only called for len=4
|
||||
_singleRealTransform2(outOff,
|
||||
off,
|
||||
step) {
|
||||
_singleRealTransform2(outOff, off, step) {
|
||||
const out = this._out;
|
||||
const data = this._data;
|
||||
|
||||
@ -458,9 +446,7 @@ export default class FFT {
|
||||
// radix-4
|
||||
//
|
||||
// NOTE: Only called for len=8
|
||||
_singleRealTransform4(outOff,
|
||||
off,
|
||||
step) {
|
||||
_singleRealTransform4(outOff, off, step) {
|
||||
const out = this._out;
|
||||
const data = this._data;
|
||||
const inv = this._inv ? -1 : 1;
|
||||
@ -500,17 +486,3 @@ export default class FFT {
|
||||
out[outOff + 7] = FDi;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
// sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file
|
||||
const WEBAUDIO_BLOCK_SIZE = 128;
|
||||
@ -168,7 +168,7 @@ class OLAProcessor extends AudioWorkletProcessor {
|
||||
|
||||
this.readInputs(inputs);
|
||||
this.shiftInputBuffers();
|
||||
this.prepareInputBuffersToSend()
|
||||
this.prepareInputBuffersToSend();
|
||||
this.processOLA(this.inputBuffersToSend, this.outputBuffersToRetrieve, params);
|
||||
this.handleOutputBuffersToRetrieve();
|
||||
this.writeOutputs(outputs);
|
||||
@ -178,7 +178,7 @@ class OLAProcessor extends AudioWorkletProcessor {
|
||||
}
|
||||
|
||||
processOLA(inputs, outputs, params) {
|
||||
console.assert(false, "Not overriden");
|
||||
console.assert(false, 'Not overriden');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -35,7 +35,6 @@
|
||||
"vite": "^5.0.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"fft.js": "^4.0.4",
|
||||
"nanostores": "^0.9.5"
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import './feedbackdelay.mjs';
|
||||
import './reverb.mjs';
|
||||
import './vowel.mjs';
|
||||
import { clamp, nanFallback, _mod } from './util.mjs';
|
||||
import workletsUrl from './worklets.mjs?url';
|
||||
import workletsUrl from './worklets.mjs?worker&url';
|
||||
import { createFilter, gainNode, getCompressor, getWorklet } from './helpers.mjs';
|
||||
import { map } from 'nanostores';
|
||||
import { logger } from './logger.mjs';
|
||||
@ -309,6 +309,13 @@ export function resetGlobalEffects() {
|
||||
}
|
||||
|
||||
export const superdough = async (value, t, hapDuration) => {
|
||||
t = typeof t === 'string' && t.startsWith('=') ? Number(t.slice(1)) : ac.currentTime + t;
|
||||
let { stretch } = value;
|
||||
if (stretch != null) {
|
||||
//account for phase vocoder latency
|
||||
const latency = 0.04;
|
||||
t = t - latency;
|
||||
}
|
||||
const ac = getAudioContext();
|
||||
if (typeof value !== 'object') {
|
||||
throw new Error(
|
||||
@ -320,7 +327,7 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
// duration is passed as value too..
|
||||
value.duration = hapDuration;
|
||||
// calculate absolute time
|
||||
t = typeof t === 'string' && t.startsWith('=') ? Number(t.slice(1)) : ac.currentTime + t;
|
||||
|
||||
if (t < ac.currentTime) {
|
||||
console.warn(
|
||||
`[superdough]: cannot schedule sounds in the past (target: ${t.toFixed(2)}, now: ${ac.currentTime.toFixed(2)})`,
|
||||
@ -371,7 +378,6 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
//
|
||||
coarse,
|
||||
crush,
|
||||
stretch,
|
||||
shape,
|
||||
shapevol = getDefaultValue('shapevol'),
|
||||
distort,
|
||||
@ -441,7 +447,6 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
chain.push(sourceNode);
|
||||
stretch !== undefined && chain.push(getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch }));
|
||||
|
||||
|
||||
// gain stage
|
||||
chain.push(gainNode(gain));
|
||||
|
||||
@ -587,4 +592,6 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
toDisconnect = chain.concat([delaySend, reverbSend, analyserSend]);
|
||||
};
|
||||
|
||||
export const superdoughTrigger = (t, hap, ct, cps) => superdough(hap, t - ct, hap.duration / cps, cps);
|
||||
export const superdoughTrigger = (t, hap, ct, cps) => {
|
||||
superdough(hap, t - ct, hap.duration / cps, cps);
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// LICENSE GNU General Public License v3.0 see https://github.com/dktr0/WebDirt/blob/main/LICENSE
|
||||
// TOFIX: THIS FILE DOES NOT SUPPORT IMPORTS ON DEPOLYMENT
|
||||
|
||||
import OLAProcessor from "./ola-processor"
|
||||
import OLAProcessor from './ola-processor';
|
||||
import FFT from './fft.js';
|
||||
|
||||
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||
@ -469,26 +469,25 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
|
||||
registerProcessor('supersaw-oscillator', SuperSawOscillatorProcessor);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Phase Vocoder sourced from // sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file
|
||||
const BUFFERED_BLOCK_SIZE = 2048;
|
||||
|
||||
function genHannWindow(length) {
|
||||
let win = new Float32Array(length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
win[i] = 0.5 * (1 - Math.cos(2 * Math.PI * i / length));
|
||||
win[i] = 0.5 * (1 - Math.cos((2 * Math.PI * i) / length));
|
||||
}
|
||||
return win;
|
||||
}
|
||||
|
||||
class PhaseVocoderProcessor extends OLAProcessor {
|
||||
static get parameterDescriptors() {
|
||||
return [{
|
||||
return [
|
||||
{
|
||||
name: 'pitchFactor',
|
||||
defaultValue: 1.0
|
||||
}];
|
||||
defaultValue: 1.0,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
constructor(options) {
|
||||
@ -513,7 +512,13 @@ class PhaseVocoderProcessor extends OLAProcessor {
|
||||
|
||||
processOLA(inputs, outputs, parameters) {
|
||||
// no automation, take last value
|
||||
const pitchFactor = parameters.pitchFactor[parameters.pitchFactor.length - 1];
|
||||
|
||||
let pitchFactor = parameters.pitchFactor[parameters.pitchFactor.length - 1];
|
||||
|
||||
if (pitchFactor < 0) {
|
||||
pitchFactor = pitchFactor * 0.25;
|
||||
}
|
||||
pitchFactor = Math.max(0, pitchFactor + 1);
|
||||
|
||||
for (var i = 0; i < this.nbInputs; i++) {
|
||||
for (var j = 0; j < inputs[i].length; j++) {
|
||||
@ -532,7 +537,6 @@ class PhaseVocoderProcessor extends OLAProcessor {
|
||||
this.fft.completeSpectrum(this.freqComplexBufferShifted);
|
||||
this.fft.inverseTransform(this.timeComplexBuffer, this.freqComplexBufferShifted);
|
||||
this.fft.fromComplexArray(this.timeComplexBuffer, output);
|
||||
|
||||
this.applyHannWindow(output);
|
||||
}
|
||||
}
|
||||
@ -543,13 +547,14 @@ class PhaseVocoderProcessor extends OLAProcessor {
|
||||
/** Apply Hann window in-place */
|
||||
applyHannWindow(input) {
|
||||
for (var i = 0; i < this.blockSize; i++) {
|
||||
input[i] = input[i] * this.hannWindow[i];
|
||||
input[i] = input[i] * this.hannWindow[i] * 1.62;
|
||||
}
|
||||
}
|
||||
|
||||
/** Compute squared magnitudes for peak finding **/
|
||||
computeMagnitudes() {
|
||||
var i = 0, j = 0;
|
||||
var i = 0,
|
||||
j = 0;
|
||||
while (i < this.magnitudes.length) {
|
||||
let real = this.freqComplexBuffer[j];
|
||||
let imag = this.freqComplexBuffer[j + 1];
|
||||
@ -621,7 +626,7 @@ class PhaseVocoderProcessor extends OLAProcessor {
|
||||
}
|
||||
|
||||
// apply phase correction
|
||||
let omegaDelta = 2 * Math.PI * (binIndexShifted - binIndex) / this.fftSize;
|
||||
let omegaDelta = (2 * Math.PI * (binIndexShifted - binIndex)) / this.fftSize;
|
||||
let phaseShiftReal = Math.cos(omegaDelta * this.timeCursor);
|
||||
let phaseShiftImag = Math.sin(omegaDelta * this.timeCursor);
|
||||
|
||||
@ -642,8 +647,4 @@ class PhaseVocoderProcessor extends OLAProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor("phase-vocoder-processor", PhaseVocoderProcessor);
|
||||
|
||||
|
||||
|
||||
|
||||
registerProcessor('phase-vocoder-processor', PhaseVocoderProcessor);
|
||||
|
||||
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@ -48,9 +48,6 @@ importers:
|
||||
events:
|
||||
specifier: ^3.3.0
|
||||
version: 3.3.0
|
||||
fft-js:
|
||||
specifier: ^0.0.12
|
||||
version: 0.0.12
|
||||
jsdoc:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2
|
||||
@ -433,9 +430,6 @@ importers:
|
||||
|
||||
packages/superdough:
|
||||
dependencies:
|
||||
fft.js:
|
||||
specifier: ^4.0.4
|
||||
version: 4.0.4
|
||||
nanostores:
|
||||
specifier: ^0.9.5
|
||||
version: 0.9.5
|
||||
@ -7700,18 +7694,6 @@ packages:
|
||||
resolution: {integrity: sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ==}
|
||||
dev: true
|
||||
|
||||
/fft-js@0.0.12:
|
||||
resolution: {integrity: sha512-nLOa0/SYYnN2NPcLrI81UNSPxyg3q0sGiltfe9G1okg0nxs5CqAwtmaqPQdGcOryeGURaCoQx8Y4AUkhGTh7IQ==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
dependencies:
|
||||
bit-twiddle: 1.0.2
|
||||
commander: 2.7.1
|
||||
dev: true
|
||||
|
||||
/fft.js@4.0.4:
|
||||
resolution: {integrity: sha512-f9c00hphOgeQTlDyavwTtu6RiK8AIFjD6+jvXkNkpeQ7rirK3uFWVpalkoS4LAwbdX7mfZ8aoBfFVQX1Re/8aw==}
|
||||
dev: false
|
||||
|
||||
/fftjs@0.0.4:
|
||||
resolution: {integrity: sha512-nIWxQyth1LVD6NH8a+YZUv+McjzbOY6dMe4wv6Pq5cGfP+c8Rd1T8Dsd50DCWlNgzSqA3y9lOkpD6dZD3qHa1A==}
|
||||
dependencies:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user