Source code: jm/audio/synth/WaveTable.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 jm.audio.AOException;
27 import jm.audio.AudioObject;
28 import jm.audio.Instrument;
29 import jm.music.data.Note;
30
31 /**
32 * Wavetable lookup creates an efficient means for resampling data
33 * into any frequency. It is particularly useful for holding simple
34 * wave information such as sinewaves and is often used as an
35 * oscillator.<br>
36 * This WaveTable implementation can accept either one or two inputs. Two
37 * inputs expects that amplitude is the left input and frequency is the right
38 * input. One input allows the user to specify whether the input is for
39 * amplitude or frequency by setting the aoDestination variable((0)amplitude
40 * (1)frequency.<br>
41 * A WaveTable can use fixed variables for both amplitude and frequency. The
42 * default is to use a default amplitude of 1.0 and a frequency based upon the
43 * value of the build methods Note.pitch(). These fixed variables are
44 * amp(amplitude) and frq(frequency) and both have set methods. NOTE (please do
45 * not add more constructors to set amp and frq but leave them as set
46 * methods).<br>
47 * The frqRatio variable is used to produce a frequency which is a ratio
48 * against the current notesFrq (the build methods note.getPitch()) variable
49 * setting.<br>
50 * It is common to use WaveTables as oscillators and even more common for these
51 * oscillators to based on simple wave forms. Simple wave forms in jMusic can
52 * be retrieved using static method calls to the Oscillator class.<br>
53 *
54 * @author Andrew Sorensen
55 * @version 1.0,Sun Feb 25 18:42:52 2001
56 */
57 public class WaveTable extends AudioObject{
58 //----------------------------------------------
59 // Attributes
60 //----------------------------------------------
61 /** this contains the wavetable data as samples */
62 private float[] waveTable;
63 /** how many samples to we skip while passing through the wavetable */
64 private float si;
65 /** what is the phase of the wavetable to start at */
66 private float phase;
67 /** If we have one audio object input is at amp(0) or freq(1) ? */
68 private int aoDestination;
69 /** Value to use as a fixed amplitude for the waveTable.*/
70 private float amp = (float) 1.0;
71 /** Value to use as a fixed frequency for the waveTable.*/
72 private float frq = (float)-1.0;
73 /** Frequency ratio allows an incoming note's pitch to be adjusted to a
74 * fixed ratio amount*/
75 private float frqRatio = (float)1.0;
76 /** constant for use with aoDestination */
77 public static final int AMPLITUDE = 0;
78 /** constant for use with aoDestination */
79 public static final int FREQUENCY = 1;
80 /** constant for use with channels */
81 public static final int MONO = 1;
82 /** constant for use with channels */
83 public static final int STEREO = 2;
84 //----------------------------------------------
85 // Constructors
86 //----------------------------------------------
87 /**
88 * This constructor sets the WaveTable to act as
89 * a processor object taking in two inputs. Input
90 * one is defined as amplitude and input two is
91 * defined as frequency.
92 * @param ao AudioObject as input
93 * @param waveTable the lookup table data
94 * @exception AOException thrown when two many inputs are attached
95 */
96 public WaveTable(AudioObject[] ao, float[] waveTable)throws AOException{
97 super(ao, "[WaveTable]");
98 if(ao.length > 2) throw new AOException(this.name,1);
99 this.waveTable = waveTable;
100 }
101
102 /**
103 * This constructor sets the WaveTable to act as
104 * a processor object taking in one input. That
105 * input can be either amplitude(0) or frequency(1)
106 * and is defined by the aoDestination variable (int).
107 * @param ao the one input audio object
108 * @param wavetable the wave table data
109 * @param aoDestination Is this input amplitude(0) or frequency(1)
110 */
111 public WaveTable(AudioObject ao, float[] waveTable, int aoDestination){
112 super(ao, "[WaveTable]");
113 this.waveTable = waveTable;
114 this.aoDestination = aoDestination;
115 }
116
117 /**
118 * This constructor sets the WaveTable to act as
119 * a generator object taking in one input. That
120 * input can be either amplitude(0) or frequency(1)
121 * and is defined by the aoDestination variable (int).
122 * @param inst the parent instrument (usually "this")
123 * @param wavetable the wave table data
124 * @param aoDestination Is this input amplitude(0) or frequency(1)
125 * @param val is used to set a fixed frequency or amplitude based on the
126 * result of aoDestination (aoDestination=1 for example will set a fixed frequency)
127 */
128 public WaveTable(Instrument inst, int sampleRate, float[] waveTable,
129 int channels, int aoDestination, float val){
130 super(inst, sampleRate, "[WaveTable]");
131 this.waveTable = waveTable;
132 this.channels = channels;
133 this.aoDestination = aoDestination;
134 if(aoDestination == 1){
135 this.frq=val;
136 }else{
137 this.amp=val;
138 }
139 }
140
141 /**
142 * This constructor sets this wavetable up as a generator
143 * object meaning that it will pass sample information
144 * down the chain based on its wave table data.<br>
145 * Set WaveTable with some initial values including
146 * the sampling rate and the samples to use for this
147 * wave table
148 * @param inst the parent instrument (usually "this")
149 * @param sampleRate the sampling rate
150 * @param waveTable the wave table data
151 * @param channels the number of channels to use
152 */
153 public WaveTable(Instrument inst, int sampleRate, float[] waveTable,
154 int channels){
155 super(inst, sampleRate, "[WaveTable]");
156 this.waveTable = waveTable;
157 this.channels = channels;
158 }
159
160 //----------------------------------------------
161 // Methods
162 //----------------------------------------------
163 /**
164 * Moves through the WaveTable array (noramally forwards but sometimes
165 * backwards) by increments set by si (sample increment value). This nextWork
166 * method can take one or two inputs which are either amplitude, frequency
167 * or both (a single input can be assigned to either frequency or amplitude
168 * by assigning the aoDestination value to either (0)Amp or (1)Frq in the
169 * appropriate constructor. A WaveTable that takes two inputs expects the
170 * first input to be amplitude and the second input to be frequency.
171 * @param buffer The sample buffer.
172 */
173 public int work(float[] buffer)throws AOException{
174 //because wavetable contains mono sample data we need to pass the same
175 //sample information to as many channels as are present.
176 int buffneed=buffer.length/channels;
177 int ret=0; //the number of samples to return
178
179 if(inputs==2){ //Amp and Freq
180 float[] ampbuf = new float[buffneed];
181 int returned = this.previous[0].nextWork(ampbuf);
182 float[] freqbuf = new float[returned];
183 if(returned != this.previous[1].work(freqbuf)){
184 throw new AOException(this.name,0);
185 }
186 for(int i=0;ret<buffer.length;i++){
187 setSI((int)freqbuf[i]);
188 if(phase < 0){
189 phase = this.waveTable.length+phase;
190 } // amplitude values assumed to be between -1 and 1
191 float sample = waveTable[(int)phase]*(this.amp * ampbuf[i]);
192 this.phase += si;
193 if(phase >= this.waveTable.length){
194 phase -= this.waveTable.length;
195 }
196 for(int j=0;j<channels;j++){
197 buffer[ret++] = sample;
198 }
199 }
200 }else if(inputs==1 && aoDestination==0){ //Amp only
201 float[] ampbuf = new float[buffneed];
202 int returned = this.previous[0].nextWork(ampbuf);
203 for(int i=0;ret<buffer.length;i++){
204 float sample = waveTable[(int)phase]*(this.amp * ampbuf[i]);
205 this.phase += si;
206 if(phase >= this.waveTable.length){
207 phase -= this.waveTable.length;
208 }
209 for(int j=0;j<channels;j++){
210 buffer[ret++] = sample;
211 }
212 }
213 }else if(inputs==1 && aoDestination==1){ //Frq only
214 float[] frqbuf = new float[buffneed];
215 int returned = this.previous[0].work(frqbuf);
216 for(int i=0;i<buffneed;i++){
217 setSI((int)frqbuf[i]);
218 if(phase < 0){
219 phase = this.waveTable.length+phase;
220 }
221 float sample = waveTable[(int)phase]*this.amp;
222 this.phase += si;
223 if(phase >= this.waveTable.length){
224 phase -= this.waveTable.length;
225 }
226 for(int j=0;j<channels;j++){
227 buffer[ret++] = sample;
228 }
229 }
230 }else{ //no inputs
231 for(;ret<buffer.length;){
232 float sample = waveTable[(int)phase]*this.amp;
233 this.phase += si;
234 if(phase >= this.waveTable.length){
235 phase -= this.waveTable.length;
236 }
237 for(int j=0;j<channels;j++){
238 try{
239 buffer[ret++] = sample;
240 }catch(ArrayIndexOutOfBoundsException e){
241 //This can happen if a non mono signal chain wants
242 //to access the wavetable as a mono signal
243 //Ignore and skip over
244 //
245 //We do need to remove one back off ret though to return
246 //the right number of samples to return
247 ret--;
248 }
249 }
250 }
251 }
252 return ret;
253 }
254
255 /**
256 */
257 public void build(){
258 this.numOfSamples = numOfSamples;
259 float notesFrq = (float)FRQ[currentNote.getPitch()]*frqRatio;
260 if(this.frq<(float)0.0){
261 this.setSI(notesFrq);
262 }else{
263 this.setSI(this.frq);
264 }
265 }
266
267 /**
268 * Set the fixed amp of this wavetable
269 * @param amp Fixed value amplitude
270 */
271 public void setAmp(float amp){
272 this.amp = amp;
273 }
274
275 /**
276 * Set the fixed Frequecy of this wavetable
277 * @param frq Fixed value frequency
278 */
279 public void setFrq(float frq){
280 this.frq = frq;
281 }
282
283 /**
284 * Sets the frequency ratio to alter a notes pitch by
285 * @param frqRatio Fixed ratio value to change frequency by
286 */
287 public void setFrqRatio(float frqRatio){
288 this.frqRatio = frqRatio;
289 }
290
291 //------------------------------------------
292 // Protected Methods
293 //------------------------------------------
294 /**
295 * Returns the sampling increment which is used
296 * to nextWork out how many samples in the wavetable
297 * skip on each pass.
298 * @param frequency the frequency used to find si
299 */
300 protected void setSI(float frequency){
301 this.si = (int)((frequency / (float)this.sampleRate) *
302 (float)this.waveTable.length);
303 }
304 }