You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

397 lines
9.5 KiB

/*
Copyright Jeroen Vreeken (jeroen@vreeken.net), 2017
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 <http://www.gnu.org/licenses/>.
*/
#include "freedv_eth.h"
#include "sound.h"
#include "io.h"
#include "ctcss.h"
#include "beacon.h"
#include "emphasis.h"
#include "freedv_eth_config.h"
#include <stdio.h>
#include <string.h>
enum tx_state {
TX_STATE_OFF,
TX_STATE_ON,
TX_STATE_TAIL,
TX_STATE_BEEP1,
TX_STATE_BEEP2,
TX_STATE_BEEPD,
};
static bool fullduplex;
static enum tx_state tx_state;
static bool tx_hadvoice;
static bool tx_waslocal;
static int tx_tail;
static int tx_state_cnt;
static struct ctcss *ctcss = NULL;
static struct beacon *beacon = NULL;
static struct emphasis *emphasis_p = NULL;
static int nr_samples = FREEDV_ALAW_NR_SAMPLES;
static bool output_tone = false;
static enum io_hl_ptt ptt = IO_HL_PTT_OFF;
static bool tx_tail_other = false;
static double amp = 1.0;
static int beacon_channel = 0;
struct beacon_sample *beep_1k;
struct beacon_sample *beep_1k2;
struct beacon_sample *beep_2k;
static int tx_sound_out(int16_t *samples0, int16_t *samples1, int nr)
{
struct tx_packet *packet = peek_baseband();
if (!samples1 && packet) {
ensure_baseband(nr*sizeof(int16_t));
packet = dequeue_baseband();
samples1 = (int16_t*)packet->data;
}
if (samples0)
sound_gain(samples0, nr, amp);
sound_out_lr(samples0, samples1, nr);
if (packet) {
tx_packet_free(packet);
}
return 0;
}
static void tx_silence(void)
{
int16_t buffer[nr_samples];
int16_t *bp0 = NULL, *bp1 = NULL;
if (tx_hadvoice) {
if (ctcss) {
memset(buffer, 0, sizeof(int16_t)*nr_samples);
if (output_tone) {
bp1 = buffer;
ctcss_add(ctcss, buffer, nr_samples);
} else {
bp0 = buffer;
ctcss_add(ctcss, buffer, nr_samples);
if (emphasis_p)
emphasis_pre(emphasis_p, buffer, nr_samples);
}
}
}
tx_sound_out(bp0, bp1, nr_samples);
}
static void tx_beep(void)
{
struct beacon_sample *bs;
if (tx_state == TX_STATE_BEEP1) {
bs = beep_1k;
} else if (tx_state == TX_STATE_BEEP2) {
bs = beep_1k2;
} else {
bs = beep_2k;
}
int16_t buffer[nr_samples];
int16_t buffer_tone[nr_samples];
int16_t *buffer1 = NULL;
memcpy(buffer, bs->samples + tx_state_cnt * nr_samples, sizeof(int16_t)*nr_samples);
if (ctcss) {
if (output_tone) {
buffer1 = buffer_tone;
memset(buffer_tone, 0, sizeof(int16_t)*nr_samples);
ctcss_add(ctcss, buffer_tone, nr_samples);
} else {
ctcss_add(ctcss, buffer, nr_samples);
}
} if (emphasis_p)
emphasis_pre(emphasis_p, buffer, nr_samples);
tx_sound_out(buffer, buffer1, nr_samples);
}
static void tx_voice(void)
{
struct tx_packet *packet = peek_voice();
if (!packet) {
if (!beacon) {
tx_silence();
} else {
int16_t buffer0[nr_samples];
int16_t buffer_tone[nr_samples];
int16_t *bpb = NULL;
int16_t *bpt = NULL;
memset(buffer0, 0, sizeof(int16_t)*nr_samples);
memset(buffer_tone, 0, sizeof(int16_t)*nr_samples);
if (beacon_channel == 0)
bpb = buffer0;
else
bpb = buffer_tone;
beacon_generate(beacon, bpb, nr_samples);
if (tx_hadvoice) {
if (ctcss) {
if (output_tone) {
bpt = buffer_tone;
} else {
bpt = buffer0;
}
ctcss_add(ctcss, bpt, nr_samples);
}
}
if (emphasis_p)
emphasis_pre(emphasis_p, buffer0, nr_samples);
tx_sound_out(buffer0, buffer_tone, nr_samples);
}
} else {
int nr = packet->len / sizeof(short);
int16_t buffer0[nr];
int16_t buffer_tone[nr];
tx_hadvoice = true;
memcpy(buffer0, packet->data, packet->len);
memset(buffer_tone, 0, packet->len);
if (beacon) {
int16_t *bpb = buffer0;
if (beacon_channel == 1)
bpb = buffer_tone;
beacon_generate_add(beacon, bpb, nr);
}
if (emphasis_p)
emphasis_pre(emphasis_p, buffer0, nr);
if (ctcss) {
if (output_tone) {
ctcss_add(ctcss, buffer_tone, nr);
} else {
ctcss_add(ctcss, buffer0, nr);
}
}
tx_sound_out(buffer0, buffer_tone, nr);
packet = dequeue_voice();
tx_packet_free(packet);
}
}
void freedv_eth_txa_state_machine(void)
{
enum io_hl_ptt new_ptt = ptt;
tx_state_cnt++;
bool bcn;
bool tail = false;
struct tx_packet *q_peek = peek_voice();
bool q_filled = q_peek;
if (q_filled) {
tx_waslocal |= q_peek->local_rx;
}
if (beacon) {
bcn = beacon_state_check(beacon);
bcn = bcn && (!freedv_eth_cdc() || fullduplex);
} else {
bcn = false;
}
switch (tx_state) {
case TX_STATE_OFF:
if (q_filled || bcn) {
new_ptt = IO_HL_PTT_OTHER;
tx_state = TX_STATE_ON;
tx_state_cnt = 0;
if (ctcss)
ctcss_reset(ctcss);
} else {
break;
}
case TX_STATE_ON:
if (!q_filled && !bcn) {
tx_state_cnt = 0;
if (tx_hadvoice && io_hl_aux2_get()) {
tx_state = TX_STATE_BEEP1;
} else if (tx_hadvoice && io_dmlassoc_get()) {
tx_state = TX_STATE_BEEPD;
} else {
tx_state = TX_STATE_TAIL;
}
} else {
break;
}
case TX_STATE_BEEP1:
case TX_STATE_BEEP2:
case TX_STATE_BEEPD:
tail = true;
if (tx_tail_other)
new_ptt = IO_HL_PTT_OTHER;
if (q_filled || bcn) {
tx_state = TX_STATE_ON;
tx_state_cnt = 0;
break;
}
if (tx_state == TX_STATE_BEEP1) {
if (tx_state_cnt >= beep_1k->nr / nr_samples) {
tx_state_cnt = 0;
if (io_hl_aux3_get()) {
tx_state = TX_STATE_BEEP2;
} else {
tx_state = TX_STATE_TAIL;
}
} else {
break;
}
}
if (tx_state == TX_STATE_BEEP2) {
if (tx_state_cnt >= beep_1k2->nr / nr_samples) {
tx_state_cnt = 0;
tx_state = TX_STATE_TAIL;
} else {
break;
}
}
if (tx_state == TX_STATE_BEEPD) {
if (tx_state_cnt >= beep_2k->nr / nr_samples) {
tx_state_cnt = 0;
tx_state = TX_STATE_TAIL;
} else {
break;
}
}
case TX_STATE_TAIL:
tail = true;
if (tx_tail_other)
new_ptt = IO_HL_PTT_OTHER;
if (tx_state_cnt >= tx_tail) {
tx_state = TX_STATE_OFF;
tx_state_cnt = 0;
tx_hadvoice = false;
tx_waslocal = false;
new_ptt = IO_HL_PTT_OFF;
} else {
if (q_filled || bcn) {
tx_state = TX_STATE_ON;
tx_state_cnt = 0;
}
}
break;
}
switch (tx_state) {
case TX_STATE_TAIL:
case TX_STATE_OFF:
tx_silence();
break;
case TX_STATE_ON:
tx_voice();
break;
case TX_STATE_BEEP1:
case TX_STATE_BEEP2:
case TX_STATE_BEEPD:
tx_beep();
break;
}
if (new_ptt != IO_HL_PTT_OFF && tx_hadvoice && !tail)
new_ptt = IO_HL_PTT_AUDIO;
if (new_ptt != ptt) {
io_hl_ptt_set(new_ptt);
ptt = new_ptt;
}
}
bool freedv_eth_txa_ptt(void)
{
return tx_state != TX_STATE_OFF;
}
int freedv_eth_txa_init(bool init_fullduplex, int hw_rate, int tx_tail_msec)
{
double analog_amp = atof(freedv_eth_config_value("analog_tx_amp", NULL, "1.0"));
double ctcss_f = atof(freedv_eth_config_value("analog_tx_ctcss_frequency", NULL, "0.0"));
double ctcss_amp = atof(freedv_eth_config_value("analog_tx_ctcss_amp", NULL, "0.15"));
int beacon_interval = atoi(freedv_eth_config_value("analog_tx_beacon_interval", NULL, "0"));
char *beacon_msg = freedv_eth_config_value("analog_tx_beacon_message", NULL, "");
bool emphasis = atoi(freedv_eth_config_value("analog_tx_emphasis", NULL, "0"));
bool init_output_tone = atoi(freedv_eth_config_value("analog_tx_tone", NULL, "0"));
char *beacon_sound_channel = freedv_eth_config_value("analog_tx_beacon_sound_channel", NULL, "left");
if (!strcmp(beacon_sound_channel, "left")) {
beacon_channel = 0;
} else if (!strcmp(beacon_sound_channel, "right")) {
beacon_channel = 1;
} else {
/* Assume it is a number and limit it to odd or even */
beacon_channel = atoi(beacon_sound_channel) & 0x1;
}
beep_1k = beacon_beep_create(hw_rate, 1000.0, 0.45, 0.25, 0.25);
beep_1k2 = beacon_beep_create(hw_rate, 1200.0, 0.15, 0.15, 0.25);
beep_2k = beacon_beep_create(hw_rate, 2000.0, 0.10, 0.20, 0.25);
fullduplex = init_fullduplex;
output_tone = init_output_tone;
amp = analog_amp;
printf("TXA fullduplex: %d\n", fullduplex);
printf("TXA output_tone: %d\n", output_tone);
printf("TXA analog amp: %f\n", amp);
tx_state = TX_STATE_OFF;
io_hl_ptt_set(false);
tx_state_cnt = 0;
tx_hadvoice = false;
int period_msec = 1000 / (FREEDV_ALAW_RATE / FREEDV_ALAW_NR_SAMPLES);
printf("TXA period: %d msec\n", period_msec);
tx_tail = tx_tail_msec / period_msec;
printf("TXA tail: %d periods\n", tx_tail);
nr_samples = FREEDV_ALAW_NR_SAMPLES * hw_rate / FREEDV_ALAW_RATE;
ctcss_destroy(ctcss);
ctcss = NULL;
if (ctcss_f != 0.0) {
printf("TXA CTCSS %fHz, amp %f\n", ctcss_f, ctcss_amp);
ctcss = ctcss_init(hw_rate, ctcss_f, ctcss_amp);
}
beacon_destroy(beacon);
beacon = NULL;
if (beacon_interval) {
printf("TXA beacon interval %d, message: %s\n", beacon_interval, beacon_msg);
beacon = beacon_init(hw_rate, nr_samples, beacon_interval, beacon_msg);
printf("TXA beacon sound channel %d\n", beacon_channel);
}
emphasis_destroy(emphasis_p);
emphasis_p = NULL;
printf("TXA emphasis: %d\n", emphasis);
if (emphasis)
emphasis_p = emphasis_init();
tx_tail_other = atoi(freedv_eth_config_value("analog_tx_tail_other", NULL, "0"));
return 0;
}