MTS/IMTS: (Improved) Mobile Telephone Service

Implementation of the 0G Mobile Phone Network of US and Canada

MTS or IMTS mode is selectable, als well as 5 or 7 digit mode.
pull/1/head
Andreas Eversberg 4 years ago
parent 31fca59294
commit 99bafb6880
  1. 2
      .gitignore
  2. 1
      README
  3. 1
      configure.ac
  4. 1
      docs/index.html
  5. 1
      src/Makefile.am
  6. 63
      src/imts/Makefile.am
  7. 354
      src/imts/dialer.c
  8. 564
      src/imts/dsp.c
  9. 6
      src/imts/dsp.h
  10. 77
      src/imts/image.c
  11. 1326
      src/imts/imts.c
  12. 139
      src/imts/imts.h
  13. 289
      src/imts/main.c
  14. 1
      src/libdebug/debug.c
  15. 27
      src/libdebug/debug.h

2
.gitignore vendored

@ -60,6 +60,8 @@ src/amps/amps
src/tacs/tacs
src/jtacs/jtacs
src/r2000/radiocom2000
src/imts/imts
src/imts/imts-dialer
src/jolly/jollycom
src/tv/osmotv
src/radio/osmoradio

@ -13,6 +13,7 @@ generated simultaniously using SDR. Currently supported networks:
* TACS (Total Access Communication System)
* JTACS (Japanese version of TACS)
* Radiocom 2000 (French network)
* IMTS / MTS ((Improved) Mobile Telephone Service)
* JollyCom (Unofficial network, invented by the author)

