Audio Object

class Audio extends Object

Plays sounds from files or memory, discovers and operates audio devices, controls what other applications are playing, observes levels, and records from a microphone or from what an output device is playing.

This class is exported by the KS module:

#Import "Ks" { Audio }

Note: This class is experimental: it and its nested types may change or be removed without deprecation. See #Warn Experimental.

Note: This class has no instances; its members are used directly on the class. Calling Audio() throws an Error.

The related types below, such as Audio.Clip, are nested in Audio, and importing Audio introduces no other names.

The Sound functions are the AutoHotkey-compatible surface. This class adds to them.

Table of Contents

Conventions

These hold for every member on this page.

SubjectRule
LevelsVolume and peak are finite numbers from 0 through 100. Playback and session volume are linear gains: 50 halves the signal amplitude. Endpoint volume follows the system mixer's scale. Pan is finite, from -100 (full left) through 100 (full right).
TimesDurations and positions are milliseconds and may be fractional.
AbsenceA value which is not yet known, does not apply, or has no default reads as an empty string. Enumerations return an empty Array and counts return 0 instead.
Tokens and namesEvery documented string token, device name and executable name is matched without case sensitivity, and getters return the canonical casing shown here. An opaque Id is compared exactly.
Sample formats"Unsigned8", "Signed16", "Signed24", "Signed32" or "Float32".
Sample rate and channels8000 through 192000 Hz; 1 or 2 channels.

Selecting a device

Everywhere a device is accepted it may be an Audio.Device, an exact Id, an exact and unique Name, or an empty string for the platform's current default. A name matching several devices throws a ValueError listing their ids; a selector matching none throws an OSError.

Error handling

ResultSituation
TypeErrorA value of the wrong kind, such as a path where a Clip is required.
ValueErrorAn unknown token, a non-finite number, a value outside a documented range, malformed PCM, an ambiguous device name, or an operation on a disposed object. Setters never clamp; use Min and Max before assigning.
OSErrorA capability this host does not provide, a denied permission, or a device, decode or write failure. The message names the missing capability and a concrete next step.
Empty stringA bounded refusal from Audio.Output.Play, and an absent default, match or result.

Where a script must not throw, check the matching probe first.

Audio

Capability probes

Each of these is a read-only true or false. Reading one never opens a device or prompts for permission.

PropertyDescription
IsPlaybackSupportedWhether this host has a playback implementation at all.
IsOutputAvailableWhether a usable output device is present. A supported host with no speakers answers false here and true to IsPlaybackSupported.
IsInputAvailableWhether a usable input device is present.
IsDeviceChangeSupportedWhether OnDeviceChange can subscribe to device arrivals, removals and default changes.
IsSessionControlSupportedWhether this host can enumerate and control other applications' audio.
IsMeteringSupportedWhether an Audio.Meter can observe a device or session level.
IsMicrophoneCaptureSupportedWhether a microphone or other input device can be recorded.
IsSystemAudioCaptureSupportedWhether what an output device is playing can be recorded.

IsFormatSupported

Supported := Audio.IsFormatSupported(Format)

Returns 1 (true) if Load can decode that container, or 0 (false) otherwise. Format is a token or an extension, with or without the leading dot, such as "wav" or ".WAV". This never inspects a file.

Note: WAV is supported on every platform. Other containers depend on the host's decoders: Windows reports mp3, m4a, aac, wma and flac; Linux requires libsndfile, or ffmpeg on PATH; Ogg and Opus are not supported on Windows or macOS. Where a container is unsupported, convert the file to WAV or pass its samples to FromPcm.

Load

Clip := Audio.Load(Path)

Decodes a file into a reusable, immutable Audio.Clip. A relative path resolves once, against the current A_WorkingDir, at the moment of the call.

An empty path, a path containing a NUL, or a syntactically invalid path throws a ValueError before any I/O. A missing or unreadable file, an invalid container, and a format this host cannot decode each throw an OSError naming the file.

FromPcm

Clip := Audio.FromPcm(Data, SampleRate , Channels, SampleFormat)

Copies headerless PCM out of a Buffer into an immutable clip. The copy is synchronous and complete, so Data may be reused or freed as soon as this returns.

