Skip to Content
This documentation is provided with the HEAT environment and describes this HEAT instance.

Interface control document

1. Identification

ItemValue
DocumentICD-CMMIC-001 rev 1
ProductMicrophone input module (Microphone Module in the module registry)
Product versions covereda49e344
PeersWindows audio endpoints via WASAPI (NAudio 2.2.1), Whisper.net 1.7.2 local inference and its GGML model file
HostHEAT Capture, HEAT.Capture.ModuleSDK 2.0.5
Configuration sectionHEAT.MicrophoneInput
Data modelSimulationFrame carrying EntityEvents only (TrackingEvent and CommsEvent)
PlatformWindows only. WASAPI, the Windows audio session API, and process enumeration are all required.
Date2026-09-02
OwnerVRAI Simulation

2. Scope

This document defines the audio interfaces the module opens against the local Windows audio stack, the local Whisper inference dependency and its model prerequisite, the utterance state machine that drives publication, and the events the module publishes to HEAT Capture.

The module is receive-only with respect to audio. It never renders audio, never writes to a device, and exposes no remote control endpoint. Its only outbound interfaces are frame publication through the Module SDK and the debug view.

3. Interface inventory

IDNameDirectionTransportPeer
IF-CMMIC-01Peak-level meteringEndpoint to moduleWASAPI IAudioMeterInformation, polledCapture endpoint, and render endpoint in MicPlusDesktopSeparated
IF-CMMIC-02Microphone capture streamEndpoint to moduleWASAPI capture, callback-driven (WasapiCapture)Selected capture endpoint
IF-CMMIC-03Desktop loopback capture streamEndpoint to moduleWASAPI loopback capture (WasapiLoopbackCapture)Default render endpoint
IF-CMMIC-04Audio session process attributionOS to moduleWASAPI session manager plus process lookupWindows audio session manager
IF-CMMIC-05Transcription runtime and modelModule to local runtimeIn-process Whisper.net native library and GGML model fileWhisper.net 1.7.2
IF-CMMIC-06Frame publicationModule to hostModule SDK publication pathHEAT Capture
IF-CMMIC-07Debug viewModule to hostSDK IDebugViewProvider, polled and broadcast over SignalRHEAT Capture

4. IF-CMMIC-01 Peak-level metering

This is the only interface the module opens with default settings. It is what decides whether speech is active; it is not published in any form.

4.1 Transport and addressing

ItemValue
Device enumerationMMDeviceEnumerator, created once during module initialisation
Capture endpointDeviceId when set, otherwise the default Capture endpoint for role Multimedia
Render endpointDefault Render endpoint for role Multimedia, resolved only when CaptureMode is MicPlusDesktopSeparated
ReadingMMDevice.AudioMeterInformation.MasterPeakValue, a linear peak in the range 0 to 1
PollingOnce per capture cycle, inside HandleCapture; there is no separate metering thread

Device selection happens once, during initialisation. Changing DeviceId or CaptureMode while the module is initialised does not re-resolve the endpoint; the host must reinitialise the module.

4.2 Wire format and conversion

The linear peak is converted to dBFS as 20 * log10(peak). A peak of exactly 0 yields negative infinity rather than a finite floor. The activity decision is:

peak = max(micPeakDbfs, desktopPeakDbfs) isActive = peak > SilenceThresholdDb

desktopPeakDbfs is negative infinity unless MicPlusDesktopSeparated is active, so in MicOnly the comparison is against the microphone alone. In MicPlusDesktopSeparated the louder of the two endpoints wins, which means desktop output on its own can open and close an utterance with the microphone silent.

The negative-infinity case is substituted with -80 only when storing the value for the debug view. The activity comparison uses the unsubstituted value.

4.3 Authentication

None. The module reads whatever endpoints the logged-in Windows session exposes, with the permissions of the HEAT Capture process.

4.4 Timing

Metering is polled at the SDK Interval, 1000 ms by default. Peak level is an instantaneous sample at the moment of the poll, not an average over the interval, so speech quieter than SilenceThresholdDb at that instant is not detected at all, and a transient between polls is invisible.

