Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e6b59cd
build: update ios libs
barhanc Apr 14, 2026
717c16c
Link phonemis with Android build
IgorSwat Apr 20, 2026
8c74d42
Link phonemis with iOS build
IgorSwat Apr 20, 2026
3609385
Adjust typescript API to new tts structure
IgorSwat Apr 21, 2026
b4a27e3
Fix model picker & adjust to new Phonemis API
IgorSwat Apr 23, 2026
ad3c76a
Add spanish
IgorSwat Apr 23, 2026
5273507
Add italian
IgorSwat Apr 23, 2026
847d352
Add basic polish, portugese and hindi
IgorSwat Apr 23, 2026
b43b75b
Partitioner refactor
IgorSwat Apr 27, 2026
7614cca
Adjust Kokoro API to new Partitioner
IgorSwat Apr 28, 2026
eca179e
Adjust native/JS API
IgorSwat Apr 28, 2026
249440d
Native side refactor
IgorSwat Apr 29, 2026
dd297d6
Implement dynamic phonemization
IgorSwat Apr 29, 2026
088dfd5
Silence tsconfig warnings
IgorSwat Apr 29, 2026
b9d736a
Introduce finetuned Kokoro
IgorSwat May 4, 2026
4b0e928
Add audio volume up
IgorSwat May 5, 2026
c3b4d9f
Improve audio trimming algorithm
IgorSwat May 6, 2026
c356673
Change the typescript API to allow custom model weights bundled with …
IgorSwat May 8, 2026
7070c4e
Fix audio api error
IgorSwat May 8, 2026
06e42b2
Bump audio-api
IgorSwat May 14, 2026
fe67c65
Add german voice
IgorSwat May 18, 2026
995a70d
Apply review suggestions
IgorSwat May 18, 2026
c2c211c
Fix hindi phonemization
IgorSwat May 18, 2026
a3c38d3
Update docs
IgorSwat May 19, 2026
3135467
Fix broken docs links: ResourceSource and TextToSpeechModelSources ar…
msluszniak May 19, 2026
1ad23ea
Update t2s tests
IgorSwat May 19, 2026
38340f6
Merge branch '@is/multilingual-tts' of https://github.com/software-ma…
IgorSwat May 19, 2026
887ad1b
Fix native test suite drift
msluszniak May 19, 2026
e173e9d
Fix audio index out of bounds bug
IgorSwat May 19, 2026
065c264
Merge branch '@is/multilingual-tts' of https://github.com/software-ma…
IgorSwat May 19, 2026
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
6 changes: 6 additions & 0 deletions .cspell-wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,9 @@ Deinitialize
fastsam
promptable
topk
phonemize
phonemization
Siwis
SIWIS
Mateusz
MATEUSZ
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
[submodule "third-party/googletest"]
path = third-party/googletest
url = https://github.com/google/googletest.git
[submodule "packages/react-native-executorch/third-party/common/phonemis"]
path = packages/react-native-executorch/third-party/common/phonemis
url = https://github.com/IgorSwat/Phonemis
branch = main
109 changes: 62 additions & 47 deletions apps/speech/components/ModelPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { useEffect, useRef, useState } from 'react';
import {
Dimensions,
Modal,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
TouchableWithoutFeedback,
View,
} from 'react-native';

Expand All @@ -21,7 +23,7 @@ type Props<T> = {
disabled?: boolean;
};

const DROPDOWN_MAX_HEIGHT = 200;
const DROPDOWN_MAX_HEIGHT = 300;

