Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added artifacts/lkma-193a/ground-truth/v21/inbound.wav
Binary file not shown.
Binary file not shown.
8 changes: 8 additions & 0 deletions artifacts/lkma-193a/ground-truth/v21/report.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"source": "synthetic-spandsp",
"note": "Generated by spandsp FSK TX - not a real hardware capture. Replace with lab recording when USB FXO rig is available.",
"sampleRate": 8000,
"channels": 1,
"bitDepth": 16,
"durationSeconds": 30
}
Binary file not shown.
Binary file not shown.
8 changes: 8 additions & 0 deletions artifacts/lkma-193a/ground-truth/v22bis/report.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"source": "synthetic-spandsp",
"note": "V.22bis placeholder silence. Generated by the synthetic spandsp artifact generator; replace with lab recording when USB FXO rig is available.",
"sampleRate": 8000,
"channels": 1,
"bitDepth": 16,
"durationSeconds": 30
}
Binary file added artifacts/lkma-193a/groundtruth-v21.wav
Binary file not shown.
257 changes: 257 additions & 0 deletions tools/generate-lkma-193a-groundtruth.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#include <spandsp.h>

#define SAMPLE_RATE 8000
#define CHANNELS 1
#define BITS_PER_SAMPLE 16
#define DURATION_SECONDS 30
#define TOTAL_SAMPLES (SAMPLE_RATE * DURATION_SECONDS)
#define BLOCK_SAMPLES 160
#define V21_MARK_BITS 150

typedef struct {
int bit_index;
} v21_bits_t;

static int make_dirs(const char *path);
static int write_wav_header(FILE *file, uint32_t samples);
static int write_silence_wav(const char *path);
static int write_v21_wav(const char *path);
static int copy_file(const char *from, const char *to);
static int write_report(const char *path, const char *note);
static int get_v21_bit(void *user_data);

int main(void) {
if (make_dirs("artifacts/lkma-193a/ground-truth/v21") != 0 ||
make_dirs("artifacts/lkma-193a/ground-truth/v22bis") != 0) {
return 1;
}

if (write_v21_wav("artifacts/lkma-193a/groundtruth-v21.wav") != 0 ||
copy_file("artifacts/lkma-193a/groundtruth-v21.wav",
"artifacts/lkma-193a/ground-truth/v21/inbound.wav") != 0 ||
write_silence_wav("artifacts/lkma-193a/ground-truth/v21/outbound.wav") != 0 ||
write_report(
"artifacts/lkma-193a/ground-truth/v21/report.json",
"Generated by spandsp FSK TX - not a real hardware capture. Replace with lab recording when USB FXO rig is available."
) != 0 ||
write_silence_wav("artifacts/lkma-193a/ground-truth/v22bis/inbound.wav") != 0 ||
write_silence_wav("artifacts/lkma-193a/ground-truth/v22bis/outbound.wav") != 0 ||
write_report(
"artifacts/lkma-193a/ground-truth/v22bis/report.json",
"V.22bis placeholder silence. Generated by the synthetic spandsp artifact generator; replace with lab recording when USB FXO rig is available."
) != 0) {
return 1;
}

return 0;
}

static int make_dirs(const char *path) {
char partial[512];
size_t length = strlen(path);

if (length >= sizeof(partial)) {
fprintf(stderr, "path too long: %s\n", path);
return -1;
}

for (size_t index = 0; index <= length; index++) {
partial[index] = path[index];
if (path[index] != '/' && path[index] != '\0') {
continue;
}

partial[index] = '\0';
if (partial[0] != '\0' && mkdir(partial, 0775) != 0 && errno != EEXIST) {
fprintf(stderr, "mkdir %s: %s\n", partial, strerror(errno));
return -1;
}
partial[index] = path[index];
}
return 0;
}

static int write_u16_le(FILE *file, uint16_t value) {
uint8_t bytes[2] = {
(uint8_t) (value & 0xff),
(uint8_t) ((value >> 8) & 0xff)
};
return fwrite(bytes, 1, sizeof(bytes), file) == sizeof(bytes) ? 0 : -1;
}

static int write_u32_le(FILE *file, uint32_t value) {
uint8_t bytes[4] = {
(uint8_t) (value & 0xff),
(uint8_t) ((value >> 8) & 0xff),
(uint8_t) ((value >> 16) & 0xff),
(uint8_t) ((value >> 24) & 0xff)
};
return fwrite(bytes, 1, sizeof(bytes), file) == sizeof(bytes) ? 0 : -1;
}

static int write_i16_le(FILE *file, int16_t value) {
return write_u16_le(file, (uint16_t) value);
}