5. IF-CMMIC-02 Microphone capture stream

5.1 When the stream is opened

A capture stream is opened at capture start only when at least one of EnableAudioInComms, EnableTranscription, or CaptureMode = MicPlusDesktopSeparated is set. With defaults, no audio stream is ever opened and no audio leaves the endpoint.

5.2 Format

The module requests no format and no share mode. It constructs WasapiCapture with the endpoint alone and then adopts the WaveFormat that NAudio reports for that stream as the authority for sample rate, bit depth and channel count. The module never resamples, requantises or downmixes before publication: buffered bytes are exactly the bytes the WASAPI callback delivered.

ItemValue
Sample rate, bit depth, channel countNot set by the module. Whatever the endpoint’s own Windows format provides.
Byte order and sample encodingAs delivered by WASAPI for that format; not inspected or normalised by the module
Observable valuesThe rate=, bits= and ch= parameters of the published audio content, and the Capture Format widget in the debug view

Because the format is not fixed, a consumer must read the format parameters from each published audio event rather than assume a rate or depth. There is no setting that constrains the format.

5.3 Exclusive versus shared mode

The module never requests exclusive mode. If another application already holds the endpoint in exclusive mode, constructing the capture throws COMException with HRESULT 0x88890004 (AUDCLNT_E_DEVICE_IN_USE). That case is caught specifically: the module logs a warning containing Microphone in exclusive use and capture disabled, monitoring only, sets its internal exclusive-mode flag, and disposes the capture. Metering (IF-CMMIC-01) is unaffected, so utterance start and end events continue to be published with no audio and no transcript behind them.

The exclusive-mode flag is the only source for the secure field on a transcript event. Since exclusive mode also means no microphone audio was buffered, no transcript can be produced in that state, so secure: true is unreachable in this revision.

5.4 Buffering gate

The capture callback appends to an in-memory buffer only while the module considers speech active. Callbacks that arrive while inactive are discarded at the callback, not filtered later. The buffer is unbounded in the module; the only size signal is the debug view gauge, whose scale ends at 2048 KB.

5.5 Lifecycle and errors

Capture start opens the stream and logs “Microphone capture started (buffering for comms events)”. Any exception other than the exclusive-mode case logs “Failed to start audio capture” and disposes the capture, leaving metering-only operation; capture start still reports success to the host.

If the stream stops on its own with an exception, the module logs “Microphone capture stopped unexpectedly” and does nothing else. There is no reconnect, no retry and no backoff on any audio interface in this revision: a device removed or reset mid-session stays down for the remainder of the session, while metering failures are silent (see section 11).

Capture end disposes both streams, clears both buffers, and resets the active, exclusive-mode, buffering and silence-timer state.

6. IF-CMMIC-03 Desktop loopback capture stream

Opened only when CaptureMode is MicPlusDesktopSeparated, compared case-insensitively after trimming. Any other value, including an empty one, means microphone only.

ItemValue
PeerDefault Render endpoint for role Multimedia, re-resolved at capture start if initialisation did not resolve one
FormatIndependent of the microphone stream, obtained the same way and reported separately
BufferA second, separate buffer; loopback bytes are never mixed into the microphone buffer
Startup log”Desktop loopback capture started (separate stream)“
No render deviceWarning “Desktop capture mode enabled but no render device was resolved”, then microphone-only operation
Stream faultWarning “Desktop capture stopped unexpectedly”, no recovery

The loopback stream contributes to the activity decision through IF-CMMIC-01 and is published as its own audio event, but it is never transcribed. Transcription runs against microphone bytes only.

7. IF-CMMIC-04 Audio session process attribution

When a cycle is active, the module enumerates audio sessions to label the utterance with a process name. In MicPlusDesktopSeparated it queries the render endpoint first and falls back to the capture endpoint; in MicOnly it queries the capture endpoint only.