export function ModelPicker<T>({
models,
Expand All @@ -31,8 +33,11 @@ export function ModelPicker<T>({
disabled,
}: Props<T>) {
const [open, setOpen] = useState(false);
const [triggerHeight, setTriggerHeight] = useState(0);
const [expandUp, setExpandUp] = useState(false);
const [dropdownLayout, setDropdownLayout] = useState({
x: 0,
y: 0,
width: 0,
});
const triggerRef = useRef<React.ComponentRef<typeof TouchableOpacity>>(null);
const selected = models.find((m) => m.value === selectedModel);

Expand All @@ -50,23 +55,22 @@ export function ModelPicker<T>({
(
_x: number,
_y: number,
_width: number,
width: number,
height: number,
_pageX: number,
pageX: number,
pageY: number
) => {
setTriggerHeight(height);
const spaceBelow = Dimensions.get('window').height - (pageY + height);
setExpandUp(spaceBelow < DROPDOWN_MAX_HEIGHT);
const y =
spaceBelow >= DROPDOWN_MAX_HEIGHT
? pageY + height + 2
: pageY - Math.min(DROPDOWN_MAX_HEIGHT, models.length * 42) - 2;
Comment thread
chmjkb marked this conversation as resolved.
setDropdownLayout({ x: pageX, y, width });
setOpen(true);
}
);
};

const dropdownPosition = expandUp
? { bottom: triggerHeight + 2 }
: { top: triggerHeight + 2 };

return (
<View style={styles.container}>
<TouchableOpacity
Expand All @@ -80,36 +84,51 @@ export function ModelPicker<T>({
<Text style={styles.chevron}>{open ? '▲' : '▼'}</Text>
</TouchableOpacity>

{open && (
<ScrollView
style={[styles.dropdown, dropdownPosition]}
nestedScrollEnabled
keyboardShouldPersistTaps="handled"
>
{models.map((item) => {
const isSelected = item.value === selectedModel;
return (
<TouchableOpacity
key={item.label}
style={[styles.option, isSelected && styles.optionSelected]}
onPress={() => {
onSelect(item.value);
setOpen(false);
}}
>
<Text
style={[
styles.optionText,
isSelected && styles.optionTextSelected,
]}
>
{item.label}
</Text>
</TouchableOpacity>
);
})}
</ScrollView>
)}
<Modal
visible={open}
transparent
animationType="none"
onRequestClose={() => setOpen(false)}
>
<TouchableWithoutFeedback onPress={() => setOpen(false)}>
<View style={StyleSheet.absoluteFill}>
<ScrollView
style={[
styles.dropdown,
{
top: dropdownLayout.y,
left: dropdownLayout.x,
width: dropdownLayout.width,
},
]}
keyboardShouldPersistTaps="handled"
>
{models.map((item) => {
const isSelected = item.value === selectedModel;
return (
<TouchableOpacity
key={item.label}
style={[styles.option, isSelected && styles.optionSelected]}
onPress={() => {
onSelect(item.value);
setOpen(false);
}}
>
<Text
style={[
styles.optionText,
isSelected && styles.optionTextSelected,
]}
>
{item.label}
</Text>
</TouchableOpacity>
);
})}
</ScrollView>
</View>
</TouchableWithoutFeedback>
</Modal>
</View>
);
}
Expand All @@ -119,7 +138,6 @@ const styles = StyleSheet.create({
marginHorizontal: 12,
marginVertical: 4,
alignSelf: 'stretch',
zIndex: 100,
},
trigger: {
flexDirection: 'row',
Expand Down Expand Up @@ -152,18 +170,15 @@ const styles = StyleSheet.create({
},
dropdown: {
position: 'absolute',
left: 0,
right: 0,
borderWidth: 1,
borderColor: '#C1C6E5',
borderRadius: 8,
backgroundColor: '#fff',
maxHeight: DROPDOWN_MAX_HEIGHT,
zIndex: 100,
elevation: 4,
elevation: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowOpacity: 0.15,
shadowRadius: 4,
},
option: {
Expand Down
2 changes: 1 addition & 1 deletion apps/speech/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"metro-config": "^0.83.0",
"react": "19.2.5",
"react-native": "0.83.4",
"react-native-audio-api": "0.12.0",
"react-native-audio-api": "0.12.2",
"react-native-device-info": "^15.0.2",
"react-native-executorch": "workspace:*",
"react-native-executorch-expo-resource-fetcher": "workspace:*",
Expand Down
8 changes: 2 additions & 6 deletions apps/speech/screens/Quiz.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import Animated, {
} from 'react-native-reanimated';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import {
KOKORO_MEDIUM,
KOKORO_VOICE_AM_SANTA,
KOKORO_AMERICAN_ENGLISH_MALE_SANTA,
useTextToSpeech,
} from 'react-native-executorch';
import {
Expand Down Expand Up @@ -60,10 +59,7 @@ const createAudioBufferFromVector = (

export const Quiz = ({ onBack }: { onBack: () => void }) => {
// --- Hooks & State ---
const model = useTextToSpeech({
model: KOKORO_MEDIUM,
voice: KOKORO_VOICE_AM_SANTA,
});
const model = useTextToSpeech(KOKORO_AMERICAN_ENGLISH_MALE_SANTA);

const [shuffledQuestions] = useState(() => shuffleArray(QUESTIONS));
const [currentIndex, setCurrentIndex] = useState(0);
Expand Down
8 changes: 2 additions & 6 deletions apps/speech/screens/TextToSpeechLLMScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import SWMIcon from '../assets/swm_icon.svg';
import {
useLLM,
useTextToSpeech,
KOKORO_MEDIUM,
KOKORO_VOICE_AF_HEART,
KOKORO_AMERICAN_ENGLISH_FEMALE_HEART,
LLAMA3_2_1B_QLORA,
} from 'react-native-executorch';
import {
Expand Down Expand Up @@ -54,10 +53,7 @@ export const TextToSpeechLLMScreen = ({ onBack }: TextToSpeechLLMProps) => {
const [displayText, setDisplayText] = useState('');
const [isTtsStreaming, setIsTtsStreaming] = useState(false);
const llm = useLLM({ model: LLAMA3_2_1B_QLORA });
const tts = useTextToSpeech({
model: KOKORO_MEDIUM,
voice: KOKORO_VOICE_AF_HEART,
});
const tts = useTextToSpeech(KOKORO_AMERICAN_ENGLISH_FEMALE_HEART);

const processedLengthRef = useRef(0);
const audioContextRef = useRef<AudioContext | null>(null);
Expand Down
Loading
Loading