diff options
author | Björn Stenberg <bjorn@haxx.se> | 2007-01-08 23:53:00 +0000 |
---|---|---|
committer | Björn Stenberg <bjorn@haxx.se> | 2007-01-08 23:53:00 +0000 |
commit | 7039a05147b8bbfc829babea1c65bd436450b505 (patch) | |
tree | 4ba555eb84ed97b72b0575034d5b0530a393713e /songdbj/org/tritonus/share/sampled/FloatSampleBuffer.java | |
parent | 6d4c19707ef95942e323cbdc89fbbfdbe45e7cc5 (diff) | |
download | rockbox-7039a05147b8bbfc829babea1c65bd436450b505.tar.gz rockbox-7039a05147b8bbfc829babea1c65bd436450b505.zip |
Splitting out songdbj
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@11953 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'songdbj/org/tritonus/share/sampled/FloatSampleBuffer.java')
-rw-r--r-- | songdbj/org/tritonus/share/sampled/FloatSampleBuffer.java | 734 |
1 files changed, 0 insertions, 734 deletions
diff --git a/songdbj/org/tritonus/share/sampled/FloatSampleBuffer.java b/songdbj/org/tritonus/share/sampled/FloatSampleBuffer.java deleted file mode 100644 index d1fe534613..0000000000 --- a/songdbj/org/tritonus/share/sampled/FloatSampleBuffer.java +++ /dev/null | |||
@@ -1,734 +0,0 @@ | |||
1 | /* | ||
2 | * FloatSampleBuffer.java | ||
3 | * | ||
4 | * This file is part of Tritonus: http://www.tritonus.org/ | ||
5 | */ | ||
6 | |||
7 | /* | ||
8 | * Copyright (c) 2000,2004 by Florian Bomers <http://www.bomers.de> | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU Library General Public License as published | ||
12 | * by the Free Software Foundation; either version 2 of the License, or | ||
13 | * (at your option) any later version. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, | ||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | * GNU Library General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU Library General Public | ||
21 | * License along with this program; if not, write to the Free Software | ||
22 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
23 | */ | ||
24 | |||
25 | /* | ||
26 | |<--- this code is formatted to fit into 80 columns --->| | ||
27 | */ | ||
28 | |||
29 | package org.tritonus.share.sampled; | ||
30 | |||
31 | import java.util.ArrayList; | ||
32 | import java.util.Iterator; | ||
33 | import java.util.Random; | ||
34 | |||
35 | import javax.sound.sampled.AudioSystem; | ||
36 | import javax.sound.sampled.AudioFormat; | ||
37 | import javax.sound.sampled.AudioFileFormat; | ||
38 | import javax.sound.sampled.AudioInputStream; | ||
39 | import javax.sound.sampled.spi.AudioFileWriter; | ||
40 | |||
41 | import org.tritonus.share.TDebug; | ||
42 | |||
43 | /** | ||
44 | * A class for small buffers of samples in linear, 32-bit | ||
45 | * floating point format. | ||
46 | * <p> | ||
47 | * It is supposed to be a replacement of the byte[] stream | ||
48 | * architecture of JavaSound, especially for chains of | ||
49 | * AudioInputStreams. Ideally, all involved AudioInputStreams | ||
50 | * handle reading into a FloatSampleBuffer. | ||
51 | * <p> | ||
52 | * Specifications: | ||
53 | * <ol> | ||
54 | * <li>Channels are separated, i.e. for stereo there are 2 float arrays | ||
55 | * with the samples for the left and right channel | ||
56 | * <li>All data is handled in samples, where one sample means | ||
57 | * one float value in each channel | ||
58 | * <li>All samples are normalized to the interval [-1.0...1.0] | ||
59 | * </ol> | ||
60 | * <p> | ||
61 | * When a cascade of AudioInputStreams use FloatSampleBuffer for | ||
62 | * processing, they may implement the interface FloatSampleInput. | ||
63 | * This signals that this stream may provide float buffers | ||
64 | * for reading. The data is <i>not</i> converted back to bytes, | ||
65 | * but stays in a single buffer that is passed from stream to stream. | ||
66 | * For that serves the read(FloatSampleBuffer) method, which is | ||
67 | * then used as replacement for the byte-based read functions of | ||
68 | * AudioInputStream.<br> | ||
69 | * However, backwards compatibility must always be retained, so | ||
70 | * even when an AudioInputStream implements FloatSampleInput, | ||
71 | * it must work the same way when any of the byte-based read methods | ||
72 | * is called.<br> | ||
73 | * As an example, consider the following set-up:<br> | ||
74 | * <ul> | ||
75 | * <li>auAIS is an AudioInputStream (AIS) that reads from an AU file | ||
76 | * in 8bit pcm at 8000Hz. It does not implement FloatSampleInput. | ||
77 | * <li>pcmAIS1 is an AIS that reads from auAIS and converts the data | ||
78 | * to PCM 16bit. This stream implements FloatSampleInput, i.e. it | ||
79 | * can generate float audio data from the ulaw samples. | ||
80 | * <li>pcmAIS2 reads from pcmAIS1 and adds a reverb. | ||
81 | * It operates entirely on floating point samples. | ||
82 | * <li>The method that reads from pcmAIS2 (i.e. AudioSystem.write) does | ||
83 | * not handle floating point samples. | ||
84 | * </ul> | ||
85 | * So, what happens when a block of samples is read from pcmAIS2 ? | ||
86 | * <ol> | ||
87 | * <li>the read(byte[]) method of pcmAIS2 is called | ||
88 | * <li>pcmAIS2 always operates on floating point samples, so | ||
89 | * it uses an own instance of FloatSampleBuffer and initializes | ||
90 | * it with the number of samples requested in the read(byte[]) | ||
91 | * method. | ||
92 | * <li>It queries pcmAIS1 for the FloatSampleInput interface. As it | ||
93 | * implements it, pcmAIS2 calls the read(FloatSampleBuffer) method | ||
94 | * of pcmAIS1. | ||
95 | * <li>pcmAIS1 notes that its underlying stream does not support floats, | ||
96 | * so it instantiates a byte buffer which can hold the number of | ||
97 | * samples of the FloatSampleBuffer passed to it. It calls the | ||
98 | * read(byte[]) method of auAIS. | ||
99 | * <li>auAIS fills the buffer with the bytes. | ||
100 | * <li>pcmAIS1 calls the <code>initFromByteArray</code> method of | ||
101 | * the float buffer to initialize it with the 8 bit data. | ||
102 | * <li>Then pcmAIS1 processes the data: as the float buffer is | ||
103 | * normalized, it does nothing with the buffer - and returns | ||
104 | * control to pcmAIS2. The SampleSizeInBits field of the | ||
105 | * AudioFormat of pcmAIS1 defines that it should be 16 bits. | ||
106 | * <li>pcmAIS2 receives the filled buffer from pcmAIS1 and does | ||
107 | * its processing on the buffer - it adds the reverb. | ||
108 | * <li>As pcmAIS2's read(byte[]) method had been called, pcmAIS2 | ||
109 | * calls the <code>convertToByteArray</code> method of | ||
110 | * the float buffer to fill the byte buffer with the | ||
111 | * resulting samples. | ||
112 | * </ol> | ||
113 | * <p> | ||
114 | * To summarize, here are some advantages when using a FloatSampleBuffer | ||
115 | * for streaming: | ||
116 | * <ul> | ||
117 | * <li>no conversions from/to bytes need to be done during processing | ||
118 | * <li>the sample size in bits is irrelevant - normalized range | ||
119 | * <li>higher quality for processing | ||
120 | * <li>separated channels (easy process/remove/add channels) | ||
121 | * <li>potentially less copying of audio data, as processing | ||
122 | * the float samples is generally done in-place. The same | ||
123 | * instance of a FloatSampleBuffer may be used from the original data source | ||
124 | * to the final data sink. | ||
125 | * </ul> | ||
126 | * <p> | ||
127 | * Simple benchmarks showed that the processing requirements | ||
128 | * for the conversion to and from float is about the same as | ||
129 | * when converting it to shorts or ints without dithering, | ||
130 | * and significantly higher with dithering. An own implementation | ||
131 | * of a random number generator may improve this. | ||
132 | * <p> | ||
133 | * "Lazy" deletion of samples and channels:<br> | ||
134 | * <ul> | ||
135 | * <li>When the sample count is reduced, the arrays are not resized, but | ||
136 | * only the member variable <code>sampleCount</code> is reduced. A subsequent | ||
137 | * increase of the sample count (which will occur frequently), will check | ||
138 | * that and eventually reuse the existing array. | ||
139 | * <li>When a channel is deleted, it is not removed from memory but only | ||
140 | * hidden. Subsequent insertions of a channel will check whether a hidden channel | ||
141 | * can be reused. | ||
142 | * </ul> | ||
143 | * The lazy mechanism can save many array instantiation (and copy-) operations | ||
144 | * for the sake of performance. All relevant methods exist in a second | ||
145 | * version which allows explicitely to disable lazy deletion. | ||
146 | * <p> | ||
147 | * Use the <code>reset</code> functions to clear the memory and remove | ||
148 | * hidden samples and channels. | ||
149 | * <p> | ||
150 | * Note that the lazy mechanism implies that the arrays returned | ||
151 | * from <code>getChannel(int)</code> may have a greater size | ||
152 | * than getSampleCount(). Consequently, be sure to never rely on the | ||
153 | * length field of the sample arrays. | ||
154 | * <p> | ||
155 | * As an example, consider a chain of converters that all act | ||
156 | * on the same instance of FloatSampleBuffer. Some converters | ||
157 | * may decrease the sample count (e.g. sample rate converter) and | ||
158 | * delete channels (e.g. PCM2PCM converter). So, processing of one | ||
159 | * block will decrease both. For the next block, all starts | ||
160 | * from the beginning. With the lazy mechanism, all float arrays | ||
161 | * are only created once for processing all blocks.<br> | ||
162 | * Having lazy disabled would require for each chunk that is processed | ||
163 | * <ol> | ||
164 | * <li>new instantiation of all channel arrays | ||
165 | * at the converter chain beginning as they have been | ||
166 | * either deleted or decreased in size during processing of the | ||
167 | * previous chunk, and | ||
168 | * <li>re-instantiation of all channel arrays for | ||
169 | * the reduction of the sample count. | ||
170 | * </ol> | ||
171 | * <p> | ||
172 | * Dithering:<br> | ||
173 | * By default, this class uses dithering for reduction | ||
174 | * of sample width (e.g. original data was 16bit, target | ||
175 | * data is 8bit). As dithering may be needed in other cases | ||
176 | * (especially when the float samples are processed using DSP | ||
177 | * algorithms), or it is preferred to switch it off, | ||
178 | * dithering can be explicitely switched on or off with | ||
179 | * the method setDitherMode(int).<br> | ||
180 | * For a discussion about dithering, see | ||
181 | * <a href="http://www.iqsoft.com/IQSMagazine/BobsSoapbox/Dithering.htm"> | ||
182 | * here</a> and | ||
183 | * <a href="http://www.iqsoft.com/IQSMagazine/BobsSoapbox/Dithering2.htm"> | ||
184 | * here</a>. | ||
185 | * | ||
186 | * @author Florian Bomers | ||
187 | */ | ||
188 | |||
189 | public class FloatSampleBuffer { | ||
190 | |||
191 | /** Whether the functions without lazy parameter are lazy or not. */ | ||
192 | private static final boolean LAZY_DEFAULT=true; | ||
193 | |||
194 | private ArrayList<float[]> channels = new ArrayList<float[]>(); // contains for each channel a float array | ||
195 | private int sampleCount=0; | ||
196 | private int channelCount=0; | ||
197 | private float sampleRate=0; | ||
198 | private int originalFormatType=0; | ||
199 | |||
200 | /** Constant for setDitherMode: dithering will be enabled if sample size is decreased */ | ||
201 | public static final int DITHER_MODE_AUTOMATIC=0; | ||
202 | /** Constant for setDitherMode: dithering will be done */ | ||
203 | public static final int DITHER_MODE_ON=1; | ||
204 | /** Constant for setDitherMode: dithering will not be done */ | ||
205 | public static final int DITHER_MODE_OFF=2; | ||
206 | |||
207 | private float ditherBits = FloatSampleTools.DEFAULT_DITHER_BITS; | ||
208 | |||
209 | // e.g. the sample rate converter may want to force dithering | ||
210 | private int ditherMode = DITHER_MODE_AUTOMATIC; | ||
211 | |||
212 | //////////////////////////////// initialization ///////////////////////////////// | ||
213 | |||
214 | /** | ||
215 | * Create an instance with initially no channels. | ||
216 | */ | ||
217 | public FloatSampleBuffer() { | ||
218 | this(0,0,1); | ||
219 | } | ||
220 | |||
221 | /** | ||
222 | * Create an empty FloatSampleBuffer with the specified number of channels, | ||
223 | * samples, and the specified sample rate. | ||
224 | */ | ||
225 | public FloatSampleBuffer(int channelCount, int sampleCount, float sampleRate) { | ||
226 | init(channelCount, sampleCount, sampleRate, LAZY_DEFAULT); | ||
227 | } | ||
228 | |||
229 | /** | ||
230 | * Creates a new instance of FloatSampleBuffer and initializes | ||
231 | * it with audio data given in the interleaved byte array <code>buffer</code>. | ||
232 | */ | ||
233 | public FloatSampleBuffer(byte[] buffer, int offset, int byteCount, | ||
234 | AudioFormat format) { | ||
235 | this(format.getChannels(), | ||
236 | byteCount/(format.getSampleSizeInBits()/8*format.getChannels()), | ||
237 | format.getSampleRate()); | ||
238 | initFromByteArray(buffer, offset, byteCount, format); | ||
239 | } | ||
240 | |||
241 | protected void init(int channelCount, int sampleCount, float sampleRate) { | ||
242 | init(channelCount, sampleCount, sampleRate, LAZY_DEFAULT); | ||
243 | } | ||
244 | |||
245 | protected void init(int channelCount, int sampleCount, float sampleRate, boolean lazy) { | ||
246 | if (channelCount<0 || sampleCount<0) { | ||
247 | throw new IllegalArgumentException( | ||
248 | "invalid parameters in initialization of FloatSampleBuffer."); | ||
249 | } | ||
250 | setSampleRate(sampleRate); | ||
251 | if (getSampleCount()!=sampleCount || getChannelCount()!=channelCount) { | ||
252 | createChannels(channelCount, sampleCount, lazy); | ||
253 | } | ||
254 | } | ||
255 | |||
256 | private void createChannels(int channelCount, int sampleCount, boolean lazy) { | ||
257 | this.sampleCount=sampleCount; | ||
258 | // lazy delete of all channels. Intentionally lazy ! | ||
259 | this.channelCount=0; | ||
260 | for (int ch=0; ch<channelCount; ch++) { | ||
261 | insertChannel(ch, false, lazy); | ||
262 | } | ||
263 | if (!lazy) { | ||
264 | // remove hidden channels | ||
265 | while (channels.size()>channelCount) { | ||
266 | channels.remove(channels.size()-1); | ||
267 | } | ||
268 | } | ||
269 | } | ||
270 | |||
271 | |||
272 | /** | ||
273 | * Resets this buffer with the audio data specified | ||
274 | * in the arguments. This FloatSampleBuffer's sample count | ||
275 | * will be set to <code>byteCount / format.getFrameSize()</code>. | ||
276 | * If LAZY_DEFAULT is true, it will use lazy deletion. | ||
277 | * | ||
278 | * @throws IllegalArgumentException | ||
279 | */ | ||
280 | public void initFromByteArray(byte[] buffer, int offset, int byteCount, | ||
281 | AudioFormat format) { | ||
282 | initFromByteArray(buffer, offset, byteCount, format, LAZY_DEFAULT); | ||
283 | } | ||
284 | |||
285 | |||
286 | /** | ||
287 | * Resets this buffer with the audio data specified | ||
288 | * in the arguments. This FloatSampleBuffer's sample count | ||
289 | * will be set to <code>byteCount / format.getFrameSize()</code>. | ||
290 | * | ||
291 | * @param lazy if true, then existing channels will be tried to be re-used | ||
292 | * to minimize garbage collection. | ||
293 | * @throws IllegalArgumentException | ||
294 | */ | ||
295 | public void initFromByteArray(byte[] buffer, int offset, int byteCount, | ||
296 | AudioFormat format, boolean lazy) { | ||
297 | if (offset+byteCount>buffer.length) { | ||
298 | throw new IllegalArgumentException | ||
299 | ("FloatSampleBuffer.initFromByteArray: buffer too small."); | ||
300 | } | ||
301 | |||
302 | int thisSampleCount = byteCount/format.getFrameSize(); | ||
303 | init(format.getChannels(), thisSampleCount, format.getSampleRate(), lazy); | ||
304 | |||
305 | // save format for automatic dithering mode | ||
306 | originalFormatType = FloatSampleTools.getFormatType(format); | ||
307 | |||
308 | FloatSampleTools.byte2float(buffer, offset, | ||
309 | channels, 0, sampleCount, format); | ||
310 | } | ||
311 | |||
312 | /** | ||
313 | * Resets this sample buffer with the data in <code>source</code>. | ||
314 | */ | ||
315 | public void initFromFloatSampleBuffer(FloatSampleBuffer source) { | ||
316 | init(source.getChannelCount(), source.getSampleCount(), source.getSampleRate()); | ||
317 | for (int ch=0; ch<getChannelCount(); ch++) { | ||
318 | System.arraycopy(source.getChannel(ch), 0, getChannel(ch), 0, sampleCount); | ||
319 | } | ||
320 | } | ||
321 | |||
322 | /** | ||
323 | * Deletes all channels, frees memory... | ||
324 | * This also removes hidden channels by lazy remove. | ||
325 | */ | ||
326 | public void reset() { | ||
327 | init(0,0,1, false); | ||
328 | } | ||
329 | |||
330 | /** | ||
331 | * Destroys any existing data and creates new channels. | ||
332 | * It also destroys lazy removed channels and samples. | ||
333 | */ | ||
334 | public void reset(int channels, int sampleCount, float sampleRate) { | ||
335 | init(channels, sampleCount, sampleRate, false); | ||
336 | } | ||
337 | |||
338 | //////////////////////////////// conversion back to bytes ///////////////////////////////// | ||
339 | |||
340 | /** | ||
341 | * @return the required size of the buffer | ||
342 | * for calling convertToByteArray(..) is called | ||
343 | */ | ||
344 | public int getByteArrayBufferSize(AudioFormat format) { | ||
345 | // make sure this format is supported | ||
346 | FloatSampleTools.getFormatType(format); | ||
347 | return format.getFrameSize() * getSampleCount(); | ||
348 | } | ||
349 | |||
350 | /** | ||
351 | * Writes this sample buffer's audio data to <code>buffer</code> | ||
352 | * as an interleaved byte array. | ||
353 | * <code>buffer</code> must be large enough to hold all data. | ||
354 | * | ||
355 | * @throws IllegalArgumentException when buffer is too small or <code>format</code> doesn't match | ||
356 | * @return number of bytes written to <code>buffer</code> | ||
357 | */ | ||
358 | public int convertToByteArray(byte[] buffer, int offset, AudioFormat format) { | ||
359 | int byteCount = getByteArrayBufferSize(format); | ||
360 | if (offset + byteCount > buffer.length) { | ||
361 | throw new IllegalArgumentException | ||
362 | ("FloatSampleBuffer.convertToByteArray: buffer too small."); | ||
363 | } | ||
364 | if (format.getSampleRate()!=getSampleRate()) { | ||
365 | throw new IllegalArgumentException | ||
366 | ("FloatSampleBuffer.convertToByteArray: different samplerates."); | ||
367 | } | ||
368 | if (format.getChannels()!=getChannelCount()) { | ||
369 | throw new IllegalArgumentException | ||
370 | ("FloatSampleBuffer.convertToByteArray: different channel count."); | ||
371 | } | ||
372 | FloatSampleTools.float2byte(channels, 0, buffer, offset, getSampleCount(), | ||
373 | format, getConvertDitherBits(FloatSampleTools.getFormatType(format))); | ||
374 | |||
375 | return byteCount; | ||
376 | } | ||
377 | |||
378 | |||
379 | /** | ||
380 | * Creates a new byte[] buffer, fills it with the audio data, and returns it. | ||
381 | * @throws IllegalArgumentException when sample rate or channels do not match | ||
382 | * @see #convertToByteArray(byte[], int, AudioFormat) | ||
383 | */ | ||
384 | public byte[] convertToByteArray(AudioFormat format) { | ||
385 | // throws exception when sampleRate doesn't match | ||
386 | // creates a new byte[] buffer and returns it | ||
387 | byte[] res = new byte[getByteArrayBufferSize(format)]; | ||
388 | convertToByteArray(res, 0, format); | ||
389 | return res; | ||
390 | } | ||
391 | |||
392 | //////////////////////////////// actions ///////////////////////////////// | ||
393 | |||
394 | /** | ||
395 | * Resizes this buffer. | ||
396 | * <p>If <code>keepOldSamples</code> is true, as much as possible samples are | ||
397 | * retained. If the buffer is enlarged, silence is added at the end. | ||
398 | * If <code>keepOldSamples</code> is false, existing samples are discarded | ||
399 | * and the buffer contains random samples. | ||
400 | */ | ||
401 | public void changeSampleCount(int newSampleCount, boolean keepOldSamples) { | ||
402 | int oldSampleCount=getSampleCount(); | ||
403 | if (oldSampleCount==newSampleCount) { | ||
404 | return; | ||
405 | } | ||
406 | Object[] oldChannels=null; | ||
407 | if (keepOldSamples) { | ||
408 | oldChannels=getAllChannels(); | ||
409 | } | ||
410 | init(getChannelCount(), newSampleCount, getSampleRate()); | ||
411 | if (keepOldSamples) { | ||
412 | // copy old channels and eventually silence out new samples | ||
413 | int copyCount=newSampleCount<oldSampleCount? | ||
414 | newSampleCount:oldSampleCount; | ||
415 | for (int ch=0; ch<getChannelCount(); ch++) { | ||
416 | float[] oldSamples=(float[]) oldChannels[ch]; | ||
417 | float[] newSamples=(float[]) getChannel(ch); | ||
418 | if (oldSamples!=newSamples) { | ||
419 | // if this sample array was not object of lazy delete | ||
420 | System.arraycopy(oldSamples, 0, newSamples, 0, copyCount); | ||
421 | } | ||
422 | if (oldSampleCount<newSampleCount) { | ||
423 | // silence out new samples | ||
424 | for (int i=oldSampleCount; i<newSampleCount; i++) { | ||
425 | newSamples[i]=0.0f; | ||
426 | } | ||
427 | } | ||
428 | } | ||
429 | } | ||
430 | } | ||
431 | |||
432 | public void makeSilence() { | ||
433 | // silence all channels | ||
434 | if (getChannelCount()>0) { | ||
435 | makeSilence(0); | ||
436 | for (int ch=1; ch<getChannelCount(); ch++) { | ||
437 | copyChannel(0, ch); | ||
438 | } | ||
439 | } | ||
440 | } | ||
441 | |||
442 | public void makeSilence(int channel) { | ||
443 | float[] samples=getChannel(channel); | ||
444 | for (int i=0; i<getSampleCount(); i++) { | ||
445 | samples[i]=0.0f; | ||
446 | } | ||
447 | } | ||
448 | |||
449 | public void addChannel(boolean silent) { | ||
450 | // creates new, silent channel | ||
451 | insertChannel(getChannelCount(), silent); | ||
452 | } | ||
453 | |||
454 | /** | ||
455 | * Insert a (silent) channel at position <code>index</code>. | ||
456 | * If LAZY_DEFAULT is true, this is done lazily. | ||
457 | */ | ||
458 | public void insertChannel(int index, boolean silent) { | ||
459 | insertChannel(index, silent, LAZY_DEFAULT); | ||
460 | } | ||
461 | |||
462 | /** | ||
463 | * Inserts a channel at position <code>index</code>. | ||
464 | * <p>If <code>silent</code> is true, the new channel will be silent. | ||
465 | * Otherwise it will contain random data. | ||
466 | * <p>If <code>lazy</code> is true, hidden channels which have at least getSampleCount() | ||
467 | * elements will be examined for reusage as inserted channel.<br> | ||
468 | * If <code>lazy</code> is false, still hidden channels are reused, | ||
469 | * but it is assured that the inserted channel has exactly getSampleCount() elements, | ||
470 | * thus not wasting memory. | ||
471 | */ | ||
472 | public void insertChannel(int index, boolean silent, boolean lazy) { | ||
473 | int physSize=channels.size(); | ||
474 | int virtSize=getChannelCount(); | ||
475 | float[] newChannel=null; | ||
476 | if (physSize>virtSize) { | ||
477 | // there are hidden channels. Try to use one. | ||
478 | for (int ch=virtSize; ch<physSize; ch++) { | ||
479 | float[] thisChannel=(float[]) channels.get(ch); | ||
480 | if ((lazy && thisChannel.length>=getSampleCount()) | ||
481 | || (!lazy && thisChannel.length==getSampleCount())) { | ||
482 | // we found a matching channel. Use it ! | ||
483 | newChannel=thisChannel; | ||
484 | channels.remove(ch); | ||
485 | break; | ||
486 | } | ||
487 | } | ||
488 | } | ||
489 | if (newChannel==null) { | ||
490 | newChannel=new float[getSampleCount()]; | ||
491 | } | ||
492 | channels.add(index, newChannel); | ||
493 | this.channelCount++; | ||
494 | if (silent) { | ||
495 | makeSilence(index); | ||
496 | } | ||
497 | } | ||
498 | |||
499 | /** performs a lazy remove of the channel */ | ||
500 | public void removeChannel(int channel) { | ||
501 | removeChannel(channel, LAZY_DEFAULT); | ||
502 | } | ||
503 | |||
504 | |||
505 | /** | ||
506 | * Removes a channel. | ||
507 | * If lazy is true, the channel is not physically removed, but only hidden. | ||
508 | * These hidden channels are reused by subsequent calls to addChannel | ||
509 | * or insertChannel. | ||
510 | */ | ||
511 | public void removeChannel(int channel, boolean lazy) { | ||
512 | if (!lazy) { | ||
513 | channels.remove(channel); | ||
514 | } else if (channel<getChannelCount()-1) { | ||
515 | // if not already, move this channel at the end | ||
516 | channels.add(channels.remove(channel)); | ||
517 | } | ||
518 | channelCount--; | ||
519 | } | ||
520 | |||
521 | /** | ||
522 | * both source and target channel have to exist. targetChannel | ||
523 | * will be overwritten | ||
524 | */ | ||
525 | public void copyChannel(int sourceChannel, int targetChannel) { | ||
526 | float[] source=getChannel(sourceChannel); | ||
527 | float[] target=getChannel(targetChannel); | ||
528 | System.arraycopy(source, 0, target, 0, getSampleCount()); | ||
529 | } | ||
530 | |||
531 | /** | ||
532 | * Copies data inside all channel. When the 2 regions | ||
533 | * overlap, the behavior is not specified. | ||
534 | */ | ||
535 | public void copy(int sourceIndex, int destIndex, int length) { | ||
536 | for (int i=0; i<getChannelCount(); i++) { | ||
537 | copy(i, sourceIndex, destIndex, length); | ||
538 | } | ||
539 | } | ||
540 | |||
541 | /** | ||
542 | * Copies data inside a channel. When the 2 regions | ||
543 | * overlap, the behavior is not specified. | ||
544 | */ | ||
545 | public void copy(int channel, int sourceIndex, int destIndex, int length) { | ||
546 | float[] data=getChannel(channel); | ||
547 | int bufferCount=getSampleCount(); | ||
548 | if (sourceIndex+length>bufferCount || destIndex+length>bufferCount | ||
549 | || sourceIndex<0 || destIndex<0 || length<0) { | ||
550 | throw new IndexOutOfBoundsException("parameters exceed buffer size"); | ||
551 | } | ||
552 | System.arraycopy(data, sourceIndex, data, destIndex, length); | ||
553 | } | ||
554 | |||
555 | /** | ||
556 | * Mix up of 1 channel to n channels.<br> | ||
557 | * It copies the first channel to all newly created channels. | ||
558 | * @param targetChannelCount the number of channels that this sample buffer | ||
559 | * will have after expanding. NOT the number of | ||
560 | * channels to add ! | ||
561 | * @exception IllegalArgumentException if this buffer does not have one | ||
562 | * channel before calling this method. | ||
563 | */ | ||
564 | public void expandChannel(int targetChannelCount) { | ||
565 | // even more sanity... | ||
566 | if (getChannelCount()!=1) { | ||
567 | throw new IllegalArgumentException( | ||
568 | "FloatSampleBuffer: can only expand channels for mono signals."); | ||
569 | } | ||
570 | for (int ch=1; ch<targetChannelCount; ch++) { | ||
571 | addChannel(false); | ||
572 | copyChannel(0, ch); | ||
573 | } | ||
574 | } | ||
575 | |||
576 | /** | ||
577 | * Mix down of n channels to one channel.<br> | ||
578 | * It uses a simple mixdown: all other channels are added to first channel.<br> | ||
579 | * The volume is NOT lowered ! | ||
580 | * Be aware, this might cause clipping when converting back | ||
581 | * to integer samples. | ||
582 | */ | ||
583 | public void mixDownChannels() { | ||
584 | float[] firstChannel=getChannel(0); | ||
585 | int sampleCount=getSampleCount(); | ||
586 | int channelCount=getChannelCount(); | ||
587 | for (int ch=channelCount-1; ch>0; ch--) { | ||
588 | float[] thisChannel=getChannel(ch); | ||
589 | for (int i=0; i<sampleCount; i++) { | ||
590 | firstChannel[i]+=thisChannel[i]; | ||
591 | } | ||
592 | removeChannel(ch); | ||
593 | } | ||
594 | } | ||
595 | |||
596 | /** | ||
597 | * Initializes audio data from the provided byte array. | ||
598 | * The float samples are written at <code>destOffset</code>. | ||
599 | * This FloatSampleBuffer must be big enough to accomodate the samples. | ||
600 | * <p> | ||
601 | * <code>srcBuffer</code> is read from index <code>srcOffset</code> | ||
602 | * to <code>(srcOffset + (lengthInSamples * format.getFrameSize()))</code. | ||
603 | * | ||
604 | * @param input the input buffer in interleaved audio data | ||
605 | * @param inByteOffset the offset in <code>input</code> | ||
606 | * @param format input buffer's audio format | ||
607 | * @param floatOffset the offset where to write the float samples | ||
608 | * @param frameCount number of samples to write to this sample buffer | ||
609 | */ | ||
610 | public void setSamplesFromBytes(byte[] input, int inByteOffset, AudioFormat format, | ||
611 | int floatOffset, int frameCount) { | ||
612 | if (floatOffset < 0 || frameCount < 0 || inByteOffset < 0) { | ||
613 | throw new IllegalArgumentException | ||
614 | ("FloatSampleBuffer.setSamplesFromBytes: negative inByteOffset, floatOffset, or frameCount"); | ||
615 | } | ||
616 | if (inByteOffset + (frameCount * format.getFrameSize()) > input.length) { | ||
617 | throw new IllegalArgumentException | ||
618 | ("FloatSampleBuffer.setSamplesFromBytes: input buffer too small."); | ||
619 | } | ||
620 | if (floatOffset + frameCount > getSampleCount()) { | ||
621 | throw new IllegalArgumentException | ||
622 | ("FloatSampleBuffer.setSamplesFromBytes: frameCount too large"); | ||
623 | } | ||
624 | |||
625 | FloatSampleTools.byte2float(input, inByteOffset, channels, floatOffset, frameCount, format); | ||
626 | } | ||
627 | |||
628 | //////////////////////////////// properties ///////////////////////////////// | ||
629 | |||
630 | public int getChannelCount() { | ||
631 | return channelCount; | ||
632 | } | ||
633 | |||
634 | public int getSampleCount() { | ||
635 | return sampleCount; | ||
636 | } | ||
637 | |||
638 | public float getSampleRate() { | ||
639 | return sampleRate; | ||
640 | } | ||
641 | |||
642 | /** | ||
643 | * Sets the sample rate of this buffer. | ||
644 | * NOTE: no conversion is done. The samples are only re-interpreted. | ||
645 | */ | ||
646 | public void setSampleRate(float sampleRate) { | ||
647 | if (sampleRate<=0) { | ||
648 | throw new IllegalArgumentException | ||
649 | ("Invalid samplerate for FloatSampleBuffer."); | ||
650 | } | ||
651 | this.sampleRate=sampleRate; | ||
652 | } | ||
653 | |||
654 | /** | ||
655 | * NOTE: the returned array may be larger than sampleCount. So in any case, | ||
656 | * sampleCount is to be respected. | ||
657 | */ | ||
658 | public float[] getChannel(int channel) { | ||
659 | if (channel<0 || channel>=getChannelCount()) { | ||
660 | throw new IllegalArgumentException( | ||
661 | "FloatSampleBuffer: invalid channel number."); | ||
662 | } | ||
663 | return (float[]) channels.get(channel); | ||
664 | } | ||
665 | |||
666 | public Object[] getAllChannels() { | ||
667 | Object[] res=new Object[getChannelCount()]; | ||
668 | for (int ch=0; ch<getChannelCount(); ch++) { | ||
669 | res[ch]=getChannel(ch); | ||
670 | } | ||
671 | return res; | ||
672 | } | ||
673 | |||
674 | /** | ||
675 | * Set the number of bits for dithering. | ||
676 | * Typically, a value between 0.2 and 0.9 gives best results. | ||
677 | * <p>Note: this value is only used, when dithering is actually performed. | ||
678 | */ | ||
679 | public void setDitherBits(float ditherBits) { | ||
680 | if (ditherBits<=0) { | ||
681 | throw new IllegalArgumentException("DitherBits must be greater than 0"); | ||
682 | } | ||
683 | this.ditherBits=ditherBits; | ||
684 | } | ||
685 | |||
686 | public float getDitherBits() { | ||
687 | return ditherBits; | ||
688 | } | ||
689 | |||
690 | /** | ||
691 | * Sets the mode for dithering. | ||
692 | * This can be one of: | ||
693 | * <ul><li>DITHER_MODE_AUTOMATIC: it is decided automatically, | ||
694 | * whether dithering is necessary - in general when sample size is | ||
695 | * decreased. | ||
696 | * <li>DITHER_MODE_ON: dithering will be forced | ||
697 | * <li>DITHER_MODE_OFF: dithering will not be done. | ||
698 | * </ul> | ||
699 | */ | ||
700 | public void setDitherMode(int mode) { | ||
701 | if (mode!=DITHER_MODE_AUTOMATIC | ||
702 | && mode!=DITHER_MODE_ON | ||
703 | && mode!=DITHER_MODE_OFF) { | ||
704 | throw new IllegalArgumentException("Illegal DitherMode"); | ||
705 | } | ||
706 | this.ditherMode=mode; | ||
707 | } | ||
708 | |||
709 | public int getDitherMode() { | ||
710 | return ditherMode; | ||
711 | } | ||
712 | |||
713 | |||
714 | /** | ||
715 | * @return the ditherBits parameter for the float2byte functions | ||
716 | */ | ||
717 | protected float getConvertDitherBits(int newFormatType) { | ||
718 | // let's see whether dithering is necessary | ||
719 | boolean doDither = false; | ||
720 | switch (ditherMode) { | ||
721 | case DITHER_MODE_AUTOMATIC: | ||
722 | doDither=(originalFormatType & FloatSampleTools.F_SAMPLE_WIDTH_MASK)> | ||
723 | (newFormatType & FloatSampleTools.F_SAMPLE_WIDTH_MASK); | ||
724 | break; | ||
725 | case DITHER_MODE_ON: | ||
726 | doDither=true; | ||
727 | break; | ||
728 | case DITHER_MODE_OFF: | ||
729 | doDither=false; | ||
730 | break; | ||
731 | } | ||
732 | return doDither?ditherBits:0.0f; | ||
733 | } | ||
734 | } | ||