/*
 * Copyright (c) 2007, 2013, Oracle and/or its affiliates. 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.sun.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
/** * Software synthesizer voice class. * * @author Karl Helgason */
public final 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; ModelChannelMixer channelmixer = null; double tunedKey = 0; SoftTuning tuning = null; SoftChannel stealer_channel = null; ModelConnectionBlock[] stealer_extendedConnectionBlocks = null; SoftPerformer stealer_performer = null; ModelChannelMixer stealer_channelmixer = null; int stealer_voiceID = -1; int stealer_noteNumber = 0; int stealer_velocity = 0; boolean stealer_releaseTriggered = false; int voiceID = -1; boolean sustain = false; boolean sostenuto = false; boolean portamento = false; private final SoftFilter filter_left; private final SoftFilter filter_right; private final SoftProcess eg = new SoftEnvelopeGenerator(); private final SoftProcess lfo = new SoftLowFrequencyOscillator(); Map<String, SoftControl> objects = new HashMap<String, SoftControl>(); SoftSynthesizer synthesizer; SoftInstrument instrument; SoftPerformer performer; SoftChannel softchannel = null; 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; 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; double[] co_noteon_keynumber = new double[1]; double[] co_noteon_velocity = new double[1]; double[] co_noteon_on = new double[1]; private final 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 final double[] co_mixer_active = new double[1]; private final double[] co_mixer_gain = new double[1]; private final double[] co_mixer_pan = new double[1]; private final double[] co_mixer_balance = new double[1]; private final double[] co_mixer_reverb = new double[1]; private final double[] co_mixer_chorus = new double[1]; private final 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 final double[] co_osc_pitch = new double[1]; private final 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 final double[] co_filter_freq = new double[1]; private final double[] co_filter_type = new double[1]; private final double[] co_filter_q = new double[1]; private final 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; } }; SoftResamplerStreamer resampler; private final 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; } 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]); } } void setNote(int noteNumber) { note = noteNumber; tunedKey = tuning.getTuning(noteNumber) / 100.0; } 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); } 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]); } 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]); } 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]); } 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]); } 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]); } 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]); } void setMute(boolean mute) { co_mixer_gain[0] -= lastMuteValue; lastMuteValue = mute ? -960 : 0; co_mixer_gain[0] += lastMuteValue; } void setSoloMute(boolean mute) { co_mixer_gain[0] -= lastSoloMuteValue; lastSoloMuteValue = mute ? -960 : 0; co_mixer_gain[0] += lastSoloMuteValue; } 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]); } void soundOff() { on = false; soundoff = true; } 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]); } 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]); } 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; } } 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; } } } } 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; } } }