0
1
mirror of https://github.com/radio95-rnt/rds95.git synced 2026-02-26 20:33:53 +01:00

add rt timeout, clean up code, add error checking for rdsencoder save, add rds eon af and remove stale values

This commit is contained in:
2025-04-05 13:48:51 +02:00
parent 5c84171497
commit 7b4706cf79
10 changed files with 159 additions and 109 deletions

View File

@@ -1,5 +1,5 @@
{
"port": 13452,
"time": 1743622887495,
"time": 1743850931238,
"version": "0.0.3"
}

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.10)
project(rds95 VERSION 1.2)
project(rds95 VERSION 1.3)
add_compile_options(-Wall -Werror -Wextra -pedantic -O2 -std=c18 -march=native -DVERSION=\"${PROJECT_VERSION}\")

View File

@@ -4,7 +4,7 @@ FFT = PLOT and True
import math
import io, os
if PLOT: import matplotlib.pyplot as plt
if FFT: import numpy as np # Import numpy for FFT
if FFT: import numpy as np
DATA_RATE = 1187.5
SIZE_RATIO = 2
@@ -16,33 +16,33 @@ if not sample_rate.is_integer(): raise ValueError("Need a even value")
# this is modified from ChristopheJacquet's pydemod
def rrcosfilter(NumSamples):
T_delta = 1/float(sample_rate)
sample_num = list(range(NumSamples))
h_rrc = [0.0] * NumSamples
SymbolPeriod = 1/(2*DATA_RATE)
T_delta = 1/float(sample_rate)
sample_num = list(range(NumSamples))
h_rrc = [0.0] * NumSamples
SymbolPeriod = 1/(2*DATA_RATE)
for x in sample_num:
t = (x-NumSamples/2)*T_delta
if t == 0.0:
h_rrc[x] = 1.0 - 1 + (4/math.pi)
elif t == SymbolPeriod/4:
h_rrc[x] = (1/math.sqrt(2))*(((1+2/math.pi)* \
(math.sin(math.pi/4))) + ((1-2/math.pi)*(math.cos(math.pi/4))))
elif t == -SymbolPeriod/4:
h_rrc[x] = (1/math.sqrt(2))*(((1+2/math.pi)* \
(math.sin(math.pi/4))) + ((1-2/math.pi)*(math.cos(math.pi/4))))
else:
h_rrc[x] = (4*(t/SymbolPeriod)*math.cos(math.pi*t*2/SymbolPeriod))/ \
(math.pi*t*(1-(4*t/SymbolPeriod)*(4*t/SymbolPeriod))/SymbolPeriod)
for x in sample_num:
t = (x-NumSamples/2)*T_delta
if t == 0.0:
h_rrc[x] = 1.0 - 1 + (4/math.pi)
elif t == SymbolPeriod/4:
h_rrc[x] = (1/math.sqrt(2))*(((1+2/math.pi)* \
(math.sin(math.pi/4))) + ((1-2/math.pi)*(math.cos(math.pi/4))))
elif t == -SymbolPeriod/4:
h_rrc[x] = (1/math.sqrt(2))*(((1+2/math.pi)* \
(math.sin(math.pi/4))) + ((1-2/math.pi)*(math.cos(math.pi/4))))
else:
h_rrc[x] = (4*(t/SymbolPeriod)*math.cos(math.pi*t*2/SymbolPeriod))/ \
(math.pi*t*(1-(4*t/SymbolPeriod)*(4*t/SymbolPeriod))/SymbolPeriod)
return h_rrc
return h_rrc
def convolve(a, b):
out = [0] * (len(a) + len(b) - 1)
for i in range(len(a)):
for j in range(len(b)):
out[i+j] += a[i] * b[j]
return out
out = [0] * (len(a) + len(b) - 1)
for i in range(len(a)):
for j in range(len(b)):
out[i+j] += a[i] * b[j]
return out
PATH = os.path.dirname(os.path.abspath(__file__))
@@ -63,61 +63,58 @@ outc.write(header)
outh.write(header)
def generate():
l = ratio // 2
l = ratio // 2
sample = [0.0] * (16*l)
sample[l] = 1
sample[2*l] = -1
sample = [0.0] * (16*l)
sample[l] = 1
sample[2*l] = -1
sf = rrcosfilter(l*16)
shapedSamples = convolve(sample, sf)
lowest = 0
lowest_idx = 0
highest = 0
highest_idx = 0
for i,j in enumerate(shapedSamples):
if j < lowest:
lowest = j
lowest_idx = i
if j > highest:
highest = j
highest_idx = i
middle = int((lowest_idx+highest_idx)/2)
sf = rrcosfilter(l*16)
shapedSamples = convolve(sample, sf)
out = shapedSamples[middle-int(ratio*SIZE_RATIO):middle+int(ratio*SIZE_RATIO)]
out = [2 * (i - min(out)) / (max(out) - min(out)) - 1 for i in out]
if max(out) > 1 or min(out) < -1: raise Exception("Clipped")
print(f"{len(out)=} {len(out)/sample_rate=} {(len(out)/sample_rate)/(1/DATA_RATE)=} {1/DATA_RATE=}")
lowest = 0
lowest_idx = 0
highest = 0
highest_idx = 0
for i,j in enumerate(shapedSamples):
if j < lowest:
lowest = j
lowest_idx = i
if j > highest:
highest = j
highest_idx = i
middle = int((lowest_idx+highest_idx)/2)
if PLOT:
# Plot the waveform
plt.plot(out, label="out")
plt.legend()
plt.grid(True)
plt.show()
out = shapedSamples[middle-int(ratio*SIZE_RATIO):middle+int(ratio*SIZE_RATIO)]
out = [2 * (i - min(out)) / (max(out) - min(out)) - 1 for i in out]
if max(out) > 1 or min(out) < -1: raise Exception("Clipped")
print(f"{len(out)=} {len(out)/sample_rate=} {(len(out)/sample_rate)/(1/DATA_RATE)=} {1/DATA_RATE=}")
if FFT:
# Compute the FFT of the waveform
N = len(out)
fft_out = np.fft.fft(out)
fft_freqs = np.fft.fftfreq(N, d=1/sample_rate)
if PLOT:
plt.plot(out, label="out")
plt.legend()
plt.grid(True)
plt.show()
# Plot the magnitude of the FFT
plt.figure(figsize=(10, 6))
plt.plot(fft_freqs[:N//2], np.abs(fft_out)[:N//2]) # Plot only the positive frequencies
plt.xlim(0,DATA_RATE*3)
plt.title("FFT of the waveform")
plt.xlabel("Frequency (Hz)")
plt.ylabel("Magnitude")
plt.grid(True)
plt.show()
if FFT:
N = len(out)
fft_out = np.fft.fft(out)
fft_freqs = np.fft.fftfreq(N, d=1/sample_rate)
outc.write(u"float waveform_biphase[{size}] = {{{values}}};\n\n".format(
values = u", ".join(map(str, out)),
size = len(out)))
plt.figure(figsize=(10, 6))
plt.plot(fft_freqs[:N//2], np.abs(fft_out)[:N//2])
plt.xlim(0,DATA_RATE*3)
plt.title("FFT of the waveform")
plt.xlabel("Frequency (Hz)")
plt.ylabel("Magnitude")
plt.grid(True)
plt.show()
outh.write(u"extern float waveform_biphase[{size}];\n".format(size=len(out)))
outc.write(u"float waveform_biphase[{size}] = {{{values}}};\n\n".format(
values = u", ".join(map(str, out)),
size = len(out)))
outh.write(u"extern float waveform_biphase[{size}];\n".format(size=len(out)))
generate()

View File

@@ -236,6 +236,12 @@ static void handle_grpseq2(char *arg, RDSModulator* mod, char* output) {
strcpy(output, "+\0");
}
static void handle_dttmout(char *arg, RDSModulator* mod, char* output) {
mod->enc->data[mod->enc->program].original_rt_text_timeout = atoi(arg);
mod->enc->data[mod->enc->program].rt_text_timeout = mod->enc->data[mod->enc->program].original_rt_text_timeout;
strcpy(output, "+\0");
}
static void handle_level(char *arg, RDSModulator* mod, char* output) {
mod->params.level = atoi(arg)/255.0f;
strcpy(output, "+\0");
@@ -313,7 +319,7 @@ static void handle_init(char *arg, RDSModulator* mod, char* output) {
static void handle_ver(char *arg, RDSModulator* mod, char* output) {
(void)arg;
(void)mod;
sprintf(output, "Firmware v. 1.2 - (C) 2025 radio95");
sprintf(output, "Encoder v. %s - (C) 2025 radio95", VERSION);
}
static void handle_eonen(char *arg, char *pattern, RDSModulator* mod, char* output) {
@@ -453,6 +459,7 @@ static const command_handler_t commands_eq8[] = {
{"PROGRAM", handle_program, 7},
{"RDS2MOD", handle_rds2mod, 7},
{"GRPSEQ2", handle_grpseq2, 7},
{"DTTMOUT", handle_dttmout, 7},
};
static const command_handler_t commands_exact[] = {
{"INIT", handle_init, 4},

View File

@@ -10,4 +10,8 @@
#define M_PI 3.14159265358979323846
#endif
#define M_2PI (M_PI * 2.0)
#define M_2PI (M_PI * 2.0)
#ifndef VERSION
#define VERSION "-.-"
#endif

View File

@@ -17,7 +17,7 @@ int _strnlen(const char *s, int maxlen) {
return len;
}
// For RDS2 RFT, and UECP
// For RDS2 RFT, file error checking, and UECP
uint16_t crc16_ccitt(char* data, uint16_t len) {
uint16_t i, crc=0xFFFF;
for (i=0; i < len; i++ ) {

View File

@@ -83,9 +83,7 @@ void init_rds_modulator(RDSModulator* rdsMod, RDSEncoder* enc) {
}
float get_rds_sample(RDSModulator* rdsMod, uint8_t stream) {
uint16_t idx;
float *cur_waveform;
float sample;
if (rdsMod->data[stream].sample_count == SAMPLES_PER_BIT) {
if (rdsMod->data[stream].bit_pos == BITS_PER_GROUP) {
get_rds_bits(rdsMod->enc, rdsMod->data[stream].bit_buffer, stream);
@@ -96,7 +94,7 @@ float get_rds_sample(RDSModulator* rdsMod, uint8_t stream) {
rdsMod->data[stream].prev_output = rdsMod->data[stream].cur_output;
rdsMod->data[stream].cur_output = rdsMod->data[stream].prev_output ^ rdsMod->data[stream].cur_bit;
idx = rdsMod->data[stream].in_sample_index;
uint16_t idx = rdsMod->data[stream].in_sample_index;
cur_waveform = waveform[rdsMod->data[stream].cur_output];
for (uint16_t i = 0; i < FILTER_SIZE; i++) {
@@ -111,19 +109,18 @@ float get_rds_sample(RDSModulator* rdsMod, uint8_t stream) {
}
rdsMod->data[stream].sample_count++;
if(rdsMod->data[stream].symbol_shifting.symbol_shift != 0) {
float sample = rdsMod->data[stream].sample_buffer[rdsMod->data[stream].out_sample_index];
if(stream != 0 && rdsMod->data[stream].symbol_shifting.symbol_shift != 0) {
rdsMod->data[stream].symbol_shifting.sample_buffer[rdsMod->data[stream].symbol_shifting.sample_buffer_idx++] = rdsMod->data[stream].sample_buffer[rdsMod->data[stream].out_sample_index];
if (rdsMod->data[stream].symbol_shifting.sample_buffer_idx == rdsMod->data[stream].symbol_shifting.symbol_shift) rdsMod->data[stream].symbol_shifting.sample_buffer_idx = 0;
sample = rdsMod->data[stream].symbol_shifting.sample_buffer[rdsMod->data[stream].symbol_shifting.sample_buffer_idx];
} else {
sample = rdsMod->data[stream].sample_buffer[rdsMod->data[stream].out_sample_index];
}
rdsMod->data[stream].sample_buffer[rdsMod->data[stream].out_sample_index++] = 0;
if (rdsMod->data[stream].out_sample_index == SAMPLE_BUFFER_SIZE)
rdsMod->data[stream].out_sample_index = 0;
if (rdsMod->data[stream].out_sample_index == SAMPLE_BUFFER_SIZE) rdsMod->data[stream].out_sample_index = 0;
uint8_t tooutput = 1;
if (rdsMod->params.rdsgen == 0) {
tooutput = 0;

View File

@@ -40,7 +40,7 @@ void saveToFile(RDSEncoder *emp, const char *option) {
} else if (strcmp(option, "PTYN") == 0) {
memcpy(tempEncoder.data[emp->program].ptyn, emp->data[emp->program].ptyn, PTYN_LENGTH);
tempEncoder.data[emp->program].ptyn_enabled = emp->data[emp->program].ptyn_enabled;
} else if (strcmp(option, "AF") == 0 || strcmp(option, "AFCH") == 0) {
} else if (strcmp(option, "AF") == 0) {
memcpy(&(tempEncoder.data[emp->program].af), &(emp->data[emp->program].af), sizeof(emp->data[emp->program].af));
} else if (strcmp(option, "ECC") == 0) {
tempEncoder.data[emp->program].ecc = emp->data[emp->program].ecc;
@@ -97,6 +97,7 @@ void saveToFile(RDSEncoder *emp, const char *option) {
memcpy(&(rdsEncoderfile.rtpData[emp->program]), &(tempEncoder.rtpData[emp->program]), sizeof(RDSRTPlusData));
memcpy(&(rdsEncoderfile.encoder_data), &(tempEncoder.encoder_data), sizeof(RDSEncoderData));
rdsEncoderfile.program = tempEncoder.program;
rdsEncoderfile.crc = crc16_ccitt((char*)&rdsEncoderfile, sizeof(RDSEncoderFile) - sizeof(uint16_t));
file = fopen(encoderPath, "wb");
if (file == NULL) {
@@ -121,10 +122,17 @@ void loadFromFile(RDSEncoder *enc) {
fclose(file);
if (rdsEncoderfile.file_starter != 225 || rdsEncoderfile.file_ender != 95 || rdsEncoderfile.file_middle != 160) {
fprintf(stderr, "Invalid file format\n");
fprintf(stderr, "[RDSENCODER-FILE] Invalid file format\n");
return;
}
uint16_t calculated_crc = crc16_ccitt((char*)&rdsEncoderfile, sizeof(RDSEncoderFile) - sizeof(uint16_t));
if (calculated_crc != rdsEncoderfile.crc) {
fprintf(stderr, "[RDSENCODER-FILE] CRC mismatch! Data may be corrupted\n");
return;
}
for (int i = 0; i < PROGRAMS; i++) {
memcpy(&(enc->data[i]), &(rdsEncoderfile.data[i]), sizeof(RDSData));
memcpy(&(enc->rtpData[i]), &(rdsEncoderfile.rtpData[i]), sizeof(RDSRTPlusData));
@@ -145,25 +153,48 @@ int rdssaved() {
}
static uint16_t get_next_af(RDSEncoder* enc) {
static uint8_t af_state;
uint16_t out;
if (enc->data[enc->program].af.num_afs) {
if (af_state == 0) {
if (enc->state[enc->program].af_state == 0) {
out = (AF_CODE_NUM_AFS_BASE + enc->data[enc->program].af.num_afs) << 8;
out |= enc->data[enc->program].af.afs[0];
af_state += 1;
enc->state[enc->program].af_state += 1;
} else {
out = enc->data[enc->program].af.afs[af_state] << 8;
if (enc->data[enc->program].af.afs[af_state + 1])
out |= enc->data[enc->program].af.afs[af_state + 1];
out = enc->data[enc->program].af.afs[enc->state[enc->program].af_state] << 8;
if (enc->data[enc->program].af.afs[enc->state[enc->program].af_state + 1])
out |= enc->data[enc->program].af.afs[enc->state[enc->program].af_state + 1];
else
out |= AF_CODE_FILLER;
af_state += 2;
enc->state[enc->program].af_state += 2;
}
if (af_state >= enc->data[enc->program].af.num_entries) af_state = 0;
if (enc->state[enc->program].af_state >= enc->data[enc->program].af.num_entries) enc->state[enc->program].af_state = 0;
} else {
out = AF_CODE_NO_AF << 8 | AF_CODE_FILLER;
out = AF_CODE_NUM_AFS_BASE << 8 | AF_CODE_FILLER;
}
return out;
}
static uint16_t get_next_af_eon(RDSEncoder* enc, uint8_t eon_index) {
uint16_t out;
if (enc->data[enc->program].eon[eon_index].af.num_afs) {
if (enc->state[enc->program].eon_states[eon_index].af_state == 0) {
out = (AF_CODE_NUM_AFS_BASE + enc->data[enc->program].af.num_afs) << 8;
out |= enc->data[enc->program].eon[eon_index].af.afs[0];
enc->state[enc->program].eon_states[eon_index].af_state += 1;
} else {
out = enc->data[enc->program].eon[eon_index].af.afs[enc->state[enc->program].eon_states[eon_index].af_state] << 8;
if (enc->data[enc->program].eon[eon_index].af.afs[enc->state[enc->program].eon_states[eon_index].af_state + 1])
out |= enc->data[enc->program].eon[eon_index].af.afs[enc->state[enc->program].eon_states[eon_index].af_state + 1];
else
out |= AF_CODE_FILLER;
enc->state[enc->program].eon_states[eon_index].af_state += 2;
}
if (enc->state[enc->program].eon_states[eon_index].af_state >= enc->data[enc->program].eon[eon_index].af.num_entries) enc->state[enc->program].eon_states[eon_index].af_state = 0;
} else {
out = AF_CODE_NUM_AFS_BASE << 8 | AF_CODE_FILLER;
}
return out;
@@ -355,7 +386,10 @@ get_eon:
blocks[2] |= eon.ps[enc->state[enc->program].eon_state*2 + 1];
blocks[1] |= enc->state[enc->program].eon_state;
break;
case 4: // 13
case 4:
blocks[2] = get_next_af_eon(enc, enc->state[enc->program].eon_index);
break;
case 5: // 13
if(eon.pty == 0 && eon.tp == 0) {
break;
}
@@ -363,12 +397,11 @@ get_eon:
if(eon.tp) blocks[2] |= eon.ta;
blocks[1] |= 13;
break;
// TODO: Add AF
}
blocks[3] = eon.pi;
if(enc->state[enc->program].eon_state == 4) {
if(enc->state[enc->program].eon_state == 5) {
enc->state[enc->program].eon_index++;
uint8_t i = 0;
@@ -556,6 +589,14 @@ static void get_rds_group(RDSEncoder* enc, uint16_t *blocks, uint8_t stream) {
}
}
if(enc->data[enc->program].rt1_enabled && enc->data[enc->program].rt_text_timeout != 0) {
enc->data[enc->program].rt_text_timeout--;
if(enc->data[enc->program].rt_text_timeout == 0) {
enc->state[enc->program].rt_update = 1;
memccpy(enc->state[enc->program].rt_text, enc->data[enc->program].default_rt, 0, RT_LENGTH);
}
}
if(enc->data[enc->program].ct && stream == 0) {
get_rds_ct_group(enc, blocks);
goto group_coded;
@@ -742,6 +783,8 @@ void init_rds_encoder(RDSEncoder* enc) {
void set_rds_rt1(RDSEncoder* enc, char *rt1) {
uint8_t i = 0, len = 0;
enc->data[enc->program].rt_text_timeout = enc->data[enc->program].original_rt_text_timeout;
enc->state[enc->program].rt_update = 1;
memset(enc->data[enc->program].rt1, ' ', RT_LENGTH);
@@ -767,7 +810,7 @@ void set_rds_rt1(RDSEncoder* enc, char *rt1) {
void set_rds_rt2(RDSEncoder* enc, char *rt2) {
uint8_t i = 0, len = 0;
enc->state[enc->program].rt2_update = 1;
memset(enc->data[enc->program].rt2, ' ', RT_LENGTH);

View File

@@ -26,8 +26,7 @@
#define MAX_AFS 25
#define AF_CODE_FILLER 205
#define AF_CODE_NO_AF 224
#define AF_CODE_NUM_AFS_BASE AF_CODE_NO_AF
#define AF_CODE_NUM_AFS_BASE 224
#define AF_CODE_LFMF_FOLLOWS 250
#define PROGRAMS 2
@@ -56,9 +55,6 @@ typedef struct {
char ps[PS_LENGTH];
char rt1[RT_LENGTH];
uint8_t dsn;
uint8_t psn;
uint8_t ecc;
uint8_t ta : 1;
@@ -103,6 +99,9 @@ typedef struct {
RDSEON eon[4];
} RDSData;
typedef struct {
uint8_t af_state : 6;
} RDSEONState;
typedef struct {
uint8_t ps_update : 1;
uint8_t tps_update : 1;
@@ -148,6 +147,9 @@ typedef struct {
uint8_t eon_index : 3;
uint8_t eon_state : 4;
RDSEONState eon_states[4];
uint8_t af_state : 6;
uint16_t last_stream0_group[3];
} RDSState;
@@ -197,6 +199,7 @@ typedef struct {
RDSEncoderData encoder_data;
uint8_t program : 3;
uint8_t file_ender; // Always is 95 my freq
uint16_t crc;
} RDSEncoderFile;
#define GROUP_TYPE_0 ( 0 << 4)

View File

@@ -13,7 +13,7 @@
#define RDS_DEVICE "RDS"
#define NUM_MPX_FRAMES 256
#define NUM_MPX_FRAMES 128
static uint8_t stop_rds;
@@ -34,7 +34,7 @@ static void *control_pipe_worker(void* modulator) {
}
static void show_version() {
printf("rds95 (a RDS encoder by radio95) version 1.2\n");
printf("rds95 (a RDS encoder by radio95) version %s\n", VERSION);
}
static void show_help(char *name) {
@@ -43,7 +43,6 @@ static void show_help(char *name) {
"Usage: %s [options]\n"
"\n"
" -C,--ctl FIFO control pipe\n"
" -h,--help Show this help text and exit\n"
"\n",
name
);
@@ -95,8 +94,8 @@ int main(int argc, char **argv) {
format.rate = RDS_SAMPLE_RATE;
buffer.prebuf = 0;
buffer.tlength = 12228;
buffer.maxlength = 12228;
buffer.tlength = NUM_MPX_FRAMES*4;
buffer.maxlength = NUM_MPX_FRAMES*4;
rds_device = pa_simple_new(
NULL,