/*
 * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package media.sound;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.sound.midi.VoiceStatus;

/**
 * Software synthesizer voice class.
 *
 * @author Karl Helgason
 */
public class SoftVoice extends VoiceStatus {

    public int exclusiveClass = 0;
    public boolean releaseTriggered = false;
    private int noteOn_noteNumber = 0;
    private int noteOn_velocity = 0;
    private int noteOff_velocity = 0;
    private int delay = 0;
    protected ModelChannelMixer channelmixer = null;
    protected double tunedKey = 0;
    protected SoftTuning tuning = null;
    protected SoftChannel stealer_channel = null;
    protected ModelConnectionBlock[] stealer_extendedConnectionBlocks = null;
    protected SoftPerformer stealer_performer = null;
    protected ModelChannelMixer stealer_channelmixer = null;
    protected int stealer_voiceID = -1;
    protected int stealer_noteNumber = 0;
    protected int stealer_velocity = 0;
    protected boolean stealer_releaseTriggered = false;
    protected int voiceID = -1;
    protected boolean sustain = false;
    protected boolean sostenuto = false;
    protected boolean portamento = false;
    private SoftFilter filter_left;
    private SoftFilter filter_right;
    private SoftProcess eg = new SoftEnvelopeGenerator();
    private SoftProcess lfo = new SoftLowFrequencyOscillator();
    protected Map<String, SoftControl> objects =
            new HashMap<String, SoftControl>();
    protected SoftSynthesizer synthesizer;
    protected SoftInstrument instrument;
    protected SoftPerformer performer;
    protected SoftChannel softchannel = null;
    protected boolean on = false;
    private boolean audiostarted = false;
    private boolean started = false;
    private boolean stopping = false;
    private float osc_attenuation = 0.0f;
    private ModelOscillatorStream osc_stream;
    private int osc_stream_nrofchannels;
    private float[][] osc_buff = new float[2][];
    private boolean osc_stream_off_transmitted = false;
    private boolean out_mixer_end = false;
    private float out_mixer_left = 0;
    private float out_mixer_right = 0;
    private float out_mixer_effect1 = 0;
    private float out_mixer_effect2 = 0;
    private float last_out_mixer_left = 0;
    private float last_out_mixer_right = 0;
    private float last_out_mixer_effect1 = 0;
    private float last_out_mixer_effect2 = 0;
    protected ModelConnectionBlock[] extendedConnectionBlocks = null;
    private ModelConnectionBlock[] connections;
    // Last value added to destination
    private double[] connections_last = new double[50];
    // Pointer to source value
    private double[][][] connections_src = new double[50][3][];
    // Key-based override (if any)
    private int[][] connections_src_kc = new int[50][3];
    // Pointer to destination value
    private double[][] connections_dst = new double[50][];
    private boolean soundoff = false;
    private float lastMuteValue = 0;
    private float lastSoloMuteValue = 0;
    protected double[] co_noteon_keynumber = new double[1];
    protected double[] co_noteon_velocity = new double[1];
    protected double[] co_noteon_on = new double[1];
    private SoftControl co_noteon = new SoftControl() {
        double[] keynumber = co_noteon_keynumber;
        double[] velocity = co_noteon_velocity;
        double[] on = co_noteon_on;
        public double[] get(int instance, String name) {
            if (name == null)
                return null;
            if (name.equals("keynumber"))
                return keynumber;
            if (name.equals("velocity"))
                return velocity;
            if (name.equals("on"))
                return on;
            return null;
        }
    };
    private double[] co_mixer_active = new double[1];
    private double[] co_mixer_gain = new double[1];
    private double[] co_mixer_pan = new double[1];
    private double[] co_mixer_balance = new double[1];
    private double[] co_mixer_reverb = new double[1];
    private double[] co_mixer_chorus = new double[1];
    private SoftControl co_mixer = new SoftControl() {
        double[] active = co_mixer_active;
        double[] gain = co_mixer_gain;
        double[] pan = co_mixer_pan;
        double[] balance = co_mixer_balance;
        double[] reverb = co_mixer_reverb;
        double[] chorus = co_mixer_chorus;
        public double[] get(int instance, String name) {
            if (name == null)
                return null;
            if (name.equals("active"))
                return active;
            if (name.equals("gain"))
                return gain;
            if (name.equals("pan"))
                return pan;
            if (name.equals("balance"))
                return balance;
            if (name.equals("reverb"))
                return reverb;
            if (name.equals("chorus"))
                return chorus;
            return null;
        }
    };
    private double[] co_osc_pitch = new double[1];
    private SoftControl co_osc = new SoftControl() {
        double[] pitch = co_osc_pitch;
        public double[] get(int instance, String name) {
            if (name == null)
                return null;
            if (name.equals("pitch"))
                return pitch;
            return null;
        }
    };
    private double[] co_filter_freq = new double[1];
    private double[] co_filter_type = new double[1];
    private double[] co_filter_q = new double[1];
    private SoftControl co_filter = new SoftControl() {
        double[] freq = co_filter_freq;
        double[] ftype = co_filter_type;
        double[] q = co_filter_q;
        public double[] get(int instance, String name) {
            if (name == null)
                return null;
            if (name.equals("freq"))
                return freq;
            if (name.equals("type"))
                return ftype;
            if (name.equals("q"))
                return q;
            return null;
        }
    };
    protected SoftResamplerStreamer resampler;
    private int nrofchannels;