Channels defaults to 1 and SampleFormat to "Signed16". The buffer must hold a whole number of frames — one frame being Channels samples — and must not be empty. A clip may hold at most 512 MiB of decoded samples.

Play

Playback := Audio.Play(Source , Volume, Loop, Pan, StartMilliseconds, Device)

Plays a clip or a file path and returns an Audio.Playback controlling that sound. Source is an Audio.Clip or a path, which is loaded through Load. Volume defaults to 100, Loop to false, Pan to 0, StartMilliseconds to 0 and Device to the current default output.

The sound plays through a private output of the script. Where Audio.Output.Play would return an empty string, this throws an OSError.

A starting point at or past the clip's duration is a ValueError.

To stop the sound, call Stop on the returned playback. To stop several sounds at once, play them through an Audio.Output and call its StopAll.

Devices

List := Audio.Devices(Kind)

Returns an Array of every present, usable Audio.Device. Kind is "Output", "Input" or "All" (the default), and any other token is a ValueError. "All" lists outputs before inputs.

A host with no audio at all returns an empty Array.

DefaultDevice

Device := Audio.DefaultDevice(Kind)

Returns the current default Audio.Device of one kind, or an empty string when the host has none. Kind is "Output" (the default) or "Input"; "All" is a ValueError.

Sessions

List := Audio.Sessions(PIDOrName, Device)

Returns an Array of every live application audio session, optionally narrowed to one process and one device.

PIDOrName is a process id, or an executable name with or without its .exe suffix. It is not a path: a value containing a slash or backslash is a ValueError. Omitted, it matches every session.

A host with no per-application audio model returns an empty Array.

SetApplicationVolume

Changed := Audio.SetApplicationVolume(PIDOrName, Volume , Device)

Sets one absolute volume on every session of a process and returns how many sessions were changed, which is 0 when nothing matched. Volume is an absolute level from 0 through 100; relative strings such as "+5" are not accepted here.

If a session which still exists refuses the change, an OSError is thrown after the other sessions are changed.

SetApplicationMute

Changed := Audio.SetApplicationMute(PIDOrName, Mute , Device)

Sets one absolute mute state on every session of a process and returns how many changed. Mute is a state, not a toggle.

OnDeviceChange

Hook := Audio.OnDeviceChange(Callback , Kind)
Hooks := Audio.Hooks

Calls Callback when a device arrives, is removed, is renamed, or becomes the default, and returns a running hook controlling the subscription.

Callback is a function object, called as Callback(Hook, Change, Device), where Change is "Added", "Removed", "Changed" or "DefaultChanged" and Device is the Audio.Device the change concerns; for "Removed" its Status is "Missing". A_EventInfo is not set. Kind filters the devices reported: "Output", "Input" or "All" (the default).

A notification which changes nothing observable does not call back.

The returned hook is an Audio.DeviceHook, an EventHook with no additional members. Audio.Hooks is an Array of the running hooks.

The hook does not keep the script running; a script which only waits for device changes calls Persistent.

An unknown Kind is a ValueError, and subscribing where IsDeviceChangeSupported is false raises an OSError.

Audio.Clip

class Audio.Clip extends Object

One decoded sound: immutable, reusable, and safe to play many times at once. A clip is produced by Audio.Load or Audio.FromPcm; calling Audio.Clip() throws an Error naming those two.

Every property is read-only.

MemberDescription
DurationMillisecondsThe clip's length, derived from its exact frame count.
FrameCountThe number of frames.
SampleRateFrames per second.
Channels1 or 2.
SampleFormatThe canonical token the samples arrived as.

Audio.Device

class Audio.Device extends Object

A snapshot of one audio endpoint, plus the controls that endpoint owns. A device is produced by Audio.Devices and Audio.DefaultDevice, and returned by several other members; calling Audio.Device() throws an Error.

Id is the durable identity: Refresh() never adopts a same-named replacement.

