Skip to content

Releases: argmaxinc/argmax-oss-swift

v1.0.0

Choose a tag to compare

@a2they a2they released this 01 May 21:42

Highlights

v1.0.0 marks the graduation of the project from WhisperKit into the Argmax Open-Source SDK, a Swift package that ships WhisperKit, SpeakerKit, TTSKit, and ArgmaxCore together. Alongside the new name, this release brings full Swift 6 concurrency support, vendors the swift-transformers Hub and Tokenizers sources directly into ArgmaxCore under their original Apache 2.0 license (see NOTICES), and adds a new dynamic library product for binary distribution.

⚠️ This is a breaking release. Deprecated APIs have been removed, and a small number of APIs changed; see API changes below.

New name: Argmax Open-Source SDK

The new name reflects the project's evolution from a single kit (WhisperKit) into a multi-kit open-source SDK, and gives us room to add more models and capabilities in the future. This open-source SDK is the foundation that the Argmax Pro SDK is built on. The Pro SDK extends what's here with additional models and advanced features.

The package is now argmax-oss-swift and the CLI is argmax-cli. A new umbrella product, ArgmaxOSS, re-exports all core modules so a single import gives you everything:

.package(url: "https://github.com/argmaxinc/argmax-oss-swift", from: "1.0.0")
import ArgmaxOSS  // WhisperKit + SpeakerKit + TTSKit + ArgmaxCore

Swift 6 Concurrency

The package is now Swift 6 compatible under Swift 6's approachable concurrency model, with Sendable adopted where appropriate and async boundaries made explicit. CI builds against the latest Swift 6 toolchains alongside Swift 5.10.

Note

The top-level kit classes (WhisperKit, SpeakerKit, TTSKit) are not yet Sendable. If you store an instance of these in a Sendable type or pass it through a @Sendable closure, you may see warnings. Full Sendable conformance for the top-level kit classes is planned for a future release.

Vendored Hub and Tokenizers

Huge thanks to the Hugging Face team and the swift-transformers community. The project has been a foundational dependency for WhisperKit since day one.

To give the OSS SDK a self-contained build and avoid version-resolution conflicts for downstream apps that already depend on swift-transformers, we've vendored the Hub and Tokenizers source files directly into Sources/ArgmaxCore/External/ under their original Apache 2.0 license (see NOTICES for full attribution). Every file carries the upstream attribution header, and all Argmax modifications are marked by // Argmax-modification: comments for easy auditing against upstream.

The public API surface is exposed through opaque wrappers (HubApiWrapper, TokenizerWrapper, and AutoTokenizerWrapper), so the embedded Hub and Tokenizer types stay internal to ArgmaxCore and won't collide if you import swift-transformers separately in the same app.

ArgmaxOSS Dynamic Library

A new ArgmaxOSSDynamic product (in Package@swift-6.2.swift) builds the SDK as a dynamic library.

CLI

The renamed argmax-cli bundles transcription, diarization, TTS, and an OpenAI-compatible local server in one binary.

Run the local server with streaming transcription support:

BUILD_ALL=1 swift run argmax-cli serve --model large-v3-v20240930_626MB --host 0.0.0.0 --port 50060

The server responds to POST /v1/audio/transcriptions and POST /v1/audio/translations, so existing OpenAI SDK clients work out of the box. See the README for client examples.

API Changes

Removed deprecated APIs

All APIs previously marked @available(*, deprecated) or @available(*, unavailable, renamed:) have been removed. Migrate to the replacements named in each original deprecation message. Notable removals:

  • WhisperKit.transcribe(audioPath:) / transcribe(audioArray:) overloads returning TranscriptionResult? -> use the [TranscriptionResult] versions
  • TextDecoding.decodeText / detectLanguage MLMultiArray overloads -> use the any AudioEncoderOutputType / any DecodingInputsType versions
  • Top-level free functions -> now on ModelUtilities, TranscriptionUtilities, TextUtilities, Logging, and FileManager
  • WhisperKit.formatModelFiles -> ModelUtilities.formatModelFiles
  • SpeakerKit.init(models:), SpeakerKit.init(diarizer:), and the SpeakerKitModelManager typealias
  • ModelUtilities.getModelInputDimention / getModelOutputDimention (typo) -> getModelInputDimension / getModelOutputDimension
  • MLTensor.asIntArray / asFloatArray / asMLMultiArray (sync) -> await toIntArray() / toFloatArray() / toMLMultiArray(). As a result, TokenSampling.update(...) is now async.

