WIP export UI
This commit is contained in:
parent
9a47e0f11e
commit
b3715dc5df
6 changed files with 134 additions and 17 deletions
|
|
@ -270,7 +270,7 @@ export class StrudelMirror {
|
||||||
}
|
}
|
||||||
async exportAudio(begin, end) {
|
async exportAudio(begin, end) {
|
||||||
await this.repl.evaluate(this.code, false)
|
await this.repl.evaluate(this.code, false)
|
||||||
this.repl.exportAudio(begin, end);
|
await this.repl.exportAudio(begin, end);
|
||||||
this.repl.scheduler.stop();
|
this.repl.scheduler.stop();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ export function repl({
|
||||||
|
|
||||||
const stop = () => scheduler.stop();
|
const stop = () => scheduler.stop();
|
||||||
const start = () => scheduler.start();
|
const start = () => scheduler.start();
|
||||||
const exportAudio = (begin, end) => scheduler.exportAudio(begin, end);
|
const exportAudio = async (begin, end) => await scheduler.exportAudio(begin, end);
|
||||||
const pause = () => scheduler.pause();
|
const pause = () => scheduler.pause();
|
||||||
const toggle = () => scheduler.toggle();
|
const toggle = () => scheduler.toggle();
|
||||||
const setCps = (cps) => {
|
const setCps = (cps) => {
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ export async function renderPatternAudio(pattern, cps, begin, end) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
context.startRendering().then(async (renderedBuffer) => {
|
return context.startRendering().then(async (renderedBuffer) => {
|
||||||
console.log(renderedBuffer)
|
console.log(renderedBuffer)
|
||||||
const wavBuffer = audioBufferToWav(renderedBuffer);
|
const wavBuffer = audioBufferToWav(renderedBuffer);
|
||||||
const blob = new Blob([wavBuffer], { type: 'audio/wav' });
|
const blob = new Blob([wavBuffer], { type: 'audio/wav' });
|
||||||
|
|
|
||||||
127
website/src/repl/components/ExportModal.jsx
Normal file
127
website/src/repl/components/ExportModal.jsx
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
||||||
|
import cx from '@src/cx.mjs';
|
||||||
|
import NumberInput from './NumberInput';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Textbox } from './textbox/Textbox';
|
||||||
|
import { setMultiChannelOrbits as setStrudelMultiChannelOrbits, setMaxPolyphony as setStrudelMaxPolyphony } from '@strudel/webaudio';
|
||||||
|
function Checkbox({ label, value, onChange, disabled = false }) {
|
||||||
|
return (
|
||||||
|
<label>
|
||||||
|
<input disabled={disabled} type="checkbox" checked={value} onChange={onChange} />
|
||||||
|
{' ' + label}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default function ExportModal(Props) {
|
||||||
|
const { started, isEmbedded, handleExport } = Props;
|
||||||
|
|
||||||
|
const [startCycle, setStartCycle] = useState(0) // TODO: make a form?
|
||||||
|
const [endCycle, setEndCycle] = useState(0)
|
||||||
|
const [multiChannelOrbits, setMultiChannelOrbits] = useState(true)
|
||||||
|
const [maxPolyphony, setMaxPolyphony] = useState(1024)
|
||||||
|
const [exporting, setExporting] = useState(false)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (started) return;
|
||||||
|
const modal = document.getElementById("exportModal");
|
||||||
|
modal.showModal();
|
||||||
|
}}
|
||||||
|
title="export"
|
||||||
|
className={cx(
|
||||||
|
'flex items-center space-x-1',
|
||||||
|
!isEmbedded ? 'p-2' : 'px-2',
|
||||||
|
started ? 'opacity-50' : 'hover:opacity-50',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!isEmbedded && <span>export</span>}
|
||||||
|
</button>
|
||||||
|
<dialog closedby={exporting ? "none" : "closerequest"} id='exportModal' className='bg-background text-foreground'>
|
||||||
|
<button autofocus onClick={() => {
|
||||||
|
if (exporting) return;
|
||||||
|
const modal = document.getElementById("exportModal");
|
||||||
|
modal.close();
|
||||||
|
}}>Close</button>
|
||||||
|
<div className='flex flex-row items-center gap-4'>
|
||||||
|
<p>Start cycle</p>
|
||||||
|
<Textbox
|
||||||
|
min={1}
|
||||||
|
max={Infinity}
|
||||||
|
onBlur={(e) => {
|
||||||
|
let v = parseInt(e.target.value);
|
||||||
|
console.log(v)
|
||||||
|
v = isNaN(v) ? 0 : Math.max(1, v);
|
||||||
|
setStartCycle(v);
|
||||||
|
}}
|
||||||
|
onChange={v => {
|
||||||
|
v = parseInt(v)
|
||||||
|
setStartCycle(v);
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
placeholder=""
|
||||||
|
value={startCycle ?? ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-row items-center gap-4'>
|
||||||
|
<p>End cycle</p>
|
||||||
|
<Textbox
|
||||||
|
min={1}
|
||||||
|
max={Infinity}
|
||||||
|
onBlur={(e) => {
|
||||||
|
let v = parseInt(e.target.value);
|
||||||
|
v = isNaN(v) ? Math.max(startCycle + 1, parseInt(v)) : v;
|
||||||
|
setEndCycle(v);
|
||||||
|
}}
|
||||||
|
onChange={(v) => {
|
||||||
|
v = parseInt(v)
|
||||||
|
setEndCycle(v);
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
placeholder=""
|
||||||
|
value={endCycle ?? ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-row items-center gap-4'>
|
||||||
|
<p>Maximum polyphony</p>
|
||||||
|
<Textbox
|
||||||
|
min={1}
|
||||||
|
max={Infinity}
|
||||||
|
onBlur={(e) => {
|
||||||
|
let v = parseInt(e.target.value);
|
||||||
|
v = isNaN(v) ? Math.max(1, parseInt(v)) : v;
|
||||||
|
setMaxPolyphony(v);
|
||||||
|
}}
|
||||||
|
onChange={(v) => {
|
||||||
|
v = Math.max(1, parseInt(v));
|
||||||
|
setMaxPolyphony(v);
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
placeholder=""
|
||||||
|
value={maxPolyphony ?? ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Checkbox
|
||||||
|
label="Multi Channel Orbits"
|
||||||
|
onChange={(cbEvent) => {
|
||||||
|
const val = cbEvent.target.checked;
|
||||||
|
setMultiChannelOrbits(val)
|
||||||
|
}}
|
||||||
|
value={multiChannelOrbits}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button onClick={async () => {
|
||||||
|
setStrudelMaxPolyphony(maxPolyphony)
|
||||||
|
setStrudelMultiChannelOrbits(multiChannelOrbits)
|
||||||
|
setExporting(true)
|
||||||
|
await handleExport(startCycle, endCycle).then(() => {
|
||||||
|
setExporting(false)
|
||||||
|
const modal = document.getElementById("exportModal");
|
||||||
|
modal.close();
|
||||||
|
})
|
||||||
|
}}>Export to wav</button>
|
||||||
|
</dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ import StopCircleIcon from '@heroicons/react/20/solid/StopCircleIcon';
|
||||||
import cx from '@src/cx.mjs';
|
import cx from '@src/cx.mjs';
|
||||||
import { useSettings, setIsZen } from '../../settings.mjs';
|
import { useSettings, setIsZen } from '../../settings.mjs';
|
||||||
import '../Repl.css';
|
import '../Repl.css';
|
||||||
|
import ExportModal from './ExportModal';
|
||||||
|
|
||||||
const { BASE_URL } = import.meta.env;
|
const { BASE_URL } = import.meta.env;
|
||||||
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||||
|
|
@ -93,18 +94,7 @@ export function Header({ context, embedded = false }) {
|
||||||
>
|
>
|
||||||
{!isEmbedded && <span>update</span>}
|
{!isEmbedded && <span>update</span>}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<ExportModal isEmbedded={isEmbedded} started={started} handleExport={handleExport} />
|
||||||
onClick={() => !started && handleExport(0, 16)}
|
|
||||||
title="export"
|
|
||||||
className={cx(
|
|
||||||
'flex items-center space-x-1',
|
|
||||||
!isEmbedded ? 'p-2' : 'px-2',
|
|
||||||
started ? 'opacity-50' : 'hover:opacity-50',
|
|
||||||
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{!isEmbedded && <span>export</span>}
|
|
||||||
</button>
|
|
||||||
{/* !isEmbedded && (
|
{/* !isEmbedded && (
|
||||||
<button
|
<button
|
||||||
title="shuffle"
|
title="shuffle"
|
||||||
|
|
|
||||||
|
|
@ -203,8 +203,8 @@ export function useReplContext() {
|
||||||
const handleEvaluate = () => {
|
const handleEvaluate = () => {
|
||||||
editorRef.current.evaluate();
|
editorRef.current.evaluate();
|
||||||
};
|
};
|
||||||
const handleExport = (begin, end) => {
|
const handleExport = async (begin, end) => {
|
||||||
editorRef.current.exportAudio(begin, end);
|
await editorRef.current.exportAudio(begin, end);
|
||||||
};
|
};
|
||||||
const handleShuffle = async () => {
|
const handleShuffle = async () => {
|
||||||
const patternData = await getRandomTune();
|
const patternData = await getRandomTune();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue