diff --git a/.vscode/.server-controller-port.log b/.vscode/.server-controller-port.log index ab62b22..ac0c151 100644 --- a/.vscode/.server-controller-port.log +++ b/.vscode/.server-controller-port.log @@ -1,5 +1,5 @@ { - "port": 9145, - "time": 1742311854709, + "port": 13452, + "time": 1742563941525, "version": "0.0.3" } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index d7f8309..09044ed 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,7 +18,8 @@ "ios": "c", "stdint.h": "c", "asoundlib.h": "c", - "hilbert.h": "c" + "hilbert.h": "c", + "fm_modulator.h": "c" }, "C_Cpp.errorSquiggles": "disabled" } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 4491d9b..5e59f22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,35 +1,24 @@ -# Set the minimum required CMake version cmake_minimum_required(VERSION 3.10) -# Define the project name and language project(FMTools LANGUAGES C) -# Set the C standard (you can adjust this based on your project needs) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED YES) -# Find all C source files in the src/ directory file(GLOB SRC_FILES "src/*.c") -# Find all C source files in the lib/ directory file(GLOB LIB_FILES "lib/*.c") -# Create a library to hold all object files from lib/ add_library(libfm OBJECT ${LIB_FILES}) -# Linker flags for libraries set(LINK_LIBS "-lpulse -lpulse-simple -lm") -# Loop through each file in src and create an executable foreach(SRC_FILE ${SRC_FILES}) - # Get the filename without the directory and extension get_filename_component(EXEC_NAME ${SRC_FILE} NAME_WE) - # Create the executable from each source file add_executable(${EXEC_NAME} ${SRC_FILE}) - target_compile_options(${EXEC_NAME} PRIVATE -O1) + target_compile_options(${EXEC_NAME} PRIVATE -O2 -Wall -Wextra -Werror -Wno-unused-parameter) - # Link the necessary libraries and object files from lib/ target_link_libraries(${EXEC_NAME} PRIVATE libfm ${LINK_LIBS}) install(TARGETS ${EXEC_NAME} diff --git a/lib/constants.h b/lib/constants.h index 5239276..59127bd 100644 --- a/lib/constants.h +++ b/lib/constants.h @@ -1,3 +1,4 @@ +#pragma once #ifndef M_PI #define M_PI 3.14159265358979323846 #endif diff --git a/src/chimer95.c b/src/chimer95.c index 19bb7bc..9072a73 100644 --- a/src/chimer95.c +++ b/src/chimer95.c @@ -5,35 +5,33 @@ #include #include -#define buffer_maxlength 2048 -#define buffer_tlength_fragsize 2048 -#define buffer_prebuf 32 +#define buffer_maxlength 1024 +#define buffer_tlength_fragsize 1024 +#define buffer_prebuf 0 #include "../lib/constants.h" #include "../lib/oscillator.h" -#define FREQ 1000.0f -#define SAMPLE_RATE 4000 +#define DEFAULT_FREQ 1000.0f +#define DEFAULT_SAMPLE_RATE 4000 -#define OUTPUT_DEVICE "alsa_output.platform-soc_sound.stereo-fallback" +#define OUTPUT_DEVICE "FM_MPX" -#define BUFFER_SIZE 512 +#define BUFFER_SIZE 256 #include #include -#define MASTER_VOLUME 0.5f // Volume -#define OFFSET 0 // Offset in seconds +#define DEFAULT_MASTER_VOLUME 0.5f +#define DEFAULT_OFFSET 0 -// Define pip and beep durations in milliseconds -#define PIP_DURATION 100 // 100ms pip -#define PIP_PAUSE 900 // 900ms pause between pips -#define BEEP_DURATION 500 // 500ms beep +#define PIP_DURATION 100 +#define PIP_PAUSE 900 +#define BEEP_DURATION 500 -// Sequence types #define SEQ_NONE 0 -#define SEQ_29_56 1 -#define SEQ_59_55 2 +#define SEQ_29_56 1 +#define SEQ_59_55 2 #define SEQ_TEST_HOUR 3 volatile sig_atomic_t to_run = 1; @@ -49,7 +47,7 @@ static void stop(int signum) { } void show_version() { - printf("chimer95 (gts time signal encoder by radio95) version 1.0\n"); + printf("chimer95 (GTS time signal encoder by radio95) version 1.1\n"); } void show_help(char *name) { @@ -63,90 +61,77 @@ void show_help(char *name) { " -T,--test Enable test mode (plays full hour signal at end of every minute)\n" ,name ,OUTPUT_DEVICE - ,FREQ - ,SAMPLE_RATE - ,MASTER_VOLUME - ,OFFSET + ,DEFAULT_FREQ + ,DEFAULT_SAMPLE_RATE + ,DEFAULT_MASTER_VOLUME + ,DEFAULT_OFFSET ); } -// Function to fill the buffer with generated signal void generate_signal(float *output, int buffer_size, Oscillator *osc, float volume, - int *elapsed_samples, int total_samples, int pip_samples, + int *elapsed_samples, int total_samples, int pip_samples, int pause_samples, int beep_samples, int num_pips) { - + for (int i = 0; i < buffer_size; i++) { if (*elapsed_samples >= total_samples) { - // End of sequence output[i] = 0; playing_sequence = 0; } else { int cycle_position = *elapsed_samples; int pip_cycle = pip_samples + pause_samples; - + if (cycle_position < num_pips * pip_cycle) { - // Pips with pauses int within_cycle = cycle_position % pip_cycle; if (within_cycle < pip_samples) { - // Playing a pip output[i] = get_oscillator_sin_sample(osc) * volume; } else { - // Silent pause output[i] = 0; } } else if (cycle_position < num_pips * pip_cycle + beep_samples) { - // Final beep output[i] = get_oscillator_sin_sample(osc) * volume; } else { - // Silent after sequence output[i] = 0; } - + (*elapsed_samples)++; } } } -// Check if it's time to start a sequence and which one int check_time_for_sequence(int test_mode, int offset) { static time_t last_check = 0; static int last_minute = -1; - - // Only check time every 100ms to reduce system calls + time_t now = time(NULL); if (now == last_check) { return SEQ_NONE; } - + last_check = now; struct tm *utc_time = gmtime(&now); int minute = utc_time->tm_min; int second = utc_time->tm_sec; - - // Check if we already played a sequence recently (within 1 second) + if (difftime(now, last_sequence_time) < 1.0) { return SEQ_NONE; } - - // Check for 29:56 sequence + if (minute == 29 && second == (56 + offset)) { last_sequence_time = now; return SEQ_29_56; } - - // Check for 59:55 sequence + if (minute == 59 && second == (55 + offset)) { last_sequence_time = now; return SEQ_59_55; } - - // Check for test mode sequence (but don't repeat for the same minute) + if (test_mode && second == (55 + offset) && minute != last_minute) { last_minute = minute; last_sequence_time = now; return SEQ_TEST_HOUR; } - + return SEQ_NONE; } @@ -155,10 +140,10 @@ int main(int argc, char **argv) { pa_simple *output_device; char audio_output_device[64] = OUTPUT_DEVICE; - float master_volume = MASTER_VOLUME; - float freq = FREQ; - int sample_rate = SAMPLE_RATE; - int offset = OFFSET; + float master_volume = DEFAULT_MASTER_VOLUME; + float freq = DEFAULT_FREQ; + int sample_rate = DEFAULT_SAMPLE_RATE; + int offset = DEFAULT_OFFSET; int test_mode = 0; // Parse command line arguments @@ -177,23 +162,23 @@ int main(int argc, char **argv) { while((opt = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) { switch(opt) { - case 'o': + case 'o': strncpy(audio_output_device, optarg, sizeof(audio_output_device) - 1); audio_output_device[sizeof(audio_output_device) - 1] = '\0'; break; - case 'F': + case 'F': freq = strtof(optarg, NULL); break; - case 's': + case 's': sample_rate = strtol(optarg, NULL, 10); break; - case 'v': + case 'v': master_volume = strtof(optarg, NULL); break; - case 't': + case 't': offset = strtol(optarg, NULL, 10); break; - case 'T': + case 'T': test_mode = 1; break; case 'h': @@ -238,96 +223,83 @@ int main(int argc, char **argv) { &output_buffer_atr, &pulse_error ); - + if (!output_device) { fprintf(stderr, "Error: cannot open output device: %s\n", pa_strerror(pulse_error)); return 1; } - // Setup oscillator Oscillator osc; init_oscillator(&osc, freq, sample_rate); signal(SIGINT, stop); signal(SIGTERM, stop); - + float output[BUFFER_SIZE]; - - // Pre-calculate samples for each sound component + int pip_samples = (int)((PIP_DURATION / 1000.0) * sample_rate); int pause_samples = (int)((PIP_PAUSE / 1000.0) * sample_rate); int beep_samples = (int)((BEEP_DURATION / 1000.0) * sample_rate); - - // Pre-calculate total sample lengths for each sequence type + int samples_29_56 = 4 * (pip_samples + pause_samples) + beep_samples; int samples_59_55 = 5 * (pip_samples + pause_samples) + beep_samples; - + printf("Ready to play time signals.\n"); printf("Will trigger at XX:29:%02d and XX:59:%02d\n", 56+offset, 55+offset); if (test_mode) { printf("TEST MODE: Will also play full hour signal at the end of every minute\n"); } - + int elapsed_samples = 0; int total_sequence_samples = 0; int sequence_completed = 0; - + while (to_run) { - // Only check for new sequence if we're not already playing one if (!playing_sequence) { int new_sequence = check_time_for_sequence(test_mode, offset); - + if (new_sequence != SEQ_NONE) { - printf("Starting sequence type %d\n", new_sequence); playing_sequence = 1; sequence_type = new_sequence; elapsed_samples = 0; sequence_completed = 0; - - // Set total samples based on sequence type + if (new_sequence == SEQ_29_56) { total_sequence_samples = samples_29_56; - } else { // SEQ_59_55 or SEQ_TEST_HOUR + } else { total_sequence_samples = samples_59_55; } - - // Clear the buffer when starting a new sequence + memset(output, 0, sizeof(output)); } else { - // Idle state - send silence and sleep to save CPU - // Only send silence occasionally to keep the stream open static int idle_counter = 0; if (idle_counter++ % 10 == 0) { memset(output, 0, sizeof(output)); pa_simple_write(output_device, output, sizeof(output), &pulse_error); } - - struct timespec ts = {0, 10000000}; // 10ms sleep + + struct timespec ts = {0, 5000000}; // 5ms sleep nanosleep(&ts, NULL); continue; } } - - // Generate signal for the current sequence + int num_pips = (sequence_type == SEQ_29_56) ? 4 : 5; - generate_signal(output, BUFFER_SIZE, &osc, master_volume, + generate_signal(output, BUFFER_SIZE, &osc, master_volume, &elapsed_samples, total_sequence_samples, pip_samples, pause_samples, beep_samples, num_pips); - - // Check if sequence just completed + if (!playing_sequence && !sequence_completed) { - printf("Time signal sequence completed\n"); sequence_completed = 1; } - - // Write to audio device + if (pa_simple_write(output_device, output, sizeof(output), &pulse_error) < 0) { fprintf(stderr, "Error writing to output device: %s\n", pa_strerror(pulse_error)); to_run = 0; break; } } - + printf("Cleaning up...\n"); pa_simple_free(output_device); return 0; diff --git a/src/dcf95.c b/src/dcf95.c index 56f7aec..979557b 100644 --- a/src/dcf95.c +++ b/src/dcf95.c @@ -15,8 +15,8 @@ #include "../lib/constants.h" #include "../lib/oscillator.h" -#define FREQ 77500.0f // DCF77 frequency is 77.5 kHz -#define SAMPLE_RATE 192000 // Higher sample rate for the carrier +#define DEFAULT_FREQ 77500.0f +#define DEFAULT_SAMPLE_RATE 192000 #define OUTPUT_DEVICE "alsa_output.platform-soc_sound.stereo-fallback" @@ -25,31 +25,27 @@ #include #include -#define MASTER_VOLUME 0.5f // Volume -#define OFFSET 0 // Offset in seconds +#define DEFAULT_MASTER_VOLUME 0.5f +#define DEFAULT_OFFSET 0 -// DCF77 specific parameters -#define PULSE_0_DURATION 100 // 100ms for binary 0 -#define PULSE_1_DURATION 200 // 200ms for binary 1 -#define REDUCED_AMPLITUDE 0.15f // Reduced to 15% of normal amplitude during pulses -#define BIT_LENGTH 1000 // 1 second per bit +#define PULSE_0_DURATION 100 +#define PULSE_1_DURATION 200 +#define REDUCED_AMPLITUDE 0.15f +#define BIT_LENGTH 1000 // this is ms -// DSSS Parameters -#define DSSS_START_MS 200 // DSSS starts at 200ms into the second -#define DSSS_DURATION_MS 793 // DSSS duration is 793ms -#define PHASE_SHIFT 15.6 // Phase shift in degrees (±15.6°) -#define CHIPS_PER_BIT 512 // Number of chips per bit -#define CHIP_CYCLES 120 // Each chip spans 120 cycles +#define DSSS_START_MS 200 +#define DSSS_DURATION_MS 793 +#define PHASE_SHIFT 15.6 +#define CHIPS_PER_BIT 512 +#define CHIP_CYCLES 120 volatile sig_atomic_t to_run = 1; volatile sig_atomic_t transmitting = 0; volatile int bit_position = 0; -volatile int test_mode = 0; // 0 = normal, 1 = test mode +volatile int test_mode = 0; -// DCF77 bits array (59 bits, indexed 0-58) -volatile int dcf77_bits[60]; // 60th position is for the 1-second pause +volatile int dcf77_bits[60]; -// LFSR state for DSSS unsigned int lfsr = 0; static void stop(int signum) { @@ -58,106 +54,80 @@ static void stop(int signum) { to_run = 0; } -// Generate next chip from LFSR unsigned int generate_chip() { unsigned int chip = lfsr & 1; - + lfsr >>= 1; if (chip || !lfsr) lfsr ^= 0x110; - + return chip; } -// Reset LFSR state at the beginning of each second void reset_lfsr() { lfsr = 0; } -// Helper function to determine if a given time is in DST for CET int is_cet_dst(struct tm *tm_time) { - // CET DST rules: starts last Sunday of March at 2:00, ends last Sunday of October at 3:00 - int month = tm_time->tm_mon + 1; // tm_mon is 0-based + int month = tm_time->tm_mon + 1; int day = tm_time->tm_mday; - int wday = tm_time->tm_wday; // 0 = Sunday, 6 = Saturday + int wday = tm_time->tm_wday; int hour = tm_time->tm_hour; - - // March - check if we're in the last Sunday or after + if (month == 3) { - // Calculate the date of the last Sunday in March - int last_sunday = 31 - ((5 + 31) % 7); // Calculate last Sunday + int last_sunday = 31 - ((5 + 31) % 7); if ((day > last_sunday) || (day == last_sunday && hour >= 2)) { - return 1; // DST has started + return 1; } - } - // April through September - definitely DST - else if (month > 3 && month < 10) { + } else if (month > 3 && month < 10) { return 1; - } - // October - check if we're before the last Sunday - else if (month == 10) { - // Calculate the date of the last Sunday in October - int last_sunday = 31 - ((5 + 31) % 7); // Calculate last Sunday + } else if (month == 10) { + int last_sunday = 31 - ((5 + 31) % 7); if ((day < last_sunday) || (day == last_sunday && hour < 3)) { - return 1; // Still in DST + return 1; } } - - return 0; // Not in DST + + return 0; } int is_timezone_change_soon() { time_t now, in_an_hour; struct tm cet_now, cet_later; - // Get current time time(&now); - in_an_hour = now + 3600; // 3600 seconds = 1 hour - - // Initialize the tm structures + in_an_hour = now + 3600; + memset(&cet_now, 0, sizeof(struct tm)); memset(&cet_later, 0, sizeof(struct tm)); - - // Convert to CET timezone explicitly - // We need to use the gmtime to get UTC and then manually adjust to CET + struct tm *gm_now = gmtime(&now); struct tm *gm_later = gmtime(&in_an_hour); - - // Copy the GMT times + cet_now = *gm_now; cet_later = *gm_later; - - // Adjust for CET (UTC+1 normal, UTC+2 during DST) - // First, set the base offset for CET (UTC+1) + cet_now.tm_hour += 1; cet_later.tm_hour += 1; - - // Check if it's DST in CET - // CET DST starts on last Sunday of March at 2:00 and ends on last Sunday of October at 3:00 + int is_dst_now = is_cet_dst(&cet_now); int is_dst_later = is_cet_dst(&cet_later); - - // Adjust hour for DST if needed + if (is_dst_now) cet_now.tm_hour += 1; if (is_dst_later) cet_later.tm_hour += 1; - - // Normalize the time values after modification + mktime(&cet_now); mktime(&cet_later); - - // Return 1 if a time zone change is about to happen, otherwise 0 + return is_dst_now != is_dst_later; } -// Function to calculate DCF77 bits based on current time void calculate_dcf77_bits(time_t now, int *bits) { - struct tm *t = gmtime(&now); // Use local time instead of UTC + struct tm *t = gmtime(&now); int cest = is_cet_dst(t); - - // Initialize all bits to 0 + memset(bits, 0, 60 * sizeof(int)); - - //bit[15] = 0; // Helper antenna + bits[16] = is_timezone_change_soon(); if(cest) { bits[17] = 1; @@ -165,8 +135,7 @@ void calculate_dcf77_bits(time_t now, int *bits) { bits[18] = 1; } bits[20] = 1; - - // Bits 20-27: Minutes (BCD encoded) + int minutes = t->tm_min; bits[21] = (minutes % 10) & 0x01; bits[22] = ((minutes % 10) >> 1) & 0x01; @@ -175,33 +144,29 @@ void calculate_dcf77_bits(time_t now, int *bits) { bits[25] = ((minutes / 10) & 0x01); bits[26] = ((minutes / 10) >> 1) & 0x01; bits[27] = ((minutes / 10) >> 2) & 0x01; - - // Bit 28: Even parity for minutes + int parity = 0; for (int i = 21; i <= 27; i++) { parity ^= bits[i]; } bits[28] = parity; - - // Bits 29-34: Hours (BCD encoded) - int hours = t->tm_hour-1; // Not sure why - hours += 1; // UTC to CET - if(cest) hours += 1; // CET to CEST + + int hours = t->tm_hour-1; + hours += 1; + if(cest) hours += 1; bits[29] = (hours % 10) & 0x01; bits[30] = ((hours % 10) >> 1) & 0x01; bits[31] = ((hours % 10) >> 2) & 0x01; bits[32] = ((hours % 10) >> 3) & 0x01; bits[33] = ((hours / 10) & 0x01); bits[34] = ((hours / 10) >> 1) & 0x01; - - // Bit 35: Even parity for hours + parity = 0; for (int i = 29; i <= 34; i++) { parity ^= bits[i]; } bits[35] = parity; - - // Bits 36-41: Day of month (1-31, BCD encoded) + int day = t->tm_mday; bits[36] = (day % 10) & 0x01; bits[37] = ((day % 10) >> 1) & 0x01; @@ -209,23 +174,20 @@ void calculate_dcf77_bits(time_t now, int *bits) { bits[39] = ((day % 10) >> 3) & 0x01; bits[40] = ((day / 10) & 0x01); bits[41] = ((day / 10) >> 1) & 0x01; - - // Bits 42-44: Day of week (1=Monday, 7=Sunday) - int dow = t->tm_wday == 0 ? 7 : t->tm_wday; // Convert Sunday from 0 to 7 + + int dow = t->tm_wday == 0 ? 7 : t->tm_wday; bits[42] = dow & 0x01; bits[43] = (dow >> 1) & 0x01; bits[44] = (dow >> 2) & 0x01; - - // Bits 45-49: Month (1-12, BCD encoded) - int month = t->tm_mon + 1; // tm_mon is 0-11 + + int month = t->tm_mon + 1; bits[45] = (month % 10) & 0x01; bits[46] = ((month % 10) >> 1) & 0x01; bits[47] = ((month % 10) >> 2) & 0x01; bits[48] = ((month % 10) >> 3) & 0x01; bits[49] = (month / 10) & 0x01; - - // Bits 50-57: Year within century (0-99, BCD encoded) - int year = t->tm_year % 100; // Get last two digits of year + + int year = t->tm_year % 100; bits[50] = (year % 10) & 0x01; bits[51] = ((year % 10) >> 1) & 0x01; bits[52] = ((year % 10) >> 2) & 0x01; @@ -234,30 +196,27 @@ void calculate_dcf77_bits(time_t now, int *bits) { bits[55] = ((year / 10) >> 1) & 0x01; bits[56] = ((year / 10) >> 2) & 0x01; bits[57] = ((year / 10) >> 3) & 0x01; - - // Bit 58: Even parity for date bits + parity = 0; for (int i = 36; i <= 57; i++) { parity ^= bits[i]; } bits[58] = parity; - - // Bit 59: Set to 2, as a full wave - bits[59] = 2; + + bits[59] = 2; } -// Print the current DCF77 bit pattern (for debugging) void print_dcf77_bits(const int *bits) { printf("DCF77 Bit Pattern: "); for (int i = 0; i < 60; i++) { printf("%d", bits[i]); - if ((i+1) % 10 == 0) printf(" "); // Space every 10 bits + if ((i+1) % 10 == 0) printf(" "); } printf("\n"); } void show_version() { - printf("dcf95 (DCF77 time signal encoder by radio95) version 1.0\n"); + printf("dcf95 (DCF77 time signal encoder by radio95) version 1.1\n"); } void show_help(char *name) { @@ -267,15 +226,15 @@ void show_help(char *name) { " -F,--frequency DCF77 Frequency [default: %.1f Hz]\n" " -s,--samplerate Output Samplerate [default: %d]\n" " -v,--volume Output volume [default: %.2f]\n" - " -t,--offset Time Offset [default: %d s]\n" - " -T,--test Enable test mode \n" - " -n,--no-phase Disable phase modulation \n" + " -t,--offset Time Offset [default: %ds]\n" + " -T,--test Enable test mode\n" + " -n,--no-phase Disable phase modulation\n" ,name ,OUTPUT_DEVICE - ,FREQ - ,SAMPLE_RATE - ,MASTER_VOLUME - ,OFFSET + ,DEFAULT_FREQ + ,DEFAULT_SAMPLE_RATE + ,DEFAULT_MASTER_VOLUME + ,DEFAULT_OFFSET ); } @@ -283,13 +242,15 @@ int main(int argc, char **argv) { show_version(); pa_simple *output_device; + char audio_output_device[64] = OUTPUT_DEVICE; - float master_volume = MASTER_VOLUME; - float freq = FREQ; - int sample_rate = SAMPLE_RATE; - int offset = OFFSET; - int test_mode = 0; // Test mode flag - int no_phase = 0; // Phase modulation disabled flag + + float master_volume = DEFAULT_MASTER_VOLUME; + float freq = DEFAULT_FREQ; + int sample_rate = DEFAULT_SAMPLE_RATE; + int offset = DEFAULT_OFFSET; + int test_mode = 0; + int no_phase = 0; // #region Parse Arguments int opt; @@ -303,7 +264,7 @@ int main(int argc, char **argv) { {"offset", required_argument, NULL, 't'}, {"test", no_argument, NULL, 'T'}, {"no-phase", no_argument, NULL, 'n'}, - + {"help", no_argument, NULL, 'h'}, {0, 0, 0, 0} }; @@ -396,81 +357,64 @@ int main(int argc, char **argv) { } // #endregion - // #region Setup Oscillator Oscillator osc; init_oscillator(&osc, freq, sample_rate); - // #endregion signal(SIGINT, stop); signal(SIGTERM, stop); - + int pulse_error; - float output[BUFFER_SIZE]; // Output buffer - - // DCF77 parameters - int elapsed_samples = 0; + float output[BUFFER_SIZE]; + int current_second = -1; int ms_within_second = 0; int last_bit = -1; - - // Pre-calculate samples for different durations + int bit_samples = (int)((BIT_LENGTH / 1000.0) * sample_rate); int pulse_0_samples = (int)((PULSE_0_DURATION / 1000.0) * sample_rate); int pulse_1_samples = (int)((PULSE_1_DURATION / 1000.0) * sample_rate); - - // DSSS parameters + int dsss_start_samples = (int)((DSSS_START_MS / 1000.0) * sample_rate); int dsss_duration_samples = (int)((DSSS_DURATION_MS / 1000.0) * sample_rate); int dsss_end_samples = dsss_start_samples + dsss_duration_samples; - float phase_shift_rad = (PHASE_SHIFT * M_PI) / 180.0; // Convert degrees to radians - - // For tracking chip generation + float phase_shift_rad = (PHASE_SHIFT * M_PI) / 180.0; + int current_chip_count = 0; int current_cycle_count = 0; int in_dsss_period = 0; - + printf("DCF77 encoder ready.\n"); - printf("Will transmit time signal continuously.\n"); - - // Main loop + while (to_run) { - // Clear the output buffer memset(output, 0, sizeof(output)); - - // Get current time - time_t now = time(NULL) + offset + 60; // Next minute + + time_t now = time(NULL) + offset + 60; struct tm *t = gmtime(&now); int second = t->tm_sec; - - // Check if we're at the start of a new minute + if (second == 0 && current_second != 0) { - // Calculate the DCF77 bits for the new minute calculate_dcf77_bits(now, (int *)dcf77_bits); #ifdef DEBUG print_dcf77_bits((int *)dcf77_bits); #endif - - // Reset counters for the new minute + bit_position = 0; elapsed_samples = 0; transmitting = 1; - + #ifdef DEBUG - printf("Starting new DCF77 transmission for %02d:%02d:%02d UTC\n", + printf("Starting new DCF77 transmission for %02d:%02d:%02d UTC\n", t->tm_hour, t->tm_min, t->tm_sec); #endif } - - // Update the current second if it has changed + if (second != current_second) { current_second = second; - - // Reset the LFSR at the start of each second for DSSS + reset_lfsr(); current_chip_count = 0; current_cycle_count = 0; - - // Update the bit position at the start of each second + if (transmitting) { if (bit_position < 59) { #ifdef DEBUG @@ -484,83 +428,65 @@ int main(int argc, char **argv) { #endif } } - - // Reset sample counter at the start of each second + elapsed_samples = 0; } - - // Generate the DCF77 signal + for (int i = 0; i < BUFFER_SIZE; i++) { - // Calculate milliseconds within the current second ms_within_second = (int)((elapsed_samples * 1000.0) / sample_rate); - - // Get the current bit (between 0-58) + int current_bit = bit_position > 0 ? bit_position - 1 : 59; - - // Determine if we're in the DSSS period (between 200ms and 993ms) - in_dsss_period = (elapsed_samples >= dsss_start_samples && + + in_dsss_period = (elapsed_samples >= dsss_start_samples && elapsed_samples < dsss_end_samples); - - // Base carrier signal (will be phase-shifted if in DSSS period) + float phase_offset = 0.0; - - // Apply DSSS if in the appropriate time window and phase modulation is enabled + if (in_dsss_period && transmitting && !no_phase) { - // Generate a chip every CHIP_CYCLES carrier cycles if (current_cycle_count == 0) { if (current_chip_count < CHIPS_PER_BIT) { - // Generate the next chip unsigned int chip = generate_chip(); - - // XOR the chip with the current bit value + unsigned int modulated_chip = chip ^ dcf77_bits[current_bit]; - - // Set phase shift based on the modulated chip + if (modulated_chip == 0) { - phase_offset = phase_shift_rad; // +15.6 degrees + phase_offset = phase_shift_rad; } else { - phase_offset = -phase_shift_rad; // -15.6 degrees + phase_offset = -phase_shift_rad; } - + current_chip_count++; } } - - // Update cycle counter within each chip + current_cycle_count = (current_cycle_count + 1) % CHIP_CYCLES; } - - // Get carrier signal with phase offset if needed + float t = osc.phase + phase_offset; float carrier = sinf(t); advance_oscillator(&osc); - + if (transmitting) { - // Determine amplitude based on AM modulation pattern - if ((dcf77_bits[current_bit] == 0 && ms_within_second < PULSE_0_DURATION) || + if ((dcf77_bits[current_bit] == 0 && ms_within_second < PULSE_0_DURATION) || (dcf77_bits[current_bit] == 1 && ms_within_second < PULSE_1_DURATION)) { - // Reduced amplitude during pulse output[i] = carrier * master_volume * REDUCED_AMPLITUDE; } else { - // Full amplitude otherwise output[i] = carrier * master_volume; } } else { - // Not transmitting (should not happen in normal operation) output[i] = carrier * master_volume; } - + elapsed_samples++; } - - // Output the audio buffer + if (pa_simple_write(output_device, output, sizeof(output), &pulse_error) < 0) { fprintf(stderr, "Error writing to output device: %s\n", pa_strerror(pulse_error)); to_run = 0; break; } } - + printf("Cleaning up...\n"); pa_simple_free(output_device); return 0; diff --git a/src/fm95.c b/src/fm95.c index 996d13a..d672811 100644 --- a/src/fm95.c +++ b/src/fm95.c @@ -4,7 +4,7 @@ #define buffer_maxlength 12288 #define buffer_tlength_fragsize 12288 -#define buffer_prebuf 32 +#define buffer_prebuf 8 #define DEFAULT_STEREO 1 #define DEFAULT_STEREO_POLAR 0 @@ -19,7 +19,7 @@ #include "../lib/filters.h" #include "../lib/fm_modulator.h" -#define SAMPLE_RATE 192000 +#define DEFAULT_SAMPLE_RATE 192000 #define INPUT_DEVICE "FM_Audio.monitor" #define OUTPUT_DEVICE "alsa_output.platform-soc_sound.stereo-fallback" @@ -33,8 +33,9 @@ #include #include -#define MASTER_VOLUME 1.0f // Volume of everything combined, for calibration -#define AUDIO_VOLUME 1.0f // Audio volume, before clipper +#define DEFAULT_MASTER_VOLUME 1.0f // Volume of everything combined, for calibration +#define DEFAULT_AUDIO_VOLUME 1.0f // Audio volume, before clipper + #define MONO_VOLUME 0.45f // L+R Signal #define PILOT_VOLUME 0.09f // 19 KHz Pilot #define STEREO_VOLUME 0.45f // L-R signal, should be same as MONO @@ -44,10 +45,9 @@ #define MPX_VOLUME 1.0f // Passtrough #define MPX_CLIPPER_THRESHOLD 1.0f -volatile sig_atomic_t to_run = 1; +static volatile sig_atomic_t to_run = 1; void uninterleave(const float *input, float *left, float *right, size_t num_samples) { - // For stereo, usually it is like this: LEFT RIGHT LEFT RIGHT LEFT RIGHT so this is used to get LEFT LEFT LEFT and RIGHT RIGHT RIGHT for (size_t i = 0; i < num_samples/2; i++) { left[i] = input[i * 2]; right[i] = input[i * 2 + 1]; @@ -61,12 +61,11 @@ static void stop(int signum) { } void show_version() { - printf("fm95 (an FM Processor by radio95) version 1.4\n"); + printf("fm95 (an FM Processor by radio95) version 1.5\n"); } void show_help(char *name) { printf( "Usage: %s\n" - " -m,--mono Force Mono [default: %d]\n" " -s,--stereo Force Stereo [default: %d]\n" " -i,--input Override input device [default: %s]\n" " -o,--output Override output device [default: %s]\n" @@ -79,13 +78,11 @@ void show_help(char *name) { " -L,--sca_clip Override the SCA clipper threshold [default: %.2f]\n" " -c,--clipper Override the clipper threshold [default: %.2f]\n" " -P,--polar Force Polar Stereo (does not take effect with -m%s)\n" - " -g,--ge Force Zenith/GE stereo (does not take effect with -m%s)\n" " -R,--preemp Override preemphasis [default: %.2f µs]\n" " -V,--calibrate Enable Calibration mode [default: off]\n" " -A,--master_vol Set master volume [default: %.3f]\n" " -v,--audio_vol Set audio volume [default: %.3f]\n" ,name - ,DEFAULT_STEREO^1 ,DEFAULT_STEREO ,INPUT_DEVICE ,OUTPUT_DEVICE @@ -114,10 +111,9 @@ void show_help(char *name) { ,DEFAULT_SCA_CLIPPER_THRESHOLD ,DEFAULT_CLIPPER_THRESHOLD ,(DEFAULT_STEREO_POLAR == 1) ? ", default" : "" - ,(DEFAULT_STEREO_POLAR == 1) ? "" : ", default" ,DEFAULT_PREEMPHASIS_TAU/0.000001 - ,MASTER_VOLUME - ,AUDIO_VOLUME + ,DEFAULT_MASTER_VOLUME + ,DEFAULT_AUDIO_VOLUME ); } @@ -163,16 +159,17 @@ int main(int argc, char **argv) { float preemphasis_tau = DEFAULT_PREEMPHASIS_TAU; int calibration_mode = 0; - float master_volume = MASTER_VOLUME; - float audio_volume = AUDIO_VOLUME; + float master_volume = DEFAULT_MASTER_VOLUME; + float audio_volume = DEFAULT_AUDIO_VOLUME; + + int sample_rate = DEFAULT_SAMPLE_RATE; // #region Parse Arguments int opt; - const char *short_opt = "msi:o:apM:r:T:C:f:F:L:c:l:PgSDR:VA:v:h"; + const char *short_opt = "s::i:o:M:r:T:C:f:F:L:c:P::R:VA:v:h"; struct option long_opt[] = { - {"mono", no_argument, NULL, 'm'}, - {"stereo", no_argument, NULL, 's'}, + {"stereo", optional_argument, NULL, 's'}, {"input", required_argument, NULL, 'i'}, {"output", required_argument, NULL, 'o'}, {"mpx", required_argument, NULL, 'M'}, @@ -183,8 +180,7 @@ int main(int argc, char **argv) { {"sca_dev", required_argument, NULL, 'F'}, {"sca_clip", required_argument, NULL, 'L'}, {"clipper", required_argument, NULL, 'c'}, - {"polar", no_argument, NULL, 'P'}, - {"ge", no_argument, NULL, 'g'}, + {"polar", optional_argument, NULL, 'P'}, {"preemp", required_argument, NULL, 'R'}, {"calibrate", no_argument, NULL, 'V'}, {"master_vol", required_argument, NULL, 'A'}, @@ -196,11 +192,12 @@ int main(int argc, char **argv) { while((opt = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) { switch(opt) { - case 'm': // Mono - stereo = 0; - break; case 's': // Stereo - stereo = 1; + if(optarg) { + stereo = atoi(optarg); + } else { + stereo = 1; + } break; case 'i': // Input Device memcpy(audio_input_device, optarg, 63); @@ -233,10 +230,11 @@ int main(int argc, char **argv) { clipper_threshold = strtof(optarg, NULL); break; case 'P': //Polar - polar_stereo = 1; - break; - case 'g': //GE - polar_stereo = 0; + if(optarg) { + polar_stereo = atoi(optarg); + } else { + polar_stereo = 1; + } break; case 'R': // Preemp preemphasis_tau = strtof(optarg, NULL)*0.000001; @@ -266,14 +264,14 @@ int main(int argc, char **argv) { // Define formats and buffer atributes pa_sample_spec stereo_format = { - .format = PA_SAMPLE_FLOAT32NE, //Float32 NE, or Float32 Native Endian, the float in c uses the endianess of your pc, or native endian, and float is float32, and double is float64 + .format = PA_SAMPLE_FLOAT32NE, .channels = 2, - .rate = SAMPLE_RATE // Same sample rate makes it easy, leave the resampling to pipewire, it should know better + .rate = sample_rate }; pa_sample_spec mono_format = { .format = PA_SAMPLE_FLOAT32NE, .channels = 1, - .rate = SAMPLE_RATE + .rate = sample_rate }; pa_buffer_attr input_buffer_atr = { @@ -419,12 +417,12 @@ int main(int argc, char **argv) { if(calibration_mode) { Oscillator osc; - init_oscillator(&osc, 400, SAMPLE_RATE); + init_oscillator(&osc, 400, sample_rate); signal(SIGINT, stop); signal(SIGTERM, stop); int pulse_error; - float output[BUFFER_SIZE]; // MPX, this goes to the output + float output[BUFFER_SIZE]; while(to_run) { for (int i = 0; i < BUFFER_SIZE; i++) { @@ -448,14 +446,14 @@ int main(int argc, char **argv) { // #region Setup Filters/Modulaltors/Oscillators Oscillator osc; - init_oscillator(&osc, polar_stereo ? 31250.0 : 19000, SAMPLE_RATE); // Pilot, it's there to indicate stereo and as a refrence signal with the stereo carrier + init_oscillator(&osc, polar_stereo ? 31250.0 : 19000, sample_rate); FMModulator sca_mod; - init_fm_modulator(&sca_mod, sca_frequency, sca_deviation, SAMPLE_RATE); + init_fm_modulator(&sca_mod, sca_frequency, sca_deviation, sample_rate); ResistorCapacitor preemp_l, preemp_r; - init_preemphasis(&preemp_l, preemphasis_tau, SAMPLE_RATE); - init_preemphasis(&preemp_r, preemphasis_tau, SAMPLE_RATE); + init_preemphasis(&preemp_l, preemphasis_tau, sample_rate); + init_preemphasis(&preemp_r, preemphasis_tau, sample_rate); // #endregion signal(SIGINT, stop);