From f4eba26d274c31be0ad678f64c72095bcd13a338 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sun, 8 Oct 2023 12:09:52 +0200 Subject: [PATCH] Golay: Add voice message support A voice message can be sent with a wave file. Pagers that support voice messages will listen to the wave file on the radio channel. Use the following command to send a voice message: $ echo "1709829,v," > /tmp/golay_msg_send --- src/golay/dsp.c | 56 ++++++++++++++- src/golay/golay.c | 172 ++++++++++++++++++++++++++++++---------------- src/golay/golay.h | 23 +++++-- src/golay/main.c | 1 + 4 files changed, 186 insertions(+), 66 deletions(-) diff --git a/src/golay/dsp.c b/src/golay/dsp.c index 24d9ac7..3a264c0 100644 --- a/src/golay/dsp.c +++ b/src/golay/dsp.c @@ -25,12 +25,14 @@ #include #include #include +#include #include "../libsample/sample.h" #include "../libdebug/debug.h" #include "golay.h" #include "dsp.h" #define MAX_DISPLAY 1.4 /* something above speech level, no emphasis */ +#define VOICE_BANDWIDTH 3000 /* just guessing */ static void dsp_init_ramp(gsc_t *gsc) { @@ -172,12 +174,63 @@ void sender_receive(sender_t __attribute__((unused)) *sender, sample_t __attribu void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length) { gsc_t *gsc = (gsc_t *) sender; + int rc; again: - /* get word */ + /* play 2 seconds of pause */ + if (gsc->wait_2_sec) { + int tosend = MIN(length, gsc->wait_2_sec); + memset(power, 1, tosend); + memset(samples, 0, sizeof(samples) * tosend); + power += tosend; + samples += tosend; + gsc->wait_2_sec -= tosend; + if (gsc->wait_2_sec) + return; + } + + /* play wave file, if open */ + if (gsc->wave_tx_play.left) { + int wave_num, s; + wave_num = samplerate_upsample_input_num(&gsc->wave_tx_upsample, length); + sample_t buffer[wave_num * 2], *wave_samples[2] = { buffer, buffer + wave_num }; + wave_read(&gsc->wave_tx_play, wave_samples, wave_num); + if (gsc->wave_tx_channels == 2) { + for (s = 0; s < wave_num; s++) { + wave_samples[0][s] += wave_samples[1][s]; + } + } + samplerate_upsample(&gsc->wave_tx_upsample, wave_samples[0], wave_num, samples, length); + if (!gsc->wave_tx_play.left) { + PDEBUG_CHAN(DDSP, DEBUG_INFO, "Voice message sent.\n"); + wave_destroy_playback(&gsc->wave_tx_play); + return; + } + return; + } + + + /* get FSK bits or start playing wave file */ if (!gsc->fsk_tx_buffer_length) { int8_t bit = get_bit(gsc); + /* bit == 2 means voice transmission. */ + if (bit == 2) { + if (gsc->wave_tx_filename[0]) { + gsc->wave_tx_samplerate = gsc->wave_tx_channels = 0; + rc = wave_create_playback(&gsc->wave_tx_play, gsc->wave_tx_filename, &gsc->wave_tx_samplerate, &gsc->wave_tx_channels, gsc->fsk_deviation); + if (rc < 0) { + gsc->wave_tx_play.left = 0; + PDEBUG_CHAN(DDSP, DEBUG_ERROR, "Failed to open wave file '%s' for voice message.\n", gsc->wave_tx_filename); + } else { + PDEBUG_CHAN(DDSP, DEBUG_INFO, "Sending wave file '%s' for voice message after 2 seconds.\n", gsc->wave_tx_filename); + init_samplerate(&gsc->wave_tx_upsample, gsc->wave_tx_samplerate, gsc->sender.samplerate, VOICE_BANDWIDTH); + } + } + gsc->wait_2_sec = gsc->sender.samplerate * 2.0; + goto again; + } + /* no message, power is off */ if (bit < 0) { memset(samples, 0, sizeof(samples) * length); @@ -206,4 +259,3 @@ again: goto again; } - diff --git a/src/golay/golay.c b/src/golay/golay.c index 3f1d8e8..789b287 100644 --- a/src/golay/golay.c +++ b/src/golay/golay.c @@ -32,6 +32,7 @@ #include #include #include +#include #include "../libsample/sample.h" #include "../libdebug/debug.h" #include "../libmobile/call.h" @@ -98,18 +99,10 @@ void golay_destroy(sender_t *sender) } /* Create message and add to queue */ -static gsc_msg_t *golay_msg_create(gsc_t *gsc, const char *address, const char *text, int force_type) +static gsc_msg_t *golay_msg_create(gsc_t *gsc, const char *address, const char *text, enum gsc_msg_type type) { gsc_msg_t *msg, **msgp; - PDEBUG(DGOLAY, DEBUG_INFO, "Creating msg instance to page address '%s'.\n", address); - - /* create */ - msg = calloc(1, sizeof(*msg)); - if (!msg) { - PDEBUG(DGOLAY, DEBUG_ERROR, "No mem!\n"); - abort(); - } if (strlen(address) != sizeof(msg->address) - 1) { PDEBUG(DGOLAY, DEBUG_NOTICE, "Address has incorrect length, cannot page!\n"); return NULL; @@ -119,10 +112,39 @@ static gsc_msg_t *golay_msg_create(gsc_t *gsc, const char *address, const char * return NULL; } + /* get type from last digit, if automatic type is given */ + if (type == TYPE_AUTO) { + switch (address[6]) { + case '1': type = TYPE_VOICE; break; + case '2': type = TYPE_VOICE; break; + case '3': type = TYPE_VOICE; break; + case '4': type = TYPE_VOICE; break; + case '5': type = TYPE_ALPHA; break; + case '6': type = TYPE_ALPHA; break; + case '7': type = TYPE_ALPHA; break; + case '8': type = TYPE_ALPHA; break; + case '9': type = TYPE_TONE; break; + case '0': type = TYPE_TONE; break; + default: + PDEBUG(DGOLAY, DEBUG_NOTICE, "Illegal function suffix '%c' in last address digit.\n", address[6]); + return NULL; + } + } else + PDEBUG(DGOLAY, DEBUG_INFO, "Overriding message type as defined by sender.\n"); + + PDEBUG(DGOLAY, DEBUG_INFO, "Creating msg instance to page address '%s'.\n", address); + + /* create */ + msg = calloc(1, sizeof(*msg)); + if (!msg) { + PDEBUG(DGOLAY, DEBUG_ERROR, "No mem!\n"); + abort(); + } + /* init */ - strcpy(msg->address, address); - msg->force_type = force_type; - strcpy(msg->data, text); + memcpy(msg->address, address, MIN(sizeof(msg->address) - 1, strlen(address) + 1)); + msg->type = type; + memcpy(msg->data, text, MIN(sizeof(msg->data) - 1, strlen(text) + 1)); /* link */ msgp = &gsc->msg_list; @@ -240,6 +262,7 @@ static const uint16_t preamble_values[] = { }; static const uint32_t start_code = 713; +static const uint32_t activation_code = 2563; /* Rep. 900-2 Table VI */ static const uint16_t word1s[50] = { @@ -455,6 +478,7 @@ static inline void queue_reset(gsc_t *gsc) { gsc->bit_index = 0; gsc->bit_num = 0; + gsc->bit_ac = 0; gsc->bit_overflow = 0; } @@ -489,9 +513,8 @@ static inline void queue_comma(gsc_t *gsc, int bits, uint8_t polarity) } } -static int queue_batch(gsc_t *gsc, const char *address, int force_type, const char *message) +static int queue_batch(gsc_t *gsc, const char *address, enum gsc_msg_type type, const char *message) { - int type; int preamble; uint16_t word1, word2; uint8_t function; @@ -514,33 +537,34 @@ static int queue_batch(gsc_t *gsc, const char *address, int force_type, const ch if (rc < 0) return rc; - /* calculate function */ + /* get function from last digit */ switch (address[6]) { - case '1': type = TYPE_VOICE; function = 0; break; - case '2': type = TYPE_VOICE; function = 1; break; - case '3': type = TYPE_VOICE; function = 2; break; - case '4': type = TYPE_VOICE; function = 3; break; - case '5': type = TYPE_ALPHA; function = 0; break; - case '6': type = TYPE_ALPHA; function = 1; break; - case '7': type = TYPE_ALPHA; function = 2; break; - case '8': type = TYPE_ALPHA; function = 3; break; - case '9': type = TYPE_TONE; function = 0; break; - case '0': type = TYPE_TONE; function = 1; break; + case '1': function = 0; break; + case '2': function = 1; break; + case '3': function = 2; break; + case '4': function = 3; break; + case '5': function = 0; break; + case '6': function = 1; break; + case '7': function = 2; break; + case '8': function = 3; break; + case '9': function = 0; break; + case '0': function = 1; break; default: PDEBUG(DGOLAY, DEBUG_NOTICE, "Illegal function suffix '%c' in last address digit.\n", address[6]); return -EINVAL; } - /* override type */ - if (force_type >= 0) { - type = force_type; - PDEBUG(DGOLAY, DEBUG_INFO, "Overriding message type as defined by sender.\n"); - } - - if (type == TYPE_ALPHA || type == TYPE_NUMERIC) + switch (type) { + case TYPE_ALPHA: + case TYPE_NUMERIC: PDEBUG(DGOLAY, DEBUG_INFO, "Coding text message for functional address '%s' and message '%s'.\n", address, message); - else + break; + case TYPE_VOICE: + PDEBUG(DGOLAY, DEBUG_INFO, "Coding voice message for functional address %s with wave file '%s'.\n", address, message); + break; + default: PDEBUG(DGOLAY, DEBUG_INFO, "Coding tone only message for functional address %s.\n", address); + } /* encode preamble and store */ PDEBUG(DGOLAY, DEBUG_DEBUG, "Encoding preamble '%d'.\n", preamble); @@ -573,7 +597,8 @@ static int queue_batch(gsc_t *gsc, const char *address, int force_type, const ch queue_dup(gsc, golay, 23); /* encode message */ - if (type == TYPE_ALPHA) { + switch (type) { + case TYPE_ALPHA: PDEBUG(DGOLAY, DEBUG_DEBUG, "Encoding %d alphanumeric digits.\n", (int)strlen(message)); for (i = 0; *message; i++) { if (i == MAX_ADB) { @@ -608,8 +633,8 @@ static int queue_batch(gsc_t *gsc, const char *address, int force_type, const ch queue_bit(gsc, (bch[k] >> j) & 1); } } - } - if (type == TYPE_NUMERIC) { + break; + case TYPE_NUMERIC: PDEBUG(DGOLAY, DEBUG_DEBUG, "Encoding %d numeric digits.\n", (int)strlen(message)); shifted = 0; for (i = 0; *message; i++) { @@ -658,12 +683,27 @@ static int queue_batch(gsc_t *gsc, const char *address, int force_type, const ch queue_bit(gsc, (bch[k] >> j) & 1); } } + break; + case TYPE_VOICE: + /* store wave file name */ + memcpy(gsc->wave_tx_filename, message, MIN(sizeof(gsc->wave_tx_filename) - 1, strlen(message) + 1)); + /* store bit number for activation code. this is used to play the AC again after voice message. */ + gsc->bit_ac = gsc->bit_num; + /* encode activation code and store */ + PDEBUG(DGOLAY, DEBUG_DEBUG, "Encoding activation code.\n"); + golay = calc_golay(activation_code); + queue_comma(gsc, 28, golay & 1); + queue_dup(gsc, golay, 23); + golay ^= 0x7fffff; + queue_bit(gsc, (golay & 1) ^ 1); + queue_dup(gsc, golay, 23); + break; + default: + /* encode comma after message and store */ + PDEBUG(DGOLAY, DEBUG_DEBUG, "Encoding 'comma' sequence after message.\n"); + queue_comma(gsc, 121 * 8, 1); } - /* encode comma after message and store */ - PDEBUG(DGOLAY, DEBUG_DEBUG, "Encoding 'comma' sequence after message.\n"); - queue_comma(gsc, 121 * 8, 1); - /* check overflow */ if (gsc->bit_overflow) { PDEBUG(DGOLAY, DEBUG_ERROR, "Bit stream (%d bits) overflows bit buffer size (%d bits), please fix!\n", gsc->bit_num, (int)sizeof(gsc->bit)); @@ -680,21 +720,31 @@ static int queue_batch(gsc_t *gsc, const char *address, int force_type, const ch * if there is a message, return next bit to be transmitted. * * if there is a message in the queue, encode message and return its first bit. + * + * if there is a voice message, return 2 at the end, to tell the DSP to send voice. */ int8_t get_bit(gsc_t *gsc) { gsc_msg_t *msg; - uint8_t bit; int rc; /* if currently transmiting message, send next bit */ if (gsc->bit_num) { - bit = gsc->bit[gsc->bit_index++]; + /* Transmission complete. */ if (gsc->bit_index == gsc->bit_num) { + /* on voice message... */ + if (gsc->bit_ac) { + /* rewind to play the AC again after voice transmission */ + gsc->bit_index = gsc->bit_ac; + gsc->bit_ac = 0; + /* indicate voice message to DSP */ + return 2; + } queue_reset(gsc); PDEBUG(DGOLAY, DEBUG_INFO, "Done transmitting message.\n"); + goto next_msg; } - return bit; + return gsc->bit[gsc->bit_index++]; } next_msg: @@ -705,7 +755,7 @@ next_msg: return -1; /* encode first message in queue */ - rc = queue_batch(gsc, msg->address, msg->force_type, msg->data); + rc = queue_batch(gsc, msg->address, msg->type, msg->data); if (rc >= 0) PDEBUG(DGOLAY, DEBUG_INFO, "Transmitting message to address '%s'.\n", msg->address); golay_msg_destroy(gsc, msg); @@ -713,33 +763,37 @@ next_msg: goto next_msg; /* return first bit */ - bit = gsc->bit[gsc->bit_index++]; - return bit; + return gsc->bit[gsc->bit_index++]; } void golay_msg_send(const char *text) { char buffer[strlen(text) + 1], *p = buffer, *address_string, *message; gsc_t *gsc; - int force_type = -1; + enum gsc_msg_type type = TYPE_AUTO; strcpy(buffer, text); address_string = strsep(&p, ","); message = p; - if (message) { - if (message[0] == 'a' && message[1] == ',') { - force_type = TYPE_ALPHA; - message += 2; - } else - if (message[0] == 'n' && message[1] == ',') { - force_type = TYPE_NUMERIC; - message += 2; - } - } else + switch ((message[0] << 8) | message[1]) { + case ('a' << 8) | ',': + type = TYPE_ALPHA; + message += 2; + break; + case ('n' << 8) | ',': + type = TYPE_NUMERIC; + message += 2; + break; + case ('v' << 8) | ',': + type = TYPE_VOICE; + message += 2; + break; + default: message = ""; + } gsc = (gsc_t *) sender_head; - golay_msg_create(gsc, address_string, message, force_type); + golay_msg_create(gsc, address_string, message, type); } void call_down_clock(void) @@ -785,7 +839,7 @@ int call_down_setup(int __attribute__((unused)) callref, const char *caller_id, message = gsc->default_message; /* create call process to page station */ - msg = golay_msg_create(gsc, address, message, -1); + msg = golay_msg_create(gsc, address, message, TYPE_AUTO); if (!msg) return -CAUSE_INVALNUMBER; return -CAUSE_NORMAL; diff --git a/src/golay/golay.h b/src/golay/golay.h index 24e201f..033decf 100644 --- a/src/golay/golay.h +++ b/src/golay/golay.h @@ -1,9 +1,13 @@ #include "../libmobile/sender.h" -#define TYPE_TONE 0 /* TONE only */ -#define TYPE_VOICE 1 /* TONE + VOICE */ -#define TYPE_ALPHA 2 /* TONE + DATA */ -#define TYPE_NUMERIC 3 /* TONE + DATA */ +enum gsc_msg_type { + TYPE_AUTO = 0, /* Defined by 7th digit */ + TYPE_TONE, /* TONE only */ + TYPE_VOICE, /* TONE + VOICE */ + TYPE_ALPHA, /* TONE + DATA */ + TYPE_NUMERIC, /* TONE + DATA */ +}; + #define MAX_ADB 10 /* 80 characters */ #define MAX_NDB 2 /* 24 digits */ @@ -11,7 +15,7 @@ typedef struct gsc_msg { struct gsc_msg *next; char address[8]; /* 7 digits + EOL */ - int force_type; /* override type from address digit 7 */ + enum gsc_msg_type type; /* type of message */ char data[256]; /* message to be transmitted */ } gsc_msg_t; @@ -25,6 +29,7 @@ typedef struct gsc { /* current trasmitting message */ uint8_t bit[4096]; int bit_num; + int bit_ac; /* where activation code starts (voice only). */ int bit_index; /* when playing out */ int bit_overflow; @@ -41,6 +46,14 @@ typedef struct gsc { int fsk_tx_buffer_pos; /* current position sending buffer */ double fsk_tx_phase; /* current bit position */ uint8_t fsk_tx_lastbit; /* last bit of last message, to correctly ramp */ + + /* voice message */ + int wait_2_sec; /* counter to wait 2 seconds before playback */ + char wave_tx_filename[256]; + int wave_tx_samplerate; + int wave_tx_channels; + wave_play_t wave_tx_play; /* wave playback */ + samplerate_t wave_tx_upsample; /* wave upsampler */ } gsc_t; int golay_create(const char *kanal, double frequency, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, double deviation, double polarity, const char *message, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback); diff --git a/src/golay/main.c b/src/golay/main.c index 09c8dad..4b595c2 100644 --- a/src/golay/main.c +++ b/src/golay/main.c @@ -73,6 +73,7 @@ void print_help(const char *arg0) printf(" Write \"
[,message]\" to it, to send a default message.\n"); printf(" Write \"
,n,message\" to it, to send a numeric message.\n"); printf(" Write \"
,a,message\" to it, to send an alphanumeric message.\n"); + printf(" Write \"
,v,\" to it, to send a voice message.\n"); printf("\n"); printf("By default, an alphanumic message is sent, if last digit of the functional\n"); printf("address is 5..8. Otherwise a tone only message is sent.\n");