# HG changeset patch # User Mychaela Falconia # Date 1672218530 0 # Node ID 546bf873ccc819e09e4fb6c9ae0a6bd5ee262d5f # Parent 5041bcb8140f947fe964348336164a983d475085 tchtools: new program fc-vm2gsmx diff -r 5041bcb8140f -r 546bf873ccc8 .hgignore --- 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/ diff -r 5041bcb8140f -r 546bf873ccc8 tchtools/Makefile --- 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 diff -r 5041bcb8140f -r 546bf873ccc8 tchtools/fc-vm2gsmx.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 +#include +#include + +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); +}