> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ai-coustics.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK quickstart

> Install the ai-coustics SDK, process audio and check the result in your preferred language.

**ai-coustics** enhances audio in your application. Choose a language to install the SDK and process a recording. The Python and Node.js examples write an enhanced WAV for comparison.

Create an SDK key on the [developer platform](https://developers.ai-coustics.com) and follow your language's `AIC_SDK_LICENSE` setup. Check runtime and platform support in the [compatibility matrix](/reference/sdk/compatibility-matrix).

Use the [shared quickstart recording](/guides/test-audio) to check the same input across languages, then substitute representative audio from your application.

Start with Quail Voice Focus for primary-speaker isolation before speech-to-text (STT). For human listening, use [Rook Multi Speaker](/models/perceptual-speech-enhancement/rook-multi-speaker). The [model reference](/reference/sdk/models) explains model loading and selection.

<Note>
  SDK key authorization and usage reporting are distinct concerns. See [authentication](/models/get-started/authenticate-apps) and [telemetry](/reference/concepts/sdk-telemetry) before deploying. Downloading a model in advance does not by itself enable offline operation.
</Note>

<Tabs>
  <Tab title="Python">
    ## Installation

    Use Python 3.11 for this example on a [supported platform](/reference/sdk/compatibility-matrix). The SDK package declares Python 3.10 or later. This example pins `aic-sdk` 3.3.0 and NumPy 2.2.6. Create a project directory and a virtual environment:

    <CodeGroup>
      ```bash macOS / Linux theme={null}
      mkdir aic-python-quickstart
      cd aic-python-quickstart
      python3 -m venv .venv
      source .venv/bin/activate
      python -m pip install "aic-sdk==3.3.0" "numpy==2.2.6"
      ```

      ```powershell Windows PowerShell theme={null}
      mkdir aic-python-quickstart
      cd aic-python-quickstart
      py -m venv .venv
      .venv\Scripts\Activate.ps1
      python -m pip install "aic-sdk==3.3.0" "numpy==2.2.6"
      ```
    </CodeGroup>

    ## Quickstart

    ### Prepare the key and input

    Generate an SDK key on the [developer platform](https://developers.ai-coustics.com). Set it in the terminal where you will run the example, replacing `YOUR_SDK_KEY` with your key. Keep it out of source control and shared terminal logs.

    <CodeGroup>
      ```bash macOS / Linux theme={null}
      export AIC_SDK_LICENSE="YOUR_SDK_KEY"
      ```

      ```powershell Windows PowerShell theme={null}
      $env:AIC_SDK_LICENSE = "YOUR_SDK_KEY"
      ```
    </CodeGroup>

    Download the [noisy speech fixture](/assets/audio/noisy-speech.wav) and save it as `input.wav` in `aic-python-quickstart`. It contains 56,080 mono PCM16 samples at 16 kHz (3.505 seconds). The [fixture guide](/guides/test-audio) includes attribution, checksums and the aligned clean reference.

    For your own recording, export a short sentence with background noise as **mono, 16 kHz, signed 16-bit PCM WAV**. Renaming a file does not convert it.

    The example downloads `quail-vf-2.2-l-16khz` into `./models`. Quail Voice Focus isolates the primary speaker for speech-to-text (STT) input. The first run needs network access for the model download and SDK key authorization. See [authentication](/models/get-started/authenticate-apps) for deployment options.

    ### Save and run

    Save this complete program as `quickstart.py` in the same directory:

    ```python quickstart.py theme={null}
    import os
    from pathlib import Path
    import sys
    import wave

    import aic_sdk as aic
    import numpy as np


    def main():
        license_key = os.environ.get("AIC_SDK_LICENSE")
        if not license_key or license_key == "YOUR_SDK_KEY":
            raise ValueError("Set AIC_SDK_LICENSE to your SDK key, then retry.")

        with wave.open("input.wav", "rb") as source:
            if (source.getnchannels(), source.getframerate(), source.getsampwidth()) != (1, 16000, 2):
                raise ValueError("Export input.wav as mono, 16 kHz, 16-bit PCM WAV, then retry.")
            frames = source.getnframes()
            raw = source.readframes(frames)
            if len(raw) != frames * 2:
                raise ValueError("input.wav is truncated. Export the complete recording, then retry.")
        audio = np.frombuffer(raw, dtype="<i2").astype(np.float32) / 32768.0
        if audio.size == 0:
            raise ValueError("input.wav is empty. Record speech, then retry.")

        model_path = aic.Model.download("quail-vf-2.2-l-16khz", Path("models"))
        model = aic.Model.from_file(model_path)
        config = aic.ProcessorConfig.optimal(model, sample_rate=16000)
        processor = aic.Processor(model, license_key)
        try:
            processor.initialize(config)
            delay = processor.get_context().get_audio_delay()
            # Pad the last block and flush the delayed tail with silence.
            padded_size = ((audio.size + delay + config.block_size - 1) // config.block_size) * config.block_size
            padded = np.zeros(padded_size, dtype=np.float32)
            padded[:audio.size] = audio
            output = np.empty_like(padded)
            for start in range(0, padded_size, config.block_size):
                end = start + config.block_size
                # Python returns a new array; the input block is unchanged.
                output[start:end] = processor.process(padded[start:end])
            enhanced = output[delay:delay + audio.size]
            if not np.isfinite(enhanced).all():
                raise ValueError("Processing returned non-finite samples; no output was written.")
            pcm = (np.clip(enhanced, -1.0, 32767 / 32768) * 32768).astype("<i2")
            with wave.open("enhanced.wav", "wb") as destination:
                destination.setnchannels(1)
                destination.setsampwidth(2)
                destination.setframerate(16000)
                destination.writeframes(pcm.tobytes())
            print(f"SDK {aic.get_sdk_version()}, model {model.get_id()}")
            print(f"Processed {audio.size} samples at 16000 Hz; wrote enhanced.wav")
        finally:
            processor.terminate_session()


    if __name__ == "__main__":
        try:
            main()
        except Exception as error:
            message = getattr(error, "message", str(error))
            print(f"{type(error).__name__}: {message}", file=sys.stderr)
            sys.exit(1)
    ```

    Run it from `aic-python-quickstart`, with the virtual environment active:

    ```bash theme={null}
    python quickstart.py
    ```

    ### Check the result

    The program prints the native SDK version, resolved model ID and processed sample count. It writes `enhanced.wav` beside `input.wav` at 16 kHz with the same sample count, compensating for [processing delay](/reference/concepts/latency).

    With the supplied fixture, the processed sample count is `56080`. Verify the file headers:

    ```bash theme={null}
    python -c "import wave; a=wave.open('input.wav'); b=wave.open('enhanced.wav'); assert a.getparams()[:4] == b.getparams()[:4]; print('PASS: matching channels, bit depth, sample rate and frame count'); a.close(); b.close()"
    ```

    Listen to both recordings and compare them with the same STT settings to evaluate quality.

    ### Recover from an error

    | Problem | Cause and retry |
    | - | - |
    | **Package installation fails** | Check `python --version` and the [compatibility matrix](/reference/sdk/compatibility-matrix). Activate the virtual environment and rerun the pinned installation. |
    | **Missing key or `LicenseFormatInvalidError`** | Set `AIC_SDK_LICENSE` in this terminal to the complete SDK key from the developer platform, then rerun `python quickstart.py`. |
    | **`LicenseExpiredError` or `ProcessingNotAllowedError`** | Check key validity, account entitlement and required connectivity in [authentication](/models/get-started/authenticate-apps). Correct the cause and restart the program with a new processor. |
    | **Missing or invalid `input.wav`** | Save a nonempty mono, 16 kHz, signed 16-bit PCM WAV in the current directory, then rerun the program. |
    | **Model download or load fails** | Check connectivity, write access to `./models` and the exact model ID. For manual model files, check the [model format version](/reference/sdk/compatibility-matrix). Retry the download after fixing the cause. |
    | **`AudioConfigMismatchError`** | Keep every call at `config.block_size` samples. The example pads the final block; do not pass a short final slice with fixed block sizes. |

    ### Adapt this example

    Use one processor per independent stream and retain it between consecutive blocks. Reset its context when seeking or starting unrelated audio. Downmix stereo input or give each channel its own processor. See [audio format](/reference/concepts/audio-format), [streams and state](/reference/concepts/streams-and-state) and the [Python binding guide](/reference/sdk/language-bindings/python#integration-behavior) for async processing and lifecycle details.

    This example reads the full file into memory. For live audio or long files, process bounded blocks and keep file I/O, model downloads and session termination outside the audio callback. For human listening, evaluate [Rook Multi Speaker](/models/perceptual-speech-enhancement/rook-multi-speaker).
  </Tab>

  <Tab title="Node.js">
    ## Installation

    Use Node.js 22 for this example. The package declares Node.js 18 or later; see the [compatibility matrix](/reference/sdk/compatibility-matrix) for native platform support. Create an isolated project and install the pinned SDK and WAV reader:

    ```bash theme={null}
    mkdir aic-node-quickstart
    cd aic-node-quickstart
    npm init -y
    npm install --save-exact @ai-coustics/aic-sdk@0.25.0 wavefile@11.0.0
    ```

    ## Quickstart

    ### Prepare the key and input

    Generate an SDK key on the [developer platform](https://developers.ai-coustics.com). Set it in the terminal where you will run the example, replacing `YOUR_SDK_KEY` with your key. Keep it out of source control and shared terminal logs.

    <CodeGroup>
      ```bash macOS / Linux theme={null}
      export AIC_SDK_LICENSE="YOUR_SDK_KEY"
      ```

      ```powershell Windows PowerShell theme={null}
      $env:AIC_SDK_LICENSE = "YOUR_SDK_KEY"
      ```
    </CodeGroup>

    Download the [noisy speech fixture](/assets/audio/noisy-speech.wav) and save it as `input.wav` in `aic-node-quickstart`. It contains 56,080 mono PCM16 samples at 16 kHz (3.505 seconds). The [fixture guide](/guides/test-audio) includes attribution, checksums and the aligned clean reference.

    For your own recording, export a short sentence with background noise as **mono, 16 kHz, signed 16-bit PCM WAV**. Renaming a file does not convert it.

    The example downloads `quail-vf-2.2-l-16khz` into `./models`. Quail Voice Focus isolates the primary speaker for speech-to-text (STT) input. The first run needs network access for the model download and SDK key authorization. See [authentication](/models/get-started/authenticate-apps) for deployment options.

    ### Save and run

    Save this complete program as `quickstart.cjs`. The `.cjs` extension makes its CommonJS module format explicit, including in projects that use `"type": "module"`.

    ```javascript quickstart.cjs theme={null}
    const fs = require("node:fs");
    const { WaveFile } = require("wavefile");
    const { Model, ProcessorAsync, getVersion } = require("@ai-coustics/aic-sdk");

    async function main() {
      const licenseKey = process.env.AIC_SDK_LICENSE;
      if (!licenseKey || licenseKey === "YOUR_SDK_KEY") {
        throw new Error("Set AIC_SDK_LICENSE to your SDK key, then retry.");
      }
      const input = new WaveFile(fs.readFileSync("input.wav"));
      if (input.fmt.numChannels !== 1 || input.fmt.sampleRate !== 16000 || input.bitDepth !== "16") {
        throw new Error("Export input.wav as mono, 16 kHz, 16-bit PCM WAV, then retry.");
      }
      if (input.data.samples.length !== input.data.chunkSize) {
        throw new Error("input.wav is truncated. Export the complete recording, then retry.");
      }
      input.toBitDepth("32f");
      const audio = input.getSamples(false, Float32Array);
      if (audio.length === 0) {
        throw new Error("input.wav is empty. Record speech, then retry.");
      }

      const modelPath = await Model.download("quail-vf-2.2-l-16khz", "./models");
      const model = Model.fromFile(modelPath);
      let processor;
      try {
        const blockSize = model.getOptimalBlockSize(16000);
        processor = new ProcessorAsync(model, licenseKey);
        await processor.initialize(16000, blockSize, false);
        const context = await processor.getContext();
        const delay = context.getAudioDelay();
        // Pad the last block and flush the delayed tail with silence.
        const paddedSize = Math.ceil((audio.length + delay) / blockSize) * blockSize;
        const padded = new Float32Array(paddedSize);
        padded.set(audio);
        const output = new Float32Array(paddedSize);
        for (let start = 0; start < paddedSize; start += blockSize) {
          // Await each block to preserve stream order. Async processing returns a new array.
          const enhanced = await processor.process(padded.subarray(start, start + blockSize));
          output.set(enhanced, start);
        }
        const enhanced = output.subarray(delay, delay + audio.length);
        if (!enhanced.every(Number.isFinite)) {
          throw new Error("Processing returned non-finite samples; no output was written.");
        }
        const pcm = Int16Array.from(enhanced, (sample) =>
          Math.trunc(Math.max(-1, Math.min(32767 / 32768, sample)) * 32768)
        );
        const result = new WaveFile();
        result.fromScratch(1, 16000, "16", pcm);
        fs.writeFileSync("enhanced.wav", result.toBuffer());
        console.log(`SDK ${getVersion()}, model ${model.getId()}`);
        console.log(`Processed ${audio.length} samples at 16000 Hz; wrote enhanced.wav`);
      } finally {
        // All processing promises above have settled before native resources are released.
        if (processor) processor.dispose();
        model.dispose();
      }
    }

    main().catch((error) => {
      console.error(`Error: ${error.message}`);
      process.exitCode = 1;
    });
    ```

    Run it from `aic-node-quickstart`:

    ```bash theme={null}
    node quickstart.cjs
    ```

    ### Check the result

    The program prints the native SDK version, resolved model ID and processed sample count. It writes `enhanced.wav` beside `input.wav` at 16 kHz with the same sample count, compensating for [processing delay](/reference/concepts/latency).

    With the supplied fixture, the processed sample count is `56080`. Verify the file headers:

    ```bash theme={null}
    node -e "const fs=require('node:fs'); const {WaveFile}=require('wavefile'); const a=new WaveFile(fs.readFileSync('input.wav')); const b=new WaveFile(fs.readFileSync('enhanced.wav')); for (const k of ['numChannels','sampleRate','bitsPerSample']) if(a.fmt[k]!==b.fmt[k]) throw Error(k+' differs'); if(a.getSamples().length!==b.getSamples().length) throw Error('sample count differs'); console.log('PASS: matching channels, bit depth, sample rate and frame count');"
    ```

    Listen to both recordings and compare them with the same STT settings to evaluate quality.

    ### Recover from an error

    | Problem | Cause and retry |
    | - | - |
    | **Native module cannot load** | Check `node --version` and your OS/architecture against the [compatibility matrix](/reference/sdk/compatibility-matrix). Install in the target environment with optional dependencies enabled, then rerun `node quickstart.cjs`. |
    | **Missing or invalid SDK key** | Set `AIC_SDK_LICENSE` in this terminal to the complete SDK key from the developer platform, then retry. |
    | **Processing is not allowed** | Check key validity, account entitlement and required connectivity in [authentication](/models/get-started/authenticate-apps). Correct the cause and restart the program with a new processor. |
    | **Missing or invalid `input.wav`** | Save a nonempty mono, 16 kHz, signed 16-bit PCM WAV in the current directory, then rerun the program. |
    | **Model download or load fails** | Check connectivity, write access to `./models` and the exact model ID. For manual model files, check the [model format version](/reference/sdk/compatibility-matrix). Retry the download after fixing the cause. |
    | **Audio configuration mismatch** | Keep every call at `blockSize` samples. The example pads the final block; do not pass a short final slice with fixed block sizes. |

    ### Adapt this example

    Use one processor per independent stream and await each operation before submitting the next block. Use separate processors for simultaneous streams; `Promise.all` over blocks from one stream can reorder processing. Downmix stereo input or give each channel its own processor. See [audio format](/reference/concepts/audio-format), [streams and state](/reference/concepts/streams-and-state) and the [Node.js binding guide](/reference/sdk/language-bindings/nodejs#integration-behavior).

    This example reads the full file into memory and uses synchronous file I/O. For live audio or long files, process bounded blocks and keep file I/O and model downloads outside the audio path. For human listening, evaluate [Rook Multi Speaker](/models/perceptual-speech-enhancement/rook-multi-speaker).
  </Tab>

  <Tab title="Rust">
    ## Installation

    Use Rust with Cargo, a native linker and libclang on a [supported platform](/reference/sdk/compatibility-matrix). The published SDK crate declares Rust 1.88 or later. Its build script generates C bindings with libclang; install your platform's compiler tools before building. See the [released linking guide](https://docs.rs/aic-sdk/0.25.0/aic_sdk/docs/linking/index.html) for local-library and deployment options.

    Create a project:

    ```bash theme={null}
    cargo new aic-rust-quickstart
    cd aic-rust-quickstart
    ```

    Replace `Cargo.toml` with the following. `download-lib` downloads the matching native library during the first build; it requires network access. The explicit `aic-sdk-sys` pin keeps the native dependency at the documented release. `hound` reads and writes WAV files.

    ```toml Cargo.toml theme={null}
    [package]
    name = "aic-rust-quickstart"
    version = "0.1.0"
    edition = "2024"

    [dependencies]
    aic-sdk = { version = "=0.25.0", features = ["download-lib"] }
    aic-sdk-sys = "=0.25.0"
    hound = "=3.5.1"
    ```

    Keep the generated `Cargo.lock` in your application to preserve resolved dependency versions.

    ## Quickstart

    ### Prepare the key, model and input

    Generate an SDK key on the [developer platform](https://developers.ai-coustics.com). Set it in the terminal where you will run the program. Keep it out of source control and shared logs.

    <CodeGroup>
      ```bash macOS / Linux theme={null}
      export AIC_SDK_LICENSE="YOUR_SDK_KEY"
      curl -fL -o model.aicmodel "https://artifacts.ai-coustics.io/models/quail-vf-2-2-l-16khz/v7/quail_vf_2_2_l_16khz_horgwub0_v14.aicmodel"
      ```

      ```powershell Windows PowerShell theme={null}
      $env:AIC_SDK_LICENSE = "YOUR_SDK_KEY"
      Invoke-WebRequest "https://artifacts.ai-coustics.io/models/quail-vf-2-2-l-16khz/v7/quail_vf_2_2_l_16khz_horgwub0_v14.aicmodel" -OutFile model.aicmodel
      ```
    </CodeGroup>

    Replace `YOUR_SDK_KEY` with your key. This pins a format-7 build of `quail-vf-2.2-l-16khz`. Quail Voice Focus isolates the primary speaker for speech-to-text (STT) input. Downloading the model in advance does not remove SDK key authorization requirements; see [authentication](/models/get-started/authenticate-apps).

    Download the [noisy speech fixture](/assets/audio/noisy-speech.wav) and save it as `input.wav` in this project directory. It contains 56,080 mono PCM16 samples at 16 kHz (3.505 seconds). The [fixture guide](/guides/test-audio) includes attribution, checksums and the aligned clean reference.

    For your own recording, export **mono, 16 kHz, signed 16-bit PCM WAV**, at most 60 seconds long. Renaming a file does not convert it.

    ### Save and run

    Replace `src/main.rs` with:

    ```rust src/main.rs theme={null}
    use aic_sdk::{Model, Processor, ProcessorConfig};
    use hound::{SampleFormat, WavReader, WavSpec, WavWriter};
    use std::error::Error;

    fn run() -> Result<(), Box<dyn Error>> {
        let key = std::env::var("AIC_SDK_LICENSE")
            .map_err(|_| "Set AIC_SDK_LICENSE to your SDK key, then retry.")?;
        if key.is_empty() || key == "YOUR_SDK_KEY" {
            return Err("Set AIC_SDK_LICENSE to your SDK key, then retry.".into());
        }
        let mut reader = WavReader::open("input.wav")?;
        let spec = reader.spec();
        if spec.channels != 1 || spec.sample_rate != 16000 || spec.bits_per_sample != 16 ||
            spec.sample_format != SampleFormat::Int || reader.duration() == 0 || reader.duration() > 960000 {
            return Err("Use a nonempty mono, 16 kHz, PCM16 input.wav, at most 60 seconds.".into());
        }
        let input: Vec<f32> = reader.samples::<i16>()
            .map(|sample| sample.map(|value| value as f32 / 32768.0))
            .collect::<Result<_, _>>()?;
        let model = Model::from_file("model.aicmodel")?;
        let config = ProcessorConfig {
            sample_rate: 16000,
            block_size: model.optimal_block_size(16000),
            variable_block_size: false,
        };
        let mut processor = Processor::new(&model, &key)?.with_config(&config)?;
        let delay = processor.context().audio_delay();
        let padded_count = input.len().checked_add(delay)
            .and_then(|n| n.checked_add(config.block_size - 1))
            .ok_or("Audio buffer size overflow")? / config.block_size * config.block_size;
        let mut output = vec![0.0_f32; padded_count];
        output[..input.len()].copy_from_slice(&input);
        for block in output.chunks_exact_mut(config.block_size) {
            processor.process(block)?;
        }
        // Flush with silence, then omit the initial delay to align the files.
        let enhanced = &output[delay..delay + input.len()];
        if enhanced.iter().any(|sample| !sample.is_finite()) {
            return Err("Non-finite output sample; no output written.".into());
        }
        let mut writer = WavWriter::create("enhanced.wav", WavSpec {
            channels: 1, sample_rate: 16000, bits_per_sample: 32, sample_format: SampleFormat::Float,
        })?;
        for &sample in enhanced { writer.write_sample(sample)?; }
        writer.finalize()?;
        println!("SDK {}, model {}", aic_sdk::get_sdk_version(), model.id());
        println!("Processed {} samples at 16000 Hz; wrote enhanced.wav", input.len());
        // Drop releases the processor and ends its session, including on an error return.
        Ok(())
    }

    fn main() {
        if let Err(error) = run() {
            eprintln!("{error}");
            std::process::exit(1);
        }
    }
    ```

    Build and run from `aic-rust-quickstart`:

    ```bash theme={null}
    cargo run --release
    ```

    The program propagates SDK and file errors with `?`. Rust drops the processor and model on a normal return or an error return from `run`, releasing native resources.

    ### Check the result

    A successful run exits with code zero, prints the native SDK version and resolved model ID, then reports `Processed N samples at 16000 Hz; wrote enhanced.wav`, where `N` is your input's sample count (`56080` for the supplied fixture). Open `enhanced.wav` in your audio editor: it should be mono, 16 kHz and the same duration as `input.wav`. The output is a **32-bit float WAV**; the input is PCM16.

    The program pads the final block, flushes the delayed tail and removes the initial [processing delay](/reference/concepts/latency), preserving the original sample count. File writes are checked; discard any incomplete file if writing fails.

    Listen to both files and compare them with the same STT settings to evaluate quality.

    ### Recover from an error

    | Problem | Cause and retry |
    | - | - |
    | **Build cannot find libclang or a linker** | Install the platform compiler tools and libclang. Set `LIBCLANG_PATH` to your libclang library directory if discovery fails, then rerun `cargo build --release`. |
    | **Native library download fails** | Check network access and target platform. For a preinstalled SDK, use `AIC_LIB_PATH` as described in the [linking guide](https://docs.rs/aic-sdk/0.25.0/aic_sdk/docs/linking/index.html). |
    | **Missing or invalid SDK key** | Set `AIC_SDK_LICENSE` in this terminal to the complete SDK key, then rerun `cargo run --release`. |
    | **Processing is not allowed or the key expired** | Check key validity, entitlement and [required connectivity](/models/get-started/authenticate-apps). Correct the cause and restart the program. |
    | **Input file rejected** | Save a nonempty mono, 16 kHz, PCM16 WAV of at most 60 seconds as `input.wav`, then retry. |
    | **Model loading fails** | Run from the project directory containing `model.aicmodel`. Re-download the exact file above for SDK 0.25.0. |
    | **Audio configuration mismatch** | Keep each `process` call at `config.block_size` samples. The example pads the final block before processing. |
    | **Output write fails** | Check free space and write permission. Discard the incomplete output, correct the cause and retry. |

    ### Adapt this example

    Keep one processor per stream and process blocks in order. Reset its context on a discontinuity or before unrelated audio. Downmix stereo or use a processor per channel. See [audio format](/reference/concepts/audio-format) and [streams and state](/reference/concepts/streams-and-state).

    The helper reads a short recording into memory. For long files and live audio, use bounded buffers and keep file I/O, model loading and session teardown outside the audio callback. For human-listening enhancement, evaluate [Rook Multi Speaker](/models/perceptual-speech-enhancement/rook-multi-speaker).
  </Tab>

  <Tab title="WebAssembly">
    ## Installation

    Use `@ai-coustics/aic-sdk-wasm` **0.25.0**, Node.js 22 or later for the local token server and a browser with WebAssembly SIMD support. This example processes a local recording into a mono WAV; it does not use an AudioWorklet or microphone pipeline.

    Download the [noisy speech fixture](/guides/test-audio), or use a short recording you have permission to process, exported as mono PCM16 WAV at 16 kHz. The example accepts up to 60 seconds. Create an SDK key in the [developer platform](https://developers.ai-coustics.com) and put it in the server's `AIC_SDK_LICENSE` environment variable. Do not put the key into HTML, browser storage or client configuration.

    ```bash theme={null}
    mkdir aic-wasm-example
    cd aic-wasm-example
    npm init -y
    npm install --save-exact @ai-coustics/aic-sdk-wasm@0.25.0
    curl --fail --location \
      'https://artifacts.ai-coustics.io/models/quail-vf-2-2-s-16khz/v7/quail_vf_2_2_s_16khz_gf70x7zf_v14.aicmodel' \
      --output model.aicmodel
    ```

    Set the server credential in this terminal without putting it in shell history:

    ```bash theme={null}
    read -r -s AIC_SDK_LICENSE
    export AIC_SDK_LICENSE
    ```

    Paste your SDK key at the hidden prompt and press Enter. The key remains on the server; the browser receives a short-lived JSON Web Token (JWT).

    ## Quickstart

    ### Create the local token server

    Save the following as `server.mjs`. This server binds to loopback and serves only the listed files. The token endpoint checks the request origin and returns no cached credentials. It decodes the SDK key on the server using the [documented token flow](/models/get-started/authenticate-apps).

    <Warning>
      This is a local development server. Before production, replace it with an HTTPS backend that authenticates users, authorizes SDK access and enforces rate and usage limits. Never expose this demo server on a public interface.
    </Warning>

    ```javascript server.mjs theme={null}
    import http from 'node:http';
    import { readFile } from 'node:fs/promises';

    const origin = 'http://127.0.0.1:4173';
    const sdkKey = process.env.AIC_SDK_LICENSE;
    if (!sdkKey) throw new Error('Set AIC_SDK_LICENSE in the server environment.');
    const files = new Map([
      ['/', ['index.html', 'text/html']],
      ['/sdk.js', ['node_modules/@ai-coustics/aic-sdk-wasm/aic_sdk_wasm.js', 'text/javascript']],
      ['/aic_sdk_wasm_bg.wasm', ['node_modules/@ai-coustics/aic-sdk-wasm/aic_sdk_wasm_bg.wasm', 'application/wasm']],
      ['/model.aicmodel', ['model.aicmodel', 'application/octet-stream']],
    ]);
    const server = http.createServer(async (req, res) => {
      res.setHeader('Cache-Control', 'no-store');
      res.setHeader('X-Content-Type-Options', 'nosniff');
      if (req.headers.host !== '127.0.0.1:4173') {
        res.writeHead(403).end('Use the loopback URL printed by the server.');
        return;
      }
      if (req.url === '/session-token' && req.method === 'POST') {
        // This is a local-only demo. Production needs authenticated users and quotas.
        if (req.headers.origin !== origin || req.headers['x-aic-demo'] !== '1') {
          res.writeHead(403).end('Same-origin demo request required.');
          return;
        }
        try {
          const payload = JSON.parse(Buffer.from(sdkKey.split('.')[0], 'base64').toString());
          if (typeof payload.api_key !== 'string') throw new Error('Invalid SDK key');
          const basic = Buffer.from(`${payload.api_key}:`).toString('base64');
          const response = await fetch('https://api.ai-coustics.io/v1/sdk/tokens', {
            method: 'POST', headers: { Authorization: `Basic ${basic}` },
            signal: AbortSignal.timeout(10000),
          });
          if (!response.ok) throw new Error('Token request rejected');
          const { token } = await response.json();
          if (typeof token !== 'string') throw new Error('Missing token');
          const { exp } = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString());
          if (!Number.isFinite(exp) || exp * 1000 <= Date.now() + 120000) {
            throw new Error('Token lifetime is too short for this demo');
          }
          res.writeHead(200, { 'Content-Type': 'application/json' });
          res.end(JSON.stringify({ token }));
        } catch {
          // Do not log credentials or return upstream response bodies.
          res.writeHead(502).end('Token request failed. Check the server key and connectivity.');
        }
        return;
      }
      const file = files.get(req.url);
      if (req.method !== 'GET' || !file) {
        res.writeHead(404).end('Not found');
        return;
      }
      try {
        const bytes = await readFile(new URL(file[0], import.meta.url));
        res.writeHead(200, { 'Content-Type': file[1] }).end(bytes);
      } catch {
        res.writeHead(404).end('File missing. Check installation and model download.');
      }
    });
    server.listen(4173, '127.0.0.1', () => console.log(`Open ${origin}`));
    ```

    ### Create the browser page

    Save this as `index.html` beside `server.mjs`. Audio stays in browser memory. The page loads the WASM module and model, requests a token, processes complete frames, pads the tail and removes the reported delay before creating the output file. Session authorization and usage reporting still require network access.

    ```html index.html theme={null}
    <!doctype html>
    <html lang="en">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ai-coustics WASM file example</title>
    <h1>Enhance a short recording</h1>
    <p>Select a mono, 16 kHz, PCM16 WAV with speech (up to 60 seconds).</p>
    <label>Recording <input id="input" type="file" accept=".wav"></label>
    <button id="run">Enhance</button>
    <p id="status" role="status">Ready.</p>
    <a id="download" hidden download="enhanced.wav">Download enhanced.wav</a>
    <script type="module">
    import init, { Model, Processor, getVersion } from '/sdk.js';
    const input = document.querySelector('#input');
    const run = document.querySelector('#run');
    const status = document.querySelector('#status');
    const download = document.querySelector('#download');
    let outputUrl;

    function readWav(bytes) {
      const view = new DataView(bytes);
      const text = (offset, length) => String.fromCharCode(...new Uint8Array(bytes, offset, length));
      if (bytes.byteLength < 44 || text(0, 4) !== 'RIFF' || text(8, 4) !== 'WAVE') {
        throw new Error('Select a RIFF WAV file.');
      }
      let format, data;
      for (let pos = 12; pos + 8 <= bytes.byteLength;) {
        const size = view.getUint32(pos + 4, true), start = pos + 8;
        if (start + size > bytes.byteLength) throw new Error('Truncated WAV chunk.');
        if (text(pos, 4) === 'fmt ' && size >= 16) {
          format = [view.getUint16(start, true), view.getUint16(start + 2, true),
            view.getUint32(start + 4, true), view.getUint16(start + 14, true)];
        }
        if (text(pos, 4) === 'data') data = [start, size];
        pos = start + size + (size % 2);
      }
      if (String(format) !== '1,1,16000,16' || !data || data[1] % 2) {
        throw new Error('Export mono PCM16 WAV at 16 kHz, then retry.');
      }
      const samples = new Float32Array(data[1] / 2);
      if (!samples.length || samples.length > 16000 * 60) {
        throw new Error('Use a nonempty recording of up to 60 seconds.');
      }
      for (let i = 0; i < samples.length; i++) samples[i] = view.getInt16(data[0] + i * 2, true) / 32768;
      return samples;
    }

    function wavBlob(samples) {
      const bytes = new ArrayBuffer(44 + samples.length * 2), view = new DataView(bytes);
      const text = (offset, value) => [...value].forEach((c, i) => view.setUint8(offset + i, c.charCodeAt(0)));
      text(0, 'RIFF'); view.setUint32(4, bytes.byteLength - 8, true); text(8, 'WAVE');
      text(12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true);
      view.setUint16(22, 1, true); view.setUint32(24, 16000, true); view.setUint32(28, 32000, true);
      view.setUint16(32, 2, true); view.setUint16(34, 16, true); text(36, 'data');
      view.setUint32(40, samples.length * 2, true);
      samples.forEach((x, i) => view.setInt16(44 + i * 2, Math.round(Math.max(-1, Math.min(32767 / 32768, x)) * 32768), true));
      return new Blob([bytes], { type: 'audio/wav' });
    }

    run.onclick = async () => {
      let model, processor, context;
      run.disabled = true; download.hidden = true;
      if (outputUrl) { URL.revokeObjectURL(outputUrl); outputUrl = undefined; }
      try {
        if (!input.files.length) throw new Error('Select a recording first.');
        if (input.files[0].size > 3_000_000) throw new Error('Select a WAV file smaller than 3 MB.');
        const samples = readWav(await input.files[0].arrayBuffer());
        status.textContent = 'Loading the WASM module and model...';
        await init();
        const response = await fetch('/model.aicmodel', { signal: AbortSignal.timeout(30000) });
        if (!response.ok) throw new Error('Model unavailable. Check the model download.');
        model = Model.fromBytes(new Uint8Array(await response.arrayBuffer()));
        const tokenResponse = await fetch('/session-token', {
          method: 'POST', headers: { 'X-Aic-Demo': '1' }, signal: AbortSignal.timeout(15000),
        });
        if (!tokenResponse.ok) throw new Error('Token unavailable. Check the server key and connectivity.');
        let { token } = await tokenResponse.json();
        processor = new Processor(model, token);
        token = undefined; // No URL, storage, console or source-code credential.
        const size = model.getOptimalBlockSize(16000);
        processor.initialize(16000, size, false);
        context = processor.getContext();
        const delay = context.getAudioDelay();
        const count = Math.ceil((samples.length + delay) / size);
        const processed = new Float32Array(count * size);
        const deadline = performance.now() + 60000;
        status.textContent = 'Processing...';
        for (let block = 0; block < count; block++) {
          if (performance.now() > deadline) throw new Error('Processing timed out. Use a shorter recording.');
          const frame = new Float32Array(size), offset = block * size;
          frame.set(samples.subarray(offset, offset + size));
          processor.process(frame);
          processed.set(frame, offset);
          // Allow browser I/O and UI tasks to run between synchronous calls.
          if (block % 10 === 0) await new Promise(resolve => setTimeout(resolve, 0));
        }
        const output = processed.slice(delay, delay + samples.length);
        if (!output.every(Number.isFinite)) throw new Error('Non-finite output; stop and check the model.');
        outputUrl = URL.createObjectURL(wavBlob(output));
        download.href = outputUrl; download.hidden = false;
        status.textContent = `Processed ${output.length} samples at 16000 Hz with SDK ${getVersion()}.`;
      } catch (error) {
        status.textContent = `Stopped: ${error.message.replace(/[.!?]+$/, '')}. Correct the problem and select Enhance to retry.`;
      } finally {
        context?.free();
        if (processor) { processor.terminateSession(); processor.free(); }
        model?.free(); run.disabled = false;
      }
    };
    </script>
    </html>
    ```

    ### Run and check the result

    ```bash theme={null}
    node server.mjs
    ```

    Open `http://127.0.0.1:4173`, select your recording and choose **Enhance**. On success, the page displays the number of processed samples and SDK version and exposes **Download enhanced.wav**. The output has the same sample count as the input, one channel and a 16 kHz sample rate. Compare both files to evaluate quality for your task.

    Each recording uses a fresh token with at least two minutes of remaining validity. Processing stops after one minute of wall time. These are demo bounds, not SDK limits. Stop the server with Ctrl+C, then run `unset AIC_SDK_LICENSE`.

    ## Recover from an error

    | Problem | Cause and retry |
    | - | - |
    | Server asks for `AIC_SDK_LICENSE` | Set the key in the server's terminal and restart it. |
    | Module or model file is missing | Run the installation/download commands in the same directory as `server.mjs`. |
    | Browser cannot compile WASM | Use a browser with WebAssembly SIMD support and verify that the `.wasm` response is served as `application/wasm`. |
    | Token unavailable | Check the server key, token-service connectivity and whether the credential can mint a JWT. Do not copy a long-lived key into the browser as a workaround. |
    | `ProcessingNotAllowed` | Check activation connectivity, credential expiry and authorization. Correct the cause and retry with a fresh token. Do not treat a stopped or bypassed run as enhancement. |
    | Invalid WAV or empty input | Export nonempty mono PCM16 audio at 16 kHz and select the corrected file. |
    | Timeout | Try a shorter recording and measure your target device's processing budget before a live integration. |

    ## Adapt this example

    Use one processor per independent audio stream. `Processor.process()` mutates its `Float32Array` in place and runs synchronously. Move sustained processing off the main UI thread and design buffering around the [audio format](/reference/concepts/audio-format) and [latency](/reference/concepts/latency) contracts.

    For a long-running stream, refresh a JWT through your authenticated backend before expiry and update the existing context:

    ```javascript theme={null}
    // context came from processor.getContext().
    // freshToken came from your authenticated backend.
    context.updateBearerToken(freshToken);
    ```

    Both the original and replacement credentials must be JWTs. A successful local update does not prove backend acceptance; handle subsequent processing errors. Keep only the current short-lived token in memory. Never log it or save it to local storage. At shutdown, free the context and model, terminate the session and free the processor as shown above.
  </Tab>

  <Tab title="C++">
    ## Installation

    Use a C++11 compiler, Git and CMake 3.24 or later on a [supported platform](/reference/sdk/compatibility-matrix). On Windows, use a Visual Studio developer terminal with the C++ build tools installed. Create an empty project directory:

    ```bash theme={null}
    mkdir aic-cpp-quickstart
    cd aic-cpp-quickstart
    ```

    The CMake project below pins the C++ wrapper to 0.25.0. On the first configure, it downloads the matching native C SDK and checks its checksum. The example links the native SDK statically; network access is needed during configuration.

    ## Quickstart

    ### Prepare the key, model and input

    Generate an SDK key on the [developer platform](https://developers.ai-coustics.com). Set it in the terminal where you will run the program. Keep it out of source control and shared logs.

    <CodeGroup>
      ```bash macOS / Linux theme={null}
      export AIC_SDK_LICENSE="YOUR_SDK_KEY"
      curl -fL -o model.aicmodel "https://artifacts.ai-coustics.io/models/quail-vf-2-2-l-16khz/v7/quail_vf_2_2_l_16khz_horgwub0_v14.aicmodel"
      ```

      ```powershell Windows PowerShell theme={null}
      $env:AIC_SDK_LICENSE = "YOUR_SDK_KEY"
      Invoke-WebRequest "https://artifacts.ai-coustics.io/models/quail-vf-2-2-l-16khz/v7/quail_vf_2_2_l_16khz_horgwub0_v14.aicmodel" -OutFile model.aicmodel
      ```
    </CodeGroup>

    Replace `YOUR_SDK_KEY` with your key. This pins a format-7 build of `quail-vf-2.2-l-16khz`. Quail Voice Focus isolates the primary speaker for speech-to-text (STT) input. Downloading the model in advance does not remove SDK key authorization requirements; see [authentication](/models/get-started/authenticate-apps).

    Download the [noisy speech fixture](/assets/audio/noisy-speech.wav) and save it as `input.wav` in this project directory. It contains 56,080 mono PCM16 samples at 16 kHz (3.505 seconds). The [fixture guide](/guides/test-audio) includes attribution, checksums and the aligned clean reference.

    For your own recording, export **mono, 16 kHz, signed 16-bit PCM WAV**, at most 60 seconds long. Renaming a file does not convert it.

    ### Add WAV file handling

    The example uses [dr\_wav 0.14.4](https://github.com/mackron/dr_libs/tree/86cc48cbfd981fa00ea94905ac9d6df4b18d4e59) for file I/O. Download its pinned single header into the project directory:

    <CodeGroup>
      ```bash macOS / Linux theme={null}
      curl -fL -o dr_wav.h "https://raw.githubusercontent.com/mackron/dr_libs/86cc48cbfd981fa00ea94905ac9d6df4b18d4e59/dr_wav.h"
      ```

      ```powershell Windows PowerShell theme={null}
      Invoke-WebRequest "https://raw.githubusercontent.com/mackron/dr_libs/86cc48cbfd981fa00ea94905ac9d6df4b18d4e59/dr_wav.h" -OutFile dr_wav.h
      ```
    </CodeGroup>

    Save this helper as `audio_file.h` alongside `dr_wav.h`. It checks input format and length, reads float samples and writes a 32-bit float WAV.

    ```c audio_file.h theme={null}
    #ifndef AUDIO_FILE_H
    #define AUDIO_FILE_H
    #define DR_WAV_IMPLEMENTATION
    #include "dr_wav.h"
    #include <math.h>
    #include <stdio.h>
    #include <stdlib.h>

    /* A short-file helper for this tutorial, independent of the SDK. */
    static float* read_input(size_t* count) {
        drwav wav;
        if (!drwav_init_file(&wav, "input.wav", NULL)) {
            fprintf(stderr, "Cannot open input.wav as a WAV file.\n");
            return NULL;
        }
        if (wav.channels != 1 || wav.sampleRate != 16000 ||
            wav.translatedFormatTag != DR_WAVE_FORMAT_PCM || wav.bitsPerSample != 16 ||
            wav.totalPCMFrameCount == 0 || wav.totalPCMFrameCount > 960000) {
            fprintf(stderr, "Use a nonempty mono, 16 kHz, PCM16 input.wav, at most 60 seconds.\n");
            drwav_uninit(&wav);
            return NULL;
        }
        *count = (size_t)wav.totalPCMFrameCount;
        float* audio = (float*)malloc(*count * sizeof(float));
        if (!audio || drwav_read_pcm_frames_f32(&wav, *count, audio) != *count) {
            fprintf(stderr, "Cannot read all input samples.\n");
            free(audio);
            audio = NULL;
        }
        drwav_uninit(&wav);
        return audio;
    }

    static int write_output(const float* audio, size_t count) {
        drwav wav;
        drwav_data_format format = {drwav_container_riff, DR_WAVE_FORMAT_IEEE_FLOAT, 1, 16000, 32};
        for (size_t i = 0; i < count; ++i) {
            if (!isfinite(audio[i])) {
                fprintf(stderr, "Non-finite output sample; no output written.\n");
                return 0;
            }
        }
        if (!drwav_init_file_write(&wav, "enhanced.wav", &format, NULL)) {
            fprintf(stderr, "Cannot create enhanced.wav.\n");
            return 0;
        }
        drwav_uint64 written = drwav_write_pcm_frames(&wav, count, audio);
        drwav_result closed = drwav_uninit(&wav);
        if (written != count || closed != DRWAV_SUCCESS) {
            fprintf(stderr, "Cannot finish enhanced.wav; discard the incomplete file.\n");
            return 0;
        }
        return 1;
    }
    #endif
    ```

    ### Save the program

    Save as `quickstart.cpp`:

    ```cpp quickstart.cpp theme={null}
    #include "aic.hpp"
    #include "audio_file.h"
    #include <algorithm>
    #include <cstring>
    #include <iostream>
    #include <limits>
    #include <memory>
    #include <stdexcept>
    #include <string>
    #include <vector>

    static void check(aic::ErrorCode error, const char* operation) {
        if (error != aic::ErrorCode::Success) {
            throw std::runtime_error(std::string(operation) + " failed (SDK error " +
                                     std::to_string(static_cast<int>(error)) + ").");
        }
    }

    int main() {
        try {
            const char* key = std::getenv("AIC_SDK_LICENSE");
            if (!key || !*key || std::strcmp(key, "YOUR_SDK_KEY") == 0)
                throw std::runtime_error("Set AIC_SDK_LICENSE to your SDK key, then retry.");
            size_t count = 0;
            std::unique_ptr<float, decltype(&std::free)> input(read_input(&count), &std::free);
            if (!input) return 1;
            auto model_result = aic::Model::create_from_file("model.aicmodel");
            check(model_result.error, "Model loading");
            auto model = model_result.take();
            auto processor_result = aic::Processor::create(model, key);
            check(processor_result.error, "Processor creation");
            auto processor = processor_result.take();
            const size_t block_size = model.get_optimal_block_size(16000);
            check(processor.initialize(16000, block_size, false), "Initialization");
            auto context_result = processor.create_context();
            check(context_result.error, "Context creation");
            auto context = context_result.take();
            const size_t delay = context.get_audio_delay();
            const size_t limit = std::numeric_limits<size_t>::max();
            if (block_size == 0 || count > limit - delay || count + delay > limit - (block_size - 1))
                throw std::runtime_error("Audio buffer size overflow.");
            const size_t padded_count = ((count + delay + block_size - 1) / block_size) * block_size;
            std::vector<float> output(padded_count, 0.0f);
            std::copy(input.get(), input.get() + count, output.begin());
            for (size_t start = 0; start < padded_count; start += block_size)
                check(processor.process(output.data() + start, block_size), "Processing");
            // Flush with silence, then omit the initial delay to align the files.
            if (!write_output(output.data() + delay, count)) return 1;
            std::cout << "SDK " << aic::get_sdk_version() << ", model " << model.get_id() << '\n';
            std::cout << "Processed " << count << " samples at 16000 Hz; wrote enhanced.wav\n";
            // Destructors release the context, processor and model on success or error.
            return 0;
        } catch (const std::exception& error) {
            std::cerr << error.what() << '\n';
            return 1;
        }
    }
    ```

    ### Build and run

    Save this complete project as `CMakeLists.txt`:

    ```cmake CMakeLists.txt theme={null}
    cmake_minimum_required(VERSION 3.24)
    project(aic_cpp_quickstart LANGUAGES C CXX)
    include(FetchContent)
    set(AIC_SDK_ALLOW_DOWNLOAD ON CACHE BOOL "Download the matching C SDK")
    FetchContent_Declare(aic_sdk
      GIT_REPOSITORY https://github.com/ai-coustics/aic-sdk-cpp.git
      GIT_TAG 0.25.0
      GIT_SHALLOW TRUE)
    FetchContent_MakeAvailable(aic_sdk)
    add_executable(quickstart quickstart.cpp)
    target_compile_features(quickstart PRIVATE cxx_std_11)
    target_link_libraries(quickstart PRIVATE aic-sdk)
    ```

    Configure and build from `aic-cpp-quickstart`:

    ```bash theme={null}
    cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
    cmake --build build --config Release
    ```

    Run from the project directory so the program can find `input.wav` and `model.aicmodel`:

    <CodeGroup>
      ```bash macOS / Linux theme={null}
      ./build/quickstart
      ```

      ```powershell Windows PowerShell theme={null}
      .\build\Release\quickstart.exe
      ```
    </CodeGroup>

    ### Check the result

    A successful run exits with code zero, prints the native SDK version and resolved model ID, then reports `Processed N samples at 16000 Hz; wrote enhanced.wav`, where `N` is your input's sample count (`56080` for the supplied fixture). Open `enhanced.wav` in your audio editor: it should be mono, 16 kHz and the same duration as `input.wav`. The output is a **32-bit float WAV**; the input is PCM16.

    The program pads the final block, flushes the delayed tail and removes the initial [processing delay](/reference/concepts/latency), preserving the original sample count. File writes are checked; discard any incomplete file if writing fails.

    Listen to both files and compare them with the same STT settings to evaluate quality.

    ### Recover from an error

    | Problem | Cause and retry |
    | - | - |
    | **Compiler, SDK header or library missing** | Install the compiler and check the selected archive or CMake download. Use the same target architecture for compiler and SDK. Rerun configure and build. |
    | **Missing SDK key or SDK error 50** | Set `AIC_SDK_LICENSE` in this terminal to the complete SDK key, then run the program again. Error 50 is an invalid license format. |
    | **SDK error 6 or 52** | Error 6 means processing is not allowed; error 52 means the license expired. Check account entitlement, key validity and [required connectivity](/models/get-started/authenticate-apps), then restart with a new processor. |
    | **Input file rejected** | Save a nonempty mono, 16 kHz, PCM16 WAV of at most 60 seconds as `input.wav`, then retry. |
    | **Model loading fails** | Keep `model.aicmodel` in the current directory. Re-download the exact compatible file above; error 100 means an invalid model, 101 an unsupported model version and 103 a filesystem error. |
    | **SDK error 5** | The block size differs from initialization. Keep every call at the configured size and pad the final block as shown. |
    | **Output write fails** | Check free space and write permission. Discard the incomplete `enhanced.wav`, fix the cause and retry. |

    The helper `check` turns a failed `ErrorCode` into an exception for this application. The SDK wrapper itself returns error codes and `Result<T>` values: calling `.take()` without checking `.error` or `.ok()` does not throw.

    ### Adapt this example

    Keep one processor per stream and process blocks in order. Reset its context on a discontinuity or before unrelated audio. Downmix stereo or use a processor per channel. See [audio format](/reference/concepts/audio-format) and [streams and state](/reference/concepts/streams-and-state).

    The helper reads a short recording into memory. For long files and live audio, use bounded buffers and keep file I/O, model loading and session teardown outside the audio callback. For human-listening enhancement, evaluate [Rook Multi Speaker](/models/perceptual-speech-enhancement/rook-multi-speaker).
  </Tab>

  <Tab title="C">
    ## Installation

    Use a C11 compiler, CMake 3.24 or later and a supported macOS, Linux or Windows platform. This tutorial selects the GNU libc Linux archive; see the [compatibility matrix](/reference/sdk/compatibility-matrix). Release 0.25.0 adds musl archives for `x86_64` and `aarch64`. They ship `lib/libaic.a` only, with no musl `libaic.so`, so link the archive statically with a musl-native toolchain such as GCC on Alpine rather than following the shared-library CMake project below. On Windows, run build commands in a Visual Studio developer terminal with the C++ build tools installed.

    Create an empty project directory:

    ```bash theme={null}
    mkdir aic-c-quickstart
    cd aic-c-quickstart
    ```

    Download and extract the native SDK 0.25.0 archive. These POSIX commands select Apple Silicon, Intel Mac, Linux ARM64 or Linux x86-64 from the host architecture:

    <CodeGroup>
      ```bash macOS / Linux theme={null}
      case "$(uname -s)-$(uname -m)" in
        Darwin-arm64) AIC_TARGET=aarch64-apple-darwin ;;
        Darwin-x86_64) AIC_TARGET=x86_64-apple-darwin ;;
        Linux-aarch64) AIC_TARGET=aarch64-unknown-linux-gnu ;;
        Linux-x86_64) AIC_TARGET=x86_64-unknown-linux-gnu ;;
        *) echo "Select a supported SDK archive from the releases page"; exit 1 ;;
      esac
      curl -fL -o sdk.tar.gz "https://github.com/ai-coustics/aic-sdk-c/releases/download/0.25.0/aic-sdk-${AIC_TARGET}-0.25.0.tar.gz"
      mkdir sdk
      tar -xzf sdk.tar.gz -C sdk
      ```

      ```powershell Windows PowerShell theme={null}
      # For an ARM64 build, replace x86_64 with aarch64.
      $AicTarget = "x86_64-pc-windows-msvc"
      Invoke-WebRequest "https://github.com/ai-coustics/aic-sdk-c/releases/download/0.25.0/aic-sdk-$AicTarget-0.25.0.zip" -OutFile sdk.zip
      Expand-Archive sdk.zip -DestinationPath sdk
      ```
    </CodeGroup>

    The project must now contain `sdk/include/aic.h` and `sdk/lib/`. Choose the archive for your compiler's target architecture when cross-compiling. [Release 0.25.0](https://github.com/ai-coustics/aic-sdk-c/releases/tag/0.25.0) includes all distributions.

    ## Quickstart

    ### Prepare the key, model and input

    Generate an SDK key on the [developer platform](https://developers.ai-coustics.com). Set it in the terminal where you will run the program. Keep it out of source control and shared logs.

    <CodeGroup>
      ```bash macOS / Linux theme={null}
      export AIC_SDK_LICENSE="YOUR_SDK_KEY"
      curl -fL -o model.aicmodel "https://artifacts.ai-coustics.io/models/quail-vf-2-2-l-16khz/v7/quail_vf_2_2_l_16khz_horgwub0_v14.aicmodel"
      ```

      ```powershell Windows PowerShell theme={null}
      $env:AIC_SDK_LICENSE = "YOUR_SDK_KEY"
      Invoke-WebRequest "https://artifacts.ai-coustics.io/models/quail-vf-2-2-l-16khz/v7/quail_vf_2_2_l_16khz_horgwub0_v14.aicmodel" -OutFile model.aicmodel
      ```
    </CodeGroup>

    Replace `YOUR_SDK_KEY` with your key. This pins a format-7 build of `quail-vf-2.2-l-16khz`. Quail Voice Focus isolates the primary speaker for speech-to-text (STT) input. Downloading the model in advance does not remove SDK key authorization requirements; see [authentication](/models/get-started/authenticate-apps).

    Download the [noisy speech fixture](/assets/audio/noisy-speech.wav) and save it as `input.wav` in this project directory. It contains 56,080 mono PCM16 samples at 16 kHz (3.505 seconds). The [fixture guide](/guides/test-audio) includes attribution, checksums and the aligned clean reference.

    For your own recording, export **mono, 16 kHz, signed 16-bit PCM WAV**, at most 60 seconds long. Renaming a file does not convert it.

    ### Add WAV file handling

    The example uses [dr\_wav 0.14.4](https://github.com/mackron/dr_libs/tree/86cc48cbfd981fa00ea94905ac9d6df4b18d4e59) for file I/O. Download its pinned single header into the project directory:

    <CodeGroup>
      ```bash macOS / Linux theme={null}
      curl -fL -o dr_wav.h "https://raw.githubusercontent.com/mackron/dr_libs/86cc48cbfd981fa00ea94905ac9d6df4b18d4e59/dr_wav.h"
      ```

      ```powershell Windows PowerShell theme={null}
      Invoke-WebRequest "https://raw.githubusercontent.com/mackron/dr_libs/86cc48cbfd981fa00ea94905ac9d6df4b18d4e59/dr_wav.h" -OutFile dr_wav.h
      ```
    </CodeGroup>

    Save this helper as `audio_file.h` alongside `dr_wav.h`. It checks input format and length, reads float samples and writes a 32-bit float WAV.

    ```c audio_file.h theme={null}
    #ifndef AUDIO_FILE_H
    #define AUDIO_FILE_H
    #define DR_WAV_IMPLEMENTATION
    #include "dr_wav.h"
    #include <math.h>
    #include <stdio.h>
    #include <stdlib.h>

    /* A short-file helper for this tutorial, independent of the SDK. */
    static float* read_input(size_t* count) {
        drwav wav;
        if (!drwav_init_file(&wav, "input.wav", NULL)) {
            fprintf(stderr, "Cannot open input.wav as a WAV file.\n");
            return NULL;
        }
        if (wav.channels != 1 || wav.sampleRate != 16000 ||
            wav.translatedFormatTag != DR_WAVE_FORMAT_PCM || wav.bitsPerSample != 16 ||
            wav.totalPCMFrameCount == 0 || wav.totalPCMFrameCount > 960000) {
            fprintf(stderr, "Use a nonempty mono, 16 kHz, PCM16 input.wav, at most 60 seconds.\n");
            drwav_uninit(&wav);
            return NULL;
        }
        *count = (size_t)wav.totalPCMFrameCount;
        float* audio = (float*)malloc(*count * sizeof(float));
        if (!audio || drwav_read_pcm_frames_f32(&wav, *count, audio) != *count) {
            fprintf(stderr, "Cannot read all input samples.\n");
            free(audio);
            audio = NULL;
        }
        drwav_uninit(&wav);
        return audio;
    }

    static int write_output(const float* audio, size_t count) {
        drwav wav;
        drwav_data_format format = {drwav_container_riff, DR_WAVE_FORMAT_IEEE_FLOAT, 1, 16000, 32};
        for (size_t i = 0; i < count; ++i) {
            if (!isfinite(audio[i])) {
                fprintf(stderr, "Non-finite output sample; no output written.\n");
                return 0;
            }
        }
        if (!drwav_init_file_write(&wav, "enhanced.wav", &format, NULL)) {
            fprintf(stderr, "Cannot create enhanced.wav.\n");
            return 0;
        }
        drwav_uint64 written = drwav_write_pcm_frames(&wav, count, audio);
        drwav_result closed = drwav_uninit(&wav);
        if (written != count || closed != DRWAV_SUCCESS) {
            fprintf(stderr, "Cannot finish enhanced.wav; discard the incomplete file.\n");
            return 0;
        }
        return 1;
    }
    #endif
    ```

    ### Save the program

    Save as `quickstart.c`:

    ```c quickstart.c theme={null}
    #include "aic.h"
    #include "audio_file.h"
    #include <string.h>

    #define CHECK(call) do { \
        enum AicErrorCode error = (call); \
        if (error != AIC_ERROR_CODE_SUCCESS) { \
            fprintf(stderr, "%s failed (SDK error %d).\n", #call, (int)error); \
            goto cleanup; \
        } \
    } while (0)

    int main(void) {
        const char* key = getenv("AIC_SDK_LICENSE");
        if (!key || !*key || strcmp(key, "YOUR_SDK_KEY") == 0) {
            fprintf(stderr, "Set AIC_SDK_LICENSE to your SDK key, then retry.\n");
            return 1;
        }
        int status = 1;
        size_t count = 0, block_size = 0, delay = 0, padded_count = 0;
        float* input = read_input(&count);
        float* output = NULL;
        struct AicModel* model = NULL;
        struct AicProcessor* processor = NULL;
        struct AicProcessorContext* context = NULL;
        if (!input) goto cleanup;
        CHECK(aic_model_create_from_file(&model, "model.aicmodel"));
        CHECK(aic_model_get_optimal_block_size(model, 16000, &block_size));
        CHECK(aic_processor_create(&processor, model, key, NULL));
        CHECK(aic_processor_initialize(processor, 16000, block_size, false));
        CHECK(aic_processor_context_create(&context, processor));
        CHECK(aic_processor_context_get_audio_delay(context, &delay));
        if (block_size == 0 || count > SIZE_MAX - delay ||
            count + delay > SIZE_MAX - (block_size - 1)) {
            fprintf(stderr, "Audio buffer size overflow.\n");
            goto cleanup;
        }
        padded_count = ((count + delay + block_size - 1) / block_size) * block_size;
        if (padded_count > SIZE_MAX / sizeof(float)) {
            fprintf(stderr, "Audio buffer size overflow.\n");
            goto cleanup;
        }
        output = (float*)calloc(padded_count, sizeof(float));
        if (!output) {
            fprintf(stderr, "Cannot allocate the processing buffer.\n");
            goto cleanup;
        }
        memcpy(output, input, count * sizeof(float));
        for (size_t start = 0; start < padded_count; start += block_size) {
            CHECK(aic_processor_process(processor, output + start, block_size));
        }
        /* Flush with silence, then omit the initial delay to align the files. */
        if (!write_output(output + delay, count)) goto cleanup;
        printf("SDK %s, model %s\n", aic_get_sdk_version(), aic_model_get_id(model));
        printf("Processed %zu samples at 16000 Hz; wrote enhanced.wav\n", count);
        status = 0;
    cleanup:
        aic_processor_context_destroy(context);
        aic_processor_destroy(processor);
        aic_model_destroy(model);
        free(output);
        free(input);
        return status;
    }
    ```

    ### Build and run

    Save this complete project as `CMakeLists.txt`:

    ```cmake CMakeLists.txt theme={null}
    cmake_minimum_required(VERSION 3.24)
    project(aic_c_quickstart LANGUAGES C)
    set(CMAKE_C_STANDARD 11)
    set(CMAKE_C_STANDARD_REQUIRED ON)
    add_library(aic SHARED IMPORTED)
    set(AIC_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/sdk")
    if(WIN32)
      set_target_properties(aic PROPERTIES
        IMPORTED_LOCATION "${AIC_ROOT}/lib/aic.dll"
        IMPORTED_IMPLIB "${AIC_ROOT}/lib/aic.dll.lib")
    elseif(APPLE)
      set_target_properties(aic PROPERTIES IMPORTED_LOCATION "${AIC_ROOT}/lib/libaic.dylib")
    else()
      set_target_properties(aic PROPERTIES IMPORTED_LOCATION "${AIC_ROOT}/lib/libaic.so")
    endif()
    set_target_properties(aic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${AIC_ROOT}/include")
    add_executable(quickstart quickstart.c)
    target_link_libraries(quickstart PRIVATE aic)
    if(UNIX AND NOT APPLE)
      target_link_libraries(quickstart PRIVATE m)
    endif()
    if(WIN32)
      add_custom_command(TARGET quickstart POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "$<TARGET_FILE:aic>" "$<TARGET_FILE_DIR:quickstart>")
    endif()
    ```

    Configure and build from `aic-c-quickstart`:

    ```bash theme={null}
    cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
    cmake --build build --config Release
    ```

    Run from the project directory so the program can find `input.wav` and `model.aicmodel`:

    <CodeGroup>
      ```bash macOS / Linux theme={null}
      ./build/quickstart
      ```

      ```powershell Windows PowerShell theme={null}
      .\build\Release\quickstart.exe
      ```
    </CodeGroup>

    ### Check the result

    A successful run exits with code zero, prints the native SDK version and resolved model ID, then reports `Processed N samples at 16000 Hz; wrote enhanced.wav`, where `N` is your input's sample count (`56080` for the supplied fixture). Open `enhanced.wav` in your audio editor: it should be mono, 16 kHz and the same duration as `input.wav`. The output is a **32-bit float WAV**; the input is PCM16.

    The program pads the final block, flushes the delayed tail and removes the initial [processing delay](/reference/concepts/latency), preserving the original sample count. File writes are checked; discard any incomplete file if writing fails.

    Listen to both files and compare them with the same STT settings to evaluate quality.

    ### Recover from an error

    | Problem | Cause and retry |
    | - | - |
    | **Compiler, SDK header or library missing** | Install the compiler and check the selected archive or CMake download. Use the same target architecture for compiler and SDK. Rerun configure and build. |
    | **Missing SDK key or SDK error 50** | Set `AIC_SDK_LICENSE` in this terminal to the complete SDK key, then run the program again. Error 50 is an invalid license format. |
    | **SDK error 6 or 52** | Error 6 means processing is not allowed; error 52 means the license expired. Check account entitlement, key validity and [required connectivity](/models/get-started/authenticate-apps), then restart with a new processor. |
    | **Input file rejected** | Save a nonempty mono, 16 kHz, PCM16 WAV of at most 60 seconds as `input.wav`, then retry. |
    | **Model loading fails** | Keep `model.aicmodel` in the current directory. Re-download the exact compatible file above; error 100 means an invalid model, 101 an unsupported model version and 103 a filesystem error. |
    | **SDK error 5** | The block size differs from initialization. Keep every call at the configured size and pad the final block as shown. |
    | **Output write fails** | Check free space and write permission. Discard the incomplete `enhanced.wav`, fix the cause and retry. |

    The CMake build uses the shared SDK library. Keep `sdk/lib` in place for this tutorial. On Windows, it copies `aic.dll` beside the executable; keep that DLL with your application. See the [C binding guide](/reference/sdk/language-bindings/c#integration-behavior) for deployment and static linking.

    ### Adapt this example

    Keep one processor per stream and process blocks in order. Reset its context on a discontinuity or before unrelated audio. Downmix stereo or use a processor per channel. See [audio format](/reference/concepts/audio-format) and [streams and state](/reference/concepts/streams-and-state).

    The helper reads a short recording into memory. For long files and live audio, use bounded buffers and keep file I/O, model loading and session teardown outside the audio callback. For human-listening enhancement, evaluate [Rook Multi Speaker](/models/perceptual-speech-enhancement/rook-multi-speaker).
  </Tab>
</Tabs>

## Find out more

After checking your output, [evaluate representative audio](/guides/evaluate-audio), then [prepare your deployment](/production/deployment). Use [troubleshooting](/production/troubleshooting) to diagnose setup and runtime failures.

<CardGroup cols={2}>
  <Card title="Audio format" href="/reference/concepts/audio-format">
    Connect your input format, channels and frame sizes to the SDK.
  </Card>

  <Card title="Streams and state" href="/reference/concepts/streams-and-state">
    Manage processor state across streams and interruptions.
  </Card>

  <Card title="Language bindings" href="/reference/sdk/language-bindings">
    Find binding-specific APIs, examples and lifecycle guidance.
  </Card>

  <Card title="Performance" href="/reference/concepts/performance">
    Evaluate runtime cost and latency for your deployment.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.