const LINUX_AUDIO_TYPE = 'alsa' const LOW_MIXER_PERCENT = 70 export interface AudioMixerLevel { command: string control: string db?: number percent: number } export interface AudioSetup { captureArgs: string[] captureLabel: string mixerLevel?: AudioMixerLevel playbackArgs: string[] playbackLabel: string } interface LinuxDevice { device: string label: string } let audioSetup: AudioSetup | undefined export function getAudioSetup() { if (audioSetup) return audioSetup audioSetup = process.platform === 'linux' ? getLinuxAudioSetup() : getDefaultAudioSetup() return audioSetup } export function isLowMixerLevel(level: AudioMixerLevel | undefined): level is AudioMixerLevel { return !!level && level.percent < LOW_MIXER_PERCENT } export function refreshAudioSetup() { audioSetup = undefined return getAudioSetup() } export function setMixerLevel(control: string, percent: number) { const proc = Bun.spawnSync(['amixer', '-q', 'sset', control, `${percent}%`], { stdout: 'ignore', stderr: 'ignore', }) if (proc.exitCode !== 0) return false audioSetup = undefined return true } function commandExists(command: string) { const proc = Bun.spawnSync(['which', command], { stdout: 'ignore', stderr: 'ignore' }) return proc.exitCode === 0 } function getCommandOutput(args: string[]) { try { const proc = Bun.spawnSync(args, { stdout: 'pipe', stderr: 'ignore' }) if (proc.exitCode === 0) return proc.stdout.toString().trim() } catch { // ignored } } function getDefaultAudioSetup(): AudioSetup { return { captureArgs: ['-d'], captureLabel: getDefaultLabel('input'), playbackArgs: ['-d'], playbackLabel: getDefaultLabel('output'), } } function getDefaultLabel(type: 'input' | 'output') { if (process.platform === 'darwin' && commandExists('SwitchAudioSource')) { const args = type === 'input' ? ['SwitchAudioSource', '-c', '-t', 'input'] : ['SwitchAudioSource', '-c'] return getCommandOutput(args) || `system default ${type}` } return `system default ${type}` } function getLinuxAudioSetup(): AudioSetup { const capture = getLinuxDevice('capture') const playback = getLinuxDevice('playback') const mixerLevel = getLinuxMixerLevel() return { captureArgs: ['-t', LINUX_AUDIO_TYPE, capture.device], captureLabel: capture.label, mixerLevel, playbackArgs: ['-t', LINUX_AUDIO_TYPE, playback.device], playbackLabel: playback.label, } } function getLinuxDevice(type: 'capture' | 'playback'): LinuxDevice { const envName = type === 'capture' ? 'BAUDY_CAPTURE_DEVICE' : 'BAUDY_PLAYBACK_DEVICE' const envDevice = process.env[envName]?.trim() if (envDevice) return { device: envDevice, label: `${envDevice} (${envName})` } const args = type === 'capture' ? ['arecord', '-l'] : ['aplay', '-l'] const output = getCommandOutput(args) const match = output?.match(/card\s+(\d+):.*?device\s+(\d+):/s) if (match) { const [, card, device] = match return { device: `plughw:${card},${device}`, label: `plughw:${card},${device}` } } return { device: 'default', label: 'default' } } function getLinuxMixerLevel(): AudioMixerLevel | undefined { if (!commandExists('amixer')) return for (const control of ['Speaker Analog', 'Speaker', 'Master', 'PCM']) { const output = getCommandOutput(['amixer', 'get', control]) if (!output) continue const percentMatch = output.match(/\[(\d+)%\]/) if (!percentMatch) continue const dbMatch = output.match(/\[(-?\d+(?:\.\d+)?)dB\]/) return { command: `amixer -q sset '${control}' 100%`, control, db: dbMatch ? Number(dbMatch[1]) : undefined, percent: Number(percentMatch[1]), } } }