    public SoftVoice(SoftSynthesizer synth) {
        synthesizer = synth;
        filter_left = new SoftFilter(synth.getFormat().getSampleRate());
        filter_right = new SoftFilter(synth.getFormat().getSampleRate());
        nrofchannels = synth.getFormat().getChannels();
    }

    private int getValueKC(ModelIdentifier id) {
        if (id.getObject().equals("midi_cc")) {
            int ic = Integer.parseInt(id.getVariable());
            if (ic != 0 && ic != 32) {
                if (ic < 120)
                    return ic;
            }
        } else if (id.getObject().equals("midi_rpn")) {
            if (id.getVariable().equals("1"))
                return 120; // Fine tuning
            if (id.getVariable().equals("2"))
                return 121; // Coarse tuning
        }
        return -1;
    }

    private double[] getValue(ModelIdentifier id) {
        SoftControl o = objects.get(id.getObject());
        if (o == null)
            return null;
        return o.get(id.getInstance(), id.getVariable());
    }

    private double transformValue(double value, ModelSource src) {
        if (src.getTransform() != null)
            return src.getTransform().transform(value);
        else
            return value;
    }

    private double transformValue(double value, ModelDestination dst) {
        if (dst.getTransform() != null)
            return dst.getTransform().transform(value);
        else
            return value;
    }

    private double processKeyBasedController(double value, int keycontrol) {
        if (keycontrol == -1)
            return value;
        if (softchannel.keybasedcontroller_active != null)
            if (softchannel.keybasedcontroller_active[note] != null)
                if (softchannel.keybasedcontroller_active[note][keycontrol]) {
                    double key_controlvalue =
                            softchannel.keybasedcontroller_value[note][keycontrol];
                    if (keycontrol == 10 || keycontrol == 91 || keycontrol == 93)
                        return key_controlvalue;
                    value += key_controlvalue * 2.0 - 1.0;
                    if (value > 1)
                        value = 1;
                    else if (value < 0)
                        value = 0;
                }
        return value;
    }

    private void processConnection(int ix) {
        ModelConnectionBlock conn = connections[ix];
        double[][] src = connections_src[ix];
        double[] dst = connections_dst[ix];
        if (dst == null || Double.isInfinite(dst[0]))
            return;

        double value = conn.getScale();
        if (softchannel.keybasedcontroller_active == null) {
            ModelSource[] srcs = conn.getSources();
            for (int i = 0; i < srcs.length; i++) {
                value *= transformValue(src[i][0], srcs[i]);
                if (value == 0)
                    break;
            }
        } else {
            ModelSource[] srcs = conn.getSources();
            int[] src_kc = connections_src_kc[ix];
            for (int i = 0; i < srcs.length; i++) {
                value *= transformValue(processKeyBasedController(src[i][0],
                        src_kc[i]), srcs[i]);
                if (value == 0)
                    break;
            }
        }

        value = transformValue(value, conn.getDestination());
        dst[0] = dst[0] - connections_last[ix] + value;
        connections_last[ix] = value;
        // co_mixer_gain[0] = 0;
    }