Selection rule, per endpoint: sessions that are not in the active state or report process id 0 are skipped; the first active session belonging to a process other than HEAT Capture wins; if the only active session is HEAT Capture’s own process, that name is used; otherwise the label is the empty string. Resolving a process id that has already exited is swallowed and the session skipped.

The label is refreshed only on cycles where audio is active, and the value in effect when the utterance closes is the one attached to every event of that utterance. It is a best-effort attribution, not evidence of which application received the speech.

Enumeration failure leaves the previous label in place and, with DebugLogs on, logs “Failed to enumerate audio sessions”.

8. IF-CMMIC-05 Transcription runtime and model

8.1 Model prerequisite and provenance

Transcription is in-process. There is no service, socket or child process, and nothing leaves the workstation.

ItemValue
Model fileModelPath, default models/ggml-base.bin
Path resolutionA relative path is resolved against the module’s own assembly directory, not the working directory
Missing fileLogs Whisper model not found: {Path} and fails module initialisation. Transcription is never silently skipped for a missing model.
ProvenanceDownloaded at build time by download-whisper-model.ps1 from https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-<model>.bin, default model base, about 142 MB
Build integrationMSBuild target EnsureWhisperModel runs after build only when the output file is absent, with ContinueOnError=WarnAndContinue, so a failed download breaks nothing at build time and surfaces as the initialisation error above
Air-gapped buildsThe script serves from its own cache directory when the file is already present there
Ships in the packageThe model is included in the packaged module, so a deployed module does not download anything

8.2 Native runtime selection

Before loading, the module points the Whisper.net native loader at its own assembly location, because the packaged layout places Whisper.net.dll in a subdirectory while the native runtimes tree sits at the package root. The runtime preference order is fixed in code: CUDA, Vulkan, CoreML, CPU, then CPU without AVX. It is not configurable, and the module does not log which runtime was selected.

8.3 Load lifecycle

The model is loaded once, during module initialisation, and only when EnableTranscription is set. Success logs Whisper model loaded: {Path}. Any load failure logs “Failed to load Whisper model” and fails initialisation.

The module does not raise the SDK StartupTimeout, so model load, native library probing and runtime selection must all complete inside the default 5 second startup budget. A cold first load of a 142 MB model, or GPU runtime probing on a machine without a usable GPU, can exceed it.

Capture end disposes the Whisper processor and factory, while the model is loaded only during initialisation, which the host calls once per module lifetime. Transcription therefore works for the first capture session after initialisation and is silently absent from later sessions in the same host process, because the publication path is gated on a live processor rather than on the setting. See section 11.

8.4 Conversion and invocation

For each completed utterance the buffered microphone PCM is converted before inference: downmixed to mono when the capture format has more than one channel, resampled to 16000 Hz, requantised to 16-bit PCM, and wrapped in a WAV container in memory. Segment texts returned by the processor are concatenated with no separator and the result is trimmed.

Language is passed straight through to the processor, default auto for automatic detection. It is not validated by the module.

Inference is awaited inline inside the capture cycle. HandleCapture has no timeout, so a slow transcription delays the next cycle and therefore delays the next activity poll for as long as it runs.

9. IF-CMMIC-06 Frame publication and data model reference

9.1 Utterance state machine

Every branch below is evaluated once per capture cycle, inside HandleCapture.

OrderConditionEffect
1Active and previously inactiveClears both audio buffers, clears the silence timer, enables buffering, publishes the start event
2Inactive and previously activeDisables buffering immediately and stamps the silence timer with the current time
3Inactive, silence timer set, and elapsed at least SilenceGapMsSnapshots and clears both buffers, clears the timer, publishes the end event, then the audio and transcript events

A cycle that matches none of the three returns nothing, and the SDK publishes nothing for that cycle.

Two consequences follow from evaluating this only at the capture cadence. First, the silence gap is measured in whole cycles: with the default Interval of 1000 ms and the default SilenceGapMs of 500 ms, the gap is already satisfied at the next cycle, so an utterance closes on the first quiet poll and any SilenceGapMs below Interval behaves identically. Second, audio is only buffered between the poll that saw speech and the poll that did not, so the trailing part of an utterance after the last active poll is never captured.

