Skip to content

Commit f3d7727

Browse files
committed
rewrite avsynth: pure ObjC shim with C FFI, no msg_send!/objc_exception
1 parent 29c5152 commit f3d7727

5 files changed

Lines changed: 238 additions & 167 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ sherpaonnx = ["sherpa-onnx", "serde_json"]
1919
tungstenite = ["dep:tungstenite"]
2020
uuid = ["dep:uuid"]
2121
url = ["dep:url"]
22-
avsynth = ["objc", "objc-foundation", "objc_exception"]
22+
avsynth = []
2323
sapi = ["windows"]
2424

2525
[dependencies]
@@ -34,15 +34,13 @@ anyhow = "1"
3434
tungstenite = { version = "0.29.0", features = ["rustls-tls-webpki-roots"], optional = true }
3535
uuid = { version = "1.23.2", features = ["v4"], optional = true }
3636
url = { version = "2.5.8", optional = true }
37-
objc = { version = "0.2", optional = true }
38-
objc-foundation = { version = "0.1", optional = true }
39-
objc_exception = { version = "0.1", optional = true }
4037

4138
[target.'cfg(target_os = "windows")'.dependencies]
4239
windows = { version = "0.61", features = ["Win32_Media_Speech", "Win32_System_Com", "Win32_System_Ole"], optional = true }
4340

4441
[build-dependencies]
4542
cbindgen = "0.28"
43+
cc = "1"
4644

4745
[lints.clippy]
4846
all = "warn"

build.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ fn main() {
44
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
55
let config = cbindgen::Config::from_file("cbindgen.toml").unwrap_or_default();
66
match cbindgen::Builder::new()
7-
.with_crate(crate_dir)
7+
.with_crate(&crate_dir)
88
.with_config(config)
99
.generate()
1010
{
@@ -15,4 +15,15 @@ fn main() {
1515
eprintln!("cbindgen warning: {e}");
1616
}
1717
}
18+
19+
if env::var("TARGET").unwrap_or_default().contains("apple") {
20+
cc::Build::new()
21+
.file("extern/avsynth_shim.m")
22+
.compiler("clang")
23+
.flag("-fobjc-arc")
24+
.compile("avsynth_shim");
25+
println!("cargo:rustc-link-lib=framework=AVFAudio");
26+
println!("cargo:rustc-link-lib=framework=Foundation");
27+
println!("cargo:rustc-link-lib=objc");
28+
}
1829
}

extern/avsynth_shim.m

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#import <Foundation/Foundation.h>
2+
#import <AVFAudio/AVSpeechSynthesis.h>
3+
4+
void* avsynth_create(void) {
5+
@try {
6+
AVSpeechSynthesizer *synth = [[AVSpeechSynthesizer alloc] init];
7+
return (void *)CFBridgingRetain(synth);
8+
} @catch (NSException *e) {
9+
return NULL;
10+
}
11+
}
12+
13+
void avsynth_destroy(void *handle) {
14+
if (!handle) return;
15+
@try {
16+
AVSpeechSynthesizer *synth = (__bridge_transfer AVSpeechSynthesizer *)handle;
17+
synth = nil;
18+
} @catch (NSException *e) {}
19+
}
20+
21+
void avsynth_speak(void *handle, const char *text, const char *voice_id,
22+
float rate, float pitch, float volume) {
23+
if (!handle || !text) return;
24+
@try {
25+
AVSpeechSynthesizer *synth = (__bridge AVSpeechSynthesizer *)handle;
26+
NSString *nsText = [NSString stringWithUTF8String:text];
27+
if (!nsText) return;
28+
AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:nsText];
29+
utterance.rate = rate < 0.1f ? 0.1f : (rate > 10.0f ? 10.0f : rate);
30+
utterance.pitchMultiplier = pitch < 0.5f ? 0.5f : (pitch > 2.0f ? 2.0f : pitch);
31+
utterance.volume = volume < 0.0f ? 0.0f : (volume > 1.0f ? 1.0f : volume);
32+
if (voice_id && voice_id[0] != '\0') {
33+
NSString *nsVoiceId = [NSString stringWithUTF8String:voice_id];
34+
AVSpeechSynthesisVoice *voice = [AVSpeechSynthesisVoice voiceWithIdentifier:nsVoiceId];
35+
if (voice) utterance.voice = voice;
36+
}
37+
[synth speakUtterance:utterance];
38+
} @catch (NSException *e) {}
39+
}
40+
41+
void avsynth_stop(void *handle) {
42+
if (!handle) return;
43+
@try {
44+
AVSpeechSynthesizer *synth = (__bridge AVSpeechSynthesizer *)handle;
45+
[synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
46+
} @catch (NSException *e) {}
47+
}
48+
49+
void avsynth_pause(void *handle) {
50+
if (!handle) return;
51+
@try {
52+
AVSpeechSynthesizer *synth = (__bridge AVSpeechSynthesizer *)handle;
53+
[synth pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
54+
} @catch (NSException *e) {}
55+
}
56+
57+
void avsynth_resume(void *handle) {
58+
if (!handle) return;
59+
@try {
60+
AVSpeechSynthesizer *synth = (__bridge AVSpeechSynthesizer *)handle;
61+
[synth continueSpeaking];
62+
} @catch (NSException *e) {}
63+
}
64+
65+
int avsynth_voice_count(void *handle) {
66+
if (!handle) return 0;
67+
@try {
68+
NSArray<AVSpeechSynthesisVoice *> *voices = [AVSpeechSynthesisVoice speechVoices];
69+
return (int)[voices count];
70+
} @catch (NSException *e) {
71+
return 0;
72+
}
73+
}
74+
75+
int avsynth_get_voice(void *handle, int index,
76+
char *id_buf, int id_buf_len,
77+
char *name_buf, int name_buf_len,
78+
char *lang_buf, int lang_buf_len) {
79+
if (!handle) return -1;
80+
@try {
81+
NSArray<AVSpeechSynthesisVoice *> *voices = [AVSpeechSynthesisVoice speechVoices];
82+
if (index < 0 || index >= (int)[voices count]) return -1;
83+
AVSpeechSynthesisVoice *voice = voices[index];
84+
85+
NSString *identifier = voice.identifier ?: @"";
86+
NSString *name = voice.name ?: @"";
87+
NSString *language = voice.language ?: @"";
88+
89+
strncpy(id_buf, [identifier UTF8String], id_buf_len - 1);
90+
id_buf[id_buf_len - 1] = '\0';
91+
strncpy(name_buf, [name UTF8String], name_buf_len - 1);
92+
name_buf[name_buf_len - 1] = '\0';
93+
strncpy(lang_buf, [language UTF8String], lang_buf_len - 1);
94+
lang_buf[lang_buf_len - 1] = '\0';
95+
return 0;
96+
} @catch (NSException *e) {
97+
return -1;
98+
}
99+
}

0 commit comments

Comments
 (0)