    protected void updateTuning(SoftTuning newtuning) {
        tuning = newtuning;
        tunedKey = tuning.getTuning(note) / 100.0;
        if (!portamento) {
            co_noteon_keynumber[0] = tunedKey * (1.0 / 128.0);
            if(performer == null)
                return;
            int[] c = performer.midi_connections[4];
            if (c == null)
                return;
            for (int i = 0; i < c.length; i++)
                processConnection(c[i]);
        }
    }

    protected void setNote(int noteNumber) {
        note = noteNumber;
        tunedKey = tuning.getTuning(noteNumber) / 100.0;
    }

    protected void noteOn(int noteNumber, int velocity, int delay) {

        sustain = false;
        sostenuto = false;
        portamento = false;

        soundoff = false;
        on = true;
        active = true;
        started = true;
        // volume = velocity;

        noteOn_noteNumber = noteNumber;
        noteOn_velocity = velocity;
        this.delay = delay;

        lastMuteValue = 0;
        lastSoloMuteValue = 0;

        setNote(noteNumber);

        if (performer.forcedKeynumber)
            co_noteon_keynumber[0] = 0;
        else
            co_noteon_keynumber[0] = tunedKey * (1f / 128f);
        if (performer.forcedVelocity)
            co_noteon_velocity[0] = 0;
        else
            co_noteon_velocity[0] = velocity * (1f / 128f);
        co_mixer_active[0] = 0;
        co_mixer_gain[0] = 0;
        co_mixer_pan[0] = 0;
        co_mixer_balance[0] = 0;
        co_mixer_reverb[0] = 0;
        co_mixer_chorus[0] = 0;
        co_osc_pitch[0] = 0;
        co_filter_freq[0] = 0;
        co_filter_q[0] = 0;
        co_filter_type[0] = 0;
        co_noteon_on[0] = 1;

        eg.reset();
        lfo.reset();
        filter_left.reset();
        filter_right.reset();

        objects.put("master", synthesizer.getMainMixer().co_master);
        objects.put("eg", eg);
        objects.put("lfo", lfo);
        objects.put("noteon", co_noteon);
        objects.put("osc", co_osc);
        objects.put("mixer", co_mixer);
        objects.put("filter", co_filter);

        connections = performer.connections;

        if (connections_last == null
                || connections_last.length < connections.length) {
            connections_last = new double[connections.length];
        }
        if (connections_src == null
                || connections_src.length < connections.length) {
            connections_src = new double[connections.length][][];
            connections_src_kc = new int[connections.length][];
        }
        if (connections_dst == null
                || connections_dst.length < connections.length) {
            connections_dst = new double[connections.length][];
        }
        for (int i = 0; i < connections.length; i++) {
            ModelConnectionBlock conn = connections[i];
            connections_last[i] = 0;
            if (conn.getSources() != null) {
                ModelSource[] srcs = conn.getSources();
                if (connections_src[i] == null
                        || connections_src[i].length < srcs.length) {
                    connections_src[i] = new double[srcs.length][];
                    connections_src_kc[i] = new int[srcs.length];
                }
                double[][] src = connections_src[i];
                int[] src_kc = connections_src_kc[i];
                connections_src[i] = src;
                for (int j = 0; j < srcs.length; j++) {
                    src_kc[j] = getValueKC(srcs[j].getIdentifier());
                    src[j] = getValue(srcs[j].getIdentifier());
                }
            }

            if (conn.getDestination() != null)
                connections_dst[i] = getValue(conn.getDestination()
                        .getIdentifier());
            else
                connections_dst[i] = null;
        }

        for (int i = 0; i < connections.length; i++)
            processConnection(i);

        if (extendedConnectionBlocks != null) {
            for (ModelConnectionBlock connection: extendedConnectionBlocks) {
                double value = 0;

                if (softchannel.keybasedcontroller_active == null) {
                    for (ModelSource src: connection.getSources()) {
                        double x = getValue(src.getIdentifier())[0];
                        ModelTransform t = src.getTransform();
                        if (t == null)
                            value += x;
                        else
                            value += t.transform(x);
                    }
                } else {
                    for (ModelSource src: connection.getSources()) {
                        double x = getValue(src.getIdentifier())[0];
                        x = processKeyBasedController(x,
                                getValueKC(src.getIdentifier()));
                        ModelTransform t = src.getTransform();
                        if (t == null)
                            value += x;
                        else
                            value += t.transform(x);
                    }
                }

                ModelDestination dest = connection.getDestination();
                ModelTransform t = dest.getTransform();
                if (t != null)
                    value = t.transform(value);
                getValue(dest.getIdentifier())[0] += value;
            }
        }

        eg.init(synthesizer);
        lfo.init(synthesizer);

    }

