smoosic
Version:
<sub>[Github site](https://github.com/Smoosic/smoosic) | [source documentation](https://smoosic.github.io/Smoosic/release/docs/modules.html) | [change notes](https://aarondavidnewman.github.io/Smoosic/changes.html) | [application](https://smoosic.github.i
714 lines (705 loc) • 26.9 kB
text/typescript
// [Smoosic](https://github.com/AaronDavidNewman/Smoosic)
// Copyright (c) Aaron David Newman 2021.
import { SmoAudioPitch } from '../../smo/data/music';
import { PromiseHelpers } from '../../common/promiseHelpers';
import { SmoOscillatorInfo, SmoInstrument, SmoOscillatorInfoAllTypes,
SmoOscillatorInfoNumberType, SmoOscillatorInfoNumberArType, SmoOscillatorInfoStringType, SmoOscillatorInfoStringNullType,
SmoOscillatorInfoWaveformType, SmoOscillatorInfoSustainType, SmoOscillatorInfoOptionsType } from '../../smo/data/staffModifiers';
/**
* A set of parameters from the instrument interface used to create audio from samples.
* @category SuiAudio
*/
export interface SampleChooserParams {
family?: string,
instrument: string,
frequency: number,
duration: number,
gain: number,
articulation?: string
}
/**
* A function prototype that chooses from among samples to return the correct one for that note
*/
export type SampleChooser = (params: SampleChooserParams, samples: SmoOscillatorInfo[]) => AudioSample | null;
/**
* A specific audio sample that can be converted into an audio node
* @category SuiAudio
*/
export interface AudioSample {
sample: AudioBuffer,
frequency: number,
patch: string,
gain: number
}
/**
* Interface for a chooser function and a set of samples
* @category SuiAudio
*/
export interface InstrumentSampleChooser {
instrument: string,
sampleChooser: SampleChooser,
samples: SmoOscillatorInfo[]
}
/**
* @category SuiAudio
* @param params
* @param samples
* @returns
*/
export const sampleForPercussion = (params: SampleChooserParams, samples: SmoOscillatorInfo[]): AudioSample | null => {
const longSamples = samples.filter((ss) => ss.instrument === 'percussion');
let sample: AudioSample | null = null;
if (longSamples.length) {
sample = sampleFromFrequency(params, longSamples);
if (sample) {
return sample;
}
}
return sampleFromFrequency(params, samples);
}
/**
* For instruments like violin that require different samples depending on note duration
* @param params
* @param samples
* @returns
* @category SuiAudio
*/
export const sampleFromMinDuration = (params: SampleChooserParams, samples: SmoOscillatorInfo[]): AudioSample | null => {
const longSamples = samples.filter((ss) => ss.minDuration < params.duration && ss.minDuration > 0);
if (longSamples.length) {
return sampleFromFrequency(params, longSamples);
}
return sampleFromFrequency(params, samples.filter((ss) => ss.minDuration === 0));
}
/**
* Give a set of samples, return the one that closest matches the frequency
* @param params
* @param samples
* @returns
* @category SuiAudio
*/
export const sampleFromFrequency = (params: SampleChooserParams, samples: SmoOscillatorInfo[]): AudioSample | null => {
let min = 9999;
let rv: AudioSample | null = null;
let i = 0;
const f = params.frequency;
for (i = 0; i < samples.length; ++i) {
const oscInfo = samples[i];
if (!oscInfo.sample || !SuiSampleMedia.sampleBufferMap[oscInfo.sample]) {
continue;
}
const gain = oscInfo.dynamic / 100;
const buffer = SuiSampleMedia.sampleBufferMap[oscInfo.sample];
if (Math.abs(f - oscInfo.nativeFrequency) < min) {
min = Math.abs(f - oscInfo.nativeFrequency);
rv = {
sample: buffer,
frequency: oscInfo.nativeFrequency,
patch: oscInfo.sample,
gain
};
}
}
return rv;
}
/**
* Logic to create audio nodes out of HTML5 media elements
* @category SuiAudio
*/
export class SuiSampleMedia {
static sampleFiles: SmoOscillatorInfo[] = [];
static sampleBufferMap: Record<string, AudioBuffer> = {};
static sampleOscMap: Record<string, SmoOscillatorInfo[]> = {};
static instrumentChooser: Record<string, InstrumentSampleChooser> = {};
static receivedBuffer: boolean = false;
static getFamilyForInstrument(instKey: string): string {
const sound = SuiSampleMedia.instrumentChooser[instKey];
if (sound && sound.samples.length) {
return sound.samples[0].family;
}
return 'keyboard';
}
static insertIntoMap(sample: Partial<SmoOscillatorInfo>) {
const oscInfo = SmoInstrument.defaultOscillatorParam;
const populatePartial = (partial: Partial<SmoOscillatorInfo>, full: SmoOscillatorInfo,
param: SmoOscillatorInfoNumberType | SmoOscillatorInfoNumberArType | SmoOscillatorInfoStringType | SmoOscillatorInfoStringNullType
| SmoOscillatorInfoOptionsType | SmoOscillatorInfoSustainType | SmoOscillatorInfoWaveformType) => {
(full[param] as any) = typeof(partial[param]) === 'undefined' ? full[param] : partial[param];
}
SmoOscillatorInfoAllTypes.forEach((paramType) => {
populatePartial(sample, oscInfo, paramType as any);
});
if (!this.sampleOscMap[oscInfo.instrument]) {
this.sampleOscMap[oscInfo.instrument] = [];
}
this.sampleOscMap[oscInfo.instrument].push(oscInfo);
SuiSampleMedia.sampleFiles.push(oscInfo);
}
static populateSampleMap() {
SuiSampleMedia.insertIntoMap({
sustain: 'percussive',
sample: 'samplecn4',
family: 'keyboard',
instrument: 'piano',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'percussive',
realOvertones: [],
imaginaryOvertones: [],
sample: 'samplebb4',
family: 'keyboard',
instrument: 'piano',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'b', accidental: 'b', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'percussive',
realOvertones: [],
imaginaryOvertones: [],
sample: 'sample-piano-a2',
family: 'keyboard',
instrument: 'piano',
dynamic: 35,
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'a', accidental: 'n', octave: 2 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'percussive',
realOvertones: [],
imaginaryOvertones: [],
sample: 'sample-piano-fs2',
family: 'keyboard',
instrument: 'piano',
dynamic: 35,
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'f', accidental: '#', octave: 2 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'percussive',
sample: 'basspizz-c2-sso',
family: 'strings',
instrument: 'jazzBass',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: '#', octave: 3 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'percussive',
sample: 'basspizz-c3-sso',
family: 'strings',
instrument: 'jazzBass',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: '#', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'eguitar-e3',
family: 'strings',
instrument: 'eGuitar',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'e', accidental: 'n', octave: 3 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'eguitar-d4',
family: 'strings',
instrument: 'eGuitar',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'd', accidental: 'n', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-bass-a1',
family: 'strings',
instrument: 'bass',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'a', accidental: 'n', octave: 1 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-bass-a3',
family: 'strings',
instrument: 'bass',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'a', accidental: 'n', octave: 3 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-violinshort-c4',
family: 'strings',
instrument: 'violin',
minDuration: 0,
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 5 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-violinshort-b5',
family: 'strings',
instrument: 'violin',
minDuration: 0,
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'b', accidental: 'n', octave: 6 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-violinshort-e5',
family: 'strings',
instrument: 'violin',
minDuration: 0,
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'e', accidental: 'n', octave: 6 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-violin-e6',
family: 'strings',
instrument: 'violin',
minDuration: 400,
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'e', accidental: 'n', octave: 6 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-violin-e5',
family: 'strings',
instrument: 'violin',
minDuration: 400,
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'e', accidental: 'n', octave: 5 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-violin-e4',
family: 'strings',
instrument: 'violin',
minDuration: 400,
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'e', accidental: 'n', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-cello-bb3',
family: 'strings',
instrument: 'cello',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'b', accidental: 'n', octave: 3 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-cello-c4',
family: 'strings',
instrument: 'cello',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trumpet-db5-sso',
family: 'brass',
instrument: 'trumpet',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'd', accidental: 'b', octave: 5 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trumpet-e6-sso',
family: 'brass',
instrument: 'trumpet',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'e', accidental: 'n', octave: 6 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trumpet-g3-sso',
family: 'brass',
instrument: 'trumpet',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'g', accidental: 'n', octave: 3 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trumpet-db4-sso',
family: 'brass',
instrument: 'trumpet',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'd', accidental: 'b', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trumpet-e3-sso',
family: 'brass',
instrument: 'trumpet',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'e', accidental: 'n', octave: 3 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trumpet-g4-sso',
family: 'brass',
instrument: 'trumpet',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'g', accidental: 'n', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trumpet-bb3-sso',
family: 'brass',
instrument: 'trumpet',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'b', accidental: 'b', octave: 3 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trumpet-bb4-sso',
family: 'brass',
instrument: 'trumpet',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'b', accidental: 'b', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trumpet-bb5-sso',
family: 'brass',
instrument: 'trumpet',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'b', accidental: 'b', octave: 5 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trumpet-db6-sso',
family: 'brass',
instrument: 'trumpet',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'd', accidental: 'b', octave: 6 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trumpet-e5-sso',
family: 'brass',
instrument: 'trumpet',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'e', accidental: 'n', octave: 5 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trumpet-e4-sso',
family: 'brass',
instrument: 'trumpet',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'e', accidental: 'n', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-horn-e4',
family: 'brass',
instrument: 'horn',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'e', accidental: 'n', octave: 4 }, 0, null),
dynamic: 100
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-horn-g3',
family: 'brass',
instrument: 'horn',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'g', accidental: 'n', octave: 3 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trombone-c#3-sso',
family: 'brass',
instrument: 'trombone',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: '#', octave: 3 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trombone-c#4-sso',
family: 'brass',
instrument: 'trombone',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: '#', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trombone-a#3-sso',
family: 'brass',
instrument: 'trombone',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'a', accidental: '#', octave: 3 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trombone-a#4-sso',
family: 'brass',
instrument: 'trombone',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'a', accidental: '#', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trombone-e3-sso',
family: 'brass',
instrument: 'trombone',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'e', accidental: 'n', octave: 3 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trombone-e4-sso',
family: 'brass',
instrument: 'trombone',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'e', accidental: 'n', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trombone-g2-sso',
family: 'brass',
instrument: 'trombone',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'g', accidental: 'n', octave: 2 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trombone-g3-sso',
family: 'brass',
instrument: 'trombone',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'g', accidental: 'n', octave: 3 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'trombone-g4-sso',
family: 'brass',
instrument: 'trombone',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'g', accidental: 'n', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-tuba-c2',
family: 'brass',
instrument: 'tuba',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 2 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-tuba-c3',
family: 'brass',
instrument: 'tuba',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 3 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-flute-c5',
family: 'wind',
instrument: 'flute',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 5 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-flute-c6',
family: 'wind',
instrument: 'flute',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 6 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-clarinet-c4',
family: 'wind',
instrument: 'clarinet',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 4 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-clarinet-c5',
family: 'wind',
instrument: 'clarinet',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 5 }, 0, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-asax-a3',
family: 'wind',
instrument: 'altoSax',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'a', accidental: 'n', octave: 3 }, 12, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-asax-c4',
family: 'wind',
instrument: 'altoSax',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 4 }, 12, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-asax-a3',
family: 'wind',
instrument: 'tenorSax',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'a', accidental: 'n', octave: 3 }, 12, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'sample-asax-c4',
family: 'wind',
instrument: 'tenorSax',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 4 }, 12, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'barisax-d2-nz10',
family: 'wind',
instrument: 'bariSax',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'd', accidental: 'n', octave: 2 }, 12, null),
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'barisax-c3-nz10',
family: 'wind',
instrument: 'bariSax',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 3 }, 12, null) - 5,
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'pad-c4-vita',
family: 'synth',
instrument: 'pad',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 3 }, 0, null),
dynamic: 100
});
SuiSampleMedia.insertIntoMap({
sustain: 'sustained',
sample: 'pad-c5-vita',
family: 'synth',
instrument: 'pad',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 4 }, 0, null),
dynamic: 100
});
SuiSampleMedia.insertIntoMap({
sustain: 'percussive',
sample: 'drum-hh-closed',
family: 'drums',
instrument: 'percussion',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'g', accidental: 'n', octave: 5 }, 0, null),
dynamic: 100
});
SuiSampleMedia.insertIntoMap({
sustain: 'percussive',
sample: 'drum-drumset-snare',
family: 'drums',
instrument: 'percussion',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'f', accidental: 'n', octave: 4 }, 0, null),
dynamic: 100
});
SuiSampleMedia.insertIntoMap({
sustain: 'percussive',
sample: 'drum-drumset-tom1',
family: 'drums',
instrument: 'percussion',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'a', accidental: 'n', octave: 4 }, 0, null),
dynamic: 100
});
SuiSampleMedia.insertIntoMap({
sustain: 'percussive',
sample: 'drum-drumset-tom2',
family: 'drums',
instrument: 'percussion',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'e', accidental: 'n', octave: 5 }, 0, null),
dynamic: 100
});
SuiSampleMedia.insertIntoMap({
sustain: 'percussive',
sample: 'drum-drumset-kick',
family: 'drums',
instrument: 'percussion',
nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 4 }, 0, null),
dynamic: 100
});
const instrumentMap = Object.keys(SuiSampleMedia.sampleOscMap);
instrumentMap.forEach((instrumentKey) => {
SuiSampleMedia.instrumentChooser[instrumentKey] = {
instrument: instrumentKey,
samples: SuiSampleMedia.sampleOscMap[instrumentKey],
sampleChooser: sampleFromFrequency
}
});
SuiSampleMedia.instrumentChooser['violin'].sampleChooser = sampleFromMinDuration;
SuiSampleMedia.instrumentChooser['percussion'].sampleChooser = sampleForPercussion;
}
static printAudioElementTags() {
const sampleKeys = Object.keys(SuiSampleMedia.sampleOscMap);
let sampleStrings = '';
sampleKeys.forEach((sk) => {
const samples = SuiSampleMedia.sampleOscMap[sk];
samples.forEach((sample) => {
const enc = encodeURIComponent(sample.sample ?? '');
let freq = SmoAudioPitch.frequencyToVexPitch(sample.nativeFrequency);
sampleStrings += '<div>\n';
sampleStrings +=
`<audio crossOrigin="anonymous" id="${sample.sample}" data-pitch="${freq}" data-patch="${sample.instrument}" \n`;
sampleStrings += `src="https://smoosic.github.io/SmoSounds/${sample.family}/${enc}.mp3">\n`;
sampleStrings += '</audio></div>\n';
});
});
console.log(sampleStrings);
}
static getSmoOscillatorInfo(instrument: string) {
if (!SuiSampleMedia.sampleOscMap[instrument]) {
return SuiSampleMedia.sampleOscMap['piano'];
}
return SuiSampleMedia.sampleOscMap[instrument];
}
/**
* Load samples so we can play the music
* @returns - promise, resolved when loaded
*/
static samplePromise(audio: AudioContext): Promise<any> {
const mediaElements: HTMLMediaElement[] = [];
let i = 0;
if (SuiSampleMedia.receivedBuffer) {
return PromiseHelpers.emptyPromise();
}
SuiSampleMedia.populateSampleMap();
const loadedSamples: Record<string, boolean> = {};
for (i = 0; i < SuiSampleMedia.sampleFiles.length; ++i) {
const file = SuiSampleMedia.sampleFiles[i];
if (!file.sample) {
continue;
}
const sampleName = file.sample;
const audioElement: HTMLMediaElement | null = document.getElementById(file.sample) as HTMLMediaElement;
if (!loadedSamples[file.sample] && audioElement) {
loadedSamples[file.sample] = true;
const media = audio.createMediaElementSource(audioElement);
mediaElements.push(audioElement);
const req = new XMLHttpRequest();
req.open('GET', media.mediaElement.src, true);
req.responseType = 'arraybuffer';
req.send();
req.onload = () => {
const audioData = req.response;
audio.decodeAudioData(audioData, (decoded) => {
SuiSampleMedia.sampleBufferMap[sampleName] = decoded;
SuiSampleMedia.receivedBuffer = true;
});
};
}
if (mediaElements.length < 1) {
return PromiseHelpers.emptyPromise();
}
}
const rv = new Promise<any>((resolve: any) => {
const checkSample = () => {
setTimeout(() => {
if (!SuiSampleMedia.receivedBuffer) {
checkSample();
} else {
resolve();
}
}, 100);
};
checkSample();
});
return rv;
}
static sampleForFrequency(f: number, oscs: SmoOscillatorInfo[]): AudioSample | null {
let min = 9999;
let rv: AudioSample | null = null;
let i = 0;
for (i = 0; i < oscs.length; ++i) {
const oscInfo = oscs[i];
if (!oscInfo.sample || !SuiSampleMedia.sampleBufferMap[oscInfo.sample]) {
continue;
}
const buffer = SuiSampleMedia.sampleBufferMap[oscInfo.sample];
const gain = oscInfo.dynamic / 100;
if (Math.abs(f - oscInfo.nativeFrequency) < min) {
min = Math.abs(f - oscInfo.nativeFrequency);
rv = {
sample: buffer,
frequency: oscInfo.nativeFrequency,
patch: oscInfo.sample,
gain
};
}
}
return rv;
}
static matchedSample(params: SampleChooserParams): AudioSample | null {
let instrumentKey = params.instrument;
if (!SuiSampleMedia.instrumentChooser[instrumentKey]) {
instrumentKey = 'piano';
}
if (!SuiSampleMedia.instrumentChooser[instrumentKey]) {
const keys = Object.keys(SuiSampleMedia.sampleOscMap);
if (keys.length === 0) {
return null;
}
instrumentKey = keys[0];
}
return SuiSampleMedia.instrumentChooser[instrumentKey].sampleChooser(params, SuiSampleMedia.instrumentChooser[instrumentKey].samples);
}
}