@ -84,6 +84,7 @@ AC_OUTPUT(
src/tacs/Makefile
src/jtacs/Makefile
src/r2000/Makefile
src/imts/Makefile
src/jolly/Makefile
src/tv/Makefile
src/radio/Makefile

@ -60,6 +60,7 @@ It started with the idea to make a base station for the German B-Netz, but more
<p>
This project is pure software that requires a transmitter and a receiver connected to the sound card of a Linux PC.
Alternatively (more suggested) you can use a full duplex SDR to even generate multiple channels at a time.
A second sound card or ISDN card is used to route calls from and to the mobile phone.
</p>

@ -46,6 +46,7 @@ SUBDIRS += \
tacs \
jtacs \
r2000 \
imts \
jolly \
tv \
radio \

@ -0,0 +1,63 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
bin_PROGRAMS = \
imts \
imts-dialer
imts_SOURCES = \
imts.c \
dsp.c \
image.c \
main.c
imts_LDADD = \
$(COMMON_LA) \
../amps/libusatone.a \
$(top_builddir)/src/liboptions/liboptions.a \
$(top_builddir)/src/libdebug/libdebug.a \
$(top_builddir)/src/libmobile/libmobile.a \
$(top_builddir)/src/libdisplay/libdisplay.a \
$(top_builddir)/src/libjitter/libjitter.a \
$(top_builddir)/src/libsquelch/libsquelch.a \
$(top_builddir)/src/libtimer/libtimer.a \
$(top_builddir)/src/libsamplerate/libsamplerate.a \
$(top_builddir)/src/libemphasis/libemphasis.a \
$(top_builddir)/src/libfm/libfm.a \
$(top_builddir)/src/libfilter/libfilter.a \
$(top_builddir)/src/libwave/libwave.a \
$(top_builddir)/src/libmncc/libmncc.a \
$(top_builddir)/src/libsample/libsample.a \
-lm
imts_dialer_SOURCES = \
dialer.c
imts_dialer_LDADD = \
$(COMMON_LA) \
$(top_builddir)/src/liboptions/liboptions.a \
$(top_builddir)/src/libdebug/libdebug.a \
$(top_builddir)/src/libwave/libwave.a \
$(top_builddir)/src/libsample/libsample.a \
-lm
if HAVE_ALSA
imts_LDADD += \
$(top_builddir)/src/libsound/libsound.a \
$(ALSA_LIBS)
imts_dialer_LDADD += \
$(top_builddir)/src/libsound/libsound.a \
$(ALSA_LIBS)
endif
if HAVE_SDR
imts_LDADD += \
$(top_builddir)/src/libsdr/libsdr.a \
$(top_builddir)/src/libfft/libfft.a \
$(UHD_LIBS) \
$(SOAPY_LIBS)
endif
if HAVE_ALSA
AM_CPPFLAGS += -DHAVE_ALSA
endif

@ -0,0 +1,354 @@
/* IMTS dialer
*
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* 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 <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <errno.h>
#include "../libsample/sample.h"
#include "../libwave/wave.h"
#include "../libdebug/debug.h"
#ifdef HAVE_ALSA
#include "../libsound/sound.h"
#endif
#include "../liboptions/options.h"
/* presets */
const char *station_id = "6681739";
const char *dialing;
const char *audiodev = "hw:0,0";
int samplerate = 48000;
const char *write_tx_wave = NULL;
int latency = 50;
#define TONE_DONE -1
#define TONE_SILENCE 0
#define TONE_GUARD 2150
#define TONE_CONNECT 1633
#define TONE_DISCONNECT 1336
/* states */
static struct dial_string {
char console;
int tone;
int length;
} dial_string[2048];
static int dial_pos = 0;
static int latspl;
/* instances */
#ifdef HAVE_ALSA
void *audio = NULL;
#endif
wave_rec_t wave_tx_rec;
/* dummy functions */
int num_kanal = 1; /* only one channel used for debugging */
void display_status_limit_scroll() {}
void *get_sender_by_empfangsfrequenz() { return NULL; }
void display_measurements_add() {}
void display_measurements_update() {}
static void print_help(const char *arg0)
{
printf("Usage: %s [options] <number> | disconnect\n\n", arg0);
/* - - */
printf("This program generates a dialing sequence to make a call via IMTS base\n");
printf("station using an amateur radio transceiver.\n");
printf("Also it can write an audio file (wave) to be fed into IMTS base station for\n");
printf("showing how it simulates an IMTS phone doing an outgoing call.\n\n");
printf("Use 'disconnect' to generate a disconnect sequence.\n\n");
printf(" -h --help\n");
printf(" This help\n");
printf(" -i --station-id <station ID>\n");
printf(" 7 Digits of ID of mobile station (default = '%s')\n", station_id);
#ifdef HAVE_ALSA
printf(" -a --audio-device hw:<card>,<device>\n");
printf(" Sound card and device number (default = '%s')\n", audiodev);
#endif
printf(" -s --samplerate <rate>\n");
printf(" Sample rate of sound device (default = '%d')\n", samplerate);
printf(" -w --write-tx-wave <file>\n");
printf(" Write audio to given wave file also.\n");
}
static void add_options(void)
{
option_add('h', "help", 0);
option_add('i', "station-id", 1);
option_add('a', "audio-device", 1);
option_add('s', "samplerate", 1);
option_add('w', "write-tx-wave", 1);
}
static int handle_options(int short_option, int __attribute__((unused)) argi, char **argv)
{
switch (short_option) {
case 'h':
print_help(argv[0]);
return 0;
case 'i':
station_id = strdup(argv[argi]);
break;
case 'a':
audiodev = strdup(argv[argi]);
break;
case 's':
samplerate = atoi(argv[argi]);
break;
case 'w':
write_tx_wave = strdup(argv[argi]);
break;
default:
return -EINVAL;
}
return 1;
}
static double phase = 0;
/* encode audio */
static void encode_audio(sample_t *samples, uint8_t *power, int length)
{
int count;
int i;
memset(power, 1, length);
again:
count = length;
if (dial_string[dial_pos].length && dial_string[dial_pos].length < count)
count = dial_string[dial_pos].length;
switch (dial_string[dial_pos].tone) {
case TONE_DONE:
case TONE_SILENCE:
memset(samples, 0, count * sizeof(*samples));
phase = 0;
break;
default:
for (i = 0; i < count; i++) {
samples[i] = cos(2.0 * M_PI * (double)dial_string[dial_pos].tone * phase);
phase += 1.0 / samplerate;
}
}
if (dial_string[dial_pos].length) {
dial_string[dial_pos].length -= count;
if (dial_string[dial_pos].length == 0) {
if (dial_string[dial_pos].console) {
printf("%c", dial_string[dial_pos].console);
fflush(stdout);
}
dial_pos++;
}
}
samples += count;
length -= count;
if (length)
goto again;
}
/* loop that gets audio from encoder and fowards it to sound card.
* alternatively a sound file is written.
*/
static void process_signal(void)
{
sample_t buff[latspl], *samples[1] = { buff };
uint8_t pbuff[latspl], *power[1] = { pbuff };
int count;
int __attribute__((unused)) rc;
while (dial_string[dial_pos].tone != TONE_DONE) {
#ifdef HAVE_ALSA
count = sound_get_tosend(audio, latspl);
#else
count = samplerate / 1000;
#endif
if (count < 0) {
PDEBUG(DDSP, DEBUG_ERROR, "Failed to get number of samples in buffer (rc = %d)!\n", count);
break;
}
/* encode dial_string of tones and lengths */
encode_audio(samples[0], power[0], count);
/* write wave, if open */
if (wave_tx_rec.fp)
wave_write(&wave_tx_rec, samples, count);
#ifdef HAVE_ALSA
/* write audio */
rc = sound_write(audio, samples, power, count, NULL, NULL, 1);
if (rc < 0) {
PDEBUG(DDSP, DEBUG_ERROR, "Failed to write TX data to audio device (rc = %d)\n", rc);
break;
}
#endif
/* sleep a while */
usleep(1000);
}
}
int main(int argc, char *argv[])
{
int i, d, p, pulses, tone = 0;
int rc, argi;
memset(dial_string, 0, sizeof(dial_string));
/* latency of send buffer in samples */
latspl = samplerate * latency / 1000;
/* handle options / config file */
add_options();
argi = options_command_line(argc, argv, handle_options);
if (argi <= 0)
return argi;
if (argi >= argc) {
printf("No phone number given!\n\n");
print_help(argv[0]);
goto exit;
}
/* check for valid station ID */
if (strlen(station_id) != 7) {
printf("Given station ID '%s' has invalid number of digits!\n", station_id);
goto exit;
}
for (i = 0; station_id[i]; i++) {
if (station_id[i] < '0' || station_id[i] > '9') {
printf("Given station ID '%s' has invalid digits!\n", station_id);
goto exit;
}
}
dialing = argv[argi];
d = 0;
dial_string[d].tone = TONE_SILENCE; dial_string[d++].length = 0.600 * (double)samplerate; /* pause */
if (!!strcasecmp(dialing, "disconnect")) {
/* check for valid phone number */
if (strlen(dialing) > 64) {
printf("Given phone number '%s' has too many digits! (more than allowed 64 digits)\n", dialing);
goto exit;
}
for (i = 0; dialing[i]; i++) {
if (dialing[i] < '0' || dialing[i] > '9') {
printf("Given phone number '%s' has invalid digits!\n", dialing);
goto exit;
}
}
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.350 * (double)samplerate; /* off-hook */
dial_string[d].console = 's';
dial_string[d].tone = TONE_CONNECT; dial_string[d++].length = 0.050 * (double)samplerate; /* seize */
dial_string[d].console = '-';
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 1.000 * (double)samplerate; /* pause */
for (i = 0; station_id[i]; i++) {
pulses = station_id[i] - '0';
if (pulses == 0)
pulses = 10;
dial_string[d].console = station_id[i];
for (p = 1; p <= pulses; p++) {
if ((p & 1) == 1)
tone = TONE_SILENCE;
else
tone = TONE_GUARD;
dial_string[d].tone = TONE_CONNECT; dial_string[d++].length = 0.025 * (double)samplerate; /* mark */
dial_string[d].tone = tone; dial_string[d++].length = 0.025 * (double)samplerate; /* space */
}
dial_string[d].tone = tone; dial_string[d++].length = 0.190 * (double)samplerate; /* after digit */
}
dial_string[d].console = '-';
dial_string[d].tone = TONE_SILENCE; dial_string[d++].length = 2.000 * (double)samplerate; /* pause */
for (i = 0; dialing[i]; i++) {
pulses = dialing[i] - '0';
if (pulses == 0)
pulses = 10;
dial_string[d].console = dialing[i];
for (p = 1; p <= pulses; p++) {
dial_string[d].tone = TONE_CONNECT; dial_string[d++].length = 0.060 * (double)samplerate; /* mark */
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.040 * (double)samplerate; /* space */
}
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.400 * (double)samplerate; /* after digit */
}
dial_string[d].console = '\n';
} else {
for (i = 0; i < 750; i += 50) {
dial_string[d].tone = TONE_DISCONNECT; dial_string[d++].length = 0.025 * (double)samplerate; /* mark */
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.025 * (double)samplerate; /* space */
}
}
dial_string[d].tone = TONE_SILENCE; dial_string[d++].length = 0.600 * (double)samplerate; /* pause */
dial_string[d].tone = TONE_DONE; dial_string[d++].length = 0; /* end */
#ifdef HAVE_ALSA
/* init sound */
audio = sound_open(audiodev, NULL, NULL, 1, 0.0, samplerate, latspl, 1.0, 4000.0);
if (!audio) {
PDEBUG(DBNETZ, DEBUG_ERROR, "No sound device!\n");
goto exit;
}
#endif
/* open wave */
if (write_tx_wave) {
rc = wave_create_record(&wave_tx_rec, write_tx_wave, samplerate, 1, 1.0);
if (rc < 0) {
PDEBUG(DBNETZ, DEBUG_ERROR, "Failed to create WAVE recoding instance!\n");
goto exit;
}
}
#ifndef HAVE_ALSA
else {
PDEBUG(DBNETZ, DEBUG_ERROR, "No sound support compiled in, so you need to write to a wave file. See help!\n");
goto exit;
}
#endif
#ifdef HAVE_ALSA
/* start sound */
sound_start(audio);
#endif
PDEBUG(DBNETZ, DEBUG_ERROR, "Start audio after pause...\n");
process_signal();
exit:
/* close wave */
wave_destroy_record(&wave_tx_rec);
#ifdef HAVE_ALSA
/* exit sound */
if (audio)
sound_close(audio);
#endif
return 0;
}

@ -0,0 +1,564 @@
/* MTS/IMTS signal processing
*
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* 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/>.
*/
#define CHAN imts->sender.kanal
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include "../libsample/sample.h"
#include "../libdebug/debug.h"
#include "../libtimer/timer.h"
#include "../libmobile/call.h"
#include "imts.h"
#include "dsp.h"
/* uncomment to debug decoder */
//#define DEBUG_DECODER
#define PI 3.1415927
/* signaling */
#define MAX_DEVIATION 7000.0 /* signaling tone plus some extra to calibrate */
#define MAX_MODULATION 3000.0 /* FIXME */
#define DBM0_DEVIATION 2500.0 /* deviation of dBm0 (with emphasis) */
#define TX_PEAK_TONE (5000.0 / DBM0_DEVIATION) /* signaling tone level (5khz, no emphasis) */
#define RX_MIN_AMPL 0.25 /* FIXME: Minimum level to detect tone */
/* Note that 75 is half of the distance between two tones (2000 and 2150 Hz)
* An error of more than 50 causes too much toggeling between two tones,
* less would take too long to detect the tone and maybe not detect it, if
* it is too far off the expected frequency.
*/
#define RX_MIN_FREQ 50.0 /* minimum frequency error to detect tone */
#define MAX_DISPLAY (MAX_DEVIATION / DBM0_DEVIATION)/* as much as MAX_DEVIATION */
/* Note that FILTER_BW / SUSTAIN and QUAL_TIME sum up and should not exeed minimum tone length */
#define RX_FILTER_BW 100.0 /* amplitude filter (causes delay) */
#define RX_SUSTAIN 0.010 /* how long a tone must sustain until detected (causes delay) */
#define RX_QUAL_TIME 0.005 /* how long a quality measurement lasts after detecting a tone */
/* carrier loss detection */
#define MUTE_TIME 0.1 /* time to mute after loosing signal */
#define DELAY_TIME 0.15 /* delay, so we don't hear the noise before squelch mutes (MTS mode) */
#define LOSS_TIME 12.0 /* duration of signal loss before release (FIXME: what was the actual duration ???) */
#define SIGNAL_DETECT 0.3 /* time to have a steady signal to detect phone (used by MTS) */
#define DISPLAY_INTERVAL 0.04
/* signaling tones to be modulated */
static double tones[NUM_SIG_TONES] = {
2150.0, /* guard */
2000.0, /* idle */
1800.0, /* seize */
1633.0, /* connect */
1336.0, /* disconnect */
600.0, /* MTS 600 Hz */
1500.0, /* MTS 1500 Hz */
};
/* signaling tones to be demodulated (a subset of tones above) */
static double tone_response[NUM_SIG_TONES] = {
0.71, /* guard */
1.08, /* idle */
1.01, /* seize */
1.04, /* connect */
0.71, /* disconnect */
0.71, /* MTS 600 Hz */
0.71, /* MTS 1500 Hz */
};
/* all tone, with signaling tones first */
static const char *tone_names[] = {
"GUARD tone",
"IDLE tone",
"SEIZE tone",
"CONNECT tone",
"DISCONNECT tone",
"600 Hz Tone",
"1500 Hz Tone",
"SILENCE",
"NOISE",
"DIALTONE",
};
#define DIALTONE1 350.0
#define DIALTONE2 440.0
/* table for fast sine generation */
static sample_t dsp_sine_tone[65536];
/* global init for audio processing */
void dsp_init(void)
{
int i;
double s;
PDEBUG(DDSP, DEBUG_DEBUG, "Generating sine tables.\n");
for (i = 0; i < 65536; i++) {
s = sin((double)i / 65536.0 * 2.0 * PI);
dsp_sine_tone[i] = s * TX_PEAK_TONE;
}
}
/* Init transceiver instance. */
int dsp_init_transceiver(imts_t *imts, double squelch_db, int ptt)
{
int rc = -1;
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Init DSP for Transceiver.\n");
imts->sample_duration = 1.0 / (double)imts->sender.samplerate;
/* init squelch */
squelch_init(&imts->squelch, imts->sender.kanal, squelch_db, MUTE_TIME, LOSS_TIME);
/* set modulation parameters */
sender_set_fm(&imts->sender, MAX_DEVIATION, MAX_MODULATION, DBM0_DEVIATION, MAX_DISPLAY);
/* init FM demodulator for tone detection */
if (imts->mode == MODE_IMTS) {
imts->demod_center = (tones[TONE_GUARD] + tones[TONE_DISCONNECT]) / 2.0;
imts->demod_bandwidth = tones[TONE_GUARD] - tones[TONE_DISCONNECT];
} else {
imts->demod_center = (tones[TONE_1500] + tones[TONE_600]) / 2.0;
imts->demod_bandwidth = tones[TONE_1500] - tones[TONE_600];
}
rc = fm_demod_init(&imts->demod, (double)imts->sender.samplerate, imts->demod_center, imts->demod_bandwidth);
if (rc < 0)
goto error;
/* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */
/* NOTE: CHANGE TONE RESPONSES ABOVE, IF YOU CHANGE FILTER */
iir_lowpass_init(&imts->demod_freq_lp, RX_FILTER_BW, (double)imts->sender.samplerate, 2);
iir_lowpass_init(&imts->demod_ampl_lp, RX_FILTER_BW, (double)imts->sender.samplerate, 2);
/* tones */
imts->tone_idle_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_IDLE]);
imts->tone_seize_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_SEIZE]);
imts->tone_600_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_600]);
imts->tone_1500_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_1500]);
imts->tone_dialtone_phaseshift65536[0] = 65536.0 / ((double)imts->sender.samplerate / DIALTONE1);
imts->tone_dialtone_phaseshift65536[1] = 65536.0 / ((double)imts->sender.samplerate / DIALTONE2);
/* demod init */
imts->demod_current_tone = TONE_SILENCE;
imts->demod_sig_tone = 0;
imts->demod_last_tone = TONE_SILENCE;
/* delay buffer */
if (ptt) {
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Push to talk: Adding delay buffer to remove noise when singal gets lost.\n");
imts->delay_max = (int)((double)imts->sender.samplerate * DELAY_TIME);
imts->delay_spl = calloc(imts->delay_max, sizeof(*imts->delay_spl));
if (!imts->delay_spl) {
PDEBUG(DDSP, DEBUG_ERROR, "No mem for delay buffer!\n");
goto error;
}
}
imts->dmp_tone_level = display_measurements_add(&imts->sender.dispmeas, "Tone Level", "%.1f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 150.0, 100.0);
imts->dmp_tone_quality = display_measurements_add(&imts->sender.dispmeas, "Tone Quality", "%.1f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 150.0, 100.0);
return 0;
error:
dsp_cleanup_transceiver(imts);
return rc;
}
/* Cleanup transceiver instance. */
void dsp_cleanup_transceiver(imts_t *imts)
{
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Cleanup DSP for Transceiver.\n");
fm_demod_exit(&imts->demod);
if (imts->delay_spl) {
free(imts->delay_spl);
imts->delay_spl = NULL;
}
}
/* Generate audio stream from tone. Keep phase for next call of function. */
static int generate_tone(imts_t *imts, sample_t *samples, int length)
{
double phaseshift[2] = {0,0}, phase[2]; // make gcc happy
int i;
switch (imts->tone) {
case TONE_IDLE:
phaseshift[0] = imts->tone_idle_phaseshift65536;
break;
case TONE_SEIZE:
phaseshift[0] = imts->tone_seize_phaseshift65536;
break;
case TONE_600:
phaseshift[0] = imts->tone_600_phaseshift65536;
break;
case TONE_1500:
phaseshift[0] = imts->tone_1500_phaseshift65536;
break;
case TONE_DIALTONE:
phaseshift[0] = imts->tone_dialtone_phaseshift65536[0];
phaseshift[1] = imts->tone_dialtone_phaseshift65536[1];
break;
case TONE_SILENCE:
break;
default:
PDEBUG_CHAN(DDSP, DEBUG_ERROR, "Software error, unsupported tone, please fix!\n");
return length;
}
/* don't send more than given length */
if (imts->tone_duration && length > imts->tone_duration)
length = imts->tone_duration;
phase[0] = imts->tone_phase65536[0];
phase[1] = imts->tone_phase65536[1];
switch (imts->tone) {
case TONE_SILENCE:
memset(samples, 0, length * sizeof(*samples));
phase[0] = phase[1] = 0;
break;
case TONE_DIALTONE:
for (i = 0; i < length; i++) {
*samples = dsp_sine_tone[(uint16_t)phase[0]];
*samples += dsp_sine_tone[(uint16_t)phase[1]];
*samples++ /= 4.0; /* not full volume */
phase[0] += phaseshift[0];
if (phase[0] >= 65536)
phase[0] -= 65536;
phase[1] += phaseshift[1];
if (phase[1] >= 65536)
phase[1] -= 65536;
}
break;
default:
for (i = 0; i < length; i++) {
*samples++ = dsp_sine_tone[(uint16_t)phase[0]];
phase[0] += phaseshift[0];
if (phase[0] >= 65536)
phase[0] -= 65536;
}
}
imts->tone_phase65536[0] = phase[0];
imts->tone_phase65536[1] = phase[1];
/* if tone has been sent completely, tell IMTS call process */
if (imts->tone_duration) {
imts->tone_duration -= length;
if (imts->tone_duration == 0)
imts_tone_sent(imts, imts->tone);
}
return length;
}
/* Provide stream of audio toward radio unit */
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
{
imts_t *imts = (imts_t *) sender;
int count;
memset(power, 1, length);
again:
switch (imts->dsp_mode) {
case DSP_MODE_OFF:
memset(power, 0, length);
memset(samples, 0, length * sizeof(*samples));
break;
case DSP_MODE_TONE:
memset(power, 1, length);
count = generate_tone(imts, samples, length);
samples += count;
length -= count;
if (length)
goto again;
break;
case DSP_MODE_AUDIO:
memset(power, 1, length);
jitter_load(&imts->sender.dejitter, samples, length);
if (imts->pre_emphasis)
pre_emphasis(&imts->estate, samples, length);
break;
}
}
static void tone_demod(imts_t *imts, sample_t *samples, int length)
{
sample_t frequency[length], amplitude[length];
sample_t I[length], Q[length];
double f, amp;
int i, t, tone = 0; // make GCC happy
/* demod frquency */
fm_demodulate_real(&imts->demod, frequency, length, samples, I, Q);
iir_process(&imts->demod_freq_lp, frequency, length);
/* demod amplitude
* peak amplitude is the length of I/Q vector
* since we filter out the unwanted modulation product, the vector is only half of length
*/
for (i = 0; i < length; i++)
amplitude[i] = sqrt(I[i] * I[i] + Q[i] * Q[i]) * 2.0;
iir_process(&imts->demod_ampl_lp, amplitude, length);
/* check result to detect tone or loss or change */
for (i = 0; i < length; i++) {
/* duration until change in tone */
imts->demod_duration += imts->sample_duration;
/* correct FSK amplitude */
amplitude[i] /= TX_PEAK_TONE;
/* see what we detect at this moment; tone is set */
if (amplitude[i] < RX_MIN_AMPL) {
/* silence */
tone = TONE_SILENCE;
} else {
/* tone */
f = frequency[i] + imts->demod_center;
if (imts->mode == MODE_IMTS) {
for (t = TONE_IDLE; t <= TONE_DISCONNECT; t++) {
/* check for frequency */
if (fabs(f - tones[t]) < RX_MIN_FREQ) {
tone = t;
break;
}
}
} else {
for (t = TONE_600; t <= TONE_1500; t++) {
/* check for frequency */
if (fabs(f - tones[t]) < RX_MIN_FREQ) {
tone = t;
break;
}
}
}
/* noise */
if (t == NUM_SIG_TONES)
tone = TONE_NOISE;
}
#ifdef DEBUG_DECODER
/* debug decoder */
if (tone < NUM_SIG_TONES) {
static int debug_interval = 0;
if (++debug_interval == 30) {
debug_interval = 0;
printf("decoder debug: diff=%s %.0f ", debug_amplitude((f - tones[t]) / RX_MIN_FREQ), fabs(f - tones[t]));
amp = amplitude[i] / tone_response[t];
printf("ampl=%s %.0f%%\n", debug_amplitude(amp), amp * 100.0);
}
}
#endif
/* display level of tones, or zero at noise/silence */
imts->display_interval +=imts->sample_duration;
if (imts->display_interval >= DISPLAY_INTERVAL) {
if (tone < NUM_SIG_TONES)
amp = amplitude[i] / tone_response[tone];
else
amp = 0.0;
display_measurements_update(imts->dmp_tone_level, amp * 100.0, 0.0);
imts->display_interval -= DISPLAY_INTERVAL;
}
/* check for tone change */
if (tone != imts->demod_current_tone) {
#ifdef DEBUG_DECODER
printf("decoder debug: %s detected, waiting to sustain\n", tone_names[tone]);
#endif
if (imts->demod_sig_tone) {
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Lost %s (duration %.0f ms)\n", tone_names[imts->demod_current_tone], imts->demod_duration * 1000.0);
imts_lost_tone(imts, imts->demod_current_tone, imts->demod_duration);
imts->demod_sig_tone = 0;
}
imts->demod_current_tone = tone;
imts->demod_sustain = 0.0;
} else if (imts->demod_sustain < RX_SUSTAIN) {
imts->demod_sustain += imts->sample_duration;
/* when sustained. also tone must change to prevent flapping; lost signaling tone can be detected again */
if (imts->demod_sustain >= RX_SUSTAIN && (imts->demod_current_tone != imts->demod_last_tone || !imts->demod_sig_tone)) {
if (imts->demod_current_tone < NUM_SIG_TONES) {
amp = amplitude[i] / tone_response[imts->demod_current_tone];
imts->demod_sig_tone = 1;
imts->demod_quality_time = 0.0;
imts->demod_quality_count = 0;
imts->demod_quality_value = 0.0;
} else {
amp = amplitude[i];
imts->demod_sig_tone = 0;
}
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected %s (level %.0f%%)\n", tone_names[imts->demod_current_tone], amp * 100);
imts_receive_tone(imts, imts->demod_current_tone, imts->demod_duration, amp);
imts->demod_last_tone = imts->demod_current_tone;
imts->demod_duration = imts->demod_sustain;
} else if (imts->demod_sig_tone && imts->demod_quality_time < RX_QUAL_TIME) {
f = frequency[i] + imts->demod_center;
imts->demod_quality_time += imts->sample_duration;
imts->demod_quality_count++;
imts->demod_quality_value += fabs((f - tones[imts->demod_current_tone])) / RX_MIN_FREQ;
if (imts->demod_quality_time >= RX_QUAL_TIME) {
double quality = 1.0 - imts->demod_quality_value / (double)imts->demod_quality_count * 2.0;
if (quality < 0)
quality = 0;
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Quality: %.0f%%\n", quality * 100.0);
display_measurements_update(imts->dmp_tone_quality, quality * 100.0, 0.0);
}
}
}
}
}
static void delay_audio(imts_t *imts, sample_t *samples, int count)
{
sample_t *spl, s;
int pos, max;
int i;
spl = imts->delay_spl;
pos = imts->delay_pos;
max = imts->delay_max;
/* feed audio though delay buffer */
for (i = 0; i < count; i++) {
s = samples[i];
samples[i] = spl[pos];
spl[pos] = s;
if (++pos == max)
pos = 0;
}
imts->delay_pos = pos;
}
/* Process received audio stream from radio unit. */
void sender_receive(sender_t *sender, sample_t *samples, int length, double rf_level_db)
{
imts_t *imts = (imts_t *) sender;
/* no processing if off */
if (imts->dsp_mode == DSP_MODE_OFF)
return;
/* process signal mute/loss, also for signalling tone */
switch (squelch(&imts->squelch, rf_level_db, (double)length * imts->sample_duration)) {
case SQUELCH_LOSS:
imts_loss_indication(imts, LOSS_TIME);
/* FALLTHRU */
case SQUELCH_MUTE:
if (imts->mode == MODE_MTS && !imts->is_mute) {
PDEBUG_CHAN(DDSP, DEBUG_INFO, "Low RF level, muting.\n");
memset(imts->delay_spl, 0, sizeof(*samples) * imts->delay_max);
imts->is_mute = 1;
}
memset(samples, 0, sizeof(*samples) * length);
/* signal is gone */
imts->rf_signal = 0;
break;
default:
if (imts->is_mute) {
PDEBUG_CHAN(DDSP, DEBUG_INFO, "High RF level, unmuting.\n");
imts->is_mute = 0;
}
/* detect signal, if it is steady for a while */
if (imts->rf_signal < SIGNAL_DETECT * (double)imts->sender.samplerate) {
/* only do this, if signal has not been detected yet */
imts->rf_signal += length;
if (imts->rf_signal >= SIGNAL_DETECT * (double)imts->sender.samplerate)
imts_signal_indication(imts);
}
break;
}
/* FM/AM demod */
tone_demod(imts, samples, length);
/* delay audio to prevent noise before squelch mutes (don't do that for signaling tones) */
if (imts->delay_spl)
delay_audio(imts, samples, length);
/* Forward audio to network (call process). */
if (imts->dsp_mode == DSP_MODE_AUDIO && imts->callref) {
sample_t *spl;
int pos;
int count;
int i;
if (imts->de_emphasis) {
dc_filter(&imts->estate, samples, length);
de_emphasis(&imts->estate, samples, length);
}
count = samplerate_downsample(&imts->sender.srstate, samples, length);
spl = imts->sender.rxbuf;
pos = imts->sender.rxbuf_pos;
for (i = 0; i < count; i++) {
spl[pos++] = samples[i];
if (pos == 160) {
call_up_audio(imts->callref, spl, 160);
pos = 0;
}
}
imts->sender.rxbuf_pos = pos;
} else
imts->sender.rxbuf_pos = 0;
}
const char *imts_dsp_mode_name(enum dsp_mode mode)
{
static char invalid[16];
switch (mode) {
case DSP_MODE_OFF:
return "TRX OFF";
case DSP_MODE_TONE:
return "TONE";
case DSP_MODE_AUDIO:
return "AUDIO";
}
sprintf(invalid, "invalid(%d)", mode);
return invalid;
}
void imts_set_dsp_mode(imts_t *imts, enum dsp_mode mode, int tone, double duration, int reset_demod)
{
/* reset demod */
if (reset_demod) {
imts->demod_current_tone = TONE_SILENCE;
imts->demod_sig_tone = 0;
imts->demod_last_tone = TONE_SILENCE;
imts->demod_duration = 0.0;
}
if (imts->dsp_mode != mode) {
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "DSP mode %s -> %s\n", imts_dsp_mode_name(imts->dsp_mode), imts_dsp_mode_name(mode));
imts->dsp_mode = mode;
}
if (mode == DSP_MODE_TONE) {
if (duration)
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Start sending %s for %.3f seconds.\n", tone_names[tone], duration);
else
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Start sending %s continuously.\n", tone_names[tone]);
imts->tone = tone;
imts->tone_duration = duration * (double)imts->sender.samplerate;
}
}