MemberDescription
IdThe opaque, platform-assigned identity, compared exactly. Read-only.
NameThe endpoint's display name. Read-only.
Kind"Output" or "Input". A device carries audio in exactly one direction. Read-only.
IsDefaultWhether this device was the default for its kind when the snapshot was taken. Read-only.
Status"Running" while any application holds a live stream, "Idle" when none does, "Unknown" when the backend cannot determine it, or "Missing" after removal is observed. Running includes an open but silent stream. Read-only.
IsRunningTrue when Status is "Running", otherwise false. Read Status to distinguish idle, unknown and missing devices. Read-only.
VolumeThis endpoint's own volume, 0 through 100. Writable.
MuteThis endpoint's own mute state. Writable.
Refresh()Re-reads this exact device. Returns the device itself when it is still present, or an empty string otherwise. An available backend reporting the device absent marks it "Missing"; an unavailable backend does not establish removal and leaves its activity unknown.
ToClr()Returns the platform's own device object, for the controls this class does not model. Work done through it bypasses this class's caching.

Operating a device whose Status is "Missing" raises an OSError telling the caller to Refresh() first. Reading or writing Volume or Mute on a host without endpoint volume control raises an OSError.

Note: What ToClr returns depends on the platform; on Windows it is a Core Audio interface. On Linux, ToClr throws an OSError. See also SoundGetInterface.

Audio.Output

class Audio.Output extends Object
Output := Audio.Output(Device, VoiceLimit, VoicePolicy, LatencyMilliseconds)

An explicit mixer and the one native stream behind it.

Construction opens nothing; call Open or TryOpen to open the stream. Device defaults to the current default output and is followed as that default changes; naming a device pins the output to it. VoiceLimit is 1 through 256 and defaults to 16. VoicePolicy defaults to "Oldest". LatencyMilliseconds is 5 through 500 and defaults to 20; it is a scheduling target, not a promised floor.

A script may hold at most 16 open outputs at once, counting the private one Audio.Play uses. It may likewise hold 8 running recorders and 16 running meters. Exceeding a cap throws an OSError.

Voice policy

A voice is one sound in flight. When every voice is taken, VoicePolicy decides what a new play does:

TokenBehavior
"Oldest"Ends the longest-running sound and takes its voice. That sound's playback reports "Stolen".
"RoundRobin"Takes voices in rotation.
"Reject"Admits nothing. Play returns an empty string and DroppedPlayCount increases.

Members

MemberDescription
DeviceThe Audio.Device this output is pinned to, or an empty string while it follows the current default. Read-only.
Status"Closed", "Opening", "Open", "Transitioning", "Unavailable" or "Disposed". Read-only.
IsAvailableWhether the configured binding resolves to a usable device, even while the output is closed. Read-only.
IsFollowingDefaultWhether the output follows the default output device rather than a pinned one. Read-only.
VoiceLimitThe configured number of simultaneous voices. Read-only.
ActiveVoiceCountHow many voices are in use. Read-only.
DroppedPlayCountHow many submissions were refused since the output was created. Read-only.
UnderrunCountHow many times the mixer could not fill the stream in time. Read-only.
SampleRate, ChannelsThe format the open stream negotiated, or an empty string while closed. Read-only.
RequestedLatencyMillisecondsThe latency asked for at construction. Read-only.
LatencyMillisecondsThe latency measured on the open stream, or an empty string when there is no measurement. Read-only.
PeakThe loudest sample of the last completed quantum after output gain, 0 through 100, or an empty string while closed. Read-only.
ErrorAn Error describing the last failure this output cached, or an empty string. Read-only.
VolumeThis output's own gain, applied after every voice is summed. Writable.
MuteSilences the output without stopping its voices. Writable.
VoicePolicyThe policy token. Writable; a change affects only later admissions.
Open()Opens the native stream and returns the output, so it chains. Raises an OSError when the device cannot be opened, playback is unsupported, or the open-output cap is reached.
TryOpen()Opens, returning true or false. A missing device or a transient failure is false; an unsupported backend or an exhausted output cap throws an OSError.
Prepare(Clip)Converts a clip into the open stream's format ahead of time, so playing it never resamples, and returns the output. Preparing while the output is closed registers the clip, which the next successful open converts along with the others. One output's converted clips may total 512 MiB.
IsPrepared(Clip)Whether that clip is converted for the current stream format. It is false while the output is closed, even for a clip which was prepared.
Play(Clip , Volume, Loop, Pan, StartMilliseconds)Submits one play and returns an Audio.Playback, or an empty string for a bounded refusal: the output is not open, the command queue is full, or a "Reject" policy found no free voice. Clip must be a clip, not a path.
StopAll()Silences every voice on this output immediately. Idempotent, and valid while closed.
Close()Reversible: retires the native stream but keeps the prepared clips for a later open.
Dispose()Terminal: releases the caches and native state. Later operations raise a ValueError. Dropping the last reference to an open output does the same.

