//________________________________________________________________________________________
//
//		FOF synthesis demo
//		2025, Stanko Juzbasic
//		Inspired by IRCAM historical program CHANT, by Jean-Baptiste Barrière,
//					Xavier Rodet, and Pierre-François Baisnée 
//		synthesizes 24 distinct vowels, oral and nasal
//
//	cc -o fof fof.c -framework AudioToolbox -framework AudioUnit -framework CoreAudio\
//  -framework CoreMidi -framework CoreFoundation -lm -lpthread
//
//
//	Start the program in demo mode by typing ./fof <frequency (Hz)>
//  or hook a MIDI keyboard and start the program by typing ./fof (without arguments)
//________________________________________________________________________________________

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <pthread.h>
#include <termios.h>
#include <AudioToolbox/AudioToolbox.h>
#include <AudioUnit/AudioUnit.h>
#include <CoreMIDI/CoreMIDI.h>

#define MAX_HELD_NOTES 16


#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#define TWO_PI 6.28318530717958647692
#define PI_OVER_20 0.15707963267948966192

#define MAX_GRAINS 1024
#define NUM_FORMANTS 6
#define NUM_VOWELS 12  // basic vowels, with nasal versions = 24

#define SMOOTHING_FACTOR 0.08

typedef struct {
    double phase;
    double phase_inc;
    double age;
    double duration;
    double attack;
    double decay_rate;
    double amplitude;
    int active;
} FofGrain;

typedef struct {
    double freq;
    double bandwidth;
    double db_amplitude;
} FormantTarget;

typedef struct {
    double tongue_front_back; 
    double tongue_high_low;   
    double mouth_stretch;     
    FormantTarget formants[NUM_FORMANTS];
} VowelVertex;

typedef struct {
    double sample_rate;
    double inv_sample_rate;
    double attack_time;
    double decay_time;
    
    volatile double control_tongue_x; 
    volatile double control_tongue_y; 
    volatile double control_stretch_z; 
    volatile double control_f0_hz;
    
    double smoothed_x;
    double smoothed_y;
    double smoothed_z;
    
    double current_f0_inc;
    double target_phase_incs[NUM_FORMANTS];
    double target_decay_rates[NUM_FORMANTS];
    double target_gains[NUM_FORMANTS];
    
    volatile int control_gate;        // 1 = MIDI key held, 0 = released
    volatile double control_velocity; // 0.0 to 1.0
    double current_amp_env;           // Smooth anti-click gate envelope
    
    double f0_phase;
    FofGrain grains[MAX_GRAINS];
    
    //volatile double control_f0_hz;
    double smoothed_f0_hz;     // Current gliding pitch
    double glide_coeff;        // Portamento speed (e.g., 0.0005 to 0.01)
    
    volatile double control_mod_wheel; // 0.0 to 1.0 (from MIDI CC 1)
    double smoothed_mod_wheel;
    double vibrato_phase;
    // Vowel coarticulation state
    int prev_vowel_idx;
    int target_vowel_idx;
    double vowel_morph;
    double morph_progress;         //(linear 0.0 → 1.0 timer)
    volatile double control_nasal; // 0.0 = oral, 1.0 = full nasalization
    double smoothed_nasal;

} FofSynthesizer;

typedef struct {
    uint8_t notes[MAX_HELD_NOTES];
    int count;
    FofSynthesizer *fof;
} MidiMonoController;

int g_demoMode = 0;
int debug = 0;
//_______________________________________________________________________________________
FofSynthesizer *g_fofEngine = NULL;

static MidiMonoController g_midiCtrl = { .count = 0, .fof = NULL };

