<commit_before>import { ToneAudioBuffer } from "../core/context/ToneAudioBuffer";
import { ToneAudioBuffers } from "../core/context/ToneAudioBuffers";
import { intervalToFrequencyRatio } from "../core/type/Conversions";
import { FrequencyClass } from "../core/type/Frequency";
import { Frequency, Interval, MidiNote, NormalRange, Note, Time } from "../core/type/Units";
import { optionsFromArguments } from "../core/util/Defaults";
import { noOp } from "../core/util/Interface";
import { isArray, isNote, isNumber } from "../core/util/TypeCheck";
import { Instrument, InstrumentOptions } from "../instrument/Instrument";
import { ToneBufferSource, ToneBufferSourceCurve } from "../source/buffer/BufferSource";

interface SamplesMap {
	[note: string]: ToneAudioBuffer | AudioBuffer | string;
	[midi: number]: ToneAudioBuffer | AudioBuffer | string;
}

interface SamplerOptions extends InstrumentOptions {
	attack: Time;
	release: Time;
	onload: () => void;
	baseUrl: string;
	curve: ToneBufferSourceCurve;
	urls: SamplesMap;
}

/**
 * Pass in an object which maps the note's pitch or midi value to the url,
 * then you can trigger the attack and release of that note like other instruments.
 * By automatically repitching the samples, it is possible to play pitches which
 * were not explicitly included which can save loading time.
 *
 * For sample or buffer playback where repitching is not necessary,
 * use {@link Player}.
 * @param samples An object of samples mapping either Midi
 *                         Note Numbers or Scientific Pitch Notation
 *                         to the url of that sample.
 * @param onload The callback to invoke when all of the samples are loaded.
 * @param baseUrl The root URL of all of the samples, which is prepended to all the URLs.
 * @example
 * var sampler = new Sampler({
 * 	"C3" : "path/to/C3.mp3",
 * 	"D#3" : "path/to/Dsharp3.mp3",
 * 	"F#3" : "path/to/Fsharp3.mp3",
 * 	"A3" : "path/to/A3.mp3",
 * }, function(){
 * 	//sampler will repitch the closest sample
 * 	sampler.triggerAttack("D3")
 * })
 */
export class Sampler extends Instrument<SamplerOptions> {

	name = "Sampler";

	/**
	 * The stored and loaded buffers
	 * @type {Tone.Buffers}
	 * @private
	 */
	private _buffers: ToneAudioBuffers;

	/**
	 * The object of all currently playing BufferSources
	 */
	private _activeSources: Map<MidiNote, ToneBufferSource[]> = new Map();

	/**
	 * The envelope applied to the beginning of the sample.
	 */
	attack: Time;

	/**
	 * The envelope applied to the end of the envelope.
	 */
	release: Time;

	/**
	 *  The shape of the attack/release curve.
	 *  Either "linear" or "exponential"
	 */
	curve: ToneBufferSourceCurve;

	protected _internalChannels = [this.output];

	constructor(options?: Partial<SamplerOptions>);
	constructor(samples?: SamplesMap, options?: Partial<Omit<SamplerOptions, "urls">>);
	constructor(samples?: SamplesMap, onload?: () => void, baseUrl?: string);
	constructor() {

		super(optionsFromArguments(Sampler.getDefaults(), arguments, ["urls", "onload", "baseUrl"], "urls"));
		const options = optionsFromArguments(Sampler.getDefaults(), arguments, ["urls", "onload", "baseUrl"], "urls");

		const urlMap = {};
		Object.keys(options.urls).forEach((note) => {
			this.assert(isNote(note) || (isNumber(note) && isFinite(note)), `url key is neither a note or midi pitch: ${note}`);
			if (isNote(note)) {
				// convert the note name to MIDI
				const mid = new FrequencyClass(this.context, note).toMidi();
				urlMap[mid] = options.urls[note];
			} else if (isNumber(note) && isFinite(note)) {
				// otherwise if it's numbers assume it's midi
				urlMap[parseInt(note, 10)] = options.urls[note];
			}
		});

		this._buffers = new ToneAudioBuffers(urlMap, options.onload, options.baseUrl);
		this.attack = options.attack;
		this.release = options.release;
		this.curve = options.curve;
	}

