aboutsummaryrefslogtreecommitdiffstats
path: root/src/common/Audio
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/Audio')
-rw-r--r--src/common/Audio/audio.c50
-rw-r--r--src/common/Audio/audio.h58
-rw-r--r--src/common/Audio/audio_in.c24
-rw-r--r--src/common/Audio/audio_in.h40
-rw-r--r--src/common/Audio/cw.c605
-rw-r--r--src/common/Audio/cw.h53
-rw-r--r--src/common/Audio/tone.c358
-rw-r--r--src/common/Audio/tone.h53
8 files changed, 1241 insertions, 0 deletions
diff --git a/src/common/Audio/audio.c b/src/common/Audio/audio.c
new file mode 100644
index 0000000..f68f867
--- /dev/null
+++ b/src/common/Audio/audio.c
@@ -0,0 +1,50 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+*/
+
+#include "Audio/audio.h"
+
+#include <stdlib.h>
+
+AudioCallbackFunction *callback_function;
+void *callback_context;
+int16_t * next_buffer_samples;
+int next_buffer_length;
+int buffer_number;
+bool dma_running;
+
+void audio_initialize(int plln, int pllr, int i2sdiv, int i2sodd, int rate) {
+
+ // Intitialize state.
+ callback_function = NULL;
+ callback_context = NULL;
+ next_buffer_samples = NULL;
+ next_buffer_length = 0;
+ buffer_number = 0;
+ dma_running = false;
+
+ audio_initialize_platform(plln, pllr, i2sdiv, i2sodd, rate);
+
+ audio_set_volume(0xff);
+
+}
diff --git a/src/common/Audio/audio.h b/src/common/Audio/audio.h
new file mode 100644
index 0000000..d48cbe5
--- /dev/null
+++ b/src/common/Audio/audio.h
@@ -0,0 +1,58 @@
+#ifndef __AUDIO_H__
+#define __AUDIO_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef void AudioCallbackFunction(void *context,int buffer);
+
+// Variables used by both glutt-o-logique and simulator
+extern AudioCallbackFunction *callback_function;
+extern void *callback_context;
+extern int16_t *next_buffer_samples;
+extern int next_buffer_length;
+extern int buffer_number;
+extern bool dma_running;
+
+void audio_initialize_platform(int plln, int pllr, int i2sdiv, int i2sodd, int rate);
+void audio_start_dma_and_request_buffers(void);
+void audio_stop_dma(void);
+
+#define Audio8000HzSettings 256,5,12,1,8000
+#define Audio16000HzSettings 213,2,13,0,16000
+#define Audio32000HzSettings 213,2,6,1,32000
+#define Audio48000HzSettings 258,3,3,1,48000
+#define Audio96000HzSettings 344,2,3,1,96000
+#define Audio22050HzSettings 429,4,9,1,22050
+#define Audio44100HzSettings 271,2,6,0,44100
+#define AudioVGAHSyncSettings 419,2,13,0,31475 // 31475.3606. Actual VGA timer is 31472.4616.
+
+#define AUDIO_BUF_LEN 4096
+
+
+// Initialize and power up audio hardware. Use the above defines for the parameters.
+// Can probably only be called once.
+void audio_initialize(int plln, int pllr, int i2sdiv, int i2sodd, int rate);
+
+// Power up and down the audio hardware.
+void audio_put_codec_in_reset(void);
+void audio_reinit_codec(void);
+
+// Set audio volume in steps of 0.5 dB. 0xff is +12 dB.
+void audio_set_volume(int volume);
+
+// Start and stop audio playback using DMA.
+// Callback is optional, and called whenever a new buffer is needed.
+void audio_play_with_callback(AudioCallbackFunction *callback,void *context);
+
+// Provide a new buffer to the audio DMA. Output is double buffered, so
+// at least two buffers must be maintained by the program. It is not allowed
+// to overwrite the previously provided buffer until after the next callback
+// invocation.
+// Buffers must reside in DMA1-accessible memory, that is, the 128k RAM bank,
+// or flash.
+bool audio_provide_buffer_without_blocking(void *samples,int numsamples);
+
+void DMA1_Stream7_IRQHandler(void);
+
+#endif
diff --git a/src/common/Audio/audio_in.c b/src/common/Audio/audio_in.c
new file mode 100644
index 0000000..dd1994e
--- /dev/null
+++ b/src/common/Audio/audio_in.c
@@ -0,0 +1,24 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Matthias P. Braendli, Maximilien Cuony
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+*/
+
diff --git a/src/common/Audio/audio_in.h b/src/common/Audio/audio_in.h
new file mode 100644
index 0000000..97a0f29
--- /dev/null
+++ b/src/common/Audio/audio_in.h
@@ -0,0 +1,40 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Matthias P. Braendli, Maximilien Cuony
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+*/
+
+#pragma once
+
+#include <stdio.h>
+#include "FreeRTOS.h"
+
+#define AUDIO_IN_RATE 16000
+#define AUDIO_IN_BUF_LEN 1600
+
+void audio_in_initialize(void);
+
+/* Enable or disable the audio input */
+void audio_in_enable(int enable);
+
+/* The audio input layer must call tone_detect_push_sample() to
+ * send the samples
+ */
diff --git a/src/common/Audio/cw.c b/src/common/Audio/cw.c
new file mode 100644
index 0000000..667d459
--- /dev/null
+++ b/src/common/Audio/cw.c
@@ -0,0 +1,605 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016 Matthias P. Braendli
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+*/
+
+/* CW and PSK31 generator
+ *
+ * Concept:
+ *
+ * +-------------------+ +----------------+
+ * | cw_push_message() | -> cw_msg_queue -> | cw_psk31task() | -> cw_audio_queue
+ * +-------------------+ +----------------+
+ *
+ * The cw_psk31_fill_buffer() function can be called to fetch audio from the
+ * audio_queue
+ */
+
+#include "Audio/cw.h"
+#include "Core/common.h"
+#include "Audio/audio.h"
+#include <string.h>
+
+#ifdef SIMULATOR
+#include <math.h>
+#define arm_cos_f32 cosf
+#define arm_sin_f32 sinf
+#else
+#include "arm_math.h"
+#endif
+
+/* Kernel includes. */
+#include "FreeRTOS.h"
+#include "task.h"
+#include "queue.h"
+#include "semphr.h"
+
+#define MAX_MESSAGE_LEN 1024
+#define MAX_ON_BUFFER_LEN 8192
+
+const uint8_t cw_mapping[60] = { // {{{
+ // Read bits from right to left
+
+ 0b110101, //+ ASCII 43
+ 0b110101, //, ASCII 44
+ 0b1011110, //- ASCII 45
+
+ 0b1010101, //., ASCII 46
+ 0b110110, // / ASCII 47
+
+ 0b100000, // 0, ASCII 48
+ 0b100001, // 1
+ 0b100011,
+ 0b100111,
+ 0b101111,
+ 0b111111,
+ 0b111110,
+ 0b111100,
+ 0b111000,
+ 0b110000, // 9, ASCII 57
+
+ // The following are mostly invalid, but
+ // required to fill the gap in ASCII between
+ // numerals and capital letters
+ 0b10, // :
+ 0b10, // ;
+ 0b10, // <
+ 0b101110, // =
+ 0b10, // >
+ 0b1110011, // ?
+ 0b1101001, //@
+
+ 0b101, // A ASCII 65
+ 0b11110,
+ 0b11010,
+ 0b1110,
+ 0b11,
+ 0b11011,
+ 0b1100,
+ 0b11111,
+ 0b111,
+ 0b10001,
+ 0b1010,
+ 0b11101,
+ 0b100, //M
+ 0b110,
+ 0b1000,
+ 0b11001,
+ 0b10100,
+ 0b1101,
+ 0b1111,
+ 0b10,
+ 0b1011,
+ 0b10111,
+ 0b1001,
+ 0b10110,
+ 0b10010,
+ 0b11100, // Z
+
+ 0b101010, //Start, ASCII [
+ 0b1010111, // SK , ASCII '\'
+}; //}}}
+
+#if ENABLE_PSK31
+/*
+ * PSK31 Varicode
+ * http://aintel.bi.ehu.es/psk31.html
+ */
+const char *psk31_varicode[] = { // {{{
+ "1010101011",
+ "1011011011",
+ "1011101101",
+ "1101110111",
+ "1011101011",
+ "1101011111",
+ "1011101111",
+ "1011111101",
+ "1011111111",
+ "11101111",
+ "11101",
+ "1101101111",
+ "1011011101",
+ "11111",
+ "1101110101",
+ "1110101011",
+ "1011110111",
+ "1011110101",
+ "1110101101",
+ "1110101111",
+ "1101011011",
+ "1101101011",
+ "1101101101",
+ "1101010111",
+ "1101111011",
+ "1101111101",
+ "1110110111",
+ "1101010101",
+ "1101011101",
+ "1110111011",
+ "1011111011",
+ "1101111111",
+ "1",
+ "111111111",
+ "101011111",
+ "111110101",
+ "111011011",
+ "1011010101",
+ "1010111011",
+ "101111111",
+ "11111011",
+ "11110111",
+ "101101111",
+ "111011111",
+ "1110101",
+ "110101",
+ "1010111",
+ "110101111",
+ "10110111",
+ "10111101",
+ "11101101",
+ "11111111",
+ "101110111",
+ "101011011",
+ "101101011",
+ "110101101",
+ "110101011",
+ "110110111",
+ "11110101",
+ "110111101",
+ "111101101",
+ "1010101",
+ "111010111",
+ "1010101111",
+ "1010111101",
+ "1111101",
+ "11101011",
+ "10101101",
+ "10110101",
+ "1110111",
+ "11011011",
+ "11111101",
+ "101010101",
+ "1111111",
+ "111111101",
+ "101111101",
+ "11010111",
+ "10111011",
+ "11011101",
+ "10101011",
+ "11010101",
+ "111011101",
+ "10101111",
+ "1101111",
+ "1101101",
+ "101010111",
+ "110110101",
+ "101011101",
+ "101110101",
+ "101111011",
+ "1010101101",
+ "111110111",
+ "111101111",
+ "111111011",
+ "1010111111",
+ "101101101",
+ "1011011111",
+ "1011",
+ "1011111",
+ "101111",
+ "101101",
+ "11",
+ "111101",
+ "1011011",
+ "101011",
+ "1101",
+ "111101011",
+ "10111111",
+ "11011",
+ "111011",
+ "1111",
+ "111",
+ "111111",
+ "110111111",
+ "10101",
+ "10111",
+ "101",
+ "110111",
+ "1111011",
+ "1101011",
+ "11011111",
+ "1011101",
+ "111010101",
+ "1010110111",
+ "110111011",
+ "1010110101",
+ "1011010111",
+ "1110110101",
+}; //}}}
+#endif
+
+// Function to display message in GUI
+void cw_message_sent(const char* str);
+
+struct cw_message_s {
+ char message[MAX_MESSAGE_LEN];
+ size_t message_len;
+
+ int freq;
+
+ // If dit_duration is 0, the message is sent in PSK31
+ int dit_duration;
+};
+
+// The queue contains above structs
+QueueHandle_t cw_msg_queue;
+
+// Queue that contains audio data
+QueueHandle_t cw_audio_queue;
+static int cw_psk31_samplerate;
+
+static int cw_transmit_ongoing;
+
+static void cw_psk31_task(void *pvParameters);
+
+void cw_psk31_init(unsigned int samplerate)
+{
+ cw_psk31_samplerate = samplerate;
+ cw_transmit_ongoing = 0;
+
+ cw_msg_queue = xQueueCreate(10, sizeof(struct cw_message_s));
+ if (cw_msg_queue == 0) {
+ while(1); /* fatal error */
+ }
+
+ cw_audio_queue = xQueueCreate(1, AUDIO_BUF_LEN * sizeof(int16_t));
+ if (cw_audio_queue == 0) {
+ while(1); /* fatal error */
+ }
+
+ xTaskCreate(
+ cw_psk31_task,
+ "CWPSKTask",
+ 8*configMINIMAL_STACK_SIZE,
+ (void*) NULL,
+ tskIDLE_PRIORITY + 3UL,
+ NULL);
+}
+
+/* Parse one CW letter/symbol, and fill in the on_buffer.
+ * Returns number of uint8_t written.
+ */
+size_t cw_symbol(uint8_t sym, uint8_t *on_buffer, size_t on_buffer_size)
+{
+ uint8_t p = 0;
+ uint8_t val = cw_mapping[sym];
+ size_t pos = 0;
+
+ while((val >> p) != 0b1) {
+
+ if (((val >> p) & 0b1) == 0b1) {
+ if (pos + 2 < on_buffer_size) {
+ // tone(1)
+ on_buffer[pos++] = 1;
+
+ // silence(1)
+ on_buffer[pos++] = 0;
+ }
+ }
+ else {
+ if (pos + 4 < on_buffer_size) {
+ // tone(3)
+ on_buffer[pos++] = 1;
+ on_buffer[pos++] = 1;
+ on_buffer[pos++] = 1;
+
+ // silence(1)
+ on_buffer[pos++] = 0;
+ }
+ }
+
+ p++;
+ }
+
+ // silence(2)
+ if (pos + 2 < on_buffer_size) {
+ for (int i = 0; i < 2; i++) {
+ on_buffer[pos++] = 0;
+ }
+ }
+
+ return pos;
+}
+
+// Transmit a string in morse code or PSK31.
+// Supported range for CW:
+// All ASCII between '+' and '\', which includes
+// numerals and capital letters.
+// Distinction between CW and PSK31 is done on dit_duration==0
+int cw_psk31_push_message(const char* text, int dit_duration, int frequency)
+{
+ const int text_len = strlen(text);
+
+ if (strlen(text) > MAX_MESSAGE_LEN) {
+ return 0;
+ }
+
+ struct cw_message_s msg;
+ for (int i = 0; i < MAX_MESSAGE_LEN; i++) {
+ if (i < text_len) {
+ msg.message[i] = text[i];
+ }
+ else {
+ msg.message[i] = '\0';
+ }
+ }
+ msg.message_len = text_len;
+ msg.freq = frequency;
+ msg.dit_duration = dit_duration;
+
+ xQueueSendToBack(cw_msg_queue, &msg, portMAX_DELAY); /* Send Message */
+
+ cw_message_sent(msg.message);
+
+ return 1;
+}
+
+/* Parse the message and fill the on_buffer with CW on/CW off information.
+ * Returns the number of on/off bits written.
+ */
+static size_t cw_text_to_on_buffer(const char *msg, uint8_t *on_buffer, size_t on_buffer_size)
+{
+ size_t pos = 0;
+ const char* sym = msg;
+ do {
+ if (*sym < '+' || *sym > '\\') {
+ if (pos + 3 < on_buffer_size) {
+ for (int i = 0; i < 3; i++) {
+ on_buffer[pos++] = 0;
+ }
+ }
+ }
+ else {
+ pos += cw_symbol(*sym - '+', on_buffer + pos, on_buffer_size - pos);
+ }
+ sym++;
+ } while (*sym != '\0');
+
+ return pos;
+}
+
+
+#if ENABLE_PSK31
+/*
+ * Turn a null terminated ASCII string into a uint8_t buffer
+ * of 0 and 1 representing the PSK31 varicode for the input.
+ *
+ * outstr must be at least size 20 + strlen(instr)*12 + 20 to accomodate
+ * the header and tail.
+ *
+ * Returns number of bytes written.
+ */
+static size_t psk31_text_to_phase_buffer(const char* instr, uint8_t* outbits)
+{
+ int i=0, j, k;
+
+ /* Header of 0s */
+ for (j=0; j < 20; j++) {
+ outbits[i++] = 0;
+ }
+
+ /* Encode the message, with 00 between letters */
+ for (j=0; j < strlen(instr); j++) {
+ const char* varicode_bits = psk31_varicode[(int)instr[j]];
+ for(k=0; k < strlen(varicode_bits); k++) {
+ outbits[i++] = (varicode_bits[k] == '1') ? 1 : 0;
+ }
+ outbits[i++] = 0;
+ outbits[i++] = 0;
+ }
+
+ /* Tail of 0s */
+ for (j=0; j < 20; j++) {
+ outbits[i++] = 0;
+ }
+
+ return i;
+}
+#endif
+
+
+size_t cw_psk31_fill_buffer(int16_t *buf, size_t bufsize)
+{
+ if (xQueueReceiveFromISR(cw_audio_queue, buf, NULL)) {
+ return bufsize;
+ }
+ else {
+ return 0;
+ }
+}
+
+static int16_t cw_audio_buf[AUDIO_BUF_LEN];
+static uint8_t cw_psk31_buffer[MAX_ON_BUFFER_LEN];
+static struct cw_message_s cw_fill_msg_current;
+
+// Routine to generate CW audio
+static float cw_generate_audio_ampl = 0.0f;
+static float cw_generate_audio_nco = 0.0f;
+static int16_t cw_generate_audio(float omega, int i, int __attribute__ ((unused))t)
+{
+ int16_t s = 0;
+ // Remove clicks from CW
+ if (cw_psk31_buffer[i]) {
+ const float remaining = 32768.0f - cw_generate_audio_ampl;
+ cw_generate_audio_ampl += remaining / 64.0f;
+ }
+ else {
+ cw_generate_audio_ampl -= cw_generate_audio_ampl / 64.0f;
+
+ if (cw_generate_audio_ampl < 0) {
+ cw_generate_audio_ampl = 0;
+ }
+ }
+
+ cw_generate_audio_nco += omega;
+ if (cw_generate_audio_nco > FLOAT_PI) {
+ cw_generate_audio_nco -= 2.0f * FLOAT_PI;
+ }
+
+ s = cw_generate_audio_ampl * arm_sin_f32(cw_generate_audio_nco);
+ return s;
+}
+
+#if ENABLE_PSK31
+static float psk31_generate_audio_nco = 0.0f;
+static int psk31_current_psk_phase = 1;
+static int16_t psk31_generate_audio(float omega, int i, int t, int samples_per_symbol)
+{
+ int16_t s = 0;
+ const float base_ampl = 20000.0f;
+ float psk31_generate_audio_ampl = 0.0f;
+
+ if (cw_psk31_buffer[i] == 1) {
+ psk31_generate_audio_ampl = base_ampl;
+ }
+ else {
+ psk31_generate_audio_ampl = base_ampl * arm_cos_f32(
+ FLOAT_PI*(float)t/(float)samples_per_symbol);
+ }
+
+ psk31_generate_audio_nco += omega;
+ if (psk31_generate_audio_nco > FLOAT_PI) {
+ psk31_generate_audio_nco -= 2.0f * FLOAT_PI;
+ }
+
+ s = psk31_generate_audio_ampl *
+ arm_sin_f32(psk31_generate_audio_nco +
+ (psk31_current_psk_phase == 1 ? 0.0f : FLOAT_PI));
+
+ return s;
+}
+#endif
+
+static void cw_psk31_task(void __attribute__ ((unused))*pvParameters)
+{
+ int buf_pos = 0;
+
+ while (1) {
+ int status = xQueueReceive(cw_msg_queue, &cw_fill_msg_current, portMAX_DELAY);
+ if (status == pdTRUE) {
+
+ size_t cw_psk31_buffer_len = 0;
+
+ cw_transmit_ongoing = 1;
+
+ if (cw_fill_msg_current.dit_duration) {
+ cw_psk31_buffer_len = cw_text_to_on_buffer(
+ cw_fill_msg_current.message,
+ cw_psk31_buffer,
+ MAX_ON_BUFFER_LEN);
+
+ }
+#if ENABLE_PSK31
+ else {
+ cw_psk31_buffer_len = psk31_text_to_phase_buffer(
+ cw_fill_msg_current.message,
+ cw_psk31_buffer);
+ }
+#endif
+
+ // Angular frequency of NCO
+ const float omega = 2.0f * FLOAT_PI * cw_fill_msg_current.freq /
+ (float)cw_psk31_samplerate;
+
+ const int samples_per_symbol = (cw_fill_msg_current.dit_duration != 0) ?
+ (cw_psk31_samplerate * cw_fill_msg_current.dit_duration) / 1000 :
+ /* BPSK31 is at 31.25 symbols per second. */
+ cw_psk31_samplerate * 100 / 3125;
+
+#if ENABLE_PSK31
+ psk31_current_psk_phase = 1;
+#endif
+
+ for (int i = 0; i < cw_psk31_buffer_len; i++) {
+ for (int t = 0; t < samples_per_symbol; t++) {
+#if ENABLE_PSK31
+ int16_t s = (cw_fill_msg_current.dit_duration != 0) ?
+ cw_generate_audio(omega, i, t) :
+ psk31_generate_audio(omega, i, t, samples_per_symbol);
+#else
+ int16_t s = cw_generate_audio(omega, i, t);
+#endif
+
+ // Stereo
+ for (int channel = 0; channel < 2; channel++) {
+ if (buf_pos == AUDIO_BUF_LEN) {
+ // It should take AUDIO_BUF_LEN/cw_psk31_samplerate seconds to send one buffer.
+ // If it takes more than 4 times as long, we think there is a problem.
+ const TickType_t reasonable_delay = pdMS_TO_TICKS(4000 * AUDIO_BUF_LEN / cw_psk31_samplerate);
+ if (xQueueSendToBack(cw_audio_queue, &cw_audio_buf, reasonable_delay) != pdTRUE) {
+ trigger_fault(FAULT_SOURCE_CW_AUDIO_QUEUE);
+ }
+ buf_pos = 0;
+ }
+ cw_audio_buf[buf_pos++] = s;
+ }
+
+ }
+
+#if ENABLE_PSK31
+ if (cw_psk31_buffer[i] == 0) {
+ psk31_current_psk_phase *= -1;
+ }
+#endif
+ }
+
+ // We have completed this message
+
+ cw_transmit_ongoing = 0;
+ }
+ }
+}
+
+int cw_psk31_busy(void)
+{
+ return cw_transmit_ongoing;
+}
+
diff --git a/src/common/Audio/cw.h b/src/common/Audio/cw.h
new file mode 100644
index 0000000..39be9c5
--- /dev/null
+++ b/src/common/Audio/cw.h
@@ -0,0 +1,53 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 Matthias P. Braendli
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+*/
+
+#ifndef __CW_H_
+#define __CW_H_
+
+#include <stdint.h>
+#include <stddef.h>
+
+// Setup the CW generator to create audio samples at the given
+// samplerate.
+void cw_psk31_init(unsigned int samplerate);
+
+// Append new CW or PSK31 text to transmit
+// CW/PSK31 audio centre frequency in Hz
+// if dit_duration == 0, message is sent in PSK31
+// otherwise it is sent in CW, with dit_duration in ms
+// returns 0 on failure, 1 on success
+int cw_psk31_push_message(const char* text, int frequency, int dit_duration);
+
+// Write the waveform into the buffer (stereo), both for cw and psk31
+size_t cw_psk31_fill_buffer(int16_t *buf, size_t bufsize);
+
+// Return 1 if the CW or PSK31 generator is running
+int cw_psk31_busy(void);
+
+void cw_message_sent(const char*);
+
+size_t cw_symbol(uint8_t, uint8_t *, size_t);
+
+#endif // __CW_H_
+
diff --git a/src/common/Audio/tone.c b/src/common/Audio/tone.c
new file mode 100644
index 0000000..90a8394
--- /dev/null
+++ b/src/common/Audio/tone.c
@@ -0,0 +1,358 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Maximilien Cuony, Matthias P. Braendli
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+*/
+
+#include "Audio/tone.h"
+#include "Core/common.h"
+#include "GPIO/usart.h"
+
+#include <stdlib.h>
+#include "queue.h"
+
+#ifdef SIMULATOR
+#include <math.h>
+#define arm_cos_f32 cosf
+#define arm_sin_f32 sinf
+#else
+#include "arm_math.h"
+#endif
+
+#define TONE_N 800
+
+/* DTMF frequencies
+ * COL_1 COL_2 COL_3 COL_A
+ * 1209 1336 1477 1633
+ * 697 [1] [2] [3] [A] ROW_1
+ * 770 [4] [5] [6] [B] ROW_4
+ * 852 [7] [8] [9] [C] ROW_7
+ * 941 [*] [0] [#] [D] ROW_STAR
+ *
+ * We need 0, 7, and 1750Hz for now, having detectors for
+ * 1209, 1336, 852, 941 and 1750 is sufficient. */
+
+#define DET_COL_1 0
+#define DET_COL_2 1
+#define DET_ROW_7 2
+#define DET_ROW_STAR 3
+#define DET_1750 4
+#define NUM_DETECTORS 5
+
+// Incomplete because not all frequencies decoded
+enum dtmf_code {
+ DTMF_NONE = 0,
+ DTMF_0,
+ DTMF_7,
+ DTMF_8,
+ DTMF_STAR,
+};
+
+struct tone_detector {
+ float coef;
+ float Q1;
+ float Q2;
+};
+
+static struct tone_detector detectors[NUM_DETECTORS];
+
+static int num_samples_analysed = 0;
+static float prev_mean = 0;
+static uint32_t accum = 0;
+static int lost_results = 0;
+static QueueHandle_t m_squared_queue;
+
+// Apply an IIR filter with alpha = 5/16 to smooth out variations.
+// Values are normalised s.t. the mean corresponds to 100
+static int32_t normalised_results[NUM_DETECTORS];
+
+static int num_tone_1750_detected = 0;
+static uint64_t tone_1750_detected_since = 0;
+static int detectors_enabled = 0;
+
+static enum dtmf_code dtmf_last_seen = 0;
+static uint64_t dtmf_last_seen_at = 0;
+
+// Store the sequence of dtmf codes in this FIFO. If no DTMF code gets
+// decoded in the interval, a NONE gets inserted into the sequence
+#define NUM_DTMF_SEQ 3
+#define DTMF_MAX_TONE_INTERVAL 2500
+static enum dtmf_code dtmf_sequence[NUM_DTMF_SEQ];
+
+static char* dtmf_to_str(enum dtmf_code code)
+{
+ char *codestr = "?";
+ switch (code) {
+ case DTMF_0: codestr = "0"; break;
+ case DTMF_7: codestr = "7"; break;
+ case DTMF_8: codestr = "8"; break;
+ case DTMF_STAR: codestr = "*"; break;
+ case DTMF_NONE: codestr = "x"; break;
+ }
+ return codestr;
+}
+
+static inline void push_dtmf_code(enum dtmf_code code)
+{
+ for (int i = 0; i < NUM_DTMF_SEQ-1; i++) {
+ dtmf_sequence[i] = dtmf_sequence[i+1];
+ }
+ dtmf_sequence[NUM_DTMF_SEQ-1] = code;
+
+ if (code != DTMF_NONE) {
+ usart_debug("DTMF: [%s, %s, %s]\r\n",
+ dtmf_to_str(dtmf_sequence[0]),
+ dtmf_to_str(dtmf_sequence[1]),
+ dtmf_to_str(dtmf_sequence[2]));
+ }
+}
+
+// TODO: Does that depend on TONE_N?
+const int thresh_dtmf = 200;
+const int thresh_1750 = 300;
+const int num_1750_required = 5;
+
+static void analyse_dtmf()
+{
+ // Bits 0 to 9 are numbers, bit 10 to 13 letters, bit 14 is star, 15 is hash
+ const uint16_t pattern =
+ ((normalised_results[DET_COL_1] > thresh_dtmf &&
+ normalised_results[DET_ROW_7] > thresh_dtmf) ? (1 << 7) : 0) +
+ ((normalised_results[DET_COL_2] > thresh_dtmf &&
+ normalised_results[DET_ROW_7] > thresh_dtmf) ? (1 << 8) : 0) +
+ ((normalised_results[DET_COL_1] > thresh_dtmf &&
+ normalised_results[DET_ROW_STAR] > thresh_dtmf) ? (1 << 14) : 0) +
+ ((normalised_results[DET_COL_2] > thresh_dtmf &&
+ normalised_results[DET_ROW_STAR] > thresh_dtmf) ? (1 << 0) : 0);
+
+ // Match patterns exactly to exclude multiple simultaneous DTMF codes.
+ if (pattern == (1 << 0)) {
+ if (dtmf_last_seen != DTMF_0) {
+ push_dtmf_code(DTMF_0);
+ }
+
+ dtmf_last_seen = DTMF_0;
+ dtmf_last_seen_at = timestamp_now();
+ }
+ else if (pattern == (1 << 7)) {
+ if (dtmf_last_seen != DTMF_7) {
+ push_dtmf_code(DTMF_7);
+ }
+
+ dtmf_last_seen = DTMF_7;
+ dtmf_last_seen_at = timestamp_now();
+ }
+ else if (pattern == (1 << 8)) {
+ if (dtmf_last_seen != DTMF_8) {
+ push_dtmf_code(DTMF_8);
+ }
+
+ dtmf_last_seen = DTMF_8;
+ dtmf_last_seen_at = timestamp_now();
+ }
+ else if (pattern == (1 << 14)) {
+ if (dtmf_last_seen != DTMF_STAR) {
+ push_dtmf_code(DTMF_STAR);
+ }
+
+ dtmf_last_seen = DTMF_STAR;
+ dtmf_last_seen_at = timestamp_now();
+ }
+ else if (dtmf_last_seen_at + DTMF_MAX_TONE_INTERVAL < timestamp_now()) {
+ // Flush out all codes
+ push_dtmf_code(DTMF_NONE);
+
+ dtmf_last_seen = DTMF_NONE;
+ dtmf_last_seen_at = timestamp_now();
+ }
+}
+
+
+int tone_1750_status()
+{
+ return num_tone_1750_detected >= num_1750_required;
+}
+
+int tone_1750_for_5_seconds()
+{
+ return (num_tone_1750_detected >= num_1750_required) &&
+ tone_1750_detected_since + 5000 < timestamp_now();
+}
+
+int tone_fax_status()
+{
+ return dtmf_sequence[0] == DTMF_0 &&
+ dtmf_sequence[1] == DTMF_7 &&
+ dtmf_sequence[2] == DTMF_STAR;
+}
+
+static inline void init_detector(struct tone_detector* detector, int freq) {
+ detector->coef = 2.0f * arm_cos_f32(2.0f * FLOAT_PI * freq / AUDIO_IN_RATE);
+ detector->Q1 = 0;
+ detector->Q2 = 0;
+}
+
+void tone_init() {
+ m_squared_queue = xQueueCreate(2, NUM_DETECTORS * sizeof(float));
+ if (m_squared_queue == 0) {
+ trigger_fault(FAULT_SOURCE_ADC2_QUEUE);
+ }
+
+ for (int i = 0; i < NUM_DTMF_SEQ; i++) {
+ dtmf_sequence[i] = DTMF_NONE;
+ }
+
+ init_detector(&detectors[DET_COL_1], 1209);
+ init_detector(&detectors[DET_COL_2], 1336);
+ init_detector(&detectors[DET_ROW_7], 852);
+ init_detector(&detectors[DET_ROW_STAR], 941);
+ init_detector(&detectors[DET_1750], 1750);
+}
+
+void tone_detector_enable(int enable)
+{
+ if (enable && !detectors_enabled) {
+ num_samples_analysed = 0;
+ prev_mean = 0;
+ accum = 0;
+ for (int det = 0; det < NUM_DETECTORS; det++) {
+ detectors[det].Q1 = 0;
+ detectors[det].Q2 = 0;
+ }
+ audio_in_enable(1);
+ detectors_enabled = 1;
+ }
+ else if (!enable && detectors_enabled) {
+ audio_in_enable(0);
+ detectors_enabled = 0;
+ }
+}
+
+void tone_detect_push_sample(const uint16_t sample, int is_irq)
+{
+ num_samples_analysed++;
+
+ accum += sample;
+
+ // Do not do tone detection before we have calculated a mean
+ if (prev_mean > 0) {
+ const float s = sample - prev_mean;
+
+ for (int det = 0; det < NUM_DETECTORS; det++) {
+ struct tone_detector *detector = &detectors[det];
+
+ float Q0 = detector->coef * detector->Q1 - detector->Q2 + s;
+ detector->Q2 = detector->Q1;
+ detector->Q1 = Q0;
+ }
+ }
+
+ if (num_samples_analysed == TONE_N) {
+ num_samples_analysed = 0;
+
+ if (prev_mean > 0) {
+ float m_squared[NUM_DETECTORS];
+ for (int det = 0; det < NUM_DETECTORS; det++) {
+ struct tone_detector *detector = &detectors[det];
+ m_squared[det] =
+ detector->Q1 * detector->Q1 +
+ detector->Q2 * detector->Q2 -
+ detector->coef * detector->Q1 * detector->Q2;
+ detector->Q1 = 0;
+ detector->Q2 = 0;
+ }
+
+ BaseType_t require_context_switch = 0;
+ int success;
+ if (is_irq) {
+ success = xQueueSendToBackFromISR(
+ m_squared_queue,
+ &m_squared,
+ &require_context_switch);
+ }
+ else {
+ success = xQueueSendToBack(
+ m_squared_queue,
+ &m_squared,
+ 0);
+ }
+
+ if (success == pdFALSE) {
+ lost_results++;
+ }
+ }
+
+ prev_mean = (float)accum / (float)TONE_N;
+ accum = 0;
+ }
+}
+
+void tone_do_analysis()
+{
+ float m[NUM_DETECTORS];
+ while (!xQueueReceive(m_squared_queue, &m, portMAX_DELAY)) {}
+
+ float inv_mean = 0;
+ for (int det = 0; det < NUM_DETECTORS; det++) {
+ m[det] = sqrtf(m[det]);
+ inv_mean += m[det];
+ }
+ inv_mean = NUM_DETECTORS / inv_mean;
+
+ for (int det = 0; det < NUM_DETECTORS; det++) {
+ normalised_results[det] =
+ (11 * normalised_results[det] +
+ (int)(5 * 100 * m[det] * inv_mean))
+ >> 4; // divide by 16
+ }
+
+ if (num_tone_1750_detected < num_1750_required &&
+ normalised_results[DET_1750] > thresh_1750) {
+ num_tone_1750_detected++;
+
+ if (num_tone_1750_detected == num_1750_required) {
+ tone_1750_detected_since = timestamp_now();
+ }
+ }
+ else if (num_tone_1750_detected > 0 &&
+ normalised_results[DET_1750] <= thresh_1750) {
+ num_tone_1750_detected--;
+ }
+
+ analyse_dtmf();
+
+#if PRINT_TONES_STATS
+ static int printcounter = 0;
+ if (++printcounter == 5) {
+ usart_debug("Tones: % 3d % 3d % 3d % 3d % 3d since %d\r\n",
+ normalised_results[0],
+ normalised_results[1],
+ normalised_results[2],
+ normalised_results[3],
+ normalised_results[4],
+ (int)(timestamp_now() - tone_1750_detected_since)
+ );
+
+ printcounter = 0;
+ }
+#endif // PRINT_TONES_STATS
+}
+
diff --git a/src/common/Audio/tone.h b/src/common/Audio/tone.h
new file mode 100644
index 0000000..ba05304
--- /dev/null
+++ b/src/common/Audio/tone.h
@@ -0,0 +1,53 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Matthias P. Braendli, Maximilien Cuony
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+*/
+
+#ifndef __TONE_H_
+#define __TONE_H_
+
+#include <stdio.h>
+#include "Audio/audio_in.h"
+
+#define TONE_BUFFER_LEN AUDIO_IN_BUF_LEN
+
+void tone_init(void);
+
+void tone_detector_enable(int enable);
+
+/* Return 1 when 1750 detected, 0 otherwise */
+int tone_1750_status(void);
+
+/* Return 1 if 1750 is currently detected, and has been already for at
+ * least 5s */
+int tone_1750_for_5_seconds(void);
+
+/* The FAX status is 1 if the recently decoded DTMF is the 0-7-* sequence. */
+int tone_fax_status(void);
+
+/* Must be called by task to do the analysis */
+void tone_do_analysis(void);
+
+/* Push a sample from the ADC ISR or Pulseaudio task */
+void tone_detect_push_sample(const uint16_t sample, int is_irq);
+
+#endif