SatCam/STM32_pocket/Src/audio.c

451 wiersze
15 KiB
C

/*************************************************************************
*
* SatCam - Camera Module for PSAT-2
* Copyright (c) 2015-2017 Ales Povalac <alpov@alpov.net>
* Dept. of Radio Electronics, Brno University of Technology
*
* This work is licensed under the terms of the MIT license
*
*************************************************************************/
#include "cube.h"
#include <arm_math.h>
#include "sstv.h"
#include "eeprom.h"
#include "comm.h"
#include "audio.h"
#define INCLUDE_VARICODE
#include "varicode.h"
#define INCLUDE_MORSE
#include "morse.h"
static uint8_t audio_buffer[AUDIO_BUFFER_LEN];
static volatile uint8_t audio_current_buffer = 0;
static uint16_t idx;
static q31_t phi;
void HAL_DACEx_ConvCpltCallbackCh2(DAC_HandleTypeDef* hdac)
{
/* DMA reached the end of buffer */
audio_current_buffer = 0;
HAL_PWR_DisableSleepOnExit();
}
void HAL_DACEx_ConvHalfCpltCallbackCh2(DAC_HandleTypeDef* hdac)
{
/* DMA at the half of buffer */
audio_current_buffer = 1;
HAL_PWR_DisableSleepOnExit();
}
static void sample_to_buffer(uint8_t value)
{
audio_buffer[idx++] = value;
if (hdma_dac2.State == HAL_DMA_STATE_READY && idx == AUDIO_BUFFER_LEN/2) {
/* buffer filled to 1st half, DMA idle -> start audio output */
HAL_TIM_Base_Start(&htim6);
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_2, (uint32_t*)audio_buffer, AUDIO_BUFFER_LEN, DAC_ALIGN_8B_R);
audio_current_buffer = 0;
}
else if (idx == AUDIO_BUFFER_LEN/2) {
/* buffer filled to 1st half, transmit 2nd half and sleep until the transmission of 1st half begins */
HAL_PWR_EnableSleepOnExit();
while (audio_current_buffer == 1) {}
}
else if (idx == AUDIO_BUFFER_LEN) {
/* buffer filled to 2nd half, transmit 1st half and sleep until the transmission of 2nd half begins */
HAL_PWR_EnableSleepOnExit();
while (audio_current_buffer == 0) {}
idx = 0;
HAL_IWDG_Refresh(&hiwdg); // 200ms period (AUDIO_BUFFER_LEN/SAMPLE_FREQ)
}
}
static void audio_to_buffer(uint16_t freq, q15_t ampl)
{
phi += (q31_t)((1ULL << 31) / SAMPLE_FREQ) * freq; // phase accumulator
phi &= 0x7fffffff;
int8_t y = (arm_sin_q15(phi >> 16) * ampl) >> (24-1); // sinus(phi) * amplitude, convert to q7_t
y ^= 0x80; // bias to Vcc/2
sample_to_buffer(y);
}
static void audio_play_tone(uint32_t samples, uint16_t freq, q15_t volume)
{
while (samples--) audio_to_buffer(freq, volume);
}
static void audio_play_line(uint16_t t, uint16_t width, uint8_t *line, q15_t volume)
{
for (int i = 0; i < t; i++) {
int idx = i * width / t;
int freq = 1500 + (2300-1500) * line[idx] / 255; // convert uint8_t to 1500-2300Hz range
audio_to_buffer(freq, volume);
}
}
static void audio_play_psk(uint16_t samples, uint16_t freq, uint8_t symbol, q15_t volume)
{
static int16_t invert = 1;
if (symbol == PSK_SYM_1) {
/* symbol 1 - keep phase */
q15_t ampl = volume;
ampl *= invert;
for (uint16_t i = 0; i < samples; i++) {
audio_to_buffer(freq, ampl);
}
} else {
/* symbol 0 - reverse phase; START/STOP - half of the symbol */
if (symbol == PSK_SYM_START) invert = 1;
uint16_t start = (symbol == PSK_SYM_START) ? samples/2 : 0;
uint16_t stop = (symbol == PSK_SYM_STOP) ? samples/2 : samples;
for (uint16_t i = start; i < stop; i++) {
q15_t ampl = ((q31_t)(volume) * arm_cos_q15((0x4000 * i) / samples)) >> (16-1);
ampl *= invert;
audio_to_buffer(freq, ampl);
}
invert *= -1;
}
}
static void audio_psk_char(uint16_t rate, uint16_t freq, char c)
{
uint16_t varicode = VARICODE_TABLE[c & 0x7f] >> 2;
while (varicode) {
audio_play_psk(rate, freq, varicode & 0x8000 ? PSK_SYM_1 : PSK_SYM_0, AUDIO_VOLUME_PSK);
varicode <<= 1;
}
}
void audio_start()
{
// reset buffer index and phase accumulator
// HAL_GPIO_WritePin(GPIO_PTT_GPIO_Port, GPIO_PTT_Pin, GPIO_PIN_SET);
syslog_event(LOG_AUDIO_START);
idx = 0;
phi = 0;
// cosinus ramp up from 0V to Vcc/2 bias
for (uint16_t i = 0; i < AUDIO_BUFFER_LEN; i++) {
q15_t theta = i * (0x4000 / AUDIO_BUFFER_LEN);
q15_t ramp = (arm_cos_q15(theta + 0x4000) / 2) + 0x4000;
sample_to_buffer(ramp >> 8);
}
}
void audio_stop()
{
// cosinus ramp down from Vcc/2 bias to 0V
for (uint16_t i = 0; i < AUDIO_BUFFER_LEN; i++) {
q15_t theta = i * (0x4000 / AUDIO_BUFFER_LEN);
q15_t ramp = (arm_cos_q15(theta) / 2) + 0x4000;
sample_to_buffer(ramp >> 8);
}
for (uint16_t i = 0; i < AUDIO_BUFFER_LEN; i++) sample_to_buffer(0); // fill buffer with zero samples
HAL_DAC_Stop_DMA(&hdac, DAC_CHANNEL_2); // stop audio output -> ca. half buffer will be lost
HAL_TIM_Base_Stop(&htim6);
// HAL_GPIO_WritePin(GPIO_PTT_GPIO_Port, GPIO_PTT_Pin, GPIO_PIN_RESET);
syslog_event(LOG_AUDIO_STOP);
}
static uint16_t audio_psk_get_rate(uint16_t speed)
{
switch (speed) {
case 31: return (SAMPLE_FREQ / 31.25);
case 63: return (SAMPLE_FREQ / 62.5);
case 125: default: return (SAMPLE_FREQ / 125);
case 250: return (SAMPLE_FREQ / 250);
case 500: return (SAMPLE_FREQ / 500);
case 1000: return (SAMPLE_FREQ / 1000);
}
}
void audio_psk(uint16_t speed, uint16_t freq, const char *s)
{
if (freq < 100) freq = PSK_FREQ;
if (freq > 5000) freq = PSK_FREQ;
uint32_t tickstart = HAL_GetTick();
uint16_t rate = audio_psk_get_rate(speed);
audio_play_psk(rate, freq, PSK_SYM_START, AUDIO_VOLUME_PSK); // start
for (uint16_t i = 0; i < (SAMPLE_FREQ / rate); i++) audio_play_psk(rate, freq, PSK_SYM_0, AUDIO_VOLUME_PSK); // 1sec of zeros - sync
audio_psk_char(rate, freq, '\r'); // CR
while (*s && (HAL_GetTick() - tickstart < AUDIO_TIMEOUT)) {
audio_psk_char(rate, freq, *s++); // data
}
audio_psk_char(rate, freq, '\r'); // CR
audio_psk_char(rate, freq, ' '); // space to fix last CR
for (uint16_t i = 0; i < (SAMPLE_FREQ / rate); i++) audio_play_psk(rate, freq, PSK_SYM_1, AUDIO_VOLUME_PSK); // 1sec of ones - no data
audio_play_psk(rate, freq, PSK_SYM_STOP, AUDIO_VOLUME_PSK); // stop
}
static void audio_play_morse(uint16_t samples, uint16_t freq, bool key)
{
static uint16_t shaping = 0;
if (key) {
shaping = 0.010*SAMPLE_FREQ; // 10ms rise/fall time for CW
audio_play_psk(shaping, freq, PSK_SYM_START, AUDIO_VOLUME_MORSE);
audio_play_psk(samples - shaping, freq, PSK_SYM_1, AUDIO_VOLUME_MORSE);
audio_play_psk(shaping, freq, PSK_SYM_STOP, AUDIO_VOLUME_MORSE);
phi = 0; // reset phase accumulator to keep DAC at bias during gap (CW is not coherent)
} else {
audio_play_tone(samples - shaping, 0, AUDIO_VOLUME_MORSE);
shaping = 0;
}
}
void audio_morse(uint16_t wpm, uint16_t freq, const char *s)
{
if (wpm < 5) wpm = 5;
if (wpm > 40) wpm = 40;
if (freq < 100) freq = CW_FREQ;
if (freq > 5000) freq = CW_FREQ;
uint32_t tickstart = HAL_GetTick();
uint16_t samples = (SAMPLE_FREQ * 6) / (5 * wpm); // u = 1.2/c for PARIS
while (*s && (HAL_GetTick() - tickstart < AUDIO_TIMEOUT)) {
char chr = *s++;
uint8_t code = 0x80;
if (chr >= '0' && chr <= '9') code = morse[chr - '0' + 0];
else if (chr >= 'A' && chr <= 'Z') code = morse[chr - 'A' + 10];
else if (chr >= 'a' && chr <= 'z') code = morse[chr - 'a' + 10];
else {
for (uint8_t i = 0; i < sizeof(spechar); i++) // Read through the array
if (chr == spechar[i]) code = morse[i+36]; // Map it to morse code
}
if (chr == ' ') {
audio_play_morse((IWGLEN - ICGLEN) * samples, freq, false); // ICG was already played after previous char
} else {
while (code != 0x80) {
if (code & 0x80) audio_play_morse(DAHLEN * samples, freq, true); // Play a dash
else audio_play_morse(DITLEN * samples, freq, true); // Play a dot
audio_play_morse(IEGLEN * samples, freq, false); // Inter Element gap
code <<= 1;
}
audio_play_morse((ICGLEN - IEGLEN) * samples, freq, false); // IEG was already played after element
}
}
}
void audio_play_vox_start()
{
audio_play_vox_stop();
audio_play_tone(0.100*SAMPLE_FREQ, 2300, AUDIO_VOLUME_SSTV);
audio_play_tone(0.100*SAMPLE_FREQ, 1500, AUDIO_VOLUME_SSTV);
audio_play_tone(0.100*SAMPLE_FREQ, 2300, AUDIO_VOLUME_SSTV);
audio_play_tone(0.100*SAMPLE_FREQ, 1500, AUDIO_VOLUME_SSTV);
}
void audio_play_vox_stop()
{
audio_play_tone(0.100*SAMPLE_FREQ, 1900, AUDIO_VOLUME_SSTV);
audio_play_tone(0.100*SAMPLE_FREQ, 1500, AUDIO_VOLUME_SSTV);
audio_play_tone(0.100*SAMPLE_FREQ, 1900, AUDIO_VOLUME_SSTV);
audio_play_tone(0.100*SAMPLE_FREQ, 1500, AUDIO_VOLUME_SSTV);
}
void audio_play_vis(uint8_t vis)
{
audio_play_tone(0.300*SAMPLE_FREQ, 1900, AUDIO_VOLUME_SSTV);
audio_play_tone(0.010*SAMPLE_FREQ, 1200, AUDIO_VOLUME_SSTV);
audio_play_tone(0.300*SAMPLE_FREQ, 1900, AUDIO_VOLUME_SSTV);
audio_play_tone(0.030*SAMPLE_FREQ, 1200, AUDIO_VOLUME_SSTV);
for (uint8_t i = 0; i < 8; i++) {
audio_play_tone(0.030*SAMPLE_FREQ, (vis & 0x01) ? 1100 : 1300, AUDIO_VOLUME_SSTV);
vis >>= 1;
}
audio_play_tone(0.030*SAMPLE_FREQ, 1200, AUDIO_VOLUME_SSTV);
}
void audio_play_vis16(uint16_t vis)
{
audio_play_tone(0.300*SAMPLE_FREQ, 1900, AUDIO_VOLUME_SSTV);
audio_play_tone(0.010*SAMPLE_FREQ, 1200, AUDIO_VOLUME_SSTV);
audio_play_tone(0.300*SAMPLE_FREQ, 1900, AUDIO_VOLUME_SSTV);
audio_play_tone(0.030*SAMPLE_FREQ, 1200, AUDIO_VOLUME_SSTV);
for (uint8_t i = 0; i < 16; i++) {
audio_play_tone(0.030*SAMPLE_FREQ, (vis & 0x0001) ? 1100 : 1300, AUDIO_VOLUME_SSTV);
vis >>= 1;
}
audio_play_tone(0.030*SAMPLE_FREQ, 1200, AUDIO_VOLUME_SSTV);
}
static void audio_compute_luma(uint8_t *scanline, uint8_t *luma, uint8_t lines)
{
for (int i = 0; i < IMG_WIDTH * lines; i++) {
uint16_t y = 0;
y += (uint16_t)scanline[i*3+0] * 30;
y += (uint16_t)scanline[i*3+1] * 59;
y += (uint16_t)scanline[i*3+2] * 11;
y /= 100;
luma[i] = y;
}
}
static void audio_compute_chroma(uint8_t *scanline, uint8_t *luma, uint8_t *chroma, uint8_t mode)
{
for (int i = 0; i < IMG_WIDTH; i++) {
uint16_t y = luma[i];
uint16_t c = scanline[i*3+mode];
chroma[i] = (c-y + 255) / 2;
}
}
static void audio_compute_chroma_2lines(uint8_t *scanline, uint8_t *luma, uint8_t *chroma, uint8_t mode)
{
for (int i = 0; i < IMG_WIDTH; i++) {
uint16_t y = ((uint16_t)luma[i] + luma[i+IMG_WIDTH])/2;
uint16_t c = ((uint16_t)scanline[i*3+mode] + scanline[i*3+mode+IMG_WIDTH*3])/2;
chroma[i] = (c-y + 255) / 2;
}
}
void audio_robot36_color(uint8_t *scanline)
{
uint8_t luma[IMG_WIDTH*2];
uint8_t chroma[IMG_WIDTH];
for (int line = 0; line < IMG_HEIGHT; line += 2) {
// luma 1st line
audio_compute_luma(scanline, luma, 2);
audio_play_tone(0.009*SAMPLE_FREQ, 1200, AUDIO_VOLUME_SSTV);
audio_play_tone(0.003*SAMPLE_FREQ, 1500, AUDIO_VOLUME_SSTV);
audio_play_line(0.088*SAMPLE_FREQ, IMG_WIDTH, luma, AUDIO_VOLUME_SSTV);
// chroma R-Y
audio_compute_chroma_2lines(scanline, luma, chroma, CHROMA_R_Y);
audio_play_tone(0.0045*SAMPLE_FREQ, 1500, AUDIO_VOLUME_SSTV);
audio_play_tone(0.0015*SAMPLE_FREQ, 1900, AUDIO_VOLUME_SSTV);
audio_play_line(0.044*SAMPLE_FREQ, IMG_WIDTH, chroma, AUDIO_VOLUME_SSTV);
// luma 2nd line
audio_play_tone(0.009*SAMPLE_FREQ, 1200, AUDIO_VOLUME_SSTV);
audio_play_tone(0.003*SAMPLE_FREQ, 1500, AUDIO_VOLUME_SSTV);
audio_play_line(0.088*SAMPLE_FREQ, IMG_WIDTH, luma+IMG_WIDTH, AUDIO_VOLUME_SSTV);
// chroma B-Y
audio_compute_chroma_2lines(scanline, luma, chroma, CHROMA_B_Y);
audio_play_tone(0.0045*SAMPLE_FREQ, 2300, AUDIO_VOLUME_SSTV);
audio_play_tone(0.0015*SAMPLE_FREQ, 1900, AUDIO_VOLUME_SSTV);
audio_play_line(0.044*SAMPLE_FREQ, IMG_WIDTH, chroma, AUDIO_VOLUME_SSTV);
scanline += IMG_WIDTH*2*3;
}
}
void audio_robot72_color(uint8_t *scanline)
{
uint8_t luma[IMG_WIDTH];
uint8_t chroma[IMG_WIDTH];
for (int line = 0; line < IMG_HEIGHT; line += 1) {
// luma
audio_compute_luma(scanline, luma, 1);
audio_play_tone(0.009*SAMPLE_FREQ, 1200, AUDIO_VOLUME_SSTV);
audio_play_tone(0.003*SAMPLE_FREQ, 1500, AUDIO_VOLUME_SSTV);
audio_play_line(0.138*SAMPLE_FREQ, IMG_WIDTH, luma, AUDIO_VOLUME_SSTV);
// chroma R-Y
audio_compute_chroma(scanline, luma, chroma, CHROMA_R_Y);
audio_play_tone(0.0045*SAMPLE_FREQ, 1500, AUDIO_VOLUME_SSTV);
audio_play_tone(0.0015*SAMPLE_FREQ, 1900, AUDIO_VOLUME_SSTV);
audio_play_line(0.069*SAMPLE_FREQ, IMG_WIDTH, chroma, AUDIO_VOLUME_SSTV);
// chroma B-Y
audio_compute_chroma(scanline, luma, chroma, CHROMA_B_Y);
audio_play_tone(0.0045*SAMPLE_FREQ, 2300, AUDIO_VOLUME_SSTV);
audio_play_tone(0.0015*SAMPLE_FREQ, 1900, AUDIO_VOLUME_SSTV);
audio_play_line(0.069*SAMPLE_FREQ, IMG_WIDTH, chroma, AUDIO_VOLUME_SSTV);
scanline += IMG_WIDTH*3;
}
}
void audio_mp73(uint8_t *scanline)
{
uint8_t luma[IMG_WIDTH*2];
uint8_t chroma[IMG_WIDTH];
for (int line = 0; line < IMG_HEIGHT; line += 2) {
// luma 1st line
audio_compute_luma(scanline, luma, 2);
audio_play_tone(0.009*SAMPLE_FREQ, 1200, AUDIO_VOLUME_SSTV);
audio_play_tone(0.001*SAMPLE_FREQ, 1500, AUDIO_VOLUME_SSTV);
audio_play_line(0.140*SAMPLE_FREQ, IMG_WIDTH, luma, AUDIO_VOLUME_SSTV);
// chroma R-Y
audio_compute_chroma_2lines(scanline, luma, chroma, CHROMA_R_Y);
audio_play_line(0.140*SAMPLE_FREQ, IMG_WIDTH, chroma, AUDIO_VOLUME_SSTV);
// chroma B-Y
audio_compute_chroma_2lines(scanline, luma, chroma, CHROMA_B_Y);
audio_play_line(0.140*SAMPLE_FREQ, IMG_WIDTH, chroma, AUDIO_VOLUME_SSTV);
// luma 2nd line
audio_play_line(0.140*SAMPLE_FREQ, IMG_WIDTH, luma+IMG_WIDTH, AUDIO_VOLUME_SSTV);
scanline += IMG_WIDTH*2*3;
}
}
void audio_mp115(uint8_t *scanline)
{
uint8_t luma[IMG_WIDTH*2];
uint8_t chroma[IMG_WIDTH];
for (int line = 0; line < IMG_HEIGHT; line += 2) {
// luma 1st line
audio_compute_luma(scanline, luma, 2);
audio_play_tone(0.009*SAMPLE_FREQ, 1200, AUDIO_VOLUME_SSTV);
audio_play_tone(0.001*SAMPLE_FREQ, 1500, AUDIO_VOLUME_SSTV);
audio_play_line(0.223*SAMPLE_FREQ, IMG_WIDTH, luma, AUDIO_VOLUME_SSTV);
// chroma R-Y
audio_compute_chroma_2lines(scanline, luma, chroma, CHROMA_R_Y);
audio_play_line(0.223*SAMPLE_FREQ, IMG_WIDTH, chroma, AUDIO_VOLUME_SSTV);
// chroma B-Y
audio_compute_chroma_2lines(scanline, luma, chroma, CHROMA_B_Y);
audio_play_line(0.223*SAMPLE_FREQ, IMG_WIDTH, chroma, AUDIO_VOLUME_SSTV);
// luma 2nd line
audio_play_line(0.223*SAMPLE_FREQ, IMG_WIDTH, luma+IMG_WIDTH, AUDIO_VOLUME_SSTV);
scanline += IMG_WIDTH*2*3;
}
}