//_______________________________________________________________________________________
// Complete 3D Spatial Vowel library mapped according to tongue coordinates and lip configuration
const VowelVertex ANATOMICAL_SPACE[NUM_VOWELS] = {
    // 0. Vowel "A": Back, Low, Stretched Wide
    {-1.0, -1.0,  1.0, {{600, 60, 0.0}, {1040, 70, -7.0}, {2250, 110, -16.0}, {2450, 120, -22.0}, {3100, 50, -28.0}, {265, 50, -90.0}}},
    // 1. Vowel "E": Front, Mid, Stretched Wide
    { 0.5,  0.0,  1.0, {{400, 40, 0.0}, {1600, 80, -12.0}, {2200, 100, -16.0}, {2400, 120, -24.0}, {3120, 50, -30.0}, {265, 50, -90.0}}},
    // 2. Vowel "I": Front, High, Stretched Wide
    { 1.0,  1.0,  1.0, {{250, 60, 0.0}, {1750, 90, -12.0}, {2600, 100, -18.0}, {3050, 120, -30.0}, {3150, 50, -35.0}, {265, 50, -90.0}}},
    // 3. Vowel "O": Back, Mid, Rounded/Compressed
    {-0.5,  0.0, -1.0, {{450, 40, 0.0}, {800, 80, -11.0}, {2300, 100, -21.0}, {2675, 120, -20.0}, {3100, 50, -30.0}, {265, 50, -90.0}}},
    // 4. Vowel "U": Back, High, Rounded/Compressed
    {-1.0,  1.0, -1.0, {{325, 50, 0.0}, {700, 60, -16.0}, {2100, 110, -28.0}, {2400, 120, -32.0}, {3100, 50, -40.0}, {265, 50, -90.0}}},
    // 5. Vowel "Ö": Front, Mid (Same Tongue space as E), but Rounded/Compressed Lips (-1.0)
    { 0.5,  0.0, -1.0, {{370, 40, 0.0}, {1500, 85, -10.0}, {2150, 100, -15.0}, {2350, 120, -22.0}, {3100, 50, -30.0}, {265, 50, -90.0}}},
    // 6. Vowel "Ü": Front, High (Same Tongue space as I), but Rounded/Compressed Lips (-1.0)
    { 1.0,  1.0, -1.0, {{230, 50, 0.0}, {1650, 90, -11.0}, {2500, 100, -16.0}, {2950, 120, -28.0}, {3100, 50, -32.0}, {265, 50, -90.0}}},
    // 7. Dark Closed-Lip Hum (Back, Tongue Low, Lips Tightly Closed)
    {-1.0, -1.0, -1.0, {{220, 40, 0.0}, {520, 50, -18.0}, {1800, 90, -36.0}, {2200, 110, -45.0}, {2750, 50, -54.0}, {265, 50, -90.0}}},
    // 8. Neutral "a" ("C" / ə): Center, Mid, Neutral Lips
    { 0.0,  0.0,  0.0, {{500, 60, 0.0}, {1500, 80, -10.0}, {2500, 100, -18.0}, {3300, 120, -24.0}, {3750, 60, -32.0}, {265, 50, -90.0}}},
    // 9. Open Front "Æ" ("X"): Front, Low, Stretched Wide
    { 0.8, -1.0,  1.0, {{750, 75, 0.0}, {1700, 90, -8.0}, {2450, 110, -15.0}, {3200, 120, -22.0}, {3600, 60, -30.0}, {265, 50, -90.0}}},
    // 10. Nasal "M" (/m/): Bilabial Closure (Lips Sealed, Oral Anti-Resonance ~1000 Hz)
    {-0.8, -0.5, -1.0, {{260, 45, 0.0}, {1000, 200, -30.0}, {2250, 65, -20.0}, {2900, 120, -34.0}, {3400, 80, -44.0}, {265, 50, -90.0}}},
    // 11. Nasal "N" (/n/): Alveolar Closure (Tongue Tip Up, Oral Anti-Resonance ~1550 Hz)
    { 0.6,  0.8, -0.5, {{280, 45, 0.0}, {1550, 220, -28.0}, {2450, 70, -18.0}, {3100, 120, -32.0}, {3600, 80, -42.0}, {265, 50, -90.0}}}
};
//_______________________________________________________________________________________
void fof_set_pitch(FofSynthesizer* fof, double f0_hz) {
   // if (f0_hz < 16.0) f0_hz = 16.0;
    if (f0_hz > 1500.0) f0_hz = 1500.0;
    fof->control_f0_hz = f0_hz;             
}
//_______________________________________________________________________________________
void fof_set_portamento(FofSynthesizer* fof, double time_ms) {
    if (time_ms <= 0.1) {
        fof->glide_coeff = 1.0; // Instant snap
    } else {
        // Convert time in ms to a sample-rate-independent 1-pole coefficient
        fof->glide_coeff = 1.0 - exp(-1.0 / (time_ms * 0.001 * fof->sample_rate));
    }
}
//_______________________________________________________________________________________
static inline double midi_note_to_hz(uint8_t note) {
    return 440.0 * pow(2.0, ((double)note - 69.0) / 12.0);
}
//_______________________________________________________________________________________
void midi_handle_note_off(MidiMonoController *ctrl, uint8_t note) {
    int found = -1;
    for (int i = 0; i < ctrl->count; i++) {
        if (ctrl->notes[i] == note) {
            found = i;
            break;
        }
    }
    if (found == -1) return;

    // Remove note from stack
    for (int i = found; i < ctrl->count - 1; i++) {
        ctrl->notes[i] = ctrl->notes[i + 1];
    }
    ctrl->count--;

    if (ctrl->count > 0) {
        // Last-note priority: revert pitch to previous held note
        uint8_t prev_note = ctrl->notes[ctrl->count - 1];
        fof_set_pitch(ctrl->fof, midi_note_to_hz(prev_note));
    } else {
        // No keys held: close gate
        ctrl->fof->control_gate = 0;
    }
}
//_______________________________________________________________________________________
void midi_handle_note_on(MidiMonoController *ctrl, uint8_t note, uint8_t velocity) {
    // check against "hacked" note-off:
    if (velocity == 0) {
        midi_handle_note_off(ctrl, note);
        return;
    }
    ////
        bool is_legato = (ctrl->count > 0);
    ctrl->notes[ctrl->count++] = note;

    double new_hz = midi_note_to_hz(note);
    fof_set_pitch(ctrl->fof, new_hz);

    if (!is_legato) {
        // First note from silence: snap pitch immediately so it doesn't slide from 0 Hz
        ctrl->fof->smoothed_f0_hz = new_hz;
        ctrl->fof->control_velocity = (double)velocity / 127.0;
        ctrl->fof->control_gate = 1;
    }
    ////

    // Remove duplicate if already present
    for (int i = 0; i < ctrl->count; i++) {
        if (ctrl->notes[i] == note) {
            for (int j = i; j < ctrl->count - 1; j++) {
                ctrl->notes[j] = ctrl->notes[j + 1];
            }
            ctrl->count--;
            break;
        }
    }

    // Push new note onto top of stack
    if (ctrl->count >= MAX_HELD_NOTES) {
        for (int i = 0; i < MAX_HELD_NOTES - 1; i++) {
            ctrl->notes[i] = ctrl->notes[i + 1];
        }
        ctrl->count = MAX_HELD_NOTES - 1;
    }
    ctrl->notes[ctrl->count++] = note;

    // Update FOF synth pitch, velocity, and open gate
    fof_set_pitch(ctrl->fof, midi_note_to_hz(note));
    ctrl->fof->control_velocity = (double)velocity / 127.0;
    ctrl->fof->control_gate = 1;
}