    protected void setPolyPressure(int pressure) {
        if(performer == null)
            return;
        int[] c = performer.midi_connections[2];
        if (c == null)
            return;
        for (int i = 0; i < c.length; i++)
            processConnection(c[i]);
    }

    protected void setChannelPressure(int pressure) {
        if(performer == null)
            return;
        int[] c = performer.midi_connections[1];
        if (c == null)
            return;
        for (int i = 0; i < c.length; i++)
            processConnection(c[i]);
    }

    protected void controlChange(int controller, int value) {
        if(performer == null)
            return;
        int[] c = performer.midi_ctrl_connections[controller];
        if (c == null)
            return;
        for (int i = 0; i < c.length; i++)
            processConnection(c[i]);
    }

    protected void nrpnChange(int controller, int value) {
        if(performer == null)
            return;
        int[] c = performer.midi_nrpn_connections.get(controller);
        if (c == null)
            return;
        for (int i = 0; i < c.length; i++)
            processConnection(c[i]);
    }

    protected void rpnChange(int controller, int value) {
        if(performer == null)
            return;
        int[] c = performer.midi_rpn_connections.get(controller);
        if (c == null)
            return;
        for (int i = 0; i < c.length; i++)
            processConnection(c[i]);
    }

    protected void setPitchBend(int bend) {
        if(performer == null)
            return;
        int[] c = performer.midi_connections[0];
        if (c == null)
            return;
        for (int i = 0; i < c.length; i++)
            processConnection(c[i]);
    }

    protected void setMute(boolean mute) {
        co_mixer_gain[0] -= lastMuteValue;
        lastMuteValue = mute ? -960 : 0;
        co_mixer_gain[0] += lastMuteValue;
    }

    protected void setSoloMute(boolean mute) {
        co_mixer_gain[0] -= lastSoloMuteValue;
        lastSoloMuteValue = mute ? -960 : 0;
        co_mixer_gain[0] += lastSoloMuteValue;
    }

    protected void shutdown() {
        if (co_noteon_on[0] < -0.5)
            return;
        on = false;

        co_noteon_on[0] = -1;

        if(performer == null)
            return;
        int[] c = performer.midi_connections[3];
        if (c == null)
            return;
        for (int i = 0; i < c.length; i++)
            processConnection(c[i]);
    }

    protected void soundOff() {
        on = false;
        soundoff = true;
    }

    protected void noteOff(int velocity) {
        if (!on)
            return;
        on = false;

        noteOff_velocity = velocity;

        if (softchannel.sustain) {
            sustain = true;
            return;
        }
        if (sostenuto)
            return;

        co_noteon_on[0] = 0;

        if(performer == null)
            return;
        int[] c = performer.midi_connections[3];
        if (c == null)
            return;
        for (int i = 0; i < c.length; i++)
            processConnection(c[i]);
    }

    protected void redamp() {
        if (co_noteon_on[0] > 0.5)
            return;
        if (co_noteon_on[0] < -0.5)
            return; // don't redamp notes in shutdown stage

        sustain = true;
        co_noteon_on[0] = 1;

        if(performer == null)
            return;
        int[] c = performer.midi_connections[3];
        if (c == null)
            return;
        for (int i = 0; i < c.length; i++)
            processConnection(c[i]);
    }

