From d70bcdf1ebb411d27b597f5d51b76ef977f9d2cd Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Mon, 9 Dec 2024 12:32:36 +1000 Subject: [PATCH 001/166] vis, modespec: Handle VIS codes that use odd parity There are a couple of exceptions to the even parity rule: - Robot 12 monochrome - Wraase SC-2 60 So use an 8th bit to encode that information in the table. This is in line with KB4YZ's VIS header reference. --- common.h | 2 ++ modespec.c | 35 +++++++++++++++++++++++------------ vis.c | 29 +++++++++++++++++++++-------- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/common.h b/common.h index 471c170..3e0c7c5 100644 --- a/common.h +++ b/common.h @@ -15,6 +15,8 @@ extern guchar *StoredLum; extern pthread_t thread1; extern guchar VISmap[]; +#define VIS_PARITY_ODD (1 << 7) + typedef struct _FFTStuff FFTStuff; struct _FFTStuff { double *in; diff --git a/modespec.c b/modespec.c index ff9d7b4..8d73111 100644 --- a/modespec.c +++ b/modespec.c @@ -371,15 +371,26 @@ _ModeSpec ModeSpec[] = { * */ -// 0 1 2 3 4 5 6 7 8 9 A B C D E F - -guchar VISmap[] = { 0, 0, R8BW, 0, R24, 0, R12BW,0, R36, 0, R24BW,0, R72, 0, 0, 0, // 0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 - M4, 0, 0, 0, M3, 0, 0, 0, M2, 0, 0, 0, M1, 0, 0, 0, // 2 - 0, 0, 0, 0, 0, 0, 0, W2180,S2, 0, 0, 0, S1, 0, 0, W2120, // 3 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, SDX, 0, 0, 0, // 4 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, PD50,PD290,PD120, // 5 - PD180,PD240,PD160,PD90,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6 - 0, P3, P5, P7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // 7 - -// 0 1 2 3 4 5 6 7 8 9 A B C D E F +// 0 1 2 3 4 5 6 7 8 9 A B C D E F + +guchar VISmap[] = { + // Normal (even) parity + 0, 0, R8BW, 0, R24, 0, 0, 0, R36, 0, R24BW,0, R72, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 + M4, 0, 0, 0, M3, 0, 0, 0, M2, 0, 0, 0, M1, 0, 0, 0, // 2 + 0, 0, 0, 0, 0, 0, 0, W2180,S2, 0, 0, 0, S1, 0, 0, W2120, // 3 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, SDX, 0, 0, 0, // 4 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, PD50,PD290,PD120, // 5 + PD180,PD240,PD160,PD90,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, P3, P5, P7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7 + // Inverted (odd) parity + 0, 0, 0, 0, 0, 0, R12BW,0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // F + +// 0 1 2 3 4 5 6 7 8 9 A B C D E F diff --git a/vis.c b/vis.c index 6f7eb26..0adf3bd 100644 --- a/vis.c +++ b/vis.c @@ -7,6 +7,14 @@ #include "common.h" +/* Handle selection of the VIS mode in the GUI */ +static void onGotVis(guchar VIS) { + gdk_threads_enter(); + gtk_combo_box_set_active (GTK_COMBO_BOX(gui.combo_mode), VISmap[VIS]-1); + gtk_spin_button_set_value (GTK_SPIN_BUTTON(gui.spin_shift), CurrentPic.HedrShift); + gdk_threads_leave(); +} + /* * * Detect VIS & frequency shift @@ -113,19 +121,24 @@ guchar GetVIS () { Parity = Bit[0] ^ Bit[1] ^ Bit[2] ^ Bit[3] ^ Bit[4] ^ Bit[5] ^ Bit[6]; - if (VISmap[VIS] == R12BW) Parity = !Parity; - if (Parity != ParityBit) { - printf(" Parity fail\n"); - gotvis = FALSE; + // Maybe this mode uses odd parity? + printf(" Parity inconclusive, trying odd parity\n"); + if (VISmap[VIS | VIS_PARITY_ODD] == UNKNOWN) { + // Nope! + printf(" Parity fail\n"); + gotvis = FALSE; + } else { + // Yep, that was it. Inverted parity. + VIS |= VIS_PARITY_ODD; + onGotVis(VIS); + break; + } } else if (VISmap[VIS] == UNKNOWN) { printf(" Unknown VIS\n"); gotvis = FALSE; } else { - gdk_threads_enter(); - gtk_combo_box_set_active (GTK_COMBO_BOX(gui.combo_mode), VISmap[VIS]-1); - gtk_spin_button_set_value (GTK_SPIN_BUTTON(gui.spin_shift), CurrentPic.HedrShift); - gdk_threads_leave(); + onGotVis(VIS); break; } } From 5b668cac2cb9caf24aa9f76531e3f87a43656912 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Mon, 9 Dec 2024 12:34:35 +1000 Subject: [PATCH 002/166] modespec, slowrx UI: Add Wraase SC-2 60 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is reverse-engineered from QSSTV by first implementing an encoder for it that QSSTV's W260 decoder agreed with… *then* using the specifications determined to implement the decoder in `slowrx`. Aside from an occasional horizontal offset (which seems to be a common issue with all modes… might be related to "where" in the 1024-sample FFT frame the SSTV image is first seen), it seems to agree with QSSTV's decoding of the same reference audio sample. --- common.h | 12 ++++++------ modespec.c | 19 +++++++++++++++++-- slowrx.ui | 1 + 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/common.h b/common.h index 3e0c7c5..c7af27f 100644 --- a/common.h +++ b/common.h @@ -95,12 +95,12 @@ extern PicMeta CurrentPic; // SSTV modes enum { UNKNOWN=0, - M1, M2, M3, M4, - S1, S2, SDX, - R72, R36, R24, R24BW, R12BW, R8BW, - PD50, PD90, PD120, PD160, PD180, PD240, PD290, - P3, P5, P7, - W2120, W2180 + M1, M2, M3, M4, + S1, S2, SDX, + R72, R36, R24, R24BW, R12BW, R8BW, + PD50, PD90, PD120, PD160, PD180, PD240, PD290, + P3, P5, P7, + W260, W2120, W2180 }; // Color encodings diff --git a/modespec.c b/modespec.c index 8d73111..5ff0059 100644 --- a/modespec.c +++ b/modespec.c @@ -204,7 +204,22 @@ _ModeSpec ModeSpec[] = { .NumLines = 120, .LineHeight = 2, .ColorEnc = BW }, - + + [W260] = { + // Reverse-engineered by taking half the W2120 pixel time as an educated guess then + // tweaking the generated image timings until QSSTV decoded them without slant. -- VK4MSL + .Name = "Wraase SC-2 60", + .ShortName = "W260", + .SyncTime = 5.5225e-3, + .PorchTime = 0.0e-3, + .SeptrTime = 0.5e-3, + .PixelTime = 0.2425859375e-3, + .LineTime = 240.405e-3, + .ImgWidth = 320, + .NumLines = 256, + .LineHeight = 1, + .ColorEnc = RGB }, + [W2120] = { // KB4YZ, 1999 .Name = "Wraase SC-2 120", .ShortName = "W2120", @@ -387,7 +402,7 @@ guchar VISmap[] = { 0, 0, 0, 0, 0, 0, R12BW,0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, W260, 0, 0, 0, 0, // B 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E diff --git a/slowrx.ui b/slowrx.ui index 85e5d62..8125b7e 100644 --- a/slowrx.ui +++ b/slowrx.ui @@ -638,6 +638,7 @@ Pasokon P3 Pasokon P5 Pasokon P7 + Wraase SC-2 60 Wraase SC-2 120 Wraase SC-2 180 From 868c64afbd2f95d578af4bf694afbe1aaab7cfe4 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 12:43:26 +1000 Subject: [PATCH 003/166] Makefile: Use $(shell ) so we can see exactly what's being injected. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index dc38a36..8989131 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ CC = gcc CFLAGS = -Wall -Wextra -std=gnu99 -pedantic -g -DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_4 -GTKCFLAGS = `pkg-config --cflags gtk+-3.0` -GTKLIBS = `pkg-config --libs gtk+-3.0` +GTKCFLAGS = $(shell pkg-config --cflags gtk+-3.0) +GTKLIBS = $(shell pkg-config --libs gtk+-3.0) OFLAGS = -O3 From cc2561be21b415fd0e0a0b7dc16288846e10cda9 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 12:48:09 +1000 Subject: [PATCH 004/166] Makefile: Derive OBJECTS from the list of source files. --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8989131..6335ba1 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,8 @@ GTKLIBS = $(shell pkg-config --libs gtk+-3.0) OFLAGS = -O3 -OBJECTS = common.o modespec.o gui.o video.o vis.o sync.o pcm.o fsk.o slowrx.o +SOURCES = common.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c slowrx.c +OBJECTS = $(patsubst %.c,%.o,$(SOURCES)) all: slowrx From c2e2a29412a505ddaa1e531a6d4835fb1982e02a Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 12:54:19 +1000 Subject: [PATCH 005/166] Makefile: Generate dependency files These mean we can move different parts out to separate header files, and `gcc` will generate the dependencies so we don't have to keep them up-to-date in the `Makefile`. If something changes then, `make` knows exactly what to rebuild. --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6335ba1..c9a9d39 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ OFLAGS = -O3 SOURCES = common.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c slowrx.c OBJECTS = $(patsubst %.c,%.o,$(SOURCES)) +DEPENDS = $(patsubst %.c,%.d,$(SOURCES)) all: slowrx @@ -15,7 +16,10 @@ slowrx: $(OBJECTS) $(CC) $(CFLAGS) -o $@ $(OBJECTS) $(GTKLIBS) -lfftw3 -lgthread-2.0 -lasound -lm -lpthread %.o: %.c common.h + $(CC) -MM -MF $(*F).d $(CFLAGS) $(GTKCFLAGS) $(OFLAGS) $< $(CC) $(CFLAGS) $(GTKCFLAGS) $(OFLAGS) -c -o $@ $< clean: - rm -f slowrx $(OBJECTS) + rm -f slowrx $(OBJECTS) $(DEPENDS) + +-include $(DEPENDS) From d090bcc5523ce0acb77b8741f36cc499a3287729 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 12:55:32 +1000 Subject: [PATCH 006/166] Makefile: Define the names of all output targets At some point, we'll make it possible for the user to rename output files or specify which ones to compile and install (e.g. for a headless daemon-only installation). --- Makefile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index c9a9d39..3b49508 100644 --- a/Makefile +++ b/Makefile @@ -6,13 +6,17 @@ GTKLIBS = $(shell pkg-config --libs gtk+-3.0) OFLAGS = -O3 +GUI_BIN = slowrx + +TARGETS = $(GUI_BIN) + SOURCES = common.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c slowrx.c OBJECTS = $(patsubst %.c,%.o,$(SOURCES)) DEPENDS = $(patsubst %.c,%.d,$(SOURCES)) -all: slowrx +all: $(TARGETS) -slowrx: $(OBJECTS) +$(GUI_BIN): $(OBJECTS) $(CC) $(CFLAGS) -o $@ $(OBJECTS) $(GTKLIBS) -lfftw3 -lgthread-2.0 -lasound -lm -lpthread %.o: %.c common.h @@ -20,6 +24,6 @@ slowrx: $(OBJECTS) $(CC) $(CFLAGS) $(GTKCFLAGS) $(OFLAGS) -c -o $@ $< clean: - rm -f slowrx $(OBJECTS) $(DEPENDS) + rm -f $(TARGETS) $(OBJECTS) $(DEPENDS) -include $(DEPENDS) From 2015a2904b22103f29e895df2bb5f5b33b96b3be Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 12:59:43 +1000 Subject: [PATCH 007/166] Makefile: Add undeclared dependency `gui.c` seems to reference `aboutdialog.ui`. --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 3b49508..e9d75ba 100644 --- a/Makefile +++ b/Makefile @@ -27,3 +27,5 @@ clean: rm -f $(TARGETS) $(OBJECTS) $(DEPENDS) -include $(DEPENDS) + +gui.c: aboutdialog.ui From 9ced2e5588822d81dce0e20e4bb8ba238f966bc6 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 13:01:19 +1000 Subject: [PATCH 008/166] Makefile: Drop explicit `common.h` dependency `gcc` is figuring this out for us now. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e9d75ba..e992785 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ all: $(TARGETS) $(GUI_BIN): $(OBJECTS) $(CC) $(CFLAGS) -o $@ $(OBJECTS) $(GTKLIBS) -lfftw3 -lgthread-2.0 -lasound -lm -lpthread -%.o: %.c common.h +%.o: %.c $(CC) -MM -MF $(*F).d $(CFLAGS) $(GTKCFLAGS) $(OFLAGS) $< $(CC) $(CFLAGS) $(GTKCFLAGS) $(OFLAGS) -c -o $@ $< From 8d7bf132ec788c515690e272ff582d42dada5faa Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 13:04:23 +1000 Subject: [PATCH 009/166] Makefile: Found another `.ui` file that `gui.c` pulls in --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e992785..7427e92 100644 --- a/Makefile +++ b/Makefile @@ -28,4 +28,4 @@ clean: -include $(DEPENDS) -gui.c: aboutdialog.ui +gui.c: aboutdialog.ui slowrx.ui From 61adb40bb837018a94d80f7239c4212b83ee045a Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 13:08:36 +1000 Subject: [PATCH 010/166] common.h: Move gui.c stuff into gui.h --- common.h | 4 ---- gui.c | 1 + gui.h | 10 ++++++++++ slowrx.c | 1 + 4 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 gui.h diff --git a/common.h b/common.h index c7af27f..f3097f2 100644 --- a/common.h +++ b/common.h @@ -126,7 +126,6 @@ extern _ModeSpec ModeSpec[]; double power (fftw_complex coeff); guchar clip (double a); -void createGUI (); double deg2rad (double Deg); double FindSync (guchar Mode, double Rate, int *Skip); void GetFSK (char *dest); @@ -138,16 +137,13 @@ void *Listen (); void populateDeviceList (); void readPcm (gint numsamples); void saveCurrentPic(); -void setVU (double *Power, int FFTLen, int WinIdx, gboolean ShowWin); void evt_AbortRx (); void evt_changeDevices (); -void evt_chooseDir (); void evt_clearPix (); void evt_clickimg (); void evt_deletewindow (); void evt_GetAdaptive (); void evt_ManualStart (); -void evt_show_about (); #endif diff --git a/gui.c b/gui.c index 63a0d17..efbd2a5 100644 --- a/gui.c +++ b/gui.c @@ -6,6 +6,7 @@ #include #include "common.h" +#include "gui.h" void createGUI() { diff --git a/gui.h b/gui.h new file mode 100644 index 0000000..74d7bfa --- /dev/null +++ b/gui.h @@ -0,0 +1,10 @@ +#ifndef _GUI_H_ +#define _GUI_H_ + +void createGUI (); +void setVU (double *Power, int FFTLen, int WinIdx, gboolean ShowWin); + +void evt_chooseDir (); +void evt_show_about (); + +#endif diff --git a/slowrx.c b/slowrx.c index d784fd2..e47a36e 100644 --- a/slowrx.c +++ b/slowrx.c @@ -19,6 +19,7 @@ #include #include "common.h" +#include "gui.h" // The thread that listens to VIS headers and calls decoders etc void *Listen() { From 8bcefbb0971312e615546cf3df5e0636f25d88c7 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 13:11:33 +1000 Subject: [PATCH 011/166] common.h: Move video.c functions to video.h --- common.h | 1 - slowrx.c | 1 + video.c | 1 + video.h | 6 ++++++ 4 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 video.h diff --git a/common.h b/common.h index f3097f2..9f0ae8f 100644 --- a/common.h +++ b/common.h @@ -129,7 +129,6 @@ guchar clip (double a); double deg2rad (double Deg); double FindSync (guchar Mode, double Rate, int *Skip); void GetFSK (char *dest); -gboolean GetVideo (guchar Mode, double Rate, int Skip, gboolean Redraw); guchar GetVIS (); guint GetBin (double Freq, guint FFTLen); int initPcmDevice (); diff --git a/slowrx.c b/slowrx.c index e47a36e..066953f 100644 --- a/slowrx.c +++ b/slowrx.c @@ -20,6 +20,7 @@ #include "common.h" #include "gui.h" +#include "video.h" // The thread that listens to VIS headers and calls decoders etc void *Listen() { diff --git a/video.c b/video.c index 389751f..10d3a53 100644 --- a/video.c +++ b/video.c @@ -8,6 +8,7 @@ #include #include "common.h" +#include "video.h" /* Demodulate the video signal & store all kinds of stuff for later stages * Mode: M1, M2, S1, S2, R72, R36... diff --git a/video.h b/video.h new file mode 100644 index 0000000..b5d190e --- /dev/null +++ b/video.h @@ -0,0 +1,6 @@ +#ifndef _VIDEO_H_ +#define _VIDEO_H_ + +gboolean GetVideo (guchar Mode, double Rate, int Skip, gboolean Redraw); + +#endif From 3bbef03ccf828faf96257a4fcb07e3c206e62a4e Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 13:13:12 +1000 Subject: [PATCH 012/166] .gitignore: Ignore object and dependency files. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 8a64134..12ae342 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ slowrx rx rx-lum + +*.d +*.o From 99b227de9456aed6fe12d53009e229f397da4935 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 13:15:44 +1000 Subject: [PATCH 013/166] common.h: Move fsk.c functions to fsk.h --- common.h | 1 - fsk.c | 1 + fsk.h | 6 ++++++ slowrx.c | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 fsk.h diff --git a/common.h b/common.h index 9f0ae8f..2210746 100644 --- a/common.h +++ b/common.h @@ -128,7 +128,6 @@ double power (fftw_complex coeff); guchar clip (double a); double deg2rad (double Deg); double FindSync (guchar Mode, double Rate, int *Skip); -void GetFSK (char *dest); guchar GetVIS (); guint GetBin (double Freq, guint FFTLen); int initPcmDevice (); diff --git a/fsk.c b/fsk.c index a1c27b2..26326f2 100644 --- a/fsk.c +++ b/fsk.c @@ -6,6 +6,7 @@ #include #include "common.h" +#include "fsk.h" /* * diff --git a/fsk.h b/fsk.h new file mode 100644 index 0000000..e6a061d --- /dev/null +++ b/fsk.h @@ -0,0 +1,6 @@ +#ifndef _FSK_H_ +#define _FSK_H_ + +void GetFSK (char *dest); + +#endif diff --git a/slowrx.c b/slowrx.c index 066953f..35070cd 100644 --- a/slowrx.c +++ b/slowrx.c @@ -19,6 +19,7 @@ #include #include "common.h" +#include "fsk.h" #include "gui.h" #include "video.h" From aa3ee13acbba39e1776a894525f6ec714ffa26bb Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 13:35:13 +1000 Subject: [PATCH 014/166] vis.c: Add missed `gui.h` include --- vis.c | 1 + 1 file changed, 1 insertion(+) diff --git a/vis.c b/vis.c index 0adf3bd..ba97da0 100644 --- a/vis.c +++ b/vis.c @@ -6,6 +6,7 @@ #include #include "common.h" +#include "gui.h" /* Handle selection of the VIS mode in the GUI */ static void onGotVis(guchar VIS) { From 1de8f6178bcc02cf7ea5be4a916ef5d144e1dd93 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 13:35:37 +1000 Subject: [PATCH 015/166] common.h: Move modespec.c definitions to modespec.h --- common.c | 1 + common.h | 19 ------------------- modespec.c | 1 + modespec.h | 29 +++++++++++++++++++++++++++++ slowrx.c | 1 + sync.c | 1 + video.c | 1 + vis.c | 1 + 8 files changed, 35 insertions(+), 19 deletions(-) create mode 100644 modespec.h diff --git a/common.c b/common.c index be4a97b..ca50233 100644 --- a/common.c +++ b/common.c @@ -10,6 +10,7 @@ #include #include "common.h" +#include "modespec.h" gboolean Abort = FALSE; gboolean Adaptive = TRUE; diff --git a/common.h b/common.h index 2210746..03e6275 100644 --- a/common.h +++ b/common.h @@ -13,9 +13,6 @@ extern gboolean ManualActivated; extern gboolean ManualResync; extern guchar *StoredLum; extern pthread_t thread1; -extern guchar VISmap[]; - -#define VIS_PARITY_ODD (1 << 7) typedef struct _FFTStuff FFTStuff; struct _FFTStuff { @@ -108,22 +105,6 @@ enum { GBR, RGB, YUV, BW }; -typedef struct ModeSpec { - char *Name; - char *ShortName; - double SyncTime; - double PorchTime; - double SeptrTime; - double PixelTime; - double LineTime; - gushort ImgWidth; - gushort NumLines; - guchar LineHeight; - guchar ColorEnc; -} _ModeSpec; - -extern _ModeSpec ModeSpec[]; - double power (fftw_complex coeff); guchar clip (double a); double deg2rad (double Deg); diff --git a/modespec.c b/modespec.c index 5ff0059..a2f6e6c 100644 --- a/modespec.c +++ b/modespec.c @@ -6,6 +6,7 @@ #include #include "common.h" +#include "modespec.h" /* * Mode specifications diff --git a/modespec.h b/modespec.h new file mode 100644 index 0000000..2f83eb1 --- /dev/null +++ b/modespec.h @@ -0,0 +1,29 @@ +#ifndef _MODESPEC_H_ +#define _MODESPEC_H_ + +#define MINSLANT 30 +#define MAXSLANT 150 +#define BUFLEN 4096 +#define SYNCPIXLEN 1.5e-3 + +extern guchar VISmap[]; + +#define VIS_PARITY_ODD (1 << 7) + +typedef struct ModeSpec { + char *Name; + char *ShortName; + double SyncTime; + double PorchTime; + double SeptrTime; + double PixelTime; + double LineTime; + gushort ImgWidth; + gushort NumLines; + guchar LineHeight; + guchar ColorEnc; +} _ModeSpec; + +extern _ModeSpec ModeSpec[]; + +#endif diff --git a/slowrx.c b/slowrx.c index 35070cd..2f15592 100644 --- a/slowrx.c +++ b/slowrx.c @@ -21,6 +21,7 @@ #include "common.h" #include "fsk.h" #include "gui.h" +#include "modespec.h" #include "video.h" // The thread that listens to VIS headers and calls decoders etc diff --git a/sync.c b/sync.c index 57177ae..786b0ba 100644 --- a/sync.c +++ b/sync.c @@ -6,6 +6,7 @@ #include #include "common.h" +#include "modespec.h" /* Find the slant angle of the sync singnal and adjust sample rate to cancel it out * Length: number of PCM samples to process diff --git a/video.c b/video.c index 10d3a53..9b16c32 100644 --- a/video.c +++ b/video.c @@ -8,6 +8,7 @@ #include #include "common.h" +#include "modespec.h" #include "video.h" /* Demodulate the video signal & store all kinds of stuff for later stages diff --git a/vis.c b/vis.c index ba97da0..b2b5aa2 100644 --- a/vis.c +++ b/vis.c @@ -7,6 +7,7 @@ #include "common.h" #include "gui.h" +#include "modespec.h" /* Handle selection of the VIS mode in the GUI */ static void onGotVis(guchar VIS) { From bc717f35b7b7f1c6969a46778ef3c310795cb58e Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 13:36:34 +1000 Subject: [PATCH 016/166] video.c: Add missed `gui.h` requirement --- video.c | 1 + 1 file changed, 1 insertion(+) diff --git a/video.c b/video.c index 9b16c32..83a2147 100644 --- a/video.c +++ b/video.c @@ -8,6 +8,7 @@ #include #include "common.h" +#include "gui.h" #include "modespec.h" #include "video.h" From c8da316daca8459e8468f350f43f1f7b71a63714 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 13:44:36 +1000 Subject: [PATCH 017/166] common.h: Move pcm.c stuff to pcm.h --- common.c | 1 + common.h | 12 ------------ fsk.c | 1 + pcm.c | 1 + pcm.h | 17 +++++++++++++++++ slowrx.c | 1 + video.c | 1 + vis.c | 1 + 8 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 pcm.h diff --git a/common.c b/common.c index ca50233..a5f56a1 100644 --- a/common.c +++ b/common.c @@ -11,6 +11,7 @@ #include "common.h" #include "modespec.h" +#include "pcm.h" gboolean Abort = FALSE; gboolean Adaptive = TRUE; diff --git a/common.h b/common.h index 03e6275..9910e64 100644 --- a/common.h +++ b/common.h @@ -23,15 +23,6 @@ struct _FFTStuff { }; extern FFTStuff fft; -typedef struct _PcmData PcmData; -struct _PcmData { - snd_pcm_t *handle; - gint16 *Buffer; - int WindowPtr; - gboolean BufferDrop; -}; -extern PcmData pcm; - typedef struct _GuiObjs GuiObjs; struct _GuiObjs { GtkWidget *button_abort; @@ -111,10 +102,7 @@ double deg2rad (double Deg); double FindSync (guchar Mode, double Rate, int *Skip); guchar GetVIS (); guint GetBin (double Freq, guint FFTLen); -int initPcmDevice (); void *Listen (); -void populateDeviceList (); -void readPcm (gint numsamples); void saveCurrentPic(); void evt_AbortRx (); diff --git a/fsk.c b/fsk.c index 26326f2..6879d33 100644 --- a/fsk.c +++ b/fsk.c @@ -7,6 +7,7 @@ #include "common.h" #include "fsk.h" +#include "pcm.h" /* * diff --git a/pcm.c b/pcm.c index db97df1..b600924 100644 --- a/pcm.c +++ b/pcm.c @@ -9,6 +9,7 @@ #include #include "common.h" +#include "pcm.h" /* * Stuff related to sound card capture diff --git a/pcm.h b/pcm.h new file mode 100644 index 0000000..5fa98f5 --- /dev/null +++ b/pcm.h @@ -0,0 +1,17 @@ +#ifndef _PCM_H +#define _PCM_H + +typedef struct _PcmData PcmData; +struct _PcmData { + snd_pcm_t *handle; + gint16 *Buffer; + int WindowPtr; + gboolean BufferDrop; +}; +extern PcmData pcm; + +int initPcmDevice (); +void populateDeviceList (); +void readPcm (gint numsamples); + +#endif diff --git a/slowrx.c b/slowrx.c index 2f15592..8acfea2 100644 --- a/slowrx.c +++ b/slowrx.c @@ -22,6 +22,7 @@ #include "fsk.h" #include "gui.h" #include "modespec.h" +#include "pcm.h" #include "video.h" // The thread that listens to VIS headers and calls decoders etc diff --git a/video.c b/video.c index 83a2147..26872ba 100644 --- a/video.c +++ b/video.c @@ -10,6 +10,7 @@ #include "common.h" #include "gui.h" #include "modespec.h" +#include "pcm.h" #include "video.h" /* Demodulate the video signal & store all kinds of stuff for later stages diff --git a/vis.c b/vis.c index b2b5aa2..9c091b8 100644 --- a/vis.c +++ b/vis.c @@ -8,6 +8,7 @@ #include "common.h" #include "gui.h" #include "modespec.h" +#include "pcm.h" /* Handle selection of the VIS mode in the GUI */ static void onGotVis(guchar VIS) { From 4a1ffe40c493bc76da92c4fbc40e5804e5c0c973 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 13:47:18 +1000 Subject: [PATCH 018/166] common.h: Move sync.c definitions to sync.h --- common.h | 1 - slowrx.c | 1 + sync.h | 6 ++++++ 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 sync.h diff --git a/common.h b/common.h index 9910e64..41c1d36 100644 --- a/common.h +++ b/common.h @@ -99,7 +99,6 @@ enum { double power (fftw_complex coeff); guchar clip (double a); double deg2rad (double Deg); -double FindSync (guchar Mode, double Rate, int *Skip); guchar GetVIS (); guint GetBin (double Freq, guint FFTLen); void *Listen (); diff --git a/slowrx.c b/slowrx.c index 8acfea2..977e1d5 100644 --- a/slowrx.c +++ b/slowrx.c @@ -23,6 +23,7 @@ #include "gui.h" #include "modespec.h" #include "pcm.h" +#include "sync.h" #include "video.h" // The thread that listens to VIS headers and calls decoders etc diff --git a/sync.h b/sync.h new file mode 100644 index 0000000..6b684aa --- /dev/null +++ b/sync.h @@ -0,0 +1,6 @@ +#ifndef _SYNC_H_ +#define _SYNC_H_ + +double FindSync (guchar Mode, double Rate, int *Skip); + +#endif From 550dd5bdde28a9ee1df6c3a3e57442a13024edfb Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 13:55:39 +1000 Subject: [PATCH 019/166] common: Move GTK event handlers to gui_events --- Makefile | 2 +- common.c | 122 --------------------------------------------- common.h | 8 --- gui.c | 1 + gui_events.c | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++ gui_events.h | 12 +++++ 6 files changed, 150 insertions(+), 131 deletions(-) create mode 100644 gui_events.c create mode 100644 gui_events.h diff --git a/Makefile b/Makefile index 7427e92..6774e02 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ GUI_BIN = slowrx TARGETS = $(GUI_BIN) -SOURCES = common.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c slowrx.c +SOURCES = common.c gui_events.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c slowrx.c OBJECTS = $(patsubst %.c,%.o,$(SOURCES)) DEPENDS = $(patsubst %.c,%.d,$(SOURCES)) diff --git a/common.c b/common.c index a5f56a1..2c7fbb4 100644 --- a/common.c +++ b/common.c @@ -93,125 +93,3 @@ void saveCurrentPic() { g_object_unref(scaledpb); g_string_free(pngfilename, TRUE); } - - -/*** Gtk+ event handlers ***/ - - -// Quit -void evt_deletewindow() { - gtk_main_quit (); -} - -// Transform the NoiseAdapt toggle state into a variable -void evt_GetAdaptive() { - Adaptive = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_adapt)); -} - -// Manual Start clicked -void evt_ManualStart() { - ManualActivated = TRUE; -} - -// Abort clicked during rx -void evt_AbortRx() { - Abort = TRUE; -} - -// Another device selected from list -void evt_changeDevices() { - - int status; - - pcm.BufferDrop = FALSE; - Abort = TRUE; - - static int init; - if (init) - pthread_join(thread1, NULL); - init = 1; - - if (pcm.handle != NULL) snd_pcm_close(pcm.handle); - - status = initPcmDevice(gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gui.combo_card))); - - - switch(status) { - case 0: - gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_YES,GTK_ICON_SIZE_SMALL_TOOLBAR); - gtk_widget_set_tooltip_text(gui.image_devstatus, "Device successfully opened"); - break; - case -1: - gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_DIALOG_WARNING,GTK_ICON_SIZE_SMALL_TOOLBAR); - gtk_widget_set_tooltip_text(gui.image_devstatus, "Device was opened, but doesn't support 44100 Hz"); - break; - case -2: - gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_DIALOG_ERROR,GTK_ICON_SIZE_SMALL_TOOLBAR); - gtk_widget_set_tooltip_text(gui.image_devstatus, "Failed to open device"); - break; - } - - g_key_file_set_string(config,"slowrx","device",gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gui.combo_card))); - - pthread_create (&thread1, NULL, Listen, NULL); - -} - -// Clear received picture & metadata -void evt_clearPix() { - gdk_pixbuf_fill (pixbuf_disp, 0); - gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp); - gtk_label_set_markup (GTK_LABEL(gui.label_fskid), ""); - gtk_label_set_markup (GTK_LABEL(gui.label_utc), ""); - gtk_label_set_markup (GTK_LABEL(gui.label_lastmode), ""); -} - -// Manual slant adjust -void evt_clickimg(GtkWidget *widget, GdkEventButton* event, GdkWindowEdge edge) { - static double prevx=0,prevy=0,newrate; - static gboolean secondpress=FALSE; - double x,y,dx,dy,xic; - - (void)widget; - (void)edge; - - if (event->type == GDK_BUTTON_PRESS && event->button == 1 && gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_setedge))) { - - x = event->x * (ModeSpec[CurrentPic.Mode].ImgWidth / 500.0); - y = event->y * (ModeSpec[CurrentPic.Mode].ImgWidth / 500.0) / ModeSpec[CurrentPic.Mode].LineHeight; - - if (secondpress) { - secondpress=FALSE; - - dx = x - prevx; - dy = y - prevy; - - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gui.tog_setedge),FALSE); - - // Adjust sample rate, if in sensible limits - newrate = CurrentPic.Rate + CurrentPic.Rate * (dx * ModeSpec[CurrentPic.Mode].PixelTime) / (dy * ModeSpec[CurrentPic.Mode].LineHeight * ModeSpec[CurrentPic.Mode].LineTime); - if (newrate > 32000 && newrate < 56000) { - CurrentPic.Rate = newrate; - - // Find x-intercept and adjust skip - xic = fmod( (x - (y / (dy/dx))), ModeSpec[CurrentPic.Mode].ImgWidth); - if (xic < 0) xic = ModeSpec[CurrentPic.Mode].ImgWidth - xic; - CurrentPic.Skip = fmod(CurrentPic.Skip + xic * ModeSpec[CurrentPic.Mode].PixelTime * CurrentPic.Rate, - ModeSpec[CurrentPic.Mode].LineTime * CurrentPic.Rate); - if (CurrentPic.Skip > ModeSpec[CurrentPic.Mode].LineTime * CurrentPic.Rate / 2.0) - CurrentPic.Skip -= ModeSpec[CurrentPic.Mode].LineTime * CurrentPic.Rate; - - // Signal the listener to exit from GetVIS() and re-process the pic - ManualResync = TRUE; - } - - } else { - secondpress = TRUE; - prevx = x; - prevy = y; - } - } else { - secondpress=FALSE; - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gui.tog_setedge), FALSE); - } -} diff --git a/common.h b/common.h index 41c1d36..a2b0d91 100644 --- a/common.h +++ b/common.h @@ -104,12 +104,4 @@ guint GetBin (double Freq, guint FFTLen); void *Listen (); void saveCurrentPic(); -void evt_AbortRx (); -void evt_changeDevices (); -void evt_clearPix (); -void evt_clickimg (); -void evt_deletewindow (); -void evt_GetAdaptive (); -void evt_ManualStart (); - #endif diff --git a/gui.c b/gui.c index efbd2a5..b9222a5 100644 --- a/gui.c +++ b/gui.c @@ -7,6 +7,7 @@ #include "common.h" #include "gui.h" +#include "gui_events.h" void createGUI() { diff --git a/gui_events.c b/gui_events.c new file mode 100644 index 0000000..631c46b --- /dev/null +++ b/gui_events.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "common.h" +#include "modespec.h" +#include "pcm.h" + + +/*** Gtk+ event handlers ***/ + + +// Quit +void evt_deletewindow() { + gtk_main_quit (); +} + +// Transform the NoiseAdapt toggle state into a variable +void evt_GetAdaptive() { + Adaptive = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_adapt)); +} + +// Manual Start clicked +void evt_ManualStart() { + ManualActivated = TRUE; +} + +// Abort clicked during rx +void evt_AbortRx() { + Abort = TRUE; +} + +// Another device selected from list +void evt_changeDevices() { + + int status; + + pcm.BufferDrop = FALSE; + Abort = TRUE; + + static int init; + if (init) + pthread_join(thread1, NULL); + init = 1; + + if (pcm.handle != NULL) snd_pcm_close(pcm.handle); + + status = initPcmDevice(gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gui.combo_card))); + + + switch(status) { + case 0: + gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_YES,GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_widget_set_tooltip_text(gui.image_devstatus, "Device successfully opened"); + break; + case -1: + gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_DIALOG_WARNING,GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_widget_set_tooltip_text(gui.image_devstatus, "Device was opened, but doesn't support 44100 Hz"); + break; + case -2: + gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_DIALOG_ERROR,GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_widget_set_tooltip_text(gui.image_devstatus, "Failed to open device"); + break; + } + + g_key_file_set_string(config,"slowrx","device",gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gui.combo_card))); + + pthread_create (&thread1, NULL, Listen, NULL); + +} + +// Clear received picture & metadata +void evt_clearPix() { + gdk_pixbuf_fill (pixbuf_disp, 0); + gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp); + gtk_label_set_markup (GTK_LABEL(gui.label_fskid), ""); + gtk_label_set_markup (GTK_LABEL(gui.label_utc), ""); + gtk_label_set_markup (GTK_LABEL(gui.label_lastmode), ""); +} + +// Manual slant adjust +void evt_clickimg(GtkWidget *widget, GdkEventButton* event, GdkWindowEdge edge) { + static double prevx=0,prevy=0,newrate; + static gboolean secondpress=FALSE; + double x,y,dx,dy,xic; + + (void)widget; + (void)edge; + + if (event->type == GDK_BUTTON_PRESS && event->button == 1 && gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_setedge))) { + + x = event->x * (ModeSpec[CurrentPic.Mode].ImgWidth / 500.0); + y = event->y * (ModeSpec[CurrentPic.Mode].ImgWidth / 500.0) / ModeSpec[CurrentPic.Mode].LineHeight; + + if (secondpress) { + secondpress=FALSE; + + dx = x - prevx; + dy = y - prevy; + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gui.tog_setedge),FALSE); + + // Adjust sample rate, if in sensible limits + newrate = CurrentPic.Rate + CurrentPic.Rate * (dx * ModeSpec[CurrentPic.Mode].PixelTime) / (dy * ModeSpec[CurrentPic.Mode].LineHeight * ModeSpec[CurrentPic.Mode].LineTime); + if (newrate > 32000 && newrate < 56000) { + CurrentPic.Rate = newrate; + + // Find x-intercept and adjust skip + xic = fmod( (x - (y / (dy/dx))), ModeSpec[CurrentPic.Mode].ImgWidth); + if (xic < 0) xic = ModeSpec[CurrentPic.Mode].ImgWidth - xic; + CurrentPic.Skip = fmod(CurrentPic.Skip + xic * ModeSpec[CurrentPic.Mode].PixelTime * CurrentPic.Rate, + ModeSpec[CurrentPic.Mode].LineTime * CurrentPic.Rate); + if (CurrentPic.Skip > ModeSpec[CurrentPic.Mode].LineTime * CurrentPic.Rate / 2.0) + CurrentPic.Skip -= ModeSpec[CurrentPic.Mode].LineTime * CurrentPic.Rate; + + // Signal the listener to exit from GetVIS() and re-process the pic + ManualResync = TRUE; + } + + } else { + secondpress = TRUE; + prevx = x; + prevy = y; + } + } else { + secondpress=FALSE; + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gui.tog_setedge), FALSE); + } +} diff --git a/gui_events.h b/gui_events.h new file mode 100644 index 0000000..8c4af98 --- /dev/null +++ b/gui_events.h @@ -0,0 +1,12 @@ +#ifndef _GUI_EVENTS_H_ +#define _GUI_EVENTS_H_ + +void evt_AbortRx (); +void evt_changeDevices (); +void evt_clearPix (); +void evt_clickimg (); +void evt_deletewindow (); +void evt_GetAdaptive (); +void evt_ManualStart (); + +#endif From cfe514cb1364319a22bb9b2ebe39fa0b69e8110f Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 13:57:58 +1000 Subject: [PATCH 020/166] common.h: Move vis.c stuff to vis.h --- common.h | 1 - slowrx.c | 1 + vis.h | 6 ++++++ 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 vis.h diff --git a/common.h b/common.h index a2b0d91..fae7727 100644 --- a/common.h +++ b/common.h @@ -99,7 +99,6 @@ enum { double power (fftw_complex coeff); guchar clip (double a); double deg2rad (double Deg); -guchar GetVIS (); guint GetBin (double Freq, guint FFTLen); void *Listen (); void saveCurrentPic(); diff --git a/slowrx.c b/slowrx.c index 977e1d5..d3ccfea 100644 --- a/slowrx.c +++ b/slowrx.c @@ -25,6 +25,7 @@ #include "pcm.h" #include "sync.h" #include "video.h" +#include "vis.h" // The thread that listens to VIS headers and calls decoders etc void *Listen() { diff --git a/vis.h b/vis.h new file mode 100644 index 0000000..8b076ab --- /dev/null +++ b/vis.h @@ -0,0 +1,6 @@ +#ifndef _VIS_H_ +#define _VIS_H_ + +guchar GetVIS (); + +#endif From 238d96e1e0366887adae688ad7bd2b46dc385691 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 14:10:49 +1000 Subject: [PATCH 021/166] common: Move GUI objects to the gui module --- common.c | 1 - common.h | 36 ------------------------------------ gui.c | 2 ++ gui.h | 36 ++++++++++++++++++++++++++++++++++++ gui_events.c | 3 +-- pcm.c | 1 + 6 files changed, 40 insertions(+), 39 deletions(-) diff --git a/common.c b/common.c index 2c7fbb4..87650e4 100644 --- a/common.c +++ b/common.c @@ -24,7 +24,6 @@ guchar *StoredLum = NULL; pthread_t thread1; FFTStuff fft; -GuiObjs gui; PicMeta CurrentPic; PcmData pcm; diff --git a/common.h b/common.h index fae7727..9eae995 100644 --- a/common.h +++ b/common.h @@ -23,42 +23,6 @@ struct _FFTStuff { }; extern FFTStuff fft; -typedef struct _GuiObjs GuiObjs; -struct _GuiObjs { - GtkWidget *button_abort; - GtkWidget *button_browse; - GtkWidget *button_clear; - GtkWidget *button_start; - GtkWidget *combo_card; - GtkWidget *combo_mode; - GtkWidget *entry_picdir; - GtkWidget *eventbox_img; - GtkWidget *frame_manual; - GtkWidget *frame_slant; - GtkWidget *grid_vu; - GtkWidget *iconview; - GtkWidget *image_devstatus; - GtkWidget *image_pwr; - GtkWidget *image_rx; - GtkWidget *image_snr; - GtkWidget *label_fskid; - GtkWidget *label_lastmode; - GtkWidget *label_utc; - GtkWidget *menuitem_about; - GtkWidget *menuitem_quit; - GtkWidget *spin_shift; - GtkWidget *statusbar; - GtkWidget *tog_adapt; - GtkWidget *tog_fsk; - GtkWidget *tog_rx; - GtkWidget *tog_save; - GtkWidget *tog_setedge; - GtkWidget *tog_slant; - GtkWidget *window_about; - GtkWidget *window_main; -}; -extern GuiObjs gui; - extern GdkPixbuf *pixbuf_PWR; extern GdkPixbuf *pixbuf_SNR; extern GdkPixbuf *pixbuf_rx; diff --git a/gui.c b/gui.c index b9222a5..ba13236 100644 --- a/gui.c +++ b/gui.c @@ -9,6 +9,8 @@ #include "gui.h" #include "gui_events.h" +GuiObjs gui; + void createGUI() { GtkBuilder *builder; diff --git a/gui.h b/gui.h index 74d7bfa..3163045 100644 --- a/gui.h +++ b/gui.h @@ -1,6 +1,42 @@ #ifndef _GUI_H_ #define _GUI_H_ +typedef struct _GuiObjs GuiObjs; +struct _GuiObjs { + GtkWidget *button_abort; + GtkWidget *button_browse; + GtkWidget *button_clear; + GtkWidget *button_start; + GtkWidget *combo_card; + GtkWidget *combo_mode; + GtkWidget *entry_picdir; + GtkWidget *eventbox_img; + GtkWidget *frame_manual; + GtkWidget *frame_slant; + GtkWidget *grid_vu; + GtkWidget *iconview; + GtkWidget *image_devstatus; + GtkWidget *image_pwr; + GtkWidget *image_rx; + GtkWidget *image_snr; + GtkWidget *label_fskid; + GtkWidget *label_lastmode; + GtkWidget *label_utc; + GtkWidget *menuitem_about; + GtkWidget *menuitem_quit; + GtkWidget *spin_shift; + GtkWidget *statusbar; + GtkWidget *tog_adapt; + GtkWidget *tog_fsk; + GtkWidget *tog_rx; + GtkWidget *tog_save; + GtkWidget *tog_setedge; + GtkWidget *tog_slant; + GtkWidget *window_about; + GtkWidget *window_main; +}; +extern GuiObjs gui; + void createGUI (); void setVU (double *Power, int FFTLen, int WinIdx, gboolean ShowWin); diff --git a/gui_events.c b/gui_events.c index 631c46b..4d0e7d3 100644 --- a/gui_events.c +++ b/gui_events.c @@ -1,8 +1,6 @@ #include #include #include -#include -#include #include #include @@ -10,6 +8,7 @@ #include #include "common.h" +#include "gui.h" #include "modespec.h" #include "pcm.h" diff --git a/pcm.c b/pcm.c index b600924..4ee6780 100644 --- a/pcm.c +++ b/pcm.c @@ -9,6 +9,7 @@ #include #include "common.h" +#include "gui.h" #include "pcm.h" /* From e6fbe4498caac9834472027e1bde0af6018f6df1 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 14:12:10 +1000 Subject: [PATCH 022/166] common: Move PcmData to pcm --- common.c | 1 - pcm.c | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common.c b/common.c index 87650e4..38d436a 100644 --- a/common.c +++ b/common.c @@ -25,7 +25,6 @@ pthread_t thread1; FFTStuff fft; PicMeta CurrentPic; -PcmData pcm; GdkPixbuf *pixbuf_rx = NULL; GdkPixbuf *pixbuf_disp = NULL; diff --git a/pcm.c b/pcm.c index 4ee6780..39c65c1 100644 --- a/pcm.c +++ b/pcm.c @@ -17,6 +17,8 @@ * */ +PcmData pcm; + // Capture fresh PCM data to buffer void readPcm(gint numsamples) { From 0254fd28fbef922ba5f934d854bae117f36488d1 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 14:17:39 +1000 Subject: [PATCH 023/166] common: Move FFT logic to separate fft module --- Makefile | 2 +- common.c | 1 - common.h | 9 --------- fft.c | 5 +++++ fft.h | 13 +++++++++++++ fsk.c | 1 + slowrx.c | 1 + video.c | 1 + vis.c | 1 + 9 files changed, 23 insertions(+), 11 deletions(-) create mode 100644 fft.c create mode 100644 fft.h diff --git a/Makefile b/Makefile index 6774e02..5f9adcc 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ GUI_BIN = slowrx TARGETS = $(GUI_BIN) -SOURCES = common.c gui_events.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c slowrx.c +SOURCES = common.c gui_events.c fft.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c slowrx.c OBJECTS = $(patsubst %.c,%.o,$(SOURCES)) DEPENDS = $(patsubst %.c,%.d,$(SOURCES)) diff --git a/common.c b/common.c index 38d436a..d7693e3 100644 --- a/common.c +++ b/common.c @@ -23,7 +23,6 @@ guchar *StoredLum = NULL; pthread_t thread1; -FFTStuff fft; PicMeta CurrentPic; GdkPixbuf *pixbuf_rx = NULL; diff --git a/common.h b/common.h index 9eae995..44bd22a 100644 --- a/common.h +++ b/common.h @@ -14,15 +14,6 @@ extern gboolean ManualResync; extern guchar *StoredLum; extern pthread_t thread1; -typedef struct _FFTStuff FFTStuff; -struct _FFTStuff { - double *in; - fftw_complex *out; - fftw_plan Plan1024; - fftw_plan Plan2048; -}; -extern FFTStuff fft; - extern GdkPixbuf *pixbuf_PWR; extern GdkPixbuf *pixbuf_SNR; extern GdkPixbuf *pixbuf_rx; diff --git a/fft.c b/fft.c new file mode 100644 index 0000000..8f1e1ad --- /dev/null +++ b/fft.c @@ -0,0 +1,5 @@ +#include + +#include "fft.h" + +FFTStuff fft; diff --git a/fft.h b/fft.h new file mode 100644 index 0000000..4e69738 --- /dev/null +++ b/fft.h @@ -0,0 +1,13 @@ +#ifndef _FFT_H_ +#define _FFT_H_ + +typedef struct _FFTStuff FFTStuff; +struct _FFTStuff { + double *in; + fftw_complex *out; + fftw_plan Plan1024; + fftw_plan Plan2048; +}; +extern FFTStuff fft; + +#endif diff --git a/fsk.c b/fsk.c index 6879d33..4c7c213 100644 --- a/fsk.c +++ b/fsk.c @@ -6,6 +6,7 @@ #include #include "common.h" +#include "fft.h" #include "fsk.h" #include "pcm.h" diff --git a/slowrx.c b/slowrx.c index d3ccfea..8c9d8bd 100644 --- a/slowrx.c +++ b/slowrx.c @@ -19,6 +19,7 @@ #include #include "common.h" +#include "fft.h" #include "fsk.h" #include "gui.h" #include "modespec.h" diff --git a/video.c b/video.c index 26872ba..5c585d3 100644 --- a/video.c +++ b/video.c @@ -8,6 +8,7 @@ #include #include "common.h" +#include "fft.h" #include "gui.h" #include "modespec.h" #include "pcm.h" diff --git a/vis.c b/vis.c index 9c091b8..6218807 100644 --- a/vis.c +++ b/vis.c @@ -6,6 +6,7 @@ #include #include "common.h" +#include "fft.h" #include "gui.h" #include "modespec.h" #include "pcm.h" From 29a1ebfff92cdfcd97cb721b1b7d3d1ffa6897a9 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 14:21:54 +1000 Subject: [PATCH 024/166] slowrx: Move listen thread code to listen module --- Makefile | 2 +- common.h | 1 - gui_events.c | 1 + listen.c | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++ listen.h | 6 ++ slowrx.c | 170 +-------------------------------------------- 6 files changed, 202 insertions(+), 171 deletions(-) create mode 100644 listen.c create mode 100644 listen.h diff --git a/Makefile b/Makefile index 5f9adcc..31d5395 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ GUI_BIN = slowrx TARGETS = $(GUI_BIN) -SOURCES = common.c gui_events.c fft.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c slowrx.c +SOURCES = common.c gui_events.c fft.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c listen.c slowrx.c OBJECTS = $(patsubst %.c,%.o,$(SOURCES)) DEPENDS = $(patsubst %.c,%.d,$(SOURCES)) diff --git a/common.h b/common.h index 44bd22a..ff98eda 100644 --- a/common.h +++ b/common.h @@ -55,7 +55,6 @@ double power (fftw_complex coeff); guchar clip (double a); double deg2rad (double Deg); guint GetBin (double Freq, guint FFTLen); -void *Listen (); void saveCurrentPic(); #endif diff --git a/gui_events.c b/gui_events.c index 4d0e7d3..1641c12 100644 --- a/gui_events.c +++ b/gui_events.c @@ -9,6 +9,7 @@ #include "common.h" #include "gui.h" +#include "listen.h" #include "modespec.h" #include "pcm.h" diff --git a/listen.c b/listen.c new file mode 100644 index 0000000..ad8a0c0 --- /dev/null +++ b/listen.c @@ -0,0 +1,193 @@ +/* + * slowrx - an SSTV decoder + * * * * * * * * * * * * * * + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include "common.h" +#include "fsk.h" +#include "gui.h" +#include "listen.h" +#include "modespec.h" +#include "pcm.h" +#include "sync.h" +#include "video.h" +#include "vis.h" + +// The thread that listens to VIS headers and calls decoders etc +void *Listen() { + + char rctime[8]; + + guchar Mode=0; + struct tm *timeptr = NULL; + time_t timet; + gboolean Finished; + char id[20]; + GtkTreeIter iter; + + while (TRUE) { + + gdk_threads_enter (); + gtk_widget_set_sensitive (gui.grid_vu, TRUE); + gtk_widget_set_sensitive (gui.button_abort, FALSE); + gtk_widget_set_sensitive (gui.button_clear, TRUE); + gdk_threads_leave (); + + pcm.WindowPtr = 0; + snd_pcm_prepare(pcm.handle); + snd_pcm_start (pcm.handle); + Abort = FALSE; + + do { + + // Wait for VIS + Mode = GetVIS(); + + // Stop listening on ALSA error + if (Abort) pthread_exit(NULL); + + // If manual resync was requested, redraw image + if (ManualResync) { + ManualResync = FALSE; + snd_pcm_drop(pcm.handle); + printf("getvideo at %.2f skip %d\n",CurrentPic.Rate,CurrentPic.Skip); + GetVideo(CurrentPic.Mode, CurrentPic.Rate, CurrentPic.Skip, TRUE); + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_save))) + saveCurrentPic(); + pcm.WindowPtr = 0; + snd_pcm_prepare(pcm.handle); + snd_pcm_start (pcm.handle); + } + + } while (Mode == 0); + + // Start reception + + CurrentPic.Rate = 44100; + CurrentPic.Mode = Mode; + + printf(" ==== %s ====\n", ModeSpec[CurrentPic.Mode].Name); + + // Store time of reception + timet = time(NULL); + timeptr = gmtime(&timet); + strftime(CurrentPic.timestr, sizeof(CurrentPic.timestr)-1,"%Y%m%d-%H%M%Sz", timeptr); + + + // Allocate space for cached Lum + free(StoredLum); + StoredLum = calloc( (int)((ModeSpec[CurrentPic.Mode].LineTime * ModeSpec[CurrentPic.Mode].NumLines + 1) * 44100), sizeof(guchar)); + if (StoredLum == NULL) { + perror("Listen: Unable to allocate memory for Lum"); + exit(EXIT_FAILURE); + } + + // Allocate space for sync signal + HasSync = calloc((int)(ModeSpec[CurrentPic.Mode].LineTime * ModeSpec[CurrentPic.Mode].NumLines / (13.0/44100) +1), sizeof(gboolean)); + if (HasSync == NULL) { + perror("Listen: Unable to allocate memory for sync signal"); + exit(EXIT_FAILURE); + } + + // Get video + strftime(rctime, sizeof(rctime)-1, "%H:%Mz", timeptr); + gdk_threads_enter (); + gtk_label_set_text (GTK_LABEL(gui.label_fskid), ""); + gtk_widget_set_sensitive (gui.frame_manual, FALSE); + gtk_widget_set_sensitive (gui.frame_slant, FALSE); + gtk_widget_set_sensitive (gui.combo_card, FALSE); + gtk_widget_set_sensitive (gui.button_abort, TRUE); + gtk_widget_set_sensitive (gui.button_clear, FALSE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gui.tog_setedge), FALSE); + gtk_statusbar_push (GTK_STATUSBAR(gui.statusbar), 0, "Receiving video..." ); + gtk_label_set_markup (GTK_LABEL(gui.label_lastmode), ModeSpec[CurrentPic.Mode].Name); + gtk_label_set_markup (GTK_LABEL(gui.label_utc), rctime); + gdk_threads_leave (); + printf(" getvideo @ %.1f Hz, Skip %d, HedrShift %+d Hz\n", 44100.0, 0, CurrentPic.HedrShift); + + Finished = GetVideo(CurrentPic.Mode, 44100, 0, FALSE); + + gdk_threads_enter (); + gtk_widget_set_sensitive (gui.button_abort, FALSE); + gdk_threads_leave (); + + id[0] = '\0'; + + if (Finished && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gui.tog_fsk))) { + gdk_threads_enter (); + gtk_statusbar_push (GTK_STATUSBAR(gui.statusbar), 0, "Receiving FSK ID..." ); + gdk_threads_leave (); + GetFSK(id); + printf(" FSKID \"%s\"\n",id); + gdk_threads_enter (); + gtk_label_set_text (GTK_LABEL(gui.label_fskid), id); + gdk_threads_leave (); + } + + snd_pcm_drop(pcm.handle); + + if (Finished && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gui.tog_slant))) { + + // Fix slant + //setVU(0,6); + gdk_threads_enter (); + gtk_statusbar_push (GTK_STATUSBAR(gui.statusbar), 0, "Calculating slant..." ); + gtk_widget_set_sensitive (gui.grid_vu, FALSE); + gdk_threads_leave (); + printf(" FindSync @ %.1f Hz\n",CurrentPic.Rate); + CurrentPic.Rate = FindSync(CurrentPic.Mode, CurrentPic.Rate, &CurrentPic.Skip); + + // Final image + printf(" getvideo @ %.1f Hz, Skip %d, HedrShift %+d Hz\n", CurrentPic.Rate, CurrentPic.Skip, CurrentPic.HedrShift); + GetVideo(CurrentPic.Mode, CurrentPic.Rate, CurrentPic.Skip, TRUE); + } + + free (HasSync); + HasSync = NULL; + + // Add thumbnail to iconview + CurrentPic.thumbbuf = gdk_pixbuf_scale_simple (pixbuf_rx, 100, + 100.0/ModeSpec[CurrentPic.Mode].ImgWidth * ModeSpec[CurrentPic.Mode].NumLines * ModeSpec[CurrentPic.Mode].LineHeight, GDK_INTERP_HYPER); + gdk_threads_enter (); + gtk_list_store_prepend (savedstore, &iter); + gtk_list_store_set (savedstore, &iter, 0, CurrentPic.thumbbuf, 1, id, -1); + gdk_threads_leave (); + + // Save PNG + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_save))) { + + //setVU(0,6); + + /*ensure_dir_exists("rx-lum"); + LumFile = fopen(lumfilename,"w"); + if (LumFile == NULL) + perror("Unable to open luma file for writing"); + fwrite(StoredLum,1,(ModeSpec[Mode].LineTime * ModeSpec[Mode].NumLines) * 44100,LumFile); + fclose(LumFile);*/ + + saveCurrentPic(); + } + + gdk_threads_enter (); + gtk_widget_set_sensitive (gui.frame_slant, TRUE); + gtk_widget_set_sensitive (gui.frame_manual, TRUE); + gtk_widget_set_sensitive (gui.combo_card, TRUE); + gdk_threads_leave (); + + } +} diff --git a/listen.h b/listen.h new file mode 100644 index 0000000..5aebf35 --- /dev/null +++ b/listen.h @@ -0,0 +1,6 @@ +#ifndef _LISTEN_H_ +#define _LISTEN_H_ + +void *Listen (); + +#endif diff --git a/slowrx.c b/slowrx.c index 8c9d8bd..413c584 100644 --- a/slowrx.c +++ b/slowrx.c @@ -20,177 +20,9 @@ #include "common.h" #include "fft.h" -#include "fsk.h" #include "gui.h" -#include "modespec.h" +#include "listen.h" #include "pcm.h" -#include "sync.h" -#include "video.h" -#include "vis.h" - -// The thread that listens to VIS headers and calls decoders etc -void *Listen() { - - char rctime[8]; - - guchar Mode=0; - struct tm *timeptr = NULL; - time_t timet; - gboolean Finished; - char id[20]; - GtkTreeIter iter; - - while (TRUE) { - - gdk_threads_enter (); - gtk_widget_set_sensitive (gui.grid_vu, TRUE); - gtk_widget_set_sensitive (gui.button_abort, FALSE); - gtk_widget_set_sensitive (gui.button_clear, TRUE); - gdk_threads_leave (); - - pcm.WindowPtr = 0; - snd_pcm_prepare(pcm.handle); - snd_pcm_start (pcm.handle); - Abort = FALSE; - - do { - - // Wait for VIS - Mode = GetVIS(); - - // Stop listening on ALSA error - if (Abort) pthread_exit(NULL); - - // If manual resync was requested, redraw image - if (ManualResync) { - ManualResync = FALSE; - snd_pcm_drop(pcm.handle); - printf("getvideo at %.2f skip %d\n",CurrentPic.Rate,CurrentPic.Skip); - GetVideo(CurrentPic.Mode, CurrentPic.Rate, CurrentPic.Skip, TRUE); - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_save))) - saveCurrentPic(); - pcm.WindowPtr = 0; - snd_pcm_prepare(pcm.handle); - snd_pcm_start (pcm.handle); - } - - } while (Mode == 0); - - // Start reception - - CurrentPic.Rate = 44100; - CurrentPic.Mode = Mode; - - printf(" ==== %s ====\n", ModeSpec[CurrentPic.Mode].Name); - - // Store time of reception - timet = time(NULL); - timeptr = gmtime(&timet); - strftime(CurrentPic.timestr, sizeof(CurrentPic.timestr)-1,"%Y%m%d-%H%M%Sz", timeptr); - - - // Allocate space for cached Lum - free(StoredLum); - StoredLum = calloc( (int)((ModeSpec[CurrentPic.Mode].LineTime * ModeSpec[CurrentPic.Mode].NumLines + 1) * 44100), sizeof(guchar)); - if (StoredLum == NULL) { - perror("Listen: Unable to allocate memory for Lum"); - exit(EXIT_FAILURE); - } - - // Allocate space for sync signal - HasSync = calloc((int)(ModeSpec[CurrentPic.Mode].LineTime * ModeSpec[CurrentPic.Mode].NumLines / (13.0/44100) +1), sizeof(gboolean)); - if (HasSync == NULL) { - perror("Listen: Unable to allocate memory for sync signal"); - exit(EXIT_FAILURE); - } - - // Get video - strftime(rctime, sizeof(rctime)-1, "%H:%Mz", timeptr); - gdk_threads_enter (); - gtk_label_set_text (GTK_LABEL(gui.label_fskid), ""); - gtk_widget_set_sensitive (gui.frame_manual, FALSE); - gtk_widget_set_sensitive (gui.frame_slant, FALSE); - gtk_widget_set_sensitive (gui.combo_card, FALSE); - gtk_widget_set_sensitive (gui.button_abort, TRUE); - gtk_widget_set_sensitive (gui.button_clear, FALSE); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gui.tog_setedge), FALSE); - gtk_statusbar_push (GTK_STATUSBAR(gui.statusbar), 0, "Receiving video..." ); - gtk_label_set_markup (GTK_LABEL(gui.label_lastmode), ModeSpec[CurrentPic.Mode].Name); - gtk_label_set_markup (GTK_LABEL(gui.label_utc), rctime); - gdk_threads_leave (); - printf(" getvideo @ %.1f Hz, Skip %d, HedrShift %+d Hz\n", 44100.0, 0, CurrentPic.HedrShift); - - Finished = GetVideo(CurrentPic.Mode, 44100, 0, FALSE); - - gdk_threads_enter (); - gtk_widget_set_sensitive (gui.button_abort, FALSE); - gdk_threads_leave (); - - id[0] = '\0'; - - if (Finished && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gui.tog_fsk))) { - gdk_threads_enter (); - gtk_statusbar_push (GTK_STATUSBAR(gui.statusbar), 0, "Receiving FSK ID..." ); - gdk_threads_leave (); - GetFSK(id); - printf(" FSKID \"%s\"\n",id); - gdk_threads_enter (); - gtk_label_set_text (GTK_LABEL(gui.label_fskid), id); - gdk_threads_leave (); - } - - snd_pcm_drop(pcm.handle); - - if (Finished && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gui.tog_slant))) { - - // Fix slant - //setVU(0,6); - gdk_threads_enter (); - gtk_statusbar_push (GTK_STATUSBAR(gui.statusbar), 0, "Calculating slant..." ); - gtk_widget_set_sensitive (gui.grid_vu, FALSE); - gdk_threads_leave (); - printf(" FindSync @ %.1f Hz\n",CurrentPic.Rate); - CurrentPic.Rate = FindSync(CurrentPic.Mode, CurrentPic.Rate, &CurrentPic.Skip); - - // Final image - printf(" getvideo @ %.1f Hz, Skip %d, HedrShift %+d Hz\n", CurrentPic.Rate, CurrentPic.Skip, CurrentPic.HedrShift); - GetVideo(CurrentPic.Mode, CurrentPic.Rate, CurrentPic.Skip, TRUE); - } - - free (HasSync); - HasSync = NULL; - - // Add thumbnail to iconview - CurrentPic.thumbbuf = gdk_pixbuf_scale_simple (pixbuf_rx, 100, - 100.0/ModeSpec[CurrentPic.Mode].ImgWidth * ModeSpec[CurrentPic.Mode].NumLines * ModeSpec[CurrentPic.Mode].LineHeight, GDK_INTERP_HYPER); - gdk_threads_enter (); - gtk_list_store_prepend (savedstore, &iter); - gtk_list_store_set (savedstore, &iter, 0, CurrentPic.thumbbuf, 1, id, -1); - gdk_threads_leave (); - - // Save PNG - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_save))) { - - //setVU(0,6); - - /*ensure_dir_exists("rx-lum"); - LumFile = fopen(lumfilename,"w"); - if (LumFile == NULL) - perror("Unable to open luma file for writing"); - fwrite(StoredLum,1,(ModeSpec[Mode].LineTime * ModeSpec[Mode].NumLines) * 44100,LumFile); - fclose(LumFile);*/ - - saveCurrentPic(); - } - - gdk_threads_enter (); - gtk_widget_set_sensitive (gui.frame_slant, TRUE); - gtk_widget_set_sensitive (gui.frame_manual, TRUE); - gtk_widget_set_sensitive (gui.combo_card, TRUE); - gdk_threads_leave (); - - } -} /* From 37982af71f9cc26436844012a54eaa3111bb99fd Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 14:30:26 +1000 Subject: [PATCH 025/166] common: Move more GUI stuff into the gui module --- common.c | 25 ------------------------- common.h | 10 ++-------- gui.c | 26 ++++++++++++++++++++++++++ gui.h | 8 ++++++++ 4 files changed, 36 insertions(+), 33 deletions(-) diff --git a/common.c b/common.c index d7693e3..cd4f6f8 100644 --- a/common.c +++ b/common.c @@ -25,13 +25,6 @@ pthread_t thread1; PicMeta CurrentPic; -GdkPixbuf *pixbuf_rx = NULL; -GdkPixbuf *pixbuf_disp = NULL; -GdkPixbuf *pixbuf_PWR = NULL; -GdkPixbuf *pixbuf_SNR = NULL; - -GtkListStore *savedstore = NULL; - GKeyFile *config = NULL; // Return the FFT bin index matching the given frequency @@ -72,21 +65,3 @@ void ensure_dir_exists(const char *dir) { } } } - -// Save current picture as PNG -void saveCurrentPic() { - GdkPixbuf *scaledpb; - GString *pngfilename; - - pngfilename = g_string_new(g_key_file_get_string(config,"slowrx","rxdir",NULL)); - g_string_append_printf(pngfilename, "/%s_%s.png", CurrentPic.timestr, ModeSpec[CurrentPic.Mode].ShortName); - printf(" Saving to %s\n", pngfilename->str); - - scaledpb = gdk_pixbuf_scale_simple (pixbuf_rx, ModeSpec[CurrentPic.Mode].ImgWidth, - ModeSpec[CurrentPic.Mode].NumLines * ModeSpec[CurrentPic.Mode].LineHeight, GDK_INTERP_HYPER); - - ensure_dir_exists(g_key_file_get_string(config,"slowrx","rxdir",NULL)); - gdk_pixbuf_savev(scaledpb, pngfilename->str, "png", NULL, NULL, NULL); - g_object_unref(scaledpb); - g_string_free(pngfilename, TRUE); -} diff --git a/common.h b/common.h index ff98eda..c7ee9c3 100644 --- a/common.h +++ b/common.h @@ -14,13 +14,6 @@ extern gboolean ManualResync; extern guchar *StoredLum; extern pthread_t thread1; -extern GdkPixbuf *pixbuf_PWR; -extern GdkPixbuf *pixbuf_SNR; -extern GdkPixbuf *pixbuf_rx; -extern GdkPixbuf *pixbuf_disp; - -extern GtkListStore *savedstore; - extern GKeyFile *config; @@ -55,6 +48,7 @@ double power (fftw_complex coeff); guchar clip (double a); double deg2rad (double Deg); guint GetBin (double Freq, guint FFTLen); -void saveCurrentPic(); + +void ensure_dir_exists(const char *dir); #endif diff --git a/gui.c b/gui.c index ba13236..a461d52 100644 --- a/gui.c +++ b/gui.c @@ -8,9 +8,17 @@ #include "common.h" #include "gui.h" #include "gui_events.h" +#include "modespec.h" GuiObjs gui; +GdkPixbuf *pixbuf_rx = NULL; +GdkPixbuf *pixbuf_disp = NULL; +GdkPixbuf *pixbuf_PWR = NULL; +GdkPixbuf *pixbuf_SNR = NULL; + +GtkListStore *savedstore = NULL; + void createGUI() { GtkBuilder *builder; @@ -157,6 +165,24 @@ void setVU (double *Power, int FFTLen, int WinIdx, gboolean ShowWin) { } +// Save current picture as PNG +void saveCurrentPic() { + GdkPixbuf *scaledpb; + GString *pngfilename; + + pngfilename = g_string_new(g_key_file_get_string(config,"slowrx","rxdir",NULL)); + g_string_append_printf(pngfilename, "/%s_%s.png", CurrentPic.timestr, ModeSpec[CurrentPic.Mode].ShortName); + printf(" Saving to %s\n", pngfilename->str); + + scaledpb = gdk_pixbuf_scale_simple (pixbuf_rx, ModeSpec[CurrentPic.Mode].ImgWidth, + ModeSpec[CurrentPic.Mode].NumLines * ModeSpec[CurrentPic.Mode].LineHeight, GDK_INTERP_HYPER); + + ensure_dir_exists(g_key_file_get_string(config,"slowrx","rxdir",NULL)); + gdk_pixbuf_savev(scaledpb, pngfilename->str, "png", NULL, NULL, NULL); + g_object_unref(scaledpb); + g_string_free(pngfilename, TRUE); +} + void evt_chooseDir() { GtkWidget *dialog; dialog = gtk_file_chooser_dialog_new ("Select folder", diff --git a/gui.h b/gui.h index 3163045..ec569f9 100644 --- a/gui.h +++ b/gui.h @@ -1,6 +1,13 @@ #ifndef _GUI_H_ #define _GUI_H_ +extern GdkPixbuf *pixbuf_PWR; +extern GdkPixbuf *pixbuf_SNR; +extern GdkPixbuf *pixbuf_rx; +extern GdkPixbuf *pixbuf_disp; + +extern GtkListStore *savedstore; + typedef struct _GuiObjs GuiObjs; struct _GuiObjs { GtkWidget *button_abort; @@ -39,6 +46,7 @@ extern GuiObjs gui; void createGUI (); void setVU (double *Power, int FFTLen, int WinIdx, gboolean ShowWin); +void saveCurrentPic(); void evt_chooseDir (); void evt_show_about (); From 83b4d7ccc6ab786ffc0d390700a5f6873a34272b Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 14:42:43 +1000 Subject: [PATCH 026/166] common: Move config logic to config module. This does not seem to be used anywhere, but I presume there's a future use case in mind for this, so we'll keep it for now. --- Makefile | 2 +- common.c | 2 -- config.c | 39 +++++++++++++++++++++++++++++++++++++++ config.h | 9 +++++++++ slowrx.c | 28 +++------------------------- 5 files changed, 52 insertions(+), 28 deletions(-) create mode 100644 config.c create mode 100644 config.h diff --git a/Makefile b/Makefile index 31d5395..f8a9beb 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ GUI_BIN = slowrx TARGETS = $(GUI_BIN) -SOURCES = common.c gui_events.c fft.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c listen.c slowrx.c +SOURCES = common.c gui_events.c fft.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c listen.c config.c slowrx.c OBJECTS = $(patsubst %.c,%.o,$(SOURCES)) DEPENDS = $(patsubst %.c,%.d,$(SOURCES)) diff --git a/common.c b/common.c index cd4f6f8..db0a3db 100644 --- a/common.c +++ b/common.c @@ -25,8 +25,6 @@ pthread_t thread1; PicMeta CurrentPic; -GKeyFile *config = NULL; - // Return the FFT bin index matching the given frequency guint GetBin (double Freq, guint FFTLen) { return (Freq / 44100 * FFTLen); diff --git a/config.c b/config.c new file mode 100644 index 0000000..7e5281b --- /dev/null +++ b/config.c @@ -0,0 +1,39 @@ +#include +#include "config.h" + +GKeyFile *config = NULL; + +void load_config_settings(GString **confpath) { + const gchar *confdir; + + // Load config + confdir = g_get_user_config_dir(); + *confpath = g_string_new(confdir); + g_string_append(*confpath, "/slowrx.ini"); + + config = g_key_file_new(); + if (g_key_file_load_from_file(config, (*confpath)->str, G_KEY_FILE_KEEP_COMMENTS, NULL)) { + + } else { + printf("No valid config file found\n"); + g_key_file_load_from_data(config, "[slowrx]\ndevice=default", -1, G_KEY_FILE_NONE, NULL); + } +} + +void save_config_settings(GString *confpath) { + FILE *ConfFile; + gchar *confdata; + gsize *keylen=NULL; + + // Save config on exit + ConfFile = fopen(confpath->str,"w"); + if (ConfFile == NULL) { + perror("Unable to open config file for writing"); + } else { + confdata = g_key_file_to_data(config,keylen,NULL); + fprintf(ConfFile,"%s",confdata); + fwrite(confdata,1,(size_t)keylen,ConfFile); + fclose(ConfFile); + } + +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..f7c7ea5 --- /dev/null +++ b/config.h @@ -0,0 +1,9 @@ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +extern GKeyFile *config; + +void load_config_settings(GString **confpath); +void save_config_settings(GString *confpath); + +#endif diff --git a/slowrx.c b/slowrx.c index 413c584..466a5f8 100644 --- a/slowrx.c +++ b/slowrx.c @@ -19,6 +19,7 @@ #include #include "common.h" +#include "config.h" #include "fft.h" #include "gui.h" #include "listen.h" @@ -30,29 +31,14 @@ */ int main(int argc, char *argv[]) { - - FILE *ConfFile; - const gchar *confdir; GString *confpath; - gchar *confdata; - gsize *keylen=NULL; gtk_init (&argc, &argv); gdk_threads_init (); // Load config - confdir = g_get_user_config_dir(); - confpath = g_string_new(confdir); - g_string_append(confpath, "/slowrx.ini"); - - config = g_key_file_new(); - if (g_key_file_load_from_file(config, confpath->str, G_KEY_FILE_KEEP_COMMENTS, NULL)) { - - } else { - printf("No valid config file found\n"); - g_key_file_load_from_data(config, "[slowrx]\ndevice=default", -1, G_KEY_FILE_NONE, NULL); - } + load_config_settings(&confpath); // Prepare FFT fft.in = fftw_alloc_real(2048); @@ -77,15 +63,7 @@ int main(int argc, char *argv[]) { gtk_main(); // Save config on exit - ConfFile = fopen(confpath->str,"w"); - if (ConfFile == NULL) { - perror("Unable to open config file for writing"); - } else { - confdata = g_key_file_to_data(config,keylen,NULL); - fprintf(ConfFile,"%s",confdata); - fwrite(confdata,1,(size_t)keylen,ConfFile); - fclose(ConfFile); - } + save_config_settings(confpath); g_object_unref(pixbuf_rx); free(StoredLum); From 69db7f780c043425e6acf30763f09ec6bb86bafa Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 14:46:01 +1000 Subject: [PATCH 027/166] slowrx: Move fft initialisation to fft module --- fft.c | 21 +++++++++++++++++++++ fft.h | 2 +- slowrx.c | 14 +------------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/fft.c b/fft.c index 8f1e1ad..393424d 100644 --- a/fft.c +++ b/fft.c @@ -1,5 +1,26 @@ +#include #include #include "fft.h" FFTStuff fft; + +int fft_init(void) { + fft.in = fftw_alloc_real(2048); + if (fft.in == NULL) { + perror("GetVideo: Unable to allocate memory for FFT"); + return -1; + } + fft.out = fftw_alloc_complex(2048); + if (fft.out == NULL) { + perror("GetVideo: Unable to allocate memory for FFT"); + fftw_free(fft.in); + return -1; + } + memset(fft.in, 0, sizeof(double) * 2048); + + fft.Plan1024 = fftw_plan_dft_r2c_1d(1024, fft.in, fft.out, FFTW_ESTIMATE); + fft.Plan2048 = fftw_plan_dft_r2c_1d(2048, fft.in, fft.out, FFTW_ESTIMATE); + + return 0; +} diff --git a/fft.h b/fft.h index 4e69738..0b384ef 100644 --- a/fft.h +++ b/fft.h @@ -1,6 +1,5 @@ #ifndef _FFT_H_ #define _FFT_H_ - typedef struct _FFTStuff FFTStuff; struct _FFTStuff { double *in; @@ -10,4 +9,5 @@ struct _FFTStuff { }; extern FFTStuff fft; +int fft_init(void); #endif diff --git a/slowrx.c b/slowrx.c index 466a5f8..073671c 100644 --- a/slowrx.c +++ b/slowrx.c @@ -41,21 +41,9 @@ int main(int argc, char *argv[]) { load_config_settings(&confpath); // Prepare FFT - fft.in = fftw_alloc_real(2048); - if (fft.in == NULL) { - perror("GetVideo: Unable to allocate memory for FFT"); + if (fft_init() < 0) { exit(EXIT_FAILURE); } - fft.out = fftw_alloc_complex(2048); - if (fft.out == NULL) { - perror("GetVideo: Unable to allocate memory for FFT"); - fftw_free(fft.in); - exit(EXIT_FAILURE); - } - memset(fft.in, 0, sizeof(double) * 2048); - - fft.Plan1024 = fftw_plan_dft_r2c_1d(1024, fft.in, fft.out, FFTW_ESTIMATE); - fft.Plan2048 = fftw_plan_dft_r2c_1d(2048, fft.in, fft.out, FFTW_ESTIMATE); createGUI(); populateDeviceList(); From 00becaa9ea286b16e7cc164009d717662b65da57 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 14:53:37 +1000 Subject: [PATCH 028/166] listen: Move start/stop listener thread logic --- common.h | 1 - gui_events.c | 4 ++-- listen.c | 10 ++++++++++ listen.h | 3 +++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/common.h b/common.h index c7ee9c3..90a07b7 100644 --- a/common.h +++ b/common.h @@ -12,7 +12,6 @@ extern gboolean *HasSync; extern gboolean ManualActivated; extern gboolean ManualResync; extern guchar *StoredLum; -extern pthread_t thread1; extern GKeyFile *config; diff --git a/gui_events.c b/gui_events.c index 1641c12..1f29227 100644 --- a/gui_events.c +++ b/gui_events.c @@ -47,7 +47,7 @@ void evt_changeDevices() { static int init; if (init) - pthread_join(thread1, NULL); + WaitForListenerStop(); init = 1; if (pcm.handle != NULL) snd_pcm_close(pcm.handle); @@ -72,7 +72,7 @@ void evt_changeDevices() { g_key_file_set_string(config,"slowrx","device",gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gui.combo_card))); - pthread_create (&thread1, NULL, Listen, NULL); + StartListener(); } diff --git a/listen.c b/listen.c index ad8a0c0..81af86a 100644 --- a/listen.c +++ b/listen.c @@ -28,6 +28,16 @@ #include "video.h" #include "vis.h" +static pthread_t listener_thread; + +void StartListener(void) { + pthread_create(&listener_thread, NULL, Listen, NULL); +} + +void WaitForListenerStop(void) { + pthread_join(listener_thread, NULL); +} + // The thread that listens to VIS headers and calls decoders etc void *Listen() { diff --git a/listen.h b/listen.h index 5aebf35..1cfbe70 100644 --- a/listen.h +++ b/listen.h @@ -3,4 +3,7 @@ void *Listen (); +void StartListener(void); +void WaitForListenerStop(void); + #endif From 3e632d1a02332627bedcc165ac5fd3520eaa9ff8 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 14:55:32 +1000 Subject: [PATCH 029/166] common: Move config definitions to config.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ahh… forgot to do this earlier! --- common.h | 2 -- gui.c | 1 + gui_events.c | 1 + pcm.c | 1 + 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common.h b/common.h index 90a07b7..5762446 100644 --- a/common.h +++ b/common.h @@ -13,8 +13,6 @@ extern gboolean ManualActivated; extern gboolean ManualResync; extern guchar *StoredLum; -extern GKeyFile *config; - typedef struct _PicMeta PicMeta; struct _PicMeta { diff --git a/gui.c b/gui.c index a461d52..6230629 100644 --- a/gui.c +++ b/gui.c @@ -6,6 +6,7 @@ #include #include "common.h" +#include "config.h" #include "gui.h" #include "gui_events.h" #include "modespec.h" diff --git a/gui_events.c b/gui_events.c index 1f29227..00a41c3 100644 --- a/gui_events.c +++ b/gui_events.c @@ -8,6 +8,7 @@ #include #include "common.h" +#include "config.h" #include "gui.h" #include "listen.h" #include "modespec.h" diff --git a/pcm.c b/pcm.c index 39c65c1..39aeae8 100644 --- a/pcm.c +++ b/pcm.c @@ -9,6 +9,7 @@ #include #include "common.h" +#include "config.h" #include "gui.h" #include "pcm.h" From 8c1bde353699993790c682200aacf3b57b1bd37d Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 15:00:14 +1000 Subject: [PATCH 030/166] gui: Merge in gui_events --- Makefile | 2 +- gui.c | 121 ++++++++++++++++++++++++++++++++++++++++++++- gui.h | 7 +++ gui_events.c | 137 --------------------------------------------------- gui_events.h | 12 ----- 5 files changed, 128 insertions(+), 151 deletions(-) delete mode 100644 gui_events.c delete mode 100644 gui_events.h diff --git a/Makefile b/Makefile index f8a9beb..72082b1 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ GUI_BIN = slowrx TARGETS = $(GUI_BIN) -SOURCES = common.c gui_events.c fft.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c listen.c config.c slowrx.c +SOURCES = common.c fft.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c listen.c config.c slowrx.c OBJECTS = $(patsubst %.c,%.o,$(SOURCES)) DEPENDS = $(patsubst %.c,%.d,$(SOURCES)) diff --git a/gui.c b/gui.c index 6230629..04e1e26 100644 --- a/gui.c +++ b/gui.c @@ -8,8 +8,9 @@ #include "common.h" #include "config.h" #include "gui.h" -#include "gui_events.h" +#include "listen.h" #include "modespec.h" +#include "pcm.h" GuiObjs gui; @@ -205,3 +206,121 @@ void evt_show_about() { gtk_dialog_run(GTK_DIALOG(gui.window_about)); gtk_widget_hide(gui.window_about); } + +// Quit +void evt_deletewindow() { + gtk_main_quit (); +} + +// Transform the NoiseAdapt toggle state into a variable +void evt_GetAdaptive() { + Adaptive = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_adapt)); +} + +// Manual Start clicked +void evt_ManualStart() { + ManualActivated = TRUE; +} + +// Abort clicked during rx +void evt_AbortRx() { + Abort = TRUE; +} + +// Another device selected from list +void evt_changeDevices() { + + int status; + + pcm.BufferDrop = FALSE; + Abort = TRUE; + + static int init; + if (init) + WaitForListenerStop(); + init = 1; + + if (pcm.handle != NULL) snd_pcm_close(pcm.handle); + + status = initPcmDevice(gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gui.combo_card))); + + + switch(status) { + case 0: + gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_YES,GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_widget_set_tooltip_text(gui.image_devstatus, "Device successfully opened"); + break; + case -1: + gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_DIALOG_WARNING,GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_widget_set_tooltip_text(gui.image_devstatus, "Device was opened, but doesn't support 44100 Hz"); + break; + case -2: + gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_DIALOG_ERROR,GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_widget_set_tooltip_text(gui.image_devstatus, "Failed to open device"); + break; + } + + g_key_file_set_string(config,"slowrx","device",gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gui.combo_card))); + + StartListener(); + +} + +// Clear received picture & metadata +void evt_clearPix() { + gdk_pixbuf_fill (pixbuf_disp, 0); + gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp); + gtk_label_set_markup (GTK_LABEL(gui.label_fskid), ""); + gtk_label_set_markup (GTK_LABEL(gui.label_utc), ""); + gtk_label_set_markup (GTK_LABEL(gui.label_lastmode), ""); +} + +// Manual slant adjust +void evt_clickimg(GtkWidget *widget, GdkEventButton* event, GdkWindowEdge edge) { + static double prevx=0,prevy=0,newrate; + static gboolean secondpress=FALSE; + double x,y,dx,dy,xic; + + (void)widget; + (void)edge; + + if (event->type == GDK_BUTTON_PRESS && event->button == 1 && gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_setedge))) { + + x = event->x * (ModeSpec[CurrentPic.Mode].ImgWidth / 500.0); + y = event->y * (ModeSpec[CurrentPic.Mode].ImgWidth / 500.0) / ModeSpec[CurrentPic.Mode].LineHeight; + + if (secondpress) { + secondpress=FALSE; + + dx = x - prevx; + dy = y - prevy; + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gui.tog_setedge),FALSE); + + // Adjust sample rate, if in sensible limits + newrate = CurrentPic.Rate + CurrentPic.Rate * (dx * ModeSpec[CurrentPic.Mode].PixelTime) / (dy * ModeSpec[CurrentPic.Mode].LineHeight * ModeSpec[CurrentPic.Mode].LineTime); + if (newrate > 32000 && newrate < 56000) { + CurrentPic.Rate = newrate; + + // Find x-intercept and adjust skip + xic = fmod( (x - (y / (dy/dx))), ModeSpec[CurrentPic.Mode].ImgWidth); + if (xic < 0) xic = ModeSpec[CurrentPic.Mode].ImgWidth - xic; + CurrentPic.Skip = fmod(CurrentPic.Skip + xic * ModeSpec[CurrentPic.Mode].PixelTime * CurrentPic.Rate, + ModeSpec[CurrentPic.Mode].LineTime * CurrentPic.Rate); + if (CurrentPic.Skip > ModeSpec[CurrentPic.Mode].LineTime * CurrentPic.Rate / 2.0) + CurrentPic.Skip -= ModeSpec[CurrentPic.Mode].LineTime * CurrentPic.Rate; + + // Signal the listener to exit from GetVIS() and re-process the pic + ManualResync = TRUE; + } + + } else { + secondpress = TRUE; + prevx = x; + prevy = y; + } + } else { + secondpress=FALSE; + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gui.tog_setedge), FALSE); + } +} diff --git a/gui.h b/gui.h index ec569f9..49ace1c 100644 --- a/gui.h +++ b/gui.h @@ -50,5 +50,12 @@ void saveCurrentPic(); void evt_chooseDir (); void evt_show_about (); +void evt_AbortRx (); +void evt_changeDevices (); +void evt_clearPix (); +void evt_clickimg (); +void evt_deletewindow (); +void evt_GetAdaptive (); +void evt_ManualStart (); #endif diff --git a/gui_events.c b/gui_events.c deleted file mode 100644 index 00a41c3..0000000 --- a/gui_events.c +++ /dev/null @@ -1,137 +0,0 @@ -#include -#include -#include - -#include -#include - -#include - -#include "common.h" -#include "config.h" -#include "gui.h" -#include "listen.h" -#include "modespec.h" -#include "pcm.h" - - -/*** Gtk+ event handlers ***/ - - -// Quit -void evt_deletewindow() { - gtk_main_quit (); -} - -// Transform the NoiseAdapt toggle state into a variable -void evt_GetAdaptive() { - Adaptive = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_adapt)); -} - -// Manual Start clicked -void evt_ManualStart() { - ManualActivated = TRUE; -} - -// Abort clicked during rx -void evt_AbortRx() { - Abort = TRUE; -} - -// Another device selected from list -void evt_changeDevices() { - - int status; - - pcm.BufferDrop = FALSE; - Abort = TRUE; - - static int init; - if (init) - WaitForListenerStop(); - init = 1; - - if (pcm.handle != NULL) snd_pcm_close(pcm.handle); - - status = initPcmDevice(gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gui.combo_card))); - - - switch(status) { - case 0: - gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_YES,GTK_ICON_SIZE_SMALL_TOOLBAR); - gtk_widget_set_tooltip_text(gui.image_devstatus, "Device successfully opened"); - break; - case -1: - gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_DIALOG_WARNING,GTK_ICON_SIZE_SMALL_TOOLBAR); - gtk_widget_set_tooltip_text(gui.image_devstatus, "Device was opened, but doesn't support 44100 Hz"); - break; - case -2: - gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_DIALOG_ERROR,GTK_ICON_SIZE_SMALL_TOOLBAR); - gtk_widget_set_tooltip_text(gui.image_devstatus, "Failed to open device"); - break; - } - - g_key_file_set_string(config,"slowrx","device",gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gui.combo_card))); - - StartListener(); - -} - -// Clear received picture & metadata -void evt_clearPix() { - gdk_pixbuf_fill (pixbuf_disp, 0); - gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp); - gtk_label_set_markup (GTK_LABEL(gui.label_fskid), ""); - gtk_label_set_markup (GTK_LABEL(gui.label_utc), ""); - gtk_label_set_markup (GTK_LABEL(gui.label_lastmode), ""); -} - -// Manual slant adjust -void evt_clickimg(GtkWidget *widget, GdkEventButton* event, GdkWindowEdge edge) { - static double prevx=0,prevy=0,newrate; - static gboolean secondpress=FALSE; - double x,y,dx,dy,xic; - - (void)widget; - (void)edge; - - if (event->type == GDK_BUTTON_PRESS && event->button == 1 && gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_setedge))) { - - x = event->x * (ModeSpec[CurrentPic.Mode].ImgWidth / 500.0); - y = event->y * (ModeSpec[CurrentPic.Mode].ImgWidth / 500.0) / ModeSpec[CurrentPic.Mode].LineHeight; - - if (secondpress) { - secondpress=FALSE; - - dx = x - prevx; - dy = y - prevy; - - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gui.tog_setedge),FALSE); - - // Adjust sample rate, if in sensible limits - newrate = CurrentPic.Rate + CurrentPic.Rate * (dx * ModeSpec[CurrentPic.Mode].PixelTime) / (dy * ModeSpec[CurrentPic.Mode].LineHeight * ModeSpec[CurrentPic.Mode].LineTime); - if (newrate > 32000 && newrate < 56000) { - CurrentPic.Rate = newrate; - - // Find x-intercept and adjust skip - xic = fmod( (x - (y / (dy/dx))), ModeSpec[CurrentPic.Mode].ImgWidth); - if (xic < 0) xic = ModeSpec[CurrentPic.Mode].ImgWidth - xic; - CurrentPic.Skip = fmod(CurrentPic.Skip + xic * ModeSpec[CurrentPic.Mode].PixelTime * CurrentPic.Rate, - ModeSpec[CurrentPic.Mode].LineTime * CurrentPic.Rate); - if (CurrentPic.Skip > ModeSpec[CurrentPic.Mode].LineTime * CurrentPic.Rate / 2.0) - CurrentPic.Skip -= ModeSpec[CurrentPic.Mode].LineTime * CurrentPic.Rate; - - // Signal the listener to exit from GetVIS() and re-process the pic - ManualResync = TRUE; - } - - } else { - secondpress = TRUE; - prevx = x; - prevy = y; - } - } else { - secondpress=FALSE; - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gui.tog_setedge), FALSE); - } -} diff --git a/gui_events.h b/gui_events.h deleted file mode 100644 index 8c4af98..0000000 --- a/gui_events.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef _GUI_EVENTS_H_ -#define _GUI_EVENTS_H_ - -void evt_AbortRx (); -void evt_changeDevices (); -void evt_clearPix (); -void evt_clickimg (); -void evt_deletewindow (); -void evt_GetAdaptive (); -void evt_ManualStart (); - -#endif From bb36762bb739886e69bcfcf9b6764d2ebd490ff4 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 15:08:22 +1000 Subject: [PATCH 031/166] gui: Drop unused ShowWin parameter from setVU --- gui.c | 2 +- gui.h | 2 +- video.c | 2 +- vis.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gui.c b/gui.c index 04e1e26..9ed75ca 100644 --- a/gui.c +++ b/gui.c @@ -98,7 +98,7 @@ void createGUI() { } // Draw signal level meters according to given values -void setVU (double *Power, int FFTLen, int WinIdx, gboolean ShowWin) { +void setVU (double *Power, int FFTLen, int WinIdx) { int x,y, W=100, H=30; guchar *pixelsPWR, *pixelsSNR, *pPWR, *pSNR; unsigned int rowstridePWR,rowstrideSNR, LoBin, HiBin, i; diff --git a/gui.h b/gui.h index 49ace1c..5ee004c 100644 --- a/gui.h +++ b/gui.h @@ -45,7 +45,7 @@ struct _GuiObjs { extern GuiObjs gui; void createGUI (); -void setVU (double *Power, int FFTLen, int WinIdx, gboolean ShowWin); +void setVU (double *Power, int FFTLen, int WinIdx); void saveCurrentPic(); void evt_chooseDir (); diff --git a/video.c b/video.c index 5c585d3..f526182 100644 --- a/video.c +++ b/video.c @@ -478,7 +478,7 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { } /* endif (SampleNum == PixelGrid[PixelIdx].Time) */ if (!Redraw && SampleNum % 8820 == 0) { - setVU(Power, FFTLen, WinIdx, TRUE); + setVU(Power, FFTLen, WinIdx); } if (Abort) { diff --git a/vis.c b/vis.c index 6218807..e95e4bd 100644 --- a/vis.c +++ b/vis.c @@ -175,7 +175,7 @@ guchar GetVIS () { } if (++ptr == 10) { - setVU(Power, 2048, 6, FALSE); + setVU(Power, 2048, 6); ptr = 0; } From b28e69b2f71b519b78e537fd179ffd57cdfb5360 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 15:12:25 +1000 Subject: [PATCH 032/166] video: Drop unused variables Comment them out for now, since there's a TODO along-side one of them. --- video.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/video.c b/video.c index f526182..1df0495 100644 --- a/video.c +++ b/video.c @@ -33,7 +33,8 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { int SampleNum, Length, NumChans; int x = 0, y = 0, tx=0, k=0; double Hann[7][1024] = {{0}}; - double Freq = 0, PrevFreq = 0, InterpFreq = 0; + double Freq = 0; + //double PrevFreq = 0, InterpFreq = 0; int NextSNRtime = 0, NextSyncTime = 0; double Praw, Psync; double Power[1024] = {0}; @@ -354,7 +355,7 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { if (SampleNum % 6 == 0) { // Take FFT every 6 samples - PrevFreq = Freq; + //PrevFreq = Freq; // Adapt window size to SNR From a531fb6dc885925681ee3772bc0d6429b923ceed Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 15:27:16 +1000 Subject: [PATCH 033/166] video: Silence warning from compiler. `SyncTargetBin` and `i` are both `guint`s, which is an unsigned integer type (Glib-flavoured). Not sure how big a `guint` can get, but if `i` ever becomes bigger than `SyncTargetBin`, you're going to get it wrapping around that maximum value. So cast it to a signed type. `gint` seems the logical choice here. --- video.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/video.c b/video.c index 1df0495..4c8db25 100644 --- a/video.c +++ b/video.c @@ -289,7 +289,7 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { Praw += power(fft.out[i]); for (i=SyncTargetBin-1; i<=SyncTargetBin+1; i++) - Psync += power(fft.out[i]) * (1- .5*abs(SyncTargetBin-i)); + Psync += power(fft.out[i]) * (1- .5*abs((gint)(SyncTargetBin-i))); Praw /= (GetBin(2300+CurrentPic.HedrShift, FFTLen) - GetBin(1500+CurrentPic.HedrShift, FFTLen)); Psync /= 2.0; From a7b2bb847a89f319d5ede02d8062de4b75e388a1 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 15:45:36 +1000 Subject: [PATCH 034/166] pcm: Move PCM dropped samples warning to GUI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The purpose is to display a warning icon… really this is a GUI function. --- gui.c | 7 +++++++ gui.h | 1 + pcm.c | 5 +---- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/gui.c b/gui.c index 9ed75ca..e5e6d53 100644 --- a/gui.c +++ b/gui.c @@ -97,6 +97,13 @@ void createGUI() { } +void showPCMDropWarning(void) { + gdk_threads_enter(); + gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_DIALOG_WARNING,GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_widget_set_tooltip_text(gui.image_devstatus, "Device is dropping samples"); + gdk_threads_leave(); +} + // Draw signal level meters according to given values void setVU (double *Power, int FFTLen, int WinIdx) { int x,y, W=100, H=30; diff --git a/gui.h b/gui.h index 5ee004c..b15ab59 100644 --- a/gui.h +++ b/gui.h @@ -46,6 +46,7 @@ extern GuiObjs gui; void createGUI (); void setVU (double *Power, int FFTLen, int WinIdx); +void showPCMDropWarning(void); void saveCurrentPic(); void evt_chooseDir (); diff --git a/pcm.c b/pcm.c index 39aeae8..3b9bd28 100644 --- a/pcm.c +++ b/pcm.c @@ -43,10 +43,7 @@ void readPcm(gint numsamples) { // On first appearance of error, update the status icon if (!pcm.BufferDrop) { - gdk_threads_enter(); - gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_DIALOG_WARNING,GTK_ICON_SIZE_SMALL_TOOLBAR); - gtk_widget_set_tooltip_text(gui.image_devstatus, "Device is dropping samples"); - gdk_threads_leave(); + showPCMDropWarning(); pcm.BufferDrop = TRUE; } From 4f4b832167415908dd7bcad7e3be1688f5ea7c9f Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 15:48:53 +1000 Subject: [PATCH 035/166] pcm: Move device list population to the GUI This too, is a GUI function, not a PCM function. --- gui.c | 36 ++++++++++++++++++++++++++++++++++++ gui.h | 1 + pcm.c | 34 ---------------------------------- pcm.h | 1 - 4 files changed, 37 insertions(+), 35 deletions(-) diff --git a/gui.c b/gui.c index e5e6d53..dec3849 100644 --- a/gui.c +++ b/gui.c @@ -192,6 +192,42 @@ void saveCurrentPic() { g_string_free(pngfilename, TRUE); } +// Populate the GUI device list box with the available PCM devices. +void populateDeviceList() { + int card; + char *cardname; + int numcards, row; + + + gdk_threads_enter(); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gui.combo_card), "default"); + gdk_threads_leave(); + + numcards = 0; + card = -1; + row = 0; + do { + snd_card_next(&card); + if (card != -1) { + row++; + snd_card_get_name(card,&cardname); + gdk_threads_enter(); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gui.combo_card), cardname); + char *dev = g_key_file_get_string(config,"slowrx","device",NULL); + if (dev == NULL || strcmp(cardname, dev) == 0) + gtk_combo_box_set_active(GTK_COMBO_BOX(gui.combo_card), row); + + gdk_threads_leave(); + numcards++; + } + } while (card != -1); + + if (numcards == 0) { + perror("No sound devices found!\n"); + exit(EXIT_FAILURE); + } +} + void evt_chooseDir() { GtkWidget *dialog; dialog = gtk_file_chooser_dialog_new ("Select folder", diff --git a/gui.h b/gui.h index b15ab59..6f519ef 100644 --- a/gui.h +++ b/gui.h @@ -47,6 +47,7 @@ extern GuiObjs gui; void createGUI (); void setVU (double *Power, int FFTLen, int WinIdx); void showPCMDropWarning(void); +void populateDeviceList (); void saveCurrentPic(); void evt_chooseDir (); diff --git a/pcm.c b/pcm.c index 3b9bd28..7ff52c5 100644 --- a/pcm.c +++ b/pcm.c @@ -65,40 +65,6 @@ void readPcm(gint numsamples) { } -void populateDeviceList() { - int card; - char *cardname; - int numcards, row; - - gdk_threads_enter(); - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gui.combo_card), "default"); - gdk_threads_leave(); - - numcards = 0; - card = -1; - row = 0; - do { - snd_card_next(&card); - if (card != -1) { - row++; - snd_card_get_name(card,&cardname); - gdk_threads_enter(); - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gui.combo_card), cardname); - char *dev = g_key_file_get_string(config,"slowrx","device",NULL); - if (dev == NULL || strcmp(cardname, dev) == 0) - gtk_combo_box_set_active(GTK_COMBO_BOX(gui.combo_card), row); - - gdk_threads_leave(); - numcards++; - } - } while (card != -1); - - if (numcards == 0) { - perror("No sound devices found!\n"); - exit(EXIT_FAILURE); - } - -} // Initialize sound card // Return value: diff --git a/pcm.h b/pcm.h index 5fa98f5..c0a8090 100644 --- a/pcm.h +++ b/pcm.h @@ -11,7 +11,6 @@ struct _PcmData { extern PcmData pcm; int initPcmDevice (); -void populateDeviceList (); void readPcm (gint numsamples); #endif From 3b71c6b4a502c023701b1f03387b220ef3501f2a Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 15:57:50 +1000 Subject: [PATCH 036/166] pcm: Move PCM error GUI logic to GUI module. --- gui.c | 4 ++++ gui.h | 1 + pcm.c | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/gui.c b/gui.c index dec3849..9758765 100644 --- a/gui.c +++ b/gui.c @@ -97,6 +97,10 @@ void createGUI() { } +void showPCMError(const char *error) { + gtk_widget_set_tooltip_text(gui.image_devstatus, error); +} + void showPCMDropWarning(void) { gdk_threads_enter(); gtk_image_set_from_stock(GTK_IMAGE(gui.image_devstatus),GTK_STOCK_DIALOG_WARNING,GTK_ICON_SIZE_SMALL_TOOLBAR); diff --git a/gui.h b/gui.h index 6f519ef..2c884da 100644 --- a/gui.h +++ b/gui.h @@ -46,6 +46,7 @@ extern GuiObjs gui; void createGUI (); void setVU (double *Power, int FFTLen, int WinIdx); +void showPCMError (const char *error); void showPCMDropWarning(void); void populateDeviceList (); void saveCurrentPic(); diff --git a/pcm.c b/pcm.c index 7ff52c5..946d304 100644 --- a/pcm.c +++ b/pcm.c @@ -34,7 +34,7 @@ void readPcm(gint numsamples) { printf("ALSA: buffer overrun\n"); else if (samplesread < 0) { printf("ALSA error %d (%s)\n", samplesread, snd_strerror(samplesread)); - gtk_widget_set_tooltip_text(gui.image_devstatus, "ALSA error"); + showPCMError("ALSA error"); Abort = TRUE; pthread_exit(NULL); } From 7dd966e9abcc9fa0ea90bd46cba99753ab60c235 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 16:05:42 +1000 Subject: [PATCH 037/166] pcm: Replace direct calls into GUI calls with function pointers. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a poor man's approach to signals… we provide a function pointer for each of these events, and a single "listener" can subscribe by assigning itself to the function pointer. Not great, but that at least allows us to decouple this module from the GUI. --- pcm.c | 9 ++++++--- pcm.h | 2 ++ slowrx.c | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pcm.c b/pcm.c index 946d304..bfe95d7 100644 --- a/pcm.c +++ b/pcm.c @@ -10,7 +10,6 @@ #include "common.h" #include "config.h" -#include "gui.h" #include "pcm.h" /* @@ -34,7 +33,9 @@ void readPcm(gint numsamples) { printf("ALSA: buffer overrun\n"); else if (samplesread < 0) { printf("ALSA error %d (%s)\n", samplesread, snd_strerror(samplesread)); - showPCMError("ALSA error"); + if (pcm.OnPCMAbort) { + pcm.OnPCMAbort("ALSA Error"); + } Abort = TRUE; pthread_exit(NULL); } @@ -43,7 +44,9 @@ void readPcm(gint numsamples) { // On first appearance of error, update the status icon if (!pcm.BufferDrop) { - showPCMDropWarning(); + if (pcm.OnPCMDrop) { + pcm.OnPCMDrop(); + } pcm.BufferDrop = TRUE; } diff --git a/pcm.h b/pcm.h index c0a8090..e8f5b5a 100644 --- a/pcm.h +++ b/pcm.h @@ -5,6 +5,8 @@ typedef struct _PcmData PcmData; struct _PcmData { snd_pcm_t *handle; gint16 *Buffer; + void (*OnPCMAbort)(const char* reason); + void (*OnPCMDrop)(void); int WindowPtr; gboolean BufferDrop; }; diff --git a/slowrx.c b/slowrx.c index 073671c..38aaf70 100644 --- a/slowrx.c +++ b/slowrx.c @@ -46,6 +46,8 @@ int main(int argc, char *argv[]) { } createGUI(); + pcm.OnPCMAbort = showPCMError; + pcm.OnPCMDrop = showPCMDropWarning; populateDeviceList(); gtk_main(); From bd397f4d14fbd2f71f71420083c7c357db298d73 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 16:10:45 +1000 Subject: [PATCH 038/166] gui: Add routine for setting the status bar text. --- gui.c | 6 ++++++ gui.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/gui.c b/gui.c index 9758765..e4055cc 100644 --- a/gui.c +++ b/gui.c @@ -97,6 +97,12 @@ void createGUI() { } +void showStatusbarMessage(const char* message) { + gdk_threads_enter(); + gtk_statusbar_push( GTK_STATUSBAR(gui.statusbar), 0, message ); + gdk_threads_leave(); +} + void showPCMError(const char *error) { gtk_widget_set_tooltip_text(gui.image_devstatus, error); } diff --git a/gui.h b/gui.h index 2c884da..9b744a1 100644 --- a/gui.h +++ b/gui.h @@ -51,6 +51,8 @@ void showPCMDropWarning(void); void populateDeviceList (); void saveCurrentPic(); +void showStatusbarMessage(const char* message); + void evt_chooseDir (); void evt_show_about (); void evt_AbortRx (); From c1804cadbe778e18102f651359c23eea96eddc4b Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 16:18:23 +1000 Subject: [PATCH 039/166] common: Define typedefs for these event definitions --- common.h | 3 +++ pcm.h | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/common.h b/common.h index 5762446..e7c24a5 100644 --- a/common.h +++ b/common.h @@ -13,6 +13,9 @@ extern gboolean ManualActivated; extern gboolean ManualResync; extern guchar *StoredLum; +// Various callback function types for various events. +typedef void (*EventCallback)(void); +typedef void (*TextStatusCallback)(const char* status); typedef struct _PicMeta PicMeta; struct _PicMeta { diff --git a/pcm.h b/pcm.h index e8f5b5a..384ba3a 100644 --- a/pcm.h +++ b/pcm.h @@ -5,8 +5,8 @@ typedef struct _PcmData PcmData; struct _PcmData { snd_pcm_t *handle; gint16 *Buffer; - void (*OnPCMAbort)(const char* reason); - void (*OnPCMDrop)(void); + TextStatusCallback OnPCMAbort; + EventCallback OnPCMDrop; int WindowPtr; gboolean BufferDrop; }; From f13325d6ea0cf821778cf5e20b05c95704cb64fa Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 16:24:23 +1000 Subject: [PATCH 040/166] listen, vis: Replace direct access to status bar with callbacks. --- listen.c | 15 ++++++++++----- listen.h | 2 ++ slowrx.c | 3 +++ vis.c | 8 +++++--- vis.h | 2 ++ 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/listen.c b/listen.c index 81af86a..13ef032 100644 --- a/listen.c +++ b/listen.c @@ -29,6 +29,7 @@ #include "vis.h" static pthread_t listener_thread; +TextStatusCallback OnListenerStatusChange; void StartListener(void) { pthread_create(&listener_thread, NULL, Listen, NULL); @@ -116,6 +117,9 @@ void *Listen() { // Get video strftime(rctime, sizeof(rctime)-1, "%H:%Mz", timeptr); + if (OnListenerStatusChange) { + OnListenerStatusChange("Receiving video..."); + } gdk_threads_enter (); gtk_label_set_text (GTK_LABEL(gui.label_fskid), ""); gtk_widget_set_sensitive (gui.frame_manual, FALSE); @@ -124,7 +128,6 @@ void *Listen() { gtk_widget_set_sensitive (gui.button_abort, TRUE); gtk_widget_set_sensitive (gui.button_clear, FALSE); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gui.tog_setedge), FALSE); - gtk_statusbar_push (GTK_STATUSBAR(gui.statusbar), 0, "Receiving video..." ); gtk_label_set_markup (GTK_LABEL(gui.label_lastmode), ModeSpec[CurrentPic.Mode].Name); gtk_label_set_markup (GTK_LABEL(gui.label_utc), rctime); gdk_threads_leave (); @@ -139,9 +142,9 @@ void *Listen() { id[0] = '\0'; if (Finished && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gui.tog_fsk))) { - gdk_threads_enter (); - gtk_statusbar_push (GTK_STATUSBAR(gui.statusbar), 0, "Receiving FSK ID..." ); - gdk_threads_leave (); + if (OnListenerStatusChange) { + OnListenerStatusChange("Receiving FSK ID..."); + } GetFSK(id); printf(" FSKID \"%s\"\n",id); gdk_threads_enter (); @@ -155,8 +158,10 @@ void *Listen() { // Fix slant //setVU(0,6); + if (OnListenerStatusChange) { + OnListenerStatusChange("Calculating slant..."); + } gdk_threads_enter (); - gtk_statusbar_push (GTK_STATUSBAR(gui.statusbar), 0, "Calculating slant..." ); gtk_widget_set_sensitive (gui.grid_vu, FALSE); gdk_threads_leave (); printf(" FindSync @ %.1f Hz\n",CurrentPic.Rate); diff --git a/listen.h b/listen.h index 1cfbe70..6e5baa1 100644 --- a/listen.h +++ b/listen.h @@ -1,6 +1,8 @@ #ifndef _LISTEN_H_ #define _LISTEN_H_ +extern TextStatusCallback OnListenerStatusChange; + void *Listen (); void StartListener(void); diff --git a/slowrx.c b/slowrx.c index 38aaf70..b107904 100644 --- a/slowrx.c +++ b/slowrx.c @@ -24,6 +24,7 @@ #include "gui.h" #include "listen.h" #include "pcm.h" +#include "vis.h" /* @@ -46,6 +47,8 @@ int main(int argc, char *argv[]) { } createGUI(); + OnListenerStatusChange = showStatusbarMessage; + OnVisStatusChange = showStatusbarMessage; pcm.OnPCMAbort = showPCMError; pcm.OnPCMDrop = showPCMDropWarning; populateDeviceList(); diff --git a/vis.c b/vis.c index e95e4bd..0d59a80 100644 --- a/vis.c +++ b/vis.c @@ -27,6 +27,8 @@ static void onGotVis(guchar VIS) { * */ +TextStatusCallback OnVisStatusChange; + guchar GetVIS () { int selmode, ptr=0; @@ -45,9 +47,9 @@ guchar GetVIS () { printf("Waiting for header\n"); - gdk_threads_enter(); - gtk_statusbar_push( GTK_STATUSBAR(gui.statusbar), 0, "Listening" ); - gdk_threads_leave(); + if (OnVisStatusChange) { + OnVisStatusChange("Listening"); + } while ( TRUE ) { diff --git a/vis.h b/vis.h index 8b076ab..70499fa 100644 --- a/vis.h +++ b/vis.h @@ -1,6 +1,8 @@ #ifndef _VIS_H_ #define _VIS_H_ +extern TextStatusCallback OnVisStatusChange; + guchar GetVIS (); #endif From b2d54a9ae6bcbfb48bcbbae3c690a4ecf7438277 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 16:38:17 +1000 Subject: [PATCH 041/166] common: Move CurrentPic to new module Need to figure out how to replace the `GtkPixbuf` with something not GUI-centric. --- Makefile | 2 +- common.c | 2 -- common.h | 11 ----------- fsk.c | 1 + gui.c | 1 + listen.c | 1 + pic.c | 3 +++ pic.h | 20 ++++++++++++++++++++ video.c | 1 + vis.c | 1 + 10 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 pic.c create mode 100644 pic.h diff --git a/Makefile b/Makefile index 72082b1..d9b182a 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ GUI_BIN = slowrx TARGETS = $(GUI_BIN) -SOURCES = common.c fft.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c listen.c config.c slowrx.c +SOURCES = common.c pic.c fft.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c listen.c config.c slowrx.c OBJECTS = $(patsubst %.c,%.o,$(SOURCES)) DEPENDS = $(patsubst %.c,%.d,$(SOURCES)) diff --git a/common.c b/common.c index db0a3db..8020e9c 100644 --- a/common.c +++ b/common.c @@ -23,8 +23,6 @@ guchar *StoredLum = NULL; pthread_t thread1; -PicMeta CurrentPic; - // Return the FFT bin index matching the given frequency guint GetBin (double Freq, guint FFTLen) { return (Freq / 44100 * FFTLen); diff --git a/common.h b/common.h index e7c24a5..df3b36d 100644 --- a/common.h +++ b/common.h @@ -17,17 +17,6 @@ extern guchar *StoredLum; typedef void (*EventCallback)(void); typedef void (*TextStatusCallback)(const char* status); -typedef struct _PicMeta PicMeta; -struct _PicMeta { - gshort HedrShift; - guchar Mode; - double Rate; - int Skip; - GdkPixbuf *thumbbuf; - char timestr[40]; -}; -extern PicMeta CurrentPic; - // SSTV modes enum { UNKNOWN=0, diff --git a/fsk.c b/fsk.c index 4c7c213..1709014 100644 --- a/fsk.c +++ b/fsk.c @@ -9,6 +9,7 @@ #include "fft.h" #include "fsk.h" #include "pcm.h" +#include "pic.h" /* * diff --git a/gui.c b/gui.c index e4055cc..fc25665 100644 --- a/gui.c +++ b/gui.c @@ -11,6 +11,7 @@ #include "listen.h" #include "modespec.h" #include "pcm.h" +#include "pic.h" GuiObjs gui; diff --git a/listen.c b/listen.c index 13ef032..25c53b3 100644 --- a/listen.c +++ b/listen.c @@ -24,6 +24,7 @@ #include "listen.h" #include "modespec.h" #include "pcm.h" +#include "pic.h" #include "sync.h" #include "video.h" #include "vis.h" diff --git a/pic.c b/pic.c new file mode 100644 index 0000000..69ffd5a --- /dev/null +++ b/pic.c @@ -0,0 +1,3 @@ +#include "pic.h" + +PicMeta CurrentPic; diff --git a/pic.h b/pic.h new file mode 100644 index 0000000..48f897b --- /dev/null +++ b/pic.h @@ -0,0 +1,20 @@ +#ifndef _PIC_H_ +#define _PIC_H_ + +#include + +#include +#include + +typedef struct _PicMeta PicMeta; +struct _PicMeta { + gshort HedrShift; + guchar Mode; + double Rate; + int Skip; + GdkPixbuf *thumbbuf; + char timestr[40]; +}; +extern PicMeta CurrentPic; + +#endif diff --git a/video.c b/video.c index 4c8db25..a270917 100644 --- a/video.c +++ b/video.c @@ -12,6 +12,7 @@ #include "gui.h" #include "modespec.h" #include "pcm.h" +#include "pic.h" #include "video.h" /* Demodulate the video signal & store all kinds of stuff for later stages diff --git a/vis.c b/vis.c index 0d59a80..e8701f5 100644 --- a/vis.c +++ b/vis.c @@ -10,6 +10,7 @@ #include "gui.h" #include "modespec.h" #include "pcm.h" +#include "pic.h" /* Handle selection of the VIS mode in the GUI */ static void onGotVis(guchar VIS) { From b8fef15df44cf98f990d7dde2ab1dcf578f8060a Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 16:39:32 +1000 Subject: [PATCH 042/166] pcm: Replace GTK+ import with specific GLIB imports --- pcm.c | 2 -- pcm.h | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pcm.c b/pcm.c index bfe95d7..ac56945 100644 --- a/pcm.c +++ b/pcm.c @@ -2,8 +2,6 @@ #include #include -#include - #include #include diff --git a/pcm.h b/pcm.h index 384ba3a..842bca3 100644 --- a/pcm.h +++ b/pcm.h @@ -1,6 +1,9 @@ #ifndef _PCM_H #define _PCM_H +#include +#include + typedef struct _PcmData PcmData; struct _PcmData { snd_pcm_t *handle; From 01167043f94825a1d7dc1c9363fea2c810dfbc78 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 16:40:23 +1000 Subject: [PATCH 043/166] common: Import the minimal headers needed - Glib headers for the glib data types (gboolean, etc) - FFTW headers for the `power` function that uses `fftw_complex`. --- common.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common.h b/common.h index df3b36d..87ac460 100644 --- a/common.h +++ b/common.h @@ -6,6 +6,11 @@ #define BUFLEN 4096 #define SYNCPIXLEN 1.5e-3 +#include +#include + +#include + extern gboolean Abort; extern gboolean Adaptive; extern gboolean *HasSync; From 933859ac589f233aef33264899cec4a53a185325 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 16:42:47 +1000 Subject: [PATCH 044/166] config: Replace GTK+ include with GLIB --- config.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config.c b/config.c index 7e5281b..80ad962 100644 --- a/config.c +++ b/config.c @@ -1,4 +1,5 @@ -#include +#include +#include #include "config.h" GKeyFile *config = NULL; From 42279e5c8935a185f9c94b774b8f90bf7cbdeb91 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 16:43:47 +1000 Subject: [PATCH 045/166] fsk: Replace GTK includes with GLIB --- fsk.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fsk.c b/fsk.c index 1709014..a481f5b 100644 --- a/fsk.c +++ b/fsk.c @@ -1,6 +1,7 @@ #include #include -#include +#include +#include #include #include From 155aa6f6802fdd9abc9b444d06aa0f452d64fc17 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 16:48:34 +1000 Subject: [PATCH 046/166] modespec: Clean up includes --- modespec.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/modespec.c b/modespec.c index a2f6e6c..d7f2517 100644 --- a/modespec.c +++ b/modespec.c @@ -1,10 +1,3 @@ -#include -#include -#include -#include - -#include - #include "common.h" #include "modespec.h" From 51d268c5fd591996d05c15a511c08e283506f55f Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 16:50:11 +1000 Subject: [PATCH 047/166] common.h: Move mode enumerations to modespec --- common.h | 16 ---------------- modespec.c | 1 - modespec.h | 23 +++++++++++++++++++---- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/common.h b/common.h index 87ac460..0d5e464 100644 --- a/common.h +++ b/common.h @@ -22,22 +22,6 @@ extern guchar *StoredLum; typedef void (*EventCallback)(void); typedef void (*TextStatusCallback)(const char* status); -// SSTV modes -enum { - UNKNOWN=0, - M1, M2, M3, M4, - S1, S2, SDX, - R72, R36, R24, R24BW, R12BW, R8BW, - PD50, PD90, PD120, PD160, PD180, PD240, PD290, - P3, P5, P7, - W260, W2120, W2180 -}; - -// Color encodings -enum { - GBR, RGB, YUV, BW -}; - double power (fftw_complex coeff); guchar clip (double a); double deg2rad (double Deg); diff --git a/modespec.c b/modespec.c index d7f2517..95a1952 100644 --- a/modespec.c +++ b/modespec.c @@ -1,4 +1,3 @@ -#include "common.h" #include "modespec.h" /* diff --git a/modespec.h b/modespec.h index 2f83eb1..9dc3cca 100644 --- a/modespec.h +++ b/modespec.h @@ -1,11 +1,26 @@ #ifndef _MODESPEC_H_ #define _MODESPEC_H_ -#define MINSLANT 30 -#define MAXSLANT 150 -#define BUFLEN 4096 -#define SYNCPIXLEN 1.5e-3 +#include +#include +// SSTV modes +enum { + UNKNOWN=0, + M1, M2, M3, M4, + S1, S2, SDX, + R72, R36, R24, R24BW, R12BW, R8BW, + PD50, PD90, PD120, PD160, PD180, PD240, PD290, + P3, P5, P7, + W260, W2120, W2180 +}; + +// Color encodings +enum { + GBR, RGB, YUV, BW +}; + +// VIS map extern guchar VISmap[]; #define VIS_PARITY_ODD (1 << 7) From c910e15a2f8771a4547408ceb886d1602f54793c Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 16:52:01 +1000 Subject: [PATCH 048/166] sync: Replace GTK includes --- sync.c | 1 - sync.h | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/sync.c b/sync.c index 786b0ba..4ea587b 100644 --- a/sync.c +++ b/sync.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include "common.h" diff --git a/sync.h b/sync.h index 6b684aa..b80e123 100644 --- a/sync.h +++ b/sync.h @@ -1,6 +1,9 @@ #ifndef _SYNC_H_ #define _SYNC_H_ +#include +#include + double FindSync (guchar Mode, double Rate, int *Skip); #endif From 75027ffd625e169f12151167c6e26d5ea412e79a Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 17:29:02 +1000 Subject: [PATCH 049/166] listener: Replace direct calls into GUI with callback functions. --- listen.c | 102 +++++++++++++++++++------------------------------------ listen.h | 10 ++++++ slowrx.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 67 deletions(-) diff --git a/listen.c b/listen.c index 25c53b3..3b9590a 100644 --- a/listen.c +++ b/listen.c @@ -11,7 +11,8 @@ #include #include -#include +#include +#include #include #include @@ -20,7 +21,6 @@ #include "common.h" #include "fsk.h" -#include "gui.h" #include "listen.h" #include "modespec.h" #include "pcm.h" @@ -31,6 +31,16 @@ static pthread_t listener_thread; TextStatusCallback OnListenerStatusChange; +EventCallback OnListenerWaiting; +EventCallback OnListenerReceivedManual; +EventCallback OnListenerReceiveStarted; +EventCallback OnListenerReceiveFSK; +EventCallback OnListenerAutoSlantCorrect; +EventCallback OnListenerReceiveFinished; +TextStatusCallback OnListenerReceivedFSKID; +gboolean ListenerAutoSlantCorrect; +gboolean ListenerEnableFSKID; +struct tm *ListenerReceiveStartTime = NULL; void StartListener(void) { pthread_create(&listener_thread, NULL, Listen, NULL); @@ -43,22 +53,15 @@ void WaitForListenerStop(void) { // The thread that listens to VIS headers and calls decoders etc void *Listen() { - char rctime[8]; - guchar Mode=0; - struct tm *timeptr = NULL; time_t timet; gboolean Finished; char id[20]; - GtkTreeIter iter; while (TRUE) { - - gdk_threads_enter (); - gtk_widget_set_sensitive (gui.grid_vu, TRUE); - gtk_widget_set_sensitive (gui.button_abort, FALSE); - gtk_widget_set_sensitive (gui.button_clear, TRUE); - gdk_threads_leave (); + if (OnListenerWaiting) { + OnListenerWaiting(); + } pcm.WindowPtr = 0; snd_pcm_prepare(pcm.handle); @@ -79,8 +82,9 @@ void *Listen() { snd_pcm_drop(pcm.handle); printf("getvideo at %.2f skip %d\n",CurrentPic.Rate,CurrentPic.Skip); GetVideo(CurrentPic.Mode, CurrentPic.Rate, CurrentPic.Skip, TRUE); - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_save))) - saveCurrentPic(); + if (OnListenerReceivedManual) { + OnListenerReceivedManual(); + } pcm.WindowPtr = 0; snd_pcm_prepare(pcm.handle); snd_pcm_start (pcm.handle); @@ -97,8 +101,8 @@ void *Listen() { // Store time of reception timet = time(NULL); - timeptr = gmtime(&timet); - strftime(CurrentPic.timestr, sizeof(CurrentPic.timestr)-1,"%Y%m%d-%H%M%Sz", timeptr); + ListenerReceiveStartTime = gmtime(&timet); + strftime(CurrentPic.timestr, sizeof(CurrentPic.timestr)-1,"%Y%m%d-%H%M%Sz", ListenerReceiveStartTime); // Allocate space for cached Lum @@ -117,54 +121,43 @@ void *Listen() { } // Get video - strftime(rctime, sizeof(rctime)-1, "%H:%Mz", timeptr); if (OnListenerStatusChange) { OnListenerStatusChange("Receiving video..."); } - gdk_threads_enter (); - gtk_label_set_text (GTK_LABEL(gui.label_fskid), ""); - gtk_widget_set_sensitive (gui.frame_manual, FALSE); - gtk_widget_set_sensitive (gui.frame_slant, FALSE); - gtk_widget_set_sensitive (gui.combo_card, FALSE); - gtk_widget_set_sensitive (gui.button_abort, TRUE); - gtk_widget_set_sensitive (gui.button_clear, FALSE); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gui.tog_setedge), FALSE); - gtk_label_set_markup (GTK_LABEL(gui.label_lastmode), ModeSpec[CurrentPic.Mode].Name); - gtk_label_set_markup (GTK_LABEL(gui.label_utc), rctime); - gdk_threads_leave (); + if (OnListenerReceiveStarted) { + OnListenerReceiveStarted(); + } printf(" getvideo @ %.1f Hz, Skip %d, HedrShift %+d Hz\n", 44100.0, 0, CurrentPic.HedrShift); Finished = GetVideo(CurrentPic.Mode, 44100, 0, FALSE); - gdk_threads_enter (); - gtk_widget_set_sensitive (gui.button_abort, FALSE); - gdk_threads_leave (); - + if (OnListenerReceiveFSK) { + OnListenerReceiveFSK(); + } + id[0] = '\0'; - if (Finished && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gui.tog_fsk))) { + if (Finished && ListenerEnableFSKID && OnListenerReceivedFSKID) { if (OnListenerStatusChange) { OnListenerStatusChange("Receiving FSK ID..."); } GetFSK(id); printf(" FSKID \"%s\"\n",id); - gdk_threads_enter (); - gtk_label_set_text (GTK_LABEL(gui.label_fskid), id); - gdk_threads_leave (); + OnListenerReceivedFSKID(id); } snd_pcm_drop(pcm.handle); - if (Finished && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gui.tog_slant))) { + if (Finished && ListenerAutoSlantCorrect) { // Fix slant //setVU(0,6); if (OnListenerStatusChange) { OnListenerStatusChange("Calculating slant..."); } - gdk_threads_enter (); - gtk_widget_set_sensitive (gui.grid_vu, FALSE); - gdk_threads_leave (); + if (OnListenerAutoSlantCorrect) { + OnListenerAutoSlantCorrect(); + } printf(" FindSync @ %.1f Hz\n",CurrentPic.Rate); CurrentPic.Rate = FindSync(CurrentPic.Mode, CurrentPic.Rate, &CurrentPic.Skip); @@ -176,34 +169,9 @@ void *Listen() { free (HasSync); HasSync = NULL; - // Add thumbnail to iconview - CurrentPic.thumbbuf = gdk_pixbuf_scale_simple (pixbuf_rx, 100, - 100.0/ModeSpec[CurrentPic.Mode].ImgWidth * ModeSpec[CurrentPic.Mode].NumLines * ModeSpec[CurrentPic.Mode].LineHeight, GDK_INTERP_HYPER); - gdk_threads_enter (); - gtk_list_store_prepend (savedstore, &iter); - gtk_list_store_set (savedstore, &iter, 0, CurrentPic.thumbbuf, 1, id, -1); - gdk_threads_leave (); - - // Save PNG - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_save))) { - - //setVU(0,6); - - /*ensure_dir_exists("rx-lum"); - LumFile = fopen(lumfilename,"w"); - if (LumFile == NULL) - perror("Unable to open luma file for writing"); - fwrite(StoredLum,1,(ModeSpec[Mode].LineTime * ModeSpec[Mode].NumLines) * 44100,LumFile); - fclose(LumFile);*/ - - saveCurrentPic(); + if (OnListenerReceiveFinished) { + OnListenerReceiveFinished(); } - - gdk_threads_enter (); - gtk_widget_set_sensitive (gui.frame_slant, TRUE); - gtk_widget_set_sensitive (gui.frame_manual, TRUE); - gtk_widget_set_sensitive (gui.combo_card, TRUE); - gdk_threads_leave (); } } diff --git a/listen.h b/listen.h index 6e5baa1..ec96072 100644 --- a/listen.h +++ b/listen.h @@ -2,6 +2,16 @@ #define _LISTEN_H_ extern TextStatusCallback OnListenerStatusChange; +extern EventCallback OnListenerWaiting; +extern EventCallback OnListenerReceivedManual; +extern EventCallback OnListenerReceiveStarted; +extern EventCallback OnListenerReceiveFSK; +extern EventCallback OnListenerAutoSlantCorrect; +extern EventCallback OnListenerReceiveFinished; +extern TextStatusCallback OnListenerReceivedFSKID; +extern gboolean ListenerAutoSlantCorrect; +extern gboolean ListenerEnableFSKID; +extern struct tm *ListenerReceiveStartTime; void *Listen (); diff --git a/slowrx.c b/slowrx.c index b107904..19bb4a1 100644 --- a/slowrx.c +++ b/slowrx.c @@ -23,9 +23,96 @@ #include "fft.h" #include "gui.h" #include "listen.h" +#include "modespec.h" #include "pcm.h" +#include "pic.h" #include "vis.h" +static const char *fsk_id; + +static void onListenerWaiting(void) { + gdk_threads_enter (); + gtk_widget_set_sensitive (gui.grid_vu, TRUE); + gtk_widget_set_sensitive (gui.button_abort, FALSE); + gtk_widget_set_sensitive (gui.button_clear, TRUE); + gdk_threads_leave (); +} + +static void onListenerReceivedManual(void) { + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_save))) + saveCurrentPic(); +} + +static void onListenerReceiveFSK(void) { + gdk_threads_enter (); + gtk_widget_set_sensitive (gui.button_abort, FALSE); + gdk_threads_leave (); +} + +static void onListenerReceivedFSKID(const char *id) { + gdk_threads_enter (); + fsk_id = id; + gtk_label_set_text (GTK_LABEL(gui.label_fskid), id); + gdk_threads_leave (); +} + +static void onListenerReceiveStarted(void) { + static char rctime[8]; + + strftime(rctime, sizeof(rctime)-1, "%H:%Mz", ListenerReceiveStartTime); + gdk_threads_enter (); + gtk_label_set_text (GTK_LABEL(gui.label_fskid), ""); + gtk_widget_set_sensitive (gui.frame_manual, FALSE); + gtk_widget_set_sensitive (gui.frame_slant, FALSE); + gtk_widget_set_sensitive (gui.combo_card, FALSE); + gtk_widget_set_sensitive (gui.button_abort, TRUE); + gtk_widget_set_sensitive (gui.button_clear, FALSE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(gui.tog_setedge), FALSE); + gtk_label_set_markup (GTK_LABEL(gui.label_lastmode), ModeSpec[CurrentPic.Mode].Name); + gtk_label_set_markup (GTK_LABEL(gui.label_utc), rctime); + gdk_threads_leave (); +} + +static void onListenerAutoSlantCorrect(void) { + gdk_threads_enter (); + gtk_widget_set_sensitive (gui.grid_vu, FALSE); + gdk_threads_leave (); +} + +static void onListenerReceiveFinished(void) { + GtkTreeIter iter; + + // Add thumbnail to iconview + CurrentPic.thumbbuf = gdk_pixbuf_scale_simple (pixbuf_rx, 100, + 100.0/ModeSpec[CurrentPic.Mode].ImgWidth * ModeSpec[CurrentPic.Mode].NumLines * ModeSpec[CurrentPic.Mode].LineHeight, GDK_INTERP_HYPER); + gdk_threads_enter (); + gtk_list_store_prepend (savedstore, &iter); + gtk_list_store_set (savedstore, &iter, 0, CurrentPic.thumbbuf, 1, fsk_id, -1); + gdk_threads_leave (); + + + // Save PNG + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_save))) { + + //setVU(0,6); + + /*ensure_dir_exists("rx-lum"); + LumFile = fopen(lumfilename,"w"); + if (LumFile == NULL) + perror("Unable to open luma file for writing"); + fwrite(StoredLum,1,(ModeSpec[Mode].LineTime * ModeSpec[Mode].NumLines) * 44100,LumFile); + fclose(LumFile);*/ + + saveCurrentPic(); + } + + gdk_threads_enter (); + gtk_widget_set_sensitive (gui.frame_slant, TRUE); + gtk_widget_set_sensitive (gui.frame_manual, TRUE); + gtk_widget_set_sensitive (gui.combo_card, TRUE); + gdk_threads_leave (); + +} /* * main @@ -48,6 +135,13 @@ int main(int argc, char *argv[]) { createGUI(); OnListenerStatusChange = showStatusbarMessage; + OnListenerWaiting = onListenerWaiting; + OnListenerReceivedManual = onListenerReceivedManual; + OnListenerReceiveStarted = onListenerReceiveStarted; + OnListenerReceiveFSK = onListenerReceiveFSK; + OnListenerAutoSlantCorrect = onListenerAutoSlantCorrect; + OnListenerReceiveFinished = onListenerReceiveFinished; + OnListenerReceivedFSKID = onListenerReceivedFSKID; OnVisStatusChange = showStatusbarMessage; pcm.OnPCMAbort = showPCMError; pcm.OnPCMDrop = showPCMDropWarning; From ee26c08905dad523e3229cc19968d29f6fc67886 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 17:52:42 +1000 Subject: [PATCH 050/166] gui: Make event handlers static --- gui.c | 28 +++++++++++++++++++--------- gui.h | 10 ---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/gui.c b/gui.c index fc25665..005d0a8 100644 --- a/gui.c +++ b/gui.c @@ -13,6 +13,16 @@ #include "pcm.h" #include "pic.h" +static void evt_chooseDir (); +static void evt_show_about (); +static void evt_AbortRx (); +static void evt_changeDevices (); +static void evt_clearPix (); +static void evt_clickimg (); +static void evt_deletewindow (); +static void evt_GetAdaptive (); +static void evt_ManualStart (); + GuiObjs gui; GdkPixbuf *pixbuf_rx = NULL; @@ -239,7 +249,7 @@ void populateDeviceList() { } } -void evt_chooseDir() { +static void evt_chooseDir() { GtkWidget *dialog; dialog = gtk_file_chooser_dialog_new ("Select folder", GTK_WINDOW(gui.window_main), @@ -256,33 +266,33 @@ void evt_chooseDir() { gtk_widget_destroy (dialog); } -void evt_show_about() { +static void evt_show_about() { gtk_dialog_run(GTK_DIALOG(gui.window_about)); gtk_widget_hide(gui.window_about); } // Quit -void evt_deletewindow() { +static void evt_deletewindow() { gtk_main_quit (); } // Transform the NoiseAdapt toggle state into a variable -void evt_GetAdaptive() { +static void evt_GetAdaptive() { Adaptive = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui.tog_adapt)); } // Manual Start clicked -void evt_ManualStart() { +static void evt_ManualStart() { ManualActivated = TRUE; } // Abort clicked during rx -void evt_AbortRx() { +static void evt_AbortRx() { Abort = TRUE; } // Another device selected from list -void evt_changeDevices() { +static void evt_changeDevices() { int status; @@ -321,7 +331,7 @@ void evt_changeDevices() { } // Clear received picture & metadata -void evt_clearPix() { +static void evt_clearPix() { gdk_pixbuf_fill (pixbuf_disp, 0); gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp); gtk_label_set_markup (GTK_LABEL(gui.label_fskid), ""); @@ -330,7 +340,7 @@ void evt_clearPix() { } // Manual slant adjust -void evt_clickimg(GtkWidget *widget, GdkEventButton* event, GdkWindowEdge edge) { +static void evt_clickimg(GtkWidget *widget, GdkEventButton* event, GdkWindowEdge edge) { static double prevx=0,prevy=0,newrate; static gboolean secondpress=FALSE; double x,y,dx,dy,xic; diff --git a/gui.h b/gui.h index 9b744a1..d600d42 100644 --- a/gui.h +++ b/gui.h @@ -53,14 +53,4 @@ void saveCurrentPic(); void showStatusbarMessage(const char* message); -void evt_chooseDir (); -void evt_show_about (); -void evt_AbortRx (); -void evt_changeDevices (); -void evt_clearPix (); -void evt_clickimg (); -void evt_deletewindow (); -void evt_GetAdaptive (); -void evt_ManualStart (); - #endif From 84c429f1f40a29cf0054a285988a3e9343716a27 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 17:52:58 +1000 Subject: [PATCH 051/166] common: Drop references to GTK+ libs --- common.c | 1 - 1 file changed, 1 deletion(-) diff --git a/common.c b/common.c index 8020e9c..3f7661e 100644 --- a/common.c +++ b/common.c @@ -4,7 +4,6 @@ #include #include -#include #include #include From 15acfd4f3df502b21acb833b0d6702f05415ad5a Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 18:03:58 +1000 Subject: [PATCH 052/166] common: Drop unused thread1 --- common.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/common.c b/common.c index 3f7661e..da94273 100644 --- a/common.c +++ b/common.c @@ -20,8 +20,6 @@ gboolean ManualActivated = FALSE; gboolean ManualResync = FALSE; guchar *StoredLum = NULL; -pthread_t thread1; - // Return the FFT bin index matching the given frequency guint GetBin (double Freq, guint FFTLen) { return (Freq / 44100 * FFTLen); From be865b2e1ace03b518a02d56d846703411d02753 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 18:09:47 +1000 Subject: [PATCH 053/166] Makefile: Separate out GUI from core functions There's a lot of "core" stuff in the GUI but that's because I'm still picking those things apart. This at least gives us the scope for having different CFLAGS for back-end and UI stuff. --- Makefile | 46 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index d9b182a..dd8c903 100644 --- a/Makefile +++ b/Makefile @@ -1,30 +1,56 @@ -CC = gcc +CC ?= gcc +AR ?= ar +RANLIB ?= ranlib + +CFLAGS = -Wall -Wextra -std=gnu99 -pedantic -g +GLIBCFLAGS = $(shell pkg-config --cflags glib-2.0) +GLIBLIBS = $(shell pkg-config --libs glib-2.0) -CFLAGS = -Wall -Wextra -std=gnu99 -pedantic -g -DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_4 GTKCFLAGS = $(shell pkg-config --cflags gtk+-3.0) GTKLIBS = $(shell pkg-config --libs gtk+-3.0) OFLAGS = -O3 GUI_BIN = slowrx +COMMON_LIB = libslowrx.a TARGETS = $(GUI_BIN) -SOURCES = common.c pic.c fft.c modespec.c gui.c video.c vis.c sync.c pcm.c fsk.c listen.c config.c slowrx.c -OBJECTS = $(patsubst %.c,%.o,$(SOURCES)) -DEPENDS = $(patsubst %.c,%.d,$(SOURCES)) +COMMON_CFLAGS = $(CFLAGS) $(OFLAGS) +COMMON_LDFLAGS = -lfftw3 -lasound -lm -lpthread + +LIB_SOURCES = common.c fft.c modespec.c sync.c pcm.c config.c +LIB_OBJECTS = $(patsubst %.c,%.o,$(LIB_SOURCES)) +LIB_DEPENDS = $(patsubst %.c,%.d,$(LIB_SOURCES)) +LIB_CFLAGS = $(COMMON_CFLAGS) $(GLIBCFLAGS) + +GUI_SOURCES = pic.c fsk.c gui.c listen.c video.c vis.c slowrx.c +GUI_OBJECTS = $(patsubst %.c,%.o,$(GUI_SOURCES)) +GUI_DEPENDS = $(patsubst %.c,%.d,$(GUI_SOURCES)) +GUI_CFLAGS = $(COMMON_CFLAGS) $(GTKCFLAGS) -DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_4 +GUI_LDFLAGS = $(COMMON_LDFLAGS) -lgthread-2.0 $(GLIBLIBS) $(GTKLIBS) + +OBJECTS = $(GUI_OBJECTS) $(LIB_OBJECTS) +DEPENDS = $(GUI_DEPENDS) $(LIB_DEPENDS) all: $(TARGETS) -$(GUI_BIN): $(OBJECTS) - $(CC) $(CFLAGS) -o $@ $(OBJECTS) $(GTKLIBS) -lfftw3 -lgthread-2.0 -lasound -lm -lpthread +$(GUI_BIN): $(COMMON_LIB) $(GUI_OBJECTS) + $(CC) $(GUI_CFLAGS) -o $@ -Wl,--as-needed -Wl,--start-group $^ $(GUI_LDFLAGS) -Wl,--end-group + +$(COMMON_LIB): $(LIB_OBJECTS) + $(AR) cr $@ $^ + $(RANLIB) $@ %.o: %.c - $(CC) -MM -MF $(*F).d $(CFLAGS) $(GTKCFLAGS) $(OFLAGS) $< - $(CC) $(CFLAGS) $(GTKCFLAGS) $(OFLAGS) -c -o $@ $< + $(CC) -MM -MF $(*F).d $(OBJ_CFLAGS) $< + $(CC) $(OBJ_CFLAGS) -c -o $@ $< + +$(GUI_OBJECTS): OBJ_CFLAGS=$(GUI_CFLAGS) +$(LIB_OBJECTS): OBJ_CFLAGS=$(LIB_CFLAGS) clean: - rm -f $(TARGETS) $(OBJECTS) $(DEPENDS) + rm -f $(TARGETS) $(COMMON_LIB) $(OBJECTS) $(DEPENDS) -include $(DEPENDS) From 12b33126cbf93721f6e71fee80e620d5cab2f0db Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 19:50:11 +1000 Subject: [PATCH 054/166] slowrx: Synchronise FSK-ID and Auto Slant Correct toggles. --- slowrx.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/slowrx.c b/slowrx.c index 19bb4a1..7d390ea 100644 --- a/slowrx.c +++ b/slowrx.c @@ -46,6 +46,11 @@ static void onListenerReceivedManual(void) { static void onListenerReceiveFSK(void) { gdk_threads_enter (); gtk_widget_set_sensitive (gui.button_abort, FALSE); + + // Refresh ListenerEnableFSKID and ListenerAutoSlantCorrect while we're here + ListenerEnableFSKID = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gui.tog_fsk)); + ListenerAutoSlantCorrect = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gui.tog_slant)); + gdk_threads_leave (); } From e4d0403d3ae7400ed9cccf30ad950e0542fbbc53 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 19:52:08 +1000 Subject: [PATCH 055/166] Makefile: Move fsk into common lib --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index dd8c903..7bfef9e 100644 --- a/Makefile +++ b/Makefile @@ -19,12 +19,12 @@ TARGETS = $(GUI_BIN) COMMON_CFLAGS = $(CFLAGS) $(OFLAGS) COMMON_LDFLAGS = -lfftw3 -lasound -lm -lpthread -LIB_SOURCES = common.c fft.c modespec.c sync.c pcm.c config.c +LIB_SOURCES = common.c fft.c fsk.c modespec.c sync.c pcm.c config.c LIB_OBJECTS = $(patsubst %.c,%.o,$(LIB_SOURCES)) LIB_DEPENDS = $(patsubst %.c,%.d,$(LIB_SOURCES)) LIB_CFLAGS = $(COMMON_CFLAGS) $(GLIBCFLAGS) -GUI_SOURCES = pic.c fsk.c gui.c listen.c video.c vis.c slowrx.c +GUI_SOURCES = pic.c gui.c listen.c video.c vis.c slowrx.c GUI_OBJECTS = $(patsubst %.c,%.o,$(GUI_SOURCES)) GUI_DEPENDS = $(patsubst %.c,%.d,$(GUI_SOURCES)) GUI_CFLAGS = $(COMMON_CFLAGS) $(GTKCFLAGS) -DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_4 From 32004e8bce1e39bca066587328b11c9ac262f8bf Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 19:57:43 +1000 Subject: [PATCH 056/166] pic, slowrx: Move thumbbuf to the GUI It's only used for generating a thumbnail in the UI, not needed for decoding, so out it comes. --- pic.h | 3 --- slowrx.c | 5 +++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pic.h b/pic.h index 48f897b..52d5b2c 100644 --- a/pic.h +++ b/pic.h @@ -1,8 +1,6 @@ #ifndef _PIC_H_ #define _PIC_H_ -#include - #include #include @@ -12,7 +10,6 @@ struct _PicMeta { guchar Mode; double Rate; int Skip; - GdkPixbuf *thumbbuf; char timestr[40]; }; extern PicMeta CurrentPic; diff --git a/slowrx.c b/slowrx.c index 7d390ea..845d0d3 100644 --- a/slowrx.c +++ b/slowrx.c @@ -29,6 +29,7 @@ #include "vis.h" static const char *fsk_id; +static GdkPixbuf *thumbbuf; static void onListenerWaiting(void) { gdk_threads_enter (); @@ -88,11 +89,11 @@ static void onListenerReceiveFinished(void) { GtkTreeIter iter; // Add thumbnail to iconview - CurrentPic.thumbbuf = gdk_pixbuf_scale_simple (pixbuf_rx, 100, + thumbbuf = gdk_pixbuf_scale_simple (pixbuf_rx, 100, 100.0/ModeSpec[CurrentPic.Mode].ImgWidth * ModeSpec[CurrentPic.Mode].NumLines * ModeSpec[CurrentPic.Mode].LineHeight, GDK_INTERP_HYPER); gdk_threads_enter (); gtk_list_store_prepend (savedstore, &iter); - gtk_list_store_set (savedstore, &iter, 0, CurrentPic.thumbbuf, 1, fsk_id, -1); + gtk_list_store_set (savedstore, &iter, 0, thumbbuf, 1, fsk_id, -1); gdk_threads_leave (); From 4b200f592256cad81c689f70b9d72433cd612967 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 20:01:19 +1000 Subject: [PATCH 057/166] Makefile: move pic to the common lib --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7bfef9e..26deaf4 100644 --- a/Makefile +++ b/Makefile @@ -19,12 +19,12 @@ TARGETS = $(GUI_BIN) COMMON_CFLAGS = $(CFLAGS) $(OFLAGS) COMMON_LDFLAGS = -lfftw3 -lasound -lm -lpthread -LIB_SOURCES = common.c fft.c fsk.c modespec.c sync.c pcm.c config.c +LIB_SOURCES = common.c fft.c fsk.c modespec.c sync.c pic.c pcm.c config.c LIB_OBJECTS = $(patsubst %.c,%.o,$(LIB_SOURCES)) LIB_DEPENDS = $(patsubst %.c,%.d,$(LIB_SOURCES)) LIB_CFLAGS = $(COMMON_CFLAGS) $(GLIBCFLAGS) -GUI_SOURCES = pic.c gui.c listen.c video.c vis.c slowrx.c +GUI_SOURCES = gui.c listen.c video.c vis.c slowrx.c GUI_OBJECTS = $(patsubst %.c,%.o,$(GUI_SOURCES)) GUI_DEPENDS = $(patsubst %.c,%.d,$(GUI_SOURCES)) GUI_CFLAGS = $(COMMON_CFLAGS) $(GTKCFLAGS) -DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_4 From bb1e55ac475b50fedd4b33ede33ebb5df1ca43e5 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 20:27:30 +1000 Subject: [PATCH 058/166] vis: Replace direct calls to GUI functions with callbacks --- slowrx.c | 37 ++++++++++++++++++++++++++++++++++ vis.c | 60 +++++++++++++++++++++----------------------------------- vis.h | 8 ++++++++ 3 files changed, 67 insertions(+), 38 deletions(-) diff --git a/slowrx.c b/slowrx.c index 845d0d3..bd79b94 100644 --- a/slowrx.c +++ b/slowrx.c @@ -31,6 +31,41 @@ static const char *fsk_id; static GdkPixbuf *thumbbuf; +static void onVisIdentified(void) { + gdk_threads_enter(); + gtk_combo_box_set_active (GTK_COMBO_BOX(gui.combo_mode), VISmap[VIS]-1); + gtk_spin_button_set_value (GTK_SPIN_BUTTON(gui.spin_shift), CurrentPic.HedrShift); + + // Synchronise tog_rx with VisAutoStart + VisAutoStart = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gui.tog_rx)); + + // Manual start: update the VIS parameters from the UI + if (ManualActivated) { + int selmode, i; + + gdk_threads_enter(); + gtk_widget_set_sensitive( gui.frame_manual, FALSE ); + gtk_widget_set_sensitive( gui.combo_card, FALSE ); + gdk_threads_leave(); + + selmode = gtk_combo_box_get_active (GTK_COMBO_BOX(gui.combo_mode)) + 1; + CurrentPic.HedrShift = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON(gui.spin_shift)); + VIS = 0; + for (i=0; i<0x80; i++) { + if (VISmap[i] == selmode) { + VIS = i; + break; + } + } + } + + gdk_threads_leave(); +} + +static void onVisPowerComputed(void) { + setVU(VisPower, 2048, 6); +} + static void onListenerWaiting(void) { gdk_threads_enter (); gtk_widget_set_sensitive (gui.grid_vu, TRUE); @@ -140,6 +175,8 @@ int main(int argc, char *argv[]) { } createGUI(); + OnVisIdentified = onVisIdentified; + OnVisPowerComputed = onVisPowerComputed; OnListenerStatusChange = showStatusbarMessage; OnListenerWaiting = onListenerWaiting; OnListenerReceivedManual = onListenerReceivedManual; diff --git a/vis.c b/vis.c index e8701f5..4a041c4 100644 --- a/vis.c +++ b/vis.c @@ -7,19 +7,10 @@ #include "common.h" #include "fft.h" -#include "gui.h" #include "modespec.h" #include "pcm.h" #include "pic.h" -/* Handle selection of the VIS mode in the GUI */ -static void onGotVis(guchar VIS) { - gdk_threads_enter(); - gtk_combo_box_set_active (GTK_COMBO_BOX(gui.combo_mode), VISmap[VIS]-1); - gtk_spin_button_set_value (GTK_SPIN_BUTTON(gui.spin_shift), CurrentPic.HedrShift); - gdk_threads_leave(); -} - /* * * Detect VIS & frequency shift @@ -29,13 +20,18 @@ static void onGotVis(guchar VIS) { */ TextStatusCallback OnVisStatusChange; +EventCallback OnVisIdentified; +EventCallback OnVisPowerComputed; +int VIS; +gboolean VisAutoStart; +double VisPower[2048] = {0}; guchar GetVIS () { - int selmode, ptr=0; - int VIS = 0, Parity = 0, HedrPtr = 0; + int ptr=0; + int Parity = 0, HedrPtr = 0; guint FFTLen = 2048, i=0, j=0, k=0, MaxBin = 0; - double Power[2048] = {0}, HedrBuf[100] = {0}, tone[100] = {0}, Hann[882] = {0}; + double HedrBuf[100] = {0}, tone[100] = {0}, Hann[882] = {0}; gboolean gotvis = FALSE; guchar Bit[8] = {0}, ParityBit = 0; @@ -68,17 +64,17 @@ guchar GetVIS () { // Find the bin with most power MaxBin = 0; for (i = 0; i <= GetBin(6000, FFTLen); i++) { - Power[i] = power(fft.out[i]); + VisPower[i] = power(fft.out[i]); if ( (i >= GetBin(500,FFTLen) && i < GetBin(3300,FFTLen)) && - (MaxBin == 0 || Power[i] > Power[MaxBin])) + (MaxBin == 0 || VisPower[i] > VisPower[MaxBin])) MaxBin = i; } // Find the peak frequency by Gaussian interpolation if (MaxBin > GetBin(500, FFTLen) && MaxBin < GetBin(3300, FFTLen) && - Power[MaxBin] > 0 && Power[MaxBin+1] > 0 && Power[MaxBin-1] > 0) - HedrBuf[HedrPtr] = MaxBin + (log( Power[MaxBin + 1] / Power[MaxBin - 1] )) / - (2 * log( pow(Power[MaxBin], 2) / (Power[MaxBin + 1] * Power[MaxBin - 1]))); + VisPower[MaxBin] > 0 && VisPower[MaxBin+1] > 0 && VisPower[MaxBin-1] > 0) + HedrBuf[HedrPtr] = MaxBin + (log( VisPower[MaxBin + 1] / VisPower[MaxBin - 1] )) / + (2 * log( pow(VisPower[MaxBin], 2) / (VisPower[MaxBin + 1] * VisPower[MaxBin - 1]))); else HedrBuf[HedrPtr] = HedrBuf[(HedrPtr-1) % 45]; // In Hertz @@ -138,14 +134,12 @@ guchar GetVIS () { } else { // Yep, that was it. Inverted parity. VIS |= VIS_PARITY_ODD; - onGotVis(VIS); break; } } else if (VISmap[VIS] == UNKNOWN) { printf(" Unknown VIS\n"); gotvis = FALSE; } else { - onGotVis(VIS); break; } } @@ -153,32 +147,22 @@ guchar GetVIS () { } } - if (gotvis) - if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gui.tog_rx))) break; + if (gotvis && OnVisIdentified) { + OnVisIdentified(); + } + if (gotvis && VisAutoStart) { + break; + } // Manual start if (ManualActivated) { - - gdk_threads_enter(); - gtk_widget_set_sensitive( gui.frame_manual, FALSE ); - gtk_widget_set_sensitive( gui.combo_card, FALSE ); - gdk_threads_leave(); - - selmode = gtk_combo_box_get_active (GTK_COMBO_BOX(gui.combo_mode)) + 1; - CurrentPic.HedrShift = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON(gui.spin_shift)); - VIS = 0; - for (i=0; i<0x80; i++) { - if (VISmap[i] == selmode) { - VIS = i; - break; - } - } - break; } if (++ptr == 10) { - setVU(Power, 2048, 6); + if (OnVisPowerComputed) { + OnVisPowerComputed(); + } ptr = 0; } diff --git a/vis.h b/vis.h index 70499fa..6fc62a2 100644 --- a/vis.h +++ b/vis.h @@ -1,7 +1,15 @@ #ifndef _VIS_H_ #define _VIS_H_ +#include +#include + extern TextStatusCallback OnVisStatusChange; +extern EventCallback OnVisIdentified; +extern EventCallback OnVisPowerComputed; +extern int VIS; +extern gboolean VisAutoStart; +extern double VisPower[2048]; guchar GetVIS (); From 659e2fa444fc904819ce5ae7bfdd42c2e2988efe Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 20:28:22 +1000 Subject: [PATCH 059/166] Makefile: move vis to common lib --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 26deaf4..c1a33e7 100644 --- a/Makefile +++ b/Makefile @@ -19,12 +19,12 @@ TARGETS = $(GUI_BIN) COMMON_CFLAGS = $(CFLAGS) $(OFLAGS) COMMON_LDFLAGS = -lfftw3 -lasound -lm -lpthread -LIB_SOURCES = common.c fft.c fsk.c modespec.c sync.c pic.c pcm.c config.c +LIB_SOURCES = common.c fft.c fsk.c modespec.c sync.c pic.c pcm.c config.c vis.c LIB_OBJECTS = $(patsubst %.c,%.o,$(LIB_SOURCES)) LIB_DEPENDS = $(patsubst %.c,%.d,$(LIB_SOURCES)) LIB_CFLAGS = $(COMMON_CFLAGS) $(GLIBCFLAGS) -GUI_SOURCES = gui.c listen.c video.c vis.c slowrx.c +GUI_SOURCES = gui.c listen.c video.c slowrx.c GUI_OBJECTS = $(patsubst %.c,%.o,$(GUI_SOURCES)) GUI_DEPENDS = $(patsubst %.c,%.d,$(GUI_SOURCES)) GUI_CFLAGS = $(COMMON_CFLAGS) $(GTKCFLAGS) -DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_4 From 0f3d5309548b8d24a576eaf53857bf8e615c161c Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 20:45:02 +1000 Subject: [PATCH 060/166] vis: Replace magic number --- slowrx.c | 2 +- vis.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/slowrx.c b/slowrx.c index bd79b94..5733bdd 100644 --- a/slowrx.c +++ b/slowrx.c @@ -63,7 +63,7 @@ static void onVisIdentified(void) { } static void onVisPowerComputed(void) { - setVU(VisPower, 2048, 6); + setVU(VisPower, VIS_POWER_SZ, 6); } static void onListenerWaiting(void) { diff --git a/vis.h b/vis.h index 6fc62a2..8e6cb6e 100644 --- a/vis.h +++ b/vis.h @@ -4,12 +4,14 @@ #include #include +#define VIS_POWER_SZ (2048) + extern TextStatusCallback OnVisStatusChange; extern EventCallback OnVisIdentified; extern EventCallback OnVisPowerComputed; extern int VIS; extern gboolean VisAutoStart; -extern double VisPower[2048]; +extern double VisPower[VIS_POWER_SZ]; guchar GetVIS (); From edba041c1e5a3e256f4bd668ba72d5a6df313c91 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 21:21:43 +1000 Subject: [PATCH 061/166] common: Define a call-back for VU updates. This is probably better than setting a global variable than calling an argument-less callback! --- common.h | 1 + 1 file changed, 1 insertion(+) diff --git a/common.h b/common.h index 0d5e464..8437f0c 100644 --- a/common.h +++ b/common.h @@ -21,6 +21,7 @@ extern guchar *StoredLum; // Various callback function types for various events. typedef void (*EventCallback)(void); typedef void (*TextStatusCallback)(const char* status); +typedef void (*UpdateVUCallback)(double* Power, int FFTLen, int WinIdx); double power (fftw_complex coeff); guchar clip (double a); From 28ee797df74f536a472d5c66529714d2fbb89bd4 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 21:25:21 +1000 Subject: [PATCH 062/166] vis: Remove unused GTK+ header --- vis.c | 1 - 1 file changed, 1 deletion(-) diff --git a/vis.c b/vis.c index 4a041c4..217a009 100644 --- a/vis.c +++ b/vis.c @@ -1,6 +1,5 @@ #include #include -#include #include #include From 79fe56d3c8bf2fcab01bc7be37cc9fac78884351 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 21:29:26 +1000 Subject: [PATCH 063/166] video: Convert to callback API --- slowrx.c | 47 ++++++++++++++++++++++++++ video.c | 100 ++++++++++++++++++++++++------------------------------- video.h | 15 +++++++++ 3 files changed, 106 insertions(+), 56 deletions(-) diff --git a/slowrx.c b/slowrx.c index 5733bdd..ce80ea3 100644 --- a/slowrx.c +++ b/slowrx.c @@ -26,10 +26,13 @@ #include "modespec.h" #include "pcm.h" #include "pic.h" +#include "video.h" #include "vis.h" static const char *fsk_id; static GdkPixbuf *thumbbuf; +static guchar *pixels, Mode; +static int rowstride; static void onVisIdentified(void) { gdk_threads_enter(); @@ -66,6 +69,45 @@ static void onVisPowerComputed(void) { setVU(VisPower, VIS_POWER_SZ, 6); } +static void onVideoInitBuffer(guchar mode) { + Mode = mode; + g_object_unref(pixbuf_rx); + pixbuf_rx = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, ModeSpec[Mode].ImgWidth, ModeSpec[Mode].NumLines); + gdk_pixbuf_fill(pixbuf_rx, 0); +} + +static void onVideoStartRedraw(void) { + rowstride = gdk_pixbuf_get_rowstride (pixbuf_rx); + pixels = gdk_pixbuf_get_pixels(pixbuf_rx); + + g_object_unref(pixbuf_disp); + pixbuf_disp = gdk_pixbuf_scale_simple(pixbuf_rx, 500, + 500.0/ModeSpec[Mode].ImgWidth * ModeSpec[Mode].NumLines * ModeSpec[Mode].LineHeight, GDK_INTERP_BILINEAR); + + gdk_threads_enter(); + gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp); + gdk_threads_leave(); + +} + +static void onVideoWritePixel(guchar x, guchar y, guchar r, guchar g, guchar b) { + guchar *p = pixels + y * rowstride + x * 3; + p[0] = r; + p[1] = g; + p[2] = b; +} + +static void onVideoRefresh(void) { + // Scale and update image + g_object_unref(pixbuf_disp); + pixbuf_disp = gdk_pixbuf_scale_simple(pixbuf_rx, 500, + 500.0 / ModeSpec[Mode].ImgWidth * ModeSpec[Mode].NumLines * ModeSpec[Mode].LineHeight, GDK_INTERP_BILINEAR); + + gdk_threads_enter(); + gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp); + gdk_threads_leave(); +} + static void onListenerWaiting(void) { gdk_threads_enter (); gtk_widget_set_sensitive (gui.grid_vu, TRUE); @@ -175,6 +217,11 @@ int main(int argc, char *argv[]) { } createGUI(); + OnVideoInitBuffer = onVideoInitBuffer; + OnVideoStartRedraw = onVideoStartRedraw; + OnVideoRefresh = onVideoRefresh; + OnVideoWritePixel = onVideoWritePixel; + OnVideoPowerCalculated = setVU; OnVisIdentified = onVisIdentified; OnVisPowerComputed = onVisPowerComputed; OnListenerStatusChange = showStatusbarMessage; diff --git a/video.c b/video.c index a270917..c17725c 100644 --- a/video.c +++ b/video.c @@ -2,19 +2,33 @@ #include #include #include -#include #include #include #include "common.h" #include "fft.h" -#include "gui.h" #include "modespec.h" #include "pcm.h" #include "pic.h" #include "video.h" +guchar VideoImage[VIDEO_MAX_WIDTH][VIDEO_MAX_HEIGHT][VIDEO_MAX_CHANNELS] = {{{0}}}; +void (*OnVideoInitBuffer)(guchar Mode); +void (*OnVideoWritePixel)(guchar x, guchar y, guchar r, guchar g, guchar b); +EventCallback OnVideoStartRedraw; +EventCallback OnVideoRefresh; +UpdateVUCallback OnVideoPowerCalculated; + +typedef struct { + int X; + int Y; + int Time; + guchar Channel; + gboolean Last; +} _PixelGrid; + + /* Demodulate the video signal & store all kinds of stuff for later stages * Mode: M1, M2, S1, S2, R72, R36... * Rate: exact sampling rate used @@ -23,7 +37,6 @@ * returns: TRUE when finished, FALSE when aborted */ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { - guint MaxBin = 0; guint VideoPlusNoiseBins=0, ReceiverBins=0, NoiseOnlyBins=0; guint n=0; @@ -42,20 +55,9 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { double Pvideo_plus_noise=0, Pnoise_only=0, Pnoise=0, Psignal=0; double SNR = 0; double ChanStart[4] = {0}, ChanLen[4] = {0}; - guchar Image[800][616][3] = {{{0}}}; guchar Channel = 0, WinIdx = 0; - typedef struct { - int X; - int Y; - int Time; - guchar Channel; - gboolean Last; - } _PixelGrid; - - _PixelGrid *PixelGrid; - PixelGrid = calloc( ModeSpec[Mode].ImgWidth * ModeSpec[Mode].NumLines * 3, sizeof(_PixelGrid) ); - + _PixelGrid *PixelGrid = calloc( ModeSpec[Mode].ImgWidth * ModeSpec[Mode].NumLines * 3, sizeof(_PixelGrid) ); // Initialize Hann windows of different lengths gushort HannLens[7] = { 48, 64, 96, 128, 256, 512, 1024 }; @@ -236,25 +238,14 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { break;*/ // Initialize pixbuffer - if (!Redraw) { - g_object_unref(pixbuf_rx); - pixbuf_rx = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, ModeSpec[Mode].ImgWidth, ModeSpec[Mode].NumLines); - gdk_pixbuf_fill(pixbuf_rx, 0); + if ((!Redraw) && OnVideoInitBuffer) { + OnVideoInitBuffer(Mode); } - int rowstride = gdk_pixbuf_get_rowstride (pixbuf_rx); - guchar *pixels, *p; - pixels = gdk_pixbuf_get_pixels(pixbuf_rx); - - g_object_unref(pixbuf_disp); - pixbuf_disp = gdk_pixbuf_scale_simple(pixbuf_rx, 500, - 500.0/ModeSpec[Mode].ImgWidth * ModeSpec[Mode].NumLines * ModeSpec[Mode].LineHeight, GDK_INTERP_BILINEAR); - - gdk_threads_enter(); - gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp); - gdk_threads_leave(); + if (OnVideoStartRedraw) { + OnVideoStartRedraw(); + } - if(NumChans == 4) //In PD* modes, each radio frame encodes two image lines Length = ModeSpec[Mode].LineTime * ModeSpec[Mode].NumLines/2 * 44100; else @@ -425,53 +416,50 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { Channel = PixelGrid[PixelIdx].Channel; // Store pixel - Image[x][y][Channel] = StoredLum[SampleNum]; + VideoImage[x][y][Channel] = StoredLum[SampleNum]; // Some modes have R-Y & B-Y channels that are twice the height of the Y channel if (Channel > 0 && (Mode == R36 || Mode == R24)) - Image[x][y+1][Channel] = StoredLum[SampleNum]; + VideoImage[x][y+1][Channel] = StoredLum[SampleNum]; // Calculate and draw pixels to pixbuf on line change if (x == ModeSpec[Mode].ImgWidth - 1 || PixelGrid[PixelIdx].Last) { for (tx = 0; tx < ModeSpec[Mode].ImgWidth; tx++) { - p = pixels + y * rowstride + tx * 3; + guchar r = 0, g = 0, b = 0; switch(ModeSpec[Mode].ColorEnc) { case RGB: - p[0] = Image[tx][y][0]; - p[1] = Image[tx][y][1]; - p[2] = Image[tx][y][2]; + r = VideoImage[tx][y][0]; + g = VideoImage[tx][y][1]; + b = VideoImage[tx][y][2]; break; case GBR: - p[0] = Image[tx][y][2]; - p[1] = Image[tx][y][0]; - p[2] = Image[tx][y][1]; + r = VideoImage[tx][y][2]; + g = VideoImage[tx][y][0]; + b = VideoImage[tx][y][1]; break; case YUV: - p[0] = clip((100 * Image[tx][y][0] + 140 * Image[tx][y][1] - 17850) / 100.0); - p[1] = clip((100 * Image[tx][y][0] - 71 * Image[tx][y][1] - 33 * - Image[tx][y][2] + 13260) / 100.0); - p[2] = clip((100 * Image[tx][y][0] + 178 * Image[tx][y][2] - 22695) / 100.0); + r = clip((100 * VideoImage[tx][y][0] + 140 * VideoImage[tx][y][1] - 17850) / 100.0); + g = clip((100 * VideoImage[tx][y][0] - 71 * VideoImage[tx][y][1] - 33 * + VideoImage[tx][y][2] + 13260) / 100.0); + b = clip((100 * VideoImage[tx][y][0] + 178 * VideoImage[tx][y][2] - 22695) / 100.0); break; case BW: - p[0] = p[1] = p[2] = Image[tx][y][0]; + r = g = b = VideoImage[tx][y][0]; break; } - } - if (!Redraw || y % 5 == 0 || PixelGrid[PixelIdx].Last) { - // Scale and update image - g_object_unref(pixbuf_disp); - pixbuf_disp = gdk_pixbuf_scale_simple(pixbuf_rx, 500, - 500.0 / ModeSpec[Mode].ImgWidth * ModeSpec[Mode].NumLines * ModeSpec[Mode].LineHeight, GDK_INTERP_BILINEAR); + if (OnVideoWritePixel) { + OnVideoWritePixel(x, y, r, g, b); + } + } - gdk_threads_enter(); - gtk_image_set_from_pixbuf(GTK_IMAGE(gui.image_rx), pixbuf_disp); - gdk_threads_leave(); + if ((!Redraw || y % 5 == 0 || PixelGrid[PixelIdx].Last) && OnVideoRefresh) { + OnVideoRefresh(); } } @@ -479,8 +467,8 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { } } /* endif (SampleNum == PixelGrid[PixelIdx].Time) */ - if (!Redraw && SampleNum % 8820 == 0) { - setVU(Power, FFTLen, WinIdx); + if (!Redraw && (SampleNum % 8820 == 0) && OnVideoPowerCalculated) { + OnVideoPowerCalculated(Power, FFTLen, WinIdx); } if (Abort) { diff --git a/video.h b/video.h index b5d190e..cb68be0 100644 --- a/video.h +++ b/video.h @@ -1,6 +1,21 @@ #ifndef _VIDEO_H_ #define _VIDEO_H_ +#include +#include + +#define VIDEO_MAX_WIDTH (800) +#define VIDEO_MAX_HEIGHT (616) +#define VIDEO_MAX_CHANNELS (3) + + +extern guchar VideoImage[VIDEO_MAX_WIDTH][VIDEO_MAX_HEIGHT][VIDEO_MAX_CHANNELS]; +extern void (*OnVideoInitBuffer)(guchar Mode); +extern EventCallback OnVideoStartRedraw; +extern void (*OnVideoWritePixel)(guchar x, guchar y, guchar r, guchar g, guchar b); +extern EventCallback OnVideoRefresh; +extern UpdateVUCallback OnVideoPowerCalculated; + gboolean GetVideo (guchar Mode, double Rate, int Skip, gboolean Redraw); #endif From 854f47258ddc1b114dec43ebc7af8880895027cb Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 21:30:59 +1000 Subject: [PATCH 064/166] Makefile: Move final pieces to common lib --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c1a33e7..3b33c10 100644 --- a/Makefile +++ b/Makefile @@ -19,12 +19,12 @@ TARGETS = $(GUI_BIN) COMMON_CFLAGS = $(CFLAGS) $(OFLAGS) COMMON_LDFLAGS = -lfftw3 -lasound -lm -lpthread -LIB_SOURCES = common.c fft.c fsk.c modespec.c sync.c pic.c pcm.c config.c vis.c +LIB_SOURCES = common.c fft.c fsk.c listen.c modespec.c sync.c pic.c pcm.c config.c vis.c video.c LIB_OBJECTS = $(patsubst %.c,%.o,$(LIB_SOURCES)) LIB_DEPENDS = $(patsubst %.c,%.d,$(LIB_SOURCES)) LIB_CFLAGS = $(COMMON_CFLAGS) $(GLIBCFLAGS) -GUI_SOURCES = gui.c listen.c video.c slowrx.c +GUI_SOURCES = gui.c slowrx.c GUI_OBJECTS = $(patsubst %.c,%.o,$(GUI_SOURCES)) GUI_DEPENDS = $(patsubst %.c,%.d,$(GUI_SOURCES)) GUI_CFLAGS = $(COMMON_CFLAGS) $(GTKCFLAGS) -DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_4 From 942e6a065a1c1493aa02fdb980472470fd8de996 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 21:33:49 +1000 Subject: [PATCH 065/166] vis: Use UpdateVUCallback for VU updates --- slowrx.c | 6 +----- vis.c | 6 +++--- vis.h | 5 +---- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/slowrx.c b/slowrx.c index ce80ea3..07ae2a2 100644 --- a/slowrx.c +++ b/slowrx.c @@ -65,10 +65,6 @@ static void onVisIdentified(void) { gdk_threads_leave(); } -static void onVisPowerComputed(void) { - setVU(VisPower, VIS_POWER_SZ, 6); -} - static void onVideoInitBuffer(guchar mode) { Mode = mode; g_object_unref(pixbuf_rx); @@ -223,7 +219,7 @@ int main(int argc, char *argv[]) { OnVideoWritePixel = onVideoWritePixel; OnVideoPowerCalculated = setVU; OnVisIdentified = onVisIdentified; - OnVisPowerComputed = onVisPowerComputed; + OnVisPowerComputed = setVU; OnListenerStatusChange = showStatusbarMessage; OnListenerWaiting = onListenerWaiting; OnListenerReceivedManual = onListenerReceivedManual; diff --git a/vis.c b/vis.c index 217a009..6ba6d84 100644 --- a/vis.c +++ b/vis.c @@ -20,10 +20,10 @@ TextStatusCallback OnVisStatusChange; EventCallback OnVisIdentified; -EventCallback OnVisPowerComputed; +UpdateVUCallback OnVisPowerComputed; int VIS; gboolean VisAutoStart; -double VisPower[2048] = {0}; +static double VisPower[2048] = {0}; guchar GetVIS () { @@ -160,7 +160,7 @@ guchar GetVIS () { if (++ptr == 10) { if (OnVisPowerComputed) { - OnVisPowerComputed(); + OnVisPowerComputed(VisPower, 2048, 6); } ptr = 0; } diff --git a/vis.h b/vis.h index 8e6cb6e..1d83321 100644 --- a/vis.h +++ b/vis.h @@ -4,14 +4,11 @@ #include #include -#define VIS_POWER_SZ (2048) - extern TextStatusCallback OnVisStatusChange; extern EventCallback OnVisIdentified; -extern EventCallback OnVisPowerComputed; +extern UpdateVUCallback OnVisPowerComputed; extern int VIS; extern gboolean VisAutoStart; -extern double VisPower[VIS_POWER_SZ]; guchar GetVIS (); From 01c1392f7a1d3e5474f0fb135ee145aba3691622 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 21:36:29 +1000 Subject: [PATCH 066/166] video: tx is the column counter not x --- video.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/video.c b/video.c index c17725c..90cdc5b 100644 --- a/video.c +++ b/video.c @@ -454,7 +454,7 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { } if (OnVideoWritePixel) { - OnVideoWritePixel(x, y, r, g, b); + OnVideoWritePixel(tx, y, r, g, b); } } From fdf5c1fd1d1fce2c9c3273847fbb9d996493b298 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 21:56:09 +1000 Subject: [PATCH 067/166] video: Use `gushort` for co-ordinates 0-320 will not fit in a `guchar`, twit! --- slowrx.c | 4 ++-- video.c | 2 +- video.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/slowrx.c b/slowrx.c index 07ae2a2..87e2bdf 100644 --- a/slowrx.c +++ b/slowrx.c @@ -86,8 +86,8 @@ static void onVideoStartRedraw(void) { } -static void onVideoWritePixel(guchar x, guchar y, guchar r, guchar g, guchar b) { - guchar *p = pixels + y * rowstride + x * 3; +static void onVideoWritePixel(gushort x, gushort y, guchar r, guchar g, guchar b) { + guchar *p = &pixels[(y * rowstride) + (x * 3)]; p[0] = r; p[1] = g; p[2] = b; diff --git a/video.c b/video.c index 90cdc5b..182d41e 100644 --- a/video.c +++ b/video.c @@ -15,7 +15,7 @@ guchar VideoImage[VIDEO_MAX_WIDTH][VIDEO_MAX_HEIGHT][VIDEO_MAX_CHANNELS] = {{{0}}}; void (*OnVideoInitBuffer)(guchar Mode); -void (*OnVideoWritePixel)(guchar x, guchar y, guchar r, guchar g, guchar b); +void (*OnVideoWritePixel)(gushort x, gushort y, guchar r, guchar g, guchar b); EventCallback OnVideoStartRedraw; EventCallback OnVideoRefresh; UpdateVUCallback OnVideoPowerCalculated; diff --git a/video.h b/video.h index cb68be0..aae022f 100644 --- a/video.h +++ b/video.h @@ -12,7 +12,7 @@ extern guchar VideoImage[VIDEO_MAX_WIDTH][VIDEO_MAX_HEIGHT][VIDEO_MAX_CHANNELS]; extern void (*OnVideoInitBuffer)(guchar Mode); extern EventCallback OnVideoStartRedraw; -extern void (*OnVideoWritePixel)(guchar x, guchar y, guchar r, guchar g, guchar b); +extern void (*OnVideoWritePixel)(gushort x, gushort y, guchar r, guchar g, guchar b); extern EventCallback OnVideoRefresh; extern UpdateVUCallback OnVideoPowerCalculated; From 66be56439899bb9a2706588d2ce9a8fb05e2766b Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 22:15:51 +1000 Subject: [PATCH 068/166] video: Hide `VideoImage` buffer I thought I might need to expose this, but found a better way. --- video.c | 6 +++++- video.h | 6 ------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/video.c b/video.c index 182d41e..62bb86b 100644 --- a/video.c +++ b/video.c @@ -13,7 +13,11 @@ #include "pic.h" #include "video.h" -guchar VideoImage[VIDEO_MAX_WIDTH][VIDEO_MAX_HEIGHT][VIDEO_MAX_CHANNELS] = {{{0}}}; +#define VIDEO_MAX_WIDTH (800) +#define VIDEO_MAX_HEIGHT (616) +#define VIDEO_MAX_CHANNELS (3) + +static guchar VideoImage[VIDEO_MAX_WIDTH][VIDEO_MAX_HEIGHT][VIDEO_MAX_CHANNELS] = {{{0}}}; void (*OnVideoInitBuffer)(guchar Mode); void (*OnVideoWritePixel)(gushort x, gushort y, guchar r, guchar g, guchar b); EventCallback OnVideoStartRedraw; diff --git a/video.h b/video.h index aae022f..d5e94b7 100644 --- a/video.h +++ b/video.h @@ -4,12 +4,6 @@ #include #include -#define VIDEO_MAX_WIDTH (800) -#define VIDEO_MAX_HEIGHT (616) -#define VIDEO_MAX_CHANNELS (3) - - -extern guchar VideoImage[VIDEO_MAX_WIDTH][VIDEO_MAX_HEIGHT][VIDEO_MAX_CHANNELS]; extern void (*OnVideoInitBuffer)(guchar Mode); extern EventCallback OnVideoStartRedraw; extern void (*OnVideoWritePixel)(gushort x, gushort y, guchar r, guchar g, guchar b); From e82b05bc7bdd9d71ba93eed21cf52360305fcd6a Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 9 Jul 2024 22:19:43 +1000 Subject: [PATCH 069/166] pcm: Fix missing parameter --- pcm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcm.h b/pcm.h index 842bca3..773cddf 100644 --- a/pcm.h +++ b/pcm.h @@ -15,7 +15,7 @@ struct _PcmData { }; extern PcmData pcm; -int initPcmDevice (); +int initPcmDevice (char *wanteddevname); void readPcm (gint numsamples); #endif From 09c11b32065f6fec4a52c8615ae74aa7c26b6812 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:03:20 +1000 Subject: [PATCH 070/166] config: Add required headers for GKeyFile type --- config.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config.h b/config.h index f7c7ea5..8ece7ef 100644 --- a/config.h +++ b/config.h @@ -1,6 +1,9 @@ #ifndef _CONFIG_H_ #define _CONFIG_H_ +#include +#include + extern GKeyFile *config; void load_config_settings(GString **confpath); From 36754c2a494f90c9ae6c6cd6c368eb6984fc7708 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:03:50 +1000 Subject: [PATCH 071/166] fsk: Include complex.h for complex numbers If included before FFTW, the latter uses the native `double complex` data type for `fftw_complex`. --- fsk.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fsk.c b/fsk.c index a481f5b..2fca0d1 100644 --- a/fsk.c +++ b/fsk.c @@ -1,4 +1,5 @@ #include +#include #include #include #include From bc5d5eece2570c60f7a3e3478dd367eef14aafd8 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:05:55 +1000 Subject: [PATCH 072/166] vis: Include complex.h for native complex types --- vis.c | 1 + 1 file changed, 1 insertion(+) diff --git a/vis.c b/vis.c index 6ba6d84..219b2b0 100644 --- a/vis.c +++ b/vis.c @@ -1,4 +1,5 @@ #include +#include #include #include From 7a20379810b453dbdfd46ba5470f09612cb1d66f Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:06:46 +1000 Subject: [PATCH 073/166] video: Include complex.h for native complex types --- video.c | 1 + 1 file changed, 1 insertion(+) diff --git a/video.c b/video.c index 62bb86b..dcc0ce3 100644 --- a/video.c +++ b/video.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include From 4c50219a5f867a37bddf7a82daf43e1492f714c8 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:10:34 +1000 Subject: [PATCH 074/166] pcm: Add ALSA headers --- pcm.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pcm.h b/pcm.h index 773cddf..0c3e03b 100644 --- a/pcm.h +++ b/pcm.h @@ -1,6 +1,8 @@ #ifndef _PCM_H #define _PCM_H +#include + #include #include From 2b5b80aee951c3d17a8ea93764a891fe4575c9a0 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:10:58 +1000 Subject: [PATCH 075/166] common: Use standard data types - stdbool _Bool for booleans - stdint integer data types - double complex for complex numbers --- common.c | 31 ++++++++++++------------------- common.h | 27 +++++++++++++-------------- 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/common.c b/common.c index da94273..a5111d4 100644 --- a/common.c +++ b/common.c @@ -1,40 +1,33 @@ -#include -#include #include +#include #include -#include - -#include - -#include #include "common.h" #include "modespec.h" #include "pcm.h" -gboolean Abort = FALSE; -gboolean Adaptive = TRUE; -gboolean *HasSync = NULL; -gshort HedrShift = 0; -gboolean ManualActivated = FALSE; -gboolean ManualResync = FALSE; -guchar *StoredLum = NULL; +_Bool Abort = FALSE; +_Bool Adaptive = TRUE; +_Bool *HasSync = NULL; +_Bool ManualActivated = FALSE; +_Bool ManualResync = FALSE; +uint8_t *StoredLum = NULL; // Return the FFT bin index matching the given frequency -guint GetBin (double Freq, guint FFTLen) { +uint32_t GetBin (double Freq, uint32_t FFTLen) { return (Freq / 44100 * FFTLen); } // Sinusoid power from complex DFT coefficients -double power (fftw_complex coeff) { - return pow(coeff[0],2) + pow(coeff[1],2); +double power (double complex coeff) { + return pow(creal(coeff),2) + pow(cimag(coeff),2); } // Clip to [0..255] -guchar clip (double a) { +uint8_t clip (double a) { if (a < 0) return 0; else if (a > 255) return 255; - return (guchar)round(a); + return (uint8_t)round(a); } // Convert degrees -> radians diff --git a/common.h b/common.h index 8437f0c..9a84295 100644 --- a/common.h +++ b/common.h @@ -6,27 +6,26 @@ #define BUFLEN 4096 #define SYNCPIXLEN 1.5e-3 -#include -#include +#include +#include +#include -#include - -extern gboolean Abort; -extern gboolean Adaptive; -extern gboolean *HasSync; -extern gboolean ManualActivated; -extern gboolean ManualResync; -extern guchar *StoredLum; +extern _Bool Abort; +extern _Bool Adaptive; +extern _Bool *HasSync; +extern _Bool ManualActivated; +extern _Bool ManualResync; +extern uint8_t *StoredLum; // Various callback function types for various events. typedef void (*EventCallback)(void); typedef void (*TextStatusCallback)(const char* status); typedef void (*UpdateVUCallback)(double* Power, int FFTLen, int WinIdx); -double power (fftw_complex coeff); -guchar clip (double a); -double deg2rad (double Deg); -guint GetBin (double Freq, guint FFTLen); +double power (double complex coeff); +uint8_t clip (double a); +double deg2rad (double Deg); +uint32_t GetBin (double Freq, uint32_t FFTLen); void ensure_dir_exists(const char *dir); From 986d1edb98764f992ea7e0eb32e0f1f92cb7130b Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:23:53 +1000 Subject: [PATCH 076/166] fsk: Pass through buffer size to prevent overflow --- fsk.c | 11 +++++++---- fsk.h | 2 +- listen.c | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/fsk.c b/fsk.c index 2fca0d1..d90d243 100644 --- a/fsk.c +++ b/fsk.c @@ -23,7 +23,7 @@ * */ -void GetFSK (char *dest) { +void GetFSK (char* const dest, uint8_t dest_sz) { guint FFTLen = 2048, i=0, LoBin, HiBin, MidBin, TestNum=0, TestPtr=0; guchar Bit = 0, AsciiByte = 0, BytePtr = 0, TestBits[24] = {0}, BitPtr=0; @@ -102,7 +102,9 @@ void GetFSK (char *dest) { if (++BitPtr == 6) { if (AsciiByte < 0x0d || BytePtr > 9) break; - dest[BytePtr] = AsciiByte + 0x20; + if (BytePtr < dest_sz) { + dest[BytePtr] = AsciiByte + 0x20; + } BitPtr = 0; AsciiByte = 0; BytePtr ++; @@ -111,6 +113,7 @@ void GetFSK (char *dest) { } - dest[BytePtr] = '\0'; - + if (BytePtr < dest_sz) { + dest[BytePtr] = '\0'; + } } diff --git a/fsk.h b/fsk.h index e6a061d..a6e23e6 100644 --- a/fsk.h +++ b/fsk.h @@ -1,6 +1,6 @@ #ifndef _FSK_H_ #define _FSK_H_ -void GetFSK (char *dest); +void GetFSK (char* const dest, uint8_t dest_sz); #endif diff --git a/listen.c b/listen.c index 3b9590a..bf73187 100644 --- a/listen.c +++ b/listen.c @@ -141,7 +141,7 @@ void *Listen() { if (OnListenerStatusChange) { OnListenerStatusChange("Receiving FSK ID..."); } - GetFSK(id); + GetFSK(id, sizeof(id)); printf(" FSKID \"%s\"\n",id); OnListenerReceivedFSKID(id); } From 7081a4e31cec37c8ea466ac0d0e06320cb773d7a Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:26:00 +1000 Subject: [PATCH 077/166] fsk: Replace glib types with stdint --- fsk.c | 18 ++++++++---------- fsk.h | 2 ++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/fsk.c b/fsk.c index d90d243..b4025c6 100644 --- a/fsk.c +++ b/fsk.c @@ -1,10 +1,10 @@ #include #include #include -#include -#include #include +#include + #include #include "common.h" @@ -14,21 +14,19 @@ #include "pic.h" /* - * * Decode FSK ID * - * * The FSK IDs are 6-bit bytes, LSB first, 45.45 baud (22 ms/bit), 1900 Hz = 1, 2100 Hz = 0 - * * Text data starts with 20 2A and ends in 01 - * * Add 0x20 and the data becomes ASCII - * + * - The FSK IDs are 6-bit bytes, LSB first, 45.45 baud (22 ms/bit), 1900 Hz = 1, 2100 Hz = 0 + * - Text data starts with 20 2A and ends in 01 + * - Add 0x20 and the data becomes ASCII */ void GetFSK (char* const dest, uint8_t dest_sz) { - guint FFTLen = 2048, i=0, LoBin, HiBin, MidBin, TestNum=0, TestPtr=0; - guchar Bit = 0, AsciiByte = 0, BytePtr = 0, TestBits[24] = {0}, BitPtr=0; + uint32_t FFTLen = 2048, i=0, LoBin, HiBin, MidBin, TestNum=0, TestPtr=0; + uint8_t Bit = 0, AsciiByte = 0, BytePtr = 0, TestBits[24] = {0}, BitPtr=0; double HiPow,LoPow,Hann[970]; - gboolean InSync = FALSE; + _Bool InSync = FALSE; // Bit-reversion lookup table static const guchar BitRev[] = { diff --git a/fsk.h b/fsk.h index a6e23e6..c3fcee3 100644 --- a/fsk.h +++ b/fsk.h @@ -1,6 +1,8 @@ #ifndef _FSK_H_ #define _FSK_H_ +#include + void GetFSK (char* const dest, uint8_t dest_sz); #endif From b341aaef2cac4a2a484d59c5e2dcf9ee33b1ab58 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:27:21 +1000 Subject: [PATCH 078/166] fsk: Replace FFTLen variable with #defined constant We don't need a variable as this never changes. --- fsk.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/fsk.c b/fsk.c index b4025c6..6929d37 100644 --- a/fsk.c +++ b/fsk.c @@ -13,6 +13,8 @@ #include "pcm.h" #include "pic.h" +#define FSK_FFT_LEN (2048) + /* * Decode FSK ID * @@ -23,7 +25,7 @@ void GetFSK (char* const dest, uint8_t dest_sz) { - uint32_t FFTLen = 2048, i=0, LoBin, HiBin, MidBin, TestNum=0, TestPtr=0; + uint32_t i=0, LoBin, HiBin, MidBin, TestNum=0, TestPtr=0; uint8_t Bit = 0, AsciiByte = 0, BytePtr = 0, TestBits[24] = {0}, BitPtr=0; double HiPow,LoPow,Hann[970]; _Bool InSync = FALSE; @@ -39,7 +41,7 @@ void GetFSK (char* const dest, uint8_t dest_sz) { 0x03, 0x23, 0x13, 0x33, 0x0b, 0x2b, 0x1b, 0x3b, 0x07, 0x27, 0x17, 0x37, 0x0f, 0x2f, 0x1f, 0x3f }; - for (i = 0; i < FFTLen; i++) fft.in[i] = 0; + for (i = 0; i < FSK_FFT_LEN; i++) fft.in[i] = 0; // Create 22ms Hann window for (i = 0; i < 970; i++) Hann[i] = 0.5 * (1 - cos( 2 * M_PI * i / 969.0 ) ); @@ -62,9 +64,9 @@ void GetFSK (char* const dest, uint8_t dest_sz) { // FFT of last 22 ms fftw_execute(fft.Plan2048); - LoBin = GetBin(1900+CurrentPic.HedrShift, FFTLen)-1; - MidBin = GetBin(2000+CurrentPic.HedrShift, FFTLen); - HiBin = GetBin(2100+CurrentPic.HedrShift, FFTLen)+1; + LoBin = GetBin(1900+CurrentPic.HedrShift, FSK_FFT_LEN)-1; + MidBin = GetBin(2000+CurrentPic.HedrShift, FSK_FFT_LEN); + HiBin = GetBin(2100+CurrentPic.HedrShift, FSK_FFT_LEN)+1; LoPow = 0; HiPow = 0; From 8f555d8a2296372d7c31d2961f1553666b38797b Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:28:52 +1000 Subject: [PATCH 079/166] .gitignore: Ignore archives --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 12ae342..fc39ca8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ rx-lum *.d *.o +*.a From 097d82d97fe13bf3758538ee9ae3602ff4031b00 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:34:11 +1000 Subject: [PATCH 080/166] vis: Replace glib types, change FFTLen to constant --- vis.c | 30 ++++++++++++++++-------------- vis.h | 10 +++++----- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/vis.c b/vis.c index 219b2b0..85eb177 100644 --- a/vis.c +++ b/vis.c @@ -22,20 +22,22 @@ TextStatusCallback OnVisStatusChange; EventCallback OnVisIdentified; UpdateVUCallback OnVisPowerComputed; -int VIS; -gboolean VisAutoStart; -static double VisPower[2048] = {0}; +uint8_t VIS; +_Bool VisAutoStart; -guchar GetVIS () { +#define VIS_FFT_LEN (2048) +static double VisPower[VIS_FFT_LEN] = {0}; - int ptr=0; - int Parity = 0, HedrPtr = 0; - guint FFTLen = 2048, i=0, j=0, k=0, MaxBin = 0; +uint8_t GetVIS () { + + int32_t ptr=0; + int32_t Parity = 0, HedrPtr = 0; + uint32_t i=0, j=0, k=0, MaxBin = 0; double HedrBuf[100] = {0}, tone[100] = {0}, Hann[882] = {0}; - gboolean gotvis = FALSE; - guchar Bit[8] = {0}, ParityBit = 0; + _Bool gotvis = FALSE; + uint8_t Bit[8] = {0}, ParityBit = 0; - for (i = 0; i < FFTLen; i++) fft.in[i] = 0; + for (i = 0; i < VIS_FFT_LEN; i++) fft.in[i] = 0; // Create 20ms Hann window for (i = 0; i < 882; i++) Hann[i] = 0.5 * (1 - cos( (2 * M_PI * (double)i) / 881 ) ); @@ -63,22 +65,22 @@ guchar GetVIS () { // Find the bin with most power MaxBin = 0; - for (i = 0; i <= GetBin(6000, FFTLen); i++) { + for (i = 0; i <= GetBin(6000, VIS_FFT_LEN); i++) { VisPower[i] = power(fft.out[i]); - if ( (i >= GetBin(500,FFTLen) && i < GetBin(3300,FFTLen)) && + if ( (i >= GetBin(500,VIS_FFT_LEN) && i < GetBin(3300,VIS_FFT_LEN)) && (MaxBin == 0 || VisPower[i] > VisPower[MaxBin])) MaxBin = i; } // Find the peak frequency by Gaussian interpolation - if (MaxBin > GetBin(500, FFTLen) && MaxBin < GetBin(3300, FFTLen) && + if (MaxBin > GetBin(500, VIS_FFT_LEN) && MaxBin < GetBin(3300, VIS_FFT_LEN) && VisPower[MaxBin] > 0 && VisPower[MaxBin+1] > 0 && VisPower[MaxBin-1] > 0) HedrBuf[HedrPtr] = MaxBin + (log( VisPower[MaxBin + 1] / VisPower[MaxBin - 1] )) / (2 * log( pow(VisPower[MaxBin], 2) / (VisPower[MaxBin + 1] * VisPower[MaxBin - 1]))); else HedrBuf[HedrPtr] = HedrBuf[(HedrPtr-1) % 45]; // In Hertz - HedrBuf[HedrPtr] = HedrBuf[HedrPtr] / FFTLen * 44100; + HedrBuf[HedrPtr] = HedrBuf[HedrPtr] / VIS_FFT_LEN * 44100; // Header buffer holds 45 * 10 msec = 450 msec HedrPtr = (HedrPtr + 1) % 45; diff --git a/vis.h b/vis.h index 1d83321..c977d15 100644 --- a/vis.h +++ b/vis.h @@ -1,15 +1,15 @@ #ifndef _VIS_H_ #define _VIS_H_ -#include -#include +#include +#include extern TextStatusCallback OnVisStatusChange; extern EventCallback OnVisIdentified; extern UpdateVUCallback OnVisPowerComputed; -extern int VIS; -extern gboolean VisAutoStart; +extern uint8_t VIS; +extern _Bool VisAutoStart; -guchar GetVIS (); +uint8_t GetVIS(); #endif From 283b77aeadc31c55cd12ea8a9acdc6fabbf52dec Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:42:33 +1000 Subject: [PATCH 081/166] video: Drop glib types, replace unchanged variables with constants --- video.c | 93 +++++++++++++++++++++++++++++---------------------------- video.h | 10 +++---- 2 files changed, 52 insertions(+), 51 deletions(-) diff --git a/video.c b/video.c index dcc0ce3..ee409a6 100644 --- a/video.c +++ b/video.c @@ -17,20 +17,21 @@ #define VIDEO_MAX_WIDTH (800) #define VIDEO_MAX_HEIGHT (616) #define VIDEO_MAX_CHANNELS (3) +#define VIDEO_FFT_LEN (1024) -static guchar VideoImage[VIDEO_MAX_WIDTH][VIDEO_MAX_HEIGHT][VIDEO_MAX_CHANNELS] = {{{0}}}; -void (*OnVideoInitBuffer)(guchar Mode); -void (*OnVideoWritePixel)(gushort x, gushort y, guchar r, guchar g, guchar b); +static uint8_t VideoImage[VIDEO_MAX_WIDTH][VIDEO_MAX_HEIGHT][VIDEO_MAX_CHANNELS] = {{{0}}}; +void (*OnVideoInitBuffer)(uint8_t Mode); +void (*OnVideoWritePixel)(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b); EventCallback OnVideoStartRedraw; EventCallback OnVideoRefresh; UpdateVUCallback OnVideoPowerCalculated; typedef struct { - int X; - int Y; - int Time; - guchar Channel; - gboolean Last; + int32_t Time; + int16_t X; + int16_t Y; + uint8_t Channel; + _Bool Last; } _PixelGrid; @@ -41,31 +42,31 @@ typedef struct { * Redraw: FALSE = Apply windowing and FFT to the signal, TRUE = Redraw from cached FFT data * returns: TRUE when finished, FALSE when aborted */ -gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { - guint MaxBin = 0; - guint VideoPlusNoiseBins=0, ReceiverBins=0, NoiseOnlyBins=0; - guint n=0; - guint SyncSampleNum; - guint i=0, j=0; - guint FFTLen=1024, WinLength=0; - guint SyncTargetBin; - int SampleNum, Length, NumChans; - int x = 0, y = 0, tx=0, k=0; - double Hann[7][1024] = {{0}}; +_Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { + uint32_t MaxBin = 0; + uint32_t VideoPlusNoiseBins=0, ReceiverBins=0, NoiseOnlyBins=0; + uint32_t n=0; + uint32_t SyncSampleNum; + uint32_t i=0, j=0; + uint32_t WinLength=0; + uint32_t SyncTargetBin; + int32_t SampleNum, Length, NumChans; + int32_t x = 0, y = 0, tx=0, k=0; + double Hann[7][VIDEO_FFT_LEN] = {{0}}; double Freq = 0; //double PrevFreq = 0, InterpFreq = 0; - int NextSNRtime = 0, NextSyncTime = 0; + int32_t NextSNRtime = 0, NextSyncTime = 0; double Praw, Psync; - double Power[1024] = {0}; + double Power[VIDEO_FFT_LEN] = {0}; double Pvideo_plus_noise=0, Pnoise_only=0, Pnoise=0, Psignal=0; double SNR = 0; double ChanStart[4] = {0}, ChanLen[4] = {0}; - guchar Channel = 0, WinIdx = 0; + uint8_t Channel = 0, WinIdx = 0; _PixelGrid *PixelGrid = calloc( ModeSpec[Mode].ImgWidth * ModeSpec[Mode].NumLines * 3, sizeof(_PixelGrid) ); // Initialize Hann windows of different lengths - gushort HannLens[7] = { 48, 64, 96, 128, 256, 512, 1024 }; + uint16_t HannLens[7] = { 48, 64, 96, 128, 256, 512, 1024 }; for (j = 0; j < 7; j++) for (i = 0; i < HannLens[j]; i++) Hann[j][i] = 0.5 * (1 - cos( (2 * M_PI * i) / (HannLens[j] - 1)) ); @@ -144,14 +145,14 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { } // Plan ahead the time instants (in samples) at which to take pixels out - int PixelIdx = 0; + int32_t PixelIdx = 0; if (NumChans == 4){ //Woking on PD* mode //Each radio frame encodes two image lines for (y = 0; y < ModeSpec[Mode].NumLines; y += 2){ for (Channel = 0; Channel < NumChans; Channel++){ for (x = 0; x < ModeSpec[Mode].ImgWidth; x++){ - PixelGrid[PixelIdx].Time = (int)round(Rate * ( y/2 * ModeSpec[Mode].LineTime + ChanStart[Channel] + + PixelGrid[PixelIdx].Time = (int32_t)round(Rate * ( y/2 * ModeSpec[Mode].LineTime + ChanStart[Channel] + ModeSpec[Mode].PixelTime * 1.0 * (x + 0.5))) + Skip; if (Channel == 0) { @@ -207,7 +208,7 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { PixelGrid[PixelIdx].Channel = Channel; } - PixelGrid[PixelIdx].Time = (int)round(Rate * (y * ModeSpec[Mode].LineTime + ChanStart[Channel] + + PixelGrid[PixelIdx].Time = (int32_t)round(Rate * (y * ModeSpec[Mode].LineTime + ChanStart[Channel] + (1.0 * (x - .5) / ModeSpec[Mode].ImgWidth * ChanLen[PixelGrid[PixelIdx].Channel]))) + Skip; PixelGrid[PixelIdx].X = x; @@ -255,7 +256,7 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { Length = ModeSpec[Mode].LineTime * ModeSpec[Mode].NumLines/2 * 44100; else Length = ModeSpec[Mode].LineTime * ModeSpec[Mode].NumLines * 44100; - SyncTargetBin = GetBin(1200 + CurrentPic.HedrShift, FFTLen); + SyncTargetBin = GetBin(1200 + CurrentPic.HedrShift, VIDEO_FFT_LEN); Abort = FALSE; SyncSampleNum = 0; @@ -275,20 +276,20 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { Praw = Psync = 0; - memset(fft.in, 0, sizeof(double)*FFTLen); + memset(fft.in, 0, sizeof(double)*VIDEO_FFT_LEN); // Hann window for (i = 0; i < 64; i++) fft.in[i] = pcm.Buffer[pcm.WindowPtr+i-32] / 32768.0 * Hann[1][i]; fftw_execute(fft.Plan1024); - for (i=GetBin(1500+CurrentPic.HedrShift,FFTLen); i<=GetBin(2300+CurrentPic.HedrShift, FFTLen); i++) + for (i=GetBin(1500+CurrentPic.HedrShift,VIDEO_FFT_LEN); i<=GetBin(2300+CurrentPic.HedrShift, VIDEO_FFT_LEN); i++) Praw += power(fft.out[i]); for (i=SyncTargetBin-1; i<=SyncTargetBin+1; i++) Psync += power(fft.out[i]) * (1- .5*abs((gint)(SyncTargetBin-i))); - Praw /= (GetBin(2300+CurrentPic.HedrShift, FFTLen) - GetBin(1500+CurrentPic.HedrShift, FFTLen)); + Praw /= (GetBin(2300+CurrentPic.HedrShift, VIDEO_FFT_LEN) - GetBin(1500+CurrentPic.HedrShift, VIDEO_FFT_LEN)); Psync /= 2.0; // If there is more than twice the amount of power per Hz in the @@ -306,35 +307,35 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { if (SampleNum == NextSNRtime) { - memset(fft.in, 0, sizeof(double)*FFTLen); + memset(fft.in, 0, sizeof(double)*VIDEO_FFT_LEN); // Apply Hann window - for (i = 0; i < FFTLen; i++) fft.in[i] = pcm.Buffer[pcm.WindowPtr + i - FFTLen/2] / 32768.0 * Hann[6][i]; + for (i = 0; i < VIDEO_FFT_LEN; i++) fft.in[i] = pcm.Buffer[pcm.WindowPtr + i - VIDEO_FFT_LEN/2] / 32768.0 * Hann[6][i]; fftw_execute(fft.Plan1024); // Calculate video-plus-noise power (1500-2300 Hz) Pvideo_plus_noise = 0; - for (n = GetBin(1500+CurrentPic.HedrShift, FFTLen); n <= GetBin(2300+CurrentPic.HedrShift, FFTLen); n++) + for (n = GetBin(1500+CurrentPic.HedrShift, VIDEO_FFT_LEN); n <= GetBin(2300+CurrentPic.HedrShift, VIDEO_FFT_LEN); n++) Pvideo_plus_noise += power(fft.out[n]); // Calculate noise-only power (400-800 Hz + 2700-3400 Hz) Pnoise_only = 0; - for (n = GetBin(400+CurrentPic.HedrShift, FFTLen); n <= GetBin(800+CurrentPic.HedrShift, FFTLen); n++) + for (n = GetBin(400+CurrentPic.HedrShift, VIDEO_FFT_LEN); n <= GetBin(800+CurrentPic.HedrShift, VIDEO_FFT_LEN); n++) Pnoise_only += power(fft.out[n]); - for (n = GetBin(2700+CurrentPic.HedrShift, FFTLen); n <= GetBin(3400+CurrentPic.HedrShift, FFTLen); n++) + for (n = GetBin(2700+CurrentPic.HedrShift, VIDEO_FFT_LEN); n <= GetBin(3400+CurrentPic.HedrShift, VIDEO_FFT_LEN); n++) Pnoise_only += power(fft.out[n]); // Bandwidths - VideoPlusNoiseBins = GetBin(2300, FFTLen) - GetBin(1500, FFTLen) + 1; + VideoPlusNoiseBins = GetBin(2300, VIDEO_FFT_LEN) - GetBin(1500, VIDEO_FFT_LEN) + 1; - NoiseOnlyBins = GetBin(800, FFTLen) - GetBin(400, FFTLen) + 1 + - GetBin(3400, FFTLen) - GetBin(2700, FFTLen) + 1; + NoiseOnlyBins = GetBin(800, VIDEO_FFT_LEN) - GetBin(400, VIDEO_FFT_LEN) + 1 + + GetBin(3400, VIDEO_FFT_LEN) - GetBin(2700, VIDEO_FFT_LEN) + 1; - ReceiverBins = GetBin(3400, FFTLen) - GetBin(400, FFTLen); + ReceiverBins = GetBin(3400, VIDEO_FFT_LEN) - GetBin(400, VIDEO_FFT_LEN); // Eq 15 Pnoise = Pnoise_only * (1.0 * ReceiverBins / NoiseOnlyBins); @@ -369,7 +370,7 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { // Minimum winlength can be doubled for Scottie DX if (Mode == SDX && WinIdx < 6) WinIdx++; - memset(fft.in, 0, sizeof(double)*FFTLen); + memset(fft.in, 0, sizeof(double)*VIDEO_FFT_LEN); memset(Power, 0, sizeof(double)*1024); // Apply window function @@ -382,7 +383,7 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { MaxBin = 0; // Find the bin with most power - for (n = GetBin(1500 + CurrentPic.HedrShift, FFTLen) - 1; n <= GetBin(2300 + CurrentPic.HedrShift, FFTLen) + 1; n++) { + for (n = GetBin(1500 + CurrentPic.HedrShift, VIDEO_FFT_LEN) - 1; n <= GetBin(2300 + CurrentPic.HedrShift, VIDEO_FFT_LEN) + 1; n++) { Power[n] = power(fft.out[n]); if (MaxBin == 0 || Power[n] > Power[MaxBin]) MaxBin = n; @@ -390,14 +391,14 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { } // Find the peak frequency by Gaussian interpolation - if (MaxBin > GetBin(1500 + CurrentPic.HedrShift, FFTLen) - 1 && MaxBin < GetBin(2300 + CurrentPic.HedrShift, FFTLen) + 1) { + if (MaxBin > GetBin(1500 + CurrentPic.HedrShift, VIDEO_FFT_LEN) - 1 && MaxBin < GetBin(2300 + CurrentPic.HedrShift, VIDEO_FFT_LEN) + 1) { Freq = MaxBin + (log( Power[MaxBin + 1] / Power[MaxBin - 1] )) / (2 * log( pow(Power[MaxBin], 2) / (Power[MaxBin + 1] * Power[MaxBin - 1]))); // In Hertz - Freq = Freq / FFTLen * 44100; + Freq = Freq / VIDEO_FFT_LEN * 44100; } else { // Clip if out of bounds - Freq = ( (MaxBin > GetBin(1900 + CurrentPic.HedrShift, FFTLen)) ? 2300 : 1500 ) + CurrentPic.HedrShift; + Freq = ( (MaxBin > GetBin(1900 + CurrentPic.HedrShift, VIDEO_FFT_LEN)) ? 2300 : 1500 ) + CurrentPic.HedrShift; } } /* endif (SampleNum == PixelGrid[PixelIdx].Time) */ @@ -430,7 +431,7 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { // Calculate and draw pixels to pixbuf on line change if (x == ModeSpec[Mode].ImgWidth - 1 || PixelGrid[PixelIdx].Last) { for (tx = 0; tx < ModeSpec[Mode].ImgWidth; tx++) { - guchar r = 0, g = 0, b = 0; + uint8_t r = 0, g = 0, b = 0; switch(ModeSpec[Mode].ColorEnc) { @@ -473,7 +474,7 @@ gboolean GetVideo(guchar Mode, double Rate, int Skip, gboolean Redraw) { } /* endif (SampleNum == PixelGrid[PixelIdx].Time) */ if (!Redraw && (SampleNum % 8820 == 0) && OnVideoPowerCalculated) { - OnVideoPowerCalculated(Power, FFTLen, WinIdx); + OnVideoPowerCalculated(Power, VIDEO_FFT_LEN, WinIdx); } if (Abort) { diff --git a/video.h b/video.h index d5e94b7..37ad9d8 100644 --- a/video.h +++ b/video.h @@ -1,15 +1,15 @@ #ifndef _VIDEO_H_ #define _VIDEO_H_ -#include -#include +#include +#include -extern void (*OnVideoInitBuffer)(guchar Mode); +extern void (*OnVideoInitBuffer)(uint8_t Mode); extern EventCallback OnVideoStartRedraw; -extern void (*OnVideoWritePixel)(gushort x, gushort y, guchar r, guchar g, guchar b); +extern void (*OnVideoWritePixel)(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b); extern EventCallback OnVideoRefresh; extern UpdateVUCallback OnVideoPowerCalculated; -gboolean GetVideo (guchar Mode, double Rate, int Skip, gboolean Redraw); +_Bool GetVideo (uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw); #endif From a90d477f1e59459ed4e79119198bf340cd95a1dd Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:45:05 +1000 Subject: [PATCH 082/166] sync: Replace glib types with stdint/stdbool --- sync.c | 26 +++++++++++++------------- sync.h | 5 ++--- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/sync.c b/sync.c index 4ea587b..c0a1ee0 100644 --- a/sync.c +++ b/sync.c @@ -15,19 +15,19 @@ * returns adjusted sample rate * */ -double FindSync (guchar Mode, double Rate, int *Skip) { - - int LineWidth = ModeSpec[Mode].LineTime / ModeSpec[Mode].SyncTime * 4; - int x,y; - int q, d, qMost, dMost; - gushort xAcc[700] = {0}; - gushort lines[600][(MAXSLANT-MINSLANT)*2]; - gushort cy, cx, Retries = 0; - gboolean SyncImg[700][630] = {{FALSE}}; +double FindSync (uint8_t Mode, double Rate, int32_t *Skip) { + + int32_t LineWidth = ModeSpec[Mode].LineTime / ModeSpec[Mode].SyncTime * 4; + int32_t x,y; + int32_t q, d, qMost, dMost; + uint16_t xAcc[700] = {0}; + uint16_t lines[600][(MAXSLANT-MINSLANT)*2]; + uint16_t cy, cx, Retries = 0; + _Bool SyncImg[700][630] = {{FALSE}}; double t=0, slantAngle, s; double ConvoFilter[8] = { 1,1,1,1,-1,-1,-1,-1 }; double convd, maxconvd=0; - int xmax=0; + int32_t xmax=0; // Repeat until slant < 0.5° or until we give up while (TRUE) { @@ -37,7 +37,7 @@ double FindSync (guchar Mode, double Rate, int *Skip) { for (y=0; y maxconvd) { maxconvd = convd; xmax = x+4; diff --git a/sync.h b/sync.h index b80e123..44fbcb9 100644 --- a/sync.h +++ b/sync.h @@ -1,9 +1,8 @@ #ifndef _SYNC_H_ #define _SYNC_H_ -#include -#include +#include -double FindSync (guchar Mode, double Rate, int *Skip); +double FindSync (uint8_t Mode, double Rate, int32_t *Skip); #endif From fdbb07fc4abdad59a7f10b065271ba91bae4421f Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:47:35 +1000 Subject: [PATCH 083/166] pic: Replace glib types and clean up - Sort the struct members from biggest alignment to smallest to better respect alignment constraints on some architectures (e.g. ARM) - Define a constant for the length of `timestr` --- pic.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pic.h b/pic.h index 52d5b2c..a1875af 100644 --- a/pic.h +++ b/pic.h @@ -1,16 +1,17 @@ #ifndef _PIC_H_ #define _PIC_H_ -#include -#include +#include + +#define PIC_TIMESTR_SZ (40) typedef struct _PicMeta PicMeta; struct _PicMeta { - gshort HedrShift; - guchar Mode; double Rate; - int Skip; - char timestr[40]; + int32_t Skip; + int16_t HedrShift; + uint8_t Mode; + char timestr[PIC_TIMESTR_SZ]; }; extern PicMeta CurrentPic; From a8ed69c656a4ab637a10ed6fba15759b2261fa6b Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:53:01 +1000 Subject: [PATCH 084/166] fsk: Use stdbool `true` and `false` --- fsk.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fsk.c b/fsk.c index 6929d37..272064d 100644 --- a/fsk.c +++ b/fsk.c @@ -28,7 +28,7 @@ void GetFSK (char* const dest, uint8_t dest_sz) { uint32_t i=0, LoBin, HiBin, MidBin, TestNum=0, TestPtr=0; uint8_t Bit = 0, AsciiByte = 0, BytePtr = 0, TestBits[24] = {0}, BitPtr=0; double HiPow,LoPow,Hann[970]; - _Bool InSync = FALSE; + _Bool InSync = false; // Bit-reversion lookup table static const guchar BitRev[] = { @@ -46,7 +46,7 @@ void GetFSK (char* const dest, uint8_t dest_sz) { // Create 22ms Hann window for (i = 0; i < 970; i++) Hann[i] = 0.5 * (1 - cos( 2 * M_PI * i / 969.0 ) ); - while ( TRUE ) { + while ( true ) { // Read data from DSP readPcm(InSync ? 970: 485); @@ -88,7 +88,7 @@ void GetFSK (char* const dest, uint8_t dest_sz) { for (i=0; i<12; i++) TestNum |= TestBits[(TestPtr - (23-i*2)) % 24] << (11-i); if (BitRev[(TestNum >> 6) & 0x3f] == 0x20 && BitRev[TestNum & 0x3f] == 0x2a) { - InSync = TRUE; + InSync = true; AsciiByte = 0; BitPtr = 0; BytePtr = 0; From b983e0163781c3d97f93db40f007a3f64fbca779 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:53:35 +1000 Subject: [PATCH 085/166] fsk: Missed one glib type --- fsk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fsk.c b/fsk.c index 272064d..ddeeba1 100644 --- a/fsk.c +++ b/fsk.c @@ -31,7 +31,7 @@ void GetFSK (char* const dest, uint8_t dest_sz) { _Bool InSync = false; // Bit-reversion lookup table - static const guchar BitRev[] = { + static const uint8_t BitRev[] = { 0x00, 0x20, 0x10, 0x30, 0x08, 0x28, 0x18, 0x38, 0x04, 0x24, 0x14, 0x34, 0x0c, 0x2c, 0x1c, 0x3c, 0x02, 0x22, 0x12, 0x32, 0x0a, 0x2a, 0x1a, 0x3a, From 0fee8b38055ef21f1058cc2d43afa69e279ce3c5 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:54:50 +1000 Subject: [PATCH 086/166] pcm: Use stdint and stdbool types --- pcm.c | 24 ++++++++++++------------ pcm.h | 14 +++++++------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pcm.c b/pcm.c index ac56945..2514cca 100644 --- a/pcm.c +++ b/pcm.c @@ -18,10 +18,10 @@ PcmData pcm; // Capture fresh PCM data to buffer -void readPcm(gint numsamples) { +void readPcm(int32_t numsamples) { - int samplesread, i; - gint32 tmp[BUFLEN]; // Holds one or two 16-bit channels, will be ANDed to single channel + int32_t samplesread, i; + int32_t tmp[BUFLEN]; // Holds one or two 16-bit channels, will be ANDed to single channel samplesread = snd_pcm_readi(pcm.handle, tmp, (pcm.WindowPtr == 0 ? BUFLEN : numsamples)); @@ -34,7 +34,7 @@ void readPcm(gint numsamples) { if (pcm.OnPCMAbort) { pcm.OnPCMAbort("ALSA Error"); } - Abort = TRUE; + Abort = true; pthread_exit(NULL); } else @@ -45,7 +45,7 @@ void readPcm(gint numsamples) { if (pcm.OnPCMDrop) { pcm.OnPCMDrop(); } - pcm.BufferDrop = TRUE; + pcm.BufferDrop = true; } } @@ -72,30 +72,30 @@ void readPcm(gint numsamples) { // 0 = opened ok // -1 = opened, but suboptimal // -2 = couldn't be opened -int initPcmDevice(char *wanteddevname) { +int32_t initPcmDevice(char *wanteddevname) { snd_pcm_hw_params_t *hwparams; char pcm_name[30]; unsigned int exact_rate = 44100; int card; - gboolean found; + _Bool found; char *cardname; - pcm.BufferDrop = FALSE; + pcm.BufferDrop = false; snd_pcm_hw_params_alloca(&hwparams); card = -1; - found = FALSE; + found = false; if (strcmp(wanteddevname,"default") == 0) { - found=TRUE; + found=true; } else { do { snd_card_next(&card); if (card != -1) { snd_card_get_name(card,&cardname); if (strcmp(cardname, wanteddevname) == 0) { - found=TRUE; + found=true; break; } } @@ -150,7 +150,7 @@ int initPcmDevice(char *wanteddevname) { return(-2); } - pcm.Buffer = calloc( BUFLEN, sizeof(gint16)); + pcm.Buffer = calloc( BUFLEN, sizeof(int16_t)); memset(pcm.Buffer, 0, BUFLEN); if (exact_rate != 44100) { diff --git a/pcm.h b/pcm.h index 0c3e03b..4dc2197 100644 --- a/pcm.h +++ b/pcm.h @@ -3,21 +3,21 @@ #include -#include -#include +#include +#include typedef struct _PcmData PcmData; struct _PcmData { snd_pcm_t *handle; - gint16 *Buffer; + int16_t *Buffer; TextStatusCallback OnPCMAbort; EventCallback OnPCMDrop; - int WindowPtr; - gboolean BufferDrop; + int32_t WindowPtr; + _Bool BufferDrop; }; extern PcmData pcm; -int initPcmDevice (char *wanteddevname); -void readPcm (gint numsamples); +int32_t initPcmDevice (char *wanteddevname); +void readPcm (int32_t numsamples); #endif From 1012734139f5ab46c4f22e9fd291eb83ec1b5557 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:55:51 +1000 Subject: [PATCH 087/166] pcm: Drop config.h --- pcm.c | 1 - 1 file changed, 1 deletion(-) diff --git a/pcm.c b/pcm.c index 2514cca..94ea7b0 100644 --- a/pcm.c +++ b/pcm.c @@ -7,7 +7,6 @@ #include #include "common.h" -#include "config.h" #include "pcm.h" /* From e03a270936f010823b09a58ff51d625ae5d1030e Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 08:58:08 +1000 Subject: [PATCH 088/166] pcm: Bring in pthread.h for pthread_exit. --- pcm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/pcm.c b/pcm.c index 94ea7b0..36d2241 100644 --- a/pcm.c +++ b/pcm.c @@ -1,6 +1,7 @@ #include #include #include +#include #include From 4e90a4cf201674fd6b7348636d151c18217ae2c9 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 09:03:01 +1000 Subject: [PATCH 089/166] common: Use stdbool singletons --- common.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common.c b/common.c index a5111d4..2062938 100644 --- a/common.c +++ b/common.c @@ -6,11 +6,11 @@ #include "modespec.h" #include "pcm.h" -_Bool Abort = FALSE; -_Bool Adaptive = TRUE; +_Bool Abort = false; +_Bool Adaptive = true; _Bool *HasSync = NULL; -_Bool ManualActivated = FALSE; -_Bool ManualResync = FALSE; +_Bool ManualActivated = false; +_Bool ManualResync = false; uint8_t *StoredLum = NULL; // Return the FFT bin index matching the given frequency From 43dd7b1d66df20dec0d34b46622830510a81d31f Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 09:04:05 +1000 Subject: [PATCH 090/166] sync: Use stdbool singletons --- sync.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sync.c b/sync.c index c0a1ee0..2e49ee6 100644 --- a/sync.c +++ b/sync.c @@ -23,14 +23,14 @@ double FindSync (uint8_t Mode, double Rate, int32_t *Skip) { uint16_t xAcc[700] = {0}; uint16_t lines[600][(MAXSLANT-MINSLANT)*2]; uint16_t cy, cx, Retries = 0; - _Bool SyncImg[700][630] = {{FALSE}}; + _Bool SyncImg[700][630] = {{false}}; double t=0, slantAngle, s; double ConvoFilter[8] = { 1,1,1,1,-1,-1,-1,-1 }; double convd, maxconvd=0; int32_t xmax=0; // Repeat until slant < 0.5° or until we give up - while (TRUE) { + while (true) { // Draw the 2D sync signal at current rate From 423ab2f1ad1c4d9246360a9743173cc0de8713a2 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 09:04:43 +1000 Subject: [PATCH 091/166] vis: Use stdbool singletons --- vis.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vis.c b/vis.c index 85eb177..8cf6094 100644 --- a/vis.c +++ b/vis.c @@ -34,7 +34,7 @@ uint8_t GetVIS () { int32_t Parity = 0, HedrPtr = 0; uint32_t i=0, j=0, k=0, MaxBin = 0; double HedrBuf[100] = {0}, tone[100] = {0}, Hann[882] = {0}; - _Bool gotvis = FALSE; + _Bool gotvis = false; uint8_t Bit[8] = {0}, ParityBit = 0; for (i = 0; i < VIS_FFT_LEN; i++) fft.in[i] = 0; @@ -42,7 +42,7 @@ uint8_t GetVIS () { // Create 20ms Hann window for (i = 0; i < 882; i++) Hann[i] = 0.5 * (1 - cos( (2 * M_PI * (double)i) / 881 ) ); - ManualActivated = FALSE; + ManualActivated = false; printf("Waiting for header\n"); @@ -50,7 +50,7 @@ uint8_t GetVIS () { OnVisStatusChange("Listening"); } - while ( TRUE ) { + while ( true ) { if (Abort || ManualResync) return(0); @@ -91,7 +91,7 @@ uint8_t GetVIS () { // Is there a pattern that looks like (the end of) a calibration header + VIS? // Tolerance ±25 Hz CurrentPic.HedrShift = 0; - gotvis = FALSE; + gotvis = false; for (i = 0; i < 3; i++) { if (CurrentPic.HedrShift != 0) break; for (j = 0; j < 3; j++) { @@ -106,12 +106,12 @@ uint8_t GetVIS () { // Attempt to read VIS - gotvis = TRUE; + gotvis = true; for (k = 0; k < 8; k++) { if (tone[6*3+i+3*k] > tone[0+j] - 625 && tone[6*3+i+3*k] < tone[0+j] - 575) Bit[k] = 0; else if (tone[6*3+i+3*k] > tone[0+j] - 825 && tone[6*3+i+3*k] < tone[0+j] - 775) Bit[k] = 1; else { // erroneous bit - gotvis = FALSE; + gotvis = false; break; } } @@ -132,7 +132,7 @@ uint8_t GetVIS () { if (VISmap[VIS | VIS_PARITY_ODD] == UNKNOWN) { // Nope! printf(" Parity fail\n"); - gotvis = FALSE; + gotvis = false; } else { // Yep, that was it. Inverted parity. VIS |= VIS_PARITY_ODD; @@ -140,7 +140,7 @@ uint8_t GetVIS () { } } else if (VISmap[VIS] == UNKNOWN) { printf(" Unknown VIS\n"); - gotvis = FALSE; + gotvis = false; } else { break; } From 786bb3490464090a087f2661a1658dbceffa3180 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 09:06:07 +1000 Subject: [PATCH 092/166] video: Clean up glib types and singletons --- video.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/video.c b/video.c index ee409a6..e82f29a 100644 --- a/video.c +++ b/video.c @@ -39,8 +39,8 @@ typedef struct { * Mode: M1, M2, S1, S2, R72, R36... * Rate: exact sampling rate used * Skip: number of PCM samples to skip at the beginning (for sync phase adjustment) - * Redraw: FALSE = Apply windowing and FFT to the signal, TRUE = Redraw from cached FFT data - * returns: TRUE when finished, FALSE when aborted + * Redraw: false = Apply windowing and FFT to the signal, true = Redraw from cached FFT data + * returns: true when finished, false when aborted */ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { uint32_t MaxBin = 0; @@ -159,7 +159,7 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { PixelGrid[PixelIdx].X = x; PixelGrid[PixelIdx].Y = y; PixelGrid[PixelIdx].Channel = Channel; - PixelGrid[PixelIdx].Last = FALSE; + PixelGrid[PixelIdx].Last = false; PixelIdx++; } @@ -167,13 +167,13 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { PixelGrid[PixelIdx].X = x; PixelGrid[PixelIdx].Y = y; PixelGrid[PixelIdx].Channel = Channel; - PixelGrid[PixelIdx].Last = FALSE; + PixelGrid[PixelIdx].Last = false; PixelIdx++; PixelGrid[PixelIdx].Time = PixelGrid[PixelIdx - 1].Time; PixelGrid[PixelIdx].X = x; PixelGrid[PixelIdx].Y = y + 1; PixelGrid[PixelIdx].Channel = Channel; - PixelGrid[PixelIdx].Last = FALSE; + PixelGrid[PixelIdx].Last = false; PixelIdx++; } @@ -181,13 +181,13 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { PixelGrid[PixelIdx].X = x; PixelGrid[PixelIdx].Y = y + 1; PixelGrid[PixelIdx].Channel = 0; - PixelGrid[PixelIdx].Last = FALSE; + PixelGrid[PixelIdx].Last = false; PixelIdx++; } } } } - PixelGrid[PixelIdx - 1].Last = TRUE; + PixelGrid[PixelIdx - 1].Last = true; } else { for (y = 0; y < ModeSpec[Mode].NumLines; y++) { @@ -214,13 +214,13 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { PixelGrid[PixelIdx].X = x; PixelGrid[PixelIdx].Y = y; - PixelGrid[PixelIdx].Last = FALSE; + PixelGrid[PixelIdx].Last = false; PixelIdx++; } } } - PixelGrid[PixelIdx - 1].Last = TRUE; + PixelGrid[PixelIdx - 1].Last = true; } for (k = 0; k < PixelIdx; k++) { @@ -257,7 +257,7 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { else Length = ModeSpec[Mode].LineTime * ModeSpec[Mode].NumLines * 44100; SyncTargetBin = GetBin(1200 + CurrentPic.HedrShift, VIDEO_FFT_LEN); - Abort = FALSE; + Abort = false; SyncSampleNum = 0; // Loop through signal @@ -287,7 +287,7 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { Praw += power(fft.out[i]); for (i=SyncTargetBin-1; i<=SyncTargetBin+1; i++) - Psync += power(fft.out[i]) * (1- .5*abs((gint)(SyncTargetBin-i))); + Psync += power(fft.out[i]) * (1- .5*abs((int32_t)(SyncTargetBin-i))); Praw /= (GetBin(2300+CurrentPic.HedrShift, VIDEO_FFT_LEN) - GetBin(1500+CurrentPic.HedrShift, VIDEO_FFT_LEN)); Psync /= 2.0; @@ -479,7 +479,7 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { if (Abort) { free(PixelGrid); - return FALSE; + return false; } pcm.WindowPtr ++; @@ -487,6 +487,6 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { } free(PixelGrid); - return TRUE; + return true; } From 7ddc0db47d54fd5b97b274ac77e40c15dace556d Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 09:06:29 +1000 Subject: [PATCH 093/166] modespec: Replace glib types with stdlib Make the vis tables `const` since they never change. --- modespec.c | 4 ++-- modespec.h | 29 ++++++++++++++--------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/modespec.c b/modespec.c index 95a1952..f09f591 100644 --- a/modespec.c +++ b/modespec.c @@ -27,7 +27,7 @@ * */ -_ModeSpec ModeSpec[] = { +const _ModeSpec ModeSpec[] = { [M1] = { // N7CXI, 2000 .Name = "Martin M1", @@ -381,7 +381,7 @@ _ModeSpec ModeSpec[] = { // 0 1 2 3 4 5 6 7 8 9 A B C D E F -guchar VISmap[] = { +const uint8_t VISmap[] = { // Normal (even) parity 0, 0, R8BW, 0, R24, 0, 0, 0, R36, 0, R24BW,0, R72, 0, 0, 0, // 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 diff --git a/modespec.h b/modespec.h index 9dc3cca..5219939 100644 --- a/modespec.h +++ b/modespec.h @@ -1,8 +1,7 @@ #ifndef _MODESPEC_H_ #define _MODESPEC_H_ -#include -#include +#include // SSTV modes enum { @@ -21,24 +20,24 @@ enum { }; // VIS map -extern guchar VISmap[]; +extern const uint8_t VISmap[]; #define VIS_PARITY_ODD (1 << 7) typedef struct ModeSpec { - char *Name; - char *ShortName; - double SyncTime; - double PorchTime; - double SeptrTime; - double PixelTime; - double LineTime; - gushort ImgWidth; - gushort NumLines; - guchar LineHeight; - guchar ColorEnc; + char *Name; + char *ShortName; + double SyncTime; + double PorchTime; + double SeptrTime; + double PixelTime; + double LineTime; + uint16_t ImgWidth; + uint16_t NumLines; + uint8_t LineHeight; + uint8_t ColorEnc; } _ModeSpec; -extern _ModeSpec ModeSpec[]; +extern const _ModeSpec ModeSpec[]; #endif From cf878dca710e8717776e5b280c8156d9f130be6f Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 09:09:36 +1000 Subject: [PATCH 094/166] listen: Replace glib types and singletons --- listen.c | 26 ++++++++++++-------------- listen.h | 6 ++++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/listen.c b/listen.c index bf73187..2482503 100644 --- a/listen.c +++ b/listen.c @@ -11,8 +11,6 @@ #include #include -#include -#include #include #include @@ -38,8 +36,8 @@ EventCallback OnListenerReceiveFSK; EventCallback OnListenerAutoSlantCorrect; EventCallback OnListenerReceiveFinished; TextStatusCallback OnListenerReceivedFSKID; -gboolean ListenerAutoSlantCorrect; -gboolean ListenerEnableFSKID; +_Bool ListenerAutoSlantCorrect; +_Bool ListenerEnableFSKID; struct tm *ListenerReceiveStartTime = NULL; void StartListener(void) { @@ -53,12 +51,12 @@ void WaitForListenerStop(void) { // The thread that listens to VIS headers and calls decoders etc void *Listen() { - guchar Mode=0; + uint8_t Mode=0; time_t timet; - gboolean Finished; + _Bool Finished; char id[20]; - while (TRUE) { + while (true) { if (OnListenerWaiting) { OnListenerWaiting(); } @@ -66,7 +64,7 @@ void *Listen() { pcm.WindowPtr = 0; snd_pcm_prepare(pcm.handle); snd_pcm_start (pcm.handle); - Abort = FALSE; + Abort = false; do { @@ -78,10 +76,10 @@ void *Listen() { // If manual resync was requested, redraw image if (ManualResync) { - ManualResync = FALSE; + ManualResync = false; snd_pcm_drop(pcm.handle); printf("getvideo at %.2f skip %d\n",CurrentPic.Rate,CurrentPic.Skip); - GetVideo(CurrentPic.Mode, CurrentPic.Rate, CurrentPic.Skip, TRUE); + GetVideo(CurrentPic.Mode, CurrentPic.Rate, CurrentPic.Skip, true); if (OnListenerReceivedManual) { OnListenerReceivedManual(); } @@ -107,14 +105,14 @@ void *Listen() { // Allocate space for cached Lum free(StoredLum); - StoredLum = calloc( (int)((ModeSpec[CurrentPic.Mode].LineTime * ModeSpec[CurrentPic.Mode].NumLines + 1) * 44100), sizeof(guchar)); + StoredLum = calloc( (int)((ModeSpec[CurrentPic.Mode].LineTime * ModeSpec[CurrentPic.Mode].NumLines + 1) * 44100), sizeof(uint8_t)); if (StoredLum == NULL) { perror("Listen: Unable to allocate memory for Lum"); exit(EXIT_FAILURE); } // Allocate space for sync signal - HasSync = calloc((int)(ModeSpec[CurrentPic.Mode].LineTime * ModeSpec[CurrentPic.Mode].NumLines / (13.0/44100) +1), sizeof(gboolean)); + HasSync = calloc((int)(ModeSpec[CurrentPic.Mode].LineTime * ModeSpec[CurrentPic.Mode].NumLines / (13.0/44100) +1), sizeof(_Bool)); if (HasSync == NULL) { perror("Listen: Unable to allocate memory for sync signal"); exit(EXIT_FAILURE); @@ -129,7 +127,7 @@ void *Listen() { } printf(" getvideo @ %.1f Hz, Skip %d, HedrShift %+d Hz\n", 44100.0, 0, CurrentPic.HedrShift); - Finished = GetVideo(CurrentPic.Mode, 44100, 0, FALSE); + Finished = GetVideo(CurrentPic.Mode, 44100, 0, false); if (OnListenerReceiveFSK) { OnListenerReceiveFSK(); @@ -163,7 +161,7 @@ void *Listen() { // Final image printf(" getvideo @ %.1f Hz, Skip %d, HedrShift %+d Hz\n", CurrentPic.Rate, CurrentPic.Skip, CurrentPic.HedrShift); - GetVideo(CurrentPic.Mode, CurrentPic.Rate, CurrentPic.Skip, TRUE); + GetVideo(CurrentPic.Mode, CurrentPic.Rate, CurrentPic.Skip, true); } free (HasSync); diff --git a/listen.h b/listen.h index ec96072..646a8d9 100644 --- a/listen.h +++ b/listen.h @@ -1,6 +1,8 @@ #ifndef _LISTEN_H_ #define _LISTEN_H_ +#include + extern TextStatusCallback OnListenerStatusChange; extern EventCallback OnListenerWaiting; extern EventCallback OnListenerReceivedManual; @@ -9,8 +11,8 @@ extern EventCallback OnListenerReceiveFSK; extern EventCallback OnListenerAutoSlantCorrect; extern EventCallback OnListenerReceiveFinished; extern TextStatusCallback OnListenerReceivedFSKID; -extern gboolean ListenerAutoSlantCorrect; -extern gboolean ListenerEnableFSKID; +extern _Bool ListenerAutoSlantCorrect; +extern _Bool ListenerEnableFSKID; extern struct tm *ListenerReceiveStartTime; void *Listen (); From 7bb42a12f5c2517e66fc7863743bbecf710e47f9 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 09:10:17 +1000 Subject: [PATCH 095/166] Makefile: Move config to the GUI It's used for parsing the config file via GLIB. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3b33c10..34917fa 100644 --- a/Makefile +++ b/Makefile @@ -19,12 +19,12 @@ TARGETS = $(GUI_BIN) COMMON_CFLAGS = $(CFLAGS) $(OFLAGS) COMMON_LDFLAGS = -lfftw3 -lasound -lm -lpthread -LIB_SOURCES = common.c fft.c fsk.c listen.c modespec.c sync.c pic.c pcm.c config.c vis.c video.c +LIB_SOURCES = common.c fft.c fsk.c listen.c modespec.c sync.c pic.c pcm.c vis.c video.c LIB_OBJECTS = $(patsubst %.c,%.o,$(LIB_SOURCES)) LIB_DEPENDS = $(patsubst %.c,%.d,$(LIB_SOURCES)) LIB_CFLAGS = $(COMMON_CFLAGS) $(GLIBCFLAGS) -GUI_SOURCES = gui.c slowrx.c +GUI_SOURCES = config.c gui.c slowrx.c GUI_OBJECTS = $(patsubst %.c,%.o,$(GUI_SOURCES)) GUI_DEPENDS = $(patsubst %.c,%.d,$(GUI_SOURCES)) GUI_CFLAGS = $(COMMON_CFLAGS) $(GTKCFLAGS) -DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_4 From 4198007251e6db65092dddbeac7918b571f5afe2 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 09:10:38 +1000 Subject: [PATCH 096/166] Makefile: Drop direct inclusion of GLIB headers/libs We'll need them indirectly for GTK+, but for the back-end, they're no longer needed. --- Makefile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 34917fa..ca04578 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,6 @@ AR ?= ar RANLIB ?= ranlib CFLAGS = -Wall -Wextra -std=gnu99 -pedantic -g -GLIBCFLAGS = $(shell pkg-config --cflags glib-2.0) -GLIBLIBS = $(shell pkg-config --libs glib-2.0) GTKCFLAGS = $(shell pkg-config --cflags gtk+-3.0) GTKLIBS = $(shell pkg-config --libs gtk+-3.0) @@ -22,13 +20,13 @@ COMMON_LDFLAGS = -lfftw3 -lasound -lm -lpthread LIB_SOURCES = common.c fft.c fsk.c listen.c modespec.c sync.c pic.c pcm.c vis.c video.c LIB_OBJECTS = $(patsubst %.c,%.o,$(LIB_SOURCES)) LIB_DEPENDS = $(patsubst %.c,%.d,$(LIB_SOURCES)) -LIB_CFLAGS = $(COMMON_CFLAGS) $(GLIBCFLAGS) +LIB_CFLAGS = $(COMMON_CFLAGS) GUI_SOURCES = config.c gui.c slowrx.c GUI_OBJECTS = $(patsubst %.c,%.o,$(GUI_SOURCES)) GUI_DEPENDS = $(patsubst %.c,%.d,$(GUI_SOURCES)) GUI_CFLAGS = $(COMMON_CFLAGS) $(GTKCFLAGS) -DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_4 -GUI_LDFLAGS = $(COMMON_LDFLAGS) -lgthread-2.0 $(GLIBLIBS) $(GTKLIBS) +GUI_LDFLAGS = $(COMMON_LDFLAGS) -lgthread-2.0 $(GTKLIBS) OBJECTS = $(GUI_OBJECTS) $(LIB_OBJECTS) DEPENDS = $(GUI_DEPENDS) $(LIB_DEPENDS) From d8d904e4541a0bb08b82c0c2d734e816fbd7f5a5 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 09:23:22 +1000 Subject: [PATCH 097/166] slowrxd: Add shell for new daemon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is just the basics right now… listen for an incoming signal, dump out the image data as hex codes. --- .gitignore | 1 + Makefile | 19 +++++-- slowrxd.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 slowrxd.c diff --git a/.gitignore b/.gitignore index fc39ca8..e879464 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ slowrx +slowrxd rx rx-lum diff --git a/Makefile b/Makefile index ca04578..f5e2a88 100644 --- a/Makefile +++ b/Makefile @@ -10,9 +10,10 @@ GTKLIBS = $(shell pkg-config --libs gtk+-3.0) OFLAGS = -O3 GUI_BIN = slowrx +DAEMON_BIN = slowrxd COMMON_LIB = libslowrx.a -TARGETS = $(GUI_BIN) +TARGETS ?= $(GUI_BIN) $(DAEMON_BIN) COMMON_CFLAGS = $(CFLAGS) $(OFLAGS) COMMON_LDFLAGS = -lfftw3 -lasound -lm -lpthread @@ -25,17 +26,26 @@ LIB_CFLAGS = $(COMMON_CFLAGS) GUI_SOURCES = config.c gui.c slowrx.c GUI_OBJECTS = $(patsubst %.c,%.o,$(GUI_SOURCES)) GUI_DEPENDS = $(patsubst %.c,%.d,$(GUI_SOURCES)) -GUI_CFLAGS = $(COMMON_CFLAGS) $(GTKCFLAGS) -DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_4 +GUI_CFLAGS = $(COMMON_CFLAGS) $(LIB_CFLAGS) $(GTKCFLAGS) -DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_4 GUI_LDFLAGS = $(COMMON_LDFLAGS) -lgthread-2.0 $(GTKLIBS) -OBJECTS = $(GUI_OBJECTS) $(LIB_OBJECTS) -DEPENDS = $(GUI_DEPENDS) $(LIB_DEPENDS) +DAEMON_SOURCES = slowrxd.c +DAEMON_OBJECTS = $(patsubst %.c,%.o,$(DAEMON_SOURCES)) +DAEMON_DEPENDS = $(patsubst %.c,%.d,$(DAEMON_SOURCES)) +DAEMON_CFLAGS = $(COMMON_CFLAGS) $(LIB_CFLAGS) +DAEMON_LDFLAGS = $(COMMON_LDFLAGS) + +OBJECTS = $(GUI_OBJECTS) $(LIB_OBJECTS) $(DAEMON_OBJECTS) +DEPENDS = $(GUI_DEPENDS) $(LIB_DEPENDS) $(DAEMON_DEPENDS) all: $(TARGETS) $(GUI_BIN): $(COMMON_LIB) $(GUI_OBJECTS) $(CC) $(GUI_CFLAGS) -o $@ -Wl,--as-needed -Wl,--start-group $^ $(GUI_LDFLAGS) -Wl,--end-group +$(DAEMON_BIN): $(COMMON_LIB) $(DAEMON_OBJECTS) + $(CC) $(DAEMON_CFLAGS) -o $@ -Wl,--as-needed -Wl,--start-group $^ $(DAEMON_LDFLAGS) -Wl,--end-group + $(COMMON_LIB): $(LIB_OBJECTS) $(AR) cr $@ $^ $(RANLIB) $@ @@ -46,6 +56,7 @@ $(COMMON_LIB): $(LIB_OBJECTS) $(GUI_OBJECTS): OBJ_CFLAGS=$(GUI_CFLAGS) $(LIB_OBJECTS): OBJ_CFLAGS=$(LIB_CFLAGS) +$(DAEMON_OBJECTS): OBJ_CFLAGS=$(DAEMON_CFLAGS) clean: rm -f $(TARGETS) $(COMMON_LIB) $(OBJECTS) $(DEPENDS) diff --git a/slowrxd.c b/slowrxd.c new file mode 100644 index 0000000..aeddd57 --- /dev/null +++ b/slowrxd.c @@ -0,0 +1,154 @@ +/* + * slowrx - an SSTV decoder + * * * * * * * * * * * * * * + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include "common.h" +#include "fft.h" +#include "listen.h" +#include "modespec.h" +#include "pcm.h" +#include "pic.h" +#include "video.h" +#include "vis.h" + +static const char *fsk_id; + +static void showStatusbarMessage(const char* msg) { + printf("Status: %s\n", msg); +} + +static void onVisIdentified(void) { + int idx = VISmap[VIS]; + printf("Detected mode %s (%d)", ModeSpec[idx].Name, VIS); +} + +static void onVideoInitBuffer(uint8_t mode) { + printf("Init buffer for mode %d", mode); +} + +static void onVideoStartRedraw(void) { + printf("\n\nBEGIN REDRAW\n\n"); +} + +static void onVideoWritePixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b) { + printf("(%d, %d) = #%02x%02x%02x\n", x, y, r, g, b); +} + +static void onVideoRefresh(void) { + printf("\n\nREFRESH\n\n"); +} + +static void onListenerWaiting(void) { + printf("Listener is waiting"); +} + +static void onListenerReceivedManual(void) { + printf("Listener received something in manual mode"); +} + +static void onListenerReceiveFSK(void) { + printf("Listener is now receiving FSK"); +} + +static void onListenerReceivedFSKID(const char *id) { + printf("Listener got FSK %s", id); + fsk_id = id; +} + +static void onListenerReceiveStarted(void) { + static char rctime[8]; + + strftime(rctime, sizeof(rctime)-1, "%H:%Mz", ListenerReceiveStartTime); + printf("Receive started at %s", rctime); +} + +static void onListenerAutoSlantCorrect(void) { + printf("Starting slant correction"); +} + +static void onListenerReceiveFinished(void) { + printf("Received %d × %d pixel image\n", + ModeSpec[CurrentPic.Mode].ImgWidth, + ModeSpec[CurrentPic.Mode].NumLines * ModeSpec[CurrentPic.Mode].LineHeight); +} + +static void showVU(double *Power, int FFTLen, int WinIdx) { + printf("VU Power data: WinIdx=%d\n", WinIdx); +#if 0 + for (int i = 0; i < FFTLen; i++) { + printf("[%4d] = %f\n", i, Power[i]); + } +#else + (void)Power; + (void)FFTLen; +#endif +} + +static void showPCMError(const char* error) { + printf("\n\nPCM Error: %s\n\n", error); +} + +static void showPCMDropWarning(void) { + printf("\n\nPCM DROP Warning!!!\n\n"); +} + +/* + * main + */ + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + // Prepare FFT + if (fft_init() < 0) { + exit(0); + } + + if (initPcmDevice("default") < 0) { + exit(1); + } + + ListenerAutoSlantCorrect = true; + ListenerEnableFSKID = true; + VisAutoStart = true; + + OnVideoInitBuffer = onVideoInitBuffer; + OnVideoStartRedraw = onVideoStartRedraw; + OnVideoRefresh = onVideoRefresh; + OnVideoWritePixel = onVideoWritePixel; + OnVideoPowerCalculated = showVU; + OnVisIdentified = onVisIdentified; + OnVisPowerComputed = showVU; + OnListenerStatusChange = showStatusbarMessage; + OnListenerWaiting = onListenerWaiting; + OnListenerReceivedManual = onListenerReceivedManual; + OnListenerReceiveStarted = onListenerReceiveStarted; + OnListenerReceiveFSK = onListenerReceiveFSK; + OnListenerAutoSlantCorrect = onListenerAutoSlantCorrect; + OnListenerReceiveFinished = onListenerReceiveFinished; + OnListenerReceivedFSKID = onListenerReceivedFSKID; + OnVisStatusChange = showStatusbarMessage; + pcm.OnPCMAbort = showPCMError; + pcm.OnPCMDrop = showPCMDropWarning; + + StartListener(); + WaitForListenerStop(); + + return (0); +} From 735ff129cbdbd88492c51c0cbf74a3d303ab572d Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 12:04:15 +1000 Subject: [PATCH 098/166] slowrxd: Implement receive logging --- slowrxd.c | 456 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 439 insertions(+), 17 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index aeddd57..9d3d567 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -4,12 +4,15 @@ * */ +#include +#include #include #include #include #include #include #include +#include #include @@ -26,15 +29,329 @@ #include "video.h" #include "vis.h" -static const char *fsk_id; +/* Exit status codes */ +#define DAEMON_EXIT_SUCCESS (0) +#define DAEMON_EXIT_INIT_FFT_ERR (1) +#define DAEMON_EXIT_INIT_PCM_ERR (2) + +/* Exit status to use when exiting */ +static int daemon_exit_status = DAEMON_EXIT_SUCCESS; + +/* Common log message types */ +const char* logmsg_receive_start = "RECEIVE_START"; +const char* logmsg_vis_detect = "VIS_DETECT"; +const char* logmsg_sig_strength = "SIG_STRENGTH"; +const char* logmsg_image_finish = "IMAGE_FINISHED"; +const char* logmsg_fsk_detect = "FSK_DETECT"; +const char* logmsg_fsk_received = "FSK_RECEIVED"; +const char* logmsg_receive_end = "RECEIVE_END"; +const char* logmsg_status = "STATUS"; +const char* logmsg_warning = "WARNING"; + +#if 0 +/* The name of the image-in-progress being received */ +static const char* path_inprogress_img = "inprogress.png"; +#endif + +/* The name of the receive log */ +static const char* path_inprogress_log = "inprogress.ndjson"; + +/* The FSK ID detected after image transmission */ +static const char *fsk_id = NULL; + +/* Pointer to the receive log (NDJSON format) */ +static FILE* rxlog = NULL; + +/* Pointer to the raw image data being received */ +#if 0 +static gdImagePtr rximg; +#endif + +/* Open the receive log ready for traffic */ +static int openReceiveLog(void) { + assert(rxlog == NULL); + rxlog = fopen(path_inprogress_log, "w+"); + if (rxlog == NULL) { + perror("Failed to open receive log"); + return -errno; + } + + printf("Opened log file: %s\n", path_inprogress_log); + return 0; +} + +/* Close and rename the receive log */ +static int closeReceiveLog(const char* new_path) { + int res; + + assert(rxlog != NULL); + res = fclose(rxlog); + if (res < 0) { + perror("Failed to close receive log"); + return -errno; + } + + printf("Closed log file: %s\n", path_inprogress_log); + if (new_path) { + res = rename(path_inprogress_log, new_path); + if (res < 0) { + perror("Failed to rename receive log"); + return -errno; + } + printf("Renamed %s to %s", path_inprogress_log, new_path); + } + + rxlog = NULL; + return 0; +} + +/* Write a (null-terminated!) raw string to the file */ +static int emitLogRecordRaw(const char* str) { + assert(rxlog != NULL); + int res = fputs(str, rxlog); + if (res < 0) { + perror("Failed to write to receive log"); + return -errno; + } + return 0; +} + +/* Log buffer flusher helper */ +static int emitLogBufferContent(char* buffer, char** const ptr, uint8_t* rem, uint8_t sz) { + /* NB: buffer is not necessarily zero terminated! */ + const uint8_t write_sz = sz - *rem; + assert(rxlog != NULL); + + size_t written = fwrite((void*)buffer, 1, write_sz, rxlog); + if (written < write_sz) { + perror("Truncated write whilst emitting to receive log"); + return -errno; + } + + /* Success, reset the remaining space pointer */ + *ptr = buffer; + *rem = sz; + return 0; +} + +/* Emit a string */ +static int emitLogRecordString(const char* str) { + int res; + char buffer[128]; + char* ptr = buffer; + uint8_t rem = (uint8_t)sizeof(buffer); + + assert(rxlog != NULL); + + // Begin with '"' + *(ptr++) = '"'; + rem--; + + while (*str) { + switch (*str) { + case '\\': + case '"': + // Escape with '\' character + if (!rem) { + res = emitLogBufferContent(buffer, &ptr, &rem, (uint8_t)sizeof(buffer)); + if (res < 0) + return res; + } + *(ptr++) = '\\'; + rem--; + if (!rem) { + res = emitLogBufferContent(buffer, &ptr, &rem, (uint8_t)sizeof(buffer)); + if (res < 0) + return res; + } + *(ptr++) = *str; + rem--; + break; + case '\n': + /* Emit '\n' */ + if (!rem) { + res = emitLogBufferContent(buffer, &ptr, &rem, (uint8_t)sizeof(buffer)); + if (res < 0) + return res; + } + *(ptr++) = '\\'; + rem--; + if (!rem) { + res = emitLogBufferContent(buffer, &ptr, &rem, (uint8_t)sizeof(buffer)); + if (res < 0) + return res; + } + *(ptr++) = 'n'; + rem--; + break; + default: + /* Pass through "safe" ranges */ + if ((*str) < ' ') + break; + + if ((*str) > '~') + break; + + if (!rem) { + res = emitLogBufferContent(buffer, &ptr, &rem, (uint8_t)sizeof(buffer)); + if (res < 0) + return res; + } + *(ptr++) = *str; + rem--; + break; + } + + str++; + } + + // End with '"' + if (!rem) { + res = emitLogBufferContent(buffer, &ptr, &rem, (uint8_t)sizeof(buffer)); + if (res < 0) + return res; + } + *(ptr++) = '"'; + rem--; + + return emitLogBufferContent(buffer, &ptr, &rem, (uint8_t)sizeof(buffer)); +} + +/* Begin a log record in the log file */ +static int beginReceiveLogRecord(const char* type, const char* msg) { + int res; + int64_t time_sec; + int16_t time_msec; + + assert(rxlog != NULL); + assert(type != NULL); + + { + struct timeval tv; + res = gettimeofday(&tv, NULL); + if (res < 0) { + perror("Failed to retrieve current time"); + return -errno; + } + time_sec = (int64_t)tv.tv_sec; + time_msec = (uint16_t)(tv.tv_usec / 1000); + } + + res = fprintf(rxlog, "{\"timestamp\": %ld%03u, \"type\": ", time_sec, time_msec); + if (res < 0) { + perror("Failed to emit record timestamp or type key"); + return -errno; + } + + res = emitLogRecordString(type); + if (res < 0) { + return res; + } + + if (msg) { + res = emitLogRecordRaw(", \"msg\": "); + if (res < 0) { + return -errno; + } + + res = emitLogRecordString(msg); + if (res < 0) { + return res; + } + } + + return 0; +} + +/* Finish a log record */ +static int finishReceiveLogRecord(const char* endstr) { + int res; + + if (endstr) { + res = emitLogRecordRaw(endstr); + if (res < 0) { + return res; + } + } + + res = emitLogRecordRaw("}\n"); + if (res < 0) { + return res; + } + + res = fflush(rxlog); + if (res < 0) { + perror("Failed to flush log record"); + return -errno; + } + + return 0; +} + +/* Emit a complete simple log message with no keys. */ +static int emitSimpleReceiveLogRecord(const char* type, const char* msg) { + int res = beginReceiveLogRecord(type, msg); + if (res < 0) + return res; + return finishReceiveLogRecord(NULL); +} static void showStatusbarMessage(const char* msg) { printf("Status: %s\n", msg); + if (rxlog) { + if (emitSimpleReceiveLogRecord(logmsg_status, msg) < 0) { + // Bail here! + Abort = true; + } + } } static void onVisIdentified(void) { - int idx = VISmap[VIS]; - printf("Detected mode %s (%d)", ModeSpec[idx].Name, VIS); + char buffer[128]; + const int idx = VISmap[VIS]; + int res = openReceiveLog(); + if (res < 0) { + Abort = true; + return; + } + + snprintf(buffer, sizeof(buffer), "Detected mode %s (VIS code 0x%02x)", ModeSpec[idx].Name, VIS); + puts(buffer); + res = beginReceiveLogRecord(logmsg_vis_detect, buffer); + if (res < 0) { + Abort = true; + return; + } + + res = fprintf(rxlog, ", \"code\": %d, \"mode\": ", VIS); + if (res < 0) { + perror("Failed to write VIS code or mode key"); + Abort = true; + return; + } + + res = emitLogRecordString(ModeSpec[idx].ShortName); + if (res < 0) { + Abort = true; + return; + } + + res = emitLogRecordRaw(", \"desc\": "); + if (res < 0) { + Abort = true; + return; + } + + res = emitLogRecordString(ModeSpec[idx].Name); + if (res < 0) { + Abort = true; + return; + } + + res = finishReceiveLogRecord(NULL); + if (res < 0) { + Abort = true; + } } static void onVideoInitBuffer(uint8_t mode) { @@ -47,6 +364,28 @@ static void onVideoStartRedraw(void) { static void onVideoWritePixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b) { printf("(%d, %d) = #%02x%02x%02x\n", x, y, r, g, b); + + /* For now, emit the pixel data in the receive log */ + + int res = beginReceiveLogRecord("PIXELDATA", NULL); + if (res < 0) { + Abort = true; + return; + } + + res = fprintf(rxlog, ", \"x\": %d, \"y\": %d, \"r\": %d, \"g\": %d, \"b\": %d", + x, y, r, g, b); + if (res < 0) { + perror("Failed to write pixel data"); + Abort = true; + return; + } + + res = finishReceiveLogRecord(NULL); + if (res < 0) { + Abort = true; + return; + } } static void onVideoRefresh(void) { @@ -54,56 +393,139 @@ static void onVideoRefresh(void) { } static void onListenerWaiting(void) { - printf("Listener is waiting"); + printf("Listener is waiting\n"); } static void onListenerReceivedManual(void) { - printf("Listener received something in manual mode"); + printf("Listener received something in manual mode\n"); } static void onListenerReceiveFSK(void) { printf("Listener is now receiving FSK"); + if (emitSimpleReceiveLogRecord(logmsg_fsk_detect, NULL) < 0) { + // Bail here! + Abort = true; + } } static void onListenerReceivedFSKID(const char *id) { printf("Listener got FSK %s", id); fsk_id = id; + + int res = beginReceiveLogRecord(logmsg_fsk_received, NULL); + if (res < 0) { + Abort = true; + return; + } + + res = emitLogRecordRaw(", \"id\": "); + if (res < 0) { + Abort = true; + return; + } + + res = emitLogRecordString(id); + if (res < 0) { + Abort = true; + return; + } + + res = finishReceiveLogRecord(NULL); + if (res < 0) { + Abort = true; + return; + } } static void onListenerReceiveStarted(void) { static char rctime[8]; - strftime(rctime, sizeof(rctime)-1, "%H:%Mz", ListenerReceiveStartTime); printf("Receive started at %s", rctime); + if (emitSimpleReceiveLogRecord(logmsg_receive_start, "Receive started") < 0) { + // Bail here! + Abort = true; + } } static void onListenerAutoSlantCorrect(void) { - printf("Starting slant correction"); + printf("Performing slant correction"); + if (emitSimpleReceiveLogRecord(logmsg_status, "Performing slant correction") < 0) { + // Bail here! + Abort = true; + } } static void onListenerReceiveFinished(void) { + int res = emitSimpleReceiveLogRecord(logmsg_receive_end, NULL); + if (res == 0) + res = closeReceiveLog(NULL); + if (res < 0) { + Abort = true; + return; + } + printf("Received %d × %d pixel image\n", ModeSpec[CurrentPic.Mode].ImgWidth, ModeSpec[CurrentPic.Mode].NumLines * ModeSpec[CurrentPic.Mode].LineHeight); } static void showVU(double *Power, int FFTLen, int WinIdx) { - printf("VU Power data: WinIdx=%d\n", WinIdx); -#if 0 + if (!rxlog) { + /* No log, so do nothing */ + return; + } + + int res = beginReceiveLogRecord(logmsg_sig_strength, NULL); + if (res < 0) { + Abort = true; + return; + } + + res = fprintf(rxlog, ", \"win\": %d, \"fft\": [", WinIdx); + if (res < 0) { + perror("Failed to write window index or start of FFT array"); + Abort = true; + return; + } + for (int i = 0; i < FFTLen; i++) { - printf("[%4d] = %f\n", i, Power[i]); + if (i > 0) { + res = emitLogRecordRaw(", "); + if (res < 0) { + Abort = true; + return; + } + } + + res = fprintf(rxlog, "%f", Power[i]); + if (res < 0) { + Abort = true; + return; + } } -#else - (void)Power; - (void)FFTLen; -#endif + + res = finishReceiveLogRecord("]"); } static void showPCMError(const char* error) { + if (rxlog) { + if (emitSimpleReceiveLogRecord(logmsg_warning, error) < 0) { + // Bail here! + Abort = true; + } + } + printf("\n\nPCM Error: %s\n\n", error); } static void showPCMDropWarning(void) { + if (rxlog) { + if (emitSimpleReceiveLogRecord(logmsg_warning, "PCM frames dropped") < 0) { + // Bail here! + Abort = true; + } + } + printf("\n\nPCM DROP Warning!!!\n\n"); } @@ -117,11 +539,11 @@ int main(int argc, char *argv[]) { // Prepare FFT if (fft_init() < 0) { - exit(0); + exit(DAEMON_EXIT_INIT_FFT_ERR); } if (initPcmDevice("default") < 0) { - exit(1); + exit(DAEMON_EXIT_INIT_PCM_ERR); } ListenerAutoSlantCorrect = true; @@ -150,5 +572,5 @@ int main(int argc, char *argv[]) { StartListener(); WaitForListenerStop(); - return (0); + return (daemon_exit_status); } From c8aae751ffae8067996ce2ebbda9944cbeef930d Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 12:39:19 +1000 Subject: [PATCH 099/166] slowrxd: Rename log file after reception --- slowrxd.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 7 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 9d3d567..23c74c4 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -56,6 +56,9 @@ static const char* path_inprogress_img = "inprogress.png"; /* The name of the receive log */ static const char* path_inprogress_log = "inprogress.ndjson"; +/* The name of the latest receive log */ +static const char* path_latest_log = "latest.ndjson"; + /* The FSK ID detected after image transmission */ static const char *fsk_id = NULL; @@ -67,6 +70,37 @@ static FILE* rxlog = NULL; static gdImagePtr rximg; #endif +/* Rename and symlink a file */ +static int renameAndSymlink(const char* existing_path, const char* new_path, const char* symlink_path) { + int res = rename(existing_path, new_path); + if (res < 0) { + perror("Failed to rename receive log"); + return -errno; + } + printf("Renamed %s to %s\n", existing_path, new_path); + + if (symlink_path) { + res = unlink(symlink_path); + if (res < 0) { + /* ENOENT is okay */ + if (errno != ENOENT) { + perror("Failed to remove old 'latest' symlink"); + return -errno; + } + } + printf("Removed old symlink %s\n", symlink_path); + + res = symlink(new_path, symlink_path); + if (res < 0) { + perror("Failed to symlink receive log"); + return -errno; + } + printf("Symlinked %s to %s\n", new_path, symlink_path); + } + + return 0; +} + /* Open the receive log ready for traffic */ static int openReceiveLog(void) { assert(rxlog == NULL); @@ -86,22 +120,20 @@ static int closeReceiveLog(const char* new_path) { assert(rxlog != NULL); res = fclose(rxlog); + rxlog = NULL; if (res < 0) { - perror("Failed to close receive log"); + perror("Failed to close receive log cleanly"); return -errno; } printf("Closed log file: %s\n", path_inprogress_log); if (new_path) { - res = rename(path_inprogress_log, new_path); + res = renameAndSymlink(path_inprogress_log, new_path, path_latest_log); if (res < 0) { - perror("Failed to rename receive log"); - return -errno; + return res; } - printf("Renamed %s to %s", path_inprogress_log, new_path); } - rxlog = NULL; return 0; } @@ -352,6 +384,8 @@ static void onVisIdentified(void) { if (res < 0) { Abort = true; } + + fsk_id = NULL; } static void onVideoInitBuffer(uint8_t mode) { @@ -456,9 +490,56 @@ static void onListenerAutoSlantCorrect(void) { } static void onListenerReceiveFinished(void) { + char timestamp[20]; + strftime(timestamp, sizeof(timestamp)-1, "%Y-%m-%dT%H-%MZ", ListenerReceiveStartTime); + + char output_path_log[128]; +#if 0 + char output_path_img[128]; +#endif + size_t output_path_rem = sizeof(output_path_log); + size_t next_len; + + next_len = strlen(timestamp); + if (next_len <= output_path_rem) { + strncpy(output_path_log, timestamp, output_path_rem); + output_path_rem -= next_len; + } + + next_len = strlen(ModeSpec[CurrentPic.Mode].ShortName) + 1; + if (next_len <= output_path_rem) { + strncat(output_path_log, "-", output_path_rem); + strncat(output_path_log, ModeSpec[CurrentPic.Mode].ShortName, output_path_rem); + output_path_rem -= next_len; + } + + if (fsk_id && output_path_rem) { + next_len = strlen(fsk_id) + 1; + if (next_len <= output_path_rem) { + strncat(output_path_log, "-", output_path_rem); + strncat(output_path_log, fsk_id, output_path_rem); + output_path_rem -= next_len; + } + } + +#if 0 + strncpy(output_path_img, output_path_log, sizeof(output_path_img)); + if (output_path_rem < 5) { + /* Truncate to make room for ".png\0" */ + output_path_img[sizeof(output_path_img) - 5] = 0; + } + strncat(output_path_img, ".png", sizeof(output_path_img) - strlen(output_path_img)); +#endif + + if (output_path_rem < 7) { + /* Truncate to make room for ".ndjson\0" */ + output_path_log[sizeof(output_path_log) - 5] = 0; + } + strncat(output_path_log, ".ndjson", sizeof(output_path_log) - strlen(output_path_log)); + int res = emitSimpleReceiveLogRecord(logmsg_receive_end, NULL); if (res == 0) - res = closeReceiveLog(NULL); + res = closeReceiveLog(output_path_log); if (res < 0) { Abort = true; return; From e83f7885a5bce0b76b83a6176ed1796c18b35621 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 13:36:48 +1000 Subject: [PATCH 100/166] slowrxd: Implement writing files using GD --- slowrxd.c | 115 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 26 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 23c74c4..f0a6dcd 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -19,6 +19,7 @@ #include #include +#include #include "common.h" #include "fft.h" @@ -34,6 +35,9 @@ #define DAEMON_EXIT_INIT_FFT_ERR (1) #define DAEMON_EXIT_INIT_PCM_ERR (2) +/* Receive refresh interval */ +#define REFRESH_INTERVAL (5) + /* Exit status to use when exiting */ static int daemon_exit_status = DAEMON_EXIT_SUCCESS; @@ -48,10 +52,8 @@ const char* logmsg_receive_end = "RECEIVE_END"; const char* logmsg_status = "STATUS"; const char* logmsg_warning = "WARNING"; -#if 0 /* The name of the image-in-progress being received */ static const char* path_inprogress_img = "inprogress.png"; -#endif /* The name of the receive log */ static const char* path_inprogress_log = "inprogress.ndjson"; @@ -59,16 +61,23 @@ static const char* path_inprogress_log = "inprogress.ndjson"; /* The name of the latest receive log */ static const char* path_latest_log = "latest.ndjson"; +/* The name of the latest received image */ +static const char* path_latest_img = "latest.png"; + +/* Time the image was last written to */ +static time_t last_refresh = 0; + /* The FSK ID detected after image transmission */ static const char *fsk_id = NULL; /* Pointer to the receive log (NDJSON format) */ static FILE* rxlog = NULL; +/* The currently selected mode */ +const _ModeSpec* rxmode = NULL; + /* Pointer to the raw image data being received */ -#if 0 static gdImagePtr rximg; -#endif /* Rename and symlink a file */ static int renameAndSymlink(const char* existing_path, const char* new_path, const char* symlink_path) { @@ -347,6 +356,7 @@ static void onVisIdentified(void) { return; } + last_refresh = 0; snprintf(buffer, sizeof(buffer), "Detected mode %s (VIS code 0x%02x)", ModeSpec[idx].Name, VIS); puts(buffer); res = beginReceiveLogRecord(logmsg_vis_detect, buffer); @@ -389,7 +399,16 @@ static void onVisIdentified(void) { } static void onVideoInitBuffer(uint8_t mode) { - printf("Init buffer for mode %d", mode); + rxmode = &ModeSpec[mode]; + + printf("Init buffer for mode %s\n", rxmode->Name); + + /* Allocate the image */ + rximg = gdImageCreateTrueColor(rxmode->ImgWidth, rxmode->NumLines * rxmode->LineHeight); + if (!rximg) { + perror("Failed to allocate image buffer"); + Abort = true; + } } static void onVideoStartRedraw(void) { @@ -397,33 +416,73 @@ static void onVideoStartRedraw(void) { } static void onVideoWritePixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b) { - printf("(%d, %d) = #%02x%02x%02x\n", x, y, r, g, b); - - /* For now, emit the pixel data in the receive log */ + /* Check bounds */ + if (x >= rxmode->ImgWidth) + return; - int res = beginReceiveLogRecord("PIXELDATA", NULL); - if (res < 0) { - Abort = true; + if (y >= (rxmode->NumLines * rxmode->LineHeight)) return; - } - res = fprintf(rxlog, ", \"x\": %d, \"y\": %d, \"r\": %d, \"g\": %d, \"b\": %d", - x, y, r, g, b); - if (res < 0) { - perror("Failed to write pixel data"); - Abort = true; + if (!rximg) return; + + /* Draw pixel */ + gdImageSetPixel(rximg, x, y, gdImageColorResolve(rximg, r, g, b)); +} + +static int refreshImage(_Bool force) { + int res; + + if (!force && last_refresh) { + struct timeval tv; + res = gettimeofday(&tv, NULL); + if (res >= 0) { + uint16_t age = (uint16_t)(tv.tv_sec - last_refresh); + + if (age < REFRESH_INTERVAL) { + /* Hasn't been long enough, don't bother */ + return 0; + } + } + } else if (force) { + printf("Forced refresh\n"); + } else if (!last_refresh) { + printf("First refresh\n"); } - res = finishReceiveLogRecord(NULL); - if (res < 0) { - Abort = true; - return; + /* Open a file for writing. "wb" means "write binary", important + under MSDOS, harmless under Unix. */ + FILE* pngout = fopen(path_inprogress_img, "wb"); + if (pngout) { + /* Output the image to the disk file in PNG format. */ + gdImagePng(rximg, pngout); + + /* Close the files. */ + res = fclose(pngout); + if (res < 0) { + perror("Failed to write in-progress image"); + return -errno; + } else { + struct timeval tv; + res = gettimeofday(&tv, NULL); + if (res >= 0) { + /* Mark refresh time */ + last_refresh = tv.tv_sec; + } + printf("Image refreshed\n"); + } + } else { + perror("Failed to open in-progress image for writing"); + return -errno; } + + return 0; } static void onVideoRefresh(void) { - printf("\n\nREFRESH\n\n"); + if (refreshImage(false) < 0) { + Abort = true; + } } static void onListenerWaiting(void) { @@ -494,9 +553,7 @@ static void onListenerReceiveFinished(void) { strftime(timestamp, sizeof(timestamp)-1, "%Y-%m-%dT%H-%MZ", ListenerReceiveStartTime); char output_path_log[128]; -#if 0 char output_path_img[128]; -#endif size_t output_path_rem = sizeof(output_path_log); size_t next_len; @@ -522,14 +579,20 @@ static void onListenerReceiveFinished(void) { } } -#if 0 strncpy(output_path_img, output_path_log, sizeof(output_path_img)); if (output_path_rem < 5) { /* Truncate to make room for ".png\0" */ output_path_img[sizeof(output_path_img) - 5] = 0; } strncat(output_path_img, ".png", sizeof(output_path_img) - strlen(output_path_img)); -#endif + + /* Refresh one more time, then rename the file */ + refreshImage(true); + renameAndSymlink(path_inprogress_img, output_path_img, path_latest_img); + + /* Release the image buffer */ + gdImageDestroy(rximg); + rximg = NULL; if (output_path_rem < 7) { /* Truncate to make room for ".ndjson\0" */ From afbd2e07401dd52af6ff5091541141f4f83e4eb3 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 13:37:02 +1000 Subject: [PATCH 101/166] Makefile: Compile slowrxd with libgd --- Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f5e2a88..be0a471 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ CFLAGS = -Wall -Wextra -std=gnu99 -pedantic -g GTKCFLAGS = $(shell pkg-config --cflags gtk+-3.0) GTKLIBS = $(shell pkg-config --libs gtk+-3.0) +GDCFLAGS = $(shell pkg-config --cflags gdlib) +GDLIBS = $(shell pkg-config --libs gdlib) + OFLAGS = -O3 GUI_BIN = slowrx @@ -32,8 +35,8 @@ GUI_LDFLAGS = $(COMMON_LDFLAGS) -lgthread-2.0 $(GTKLIBS) DAEMON_SOURCES = slowrxd.c DAEMON_OBJECTS = $(patsubst %.c,%.o,$(DAEMON_SOURCES)) DAEMON_DEPENDS = $(patsubst %.c,%.d,$(DAEMON_SOURCES)) -DAEMON_CFLAGS = $(COMMON_CFLAGS) $(LIB_CFLAGS) -DAEMON_LDFLAGS = $(COMMON_LDFLAGS) +DAEMON_CFLAGS = $(COMMON_CFLAGS) $(LIB_CFLAGS) $(GDCFLAGS) +DAEMON_LDFLAGS = $(COMMON_LDFLAGS) $(GDLIBS) OBJECTS = $(GUI_OBJECTS) $(LIB_OBJECTS) $(DAEMON_OBJECTS) DEPENDS = $(GUI_DEPENDS) $(LIB_DEPENDS) $(DAEMON_DEPENDS) From 9bacc77ce0367b5d4bad20e18c287a9c78754006 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 10 Jul 2024 13:37:20 +1000 Subject: [PATCH 102/166] README.md: Document libgd dependency --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e1bd285..28c6c26 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,14 @@ Requirements * Linux * Alsa (`libasound2-dev`) -* Gtk+ 3.4 (`libgtk-3-dev`) * FFTW 3 (`libfftw3-dev`) +For the GUI: +* Gtk+ 3.4 (`libgtk-3-dev`) + +For the daemon: +* libgd + And, obviously: * shortwave radio with SSB From c8e1746259ea17b594054783011709f24eed94d3 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 08:16:41 +1000 Subject: [PATCH 103/166] pcm: Make wanteddevname a constant It never changes. --- pcm.c | 2 +- pcm.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pcm.c b/pcm.c index 36d2241..6ffa189 100644 --- a/pcm.c +++ b/pcm.c @@ -72,7 +72,7 @@ void readPcm(int32_t numsamples) { // 0 = opened ok // -1 = opened, but suboptimal // -2 = couldn't be opened -int32_t initPcmDevice(char *wanteddevname) { +int32_t initPcmDevice(const char *wanteddevname) { snd_pcm_hw_params_t *hwparams; char pcm_name[30]; diff --git a/pcm.h b/pcm.h index 4dc2197..56e9f1f 100644 --- a/pcm.h +++ b/pcm.h @@ -17,7 +17,7 @@ struct _PcmData { }; extern PcmData pcm; -int32_t initPcmDevice (char *wanteddevname); +int32_t initPcmDevice (const char *wanteddevname); void readPcm (int32_t numsamples); #endif From cac3bf8a32ce5b600c5599f90a53f2965f197f68 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 08:18:27 +1000 Subject: [PATCH 104/166] slowrxd: Add some command-line arguments --- slowrxd.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 11 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index f0a6dcd..0574577 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -32,8 +32,9 @@ /* Exit status codes */ #define DAEMON_EXIT_SUCCESS (0) -#define DAEMON_EXIT_INIT_FFT_ERR (1) -#define DAEMON_EXIT_INIT_PCM_ERR (2) +#define DAEMON_EXIT_INVALID_ARG (1) +#define DAEMON_EXIT_INIT_FFT_ERR (2) +#define DAEMON_EXIT_INIT_PCM_ERR (3) /* Receive refresh interval */ #define REFRESH_INTERVAL (5) @@ -64,6 +65,9 @@ static const char* path_latest_log = "latest.ndjson"; /* The name of the latest received image */ static const char* path_latest_img = "latest.png"; +/* The name of the directory where all images will be kept */ +static const char* path_dir = NULL; + /* Time the image was last written to */ static time_t last_refresh = 0; @@ -554,12 +558,23 @@ static void onListenerReceiveFinished(void) { char output_path_log[128]; char output_path_img[128]; - size_t output_path_rem = sizeof(output_path_log); + size_t output_path_rem = sizeof(output_path_log) - 1; size_t next_len; + if (path_dir) { + next_len = strlen(path_dir) + 1; + if (next_len <= output_path_rem) { + strncpy(output_path_log, path_dir, output_path_rem); + strncpy(output_path_log, "/", output_path_rem - 1); + output_path_rem -= next_len; + } + } else { + output_path_log[0] = 0; + } + next_len = strlen(timestamp); if (next_len <= output_path_rem) { - strncpy(output_path_log, timestamp, output_path_rem); + strncat(output_path_log, timestamp, output_path_rem); output_path_rem -= next_len; } @@ -678,22 +693,63 @@ static void showPCMDropWarning(void) { */ int main(int argc, char *argv[]) { - (void)argc; - (void)argv; + // Set up defaults + const char* pcm_device = "default"; + ListenerAutoSlantCorrect = true; + ListenerEnableFSKID = true; + VisAutoStart = true; + + { + int opt; + while ((opt = getopt(argc, argv, "FI:L:Sd:hi:l:p:")) != -1) { + switch (opt) { + case 'F': // Disable FSKID + ListenerEnableFSKID = false; + break; + case 'I': // In-progress image path + path_inprogress_img = optarg; + break; + case 'L': // In-progress receive log path + path_inprogress_log = optarg; + break; + case 'S': // Disable slant correction + ListenerAutoSlantCorrect = false; + break; + case 'h': + printf("Usage: %s [-h] [-F] [-S] [-I inprogress.png]\n" + "[-L inprogress.ndjson] [-d directory] [-i latest.png]\n" + "[-l latest.ndjson] [-p pcmdevice]\n" + "\n" + "where:\n" + " -F : disable FSK ID detection\n" + " -S : disable slant correction\n" + " -h : display this help and exit\n" + " -d : set the directory where images are kept\n" + " -I : set the in-progress image path\n" + " -L : set the in-progress receive log path\n" + " -i : set the latest image path\n" + " -l : set the latest receive log path\n" + " -p : set the ALSA PCM capture device\n", + argv[0]); + exit(DAEMON_EXIT_SUCCESS); + break; + default: + printf("Unrecognised: %s", optarg); + exit(DAEMON_EXIT_INVALID_ARG); + break; + } + } + } // Prepare FFT if (fft_init() < 0) { exit(DAEMON_EXIT_INIT_FFT_ERR); } - if (initPcmDevice("default") < 0) { + if (initPcmDevice(pcm_device) < 0) { exit(DAEMON_EXIT_INIT_PCM_ERR); } - ListenerAutoSlantCorrect = true; - ListenerEnableFSKID = true; - VisAutoStart = true; - OnVideoInitBuffer = onVideoInitBuffer; OnVideoStartRedraw = onVideoStartRedraw; OnVideoRefresh = onVideoRefresh; From a197f9c4a6a8eebaf7f942759b862fd426534780 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 08:23:25 +1000 Subject: [PATCH 105/166] slowrxd: Use inttypes.h PRId64 to format int64_t --- slowrxd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slowrxd.c b/slowrxd.c index 0574577..ac059a4 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -282,7 +283,7 @@ static int beginReceiveLogRecord(const char* type, const char* msg) { time_msec = (uint16_t)(tv.tv_usec / 1000); } - res = fprintf(rxlog, "{\"timestamp\": %ld%03u, \"type\": ", time_sec, time_msec); + res = fprintf(rxlog, "{\"timestamp\": %" PRId64 "%03u, \"type\": ", time_sec, time_msec); if (res < 0) { perror("Failed to emit record timestamp or type key"); return -errno; From 5e9538f73168d2a751a0bd053c98afb7d3cc5981 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 08:30:27 +1000 Subject: [PATCH 106/166] slowrxd: Add missed directory option --- slowrxd.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index ac059a4..7d7f1e8 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -702,7 +702,7 @@ int main(int argc, char *argv[]) { { int opt; - while ((opt = getopt(argc, argv, "FI:L:Sd:hi:l:p:")) != -1) { + while ((opt = getopt(argc, argv, "FISh:L:d:i:l:p:")) != -1) { switch (opt) { case 'F': // Disable FSKID ListenerEnableFSKID = false; @@ -716,6 +716,9 @@ int main(int argc, char *argv[]) { case 'S': // Disable slant correction ListenerAutoSlantCorrect = false; break; + case 'd': + path_dir = optarg; + break; case 'h': printf("Usage: %s [-h] [-F] [-S] [-I inprogress.png]\n" "[-L inprogress.ndjson] [-d directory] [-i latest.png]\n" @@ -735,7 +738,7 @@ int main(int argc, char *argv[]) { exit(DAEMON_EXIT_SUCCESS); break; default: - printf("Unrecognised: %s", optarg); + printf("Unrecognised: %s\n", optarg); exit(DAEMON_EXIT_INVALID_ARG); break; } From e9ed3ec894738b83a0f2a89bfdfa6d4d232bc249 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 08:31:15 +1000 Subject: [PATCH 107/166] slowrxd: Add some newlines to strings --- slowrxd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 7d7f1e8..823e29e 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -538,7 +538,7 @@ static void onListenerReceivedFSKID(const char *id) { static void onListenerReceiveStarted(void) { static char rctime[8]; strftime(rctime, sizeof(rctime)-1, "%H:%Mz", ListenerReceiveStartTime); - printf("Receive started at %s", rctime); + printf("Receive started at %s\n", rctime); if (emitSimpleReceiveLogRecord(logmsg_receive_start, "Receive started") < 0) { // Bail here! Abort = true; @@ -546,7 +546,7 @@ static void onListenerReceiveStarted(void) { } static void onListenerAutoSlantCorrect(void) { - printf("Performing slant correction"); + printf("Performing slant correction\n"); if (emitSimpleReceiveLogRecord(logmsg_status, "Performing slant correction") < 0) { // Bail here! Abort = true; From ae1dff1056b411e06732f02df619ce5ce56bdde3 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 08:33:57 +1000 Subject: [PATCH 108/166] slowrxd: Suppress FSK if none received --- slowrxd.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 823e29e..7b60878 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -507,8 +507,14 @@ static void onListenerReceiveFSK(void) { } static void onListenerReceivedFSKID(const char *id) { - printf("Listener got FSK %s", id); - fsk_id = id; + if (strlen(id)) { + printf("Listener got FSK %s", id); + fsk_id = id; + } else { + printf("No FSK received\n"); + fsk_id = NULL; + return; + } int res = beginReceiveLogRecord(logmsg_fsk_received, NULL); if (res < 0) { From c3c0ef2cec704e051b10400fb1ff54ae70687872 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 09:37:26 +1000 Subject: [PATCH 109/166] slowrxd: Add latest path options, prepend directory if given --- slowrxd.c | 216 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 168 insertions(+), 48 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 7b60878..1c13f07 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -36,6 +36,7 @@ #define DAEMON_EXIT_INVALID_ARG (1) #define DAEMON_EXIT_INIT_FFT_ERR (2) #define DAEMON_EXIT_INIT_PCM_ERR (3) +#define DAEMON_EXIT_INIT_PATH (4) /* Receive refresh interval */ #define REFRESH_INTERVAL (5) @@ -55,16 +56,16 @@ const char* logmsg_status = "STATUS"; const char* logmsg_warning = "WARNING"; /* The name of the image-in-progress being received */ -static const char* path_inprogress_img = "inprogress.png"; +static char* path_inprogress_img = "inprogress.png"; /* The name of the receive log */ -static const char* path_inprogress_log = "inprogress.ndjson"; +static char* path_inprogress_log = "inprogress.ndjson"; /* The name of the latest receive log */ -static const char* path_latest_log = "latest.ndjson"; +static char* path_latest_log = "latest.ndjson"; /* The name of the latest received image */ -static const char* path_latest_img = "latest.png"; +static char* path_latest_img = "latest.png"; /* The name of the directory where all images will be kept */ static const char* path_dir = NULL; @@ -84,6 +85,43 @@ const _ModeSpec* rxmode = NULL; /* Pointer to the raw image data being received */ static gdImagePtr rximg; +/* Safely concatenate two strings */ +static int safe_strncat(char* dest, const char* src, size_t* dest_rem) { + size_t src_len = strlen(src); + if (src_len <= *dest_rem) { + strncat(dest, src, *dest_rem); + *dest_rem -= src_len; + + return src_len; + } else { + errno = E2BIG; + return -errno; + } +} + +/* Append a path segment */ +static int path_append(char* path, const char* filename, size_t* path_rem) { + char* path_end = &path[strlen(path)]; + int res; + + if ((path_end > path) && (path_end[-1] != '/')) { + /* Path does not end in a slash, so append one now */ + res = safe_strncat(path, "/", path_rem); + if (res < 0) { + return res; + } + } + + res = safe_strncat(path, filename, path_rem); + if (res < 0) { + /* Wind back concatenations */ + path_end = 0; + return res; + } + + return res; +} + /* Rename and symlink a file */ static int renameAndSymlink(const char* existing_path, const char* new_path, const char* symlink_path) { int res = rename(existing_path, new_path); @@ -566,38 +604,26 @@ static void onListenerReceiveFinished(void) { char output_path_log[128]; char output_path_img[128]; size_t output_path_rem = sizeof(output_path_log) - 1; - size_t next_len; if (path_dir) { - next_len = strlen(path_dir) + 1; - if (next_len <= output_path_rem) { - strncpy(output_path_log, path_dir, output_path_rem); - strncpy(output_path_log, "/", output_path_rem - 1); - output_path_rem -= next_len; - } + strncpy(output_path_log, path_dir, output_path_rem); + output_path_rem -= strlen(path_dir); } else { output_path_log[0] = 0; } - next_len = strlen(timestamp); - if (next_len <= output_path_rem) { - strncat(output_path_log, timestamp, output_path_rem); - output_path_rem -= next_len; - } - - next_len = strlen(ModeSpec[CurrentPic.Mode].ShortName) + 1; - if (next_len <= output_path_rem) { - strncat(output_path_log, "-", output_path_rem); - strncat(output_path_log, ModeSpec[CurrentPic.Mode].ShortName, output_path_rem); - output_path_rem -= next_len; + int res = path_append(output_path_log, timestamp, &output_path_rem); + if (res >= 0) { + res = safe_strncat(output_path_log, "-", &output_path_rem); + if (res >= 0) { + res = safe_strncat(output_path_log, ModeSpec[CurrentPic.Mode].ShortName, &output_path_rem); + } } - if (fsk_id && output_path_rem) { - next_len = strlen(fsk_id) + 1; - if (next_len <= output_path_rem) { - strncat(output_path_log, "-", output_path_rem); - strncat(output_path_log, fsk_id, output_path_rem); - output_path_rem -= next_len; + if (fsk_id && output_path_rem && (res >= 0)) { + res = safe_strncat(output_path_log, "-", &output_path_rem); + if (res >= 0) { + res = safe_strncat(output_path_log, fsk_id, &output_path_rem); } } @@ -606,7 +632,10 @@ static void onListenerReceiveFinished(void) { /* Truncate to make room for ".png\0" */ output_path_img[sizeof(output_path_img) - 5] = 0; } - strncat(output_path_img, ".png", sizeof(output_path_img) - strlen(output_path_img)); + strncat(output_path_img, ".png", sizeof(output_path_img) - strlen(output_path_img) - 1); + + printf("Output files will be %s (image) and %s (log)\n", + output_path_img, output_path_log); /* Refresh one more time, then rename the file */ refreshImage(true); @@ -622,7 +651,7 @@ static void onListenerReceiveFinished(void) { } strncat(output_path_log, ".ndjson", sizeof(output_path_log) - strlen(output_path_log)); - int res = emitSimpleReceiveLogRecord(logmsg_receive_end, NULL); + res = emitSimpleReceiveLogRecord(logmsg_receive_end, NULL); if (res == 0) res = closeReceiveLog(output_path_log); if (res < 0) { @@ -699,7 +728,27 @@ static void showPCMDropWarning(void) { * main */ +char* path_append_dir_dup(const char* filename) { + char path[128] = {0}; + size_t path_rem = sizeof(path) - 1; + + if (path_dir) { + strncpy(path, path_dir, path_rem); + path_rem -= strlen(path_dir); + + int res = path_append(path, filename, &path_rem); + if (res < 0) { + return NULL; + } + } else { + strncpy(path, filename, path_rem); + } + + return strdup(path); +} + int main(int argc, char *argv[]) { + // Set up defaults const char* pcm_device = "default"; ListenerAutoSlantCorrect = true; @@ -707,48 +756,119 @@ int main(int argc, char *argv[]) { VisAutoStart = true; { + _Bool path_inprogress_img_set = false; + _Bool path_inprogress_log_set = false; + _Bool path_latest_img_set = false; + _Bool path_latest_log_set = false; int opt; + while ((opt = getopt(argc, argv, "FISh:L:d:i:l:p:")) != -1) { switch (opt) { case 'F': // Disable FSKID ListenerEnableFSKID = false; break; case 'I': // In-progress image path - path_inprogress_img = optarg; + if (path_inprogress_img_set) { + free(path_inprogress_img); + } + path_inprogress_img = path_append_dir_dup(optarg); + if (!path_inprogress_img) { + perror("Failed to compute full in-progress image path"); + exit(DAEMON_EXIT_INIT_PATH); + } break; case 'L': // In-progress receive log path - path_inprogress_log = optarg; + if (path_inprogress_log_set) { + free(path_inprogress_log); + } + path_inprogress_log = path_append_dir_dup(optarg); + if (!path_inprogress_log) { + perror("Failed to compute full in-progress log path"); + exit(DAEMON_EXIT_INIT_PATH); + } break; case 'S': // Disable slant correction ListenerAutoSlantCorrect = false; break; - case 'd': - path_dir = optarg; + case 'd': // Set the output directory + path_dir = strdup(optarg); break; case 'h': printf("Usage: %s [-h] [-F] [-S] [-I inprogress.png]\n" - "[-L inprogress.ndjson] [-d directory] [-i latest.png]\n" - "[-l latest.ndjson] [-p pcmdevice]\n" - "\n" - "where:\n" - " -F : disable FSK ID detection\n" - " -S : disable slant correction\n" - " -h : display this help and exit\n" - " -d : set the directory where images are kept\n" - " -I : set the in-progress image path\n" - " -L : set the in-progress receive log path\n" - " -i : set the latest image path\n" - " -l : set the latest receive log path\n" - " -p : set the ALSA PCM capture device\n", - argv[0]); + "[-L inprogress.ndjson] [-d directory] [-i latest.png]\n" + "[-l latest.ndjson] [-p pcmdevice]\n" + "\n" + "where:\n" + " -F : disable FSK ID detection\n" + " -S : disable slant correction\n" + " -h : display this help and exit\n" + " -d : set the directory where images are kept\n" + " -I : set the in-progress image path\n" + " -L : set the in-progress receive log path\n" + " -i : set the latest image path\n" + " -l : set the latest receive log path\n" + " -p : set the ALSA PCM capture device\n", + argv[0]); exit(DAEMON_EXIT_SUCCESS); break; + case 'i': // Latest image path + if (path_latest_img_set) { + free(path_latest_img); + } + path_latest_img = path_append_dir_dup(optarg); + if (!path_latest_img) { + perror("Failed to compute full latest image path"); + exit(DAEMON_EXIT_INIT_PATH); + } + break; + case 'l': // Latest receive log path + if (path_latest_log_set) { + free(path_latest_log); + } + path_latest_log = path_append_dir_dup(optarg); + if (!path_latest_log) { + perror("Failed to compute full latest log path"); + exit(DAEMON_EXIT_INIT_PATH); + } + break; default: printf("Unrecognised: %s\n", optarg); exit(DAEMON_EXIT_INVALID_ARG); break; } } + + if (!path_inprogress_img_set) { + path_inprogress_img = path_append_dir_dup(path_inprogress_img); + if (!path_inprogress_img) { + perror("Failed to compute full in-progress image path"); + exit(DAEMON_EXIT_INIT_PATH); + } + } + + if (!path_inprogress_log_set) { + path_inprogress_log = path_append_dir_dup(path_inprogress_log); + if (!path_inprogress_log) { + perror("Failed to compute full in-progress log path"); + exit(DAEMON_EXIT_INIT_PATH); + } + } + + if (!path_latest_img_set) { + path_latest_img = path_append_dir_dup(path_latest_img); + if (!path_latest_img) { + perror("Failed to compute full latest image path"); + exit(DAEMON_EXIT_INIT_PATH); + } + } + + if (!path_latest_log_set) { + path_latest_log = path_append_dir_dup(path_latest_log); + if (!path_latest_log) { + perror("Failed to compute full latest log path"); + exit(DAEMON_EXIT_INIT_PATH); + } + } } // Prepare FFT From ca722bae586b6c63cc23878f4424297a6afc91f2 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 09:44:09 +1000 Subject: [PATCH 110/166] slowrxd: Fix handling of line height --- slowrxd.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 1c13f07..1ed84a1 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -463,14 +463,19 @@ static void onVideoWritePixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint if (x >= rxmode->ImgWidth) return; - if (y >= (rxmode->NumLines * rxmode->LineHeight)) + if (y >= rxmode->NumLines) return; if (!rximg) return; - /* Draw pixel */ - gdImageSetPixel(rximg, x, y, gdImageColorResolve(rximg, r, g, b)); + /* Handle line height */ + y *= rxmode->LineHeight; + + /* Draw pixel or line */ + for (uint16_t yo = 0; yo < rxmode->LineHeight; yo++) { + gdImageSetPixel(rximg, x, y + yo, gdImageColorResolve(rximg, r, g, b)); + } } static int refreshImage(_Bool force) { From 13d4a68dbdbb747f210b585f437e75df9a1551c9 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 09:47:32 +1000 Subject: [PATCH 111/166] slowrxd: Move .ndjson append step --- slowrxd.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 1ed84a1..e674cd2 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -639,6 +639,12 @@ static void onListenerReceiveFinished(void) { } strncat(output_path_img, ".png", sizeof(output_path_img) - strlen(output_path_img) - 1); + if (output_path_rem < 7) { + /* Truncate to make room for ".ndjson\0" */ + output_path_log[sizeof(output_path_log) - 5] = 0; + } + strncat(output_path_log, ".ndjson", sizeof(output_path_log) - strlen(output_path_log)); + printf("Output files will be %s (image) and %s (log)\n", output_path_img, output_path_log); @@ -650,12 +656,6 @@ static void onListenerReceiveFinished(void) { gdImageDestroy(rximg); rximg = NULL; - if (output_path_rem < 7) { - /* Truncate to make room for ".ndjson\0" */ - output_path_log[sizeof(output_path_log) - 5] = 0; - } - strncat(output_path_log, ".ndjson", sizeof(output_path_log) - strlen(output_path_log)); - res = emitSimpleReceiveLogRecord(logmsg_receive_end, NULL); if (res == 0) res = closeReceiveLog(output_path_log); From 367674471760fdbf6d43d99e0baf9e1d7d68a56d Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 10:18:15 +1000 Subject: [PATCH 112/166] slowrxd: Use PATH_MAX for path length --- slowrxd.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index e674cd2..7b74f82 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -606,8 +607,8 @@ static void onListenerReceiveFinished(void) { char timestamp[20]; strftime(timestamp, sizeof(timestamp)-1, "%Y-%m-%dT%H-%MZ", ListenerReceiveStartTime); - char output_path_log[128]; - char output_path_img[128]; + char output_path_log[PATH_MAX]; + char output_path_img[PATH_MAX]; size_t output_path_rem = sizeof(output_path_log) - 1; if (path_dir) { @@ -734,7 +735,7 @@ static void showPCMDropWarning(void) { */ char* path_append_dir_dup(const char* filename) { - char path[128] = {0}; + char path[PATH_MAX] = {0}; size_t path_rem = sizeof(path) - 1; if (path_dir) { From ff2e494bccdad77803c3c24b23653042eb661955 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 10:23:59 +1000 Subject: [PATCH 113/166] slowrxd: Use absolute paths --- slowrxd.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/slowrxd.c b/slowrxd.c index 7b74f82..a9510a0 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -797,7 +797,15 @@ int main(int argc, char *argv[]) { ListenerAutoSlantCorrect = false; break; case 'd': // Set the output directory - path_dir = strdup(optarg); + { + char abs_dir[PATH_MAX]; + if (realpath(optarg, abs_dir) == abs_dir) { + path_dir = strdup(abs_dir); + } else { + perror("Failed to determine absolute directory"); + exit(DAEMON_EXIT_INIT_PATH); + } + } break; case 'h': printf("Usage: %s [-h] [-F] [-S] [-I inprogress.png]\n" @@ -844,6 +852,17 @@ int main(int argc, char *argv[]) { } } + if (!path_dir) { + /* Figure out the current working directory */ + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd)) == cwd) { + path_dir = strdup(path_dir); + } else { + perror("Failed to determine current working directory"); + exit(DAEMON_EXIT_INIT_PATH); + } + } + if (!path_inprogress_img_set) { path_inprogress_img = path_append_dir_dup(path_inprogress_img); if (!path_inprogress_img) { From aaee9e27e11b02cab9eb7ecf65ade915b5551225 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 10:31:07 +1000 Subject: [PATCH 114/166] slowrxd: Strip full path from symlink target This assumes it's in the same directory as the target file. Not always a valid assumption, but until I can figure out how to determine the relative path, it'll do. --- slowrxd.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index a9510a0..1259e63 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -133,6 +134,12 @@ static int renameAndSymlink(const char* existing_path, const char* new_path, con printf("Renamed %s to %s\n", existing_path, new_path); if (symlink_path) { + /* Figure out the target */ + char target[PATH_MAX]; + const char* symlink_target; + strncpy(target, new_path, sizeof(target) - 1); + symlink_target = basename(target); + res = unlink(symlink_path); if (res < 0) { /* ENOENT is okay */ @@ -143,12 +150,12 @@ static int renameAndSymlink(const char* existing_path, const char* new_path, con } printf("Removed old symlink %s\n", symlink_path); - res = symlink(new_path, symlink_path); + res = symlink(symlink_target, symlink_path); if (res < 0) { perror("Failed to symlink receive log"); return -errno; } - printf("Symlinked %s to %s\n", new_path, symlink_path); + printf("Symlinked %s to %s\n", symlink_target, symlink_path); } return 0; From 9023f1a8c79c4cc1c34a917cf8f76a6c0d5fc38f Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 11:34:35 +1000 Subject: [PATCH 115/166] slowrxd: Add ability to execute script on receive --- slowrxd.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/slowrxd.c b/slowrxd.c index 1259e63..46d78fa 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -50,6 +51,7 @@ static int daemon_exit_status = DAEMON_EXIT_SUCCESS; const char* logmsg_receive_start = "RECEIVE_START"; const char* logmsg_vis_detect = "VIS_DETECT"; const char* logmsg_sig_strength = "SIG_STRENGTH"; +const char* logmsg_image_refreshed = "IMAGE_REFRESHED"; const char* logmsg_image_finish = "IMAGE_FINISHED"; const char* logmsg_fsk_detect = "FSK_DETECT"; const char* logmsg_fsk_received = "FSK_RECEIVED"; @@ -72,6 +74,9 @@ static char* path_latest_img = "latest.png"; /* The name of the directory where all images will be kept */ static const char* path_dir = NULL; +/* The path to an executable to run when an event happens */ +static const char* rx_exec = NULL; + /* Time the image was last written to */ static time_t last_refresh = 0; @@ -87,6 +92,47 @@ const _ModeSpec* rxmode = NULL; /* Pointer to the raw image data being received */ static gdImagePtr rximg; +/* Execute a script, passing in the image file and receive log */ +static void exec_rx_cmd(const char* event, const char* img_path, const char* log_path) { + if (rx_exec) { + printf("Running %s %s %s %s\n", rx_exec, event, img_path, log_path); + pid_t child_pid = fork(); + if (child_pid == 0) { + char* _rx_exec = strdup(rx_exec); + if (!_rx_exec) { + perror("Failed to strdup process name"); + return; + } + + char* _event = strdup(event); + if (!_event) { + perror("Failed to strdup event"); + return; + } + + char* _img_path = strdup(img_path); + if (!_img_path) { + perror("Failed to strdup image path"); + return; + } + + char* _log_path = strdup(log_path); + if (!_log_path) { + perror("Failed to strdup log path"); + return; + } + + char* argv[] = { _rx_exec, _event, _img_path, _log_path, NULL }; + int res = execve(rx_exec, argv, NULL); + if (res < 0) { + perror("Failed to exec() receive command"); + } + } else if (child_pid < 0) { + perror("Failed to fork() for exec"); + } + } +} + /* Safely concatenate two strings */ static int safe_strncat(char* dest, const char* src, size_t* dest_rem) { size_t src_len = strlen(src); @@ -447,6 +493,7 @@ static void onVisIdentified(void) { } fsk_id = NULL; + exec_rx_cmd(logmsg_vis_detect, path_inprogress_img, path_inprogress_log); } static void onVideoInitBuffer(uint8_t mode) { @@ -538,6 +585,8 @@ static int refreshImage(_Bool force) { static void onVideoRefresh(void) { if (refreshImage(false) < 0) { Abort = true; + } else { + exec_rx_cmd(logmsg_image_refreshed, path_inprogress_img, path_inprogress_log); } } @@ -675,6 +724,10 @@ static void onListenerReceiveFinished(void) { printf("Received %d × %d pixel image\n", ModeSpec[CurrentPic.Mode].ImgWidth, ModeSpec[CurrentPic.Mode].NumLines * ModeSpec[CurrentPic.Mode].LineHeight); + exec_rx_cmd(logmsg_receive_end, output_path_img, output_path_log); + + /* Wait for all children to exit */ + wait(NULL); } static void showVU(double *Power, int FFTLen, int WinIdx) { @@ -775,7 +828,7 @@ int main(int argc, char *argv[]) { _Bool path_latest_log_set = false; int opt; - while ((opt = getopt(argc, argv, "FISh:L:d:i:l:p:")) != -1) { + while ((opt = getopt(argc, argv, "FISh:L:d:i:l:p:x:")) != -1) { switch (opt) { case 'F': // Disable FSKID ListenerEnableFSKID = false; @@ -852,6 +905,9 @@ int main(int argc, char *argv[]) { exit(DAEMON_EXIT_INIT_PATH); } break; + case 'x': // Execute script on event + rx_exec = strdup(optarg); + break; default: printf("Unrecognised: %s\n", optarg); exit(DAEMON_EXIT_INVALID_ARG); From 84967c1856214f89cf4c5599db4846d086ada745 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 14:50:02 +1000 Subject: [PATCH 116/166] slowrxd: Fix skyrocketing CPU during receive --- slowrxd.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 46d78fa..867c4b9 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -97,7 +97,9 @@ static void exec_rx_cmd(const char* event, const char* img_path, const char* log if (rx_exec) { printf("Running %s %s %s %s\n", rx_exec, event, img_path, log_path); pid_t child_pid = fork(); - if (child_pid == 0) { + if (child_pid >= 0) { + waitpid(child_pid, NULL, WNOHANG); + } else if (child_pid == 0) { char* _rx_exec = strdup(rx_exec); if (!_rx_exec) { perror("Failed to strdup process name"); @@ -579,14 +581,13 @@ static int refreshImage(_Bool force) { return -errno; } + exec_rx_cmd(logmsg_image_refreshed, path_inprogress_img, path_inprogress_log); return 0; } static void onVideoRefresh(void) { if (refreshImage(false) < 0) { Abort = true; - } else { - exec_rx_cmd(logmsg_image_refreshed, path_inprogress_img, path_inprogress_log); } } From 594abdb232a4541b883b812956fd381bccdb208d Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 15:20:55 +1000 Subject: [PATCH 117/166] slowrxd: Limit the frequency of script executions during RX --- slowrxd.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/slowrxd.c b/slowrxd.c index 867c4b9..c77ff67 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -44,6 +44,9 @@ /* Receive refresh interval */ #define REFRESH_INTERVAL (5) +/* Receive execute run interval */ +#define RX_EXEC_INTERVAL (30) + /* Exit status to use when exiting */ static int daemon_exit_status = DAEMON_EXIT_SUCCESS; @@ -80,6 +83,9 @@ static const char* rx_exec = NULL; /* Time the image was last written to */ static time_t last_refresh = 0; +/* Time the script was last run */ +static time_t last_rx_exec = 0; + /* The FSK ID detected after image transmission */ static const char *fsk_id = NULL; @@ -581,7 +587,9 @@ static int refreshImage(_Bool force) { return -errno; } - exec_rx_cmd(logmsg_image_refreshed, path_inprogress_img, path_inprogress_log); + if (force || ((last_refresh - last_rx_exec) > RX_EXEC_INTERVAL)) { + exec_rx_cmd(logmsg_image_refreshed, path_inprogress_img, path_inprogress_log); + } return 0; } From 5ab8c0c5bd6f3d8a9940dee417171b2781979f57 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 16:08:10 +1000 Subject: [PATCH 118/166] slowrxd: Use mutexes around logs to prevent records getting mixed --- slowrxd.c | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index c77ff67..d47f21c 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -40,6 +40,7 @@ #define DAEMON_EXIT_INIT_FFT_ERR (2) #define DAEMON_EXIT_INIT_PCM_ERR (3) #define DAEMON_EXIT_INIT_PATH (4) +#define DAEMON_EXIT_INIT_MUTEX (5) /* Receive refresh interval */ #define REFRESH_INTERVAL (5) @@ -91,6 +92,7 @@ static const char *fsk_id = NULL; /* Pointer to the receive log (NDJSON format) */ static FILE* rxlog = NULL; +static pthread_mutex_t rxlog_mutex; /* The currently selected mode */ const _ModeSpec* rxmode = NULL; @@ -383,6 +385,12 @@ static int beginReceiveLogRecord(const char* type, const char* msg) { time_msec = (uint16_t)(tv.tv_usec / 1000); } + res = pthread_mutex_lock(&rxlog_mutex); + if (res < 0) { + perror("Failed to lock output log"); + return -errno; + } + res = fprintf(rxlog, "{\"timestamp\": %" PRId64 "%03u, \"type\": ", time_sec, time_msec); if (res < 0) { perror("Failed to emit record timestamp or type key"); @@ -411,27 +419,40 @@ static int beginReceiveLogRecord(const char* type, const char* msg) { /* Finish a log record */ static int finishReceiveLogRecord(const char* endstr) { - int res; + int res, unlock_res; if (endstr) { res = emitLogRecordRaw(endstr); if (res < 0) { - return res; + goto unlock; } } res = emitLogRecordRaw("}\n"); if (res < 0) { - return res; + goto unlock; } res = fflush(rxlog); if (res < 0) { perror("Failed to flush log record"); - return -errno; + res = -errno; + goto unlock; } - return 0; + if (res < 0) { + perror("Failed to lock output log"); + res = -errno; + goto unlock; + } + +unlock: + unlock_res = pthread_mutex_unlock(&rxlog_mutex); + if (unlock_res < 0) { + perror("FAILED TO UNLOCK OUTPUT LOG!!!"); + assert(0); + } + return res; } /* Emit a complete simple log message with no keys. */ @@ -823,6 +844,12 @@ char* path_append_dir_dup(const char* filename) { } int main(int argc, char *argv[]) { + // Initialise mutex + int res = pthread_mutex_init(&rxlog_mutex, NULL); + if (res < 0) { + perror("Failed to create mutex"); + exit(DAEMON_EXIT_INIT_MUTEX); + } // Set up defaults const char* pcm_device = "default"; From 22538b3282ccbff50e6635a478d08192349c81b3 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 11 Jul 2024 16:08:29 +1000 Subject: [PATCH 119/166] slowrxd: Fix forking logic --- slowrxd.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index d47f21c..1d75ff3 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -41,6 +41,7 @@ #define DAEMON_EXIT_INIT_PCM_ERR (3) #define DAEMON_EXIT_INIT_PATH (4) #define DAEMON_EXIT_INIT_MUTEX (5) +#define DAEMON_EXIT_FORK_FAILURE (6) /* Receive refresh interval */ #define REFRESH_INTERVAL (5) @@ -105,8 +106,9 @@ static void exec_rx_cmd(const char* event, const char* img_path, const char* log if (rx_exec) { printf("Running %s %s %s %s\n", rx_exec, event, img_path, log_path); pid_t child_pid = fork(); - if (child_pid >= 0) { - waitpid(child_pid, NULL, WNOHANG); + if (child_pid > 0) { + printf("Waiting for PID %d\n", child_pid); + waitpid(child_pid, NULL, 0); } else if (child_pid == 0) { char* _rx_exec = strdup(rx_exec); if (!_rx_exec) { @@ -133,10 +135,12 @@ static void exec_rx_cmd(const char* event, const char* img_path, const char* log } char* argv[] = { _rx_exec, _event, _img_path, _log_path, NULL }; + printf("Executing script\n"); int res = execve(rx_exec, argv, NULL); if (res < 0) { perror("Failed to exec() receive command"); } + exit(DAEMON_EXIT_FORK_FAILURE); } else if (child_pid < 0) { perror("Failed to fork() for exec"); } From a08af7e76345317a3e23df988a773cef79647ae8 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 09:02:03 +1000 Subject: [PATCH 120/166] pcm: Define constants for the return value --- pcm.c | 26 +++++++++++++------------- pcm.h | 4 ++++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/pcm.c b/pcm.c index 6ffa189..688dc7b 100644 --- a/pcm.c +++ b/pcm.c @@ -69,9 +69,9 @@ void readPcm(int32_t numsamples) { // Initialize sound card // Return value: -// 0 = opened ok -// -1 = opened, but suboptimal -// -2 = couldn't be opened +// PCM_RES_SUCCESS = opened ok +// PCM_RES_SUBOPTIMAL = opened, but suboptimal +// PCM_RES_FAILURE = couldn't be opened int32_t initPcmDevice(const char *wanteddevname) { snd_pcm_hw_params_t *hwparams; @@ -104,7 +104,7 @@ int32_t initPcmDevice(const char *wanteddevname) { if (!found) { perror("Device disconnected?\n"); - return(-2); + return(PCM_RES_FAILURE); } if (strcmp(wanteddevname,"default") == 0) { @@ -115,26 +115,26 @@ int32_t initPcmDevice(const char *wanteddevname) { if (snd_pcm_open(&pcm.handle, pcm_name, SND_PCM_STREAM_CAPTURE, 0) < 0) { perror("ALSA: Error opening PCM device"); - return(-2); + return(PCM_RES_FAILURE); } /* Init hwparams with full configuration space */ if (snd_pcm_hw_params_any(pcm.handle, hwparams) < 0) { perror("ALSA: Can not configure this PCM device."); - return(-2); + return(PCM_RES_FAILURE); } if (snd_pcm_hw_params_set_access(pcm.handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { perror("ALSA: Error setting interleaved access."); - return(-2); + return(PCM_RES_FAILURE); } if (snd_pcm_hw_params_set_format(pcm.handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0) { perror("ALSA: Error setting format S16_LE."); - return(-2); + return(PCM_RES_FAILURE); } if (snd_pcm_hw_params_set_rate_near(pcm.handle, hwparams, &exact_rate, 0) < 0) { perror("ALSA: Error setting sample rate."); - return(-2); + return(PCM_RES_FAILURE); } // Try stereo first @@ -142,12 +142,12 @@ int32_t initPcmDevice(const char *wanteddevname) { // Fall back to mono if (snd_pcm_hw_params_set_channels(pcm.handle, hwparams, 1) < 0) { perror("ALSA: Error setting channels."); - return(-2); + return(PCM_RES_FAILURE); } } if (snd_pcm_hw_params(pcm.handle, hwparams) < 0) { perror("ALSA: Error setting HW params."); - return(-2); + return(PCM_RES_FAILURE); } pcm.Buffer = calloc( BUFLEN, sizeof(int16_t)); @@ -155,9 +155,9 @@ int32_t initPcmDevice(const char *wanteddevname) { if (exact_rate != 44100) { fprintf(stderr, "ALSA: Got %d Hz instead of 44100. Expect artifacts.\n", exact_rate); - return(-1); + return(PCM_RES_SUBOPTIMAL); } - return(0); + return(PCM_RES_SUCCESS); } diff --git a/pcm.h b/pcm.h index 56e9f1f..e4bde77 100644 --- a/pcm.h +++ b/pcm.h @@ -17,6 +17,10 @@ struct _PcmData { }; extern PcmData pcm; +#define PCM_RES_SUCCESS (0) +#define PCM_RES_SUBOPTIMAL (-1) +#define PCM_RES_FAILURE (-2) + int32_t initPcmDevice (const char *wanteddevname); void readPcm (int32_t numsamples); From 3b52f4219cfd23f830ea450c9e49cb322c0960ce Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 09:07:06 +1000 Subject: [PATCH 121/166] slowrxd: Handle sub-optimal PCM init case --- slowrxd.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 1d75ff3..5aa9ca0 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -1004,8 +1004,17 @@ int main(int argc, char *argv[]) { exit(DAEMON_EXIT_INIT_FFT_ERR); } - if (initPcmDevice(pcm_device) < 0) { - exit(DAEMON_EXIT_INIT_PCM_ERR); + int res = initPcmDevice(pcm_device); + switch (res) { + case PCM_RES_SUCCESS: + break; + case PCM_RES_SUBOPTIMAL: + printf("PCM setup is sub-optimal, doing the best we can.\n"); + break; + default: + if (res < 0) { + exit(DAEMON_EXIT_INIT_PCM_ERR); + } } OnVideoInitBuffer = onVideoInitBuffer; From 098b84d95c613c273322730a70ac29d47d98da3d Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 09:14:22 +1000 Subject: [PATCH 122/166] pcm: Don't hardcode sample rate to 44100 Lots of sound cards today do not perform well at 44.1kHz, as many are tuned for the 48kHz sample rate of DVDs. --- common.c | 2 +- gui.c | 6 +++--- listen.c | 10 +++++----- pcm.c | 10 ++++++---- pcm.h | 3 ++- slowrx.c | 2 +- slowrxd.c | 2 +- sync.c | 6 +++--- video.c | 6 +++--- vis.c | 6 +++--- 10 files changed, 28 insertions(+), 25 deletions(-) diff --git a/common.c b/common.c index 2062938..a234fff 100644 --- a/common.c +++ b/common.c @@ -15,7 +15,7 @@ uint8_t *StoredLum = NULL; // Return the FFT bin index matching the given frequency uint32_t GetBin (double Freq, uint32_t FFTLen) { - return (Freq / 44100 * FFTLen); + return (Freq / pcm.SampleRate * FFTLen); } // Sinusoid power from complex DFT coefficients diff --git a/gui.c b/gui.c index 005d0a8..d4bb1ed 100644 --- a/gui.c +++ b/gui.c @@ -162,8 +162,8 @@ void setVU (double *Power, int FFTLen, int WinIdx) { } } - LoBin = (int)((W-1-x)*(6000/W)/44100.0 * FFTLen); - HiBin = (int)((W -x)*(6000/W)/44100.0 * FFTLen); + LoBin = (int)((W-1-x)*(6000/W)/((double)pcm.SampleRate) * FFTLen); + HiBin = (int)((W -x)*(6000/W)/((double)pcm.SampleRate) * FFTLen); logpow = 0; for (i=LoBin; i 44100\n"); + Rate = pcm.SampleRate; + printf(" -> %u\n", pcm.SampleRate); break; } printf(" -> %.1f recalculating\n", Rate); @@ -98,7 +98,7 @@ double FindSync (uint8_t Mode, double Rate, int32_t *Skip) { for (y=0; y GetBin(1900 + CurrentPic.HedrShift, VIDEO_FFT_LEN)) ? 2300 : 1500 ) + CurrentPic.HedrShift; diff --git a/vis.c b/vis.c index 8cf6094..55a4280 100644 --- a/vis.c +++ b/vis.c @@ -80,7 +80,7 @@ uint8_t GetVIS () { else HedrBuf[HedrPtr] = HedrBuf[(HedrPtr-1) % 45]; // In Hertz - HedrBuf[HedrPtr] = HedrBuf[HedrPtr] / VIS_FFT_LEN * 44100; + HedrBuf[HedrPtr] = HedrBuf[HedrPtr] / VIS_FFT_LEN * pcm.SampleRate; // Header buffer holds 45 * 10 msec = 450 msec HedrPtr = (HedrPtr + 1) % 45; @@ -172,8 +172,8 @@ uint8_t GetVIS () { } // Skip the rest of the stop bit - readPcm(20e-3 * 44100); - pcm.WindowPtr += 20e-3 * 44100; + readPcm(20e-3 * pcm.SampleRate); + pcm.WindowPtr += 20e-3 * pcm.SampleRate; if (VISmap[VIS] != UNKNOWN) return VISmap[VIS]; else printf(" No VIS found\n"); From a23f5fab1d9f9699567a5505c0419c1d393460e0 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 09:19:58 +1000 Subject: [PATCH 123/166] slowrxd: Allow changing the sample rate --- slowrxd.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 5400ec5..5c5edee 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -860,6 +860,7 @@ int main(int argc, char *argv[]) { ListenerAutoSlantCorrect = true; ListenerEnableFSKID = true; VisAutoStart = true; + uint16_t sample_rate = 44100; { _Bool path_inprogress_img_set = false; @@ -868,7 +869,7 @@ int main(int argc, char *argv[]) { _Bool path_latest_log_set = false; int opt; - while ((opt = getopt(argc, argv, "FISh:L:d:i:l:p:x:")) != -1) { + while ((opt = getopt(argc, argv, "FISh:L:d:i:l:p:r:x:")) != -1) { switch (opt) { case 'F': // Disable FSKID ListenerEnableFSKID = false; @@ -910,7 +911,7 @@ int main(int argc, char *argv[]) { case 'h': printf("Usage: %s [-h] [-F] [-S] [-I inprogress.png]\n" "[-L inprogress.ndjson] [-d directory] [-i latest.png]\n" - "[-l latest.ndjson] [-p pcmdevice]\n" + "[-l latest.ndjson] [-p pcmdevice] [-r samplerate]\n" "\n" "where:\n" " -F : disable FSK ID detection\n" @@ -921,6 +922,7 @@ int main(int argc, char *argv[]) { " -L : set the in-progress receive log path\n" " -i : set the latest image path\n" " -l : set the latest receive log path\n" + " -r : set the ALSA PCM sample rate\n", " -p : set the ALSA PCM capture device\n", argv[0]); exit(DAEMON_EXIT_SUCCESS); @@ -945,6 +947,15 @@ int main(int argc, char *argv[]) { exit(DAEMON_EXIT_INIT_PATH); } break; + case 'r': // Sample rate + { + char* endptr = NULL; + sample_rate = strtoul(optarg, &endptr, 10); + if (endptr) { + printf("Invalid sample rate: %s", optarg); + exit(DAEMON_EXIT_INVALID_ARG); + } + } case 'x': // Execute script on event rx_exec = strdup(optarg); break; @@ -1004,7 +1015,7 @@ int main(int argc, char *argv[]) { exit(DAEMON_EXIT_INIT_FFT_ERR); } - int res = initPcmDevice(pcm_device, 44100); + int res = initPcmDevice(pcm_device, sample_rate); switch (res) { case PCM_RES_SUCCESS: break; From 31ba25b2064541d799474b1eb16ba5a4fe9b86d9 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 09:20:50 +1000 Subject: [PATCH 124/166] slowrxd: Document -x option --- slowrxd.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slowrxd.c b/slowrxd.c index 5c5edee..42d191a 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -912,6 +912,7 @@ int main(int argc, char *argv[]) { printf("Usage: %s [-h] [-F] [-S] [-I inprogress.png]\n" "[-L inprogress.ndjson] [-d directory] [-i latest.png]\n" "[-l latest.ndjson] [-p pcmdevice] [-r samplerate]\n" + "[-x script]\n" "\n" "where:\n" " -F : disable FSK ID detection\n" @@ -924,6 +925,7 @@ int main(int argc, char *argv[]) { " -l : set the latest receive log path\n" " -r : set the ALSA PCM sample rate\n", " -p : set the ALSA PCM capture device\n", + " -x : specify a script to run on receive events\n", argv[0]); exit(DAEMON_EXIT_SUCCESS); break; From d64871e23e52413e61c9cd3129c9c5a624321a55 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 09:21:51 +1000 Subject: [PATCH 125/166] sync: Add missed pcm.h include --- sync.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sync.c b/sync.c index e2fbd9d..4df0601 100644 --- a/sync.c +++ b/sync.c @@ -6,6 +6,7 @@ #include "common.h" #include "modespec.h" +#include "pcm.h" /* Find the slant angle of the sync singnal and adjust sample rate to cancel it out * Length: number of PCM samples to process From 22a3622e2b92c919e975d0e52c7786812c422f95 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 09:22:02 +1000 Subject: [PATCH 126/166] slowrxd: Add missed break --- slowrxd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/slowrxd.c b/slowrxd.c index 42d191a..15bbb83 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -958,6 +958,7 @@ int main(int argc, char *argv[]) { exit(DAEMON_EXIT_INVALID_ARG); } } + break; case 'x': // Execute script on event rx_exec = strdup(optarg); break; From cf4f21fa7ecf84dcfd79c8de7e2a7901fae1d599 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 09:22:17 +1000 Subject: [PATCH 127/166] slowrxd: Remove redundant `int` Yes, bit dopy this morning, it's before my morning cup of tea. --- slowrxd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slowrxd.c b/slowrxd.c index 15bbb83..7522248 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -1018,7 +1018,7 @@ int main(int argc, char *argv[]) { exit(DAEMON_EXIT_INIT_FFT_ERR); } - int res = initPcmDevice(pcm_device, sample_rate); + res = initPcmDevice(pcm_device, sample_rate); switch (res) { case PCM_RES_SUCCESS: break; From 9ed47f1d9a63259a96e428f2ec67903e87fecd21 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 09:23:10 +1000 Subject: [PATCH 128/166] slowrxd: Remove stray commas --- slowrxd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 7522248..1a40df1 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -923,8 +923,8 @@ int main(int argc, char *argv[]) { " -L : set the in-progress receive log path\n" " -i : set the latest image path\n" " -l : set the latest receive log path\n" - " -r : set the ALSA PCM sample rate\n", - " -p : set the ALSA PCM capture device\n", + " -r : set the ALSA PCM sample rate\n" + " -p : set the ALSA PCM capture device\n" " -x : specify a script to run on receive events\n", argv[0]); exit(DAEMON_EXIT_SUCCESS); From 4110b5ca3060cb18c3b589132e379d6fcb28bacd Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 09:28:00 +1000 Subject: [PATCH 129/166] slowrxd: Fix invalid parsing on sample rate --- slowrxd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 1a40df1..8ccf83b 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -953,8 +953,8 @@ int main(int argc, char *argv[]) { { char* endptr = NULL; sample_rate = strtoul(optarg, &endptr, 10); - if (endptr) { - printf("Invalid sample rate: %s", optarg); + if (*endptr) { + printf("Invalid sample rate: %s\n", endptr); exit(DAEMON_EXIT_INVALID_ARG); } } From 7406e4500ff65781550c8e73f25fe67bc68546c7 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 09:29:34 +1000 Subject: [PATCH 130/166] slowrxd: Fix detection of current directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `path_dir` is `NULL` at the time → instant segfault, duh! --- slowrxd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slowrxd.c b/slowrxd.c index 8ccf83b..27df09a 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -973,7 +973,7 @@ int main(int argc, char *argv[]) { /* Figure out the current working directory */ char cwd[PATH_MAX]; if (getcwd(cwd, sizeof(cwd)) == cwd) { - path_dir = strdup(path_dir); + path_dir = strdup(cwd); } else { perror("Failed to determine current working directory"); exit(DAEMON_EXIT_INIT_PATH); From f7c0bf9a0c483aee36c2c04ef248c536a119bfe7 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 10:28:54 +1000 Subject: [PATCH 131/166] vis: Derive number of frames from sample rate At 44.1kHz, 10ms is 441 frames. BUT not at other sample rates, so use the current sample rate to decide how much data to read. --- pcm.h | 3 +++ vis.c | 27 ++++++++++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/pcm.h b/pcm.h index 39f8e1b..6c5c63a 100644 --- a/pcm.h +++ b/pcm.h @@ -22,6 +22,9 @@ extern PcmData pcm; #define PCM_RES_SUBOPTIMAL (-1) #define PCM_RES_FAILURE (-2) +/* Compute the number of frames for the given time in msec, rounded to the nearest frame */ +#define PCM_MS_FRAMES(ms) (((((uint32_t)(ms)) * pcm.SampleRate) + 500) / 1000) + int32_t initPcmDevice (const char *wanteddevname, uint16_t samplerate); void readPcm (int32_t numsamples); diff --git a/vis.c b/vis.c index 55a4280..a1618b9 100644 --- a/vis.c +++ b/vis.c @@ -11,6 +11,13 @@ #include "pcm.h" #include "pic.h" +/* Approximate number of frames / samples for 10ms: NB can change at runtime! */ +#define VIS_10MS_FRAMES PCM_MS_FRAMES(10) +#define VIS_10MS_SAMPLES (VIS_10MS_FRAMES*2) + +/* Ditto, for 20ms */ +#define VIS_20MS_FRAMES PCM_MS_FRAMES(20) + /* * * Detect VIS & frequency shift @@ -33,14 +40,18 @@ uint8_t GetVIS () { int32_t ptr=0; int32_t Parity = 0, HedrPtr = 0; uint32_t i=0, j=0, k=0, MaxBin = 0; - double HedrBuf[100] = {0}, tone[100] = {0}, Hann[882] = {0}; + double HedrBuf[100] = {0}, tone[100] = {0}, Hann[VIS_10MS_SAMPLES]; _Bool gotvis = false; uint8_t Bit[8] = {0}, ParityBit = 0; + memset(Hann, 0, sizeof(double) * VIS_10MS_SAMPLES); + for (i = 0; i < VIS_FFT_LEN; i++) fft.in[i] = 0; // Create 20ms Hann window - for (i = 0; i < 882; i++) Hann[i] = 0.5 * (1 - cos( (2 * M_PI * (double)i) / 881 ) ); + for (i = 0; i < VIS_10MS_SAMPLES; i++) { + Hann[i] = 0.5 * (1 - cos( (2 * M_PI * (double)i) / (VIS_10MS_SAMPLES-1) ) ); + } ManualActivated = false; @@ -55,10 +66,12 @@ uint8_t GetVIS () { if (Abort || ManualResync) return(0); // Read 10 ms from sound card - readPcm(441); + readPcm(VIS_10MS_FRAMES); // Apply Hann window - for (i = 0; i < 882; i++) fft.in[i] = pcm.Buffer[pcm.WindowPtr + i - 441] / 32768.0 * Hann[i]; + for (i = 0; i < VIS_10MS_SAMPLES; i++) { + fft.in[i] = pcm.Buffer[pcm.WindowPtr + i - VIS_10MS_FRAMES] / 32768.0 * Hann[i]; + } // FFT of last 20 ms fftw_execute(fft.Plan2048); @@ -168,12 +181,12 @@ uint8_t GetVIS () { ptr = 0; } - pcm.WindowPtr += 441; + pcm.WindowPtr += VIS_10MS_FRAMES; } // Skip the rest of the stop bit - readPcm(20e-3 * pcm.SampleRate); - pcm.WindowPtr += 20e-3 * pcm.SampleRate; + readPcm(VIS_20MS_FRAMES); + pcm.WindowPtr += VIS_20MS_FRAMES; if (VISmap[VIS] != UNKNOWN) return VISmap[VIS]; else printf(" No VIS found\n"); From 1bf7921192cf2dd0b7aaefd4764d8c0c14c85e52 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 10:32:57 +1000 Subject: [PATCH 132/166] video: Derive 100ms of samples from sample rate --- video.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/video.c b/video.c index b29545a..887ef60 100644 --- a/video.c +++ b/video.c @@ -473,7 +473,7 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { } } /* endif (SampleNum == PixelGrid[PixelIdx].Time) */ - if (!Redraw && (SampleNum % 8820 == 0) && OnVideoPowerCalculated) { + if (!Redraw && (SampleNum % (2*PCM_MS_FRAMES(100)) == 0) && OnVideoPowerCalculated) { OnVideoPowerCalculated(Power, VIDEO_FFT_LEN, WinIdx); } From 99798f0694080f5b1c646076bb4e3c05fc2731b2 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 11:03:46 +1000 Subject: [PATCH 133/166] fsk: Derive sample counts from sample rate --- fsk.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/fsk.c b/fsk.c index ddeeba1..0cb07b0 100644 --- a/fsk.c +++ b/fsk.c @@ -15,6 +15,16 @@ #define FSK_FFT_LEN (2048) +/* 11ms approximately in frames; half of 22ms bit period */ +#define FSK_11MS_FRAMES PCM_MS_FRAMES(11) +#define FSK_11MS_SAMPLES (FSK_11MS_FRAMES*2) + +/* + * 5.5ms in samples; which is half FSK_11MS_SAMPLES. + * GCC should be smart enough to realise the *2 and /2 cancel. + */ +#define FSK_5M5S_SAMPLES (FSK_11MS_SAMPLES/2) + /* * Decode FSK ID * @@ -27,7 +37,7 @@ void GetFSK (char* const dest, uint8_t dest_sz) { uint32_t i=0, LoBin, HiBin, MidBin, TestNum=0, TestPtr=0; uint8_t Bit = 0, AsciiByte = 0, BytePtr = 0, TestBits[24] = {0}, BitPtr=0; - double HiPow,LoPow,Hann[970]; + double HiPow,LoPow,Hann[FSK_11MS_SAMPLES]; _Bool InSync = false; // Bit-reversion lookup table @@ -44,22 +54,26 @@ void GetFSK (char* const dest, uint8_t dest_sz) { for (i = 0; i < FSK_FFT_LEN; i++) fft.in[i] = 0; // Create 22ms Hann window - for (i = 0; i < 970; i++) Hann[i] = 0.5 * (1 - cos( 2 * M_PI * i / 969.0 ) ); + for (i = 0; i < FSK_11MS_SAMPLES; i++) { + Hann[i] = 0.5 * (1 - cos( 2 * M_PI * i / ((double)(FSK_11MS_SAMPLES-1)) ) ); + } while ( true ) { - // Read data from DSP - readPcm(InSync ? 970: 485); + // Read data from DSP: half the number of samples if not in sync. + readPcm(InSync ? FSK_11MS_SAMPLES: FSK_5M5S_SAMPLES); - if (pcm.WindowPtr < 485) { - pcm.WindowPtr += (InSync ? 970 : 485); + if (pcm.WindowPtr < (int32_t)FSK_5M5S_SAMPLES) { + pcm.WindowPtr += (InSync ? FSK_11MS_SAMPLES: FSK_5M5S_SAMPLES); continue; } // Apply Hann window - for (i = 0; i < 970; i++) fft.in[i] = pcm.Buffer[pcm.WindowPtr+i- 485] * Hann[i]; + for (i = 0; i < FSK_11MS_SAMPLES; i++) { + fft.in[i] = pcm.Buffer[pcm.WindowPtr+i- FSK_5M5S_SAMPLES] * Hann[i]; + } - pcm.WindowPtr += (InSync ? 970 : 485); + pcm.WindowPtr += (InSync ? FSK_11MS_SAMPLES : FSK_5M5S_SAMPLES); // FFT of last 22 ms fftw_execute(fft.Plan2048); From 9e2c53b6b4451f7e45e7438be8633abaec16e240 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 12:14:08 +1000 Subject: [PATCH 134/166] fsk: Give up after 5 seconds If FSK sync never happens, we can be stuck trying to sync to a FSK ID that never synchronises, it's an infinite loop. Make the infinite loop finite! --- fsk.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/fsk.c b/fsk.c index 0cb07b0..38a1e05 100644 --- a/fsk.c +++ b/fsk.c @@ -25,6 +25,12 @@ */ #define FSK_5M5S_SAMPLES (FSK_11MS_SAMPLES/2) +/* + * Set a limit of 5 seconds for a FSK. That allows for ~227 bits of FSK + * data or approximately 34 characters for an ID. That should be plenty! + */ +#define FSK_TIMEOUT PCM_MS_FRAMES(5000) + /* * Decode FSK ID * @@ -40,6 +46,8 @@ void GetFSK (char* const dest, uint8_t dest_sz) { double HiPow,LoPow,Hann[FSK_11MS_SAMPLES]; _Bool InSync = false; + uint32_t remain = FSK_TIMEOUT; + // Bit-reversion lookup table static const uint8_t BitRev[] = { 0x00, 0x20, 0x10, 0x30, 0x08, 0x28, 0x18, 0x38, @@ -58,13 +66,21 @@ void GetFSK (char* const dest, uint8_t dest_sz) { Hann[i] = 0.5 * (1 - cos( 2 * M_PI * i / ((double)(FSK_11MS_SAMPLES-1)) ) ); } - while ( true ) { + while ( remain > 0 ) { + // Figure out how much data to read? If not in sync, use 5.5ms steps. + uint32_t read_sz = (InSync ? FSK_11MS_SAMPLES: FSK_5M5S_SAMPLES); // Read data from DSP: half the number of samples if not in sync. - readPcm(InSync ? FSK_11MS_SAMPLES: FSK_5M5S_SAMPLES); + readPcm(read_sz); + + if (remain > read_sz) { + remain -= read_sz; + } else { + remain = 0; + } if (pcm.WindowPtr < (int32_t)FSK_5M5S_SAMPLES) { - pcm.WindowPtr += (InSync ? FSK_11MS_SAMPLES: FSK_5M5S_SAMPLES); + pcm.WindowPtr += read_sz; continue; } @@ -73,7 +89,7 @@ void GetFSK (char* const dest, uint8_t dest_sz) { fft.in[i] = pcm.Buffer[pcm.WindowPtr+i- FSK_5M5S_SAMPLES] * Hann[i]; } - pcm.WindowPtr += (InSync ? FSK_11MS_SAMPLES : FSK_5M5S_SAMPLES); + pcm.WindowPtr += read_sz; // FFT of last 22 ms fftw_execute(fft.Plan2048); @@ -124,7 +140,6 @@ void GetFSK (char* const dest, uint8_t dest_sz) { BytePtr ++; } } - } if (BytePtr < dest_sz) { From d3a9ec14e0ca92da8c364707a7b35677f06ec14d Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 12:46:12 +1000 Subject: [PATCH 135/166] common.h: Enlarge audio buffer to 5kB When using higher sample rates, this helps guarantee we've got sufficient audio in the buffer to detect signals. --- common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.h b/common.h index 9a84295..cc95687 100644 --- a/common.h +++ b/common.h @@ -3,7 +3,7 @@ #define MINSLANT 30 #define MAXSLANT 150 -#define BUFLEN 4096 +#define BUFLEN 5120 #define SYNCPIXLEN 1.5e-3 #include From defc466ac229dc63d51dc3948e7d1f9747eea966 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 12:59:48 +1000 Subject: [PATCH 136/166] slowrxd: Clamp sample rate to 48kHz. SSTV signals max out at 2.3kHz, which means we need a nyquist rate of at least ~6kHz to sample them properly. There's definitely advantages in going above the 8kHz sample rate most sound cards start at, but the limit above 48kHz is a case of diminishing returns. In any case, 96000 won't fit in `uint16_t` which we chose for the sample rate (it overflows, ask how I know). --- slowrxd.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/slowrxd.c b/slowrxd.c index 27df09a..af6e1bd 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -952,11 +952,19 @@ int main(int argc, char *argv[]) { case 'r': // Sample rate { char* endptr = NULL; - sample_rate = strtoul(optarg, &endptr, 10); + uint32_t rate = strtoul(optarg, &endptr, 10); if (*endptr) { printf("Invalid sample rate: %s\n", endptr); exit(DAEMON_EXIT_INVALID_ARG); } + if (rate > 48000) { + printf( + "Sample rate %" PRIu32 " out of range, " + "Clamping to 48kHz.\n", + rate); + rate = 48000; + } + sample_rate = rate; } break; case 'x': // Execute script on event From d244fa173bdffc629acaf2ef7cf467f9c147287b Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 14:20:18 +1000 Subject: [PATCH 137/166] slowrxd: Sanitise non-alphanumeric characters in FSK ID --- slowrxd.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/slowrxd.c b/slowrxd.c index af6e1bd..846af71 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -717,9 +718,25 @@ static void onListenerReceiveFinished(void) { } if (fsk_id && output_path_rem && (res >= 0)) { + /* fsk_id might contain characters that are unsafe! */ + char fsk_safe[strlen(fsk_id) + 1]; + char *c = fsk_safe; + strcpy(fsk_safe, fsk_id); + + while (*c) { + if (isalpha(*c)) { + /* Upper-case letters */ + *c = toupper(*c); + } else if (!isdigit(*c)) { + /* Convert non-digits to hypens */ + *c = '-'; + } + c++; + } + res = safe_strncat(output_path_log, "-", &output_path_rem); if (res >= 0) { - res = safe_strncat(output_path_log, fsk_id, &output_path_rem); + res = safe_strncat(output_path_log, fsk_safe, &output_path_rem); } } From 95d8fffac0a5c302f49b73575e1a0122da2f2412 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 15:05:07 +1000 Subject: [PATCH 138/166] slowrxd: Add some missed \n's --- slowrxd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 846af71..29ddc2a 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -634,7 +634,7 @@ static void onListenerReceivedManual(void) { } static void onListenerReceiveFSK(void) { - printf("Listener is now receiving FSK"); + printf("Listener is now receiving FSK\n"); if (emitSimpleReceiveLogRecord(logmsg_fsk_detect, NULL) < 0) { // Bail here! Abort = true; @@ -643,7 +643,7 @@ static void onListenerReceiveFSK(void) { static void onListenerReceivedFSKID(const char *id) { if (strlen(id)) { - printf("Listener got FSK %s", id); + printf("Listener got FSK %s\n", id); fsk_id = id; } else { printf("No FSK received\n"); From c6cc8bef02c4f33fa5ef12636445edcb73736b08 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 15:06:58 +1000 Subject: [PATCH 139/166] fft: Declare a constant for the FFT size --- fft.c | 10 +++++----- fft.h | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/fft.c b/fft.c index 393424d..61282cc 100644 --- a/fft.c +++ b/fft.c @@ -6,21 +6,21 @@ FFTStuff fft; int fft_init(void) { - fft.in = fftw_alloc_real(2048); + fft.in = fftw_alloc_real(FFT_BUFFER_SZ); if (fft.in == NULL) { perror("GetVideo: Unable to allocate memory for FFT"); return -1; } - fft.out = fftw_alloc_complex(2048); + fft.out = fftw_alloc_complex(FFT_BUFFER_SZ); if (fft.out == NULL) { perror("GetVideo: Unable to allocate memory for FFT"); fftw_free(fft.in); return -1; } - memset(fft.in, 0, sizeof(double) * 2048); + memset(fft.in, 0, sizeof(double) * FFT_BUFFER_SZ); - fft.Plan1024 = fftw_plan_dft_r2c_1d(1024, fft.in, fft.out, FFTW_ESTIMATE); - fft.Plan2048 = fftw_plan_dft_r2c_1d(2048, fft.in, fft.out, FFTW_ESTIMATE); + fft.Plan1024 = fftw_plan_dft_r2c_1d(FFT_BUFFER_SZ/2, fft.in, fft.out, FFTW_ESTIMATE); + fft.Plan2048 = fftw_plan_dft_r2c_1d(FFT_BUFFER_SZ, fft.in, fft.out, FFTW_ESTIMATE); return 0; } diff --git a/fft.h b/fft.h index 0b384ef..dff6ccf 100644 --- a/fft.h +++ b/fft.h @@ -1,5 +1,8 @@ #ifndef _FFT_H_ #define _FFT_H_ + +#define FFT_BUFFER_SZ (2048) + typedef struct _FFTStuff FFTStuff; struct _FFTStuff { double *in; From e554507631445030d65a16269a0e6bbda10f37d3 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 15:27:05 +1000 Subject: [PATCH 140/166] fft: Replace references to 1024 and 2048 magic numbers Refer to "half" and "full" size FFTs, in case we change the FFT size later on. --- fft.c | 4 ++-- fft.h | 6 ++++-- fsk.c | 4 ++-- video.c | 12 ++++++------ vis.c | 6 +++--- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/fft.c b/fft.c index 61282cc..fb01664 100644 --- a/fft.c +++ b/fft.c @@ -19,8 +19,8 @@ int fft_init(void) { } memset(fft.in, 0, sizeof(double) * FFT_BUFFER_SZ); - fft.Plan1024 = fftw_plan_dft_r2c_1d(FFT_BUFFER_SZ/2, fft.in, fft.out, FFTW_ESTIMATE); - fft.Plan2048 = fftw_plan_dft_r2c_1d(FFT_BUFFER_SZ, fft.in, fft.out, FFTW_ESTIMATE); + fft.PlanHalf = fftw_plan_dft_r2c_1d(FFT_HALF_SZ, fft.in, fft.out, FFTW_ESTIMATE); + fft.PlanFull = fftw_plan_dft_r2c_1d(FFT_FULL_SZ, fft.in, fft.out, FFTW_ESTIMATE); return 0; } diff --git a/fft.h b/fft.h index dff6ccf..13180ad 100644 --- a/fft.h +++ b/fft.h @@ -2,13 +2,15 @@ #define _FFT_H_ #define FFT_BUFFER_SZ (2048) +#define FFT_HALF_SZ (FFT_BUFFER_SZ/2) +#define FFT_FULL_SZ (FFT_BUFFER_SZ) typedef struct _FFTStuff FFTStuff; struct _FFTStuff { double *in; fftw_complex *out; - fftw_plan Plan1024; - fftw_plan Plan2048; + fftw_plan PlanHalf; + fftw_plan PlanFull; }; extern FFTStuff fft; diff --git a/fsk.c b/fsk.c index 38a1e05..c44082a 100644 --- a/fsk.c +++ b/fsk.c @@ -13,7 +13,7 @@ #include "pcm.h" #include "pic.h" -#define FSK_FFT_LEN (2048) +#define FSK_FFT_LEN (FFT_FULL_SZ) /* 11ms approximately in frames; half of 22ms bit period */ #define FSK_11MS_FRAMES PCM_MS_FRAMES(11) @@ -92,7 +92,7 @@ void GetFSK (char* const dest, uint8_t dest_sz) { pcm.WindowPtr += read_sz; // FFT of last 22 ms - fftw_execute(fft.Plan2048); + fftw_execute(fft.PlanFull); LoBin = GetBin(1900+CurrentPic.HedrShift, FSK_FFT_LEN)-1; MidBin = GetBin(2000+CurrentPic.HedrShift, FSK_FFT_LEN); diff --git a/video.c b/video.c index 887ef60..6fd1f2a 100644 --- a/video.c +++ b/video.c @@ -17,7 +17,7 @@ #define VIDEO_MAX_WIDTH (800) #define VIDEO_MAX_HEIGHT (616) #define VIDEO_MAX_CHANNELS (3) -#define VIDEO_FFT_LEN (1024) +#define VIDEO_FFT_LEN (FFT_HALF_SZ) static uint8_t VideoImage[VIDEO_MAX_WIDTH][VIDEO_MAX_HEIGHT][VIDEO_MAX_CHANNELS] = {{{0}}}; void (*OnVideoInitBuffer)(uint8_t Mode); @@ -267,7 +267,7 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { /*** Read ahead from sound card ***/ - if (pcm.WindowPtr == 0 || pcm.WindowPtr >= BUFLEN-1024) readPcm(2048); + if (pcm.WindowPtr == 0 || pcm.WindowPtr >= BUFLEN-FFT_HALF_SZ) readPcm(FFT_FULL_SZ); /*** Store the sync band for later adjustments ***/ @@ -281,7 +281,7 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { // Hann window for (i = 0; i < 64; i++) fft.in[i] = pcm.Buffer[pcm.WindowPtr+i-32] / 32768.0 * Hann[1][i]; - fftw_execute(fft.Plan1024); + fftw_execute(fft.PlanHalf); for (i=GetBin(1500+CurrentPic.HedrShift,VIDEO_FFT_LEN); i<=GetBin(2300+CurrentPic.HedrShift, VIDEO_FFT_LEN); i++) Praw += power(fft.out[i]); @@ -312,7 +312,7 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { // Apply Hann window for (i = 0; i < VIDEO_FFT_LEN; i++) fft.in[i] = pcm.Buffer[pcm.WindowPtr + i - VIDEO_FFT_LEN/2] / 32768.0 * Hann[6][i]; - fftw_execute(fft.Plan1024); + fftw_execute(fft.PlanHalf); // Calculate video-plus-noise power (1500-2300 Hz) @@ -371,14 +371,14 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { if (Mode == SDX && WinIdx < 6) WinIdx++; memset(fft.in, 0, sizeof(double)*VIDEO_FFT_LEN); - memset(Power, 0, sizeof(double)*1024); + memset(Power, 0, sizeof(double)*VIDEO_FFT_LEN); // Apply window function WinLength = HannLens[WinIdx]; for (i = 0; i < WinLength; i++) fft.in[i] = pcm.Buffer[pcm.WindowPtr + i - WinLength/2] / 32768.0 * Hann[WinIdx][i]; - fftw_execute(fft.Plan1024); + fftw_execute(fft.PlanHalf); MaxBin = 0; diff --git a/vis.c b/vis.c index a1618b9..eaf0aaa 100644 --- a/vis.c +++ b/vis.c @@ -32,7 +32,7 @@ UpdateVUCallback OnVisPowerComputed; uint8_t VIS; _Bool VisAutoStart; -#define VIS_FFT_LEN (2048) +#define VIS_FFT_LEN (FFT_FULL_SZ) static double VisPower[VIS_FFT_LEN] = {0}; uint8_t GetVIS () { @@ -74,7 +74,7 @@ uint8_t GetVIS () { } // FFT of last 20 ms - fftw_execute(fft.Plan2048); + fftw_execute(fft.PlanFull); // Find the bin with most power MaxBin = 0; @@ -176,7 +176,7 @@ uint8_t GetVIS () { if (++ptr == 10) { if (OnVisPowerComputed) { - OnVisPowerComputed(VisPower, 2048, 6); + OnVisPowerComputed(VisPower, VIS_FFT_LEN, 6); } ptr = 0; } From 44f7a54b48f37c4b62a2be4d040485ffe2e51137 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 15:46:05 +1000 Subject: [PATCH 141/166] video: Make FFT interval a constant Tried playing with this to see if I could get a "better" looking FFT for waterfall generation purposes. --- video.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/video.c b/video.c index 6fd1f2a..5eb6eb7 100644 --- a/video.c +++ b/video.c @@ -19,6 +19,9 @@ #define VIDEO_MAX_CHANNELS (3) #define VIDEO_FFT_LEN (FFT_HALF_SZ) +// Perform FFT every 6 samples +#define VIDEO_FFT_INTERVAL (6) + static uint8_t VideoImage[VIDEO_MAX_WIDTH][VIDEO_MAX_HEIGHT][VIDEO_MAX_CHANNELS] = {{{0}}}; void (*OnVideoInitBuffer)(uint8_t Mode); void (*OnVideoWritePixel)(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b); @@ -61,7 +64,7 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { double Pvideo_plus_noise=0, Pnoise_only=0, Pnoise=0, Psignal=0; double SNR = 0; double ChanStart[4] = {0}, ChanLen[4] = {0}; - uint8_t Channel = 0, WinIdx = 0; + uint8_t Channel = 0, WinIdx = 0; _PixelGrid *PixelGrid = calloc( ModeSpec[Mode].ImgWidth * ModeSpec[Mode].NumLines * 3, sizeof(_PixelGrid) ); @@ -351,7 +354,7 @@ _Bool GetVideo(uint8_t Mode, double Rate, int32_t Skip, _Bool Redraw) { /*** FM demodulation ***/ - if (SampleNum % 6 == 0) { // Take FFT every 6 samples + if (SampleNum % VIDEO_FFT_INTERVAL == 0) { // Take FFT every interval //PrevFreq = Freq; From 0acccb82b5ca37eb03f79660e489df6f430077d2 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 17:25:54 +1000 Subject: [PATCH 142/166] slowrxd: Allow some symlinks to be disabled --- slowrxd.c | 74 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 29ddc2a..89a9a09 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -864,6 +864,10 @@ char* path_append_dir_dup(const char* filename) { return strdup(path); } +static _Bool is_disabled_path(const char* path) { + return (path[0] == '-') && (path[1] == 0); +} + int main(int argc, char *argv[]) { // Initialise mutex int res = pthread_mutex_init(&rxlog_mutex, NULL); @@ -894,21 +898,33 @@ int main(int argc, char *argv[]) { case 'I': // In-progress image path if (path_inprogress_img_set) { free(path_inprogress_img); + } else { + path_inprogress_img_set = true; } - path_inprogress_img = path_append_dir_dup(optarg); - if (!path_inprogress_img) { - perror("Failed to compute full in-progress image path"); - exit(DAEMON_EXIT_INIT_PATH); + if (is_disabled_path(optarg)) { + path_inprogress_img = NULL; + } else { + path_inprogress_img = path_append_dir_dup(optarg); + if (!path_inprogress_img) { + perror("Failed to compute full in-progress image path"); + exit(DAEMON_EXIT_INIT_PATH); + } } break; case 'L': // In-progress receive log path if (path_inprogress_log_set) { free(path_inprogress_log); + } else { + path_inprogress_log_set = true; } - path_inprogress_log = path_append_dir_dup(optarg); - if (!path_inprogress_log) { - perror("Failed to compute full in-progress log path"); - exit(DAEMON_EXIT_INIT_PATH); + if (is_disabled_path(optarg)) { + path_inprogress_log = NULL; + } else { + path_inprogress_log = path_append_dir_dup(optarg); + if (!path_inprogress_log) { + perror("Failed to compute full in-progress log path"); + exit(DAEMON_EXIT_INIT_PATH); + } } break; case 'S': // Disable slant correction @@ -936,10 +952,10 @@ int main(int argc, char *argv[]) { " -S : disable slant correction\n" " -h : display this help and exit\n" " -d : set the directory where images are kept\n" - " -I : set the in-progress image path\n" - " -L : set the in-progress receive log path\n" - " -i : set the latest image path\n" - " -l : set the latest receive log path\n" + " -I : set the in-progress image path (- to disable)\n" + " -L : set the in-progress receive log path (- to disable)\n" + " -i : set the latest image path (- to disable)\n" + " -l : set the latest receive log path (- to disable)\n" " -r : set the ALSA PCM sample rate\n" " -p : set the ALSA PCM capture device\n" " -x : specify a script to run on receive events\n", @@ -949,21 +965,33 @@ int main(int argc, char *argv[]) { case 'i': // Latest image path if (path_latest_img_set) { free(path_latest_img); + } else { + path_latest_img_set = true; } - path_latest_img = path_append_dir_dup(optarg); - if (!path_latest_img) { - perror("Failed to compute full latest image path"); - exit(DAEMON_EXIT_INIT_PATH); + if (is_disabled_path(optarg)) { + path_latest_img = NULL; + } else { + path_latest_img = path_append_dir_dup(optarg); + if (!path_latest_img) { + perror("Failed to compute full latest image path"); + exit(DAEMON_EXIT_INIT_PATH); + } } break; case 'l': // Latest receive log path if (path_latest_log_set) { free(path_latest_log); + } else { + path_latest_log_set = true; } - path_latest_log = path_append_dir_dup(optarg); - if (!path_latest_log) { - perror("Failed to compute full latest log path"); - exit(DAEMON_EXIT_INIT_PATH); + if (is_disabled_path(optarg)) { + path_latest_log = NULL; + } else { + path_latest_log = path_append_dir_dup(optarg); + if (!path_latest_log) { + perror("Failed to compute full latest log path"); + exit(DAEMON_EXIT_INIT_PATH); + } } break; case 'r': // Sample rate @@ -1005,7 +1033,7 @@ int main(int argc, char *argv[]) { } } - if (!path_inprogress_img_set) { + if (!path_inprogress_img_set && path_inprogress_img) { path_inprogress_img = path_append_dir_dup(path_inprogress_img); if (!path_inprogress_img) { perror("Failed to compute full in-progress image path"); @@ -1013,7 +1041,7 @@ int main(int argc, char *argv[]) { } } - if (!path_inprogress_log_set) { + if (!path_inprogress_log_set && path_inprogress_log) { path_inprogress_log = path_append_dir_dup(path_inprogress_log); if (!path_inprogress_log) { perror("Failed to compute full in-progress log path"); @@ -1021,7 +1049,7 @@ int main(int argc, char *argv[]) { } } - if (!path_latest_img_set) { + if (!path_latest_img_set && path_latest_img) { path_latest_img = path_append_dir_dup(path_latest_img); if (!path_latest_img) { perror("Failed to compute full latest image path"); From fff93141465ec1f0fd7d640a5bc3a475da5da8e3 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 17:31:56 +1000 Subject: [PATCH 143/166] pcm: Add callback for reception of audio data This allows us to implement audio dump hooks to dump recordings of the incoming audio. --- pcm.c | 3 +++ pcm.h | 1 + 2 files changed, 4 insertions(+) diff --git a/pcm.c b/pcm.c index 2ef7cba..6cbf651 100644 --- a/pcm.c +++ b/pcm.c @@ -64,6 +64,9 @@ void readPcm(int32_t numsamples) { pcm.WindowPtr -= numsamples; } + if (pcm.PCMReadCallback) { + pcm.PCMReadCallback(numsamples, &pcm.Buffer[BUFLEN - numsamples]); + } } diff --git a/pcm.h b/pcm.h index 6c5c63a..6540cd5 100644 --- a/pcm.h +++ b/pcm.h @@ -12,6 +12,7 @@ struct _PcmData { int16_t *Buffer; TextStatusCallback OnPCMAbort; EventCallback OnPCMDrop; + void (*PCMReadCallback)(int32_t numsamples, const int16_t* samples); int32_t WindowPtr; uint16_t SampleRate; _Bool BufferDrop; From bfce8b83dd7b1b1be9386dabc18d3bc87f61cd87 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Fri, 12 Jul 2024 17:33:10 +1000 Subject: [PATCH 144/166] slowrxd: Add audio dump feature This causes the recording of the SSTV image to be dumped to a file (Sun audio format, because it's dead simple to implement) for later replay. --- slowrxd.c | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 200 insertions(+), 11 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 89a9a09..fabcd59 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -71,12 +72,18 @@ static char* path_inprogress_img = "inprogress.png"; /* The name of the receive log */ static char* path_inprogress_log = "inprogress.ndjson"; +/* The name of the audio dump file */ +static char* path_inprogress_audio = "inprogress.au"; + /* The name of the latest receive log */ static char* path_latest_log = "latest.ndjson"; /* The name of the latest received image */ static char* path_latest_img = "latest.png"; +/* The name of the latest received audio file */ +static char* path_latest_audio = "latest.au"; + /* The name of the directory where all images will be kept */ static const char* path_dir = NULL; @@ -96,6 +103,9 @@ static const char *fsk_id = NULL; static FILE* rxlog = NULL; static pthread_mutex_t rxlog_mutex; +/* Pointer to the audio dump (Sun Audio format) */ +static FILE* audiofile = NULL; + /* The currently selected mode */ const _ModeSpec* rxmode = NULL; @@ -103,9 +113,9 @@ const _ModeSpec* rxmode = NULL; static gdImagePtr rximg; /* Execute a script, passing in the image file and receive log */ -static void exec_rx_cmd(const char* event, const char* img_path, const char* log_path) { +static void exec_rx_cmd(const char* event, const char* img_path, const char* log_path, const char* audio_path) { if (rx_exec) { - printf("Running %s %s %s %s\n", rx_exec, event, img_path, log_path); + printf("Running %s %s %s %s %s\n", rx_exec, event, img_path, log_path, (audio_path ? audio_path : "")); pid_t child_pid = fork(); if (child_pid > 0) { printf("Waiting for PID %d\n", child_pid); @@ -135,7 +145,16 @@ static void exec_rx_cmd(const char* event, const char* img_path, const char* log return; } - char* argv[] = { _rx_exec, _event, _img_path, _log_path, NULL }; + char* _audio_path = NULL; + if (audio_path) { + _audio_path = strdup(log_path); + if (!_audio_path) { + perror("Failed to strdup audio path"); + return; + } + } + + char* argv[] = { _rx_exec, _event, _img_path, _log_path, _audio_path, NULL }; printf("Executing script\n"); int res = execve(rx_exec, argv, NULL); if (res < 0) { @@ -222,6 +241,96 @@ static int renameAndSymlink(const char* existing_path, const char* new_path, con return 0; } +/* Open the receive log ready for traffic */ +static int openAudioDump(void) { + assert(audiofile == NULL); + audiofile = fopen(path_inprogress_audio, "wb+"); + if (audiofile == NULL) { + perror("Failed to open audio dump file"); + return -errno; + } + + /* https://en.wikipedia.org/wiki/Au_file_format */ + uint32_t hdr[7] = { + 0x2e736e64, // Magic ".snd" + 28, // Data offset: 28 bytes (7*4-bytes) + UINT32_MAX, // Data size: unknown + 3, // Encoding: int16_t linear + pcm.SampleRate, // Sample rate + 1, // Channels + 0, // Annotation (unused) + }; + for (int i = 0; i < 7; i++) { + // Byte swap to big-endian + hdr[i] = htonl(hdr[i]); + } + + // Write the header + size_t sz = fwrite(hdr, sizeof(uint32_t), 7, audiofile); + if (sz < 7) { + perror("Failed to write audio file header"); + int res = fclose(audiofile); + if (res < 0) { + perror("Also failed to close partial file"); + } + return -errno; + } + + printf("Opened audio dump file: %s\n", path_inprogress_audio); + return 0; +} + +static int closeAudioDump(void) { + if (audiofile == NULL) + return 0; + + pcm.PCMReadCallback = NULL; + + int res = fclose(audiofile); + audiofile = NULL; + if (res < 0) { + perror("Failed to clse audio dump file"); + return -errno; + } + + return 0; +} + +static void writeToAudioDump(int32_t numsamples, const int16_t* samples) { + if (audiofile) { + int16_t be_samples[512]; + int res, i; + size_t sz; + + while (numsamples) { + // Clamp at the block size + int32_t block_sz = (numsamples > 512) ? 512 : numsamples; + // Copy to temp buffer + memcpy(be_samples, samples, sizeof(int16_t)*block_sz); + // Byteswap buffer + for (i = 0; i < block_sz; i++) { + be_samples[i] = htons(be_samples[i]); + } + // Write buffer + sz = fwrite(be_samples, sizeof(int16_t), block_sz, audiofile); + if (sz < (size_t)block_sz) { + perror("Failed to write audio block"); + closeAudioDump(); + return; + } + // Advance + numsamples -= block_sz; + samples += block_sz; + } + + res = fflush(audiofile); + if (res < 0) { + perror("Failed to flush audio file"); + closeAudioDump(); + } + } +} + /* Open the receive log ready for traffic */ static int openReceiveLog(void) { assert(rxlog == NULL); @@ -527,7 +636,18 @@ static void onVisIdentified(void) { } fsk_id = NULL; - exec_rx_cmd(logmsg_vis_detect, path_inprogress_img, path_inprogress_log); + exec_rx_cmd(logmsg_vis_detect, path_inprogress_img, path_inprogress_log, path_inprogress_audio); + + if (path_inprogress_audio) { + res = openAudioDump(); + if (res == 0) { + // Fetch the initial part that triggered this recording. + writeToAudioDump(pcm.WindowPtr*2, pcm.Buffer); + } + if (res == 0) { + pcm.PCMReadCallback = writeToAudioDump; + } + } } static void onVideoInitBuffer(uint8_t mode) { @@ -614,7 +734,7 @@ static int refreshImage(_Bool force) { } if (force || ((last_refresh - last_rx_exec) > RX_EXEC_INTERVAL)) { - exec_rx_cmd(logmsg_image_refreshed, path_inprogress_img, path_inprogress_log); + exec_rx_cmd(logmsg_image_refreshed, path_inprogress_img, path_inprogress_log, path_inprogress_audio); } return 0; } @@ -700,6 +820,7 @@ static void onListenerReceiveFinished(void) { char output_path_log[PATH_MAX]; char output_path_img[PATH_MAX]; + char output_path_audio[PATH_MAX]; size_t output_path_rem = sizeof(output_path_log) - 1; if (path_dir) { @@ -740,6 +861,15 @@ static void onListenerReceiveFinished(void) { } } + if (path_inprogress_audio) { + strncpy(output_path_audio, output_path_log, sizeof(output_path_audio)); + if (output_path_rem < 4) { + /* Truncate to make room for ".au\0" */ + output_path_audio[sizeof(output_path_audio) - 4] = 0; + } + strncat(output_path_audio, ".au", sizeof(output_path_audio) - strlen(output_path_audio) - 1); + } + strncpy(output_path_img, output_path_log, sizeof(output_path_img)); if (output_path_rem < 5) { /* Truncate to make room for ".png\0" */ @@ -772,10 +902,17 @@ static void onListenerReceiveFinished(void) { return; } + if (path_inprogress_audio) { + res = closeAudioDump(); + if (res == 0) { + res = renameAndSymlink(path_inprogress_audio, output_path_audio, path_latest_audio); + } + } + printf("Received %d × %d pixel image\n", ModeSpec[CurrentPic.Mode].ImgWidth, ModeSpec[CurrentPic.Mode].NumLines * ModeSpec[CurrentPic.Mode].LineHeight); - exec_rx_cmd(logmsg_receive_end, output_path_img, output_path_log); + exec_rx_cmd(logmsg_receive_end, output_path_img, output_path_log, output_path_audio); /* Wait for all children to exit */ wait(NULL); @@ -886,12 +1023,30 @@ int main(int argc, char *argv[]) { { _Bool path_inprogress_img_set = false; _Bool path_inprogress_log_set = false; + _Bool path_inprogress_audio_set = false; _Bool path_latest_img_set = false; _Bool path_latest_log_set = false; + _Bool path_latest_audio_set = false; int opt; while ((opt = getopt(argc, argv, "FISh:L:d:i:l:p:r:x:")) != -1) { switch (opt) { + case 'A': // In-progress audio path + if (path_inprogress_audio_set) { + free(path_inprogress_audio); + } else { + path_inprogress_audio_set = true; + } + if (is_disabled_path(optarg)) { + path_inprogress_audio = NULL; + } else { + path_inprogress_audio = path_append_dir_dup(optarg); + if (!path_inprogress_audio) { + perror("Failed to compute full in-progress audio path"); + exit(DAEMON_EXIT_INIT_PATH); + } + } + break; case 'F': // Disable FSKID ListenerEnableFSKID = false; break; @@ -930,6 +1085,22 @@ int main(int argc, char *argv[]) { case 'S': // Disable slant correction ListenerAutoSlantCorrect = false; break; + case 'a': // Latest audio path + if (path_latest_audio_set) { + free(path_latest_audio); + } else { + path_latest_audio_set = true; + } + if (is_disabled_path(optarg)) { + path_latest_audio = NULL; + } else { + path_latest_audio = path_append_dir_dup(optarg); + if (!path_latest_audio) { + perror("Failed to compute full latest audio path"); + exit(DAEMON_EXIT_INIT_PATH); + } + } + break; case 'd': // Set the output directory { char abs_dir[PATH_MAX]; @@ -942,18 +1113,20 @@ int main(int argc, char *argv[]) { } break; case 'h': - printf("Usage: %s [-h] [-F] [-S] [-I inprogress.png]\n" - "[-L inprogress.ndjson] [-d directory] [-i latest.png]\n" - "[-l latest.ndjson] [-p pcmdevice] [-r samplerate]\n" - "[-x script]\n" + printf("Usage: %s [-h] [-F] [-S] [-A inprogress.au]\n" + "[-I inprogress.png] [-L inprogress.ndjson] [-a latest.au]\n" + "[-d directory] [-i latest.png] [-l latest.ndjson]\n" + "[-p pcmdevice] [-r samplerate] [-x script]\n" "\n" "where:\n" " -F : disable FSK ID detection\n" " -S : disable slant correction\n" " -h : display this help and exit\n" " -d : set the directory where images are kept\n" + " -A : set the in-progress audio dump path (- to disable)\n" " -I : set the in-progress image path (- to disable)\n" " -L : set the in-progress receive log path (- to disable)\n" + " -a : set the latest audio dump path (- to disable)\n" " -i : set the latest image path (- to disable)\n" " -l : set the latest receive log path (- to disable)\n" " -r : set the ALSA PCM sample rate\n" @@ -1049,6 +1222,14 @@ int main(int argc, char *argv[]) { } } + if (!path_inprogress_audio_set && path_inprogress_audio) { + path_inprogress_audio = path_append_dir_dup(path_inprogress_audio); + if (!path_inprogress_audio) { + perror("Failed to compute full in-progress audio dump path"); + exit(DAEMON_EXIT_INIT_PATH); + } + } + if (!path_latest_img_set && path_latest_img) { path_latest_img = path_append_dir_dup(path_latest_img); if (!path_latest_img) { @@ -1057,13 +1238,21 @@ int main(int argc, char *argv[]) { } } - if (!path_latest_log_set) { + if (!path_latest_log_set && path_latest_log) { path_latest_log = path_append_dir_dup(path_latest_log); if (!path_latest_log) { perror("Failed to compute full latest log path"); exit(DAEMON_EXIT_INIT_PATH); } } + + if (!path_latest_audio_set) { + path_latest_audio = path_append_dir_dup(path_latest_audio); + if (!path_latest_audio) { + perror("Failed to compute full latest audio dump path"); + exit(DAEMON_EXIT_INIT_PATH); + } + } } // Prepare FFT From 792f04ef415a441776bc0fb67f0e1a24dda50a57 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Sat, 13 Jul 2024 08:44:57 +1000 Subject: [PATCH 145/166] slowrxd: Fix copy-paste error Wrong variable, twit! --- slowrxd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slowrxd.c b/slowrxd.c index fabcd59..41bca2a 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -147,7 +147,7 @@ static void exec_rx_cmd(const char* event, const char* img_path, const char* log char* _audio_path = NULL; if (audio_path) { - _audio_path = strdup(log_path); + _audio_path = strdup(audio_path); if (!_audio_path) { perror("Failed to strdup audio path"); return; From 6f8fa1b0b6669061c268aa001ee10aad7e2e5d23 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Sat, 13 Jul 2024 10:45:35 +1000 Subject: [PATCH 146/166] slowrxd: SIG_STRENGTH: Omit zero buckets These waste a lot of space in the JSON output as most of the 2048 buckets are zeroes. --- slowrxd.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 41bca2a..92bbf00 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -924,21 +924,44 @@ static void showVU(double *Power, int FFTLen, int WinIdx) { return; } + /* Scan forwards, find the first non-zero record */ + int first_bucket = 0; + for (int i = first_bucket; i < FFTLen; i++) { + if (Power[i] != 0.0) { + first_bucket = i; + break; + } + } + + /* Scan backwards, find the first non-zero record */ + int last_bucket = FFTLen-1; + for (int i = last_bucket; i >= first_bucket; i--) { + if (Power[i] != 0.0) { + last_bucket = i; + break; + } + } + int res = beginReceiveLogRecord(logmsg_sig_strength, NULL); if (res < 0) { Abort = true; return; } - res = fprintf(rxlog, ", \"win\": %d, \"fft\": [", WinIdx); + res = fprintf(rxlog, + ", \"win\": %d, " + "\"num\": %d, " + "\"first\": %d, " + "\"last\": \"fft\": [", + WinIdx, FFTLen, first_bucket); if (res < 0) { perror("Failed to write window index or start of FFT array"); Abort = true; return; } - for (int i = 0; i < FFTLen; i++) { - if (i > 0) { + for (int i = first_bucket; i <= last_bucket; i++) { + if (i > first_bucket) { res = emitLogRecordRaw(", "); if (res < 0) { Abort = true; From 5d0c6c585c3959e662babf211c68f76b042d52d5 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Sat, 13 Jul 2024 11:04:24 +1000 Subject: [PATCH 147/166] slowrxd: Add missed -a and -A options, tweak option settings --- slowrxd.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 92bbf00..4fdba4c 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -1052,7 +1052,7 @@ int main(int argc, char *argv[]) { _Bool path_latest_audio_set = false; int opt; - while ((opt = getopt(argc, argv, "FISh:L:d:i:l:p:r:x:")) != -1) { + while ((opt = getopt(argc, argv, "A:FI:Sh:L:a:d:i:l:p:r:x:")) != -1) { switch (opt) { case 'A': // In-progress audio path if (path_inprogress_audio_set) { @@ -1089,6 +1089,9 @@ int main(int argc, char *argv[]) { } } break; + case 'S': // Disable slant correction + ListenerAutoSlantCorrect = false; + break; case 'L': // In-progress receive log path if (path_inprogress_log_set) { free(path_inprogress_log); @@ -1105,9 +1108,6 @@ int main(int argc, char *argv[]) { } } break; - case 'S': // Disable slant correction - ListenerAutoSlantCorrect = false; - break; case 'a': // Latest audio path if (path_latest_audio_set) { free(path_latest_audio); From 103ada6981a647f72b6ac499cd64a586ec4510fb Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Sat, 13 Jul 2024 11:08:46 +1000 Subject: [PATCH 148/166] pcm: Add channel selection Some radio interfaces use a stereo input to support two mono receivers (e.g. NWDR UDRC-II and DRAWS both have this). This allows for selection of one channel over the other, or the option of mixing both channels together. The default is to use the left channel, as that matches what the audio subsystem did prior to this feature being added. --- gui.c | 6 +++++- pcm.c | 42 +++++++++++++++++++++++++++++++++++++++--- pcm.h | 7 ++++++- slowrxd.c | 28 ++++++++++++++++++++++++---- 4 files changed, 74 insertions(+), 9 deletions(-) diff --git a/gui.c b/gui.c index d4bb1ed..c87a6da 100644 --- a/gui.c +++ b/gui.c @@ -306,7 +306,11 @@ static void evt_changeDevices() { if (pcm.handle != NULL) snd_pcm_close(pcm.handle); - status = initPcmDevice(gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gui.combo_card)), 44100); + /* + * Choose 44.1kHz and left channel, to preserve current default behaviour. + * TODO: add UI elements to let the user choose. + */ + status = initPcmDevice(gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gui.combo_card)), 44100, PCM_CH_LEFT); switch(status) { diff --git a/pcm.c b/pcm.c index 6cbf651..4125df7 100644 --- a/pcm.c +++ b/pcm.c @@ -50,16 +50,51 @@ void readPcm(int32_t numsamples) { } + /* + * Channel selection: + * - For the left channel, we mask and sign-extend the lower 16-bits. + * - For the right channel, we arithmetically right-shift 16-bits. + * - For mono, we sum the left and right channels, divide by two then clamp. + */ + for (i = 0; i < samplesread; i++) { + switch (pcm.Channel) { + case PCM_CH_LEFT: + tmp[i] &= 0x0000ffff; + // Sign-extend! + if (tmp[i] & 0x00008000) { + tmp[i] |= 0xffff0000; + } + break; + case PCM_CH_MONO: + tmp[i] >>= 16; + break; + default: + { + const int16_t right = (int16_t)(tmp[i] >> 16); + const int16_t left = (int16_t)(tmp[i] & 0x0000ffff); + int32_t mono = (left + right) / 2; + if (mono > INT16_MAX) { + tmp[i] = INT16_MAX; + } else if (mono < INT16_MIN) { + tmp[i] = INT16_MIN; + } else { + tmp[i] = mono; + } + } + break; + } + } + if (pcm.WindowPtr == 0) { // Fill buffer on first run for (i=0; i Date: Sat, 13 Jul 2024 11:29:18 +1000 Subject: [PATCH 149/166] slowrxd: Fix malformed JSON output --- slowrxd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 52b5130..157fd22 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -952,8 +952,8 @@ static void showVU(double *Power, int FFTLen, int WinIdx) { ", \"win\": %d, " "\"num\": %d, " "\"first\": %d, " - "\"last\": \"fft\": [", - WinIdx, FFTLen, first_bucket); + "\"last\": %d, \"fft\": [", + WinIdx, FFTLen, first_bucket, last_bucket); if (res < 0) { perror("Failed to write window index or start of FFT array"); Abort = true; From 27d1145b7c21603f6a1ee524573c7d688066583a Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Sat, 13 Jul 2024 13:13:38 +1000 Subject: [PATCH 150/166] slowrxd: Fix -h requiring argument --- slowrxd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slowrxd.c b/slowrxd.c index 157fd22..82e5be8 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -1053,7 +1053,7 @@ int main(int argc, char *argv[]) { _Bool path_latest_audio_set = false; int opt; - while ((opt = getopt(argc, argv, "A:FI:Sh:L:a:c:d:i:l:p:r:x:")) != -1) { + while ((opt = getopt(argc, argv, "A:FI:SL:a:c:d:hi:l:p:r:x:")) != -1) { switch (opt) { case 'A': // In-progress audio path if (path_inprogress_audio_set) { From bf8b9de504a25c173f88e4d3f5d8cf2a5f282afe Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Sat, 13 Jul 2024 13:17:32 +1000 Subject: [PATCH 151/166] slowrxd: Indent command line arguments --- slowrxd.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/slowrxd.c b/slowrxd.c index 82e5be8..1667bda 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -1155,10 +1155,10 @@ int main(int argc, char *argv[]) { break; case 'h': printf("Usage: %s [-h] [-F] [-S] [-A inprogress.au]\n" - "[-I inprogress.png] [-L inprogress.ndjson] [-a latest.au]\n" - "[-c channel] [-d directory] [-i latest.png]\n" - "[-l latest.ndjson] [-p pcmdevice] [-r samplerate]\n" - "[-x script]\n" + "\t[-I inprogress.png] [-L inprogress.ndjson] [-a latest.au]\n" + "\t[-c channel] [-d directory] [-i latest.png]\n" + "\t[-l latest.ndjson] [-p pcmdevice] [-r samplerate]\n" + "\t[-x script]\n" "\n" "where:\n" " -F : disable FSK ID detection\n" From e3654689902af83a6e422c56e1b4abc516625612 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Sat, 13 Jul 2024 13:20:33 +1000 Subject: [PATCH 152/166] slowrxd: If filename given starts with /, assume absolute --- slowrxd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slowrxd.c b/slowrxd.c index 1667bda..e7ac790 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -1009,7 +1009,7 @@ char* path_append_dir_dup(const char* filename) { char path[PATH_MAX] = {0}; size_t path_rem = sizeof(path) - 1; - if (path_dir) { + if (path_dir || (filename[0] == '/')) { strncpy(path, path_dir, path_rem); path_rem -= strlen(path_dir); From 7a5a2c1f030e1268c4ae1a857d548dd10c5a1fe6 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Sat, 13 Jul 2024 13:26:01 +1000 Subject: [PATCH 153/166] slowrxd: require in-progress image path be set. --- slowrxd.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/slowrxd.c b/slowrxd.c index e7ac790..f6bded1 100644 --- a/slowrxd.c +++ b/slowrxd.c @@ -1166,7 +1166,7 @@ int main(int argc, char *argv[]) { " -h : display this help and exit\n" " -d : set the directory where images are kept\n" " -A : set the in-progress audio dump path (- to disable)\n" - " -I : set the in-progress image path (- to disable)\n" + " -I : set the in-progress image path\n" " -L : set the in-progress receive log path (- to disable)\n" " -a : set the latest audio dump path (- to disable)\n" " -c : set the audio channel to use, left, right or mono\n" @@ -1298,6 +1298,12 @@ int main(int argc, char *argv[]) { } } + // Require an in-progress image file output be provided + if (!path_inprogress_img) { + printf("In-progress image path is required\n"); + exit(DAEMON_EXIT_INVALID_ARG); + } + // Prepare FFT if (fft_init() < 0) { exit(DAEMON_EXIT_INIT_FFT_ERR); From 109f754f894185a797f7f2b32a3632ab9dd7fc1c Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Sat, 13 Jul 2024 14:10:35 +1000 Subject: [PATCH 154/166] README.md: Document the daemon --- README.md | 255 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 248 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 28c6c26..1c2e4c3 100644 --- a/README.md +++ b/README.md @@ -21,28 +21,269 @@ Features Requirements ------------ +### Common requirements + * Linux * Alsa (`libasound2-dev`) * FFTW 3 (`libfftw3-dev`) -For the GUI: -* Gtk+ 3.4 (`libgtk-3-dev`) - -For the daemon: -* libgd - And, obviously: * shortwave radio with SSB -* computer with sound card +* computer with sound card capable of at least 6kHz sample rate (>24kHz + strongly recommended) * means of getting sound from radio to sound card +### For the GUI: + +* gtk+ 3.4 (`libgtk-3-dev`) + +### For the daemon: + +* libgd + Compiling --------- `make` +### GUI only + +``` +make slowrx +``` + +### Daemon only + +The daemon is experimental. + +``` +make slowrxd +``` + Running ------- +### GUI + `./slowrx` + +### Daemon + +`./slowrxd [arguments]` + +The daemon runs in the foreground, the intention is to run it beneath a daemon +service like `supervisord` or `systemd`. An example unit file for `systemd` +might be (see the +[`systemd-unit` manpage](https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html) +for correct syntax): + +``` +[Unit] +Description=slowrxd + +[Service] +User=slowrxuser +ExecStart=/path/to/slowrxd [arguments] + +[Install] +WantedBy=multi-user.target +``` + +#### Daemon arguments + +These are listed with the `-h` argument: + +``` +$ ./slowrxd -h +Usage: ./slowrxd [-h] [-F] [-S] [-A inprogress.au] + [-I inprogress.png] [-L inprogress.ndjson] [-a latest.au] + [-c channel] [-d directory] [-i latest.png] + [-l latest.ndjson] [-p pcmdevice] [-r samplerate] + [-x script] + +where: + -F : disable FSK ID detection + -S : disable slant correction + -h : display this help and exit + -d : set the directory where images are kept + -A : set the in-progress audio dump path (- to disable) + -I : set the in-progress image path (- to disable) + -L : set the in-progress receive log path (- to disable) + -a : set the latest audio dump path (- to disable) + -c : set the audio channel to use, left, right or mono + -i : set the latest image path (- to disable) + -l : set the latest receive log path (- to disable) + -r : set the ALSA PCM sample rate + -p : set the ALSA PCM capture device + -x : specify a script to run on receive events +``` + +##### Paths + +`-d directory` sets the destination for files to `directory`, the path must +already exist. Default is the current working directory. All output files are +assumed to reside in the same directory (and **must** reside on the same +filesystem volume). As the image is received, it is periodically written to +the _in progress_ image path (see `-I`) as a PNG image. + +Alongside this file are two other files (unless disabled): + + * `inprogress.ndjson`: a [NDJSON](https://github.com/ndjson/ndjson-spec) log + file storing the events as the file was received. + * `inprogress.au`: a [Sun Audio](https://en.wikipedia.org/wiki/Au_file_format) + audio file containing the recording of the transmission. + +`-A`, `-I` and `-L` set the path to the audio dump, image and receive log of +the transmission currently being received. Relative to the output directory +(see `-d`) unless they start with a `/`. `-A` and `-L` may be disabled by +specifying `-` as the path. `-I` may not be disabled. + +`-a`, `-i` and `-l`: same as the upper-case counterparts, but these are for the +symbolic link to the _latest_ received image. Again, these are relative to the +output directory unless they start with a `/`. Pass `-` as the argument to +disable the corresponding symbolic link being generated. + +(**NOTE** the code that figures out what the _target_ of the symlink is, is +especially dumb and does **NOT** handle the symlink residing in a different +place to the output file.) + +When a SSTV image is fully received (after the FSK ID), the file names of the +three (or less) files currently named `inprogress` will be renamed to one of +the following forms: + + * `YYYY-mm-ddTHH-MMZ-MODE-FSKID` if the FSK ID was decoded (non-alphanumeric + characters in the FSK ID are replaced with `-`). + * `YYYY-mm-ddTHH-MMZ-MODE` if no FSK ID was detected (or it was disabled). + +The "latest" image symbolic links (`-a`, `-i` and `-l`) will then be re-created +to point to these new files. + +##### PCM input options + +`slowrxd` uses ALSA device names (see `arecord -L`). You can specify a +different PCM capture device with `-p name`. e.g. Pipewire users may want +`-p pipewire` or `-p pulse`. + +The sample rate can be adjusted with `-r`. The default is 44100 Hz for +historical reasons, however some users may want to set this to 48000 if they +have hardware that requires it (some modern sound cards do not natively support +44.1kHz) or they are using a userspace subsystem like PulseAudio, PipeWire or +JACK running at this (or any other) sample rate. + +Bare minimum sample rate required is 6kHz, and will work but deliver poor +results ([see this Mastodon thread](https://mastodon.longlandclan.id.au/@stuartl/112771433903628425) +for an example of how poor). + +`-c` selects which channel is used. By default, the left channel is used, but +if your interface requires it, you may choose the right channel, or have both +summed together (mono). Only the first letter (`l`, `m` or `r`) is used, and +case does not matter. + +##### SSTV decoder features + +By default, FSK ID detection and slant correction are enabled, you can disable +these with `-F` and `-S` respectively. + +##### Running scripts + +The daemon can run a script on events. The path to the script must include the +relative or full path to the script if it is not in `${PATH}` and must be +executable by the process running `slowrxd`. + +It will be called with 4 arguments: + + * the event being triggered (see log file format) + * the path to the image file (either the in-progress or final image) + * the path to the log file for the image + * the path to the audio file for the image + +This script **MUST** do its thing then return back to the process as soon as +possible, because it will block the SSTV receiver otherwise! + +A recommended solution to this is to write a simple shell script that launches +the real workhorse as a forked process in the background using +[`nohup`](https://en.wikipedia.org/wiki/Nohup): + +```bash +#!/bin/sh + +nohup $( dirname $( realpath $0 ) )/real-upload.sh "$@" > upload.log 2>&1 & +``` + +This script can do whatever you want: + + * upload the file to a web server for a SSTV cam + * notify another program + * post the image to social media + +…etc… + +#### The log file format + +NDJSON was used since JSON itself is relatively easy to generate from C code, +and this allows for separate JSON "packets" to be logged one per line, to the +file and still maintain valid JSON at all times. + +Each line of the NDJSON file is a JSON object storing a log record. + + * `timestamp` (required): The timestamp of the event, in milliseconds since + the 1st January 1970 (UTC). + * `type` (required): The type of log record being emitted. + * `msg` (optional): Message text string, if applicable. + +Each record type may have its own special parameters. + +##### `RECEIVE_START` records + +This indicates the start of a transmission. No special log records here. + +##### `VIS_DETECT` records + +This indicates the VIS header has been detected and decoded. + + * `code`: Raw VIS code as an integer + * `mode`: Human+Machine-readable "short" descriptor of the mode. See the + `ShortName` fields in `modespec.c`. + * `desc`: Human-readable description of the mode. See the `Name` field in + `modespec.c`. + +##### `SIG_STRENGTH` records + +This is the FFT power calculation. Zero-valued FFT buckets before and after +the signal are omitted. + + * `win`: The Hann window index being used + * `num`: the number of FFT buckets computed (usually 1024) + * `first`: the FFT bucket number of the first bucket storing non-zero data, + this will correspond to the first element of `fft`. + * `last`: the FFT bucket number of the last bucket storing non-zero data, this + will correspond to the last element of `fft`. + * `fft`: the FFT buckets between `first` and `last` (inclusive). + +All omitted buckets may be assumed to contain zeroes (`0.000000`). + +##### `IMAGE_REFRESHED` records + +These do not actually appear in the log, but instead are used exclusively with +the receive script. This event tells the receive script that the image has +been re-drawn with new image data. + +##### `FSK_DETECT` records + +Indication that the actual SSTV image has been received in full (and no slant +correction yet applied). + +##### `STATUS` records + +These indicate the status bar text that would be seen in the GTK+ UI. + +##### `FSK_RECEIVED` records + +These are emitted if a FSK ID is successfully decoded. + + * `id` is the FSK ID decoded. + +##### `RECEIVE_END` records + +Indicate that reception of this particular image has finished. From f5784fa086d2209a74156eaa986e9895c16e62c4 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Sat, 13 Jul 2024 14:19:47 +1000 Subject: [PATCH 155/166] contrib sstv-cam: Add an example SSTV cam script --- contrib/sstv-cam-script/README.md | 16 ++ contrib/sstv-cam-script/gengallery.sh | 248 ++++++++++++++++++++++++++ contrib/sstv-cam-script/style.css | 21 +++ contrib/sstv-cam-script/upload.sh | 6 + 4 files changed, 291 insertions(+) create mode 100644 contrib/sstv-cam-script/README.md create mode 100755 contrib/sstv-cam-script/gengallery.sh create mode 100644 contrib/sstv-cam-script/style.css create mode 100755 contrib/sstv-cam-script/upload.sh diff --git a/contrib/sstv-cam-script/README.md b/contrib/sstv-cam-script/README.md new file mode 100644 index 0000000..0f74195 --- /dev/null +++ b/contrib/sstv-cam-script/README.md @@ -0,0 +1,16 @@ +Example SSTV CAM page script +============================ + +Requirements +------------ + +- `jq`: for decoding the NDJSON files to extract the FSK ID and mode +- `file` for figuring out the dimensions of the PNG image +- `lame` for MP3-encoding the SSTV signals +- `rsync` for uploading the result + +Usage +----- + +Drop these files into your image directory and adjust to taste. Point +`slowrxd -x` at the `upload.sh` script. diff --git a/contrib/sstv-cam-script/gengallery.sh b/contrib/sstv-cam-script/gengallery.sh new file mode 100755 index 0000000..68826a4 --- /dev/null +++ b/contrib/sstv-cam-script/gengallery.sh @@ -0,0 +1,248 @@ +#!/bin/sh + +# gengallery: Generate a SSTV image gallery then upload it to a remote server. +# +# Requirements: +# - jq +# - file +# - lame +# - rsync +# +# The HTML fragments for each image are generated, then the resulting fragments +# assembled into a single HTML file for upload. + +echo "Handling event ${1}" +echo "Image file: ${2}" +echo "Log file: ${3}" +echo "Audio dump: ${4}" + +set -x + +IMAGE_DIR=$( dirname $( realpath ${0} ) ) + +parsets() { + if [ -n "$1" ]; then + TZ=UTC date --date @$(( $1 / 1000 )) -Isec + fi +} + +imgdims() { + file "$( realpath "$1" )" | cut -f 2 -d, \ + | sed -e 's/^ \+/w=/; s/ x / h=/; s/[^0-9]\+$//g;' + } + + showimg() { + if [ -f "${3}" ]; then + case "${3}" in + *.ndjson) + mode="$( head -n 1 "${3}" | jq -r .desc )" + start_ts="$( parsets $( head -n 1 "${3}" | jq -r .timestamp ) )" + end_ts="$( grep RECEIVE_END "${3}" | jq -r .timestamp )" + fsk_id="$( grep FSK_RECEIVED "${3}" | jq -r .id )" + ;; + *.json) + mode="$( jq -r .VIS_DETECT.desc "${3}" )" + start_ts="$( parsets $( jq -r .VIS_DETECT.timestamp "${3}" ) )" + end_ts="$( jq -r '.RECEIVE_END.timestamp // ""' "${3}" )" + fsk_id="$( jq -r '.FSK_RECEIVED.id // ""' "${3}" | tr -d '\n\t' | tr -C '[:alnum:]_/-' '?' )" + ;; + esac + else + mode="???" + start_ts="???" + end_ts="" + fsk_id="" + fi + + eval $( imgdims "${2}" ) + audio="${2%.png}.mp3" + + if [ "${5}" = featured ] && [ -n "${w}" ] && [ -n "${h}" ]; then + # Scale the image up + h=$(( (${h} * 720) / ${w} )) + w=720 + fi + + if [ -n "${w}" ]; then + w="width=\"${w}\"" + fi + + if [ -n "${h}" ]; then + h="height=\"${h}\"" + fi + + cat < +${1} +