//_______________________________________________________________________________________

void midi_read_callback1(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon) {
    MidiMonoController *ctrl = (MidiMonoController *)readProcRefCon;
    if (!ctrl || !ctrl->fof) return;

    const MIDIPacket *packet = pktlist->packet;
    for (UInt32 i = 0; i < pktlist->numPackets; ++i) {
        UInt16 j = 0;
        uint8_t running_status = 0;

        while (j < packet->length) {
            uint8_t byte = packet->data[j];

            if (byte >= 0x80) {
                // Real-time system messages (0xF8–0xFF) do NOT cancel Running Status per MIDI spec
                if (byte >= 0xF8) {
                    j++;
                    continue;
                }
                // Channel voice message (0x80–0xEF): update running_status and advance to data byte
                running_status = byte;
                j++;
            } else if (running_status == 0) {
                // Orphaned data byte with no prior status: skip safely
                j++;
                continue;
            }

            uint8_t msg_type = running_status & 0xF0;

            if (msg_type == 0x90 && j + 1 < packet->length) {    // Note On (also handles vel == 0)
                uint8_t note = packet->data[j] & 0x7F;
                uint8_t vel  = packet->data[j + 1] & 0x7F;
                midi_handle_note_on(ctrl, note, vel);
                j += 2;
            } else if (msg_type == 0x80 && j + 1 < packet->length) { // Real Note Off
                uint8_t note = packet->data[j] & 0x7F;
                midi_handle_note_off(ctrl, note);
                j += 2;
            } else if (msg_type == 0xB0 && j + 1 < packet->length) { // Control Change
                uint8_t cc_num = packet->data[j] & 0x7F;
                if (cc_num == 123) { // All Notes Off
                    ctrl->count = 0;
                    ctrl->fof->control_gate = 0;
                }
                j += 2;
            } else if ((msg_type == 0xC0 || msg_type == 0xD0) && j < packet->length) {
                // Program Change / Channel Aftertouch are 1-byte data messages
                j += 1;
            } else if (msg_type == 0xE0 && j + 1 < packet->length) {
                // Pitch Bend (2-byte data message)
                j += 2;
            } else {
                // Unknown or incomplete SysEx/System Common message: reset running_status and advance
                running_status = 0;
                j++;
            }
        }
        packet = MIDIPacketNext(packet);
    }
}

