 
 <<includeIntrinsic>>
//----------------------------------------------------------------------------
//  FAUST generated signal processor
//----------------------------------------------------------------------------
<<includeclass>>

// Decode MIDI events and send freq, gain, gate values to the processor
processor dsp_wrapper
{
    input event
    {
        soul::NoteEvents::NoteOn    noteOn;
        soul::NoteEvents::NoteOff   noteOff;
        soul::NoteEvents::PitchBend pitchBend;
    }

    output event float32 freq, gain, gate;

    event noteOn (soul::NoteEvents::NoteOn e)
    {
    	freq << soul::noteNumberToFrequency (e.note);
        gain << e.velocity;
        gate << 1.0f;
    }
     
    event noteOff (soul::NoteEvents::NoteOff e) 
    { 
        gate << 0.0f;
    }
    
    event pitchBend (soul::NoteEvents::PitchBend e) 
    {
    	// TODO
    }
     
    void run()
    {
        advance();
    }
}

/*
	Connect the wrapper with the real processor. 
	This indirection is done since the "expose control event" model is not yet in place.
	So we have some 'ad-hoc' code for now that may change in the future
*/

graph dsp_voice
{
    input event
    {
        soul::NoteEvents::NoteOn   	 noteOn;
        soul::NoteEvents::NoteOff  	 noteOff;
        soul::NoteEvents::PitchBend  pitchBend;
    }
    
    output stream float audioOut0;
    output stream float audioOut1;
    
    connection
    {
        noteOn    -> dsp_wrapper.noteOn;
        noteOff   -> dsp_wrapper.noteOff;
        pitchBend -> dsp_wrapper.pitchBend;
        
        // This is still the 'hard coded' part
        dsp_wrapper.freq  -> mydsp.event_freq;
        dsp_wrapper.gain  -> mydsp.event_gain;
        dsp_wrapper.gate  -> mydsp.event_gate;
 
        mydsp.output0 ->  audioOut0;
        mydsp.output1 ->  audioOut1;
    }
}

processor RoundRobinVoiceAllocator (int voiceCount)
{
    input event
    {
        soul::NoteEvents::NoteOn     noteOn;
        soul::NoteEvents::NoteOff    noteOff;
        soul::NoteEvents::PitchBend  pitchBend;
    }

    output event
    {
        soul::NoteEvents::NoteOn     voiceNoteOn[voiceCount];
        soul::NoteEvents::NoteOff    voiceNoteOff[voiceCount];
        soul::NoteEvents::PitchBend  voicePitchBend[voiceCount];
    }

    event noteOn (soul::NoteEvents::NoteOn e)
    {
        wrap<voiceCount> allocatedVoice = 0;
        var allocatedVoiceAge = voiceInfo[allocatedVoice].voiceAge;

        // Find the oldest voice to reuse
        for (int i = 1; i < voiceCount; i++)
        {
            let age = voiceInfo.at (i).voiceAge;

            if (age < allocatedVoiceAge)
            {
                allocatedVoiceAge = age;
                allocatedVoice = wrap<voiceCount>(i);
            }
        }

        // Update the VoiceInfo for our chosen voice
        voiceInfo[allocatedVoice].channel  = e.channel;
        voiceInfo[allocatedVoice].note     = e.note;
        voiceInfo[allocatedVoice].voiceAge = nextAllocatedVoiceAge++;

        // Send the note on to the voice
        voiceNoteOn[allocatedVoice] << e;
    }

    event noteOff (soul::NoteEvents::NoteOff e)
    {
        // Release all voices associated with this note/channel
        wrap<voiceCount> voice = 0;

        loop (voiceCount)
        {
            if (voiceInfo[voice].channel == e.channel
                 && voiceInfo[voice].note == e.note)
            {
                // Mark the voice as being unused
                voiceInfo[voice].voiceAge = nextUnallocatedVoiceAge++;
                voiceNoteOff[voice] << e;
            }

            ++voice;
        }
    }

    event pitchBend (soul::NoteEvents::PitchBend e)
    {
        // Forward the pitch bend to all notes on this channel
        wrap<voiceCount> voice = 0;

        loop (voiceCount)
        {
            if (voiceInfo[voice].channel == e.channel)
                voicePitchBend[voice] << e;

            voice++;
        }
    }

    struct VoiceInfo
    {
        bool active;
        float note;
        int channel, voiceAge;
    }

    int nextAllocatedVoiceAge   = 1000000000;
    int nextUnallocatedVoiceAge = 1;

    VoiceInfo[voiceCount] voiceInfo;

    void run()
    {
        loop { advance(); }
    }
}

// Allocates voices, does dynamic voice management, connect to MIDI parser
graph mydsp_poly [[ main ]]
{
    input event midi::Message midiIn;
    output stream float audioOut0;
    output stream float audioOut1;
    
    let
    {
        midiParser = midi::MPEParser;
        voices = dsp_voice[NVOICES];
        voiceAllocator = RoundRobinVoiceAllocator (NVOICES);
    }
  
    connection
    {
        midiIn -> midiParser.parseMIDI;
   
        midiParser.eventOut -> voiceAllocator.noteOn, 
                               voiceAllocator.noteOff,
                               voiceAllocator.pitchBend;

        // Plumb the voice allocator to the voices array
        voiceAllocator.voiceNoteOn    -> voices.noteOn;
        voiceAllocator.voiceNoteOff   -> voices.noteOff;
        voiceAllocator.voicePitchBend -> voices.pitchBend;

        // Sum the voices audio out to the output
        voices.audioOut0 -> audioOut0;
        voices.audioOut1 -> audioOut1;
    }
}
