changeset 905:546bf873ccc8

tchtools: new program fc-vm2gsmx
author Mychaela Falconia <falcon@freecalypso.org>
date Wed, 28 Dec 2022 09:08:50 +0000
parents 5041bcb8140f
children 94890123a74f
files .hgignore tchtools/Makefile tchtools/fc-vm2gsmx.c
diffstat 3 files changed, 109 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Wed Dec 28 08:41:57 2022 +0000
+++ b/.hgignore	Wed Dec 28 09:08:50 2022 +0000
@@ -71,6 +71,7 @@
 ^tchtools/fc-fr2tch$
 ^tchtools/fc-gsm2vm$
 ^tchtools/fc-tch2fr$
+^tchtools/fc-vm2gsmx$
 ^tchtools/fc-vm2hex$
 
 ^toolchain/binutils-2\.21\.1/
--- a/tchtools/Makefile	Wed Dec 28 08:41:57 2022 +0000
+++ b/tchtools/Makefile	Wed Dec 28 09:08:50 2022 +0000
@@ -1,6 +1,6 @@
 CC=	gcc
 CFLAGS=	-O2
-PROGS=	fc-fr2tch fc-gsm2vm fc-tch2fr fc-vm2hex
+PROGS=	fc-fr2tch fc-gsm2vm fc-tch2fr fc-vm2gsmx fc-vm2hex
 
 INSTALL_PREFIX=	/opt/freecalypso
 
@@ -11,6 +11,7 @@
 FR2TCH_OBJS=	fc-fr2tch.o gsm0610.o
 GSM2VM_OBJS=	fc-gsm2vm.o gsm0610.o
 TCH2FR_OBJS=	fc-tch2fr.o gsm0610.o
+VM2GSMX_OBJS=	fc-vm2gsmx.o gsm0610.o
 
 fc-fr2tch:	${FR2TCH_OBJS}
 	${CC} ${CFLAGS} -o $@ ${FR2TCH_OBJS}
@@ -21,6 +22,9 @@
 fc-tch2fr:	${TCH2FR_OBJS}
 	${CC} ${CFLAGS} -o $@ ${TCH2FR_OBJS}
 
+fc-vm2gsmx:	${VM2GSMX_OBJS}
+	${CC} ${CFLAGS} -o $@ ${VM2GSMX_OBJS}
+
 fc-vm2hex:	fc-vm2hex.c
 	${CC} ${CFLAGS} -o $@ $@.c
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tchtools/fc-vm2gsmx.c	Wed Dec 28 09:08:50 2022 +0000
@@ -0,0 +1,103 @@
+/*
+ * This utility converts old-fashioned (non-AMR) TCS211 voice memo files
+ * read out of FFS into Themyscira Wireless gsmx (extended-libgsm) format,
+ * allowing further decoding into playable speech with gsmfr-decode.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static char *infname;
+static FILE *inf, *outf;
+static u_long file_offset;
+
+static const u_char bfi_marker[2] = {0xBF, 0x00};
+
+static unsigned
+get_word()
+{
+	u_char b[2];
+	int i, c;
+
+	for (i = 0; i < 2; i++) {
+		c = getc(inf);
+		if (c < 0) {
+			fprintf(stderr, "error: premature EOF in %s\n",
+				infname);
+			exit(1);
+		}
+		b[i] = c;
+		file_offset++;
+	}
+	return((b[1] << 8) | b[0]);
+}
+
+static void
+read_dsp_bytes(bytes)
+	u_char *bytes;
+{
+	int i, dp;
+	unsigned word;
+
+	dp = 0;
+	for (i = 0; i < 17; i++) {
+		word = get_word();
+		bytes[dp++] = word >> 8;
+		bytes[dp++] = word;
+	}
+}
+
+static void
+convert_speech_sample()
+{
+	u_char tidsp_bytes[34], libgsm_bytes[33];
+
+	read_dsp_bytes(tidsp_bytes);
+	gsm0610_tidsp_to_libgsm(tidsp_bytes, libgsm_bytes);
+	fwrite(libgsm_bytes, 1, 33, outf);
+}
+
+main(argc, argv)
+	char **argv;
+{
+	unsigned first_word;
+
+	if (argc != 3) {
+		fprintf(stderr, "usage: %s infile outfile\n", argv[0]);
+		exit(1);
+	}
+	infname = argv[1];
+	inf = fopen(infname, "r");
+	if (!inf) {
+		perror(infname);
+		exit(1);
+	}
+	outf = fopen(argv[2], "w");
+	if (!outf) {
+		perror(argv[2]);
+		exit(1);
+	}
+	for (;;) {
+		first_word = get_word();
+		if (first_word == 0xFBFF)	/* SC_VM_END_MASK */
+			break;
+		if ((first_word & 0xB7FF) == 0x8400) {
+			/* skip dummy header words 1 and 2 */
+			get_word();
+			get_word();
+			/* process the actual speech or SID frame */
+			convert_speech_sample();
+		} else if (first_word == 0x0400) {
+			/* it's a skipped frame, aka BFI */
+			fwrite(bfi_marker, 1, 2, outf);
+		} else {
+			fprintf(stderr,
+	"error in %s at offset 0x%lx: invalid frame header word 0x%04X\n",
+				infname, file_offset, first_word);
+			exit(1);
+		}
+	}
+	fclose(outf);
+	exit(0);
+}