//_______________________________________________________________________________________
void midi_read_callback(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon) {
    MidiMonoController *ctrl = (MidiMonoController *)readProcRefCon;
    if (!ctrl || !ctrl->fof) return;

    const MIDIPacket *packet = pktlist->packet;

    for (UInt32 i = 0; i < pktlist->numPackets; ++i) {
        UInt16 j = 0;
        uint8_t running_status = 0;

        while (j < packet->length) {
            uint8_t byte = packet->data[j];

            if (byte >= 0x80) {
                // System Real-Time messages (0xF8–0xFF) do not affect Running Status
                if (byte >= 0xF8) {
                    j++;
                    continue;
                }
                // System Common / SysEx messages (0xF0–0xF7) cancel Running Status
                if (byte >= 0xF0) {
                    running_status = 0;
                    j++;
                    continue;
                }
                // Channel Voice status byte (0x80–0xEF)
                running_status = byte;
                j++;
            } else if (running_status == 0) {
                // Orphaned data byte with no valid status: skip safely
                j++;
                continue;
            }

            uint8_t msg_type = running_status & 0xF0;

            if (msg_type == 0x90 && j + 1 < packet->length) {
                // Note On (velocity == 0 is handled inside midi_handle_note_on as Note Off)
                uint8_t note = packet->data[j] & 0x7F;
                uint8_t vel  = packet->data[j + 1] & 0x7F;
                midi_handle_note_on(ctrl, note, vel);
                j += 2;
            } else if (msg_type == 0x80 && j + 1 < packet->length) {
                // Explicit Note Off
                uint8_t note = packet->data[j] & 0x7F;
                midi_handle_note_off(ctrl, note);
                j += 2;
            } else if (msg_type == 0xB0 && j + 1 < packet->length) {
                // Control Change (CC)
                uint8_t cc_num = packet->data[j] & 0x7F;
                uint8_t cc_val = packet->data[j + 1] & 0x7F;

                if (cc_num == 1) {
                    // Mod Wheel -> Vibrato Depth (0.0 to 1.0)
                    ctrl->fof->control_mod_wheel = (double)cc_val / 127.0;
                } else if (cc_num == 120 || cc_num == 123) {
                    // All Sound Off (120) / All Notes Off (123)
                    ctrl->count = 0;
                    ctrl->fof->control_gate = 0;
                }
                j += 2;
            } else if ((msg_type == 0xC0 || msg_type == 0xD0) && j < packet->length) {
                // 1-byte data messages: Program Change (0xC0) / Channel Pressure (0xD0)
                j += 1;
            } else if (msg_type == 0xE0 && j + 1 < packet->length) {
                // 2-byte data message: Pitch Bend (0xE0)
                j += 2;
            } else {
                // Malformed or truncated packet: reset status and advance
                running_status = 0;
                j++;
            }
        }

        packet = MIDIPacketNext(packet);
    }
}
//_______________________________________________________________________________________
void midi_notify_callback(const MIDINotification *message, void *refCon) {
    if (message->messageID == kMIDIMsgObjectAdded) {
        const MIDIObjectAddRemoveNotification *addMsg = (const MIDIObjectAddRemoveNotification *)message;
        if (addMsg->childType == kMIDIObjectType_Source) {
            MIDIPortRef inPort = *(MIDIPortRef *)refCon;
            MIDIPortConnectSource(inPort, (MIDIEndpointRef)addMsg->child, NULL);
        }
    }
}
//_______________________________________________________________________________________
void fof_init_midi(FofSynthesizer *fof) {
    g_midiCtrl.fof = fof;
    g_midiCtrl.count = 0;

    static MIDIClientRef client;
    static MIDIPortRef inPort;

    MIDIClientCreate(CFSTR("FOFSynthMIDI"), midi_notify_callback, &inPort, &client);
    MIDIInputPortCreate(client, CFSTR("FOFSynthInput"), midi_read_callback, &g_midiCtrl, &inPort);

    ItemCount sources = MIDIGetNumberOfSources();
    printf("Found %lu MIDI sources\n", sources);
    for (ItemCount i = 0; i < sources; ++i) {
        MIDIPortConnectSource(inPort, MIDIGetSource(i), NULL);
    }
}
//_______________________________________________________________________________________
void fof_set_nasal(FofSynthesizer* fof, double nasal_amount) {
    if (nasal_amount < 0.0) nasal_amount = 0.0;
    if (nasal_amount > 1.0) nasal_amount = 1.0;
    fof->control_nasal = nasal_amount;
}
//_______________________________________________________________________________________
void fof_set_vocal_tract_3d(FofSynthesizer* fof, 
							double front_back, 
							double high_low, 
							double mouth_stretch) {
    if (front_back < -1.0) front_back = -1.0;
    if (front_back >  1.0) front_back =  1.0;
    if (high_low < -1.0)   high_low = -1.0;
    if (high_low >  1.0)   high_low =  1.0;
    if (mouth_stretch < -1.0) mouth_stretch = -1.0;
    if (mouth_stretch >  1.0) mouth_stretch =  1.0;

    fof->control_tongue_x = front_back;
    fof->control_tongue_y = high_low;
    fof->control_stretch_z = mouth_stretch;

    // Find the vowel vertex matching these 3D coordinates
    int best_v = 0;
    double min_d2 = 1e9;
    for (int v = 0; v < NUM_VOWELS; v++) {
        double dx = front_back - ANATOMICAL_SPACE[v].tongue_front_back;
        double dy = high_low   - ANATOMICAL_SPACE[v].tongue_high_low;
        double dz = mouth_stretch - ANATOMICAL_SPACE[v].mouth_stretch;
        double d2 = (dx * dx) + (dy * dy) + (dz * dz);
        if (d2 < min_d2) {
            min_d2 = d2;
            best_v = v;
        }
    }

    // Trigger natural transition to the new vowel
    if (best_v != fof->target_vowel_idx) {
        if (fof->vowel_morph > 0.5) {
            fof->prev_vowel_idx = fof->target_vowel_idx;
        }
        fof->target_vowel_idx = best_v;
        fof->vowel_morph = 0.0;
    }
}
//_______________________________________________________________________________________
void fof_set_vocal_tract_3d0(FofSynthesizer* fof, 
								double front_back, 
								double high_low, 
								double mouth_stretch) {
    if (front_back < -1.0) front_back = -1.0;
    if (front_back >  1.0) front_back =  1.0;
    if (high_low < -1.0)   high_low = -1.0;
    if (high_low >  1.0)   high_low =  1.0;
    if (mouth_stretch < -1.0) mouth_stretch = -1.0;
    if (mouth_stretch >  1.0) mouth_stretch =  1.0;
    
    fof->control_tongue_x = front_back;
    fof->control_tongue_y = high_low;
    fof->control_stretch_z = mouth_stretch;
}
//_______________________________________________________________________________________
static inline void fof_audio_thread_sync_controls(FofSynthesizer* fof) {
    if (fof->smoothed_f0_hz <= 0.0) {
        fof->smoothed_f0_hz = fof->control_f0_hz;
    }
    fof->smoothed_f0_hz += 0.05 * (fof->control_f0_hz - fof->smoothed_f0_hz);

    fof->smoothed_mod_wheel += 0.08 * (fof->control_mod_wheel - fof->smoothed_mod_wheel);
    fof->vibrato_phase += (5.2 * 512.0) * fof->inv_sample_rate;
    if (fof->vibrato_phase >= 1.0) {
        fof->vibrato_phase -= 1.0;
    }

    double vibrato_semitones = sin(fof->vibrato_phase * TWO_PI) * (fof->smoothed_mod_wheel * 0.6);
    double modulated_f0_hz = fof->smoothed_f0_hz * pow(2.0, vibrato_semitones / 12.0);
    fof->current_f0_inc = modulated_f0_hz * fof->inv_sample_rate;

    fof->vowel_morph += 0.12;
    if (fof->vowel_morph > 1.0) fof->vowel_morph = 1.0;

    fof->smoothed_nasal += 0.12 * (fof->control_nasal - fof->smoothed_nasal);
    double nas = fof->smoothed_nasal;

    double t_raw = fof->vowel_morph;
    int vA = fof->prev_vowel_idx;
    int vB = fof->target_vowel_idx;

    double mid_motion = sin(t_raw * M_PI);
    double bw_damping_mult = 1.0 + (0.14 * mid_motion);

    double phase_coef = TWO_PI * fof->inv_sample_rate;
    for (int f = 0; f < NUM_FORMANTS; f++) {
        double stagger = (f == 0) ? 1.15 : (f == 1 ? 1.0 : 0.92);
        double t_staggered = t_raw * stagger;
        if (t_staggered > 1.0) t_staggered = 1.0;

        double t_curve = 0.5 * (1.0 - cos(t_staggered * M_PI));
        double inv_t = 1.0 - t_curve;

        double fA = ANATOMICAL_SPACE[vA].formants[f].freq;
        double fB = ANATOMICAL_SPACE[vB].formants[f].freq;
        double interp_freq = fA * pow(fB / fA, t_curve);

        double interp_bw = ((ANATOMICAL_SPACE[vA].formants[f].bandwidth * inv_t)
                          + (ANATOMICAL_SPACE[vB].formants[f].bandwidth * t_curve))
                          * bw_damping_mult;

        double interp_db = (ANATOMICAL_SPACE[vA].formants[f].db_amplitude * inv_t)
                         + (ANATOMICAL_SPACE[vB].formants[f].db_amplitude * t_curve);
/*
        if (nas > 0.001) {
            if (f == 0) {
                interp_freq = interp_freq * pow(270.0 / interp_freq, nas * 0.65);
                interp_bw  += 75.0 * nas;
            } else if (f == 1) {
                interp_bw  += 65.0 * nas;
                interp_db  -= 7.0 * nas;
            } else if (f == 2) {
                interp_freq = interp_freq * pow(2350.0 / interp_freq, nas * 0.35);
                interp_bw  *= (1.0 - 0.25 * nas);
                interp_db  += 3.5 * nas;
            } else {
                interp_db  -= 4.0 * nas;
            }
        }
*/

       if (nas > 0.001) {
            if (f == 0) {
                interp_bw += 35.0 * nas;
                interp_db -= 3.5 * nas;
            } else if (f == 1) {
                interp_bw += 85.0 * nas;
                interp_db -= 9.5 * nas;
            } else if (f == 2) {
                interp_bw *= (1.0 - 0.30 * nas);
                interp_db += 4.5 * nas;
            } else if (f == 5) {
                interp_db = -90.0;
            }
        }
        
        fof->target_phase_incs[f] = interp_freq * phase_coef;
        fof->target_decay_rates[f] = M_PI * interp_bw;
        fof->target_gains[f] = exp(interp_db * PI_OVER_20) * 0.05;
    }
}