@ -0,0 +1,6 @@
void dsp_init(void);
int dsp_init_transceiver(imts_t *imts, double squelch_db, int ptt);
void dsp_cleanup_transceiver(imts_t *imts);
void imts_set_dsp_mode(imts_t *imts, enum dsp_mode mode, int tone, double duration, int reset_demod);

@ -0,0 +1,77 @@
#include <stdio.h>
#include <string.h>
#include "../libmobile/image.h"
const char *image[] = {
"@W",
"IMTS / MTS is back!",
"",
NULL
};
void print_image(void)
{
int i, j;
for (i = 0; image[i]; i++) {
for (j = 0; j < (int)strlen(image[i]); j++) {
if (image[i][j] == '@') {
j++;
switch(image[i][j]) {
case 'k': /* black */
printf("\033[0;30m");
break;
case 'r': /* red */
printf("\033[0;31m");
break;
case 'g': /* green */
printf("\033[0;32m");
break;
case 'y': /* yellow */
printf("\033[0;33m");
break;
case 'b': /* blue */
printf("\033[0;34m");
break;
case 'm': /* magenta */
printf("\033[0;35m");
break;
case 'c': /* cyan */
printf("\033[0;36m");
break;
case 'w': /* white */
printf("\033[0;37m");
break;
case 'K': /* bright black */
printf("\033[1;30m");
break;
case 'R': /* bright red */
printf("\033[1;31m");
break;
case 'G': /* bright green */
printf("\033[1;32m");
break;
case 'Y': /* bright yellow */
printf("\033[1;33m");
break;
case 'B': /* bright blue */
printf("\033[1;34m");
break;
case 'M': /* bright magenta */
printf("\033[1;35m");
break;
case 'C': /* bright cyan */
printf("\033[1;36m");
break;
case 'W': /* bright white */
printf("\033[1;37m");
break;
}
} else
printf("%c", image[i][j]);
}
printf("\n");
}
printf("\033[0;39m");
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,139 @@
#include "../libsquelch/squelch.h"
#include "../libfm/fm.h"
#include "../libmobile/sender.h"
enum dsp_mode {
DSP_MODE_OFF = 0, /* transmitter off */
DSP_MODE_TONE, /* send tone or silence */
DSP_MODE_AUDIO, /* send audio */
};
#define TONE_GUARD 0
#define TONE_IDLE 1
#define TONE_SEIZE 2
#define TONE_CONNECT 3
#define TONE_DISCONNECT 4
#define TONE_600 5
#define TONE_1500 6
#define TONE_SILENCE 7
#define TONE_NOISE 8
#define TONE_DIALTONE 9
#define NUM_SIG_TONES 7
enum mode {
MODE_IMTS = 0,
MODE_MTS,
};
enum imts_state {
IMTS_NULL = 0,
/* channel is idle */
IMTS_OFF, /* base station not in use and turned off */
IMTS_IDLE, /* base station not in use and sending 2000 Hz Idle tone */
/* mobile originated */
IMTS_SEIZE, /* base station sends seize to acknowldege call from mobile */
IMTS_ANI, /* base station is receiving ANI from mobile */
IMTS_DIALING, /* base station is receiving dial digits */
/* mobile terminated */
IMTS_PAGING, /* base station is paging mobile */
IMTS_RINGING, /* base station is ringing mobile */
/* active call */
IMTS_CONVERSATION, /* base station and mobile have conversation */
/* releasing call */
IMTS_RELEASE, /* base station turned off */
/* loopback test */
IMTS_PAGING_TEST, /* loopback test sequence */
/* detector test */
IMTS_DETECTOR_TEST, /* detector test sequence */
};
typedef struct imts {
sender_t sender;
/* channel's states */
enum imts_state state; /* current sender's state */
int pre_emphasis; /* use pre_emphasis by this instance */
int de_emphasis; /* use de_emphasis by this instance */
emphasis_t estate;
int callref; /* call reference */
char station_id[11]; /* current station ID (also used for test pattern) */
int station_length; /* digit length of station ID */
char dial_number[33]; /* number dialing */
struct timer timer;
int last_tone; /* last tone received */
double last_sigtone_amplitude; /* amplitude of last signaling tone received */
double fast_seize; /* fast seize: guard-length - roundtrip-delay */
double rx_guard_timestamp; /* start of guard tone (seize by mobile) */
double rx_guard_duration; /* duration of guard (only long guards are detected) */
int rx_ani_pulse; /* current pulse # receiving */
int rx_ani_index; /* current digit # receiving */
int rx_ani_totpulses; /* total pulses count receiving */
int rx_dial_pulse; /* current pulse # receiving */
int rx_dial_index; /* current digit # receiving */
int rx_disc_pulse; /* current pulse # receiving */
int tx_page_pulse; /* current pulse # transmitting */
int tx_page_index; /* current digit # transmitting */
double tx_page_timestamp; /* last pulse of digit transmitting */
int tx_ring_pulse; /* current pulse # transmitting */
int rx_page_pulse; /* current pulse # receiving */
double detector_test_length_1; /* detector test tone duration */
double detector_test_length_2; /* detector test tone duration */
double detector_test_length_3; /* detector test tone duration */
/* MTS additional states */
enum mode mode; /* set if MTS mode is used */
const char *operator; /* operator's number to call when seizing the channel */
/* display measurements */
dispmeasparam_t *dmp_tone_level;
dispmeasparam_t *dmp_tone_quality;
/* dsp states */
double sample_duration; /* 1 / samplerate */
double demod_center; /* center frequency for tone demodulation */
double demod_bandwidth; /* bandwidth for tone demodulation */
fm_demod_t demod; /* demodulator for frequency / amplitude */
iir_filter_t demod_freq_lp; /* filter for frequency response */
iir_filter_t demod_ampl_lp; /* filter for amplitude response */
int demod_current_tone; /* current tone being detected */
int demod_sig_tone; /* current tone is a signaling tone */
int demod_last_tone; /* last tone being detected */
double demod_sustain; /* how long a tone must sustain */
double demod_duration; /* duration of last tone */
double demod_quality_time; /* time counter to measure quality */
int demod_quality_count; /* counter to measure quality */
double demod_quality_value; /* sum of quality samples (must be divided by count) */
double display_interval; /* used to update tone levels */
enum dsp_mode dsp_mode; /* current mode: audio, durable tone 0 or 1, paging */
int ptt; /* set, if push to talk is used (transmitter of phone off) */
int tone; /* current tone to send */
int tone_duration; /* if set, tone is limited to this duration (in samples) */
double tone_idle_phaseshift65536;/* how much the phase of sine wave changes per sample */
double tone_seize_phaseshift65536;/* how much the phase of sine wave changes per sample */
double tone_600_phaseshift65536;/* how much the phase of sine wave changes per sample */
double tone_1500_phaseshift65536;/* how much the phase of sine wave changes per sample */
double tone_dialtone_phaseshift65536[2];/* how much the phase of sine wave changes per sample */
double tone_phase65536[2]; /* current phase */
squelch_t squelch; /* squelch detection process */
int is_mute; /* set if quelch has muted */
int rf_signal; /* set if we have currently an RF signal */
sample_t *delay_spl; /* delay buffer for delaying audio */
int delay_pos; /* position in delay buffer */
int delay_max; /* number of samples in delay buffer */
} imts_t;
void imts_list_channels(void);
double imts_channel2freq(const char *kanal, int uplink);
int imts_init(void);
int imts_create(const char *channel, const char *audiodev, int use_sdr, int samplerate, double rx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback, double squelch_db, int ptt, int station_length, double fast_seize, enum mode mode, const char *operator, double length_1, double length_2, double length_3);
void imts_destroy(sender_t *sender);
void imts_loss_indication(imts_t *imts, double loss_time);
void imts_signal_indication(imts_t *imts);
void imts_receive_tone(imts_t *imts, int tone, double elapsed, double amplitude);
void imts_lost_tone(imts_t *imts, int tone, double elapsed);
void imts_tone_sent(imts_t *imts, int tone);

<
@ -0,0 +1,289 @@
/* MTS/IMTS main
*
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* 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 <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include "../libsample/sample.h"
#include "../libmobile/main_mobile.h"
#include "../libdebug/debug.h"
#include "../libtimer/timer.h"
#include "../libmobile/call.h"
#include "../liboptions/options.h"
#include "../amps/tones.h"
#include "../amps/outoforder.h"
#include "../amps/noanswer.h"
#include "../amps/invalidnumber.h"
#include "../amps/congestion.h"
#include "imts.h"
#include "dsp.h"
/* settings */
static double squelch_db = -INFINITY;
static int ptt = 0;
static int station_length = 0; /* defined by mode */
static double fast_seize = 0.0;
static enum mode mode = MODE_IMTS;
static char operator[32] = "010";
static double detector_test_length_1 = 0.0;
static double detector_test_length_2 = 0.0;
static double detector_test_length_3 = 0.0;
void print_help(const char *arg0)
{
main_mobile_print_help(arg0, "-b 5 ");
/* - - */
printf(" -S --squelch <dB> | auto\n");
printf(" Use given RF level to detect loss of signal. When the signal gets lost\n");
printf(" and stays below this level, the connection is released.\n");
printf(" Use 'auto' to do automatic noise floor calibration to detect loss.\n");
printf(" Only works with SDR! (disabled by default)\n");
printf(" -P --push-to-talk\n");
printf(" Allow push-to-talk operation for IMTS mode. (MTS always uses it.)\n");
printf(" This adds extra delay to received audio, to eliminate noise when the\n");
printf(" transmitter of the phone is turned off. Also this disables release on\n");
printf(" loss of RF signal. (Squelch is required for this to operate.)\n");
printf(" -5 --five\n");
printf(" -7 --seven\n");
printf(" Force station ID length (default is 7 for IMTS, 5 for MTS)\n");
printf(" -F --fast-seize <delay in ms>\n");
printf(" To compensate audio processing latency, give delay when to respond,\n");
printf(" after detection of Guard tone from mobile phone.\n");
printf(" Run software in loopback mode '-l 2' to measure round trip delay.\n");
printf(" Substract delay from 350 ms. If the phone has different Guard tone\n");
printf(" length, substract from that value.\n");
printf(" -D --detector-test <idle length> <seize lenght> <silence length>\n");
printf(" Transmit detector test signal, to adjust decoder inside mobile phone.\n");
printf(" Give length of idle / seize and silence in seconds. Listen to it with\n");
printf(" a radio receiver. To exclude an element, set its length to '0'.\n");
printf(" Example: '-D 0.5 0.5 0' plays alternating idle/seize tone.\n");
printf("\nMTS mode options\n");
printf(" -M --mts\n");
printf(" Run base station in MTS mode, rather than in IMTS mode.\n");
printf(" -O --operator <number>\n");
printf(" Give number to dial when mobile station initiated a call in MTS mode.\n");
printf(" Because there is no dial on the mobile phone, operator assistance is\n");
printf(" required to complete the call.\n");
printf(" By default, the operator '%s' is dialed.\n", operator);
printf(" -D --detector-test <600 Hz length> <1500 Hz lenght> <silence length>\n");
printf(" Transmit detector test signal, to adjust decoder inside MTS phone.\n");
printf(" Give length of 600/1500 Hz and silence in seconds. Listen to it with\n");
printf(" a radio receiver. To exclude an element, set its length to '0'.\n");
printf(" Example: '-D 0.5 0.5 0' plays alternating 600/1500 Hz tone.\n");
printf("\nstation-id: Give %d digits of station-id, you don't need to enter it after\n", station_length);
printf(" every start of this program.\n");
main_mobile_print_hotkeys();
}