#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <sys/ioctl.h>
#include <syslog.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
#include <alsa/asoundlib.h>
#include <ltc.h>

#define SAMPLE_RATE 48000
#define FPS          25
#define SAMPLES_PER_FRAME (SAMPLE_RATE / FPS) // Exactly 1920 samples

// Mutex-protected cross-thread state registers
static pthread_mutex_t sync_mutex = PTHREAD_MUTEX_INITIALIZER;
static struct timespec anchor_monotonic = {0, 0};
static struct timespec anchor_realtime = {0, 0};
static int64_t target_drift_offset_ns = 0; 
static char active_os_time[32] = "00:00:00.000";

// Dedicated UI registers to isolate terminal formatting overhead
static int ui_hours = 0, ui_minutes = 0, ui_seconds = 0, ui_frame = 0;
static uint32_t ui_user_bits = 0;

static void load_config_file(char *tz_setting, size_t tz_len, char *device_name, size_t dev_len, int *null_mode) {
    FILE *fp = fopen("/etc/ltc_master.conf", "r");
    if (!fp) return;

    char line[256];
    while (fgets(line, sizeof(line), fp)) {
        line[strcspn(line, "\r\n")] = 0;
        if (line[0] == '#' || line[0] == '\0' || line[0] == ' ') continue;

        char key[128], val[128];
        if (sscanf(line, "%127[^= ] = %127s", key, val) == 2) {
            if (strcmp(key, "timezone") == 0) {
                strncpy(tz_setting, val, tz_len - 1);
            } else if (strcmp(key, "audio_device") == 0) {
                strncpy(device_name, val, dev_len - 1);
                if (strcasecmp(device_name, "null") == 0) *null_mode = 1;
            }
        }
    }
    fclose(fp);
    setenv("TZ", tz_setting, 1);
    tzset();
}

