From d5568b8ab326d4ecce9d7daa845c1f8330ab98c0 Mon Sep 17 00:00:00 2001 From: vk7js <58905135+vk7js@users.noreply.github.com> Date: Wed, 4 May 2022 21:57:46 +1000 Subject: [PATCH] Adding voice prompts skeleton. --- openrtx/include/core/voicePrompts.h | 172 +++++++++++++++ openrtx/src/core/voicePrompts.c | 318 ++++++++++++++++++++++++++++ openrtx/src/ui/ui_menu.c | 2 +- 3 files changed, 491 insertions(+), 1 deletion(-) create mode 100644 openrtx/include/core/voicePrompts.h create mode 100644 openrtx/src/core/voicePrompts.c diff --git a/openrtx/include/core/voicePrompts.h b/openrtx/include/core/voicePrompts.h new file mode 100644 index 00000000..a096bf80 --- /dev/null +++ b/openrtx/include/core/voicePrompts.h @@ -0,0 +1,172 @@ +/*************************************************************************** + * Copyright (C) 2022 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN, * + * Silvano Seva IU2KWO * + * Joseph Stephen VK7JS * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * 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 +/* +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. +*/ +typedef enum +{ +PROMPT_SILENCE, // +PROMPT_POINT, // POINT +PROMPT_0, // 0 +PROMPT_1, // 1 +PROMPT_2, // 2 +PROMPT_3, // 3 +PROMPT_4, // 4 +PROMPT_5, // 5 +PROMPT_6, // 6 +PROMPT_7, // 7 +PROMPT_8, // 8 +PROMPT_9, // 9 +PROMPT_A, // A +PROMPT_B, // B +PROMPT_C, // C +PROMPT_D, // D +PROMPT_E, // E +PROMPT_F, // F +PROMPT_G, // G +PROMPT_H, // H +PROMPT_I, // I +PROMPT_J, // J +PROMPT_K, // K +PROMPT_L, // L +PROMPT_M, // M +PROMPT_N, // N +PROMPT_O, // O +PROMPT_P, // P +PROMPT_Q, // Q +PROMPT_R, // R +PROMPT_S, // S +PROMPT_T, // T +PROMPT_U, // U +PROMPT_V, // V +PROMPT_W, // W +PROMPT_X, // X +PROMPT_Y, // Y +PROMPT_Z, // Zed +PROMPT_A_PHONETIC, // alpha +PROMPT_B_PHONETIC, // bravo +PROMPT_C_PHONETIC, // charlie +PROMPT_D_PHONETIC, // delta +PROMPT_E_PHONETIC, // echo +PROMPT_F_PHONETIC, // foxtrot +PROMPT_G_PHONETIC, // golf +PROMPT_H_PHONETIC, // hotel +PROMPT_I_PHONETIC, // india +PROMPT_J_PHONETIC, // juliet +PROMPT_K_PHONETIC, // kilo +PROMPT_L_PHONETIC, // lema +PROMPT_M_PHONETIC, // mike +PROMPT_N_PHONETIC, // november +PROMPT_O_PHONETIC, // oscar +PROMPT_P_PHONETIC, // papa +PROMPT_Q_PHONETIC, // quebec +PROMPT_R_PHONETIC, // romeo +PROMPT_S_PHONETIC, // siera +PROMPT_T_PHONETIC, // tango +PROMPT_U_PHONETIC, // uniform +PROMPT_V_PHONETIC, // victor +PROMPT_W_PHONETIC, // whisky +PROMPT_X_PHONETIC, // exray +PROMPT_Y_PHONETIC, // yankie +PROMPT_Z_PHONETIC, // zulu +PROMPT_CAP, // cap +PROMPT_HERTZ, // hertz +PROMPT_KILOHERTZ, // Kilohertz +PROMPT_MEGAHERTZ, // Megahertz +PROMPT_VFO, // V F O +PROMPT_MILLISECONDS, // Milliseconds +PROMPT_SECONDS, // Seconds +PROMPT_MINUTES, // Minutes +PROMPT_VOLTS, // Volts +PROMPT_MILLIWATTS, // Milliwatts +PROMPT_WATT, // Wattt +PROMPT_WATTS, // Watts +PROMPT_PERCENT, // Percent +PROMPT_RECEIVE, // Receive +PROMPT_TRANSMIT, // Transmit +PROMPT_MODE, // Mode +PROMPT_DMR, // D M R +PROMPT_FM, // F M +PROMPT_M17, // M seventeen +PROMPT_PLUS, // Plus +PROMPT_MINUS, // Minus +PROMPT_STAR, // Star +PROMPT_HASH, // Hash +PROMPT_SPACE, // space +PROMPT_EXCLAIM, // exclaim +PROMPT_COMMA, // comma +PROMPT_AT, // at +PROMPT_COLON, // colon +PROMPT_QUESTION, // question +PROMPT_LEFT_PAREN, // left paren +PROMPT_RIGHT_PAREN, // right paren +PROMPT_TILDE, // tilde +PROMPT_SLASH, // slash +PROMPT_LEFT_BRACKET, // left bracket +PROMPT_RIGHT_BRACKET, // right bracket +PROMPT_LESS, // less +PROMPT_GREATER, // greater +PROMPT_EQUALS, // equals +PROMPT_DOLLAR, // dollar +PROMPT_APOSTROPHE, // apostrophe +PROMPT_GRAVE, // grave +PROMPT_AMPERSAND, // and +PROMPT_BAR, // bar +PROMPT_UNDERLINE, // underline +PROMPT_CARET, // caret +PROMPT_LEFT_BRACE, // left brace +NUM_VOICE_PROMPTS, + __MAKE_ENUM_16BITS = INT16_MAX +} voicePrompt_t; + +#define PROMPT_VOICE_NAME (NUM_VOICE_PROMPTS + (sizeof(stringsTable_t)/sizeof(char*))) +typedef enum +{ + vpAnnounceCaps=0x01, + vpAnnounceCustomPrompts=0x02, + vpAnnounceSpaceAndSymbols=0x04, + vpAnnouncePhoneticRendering=0x08, +} VoicePromptFlags_T; + +extern bool voicePromptDataIsLoaded; +extern const uint32_t VOICE_PROMPTS_FLASH_HEADER_ADDRESS; +// Loads just the TOC from Flash and stores in RAM for fast access. +void vpCacheInit(void); +// event driven to play a voice prompt in progress. +void vpTick(void); + +void vpInit(void);// Call before building the prompt sequence +void vpAppendPrompt(uint16_t prompt);// Append an individual prompt item. This can be a single letter number or a phrase +void vpQueueString(char *promptString, VoicePromptFlags_T flags); +void vpQueueInteger(int32_t value); // Append a signed integer +void vpQueueStringTableEntry(const char * const *);//Append a text string from the current language e.g. currentLanguage->off +void vpPlay(void);// Starts prompt playback +extern bool vpIsPlaying(void); +bool vpHasDataToPlay(void); +void vpTerminate(void); +bool vpCheckHeader(uint32_t *bufferAddress); + +#endif \ No newline at end of file diff --git a/openrtx/src/core/voicePrompts.c b/openrtx/src/core/voicePrompts.c new file mode 100644 index 00000000..2b14e845 --- /dev/null +++ b/openrtx/src/core/voicePrompts.c @@ -0,0 +1,318 @@ +/*************************************************************************** + * Copyright (C) 2022 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN, * + * Silvano Seva IU2KWO * + * Joseph Stephen VK7JS * + * Roger Clark VK3KYY * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ +#include +#include "core/voicePrompts.h" +#include "ui/UIStrings.h" +const uint32_t VOICE_PROMPTS_DATA_MAGIC = 0x5056;//'VP' +const uint32_t VOICE_PROMPTS_DATA_VERSION = 0x1000; // v1000 OpenRTX +#define VOICE_PROMPTS_TOC_SIZE 350 +static void getM17Data(int offset,int length); + +typedef struct +{ + uint32_t magic; + uint32_t version; +} voicePromptsDataHeader_t; + + + +const uint32_t VOICE_PROMPTS_FLASH_HEADER_ADDRESS = 0x8F400; // todo figure this out for OpenRTX +static uint32_t vpFlashDataAddress;// = VOICE_PROMPTS_FLASH_HEADER_ADDRESS + sizeof(voicePromptsDataHeader_t) + sizeof(uint32_t)*VOICE_PROMPTS_TOC_SIZE ; +// TODO figure out M17 frame equivalent. +// 76 x 27 byte ambe frames +#define M17_DATA_BUFFER_SIZE 2052 + +bool voicePromptDataIsLoaded = false; +static bool voicePromptIsActive = false; +static int promptDataPosition = -1; +static int currentPromptLength = -1; + +#define PROMPT_TAIL 30 +static int promptTail = 0; + +static uint8_t M17Data[M17_DATA_BUFFER_SIZE]; + +#define VOICE_PROMPTS_SEQUENCE_BUFFER_SIZE 128 + +typedef struct +{ + uint16_t Buffer[VOICE_PROMPTS_SEQUENCE_BUFFER_SIZE]; + int Pos; + int Length; +} vpSequence_t; + +static vpSequence_t vpCurrentSequence = +{ + .Pos = 0, + .Length = 0 +}; + +uint32_t tableOfContents[VOICE_PROMPTS_TOC_SIZE]; + + +void vpCacheInit(void) +{ + voicePromptsDataHeader_t header; + + SPI_Flash_read(VOICE_PROMPTS_FLASH_HEADER_ADDRESS,(uint8_t *)&header,sizeof(voicePromptsDataHeader_t)); + + if (vpCheckHeader((uint32_t *)&header)) + { + voicePromptDataIsLoaded = SPI_Flash_read(VOICE_PROMPTS_FLASH_HEADER_ADDRESS + sizeof(voicePromptsDataHeader_t), (uint8_t *)&tableOfContents, sizeof(uint32_t) * VOICE_PROMPTS_TOC_SIZE); + vpFlashDataAddress = VOICE_PROMPTS_FLASH_HEADER_ADDRESS + sizeof(voicePromptsDataHeader_t) + sizeof(uint32_t)*VOICE_PROMPTS_TOC_SIZE ; + } + +} + +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 getM17Data(int offset,int length) +{ + if (length <= M17_DATA_BUFFER_SIZE) + { + SPI_Flash_read(vpFlashDataAddress + offset, (uint8_t *)&M17Data, length); + } +} + +void vpTick(void) +{ + if (voicePromptIsActive) + { + if (promptDataPosition < currentPromptLength) + { + //if (wavbuffer_count <= (WAV_BUFFER_COUNT / 2)) + { +// codecDecode((uint8_t *)&M17Data[promptDataPosition], 3); + promptDataPosition += 27; + } + + //soundTickRXBuffer(); + } + else + { + if ( vpCurrentSequence.Pos < (vpCurrentSequence.Length - 1)) + { + vpCurrentSequence.Pos++; + promptDataPosition = 0; + + int promptNumber = vpCurrentSequence.Buffer[vpCurrentSequence.Pos]; + currentPromptLength = tableOfContents[promptNumber + 1] - tableOfContents[promptNumber]; + getM17Data(tableOfContents[promptNumber], currentPromptLength); + } + else + { + // wait for wave buffer to empty when prompt has finished playing + +// if (wavbuffer_count == 0) + { + vpTerminate(); + } + } + } + } + else + { + if (promptTail > 0) + { + promptTail--; + + if ((promptTail == 0) && trxCarrierDetected() && (trxGetMode() == RADIO_MODE_ANALOG)) + { + //GPIO_PinWrite(GPIO_RX_audio_mux, Pin_RX_audio_mux, 1); // Set the audio path to AT1846 -> audio amp. + } + } + } +} + +void vpTerminate(void) +{ + if (voicePromptIsActive) + { + //disableAudioAmp(AUDIO_AMP_MODE_PROMPT); + + vpCurrentSequence.Pos = 0; + //soundTerminateSound(); + //soundInit(); + promptTail = PROMPT_TAIL; + + voicePromptIsActive = false; + } +} + +void vpInit(void) +{ + if (voicePromptIsActive) + { + vpTerminate(); + } + + vpCurrentSequence.Length = 0; + vpCurrentSequence.Pos = 0; +} + +void vpQueuePrompt(uint16_t prompt) +{ + if (voicePromptIsActive) + { + vpInit(); + } + if (vpCurrentSequence.Length < VOICE_PROMPTS_SEQUENCE_BUFFER_SIZE) + { + vpCurrentSequence.Buffer[vpCurrentSequence.Length] = prompt; + vpCurrentSequence.Length++; + } +} + +// This function spells out a string letter by letter. +void vpQueueString(char *promptString, VoicePromptFlags_T flags) +{ + const char indexedSymbols[] = "!,@:?()~/[]<>=$'`&|_^{}"; // handles most of them in indexed order, must match order of vps. + if (voicePromptIsActive) + { + vpInit(); + } + while (*promptString != 0) + { + if ((*promptString >= '0') && (*promptString <= '9')) + { + vpQueuePrompt(*promptString - '0' + PROMPT_0); + } + else if ((*promptString >= 'A') && (*promptString <= 'Z')) + { + if (flags&vpAnnounceCaps) + vpQueuePrompt(PROMPT_CAP); + if (flags&vpAnnouncePhoneticRendering) + vpQueuePrompt((*promptString - 'A') + PROMPT_A_PHONETIC); + else + vpQueuePrompt(*promptString - 'A' + PROMPT_A); + } + else if ((*promptString >= 'a') && (*promptString <= 'z')) + { + if (flags&vpAnnouncePhoneticRendering) + vpQueuePrompt((*promptString - 'a') + PROMPT_A_PHONETIC); + else + vpQueuePrompt(*promptString - 'a' + PROMPT_A); + } + else if (*promptString == '.') + { + vpQueuePrompt(PROMPT_POINT); + } + else if (*promptString == '+') + { + vpQueuePrompt(PROMPT_PLUS); + } + else if (*promptString == '-') + { + vpQueuePrompt(PROMPT_MINUS); + } + else if (*promptString == '%') + { + vpQueuePrompt(PROMPT_PERCENT); + } + else if (*promptString == '*') + { + vpQueuePrompt(PROMPT_STAR); + } + else if (*promptString == '#') + { + vpQueuePrompt(PROMPT_HASH); + } + else if (flags&(vpAnnounceSpaceAndSymbols)) + { + if (*promptString==' ') + vpQueuePrompt(PROMPT_SPACE); + else + { + char* ptr=strchr(indexedSymbols, *promptString); + if (ptr) + { + vpQueuePrompt(PROMPT_EXCLAIM+(ptr-indexedSymbols)); + } + else + { + int32_t val = *promptString; + vpQueueLanguageString(¤tLanguage->dtmf_code); // just the word "code" as we don't have character. + vpQueueInteger(val); + } + } + } + else + // otherwise just add silence + vpQueuePrompt(PROMPT_SILENCE); + + promptString++; + } +} + +void vpQueueInteger(int32_t value) +{ + char buf[12] = {0}; // min: -2147483648, max: 2147483647 + itoa(value, buf, 10); + vpQueueString(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) +{ + if (stringTableStringPtr == NULL) + { + return; + } + vpQueuePrompt(NUM_VOICE_PROMPTS + (stringTableStringPtr - currentLanguage->languageName)); +} + +void vpPlay(void) +{ + if ((voicePromptIsActive == false) && (vpCurrentSequence.Length > 0)) + { + voicePromptIsActive = true;// Start the playback + int promptNumber = vpCurrentSequence.Buffer[0]; + + vpCurrentSequence.Pos = 0; + + currentPromptLength = tableOfContents[promptNumber + 1] - tableOfContents[promptNumber]; + getM17Data(tableOfContents[promptNumber], currentPromptLength); + +// GPIO_PinWrite(GPIO_RX_audio_mux, Pin_RX_audio_mux, 0);// set the audio mux HR-C6000 -> audio amp + //enableAudioAmp(AUDIO_AMP_MODE_PROMPT); + + //codecInit(true); + promptDataPosition = 0; + + } +} + +inline bool vpIsPlaying(void) +{ + return (voicePromptIsActive || (promptTail > 0)); +} + +bool vpHasDataToPlay(void) +{ + return (vpCurrentSequence.Length > 0); +} + diff --git a/openrtx/src/ui/ui_menu.c b/openrtx/src/ui/ui_menu.c index 3cde3984..b551afeb 100644 --- a/openrtx/src/ui/ui_menu.c +++ b/openrtx/src/ui/ui_menu.c @@ -28,7 +28,7 @@ #include #include #include -#include +#include /* UI main screen helper functions, their implementation is in "ui_main.c" */ extern void _ui_drawMainBottom();