	static getDefaults(): SamplerOptions {
		return Object.assign(Instrument.getDefaults(), {
			attack : 0,
			baseUrl : "",
			curve : "exponential" as "exponential",
			onload : noOp,<commit_msg>better type checking for sampler url parameters<commit_after>import { ToneAudioBuffer } from "../core/context/ToneAudioBuffer";
import { ToneAudioBuffers } from "../core/context/ToneAudioBuffers";
import { intervalToFrequencyRatio } from "../core/type/Conversions";
import { FrequencyClass } from "../core/type/Frequency";
import { Frequency, Interval, MidiNote, NormalRange, Note, Time } from "../core/type/Units";
import { optionsFromArguments } from "../core/util/Defaults";
import { noOp } from "../core/util/Interface";
import { isArray, isNote, isNumber, isString } from "../core/util/TypeCheck";
import { Instrument, InstrumentOptions } from "../instrument/Instrument";
import { ToneBufferSource, ToneBufferSourceCurve } from "../source/buffer/BufferSource";

interface SamplesMap {
	[note: string]: ToneAudioBuffer | AudioBuffer | string;
	[midi: number]: ToneAudioBuffer | AudioBuffer | string;
}

interface SamplerOptions extends InstrumentOptions {
	attack: Time;
	release: Time;
	onload: () => void;
	baseUrl: string;
	curve: ToneBufferSourceCurve;
	urls: SamplesMap;
}

/**
 * Pass in an object which maps the note's pitch or midi value to the url,
 * then you can trigger the attack and release of that note like other instruments.
 * By automatically repitching the samples, it is possible to play pitches which
 * were not explicitly included which can save loading time.
 *
 * For sample or buffer playback where repitching is not necessary,
 * use {@link Player}.
 * @param samples An object of samples mapping either Midi
 *                         Note Numbers or Scientific Pitch Notation
 *                         to the url of that sample.
 * @param onload The callback to invoke when all of the samples are loaded.
 * @param baseUrl The root URL of all of the samples, which is prepended to all the URLs.
 * @example
 * var sampler = new Sampler({
 * 	"C3" : "path/to/C3.mp3",
 * 	"D#3" : "path/to/Dsharp3.mp3",
 * 	"F#3" : "path/to/Fsharp3.mp3",
 * 	"A3" : "path/to/A3.mp3",
 * }, function(){
 * 	//sampler will repitch the closest sample
 * 	sampler.triggerAttack("D3")
 * })
 */
export class Sampler extends Instrument<SamplerOptions> {

	name = "Sampler";

	/**
	 * The stored and loaded buffers
	 * @type {Tone.Buffers}
	 * @private
	 */
	private _buffers: ToneAudioBuffers;

	/**
	 * The object of all currently playing BufferSources
	 */
	private _activeSources: Map<MidiNote, ToneBufferSource[]> = new Map();

	/**
	 * The envelope applied to the beginning of the sample.
	 */
	attack: Time;

	/**
	 * The envelope applied to the end of the envelope.
	 */
	release: Time;

	/**
	 *  The shape of the attack/release curve.
	 *  Either "linear" or "exponential"
	 */
	curve: ToneBufferSourceCurve;

	protected _internalChannels = [this.output];

	constructor(options?: Partial<SamplerOptions>);
	constructor(samples?: SamplesMap, options?: Partial<Omit<SamplerOptions, "urls">>);
	constructor(samples?: SamplesMap, onload?: () => void, baseUrl?: string);
	constructor() {

		super(optionsFromArguments(Sampler.getDefaults(), arguments, ["urls", "onload", "baseUrl"], "urls"));
		const options = optionsFromArguments(Sampler.getDefaults(), arguments, ["urls", "onload", "baseUrl"], "urls");

		const urlMap = {};
		Object.keys(options.urls).forEach((note) => {
			const noteNumber = parseInt(note, 10);
			this.assert(isNote(note)
				|| (isNumber(noteNumber) && isFinite(noteNumber)), `url key is neither a note or midi pitch: ${note}`);
			if (isNote(note)) {
				// convert the note name to MIDI
				const mid = new FrequencyClass(this.context, note).toMidi();
				urlMap[mid] = options.urls[note];
			} else if (isNumber(noteNumber) && isFinite(noteNumber)) {
				// otherwise if it's numbers assume it's midi
				urlMap[noteNumber] = options.urls[noteNumber];
			}
		});

		this._buffers = new ToneAudioBuffers(urlMap, options.onload, options.baseUrl);
		this.attack = options.attack;
		this.release = options.release;
		this.curve = options.curve;
	}

	static getDefaults(): SamplerOptions {
		return Object.assign(Instrument.getDefaults(), {
			attack : 0,
			baseUrl : "",
			curve : "exponential" as "exponential",
			onload : noOp,
{'fraction_non_alphanumeric': 0.13207776427703524, 'fraction_numerical': 0.003888213851761847, 'mean_word_length': 3.9854633555420955, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6928472', 'n_tokens_mistral': 2669, 'n_tokens_neox': 2472, 'n_words': 765}