    protected void processControlLogic() {
        if (stopping) {
            active = false;
            stopping = false;
            audiostarted = false;
            instrument = null;
            performer = null;
            connections = null;
            extendedConnectionBlocks = null;
            channelmixer = null;            
            if (osc_stream != null)
                try {
                    osc_stream.close();
                } catch (IOException e) {
                    //e.printStackTrace();
                }

            if (stealer_channel != null) {
                stealer_channel.initVoice(this, stealer_performer,
                        stealer_voiceID, stealer_noteNumber, stealer_velocity, 0,
                        stealer_extendedConnectionBlocks, stealer_channelmixer,
                        stealer_releaseTriggered);
                stealer_releaseTriggered = false;
                stealer_channel = null;
                stealer_performer = null;
                stealer_voiceID = -1;
                stealer_noteNumber = 0;
                stealer_velocity = 0;
                stealer_extendedConnectionBlocks = null;
                stealer_channelmixer = null;
            }
        }
        if (started) {
            audiostarted = true;

            ModelOscillator osc = performer.oscillators[0];

            osc_stream_off_transmitted = false;
            if (osc instanceof ModelWavetable) {
                try {
                    resampler.open((ModelWavetable)osc,
                            synthesizer.getFormat().getSampleRate());
                    osc_stream = resampler;
                } catch (IOException e) {
                    //e.printStackTrace();
                }
            } else {
                osc_stream = osc.open(synthesizer.getFormat().getSampleRate());
            }
            osc_attenuation = osc.getAttenuation();
            osc_stream_nrofchannels = osc.getChannels();
            if (osc_buff == null || osc_buff.length < osc_stream_nrofchannels)
                osc_buff = new float[osc_stream_nrofchannels][];

            if (osc_stream != null)
                osc_stream.noteOn(softchannel, this, noteOn_noteNumber,
                        noteOn_velocity);


        }
        if (audiostarted) {
            if (portamento) {
                double note_delta = tunedKey - (co_noteon_keynumber[0] * 128);
                double note_delta_a = Math.abs(note_delta);
                if (note_delta_a < 0.0000000001) {
                    co_noteon_keynumber[0] = tunedKey * (1.0 / 128.0);
                    portamento = false;
                } else {
                    if (note_delta_a > softchannel.portamento_time)
                        note_delta = Math.signum(note_delta)
                                * softchannel.portamento_time;
                    co_noteon_keynumber[0] += note_delta * (1.0 / 128.0);
                }

                int[] c = performer.midi_connections[4];
                if (c == null)
                    return;
                for (int i = 0; i < c.length; i++)
                    processConnection(c[i]);
            }

            eg.processControlLogic();
            lfo.processControlLogic();

            for (int i = 0; i < performer.ctrl_connections.length; i++)
                processConnection(performer.ctrl_connections[i]);

            osc_stream.setPitch((float)co_osc_pitch[0]);

            int filter_type = (int)co_filter_type[0];
            double filter_freq;

            if (co_filter_freq[0] == 13500.0)
                filter_freq = 19912.126958213175;
            else
                filter_freq = 440.0 * Math.exp(
                        ((co_filter_freq[0]) - 6900.0) *
                        (Math.log(2.0) / 1200.0));
            /*
            filter_freq = 440.0 * Math.pow(2.0,
            ((co_filter_freq[0]) - 6900.0) / 1200.0);*/
            /*
             * double velocity = co_noteon_velocity[0]; if(velocity < 0.5)
             * filter_freq *= ((velocity * 2)*0.75 + 0.25);
             */

            double q = co_filter_q[0] / 10.0;
            filter_left.setFilterType(filter_type);
            filter_left.setFrequency(filter_freq);
            filter_left.setResonance(q);
            filter_right.setFilterType(filter_type);
            filter_right.setFrequency(filter_freq);
            filter_right.setResonance(q);
            /*
            float gain = (float) Math.pow(10,
            (-osc_attenuation + co_mixer_gain[0]) / 200.0);
             */
            float gain = (float)Math.exp(
                    (-osc_attenuation + co_mixer_gain[0])*(Math.log(10) / 200.0));

            if (co_mixer_gain[0] <= -960)
                gain = 0;

            if (soundoff) {
                stopping = true;
                gain = 0;
                /*
                 * if(co_mixer_gain[0] > -960)
                 *   co_mixer_gain[0] -= 960;
                 */
            }

            volume = (int)(Math.sqrt(gain) * 128);

            // gain *= 0.2;

            double pan = co_mixer_pan[0] * (1.0 / 1000.0);
            // System.out.println("pan = " + pan);
            if (pan < 0)
                pan = 0;
            else if (pan > 1)
                pan = 1;

            if (pan == 0.5) {
                out_mixer_left = gain * 0.7071067811865476f;
                out_mixer_right = out_mixer_left;
            } else {
                out_mixer_left = gain * (float)Math.cos(pan * Math.PI * 0.5);
                out_mixer_right = gain * (float)Math.sin(pan * Math.PI * 0.5);
            }

            double balance = co_mixer_balance[0] * (1.0 / 1000.0);
            if (balance != 0.5) {
                if (balance > 0.5)
                    out_mixer_left *= (1 - balance) * 2;
                else
                    out_mixer_right *= balance * 2;
            }

            if (synthesizer.reverb_on) {
                out_mixer_effect1 = (float)(co_mixer_reverb[0] * (1.0 / 1000.0));
                out_mixer_effect1 *= gain;
            } else
                out_mixer_effect1 = 0;
            if (synthesizer.chorus_on) {
                out_mixer_effect2 = (float)(co_mixer_chorus[0] * (1.0 / 1000.0));
                out_mixer_effect2 *= gain;
            } else
                out_mixer_effect2 = 0;
            out_mixer_end = co_mixer_active[0] < 0.5;

            if (!on)
                if (!osc_stream_off_transmitted) {
                    osc_stream_off_transmitted = true;
                    if (osc_stream != null)
                        osc_stream.noteOff(noteOff_velocity);
                }

        }
        if (started) {
            last_out_mixer_left = out_mixer_left;
            last_out_mixer_right = out_mixer_right;
            last_out_mixer_effect1 = out_mixer_effect1;
            last_out_mixer_effect2 = out_mixer_effect2;
            started = false;
        }

    }

