proton_microphone

Microphone capture device and session helpers.

moonbit
native
microphone
audio
moon add moonbit-community/proton_microphone@0.1.16
Download zip
Version
0.1.16
License
Apache-2.0
Last updated
3 days ago
Downloads
6
README

#moonbit-community/proton_microphone

Native-only microphone device discovery helpers for MoonBit.

///|
fn main {
let devices = @proton_microphone.MicrophoneDevice::list()
for device in devices {
println(device.session_label())
}
}

///|
fn example_label {
let device = @proton_microphone.MicrophoneDevice::{
id: "mic-1",
name: "Built-in Mic",
state: Idle,
default_config: @proton_microphone.CaptureConfig::new(
echo_cancellation=true,
noise_suppression=true,
),
monitor_supported: true,
}
println(device.session_label())
}

#
CaptureConfig

pub(all) struct CaptureConfig {
channels : Int
sample_rate_hz : Int
sample_format : SampleFormat
echo_cancellation : Bool
noise_suppression : Bool
} derive(Eq,
Debug
)

Capture settings used when opening or describing a microphone stream.

channels and sample_rate_hz are normalized by CaptureConfig::normalized before chunk sizing is computed. echo_cancellation and noise_suppression are advisory voice-processing flags; support depends on the actual host audio stack, but keeping them in the config makes intent explicit at API boundaries.

#
CaptureConfig::new

fn CaptureConfig::new(channels? : Int, sample_rate_hz? : Int, sample_format? : SampleFormat, echo_cancellation? : Bool, noise_suppression? : Bool) -> CaptureConfig

Create a capture configuration.

Every argument has a conservative default: mono, 48 kHz, floating-point audio with voice-processing flags disabled. Callers can override only the fields they care about, then call normalized before sizing buffers or opening a native capture stream. This keeps common code short while still making platform-sensitive options explicit at the call site.

Example

test "CaptureConfig::new applies conservative defaults" {
let config = CaptureConfig::new(sample_rate_hz=44_100)
assert_eq(config.channels, 1)
assert_eq(config.sample_rate_hz, 44_100)
}

#
CaptureConfig::normalized

fn CaptureConfig::normalized(self : CaptureConfig) -> CaptureConfig

Normalize a capture configuration into safe runtime bounds.

A configuration with fewer than one channel is clamped to mono, and a sample rate below 8 kHz is clamped to 8 kHz. The sample format and voice-processing flags are preserved so callers do not lose intent when normalizing user-provided settings.

Example

test "normalized clamps channels and sample rate to safe bounds" {
let config = CaptureConfig::new(channels=0, sample_rate_hz=4_000).normalized()
assert_eq(config.channels, 1)
assert_eq(config.sample_rate_hz, 8_000)
}

#
CaptureConfig::recommended_chunk_frames

fn CaptureConfig::recommended_chunk_frames(self : CaptureConfig) -> Int

Return the preferred frame count for one responsive capture chunk.

The recommendation is ten milliseconds of audio after normalization. For example, 48 kHz audio yields 480 frames, while an invalid 4 kHz input first normalizes to 8 kHz and then yields 80 frames.

Example

test "recommended_chunk_frames is ten milliseconds of audio" {
assert_eq(
CaptureConfig::new(sample_rate_hz=48_000).recommended_chunk_frames(),
480,
)
}

#
CaptureConfig::uses_voice_processing

fn CaptureConfig::uses_voice_processing(self : CaptureConfig) -> Bool

Whether a config requests voice-processing behavior.

This helper only checks caller intent. It does not promise that the current operating system or selected device can actually provide echo cancellation or noise suppression.

Example

test "uses_voice_processing reflects requested flags" {
assert_false(CaptureConfig::new().uses_voice_processing())
assert_true(
CaptureConfig::new(noise_suppression=true).uses_voice_processing(),
)
}

#
CaptureState

pub(all) enum CaptureState {
Idle
Armed
Recording
Muted
} derive(Eq,
Debug
)

Runtime state for a microphone capture session.