static int configure_alsa_subsystem(snd_pcm_t *pcm) {
    snd_pcm_hw_params_t *hw_params;
    snd_pcm_sw_params_t *sw_params;
    unsigned int rate = SAMPLE_RATE;
    int dir = 0;

    snd_pcm_hw_params_alloca(&hw_params);
    snd_pcm_sw_params_alloca(&sw_params);

    // Hardware Parameter Allocations
    if (snd_pcm_hw_params_any(pcm, hw_params) < 0) return -1;
    if (snd_pcm_hw_params_set_access(pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) return -1;
    if (snd_pcm_hw_params_set_format(pcm, hw_params, SND_PCM_FORMAT_S16_LE) < 0) return -1;
    if (snd_pcm_hw_params_set_channels(pcm, hw_params, 2) < 0) return -1;
    if (snd_pcm_hw_params_set_rate_near(pcm, hw_params, &rate, 0) < 0) return -1;
    
    // Strict Hardware Invariant Check
    if (rate != SAMPLE_RATE) {
        syslog(LOG_CRIT, "ALSA Hardware Error: Soundcard refused 48000Hz sampling lock. Allocated rate: %u", rate);
        return -2;
    }

    unsigned int period_time = 40000; // 40ms physical period chunks
    if (snd_pcm_hw_params_set_period_time_near(pcm, hw_params, &period_time, &dir) < 0) return -1;

    unsigned int periods = 2; // Strict double buffering to eliminate processing latency
    if (snd_pcm_hw_params_set_periods_near(pcm, hw_params, &periods, &dir) < 0) return -1;
    if (snd_pcm_hw_params(pcm, hw_params) < 0) return -1;

    // Software Parameter Configuration
    if (snd_pcm_sw_params_current(pcm, sw_params) < 0) return -1;
    if (snd_pcm_sw_params_set_start_threshold(pcm, sw_params, SAMPLES_PER_FRAME) < 0) return -1;
    if (snd_pcm_sw_params_set_avail_min(pcm, sw_params, SAMPLES_PER_FRAME) < 0) return -1;
    if (snd_pcm_sw_params(pcm, sw_params) < 0) return -1;

    return 0;
}

/* -------------------------------------------------------------------------
   THREAD 1: ASYNCHRONOUS GRANDMASTER HOUSEKEEPING REFERENCE (PLL DRIVER)
   ------------------------------------------------------------------------- */
void *sync_thread_worker(void *arg) {
    (void)arg;
    struct timespec ts_mono, ts_real;
    struct tm tm_os;

    while (1) {
        clock_gettime(CLOCK_MONOTONIC_RAW, &ts_mono);
        clock_gettime(CLOCK_REALTIME, &ts_real);
        localtime_r(&ts_real.tv_sec, &tm_os);

        int64_t mono_ns = (ts_mono.tv_sec * 1000000000LL) + ts_mono.tv_nsec;
        int64_t real_ns = (ts_real.tv_sec * 1000000000LL) + ts_real.tv_nsec;

        pthread_mutex_lock(&sync_mutex);
        target_drift_offset_ns = real_ns - mono_ns;
        snprintf(active_os_time, sizeof(active_os_time), "%02d:%02d:%02d.%03ld",
                 tm_os.tm_hour, tm_os.tm_min, tm_os.tm_sec, ts_real.tv_nsec / 1000000L);
        pthread_mutex_unlock(&sync_mutex);

        usleep(200000); 
    }
    return NULL;
}

/* -------------------------------------------------------------------------
   THREAD 2: HARD-REALTIME COMPENSATED AUDIO REPROCESSING ENGINE
   ------------------------------------------------------------------------- */
void *audio_thread_worker(void *arg) {
    (void)arg;
    snd_pcm_t *pcm = NULL;

    if (!null_device_mode) {
        if (snd_pcm_open(&pcm, device_name, SND_PCM_STREAM_PLAYBACK, 0) < 0) {
            syslog(LOG_CRIT, "CRITICAL: Audio hardware device target open failed.");
            exit(1);
        }
        int rc = configure_alsa_subsystem(pcm);
        if (rc < 0) {
            if (rc == -2) fprintf(stderr, "FATAL: Card refused 48000Hz. Check syslog.\n");
            exit(1);
        }
    }

    LTCEncoder *enc = ltc_encoder_create(SAMPLE_RATE, FPS, LTC_TV_625_50, 0);
    ltc_encoder_set_filter(enc, 0.0);

    size_t ltc_max_alloc_size = ltc_encoder_get_buffersize(enc) + 16;
    ltcsnd_sample_t *ltc_buf = malloc(ltc_max_alloc_size * sizeof(ltcsnd_sample_t));
    int16_t *audio = malloc(ltc_max_alloc_size * 2 * sizeof(int16_t));

    if (!ltc_buf || !audio) {
        syslog(LOG_CRIT, "FATAL: Out of memory in audio worker allocations.");
        exit(1);
    }

    uint64_t hardware_samples_written = 0;
    struct timespec loop_anchor_mono;
    int64_t operational_drift_offset_ns = 0;

    clock_gettime(CLOCK_MONOTONIC_RAW, &loop_anchor_mono);
    
    pthread_mutex_lock(&sync_mutex);
    operational_drift_offset_ns = target_drift_offset_ns;
    pthread_mutex_unlock(&sync_mutex);

    while (1) {
        snd_pcm_sframes_t delay_samples = 0;

        if (!null_device_mode && pcm) {
            if (snd_pcm_delay(pcm, &delay_samples) < 0) {
                delay_samples = 0; 
            }
            if (delay_samples < 0) delay_samples = 0;
            if (delay_samples > SAMPLE_RATE) delay_samples = SAMPLE_RATE; 
        }

        int64_t exact_sample_index = (int64_t)hardware_samples_written - (int64_t)delay_samples;
        if (exact_sample_index < 0) exact_sample_index = 0;

        int64_t timeline_ns = (exact_sample_index * 1000000000LL) / SAMPLE_RATE;
        int64_t anchor_base_ns = (loop_anchor_mono.tv_sec * 1000000000LL) + loop_anchor_mono.tv_nsec;
        
        int64_t absolute_realtime_ns = anchor_base_ns + timeline_ns + operational_drift_offset_ns;

        time_t absolute_secs = absolute_realtime_ns / 1000000000LL;
        int64_t residual_ns = absolute_realtime_ns % 1000000000LL;

        struct tm calculation_tm;
        localtime_r(&absolute_secs, &calculation_tm);

        int calculated_frame_index = (exact_sample_index / SAMPLES_PER_FRAME) % FPS;

        SMPTETimecode rolling_tc;
        rolling_tc.hours  = calculation_tm.tm_hour;
        rolling_tc.mins   = calculation_tm.tm_min;
        rolling_tc.secs   = calculation_tm.tm_sec;
        rolling_tc.frame  = calculated_frame_index;
        rolling_tc.days   = calculation_tm.tm_mday;
        rolling_tc.months = calculation_tm.tm_mon + 1;
        rolling_tc.years  = (1900 + calculation_tm.tm_year) % 100;

        uint8_t bcd_year  = ((rolling_tc.years / 10)  << 4) | (rolling_tc.years % 10);
        uint8_t bcd_month = ((rolling_tc.months / 10) << 4) | (rolling_tc.months % 10);
        uint8_t bcd_day   = ((rolling_tc.days / 10)   << 4) | (rolling_tc.days % 10);
        uint32_t user_bits = (0x00 << 24) | (bcd_year << 16) | (bcd_month << 8) | bcd_day;

        ltc_encoder_set_timecode(enc, &rolling_tc);
        ltc_encoder_set_user_bits(enc, user_bits);
        ltc_encoder_encode_frame(enc);

        int n = ltc_encoder_get_buffersize(enc);
        if (n != SAMPLES_PER_FRAME) {
            syslog(LOG_CRIT, "FATAL: libltc buffer size mismatch. Expected %d, got %d", SAMPLES_PER_FRAME, n);
            exit(1);
        }
        
        // Native Mint-safe buffer map extraction
        ltcsnd_sample_t *encoder_raw_buffer = ltc_encoder_get_buf(enc);
        memcpy(ltc_buf, encoder_raw_buffer, n * sizeof(ltcsnd_sample_t));

        pthread_mutex_lock(&sync_mutex);
        ui_hours     = rolling_tc.hours;
        ui_minutes   = rolling_tc.mins;
        ui_seconds   = rolling_tc.secs;
        ui_frame     = rolling_tc.frame;
        ui_user_bits = user_bits;
        
        if (calculated_frame_index == 0) {
            operational_drift_offset_ns = target_drift_offset_ns;
        }
        pthread_mutex_unlock(&sync_mutex);

        if (!null_device_mode && pcm) {
            for (int i = 0; i < n; i++) {
                int16_t sample = ((int16_t)ltc_buf[i] - 128) << 8;
                audio[i * 2]     = sample;        
                audio[i * 2 + 1] = (int16_t)(-sample); 
            }

            snd_pcm_sframes_t written = snd_pcm_writei(pcm, audio, SAMPLES_PER_FRAME);

            if (written < 0) {
                syslog(LOG_CRIT, "[ALSA PIPELINE XRUN] Executing baseline reset: %s", snd_strerror(written));
                snd_pcm_prepare(pcm);
                
                clock_gettime(CLOCK_MONOTONIC_RAW, &loop_anchor_mono);
                pthread_mutex_lock(&sync_mutex);
                operational_drift_offset_ns = target_drift_offset_ns;
                hardware_samples_written = 0;
                pthread_mutex_unlock(&sync_mutex);
            } else {
                hardware_samples_written += written;
            }
        } else {
            usleep(40000); 
            hardware_samples_written += SAMPLES_PER_FRAME;
        }
    }

    free(ltc_buf); free(audio);
    ltc_encoder_free(enc);
    if (pcm) snd_pcm_close(pcm);
    return NULL;
}

/* -------------------------------------------------------------------------
   THREAD 3: LOW-PRIORITY REAL-TIME SCREEN REPAINT LOOP
   ------------------------------------------------------------------------- */
void *ui_thread_worker(void *arg) {
    (void)arg;
    int last_frame = -1;

    printf("\033[2J\033[H");
    fflush(stdout);

    while (1) {
        pthread_mutex_lock(&sync_mutex);
        if (ui_frame != last_frame) {
            printf("\r[LTC TRANSMISSION LOCK] %02d:%02d:%02d:%02d | USER BITS: UB=%08X | MONITORED WALL: %s\033[K",
                   ui_hours, ui_minutes, ui_seconds, ui_frame, ui_user_bits, active_os_time);
            fflush(stdout);
            last_frame = ui_frame;
        }
        pthread_mutex_unlock(&sync_mutex);
        usleep(10000); 
    }
    return NULL;
}

/* -------------------------------------------------------------------------
   MAIN CONFIGURATION ENTRY POINT
   ------------------------------------------------------------------------- */
int main(void) {
    openlog("ltc_master", LOG_PID | LOG_CONS, LOG_DAEMON);

    char tz_setting[128] = "Pacific/Auckland";
    char device_name[128] = "hw:0,0";
    int null_device_mode_local = 0;

    load_config_file(tz_setting, sizeof(tz_setting), device_name, sizeof(device_name), &null_device_mode_local);
    null_device_mode = null_device_mode_local;

    pthread_attr_t realtime_attr;
    struct sched_param sched_reg;

    pthread_attr_init(&realtime_attr);
    pthread_attr_setinheritsched(&realtime_attr, PTHREAD_EXPLICIT_SCHED);
    pthread_attr_setschedpolicy(&realtime_attr, SCHED_FIFO);
    
    sched_reg.sched_priority = 65; 
    pthread_attr_setschedparam(&realtime_attr, &sched_reg);

    printf("Launching Transmitter-Grade Phase-Locked LTC Generator...\n");
    fflush(stdout);

    pthread_t sync_thread, audio_thread, ui_thread;

    pthread_create(&sync_thread, NULL, sync_thread_worker, NULL);
    
    if (pthread_create(&audio_thread, &realtime_attr, audio_thread_worker, NULL) != 0) {
        syslog(LOG_ERR, "CRITICAL: Realtime prioritization failed. Check execution permissions.");
        pthread_create(&audio_thread, NULL, audio_thread_worker, NULL);
    } else {
        syslog(LOG_INFO, "SUCCESS: Hard-Realtime SCHED_FIFO 65 applied to audio thread.");
    }
    
    pthread_create(&ui_thread, NULL, ui_thread_worker, NULL);

    pthread_attr_destroy(&realtime_attr);

    pthread_join(sync_thread, NULL);
    pthread_join(audio_thread, NULL);
    pthread_join(ui_thread, NULL);

    closelog();
    return 0;
}

Leave a Reply