From df5341e103f10a0fc029e7ca14c9f9bb3cd6d732 Mon Sep 17 00:00:00 2001 From: Silvano Seva Date: Tue, 16 Aug 2022 10:39:48 +0200 Subject: [PATCH] Refactored voicePrompts.h, aligned function names of voice prompt API to OpenRTX coding style --- openrtx/include/core/voicePrompts.h | 176 ++++++++------- openrtx/src/core/openrtx.c | 2 +- openrtx/src/core/threads.c | 2 +- openrtx/src/core/voicePromptUtils.c | 322 ++++++++++++++-------------- openrtx/src/core/voicePrompts.c | 108 +++++----- openrtx/src/ui/ui.c | 14 +- openrtx/src/ui/ui_menu.c | 6 +- tests/unit/voice_prompts.c | 10 +- 8 files changed, 333 insertions(+), 307 deletions(-) diff --git a/openrtx/include/core/voicePrompts.h b/openrtx/include/core/voicePrompts.h index ebd9a5ca..9bf74da3 100644 --- a/openrtx/include/core/voicePrompts.h +++ b/openrtx/include/core/voicePrompts.h @@ -16,25 +16,20 @@ * You should have received a copy of the GNU General Public License * * along with this program; if not, see * ***************************************************************************/ -#ifndef voice_prompts_h_included -#define voice_prompts_h_included +#ifndef VOICEPROMPTS_H +#define VOICEPROMPTS_H #include #include -// Voice prompts are encoded using the codec2 file format used by ffmpeg -#define CODEC2_HEADER_SIZE 7 - -/* -Please note, these prompts represent spoken words or phrases which are not in -the UI string table, for example letters of the alphabet, digits, and -descriptive words not displayed in the UI. -The voice prompt data file stores these first, then after the data for these -prompts, the data for the indexed string table phrases. -*/ -/* Please note! this enum must match the order of prompts defined in the -wordlist.csv file in the voicePrompts generator project. -*/ +/** + * List of voice prompts for spoken words or phrases which are not in the UI + * string table. The voice prompt data file stores these first, then after the + * data for these prompts, the data for the indexed string table phrases. + * + * WARNING: this enum must match the order of prompts defined in the + * wordlist.csv file in the voicePrompts generator project. + */ typedef enum { PROMPT_SILENCE, // @@ -181,19 +176,12 @@ typedef enum PROMPT_CUSTOM9, // parrot PROMPT_CUSTOM10, // unused NUM_VOICE_PROMPTS, -} voicePrompt_t; +} +voicePrompt_t; -// PROMPT_VOICE_NAME is always the very last prompt after the indexed prompts -// from the strings table. -#define PROMPT_VOICE_NAME \ - (NUM_VOICE_PROMPTS + (sizeof(stringsTable_t) / sizeof(char*))) -/* -These flags govern how vpQueueString operates. -For example, when editing, it is desireable to hear spaces, capitals and -extended symbols. -When just arrowing through menus, spaces, extended symbols etc should not be -announced. -*/ +/** + * Flags controlling how vp_queueString operates. + */ typedef enum { vpAnnounceCaps = 0x01, @@ -203,30 +191,27 @@ typedef enum vpAnnounceLessCommonSymbols = 0x10, vpAnnounceASCIIValueForUnknownChars = 0x20, vpAnnouncePhoneticRendering = 0x40, -} VoicePromptFlags_T; -/* -These queuing flags determine if speech is interrupted, played -immediately, whether prompts are queued for values, etc. -They are necessary because for example if you call the announceXX functions -consecutively, it is only desireable to initially stop speech in -progress and only play after the last prompt is queued. -If however calling an announceXX function in isolation, normally any prompt in -progress should be interrupted and play should be called immediately. -At Voice level 1, changing channels in memory mode or frequencies in VFO mode -is indicated by a beep however if F1 is pressed, we will still say the current -channel name or frequency. This is accomplished by queueing but not playing a -prompt. -*/ +} +VoicePromptFlags_T; + +/** + * Queuing flags determining if speech is interrupted, played immediately, + * whether prompts are queued for values, etc. + */ typedef enum { - vpqDefault = 0, - vpqInit = 0x01, // stop any voice prompts already in progress. - vpqPlayImmediately = 0x02, // call play after queue at all levels. + vpqDefault = 0, + vpqInit = 0x01, // stop any voice prompts already in progress. + vpqPlayImmediately = 0x02, // call play after queue at all levels. vpqPlayImmediatelyAtMediumOrHigher = 0x04, vpqIncludeDescriptions = 0x08, vpqAddSeparatingSilence = 0x10 -} VoicePromptQueueFlags_T; +} +VoicePromptQueueFlags_T; +/** + * Voice prompt verbosity levels. + */ typedef enum { vpNone = 0, @@ -234,43 +219,74 @@ typedef enum vpLow, vpMedium, vpHigh -} VoicePromptVerbosity_T; +} +VoicePromptVerbosity_T; -typedef struct -{ - const char* userWord; - const voicePrompt_t vp; -} userDictEntry; +/** + * Initialise the voice prompt system and load vp table of contents. + */ +void vp_init(); -extern bool vpDataIsLoaded; -extern const uint32_t VOICE_PROMPTS_FLASH_HEADER_ADDRESS; -extern VoicePromptVerbosity_T vpLevel; -// Loads just the TOC from Flash and stores in RAM for fast access. -void vpCacheInit(void); -// Call before building the prompt sequence to clear prompt in progress. -void vpInit(void); -// This function appends an individual prompt item to the prompt queue. -// This can be a single letter, number, or a phrase. -void vpQueuePrompt(uint16_t prompt); -// This function appends the spelling of a complete string to the queue. -// It is used to pronounce strings for which we do not have a recorded voice -// prompt. -void vpQueueString(char* promptString, VoicePromptFlags_T flags); -// This function appends a signed integer to the queue. -void vpQueueInteger(int32_t value); -// This function appends a text string from the current language to the queue. -// e.g. currentLanguage->off -// These are recorded prompts which correspond to the strings in the strings -// table. -void vpQueueStringTableEntry(const char* const*); +/** + * Terminate the currently ongoing prompt and shutdown the voice prompt system. + */ +void vp_terminate(); -void vpPlay(void); // Starts prompt playback -void vpTick(); // called to process vp data being decoded. -extern bool vpIsPlaying(void); -bool vpHasDataToPlay(void); -void vpTerminate(void); -bool vpCheckHeader(uint32_t* bufferAddress); -int vp_open(char *vp_name); -void vp_close(); +/** + * Clear the currently in-progress prompt, to be called before building a new + * voice prompt sequence. + */ +void vp_clearCurrPrompt(); + +/** + * Append an individual prompt item to the prompt queue. + * + * @param prompt: voice prompt ID. + */ +void vp_queuePrompt(const uint16_t prompt); + +/** + * Append the spelling of a complete string to the queue. + * + * @param promptString: string to be spelled. + * @param flags: control flags. + */ +void vp_queueString(char* promptString, VoicePromptFlags_T flags); + +/** + * Append a signed integer to the queue. + * + * @param value: value to be appended. + */ +void vp_queueInteger(const int32_t value); + +/** + * Append a text string from the current language to the queue. + */ +void vp_queueStringTableEntry(const char* const* stringTableStringPtr); + +/** + * Start prompt playback. + */ +void vp_play(); + +/** + * Function handling vp data decoding, to be called periodically. + */ +void vp_tick(); + +/** + * Check if a voice prompt is being played. + * + * @return true if a voice prompt is being played. + */ +bool vp_isPlaying(); + +/** + * Check if the voice prompt sequence is empty. + * + * @return true if the voice prompt sequence is empty. + */ +bool vp_sequenceNotEmpty(); #endif diff --git a/openrtx/src/core/openrtx.c b/openrtx/src/core/openrtx.c index 7020e446..c0ee4adb 100644 --- a/openrtx/src/core/openrtx.c +++ b/openrtx/src/core/openrtx.c @@ -44,7 +44,7 @@ void openrtx_init() gfx_init(); // Initialize display and graphics driver kbd_init(); // Initialize keyboard driver ui_init(); // Initialize user interface - vpCacheInit(); // Checks to see if voice prompts are loaded and initializes them + vp_init(); // Initialize voice prompts #ifdef SCREEN_CONTRAST display_setContrast(state.settings.contrast); #endif diff --git a/openrtx/src/core/threads.c b/openrtx/src/core/threads.c index b059a870..753b054f 100644 --- a/openrtx/src/core/threads.c +++ b/openrtx/src/core/threads.c @@ -75,7 +75,7 @@ void *ui_threadFunc(void *arg) ui_saveState(); // Save local state copy pthread_mutex_unlock(&state_mutex); // Unlock r/w access to radio state - vpTick(); // continue playing voice prompts in progress if any. + vp_tick(); // continue playing voice prompts in progress if any. // If synchronization needed take mutex and update RTX configuration if(sync_rtx) diff --git a/openrtx/src/core/voicePromptUtils.c b/openrtx/src/core/voicePromptUtils.c index 220577e8..c58373ff 100644 --- a/openrtx/src/core/voicePromptUtils.c +++ b/openrtx/src/core/voicePromptUtils.c @@ -31,26 +31,26 @@ #include "interfaces/cps_io.h" -static void vpInitIfNeeded(VoicePromptQueueFlags_T flags) +static void vp_clearCurrPromptIfNeeded(VoicePromptQueueFlags_T flags) { - if (flags & vpqInit) vpInit(); + if (flags & vpqInit) vp_clearCurrPrompt(); } -static void vpPlayIfNeeded(VoicePromptQueueFlags_T flags) +static void vp_playIfNeeded(VoicePromptQueueFlags_T flags) { uint8_t vpLevel = state.settings.vpLevel; if ((flags & vpqPlayImmediately) || ((flags & vpqPlayImmediatelyAtMediumOrHigher) && (vpLevel >= vpMedium))) - vpPlay(); + vp_play(); } static void addSilenceIfNeeded(VoicePromptQueueFlags_T flags) { if ((flags & vpqAddSeparatingSilence) == 0) return; - vpQueuePrompt(PROMPT_SILENCE); - vpQueuePrompt(PROMPT_SILENCE); + vp_queuePrompt(PROMPT_SILENCE); + vp_queuePrompt(PROMPT_SILENCE); } static void removeUnnecessaryZerosFromVoicePrompts(char* str) @@ -69,32 +69,32 @@ static void removeUnnecessaryZerosFromVoicePrompts(char* str) void announceVFO() { - vpInit(); + vp_clearCurrPrompt(); - vpQueuePrompt(PROMPT_VFO); + vp_queuePrompt(PROMPT_VFO); - vpPlay(); + vp_play(); } void announceChannelName(channel_t* channel, uint16_t channelIndex, VoicePromptQueueFlags_T flags) { - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); if (flags & vpqIncludeDescriptions) { - vpQueuePrompt(PROMPT_CHANNEL); + vp_queuePrompt(PROMPT_CHANNEL); } - vpQueueInteger(channelIndex); + vp_queueInteger(channelIndex); // Only queue the name if it is not the same as the raw number. // Otherwise the radio will say channel 1 1 for channel 1. char numAsStr[16] = "\0"; snprintf(numAsStr, 16, "%d", channelIndex); if (strcmp(numAsStr, channel->name) != 0) - vpQueueString(channel->name, vpAnnounceCommonSymbols); + vp_queueString(channel->name, vpAnnounceCommonSymbols); - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void vpQueueFrequency(freq_t freq) @@ -106,77 +106,77 @@ void vpQueueFrequency(freq_t freq) snprintf(buffer, 16, "%d.%05d", mhz, khz); removeUnnecessaryZerosFromVoicePrompts(buffer); - vpQueueString(buffer, vpAnnounceCommonSymbols); + vp_queueString(buffer, vpAnnounceCommonSymbols); - vpQueuePrompt(PROMPT_MEGAHERTZ); + vp_queuePrompt(PROMPT_MEGAHERTZ); } void announceFrequencies(freq_t rx, freq_t tx, VoicePromptQueueFlags_T flags) { - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); // If rx and tx frequencies differ, announce both, otherwise just one if (rx == tx) vpQueueFrequency(rx); else { - vpQueuePrompt(PROMPT_RECEIVE); + vp_queuePrompt(PROMPT_RECEIVE); vpQueueFrequency(rx); - vpQueuePrompt(PROMPT_TRANSMIT); + vp_queuePrompt(PROMPT_TRANSMIT); vpQueueFrequency(tx); } - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void announceRadioMode(uint8_t mode, VoicePromptQueueFlags_T flags) { - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); - if (flags & vpqIncludeDescriptions) vpQueuePrompt(PROMPT_MODE); + if (flags & vpqIncludeDescriptions) vp_queuePrompt(PROMPT_MODE); switch (mode) { case OPMODE_DMR: - vpQueueStringTableEntry(¤tLanguage->dmr); + vp_queueStringTableEntry(¤tLanguage->dmr); break; case OPMODE_FM: - vpQueueStringTableEntry(¤tLanguage->fm); + vp_queueStringTableEntry(¤tLanguage->fm); break; case OPMODE_M17: - vpQueueStringTableEntry(¤tLanguage->m17); + vp_queueStringTableEntry(¤tLanguage->m17); break; } - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void announceBandwidth(uint8_t bandwidth, VoicePromptQueueFlags_T flags) { if (bandwidth > BW_25) bandwidth = BW_25; // Should probably never happen! - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); - if (flags & vpqIncludeDescriptions) vpQueuePrompt(PROMPT_BANDWIDTH); + if (flags & vpqIncludeDescriptions) vp_queuePrompt(PROMPT_BANDWIDTH); char* bandwidths[] = {"12.5", "20", "25"}; - vpQueueString(bandwidths[bandwidth], vpAnnounceCommonSymbols); - vpQueuePrompt(PROMPT_KILOHERTZ); + vp_queueString(bandwidths[bandwidth], vpAnnounceCommonSymbols); + vp_queuePrompt(PROMPT_KILOHERTZ); - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void anouncePower(float power, VoicePromptQueueFlags_T flags) { - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); char buffer[16] = "\0"; - if (flags & vpqIncludeDescriptions) vpQueuePrompt(PROMPT_POWER); + if (flags & vpqIncludeDescriptions) vp_queuePrompt(PROMPT_POWER); snprintf(buffer, 16, "%1.1f", power); - vpQueueString(buffer, vpAnnounceCommonSymbols); - vpQueuePrompt(PROMPT_WATTS); + vp_queueString(buffer, vpAnnounceCommonSymbols); + vp_queuePrompt(PROMPT_WATTS); - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void announceChannelSummary(channel_t* channel, uint16_t channelIndex, @@ -184,7 +184,7 @@ void announceChannelSummary(channel_t* channel, uint16_t channelIndex, { if (!channel) return; - vpInit(); + vp_clearCurrPrompt(); VoicePromptQueueFlags_T localFlags = vpqAddSeparatingSilence; // Force on the descriptions for level 3. @@ -192,7 +192,7 @@ void announceChannelSummary(channel_t* channel, uint16_t channelIndex, // If VFO mode, announce VFO. // channelIndex will be 0 if called from VFO mode. if (channelIndex == 0) - vpQueuePrompt(PROMPT_VFO); + vp_queuePrompt(PROMPT_VFO); else announceChannelName(channel, channelIndex, localFlags); announceFrequencies(channel->rx_frequency, channel->tx_frequency, @@ -233,7 +233,7 @@ void announceChannelSummary(channel_t* channel, uint16_t channelIndex, if (channelIndex > 0) // i.e. not called from VFO. announceBank(bank, localFlags); - vpPlay(); + vp_play(); } void AnnounceInputChar(char ch) @@ -241,72 +241,72 @@ void AnnounceInputChar(char ch) char buf[2] = "\0"; buf[0] = ch; - vpInit(); + vp_clearCurrPrompt(); uint8_t flags = vpAnnounceCaps | vpAnnounceSpace | vpAnnounceCommonSymbols | vpAnnounceLessCommonSymbols; - vpQueueString(buf, flags); + vp_queueString(buf, flags); - vpPlay(); + vp_play(); } void announceInputReceiveOrTransmit(bool tx, VoicePromptQueueFlags_T flags) { - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); if (tx) - vpQueuePrompt(PROMPT_TRANSMIT); + vp_queuePrompt(PROMPT_TRANSMIT); else - vpQueuePrompt(PROMPT_RECEIVE); + vp_queuePrompt(PROMPT_RECEIVE); - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void ReplayLastPrompt() { - if (vpIsPlaying()) - vpTerminate(); + if (vp_isPlaying()) + vp_terminate(); else - vpPlay(); + vp_play(); } void announceError(VoicePromptQueueFlags_T flags) { - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); - vpQueueStringTableEntry(¤tLanguage->error); + vp_queueStringTableEntry(¤tLanguage->error); - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void announceText(char* text, VoicePromptQueueFlags_T flags) { if (!text || !*text) return; - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); // See if we have a prompt for this string. int offset = GetEnglishStringTableOffset(text); if (offset != -1) - vpQueueStringTableEntry( + vp_queueStringTableEntry( (const char* const*)(¤tLanguage->languageName + offset)); else // Just spell it out - vpQueueString(text, vpAnnounceCommonSymbols); + vp_queueString(text, vpAnnounceCommonSymbols); - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void announceCTCSS(bool rxToneEnabled, uint8_t rxTone, bool txToneEnabled, uint8_t txTone, VoicePromptQueueFlags_T flags) { - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); if (!rxToneEnabled && !txToneEnabled) { - vpQueuePrompt(PROMPT_TONE); - vpQueueStringTableEntry(¤tLanguage->off); - vpPlayIfNeeded(flags); + vp_queuePrompt(PROMPT_TONE); + vp_queueStringTableEntry(¤tLanguage->off); + vp_playIfNeeded(flags); return; } @@ -315,68 +315,68 @@ void announceCTCSS(bool rxToneEnabled, uint8_t rxTone, bool txToneEnabled, // If the rx and tx tones are the same and both are enabled, just say Tone. if ((rxToneEnabled && txToneEnabled) && (rxTone == txTone)) { - vpQueuePrompt(PROMPT_TONE); + vp_queuePrompt(PROMPT_TONE); snprintf(buffer, 16, "%3.1f", ctcss_tone[rxTone] / 10.0f); - vpQueueString(buffer, vpqDefault); - vpQueuePrompt(PROMPT_HERTZ); - vpPlayIfNeeded(flags); + vp_queueString(buffer, vpqDefault); + vp_queuePrompt(PROMPT_HERTZ); + vp_playIfNeeded(flags); return; } // Speak the individual rx and tx tones. if (rxToneEnabled) { - vpQueuePrompt(PROMPT_RECEIVE); - vpQueuePrompt(PROMPT_TONE); + vp_queuePrompt(PROMPT_RECEIVE); + vp_queuePrompt(PROMPT_TONE); snprintf(buffer, 16, "%3.1f", ctcss_tone[rxTone] / 10.0f); - vpQueueString(buffer, vpqDefault); - vpQueuePrompt(PROMPT_HERTZ); + vp_queueString(buffer, vpqDefault); + vp_queuePrompt(PROMPT_HERTZ); } if (txToneEnabled) { - vpQueuePrompt(PROMPT_TRANSMIT); - vpQueuePrompt(PROMPT_TONE); + vp_queuePrompt(PROMPT_TRANSMIT); + vp_queuePrompt(PROMPT_TONE); snprintf(buffer, 16, "%3.1f", ctcss_tone[txTone] / 10.0f); - vpQueueString(buffer, vpqDefault); - vpQueuePrompt(PROMPT_HERTZ); + vp_queueString(buffer, vpqDefault); + vp_queuePrompt(PROMPT_HERTZ); } - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void announceBrightness(uint8_t brightness, VoicePromptQueueFlags_T flags) { - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); if (flags & vpqIncludeDescriptions) - vpQueueStringTableEntry(¤tLanguage->brightness); + vp_queueStringTableEntry(¤tLanguage->brightness); - vpQueueInteger(brightness); + vp_queueInteger(brightness); - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void announceSquelch(uint8_t squelch, VoicePromptQueueFlags_T flags) { - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); - if (flags & vpqIncludeDescriptions) vpQueuePrompt(PROMPT_SQUELCH); + if (flags & vpqIncludeDescriptions) vp_queuePrompt(PROMPT_SQUELCH); - vpQueueInteger(squelch); + vp_queueInteger(squelch); - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void announceContact(contact_t* contact, VoicePromptQueueFlags_T flags) { if (!contact) return; - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); - if (flags & vpqIncludeDescriptions) vpQueuePrompt(PROMPT_CONTACT); + if (flags & vpqIncludeDescriptions) vp_queuePrompt(PROMPT_CONTACT); - if (contact->name[0]) vpQueueString(contact->name, vpAnnounceCommonSymbols); + if (contact->name[0]) vp_queueString(contact->name, vpAnnounceCommonSymbols); - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void announceContactWithIndex(uint16_t index, VoicePromptQueueFlags_T flags) @@ -392,69 +392,69 @@ void announceContactWithIndex(uint16_t index, VoicePromptQueueFlags_T flags) void announceTimeslot(uint8_t timeslot, VoicePromptQueueFlags_T flags) { - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); - if (flags & vpqIncludeDescriptions) vpQueuePrompt(PROMPT_TIMESLOT); + if (flags & vpqIncludeDescriptions) vp_queuePrompt(PROMPT_TIMESLOT); - vpQueueInteger(timeslot); + vp_queueInteger(timeslot); - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void announceColorCode(uint8_t rxColorCode, uint8_t txColorCode, VoicePromptQueueFlags_T flags) { - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); - if (flags & vpqIncludeDescriptions) vpQueuePrompt(PROMPT_COLORCODE); + if (flags & vpqIncludeDescriptions) vp_queuePrompt(PROMPT_COLORCODE); if (rxColorCode == txColorCode) { - vpQueueInteger(rxColorCode); + vp_queueInteger(rxColorCode); } else { - vpQueuePrompt(PROMPT_RECEIVE); - vpQueueInteger(rxColorCode); - vpQueuePrompt(PROMPT_TRANSMIT); - vpQueueInteger(txColorCode); + vp_queuePrompt(PROMPT_RECEIVE); + vp_queueInteger(rxColorCode); + vp_queuePrompt(PROMPT_TRANSMIT); + vp_queueInteger(txColorCode); } - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void announceBank(uint16_t bank, VoicePromptQueueFlags_T flags) { - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); if (flags & vpqIncludeDescriptions) - vpQueueStringTableEntry(¤tLanguage->banks); + vp_queueStringTableEntry(¤tLanguage->banks); if (state.bank_enabled) { bankHdr_t bank_hdr = {0}; cps_readBankHeader(&bank_hdr, bank); - vpQueueString(bank_hdr.name, vpAnnounceCommonSymbols); + vp_queueString(bank_hdr.name, vpAnnounceCommonSymbols); } else - vpQueueStringTableEntry(¤tLanguage->allChannels); + vp_queueStringTableEntry(¤tLanguage->allChannels); - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } void announceM17Info(channel_t* channel, VoicePromptQueueFlags_T flags) { if (!channel) return; - vpInitIfNeeded(flags); + vp_clearCurrPromptIfNeeded(flags); if (state.m17_data.dst_addr[0]) { - if (flags & vpqIncludeDescriptions) vpQueuePrompt(PROMPT_DEST_ID); - vpQueueString(state.m17_data.dst_addr, vpAnnounceCommonSymbols); + if (flags & vpqIncludeDescriptions) vp_queuePrompt(PROMPT_DEST_ID); + vp_queueString(state.m17_data.dst_addr, vpAnnounceCommonSymbols); } else if (channel->m17.contact_index) announceContactWithIndex(channel->m17.contact_index, flags); - vpPlayIfNeeded(flags); + vp_playIfNeeded(flags); } #ifdef GPS_PRESENT @@ -462,33 +462,33 @@ void announceGPSInfo() { if (!state.settings.gps_enabled) return; - vpInit(); + vp_clearCurrPrompt(); VoicePromptQueueFlags_T flags = vpqIncludeDescriptions | vpqAddSeparatingSilence; - vpQueueStringTableEntry(¤tLanguage->gps); + vp_queueStringTableEntry(¤tLanguage->gps); switch (state.gps_data.fix_quality) { case 0: - vpQueueStringTableEntry(¤tLanguage->noFix); + vp_queueStringTableEntry(¤tLanguage->noFix); break; case 1: - vpQueueString("SPS", vpAnnounceCommonSymbols); + vp_queueString("SPS", vpAnnounceCommonSymbols); break; case 2: - vpQueueString("DGPS", vpAnnounceCommonSymbols); + vp_queueString("DGPS", vpAnnounceCommonSymbols); break; case 3: - vpQueueString("PPS", vpAnnounceCommonSymbols); + vp_queueString("PPS", vpAnnounceCommonSymbols); break; case 6: - vpQueueStringTableEntry(¤tLanguage->fixLost); + vp_queueStringTableEntry(¤tLanguage->fixLost); break; default: - vpQueueStringTableEntry(¤tLanguage->error); + vp_queueStringTableEntry(¤tLanguage->error); - vpPlay(); + vp_play(); return; } @@ -497,113 +497,113 @@ void announceGPSInfo() switch (state.gps_data.fix_type) { case 2: - vpQueueString("2D", vpAnnounceCommonSymbols); + vp_queueString("2D", vpAnnounceCommonSymbols); break; case 3: - vpQueueString("3D", vpAnnounceCommonSymbols); + vp_queueString("3D", vpAnnounceCommonSymbols); break; } addSilenceIfNeeded(flags); // lat/long char buffer[16] = "\0"; - vpQueuePrompt(PROMPT_LATITUDE); + vp_queuePrompt(PROMPT_LATITUDE); snprintf(buffer, 16, "%8.6f", state.gps_data.latitude); - vpQueueString(buffer, vpAnnounceCommonSymbols); - vpQueuePrompt(PROMPT_NORTH); + vp_queueString(buffer, vpAnnounceCommonSymbols); + vp_queuePrompt(PROMPT_NORTH); float longitude = state.gps_data.longitude; voicePrompt_t direction = (longitude < 0) ? PROMPT_WEST : PROMPT_EAST; longitude = (longitude < 0) ? -longitude : longitude; snprintf(buffer, 16, "%8.6f", longitude); - vpQueuePrompt(PROMPT_LONGITUDE); - vpQueueString(buffer, vpAnnounceCommonSymbols); - vpQueuePrompt(direction); + vp_queuePrompt(PROMPT_LONGITUDE); + vp_queueString(buffer, vpAnnounceCommonSymbols); + vp_queuePrompt(direction); addSilenceIfNeeded(flags); // speed/altitude: - vpQueuePrompt(PROMPT_SPEED); + vp_queuePrompt(PROMPT_SPEED); snprintf(buffer, 16, "%4.1fkm/h", state.gps_data.speed); - vpQueueString(buffer, vpAnnounceCommonSymbols); - vpQueuePrompt(PROMPT_ALTITUDE); + vp_queueString(buffer, vpAnnounceCommonSymbols); + vp_queuePrompt(PROMPT_ALTITUDE); snprintf(buffer, 16, "%4.1fm", state.gps_data.altitude); - vpQueueString(buffer, vpAnnounceCommonSymbols); + vp_queueString(buffer, vpAnnounceCommonSymbols); addSilenceIfNeeded(flags); - vpQueuePrompt(PROMPT_COMPASS); + vp_queuePrompt(PROMPT_COMPASS); snprintf(buffer, 16, "%3.1f", state.gps_data.tmg_true); - vpQueueString(buffer, vpAnnounceCommonSymbols); - vpQueuePrompt(PROMPT_DEGREES); + vp_queueString(buffer, vpAnnounceCommonSymbols); + vp_queuePrompt(PROMPT_DEGREES); addSilenceIfNeeded(flags); - vpQueuePrompt(PROMPT_SATELLITES); - vpQueueInteger(__builtin_popcount(state.gps_data.active_sats)); + vp_queuePrompt(PROMPT_SATELLITES); + vp_queueInteger(__builtin_popcount(state.gps_data.active_sats)); - vpPlay(); + vp_play(); } #endif // GPS_PRESENT void announceAboutScreen() { - vpInit(); + vp_clearCurrPrompt(); - vpQueueStringTableEntry(¤tLanguage->openRTX); + vp_queueStringTableEntry(¤tLanguage->openRTX); - vpQueueStringTableEntry(¤tLanguage->Niccolo); - vpQueueStringTableEntry(¤tLanguage->Silvano); - vpQueueStringTableEntry(¤tLanguage->Federico); - vpQueueStringTableEntry(¤tLanguage->Fred); - vpQueueStringTableEntry(¤tLanguage->Joseph); + vp_queueStringTableEntry(¤tLanguage->Niccolo); + vp_queueStringTableEntry(¤tLanguage->Silvano); + vp_queueStringTableEntry(¤tLanguage->Federico); + vp_queueStringTableEntry(¤tLanguage->Fred); + vp_queueStringTableEntry(¤tLanguage->Joseph); - vpPlay(); + vp_play(); } void announceBackupScreen() { - vpInit(); + vp_clearCurrPrompt(); - vpQueueStringTableEntry(¤tLanguage->flashBackup); + vp_queueStringTableEntry(¤tLanguage->flashBackup); - vpQueueStringTableEntry(¤tLanguage->connectToRTXTool); - vpQueueStringTableEntry(¤tLanguage->toBackupFlashAnd); - vpQueueStringTableEntry(¤tLanguage->pressPTTToStart); - vpQueuePrompt(PROMPT_VP_UNAVAILABLE); + vp_queueStringTableEntry(¤tLanguage->connectToRTXTool); + vp_queueStringTableEntry(¤tLanguage->toBackupFlashAnd); + vp_queueStringTableEntry(¤tLanguage->pressPTTToStart); + vp_queuePrompt(PROMPT_VP_UNAVAILABLE); - vpPlay(); + vp_play(); } void announceRestoreScreen() { - vpInit(); + vp_clearCurrPrompt(); - vpQueueStringTableEntry(¤tLanguage->flashRestore); + vp_queueStringTableEntry(¤tLanguage->flashRestore); - vpQueueStringTableEntry(¤tLanguage->connectToRTXTool); - vpQueueStringTableEntry(¤tLanguage->toRestoreFlashAnd); - vpQueueStringTableEntry(¤tLanguage->pressPTTToStart); - vpQueuePrompt(PROMPT_VP_UNAVAILABLE); + vp_queueStringTableEntry(¤tLanguage->connectToRTXTool); + vp_queueStringTableEntry(¤tLanguage->toRestoreFlashAnd); + vp_queueStringTableEntry(¤tLanguage->pressPTTToStart); + vp_queuePrompt(PROMPT_VP_UNAVAILABLE); - vpPlay(); + vp_play(); } #ifdef RTC_PRESENT void announceSettingsTimeDate() { - vpInit(); + vp_clearCurrPrompt(); - vpQueueStringTableEntry(¤tLanguage->timeAndDate); + vp_queueStringTableEntry(¤tLanguage->timeAndDate); datetime_t local_time = utcToLocalTime(state.time, state.settings.utc_timezone); char buffer[16] = "\0"; snprintf(buffer, 16, "%02d/%02d/%02d", local_time.date, local_time.month, local_time.year); - vpQueueString(buffer, + vp_queueString(buffer, (vpAnnounceCommonSymbols | vpAnnounceLessCommonSymbols)); snprintf(buffer, 16, "%02d:%02d:%02d", local_time.hour, local_time.minute, local_time.second); - vpQueueString(buffer, + vp_queueString(buffer, (vpAnnounceCommonSymbols | vpAnnounceLessCommonSymbols)); - vpPlay(); + vp_play(); } #endif // RTC_PRESENT @@ -647,7 +647,7 @@ VoicePromptQueueFlags_T GetQueueFlagsForVoiceLevel() // Play immediately with descriptions unless speech is in progress. case vpHigh: flags |= vpqPlayImmediately; - if (!vpIsPlaying()) flags |= vpqIncludeDescriptions; + if (!vp_isPlaying()) flags |= vpqIncludeDescriptions; break; } diff --git a/openrtx/src/core/voicePrompts.c b/openrtx/src/core/voicePrompts.c index fb4d6e50..5d5b2333 100644 --- a/openrtx/src/core/voicePrompts.c +++ b/openrtx/src/core/voicePrompts.c @@ -40,8 +40,16 @@ const uint32_t VOICE_PROMPTS_DATA_VERSION = 0x1000; // v1000 OpenRTX // The length is the length in bytes of the data. static void GetCodec2Data(int offset, int length); +#define CODEC2_HEADER_SIZE 7 + static FILE *voice_prompt_file = NULL; +typedef struct +{ + const char* userWord; + const voicePrompt_t vp; +} userDictEntry; + typedef struct { uint32_t magic; @@ -50,7 +58,7 @@ typedef struct // offset into voice prompt vpc file where actual codec2 data starts. static uint32_t vpDataOffset = 0; // Each codec2 frame is 8 bytes. -// 256 x 8 bytes +// 256 x 8 bytes #define Codec2DataBufferSize 2048 bool vpDataIsLoaded = false; @@ -70,7 +78,7 @@ typedef struct uint16_t buffer[VOICE_PROMPTS_SEQUENCE_BUFFER_SIZE]; int pos; // index into above buffer. int length; // number of entries in above buffer. - int codec2DataIndex; // index into current codec2 data + int codec2DataIndex; // index into current codec2 data //(buffer content sent in lots of 8 byte frames.) int codec2DataLength; // length of codec2 data for current prompt. } vpSequence_t; @@ -106,17 +114,25 @@ void vp_close() fclose(voice_prompt_file); } -void vpCacheInit(void) +bool vpCheckHeader(uint32_t* bufferAddress) +{ + voicePromptsDataHeader_t* header = (voicePromptsDataHeader_t*)bufferAddress; + + return ((header->magic == VOICE_PROMPTS_DATA_MAGIC) && + (header->version == VOICE_PROMPTS_DATA_VERSION)); +} + +void vp_init(void) { voicePromptsDataHeader_t header; vpDataOffset=0; - + if (!voice_prompt_file) vp_open(NULL); - + if (!voice_prompt_file) return; - + fseek(voice_prompt_file, 0L, SEEK_SET); fread((void*)&header, sizeof(header), 1, voice_prompt_file); @@ -141,26 +157,20 @@ void vpCacheInit(void) codec_init(); } -bool vpCheckHeader(uint32_t* bufferAddress) -{ - voicePromptsDataHeader_t* header = (voicePromptsDataHeader_t*)bufferAddress; - return ((header->magic == VOICE_PROMPTS_DATA_MAGIC) && - (header->version == VOICE_PROMPTS_DATA_VERSION)); -} static void GetCodec2Data(int offset, int length) { if (!voice_prompt_file || (vpDataOffset < (sizeof(voicePromptsDataHeader_t) + sizeof(tableOfContents)))) return; - + if ((offset < 0) || (length > Codec2DataBufferSize)) return; // Skip codec2 header fseek(voice_prompt_file, vpDataOffset+offset+CODEC2_HEADER_SIZE, SEEK_SET); fread((void*)&Codec2Data, length, 1, voice_prompt_file); - // zero buffer from length to the next multiple of 8 to avoid garbage + // zero buffer from length to the next multiple of 8 to avoid garbage // being played back, since codec2 frames are pushed in lots of 8 bytes. if ((length % 8) != 0) { @@ -169,7 +179,7 @@ static void GetCodec2Data(int offset, int length) } } -void vpTerminate(void) +void vp_terminate(void) { if (voicePromptIsActive) { @@ -182,7 +192,7 @@ void vpTerminate(void) } } -void vpInit(void) +void vp_clearCurrPrompt(void) { vpCurrentSequence.length = 0; vpCurrentSequence.pos = 0; @@ -190,13 +200,13 @@ void vpInit(void) vpCurrentSequence.codec2DataLength = 0; } -void vpQueuePrompt(uint16_t prompt) +void vp_queuePrompt(const uint16_t prompt) { if (state.settings.vpLevel < vpLow) return; if (voicePromptIsActive) { - vpInit(); + vp_clearCurrPrompt(); } if (vpCurrentSequence.length < VOICE_PROMPTS_SEQUENCE_BUFFER_SIZE) { @@ -254,13 +264,13 @@ static bool GetSymbolVPIfItShouldBeAnnounced(char symbol, } // This function spells out a string letter by letter. -void vpQueueString(char* promptString, VoicePromptFlags_T flags) +void vp_queueString(char* promptString, VoicePromptFlags_T flags) { if (state.settings.vpLevel < vpLow) return; if (voicePromptIsActive) { - vpInit(); + vp_clearCurrPrompt(); } if (state.settings.vpPhoneticSpell) flags |= vpAnnouncePhoneticRendering; @@ -270,70 +280,70 @@ void vpQueueString(char* promptString, VoicePromptFlags_T flags) voicePrompt_t vp = UserDictLookup(promptString, &advanceBy); if (vp) { - vpQueuePrompt(vp); + vp_queuePrompt(vp); promptString += advanceBy; continue; } else if ((*promptString >= '0') && (*promptString <= '9')) { - vpQueuePrompt(*promptString - '0' + PROMPT_0); + vp_queuePrompt(*promptString - '0' + PROMPT_0); } else if ((*promptString >= 'A') && (*promptString <= 'Z')) { - if (flags & vpAnnounceCaps) vpQueuePrompt(PROMPT_CAP); + if (flags & vpAnnounceCaps) vp_queuePrompt(PROMPT_CAP); if (flags & vpAnnouncePhoneticRendering) - vpQueuePrompt((*promptString - 'A') + PROMPT_A_PHONETIC); + vp_queuePrompt((*promptString - 'A') + PROMPT_A_PHONETIC); else - vpQueuePrompt(*promptString - 'A' + PROMPT_A); + vp_queuePrompt(*promptString - 'A' + PROMPT_A); } else if ((*promptString >= 'a') && (*promptString <= 'z')) { if (flags & vpAnnouncePhoneticRendering) - vpQueuePrompt((*promptString - 'a') + PROMPT_A_PHONETIC); + vp_queuePrompt((*promptString - 'a') + PROMPT_A_PHONETIC); else - vpQueuePrompt(*promptString - 'a' + PROMPT_A); + vp_queuePrompt(*promptString - 'a' + PROMPT_A); } else if ((*promptString == ' ') && (flags & vpAnnounceSpace)) { - vpQueuePrompt(PROMPT_SPACE); + vp_queuePrompt(PROMPT_SPACE); } else if (GetSymbolVPIfItShouldBeAnnounced(*promptString, flags, &vp)) { if (vp != PROMPT_SILENCE) - vpQueuePrompt(vp); + vp_queuePrompt(vp); else // announce ASCII { int32_t val = *promptString; - vpQueuePrompt(PROMPT_CHARACTER); // just the word "code" as we + vp_queuePrompt(PROMPT_CHARACTER); // just the word "code" as we // don't have character. - vpQueueInteger(val); + vp_queueInteger(val); } } else { // otherwise just add silence - vpQueuePrompt(PROMPT_SILENCE); + vp_queuePrompt(PROMPT_SILENCE); } promptString++; } - if (flags & vpqAddSeparatingSilence) vpQueuePrompt(PROMPT_SILENCE); + if (flags & vpqAddSeparatingSilence) vp_queuePrompt(PROMPT_SILENCE); } -void vpQueueInteger(int32_t value) +void vp_queueInteger(const int32_t value) { if (state.settings.vpLevel < vpLow) return; char buf[12] = {0}; // min: -2147483648, max: 2147483647 snprintf(buf, 12, "%d", value); - vpQueueString(buf, 0); + vp_queueString(buf, 0); } // This function looks up a voice prompt corresponding to a string table entry. // These are stored in the voice data after the voice prompts with no // corresponding string table entry, hence the offset calculation: // NUM_VOICE_PROMPTS + (stringTableStringPtr - currentLanguage->languageName) -void vpQueueStringTableEntry(const char* const* stringTableStringPtr) +void vp_queueStringTableEntry(const char* const* stringTableStringPtr) { if (state.settings.vpLevel < vpLow) return; @@ -341,31 +351,31 @@ void vpQueueStringTableEntry(const char* const* stringTableStringPtr) { return; } - vpQueuePrompt(NUM_VOICE_PROMPTS + 1 + + vp_queuePrompt(NUM_VOICE_PROMPTS + 1 + (stringTableStringPtr - ¤tLanguage->languageName) / sizeof(const char *)); } -void vpPlay(void) +void vp_play(void) { if (state.settings.vpLevel < vpLow) return; if (voicePromptIsActive) return; - + if (vpCurrentSequence.length <= 0) return; - + voicePromptIsActive = true; // Start the playback - + codec_startDecode(SINK_SPK); - + audio_enableAmp(); } // Call this from the main timer thread to continue voice prompt playback. -void vpTick() +void vp_tick() { if (!voicePromptIsActive) return; - + while (vpCurrentSequence.pos < vpCurrentSequence.length) {// get the codec2 data for the current prompt if needed. if (vpCurrentSequence.codec2DataLength == 0) @@ -374,16 +384,16 @@ void vpTick() vpCurrentSequence.codec2DataLength = tableOfContents[promptNumber + 1] - tableOfContents[promptNumber]; - + GetCodec2Data(tableOfContents[promptNumber], vpCurrentSequence.codec2DataLength); - + vpCurrentSequence.codec2DataIndex = 0; } // push the codec2 data in lots of 8 byte frames. while (vpCurrentSequence.codec2DataIndex < vpCurrentSequence.codec2DataLength) { if (!codec_pushFrame(Codec2Data+vpCurrentSequence.codec2DataIndex, false)) - return; // wait until there is room, perhaps next vpTick call. + return; // wait until there is room, perhaps next vp_tick call. vpCurrentSequence.codec2DataIndex += 8; } @@ -396,12 +406,12 @@ void vpTick() voicePromptIsActive=false; } -inline bool vpIsPlaying(void) +bool vp_isPlaying(void) { return voicePromptIsActive; } -bool vpHasDataToPlay(void) +bool vp_sequenceNotEmpty(void) { return (vpCurrentSequence.length > 0); } diff --git a/openrtx/src/ui/ui.c b/openrtx/src/ui/ui.c index d47aa08c..b15ff3db 100644 --- a/openrtx/src/ui/ui.c +++ b/openrtx/src/ui/ui.c @@ -616,7 +616,7 @@ int _ui_fsm_loadChannel(int16_t channel_index, bool *sync_rtx) { void _ui_fsm_confirmVFOInput(bool *sync_rtx) { - vpInit(); + vp_clearCurrPrompt(); // Switch to TX input if(ui_state.input_set == SET_RX) { @@ -653,7 +653,7 @@ void _ui_fsm_confirmVFOInput(bool *sync_rtx) announceError(vpqInit); state.ui_screen = MAIN_VFO; } - vpPlay(); + vp_play(); } void _ui_fsm_insertVFONumber(kbd_msg_t msg, bool *sync_rtx) @@ -661,14 +661,14 @@ void _ui_fsm_insertVFONumber(kbd_msg_t msg, bool *sync_rtx) // Advance input position ui_state.input_position += 1; // clear any prompts in progress. - vpInit(); + vp_clearCurrPrompt(); // Save pressed number to calculate frequency and show in GUI ui_state.input_number = input_getPressedNumber(msg); // queue the digit just pressed. - vpQueueInteger(ui_state.input_number); + vp_queueInteger(ui_state.input_number); // queue point if user has entered three digits. if (ui_state.input_position==3) - vpQueuePrompt(PROMPT_POINT); + vp_queuePrompt(PROMPT_POINT); if(ui_state.input_set == SET_RX) { @@ -681,7 +681,7 @@ void _ui_fsm_insertVFONumber(kbd_msg_t msg, bool *sync_rtx) {// queue the rx freq just completed. vpQueueFrequency(ui_state.new_rx_frequency); /// now queue tx as user has changed fields. - vpQueuePrompt(PROMPT_TRANSMIT); + vp_queuePrompt(PROMPT_TRANSMIT); // Switch to TX input ui_state.input_set = SET_TX; // Reset input position @@ -712,7 +712,7 @@ void _ui_fsm_insertVFONumber(kbd_msg_t msg, bool *sync_rtx) state.ui_screen = MAIN_VFO; } } - vpPlay(); + vp_play(); } void _ui_changeBrightness(int variation) diff --git a/openrtx/src/ui/ui_menu.c b/openrtx/src/ui/ui_menu.c index 9ac90f16..c04c5ec7 100644 --- a/openrtx/src/ui/ui_menu.c +++ b/openrtx/src/ui/ui_menu.c @@ -99,9 +99,9 @@ static void announceMenuItemIfNeeded(char* name, char* value) // See if we are already in the middle of speaking a menu item. // e.g. when changing a value with left or right, we don't want to repeat the // prompt if arrowing rapidly. - bool voicePromptWasPlaying=vpIsPlaying(); + bool voicePromptWasPlaying=vp_isPlaying(); // Stop any prompt in progress and clear the buffer. - vpInit(); + vp_clearCurrPrompt(); // If no value is supplied, or, no prompt is in progress, announce the name. if (!voicePromptWasPlaying || !value || !*value) announceText(name, vpqDefault); @@ -109,7 +109,7 @@ static void announceMenuItemIfNeeded(char* name, char* value) if (value && *value) announceText(value, vpqDefault); - vpPlay(); + vp_play(); } void _ui_drawMenuList(uint8_t selected, int (*getCurrentEntry)(char *buf, uint8_t max_len, uint8_t index)) diff --git a/tests/unit/voice_prompts.c b/tests/unit/voice_prompts.c index b6972043..a33dbe7a 100644 --- a/tests/unit/voice_prompts.c +++ b/tests/unit/voice_prompts.c @@ -34,12 +34,12 @@ int main() state.settings.vpLevel = 3; VoicePromptQueueFlags_T flags = GetQueueFlagsForVoiceLevel(); - vpCacheInit(); - vpInit(); - vpQueueStringTableEntry(¤tLanguage->allChannels); - vpPlay(); + vp_init(); + vp_clearCurrPrompt(); + vp_queueStringTableEntry(¤tLanguage->allChannels); + vp_play(); while(true) { - vpTick(); + vp_tick(); } }