UNPKG

audio-source-composer

Version:

Audio Source Composer

294 lines (254 loc) 11.5 kB
const fetch = require("node-fetch"); const fs = require('fs'); const path = require('path'); class SampleSpider { constructor(libraryPath, sampleFolder='', libraryData={}) { this.libraryPath = libraryPath; this.data = Object.assign({}, { name: libraryPath.split('/').pop(), urlSamples: sampleFolder, presets: [], samples: [] }, libraryData); if(fs.existsSync(libraryPath)) Object.assign(this.data, JSON.parse(fs.readFileSync(libraryPath))); this.drumPresetPrefixes = {}; console.log(this.data); } async addSample(sampleURL, sampleConfig={}) { const sampleName = sampleURL.split('/').pop(); sampleConfig.url = sampleName; // if(!sampleConfig.name) sampleConfig.name = sampleName; const samplePath = path.resolve(this.data.urlSamples, sampleName); if(fs.existsSync(samplePath)) { console.log("Skipping download: ", sampleURL); } else { console.log("Downloading: ", sampleURL); const response = await fetch(sampleURL); const sampleData = await response.arrayBuffer(); console.log("Writing: ", samplePath); fs.writeFileSync(samplePath, sampleData); } const sampleIndex = this.data.samples.findIndex(s => s.url === sampleConfig.url); if(sampleIndex === -1) { console.log("Adding samples to library: ", sampleName); this.data.samples.push(sampleConfig) } else { console.log("Updating existing samples: ", sampleName); this.data.samples[sampleIndex] = sampleConfig; } this.parseSampleConfig(sampleConfig, sampleName); } combineDrumKits() { const drumPresetPrefixes = this.drumPresetPrefixes; for(let drumPresetPrefix in drumPresetPrefixes) { if(drumPresetPrefixes.hasOwnProperty(drumPresetPrefix)) { if(!drumPresetPrefix) continue; const sampleNames = drumPresetPrefixes[drumPresetPrefix].samples; // const singleDrumInstruments = drumPresetPrefixes[drumPresetPrefix].programs; for(let j=0; j<sampleNames.length; j++) { const sampleName = sampleNames[j]; const presetIndex = this.data.presets.findIndex(p => p.name === drumPresetPrefix); let presetConfig = {name: drumPresetPrefix}; if(presetIndex === -1) { console.log("Adding drum kit preset to library: ", drumPresetPrefix); this.data.presets.push(presetConfig) } else { presetConfig = this.data.presets[presetIndex]; } if(!presetConfig.samples) presetConfig.samples = []; if(presetConfig.samples.indexOf(sampleName) === -1) presetConfig.samples.push(sampleName) // if(!this.data.presets[drumPresetPrefix]) // this.data.presets[drumPresetPrefix] = {samples:{}}; // const drumInstrument = this.data.presets[drumPresetPrefix]; // drumInstrument.samples[sampleName] = {}; } // for(let j=0; j<singleDrumInstruments.length; j++) { // const singleDrumInstrumentName = singleDrumInstruments[j]; // delete libraryJSON.programs[singleDrumInstrumentName]; // // } } } } writeLibrary() { console.log("Writing ", this.libraryPath); let stringData = JSON.stringify(this.data, null, "\t"); stringData = stringData.replace(/[{]\n\t+"/g, '{\t"'); fs.writeFileSync(this.libraryPath, stringData); } parseSampleConfig(sampleConfig, sampleName) { let presetName = path.parse(sampleName).name; // Find root key: let rootKey = presetName.split(/[^a-z0-9#]+/gi).pop(); if(rootKey && rootKey.match(/^[a-f][0-6]$/i)) { sampleConfig.root = rootKey.toUpperCase(); presetName = presetName.substr(0, presetName.length - rootKey.length) .replace(/\W+$/, ''); } presetName = this.parseDrumSampleConfig(sampleConfig, sampleName, presetName); this.parseLoopSampleConfig(sampleConfig, sampleName); // Object.assign(sampleConfig, {url: sampleName}, sampleConfig); this.parsePresetConfig(presetName, sampleConfig); // if(!this.data.presets[presetName]) // instrumentList[presetName] = {}; // const instrumentConfig = instrumentList[presetName]; // if(!instrumentConfig.samples) // instrumentConfig.samples = {}; // instrumentConfig.samples[sampleName] = {}; // sampleList[sampleName] = sampleConfig; // // parseInstrumentConfig(presetName, sampleList, instrumentList); // return sampleName; return sampleConfig; } parsePresetConfig(presetName, sampleConfig) { const presetIndex = this.data.presets.findIndex(p => p.name === presetName); let presetConfig = {name: presetName}; if(presetIndex === -1) { console.log("Adding preset to library: ", presetName); this.data.presets.push(presetConfig) } else { presetConfig = this.data.presets[presetIndex]; } if(!presetConfig.samples) presetConfig.samples = []; if(presetConfig.samples.indexOf(sampleConfig.url) === -1) presetConfig.samples.push(sampleConfig.url); const sampleList = presetConfig.samples; // Skip if there is only one samples. if(sampleList.length === 1) return; // Check for drum samples for(let i=0; i<sampleList.length; i++) { const sampleConfig = this.data.samples.find(s => s.url === sampleList[i]); if(typeof sampleConfig.alias !== "undefined") return; } // if(!instrumentConfig.samples) // throw new Error("Invalid program samples"); // const instrumentSamples = instrumentConfig.samples; // const sampleValues = Object.values(instrumentSamples); if(!presetName.endsWith('Kit')) { // Span out the key zones const keyNumberSamples = []; for(let i=0; i<sampleList.length; i++) { const sampleConfig = this.data.samples.find(s => s.url === sampleList[i]); // const combinedSampleConfig = Object.assign({}, instrumentSamples[sampleName], sampleList[sampleName]); if (!sampleConfig.root) throw new Error("Multi samples program requires keyRoot"); const keyNumber = this.getNoteKeyNumber(sampleConfig.root); keyNumberSamples.push({keyNumber, sampleConfig}); } let currentSample = 0, currentRange = [0,0]; for (let i=0; i<12*12; i++) { const {keyNumber, sampleConfig} = keyNumberSamples[currentSample]; currentRange[1] = i; sampleConfig.range = this.getNoteByKeyNumber(currentRange[0]) + ':' + this.getNoteByKeyNumber(currentRange[1]); // TODO: move key range config to preset if(!keyNumberSamples[currentSample+1]) continue; const nextKeyNumber = keyNumberSamples[currentSample+1].keyNumber; if(i > (keyNumber + nextKeyNumber) / 2) { currentSample++; currentRange = [i+1, i+1]; } } } } parseLoopSampleConfig(sampleConfig, fileName) { const loopSampleNames = [ 'loop', ]; for(let i=0; i<loopSampleNames.length; i++) { const loopSampleName = loopSampleNames[i]; if(fileName.toLowerCase().indexOf(loopSampleName) !== -1) { sampleConfig.loop = true; } } } parseDrumSampleConfig(sampleConfig, sampleName, presetName) { const drumSampleNotes = this.drumSampleNotes; for(let drumSampleName in drumSampleNotes) { if(drumSampleNotes.hasOwnProperty(drumSampleName)) { let pos = sampleName.toLowerCase().indexOf(drumSampleName); if(pos !== -1) { sampleConfig.loop = false; sampleConfig.alias = drumSampleNotes[drumSampleName]; presetName = (presetName.substr(0, pos) .replace(/\W$/, '') || 'Generic'); const matchNumbered = sampleName.match(/-\d+$/); if(matchNumbered) { presetName += matchNumbered[0]; } presetName += '-Kit'; if(!this.drumPresetPrefixes[presetName]) this.drumPresetPrefixes[presetName] = {samples:[],presets:[]}; this.drumPresetPrefixes[presetName].samples.push(sampleName); // drumPresetPrefixes[drumPresetPrefix].presets.push(presetName); break; } } } return presetName; } getNoteKeyNumber (namedFrequency) { if(Number(namedFrequency) === namedFrequency && namedFrequency % 1 !== 0) return namedFrequency; if(!namedFrequency) return null; let octave = parseInt(namedFrequency.replace(/\D/g,'')); let freq = namedFrequency.replace(/\d/g,'').toUpperCase(); const freqs = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; let keyNumber = freqs.indexOf(freq); if(keyNumber === -1) throw new Error("Invalid note: " + namedFrequency); if (keyNumber < 3) keyNumber = keyNumber + 12 + ((octave + 1) * 12); else keyNumber = keyNumber + ((octave + 1) * 12); return keyNumber; } getNoteByKeyNumber(keyNumber) { keyNumber = parseInt(keyNumber); const freqs = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; const octave = Math.floor(keyNumber / 12) - 2; const freq = freqs[keyNumber % 12]; return freq + octave; } get drumSampleNotes() { return { 'rap-kick': 'C3', 'kick': 'C3', 'low-mid-synth-tom': 'A2', 'hi-mid-synth-tom': 'A2', 'hi-synth-tom': 'A2', 'mid-synth-tom': 'A2', 'low-synth-tom': 'A2', 'synth-tom': 'A2', 'tom': 'A2', 'snare': 'D3', 'steel-drum': 'D3', 'drum': 'D3', 'clap': 'E3', 'closed-hi-hat': 'G#2', 'closed-hi': 'G#2', 'open-hi-hat': 'G#2', 'open-hi': 'G#2', 'open': 'A#2', 'hat': 'G#2', 'cow': 'D#3', 'cowbell': 'D#3', 'splash-cymbal': 'C#3', 'crash-cymbal': 'C#3', 'ride-cymbal': 'D3', 'cymbal': 'C#3', 'crash': 'C#3', // 'bell': 'F3', 'stick': 'D3', 'ride': 'D3', 'doumbek': 'A2' }; } } module.exports = SampleSpider;