martin modes, and some bugfixes to get them working

This commit is contained in:
2026-02-22 19:38:10 +01:00
parent 16c5bbf28a
commit ba38a51a68
6 changed files with 162 additions and 22 deletions

View File

@@ -3,7 +3,7 @@ CFLAGS = -Wall -O2
LDFLAGS = -lm
TARGET = sstv
SRCS = main.c sstv.c robot36.c image.c wav.c
SRCS = main.c sstv.c robot36.c image.c wav.c martin.c
OBJS = $(SRCS:.c=.o)
$(TARGET): $(OBJS)

35
main.c
View File

@@ -1,14 +1,41 @@
#include "sstv.h"
#include "martin.h"
#include <stdio.h>
#include <string.h>
static void usage(const char *prog) {
printf("Usage: %s <mode> input_image output.wav\n", prog);
printf("Modes:\n");
printf(" robot36 - Robot 36 (default)\n");
printf(" martin1 - Martin M1\n");
printf(" martin2 - Martin M2\n");
}
int main(int argc, char **argv) {
if (argc < 3) {
printf("Usage: %s input_image output.wav\n", argv[0]);
if (argc < 4) {
usage(argv[0]);
return 1;
}
if (!sstv_encode_robot36(argv[1], argv[2])) {
printf("Encoding failed\n");
const char *mode_str = argv[1];
const char *input_img = argv[2];
const char *output_wav = argv[3];
int ok = 0;
if (strcmp(mode_str, "robot36") == 0) {
ok = sstv_encode_robot36(input_img, output_wav);
} else if (strcmp(mode_str, "martin1") == 0) {
ok = sstv_encode_martin(input_img, output_wav, &MARTIN_M1);
} else if (strcmp(mode_str, "martin2") == 0) {
ok = sstv_encode_martin(input_img, output_wav, &MARTIN_M2);
} else {
fprintf(stderr, "Unknown mode: %s\n", mode_str);
usage(argv[0]);
return 1;
}
if (!ok) {
fprintf(stderr, "Encoding failed.\n");
return 1;
}

78
martin.c Normal file
View File

@@ -0,0 +1,78 @@
#include "martin.h"
#include "image.h"
#include "sstv.h"
#include "wav.h"
#include <stdint.h>
const martin_mode_t MARTIN_M1 = {
.vis_code = 0x2C, // hex (dd -> 44d)
.width = 320,
.height = 256,
.sync_ms = 4.862,
.sync_porch_ms = 0.572,
.separator_ms = 0.572,
.scan_ms = 146.432, // per channel , per line
};
const martin_mode_t MARTIN_M2 = {
.vis_code = 0x28, // hex (dd -> 40d)
.width = 320,
.height = 256,
.sync_ms = 4.862,
.sync_porch_ms = 0.572,
.separator_ms = 0.572,
.scan_ms = 73.216, // half of M1
};
static void scan_line_channel(wav_t *wav, uint8_t *line, int width, int channel, double scan_ms) {
double px_ms = scan_ms / width;
for (int x = 0; x < width; x++) {
uint8_t val = line[x * 3 + channel];
double freq = 1500.0 + val * 3.1372549;
sstv_tone(wav, freq, px_ms);
}
}
void martin_encode_image(wav_t *wav, uint8_t *rgb, const martin_mode_t *mode) {
for (int y = 0; y < mode->height; y++) {
uint8_t *line = &rgb[y * mode->width * 3];
sstv_tone(wav, 1200.0, mode->sync_ms); // sync pulse
sstv_tone(wav, 1500.0, mode->sync_porch_ms); // sync porch
scan_line_channel(wav, line, mode->width, 1, mode->scan_ms); // G
sstv_tone(wav, 1500.0, mode->separator_ms);
scan_line_channel(wav, line, mode->width, 2, mode->scan_ms); // B
sstv_tone(wav, 1500.0, mode->separator_ms);
scan_line_channel(wav, line, mode->width, 0, mode->scan_ms); // R
sstv_tone(wav, 1500.0, mode->separator_ms);
}
}
int sstv_encode_martin(const char *input_image, const char *output_wav, const martin_mode_t *mode) {
image_t img, resized;
wav_t wav;
if (!image_load(&img, input_image)) {
return 0;
}
if (!image_resize(&img, &resized, mode->width, mode->height)) {
image_free(&img);
return 0;
}
if (!wave_open(&wav, output_wav, 44100)) {
image_free(&img);
image_free_raw(&resized);
return 0;
}
sstv_vis_header_ex(&wav, mode->vis_code);
martin_encode_image(&wav, resized.data, mode);
wave_close(&wav);
image_free(&img);
image_free_raw(&resized);
return 1;
}

23
martin.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef MARTIN_H
#define MARTIN_H
#include "wav.h"
#include <stdint.h>
typedef struct {
uint8_t vis_code;
int width;
int height;
double sync_ms;
double sync_porch_ms;
double separator_ms;
double scan_ms;
} martin_mode_t;
extern const martin_mode_t MARTIN_M1;
extern const martin_mode_t MARTIN_M2;
void martin_encode_image(wav_t *wav, uint8_t *rgb, const martin_mode_t *mode);
int sstv_encode_martin(const char *input_image, const char *output_wav, const martin_mode_t *mode);
#endif // !MARTIN_H

32
sstv.c
View File

@@ -9,12 +9,15 @@
#define AMPLITUDE 12000
static double phase = 0.0;
static double sample_accum = 0.0; // accumulator
void sstv_tone(wav_t *wav, double freq, double duration_ms) {
uint32_t total_samples = (uint32_t)((duration_ms / 1000.0) * wav->sample_rate);
sample_accum += (duration_ms / 1000.0) * wav->sample_rate;
uint32_t total_samples = (uint32_t)sample_accum;
sample_accum -= total_samples;
for (uint32_t i = 0; i < total_samples; i++) {
double step = 2.0 * PI * freq / wav->sample_rate;
for (uint32_t i = 0; i < total_samples; i++) {
phase += step;
if (phase > 2.0 * PI)
phase -= 2.0 * PI;
@@ -24,31 +27,36 @@ void sstv_tone(wav_t *wav, double freq, double duration_ms) {
}
}
void sstv_vis_header(wav_t *wav) {
uint8_t vis = 0x08;
uint8_t parity = 0;
void sstv_vis_header_ex(wav_t *wav, uint8_t vis_code) {
// reset accumulator
phase = 0.0;
sample_accum = 0.0;
/* preamble */
sstv_tone(wav, 1900, 300);
sstv_tone(wav, 1200, 10);
sstv_tone(wav, 1900, 300);
sstv_tone(wav, 1200, 30);
uint8_t parity = 0;
for (int i = 0; i < 7; i++) {
uint8_t bit = (vis >> i) & 1;
if (bit) {
sstv_tone(wav, 1100, 30);
parity ^= 1;
} else {
sstv_tone(wav, 1300, 30);
}
uint8_t bit = (vis_code >> i) & 1;
sstv_tone(wav, bit ? 1100 : 1300, 30);
parity ^= bit;
}
/* parity bit */
sstv_tone(wav, parity ? 1100 : 1300, 30);
/* stop bit */
sstv_tone(wav, 1200, 30);
}
void sstv_vis_header(wav_t *wav) {
sstv_vis_header_ex(wav, 0x08);
}
int sstv_encode_robot36(const char *input_image, const char *output_wav) {
image_t img, resized;
wav_t wav;

4
sstv.h
View File

@@ -5,7 +5,11 @@
#include <stdint.h>
void sstv_tone(wav_t *wav, double freq, double duration_ms);
void sstv_vis_header(wav_t *wav);
void sstv_vis_header_ex(wav_t *wav, uint8_t vis_code);
int sstv_encode_robot36(const char *input_image, const char *output_wav);
#endif // !SSTV_H