audio-source-composer
Version:
Audio Source Composer
390 lines (332 loc) • 14.1 kB
JavaScript
import React from "react";
import {
ASUIClickable,
ASUIForm, ASUIFormEntry,
ASUIInputTextArea,
ASUIMenuAction,
ASUIMenuBreak,
ASUIMenuDropDown,
ASUIMenuItem,
} from "../../../components";
import {PresetLibrary, ProgramLoader} from "../../../song";
import ASCPresetBrowser from "../browser/ASCPresetBrowser";
import ASCKeyboard from "../../../common/keyboard/ASCKeyboard";
export default class ASCProgramRendererBase extends React.Component {
constructor(props) {
super(props);
this.cb = {
togglePresetBrowser: () => this.togglePresetBrowser(),
toggleSourceEdit: () => this.toggleSourceEdit(),
onSourceEdit: e => this.onSourceEdit(e),
toggleContainer: e => this.toggleContainer(),
onFocus: e => this.onFocus(e),
onKeyPress: e => this.onKeyPress(e),
menuRoot: () => this.renderMenuRoot(),
parentMenu: programID => this.renderMenuManageProgram(programID),
programLoad: (programID, programClass, programConfig) => this.programLoad(programID, programClass, programConfig),
setProps: (programID, props) => this.setProps(programID, props),
}
this.ref = {
textSource: React.createRef(),
program: React.createRef()
}
this.state = {
playingVoices: []
}
this.playingKeys = {};
}
// constructor(props) {
// super(props);
// // this.state = {
// // open: false
// // }
// }
getProgramID() { return this.props.programID; }
getComposer() { return this.props.composer; }
getSong() { return this.getComposer().getSong(); }
getProgramData(proxiedData=true) {
const loader = new ProgramLoader(this.getSong());
return loader.getData(this.getProgramID(), proxiedData)
}
// getProgramEntry(proxiedData=true) {
// let songData = proxiedData
// ? this.getSong().data
// :this.getSong().getProxiedData();
// return songData.programs[this.props.programID];
// }
renderProgramContent() {
try {
let songData = this.getSong().getProxiedData();
const programID = this.props.programID;
const program = songData.programs[programID];
const [className, config] = program;
const {classRenderer: Renderer} = ProgramLoader.getProgramClassInfo(className);
const programProps = this.getRendererState().programProps || {};
// console.log('this.getRendererState()', this.getRendererState(), className);
return (
<Renderer
ref={this.ref.program}
programID={programID}
program={program}
config={config}
setProps={this.cb.setProps}
programLoad={this.cb.programLoad}
parentMenu={this.cb.parentMenu}
{...programProps}
/>
);
} catch (e) {
return e.message;
}
}
renderPresetBrowser() {
const program = this.getProgramData(this.props.programID, true) || ['Empty', {}];
return <ASCPresetBrowser
programLoad={this.cb.programLoad}
programID={this.props.programID}
program={program}
/>;
}
renderSourceEdit() {
const [className, config] = this.getProgramData(this.props.programID, true) || ['Empty', {}];
const jsonString = JSON.stringify(config, null, "\t");
return <ASUIForm
onSubmit={this.cb.onSourceEdit}
>
<ASUIFormEntry header={`Class: ${className}`}>
<ASUIInputTextArea
defaultValue={jsonString}
ref={this.ref.textSource}
placeholder={`{\n "type":"square"\n}`}
rows={16}
cols={28}
/>
</ASUIFormEntry>
<ASUIFormEntry header={`Update`}>
<ASUIClickable onAction={this.cb.onSourceEdit}>
Update
</ASUIClickable>
</ASUIFormEntry>
</ASUIForm>;
}
onSourceEdit(e) {
const textSource = this.ref.textSource.current;
let jsonString = textSource.getValue();
try {
let json = JSON.parse(jsonString);
const program = this.getSong().getProxiedData().programs[this.props.programID];
program[1] = json;
console.log("Updated Program Source: ", json);
this.toggleContainer(true);
} catch (e) {
this.getComposer().setError("Invalid Source: " + e.message);
}
}
/** Events **/
onSongEvent(e) {
// console.log("Program Song Event: ", e.type);
const programRef = this.ref.program.current;
if(programRef && typeof programRef.onSongEvent === "function")
programRef.onSongEvent(e);
}
/** Actions **/
// programReplace(e, programClassName, programConfig={}) {
// const programID = this.props.programID;
// // programConfig = ProgramLoader.createProgramConfig(programClassName, programConfig);
// this.getSong().programReplace(programID, programClassName, programConfig);
// }
programRename() {
const programID = this.props.programID;
this.getComposer().programRenamePrompt(programID);
}
programRemove() {
const programID = this.props.programID;
this.getComposer().programRemovePrompt(programID);
}
async programLoad(presetClassName, presetConfig={}) {
const song = this.getSong();
let [, oldProgramConfig] = song.data.programs[this.props.programID] || [null, null];
console.log("Loading preset: ", presetClassName, presetConfig, oldProgramConfig);
if(oldProgramConfig && oldProgramConfig.title && !presetConfig.title)
presetConfig.title = oldProgramConfig.title;
const instance = ProgramLoader.loadInstance(presetClassName, presetConfig);
if(typeof instance.waitForAssetLoad)
await instance.waitForAssetLoad();
const programID = this.props.programID;
song.programReplace(programID, presetClassName, presetConfig);
this.toggleContainer(true);
}
// getProgramState() {
// return this.getComposer().programGetState(this.props.programID) || {};
// }
//
// setProgramState(state) {
// this.getComposer().programSetState(this.props.programID, state);
// }
setProps(programID, props) {
const rendererState = this.getRendererState();
if(!rendererState.programProps) rendererState.programProps = props;
else Object.assign(rendererState.programProps, props);
this.setRendererState(rendererState);
}
getRendererState() {
return this.props.composer.programGetRendererState(this.props.programID);
}
setRendererState(state) {
return this.props.composer.programSetRendererState(this.props.programID, state);
}
toggleContainer(openState=null) {
const rendererState = this.getRendererState();
if(openState === null) {
if(typeof rendererState.open === "string")
openState = true;
else
openState = !rendererState.open;
}
rendererState.open = openState;
// if(!rendererState.programProps)
// rendererState.programProps = {open: true};
this.setRendererState(rendererState);
}
togglePresetBrowser() {
const rendererState = this.getRendererState();
rendererState.open = rendererState.open === 'browser' ? true : 'browser';
this.setRendererState(rendererState);
}
toggleSourceEdit() {
const rendererState = this.getRendererState();
rendererState.open = rendererState.open === 'source' ? true : 'source';
this.setRendererState(rendererState);
}
// wrapPreset(presetClassName, presetConfig={}) {
// const {classRenderer: Renderer} = ProgramLoader.getProgramClassInfo(presetClassName);
// const [oldClassName, oldConfig] = this.getSong().getProxiedData().programs[this.props.programID] || ['Empty', {}];
// Renderer.addChildProgramToConfig(presetConfig, oldClassName, oldConfig);
// this.loadPreset(presetClassName, presetConfig);
// // TODO: if classes match, prompt confirm
// }
/** Menu **/
renderMenuManageProgram() {
const composer = this.getComposer();
return (<>
<ASUIMenuAction onAction={e => composer.programRenamePrompt(this.props.programID)}>Rename Program</ASUIMenuAction>
<ASUIMenuAction onAction={e => composer.programRemovePrompt(this.props.programID)}>Remove Program</ASUIMenuAction>
</>);
}
renderMenuRoot() {
// const composer = this.getComposer();
// const programState = this.getGlobalProgramState();
return (<>
<ASUIMenuAction onAction={this.cb.togglePresetBrowser}>{this.props.open === 'browser' ? 'Hide' : 'Show'} Preset Browser</ASUIMenuAction>
<ASUIMenuBreak />
<ASUIMenuDropDown options={() => this.renderMenuChangeProgram()}>Change Program</ASUIMenuDropDown>
<ASUIMenuDropDown options={() => this.renderMenuChangePreset()}>Change Preset</ASUIMenuDropDown>
<ASUIMenuBreak />
<ASUIMenuAction onAction={this.cb.toggleSourceEdit}>{this.props.open === 'source' ? 'Hide' : 'Edit'} Source</ASUIMenuAction>
<ASUIMenuBreak />
{this.renderMenuManageProgram()}
</>);
}
renderMenuChangeProgram() {
return (<>
{ProgramLoader.getRegisteredPrograms().map((config, i) =>
<ASUIMenuAction key={i} onAction={e => this.programLoad(config.className)} >{config.title}</ASUIMenuAction>
)}
</>);
}
// renderMenuWrapProgram(menuTitle = "Wrap Program") {
// return (<>
// <ASUIMenuItem>{menuTitle}</ASUIMenuItem>
// <ASUIMenuBreak/>
// <ASUIMenuDropDown options={() => this.renderMenuChangePreset()}>Using Preset</ASUIMenuDropDown>
// <ASUIMenuBreak />
// {ProgramLoader.getRegisteredPrograms().filter((config, i) => {
// return config.classRenderer && config.classRenderer.addChildProgramToConfig;
// }).map((config, i) =>
// <ASUIMenuAction key={i} onAction={e => this.wrapPreset(config.className)} >{config.title}</ASUIMenuAction>
// )}
// </>);
// }
renderMenuChangePreset() {
const onSelectPreset = (presetClass, presetConfig) => this.programLoad(presetClass, presetConfig);
const libraries = PresetLibrary.renderMenuLibraryOptions(async (library) => {
await library.waitForAssetLoad();
return library.renderMenuPresets(onSelectPreset);
});
const recentPresets = PresetLibrary.renderMenuRecentPresets(onSelectPreset);
return (
<>
<ASUIMenuBreak />
{libraries}
{recentPresets ? <>
<ASUIMenuBreak />
<ASUIMenuItem>Recent Presets</ASUIMenuItem>
{recentPresets}
</> : null}
</>
);
}
/** Input **/
handleMIDIInput(e) {
// console.log('handleMIDIInput', e);
const destination = this.getComposer().getDestination();
this.getSong().playMIDIEvent(destination, this.getProgramID(), e.data);
}
onFocus(e) {
// console.log(e.type, this);
switch(e.type) {
case 'focus':
this.getComposer().setSelectedComponent('program', this.getProgramID());
break;
case 'blur':
this.stopAllPlayingKeys();
break;
default:
break;
}
}
stopAllPlayingKeys() {
Object.keys(this.playingKeys).forEach(playingKey => {
const source = this.playingKeys[playingKey];
source.noteOff ? source.noteOff() : source.stop();
})
this.playingKeys = {};
}
onKeyPress(e) {
const rendererState = this.getRendererState();
switch(rendererState.open) {
case 'source':
case false:
return;
default:
}
const {keyboardOctave} = this.getComposer().getTrackPanelState();
const keyboardCommand = ASCKeyboard.instance.getKeyboardCommand(e.key, keyboardOctave);
if(keyboardCommand) {
switch(e.type) {
case 'keydown':
if(!this.playingKeys[e.key]) {
const destination = this.getComposer().getDestination();
this.playingKeys[e.key] = this.getSong().playInstrumentFrequency(destination, this.getProgramID(), keyboardCommand);
} else {
// console.warn("Key already playing: ", e.key, this.playingKeys);
}
break;
case 'keyup':
if(typeof this.playingKeys.hasOwnProperty(e.key)) {
const source = this.playingKeys[e.key];
delete this.playingKeys[e.key];
source && source.noteOff();
// console.log('keyup source', source, e.key);
} else {
console.warn("Playing key not found: ", e.key, this.playingKeys);
}
break;
default:
break;
}
} else {
console.warn("No keyboard command found for: ", e.key);
}
}
}