UNPKG

audio-source-composer

Version:

Audio Source Composer

1,130 lines (908 loc) 39.4 kB
import {PresetLibrary, ProgramLoader, Song, ClientStorage, FileService, FileSupport} from "../song"; import {Instruction, Values} from "../song"; import PromptManager from "../common/prompt/PromptManager"; import ASComposerMenu from "./ASComposerMenu"; import ClientUserAPI from "../server/user/ClientUserAPI"; // import {TrackInfo} from "./track/"; class ASComposerActions extends ASComposerMenu { /** Status **/ setStatus(statusText, statusType='log') { console[statusType](statusText); this.setState({statusText, statusType: statusType + ''}); } setError(statusText) { this.setStatus(statusText, 'error'); } /** User Session **/ async sessionRefresh() { const userAPI = new ClientUserAPI(); const session = await userAPI.getSession(); // console.log('User Session: ', session); this.setState({ session }) } sessionIsUserLoggedIn() { return this.state.session.loggedIn; } toggleModal(modalName) { this.setState({ showModal: this.state.showModal === modalName ? null : modalName }) } showModal(modalName, modalArgs=null) { const state = { showModal: modalName, modalArgs }; this.setState(state); } /** Library **/ setLibrary(library) { if(!(library instanceof PresetLibrary)) throw new Error("Invalid library: " + typeof library); this.library = library; // console.log('Current library: ', library); } /** Selected Component **/ setSelectedComponent(type, id) { const selectedComponent = [type, id]; console.log('selectedComponent', selectedComponent); if(this.state.selectedComponent.type === type && this.state.selectedComponent.id === id) return; this.setState({selectedComponent}) } getSelectedComponent() { const [type, id] = this.state.selectedComponent; switch(type) { case 'program': return this.programGetRef(id); case 'track': return this.trackGetRef(id); default: throw new Error("Invalid component type: " + type); } } // getSelectedViewKey() { // return this.state.selectedViewKey; // } getSelectedTrackName() { const [type, id] = this.state.selectedComponent; if(type === 'track') return id; return null; } /** Song rendering **/ getSong() { return this.song; } /** * Sets current composer song * @param song */ async setCurrentSong(song) { if(!song instanceof Song) throw new Error("Invalid current song"); if(this.song) { this.setStatus("Unloading song: " + this.song.data.title); if(this.song.isPlaying()) { this.song.stopPlayback(); } this.song.removeEventListener('*', this.cb.onSongEventCallback); this.song.unloadAll(); } this.song = song; this.song.addEventListener('*', this.cb.onSongEventCallback); let songLength = 0; try { songLength = song.getSongLengthInSeconds(); } catch (e) { console.error("Error loading song length: ", e); } if(this.props.onSongLoad) this.props.onSongLoad(song); const startTrackName = song.getStartTrackName() || 'root'; const state = { statusText: "Loaded song: " + song.data.title, statusType: 'log', title: song.data.title, songUUID: song.data.uuid, songLength, selectedComponent: ['track', startTrackName], activeTracks: {}, programStates: [], } state.activeTracks[startTrackName] = {viewMode: 'none'}; // TODO: Reset offsets await this.setStateAsync(state); console.log("Loading Song: ", song.data, songLength); await this.song.programLoadAll(); // console.log("Current Song: ", song.data, songLength); } updateCurrentSong() { const selectedTrack = this.getSelectedTrackName(); const activeTracks = this.state.activeTracks; if(Object.keys(activeTracks).length === 0) activeTracks[selectedTrack || 'root'] = {} // const songData = this.song.getProxiedData(); // for(let trackName in this.state.activeTracks) { // if(this.state.activeTracks.hasOwnProperty(trackName)) { // if (songData.tracks[trackName]) { // // if (this.state.activeTracks.hasOwnProperty(trackName)) { // // const trackState = new TrackState(this, trackName); // // trackState.updateRenderingStats(); // // } // console.log("TODO finish") // } else { // // delete activeTracks[trackName]; // console.warn("Removing unavailable active track: " + trackName); // } // } // } let songLength = 0; try { songLength = this.song.getSongLengthInSeconds(); } catch (e) { console.error("Error loading song length: ", e); } this.setState({ songLength, activeTracks }); } /** Menu **/ openMenuByKey(menuName) { if(this.state.portrait) { let options = this.renderMenuByKey(menuName); /** @var {ASUIContextMenuContainer} **/ const menuContextContainer = this.ref.menu.contextContainer.current; menuContextContainer.openMenu(options); return; } const menu = this.ref.menu[menuName]; console.log('menu', menu); if(!menu) throw new Error("Menu not found: " + menu); if(!menu.current) throw new Error("Menu not rendered: " + menu); menu.current.openDropDownMenu(); } /** State **/ async setStateAsync(state) { await new Promise(resolve => { this.setState(state, resolve); }) } async loadState() { const storage = new ClientStorage(); const state = await storage.loadState('audio-source-composer-state'); console.log('Loading State: ', state); if (state) { // if (typeof state.volume !== "undefined") // this.setVolume(state.volume); // delete state.volume; // if(state.songUUID) await this.loadDefaultSong(state.songUUID); const recentValues = state.recentValues; delete state.songUUID; delete state.songLength; delete state.recentValues; delete state.session; await this.setStateAsync(state); // this.updateCurrentSong(); // this.setCurrentSong(this.song); // Hack: resetting current song after setting state, bad idea if(recentValues) { Values.recentDurations = recentValues.recentDurations || []; Values.recentFrequencies = recentValues.recentFrequencies || []; PresetLibrary.recentSampleURLs = recentValues.recentLibrarySampleURLs || []; } const panelTrack = this.ref.panelTrack.current; if(panelTrack) panelTrack.setState(state.panelTrackState); for(let key in state.activeTracks) { if(state.activeTracks.hasOwnProperty(key)) try { const trackState = state.activeTracks[key]; const activeTrack = this.ref.activeTracks[key].current; activeTrack.setState(trackState, () => activeTrack.updateRenderingStats()); } catch (e) { console.error(e); } } } else { await this.loadDefaultSong(); } } // async saveAll() { // await this.saveSongToMemory(); // // await this.saveState() // } saveState() { const storage = new ClientStorage(); const state = Object.assign({}, this.state, { activeTracks: {} }); for(let key in this.ref.activeTracks) { if(this.ref.activeTracks.hasOwnProperty(key)) try { const activeTrack = this.ref.activeTracks[key]; if(activeTrack.current) state.activeTracks[key] = activeTrack.current.getStorageState(); } catch (e) { console.error(e); } } state.recentValues = { recentFrequencies: Values.recentFrequencies, recentDurations: Values.recentDurations, recentLibrarySampleURLs: PresetLibrary.recentSampleURLs, } const panelTrack = this.ref.panelTrack.current; if(panelTrack) state.panelTrackState = panelTrack.state; // Delete playback stats delete state.paused; delete state.playing; delete state.statusText; delete state.statusType; delete state.version; delete state.portrait; state.songUUID = this.song.data.uuid; console.log('Saving State: ', state, this.ref.activeTracks); storage.saveState(state, 'audio-source-composer-state'); } clearUIState() { this.setState({ viewModes: {}, activeTracks: {}, }); } /** Volume **/ getVolume () { return this.state.volume; } setVolume (volume) { console.info("Setting volume: ", volume); this.setState({volume}); if(this.lastVolumeGain) this.lastVolumeGain.gain.value = volume; } /** Song actions **/ async setSongNamePrompt(newSongTitle) { newSongTitle = await PromptManager.openPromptDialog("Enter a new song name:", this.song.data.title); this.setSongName(newSongTitle); } setSongName(newSongTitle=null) { if(typeof newSongTitle !== "string") return this.setError("Invalid song title: " + newSongTitle); const data = this.song.getProxiedData(); data.title = newSongTitle; this.setStatus(`Song title updated: ${newSongTitle}`); } async setSongVersionPrompt(newSongVersion) { newSongVersion = await PromptManager.openPromptDialog("Enter a new song version:", this.song.data.version); this.setSongVersion(newSongVersion); } setSongVersion(newSongVersion) { if(typeof newSongVersion !== "string") return this.setError("Invalid song version: " + newSongVersion); const data = this.song.getProxiedData(); data.version = newSongVersion; this.setStatus(`Song version updated: ${newSongVersion}`); } async setSongStartingBPMPrompt() { const beatsPerMinute = await PromptManager.openPromptDialog("Enter a new song BPM:", this.song.data.beatsPerMinute); this.setSongStartingBPM(beatsPerMinute); } setSongStartingBPM(newSongBeatsPerMinute) { const bpm = Number.parseFloat(newSongBeatsPerMinute) if(Number.isNaN(bpm)) return this.setError("Invalid BPM: " + newSongBeatsPerMinute) const data = this.song.getProxiedData(); data.beatsPerMinute = bpm; // songChangeStartingBeatsPerMinute(newSongBeatsPerMinute); this.setStatus(`Song beats per minute updated: ${bpm}`); } async openSongFromFileDialog(e, accept=null) { const file = await this.openFileDialog(accept); await this.loadSongFromFileInput(e, file); } /** Song Loading **/ async loadDefaultSong(recentSongUUID = null) { const src = this.props.src || this.props.url; if (src) { await this.loadSongFromURL(src); return true; } if (recentSongUUID) { try { await this.loadSongFromMemory(recentSongUUID); return; } catch (e) { console.error(e); this.setError("Error: " + e.message) } } await this.loadNewSongData(); return false; } async loadNewSongData() { // const storage = new Storage(); // const defaultProgramURL = this.getDefaultProgramClass() + ''; // let songData = storage.generateDefaultSong(defaultProgramURL); // const song = Song.loadSongFromData(songData); const song = new Song(); await this.setCurrentSong(song); await this.saveSongToMemory(); // this.forceUpdate(); this.clearUIState(); this.setStatus("Loaded new song"); } async loadRecentSongData() { const storage = new ClientStorage(); let songRecentUUIDs = await storage.getRecentSongList(); if (songRecentUUIDs[0] && songRecentUUIDs[0].uuid) { this.setStatus("Loading recent song: " + songRecentUUIDs[0].uuid); await this.loadSongFromMemory(songRecentUUIDs[0].uuid); return true; } return false; } async loadSongFromURLPrompt() { const url = await PromptManager.openPromptDialog("URL:", 'https://'); return await this.loadSongFromURL(url); } async loadSongFromURL(url) { if(typeof url !== "string") throw new Error("Invalid URL: " + typeof url); this.setStatus("Loading song from url: " + url); try { const fileService = new FileService(); const buffer = await fileService.loadBufferFromURL(url); const song = await this.loadSongFromBuffer(buffer, url); // await this.saveSongToMemory(); this.setStatus("Song loaded from url: " + url); return song; } catch (e) { this.setError(e.message); throw e; } } async loadSongFromFileInput(e, file=null, accept=null) { if(file === null) file = await this.openFileDialog(accept); if (!file) throw new Error("Invalid file input"); const buffer = await new Promise((resolve, reject) => { let reader = new FileReader(); // prepare the file Reader reader.readAsArrayBuffer(file); // read the binary data reader.onload = (e) => { resolve(e.target.result); }; }); const song = await this.loadSongFromBuffer(buffer, file.name); await this.saveSongToMemory(); return song; } async loadSongFromBuffer(buffer, filePath) { const fileSupport = new FileSupport(); const song = await fileSupport.processSongFromFileBuffer(buffer, filePath); await this.setCurrentSong(song); this.clearUIState(); return song; } async loadSongFromMemory(songUUID) { const storage = new ClientStorage(); const songData = await storage.loadSongFromMemory(songUUID); const songHistory = await storage.loadSongHistoryFromMemory(songUUID); const song = new Song(songData); // song.loadSongData(songData); song.loadSongHistory(songHistory); await this.setCurrentSong(song); this.saveState(); this.setStatus("Song loaded from memory: " + songUUID); return song; } async saveSongToMemory(setStatus=true) { const song = this.song; const songData = song.data; // getProxiedData(); const songHistory = song.history; const storage = new ClientStorage(); setStatus && this.setStatus("Saving song to memory..."); await storage.saveSongToMemory(songData, songHistory); this.saveState(); setStatus && this.setStatus("Saved song to memory: " + (songData.title || songData.uuid)); } saveSongToMemoryWithTimeout(autoSaveTimeout=null, setStatus=false) { clearTimeout(this.timeouts.saveSongToMemory); this.timeouts.saveSongToMemory = setTimeout(e => this.saveSongToMemory(setStatus), autoSaveTimeout || this.autoSaveTimeout); } async saveSongToFile(prompt=true) { const songData = this.song.data; // getProxiedData(); let fileName = (songData.title || "untitled") .replace(/\s+/g, '_') + '.json'; if (prompt) fileName = await PromptManager.openPromptDialog("Download as file?", fileName); if (!fileName) { this.setError("Download canceled"); return false; } const storage = new ClientStorage(); storage.saveSongToFile(fileName, songData) && this.setStatus("Saved song to local file: " + fileName); } /** Song Playback **/ setSongPositionPercentage(playbackPercentage) { const playbackPosition = (playbackPercentage / 100) * this.state.songLength; return this.setSongPosition(playbackPosition); } // setSongPosition(songPosition) { // this.updateSongPositionValue(songPosition); // // this.state.songPosition = songPosition; // // this.setState({songPosition}) // // console.info('setSongPosition', songPosition); // } async setSongPositionPrompt() { let songPosition = Values.instance.formatPlaybackPosition(this.songStats.position || 0); songPosition = await PromptManager.openPromptDialog("Set playback position:", songPosition); this.setSongPosition(songPosition); } setSongPosition(playbackPositionInSeconds, updateTrackPositions=true) { // TODO: parse % percentage if(typeof playbackPositionInSeconds === 'string') playbackPositionInSeconds = Values.instance.parsePlaybackPosition(playbackPositionInSeconds); if(isNaN(playbackPositionInSeconds)) throw new Error("Invalid song position: " + playbackPositionInSeconds); this.songStats.position = playbackPositionInSeconds; // Update Song Panel const panelSong = this.ref.panelSong.current; panelSong && panelSong.forceUpdate(); // Update Tracks if(updateTrackPositions) { for (const trackName in this.ref.activeTracks) { if (this.ref.activeTracks.hasOwnProperty(trackName)) { const activeTrack = this.ref.activeTracks[trackName].current; if(activeTrack) { activeTrack.updateSongPosition(playbackPositionInSeconds); } else { delete this.ref.activeTracks[trackName]; } } } } // this.setState({songPosition:playbackPositionInSeconds}) } /** Keyboard Commands **/ keyboardChangeOctave(keyboardOctave = null) { this.ref.panelTrack.keyboardChangeOctave(keyboardOctave) } /** ASComposer State **/ async updateState(newState) { new Promise(resolve => { this.setState(state => { if(typeof newState === "function") newState = newState(state) || state; return newState; }, resolve) }) } /** Song Playback **/ getDestination() { const audioContext = this.getAudioContext(); return this.getVolumeGain(audioContext.destination); // TODO: get track destination } songPlay(songPosition=null, onended=null) { this.song.play(this.getDestination(), songPosition === null ? this.songStats.position : songPosition, onended); } songPause() { this.song.stopPlayback(); } songStop() { if (this.song.playback) this.song.stopPlayback(); ProgramLoader.stopAllPlayback(); this.song.setPlaybackPositionInTicks(0); } /** Track Playback **/ trackPlaySelected(stopPlayback=true) { const {selectedIndices, selectedTrackName} = this.getTrackPanelState() return this.trackPlay(selectedTrackName, selectedIndices.selectedIndices, stopPlayback); } trackPlay(trackName, selectedIndices, stopPlayback=true) { // console.log('trackPlay', trackName, selectedIndices) if(typeof selectedIndices === "number") selectedIndices = [selectedIndices]; if(!Array.isArray(selectedIndices)) throw new Error("Invalid selectedIndices: " + typeof selectedIndices); // const trackState = new ActiveTrackState(this, trackName); const song = this.getSong(); if(stopPlayback && song.isPlaying()) song.stopPlayback(); let destination = this.getDestination(); // let destination = audioContext.destination; // destination = destination || this.getDestination(); // console.log('playInstructions', selectedIndices); // const programID = typeof trackState.programID !== "undefined" ? trackState.programID : 0; // if(stopPlayback) // song.programLoader.stopAllPlayback(); song.playSelectedInstructions(destination, trackName, selectedIndices); } /** Track Commands **/ getTrackPanelState() { const panelTrack = this.ref.panelTrack.current; if(panelTrack) return panelTrack.state; return {}; } trackSelect(selectedTrack, selectedIndices=null) { const [type, id] = this.state.selectedComponent; if(type !== 'track' || id !== selectedTrack) { this.setState({ selectedComponent: ['track', selectedTrack], }); const track = this.trackGetRef(selectedTrack); track.setState({viewMode: true}); } if(selectedIndices !== null) this.trackSelectIndices(selectedTrack, selectedIndices); } trackUnselect(trackName) { this.setState(state => { delete state.activeTracks[trackName]; state.selectedComponent = ['track', Object.keys(state.activeTracks)[0]]; return state; }); } trackSelectIndices(selectedTrack, selectedIndices=[]) { if(typeof selectedIndices === "number") selectedIndices = [selectedIndices]; if(!Array.isArray(selectedIndices)) throw new Error("Invalid selectedIndices: " + typeof selectedIndices); // console.log('selectTrack', state); const panelTrack = this.ref.panelTrack.current; panelTrack.updateSelectedTrackIndices(selectedTrack, selectedIndices) } async trackAdd(newTrackName = null, promptUser = true) { const song = this.song; newTrackName = newTrackName || song.generateInstructionTrackName(); if(promptUser) newTrackName = await PromptManager.openPromptDialog("Create new instruction track?", newTrackName); if (newTrackName) { song.trackAdd(newTrackName, []); this.trackSelect(newTrackName); } else { this.setError("Create instruction track canceled"); } } async trackRenamePrompt(oldTrackName) { const newTrackName = await PromptManager.openPromptDialog(`Rename instruction track "${oldTrackName}"?`, oldTrackName); this.trackRename(oldTrackName, newTrackName); } trackRename(oldTrackName, newTrackName) { const song = this.song; if (!newTrackName || newTrackName !== oldTrackName) { song.trackRename(oldTrackName, newTrackName); this.trackSelect(newTrackName); // this.trackSelect(oldTrackName); } else { this.setError("Rename instruction track canceled"); } } trackRemove(trackName) { const song = this.song; song.trackRemove(trackName); this.trackUnselect(trackName); } async trackRemovePrompt(trackName) { const result = await PromptManager.openConfirmDialog(`Remove instruction track "${trackName}"?`); if (result) { this.trackRemove(trackName); } else { this.setError("Remove instruction track canceled"); } } /** Track Reference **/ trackGetRef(trackName, throwException = true) { const trackRef = this.ref.activeTracks[trackName]; if(!trackRef) { if(throwException) throw new Error("Invalid Track ref: " + trackName); return null; } if(!trackRef.current) { if(throwException) throw new Error("Track ref is not rendered: " + trackName); return null; } return trackRef.current; } /** Current Instruction Args **/ /** Instruction Modification **/ // async instructionInsertAtCursorPrompt(trackName = null, newInstructionData = null) { // newCommand = await PromptManager.openPromptDialog("Set custom command:", newCommand || ''); // return this.instructionInsertAtCursor(trackName, newInstructionData); // } // instructionInsertBeforeCursor(trackName = null, newCommand = null, select=true, playback=true) { // // } instructionInsertAtSelectedTrackCursor(trackName = null, commandString = null, parameters=null) { trackName = trackName || this.getSelectedTrackName(); this.trackGetRef(trackName).instructionInsertAtCursor(commandString, parameters); } instructionInsertAtPosition(trackName, positionTicks, commandString, parameters = null, select=true) { console.log('instructionInsertAtPosition', trackName, positionTicks, commandString, parameters); //: TODO: check for recursive group let newInstructionData; if (Array.isArray(commandString)) { newInstructionData = commandString; } else if(parameters) { newInstructionData = [commandString].concat(parameters); } else { newInstructionData = this.state.selectedInstructionData.slice(); newInstructionData.splice(1, 1, commandString); } const index = this.song.instructionInsertAtPosition(trackName, positionTicks, newInstructionData); if(select) this.trackSelect(trackName, index); if(this.state.playbackOnChange) this.trackPlay(trackName, index); this.updateCurrentSong(); return index; } /** Instruction Args **/ instructionReplaceArg(trackName, selectedIndices, argIndex, newArgValue) { const song = this.song; selectedIndices = Values.instance.parseSelectedIndices(selectedIndices); for (let i = 0; i < selectedIndices.length; i++) { song.instructionReplaceArg(trackName, selectedIndices[i], argIndex, newArgValue); } if(this.state.playbackOnChange) this.trackPlay(trackName, selectedIndices); // trackInfo.updateCurrentInstruction(); } instructionReplaceArgByType(trackName, selectedIndices, argType, newArgValue) { const song = this.song; selectedIndices = Values.instance.parseSelectedIndices(selectedIndices); // console.log('instructionReplaceArg', trackName, selectedIndices, argIndex, newArgValue, selectedInstructionData); const selectedInstructionData = this.state.selectedInstructionData; const processor = new Instruction(selectedInstructionData); processor.updateArg(argType, newArgValue) this.setState({selectedInstructionData}); for (let i = 0; i < selectedIndices.length; i++) { song.instructionReplaceArgByType(trackName, selectedIndices[i], argType, newArgValue); } if(this.state.playbackOnChange) this.trackPlay(trackName, selectedIndices); // trackInfo.updateCurrentInstruction(); } /** Instruction Delete **/ instructionDeleteIndices() { const {selectedIndices, selectedTrackName:trackName} = this.getTrackPanelState() // selectedIndices = this.values.parseSelectedIndices(selectedIndices); selectedIndices.sort((a, b) => a - b); for (let i=selectedIndices.length-1; i>=0; i--) this.song.instructionDeleteAtIndex(trackName, selectedIndices[i]); this.updateCurrentSong(); } /** Tracker Clip Board **/ instructionCopySelected() { const {selectedIndices, selectedTrackName:trackName} = this.getTrackPanelState() const iterator = this.instructionGetIterator(trackName); let startPosition = null, lastPosition=null, copyTrack=[]; iterator.seekToEnd((instructionData) => { instructionData = instructionData.slice(); const index = iterator.getIndex(); for(let i=0; i<selectedIndices.length; i++) if(selectedIndices[i] === index) { if(startPosition === null) lastPosition = startPosition = iterator.getPositionInTicks(); instructionData[0] = iterator.getPositionInTicks() - lastPosition; lastPosition = iterator.getPositionInTicks(); copyTrack.push(instructionData) return instructionData; } }); console.log("Clipboard:", copyTrack); this.setState({clipboard: copyTrack}); } instructionCutSelected() { this.instructionCopySelected(); this.instructionDeleteIndices(); } instructionPasteAtCursor() { const trackName = this.getSelectedTrackName(); const trackRef = this.trackGetRef(trackName); trackRef.instructionPasteAtCursor(); } instructionPasteAtPosition(trackName, startPositionTicks) { // trackName = trackName || this.getSelectedTrackName(); // trackName = trackName || this.getSelectedTrackName(); const copyTrack = this.state.clipboard; if(!Array.isArray(copyTrack)) throw new Error("Invalid clipboard data: " + typeof copyTrack); const selectedIndices = []; for(let i=0; i<copyTrack.length; i++) { const copyInstructionData = copyTrack[i]; const insertPositionTicks = startPositionTicks + copyInstructionData[0]; startPositionTicks = insertPositionTicks; const insertIndex = this.instructionInsertAtPosition(trackName, insertPositionTicks, copyInstructionData); selectedIndices.push(insertIndex); } // console.log('pastedIndices', selectedIndices); this.trackSelect(trackName, selectedIndices); } instructionGetIndicesInRange(trackName, positionTicksStart, positionTicksEnd) { const iterator = this.instructionGetIterator(trackName); const rangeIndices = []; // eslint-disable-next-line no-unused-vars for(const instructionData of iterator) { if(iterator.getPositionInTicks() < positionTicksStart) continue; if(iterator.getPositionInTicks() >= positionTicksEnd) continue; rangeIndices.push(iterator.getIndex()); } console.log('instructionGetIndicesInRange', trackName, positionTicksStart, positionTicksEnd, rangeIndices); return rangeIndices; } /** Instruction Iterator **/ instructionGetIterator(trackName, timeDivision=null, beatsPerMinute=null) { return this.trackGetRef(trackName) .getIterator(timeDivision, beatsPerMinute); } /** Programs **/ programSelect(programID) { const [type, id] = this.state.selectedComponent; if(type !== 'program' || id !== programID) { this.setState({ selectedComponent: ['program', programID], }, () => this.ref.panelProgram.current.forceUpdate()); } } programGetRef(programID) { const programRef = this.ref.activePrograms[programID]; if(!programRef) throw new Error("Invalid Program ref: " + programID); if(!programRef.current) throw new Error("Program ref is not rendered: " + programID); return programRef.current; } async programAddPrompt(programClassName, programConfig = {}) { this.setError(`New program canceled: ${programClassName}`); } programAdd(programClassName, programConfig = {}) { if (!programClassName) throw new Error(`Invalid program class`); // Verify program class name ProgramLoader.getProgramClassInfo(programClassName); const programID = this.song.programAdd(programClassName, programConfig); this.setStatus(`New program class '${programClassName}' added to song at position ${programID}`); this.programSetRendererState(programID, {open: true}) } async programReplacePrompt(programID, programClassName, programConfig = {}) { if (!Number.isInteger(programID)) throw new Error(`Invalid Program ID: Not an integer`); if (!programClassName) throw new Error(`Invalid Program class`); if (await PromptManager.openPromptDialog(`Change Program (${programID}) to ${programClassName}`)) { await this.song.programReplace(programID, programClassName, programConfig); this.setStatus(`Program (${programID}) changed to: ${programClassName}`); } else { this.setError(`Change program canceled: ${programClassName}`); } } async programRenamePrompt(programID, newProgramTitle = null) { const programConfig = this.song.programGetConfig(programID); // console.log(programConfig, programID); const oldProgramTitle = programConfig.title; if (!newProgramTitle) newProgramTitle = await PromptManager.openPromptDialog(`Change name for programs ${programID}: `, oldProgramTitle); if (!newProgramTitle) return console.error("Program name change canceled"); this.song.programRename(programID, newProgramTitle); this.setStatus(`Program title updated: ${newProgramTitle}`); } async programRemovePrompt(programRemoveID = null) { if (await PromptManager.openConfirmDialog(`Remove Program ID: ${programRemoveID}`)) { this.song.programRemove(programRemoveID); this.setStatus(`Program (${programRemoveID}) removed`); } else { this.setError(`Remove program canceled`); } } /** Program State **/ programGetRendererState(programID) { return this.state.programStates[programID] || {}; } programSetRendererState(programID, state) { const programStates = this.state.programStates; if(!programStates[programID]) programStates[programID] = state; else Object.assign(programStates[programID], state); this.setState({ programStates }, () => this.forceUpdate()); } /** Settings **/ toggleSetting(stateKey, callback=null) { if(typeof this.state[stateKey] === 'undefined') throw new Error("Invalid state key: " + stateKey); const newState = {}; newState[stateKey] = !this.state[stateKey]; this.setState(newState, callback); return false; } // /** Toggle Playback Settings **/ // // togglePlaybackOnSelection() { return this.toggleSetting('playbackOnSelect'); } // togglePlaybackOnChange() { return this.toggleSetting('playbackOnChange'); } // // /** Toggle Track Formatting **/ // // toggleTrackRowPositionInTicks() { return this.toggleSetting('showTrackRowPositionInTicks'); } // toggleTrackRowDurationInTicks() { return this.toggleSetting('showTrackRowDurationInTicks'); } /** Toggle View Settings **/ setViewMode(viewKey, mode) { const viewModes = this.state.viewModes || {}; if(mode === null) delete viewModes[viewKey]; else viewModes[viewKey] = mode; console.log('viewModes', viewModes); this.setState({viewModes}); } getViewMode(viewKey) { return this.state.viewModes[viewKey]; } // toggleSongPanel() { return this.toggleSetting('showPanelSong'); } // toggleProgramPanel() { return this.toggleSetting('showPanelProgram'); } // toggleInstructionPanel() { return this.toggleSetting('showPanelInstruction'); } // toggleTrackPanel() { return this.toggleSetting('showPanelTrack'); } // togglePresetBrowserPanel() { return this.toggleSetting('showPanelPresetBrowser'); } toggleFullscreen() { return this.toggleSetting('fullscreen', () => { this.onResize(); }); } /** Tools **/ async batchSelect(e, searchCallbackString = null, promptUser = false) { if (promptUser || !searchCallbackString) searchCallbackString = await PromptManager.openPromptDialog("Run custom search:", searchCallbackString || `/** Example Search **/ i.command === "C3" && i.program === 0`); if (!searchCallbackString) throw new Error("Batch command canceled: Invalid search"); const storage = new ClientStorage(); storage.addBatchRecentSearches(searchCallbackString); throw new Error("TODO Implement"); } async batchRunCommand(e, commandCallbackString = null, searchCallbackString = null, promptUser = false) { const storage = new ClientStorage(); if (promptUser || !searchCallbackString) searchCallbackString = await PromptManager.openPromptDialog("Run custom search:", searchCallbackString || `/** Example Search **/ i.command === "C3" && i.program === 0`); if (!searchCallbackString) throw new Error("Batch command canceled: Invalid search"); storage.addBatchRecentSearches(searchCallbackString); if (promptUser || !commandCallbackString) commandCallbackString = await PromptManager.openPromptDialog(`Run custom command:`, commandCallbackString || `/** Example Command **/ i.command='C4';`); if (!commandCallbackString) throw new Error("Batch command canceled: Invalid command"); storage.addBatchRecentCommands(commandCallbackString); throw new Error("TODO Implement"); } /** Prompt **/ openPromptDialog(message, defaultValue='') { return window.prompt(message, defaultValue); } async openFileDialog(accept=null) { return await new Promise((resolve, reject) => { const input = document.createElement('input'); input.setAttribute('type', 'file'); if(accept) input.setAttribute('accept', accept); input.addEventListener('change', () => { const file = input.files[0]; if(file) resolve(file); else reject(); }); input.click(); }) } } export default ASComposerActions;