Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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 }