/* * Copyright 2010 Phil Burk, Mobileer Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.jsyn.unitgen; import com.jsyn.data.SegmentedEnvelope; import com.jsyn.engine.SynthesisEngine; import com.jsyn.ports.UnitInputPort; import com.jsyn.ports.UnitOutputPort; /** * Six stage envelope similar to an ADSR. DAHDSR is like an ADSR but with an additional Delay stage * before the attack, and a Hold stage after the Attack. If Delay and Hold are both set to zero then * it will act like an ADSR. The envelope is triggered when the input goes above THRESHOLD. The * envelope is released when the input goes below THRESHOLD. The THRESHOLD is currently 0.01 but may * change so it would be best to use an input signal that went from 0 to 1. Mathematically an * exponential Release will never reach 0.0. But when it reaches -96 dB the DAHDSR just sets its * output to 0.0 and stops. There is an example program in the ZIP archive called HearDAHDSR. It * drives a DAHDSR with a square wave. * * @author Phil Burk (C) 2010 Mobileer Inc * @see SegmentedEnvelope */ public class EnvelopeDAHDSR extends UnitGate implements UnitSource { private static final double MIN_DURATION = (1.0 / 100000.0); /** * Time in seconds for first stage of the envelope, before the attack. Typically zero. */ public UnitInputPort delay; /** * Time in seconds for the rising stage of the envelope to go from 0.0 to 1.0. The attack is a * linear ramp. */ public UnitInputPort attack; /** Time in seconds for the plateau between the attack and decay stages. */ public UnitInputPort hold; /** * Time in seconds for the falling stage to go from 0 dB to -90 dB. The decay stage will stop at * the sustain level. But we calculate the time to fall to -90 dB so that the decay * rate will be unaffected by the sustain level. */ public UnitInputPort decay; /** * Level for the sustain stage. The envelope will hold here until the input goes to zero or * less. This should be set between 0.0 and 1.0. */ public UnitInputPort sustain; /** * Time in seconds to go from 0 dB to -90 dB. This stage is triggered when the input goes to * zero or less. The release stage will start from the sustain level. But we calculate the time * to fall from full amplitude so that the release rate will be unaffected by the * sustain level. */ public UnitInputPort release; public UnitInputPort amplitude; enum State { IDLE, DELAYING, ATTACKING, HOLDING, DECAYING, SUSTAINING, RELEASING } private State state = State.IDLE; private double countdown; private double scaler = 1.0; private double level; private double increment; public EnvelopeDAHDSR() { super(); addPort(delay = new UnitInputPort("Delay", 0.0)); delay.setup(0.0, 0.0, 2.0); addPort(attack = new UnitInputPort("Attack", 0.1)); attack.setup(0.01, 0.1, 8.0); addPort(hold = new UnitInputPort("Hold", 0.0)); hold.setup(0.0, 0.0, 2.0); addPort(decay = new UnitInputPort("Decay", 0.2)); decay.setup(0.01, 0.2, 8.0); addPort(sustain = new UnitInputPort("Sustain", 0.5)); sustain.setup(0.0, 0.5, 1.0); addPort(release = new UnitInputPort("Release", 0.3)); release.setup(0.01, 0.3, 8.0); addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); } @Override public void generate(int start, int limit) { double[] sustains = sustain.getValues(); double[] amplitudes = amplitude.getValues(); double[] outputs = output.getValues(); for (int i = start; i < limit;) { boolean triggered = input.checkGate(i); switch (state) { case IDLE: for (; i < limit; i++) { outputs[i] = level * amplitudes[i]; if (triggered) { startDelay(i); break; } } break; case DELAYING: for (; i < limit; i++) { outputs[i] = level * amplitudes[i]; if (input.isOff()) { startRelease(i); break; } else { countdown -= 1; if (countdown <= 0) { startAttack(i); break; } } } break; case ATTACKING: for (; i < limit; i++) { // Increment first so we can render fast attacks. level += increment; if (level >= 1.0) { level = 1.0; outputs[i] = level * amplitudes[i]; startHold(i); break; } else { outputs[i] = level * amplitudes[i]; if (input.isOff()) { startRelease(i); break; } } } break; case HOLDING: for (; i < limit; i++) { outputs[i] = amplitudes[i]; // level is 1.0 countdown -= 1; if (countdown <= 0) { startDecay(i); break; } else if (input.isOff()) { startRelease(i); break; } } break; case DECAYING: for (; i < limit; i++) { outputs[i] = level * amplitudes[i]; level *= scaler; // exponential decay if (triggered) { startDelay(i); break; } else if (level < sustains[i]) { level = sustains[i]; startSustain(i); break; } else if (level < SynthesisEngine.DB96) { input.checkAutoDisable(); startIdle(); break; } else if (input.isOff()) { startRelease(i); break; } } break; case SUSTAINING: for (; i < limit; i++) { level = sustains[i]; outputs[i] = level * amplitudes[i]; if (triggered) { startDelay(i); break; } else if (input.isOff()) { startRelease(i); break; } } break; case RELEASING: for (; i < limit; i++) { outputs[i] = level * amplitudes[i]; level *= scaler; // exponential decay if (triggered) { startDelay(i); break; } else if (level < SynthesisEngine.DB96) { input.checkAutoDisable(); startIdle(); break; } } break; } } } private void startIdle() { state = State.IDLE; level = 0.0; } private void startDelay(int i) { double[] delays = delay.getValues(); if (delays[i] <= 0.0) { startAttack(i); } else { countdown = (int) (delays[i] * getFrameRate()); state = State.DELAYING; } } private void startAttack(int i) { double[] attacks = attack.getValues(); double duration = attacks[i]; if (duration < MIN_DURATION) { level = 1.0; startHold(i); } else { increment = getFramePeriod() / duration; state = State.ATTACKING; } } private void startHold(int i) { double[] holds = hold.getValues(); if (holds[i] <= 0.0) { startDecay(i); } else { countdown = (int) (holds[i] * getFrameRate()); state = State.HOLDING; } } private void startDecay(int i) { double[] decays = decay.getValues(); double duration = decays[i]; if (duration < MIN_DURATION) { startSustain(i); } else { scaler = getSynthesisEngine().convertTimeToExponentialScaler(duration); state = State.DECAYING; } } private void startSustain(int i) { state = State.SUSTAINING; } private void startRelease(int i) { double[] releases = release.getValues(); double duration = releases[i]; if (duration < MIN_DURATION) { duration = MIN_DURATION; } scaler = getSynthesisEngine().convertTimeToExponentialScaler(duration); state = State.RELEASING; } public void export(Circuit circuit, String prefix) { circuit.addPort(attack, prefix + attack.getName()); circuit.addPort(decay, prefix + decay.getName()); circuit.addPort(sustain, prefix + sustain.getName()); circuit.addPort(release, prefix + release.getName()); } @Override public UnitOutputPort getOutput() { return output; } }