Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ElthonJohan/comunitea/llms.txt

Use this file to discover all available pages before exploring further.

Every pictogram on the ComuniTEA board speaks. When a child taps a pictogram, the app resolves the audio through a three-tier priority chain: first a bundled MP3 file pre-packaged with the app (zero latency, no network required), then a neural text-to-speech call to ElevenLabs via a Supabase Edge Function (high-quality online synthesis), and finally the Expo Speech system TTS as a last-resort fallback. Voice settings control which neural voice profile is active — female or male — and that choice propagates to all three tiers, ensuring a consistent, familiar voice whether the device is online or not.

Voice Profiles

ComuniTEA exposes two neural voice profiles via the VoiceProfile type, defined in constants/AudioAssets.ts:
export type VoiceProfile = 'femenina' | 'masculina';
ProfileVoiceProfile valueDescription
Female voicefemeninaAdult female tone — the default profile
Male voicemasculinaAdult male tone
Both profiles have a complete, independently recorded set of bundled MP3 clips and distinct ElevenLabs voice IDs for neural synthesis.

Voice Selection Screen

The Voice Selection screen (app/voice-selection.tsx) is the very first interactive screen a new user sees — it appears before the onboarding wizard on first launch. It can also be accessed later from Settings → General → Voz de pictogramas. Selecting a profile calls setVoice(voiceType) and writes the preference to AsyncStorage under STORAGE_KEYS.VOICE_PREFERENCE:
const handleSelection = async (voiceType: VoiceProfile) => {
    setVoice(voiceType);
    await AsyncStorage.setItem(STORAGE_KEYS.VOICE_PREFERENCE, voiceType);
    // ... navigate to onboarding or tutorial
};
After saving, the app navigates to the onboarding flow (if not yet completed) or directly to the communication board.

Audio Resolution Priority Order

When the useSpeech() hook is asked to play a pictogram, it follows this resolution order:
1

Bundled MP3 file (local asset)

useSpeech calls getAudioAssets(voice) to obtain the asset map for the active VoiceProfile. If a module ID exists for the given pictogram id, it resolves the local file path with bundledModuleToPlayableUri() and plays it immediately using expo-audio’s AudioPlayer. No network call is made.
const audioAssets = getAudioAssets(voice);
if (id && audioAssets[id]) {
    const uri = await bundledModuleToPlayableUri(audioAssets[id] as number);
    const newSound = createAudioPlayer({ uri });
    await playAudioPlayerUntilDone(newSound, 12_000);
    return;
}
2

ElevenLabs TTS via Supabase Edge Function