Audit finding MIC-01 applies to this revision. Step 2 clears the active state on a single inactive poll and stops buffering before the configured gap has expired, and step 1 then treats resumed speech as a new utterance and clears the buffer. An intra-sentence pause therefore produces a second start event and discards the audio buffered for the first part of the sentence. Utterance events in captured data are not a reliable count of sentences spoken, and a published utterance is not guaranteed to hold all of the speech that preceded it.

9.2 Entity identity

Events carry an entity id derived from the capture endpoint’s WASAPI device id string: the string’s hash code, truncated to 32 bits and widened. The module publishes no entity and no component: a frame from this module contains only events. A consumer that expects a declared entity for that id will not find one from this module. The .NET runtime does not contract string hash codes as stable across processes, so the value should not be treated as a persistent identifier for the device across host restarts.

9.3 Published messages

Every message below is an EntityEvent on the frame. Frame capture time and every event timestamp in a cycle use the same value, UTC Unix milliseconds read at the top of that cycle.

MessagePublished whenContent
TrackingEvent, TRACKING_EVENT_TYPE_VOICE_TRANSMISSION_STARTEDSpeech becomes activeEntity id and timestamp only. TrackingEvent carries no payload and no secure field.
TrackingEvent, TRACKING_EVENT_TYPE_VOICE_TRANSMISSION_ENDEDSilence gap satisfiedEntity id and timestamp only
CommsEvent, comms_type: COMMS_TYPE_VOICEUtterance close, EnableAudioInComms on, and the microphone buffer is not emptytarget is the attributed process name; content is audio/pcm;src=mic;rate=RATE;bits=BITS;ch=CHANNELS;enc=base64,DATA
CommsEvent, comms_type: COMMS_TYPE_VOICESame, for the loopback buffer in MicPlusDesktopSeparatedIdentical shape with src=desktop
CommsEvent, comms_type: COMMS_TYPE_TTSUtterance close, EnableTranscription on, a live processor, a known capture format, non-empty microphone buffer, and a non-blank transcripttarget as above; content is the trimmed transcript text; secure is set from the exclusive-mode flag

COMMS_TYPE_TTS is the text-to-speech enum value, reused here for a speech-to-text transcript. The frequency and radio_band fields are never set by this module. secure is set only on the transcript event; audio events omit it regardless of exclusive mode.

When the capture format is unknown at close, the audio content header degrades to audio/pcm;src=<source>;enc=base64, with no format parameters, and the payload is published anyway.

Audio and transcript events are complementary, not alternatives: with both settings on, a single utterance close publishes the end event, one audio event per source, and the transcript event, all in the same frame.

9.4 SDK envelope

The SDK wraps the returned frame as a DataPacket with TimestampUnixMs set to UTC now at publication and the frame packed into Payload, carrying NodeId when OverrideNodeId is set and -1 otherwise. See the Module SDK ICD.

10. IF-CMMIC-07 Debug view

The module implements the SDK debug view contract, which the backend polls every 250 ms while the module is running and broadcasts to connected frontend clients. The state object exposes capture mode, voice-active and exclusive-mode indicators, the attributed process name, both buffer sizes in KB, both peak levels in dBFS rounded to one decimal, both capture formats as <rate>Hz / <bits>bit / <channels>ch, and the last transcript text.

This interface carries speech content: the last transcript and live level meters are broadcast to any client authorised to view the module’s debug state, whether or not transcripts are being published into captured data. Buffer sizes and peak levels are read from the capture loop’s own state, so the debug view is the only place the module exposes level or format information.

11. Error behaviour