//_______________________________________________________________________________________
static inline void fof_spawn_formant_grains_rt(FofSynthesizer* fof) {
    for (int f = 0; f < NUM_FORMANTS; f++) {
        for (int i = 0; i < MAX_GRAINS; i++) {
            if (!fof->grains[i].active) {
                FofGrain* g = &fof->grains[i];
                g->phase = 0.0;
                g->phase_inc = fof->target_phase_incs[f];
                g->age = 0.0;
                g->attack = fof->attack_time;
                g->duration = fof->attack_time + fof->decay_time;
                g->amplitude = fof->target_gains[f];
                g->decay_rate = fof->target_decay_rates[f];
                g->active = 1;
                fof->glide_coeff = 0.0008; // Default legato portamento speed
                break;
            }
        }
    }
}
//_______________________________________________________________________________________
OSStatus FofAudioUnitRenderCallback(void                        *inRefCon,
                                    AudioUnitRenderActionFlags  *ioActionFlags,
                                    const AudioTimeStamp        *inTimeStamp,
                                    UInt32                       inBusNumber,
                                    UInt32                       inNumberFrames,
                                    AudioBufferList             *ioData)
{
    FofSynthesizer *fof = (FofSynthesizer *)inRefCon;
    if (!fof) return noErr;
    
    const int left_channel_idx = 0;
    const int right_channel_idx = 1;
    
    float *leftChannel  = (float *)ioData->mBuffers[left_channel_idx].mData;
    float *rightChannel = (ioData->mNumberBuffers > 1) ? (float *)ioData->mBuffers[right_channel_idx].mData : NULL;
    
    double dt = fof->inv_sample_rate;
    fof_audio_thread_sync_controls(fof);
    
    for (UInt32 frame = 0; frame < inNumberFrames; frame++) {
        double sample_out = 0.0;
        
        ///
               // Smooth gate envelope (prevents clicks on key press/release)
        double target_env = fof->control_gate ? fof->control_velocity : 0.0;
        double env_rate = fof->control_gate ? 0.005 : 0.0005; // Fast attack, smooth release
        fof->current_amp_env += env_rate * (target_env - fof->current_amp_env);
        
        fof->f0_phase += fof->current_f0_inc;
        if (fof->f0_phase >= 1.0) {
            fof->f0_phase -= 1.0;
            // Only trigger new vocal glottal pulses while a MIDI key is held
            if (fof->control_gate) {
                fof_spawn_formant_grains_rt(fof);
            }
        }
        ///
        
        
       /* 
        fof->f0_phase += fof->current_f0_inc;
        if (fof->f0_phase >= 1.0) {
            fof->f0_phase -= 1.0;
            fof_spawn_formant_grains_rt(fof); 
        }
        */
        for (int i = 0; i < MAX_GRAINS; i++) {
            if (!fof->grains[i].active) continue;
            
            FofGrain* g = &fof->grains[i];
            double carrier = sin(g->phase);
            double env = (g->age < g->attack) ? (g->age / g->attack) : exp(-g->decay_rate * (g->age - g->attack));
            
            sample_out += g->amplitude * env * carrier;
            
            g->phase += g->phase_inc;
            if (g->phase >= TWO_PI) g->phase -= TWO_PI;
            
            g->age += dt;
            if (g->age >= g->duration) g->active = 0;
        }
        sample_out *= fof->current_amp_env;
        leftChannel[frame] = (float)sample_out;
        if (rightChannel) rightChannel[frame] = (float)sample_out;
    }
    
    return noErr;
}