While an output is "Transitioning" after a default change, IsAvailable is false, nothing is prepared, and Play returns an empty string; nothing is played through the stale stream. An output pinned to a specific device which goes away becomes "Unavailable" and waits for that same id to return.

Volume and Mute belong to the open stream: a closed output reports 100 and false, and a value assigned to either while it is closed is discarded. VoicePolicy is held on the output itself, so it survives a Close and applies to the next Open.

Audio.Playback

class Audio.Playback extends Object

One sound in flight. A playback is produced by Audio.Play and Audio.Output.Play; calling Audio.Playback() throws an Error.

A playback controls only its own sound; after its voice is stolen, it controls nothing.

MemberDescription
DeviceThe Audio.Device this sound was admitted on. It does not track a later default change. Read-only.
Status"Queued", "Playing", "Paused", "Ended", "Stopped", "Stolen", "DeviceLost" or "Error". The first terminal reason wins, so a sound which reached its own end reports "Ended" even after a later Stop(). Read-only.
IsPlayingTrue when Status is "Playing" or "Queued"; false while paused or ended. Read-only.
DurationMillisecondsThe source clip's length. Read-only.
PeakThis voice's own peak after its gain and pan, 0 through 100, or an empty string before a frame was rendered. Read-only.
PositionMillisecondsThe play position, or an empty string once the output that carried the sound is gone. Writable while the sound is live: assigning to a playback which has ended, or a position at or past the duration, is a ValueError.
VolumeThis voice's own level. Writable.
Pan-100 through 100. Writable.
LoopWhether the sound repeats. Writable while it is live.
MuteSilences this voice without giving it up. Writable.
Pause()Pauses, holding both the position and the voice. Returns the resulting paused state, which is false once the sound is terminal.
Resume()Resumes and returns the playback, so it chains.
Stop()Ends the sound. Idempotent and harmless after it already ended.

Audio.Session

class Audio.Session extends Object

One application's audio on one device. Sessions are produced by Audio.Sessions; calling Audio.Session() throws an Error.

On Linux, retitling a live audio stream keeps its session identity. This correction is source-reviewed and awaits Linux verification.

A session object is bound to that exact session; a later session of the same application needs a new object from Audio.Sessions.

MemberDescription
IdThe session's opaque identity. Read-only.
DeviceThe Audio.Device this session plays or records on. Read-only.
ProcessIdThe owning process id, or an empty string when the backend does not report one. Read-only.
ProcessNameThe owning executable's name, or an empty string. Read-only.
DisplayNameThe name the application chose for itself, or an empty string. Read-only.
Status"Active", "Inactive" or "Expired". Read-only.
IsSystemSoundsWhether this is the system-sounds session rather than an application's. Read-only.
IsVolumeSupported, IsMuteSupported, IsMeteringSupportedWhat this particular session offers. Read-only.
VolumeThe application's own level on this device, 0 through 100. Writable.
MuteThe application's own mute state. Writable.
Refresh()Re-reads this exact session. Returns the session when it is still live, or an empty string after marking it "Expired".
ToClr()Returns the platform's own session object, with the same unspecified-type contract as Audio.Device.ToClr.

Operating an expired session raises an OSError telling the caller to Refresh() first. On a platform with no per-application audio model, every member which reaches the platform raises an OSError explaining what to use instead.

Audio.Meter

class Audio.Meter extends Object
Meter := Audio.Meter(Target, IntervalMilliseconds)

An explicit observation of a device's or a session's level.

Target is an Audio.Device, an Audio.Session, a device selector, or an empty string for the current default output. IntervalMilliseconds is 20 through 1000 and defaults to 50. Construction snapshots the target and opens nothing.

Read Peak whenever a value is wanted, for instance from a timer.