    protected void mixAudioStream(SoftAudioBuffer in, SoftAudioBuffer out,
            SoftAudioBuffer dout,
            float amp_from, float amp_to) {
        int bufferlen = in.getSize();
        if (amp_from < 0.000000001 && amp_to < 0.000000001)
            return;
        if(dout != null && delay != 0)
        {
            if (amp_from == amp_to) {
                float[] fout = out.array();
                float[] fin = in.array();                
                int j = 0;
                for (int i = delay; i < bufferlen; i++)
                    fout[i] += fin[j++] * amp_to;
                fout = dout.array();
                for (int i = 0; i < delay; i++)
                    fout[i] += fin[j++] * amp_to;
            } else {
                float amp = amp_from;
                float amp_delta = (amp_to - amp_from) / bufferlen;
                float[] fout = out.array();
                float[] fin = in.array();
                int j = 0;
                for (int i = delay; i < bufferlen; i++) {
                    amp += amp_delta;
                    fout[i] += fin[j++] * amp;
                }
                fout = dout.array();
                for (int i = 0; i < delay; i++) {
                    amp += amp_delta;
                    fout[i] += fin[j++] * amp;
                }                
            }
        }
        else
        {
            if (amp_from == amp_to) {
                float[] fout = out.array();
                float[] fin = in.array();
                for (int i = 0; i < bufferlen; i++)
                    fout[i] += fin[i] * amp_to;
            } else {
                float amp = amp_from;
                float amp_delta = (amp_to - amp_from) / bufferlen;
                float[] fout = out.array();
                float[] fin = in.array();
                for (int i = 0; i < bufferlen; i++) {
                    amp += amp_delta;
                    fout[i] += fin[i] * amp;
                }
            }
        }

    }

