UNPKG

audio-source-composer

Version:

Audio Source Composer

424 lines (366 loc) 15.2 kB
import React from 'react'; import { ASUIMenuAction, ASUIMenuBreak, ASUIInputRange, ASUIMenuDropDown, ASUIClickableDropDown, ASUIMenuItem, ASUIGlobalContext, } from "../../../../components"; import {PresetLibrary, ProgramLoader, Values} from "../../../../song"; import PropTypes from "prop-types"; import PeriodicWaveLoader from "../loader/PeriodicWaveLoader"; import OscillatorInstrument from "../OscillatorInstrument"; export default class OscillatorInstrumentRendererBase extends React.Component { /** Property validation **/ static propTypes = { parentMenu: PropTypes.func, setProps: PropTypes.func.isRequired, }; /** Program Context **/ static contextType = ASUIGlobalContext; getGlobalContext() { return this.context; } setStatus(message) { this.context.addLogEntry(message); } setError(message) { this.context.addLogEntry(message, 'error'); } constructor(props) { super(props); this.cb = { onClick: e => this.toggleOpen(), renderLFOMenu: lfoID => this.renderLFOMenu(lfoID), renderMenu: { root: () => this.renderMenuRoot(), }, renderParamMenu: { keyRoot: () => this.renderMenuChangeKeyRoot(), // alias: () => this.renderMenuChangeKeyAlias(), // range: () => this.renderMenuChangeKeyRange(), source: () => this.renderMenuChangeOscillator(), }, changeParam: { mixer: (newValue) => this.changeParam('mixer', newValue), detune: (newValue) => this.changeParam('detune', newValue), pulseWidth: (newValue) => this.changeParam('pulseWidth', newValue), }, setLFOProps: (lfoID, props) => this.setLFOProps(lfoID, props), setEnvelopeProps: (programID, props) => this.setEnvelopeProps(props), }; this.state = { status: null } this.library = PresetLibrary.loadDefault(); } componentDidMount() { const config = this.props.config; if(!config.type) { config.type = 'sawtooth'; console.warn(`No default Oscillator was set. Using ${config.type}`); } if(typeof this.props.open === "undefined") { console.info("Auto opening", this) this.setProps({open: true}) } } getTitle() { return this.props.config.title || this.props.config.type || getNameFromURL(this.props.config.url) || "Unknown Osc"; } /** Parameters **/ getParameters() { const config = this.props.config; const list = [ 'source', 'mixer', 'detune', ] if(config.type === 'pulse') list.splice(1, 0, 'pulseWidth'); if(config.keyRoot) list.push('keyRoot'); const inputParameters = OscillatorInstrument.inputParameters; return list.map(parameterName => { const parameterInfo = inputParameters[parameterName] || {}; return { label: parameterInfo.label || parameterName, title: parameterInfo.title || parameterName, children: this.renderInput(parameterName), } }) } /** Properties **/ setProps(props) { this.props.setProps(this.props.programID, props); } /** LFO **/ renderLFO(lfoID, lfoProgram) { const [className, config] = lfoProgram; const {classRenderer: Renderer} = ProgramLoader.getProgramClassInfo(className); const lfosProps = this.props.lfosProps || []; const lfoProps = lfosProps[lfoID] || {}; return <Renderer key={lfoID} parentMenu={this.cb.renderLFOMenu} config={config} programID={lfoID} program={lfoProgram} parameters={OscillatorInstrument.sourceParameters} setProps={this.cb.setLFOProps} {...lfoProps} />; } setLFOProps(lfoID, props) { const lfosProps = this.props.lfosProps || []; if(lfosProps[lfoID]) Object.assign(lfosProps[lfoID], props); else lfosProps[lfoID] = props; this.setProps({lfosProps: lfosProps}); } /** Envelope **/ renderEnvelope(envelopeProgram) { if(envelopeProgram) { const [className, config] = envelopeProgram; const {classRenderer: Renderer} = ProgramLoader.getProgramClassInfo(className); const envelopeProps = this.props.envelopeProps || []; return <Renderer config={config} program={envelopeProgram} parameters={OscillatorInstrument.sourceParameters} setProps={this.cb.setEnvelopeProps} {...envelopeProps} />; } } setEnvelopeProps(props) { // console.log('setEnvelopeProps', props); const envelopeProps = this.props.envelopeProps || {}; Object.assign(envelopeProps, props); this.setProps({envelopeProps}); } /** Inputs **/ renderInput(paramName) { const config = this.props.config; const inputParameters = OscillatorInstrument.inputParameters[paramName] || {}; const onChange = this.cb.changeParam[paramName]; let value = config[paramName]; if(typeof value === "undefined") value = inputParameters.default; switch(paramName) { case 'source': let source = "N/A"; if(config.type) source = config.type; if(config.url) source = getNameFromURL(config.url); if(source && source.length > 16) source = '...' + source.substr(-16); return <ASUIClickableDropDown {...inputParameters} // className="small" vertical options={this.cb.renderParamMenu.source} >{source}</ASUIClickableDropDown>; case 'keyRoot': return <ASUIClickableDropDown className="small" vertical options={this.cb.renderParamMenu.keyRoot} >{config.keyRoot ? config.keyRoot : "[No Root Set]"}</ASUIClickableDropDown> case 'mixer': case 'detune': case 'pulseWidth': return <ASUIInputRange {...inputParameters} value={value} onChange={onChange} /> // case 'root': // return <ASUIClickableDropDown // className="small" // options={this.cb.renderParamMenu.root} // >{config.keyRoot ? config.keyRoot : "-"}</ASUIClickableDropDown> // case 'alias': // return <ASUIClickableDropDown // className="small" // options={this.cb.renderParamMenu.alias} // >{config.alias ? config.alias : "-"}</ASUIClickableDropDown> // case 'range': // return <ASUIClickableDropDown // className="small" // options={this.cb.renderParamMenu.range} // >{config.range ? config.range : "[any]"}</ASUIClickableDropDown> default: return 'Unknown'; } } /** Actions **/ toggleOpen() { const open = !this.props.open; this.props.setProps(this.props.programID, {open}); } changeParam(paramName, newValue) { // console.log(`Changing parameter ${paramName}: ${newValue} [Old: ${this.props.config[paramName]}]`); this.props.config[paramName] = newValue; } removeParam(paramName) { // console.log(`Removing parameter ${paramName}: ${this.props.config[paramName]}`); delete this.props.config[paramName]; } changeOscillatorStandard(newType) { const oldConfig = Object.assign({}, this.props.config); delete oldConfig.url; oldConfig.type = newType; switch(newType) { case 'pulse': oldConfig.pulseWidth = OscillatorInstrument.inputParameters.pulseWidth.default; break; default: delete oldConfig.pulseWidth; break; } this.props.program[1] = oldConfig; } async changeSampleURL(url) { const service = new PeriodicWaveLoader(); try { this.setState({status: 'loading', error: null}); this.setStatus("Loading Sample: " + url); await service.loadPeriodicWaveFromURL(url); this.props.config.url = url; this.props.config.type = 'custom'; this.setState({status: 'loaded'}); this.setStatus("Loaded Sample: " + url); } catch (e) { this.setState({status: 'error', error: e.message}); this.setError(e); } } // changeLoop(newLoopValue=null) { // if(newLoopValue === null) // newLoopValue = !this.props.config.loop; // this.props.config.loop = newLoopValue?1:0; // } // loadPreset(className, presetConfig) { // if(className !== this.props.program[0]) // throw new Error(`This preset is for class ${className}, not ${this.props.program[0]}`); // if(!presetConfig.type) // presetConfig.type = 'custom'; // this.props.program[1] = presetConfig; // console.log("Loaded preset: ", presetConfig); // } /** LFO Actions **/ addLFO(parameterName) { const lfos = this.props.config.lfos || []; lfos.push(['LFO', { parameter: parameterName }]) this.props.config.lfos = lfos; } removeLFO(lfoID) { const lfos = this.props.config.lfos; if(!lfos[lfoID]) throw new Error("LFO not found: " + lfoID); lfos.splice(lfoID, 1); this.props.config.lfos = lfos; } /** Menus **/ renderMenuRoot() { return (<> <ASUIMenuAction onAction={()=>{}} disabled>{`Oscillator: ${this.getTitle()}`}</ASUIMenuAction> <ASUIMenuBreak /> <ASUIMenuDropDown options={() => this.renderMenuChangeOscillator()}>Change Oscillator</ASUIMenuDropDown> <ASUIMenuBreak /> <ASUIMenuDropDown options={() => this.renderMenuChangeParameter('mixer')}>Edit Mixer</ASUIMenuDropDown> <ASUIMenuDropDown options={() => this.renderMenuChangeParameter('detune')}>Edit Detune</ASUIMenuDropDown> <ASUIMenuDropDown options={() => this.renderMenuChangeKeyRoot()}>Edit Key Root</ASUIMenuDropDown> <ASUIMenuBreak /> <ASUIMenuDropDown options={() => this.renderMenuAddLFO()}>Add LFO</ASUIMenuDropDown> {this.props.parentMenu ? <> <ASUIMenuBreak /> {this.props.parentMenu(this.props.programID)} </> : null} </>); } renderMenuChangeOscillator() { return (<> <ASUIMenuDropDown options={() => this.renderMenuChangeOscillatorStandard()}>Standard</ASUIMenuDropDown> <ASUIMenuDropDown options={() => this.renderMenuChangeOscillatorSample()}>Sample</ASUIMenuDropDown> {this.renderMenuChangeOscillatorSampleRecent()} {/*{await this.library.renderMenuPresets((className, presetConfig) => {*/} {/* this.loadPreset(className, presetConfig);*/} {/*}, this.props.program[0])}*/} </>); } renderMenuChangeOscillatorStandard() { return (<> <ASUIMenuAction onAction={e => this.changeOscillatorStandard('sine')}>Sine</ASUIMenuAction> <ASUIMenuAction onAction={e => this.changeOscillatorStandard('square')}>Square</ASUIMenuAction> <ASUIMenuAction onAction={e => this.changeOscillatorStandard('sawtooth')}>Sawtooth</ASUIMenuAction> <ASUIMenuAction onAction={e => this.changeOscillatorStandard('triangle')}>Triangle</ASUIMenuAction> <ASUIMenuAction onAction={e => this.changeOscillatorStandard('pulse')}>Pulse</ASUIMenuAction> </>); } renderMenuChangeOscillatorSample() { const libraries = PresetLibrary.renderMenuLibraryOptions(async (library) => { await library.waitForAssetLoad(); return this.renderMenuChangeOscillatorSampleWithLibrary(library); }); return ( <> <ASUIMenuItem>Select New Sample</ASUIMenuItem> <ASUIMenuBreak /> {libraries} {this.renderMenuChangeOscillatorSampleRecent()} </> ); } renderMenuChangeOscillatorSampleWithLibrary(library) { return library.renderMenuSamples( (sampleURL) => this.changeSampleURL(sampleURL), OscillatorInstrument.sampleFileRegex); } renderMenuChangeOscillatorSampleRecent() { const recentSamples = PresetLibrary.renderMenuRecentSamples( sampleURL => this.changeSampleURL(sampleURL), OscillatorInstrument.sampleFileRegex ) return recentSamples ? <> <ASUIMenuBreak /> <ASUIMenuItem>Recent Samples</ASUIMenuItem> {recentSamples} </> : null; } renderMenuChangeParameter(parameterName, parameterTitle=null) { return (<> {this.renderInput(parameterName)} <ASUIMenuBreak/> <ASUIMenuAction onAction={() => this.removeParam(parameterName)}>Clear {parameterTitle || parameterName}</ASUIMenuAction> <ASUIMenuBreak/> <ASUIMenuAction onAction={() => true}>Done</ASUIMenuAction> </>); } renderMenuChangeKeyRoot() { let title = "Set Root Key"; if(this.props.config.keyRoot) title = "Change Root Key: " + this.props.config.keyRoot; return (<> {Values.instance.renderMenuSelectFrequencyWithRecent(noteNameOctave => { this.changeParam('keyRoot', noteNameOctave) }, this.props.config.keyRoot, title)} <ASUIMenuBreak/> <ASUIMenuAction onAction={() => this.removeParam('keyRoot')}>Clear Root</ASUIMenuAction> </>); } /** LFO **/ renderMenuAddLFO() { const sourceParameters = OscillatorInstrument.sourceParameters; return Object.keys(sourceParameters).map((sourceParameter, i) => <ASUIMenuAction key={i++} onAction={() => this.addLFO(sourceParameter)} >{`on ${sourceParameters[sourceParameter]}`}</ASUIMenuAction>) } renderLFOMenu(lfoID) { let i=0; return [ <ASUIMenuAction key={i++} onAction={e => this.removeLFO(lfoID)}>Remove LFO</ASUIMenuAction> ]; } } function getNameFromURL(url) { return url ? url.split('/').pop().replace('.json', '') : null; }