If no bundled clip exists for the given text/ID — for example, a custom pictogram the caregiver created — playWithElevenLabs() is called. It posts to the elevenlabs-tts Supabase Edge Function with the text, the voice ID, and the current TTS speed from parental settings:
const edgeUrl = `${supabaseUrl}/functions/v1/elevenlabs-tts`;
const response = await fetch(edgeUrl, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${token}`,
    },
    body: JSON.stringify({ text, voiceId, speed: ttsSpeed }),
});
The Edge Function returns raw MP3 audio bytes. The hook writes them to a temporary file in documentDirectory and plays them with expo-audio. The temp file is deleted immediately after playback.
3

Expo Speech system TTS fallback

If the device has no internet connection (isConnected === false) or if the ElevenLabs request fails (non-200 status or network error), fallbackSpeech() is called. This uses expo-speech with the system TTS engine:
const fallbackSpeech = (text: string, onDone: () => void) => {
    Speech.speak(text, {
        language: 'es-MX',
        pitch: voice === 'femenina' ? 1.2 : 0.8,
        rate: 0.9 * ttsSpeed,
        onDone: () => onDone(),
        onError: () => onDone(),
    });
};
Pitch is adjusted per profile: 1.2 for femenina, 0.8 for masculina.

Bundled Audio Asset Coverage

Each voice profile ships with over 500 pre-recorded MP3 clips, organised by category inside assets/audio/{voice}/. The categories mirror the communication board structure:
FolderExamples
dashboard/comida, emociones, bano, jugar, ropa, numeros, colores
core/core-yo, core-quiero, core-no, core-gracias, core-hola
yo/tengo-hambre, tengo-sed, me-duele, estoy-cansado
emociones/feliz, triste, enojado, asustado, calma, alegria
comida/frutas/manzana, platano, fresas, naranja, mango, pina
comida/verduras/zanahoria, lechuga, tomate, brocoli, espinaca
comida/bebidas/agua, leche, jugo, chocolatada, gaseosa
ropa/polo, pantalon, zapatos, casaca, chompa, vestido
baño/inodoro, papel, ducharse, lavado, dientes
dormir/cama, pijama, luz, osito, silencio
personas/hermano, hermana, abuela, abuelo, maestra, amigo
social/hola, adios, gracias, por-favor, si, no
avanzado/Verbs, school items, agenda days, conversation starters, help phrases
Both femenina and masculina asset maps are defined identically in constants/AudioAssets.ts. getAudioAssets(profile) merges the requested profile’s map over the femenina base, so any missing entry in masculina gracefully falls back to the female recording.
export function getAudioAssets(profile: VoiceProfile = 'femenina'): Record<string, any> {
    const assets = PROFILE_MAP[profile];
    if (!assets || Object.keys(assets).length === 0) {
        return AUDIO_ASSETS_FEMENINA;
    }
    return { ...AUDIO_ASSETS_FEMENINA, ...assets };
}

VoiceContext and the useVoice() Hook

Voice preference is persisted through a Zustand store (stores/settingsStore.ts) that reads and writes from AsyncStorage using a custom voiceLegacyStorage adapter. This keeps the stored format as a plain string ('femenina' or 'masculina') for backwards compatibility.
// stores/settingsStore.ts
interface SettingsState {
    voice: VoiceType;
    hydrated: boolean;
    setVoice: (voice: VoiceType) => void;
}

export const useSettingsStore = create<SettingsState>()(
    persist(
        (set) => ({
            voice: 'femenina',
            hydrated: false,
            setVoice: (voice) => set({ voice }),
        }),
        {
            name: 'voice-legacy',
            storage: createJSONStorage(() => voiceLegacyStorage),
        },
    ),
);
Components that need the current voice profile use the useVoice() hook from lib/hooks/useVoice.ts:
import { useVoice } from '../lib/hooks/useVoice';

function MyComponent() {
    const { voice, setVoice, isLoading } = useVoice();
    // voice: 'femenina' | 'masculina'
    // isLoading: true until Zustand has rehydrated from AsyncStorage
}
isLoading is true until the store’s hydrated flag is set by onRehydrateStorage, so components can gate audio playback until the preference is confirmed.

ElevenLabs Configuration

The elevenlabs-tts Supabase Edge Function requires an ELEVENLABS_API_KEY environment variable set in the Supabase dashboard (Project → Edge Functions → Secrets). The voice IDs used for each profile are imported from lib/speakFrasePhrase.ts:
import {
    ELEVENLABS_VOICE_FRASE_FEMENINA,
    ELEVENLABS_VOICE_FRASE_MASCULINA,
} from '../../../lib/speakFrasePhrase';

const voiceId =
    voice === 'masculina'
        ? ELEVENLABS_VOICE_FRASE_MASCULINA
        : ELEVENLABS_VOICE_FRASE_FEMENINA;
The Edge Function is called with the user’s Supabase session JWT as the Authorization header. If the session has expired, useSpeech refreshes it before the call.
Offline mode: All 500+ bundled MP3 clips in assets/audio/ are packaged directly with the app binary. As long as the pictogram has a corresponding bundled asset key, audio playback works with no internet connection whatsoever. Only custom pictograms added by the caregiver — which have no pre-recorded clip — require network access for ElevenLabs TTS. When offline, those fall back to the system speech engine.

TTS Playback Speed

Voice speed is a parental setting (not a child-facing preference) stored in parental_settings.tts_speed as a numeric(3,1) between 0.5 and 2.0. Four speed presets are available from Settings → Configuración sensorial → Velocidad de la voz:
LabelMultiplier
Muy lenta0.6×
Lenta0.8×
Normal1.0× (default)
Rápida1.2×
The ttsSpeed value from useParental().settings is passed to both the ElevenLabs request body and the expo-speech rate option, so all three audio tiers honour the same speed setting.

Build docs developers (and LLMs) love