fixed offset time

This commit is contained in:
Jade (Rose) Rowland 2024-06-12 01:11:58 -04:00
parent 065b24a2ee
commit 9f958abb08
9 changed files with 784 additions and 825 deletions

View File

@ -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",

View File

@ -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:

View File

@ -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:
*

View File

@ -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;
}
}

View File

@ -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;
@ -66,7 +66,7 @@ class OLAProcessor extends AudioWorkletProcessor {
this.inputBuffersHead[inputIndex] = new Array(nbChannels);
this.inputBuffersToSend[inputIndex] = new Array(nbChannels);
for (let i = 0; i < nbChannels; i++) {
this.inputBuffersHead[inputIndex][i] = this.inputBuffers[inputIndex][i] .subarray(0, this.blockSize);
this.inputBuffersHead[inputIndex][i] = this.inputBuffers[inputIndex][i].subarray(0, this.blockSize);
this.inputBuffersToSend[inputIndex][i] = new Float32Array(this.blockSize);
}
}
@ -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');
}
}

View File

@ -35,7 +35,6 @@
"vite": "^5.0.10"
},
"dependencies": {
"fft.js": "^4.0.4",
"nanostores": "^0.9.5"
}
}

View File

@ -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);
};

View File

@ -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,20 +547,21 @@ 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];
// no need to sqrt for peak finding
this.magnitudes[i] = real ** 2 + imag ** 2;
i+=1;
j+=2;
i += 1;
j += 2;
}
}
@ -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
View File

@ -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: