Macotron

macotron.audio

List the Mac's audio devices, pick the default input and output, change volume and mute, and record the microphone to a file.

// Cycle the default output through every device that can play sound.
const outputs = macotron.audio.devices().filter((d) => d.output);
const current = macotron.audio.output();
const i = outputs.findIndex((d) => current && d.id === current.id);
const next = outputs[(i + 1) % outputs.length];
if (next) macotron.audio.setOutput(next.id);

Devices

A device is { id, name, uid, input, output }. id is the Core Audio device number, which can change after a restart or a reconnect. uid is a stable string. input and output say whether the device can record and play. A device that does both, such as a headset, shows up once with both set to true.

devices(): Array<{ id, name, uid, input, output }>

Every audio device macOS knows about, including virtual ones.

input(): { id, name, uid, input, output } | null

The default input device, or null when there is none.

output(): { id, name, uid, input, output } | null

The default output device, or null when there is none.

setInput(idOrName: number | string): boolean

Makes a device the default input. Pass its id, its exact name, or its uid. Returns false when no device matches or macOS refuses the change.

setOutput(idOrName: number | string): boolean

Makes a device the default output. It takes the same arguments as setInput.

Volume and mute

Each call takes an optional device id, name, or uid. Without one, it acts on the default output, which is the system volume.

volume(id?: number | string): number | null

The output volume from 0 to 1. Returns null when the device has no volume control, which is common for HDMI and some USB devices.

setVolume(level: number, id?: number | string): boolean

Sets the output volume. level is clamped to 0…1. Returns false when the device has no volume control.

isMuted(id?: number | string): boolean

Whether the device is muted. When you pass a device that can record, this reads the input mute, so isMuted(macotron.audio.input().id) tells you whether the microphone is muted. Returns false when the device has no mute control.

setMuted(on: boolean, id?: number | string): boolean

Mutes or unmutes the device. Like isMuted, a device that can record has its input muted. Returns false when the device does not accept the change.

// Toggle the microphone.
const mic = macotron.audio.input();
if (mic) macotron.audio.setMuted(!macotron.audio.isMuted(mic.id), mic.id);

Recording

Macotron records one file at a time from the default input, as AAC in an .m4a container: 44.1 kHz, mono.

Recording needs Microphone permission. Add "microphone" to permissions in macotron.plugin() so the plugin's Settings page shows it.

record(opts: { path: string }): boolean

Starts recording to path. A leading ~ expands to your home folder, and missing folders are created. A recording that is already running stops first. Returns false when path is empty or the recorder cannot start.

stopRecord(): { path: string; seconds: number } | null

Stops recording and returns the file path and its length in seconds. Returns null when nothing was recording.

isRecording(): boolean

Whether a recording is running.

macotron.command("Start Recording", "Record the microphone", () => {
  if (!macotron.audio.record({ path: "~/Desktop/macotron.m4a" })) {
    macotron.notify.toast("Record", "Could not start", { color: "error" });
  }
});

macotron.command("Stop Recording", "Stop the microphone recording", () => {
  const result = macotron.audio.stopRecord();
  if (result) macotron.notify.toast("Saved", result.path);
});

Events

audio:changed

Fires when the default input or output changes, or when a device is added or removed. The payload is { flags }, an array that holds one or more of "input", "output", and "devices". Read the new state with input(), output(), or devices().

// Pause playback when headphones go away.
let last = macotron.audio.output();
macotron.on("audio:changed", ({ flags }) => {
  if (!flags.includes("output") && !flags.includes("devices")) return;
  const out = macotron.audio.output();
  if (last && out && out.uid !== last.uid && /headphone|airpods/i.test(last.name)) {
    if (macotron.media.nowPlaying().playing) macotron.media.playPause();
  }
  last = out;
});

See also