ConditionBehaviourLog
Device enumeration or endpoint resolution failsModule initialisation failsError, “Failed to initialize audio devices”
Model file absent, with transcription enabledModule initialisation failsError, Whisper model not found: {Path}
Model load fails, with transcription enabledModule initialisation failsError, “Failed to load Whisper model”
No device resolved at capture startCapture start reports failureNone
Endpoint held in exclusive modeCapture disabled, metering continues, exclusive-mode flag setWarning containing Microphone in exclusive use and capture disabled, monitoring only
Any other capture-start failureCapture disposed, metering continues, capture start still reports successError, “Failed to start audio capture”
Render endpoint unresolved in desktop modeMicrophone-only operation for the sessionWarning, “Desktop capture mode enabled but no render device was resolved”
Capture stream stops unexpectedlyNo recovery for the rest of the sessionWarning, “Microphone capture stopped unexpectedly” or “Desktop capture stopped unexpectedly”
Microphone peak read throwsCycle publishes nothing, no state changeDebug only, “Failed to read mic peak level”
Desktop peak read throwsThat endpoint treated as silent for the cycle; the cycle still proceeds on the microphone peakDebug only, “Failed to read desktop peak level”
Buffer write throwsThose bytes are lost, capture continuesDebug only, “Speech buffer write error” or “Desktop speech buffer write error”
Transcription throwsNo transcript event for that utterance; the utterance’s other events are still publishedDebug only, “Transcription failed”
Base64 encoding of a buffer throwsThe event publishes the literal string inactive as its content instead of audioDebug only, “Failed to encode speech buffer”
Transcription enabled but the processor was disposed by a previous capture endTranscript events stop, with no error and no logNone
Unhandled exception in a capture cycleSDK logs it and the capture loop continuesError, “Error in module”

Five of these paths are visible only when DebugLogs is enabled. With it off, lost audio bytes, failed transcriptions and failed peak reads leave no trace, so a session with missing utterances cannot be diagnosed after the fact unless DebugLogs was on while it ran.

12. Units and conventions

ConceptConvention
Peak leveldBFS, 20 * log10(linear peak), 0 dBFS is full scale. Internal only, never published.
SilenceThresholdDbdBFS, compared with strict greater-than, so a peak exactly at the threshold counts as silent
SilenceGapMsMilliseconds, but only observable at multiples of the SDK Interval
Audio payloadRaw interleaved PCM as delivered by WASAPI, base64-encoded, no container and no header beyond the content-type prefix
Transcription audio16000 Hz, mono, 16-bit PCM in a WAV container, produced only for inference and never published
Time baseHost wall clock, UTC Unix milliseconds, sampled once per capture cycle. No device or stream timestamp is carried, so an event’s timestamp is the poll that observed the transition, not the instant the sound occurred.
Utterance durationNot published. It can only be inferred from the interval between the start and end events, which is biased by the polling cadence and by SilenceGapMs.

13. Configuration affecting the interfaces

Settings live under the HEAT.MicrophoneInput section.

SettingDefaultInterface effect
CaptureModeMicOnlyMicPlusDesktopSeparated adds IF-CMMIC-03, brings the render endpoint into the activity decision, and prefers the render endpoint for process attribution. Read per cycle for the activity decision, but the endpoint itself is resolved only at initialisation.
DeviceIdnullSelects the capture endpoint by WASAPI device id; empty or null uses the OS default. Read once, at initialisation.
EnableAudioInCommsfalseOpens IF-CMMIC-02 and publishes the audio CommsEvents. Off means no audio is buffered or published.
SilenceThresholdDb-60The activity threshold in IF-CMMIC-01
SilenceGapMs500Silence required before an utterance closes, subject to the cadence limit in 9.1
EnableTranscriptionfalseOpens IF-CMMIC-02 and enables IF-CMMIC-05, including the model prerequisite that can fail initialisation
ModelPathmodels/ggml-base.binThe model file for IF-CMMIC-05
LanguageautoTranscription language hint passed to Whisper
DebugLogsfalseWhether the debug-only rows in section 11 are recorded

The generic SDK settings apply as documented in Manage capture modules. Interval is the only one that changes interface behaviour: it sets the metering cadence and the effective resolution of the silence gap.

The module’s own README also documents a PublishRmsLevel setting and a MicrophoneLevel reading on a BiometricSensorComponent. Neither exists in this revision, and no such reading is published under any configuration.

