From 05ac22429a3c21ba94b523ff280d45069d2351e3 Mon Sep 17 00:00:00 2001 From: Alex-Wengg Date: Sat, 28 Mar 2026 14:19:11 -0400 Subject: [PATCH 1/5] Simplify folderName logic by stripping -coreml suffix by default Remove redundant special cases in folderName property. Now only keeps special cases for nested directory structures (EOU and Nemotron variants) and uses a simple default rule: strip "-coreml" suffix from the name. This eliminates the inconsistency raised in #442 by applying a consistent pattern across all models. Before: - Had 10+ special cases explicitly returning shortened names - parakeetTdtCtc110m was inconsistent with other Parakeet models After: - Only 5 special cases for nested directories (parakeet-eou-streaming/*, nemotron-streaming/*) - Default strips -coreml suffix for all other models - All Parakeet models now follow the same pattern Fixes #442 --- Sources/FluidAudio/ModelNames.swift | 14 +------------- .../ASR/Parakeet/ModelNamesTests.swift | 2 +- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/Sources/FluidAudio/ModelNames.swift b/Sources/FluidAudio/ModelNames.swift index 1d4d7e9fc..d5efec205 100644 --- a/Sources/FluidAudio/ModelNames.swift +++ b/Sources/FluidAudio/ModelNames.swift @@ -115,8 +115,6 @@ public enum Repo: String, CaseIterable { /// Local folder name used for caching public var folderName: String { switch self { - case .kokoro: - return "kokoro" case .parakeetEou160: return "parakeet-eou-streaming/160ms" case .parakeetEou320: @@ -127,18 +125,8 @@ public enum Repo: String, CaseIterable { return "nemotron-streaming/1120ms" case .nemotronStreaming560: return "nemotron-streaming/560ms" - case .sortformer: - return "sortformer" - case .lseend: - return "ls-eend" - case .pocketTts: - return "pocket-tts" - case .multilingualG2p: - return "charsiu-g2p-byt5" - case .parakeetTdtCtc110m: - return "parakeet-tdt-ctc-110m" default: - return name + return name.replacingOccurrences(of: "-coreml", with: "") } } } diff --git a/Tests/FluidAudioTests/ASR/Parakeet/ModelNamesTests.swift b/Tests/FluidAudioTests/ASR/Parakeet/ModelNamesTests.swift index 3e3607394..6048ff95c 100644 --- a/Tests/FluidAudioTests/ASR/Parakeet/ModelNamesTests.swift +++ b/Tests/FluidAudioTests/ASR/Parakeet/ModelNamesTests.swift @@ -125,7 +125,7 @@ final class ModelNamesTests: XCTestCase { // Verify name (repo slug with -coreml suffix) XCTAssertEqual(repo.name, "parakeet-tdt-ctc-110m-coreml") - // Verify folder name (simplified local folder name) + // Verify folder name (simplified - strips -coreml suffix by default) XCTAssertEqual(repo.folderName, "parakeet-tdt-ctc-110m") // Should have no subpath (not a variant repo) From c3aba6a96c7fa63fbb1fee75e8b78de312525f99 Mon Sep 17 00:00:00 2001 From: Alex-Wengg Date: Sat, 28 Mar 2026 14:24:11 -0400 Subject: [PATCH 2/5] Update kokoro folderName test expectation to match simplified logic --- Tests/FluidAudioTests/TTS/MultilingualG2PTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/FluidAudioTests/TTS/MultilingualG2PTests.swift b/Tests/FluidAudioTests/TTS/MultilingualG2PTests.swift index 8a059833c..1f8eb3274 100644 --- a/Tests/FluidAudioTests/TTS/MultilingualG2PTests.swift +++ b/Tests/FluidAudioTests/TTS/MultilingualG2PTests.swift @@ -111,7 +111,7 @@ final class MultilingualG2PTests: XCTestCase { func testRepoMultilingualG2P() { // Multilingual G2P models are bundled inside the kokoro repo - XCTAssertEqual(Repo.kokoro.folderName, "kokoro") + XCTAssertEqual(Repo.kokoro.folderName, "kokoro-82m") XCTAssertEqual(Repo.kokoro.remotePath, "FluidInference/kokoro-82m-coreml") } } From 8b79517cde879e4f9c23c910895461343f661971 Mon Sep 17 00:00:00 2001 From: Alex-Wengg Date: Sat, 28 Mar 2026 14:29:29 -0400 Subject: [PATCH 3/5] Keep kokoro and sortformer special cases to avoid breaking changes Add back special cases for kokoro and sortformer to preserve existing folder names and avoid forcing users to re-download models. Still removes redundant special cases (lseend, pocketTts, multilingualG2p, parakeetTdtCtc110m) that can safely use the default -coreml stripping logic. Result: 7 special cases total (kokoro, sortformer, + 5 nested directories) vs 11 special cases before. Still achieves consistency for Parakeet models without breaking existing cached model locations. --- Sources/FluidAudio/ModelNames.swift | 4 ++++ Tests/FluidAudioTests/TTS/MultilingualG2PTests.swift | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/FluidAudio/ModelNames.swift b/Sources/FluidAudio/ModelNames.swift index d5efec205..afc7d6af3 100644 --- a/Sources/FluidAudio/ModelNames.swift +++ b/Sources/FluidAudio/ModelNames.swift @@ -115,6 +115,8 @@ public enum Repo: String, CaseIterable { /// Local folder name used for caching public var folderName: String { switch self { + case .kokoro: + return "kokoro" case .parakeetEou160: return "parakeet-eou-streaming/160ms" case .parakeetEou320: @@ -125,6 +127,8 @@ public enum Repo: String, CaseIterable { return "nemotron-streaming/1120ms" case .nemotronStreaming560: return "nemotron-streaming/560ms" + case .sortformer: + return "sortformer" default: return name.replacingOccurrences(of: "-coreml", with: "") } diff --git a/Tests/FluidAudioTests/TTS/MultilingualG2PTests.swift b/Tests/FluidAudioTests/TTS/MultilingualG2PTests.swift index 1f8eb3274..8a059833c 100644 --- a/Tests/FluidAudioTests/TTS/MultilingualG2PTests.swift +++ b/Tests/FluidAudioTests/TTS/MultilingualG2PTests.swift @@ -111,7 +111,7 @@ final class MultilingualG2PTests: XCTestCase { func testRepoMultilingualG2P() { // Multilingual G2P models are bundled inside the kokoro repo - XCTAssertEqual(Repo.kokoro.folderName, "kokoro-82m") + XCTAssertEqual(Repo.kokoro.folderName, "kokoro") XCTAssertEqual(Repo.kokoro.remotePath, "FluidInference/kokoro-82m-coreml") } } From 443cd03083b577fbf5cfd9f5f46216d381e9fb50 Mon Sep 17 00:00:00 2001 From: Alex-Wengg Date: Sat, 28 Mar 2026 16:27:09 -0400 Subject: [PATCH 4/5] Add plda-parameters.json to OfflineDiarizer required models The offline diarizer benchmark was failing in CI because the PLDA parameters JSON file was not being downloaded when downloading offline diarizer models. The requiredModels set only included the 4 .mlmodelc files but not the plda-parameters.json file that's required by OfflineDiarizerModels.loadPLDAPsi(). This caused the error: PLDA parameters file not found in /Users/runner/Library/Application Support/FluidAudio/Models Fixes the diarization-benchmark.yml workflow failure. --- Sources/FluidAudio/ModelNames.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/FluidAudio/ModelNames.swift b/Sources/FluidAudio/ModelNames.swift index afc7d6af3..5f9b35fce 100644 --- a/Sources/FluidAudio/ModelNames.swift +++ b/Sources/FluidAudio/ModelNames.swift @@ -158,6 +158,7 @@ public enum ModelNames { public static let fbank = "FBank" public static let embedding = "Embedding" public static let pldaRho = "PldaRho" + public static let pldaParameters = "plda-parameters.json" public static let segmentationFile = segmentation + ".mlmodelc" public static let fbankFile = fbank + ".mlmodelc" @@ -174,6 +175,7 @@ public enum ModelNames { fbankPath, embeddingPath, pldaRhoPath, + pldaParameters, ] } From 60b40f248b5218b28c4921d75f9a5bd9fa9f2c70 Mon Sep 17 00:00:00 2001 From: Alex-Wengg Date: Sat, 28 Mar 2026 17:03:42 -0400 Subject: [PATCH 5/5] Add speaker-diarization path to PLDA candidate paths With the folderName simplification, diarizer models are now stored in 'speaker-diarization/' instead of 'speaker-diarization-coreml/'. Update the PLDA parameter file lookup to check the new folder location while maintaining backward compatibility with old paths. --- .../FluidAudio/Diarizer/Offline/Core/OfflineDiarizerModels.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/FluidAudio/Diarizer/Offline/Core/OfflineDiarizerModels.swift b/Sources/FluidAudio/Diarizer/Offline/Core/OfflineDiarizerModels.swift index 980fddb63..cfe5cb5a4 100644 --- a/Sources/FluidAudio/Diarizer/Offline/Core/OfflineDiarizerModels.swift +++ b/Sources/FluidAudio/Diarizer/Offline/Core/OfflineDiarizerModels.swift @@ -17,6 +17,7 @@ public struct OfflineDiarizerModels: Sendable { private static func loadPLDAPsi(from directory: URL) throws -> [Double] { let candidatePaths = [ directory.appendingPathComponent("plda-parameters.json", isDirectory: false), + directory.appendingPathComponent("speaker-diarization/plda-parameters.json", isDirectory: false), directory.appendingPathComponent("speaker-diarization-coreml/plda-parameters.json", isDirectory: false), directory.appendingPathComponent("speaker-diarization-offline/plda-parameters.json", isDirectory: false), ]