static int write_wav_header(FILE *file, uint32_t samples) {
const uint16_t block_align = CHANNELS * (BITS_PER_SAMPLE / 8);
const uint32_t byte_rate = SAMPLE_RATE * block_align;
const uint32_t data_bytes = samples * block_align;

return fwrite("RIFF", 1, 4, file) == 4 &&
write_u32_le(file, 36 + data_bytes) == 0 &&
fwrite("WAVEfmt ", 1, 8, file) == 8 &&
write_u32_le(file, 16) == 0 &&
write_u16_le(file, 1) == 0 &&
write_u16_le(file, CHANNELS) == 0 &&
write_u32_le(file, SAMPLE_RATE) == 0 &&
write_u32_le(file, byte_rate) == 0 &&
write_u16_le(file, block_align) == 0 &&
write_u16_le(file, BITS_PER_SAMPLE) == 0 &&
fwrite("data", 1, 4, file) == 4 &&
write_u32_le(file, data_bytes) == 0 ? 0 : -1;
}

static int write_silence_wav(const char *path) {
FILE *file = fopen(path, "wb");
if (!file) {
fprintf(stderr, "open %s: %s\n", path, strerror(errno));
return -1;
}
if (write_wav_header(file, TOTAL_SAMPLES) != 0) {
fclose(file);
fprintf(stderr, "write header %s failed\n", path);
return -1;
}
for (int sample = 0; sample < TOTAL_SAMPLES; sample++) {
if (write_i16_le(file, 0) != 0) {
fclose(file);
fprintf(stderr, "write sample %s failed\n", path);
return -1;
}
}
return fclose(file);
}

static int write_v21_wav(const char *path) {
FILE *file = fopen(path, "wb");
if (!file) {
fprintf(stderr, "open %s: %s\n", path, strerror(errno));
return -1;
}

if (write_wav_header(file, TOTAL_SAMPLES) != 0) {
fclose(file);
fprintf(stderr, "write header %s failed\n", path);
return -1;
}

v21_bits_t bits = {0};
fsk_tx_state_t *tx = fsk_tx_init(NULL, &preset_fsk_specs[FSK_V21CH1], get_v21_bit, &bits);
if (!tx) {
fclose(file);
fprintf(stderr, "fsk_tx_init failed\n");
return -1;
}

int16_t samples[BLOCK_SAMPLES];
int remaining = TOTAL_SAMPLES;
while (remaining > 0) {
int count = remaining > BLOCK_SAMPLES ? BLOCK_SAMPLES : remaining;
int generated = fsk_tx(tx, samples, count);
if (generated != count) {
fsk_tx_free(tx);
fclose(file);
fprintf(stderr, "fsk_tx generated %d of %d samples\n", generated, count);
return -1;
}
for (int index = 0; index < generated; index++) {
if (write_i16_le(file, samples[index]) != 0) {
fsk_tx_free(tx);
fclose(file);
fprintf(stderr, "write sample %s failed\n", path);
return -1;
}
}
remaining -= generated;
}

fsk_tx_free(tx);
return fclose(file);
}

static int copy_file(const char *from, const char *to) {
FILE *source = fopen(from, "rb");
if (!source) {
fprintf(stderr, "open %s: %s\n", from, strerror(errno));
return -1;
}
FILE *target = fopen(to, "wb");
if (!target) {
fclose(source);
fprintf(stderr, "open %s: %s\n", to, strerror(errno));
return -1;
}

uint8_t buffer[8192];
size_t count;
while ((count = fread(buffer, 1, sizeof(buffer), source)) > 0) {
if (fwrite(buffer, 1, count, target) != count) {
fclose(source);
fclose(target);
fprintf(stderr, "write %s failed\n", to);
return -1;
}
}

int read_error = ferror(source);
int close_source = fclose(source);
int close_target = fclose(target);
return read_error || close_source || close_target ? -1 : 0;
}

static int write_report(const char *path, const char *note) {
FILE *file = fopen(path, "wb");
if (!file) {
fprintf(stderr, "open %s: %s\n", path, strerror(errno));
return -1;
}

int ok = fprintf(
file,
"{\n"
" \"source\": \"synthetic-spandsp\",\n"
" \"note\": \"%s\",\n"
" \"sampleRate\": 8000,\n"
" \"channels\": 1,\n"
" \"bitDepth\": 16,\n"
" \"durationSeconds\": 30\n"
"}\n",
note
) > 0;

return fclose(file) == 0 && ok ? 0 : -1;
}

static int get_v21_bit(void *user_data) {
static const uint8_t pattern[] = {0xff, 0x01, 0x7e};
v21_bits_t *bits = (v21_bits_t *) user_data;
int bit;

if (bits->bit_index < V21_MARK_BITS) {
bit = 1;
} else {
int payload_bit = bits->bit_index - V21_MARK_BITS;
uint8_t byte = pattern[(payload_bit / 8) % (int) (sizeof(pattern) / sizeof(pattern[0]))];
bit = (byte >> (payload_bit % 8)) & 1;
}
bits->bit_index++;
return bit;
}
Loading