1 | package org.apollo.meldex;
|
---|
2 |
|
---|
3 |
|
---|
4 | import java.io.File;
|
---|
5 | import java.util.ArrayList;
|
---|
6 | import javax.sound.sampled.AudioFileFormat;
|
---|
7 | import javax.sound.sampled.AudioFormat;
|
---|
8 | import javax.sound.sampled.AudioInputStream;
|
---|
9 | import javax.sound.sampled.AudioSystem;
|
---|
10 |
|
---|
11 | import org.expeditee.gio.EcosystemManager;
|
---|
12 |
|
---|
13 | public class WavSample
|
---|
14 | {
|
---|
15 | // Data representing the currently loaded file at a high level of abstraction
|
---|
16 | private File file = null;
|
---|
17 | private AudioFormat format = null;
|
---|
18 | AudioInputStream audioData = null;
|
---|
19 |
|
---|
20 | // The actual raw audio data (without header). This shouldn't need to be used often.
|
---|
21 | byte[] rawData = null;
|
---|
22 |
|
---|
23 | // The standardised unsigned 8-bit mono data. This should be used instead of the raw data.
|
---|
24 | private byte[] stdData = null;
|
---|
25 |
|
---|
26 |
|
---|
27 | public WavSample(byte[] rawData, AudioFormat format)
|
---|
28 | {
|
---|
29 | this.rawData = rawData;
|
---|
30 | this.format = format;
|
---|
31 | }
|
---|
32 |
|
---|
33 | public WavSample()
|
---|
34 | {
|
---|
35 | }
|
---|
36 |
|
---|
37 |
|
---|
38 | public WavSample(AudioInputStream data)
|
---|
39 | {
|
---|
40 | audioData = data;
|
---|
41 |
|
---|
42 | buildSampleData();
|
---|
43 | }
|
---|
44 |
|
---|
45 |
|
---|
46 | // ----------------------------------------------------------------------------------
|
---|
47 | // Method : buildSampleData
|
---|
48 | // Returns : true - if the sample data was successfully built
|
---|
49 | // false - if the sample data was not successfully built
|
---|
50 | // ----------------------------------------------------------------------------------
|
---|
51 | private boolean buildSampleData()
|
---|
52 | {
|
---|
53 | // Reset the sample to the beginning
|
---|
54 | if (reset() == false) {
|
---|
55 | return false;
|
---|
56 | }
|
---|
57 |
|
---|
58 | // Get the format of the loaded file
|
---|
59 | format = audioData.getFormat();
|
---|
60 |
|
---|
61 | try {
|
---|
62 | // Get the length of the loaded sample
|
---|
63 | int rawLength = audioData.available();
|
---|
64 |
|
---|
65 | // Load the sample data into an array ready for processing
|
---|
66 | rawData = new byte[rawLength];
|
---|
67 | audioData.read(rawData, 0, rawLength);
|
---|
68 | }
|
---|
69 | catch (Exception ex) {
|
---|
70 | EcosystemManager.getGraphicsManager().showDialog("Exception", "Exception occurred creating sample.\n\n" + ex);
|
---|
71 | return false;
|
---|
72 | }
|
---|
73 |
|
---|
74 | // Sample data built successfully
|
---|
75 | return true;
|
---|
76 | }
|
---|
77 |
|
---|
78 |
|
---|
79 | // ----------------------------------------------------------------------------------
|
---|
80 | // Method : reset
|
---|
81 | // Returns : true - if the sample was successfully reset
|
---|
82 | // false - if the sample was not successfully reset
|
---|
83 | // ----------------------------------------------------------------------------------
|
---|
84 | public boolean reset()
|
---|
85 | {
|
---|
86 | // For some bizarre reason we cannot reset a loaded file, so it is necessary to reload it
|
---|
87 | if (file != null) {
|
---|
88 | try {
|
---|
89 | audioData = AudioSystem.getAudioInputStream(file);
|
---|
90 | }
|
---|
91 | catch (Exception ex) {
|
---|
92 | EcosystemManager.getGraphicsManager().showDialog("Exception", "Exception occurred reloading sample.\n\n" + ex);
|
---|
93 | return false;
|
---|
94 | }
|
---|
95 | }
|
---|
96 |
|
---|
97 | // Otherwise we just reset the stream to the start, ready for playback
|
---|
98 | else if (audioData != null) {
|
---|
99 | try {
|
---|
100 | audioData.reset();
|
---|
101 | }
|
---|
102 | catch (Exception ex) {
|
---|
103 | EcosystemManager.getGraphicsManager().showDialog("Exception", "Exception occurred resetting audio stream.\n\n" + ex);
|
---|
104 | return false;
|
---|
105 | }
|
---|
106 | }
|
---|
107 |
|
---|
108 | // Reset performed successfully
|
---|
109 | return true;
|
---|
110 | }
|
---|
111 |
|
---|
112 |
|
---|
113 | // ----------------------------------------------------------------------------------
|
---|
114 | // Method : getFormat
|
---|
115 | // Returns : null - if the sample does not have a specified format
|
---|
116 | // else - the AudioFormat of the audio sample
|
---|
117 | // ----------------------------------------------------------------------------------
|
---|
118 | public AudioFormat getFormat()
|
---|
119 | {
|
---|
120 | if (format == null) {
|
---|
121 | if (audioData != null) {
|
---|
122 | format = audioData.getFormat();
|
---|
123 | }
|
---|
124 | }
|
---|
125 |
|
---|
126 | return format;
|
---|
127 | }
|
---|
128 |
|
---|
129 |
|
---|
130 | // ----------------------------------------------------------------------------------
|
---|
131 | // Method : getBytesPerSecond
|
---|
132 | // Returns : -1 - if the value could not be successfully calculated
|
---|
133 | // else - the number of bytes of audio data per second
|
---|
134 | // ----------------------------------------------------------------------------------
|
---|
135 | public int getBytesPerSecond()
|
---|
136 | {
|
---|
137 | if (format == null) {
|
---|
138 | return -1;
|
---|
139 | }
|
---|
140 |
|
---|
141 | int frameSize = (format.getChannels() * (format.getSampleSizeInBits() / 8));
|
---|
142 | return (int) (format.getSampleRate() * frameSize);
|
---|
143 | }
|
---|
144 |
|
---|
145 |
|
---|
146 | // ----------------------------------------------------------------------------------
|
---|
147 | // Method : getStandardisedData
|
---|
148 | // Returns : null - if the sample could not be successfully standardised
|
---|
149 | // else - byte[] containing the standardised unsigned 8-bit mono data
|
---|
150 | // ----------------------------------------------------------------------------------
|
---|
151 | public byte[] getStandardisedData(boolean createIfNotComputed)
|
---|
152 | {
|
---|
153 | if (stdData == null && createIfNotComputed) {
|
---|
154 | // Create the standardised unsigned 8-bit mono data
|
---|
155 | // stdData = toStandardForm(format, rawData, rawData.length, (int) format.getSampleRate());
|
---|
156 | stdData = toStandardForm(format, rawData, (int) format.getSampleRate());
|
---|
157 | }
|
---|
158 |
|
---|
159 | return stdData;
|
---|
160 | }
|
---|
161 |
|
---|
162 |
|
---|
163 | // ----------------------------------------------------------------------------------
|
---|
164 | // Method : toStandardForm
|
---|
165 | // Returns : null - if the sample could not be successfully standardised
|
---|
166 | // else - byte[] containing the standardised unsigned 8-bit mono data
|
---|
167 | // ----------------------------------------------------------------------------------
|
---|
168 | // public static byte[] toStandardForm(AudioFormat format, byte[] inData, int amount, int frequency)
|
---|
169 | public static byte[] toStandardForm(AudioFormat format, byte[] inData, int frequency)
|
---|
170 | {
|
---|
171 | // Check for (supposedly) invalid sample formats
|
---|
172 | if (format.getSampleSizeInBits() == 8) {
|
---|
173 | if (format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)) {
|
---|
174 | EcosystemManager.getGraphicsManager().showDialog("Exception", "Oops! Internal Error: Unexpected audio format (signed 8-bit).");
|
---|
175 | return null;
|
---|
176 | }
|
---|
177 | }
|
---|
178 | if (format.getSampleSizeInBits() == 16) {
|
---|
179 | if (format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED)) {
|
---|
180 | EcosystemManager.getGraphicsManager().showDialog("Exception", "Oops! Internal Error: Unexpected audio format (unsigned 16-bit).");
|
---|
181 | return null;
|
---|
182 | }
|
---|
183 | }
|
---|
184 |
|
---|
185 | int baseSampleSize = (int) (format.getSampleRate() / frequency);
|
---|
186 | int frameSize = (format.getChannels() * (format.getSampleSizeInBits() / 8));
|
---|
187 | int sampleSize = (baseSampleSize * frameSize);
|
---|
188 |
|
---|
189 | // Create a new list to store the standardised sample values
|
---|
190 | ArrayList<Integer> stdTemp = new ArrayList<Integer>();
|
---|
191 |
|
---|
192 | // Simply pick values from the sample at the frequency specified
|
---|
193 | // for (int i = 0; i < amount; i += sampleSize) {
|
---|
194 | for (int i = 0; i < inData.length; i += sampleSize) {
|
---|
195 | for (int j = 0; j < frameSize; j++) {
|
---|
196 | int val = inData[i+j];
|
---|
197 | stdTemp.add(new Integer(val));
|
---|
198 | }
|
---|
199 | }
|
---|
200 |
|
---|
201 | // Convert the sample to standard unsigned, 8-bit, mono values
|
---|
202 | stdTemp = toStandardEndian(format, stdTemp);
|
---|
203 | stdTemp = toUnsignedValues(format, stdTemp);
|
---|
204 | stdTemp = to8bit(format, stdTemp);
|
---|
205 | stdTemp = toMono(format, stdTemp);
|
---|
206 |
|
---|
207 | // Allocate memory for the standardised data
|
---|
208 | byte[] tmpData = new byte[stdTemp.size()];
|
---|
209 |
|
---|
210 | // Convert the ArrayList back to an array of bytes
|
---|
211 | for (int i = 0; i < stdTemp.size(); i++) {
|
---|
212 | int val = ((Integer) stdTemp.get(i)).intValue();
|
---|
213 | tmpData[i] = (byte) val;
|
---|
214 | }
|
---|
215 |
|
---|
216 | return tmpData;
|
---|
217 | }
|
---|
218 |
|
---|
219 |
|
---|
220 | private static ArrayList<Integer> toStandardEndian(AudioFormat format, ArrayList<Integer> inData)
|
---|
221 | {
|
---|
222 | // Endian details do not apply to 8-bit samples, so no conversion needed
|
---|
223 | if (format.getSampleSizeInBits() == 8) {
|
---|
224 | return inData;
|
---|
225 | }
|
---|
226 |
|
---|
227 | // Create a new list to store the results
|
---|
228 | ArrayList<Integer> outData = new ArrayList<Integer>();
|
---|
229 |
|
---|
230 | // Simply convert each number in the sample to the endian form for the current system
|
---|
231 | for (int i = 0; i < inData.size(); i += 2) {
|
---|
232 |
|
---|
233 | int MSB, LSB;
|
---|
234 | if (format.isBigEndian()) {
|
---|
235 | MSB = ((Integer) inData.get(i)).intValue(); // First byte is MSB (high order)
|
---|
236 | LSB = ((Integer) inData.get(i+1)).intValue(); // Second byte is LSB (low order)
|
---|
237 | }
|
---|
238 | else {
|
---|
239 | LSB = ((Integer) inData.get(i)).intValue(); // First byte is LSB (low order)
|
---|
240 | MSB = ((Integer) inData.get(i+1)).intValue(); // Second byte is MSB (high order)
|
---|
241 | }
|
---|
242 |
|
---|
243 | int val = (MSB << 8) + (LSB & 255);
|
---|
244 | outData.add(new Integer(val));
|
---|
245 | }
|
---|
246 |
|
---|
247 | return outData;
|
---|
248 | }
|
---|
249 |
|
---|
250 |
|
---|
251 | private static ArrayList<Integer> toUnsignedValues(AudioFormat format, ArrayList<Integer> inData)
|
---|
252 | {
|
---|
253 | // PCM_UNSIGNED samples are what we want, so no conversion needed
|
---|
254 | if (format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED)) {
|
---|
255 | return inData;
|
---|
256 | }
|
---|
257 |
|
---|
258 | // Create a new list to store the results
|
---|
259 | ArrayList<Integer> outData = new ArrayList<Integer>();
|
---|
260 |
|
---|
261 | // Simply convert each signed number in the sample to an unsigned value
|
---|
262 | for (int i = 0; i < inData.size(); i++) {
|
---|
263 |
|
---|
264 | int val = ((Integer) inData.get(i)).intValue();
|
---|
265 | val = val - 32768;
|
---|
266 | if (val < -32768) {
|
---|
267 | val += 65536;
|
---|
268 | }
|
---|
269 |
|
---|
270 | outData.add(new Integer(val));
|
---|
271 | }
|
---|
272 |
|
---|
273 | return outData;
|
---|
274 | }
|
---|
275 |
|
---|
276 |
|
---|
277 | private static ArrayList<Integer> to8bit(AudioFormat format, ArrayList<Integer> inData)
|
---|
278 | {
|
---|
279 | // 8-bit data is what we want, so no conversion necessary
|
---|
280 | if (format.getSampleSizeInBits() == 8) {
|
---|
281 | return inData;
|
---|
282 | }
|
---|
283 |
|
---|
284 | // Create a new list to store the results
|
---|
285 | ArrayList<Integer> outData = new ArrayList<Integer>();
|
---|
286 |
|
---|
287 | // Simply reduce each value in the sample to an 8-bit value
|
---|
288 | for (int i = 0; i < inData.size(); i++) {
|
---|
289 |
|
---|
290 | int val = ((Integer) inData.get(i)).intValue();
|
---|
291 | val = val / 256;
|
---|
292 |
|
---|
293 | outData.add(new Integer(val));
|
---|
294 | }
|
---|
295 |
|
---|
296 | return outData;
|
---|
297 | }
|
---|
298 |
|
---|
299 |
|
---|
300 | private static ArrayList<Integer> toMono(AudioFormat format, ArrayList<Integer> inData)
|
---|
301 | {
|
---|
302 | // Mono-channel data is what we want, so no conversion necessary
|
---|
303 | if (format.getChannels() == 1) {
|
---|
304 | return inData;
|
---|
305 | }
|
---|
306 |
|
---|
307 | // Create a new list to store the results
|
---|
308 | ArrayList<Integer> outData = new ArrayList<Integer>();
|
---|
309 |
|
---|
310 | // Simply average all of the channels for each frame
|
---|
311 | for (int i = 0; i < inData.size(); i += format.getChannels()) {
|
---|
312 |
|
---|
313 | int sum = 0;
|
---|
314 | for (int j = 0; j < format.getChannels(); j++) {
|
---|
315 | int val = ((Integer) inData.get(i+j)).intValue();
|
---|
316 | // We need to use the true unsigned value
|
---|
317 | if (val < 0) {
|
---|
318 | val = val + 256;
|
---|
319 | }
|
---|
320 | sum += val;
|
---|
321 | }
|
---|
322 |
|
---|
323 | int val = sum / format.getChannels();
|
---|
324 | // Revert back to signed representation
|
---|
325 | if (val > 127) {
|
---|
326 | val = val - 256;
|
---|
327 | }
|
---|
328 |
|
---|
329 | outData.add(new Integer(val));
|
---|
330 | }
|
---|
331 |
|
---|
332 | return outData;
|
---|
333 | }
|
---|
334 |
|
---|
335 |
|
---|
336 | // ----------------------------------------------------------------------------------
|
---|
337 | // Method : loadFromFile
|
---|
338 | // Returns : false - if the track could not be successfully loaded
|
---|
339 | // true - if the track was successfully loaded
|
---|
340 | // ----------------------------------------------------------------------------------
|
---|
341 | public boolean loadFromFile(File wavFile)
|
---|
342 | {
|
---|
343 | file = wavFile;
|
---|
344 |
|
---|
345 | return buildSampleData();
|
---|
346 | }
|
---|
347 |
|
---|
348 |
|
---|
349 | // ----------------------------------------------------------------------------------
|
---|
350 | // Method : saveToFile
|
---|
351 | // Returns : false - if the sample could not be successfully saved
|
---|
352 | // true - if the sample was successfully saved
|
---|
353 | // ----------------------------------------------------------------------------------
|
---|
354 | public boolean saveToFile(File outFile)
|
---|
355 | {
|
---|
356 | // Reset the sample to the beginning
|
---|
357 | if (reset() == false) {
|
---|
358 | return false;
|
---|
359 | }
|
---|
360 |
|
---|
361 | // Write the audio data to the selected file in the format specified
|
---|
362 | try {
|
---|
363 | if (AudioSystem.write(audioData, AudioFileFormat.Type.WAVE, outFile) == -1) {
|
---|
364 | EcosystemManager.getGraphicsManager().showDialog("Exception", "Problem occurred writing to file.");
|
---|
365 | return false;
|
---|
366 | }
|
---|
367 | }
|
---|
368 | catch (Exception ex) {
|
---|
369 | EcosystemManager.getGraphicsManager().showDialog("Exception", "Exception occurred saving file.\n\n" + ex);
|
---|
370 | return false;
|
---|
371 | }
|
---|
372 |
|
---|
373 | // File saved successfully
|
---|
374 | return true;
|
---|
375 | }
|
---|
376 |
|
---|
377 | public byte[] getRawAudio() {
|
---|
378 | return rawData;
|
---|
379 | }
|
---|
380 | }
|
---|