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();