//_______________________________________________________________________________________
void* keyboard_input_thread(void* arg) {
    struct termios orig_termios;
    struct termios raw_termios;
    
    tcgetattr(STDIN_FILENO, &orig_termios);
    raw_termios = orig_termios;
    raw_termios.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &raw_termios);
    
    printf("\n--- Extended 3D Anatomical Vocal Space Interface ---\n");
    printf("[a] = A (Back, Low, Stretched)   | [e] = E (Front, Mid, Stretched)\n");
    printf("[i] = I (Front, High, Stretched) | [o] = O (Back, Mid, Rounded)\n");
    printf("[u] = U (Back, High, Rounded)    | [w] = Ö (Front, Mid, Rounded)\n");
    printf("[x] = Æ (Front, Low, Stretched)  | [c] = å (Mid, Mid, Mid)\n");   
    printf("[y] = Ü (Front, High, Rounded)   | [m] = M \n");
    printf("[n] = N                          | [v] = Dark, closed Uum \n");    
    printf("Same keys with <Shift> play nasal versions of phonemes\n");
    printf("MIDI keyboard -> pitch; mod_wheel -> vibrato\n");
    printf("In Demo Mode + and - keys step the pitch half-step up/down\n");   
    printf("No MIDI in Demo Mode \n");   
	printf("[q]  quits the program\n\n");    
    
    while (1) {
        int ch = getchar(); 
        if (debug)  {	
			printf("KEY: %d\n", ch);
			printf("%d\n", g_demoMode);
		}
        if (ch == EOF || ch == 'q') break; 
        
    switch (ch) {
            // Arguments: (Tongue Front/Back, Tongue High/Low, Mouth Round/Stretch)
     case 'a':
	    fof_set_nasal(g_fofEngine, 0.0);
	    fof_set_vocal_tract_3d(g_fofEngine, -1.0, -1.0,  1.0);
	    printf("Vowel: A (Oral)\n");
   		 break;
	case 'e':
	    fof_set_nasal(g_fofEngine, 0.0);
	    fof_set_vocal_tract_3d(g_fofEngine,  0.5,  0.0,  1.0);
	    printf("Vowel: E (Oral)\n");
   	 break;
	case 'i':
		fof_set_nasal(g_fofEngine, 0.0);
		fof_set_vocal_tract_3d(g_fofEngine,  1.0,  1.0,  1.0);
		printf("Vowel: I (Oral)\n");
		break;
	case 'o':
		fof_set_nasal(g_fofEngine, 0.0);
		fof_set_vocal_tract_3d(g_fofEngine, -0.5,  0.0, -1.0);
		printf("Vowel: O (Oral)\n");
		break;
	case 'u':
		fof_set_nasal(g_fofEngine, 0.0);
		fof_set_vocal_tract_3d(g_fofEngine, -1.0,  1.0, -1.0);
		printf("Vowel: U (Oral)\n");
		break;
	case 'w':
		fof_set_nasal(g_fofEngine, 0.0);
		fof_set_vocal_tract_3d(g_fofEngine,  0.5,  0.0, -1.0);
		printf("Vowel: Ö (Oral)\n");
		break;
	case 'y':
		fof_set_nasal(g_fofEngine, 0.0);
		fof_set_vocal_tract_3d(g_fofEngine,  1.0,  1.0, -1.0);
		printf("Vowel: Ü (Oral)\n");
		break;
	case 'v':
		fof_set_nasal(g_fofEngine, 0.0);
		fof_set_vocal_tract_3d(g_fofEngine, -1.0, -1.0, -1.0);
		printf("Vowel: Dark Closed Hum (Oral)\n");
		break;
	case 'c':
		fof_set_nasal(g_fofEngine, 0.0);
		fof_set_vocal_tract_3d(g_fofEngine,  0.0,  0.0,  0.0);
		printf("Vowel: Schwa (Oral)\n");
		break;
	case 'x':
		fof_set_nasal(g_fofEngine, 0.0);
		fof_set_vocal_tract_3d(g_fofEngine,  0.8, -1.0,  1.0);
		printf("Vowel: Æ (Oral)\n");
		break;
	case 'm':
		fof_set_nasal(g_fofEngine, 0.0);
		fof_set_vocal_tract_3d(g_fofEngine, -0.8, -0.5, -1.0);
		printf("Consonant: M\n");
		break;
	case 'n':
		fof_set_nasal(g_fofEngine, 0.0);
		fof_set_vocal_tract_3d(g_fofEngine,  0.6,  0.8, -0.5);
		printf("Consonant: N\n");
		break;
	
	case 'A':
		fof_set_nasal(g_fofEngine, 1.0);
		fof_set_vocal_tract_3d(g_fofEngine, -1.0, -1.0,  1.0);
		printf("Vowel: Ã (Nasal)\n");
		break;
	case 'E':
		fof_set_nasal(g_fofEngine, 1.0);
		fof_set_vocal_tract_3d(g_fofEngine,  0.5,  0.0,  1.0);
		printf("Vowel: Ẽ (Nasal)\n");
		break;
	case 'I':
		fof_set_nasal(g_fofEngine, 1.0);
		fof_set_vocal_tract_3d(g_fofEngine,  1.0,  1.0,  1.0);
		printf("Vowel: Ĩ (Nasal)\n");
		break;
	case 'O':
		fof_set_nasal(g_fofEngine, 1.0);
		fof_set_vocal_tract_3d(g_fofEngine, -0.5,  0.0, -1.0);
		printf("Vowel: Õ (Nasal)\n");
		break;
	case 'U':
		fof_set_nasal(g_fofEngine, 1.0);
		fof_set_vocal_tract_3d(g_fofEngine, -1.0,  1.0, -1.0);
		printf("Vowel: Ũ (Nasal)\n");
		break;
	case 'W':
		fof_set_nasal(g_fofEngine, 1.0);
		fof_set_vocal_tract_3d(g_fofEngine,  0.5,  0.0, -1.0);
		printf("Vowel: Ö (Nasal)\n");
		break;
	case 'Y':
		fof_set_nasal(g_fofEngine, 1.0);
		fof_set_vocal_tract_3d(g_fofEngine,  1.0,  1.0, -1.0);
		printf("Vowel: Ü (Nasal)\n");
		break;
	case 'V':
		fof_set_nasal(g_fofEngine, 1.0);
		fof_set_vocal_tract_3d(g_fofEngine, -1.0, -1.0, -1.0);
		printf("Vowel: Dark Closed Hum (Nasal)\n");
		break;
	case 'C':
		fof_set_nasal(g_fofEngine, 1.0);
		fof_set_vocal_tract_3d(g_fofEngine,  0.0,  0.0,  0.0);
		printf("Vowel: Schwa (Nasal)\n");
		break;
	case 'X':
		fof_set_nasal(g_fofEngine, 1.0);
		fof_set_vocal_tract_3d(g_fofEngine,  0.8, -1.0,  1.0);
		printf("Vowel: Æ (Nasal)\n");
		break;
	case 'M':
		fof_set_nasal(g_fofEngine, 1.0);
		fof_set_vocal_tract_3d(g_fofEngine, -0.8, -0.5, -1.0);
		printf("Consonant: M (Deep Nasal)\n");
		break;
	case 'N':
		fof_set_nasal(g_fofEngine, 1.0);
		fof_set_vocal_tract_3d(g_fofEngine,  0.6,  0.8, -0.5);
		printf("Consonant: N (Deep Nasal)\n");
		break;
	  case 61:
	  case 43:
		if (g_demoMode) {
			fof_set_pitch(g_fofEngine, g_fofEngine->control_f0_hz * 1.0594630943592953);
			printf("Pitch (+1 semitone): %.2f Hz\n", g_fofEngine->control_f0_hz);
		}
		break;
	 case 45:
	 case 95:
		if (g_demoMode) {
			fof_set_pitch(g_fofEngine, g_fofEngine->control_f0_hz / 1.0594630943592953);
			printf("Pitch (-1 semitone): %.2f Hz\n", g_fofEngine->control_f0_hz);
		}
   		 break;


		
    default: break;
	}
	
}
tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
return NULL;
}
//_______________________________________________________________________________________
int main1(int argc, const char *argv[]) {
    double sample_rate = 44100.0;
    double initial_pitch = 130.65;

  if (argc > 1) {
        char *endptr;
        double parsed_pitch = strtod(argv[1], &endptr);
        if (endptr != argv[1]) {
            initial_pitch = parsed_pitch;
        } else {
            fprintf(stderr, "Invalid pitch value provided. Using default %.2f Hz.\n", initial_pitch);
        }
    }

    g_fofEngine = calloc(1, sizeof(FofSynthesizer));
    if (!g_fofEngine) return 1;

    g_fofEngine->sample_rate = sample_rate;
    g_fofEngine->inv_sample_rate = 1.0 / sample_rate;
    g_fofEngine->attack_time = 0.002;
    g_fofEngine->decay_time = 0.040;
    g_fofEngine->smoothed_x = -1.0;
    g_fofEngine->smoothed_y = -1.0;
    g_fofEngine->smoothed_z =  1.0;
    g_fofEngine->vowel_morph = 1.0;

    fof_set_pitch(g_fofEngine, initial_pitch);
    g_fofEngine->smoothed_f0_hz = g_fofEngine->control_f0_hz;
    g_fofEngine->current_f0_inc = g_fofEngine->control_f0_hz * g_fofEngine->inv_sample_rate;
    fof_set_vocal_tract_3d(g_fofEngine, -1.0, -1.0, 1.0);

    if (argc > 1) {
        g_fofEngine->control_velocity = 0.8;
        g_fofEngine->control_gate = 1;
        printf("Playing at %.2f Hz\n", g_fofEngine->control_f0_hz);
    } else {
        g_fofEngine->control_gate = 0;
        printf("Waiting for MIDI Note-On...\n");
    }

    fof_init_midi(g_fofEngine);
    printf("FOF Synth listening for MIDI. Press Ctrl+C to quit.\n");

    AudioComponentDescription desc = {
        .componentType = kAudioUnitType_Output,
        .componentSubType = kAudioUnitSubType_DefaultOutput,
        .componentManufacturer = kAudioUnitManufacturer_Apple
    };
    AudioComponent comp = AudioComponentFindNext(NULL, &desc);
    AudioUnit outputAudioUnit;
    AudioComponentInstanceNew(comp, &outputAudioUnit);

    AudioStreamBasicDescription streamFormat = {
        .mSampleRate = sample_rate,
        .mFormatID = kAudioFormatLinearPCM,
        .mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved,
        .mBytesPerPacket = sizeof(float),
        .mFramesPerPacket = 1,
        .mBytesPerFrame = sizeof(float),
        .mChannelsPerFrame = 2,
        .mBitsPerChannel = 32
    };
    AudioUnitSetProperty(outputAudioUnit, 
    				kAudioUnitProperty_StreamFormat, 
    				kAudioUnitScope_Input, 
    				0, &streamFormat, 
    				sizeof(streamFormat));

    AURenderCallbackStruct callbackStruct = {
        .inputProc = FofAudioUnitRenderCallback,
        .inputProcRefCon = g_fofEngine
    };
    AudioUnitSetProperty(outputAudioUnit, 
    				kAudioUnitProperty_SetRenderCallback, k
    				AudioUnitScope_Input, 
    				0, 
    				&callbackStruct, 
    				sizeof(callbackStruct));

    AudioUnitInitialize(outputAudioUnit);
    AudioOutputUnitStart(outputAudioUnit);

    pthread_t input_thread_id;
    pthread_create(&input_thread_id, NULL, keyboard_input_thread, NULL);
    pthread_join(input_thread_id, NULL);

    AudioOutputUnitStop(outputAudioUnit);
    AudioUnitUninitialize(outputAudioUnit);
    AudioComponentInstanceDispose(outputAudioUnit);
    free(g_fofEngine);

    return 0;
}