Typo fix: DecodingOptions.supressTokens -> suppressTokens.

Also removed: the TextDecoderContextPrefill model and the usePrefillCache decoding path (a separate CoreML model that pre-seeded the KV cache for SOT/language/task/timestamp tokens). The decoder's own KV cache is unaffected.

Upgrade Guide

Package and CLI rename

Package.swift:

// before
.package(url: "https://github.com/argmaxinc/WhisperKit", from: "0.18.0")

// after
.package(url: "https://github.com/argmaxinc/argmax-oss-swift", from: "1.0.0")

CLI:

# before
swift run whisperkit-cli transcribe --audio-path audio.wav

# after
swift run argmax-cli transcribe --audio-path audio.wav

TranscriptionCallback in stored variables

If you pass a callback inline (trailing closure or callback: nil), no change is needed. Only stored-variable cases need an ?:

// before
let callback: TranscriptionCallback = { _ in true }
whisperKit.transcribe(..., callback: callback)

// after
let callback: TranscriptionCallback? = { _ in true }
whisperKit.transcribe(..., callback: callback)

Community

Reaching v1.0.0 is a milestone two and a half years in the making, and it would not have been possible without the people who have filed issues, opened pull requests, shipped apps on top of WhisperKit, and shown up in Discord to help one another. Every contribution, big or small, has shaped what this SDK has become. From all of us at Argmax: thank you. 🙏

This is a sizable breaking release, so please give it a spin and let us know how it goes. Open an issue or join us in Discord. 🚀

What's Changed

New Contributors

Full Changelog: v0.18.0...v1.0.0

v0.18.0

Choose a tag to compare

@a2they a2they released this 01 Apr 03:25

Highlights

This release refactors SpeakerKit around a new shared ModelManager base class, unifying the download → load → unload lifecycle across kits and simplifying the SpeakerKit public API.

The top-level entry point is now just:

let speakerKit = try await SpeakerKit()

No config object required for the default case.

Architecture Changes

ModelManager (new, ArgmaxCore)

A reusable base class for managing the full model lifecycle that all kits can now inherit from. It handles state transitions, error recovery, and concurrent load coalescing via an internal LoadModelsCoordinator — concurrent callers to ensureModelsLoaded() coalesce onto a single in-flight task rather than racing.

Backend-specific I/O is delegated to a new ModelLoader protocol:

public protocol ModelLoader: AnyObject, Sendable {
    var modelFolder: String? { get }
    func resolveModels(downloader: ModelDownloader, progressCallback: ((Progress) -> Void)?) async throws -> String
    func load(from modelPath: String, prewarm: Bool) async throws
    func unload() async
}

SpeakerKitDiarizer (replaces SpeakerKitModelManager)

SpeakerKitModelManager has been replaced by SpeakerKitDiarizer, which inherits from ModelManager and conforms to the new Diarizer protocol. Create it via the static factory:

let diarizer = SpeakerKitDiarizer.pyannote(config: config)

SpeakerKitModelManager is still available as a deprecated typealias pointing to SpeakerKitDiarizer so existing code compiles with a warning rather than breaking.

Diarizer protocol (new)

A clean protocol for plugging in diarization backends:

public protocol Diarizer: Sendable {
    var modelState: ModelState { get }
    func downloadModels() async throws
    func loadModels() async throws
    func unloadModels() async
    func diarize(audioArray: [Float], options: (any DiarizationOptions)?, progressCallback: ...) async throws -> DiarizationResult
}

ModelDownloadConfig (new, ArgmaxCore)

All download parameters (endpoint, repo, token, revision, background session flag) are now bundled in a ModelDownloadConfig struct rather than passed individually to ModelDownloader. The existing convenience init still works unchanged.

SpeakerKitConfig (new base class)

PyannoteConfig now inherits from SpeakerKitConfig, which adds a load: Bool flag. When false (the default), models are loaded lazily on the first diarize() call. Set load: true to eager-load during SpeakerKit init.

