{"version":3,"file":"index.modern.mjs","sources":["../src/audio/utils.ts","../src/audio/states.ts","../src/EventEmitter.ts","../src/EventHandler.ts","../src/audio/Audio.ts","../src/audio/AudioCtx.ts","../src/audio/decodeAudioData.ts","../src/audio/initializeSource.ts","../src/playlist/playAudio.ts","../src/playlist/states.ts","../src/playlist/AudioPlaylist.ts","../src/playlist/utils.ts"],"sourcesContent":["/**\n * Fetches an audio file and returns it as an ArrayBuffer.\n */\nexport const getBuffer = (file: string): Promise<ArrayBuffer> =>\n  fetch(file).then((response) => {\n    if (!response.ok) {\n      throw new Error(`HTTP error, status = ${response.status}`)\n    }\n\n    return response.arrayBuffer()\n  })\n\n/**\n * Throws a formatted error with the ts-audio prefix.\n */\nexport const throwsError = (value: string): void => {\n  throw new Error(`\\`ts-audio\\`: ${value}`)\n}\n\n/**\n * Attempts to preload an audio file with automatic retry mechanism.\n * Will recursively retry loading the file up to the specified number of attempts.\n */\nexport const preloadFile = (file: string, attempts = 3, done?: () => void): void => {\n  fetch(file)\n    .then(done)\n    .catch(() => {\n      if (!attempts) {\n        return\n      }\n\n      preloadFile(file, attempts - 1)\n    })\n}\n","/**\n * Type definition for the audio player's internal state.\n */\nexport type AudioState = {\n  isDecoded: boolean\n  isPlaying: boolean\n  hasStarted: boolean\n  source: AudioBufferSourceNode | null\n  gainNode: GainNode | null\n}\n\n/**\n * Default initial state for audio decoding and playback.\n */\nexport const defaultStates: AudioState = {\n  isDecoded: false,\n  isPlaying: false,\n  hasStarted: false,\n  source: null,\n  gainNode: null,\n}\n","/**\n * Represents an event with associated data.\n */\nexport type Event = {\n  /**\n   * The data associated with the event. The type of data is unknown.\n   */\n  data: unknown\n}\n\n/**\n * Event emitter class that allows registering event listeners and emitting events.\n */\nexport class EventEmitter {\n  /**\n   * A map of event keys to their respective callback functions.\n   * @private\n   */\n  private events: { [key: string]: (param: Event) => void }\n\n  /**\n   * Initializes a new instance of the EventEmitter class.\n   */\n  constructor() {\n    this.events = {}\n  }\n\n  /**\n   * Registers a listener for a specific event key.\n   *\n   * @param {string} keyEvent - The key of the event to listen for.\n   * @param {(param: Event) => void} callback - The callback function to be invoked when the event is emitted.\n   */\n  public listener(keyEvent: string, callback: (param: Event) => void): void {\n    this.events[keyEvent] = callback\n  }\n\n  /**\n   * Emits an event, invoking the corresponding listener with the provided parameter.\n   *\n   * @param {string} keyEvent - The key of the event to emit.\n   * @param {Event} param - The parameter to pass to the event's callback function.\n   */\n  public emit(keyEvent: string, param: Event): void {\n    if (this.events[keyEvent]) {\n      this.events[keyEvent](param)\n    }\n  }\n}\n","import type { EventEmitter } from './EventEmitter'\n\ntype callbackType = <T>(param: { [data: string]: T }) => void\n\n/**\n * EventHandler class to manage event listeners for an audio context.\n */\nexport class EventHandler {\n  private emitter: EventEmitter\n  private audioCtx: AudioContext | undefined\n\n  /**\n   * Creates an instance of EventHandler.\n   * @param emitter - The event emitter instance to manage event listeners.\n   * @param audioCtx - AudioContext instance to monitor state changes. Optional to facilitate testing.\n   */\n  constructor(emitter: EventEmitter, audioCtx?: AudioContext) {\n    this.emitter = emitter\n    this.audioCtx = audioCtx\n  }\n\n  /**\n   * Registers a callback for the 'decoded' event.\n   * @param callback - The callback to be invoked when the event occurs.\n   */\n  public ready(callback: callbackType) {\n    this.emitter.listener('decoded', callback)\n  }\n\n  /**\n   * Registers a callback for the 'start' event.\n   * @param callback - The callback to be invoked when the event occurs.\n   */\n  public start(callback: callbackType) {\n    this.emitter.listener('start', callback)\n  }\n\n  /**\n   * Registers a callback for the 'end' event.\n   * @param callback - The callback to be invoked when the event occurs.\n   */\n  public end(callback: callbackType) {\n    this.emitter.listener('end', callback)\n  }\n\n  /**\n   * Monitors the state changes of the AudioContext and invokes the callback.\n   * @param callback - The callback to be invoked when the AudioContext state changes.\n   */\n  public state(callback: callbackType) {\n    if (!this.audioCtx) return\n\n    this.audioCtx.onstatechange = () => callback({ data: this.audioCtx?.state })\n  }\n}\n","import { AudioCtx } from './AudioCtx'\nimport { defaultStates } from './states'\nimport { EventEmitter } from '../EventEmitter'\nimport { EventHandler } from '../EventHandler'\nimport { decodeAudioData } from './decodeAudioData'\nimport { initializeSource } from './initializeSource'\nimport { getBuffer, preloadFile } from './utils'\n\n/**\n * Configuration options for creating an Audio instance.\n */\ntype AudioProp = {\n  file: string\n  volume?: number\n  autoPlay?: boolean\n  loop?: boolean\n  preload?: boolean\n}\n\n/**\n * Valid event types that can be emitted by the Audio instance.\n */\ntype AudioEvent = 'ready' | 'start' | 'state' | 'end'\n\n/**\n * If `AudioContext` is initialized before a user gesture on the page, its\n * state becomes `suspended` by default. Once `AudioContext.state` is `suspended`,\n * the only way to start it after a user gesture is executing the `resume` method.\n */\nconst start = (audioCtx: AudioContext, source: AudioBufferSourceNode) =>\n  audioCtx.state === 'suspended' ? audioCtx.resume().then(() => source.start(0)) : source.start(0)\n\n/**\n * Audio player class that provides control over a single audio file.\n * Implements the AudioType interface for managing audio playback, volume, and events.\n */\nexport class AudioClass {\n  /** @private Path or URL to the audio file */\n  private _file: AudioProp['file']\n  /** @private Initial volume level set during construction */\n  private _initialVolume: number\n  /** @private Flag indicating if audio should play automatically */\n  private _autoPlay: boolean\n  /** @private Initial loop state set during construction */\n  private _initialLoop: boolean\n  /** @private Web Audio API context */\n  private _audioCtx: AudioContext\n  /** @private Internal state management object */\n  private _states: typeof defaultStates\n  /** @private Event emitter for handling audio events */\n  private _emitter: EventEmitter\n  /** @private Event handler for managing event subscriptions */\n  private _eventHandler: EventHandler\n\n  /**\n   * Creates an instance of Audio player.\n   *\n   * @param {AudioProp} config - The audio configuration object\n   * @param {string} config.file - Path or URL to the audio file\n   * @param {number} [config.volume=1] - Initial volume level (0 to 1)\n   * @param {boolean} [config.autoPlay=false] - Whether to start playing automatically\n   * @param {boolean} [config.loop=false] - Whether to loop the audio\n   * @param {boolean} [config.preload=false] - Whether to preload the audio file\n   */\n  constructor({ file, volume = 1, autoPlay = false, loop = false, preload = false }: AudioProp) {\n    this._file = file\n    this._initialVolume = volume\n    this._autoPlay = autoPlay\n    this._initialLoop = loop\n    this._audioCtx = AudioCtx()\n    this._states = { ...defaultStates }\n    this._emitter = new EventEmitter()\n    this._eventHandler = new EventHandler(this._emitter, this._audioCtx)\n\n    if (preload) {\n      preloadFile(file)\n    }\n  }\n\n  /**\n   * Fetches and decodes the audio buffer for the given source node.\n   * @private\n   * @param {AudioBufferSourceNode} source - The audio source node to load buffer into\n   */\n  private curryGetBuffer(source: AudioBufferSourceNode): void {\n    this._states.isDecoded = false\n\n    getBuffer(this._file)\n      .then((arrayBuffer) => {\n        decodeAudioData({\n          audioCtx: this._audioCtx,\n          source,\n          arrayBuffer,\n          autoPlay: this._autoPlay,\n          loop: this._initialLoop,\n          states: this._states,\n          emitter: this._emitter,\n        })\n      })\n      .catch(console.error)\n  }\n\n  /**\n   * Starts or resumes audio playback.\n   * If playback hasn't started, initializes audio source and begins playback.\n   * If playback was paused, resumes from the current position.\n   */\n  public play(): void {\n    if (this._states.hasStarted) {\n      this._audioCtx.resume()\n      this._states.isPlaying = true\n      return\n    }\n\n    initializeSource({\n      audioCtx: this._audioCtx,\n      volume: this._initialVolume,\n      emitter: this._emitter,\n      states: this._states,\n    })\n\n    const { source } = this._states\n\n    if (source) {\n      this.curryGetBuffer(source)\n\n      if (this._states.isDecoded) {\n        start(this._audioCtx, source)\n      } else {\n        this._emitter.listener('decoded', () => start(this._audioCtx, source))\n      }\n\n      this._states.hasStarted = true\n      this._states.isPlaying = true\n      this._emitter.emit('start', { data: null })\n    }\n  }\n\n  /**\n   * Pauses audio playback by suspending the audio context.\n   */\n  public pause(): void {\n    this._audioCtx.suspend()\n    this._states.isPlaying = false\n  }\n\n  /**\n   * Toggles between play and pause states.\n   */\n  public toggle(): void {\n    this._states.isPlaying ? this.pause() : this.play()\n  }\n\n  /**\n   * Stops audio playback completely.\n   * Different from pause as it resets the playback position.\n   */\n  public stop(): void {\n    if (this._states.hasStarted) {\n      this._states.source?.stop(0)\n      this._states.isPlaying = false\n    }\n  }\n\n  /**\n   * Subscribes to audio events.\n   * @param {AudioEvent} eventType - Type of event to listen for\n   * @param {Function} callback - Function to call when event occurs\n   */\n  public on(eventType: AudioEvent, callback: <T>(param: { [data: string]: T }) => void): void {\n    this._eventHandler[eventType]?.(callback)\n  }\n\n  /**\n   * Gets the current volume level.\n   * @returns {number} Current volume value between 0 and 1\n   */\n  public get volume(): number {\n    return this._states.gainNode?.gain.value ?? 0\n  }\n\n  /**\n   * Sets the audio volume level.\n   * @param {number} newVolume - New volume value between 0 and 1\n   */\n  public set volume(newVolume: number) {\n    if (this._states.gainNode) {\n      this._states.gainNode.gain.value = newVolume\n    }\n  }\n\n  /**\n   * Gets the current loop state.\n   * @returns {boolean} Whether audio is set to loop\n   */\n  public get loop(): boolean {\n    return this._states.source?.loop ?? false\n  }\n\n  /**\n   * Sets the loop state.\n   * @param {boolean} newLoop - Whether audio should loop\n   */\n  public set loop(newLoop: boolean) {\n    if (this._states.source) {\n      this._states.source.loop = newLoop\n    }\n  }\n\n  /**\n   * Gets the current state of the audio context.\n   * @returns {AudioContextState} Current state of the audio context\n   */\n  public get state(): AudioContextState {\n    return this._audioCtx.state\n  }\n\n  /**\n   * Gets the current AudioContext instance.\n   * @returns {AudioContext} The current AudioContext instance\n   */\n  public get audioCtx(): AudioContext {\n    return this._audioCtx\n  }\n}\n\n/**\n * Factory function to create a new Audio instance.\n *\n * @param {AudioPropType} props - The audio configuration properties\n * @returns {AudioType} A new Audio instance\n */\nexport default (props: AudioProp): AudioClass => new AudioClass(props)\n","import { throwsError } from './utils'\n\ndeclare global {\n  interface Window {\n    webkitAudioContext: typeof window.AudioContext\n  }\n}\n\n/**\n * Creates and returns a new AudioContext instance with cross-browser support.\n * Attempts to use standard AudioContext first, falls back to webkitAudioContext for older browsers.\n *\n * @returns {AudioContext} A new AudioContext instance\n * @throws {Error} If the browser doesn't support AudioContext\n */\nexport const AudioCtx = (): AudioContext => {\n  const Context = window.AudioContext || window.webkitAudioContext\n\n  if (!Context) {\n    throwsError(\"Your browser doesn't support AudioContext - https://bit.ly/2YWmpnX\")\n  }\n\n  return new Context()\n}\n","import type { AudioState } from './states'\nimport type { EventEmitter } from '../EventEmitter'\n\n/**\n * Configuration options for audio decoding\n */\ntype DecodeAudioDataConfig = {\n  /** The Web Audio API context */\n  audioCtx: AudioContext\n  /** The audio source node to configure */\n  source: AudioBufferSourceNode\n  /** The raw audio data to decode */\n  arrayBuffer: ArrayBuffer\n  /** Whether to start playback immediately after decoding */\n  autoPlay: boolean\n  /** Whether the audio should loop when playing */\n  loop: boolean\n  /** State object to track decoding and playback status */\n  states: AudioState\n  /** Event emitter to broadcast decode completion */\n  emitter: EventEmitter\n}\n\n/**\n * Decodes audio data from an ArrayBuffer and configures the audio source node.\n * On successful decode, sets up the audio buffer, configures looping, and optionally starts playback.\n *\n * @param {DecodeAudioDataConfig} config - Configuration object containing all necessary parameters\n * @returns {void}\n * @emits {Event} 'decoded' - Emitted when audio data is successfully decoded, includes the AudioBuffer\n */\nexport const decodeAudioData = ({\n  audioCtx,\n  source,\n  arrayBuffer,\n  autoPlay,\n  loop,\n  states,\n  emitter,\n}: DecodeAudioDataConfig): void => {\n  const onSuccess = (buffer: AudioBuffer) => {\n    source.buffer = buffer\n    source.loop = loop\n\n    states.isDecoded = true\n    emitter.emit('decoded', { data: buffer })\n\n    if (autoPlay) {\n      source.start(0)\n      states.isPlaying = true\n    }\n  }\n\n  audioCtx.decodeAudioData(arrayBuffer, onSuccess, console.error)\n}\n","import type { EventEmitter } from '../EventEmitter'\nimport type { AudioState } from './states'\n\ntype InitializeSourceConfig = {\n  audioCtx: AudioContext\n  volume: number\n  emitter: EventEmitter\n  states: AudioState\n}\n\n/**\n * Initializes and configures an audio source node with gain control.\n * Sets up the audio processing chain and configures event handling for playback completion.\n *\n * @param {InitializeSourceConfig} config - Configuration object containing:\n *   - audioCtx: Web Audio API context\n *   - volume: Initial volume level (0 to 1)\n *   - emitter: Event emitter for broadcasting audio events\n *   - states: State management object for tracking audio state\n * @returns {void}\n * @emits {Event} 'end' - Emitted when audio playback completes\n */\nexport const initializeSource = ({\n  audioCtx,\n  volume,\n  emitter,\n  states,\n}: InitializeSourceConfig): void => {\n  const source = (states.source = audioCtx.createBufferSource())\n  const gainNode = (states.gainNode = audioCtx.createGain())\n\n  gainNode.gain.value = volume\n  gainNode.connect(audioCtx.destination)\n  source.connect(gainNode)\n\n  source.onended = () => {\n    states.hasStarted = false\n    states.isPlaying = false\n    emitter.emit('end', { data: null })\n  }\n}\n","import type { Event, EventEmitter } from '../EventEmitter'\nimport type { AudioPlaylistState } from './states'\n\nimport Audio from '../audio/Audio'\n\n/**\n * Creates an audio playback controller function that manages playlist state and events.\n *\n * @param {AudioPlaylistState} states - Global state object containing audio playback states\n * @param {EventEmitter} emmiter - Event emitter for handling playlist events\n * @returns {(files: string[], loop: boolean) => void} A function that handles audio playback\n */\nconst playAudio = (\n  states: AudioPlaylistState,\n  emmiter: EventEmitter,\n): ((files: string[], loop: boolean) => void) => {\n  const playAudioHelper = (files: string[], loop: boolean) => {\n    const file = files[states.audioIndex]\n    const audio = Audio({ file, volume: states.volume })\n    states.audio = audio\n\n    audio.on('start', (e) => {\n      emmiter.emit('start', e as Event)\n    })\n\n    audio.on('end', () => {\n      if (states.isStopped) return\n\n      if (files.length === states.audioIndex + 1) {\n        states.audio = null\n        states.audioIndex = 0\n\n        if (states.loop) {\n          playAudioHelper(files, loop)\n        } else {\n          emmiter.emit('end', { data: null })\n          states.isPlaying = false\n        }\n      } else {\n        states.audioIndex++\n        playAudioHelper(files, loop)\n      }\n    })\n\n    audio.play()\n  }\n\n  return playAudioHelper\n}\n\nexport default playAudio\n","import type { AudioClass } from '../audio/Audio'\n\n/**\n * Represents the global state for an audio playlist.\n */\nexport type AudioPlaylistState = {\n  volume: number\n  loop: boolean\n  audio: AudioClass | null\n  isStopped: boolean\n  isPlaying: boolean\n  audioIndex: number\n}\n\n/**\n * Default initial state for the audio playlist.\n */\nconst states: AudioPlaylistState = {\n  volume: 1,\n  loop: false,\n  audio: null,\n  isStopped: false,\n  isPlaying: false,\n  audioIndex: 0,\n}\n\nexport default states\n","/**\n * @fileoverview Audio playlist manager that handles playback of multiple audio files\n * with features like shuffle, loop, and weighted random selection.\n */\n\nimport Audio from '../audio/Audio'\nimport { EventEmitter } from '../EventEmitter'\nimport playAudio from './playAudio'\nimport globalStates from './states'\nimport { shuffle as shuffleHelper, weightedFiles, preloadFiles } from './utils'\n\n/**\n * Configuration options for initializing an AudioPlaylist instance.\n */\ntype AudioPlaylistConfig = {\n  files: string[] | { [key: string]: number }\n  volume?: number\n  loop?: boolean\n  shuffle?: boolean\n  preload?: boolean\n  preloadLimit?: number\n}\n\n/**\n * Events that can be emitted by the AudioPlaylist.\n */\ntype AudioPlaylistEvent = 'start' | 'end'\n\n/**\n * Manages audio playlist functionality including playback control, file management,\n * and event handling.\n */\nclass AudioPlaylist {\n  /** Event emitter for handling playlist events */\n  private emmiter: EventEmitter\n  /** Global state object containing audio playback states */\n  private states: typeof globalStates\n  /** Array of audio file paths to be played */\n  private copiedFiles: string[]\n  /** Flag indicating if playlist should loop */\n  private shouldLoop: boolean\n  /** Curried function for playing audio files */\n  private curryPlayAudio: ReturnType<typeof playAudio>\n\n  /**\n   * Creates an instance of AudioPlaylist.\n   * @param {Object} config - The playlist configuration\n   * @param {(string[] | Record<string, number>)} config.files - Array of audio files or weighted object\n   * @param {number} [config.volume=1] - Initial volume level (0-1)\n   * @param {boolean} [config.loop=false] - Whether to loop the playlist\n   * @param {boolean} [config.shuffle=false] - Whether to shuffle the playlist\n   * @param {boolean} [config.preload=false] - Whether to preload audio files\n   * @param {number} [config.preloadLimit=3] - Number of files to preload\n   */\n  constructor({\n    files,\n    volume = 1,\n    loop = false,\n    shuffle = false,\n    preload = false,\n    preloadLimit = 3,\n  }: AudioPlaylistConfig) {\n    this.emmiter = new EventEmitter()\n    this.states = { ...globalStates, ...{ volume, loop } }\n\n    const hasWeights = !Array.isArray(files)\n    this.shouldLoop = loop || hasWeights\n\n    const normalizedFiles: string[] = hasWeights\n      ? weightedFiles(files as { [key: string]: number })\n      : (files as string[])\n\n    this.copiedFiles =\n      shuffle || hasWeights ? shuffleHelper(normalizedFiles) : normalizedFiles.slice()\n\n    this.curryPlayAudio = playAudio(this.states, this.emmiter)\n\n    if (preload) {\n      preloadFiles(this.copiedFiles, preloadLimit)\n    }\n  }\n\n  /**\n   * Starts or resumes audio playback.\n   * If no audio is playing or playback was stopped, starts from current position.\n   */\n  play(): void {\n    const { audio } = this.states\n    this.states.isPlaying = true\n\n    if (!audio || this.states.isStopped) {\n      this.curryPlayAudio(this.copiedFiles, this.shouldLoop)\n      this.states.isStopped = false\n      return\n    }\n\n    audio.play()\n  }\n\n  /**\n   * Toggles between play and pause states.\n   */\n  toggle(): void {\n    this.states.isPlaying ? this.pause() : this.play()\n  }\n\n  /**\n   * Pauses the current audio playback.\n   */\n  pause(): void {\n    this.states.audio?.pause()\n    this.states.isPlaying = false\n  }\n\n  /**\n   * Stops the current audio playback and resets the player.\n   */\n  stop(): void {\n    this.states.isPlaying = false\n    this.states.isStopped = true\n    this.states.audio?.stop()\n  }\n\n  /**\n   * Plays the next audio file in the playlist sequence.\n   * Handles wrapping back to the beginning when reaching the end of the playlist.\n   */\n  next(): void {\n    const isLastFile = this.states.audioIndex === this.copiedFiles.length - 1\n    this.states.audioIndex = isLastFile ? 0 : this.states.audioIndex + 1\n\n    this.states.audio?.pause()\n\n    const file = this.copiedFiles[this.states.audioIndex]\n    const audio = Audio({ file, volume: this.states.volume })\n\n    this.states.audio = audio\n    audio.play()\n  }\n\n  /**\n   * Plays the previous audio file in the playlist sequence.\n   * Handles wrapping to the end of the playlist when at the beginning.\n   */\n  prev(): void {\n    const isFirstFile = this.states.audioIndex === 0\n    this.states.audioIndex = isFirstFile ? this.copiedFiles.length - 1 : this.states.audioIndex - 1\n\n    this.states.audio?.pause()\n\n    const file = this.copiedFiles[this.states.audioIndex]\n    const audio = Audio({ file, volume: this.states.volume })\n    this.states.audio = audio\n    audio.play()\n  }\n\n  /**\n   * Registers an event listener for playlist events.\n   * @param {AudioPlaylistEvent} eventType - Type of event to listen for\n   * @param {Function} callback - Callback function to execute when event occurs\n   */\n  on(eventType: AudioPlaylistEvent, callback: (param: { [data: string]: unknown }) => void): void {\n    this.emmiter.listener(eventType, callback)\n  }\n\n  /**\n   * Gets the current volume level.\n   * @returns {number} Current volume level (0-1)\n   */\n  get volume(): number {\n    return this.states.volume\n  }\n\n  /**\n   * Sets the volume level.\n   * @param {number} newVolume - New volume level (0-1)\n   */\n  set volume(newVolume: number) {\n    this.states.volume = newVolume\n\n    if (this.states.audio) {\n      this.states.audio.volume = newVolume\n    }\n  }\n\n  /**\n   * Gets the current loop state.\n   * @returns {boolean} Whether playlist is set to loop\n   */\n  get loop(): boolean {\n    return this.states.loop\n  }\n\n  /**\n   * Sets the loop state.\n   * @param {boolean} newLoop - New loop state\n   */\n  set loop(newLoop: boolean) {\n    this.states.loop = newLoop\n  }\n\n  /**\n   * Gets the current AudioContext instance.\n   * @returns {(AudioContext | undefined)} Current AudioContext or undefined if not initialized\n   */\n  get audioCtx(): AudioContext | undefined {\n    return this.states.audio?.audioCtx\n  }\n}\n\n/**\n * Factory function to create a new AudioPlaylist instance.\n * @param {AudioPlaylistConfig} params - Configuration parameters for the playlist\n */\nexport default (params: AudioPlaylistConfig) => new AudioPlaylist(params)\n","/**\n * Converts an object of weighted file paths into an array where each file appears\n * multiple times based on its weight.\n *\n * @example\n * weightedFiles({ 'song1.mp3': 2, 'song2.mp3': 1 })\n * // returns ['song1.mp3', 'song1.mp3', 'song2.mp3']\n *\n * @param {Object.<string, number>} files - Object with file paths as keys and weights as values\n * @returns {string[]} Array of file paths repeated according to their weights\n */\nexport const weightedFiles = (files: { [key: string]: number }): string[] => {\n  return Object.entries(files).flatMap(([file, weight]) => Array(Math.floor(weight)).fill(file))\n}\n\n/**\n * Shuffles an array of strings using the Fisher-Yates algorithm.\n * Creates a new array instead of modifying the original.\n *\n * @example\n * shuffle(['a', 'b', 'c'])\n * // might return ['b', 'c', 'a']\n *\n * @param {string[]} list - Array of strings to shuffle\n * @returns {string[]} New array with shuffled elements\n */\nexport const shuffle = (list: string[]): string[] => {\n  const result = list.slice()\n  let index = list.length - 1\n\n  while (index >= 0) {\n    const randomIdx = Math.floor(Math.random() * index + 1)\n    const tmp = result[index]\n    result[index] = result[randomIdx]\n    result[randomIdx] = tmp\n\n    index--\n  }\n\n  return result\n}\n\n/**\n * Preloads audio files in batches with a specified limit.\n * Uses a queue system to load files sequentially after the initial batch.\n *\n * @param {string[]} files - Array of file URLs to preload\n * @param {number} limit - Maximum number of files to load simultaneously\n * @param {Function} [api=fetch] - Function to use for fetching files\n * @param {Function} [done] - Callback function to execute when all files are loaded\n */\nexport const preloadFiles = (\n  files: string[],\n  limit: number,\n  api: (input: string, init?: RequestInit | undefined) => Promise<Response> = fetch,\n  done?: () => void,\n): void => {\n  const queue: string[] = files.slice(limit).reverse()\n  let isDone = false\n\n  /**\n   * Processes the next item in the queue or calls the done callback if queue is empty\n   */\n  const requestNext = () => {\n    if (!queue.length) {\n      if (!isDone) {\n        done?.()\n        isDone = true\n      }\n    } else {\n      request(queue.pop() as string)\n    }\n  }\n\n  const request = (fileName: string) => {\n    api(fileName).then(requestNext).catch(requestNext)\n  }\n\n  for (let i = 0; i < limit; i++) {\n    request(files[i])\n  }\n}\n"],"names":["preloadFile","file","attempts","done","fetch","then","catch","defaultStates","isDecoded","isPlaying","hasStarted","source","gainNode","EventEmitter","constructor","events","this","listener","keyEvent","callback","emit","param","EventHandler","emitter","audioCtx","ready","start","end","state","onstatechange","data","resume","AudioClass","volume","autoPlay","loop","preload","_file","_initialVolume","_autoPlay","_initialLoop","_audioCtx","_states","_emitter","_eventHandler","AudioCtx","Context","window","AudioContext","webkitAudioContext","value","Error","throwsError","curryGetBuffer","response","ok","status","arrayBuffer","decodeAudioData","states","buffer","console","error","play","initializeSource","createBufferSource","createGain","gain","connect","destination","onended","pause","suspend","toggle","stop","on","eventType","newVolume","newLoop","props","audio","isStopped","audioIndex","AudioPlaylist","files","shuffle","preloadLimit","emmiter","copiedFiles","shouldLoop","curryPlayAudio","globalStates","hasWeights","Array","isArray","normalizedFiles","Object","entries","flatMap","weight","Math","floor","fill","weightedFiles","list","result","slice","index","length","randomIdx","random","tmp","shuffleHelper","playAudio","playAudioHelper","Audio","e","preloadFiles","limit","api","queue","reverse","isDone","requestNext","request","pop","fileName","i","next","prev","params"],"mappings":"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"}