//_______________________________________________________________________________________
int main(int argc, const char *argv[]) {
double sample_rate = 44100.0;
double initial_pitch = 130.65;
int has_valid_pitch_arg = 0;

double parsed_pitch;
char *endptr;

// 0. Parse arguments
    if (argc > 1) {
        parsed_pitch = strtod(argv[1], &endptr);
        if (endptr != argv[1]) {
            initial_pitch = parsed_pitch;
            has_valid_pitch_arg = 1;
        } else {
            fprintf(stderr, "Invalid pitch value provided.\n");
        }
    }

// 1. Setup CoreAudio 
    g_fofEngine = calloc(1, sizeof(FofSynthesizer));
    if (!g_fofEngine) return 1;

    g_fofEngine->sample_rate = sample_rate;
    g_fofEngine->inv_sample_rate = 1.0 / sample_rate;
    g_fofEngine->attack_time = 0.002;
    g_fofEngine->decay_time = 0.040;
    g_fofEngine->smoothed_x = -1.0;
    g_fofEngine->smoothed_y = -1.0;
    g_fofEngine->smoothed_z =  1.0;
    g_fofEngine->vowel_morph = 1.0;

    fof_set_pitch(g_fofEngine, initial_pitch);
    g_fofEngine->smoothed_f0_hz = g_fofEngine->control_f0_hz;
    g_fofEngine->current_f0_inc = g_fofEngine->control_f0_hz * g_fofEngine->inv_sample_rate;
    fof_set_vocal_tract_3d(g_fofEngine, -1.0, -1.0, 1.0);

    fof_init_midi(g_fofEngine);

    if (has_valid_pitch_arg) {
        g_demoMode = 1;
        g_fofEngine->control_velocity = 0.8;
        g_fofEngine->control_gate = 1;
        printf("Demo mode: Playing at %.2f Hz (Press 'q' to quit)\n", g_fofEngine->control_f0_hz);
    } else {
        g_demoMode = 0;
        g_fofEngine->control_gate = 0;
        printf("FOF Synth listening for MIDI. Press Ctrl+C to quit.\n");
    }


// 2. Initialize CoreMIDI and connect to MIDI sources
	fof_init_midi (g_fofEngine);
	for (int i = 0; i < MAX_GRAINS; i++) g_fofEngine->grains[i].active = 0;
	fof_set_pitch(g_fofEngine, initial_pitch);
	fof_set_vocal_tract_3d(g_fofEngine, -1.0, -1.0, 1.0);


AudioComponentDescription desc = {
	.componentType = kAudioUnitType_Output,
	.componentSubType = kAudioUnitSubType_DefaultOutput,
	.componentManufacturer = kAudioUnitManufacturer_Apple
	};
AudioComponent comp = AudioComponentFindNext(NULL, &desc);
AudioUnit outputAudioUnit;
AudioComponentInstanceNew(comp, &outputAudioUnit);
AudioStreamBasicDescription streamFormat = {
	.mSampleRate = sample_rate,
	.mFormatID = kAudioFormatLinearPCM,
	.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved,
	.mBytesPerPacket = sizeof(float),
	.mFramesPerPacket = 1,
	.mBytesPerFrame = sizeof(float),
	.mChannelsPerFrame = 2,
	.mBitsPerChannel = 32
};
AudioUnitSetProperty(outputAudioUnit, 
					kAudioUnitProperty_StreamFormat, 
					kAudioUnitScope_Input, 
					0, &streamFormat, 
					sizeof(streamFormat));
AURenderCallbackStruct callbackStruct = { .inputProc = FofAudioUnitRenderCallback, 
					.inputProcRefCon = g_fofEngine };
					
AudioUnitSetProperty(outputAudioUnit, 
					kAudioUnitProperty_SetRenderCallback, 
					kAudioUnitScope_Input, 
					0, &callbackStruct, 
					sizeof(callbackStruct));
					
AudioUnitInitialize(outputAudioUnit);

AudioOutputUnitStart(outputAudioUnit);

pthread_t input_thread_id;

pthread_create(&input_thread_id, NULL, keyboard_input_thread, NULL);

pthread_join(input_thread_id, NULL);

AudioOutputUnitStop(outputAudioUnit);

AudioComponentInstanceDispose(outputAudioUnit);

free(g_fofEngine);

return 0;
}
