{"id":"biotoxin495/audiokit","name":"audiokit","scope":"biotoxin495","platform":"roblox","description":"A config-driven audio manager for Roblox.","version":"1.0.0","latest":"1.0.0","versions":["1.0.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"56e3cc8493562bcc62562ac8d8c510e04c053836a21da62a93d25fb0d1db834d","likes":0,"downloads":0,"install":"forest install biotoxin495/audiokit","url":"https://forest.dev/p/roblox/biotoxin495/audiokit","files":"https://api.forest.dev/ai/package/roblox/biotoxin495/audiokit/files","readme":"# AudioKit — A config-driven audio manager for Roblox\n\n**AudioKit**, a standalone audio manager for Roblox experiences.\n\nAudioKit lets you define sound effects and music once, refer to them by logical name throughout your game, and control playback through one reusable API.\n\nThe module handles overlapping sound effects, variations, cooldowns, looping audio, playlists, crossfades, temporary music overrides, preloading, volume controls, and cleanup internally—with no external dependencies.\n\n## Quick example\n\nAudioKit plays configured sounds by name through `Play`.\n\n```lua\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\n\nlocal AudioKit = require(ReplicatedStorage.Packages.AudioKit)\n\nlocal Audio = AudioKit.new({\n\tSounds = {\n\t\tUIClick = {\n\t\t\tSoundId = \"rbxassetid://123456789\",\n\t\t\tVolume = 0.8,\n\t\t},\n\t},\n})\n\nAudio:Play(\"UIClick\")\n```\n\nAudio definitions are available immediately. Preloading is optional and can be performed separately when needed.\n\n## 🚀 Features\n\n### Config-driven audio\n\nKeep sound effects, variations, playlists, default volumes, and preload selections in one configuration table. Definitions may be tables, asset ID strings, or numeric asset IDs.\n\n```lua\nlocal Audio = AudioKit.new({\n\tVolumes = {\n\t\tMaster = 1,\n\t\tSFX = 0.8,\n\t\tMusic = 0.4,\n\t},\n\n\tSounds = {\n\t\tConfirm = \"rbxassetid://123456789\",\n\t},\n\n\tMusic = {\n\t\tMain = \"rbxassetid://987654321\",\n\t},\n})\n```\n\n### Sound variations\n\nMultiple physical sounds can sit behind one logical name. AudioKit supports random, non-repeating random, rotating, shuffled, and weighted selection.\n\n```lua\nSounds = {\n\tFootstep = {\n\t\tVariants = {\n\t\t\t{ SoundId = \"rbxassetid://111\", Weight = 4 },\n\t\t\t{ SoundId = \"rbxassetid://222\", Weight = 2 },\n\t\t\t{ SoundId = \"rbxassetid://333\", Weight = 1 },\n\t\t},\n\t\tSelection = \"Weighted\",\n\t},\n}\n```\n\n### Overlapping and looping playback\n\nEvery playback uses its own `Sound` clone, allowing the same effect to overlap without restarting an existing voice. `Play` returns a handle that can pause, resume, fade, retune, or stop that playback.\n\n```lua\nlocal engine = Audio:Play(\"Engine\", {\n\tLooped = true,\n\tFadeInTime = 0.25,\n})\n\nif engine then\n\tengine:SetVolume(0.65, 0.2)\n\tengine:SetPlaybackSpeed(1.15)\n\tengine:Stop(0.3)\nend\n```\n\n### Cooldowns and concurrency limits\n\nFrequently triggered sounds can limit how often they start and how many instances may play simultaneously.\n\n```lua\nSounds = {\n\tHover = {\n\t\tSoundId = \"rbxassetid://123\",\n\t\tCooldown = 0.03,\n\t\tMaxInstances = 2,\n\t\tOverflowBehavior = \"StopOldest\",\n\t},\n}\n```\n\n### Music playlists and crossfades\n\nMusic entries may contain multiple tracks. AudioKit advances through the playlist and overlaps outgoing and incoming tracks during crossfades.\n\n```lua\nMusic = {\n\tMain = {\n\t\tTracks = {\n\t\t\t{ SoundId = \"rbxassetid://111\", Volume = 0.5 },\n\t\t\t{ SoundId = \"rbxassetid://222\", Volume = 0.5 },\n\t\t},\n\t\tSelection = \"Shuffle\",\n\t\tRepeat = true,\n\t\tCrossfadeTime = 1.5,\n\t},\n}\n```\n\n### Temporary music overrides\n\n`PushMusic` temporarily places another music context above the current one. Stopping its token automatically restores the previous context, and overrides may be nested.\n\n```lua\nlocal shopMusic = Audio:PushMusic(\"Shop\")\n\nif shopMusic then\n\tshopMusic:Stop()\nend\n```\n\n### Volume and mute controls\n\nMaster, sound-effect, and music levels can be adjusted independently without destroying active playback.\n\n```lua\nAudio:SetMasterVolume(0.8)\nAudio:SetBusVolume(\"SFX\", 0.6)\nAudio:SetBusVolume(\"Music\", 0.4)\nAudio:SetBusMuted(\"Music\", true)\n```\n\n### Selective preloading\n\nPreload the complete library, selected logical names, or the entries listed in the configuration.\n\n```lua\nlocal ok, err = Audio:PreloadAsync({\n\tSounds = { \"UIClick\", \"Purchase\" },\n\tMusic = { \"Main\" },\n})\n```\n\n`PreloadAsync` yields until Roblox finishes the request, so run it in a separate task if the rest of startup should continue.\n\n### Positional audio and effects\n\nParent playback to a `BasePart` or `Attachment` for positional audio. Definitions may also configure roll-off properties and legacy Roblox `SoundEffect` instances.\n\n```lua\nAudio:PlayAt(\"Explosion\", workspace.ExplosionPoint)\n```\n\n### Fully typed, no dependencies\n\nAudioKit uses strict Luau, ships with reusable exported types, and does not require Promise, Signal, Maid, Kernel, or another framework.\n\n## 📖 Basic usage\n\nPlace AudioKit somewhere accessible to your client scripts, such as `ReplicatedStorage.Packages`, then require it from a `LocalScript`.\n\n```lua\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\n\nlocal AudioKit = require(ReplicatedStorage.Packages.AudioKit)\n\nlocal Audio = AudioKit.new({\n\tDefaultFadeTime = 0.5,\n\n\tSounds = {\n\t\tUIClick = \"rbxassetid://123456789\",\n\t\tExplosion = {\n\t\t\tSoundId = \"rbxassetid://234567891\",\n\t\t\tVolume = 0.7,\n\t\t},\n\t},\n\n\tMusic = {\n\t\tMain = {\n\t\t\tTracks = {\n\t\t\t\t\"rbxassetid://345678912\",\n\t\t\t\t\"rbxassetid://456789123\",\n\t\t\t},\n\t\t\tSelection = \"Shuffle\",\n\t\t\tRepeat = true,\n\t\t\tCrossfadeTime = 1.5,\n\t\t},\n\t},\n})\n\nAudio:Play(\"UIClick\")\nAudio:PlayMusic(\"Main\")\n```\n\n### Example: per-play options\n\n```lua\nlocal handle = Audio:Play(\"Explosion\", {\n\tParent = workspace.ExplosionPoint,\n\tVolumeScale = 0.8,\n\tPlaybackSpeedScale = 1.05,\n\tFadeInTime = 0.1,\n})\n```\n\n### Example: temporary one-shot music\n\n```lua\nAudio:PushMusic(\"CrateOpening\", {\n\tRepeat = false,\n\tOnComplete = function(reason)\n\t\tprint(\"Crate music finished:\", reason)\n\tend,\n})\n```\n\n## ⚙️ API\n\n### `AudioKit.new(config, options?)`\n\nCreates an AudioKit instance and synchronously prepares its configured source sounds. Constructor options may override the generated folder's `Name` and `Parent`; it otherwise defaults to `config.Name` or `AudioKit` under `SoundService`.\n\n### `Audio:Play(name, options?)`\n\nPlays a configured sound effect and returns a playback handle, or `nil` when the name is missing, the bus is muted, or cooldown/concurrency rules reject playback.\n\n### `Audio:CreateSound(name, options?)`\n\nCreates and returns an unplayed `Sound` clone. The caller controls its parent and lifetime.\n\n### `Audio:PlayAt(name, parent, options?)`\n\nPlays a sound parented to the supplied `Instance`, which is useful for positional playback.\n\n### `Audio:PlayMusic(name, options?)`\n\nSets the base music context and returns whether the configured entry was found. If a temporary override is active, the new base context begins when that override is removed.\n\n### `Audio:PushMusic(name, options?)`\n\nPushes a temporary music context and returns a token. Call `token:Stop()` or `token:Destroy()` to restore the context beneath it. `token:IsActive()` reports whether it remains on the stack.\n\n### `Audio:StopMusic(fadeTime?)`\n\nStops current music and clears the base context and all temporary overrides.\n\n### `Audio:GetCurrentMusicName()`\n\nReturns the active logical music name, or `nil` when no context is selected.\n\n### Volume and mute methods\n\n| Method | Description |\n| --- | --- |\n| `SetMasterVolume(volume)` / `GetMasterVolume()` | Sets or returns the master volume |\n| `SetBusVolume(bus, volume)` / `GetBusVolume(bus)` | Sets or returns the `\"SFX\"` or `\"Music\"` volume |\n| `SetMasterMuted(muted)` / `IsMasterMuted()` | Sets or returns the master mute state |\n| `SetBusMuted(bus, muted)` / `IsBusMuted(bus)` | Sets or returns a bus mute state |\n| `SetSFXEnabled(enabled)` | Convenience setter for the SFX mute state |\n| `SetMusicEnabled(enabled)` | Convenience setter for the music mute state |\n\n### Preloading and inspection methods\n\n| Method | Description |\n| --- | --- |\n| `PreloadAsync(selection?, onProgress?)` | Preloads all or selected assets and returns `ok, err` |\n| `PreloadConfiguredAsync(onProgress?)` | Preloads the selection in `config.Preload` |\n| `GetActivePlaybackCount(bus?)` | Returns the number of live handles, optionally by bus |\n| `GetDebugState()` | Returns music, playback, volume, mute, and version state |\n\n### Cleanup methods\n\n| Method | Description |\n| --- | --- |\n| `StopAllSFX(fadeTime?)` | Stops all sound-effect playback |\n| `StopAll(fadeTime?)` | Stops all sound effects and music |\n| `Destroy()` | Stops playback, invalidates music tasks, and destroys generated instances |\n\n## Playback handle API\n\n| Member | Description |\n| --- | --- |\n| `Sound` | Runtime `Sound` owned by the handle |\n| `Completed` | Signal fired with the completion reason |\n| `Play()` | Starts the sound |\n| `Pause()` / `Resume()` | Pauses or resumes playback |\n| `IsAlive()` / `IsPlaying()` | Returns handle or playback state |\n| `SetPlaybackSpeed(speed)` | Updates playback speed |\n| `SetVolume(scale, fadeTime?)` | Updates per-play volume, optionally over time |\n| `FadeTo(gain, fadeTime, callback?)` | Fades gain and optionally invokes a callback |\n| `Stop(fadeTime?)` / `Destroy()` | Stops and cleans up playback |\n| `Wait()` | Yields until completion and returns the reason |\n| `OnCompleted(callback)` | Connects a completion callback |\n\nThe handle destroys its runtime `Sound` when playback finishes or the handle is destroyed.\n\n## Complete options reference\n\nYou normally only need to provide the properties you want to change. Omitted values use the module defaults.\n\n### Root configuration\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `Name` | `string` | Name of the generated root folder |\n| `DefaultFadeTime` | `number` | Default music transition duration |\n| `Volumes` | `table` | Initial `Master`, `SFX`, and `Music` volumes |\n| `Muted` | `table` | Initial `Master`, `SFX`, and `Music` mute states |\n| `Sounds` | `{ [string]: SoundDefinition }` | Named sound-effect definitions |\n| `Music` | `{ [string]: MusicDefinition }` | Named music definitions |\n| `Preload` | `boolean | table` | Selection used by configured preloading |\n\n### Sound and track properties\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `SoundId` | `string | number` | Roblox audio asset ID |\n| `Volume` | `number` | Base volume |\n| `PlaybackSpeed` | `number` | Base playback speed |\n| `Looped` | `boolean` | Whether playback loops |\n| `TimePosition` | `number` | Starting playback position |\n| `Weight` | `number` | Relative weight for weighted selection |\n| `Effects` | `{ EffectDefinition | SoundEffect }` | Effects cloned or created under the sound |\n| `RollOffMinDistance` | `number` | Minimum positional roll-off distance |\n| `RollOffMaxDistance` | `number` | Maximum positional roll-off distance |\n| `RollOffMode` | `Enum.RollOffMode` | Positional roll-off curve |\n| `EmitterSize` | `number` | Positional emitter size |\n\nSound definitions also accept `Variants`, `Selection`, `Cooldown`, `MaxInstances`, and `OverflowBehavior`. Music definitions accept `Tracks` or `Variants`, plus `Selection`, `Repeat`, and `CrossfadeTime`.\n\n### Selection modes\n\n| Value | Behavior |\n| --- | --- |\n| `Random` | Selects any variant randomly |\n| `RandomNoRepeat` | Avoids immediately replaying the previous variant |\n| `Rotate` | Selects variants sequentially |\n| `Shuffle` | Selects every variant once before reshuffling |\n| `Weighted` | Uses each variant's `Weight` |\n\n### Overflow behaviors\n\n| Value | Behavior |\n| --- | --- |\n| `Reject` | Refuses the new playback |\n| `StopOldest` | Stops the oldest playback before starting the new one |\n| `RestartOldest` | Replaces the oldest playback with the new one |\n\n### Play options\n\n| Option | Type | Description |\n| --- | --- | --- |\n| `Parent` | `Instance` | Parent for the runtime sound |\n| `VolumeScale` | `number` | Per-play volume multiplier |\n| `PlaybackSpeedScale` | `number` | Per-play speed multiplier |\n| `Looped` | `boolean` | Overrides looping |\n| `TimePosition` | `number` | Overrides the starting position |\n| `FadeInTime` | `number` | Fade-in duration |\n| `IgnoreCooldown` | `boolean` | Bypasses the cooldown |\n| `MaxInstances` | `number` | Overrides the concurrency limit |\n| `OverflowBehavior` | `OverflowBehavior` | Overrides overflow handling |\n| `PlayWhenMuted` | `boolean` | Allows creation while SFX is muted |\n\nMusic options additionally support `FadeTime`, `Repeat`, `OnComplete`, and `OnStopped`.\n\n## Complete example\n\n```lua\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\nlocal AudioKit = require(ReplicatedStorage.Packages.AudioKit)\n\nlocal Audio = AudioKit.new({\n\tName = \"GameAudio\",\n\tDefaultFadeTime = 0.5,\n\tVolumes = { Master = 1, SFX = 0.8, Music = 0.4 },\n\n\tSounds = {\n\t\tUIClick = \"rbxassetid://123456789\",\n\t\tFootstep = {\n\t\t\tVariants = {\n\t\t\t\t\"rbxassetid://111111111\",\n\t\t\t\t\"rbxassetid://222222222\",\n\t\t\t\t\"rbxassetid://333333333\",\n\t\t\t},\n\t\t\tSelection = \"RandomNoRepeat\",\n\t\t\tCooldown = 0.05,\n\t\t\tMaxInstances = 4,\n\t\t\tOverflowBehavior = \"StopOldest\",\n\t\t},\n\t},\n\n\tMusic = {\n\t\tMain = {\n\t\t\tTracks = {\n\t\t\t\t{ SoundId = \"rbxassetid://444444444\", Volume = 0.5 },\n\t\t\t\t{ SoundId = \"rbxassetid://555555555\", Volume = 0.5 },\n\t\t\t},\n\t\t\tSelection = \"Shuffle\",\n\t\t\tRepeat = true,\n\t\t\tCrossfadeTime = 1.5,\n\t\t},\n\t},\n\n\tPreload = {\n\t\tSounds = { \"UIClick\", \"Footstep\" },\n\t\tMusic = { \"Main\" },\n\t},\n})\n\ntask.spawn(function()\n\tAudio:PreloadConfiguredAsync()\nend)\n\nAudio:Play(\"UIClick\")\nAudio:PlayMusic(\"Main\")\n```\n\n## Legacy AudioController compatibility\n\nAudioKit automatically converts the previous configuration shape containing `SoundEffects`, `BackgroundMusic`, `AdditionalMusic`, and legacy `Sounds` groups.\n\nCompatibility methods include `PlaySound`, `GetSoundInstance`, `PlaySoundVariant`, `StartLoopingSound`, `StopLoopingSound`, `PlaySpecialMusic`, and `StopSpecialMusic`.\n\nGame-specific methods such as `StartShopMusic` should remain in the consuming game's controller and call the generic AudioKit API internally.\n\n## Behavior\n\nEach playback receives its own runtime `Sound` clone, so repeated effects can overlap. Source instances are created synchronously during `AudioKit.new`; preloading is a separate, optional operation.\n\nMusic overrides form a stack. The most recently pushed override plays until its token stops, then AudioKit restores the context beneath it. Crossfades use simultaneous outgoing and incoming sounds.\n\nAudioKit is designed primarily for a local player's client-side audio context. It does not prescribe an options menu, persistence system, or server-authoritative replication model.\n\n## 📝 Notes\n\n* AudioKit currently uses Roblox `Sound` instances rather than an `AudioPlayer` and `Wire` backend.\n* Beat-, bar-, and BPM-synchronized transitions are not included.\n* Effects use legacy `SoundEffect` instances because the playback backend uses `Sound`.\n* Roblox audio ownership and experience permissions remain the consuming game's responsibility.\n* Call `Audio:Destroy()` when the audio context is no longer needed.\n\n## 🛠️ Installation\n\n### Roblox Studio\n\nImport `AudioKitStandalone.rbxmx` for a single-ModuleScript package, or `AudioKit.rbxmx` for the structured version with child modules.\n\n```text\nReplicatedStorage\n└── Packages\n    └── AudioKit\n```\n\n### Rojo\n\nThe included project maps AudioKit to `ReplicatedStorage.Packages.AudioKit`.\n\n```bash\nrojo serve default.project.json\n```\n\n### Single ModuleScript\n\n`dist/AudioKit.lua` contains the complete bundled module. Create a ModuleScript named `AudioKit`, paste the bundled source into it, and require it normally.\n\n## License\n\nThis project is released under the MIT License.\n\nSee `LICENSE` for details.\n\nmade with ❤️ by biotoxin495\n","readmeTruncated":false}