Source code: jm/audio/synth/ADSR.java
1 /*
2
3 <This Java Class is part of the jMusic API version 1.4, February 2003.>
4
5 Copyright (C) 2000 Andrew Sorensen & Andrew Brown
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or any
10 later version.
11
12 This program is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20
21 */
22
23 package jm.audio.synth;
24
25 import java.io.IOException;
26 import java.io.EOFException;
27 import jm.music.data.Note;
28 import jm.audio.AudioObject;
29 import jm.audio.Instrument;
30 import jm.audio.AOException;
31 import jm.JMC;
32
33 /**
34 * Envelope which can be set with an arbitrary number of points
35 * the envelope is constructed with linear lines between each
36 * specifed point.
37 * The points excepted by this class are positioned as a percent
38 * of the total length of the sound data being nextWorked on and
39 * the envelope itself is constructed in the build() method.<br>
40 * Envelope objects can be used as either Generator Audio Objects
41 * (ie the first in the chain) or as processor Audio Objects (ie
42 * in the centre or the chain) depending on the constructor used.<br>
43 * As a generator the Envelope can be used to pass each envelope position
44 * onto another Audio Object as input data.<br>
45 * As a processor the Envelope object is used to change the Amplitude
46 * of incoming samples to reflect the shape of the envelope.<br>
47 * NOTE: The important distinction here is that when being used as
48 * a processor object the envelopes only possible function is to
49 * alter amplitude. But when used as a generator the Envelope can
50 * be used to send data to any AudioObjects input. (the volume of a
51 * volume object for example for doing crescendos on each note)
52 * @author Andrew Sorensen, Andrew Brown, Joel Joslin
53 * @version 1.0,Sun Feb 25 18:42:47 2001
54 */
55 public class ADSR extends AudioObject implements JMC{
56 //----------------------------------------------
57 // Attributes
58 //----------------------------------------------
59 /** points on the graph */
60 private EnvPoint[] graphPoints;
61 /** a calculated graph with all points filled in */
62 private float[] graphShape;
63 /** is the a primary object? */
64 private boolean primary;
65 // ADSR values
66 private int attack, decay, release;
67 private double sustain;
68 // the number of samples that takes into account the release time
69 private int totalSamples;
70 // keep track of the number of sampled processed
71 private int sampleCounter = 0;
72 // keep track of the location along the envelope array
73 private int position = 0;
74 // keep the number of samples for attack, decay and release
75 private double attackSamps, decaySamps, releaseSamps;
76 // the previous rhythmValue
77 private double prevRV = 0.0;
78
79 //----------------------------------------------
80 // Constructors
81 //----------------------------------------------
82
83 /**
84 * An ADSR object can be used as a generator. This
85 * is a method to call to do this.
86 * @param The instrumt to use - Usually put 'this' here.
87 * @param sampleRate the sampleRate for this AudioObject.
88 * @param channels The number of tracks for this file (1 = mono , 2 = stereo)
89 * @param attack The number of milliseconds for the attack portion of the envelope
90 * @param decay The number of milliseconds for the decay portion of the envelope
91 * @param sustain The percentage lavel for the sustain portion of the envelope (0.0 - 1.0)
92 * @param release The number of milliseconds for the release portion of the envelope
93 */
94 public ADSR(Instrument inst, int sampleRate, int channels,
95 int attack, int decay, double sustain, int release){
96 super(inst, sampleRate, "[ADSR]");
97 this.channels = channels;
98 this.attack = attack;
99 this.decay = decay;
100 this.sustain = sustain;
101 this.release = release;
102 this.primary = true;
103 this.finished = false;
104 calcSamps();
105 }
106
107 /**
108 * This constructor takes a single AudioObject as input
109 * and in this form becomes a processor object which
110 * changes the amplitude of incoming samples based on
111 * the envelope.
112 * @param The instrumt to use - Usually put 'this' here.
113 * @param sampleRate the sampleRate for this AudioObject.
114 * @param channels The number of tracks for this file (1 = mono , 2 = stereo)
115 * @param attack The number of milliseconds for the attack portion of the envelope
116 * @param decay The number of milliseconds for the decay portion of the envelope
117 * @param sustain The percentage lavel for the sustain portion of the envelope (0.0 - 1.0)
118 * @param release The number of milliseconds for the release portion of the envelope
119 */
120 public ADSR(AudioObject ao, int attack, int decay, double sustain, int release){
121 super(ao, "[ADSR]");
122 // ADSR -> points
123 this.attack = attack;
124 this.decay = decay;
125 this.sustain = sustain;
126 this.release = release;
127 this.primary = false;
128 this.finished = false;
129 }
130
131
132
133 //----------------------------------------------
134 // Protected Methods
135 //----------------------------------------------
136 /**
137 * Alter the samples value so that it meets the
138 * shape of the graph, then send the new sample
139 * onto the next audio object.<br>
140 * NOTE: if the nextWork method receives a value
141 * of 1.0 the graphs current poitional value
142 * will be passed on unchanged.
143 * @param input input data
144 */
145 public int work(float[] buffer)throws AOException{
146 // claculate the number of samples processed
147 if (sampleCounter > totalSamples * channels) {
148 this.finished = true;
149 //this.inst.finishedNewData = true;
150 }/*else{
151 this.inst.finishedNewData = false;
152 }*/
153 sampleCounter += buffer.length;
154
155 // pass on data unchanged after the end of the envelope
156 //
157 //if(this.finished==true && this.inst.iterations<=0)return buffer.length;
158 //
159 // process data
160 if (primary) {
161 int returned = buffer.length;
162 int chancount=1;
163 for(int i=0;i<returned;i++){
164 for(int j=0; j<channels; j++) {
165 try{
166 buffer[i+j] = graphShape[this.position];
167 //System.out.println("buffer = " + buffer[i]);
168 }catch(ArrayIndexOutOfBoundsException aob){
169 buffer[i+j] = 0.0f;
170 }
171 }
172 this.position++;
173 }
174 return returned;
175 } else {
176 //System.out.println("in NOT primary");
177 int returned = this.previous[0].nextWork(buffer);
178 int chancount=1;
179 for(int i=0;i<buffer.length;i+=channels){
180 for(int j=0; j<channels; j++) {
181 try{
182 buffer[i+j] = buffer[i+j] * graphShape[this.position];
183 }catch(ArrayIndexOutOfBoundsException aob){
184 buffer[i+j] = 0.0f;
185 }
186 }
187 this.position++;
188 }
189 return returned;
190 }
191 }
192
193
194 //----------------------------------------------
195 // Private Methods
196 //----------------------------------------------
197
198 private void calcSamps() {
199 this.attackSamps = getSamps(this.attack);
200 this.decaySamps = getSamps(this.decay);
201 this.releaseSamps = getSamps(this.release);
202 }
203
204 private double getSamps(int milli){
205 return ((double)milli/1000.0) * (double)sampleRate;
206 }
207
208 /**
209 * Calculates the sampleData for this Envelope
210 */
211 public void build(){
212 //this.inst.finishedNewData = false;
213 sampleCounter = 0;
214 this.position = 0;
215 if(numOfSamples == 0){
216 return;
217 }
218 // avoid recalc?
219 if (currentNote.getRhythmValue() == prevRV) return;
220
221 calcSamps();
222 // extend note to account for release
223 // note; numOfSamples is in mono
224 totalSamples = this.numOfSamples + (int)releaseSamps;
225
226 graphShape = new float[totalSamples];
227 // Attack
228 int maxAttackCount = Math.min((int)attackSamps, this.numOfSamples);
229 double inc = 1.0 / (double)maxAttackCount;
230 for(int i=0; i< maxAttackCount; i++) {
231 graphShape[i] = (float)(inc * i);
232 }
233 // decay
234 int maxDecayCount = maxAttackCount;
235 if (sustain < 1.0) {
236 maxDecayCount = Math.min((int)attackSamps+(int)decaySamps, this.numOfSamples);
237 double diff = (1.0 - sustain)/(double)maxDecayCount;
238 for(int i=maxAttackCount; i< maxDecayCount; i++) {
239 graphShape[i] = (float)(1.0 - diff * (i - maxAttackCount));
240 }
241 }
242 // sustain
243 for(int i=maxDecayCount; i < this.numOfSamples; i++) {
244 graphShape[i] = (float)(sustain);
245 }
246 // release
247 double startVal = (double)graphShape[this.numOfSamples - 1];
248 inc = startVal/releaseSamps;
249 for(int i=this.numOfSamples; i < totalSamples; i++) {
250 graphShape[i] = (float)(startVal - inc * (i - this.numOfSamples));
251 }
252 this.finished = false;
253 }
254 }