Idle means the device is known but not prepared, Armed means the application is ready to begin capture, Recording means audio frames are actively being collected, and Muted means capture is intentionally suppressed while the device remains selected.

#
CaptureState::label

fn CaptureState::label(self : CaptureState) -> String

Return a stable lowercase label for a capture state.

The labels are designed for logs, telemetry, and user settings where a compact string is easier to persist than a debug representation. They are intentionally independent of the derived Show output so future internal formatting changes do not affect persisted labels.

Example

test "label returns a stable lowercase string" {
assert_eq(CaptureState::Recording.label(), "recording")
}

#
MicrophoneDevice

pub(all) struct MicrophoneDevice {
id : String
name : String
state : CaptureState
default_config : CaptureConfig
monitor_supported : Bool
} derive(Eq,
Debug
)

Microphone device descriptor returned by native discovery.

id is a stable identifier for the parsed listing within one discovery result, name is the human-facing device name, state starts at Idle, default_config is safe for low-latency voice capture, and monitor_supported reports whether this package recognized the entry as a monitor/source-loopback device.

#
MicrophoneDevice::is_live

fn MicrophoneDevice::is_live(self : MicrophoneDevice) -> Bool

Whether the microphone is currently armed or actively capturing.

Armed and Recording are live states because either state means the application has reserved the selected device for immediate capture. Idle and Muted are not live states, so this helper is suitable for UI badges, telemetry, and guard checks before reading capture buffers.

#
MicrophoneDevice::list

List microphone-like capture devices visible to the current platform.

Discovery is best-effort and uses the host audio API directly: Windows uses Core Audio capture endpoints, Linux uses ALSA device hints when available, and macOS uses Core Audio device properties. If the platform API is unavailable, blocked, or returns no capture devices, the function returns an empty array instead of raising.

Example

for device in @proton_microphone.MicrophoneDevice::list() {
println(device.session_label())
}

#
MicrophoneDevice::parse_listing

fn MicrophoneDevice::parse_listing(output : String) -> Array[MicrophoneDevice]

Parse a native microphone listing into device descriptors.

The parser expects one device name per line after the private FFI layer has decoded the native string encoding. Empty lines are ignored, Linux monitor/source-loopback names are filtered out, and parsed ids are assigned densely as mic-0, mic-1, and so on. Each parsed device receives CaptureConfig::new() defaults and starts in Idle, which keeps tests deterministic even when the host platform reports devices in a different order.

Example

test "parse_listing assigns dense ids and drops monitors" {
let devices = MicrophoneDevice::parse_listing(
"Built-in Mic\nMonitor of Output\nUSB Mic\n",
)
assert_eq(devices.length(), 2)
assert_eq(devices[0].id, "mic-0")
assert_eq(devices[1].name, "USB Mic")
}

#
MicrophoneDevice::session_label

fn MicrophoneDevice::session_label(self : MicrophoneDevice) -> String

Produce a concise session label for settings or telemetry.

The label combines the parsed device id, stable state label, and device name. It is deterministic for a single discovery result and convenient for logs such as mic-0:idle:Built-in Microphone.

#
SampleFormat

pub(all) enum SampleFormat {
I16
U16
F32
} derive(Eq,
Debug
)

Sample representation requested from a microphone capture session.

I16 and U16 are 16-bit integer formats that are common in lower-level APIs. F32 is the preferred normalized floating-point representation for processing pipelines because each sample is four bytes and typically maps to the [-1.0, 1.0] audio range.

#
SampleFormat::bytes_per_sample

fn SampleFormat::bytes_per_sample(self : SampleFormat) -> Int

Return the number of bytes occupied by one sample.

This is useful when translating frame counts into byte sizes for native buffers. I16 and U16 occupy two bytes per channel, while F32 occupies four bytes per channel.

Example

test "bytes_per_sample reflects the format width" {
assert_eq(SampleFormat::I16.bytes_per_sample(), 2)
assert_eq(SampleFormat::F32.bytes_per_sample(), 4)
}

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io