Skip to content

Commit

Permalink
Initial work on code editor for wavetables
Browse files Browse the repository at this point in the history
  • Loading branch information
Ameobea committed Nov 21, 2023
1 parent da0fd47 commit 6462fcb
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 42 deletions.
1 change: 0 additions & 1 deletion public/FMSynthAWP.js
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,6 @@ class FMSynthAWP extends AudioWorkletProcessor {
for (let voiceIx = 0; voiceIx < VOICE_COUNT; voiceIx++) {
const voiceIsTacent = this.tacentVoiceFlags[voiceIx];
if (voiceIsTacent) {
// outputs[voiceIx]?.[0]?.fill(0);
continue;
}

Expand Down
4 changes: 4 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ export interface WavetablePreset {
instState: BuildWavetableInstanceState;
renderedWaveformSamplesBase64: string;
}[];
/**
* JavaScript source code used to produce the wavetable
*/
sourceCode?: string;
}

export interface WavetablePresetDescriptor {
Expand Down
7 changes: 5 additions & 2 deletions src/faustEditor/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,25 @@ import ReactAce from 'react-ace';
// prettier-ignore
import 'ace-builds/src-noconflict/ace';
import 'ace-builds/src-min-noconflict/theme-twilight';
import 'ace-builds/src-noconflict/mode-javascript';

interface CodeEditorProps {
value: string;
onChange: (newContent: string) => void;
mode?: string;
}

const CodeEditor: React.FC<CodeEditorProps> = ({ value, onChange }) => (
const CodeEditor: React.FC<CodeEditorProps> = ({ value, onChange, mode }) => (
<ReactAce
theme='twilight'
mode='text'
mode={mode ?? 'text'}
showPrintMargin={false}
onChange={onChange}
name='ace-editor'
width='calc(50vw - 40px)'
value={value}
style={{ border: '1px solid #555' }}
setOptions={{ useWorker: false }}
/>
);

Expand Down
4 changes: 2 additions & 2 deletions src/faustEditor/FaustEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ export const mkCompileButtonClickHandler =
}
setErrMessage('');

if (!context) {
throw new Error(`No context found for code editor vcId ${vcId}`);
if (!faustEditorContextMap[vcId]) {
return false;
}
const settings = codeNode.getParamSettings(context.paramDefaultValues);

Expand Down
10 changes: 10 additions & 0 deletions src/faustEditor/SvelteCodeEditor.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script lang="ts">
import ReactShim from 'src/misc/ReactShim.svelte';
import CodeEditor from './CodeEditor';
export let value: string;
export let onChange: (newContent: string) => void;
export let mode: string | undefined;
</script>

<ReactShim Component={CodeEditor} props={{ value, onChange, mode }} />
154 changes: 119 additions & 35 deletions src/fmSynth/Wavetable/BuildWavetable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@
volumeDb: number;
wavetablePosition: number;
frequency: number;
enableCodeEditor: boolean;
}
const buildDefaultBuildWavetableState = (): BuildWavetableState => ({
isPlaying: false,
volumeDb: -30,
wavetablePosition: 0,
frequency: 180,
enableCodeEditor: false,
});
const DEFAULT_WAVETABLE_SOURCE_CODE = `// Sawtooth wave
return new Array(32).fill(null).map((_, i) => 1.0 / (i + 1));
`;
</script>

<script lang="ts">
Expand All @@ -25,6 +31,7 @@
} from 'src/api';
import { renderGenericPresetSaverWithModal } from 'src/controls/GenericPresetPicker/GenericPresetSaver';
import SvelteControlPanel from 'src/controls/SvelteControlPanel/SvelteControlPanel.svelte';
import SvelteCodeEditor from 'src/faustEditor/SvelteCodeEditor.svelte';
import {
BUILD_WAVETABLE_INST_HEIGHT_PX,
BUILD_WAVETABLE_INST_WAVEFORM_LENGTH_SAMPLES,
Expand All @@ -35,6 +42,7 @@
} from 'src/fmSynth/Wavetable/BuildWavetableInstance';
import StackedWaveforms from 'src/fmSynth/Wavetable/StackedWaveforms.svelte';
import { WavetableConfiguratorWorker } from 'src/fmSynth/Wavetable/WavetableConfiguratorWorker.worker';
import { HARMONICS_COUNT } from 'src/fmSynth/Wavetable/conf';
import { logError } from 'src/sentry';
export let onSubmit: (val: WavetablePreset) => void;
Expand All @@ -45,12 +53,17 @@
let sliderMode: BuildWavetableSliderMode = BuildWavetableSliderMode.Magnitude;
let inst: BuildWavetableInstance | null = null;
let uiState: BuildWavetableState = buildDefaultBuildWavetableState();
let uiState: BuildWavetableState = {
...buildDefaultBuildWavetableState(),
enableCodeEditor: !!initialInstState?.sourceCode,
};
let presetState: WavetablePreset = {
waveforms: initialInstState?.waveforms ?? [
{ instState: buildDefaultBuildWavetableInstanceState(), renderedWaveformSamplesBase64: '' },
],
sourceCode: initialInstState?.sourceCode ?? DEFAULT_WAVETABLE_SOURCE_CODE,
};
let isExecutingCode = false;
const renderedWavetableRef = {
renderedWavetable: [
new Float32Array(BUILD_WAVETABLE_INST_WAVEFORM_LENGTH_SAMPLES * presetState.waveforms.length),
Expand Down Expand Up @@ -184,6 +197,9 @@
uiState.wavetablePosition = val;
inst?.setWavetablePosition(val);
break;
case 'enable code editor':
uiState.enableCodeEditor = val;
break;
default:
console.error('unhandled key', key);
}
Expand Down Expand Up @@ -246,41 +262,104 @@
style="max-width: {BUILD_WAVETABLE_INST_WIDTH_PX}px; max-height: {BUILD_WAVETABLE_INST_HEIGHT_PX}px;"
use:buildWavetableInstance
/>
<SvelteControlPanel
style={{ width: 440 }}
settings={[
{
type: 'button',
label: `toggle sliders to ${
sliderMode === BuildWavetableSliderMode.Magnitude ? 'phase' : 'magnitude'
}`,
action: () => {
sliderMode =
sliderMode === BuildWavetableSliderMode.Magnitude
? BuildWavetableSliderMode.Phase
: BuildWavetableSliderMode.Magnitude;
inst?.setSliderMode(sliderMode);
<div class="controls-container">
<SvelteControlPanel
style={{ width: 440 }}
settings={[
{
type: 'button',
label: `toggle sliders to ${
sliderMode === BuildWavetableSliderMode.Magnitude ? 'phase' : 'magnitude'
}`,
action: () => {
sliderMode =
sliderMode === BuildWavetableSliderMode.Magnitude
? BuildWavetableSliderMode.Phase
: BuildWavetableSliderMode.Magnitude;
inst?.setSliderMode(sliderMode);
},
},
},
{ type: 'checkbox', label: 'play' },
{ type: 'range', label: 'wavetable position', min: 0, max: 1, step: 0.0001 },
{ type: 'range', label: 'frequency hz', min: 10, max: 20_000, scale: 'log' },
{ type: 'range', label: 'volume db', min: -60, max: 0 },
{ type: 'button', label: 'reset waveform', action: () => inst?.reset() },
{
type: 'button',
label: 'delete waveform',
action: () => deleteWaveform(activeWaveformIx),
},
]}
state={{
play: uiState.isPlaying,
'wavetable position': uiState.wavetablePosition,
'volume db': uiState.volumeDb,
'frequency hz': uiState.frequency,
}}
onChange={handleChange}
/>
{ type: 'checkbox', label: 'play' },
{ type: 'range', label: 'wavetable position', min: 0, max: 1, step: 0.0001 },
{ type: 'range', label: 'frequency hz', min: 10, max: 20_000, scale: 'log' },
{ type: 'range', label: 'volume db', min: -60, max: 0 },
{ type: 'button', label: 'reset waveform', action: () => inst?.reset() },
{
type: 'button',
label: 'delete waveform',
action: () => deleteWaveform(activeWaveformIx),
},
{ type: 'checkbox', label: 'enable code editor' },
]}
state={{
play: uiState.isPlaying,
'wavetable position': uiState.wavetablePosition,
'volume db': uiState.volumeDb,
'frequency hz': uiState.frequency,
'enable code editor': uiState.enableCodeEditor,
}}
onChange={handleChange}
/>
{#if uiState.enableCodeEditor}
<div class="code-editor-container">
<SvelteCodeEditor
value={presetState.sourceCode ?? ''}
onChange={newSourceCode => {
presetState.sourceCode = newSourceCode;
}}
mode="javascript"
/>
<SvelteControlPanel
style={{ width: 440 }}
settings={[
{
type: 'button',
label: 'execute',
action: async () => {
if (isExecutingCode) {
return;
}
isExecutingCode = true;

try {
const fn = new Function(presetState.sourceCode ?? '');
const harmonicAmplitudes = fn();
if (!Array.isArray(harmonicAmplitudes)) {
throw new Error('Provided code did not return an array');
}

// Currently expect each harmonic to be a number
if (!harmonicAmplitudes.every(h => typeof h === 'number')) {
throw new Error('Provided code did not return an array of numbers');
}

// Pad/clamp to expected length
while (harmonicAmplitudes.length < HARMONICS_COUNT) {
harmonicAmplitudes.push(0);
}
while (harmonicAmplitudes.length > HARMONICS_COUNT) {
harmonicAmplitudes.pop();
}

const harmonics = harmonicAmplitudes.map(amp => ({
magnitude: amp,
phase: 0,
}));
const rendered = await worker.renderWavetable([{ harmonics }]);
renderedWavetableRef.renderedWavetable = rendered;
inst?.setState({ harmonics, sliderMode });
} catch (err) {
alert(`Error executing code: ${err}`);
} finally {
isExecutingCode = false;
}
},
},
]}
/>
</div>
{/if}
</div>
</div>
</div>
{#if !hideSaveControls}
Expand Down Expand Up @@ -315,6 +394,11 @@
flex-direction: column;
}
.controls-container {
display: flex;
flex-direction: row;
}
.bottom {
display: flex;
flex: 0;
Expand Down
2 changes: 1 addition & 1 deletion src/fmSynth/Wavetable/BuildWavetableInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as R from 'ramda';
import * as PIXI from 'src/controls/pixi';
import { destroyPIXIApp, makeDraggable } from 'src/controls/pixiUtils';
import { WavetableConfiguratorWorker } from 'src/fmSynth/Wavetable/WavetableConfiguratorWorker.worker';
import { HARMONICS_COUNT } from 'src/fmSynth/Wavetable/conf';
import WaveTable, {
type WavetableDef,
} from 'src/graphEditor/nodes/CustomAudio/WaveTable/WaveTable';
Expand All @@ -16,7 +17,6 @@ const BACKGROUND_COLOR = 0x020202;
export const BUILD_WAVETABLE_INST_HEIGHT_PX = 446;
export const BUILD_WAVETABLE_INST_WIDTH_PX = 1234;

const HARMONICS_COUNT = 64;
export const BUILD_WAVETABLE_INST_WAVEFORM_LENGTH_SAMPLES = 1024 * 4;

const SLIDER_BG_COLOR = 0x1f1f1f;
Expand Down
1 change: 1 addition & 0 deletions src/fmSynth/Wavetable/conf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const HARMONICS_COUNT = 64;
1 change: 0 additions & 1 deletion src/redux/modules/synthDesigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,6 @@ export const deserializeSynthModule = (

const voices = base.voices.map(voice => {
voice.outerGainNode.gain.value = masterGain + 1;
// voice.filterNode.getOutput().connect(voice.outerGainNode);
Object.entries(filterParams)
.filter(([k, _v]) => k !== 'type')
.forEach(([key, val]: [keyof typeof filterParams, any]) =>
Expand Down

0 comments on commit 6462fcb

Please sign in to comment.