    protected void processAudioLogic(SoftAudioBuffer[] buffer) {
        if (!audiostarted)
            return;

        int bufferlen = buffer[0].getSize();

        try {
            osc_buff[0] = buffer[SoftMainMixer.CHANNEL_LEFT_DRY].array();
            if (nrofchannels != 1)
                osc_buff[1] = buffer[SoftMainMixer.CHANNEL_RIGHT_DRY].array();
            int ret = osc_stream.read(osc_buff, 0, bufferlen);
            if (ret == -1) {
                stopping = true;
                return;
            }
            if (ret != bufferlen) {
                Arrays.fill(osc_buff[0], ret, bufferlen, 0f);
                if (nrofchannels != 1)
                    Arrays.fill(osc_buff[1], ret, bufferlen, 0f);
            }

        } catch (IOException e) {
            //e.printStackTrace();
        }

        SoftAudioBuffer left = buffer[SoftMainMixer.CHANNEL_LEFT];
        SoftAudioBuffer right = buffer[SoftMainMixer.CHANNEL_RIGHT];
        SoftAudioBuffer mono = buffer[SoftMainMixer.CHANNEL_MONO];
        SoftAudioBuffer eff1 = buffer[SoftMainMixer.CHANNEL_EFFECT1];
        SoftAudioBuffer eff2 = buffer[SoftMainMixer.CHANNEL_EFFECT2];

        SoftAudioBuffer dleft = buffer[SoftMainMixer.CHANNEL_DELAY_LEFT];
        SoftAudioBuffer dright = buffer[SoftMainMixer.CHANNEL_DELAY_RIGHT];
        SoftAudioBuffer dmono = buffer[SoftMainMixer.CHANNEL_DELAY_MONO];
        SoftAudioBuffer deff1 = buffer[SoftMainMixer.CHANNEL_DELAY_EFFECT1];
        SoftAudioBuffer deff2 = buffer[SoftMainMixer.CHANNEL_DELAY_EFFECT2];
        
        SoftAudioBuffer leftdry = buffer[SoftMainMixer.CHANNEL_LEFT_DRY];
        SoftAudioBuffer rightdry = buffer[SoftMainMixer.CHANNEL_RIGHT_DRY];

        if (osc_stream_nrofchannels == 1)
            rightdry = null;

        if (!Double.isInfinite(co_filter_freq[0])) {
            filter_left.processAudio(leftdry);
            if (rightdry != null)
                filter_right.processAudio(rightdry);
        }

        if (nrofchannels == 1) {
            out_mixer_left = (out_mixer_left + out_mixer_right) / 2;
            mixAudioStream(leftdry, left, dleft, last_out_mixer_left, out_mixer_left);
            if (rightdry != null)
                mixAudioStream(rightdry, left, dleft, last_out_mixer_left,
                        out_mixer_left);
        } else {
            if(rightdry == null && 
                    last_out_mixer_left == last_out_mixer_right &&
                    out_mixer_left == out_mixer_right)
            {
                mixAudioStream(leftdry, mono, dmono, last_out_mixer_left, out_mixer_left);
            }
            else
            {
                mixAudioStream(leftdry, left, dleft, last_out_mixer_left, out_mixer_left);
                if (rightdry != null)
                    mixAudioStream(rightdry, right, dright, last_out_mixer_right,
                        out_mixer_right);
                else
                    mixAudioStream(leftdry, right, dright, last_out_mixer_right,
                        out_mixer_right);
            }
        }

        if (rightdry == null) {
            mixAudioStream(leftdry, eff1, deff1, last_out_mixer_effect1,
                    out_mixer_effect1);
            mixAudioStream(leftdry, eff2, deff2, last_out_mixer_effect2,
                    out_mixer_effect2);
        } else {
            mixAudioStream(leftdry, eff1, deff1, last_out_mixer_effect1 * 0.5f,
                    out_mixer_effect1 * 0.5f);
            mixAudioStream(leftdry, eff2, deff2, last_out_mixer_effect2 * 0.5f,
                    out_mixer_effect2 * 0.5f);
            mixAudioStream(rightdry, eff1, deff1, last_out_mixer_effect1 * 0.5f,
                    out_mixer_effect1 * 0.5f);
            mixAudioStream(rightdry, eff2, deff2, last_out_mixer_effect2 * 0.5f,
                    out_mixer_effect2 * 0.5f);
        }

        last_out_mixer_left = out_mixer_left;
        last_out_mixer_right = out_mixer_right;
        last_out_mixer_effect1 = out_mixer_effect1;
        last_out_mixer_effect2 = out_mixer_effect2;

        if (out_mixer_end) {
            stopping = true;
        }

    }
}