API Changes

SpeakerKit initialization simplified

// Before
let config = PyannoteConfig()
let speakerKit = try await SpeakerKit(config)

// After
let speakerKit = try await SpeakerKit()

Local models path is now a String instead of URL:

// Before
let config = PyannoteConfig(modelFolder: URL(filePath: "/path/to/models"))

// After
let config = PyannoteConfig(modelFolder: "/path/to/models")

Lazy loading by default

Models are now loaded on the first diarize() call unless load: true is set in config.

CLI

diarize and transcribe --diarization now use the simplified SpeakerKit(config) API internally. No functional changes to CLI flags.

What's Changed

  • Fix SpeakerKit tests on iOS 17 by @a2they in #444
  • Refactor SpeakerKit around a reusable ModelManager base class by @a2they in #452

Full Changelog: v0.17.0...v0.18.0

v0.17.0

Choose a tag to compare

@a2they a2they released this 13 Mar 15:38
26577ce

Highlights

We're excited to bring our commercial-grade speaker diarization framework, SpeakerKit, to open-source!

With NVIDIA Sortformer now powering real-time speaker diarization in the Argmax Pro SDK, we're open-sourcing our implementation of Pyannote 4 (community-1). Pyannote is well-known for solving the "who spoke when" problem and has shown strong results on datasets such as AMI, DIHARD, and VoxConverse. Read the blog post for architecture details and benchmarks.

Quickstart

Download, load, diarize, and generate RTTM (Rich Transcription Time Marked) output in just a few lines of code:

import SpeakerKit

let speakerKit = try await SpeakerKit()
let result = try await speakerKit.diarize(audioPath: "audio.wav")
let rttm = speakerKit.generateRTTM(result: result)

Key features

  • End-to-end Pyannote-style diarization pipeline
  • Automatically estimate the number of speakers or set it manually
  • Utilities to add speaker information to WhisperKit outputs
  • Standard RTTM export

Explore the new SpeakerKit README section for API documentation, configuration details, and optimization tips.

CLI

whisperkit-cli now includes a dedicated diarize subcommand:

swift run -c release whisperkit-cli diarize --audio-path audio.wav --rttm-path output.rttm

With Homebrew:

brew install whisperkit-cli
whisperkit-cli diarize --audio-path audio.wav --rttm-path output.rttm

You can also run transcription and diarization together using the new --diarization flag:

whisperkit-cli transcribe --audio-path audio.wav --diarization

Example output:

---- Speaker Diarization Results ----
SPEAKER audio 1 0.220 7.360 What is RLHF? reinforcement learning with human feedback. What was that little magic ingredient to the dish that made it so much more delicious? <NA> A <NA> <NA>
SPEAKER audio 1 7.610 14.850 - So we train these models on a lot of text data. And in that process, they learn something about the underlying representations of what's in here or in there. <NA> B <NA> <NA>

Additional flags are available for speaker counts, model variants, clustering algorithms tuning, and more.

WhisperAX example updates

The WhisperAX example app has been updated with SpeakerKit support. It now includes diarization toggles, a flexible pipeline selector, and a Speakers tab for browsing labeled segments.

What's Changed

  • Add SpeakerKit with Pyannote speaker diarization support by @a2they in #440

Full Changelog: v0.16.0...v0.17.0

v0.16.0

Choose a tag to compare

@ZachNagengast ZachNagengast released this 03 Mar 02:49

Highlights

This release introduces TTSKit - a brand-new optional library that brings high-quality text-to-speech capabilities on-device using the latest Core ML features such as MLState and MLTensor for optimal inference on the Apple Neural Engine.

With this first release, we're launching Qwen3-TTS CustomVoice models 0.6b and 1.7b with instruction control, with more to come in future releases (including voice cloning).

Download, load, generate, and stream playback in 3 lines of code:

import TTSKit

let ttsKit = try await TTSKit()
try await ttsKit.play(text: "Hello from TTSKit!")

Key Features

  • Real-time adaptive streaming
    • Plays audio while it's still generating for the fastest time from text input to first audio buffer output
    • .auto mode adapts based on the inference speed of the device for consistent, smooth playback.
  • 9 built-in voices
  • 10 languages
  • Style instruction support (1.7B model only)
  • Automatic chunking for long-form inputs
  • Audio file exports in wav/m4a format with optional metadata.
  • Modular protocol-based architecture (6 swappable Core ML components) for easy customization and future model adoption.