MemberDescription
TargetThe device or session being observed, or an empty string when none resolved. Read-only.
Status"Stopped", "Active", "Error" or "Disposed". Read-only.
IntervalMillisecondsThe publication cadence asked for at construction. Some backends ignore it. Read-only.
IsSupportedWhether this target can be metered at all. False while no target resolved. Read-only.
PeakThe most recent peak, 0 through 100, or an empty string before any observation completed and while the meter is not active. Read-only.
ErrorAn Error describing why the meter could not open, or an empty string. Read-only.
Start()Opens the native observation and returns the meter. Raises an OSError for an unsupported host, an absent target, or a target which refuses metering. Calling it on an already active meter does nothing.
Stop()Reversible: releases the observation but keeps the target, so Start can be called again.
Dispose()Terminal: stops the meter and refuses a later Start. Dropping the last reference to a running meter does the same.

Audio.Recorder

class Audio.Recorder extends Object
Recorder := Audio.Recorder(Source, Path, Device, SampleRate, Channels, SampleFormat, ChunkMilliseconds, MaximumDurationMilliseconds)

One capture, from a microphone or from what an output device is playing. Construction checks every option and opens nothing.

Source is "Microphone" (the default) or "SystemOutput". Path is a .wav file to write; left empty, the recording is kept in memory instead. Device is a device selector of the kind that source implies, defaulting to the current default. SampleRate defaults to 48000, Channels to 1, SampleFormat to "Signed16", and ChunkMilliseconds to 100 within 10 through 1000 — the buffer size the capture device is asked for, which a backend may deliver more often than. MaximumDurationMilliseconds is 0 for unbounded, or 1 through 86400000.

A recorder is single-use: construct another for a second recording.

Recording captures whatever the device carries, which can include other people and other applications. Microphone capture requires the script's AudioCapture capability, granted at build time or through RequestCapabilities, and on platforms which ask, the operating system's own microphone permission. A denial raises an OSError naming the privacy setting to change, and no frames are captured.

MemberDescription
Source"Microphone" or "SystemOutput". Read-only.
DeviceThe Audio.Device being captured, or an empty string while the default is used and nothing is open. Read-only.
PathThe resolved destination file, or an empty string for an in-memory recording. Read-only.
Status"Ready", "Opening", "Recording", "Paused", "Finalizing", "Stopped", "DeviceLost", "Error" or "Disposed". Read-only.
DurationMillisecondsHow much audio has been captured. Read-only.
MaximumDurationMillisecondsThe configured bound, or 0 for unbounded. Read-only.
SampleRate, Channels, SampleFormat, ChunkMillisecondsThe configured capture format. SampleRate and Channels report what the device actually negotiated once Start succeeded. Read-only.
PeakThe loudest sample captured so far, 0 through 100, or an empty string before any frame arrived. Read-only.
OverrunCountHow many captured chunks were dropped because the destination could not keep up. Nonzero means the recording has gaps, which is otherwise invisible. Read-only.
ErrorAn Error describing a terminal fault, or an empty string. Read-only.
ResultThe finished Audio.Recording, or an empty string until one exists. Reading it does not stop the recorder. Read-only.
Start()Opens the device and begins capture, returning the recorder. Raises an OSError for an unsupported source, a denied permission or an absent device, leaving the recorder retryable. Starting twice is a ValueError.
Pause()Holds media time without ending the recording. Returns the resulting paused state.
Resume()Resumes and returns the recorder.
Stop()Ends the recording and returns its Audio.Recording, or an empty string when no honest result could be produced. Repeating it returns the same outcome. Stopping a recorder which was never started is a ValueError.
Dispose()Releases the capture and finalizes whatever result exists. Dropping the last reference to a running recorder does the same.

An in-memory recording stops at 10 minutes or 256 MiB, whichever arrives first; a file recording is bounded only by MaximumDurationMilliseconds. The destination's parent directory must already exist — no directory is created. The device is opened before the file is created or truncated, so a failed start leaves no empty file behind; a failure after frames have arrived keeps the partial file and finalizes a valid WAV header where it can.

Audio.Recording

class Audio.Recording extends Object

What a finished capture produced: an in-memory clip or a file path. A recording is produced by Audio.Recorder.Stop; calling Audio.Recording() throws an Error.

Every property is read-only.

MemberDescription
ClipThe captured audio as an Audio.Clip, ready to play, or an empty string when the recording went to a file.
PathThe file which was written, or an empty string when the recording was kept in memory.
DurationMillisecondsThe captured length.
FrameCountThe number of frames captured.
SampleRate, ChannelsThe format which was captured.
SampleFormatThe token describing the result's samples. An in-memory recording holds "Float32" samples whatever the capture format was; a file holds the configured format.

