Skip to content

Commit

Permalink
feat: expose parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
danigb committed Sep 8, 2024
1 parent 5599f9e commit 2a92557
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 22 deletions.
2 changes: 1 addition & 1 deletion packages/chorus/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# @synthlet/chorus

> Chorus mono effect written in Faust and ported to the web
> Chorus effect written in Faust and ported to the web
Part of [Synthlet](https://github.com/danigb/synthlet)
12 changes: 9 additions & 3 deletions packages/chorus/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,24 @@ import { PROCESSOR } from "./processor";
export const registerChorusWorklet = createRegistrar("CHORUS", PROCESSOR);

export type ChorusInputs = {
trigger: ParamInput;
delay: ParamInput;
rate: ParamInput;
depth: ParamInput;
deviation: ParamInput;
};

export type ChorusWorkletNode = AudioWorkletNode & {
trigger: AudioParam;
delay: AudioParam;
rate: AudioParam;
depth: AudioParam;
deviation: AudioParam;
dispose(): void;
};

export const Chorus = createWorkletConstructor<ChorusWorkletNode, ChorusInputs>(
{
processorName: "ChorusProcessor",
paramNames: ["trigger"],
paramNames: ["delay", "rate", "depth", "deviation"],
workletOptions: () => ({
numberOfInputs: 1,
numberOfOutputs: 1,
Expand Down
2 changes: 1 addition & 1 deletion packages/chorus/src/processor.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const PROCESSOR = `"use strict";(()=>{function pt(f){let l=new Float32Array(65536),a=new Float32Array(65536),i=f,r=new Float32Array(2),t=new Float32Array(2),m=new Float32Array(2),p=new Float32Array(2),c=new Float32Array(2),h=new Float32Array(2),T=new Float32Array(2),n=new Float32Array(2),u=new Float32Array(2),x=new Float32Array(2),y=new Float32Array(2),o=new Float32Array(8192),e=0,w=new Int32Array(2),M=Math.min(192e3,Math.max(1,i)),I=Math.exp(-(44.12234/M)),D=1-I,ct=.33333334/M,Tt=1/M,ut=.14285715/M,xt=.5/M,yt=.25/M,At=.16666667/M,wt=.125/M,E=.5,G=.5,O=.5,L=.5;st(l,St),st(a,Vt);function Ft(F,k,v){let P=D*E,Ct=4.096*G,gt=625e-7*O,bt=D*L;for(let A=0;A<F.length;A++){let W=F[A];w[0]=1,r[0]=P+I*r[1];let dt=W*r[0];o[e&8191]=dt,t[0]=Ct+.999*t[1],h[0]=gt*t[0]+.999*h[1];let s=1-w[1];n[0]=bt+I*n[1];let j=s!==0?0:T[1]+ct*n[0];T[0]=j-Math.floor(j);let R=Math.min(4096,.375*t[0]+h[0]*l[Math.max(0,Math.min(Math.floor(65536*T[0]),65535))]),q=Math.floor(R),z=Math.floor(R),H=s!==0?0:u[1]+Tt*n[0];u[0]=H-Math.floor(H);let C=Math.min(4096,.125*t[0]+h[0]*a[Math.max(0,Math.min(Math.floor(65536*u[0]),65535))]),J=Math.floor(C),K=Math.floor(C),N=W*(1-r[0]),Q=s!==0?0:x[1]+ut*n[0];x[0]=Q-Math.floor(Q);let g=Math.min(4096,.875*t[0]-h[0]*l[Math.max(0,Math.min(Math.floor(65536*x[0]),65535))]),U=Math.floor(g),X=Math.floor(g);k[A]=.70710677*(o[e-Math.min(4097,Math.max(0,q))&8191]*(z+(1-R))+(R-z)*o[e-Math.min(4097,Math.max(0,q+1))&8191])+(C-J)*o[e-Math.min(4097,Math.max(0,K+1))&8191]+N+o[e-Math.min(4097,Math.max(0,K))&8191]*(J+(1-C))-.70710677*(o[e-Math.min(4097,Math.max(0,U))&8191]*(X+(1-g))+(g-X)*o[e-Math.min(4097,Math.max(0,U+1))&8191]);let Y=s!==0?0:y[1]+xt*n[0];y[0]=Y-Math.floor(Y);let Z=Math.max(0,Math.min(Math.floor(65536*y[0]),65535)),b=Math.min(4096,.25*t[0]+h[0]*(.70710677*a[Z]+.70710677*l[Z])),_=Math.floor(b),$=Math.floor(b),tt=s!==0?0:m[1]+yt*n[0];m[0]=tt-Math.floor(tt);let et=Math.max(0,Math.min(Math.floor(65536*m[0]),65535)),d=Math.min(4096,.5*t[0]+h[0]*(.70710677*l[et]-.70710677*a[et])),at=Math.floor(d),lt=Math.floor(d),ot=s!==0?0:p[1]+At*n[0];p[0]=ot-Math.floor(ot);let rt=Math.max(0,Math.min(Math.floor(65536*p[0]),65535)),S=Math.min(4096,.75*t[0]-h[0]*(.70710677*a[rt]+.70710677*l[rt])),mt=Math.floor(S),ht=Math.floor(S),nt=s!==0?0:c[1]+wt*n[0];c[0]=nt-Math.floor(nt);let ft=Math.max(0,Math.min(Math.floor(65536*c[0]),65535)),V=Math.min(4096,t[0]+h[0]*(.70710677*a[ft]-.70710677*l[ft])),Mt=Math.floor(V),it=Math.floor(V);v[A]=N-(.38268343*(o[e-Math.min(4097,Math.max(0,_))&8191]*($+(1-b))+(b-$)*o[e-Math.min(4097,Math.max(0,_+1))&8191])+.9238795*(o[e-Math.min(4097,Math.max(0,at))&8191]*(lt+(1-d))+(d-lt)*o[e-Math.min(4097,Math.max(0,at+1))&8191])+.9238795*(o[e-Math.min(4097,Math.max(0,mt))&8191]*(ht+(1-S))+(S-ht)*o[e-Math.min(4097,Math.max(0,mt+1))&8191])+.38268343*(o[e-Math.min(4097,Math.max(0,Mt))&8191]*(it+(1-V))+(V-it)*o[e-Math.min(4097,Math.max(0,Mt+1))&8191])),w[1]=w[0],r[1]=r[0],e=e+1>>>0,t[1]=t[0],h[1]=h[0],n[1]=n[0],T[1]=T[0],u[1]=u[0],x[1]=x[0],y[1]=y[0],m[1]=m[0],p[1]=p[0],c[1]=c[0]}}function Rt(F,k,v,P){E=F,G=k,O=v,L=P}return{update:Rt,compute:Ft}}var St=f=>Math.cos(958738e-10*f),Vt=f=>Math.sin(958738e-10*f);function st(f,l){if(f.length!==65536)throw new Error("Table must be 65536 samples long");let a=0,i=0,r=0,t=0;for(let m=0;m<65536;m++)a=1,r=(i+t)%65536,f[m]=Math.cos(958738e-10*r),i=a,t=r;return f}var B=class extends AudioWorkletProcessor{r;g;constructor(){super(),this.r=!0;let{compute:l}=pt(sampleRate);this.g=l,this.port.onmessage=a=>{switch(a.data.type){case"DISPOSE":this.r=!1;break}}}process(l,a,i){if(l[0].length===0)return this.r;let r=l[0][0],t=a[0][0],m=a[0][1];return this.g(r,t,m),this.r}static get parameterDescriptors(){return[["trigger",0,0,1]].map(([l,a,i,r])=>({name:l,defaultValue:a,minValue:i,maxValue:r,automationRate:"k-rate"}))}};registerProcessor("ChorusProcessor",B);})();`;
export const PROCESSOR = `"use strict";(()=>{function pt(i){let l=new Float32Array(65536),a=new Float32Array(65536),h=i,r=new Float32Array(2),t=new Float32Array(2),m=new Float32Array(2),p=new Float32Array(2),c=new Float32Array(2),n=new Float32Array(2),u=new Float32Array(2),f=new Float32Array(2),T=new Float32Array(2),y=new Float32Array(2),x=new Float32Array(2),o=new Float32Array(8192),e=0,w=new Int32Array(2),M=Math.min(192e3,Math.max(1,h)),v=Math.exp(-(44.12234/M)),D=1-v,ct=.33333334/M,ut=1/M,Tt=.14285715/M,yt=.5/M,xt=.25/M,At=.16666667/M,wt=.125/M,E=.5,G=.5,O=.5,L=.5;st(l,St),st(a,Vt);function Ft(F,I,k){let P=D*E,Rt=4.096*G,Ct=625e-7*O,bt=D*L;for(let A=0;A<F.length;A++){let W=F[A];w[0]=1,r[0]=P+v*r[1];let gt=W*r[0];o[e&8191]=gt,t[0]=Rt+.999*t[1],n[0]=Ct*t[0]+.999*n[1];let s=1-w[1];f[0]=bt+v*f[1];let j=s!==0?0:u[1]+ct*f[0];u[0]=j-Math.floor(j);let d=Math.min(4096,.375*t[0]+n[0]*l[Math.max(0,Math.min(Math.floor(65536*u[0]),65535))]),q=Math.floor(d),z=Math.floor(d),H=s!==0?0:T[1]+ut*f[0];T[0]=H-Math.floor(H);let R=Math.min(4096,.125*t[0]+n[0]*a[Math.max(0,Math.min(Math.floor(65536*T[0]),65535))]),J=Math.floor(R),K=Math.floor(R),N=W*(1-r[0]),Q=s!==0?0:y[1]+Tt*f[0];y[0]=Q-Math.floor(Q);let C=Math.min(4096,.875*t[0]-n[0]*l[Math.max(0,Math.min(Math.floor(65536*y[0]),65535))]),U=Math.floor(C),X=Math.floor(C);I[A]=.70710677*(o[e-Math.min(4097,Math.max(0,q))&8191]*(z+(1-d))+(d-z)*o[e-Math.min(4097,Math.max(0,q+1))&8191])+(R-J)*o[e-Math.min(4097,Math.max(0,K+1))&8191]+N+o[e-Math.min(4097,Math.max(0,K))&8191]*(J+(1-R))-.70710677*(o[e-Math.min(4097,Math.max(0,U))&8191]*(X+(1-C))+(C-X)*o[e-Math.min(4097,Math.max(0,U+1))&8191]);let Y=s!==0?0:x[1]+yt*f[0];x[0]=Y-Math.floor(Y);let Z=Math.max(0,Math.min(Math.floor(65536*x[0]),65535)),b=Math.min(4096,.25*t[0]+n[0]*(.70710677*a[Z]+.70710677*l[Z])),_=Math.floor(b),$=Math.floor(b),tt=s!==0?0:m[1]+xt*f[0];m[0]=tt-Math.floor(tt);let et=Math.max(0,Math.min(Math.floor(65536*m[0]),65535)),g=Math.min(4096,.5*t[0]+n[0]*(.70710677*l[et]-.70710677*a[et])),at=Math.floor(g),lt=Math.floor(g),ot=s!==0?0:p[1]+At*f[0];p[0]=ot-Math.floor(ot);let rt=Math.max(0,Math.min(Math.floor(65536*p[0]),65535)),S=Math.min(4096,.75*t[0]-n[0]*(.70710677*a[rt]+.70710677*l[rt])),ht=Math.floor(S),mt=Math.floor(S),nt=s!==0?0:c[1]+wt*f[0];c[0]=nt-Math.floor(nt);let ft=Math.max(0,Math.min(Math.floor(65536*c[0]),65535)),V=Math.min(4096,t[0]+n[0]*(.70710677*a[ft]-.70710677*l[ft])),it=Math.floor(V),Mt=Math.floor(V);k[A]=N-(.38268343*(o[e-Math.min(4097,Math.max(0,_))&8191]*($+(1-b))+(b-$)*o[e-Math.min(4097,Math.max(0,_+1))&8191])+.9238795*(o[e-Math.min(4097,Math.max(0,at))&8191]*(lt+(1-g))+(g-lt)*o[e-Math.min(4097,Math.max(0,at+1))&8191])+.9238795*(o[e-Math.min(4097,Math.max(0,ht))&8191]*(mt+(1-S))+(S-mt)*o[e-Math.min(4097,Math.max(0,ht+1))&8191])+.38268343*(o[e-Math.min(4097,Math.max(0,it))&8191]*(Mt+(1-V))+(V-Mt)*o[e-Math.min(4097,Math.max(0,it+1))&8191])),w[1]=w[0],r[1]=r[0],e=e+1>>>0,t[1]=t[0],n[1]=n[0],f[1]=f[0],u[1]=u[0],T[1]=T[0],y[1]=y[0],x[1]=x[0],m[1]=m[0],p[1]=p[0],c[1]=c[0]}}function dt(F,I,k,P){E=F,G=I,O=k,L=P}return{update:dt,compute:Ft}}var St=i=>Math.cos(958738e-10*i),Vt=i=>Math.sin(958738e-10*i);function st(i,l){if(i.length!==65536)throw new Error("Table must be 65536 samples long");let a=0,h=0,r=0,t=0;for(let m=0;m<65536;m++)a=1,r=(h+t)%65536,i[m]=Math.cos(958738e-10*r),h=a,t=r;return i}var B=class extends AudioWorkletProcessor{r;u;g;constructor(){super(),this.r=!0;let{compute:l,update:a}=pt(sampleRate);this.u=a,this.g=l,this.port.onmessage=h=>{switch(h.data.type){case"DISPOSE":this.r=!1;break}}}process(l,a,h){if(l[0].length===0)return this.r;this.u(h.delay[0],h.rate[0],h.depth[0],h.deviation[0]);let r=l[0][0],t=a[0][0],m=a[0][1];return this.g(r,t,m),this.r}static get parameterDescriptors(){return[["delay",.5,0,1],["rate",.5,0,1],["depth",.5,0,1],["deviation",.5,0,1]].map(([l,a,h,r])=>({name:l,defaultValue:a,minValue:h,maxValue:r,automationRate:"k-rate"}))}};registerProcessor("ChorusProcessor",B);})();`;
38 changes: 23 additions & 15 deletions packages/chorus/src/worklet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { createChorus } from "./dsp";

export class ChorusProcessor extends AudioWorkletProcessor {
r: boolean; // running
u: ReturnType<typeof createChorus>["update"];
g: ReturnType<typeof createChorus>["compute"];

constructor() {
super();
this.r = true;
const { compute } = createChorus(sampleRate);
const { compute, update } = createChorus(sampleRate);
this.u = update;
this.g = compute;
this.port.onmessage = (event) => {
switch (event.data.type) {
Expand All @@ -18,12 +20,15 @@ export class ChorusProcessor extends AudioWorkletProcessor {
};
}

process(
inputs: Float32Array[][],
outputs: Float32Array[][],
parameters: any
) {
process(inputs: Float32Array[][], outputs: Float32Array[][], params: any) {
if (inputs[0].length === 0) return this.r;

this.u(
params.delay[0],
params.rate[0],
params.depth[0],
params.deviation[0]
);
const input = inputs[0][0];
const outputL = outputs[0][0];
const outputR = outputs[0][1];
Expand All @@ -32,15 +37,18 @@ export class ChorusProcessor extends AudioWorkletProcessor {
}

static get parameterDescriptors() {
return [["trigger", 0, 0, 1]].map(
([name, defaultValue, minValue, maxValue]) => ({
name,
defaultValue,
minValue,
maxValue,
automationRate: "k-rate",
})
);
return [
["delay", 0.5, 0, 1],
["rate", 0.5, 0, 1],
["depth", 0.5, 0, 1],
["deviation", 0.5, 0, 1],
].map(([name, defaultValue, minValue, maxValue]) => ({
name,
defaultValue,
minValue,
maxValue,
automationRate: "k-rate",
}));
}
}

Expand Down
4 changes: 4 additions & 0 deletions site/content/docs/(effects)/chorus-t.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ description: A chorus effect based on the TAL-Noisemaker one

A Chorus effect based on the TAL-Noisemaker that in turn models a Roland Juno-60 chorus.

<Callout title="WIP" type="warn">
This module is WIP.
</Callout>

```ts
import { ChorusT } from "synthlet";

Expand Down
13 changes: 11 additions & 2 deletions site/content/docs/(effects)/chorus.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ description: A chorus effect implemented in Faust

import ChorusExample from "../../../examples/ChorusExample";

This is a mono-to-stereo chorus effect implemented in Faust and ported to the web.

```ts
import { Chorus } from "synthlet";

Expand All @@ -16,10 +18,17 @@ const chorus = Chorus(audioContext, {
});

osc.connect(chorus).connect(audioContext.destination);

chorus.lfoRate1.value = 0.2;
```

<ChorusExample />

## Parameters

- `delay`: The delay time [0, 1]
- `rate`: The modulation rate [0, 1]
- `depth`: The modulation depth [0, 1]
- `deviation`: The modulation deviation [0, 1]

## References

- [Faust original code](https://github.com/grame-cncm/faust/blob/master-dev/examples/SAM/chorus/chorus.dsp)
23 changes: 23 additions & 0 deletions site/examples/ChorusExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { AdsrAmp, Oscillator, Param } from "synthlet";
import { Chorus } from "../../packages/chorus/src";
import { ExamplePane, GateButton } from "./components/ExamplePane";
import { Slider } from "./components/Slider";
import { useSynth } from "./useSynth";

function ChorusSynth(context: AudioContext) {
Expand All @@ -22,6 +23,28 @@ function Example() {

return (
<>
<div className="grid grid-cols-4 gap-2">
<Slider
label="Delay"
inputClassName="col-span-2"
param={synth.chorus.delay}
/>
<Slider
label="Rate"
inputClassName="col-span-2"
param={synth.chorus.rate}
/>
<Slider
label="Depth"
inputClassName="col-span-2"
param={synth.chorus.depth}
/>
<Slider
label="Deviation"
inputClassName="col-span-2"
param={synth.chorus.deviation}
/>
</div>
<GateButton gate={synth.gate.input} />
</>
);
Expand Down

0 comments on commit 2a92557

Please sign in to comment.