See the new TTSKit section in the README.md for full API docs, model selection, and advanced usage.

CLI

Try it out with the following command:

swift run -c release whisperkit-cli tts --text "Hello from TTSKit" --play

Also available via Homebrew upon release:

brew install whisperkit-cli
whisperkit-cli tts --text "Hello from TTSKit" --play

Gives full control over speaker, language, model variant, style, temperature, chunking strategy, compute units, seed for reproducibility, and more.

Example App

Along with the CLI, we're also releasing a new example app for developers to reference when building TTSKit into their apps. It features real-time waveform visualization, model management, persistent audio file history with metadata, and multi-platform support. Here's a screenshot:
image

More info about running this app in the example's README.md

Architecture Changes

  • New shared ArgmaxCore target for common utilities
  • TTSKit ships as an optional product in the same Swift package (no breaking changes to existing WhisperKit code).
.target(
    name: "YourApp",
    dependencies: [
        "WhisperKit", // speech-to-text
        "TTSKit",     // text-to-speech
    ]
),
  • The repo will be renamed to reflect the new multi-kit architecture in an upcoming release.

Thank you to @naykutguven and @shura-v for the excellent improvements packaged with this release prior to TTSKit listed below 🚀

What's Changed

New Contributors

Full Changelog: v0.15.0...v0.16.0

v0.15.0

Choose a tag to compare

@chen-argmax chen-argmax released this 07 Nov 21:42

This minor release bumps our swift-transformers dependency to 1.1.2, and it promotes TranscriptionResult from a struct to an open class so advanced clients can override behavior.

TranscriptionResult API change

Since it's changed from a struct to a class, if you depended on the old value semantics, copying now just passes the same reference around, so mutations will be shared. Audit any code that assumes independent copies (arrays, captured values, etc.) and initialize a fresh TranscriptionResult when isolation is required. If you subclass it, protect any new stored properties with the same locking approach (TranscriptionPropertyLock) to maintain thread safety.

What's Changed

New Contributors

Full Changelog: v0.14.1...v0.15.0

v0.14.1

Choose a tag to compare

@ZachNagengast ZachNagengast released this 17 Oct 00:54

This patch release is an initial start to upgrading to swift-transformers >1.0.0 and Swift 6 concurrency. It also includes some QoL fixes for tests and adds flexibility to the TranscribeTask for subclassing.

What's Changed

  • Use default checkout setting in CI script for unit tests by @naykutguven in #363
  • Add Sendable conformance to public structs by @naykutguven in #362
  • Set watchOS and visionOS minimum deployment targets in Package manifest instead by @naykutguven in #360
  • Prepare for new swift-transformers, add TranscribeTask hooks by @ZachNagengast in #367

New Contributors

Full Changelog: v0.14.0...v0.14.1

v0.14.0

Choose a tag to compare

@ZachNagengast ZachNagengast released this 20 Sep 21:14

Highlights

This release introduces the WhisperKit Local Server! A OpenAI-compatible Vapor based HTTP server that can be run via CLI or as a subprocess.

Try it out with the following command:

BUILD_ALL=1 swift run whisperkit-cli serve

Key Features

  • Local Server: OpenAI-compatible /v1/audio/transcriptions and /v1/audio/translations endpoints
  • Transcription Streaming: Server-sent events as files get transcribed
  • Response Formats: Support for json, verbose_json
  • Timestamp Granularities: Word-level and segment-level timestamps
  • Client Examples: Python, Swift, and Bash with curl

You can also use the Makefile command make build-local-server to generate an exectuable that can be bundled in your electron or tauri apps without needing any native integration.

There are also several bugfixes and quality of life improvements relating to tokenizer loading and VAD access.

What's Changed

  • Add WhisperKit Local Server with audio transcription and translation APIs by @a2they in #348
  • Tokenizer and punctuation fixes, better remote config handling by @ZachNagengast in #350
  • Update README.md with Argmax SDK by @atiorh in #346
  • Make EnergyVAD public by @finnvoor in #347