Platform Support

Playback, clips, device discovery, endpoint volume and mute, and device-change events are part of every backend. What varies is application sessions, metering and capture.

PlatformSessionsMeteringMicrophoneSystem output
WindowsSupportedSupportedSupportedSupported on Windows 10 version 1703 and later
LinuxSupportedSupportedSupportedSupported where the audio server exposes a monitor for the sink
macOSUnsupportedUnsupportedSupportedNot implemented

Note: The Windows backend is verified on hardware, apart from recording. On Linux, device enumeration, playback and system-output recording are verified, and the rest is unverified. The macOS backend is unverified.

Linux

Works on X11 and Wayland alike, and requires a reachable PulseAudio or pipewire-pulse server. An application's process name and display name are available only when it publishes them.

Audio.Device.Volume uses the system mixer's slider percentage, so 50 means 50% in the mixer. Endpoint and session volume writes preserve channel balance; readings above 100 are clamped to 100. Session volume retains the linear-gain scale described above.

Note: A recording can include about 100 ms of audio buffered before it started, and a capture opened immediately after another on the same source can lose a similar amount. Use DurationMilliseconds for a recording's length, and leave a short gap between back-to-back captures.

macOS

Device volume and mute use the selected input or output direction. If the device only exposes channel controls, writes cover every channel. This channel fallback is unverified on hardware.

Audio.Sessions returns an empty Array, and SetApplicationVolume and SetApplicationMute throw an OSError. Use Audio.Device.Volume and Mute instead.

Audio.Meter.Start throws an OSError. Audio.Output.Peak, Audio.Playback.Peak and Audio.Recorder.Peak are supported.

To record system output, install a loopback device such as BlackHole and record it as an input device.

Examples

These examples were verified on Windows, except the recording example.

Plays a sound file and waits for it to finish.

#Import "Ks" { Audio }

Sound := Audio.Play(A_WinDir "\Media\notify.wav")
while Sound.IsPlaying
    Sleep 50

Prepares one clip on an output the script owns, then plays it repeatedly. Each press overlaps the previous sound instead of cutting it off, and no press decodes or resamples anything.

#Import "Ks" { Audio }

Click := Audio.Load(A_ScriptDir "\click.wav")
Out := Audio.Output("", 8, "Oldest")
Out.Open()
Out.Prepare(Click)

F3::Out.Play(Click, 70)
F4::Out.StopAll()

Loops background music at a low level, then fades it out and stops it.

#Import "Ks" { Audio }

Music := Audio.Play(A_ScriptDir "\theme.wav", 40, true)

Sleep 5000
Level := Music.Volume
while (Level > 0) {
    Music.Volume := Level := Max(0, Level - 2)
    Sleep 50
}
Music.Stop()

Lists the output devices and their levels, marking the default one.

#Import "Ks" { Audio }

Text := ""
for D in Audio.Devices("Output")
    Text .= D.Name " " Round(D.Volume) (D.IsDefault ? " (default)" : "") "`n"
MsgBox Text

Mutes one application without touching the system volume. Application sessions are unsupported on macOS, so the capability is checked first.

#Import "Ks" { Audio }

if Audio.IsSessionControlSupported
    MsgBox Audio.SetApplicationMute("chrome", true) " sessions muted"

Records five seconds from the microphone to a file. This asks for microphone permission on platforms which require it, and captures whatever the microphone hears.

#Import "Ks" { Audio }

Rec := Audio.Recorder("Microphone", A_Desktop "\memo.wav")
Rec.Start()
Sleep 5000
if (Result := Rec.Stop())
    MsgBox "Wrote " Result.Path ", " Round(Result.DurationMilliseconds) " ms"

Reports when headphones are plugged in or the default output changes. The hook does not keep the script running, so Persistent does.

#Import "Ks" { Audio }

Hook := Audio.OnDeviceChange(DeviceChanged, "Output")
Persistent

DeviceChanged(ThisHook, Change, Device)
{
    ToolTip Change ": " Device.Name
    SetTimer () => ToolTip(), -2000
}

Sound functions, SoundPlay, SoundBeep, SoundSetVolume, SoundGetInterface, EventHook, Buffer, KS module, Platform Support