Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ exclude = ["plugins/app-test-driver"]

[build-dependencies]
tauri-build = { version = "2", features = [] }
cc = "1"

[dependencies]
anyhow = "1"
Expand Down
13 changes: 13 additions & 0 deletions src-tauri/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,18 @@ fn main() {
std::env::var("BERD_APP_VERSION").unwrap_or_else(|_| env!("CARGO_PKG_VERSION").to_owned());
println!("cargo:rustc-env=BERD_BUILD_VERSION={app_version}");

#[cfg(target_os = "macos")]
{
println!("cargo:rerun-if-changed=native/siri_tts_bridge.h");
println!("cargo:rerun-if-changed=native/siri_tts_bridge.m");
cc::Build::new()
.file("native/siri_tts_bridge.m")
.flag("-fobjc-arc")
.compile("berd_siri_tts_bridge");
for framework in ["Foundation", "AVFoundation", "AudioToolbox", "CoreAudio"] {
println!("cargo:rustc-link-lib=framework={framework}");
}
}

tauri_build::build()
}
3 changes: 2 additions & 1 deletion src-tauri/crates/berd-voice/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
mod pocket;

pub use pocket::{
load_text_to_speech, load_voice_style, PocketTts, StreamingTextChunks, VoiceStyle, SAMPLE_RATE,
load_text_to_speech, load_voice_style, take_streaming_text_chunks, PocketTts,
StreamingTextChunks, VoiceStyle, SAMPLE_RATE,
};
25 changes: 25 additions & 0 deletions src-tauri/crates/berd-voice/src/pocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,31 @@ pub const SAMPLE_RATE: u32 = 24_000;

const TTS_NUM_THREADS: usize = 1;

/// Drain stable, sentence-aware chunks from text that may still be growing.
///
/// This backend-neutral form uses a word-count budget. It lets system speech
/// engines share Berd's first-sentence latency behavior without loading a
/// Pocket model solely to segment text.
pub fn take_streaming_text_chunks(
text: &str,
first_chunk_pending: bool,
flush: bool,
) -> Result<StreamingTextChunks, String> {
let (ready, pending, first_chunk_pending) =
pocket_april::take_streaming_chunks_at_natural_boundaries(
text,
50,
first_chunk_pending,
flush,
|candidate| Ok(candidate.split_whitespace().count()),
)?;
Ok(StreamingTextChunks {
ready,
pending,
first_chunk_pending,
})
}

thread_local! {
static ACTIVE_SYNTHESIS_ENGINES: RefCell<Vec<usize>> = const { RefCell::new(Vec::new()) };
}
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/crates/berd-voice/src/pocket_april.rs
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,7 @@ where
Ok(chunks)
}

fn take_streaming_chunks_at_natural_boundaries<F>(
pub(crate) fn take_streaming_chunks_at_natural_boundaries<F>(
text: &str,
max_tokens: usize,
mut first_chunk_pending: bool,
Expand Down
81 changes: 81 additions & 0 deletions src-tauri/native/siri_tts_bridge.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#ifndef BERD_SIRI_TTS_BRIDGE_H
#define BERD_SIRI_TTS_BRIDGE_H

#include <stdbool.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/// Returns a malloc-owned JSON array of Siri voices for the requested language
/// prefix. Each item contains name, language, sizeBytes, and installed. Returns
/// NULL and sets error_out on failure.
char *berd_siri_tts_catalog_json(const char *language_prefix, char **error_out);

/// Returns the locale tags represented in the complete Siri voice catalog.
/// This does not perform per-voice daemon validation.
char *berd_siri_tts_languages_json(char **error_out);

/// Downloads and validates one exact Siri voice. This call blocks until the
/// voice is usable or the timeout elapses.
bool berd_siri_tts_download_voice(
const char *language,
const char *voice_name,
double timeout_seconds,
char **error_out
);

typedef bool (*BerdSiriTTSShouldStop)(void *context);
typedef void (*BerdSiriTTSPlaybackStarted)(void *context);

/// Plays the small per-voice sample bundled with macOS. This works before the
/// full Siri voice has been downloaded.
bool berd_siri_tts_play_sample(
const char *voice_name,
const char *language,
float rate,
BerdSiriTTSShouldStop should_stop,
void *context,
char **error_out
);

/// Opaque streaming player. Text chunks are synthesized in order while
/// previously queued audio continues playing.
void *berd_siri_tts_stream_create(
const char *language,
const char *voice_name,
float rate,
BerdSiriTTSPlaybackStarted playback_started,
void *context,
char **error_out
);
bool berd_siri_tts_stream_enqueue(void *stream, const char *text, char **error_out);
void berd_siri_tts_stream_finish(void *stream);
bool berd_siri_tts_stream_is_finished(void *stream);
uint64_t berd_siri_tts_stream_progress(void *stream);
char *berd_siri_tts_stream_copy_error(void *stream);
void berd_siri_tts_stream_cancel(void *stream);
void berd_siri_tts_stream_release(void *stream);

/// Synthesizes one utterance through sirittsd and streams its audio packets to
/// the default macOS output. This call blocks until playback completes.
bool berd_siri_tts_speak(
const char *text,
const char *language,
const char *voice_name,
float rate,
BerdSiriTTSShouldStop should_stop,
BerdSiriTTSPlaybackStarted playback_started,
void *context,
char **error_out
);

/// Frees strings returned by this bridge.
void berd_siri_tts_free_string(char *value);

#ifdef __cplusplus
}
#endif

#endif
Loading