diff --git a/openrtx/include/core/voicePrompts.h b/openrtx/include/core/voicePrompts.h
index 9bf74da3..9e44a1d0 100644
--- a/openrtx/include/core/voicePrompts.h
+++ b/openrtx/include/core/voicePrompts.h
@@ -248,17 +248,17 @@ void vp_queuePrompt(const uint16_t prompt);
/**
* Append the spelling of a complete string to the queue.
*
- * @param promptString: string to be spelled.
+ * @param string: string to be spelled.
* @param flags: control flags.
*/
-void vp_queueString(char* promptString, VoicePromptFlags_T flags);
+void vp_queueString(const char* string, VoicePromptFlags_T flags);
/**
* Append a signed integer to the queue.
*
* @param value: value to be appended.
*/
-void vp_queueInteger(const int32_t value);
+void vp_queueInteger(const int value);
/**
* Append a text string from the current language to the queue.
diff --git a/openrtx/src/core/voicePrompts.c b/openrtx/src/core/voicePrompts.c
index 5d5b2333..61318042 100644
--- a/openrtx/src/core/voicePrompts.c
+++ b/openrtx/src/core/voicePrompts.c
@@ -17,209 +17,139 @@
* You should have received a copy of the GNU General Public License *
* along with this program; if not, see *
***************************************************************************/
-#include "core/voicePrompts.h"
+#include
+#include
+#include
+#include
+#include
#include
#include
#include
#include
#include
-#include
-#include
+static const uint32_t VOICE_PROMPTS_DATA_MAGIC = 0x5056; //'VP'
+static const uint32_t VOICE_PROMPTS_DATA_VERSION = 0x1000; // v1000 OpenRTX
-#include "interfaces/keyboard.h"
-#include "ui/ui_strings.h"
-
-const uint32_t VOICE_PROMPTS_DATA_MAGIC = 0x5056; //'VP'
-const uint32_t VOICE_PROMPTS_DATA_VERSION = 0x1000; // v1000 OpenRTX
-// Must match the number of voice prompts allowed by the generator script.
#define VOICE_PROMPTS_TOC_SIZE 350
-// This gets the data for a voice prompt to be demodulated using Codec2.
-// The offset is relative to the start of the voice prompt data.
-// The length is the length in bytes of the data.
-static void GetCodec2Data(int offset, int length);
+#define CODEC2_HEADER_SIZE 7
+#define VP_SEQUENCE_BUF_SIZE 128
+#define CODEC2_DATA_BUF_SIZE 2048
-#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;
uint32_t version;
-} voicePromptsDataHeader_t;
-// 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
-#define Codec2DataBufferSize 2048
-
-bool vpDataIsLoaded = false;
-
-static bool voicePromptIsActive = false;
-// Uninitialized is -1.
-static int promptDataPosition = -1;
-static int currentPromptLength = -1;
-// Number of ms from end of playing prompt to disabling amp.
-
-static uint8_t Codec2Data[Codec2DataBufferSize];
-
-#define VOICE_PROMPTS_SEQUENCE_BUFFER_SIZE 128
+}
+vpHeader_t;
typedef struct
-{ // buffer of individual prompt indices.
- 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
- //(buffer content sent in lots of 8 byte frames.)
- int codec2DataLength; // length of codec2 data for current prompt.
-} vpSequence_t;
+{
+ const char* userWord;
+ const voicePrompt_t vp;
+}
+userDictEntry_t;
-static vpSequence_t vpCurrentSequence = {.pos = 0, .length = 0, .codec2DataIndex = 0, .codec2DataLength = 0};
+typedef struct
+{
+ uint16_t buffer[VP_SEQUENCE_BUF_SIZE]; // Buffer of individual prompt indices.
+ uint16_t pos; // Index into above buffer.
+ uint16_t length; // Number of entries in above buffer.
+ uint32_t c2DataIndex; // Index into current codec2 data
+ uint32_t c2DataLength; // Length of codec2 data for current prompt.
+}
+vpSequence_t;
-uint32_t tableOfContents[VOICE_PROMPTS_TOC_SIZE];
-const userDictEntry userDictionary[] = {
- {"hotspot", PROMPT_CUSTOM1}, // Hotspot
+static const userDictEntry_t userDictionary[] =
+{
+ {"hotspot", PROMPT_CUSTOM1}, // Hotspot
{"clearnode", PROMPT_CUSTOM2}, // ClearNode
{"sharinode", PROMPT_CUSTOM3}, // ShariNode
- {"microhub", PROMPT_CUSTOM4}, // MicroHub
- {"openspot", PROMPT_CUSTOM5}, // Openspot
- {"repeater", PROMPT_CUSTOM6}, // repeater
+ {"microhub", PROMPT_CUSTOM4}, // MicroHub
+ {"openspot", PROMPT_CUSTOM5}, // Openspot
+ {"repeater", PROMPT_CUSTOM6}, // repeater
{"blindhams", PROMPT_CUSTOM7}, // BlindHams
- {"allstar", PROMPT_CUSTOM8}, // Allstar
- {"parrot", PROMPT_CUSTOM9}, // Parrot
- {"channel", PROMPT_CHANNEL}, {0, 0}};
+ {"allstar", PROMPT_CUSTOM8}, // Allstar
+ {"parrot", PROMPT_CUSTOM9}, // Parrot
+ {"channel", PROMPT_CHANNEL}, // Channel
+ {0, 0}
+};
-int vp_open(char *vp_name)
+static vpSequence_t vpCurrentSequence =
{
- if (!vp_name)
- vp_name = "voiceprompts.vpc";
- voice_prompt_file = fopen(vp_name, "r");
- if (!voice_prompt_file)
- return -1;
- return 0;
+ .pos = 0,
+ .length = 0,
+ .c2DataIndex = 0,
+ .c2DataLength = 0
+};
+
+static uint8_t codec2Data[CODEC2_DATA_BUF_SIZE];
+static uint32_t tableOfContents[VOICE_PROMPTS_TOC_SIZE];
+static uint32_t vpDataOffset = 0;
+static bool vpDataLoaded = false;
+static bool voicePromptActive = false;
+static FILE *vpFile = NULL;
+
+/**
+ * \internal
+ * Load Codec2 data for a voice prompt.
+ *
+ * @param offset: offset relative to the start of the voice prompt data.
+ * @param length: data length in bytes.
+ */
+static void loadCodec2Data(const int offset, const int length)
+{
+ const uint32_t minOffset = sizeof(vpHeader_t) + sizeof(tableOfContents);
+
+ if ((vpFile == NULL) || (vpDataOffset < minOffset))
+ return;
+
+ if ((offset < 0) || (length > CODEC2_DATA_BUF_SIZE))
+ return;
+
+ // Skip codec2 header
+ fseek(vpFile, vpDataOffset + offset + CODEC2_HEADER_SIZE, SEEK_SET);
+ fread((void*)&codec2Data, length, 1, vpFile);
+
+ // 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)
+ {
+ int bytesToZero = length % 8;
+ memset(codec2Data + length, 0, bytesToZero);
+ }
}
-void vp_close()
+/**
+ * \internal
+ * Check validity of voice prompt header.
+ *
+ * @param header: voice prompt header to be checked.
+ * @return true if header is valid
+ */
+static inline bool checkVpHeader(const vpHeader_t* header)
{
- fclose(voice_prompt_file);
-}
-
-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)
+/**
+ * \internal
+ * Perform a string lookup inside user dictionary.
+ *
+ * @param ptr: string to be searched.
+ * @param advanceBy: final offset with respect of dictionary beginning.
+ * @return index of user dictionary's voice prompt.
+ */
+static uint16_t UserDictLookup(const char* ptr, int* advanceBy)
{
- voicePromptsDataHeader_t header;
- vpDataOffset=0;
+ if ((ptr == NULL) || (*ptr == '\0'))
+ return 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);
-
- if (vpCheckHeader((uint32_t*)&header))
- { // read in the TOC.
- fread((void*)&tableOfContents, sizeof(tableOfContents), 1, voice_prompt_file);
- vpDataOffset = ftell(voice_prompt_file);
- if(vpDataOffset == (sizeof(voicePromptsDataHeader_t) + sizeof(tableOfContents)))
- vpDataIsLoaded = true;
- }
- if (vpDataIsLoaded)
- { // if the hash key is down, set vpLevel to high, if beep or less.
- if ((kbd_getKeys() & KEY_HASH) && (state.settings.vpLevel <= vpBeep))
- state.settings.vpLevel = vpHigh;
- }
- else
- { // ensure we at least have beeps in the event no voice prompts are
- // loaded.
- if (state.settings.vpLevel > vpBeep) state.settings.vpLevel = vpBeep;
- }
- // TODO: Move this somewhere else for compatibility with M17
- codec_init();
-}
-
-
-
-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
- // being played back, since codec2 frames are pushed in lots of 8 bytes.
- if ((length % 8) != 0)
- {
- int bytesToZero = length % 8;
- memset(Codec2Data+length, 0, bytesToZero);
- }
-}
-
-void vp_terminate(void)
-{
- if (voicePromptIsActive)
- {
- audio_disableAmp();
- codec_stop();
-
- vpCurrentSequence.pos = 0;
-
- voicePromptIsActive = false;
- }
-}
-
-void vp_clearCurrPrompt(void)
-{
- vpCurrentSequence.length = 0;
- vpCurrentSequence.pos = 0;
- vpCurrentSequence.codec2DataIndex = 0;
- vpCurrentSequence.codec2DataLength = 0;
-}
-
-void vp_queuePrompt(const uint16_t prompt)
-{
- if (state.settings.vpLevel < vpLow) return;
-
- if (voicePromptIsActive)
- {
- vp_clearCurrPrompt();
- }
- if (vpCurrentSequence.length < VOICE_PROMPTS_SEQUENCE_BUFFER_SIZE)
- {
- vpCurrentSequence.buffer[vpCurrentSequence.length] = prompt;
- vpCurrentSequence.length++;
- }
-}
-
-static uint16_t UserDictLookup(char* ptr, int* advanceBy)
-{
- if (!ptr || !*ptr) return 0;
-
- for (int index = 0; userDictionary[index].userWord != 0; ++index)
+ for(uint32_t index = 0; userDictionary[index].userWord != 0; index++)
{
int len = strlen(userDictionary[index].userWord);
if (strncasecmp(userDictionary[index].userWord, ptr, len) == 0)
@@ -263,59 +193,142 @@ static bool GetSymbolVPIfItShouldBeAnnounced(char symbol,
(!commonSymbol && announceLessCommonSymbols));
}
-// This function spells out a string letter by letter.
-void vp_queueString(char* promptString, VoicePromptFlags_T flags)
-{
- if (state.settings.vpLevel < vpLow) return;
- if (voicePromptIsActive)
+void vp_init()
+{
+ vpDataOffset = 0;
+
+ if(vpFile == NULL)
+ vpFile = fopen("voiceprompts.vpc", "r");
+
+ if(vpFile == NULL)
+ return;
+
+ // Read header
+ vpHeader_t header;
+ fseek(vpFile, 0L, SEEK_SET);
+ fread((void*)&header, sizeof(header), 1, vpFile);
+
+ if(checkVpHeader(&header) == true)
{
- vp_clearCurrPrompt();
+ // Read in the TOC.
+ fread((void*)&tableOfContents, sizeof(tableOfContents), 1, vpFile);
+ vpDataOffset = ftell(vpFile);
+
+ if(vpDataOffset == (sizeof(vpHeader_t) + sizeof(tableOfContents)))
+ vpDataLoaded = true;
}
- if (state.settings.vpPhoneticSpell) flags |= vpAnnouncePhoneticRendering;
- while (*promptString != 0)
+ if (vpDataLoaded)
+ {
+ // If the hash key is down, set vpLevel to high, if beep or less.
+ if ((kbd_getKeys() & KEY_HASH) && (state.settings.vpLevel <= vpBeep))
+ state.settings.vpLevel = vpHigh;
+ }
+ else
+ {
+ // ensure we at least have beeps in the event no voice prompts are
+ // loaded.
+ if (state.settings.vpLevel > vpBeep)
+ state.settings.vpLevel = vpBeep;
+ }
+
+ // TODO: Move this somewhere else for compatibility with M17
+ codec_init();
+}
+
+void vp_terminate()
+{
+ if (voicePromptActive)
+ {
+ audio_disableAmp();
+ codec_stop();
+
+ vpCurrentSequence.pos = 0;
+ voicePromptActive = false;
+ }
+
+ fclose(vpFile);
+}
+
+void vp_clearCurrPrompt()
+{
+ vpCurrentSequence.length = 0;
+ vpCurrentSequence.pos = 0;
+ vpCurrentSequence.c2DataIndex = 0;
+ vpCurrentSequence.c2DataLength = 0;
+}
+
+void vp_queuePrompt(const uint16_t prompt)
+{
+ if (state.settings.vpLevel < vpLow)
+ return;
+
+ if (voicePromptActive)
+ vp_clearCurrPrompt();
+
+ if (vpCurrentSequence.length < VP_SEQUENCE_BUF_SIZE)
+ {
+ vpCurrentSequence.buffer[vpCurrentSequence.length] = prompt;
+ vpCurrentSequence.length++;
+ }
+}
+
+void vp_queueString(const char* string, VoicePromptFlags_T flags)
+{
+ if (state.settings.vpLevel < vpLow)
+ return;
+
+ if (voicePromptActive)
+ vp_clearCurrPrompt();
+
+ if (state.settings.vpPhoneticSpell)
+ flags |= vpAnnouncePhoneticRendering;
+
+ while (*string != '\0')
{
int advanceBy = 0;
- voicePrompt_t vp = UserDictLookup(promptString, &advanceBy);
- if (vp)
+ voicePrompt_t vp = UserDictLookup(string, &advanceBy);
+
+ if (vp != 0)
{
vp_queuePrompt(vp);
- promptString += advanceBy;
+ string += advanceBy;
continue;
}
- else if ((*promptString >= '0') && (*promptString <= '9'))
+ else if ((*string >= '0') && (*string <= '9'))
{
- vp_queuePrompt(*promptString - '0' + PROMPT_0);
+ vp_queuePrompt(*string - '0' + PROMPT_0);
}
- else if ((*promptString >= 'A') && (*promptString <= 'Z'))
+ else if ((*string >= 'A') && (*string <= 'Z'))
{
- if (flags & vpAnnounceCaps) vp_queuePrompt(PROMPT_CAP);
+ if (flags & vpAnnounceCaps)
+ vp_queuePrompt(PROMPT_CAP);
if (flags & vpAnnouncePhoneticRendering)
- vp_queuePrompt((*promptString - 'A') + PROMPT_A_PHONETIC);
+ vp_queuePrompt((*string - 'A') + PROMPT_A_PHONETIC);
else
- vp_queuePrompt(*promptString - 'A' + PROMPT_A);
+ vp_queuePrompt(*string - 'A' + PROMPT_A);
}
- else if ((*promptString >= 'a') && (*promptString <= 'z'))
+ else if ((*string >= 'a') && (*string <= 'z'))
{
if (flags & vpAnnouncePhoneticRendering)
- vp_queuePrompt((*promptString - 'a') + PROMPT_A_PHONETIC);
+ vp_queuePrompt((*string - 'a') + PROMPT_A_PHONETIC);
else
- vp_queuePrompt(*promptString - 'a' + PROMPT_A);
+ vp_queuePrompt(*string - 'a' + PROMPT_A);
}
- else if ((*promptString == ' ') && (flags & vpAnnounceSpace))
+ else if ((*string == ' ') && (flags & vpAnnounceSpace))
{
vp_queuePrompt(PROMPT_SPACE);
}
- else if (GetSymbolVPIfItShouldBeAnnounced(*promptString, flags, &vp))
+ else if (GetSymbolVPIfItShouldBeAnnounced(*string, flags, &vp))
{
if (vp != PROMPT_SILENCE)
vp_queuePrompt(vp);
- else // announce ASCII
+ else
{
- int32_t val = *promptString;
- vp_queuePrompt(PROMPT_CHARACTER); // just the word "code" as we
- // don't have character.
+ // announce ASCII
+ int32_t val = *string;
+ vp_queuePrompt(PROMPT_CHARACTER);
vp_queueInteger(val);
}
}
@@ -325,93 +338,110 @@ void vp_queueString(char* promptString, VoicePromptFlags_T flags)
vp_queuePrompt(PROMPT_SILENCE);
}
- promptString++;
+ string++;
}
- if (flags & vpqAddSeparatingSilence) vp_queuePrompt(PROMPT_SILENCE);
+
+ if (flags & vpqAddSeparatingSilence)
+ vp_queuePrompt(PROMPT_SILENCE);
}
-void vp_queueInteger(const int32_t value)
+void vp_queueInteger(const int value)
{
- if (state.settings.vpLevel < vpLow) return;
+ if (state.settings.vpLevel < vpLow)
+ return;
char buf[12] = {0}; // min: -2147483648, max: 2147483647
snprintf(buf, 12, "%d", value);
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 vp_queueStringTableEntry(const char* const* stringTableStringPtr)
{
- if (state.settings.vpLevel < vpLow) return;
+ /*
+ * 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)
+ */
+
+ if (state.settings.vpLevel < vpLow)
+ return;
if (stringTableStringPtr == NULL)
- {
return;
- }
- vp_queuePrompt(NUM_VOICE_PROMPTS + 1 +
- (stringTableStringPtr - ¤tLanguage->languageName)
- / sizeof(const char *));
+
+ uint16_t pos = NUM_VOICE_PROMPTS
+ + 1
+ + (stringTableStringPtr - ¤tLanguage->languageName) /
+ sizeof(const char *);
+
+ vp_queuePrompt(pos);
}
-void vp_play(void)
+void vp_play()
{
- if (state.settings.vpLevel < vpLow) return;
+ if (state.settings.vpLevel < vpLow)
+ return;
- if (voicePromptIsActive) return;
+ if (voicePromptActive)
+ return;
- if (vpCurrentSequence.length <= 0) return;
+ if (vpCurrentSequence.length <= 0)
+ return;
- voicePromptIsActive = true; // Start the playback
+ voicePromptActive = true;
codec_startDecode(SINK_SPK);
-
audio_enableAmp();
}
-// Call this from the main timer thread to continue voice prompt playback.
void vp_tick()
{
- if (!voicePromptIsActive) return;
+ if (!voicePromptActive)
+ return;
- while (vpCurrentSequence.pos < vpCurrentSequence.length)
- {// get the codec2 data for the current prompt if needed.
- if (vpCurrentSequence.codec2DataLength == 0)
- { // obtain the data for the prompt.
- int promptNumber = vpCurrentSequence.buffer[vpCurrentSequence.pos];
-
- 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)
+ while(vpCurrentSequence.pos < vpCurrentSequence.length)
+ {
+ // get the codec2 data for the current prompt if needed.
+ if (vpCurrentSequence.c2DataLength == 0)
{
- if (!codec_pushFrame(Codec2Data+vpCurrentSequence.codec2DataIndex, false))
- return; // wait until there is room, perhaps next vp_tick call.
- vpCurrentSequence.codec2DataIndex += 8;
+ // obtain the data for the prompt.
+ int promptNumber = vpCurrentSequence.buffer[vpCurrentSequence.pos];
+
+ vpCurrentSequence.c2DataLength = tableOfContents[promptNumber + 1]
+ - tableOfContents[promptNumber];
+
+ loadCodec2Data(tableOfContents[promptNumber],
+ vpCurrentSequence.c2DataLength);
+
+ vpCurrentSequence.c2DataIndex = 0;
}
- vpCurrentSequence.pos++; // ready for next prompt in sequence.
- vpCurrentSequence.codec2DataLength = 0; // flag that we need to get more data.
- vpCurrentSequence.codec2DataIndex = 0;
+ while (vpCurrentSequence.c2DataIndex < vpCurrentSequence.c2DataLength)
+ {
+ // push the codec2 data in lots of 8 byte frames.
+ if (!codec_pushFrame(codec2Data+vpCurrentSequence.c2DataIndex, false))
+ return;
+
+ vpCurrentSequence.c2DataIndex += 8;
+ }
+
+ vpCurrentSequence.pos++; // ready for next prompt in sequence.
+ vpCurrentSequence.c2DataLength = 0; // flag that we need to get more data.
+ vpCurrentSequence.c2DataIndex = 0;
}
+
// see if we've finished.
if(vpCurrentSequence.pos == vpCurrentSequence.length)
- voicePromptIsActive=false;
+ voicePromptActive = false;
}
-bool vp_isPlaying(void)
+bool vp_isPlaying()
{
- return voicePromptIsActive;
+ return voicePromptActive;
}
-bool vp_sequenceNotEmpty(void)
+bool vp_sequenceNotEmpty()
{
return (vpCurrentSequence.length > 0);
}