14. Compatibility and versioning

There is no version negotiation on any interface. The module depends on WASAPI through NAudio 2.2.1, on the SDK 2.0.5 publication contract, and on Whisper.net 1.7.2 with a GGML model file whose format is defined by whisper.cpp. A GGML format change that Whisper.net 1.7.2 cannot read surfaces as a failed initialisation, not as a degraded session.

The audio format is not part of this contract. It is whatever the endpoint provides, so the same configuration on two workstations can publish different sample rates and bit depths. Consumers must read the format parameters from the event content.

The published message types are stable enum values in the shared schema. The reuse of COMMS_TYPE_TTS for transcripts is a property of this module, not of the schema, so a consumer filtering on COMMS_TYPE_VOICE alone will not see transcripts.

15. Security considerations

Audio and transcripts are personal data. This module can record what a person said and a machine-readable transcript of it, tagged with the process they were speaking to and, in MicPlusDesktopSeparated mode, the audio of everyone else on the call coming out of the speakers. Treat both as personal data subject to the deployment’s own data-protection obligations, and obtain consent from every participant, not only the workstation user, before enabling either setting.

PropertyValue
AuthenticationNone on any interface. Local endpoint access with the host process’s own permissions.
Encryption in transitNone locally. Audio and transcripts travel in plaintext inside the process and inside the published frame; the transport that carries the frame onward is the host’s, not this module’s.
secure fieldSet from exclusive-mode state on the transcript event only. It does not indicate an encrypted channel and, as noted in 5.3, cannot be true in this revision. Do not use it as a confidentiality signal.
Audio at rest in the moduleHeld in unencrypted process memory for the duration of an utterance, then cleared at close and at capture end
Model fileRead from the module’s install location. A writable install directory allows substituting the model that transcribes captured speech; protect it with filesystem permissions.
Build-time downloadOver HTTPS from a third-party host, with no checksum or signature verification in download-whisper-model.ps1. Use the cache directory with a model obtained through a trusted channel where provenance matters.
Debug view exposureBroadcasts the last transcript to authorised clients independently of what is published into captured data
Third-party attributionProcess names of other applications are attached to utterances, and in MicPlusDesktopSeparated mode other participants’ audio is captured from the speakers

The defaults are the privacy-preserving configuration: no stream is opened, and only voice-activity timing is published. Both EnableAudioInComms and EnableTranscription are policy decisions, not tuning.

16. Conformance

  1. With default settings, start a capture session and speak. Confirm start and end tracking events appear, and confirm no audio event, no transcript and no level reading are published.
  2. Confirm the log records the selected device name and id, and that it matches DeviceId when one is set.
  3. Enable EnableAudioInComms and repeat. Confirm one voice CommsEvent per utterance, and record the rate, bits and ch values from its content as the effective format for that workstation.
  4. Enable EnableTranscription and confirm the log records the loaded model path, then confirm a transcript CommsEvent with COMMS_TYPE_TTS for a spoken phrase.
  5. Stop the session, start a new one without restarting HEAT Capture, and speak again. Confirm whether transcript events still appear. Their absence is the disposal behaviour described in 8.3.
  6. Rename or remove the model file with transcription enabled and confirm initialisation fails with “Whisper model not found”, rather than starting without transcription.
  7. Open the device in exclusive mode from another application and confirm the exclusive-use warning, that tracking events continue, and that no audio or transcript is published.
  8. Speak a sentence containing a short pause. Count the start events. Until MIC-01 is fixed, expect more than one utterance for a single sentence and expect the audio of the part before the pause to be absent from the published payload.
  9. Set SilenceGapMs below the SDK Interval and confirm utterance closing is unchanged, per the cadence limit in 9.1.
  10. In MicPlusDesktopSeparated mode, play audio through the speakers with the microphone muted and confirm an utterance is opened and a src=desktop audio event is published.

17. Revision history

RevDateProduct versionChange
12026-09-02a49e344Initial issue.
Opens your browser's print dialog. Choose "Save as PDF" as the destination.