Full Changelog: v0.13.1...v0.14.0

v0.13.1

Choose a tag to compare

@ZachNagengast ZachNagengast released this 31 Jul 18:14
c814cae

Patch release to fix some issues reported relating to tokenizer loading and config-based logit filters.

  • Tokenizer downloading now respects downloadBase if specified #339 thanks for the suggestion @Kavi-Gupta
  • Tokenizer will now load offline with the CLI if it exists in the modelFolder path #340 thanks for reporting @cedricporter
  • Logits filters were observed never actually being passed to the text decoder if defined in the config, this patch makes sure they are observed by passing them to the text decoder on WhisperKit initialization.

Also includes improved logging contributed by @JimLiu, thanks everyone for helping make WhisperKit ever better! 🚀

What's Changed

  • feat: enhance verbose logging in WhisperKit CLI by @JimLiu in #335
  • Pass logitfilters to textdecoder, improve tokenizer loading by @ZachNagengast in #343

Full Changelog: v0.13.0...v0.13.1

v0.13.0

Choose a tag to compare

@a2they a2they released this 13 Jun 04:01

New API

  • Async VAD Support: voiceActivityAsync(in:) method for VoiceActivityDetector
  • Segments Discovery Callback: transcribe() method is now accepting SegmentDiscoveryCallback to receive sortable segments while transcribing with accurate seek values

⚠️ Deprecated Functions → Utility Classes

Existing code continues to work with deprecation warnings.

// Old → New
compressionRatio(of:)  TextUtilities.compressionRatio(of:)
formatSegments(_:withTimestamps:)  TranscriptionUtilities.formatSegments(_:withTimestamps:)
loadTokenizer(for:tokenizerFolder:useBackgroundSession:)  ModelUtilities.loadTokenizer(for:tokenizerFolder:useBackgroundSession:)
modelSupport(for:from:)  ModelUtilities.modelSupport(for:from:)
detectModelURL(inFolder:named:)  ModelUtilities.detectModelURL(inFolder:named:)
findLongestCommonPrefix(_:_:)  TranscriptionUtilities.findLongestCommonPrefix(_:_:)
mergeTranscriptionResults(_:confirmedWords:)  TranscriptionUtilities.mergeTranscriptionResults(_:confirmedWords:)
resolveAbsolutePath(_:)  FileManager.resolveAbsolutePath(_:)

Protocol-Based Decoder Inputs

// Old
func decodeText(using decoderInputs: DecodingInputs) -> DecodingResult

// New  
func decodeText(using decoderInputs: any DecodingInputsType) -> DecodingResult

What's Changed

  • Fix modelSupport where prefix overlaps with different hardware chips by @a2they in #326
  • fix: the issue where filenames containing dots are not handled correctly when generating the report path by @JimLiu in #333
  • Sortable segment discovery during VAD chunking by @ZachNagengast in #334
  • Refactor and cleanup utils, add protocol DecodingInputsType for DecoderInputs by @a2they in #338

New Contributors

Full Changelog: v0.12.0...v0.13.0

v0.12.0

Choose a tag to compare

@ZachNagengast ZachNagengast released this 15 Apr 21:03

This minor release brings in multi-channel audio merging which was a frequently requested feature. This changes the default audio processing code path to always merge all channels if multiple are detected, whereas before it was only using channel 0. You can also select specific channels when loading audio as part of the config:

        let config = WhisperKitConfig(
            ...
            audioInputConfig: AudioInputConfig(channelMode: .sumChannels([1, 3, 5]))
        )

This could be used as a simplified form of speaker separation if your audio file has distinct speakers in different channels.

From #320:

The audio merging algorithm works like this:
We find the peak across all channels, check if the peak of the mono (summed) version is higher than any of the peaks of the channels, then we multiply the whole track so that the peak of the mono channel matches the peak of the loudest channel.

Eg: Top mono (merged) buffer, bottom individual channels (pre-merge)
image
Here you can see how the merged audio maintains the same loudness as the original multi-channel audio file, and you can see the total merged waveform of all the channels.

This release also brings in updates to the recommendedModels function for the latest devices released since the last update, as well as some improved testing methods.

What's Changed

New Contributors

Full Changelog: v0.11.0...v0.12.0