annotate src/uk/ac/qmul/eecs/depic/daw/AudioLoader.java @ 4:473da40f3d39 tip

added html formatting to Daw/package-info.java
author Fiore Martin <f.martin@qmul.ac.uk>
date Thu, 25 Feb 2016 17:50:09 +0000
parents 629262395647
children
rev   line source
f@0 1 /*
f@0 2 Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation.
f@0 3
f@0 4 Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/)
f@0 5
f@0 6 This program is free software: you can redistribute it and/or modify
f@0 7 it under the terms of the GNU General Public License as published by
f@0 8 the Free Software Foundation, either version 3 of the License, or
f@0 9 (at your option) any later version.
f@0 10
f@0 11 This program is distributed in the hope that it will be useful,
f@0 12 but WITHOUT ANY WARRANTY; without even the implied warranty of
f@0 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
f@0 14 GNU General Public License for more details.
f@0 15
f@0 16 You should have received a copy of the GNU General Public License
f@0 17 along with this program. If not, see <http://www.gnu.org/licenses/>.
f@0 18 */
f@0 19
f@0 20 package uk.ac.qmul.eecs.depic.daw;
f@0 21
f@0 22 import java.io.BufferedInputStream;
f@0 23 import java.io.File;
f@0 24 import java.nio.ByteBuffer;
f@0 25 import java.nio.ByteOrder;
f@0 26 import java.util.ArrayList;
f@0 27 import java.util.List;
f@0 28
f@0 29 import javax.sound.sampled.AudioFormat;
f@0 30 import javax.sound.sampled.AudioInputStream;
f@0 31 import javax.sound.sampled.AudioSystem;
f@0 32 import javax.sound.sampled.UnsupportedAudioFileException;
f@0 33 import javax.swing.SwingWorker;
f@0 34
f@0 35 import uk.ac.qmul.eecs.depic.daw.AudioLoader.ReturnObject;
f@0 36
f@1 37 /**
f@1 38 *
f@1 39 * A swing worker that loads an audio file in a separate thread. It returns a RetunObject when the loading is complete.
f@1 40 *
f@1 41 */
f@0 42 public class AudioLoader extends SwingWorker<ReturnObject,Void>{
f@0 43 public static final int FILE_LOAD_TOTAL_PROGRESS = 100;
f@0 44 /**
f@0 45 * The default conversion format used. Also the conversion format returned by {@code getConversionFormat()}
f@0 46 */
f@0 47 public static final AudioFormat DEFAULT_CONVERSION_FORMAT = new AudioFormat(
f@0 48 8000.0f, // sample rate
f@0 49 16, // bit per sample
f@0 50 1, // mono
f@0 51 true, // signed
f@0 52 false // little endian (default .wav files)
f@0 53 );
f@0 54
f@0 55 private File audioFile;
f@0 56 private int minChunkSize;
f@0 57 private int maxScaleFactor;
f@0 58
f@0 59 public AudioLoader(File audioFile, int minChunkSize, int maxScaleFactor){
f@0 60 this.audioFile = audioFile;
f@0 61 this.minChunkSize = minChunkSize;
f@0 62 this.maxScaleFactor = maxScaleFactor;
f@0 63 }
f@0 64
f@0 65 /**
f@0 66 * Reads the audio files and build all the min and max for all the shunks of frames
f@0 67 */
f@0 68 @Override
f@0 69 protected ReturnObject doInBackground() throws Exception {
f@0 70 /* get all the info about the file format, needed later for the estimate of the converted file length */
f@0 71 AudioInputStream originalFile = AudioSystem.getAudioInputStream(audioFile);
f@0 72
f@0 73 AudioFormat originalAudioFormat = originalFile.getFormat();
f@0 74 long originalNumTotalFrames = originalFile.getFrameLength();
f@0 75 float originalFrameSize = originalAudioFormat.getFrameSize();
f@0 76 float originalFrameRate = originalAudioFormat.getFrameRate();
f@0 77 float originalNumChannels = originalAudioFormat.getChannels();
f@0 78
f@0 79 if(originalNumTotalFrames == 0)
f@0 80 throw new UnsupportedAudioFileException("File Empty");
f@0 81 /* convert the audio format to the one suitable for parsing chunks and get min and max from them (see getConversionFormat()) */
f@0 82 AudioFormat conversionFormat = getConversionFormat();
f@0 83 if(!AudioSystem.isConversionSupported(conversionFormat,originalAudioFormat)){
f@0 84 throw new UnsupportedAudioFileException("Cannot convert file to the following format: "+conversionFormat);
f@0 85 }
f@0 86
f@0 87 AudioInputStream convertedFile = AudioSystem.getAudioInputStream(conversionFormat, originalFile);
f@0 88 /* start parsing the file and building the chunks' minimums and maximums */
f@0 89 /* all the variable from here on, unless they begin with "originalFile" refer to the converted audio stream */
f@0 90
f@0 91 byte[] audioBytes = new byte[minChunkSize * conversionFormat.getFrameSize()];
f@0 92 /* prepare the ByteBuffer, wrapping the byte array, that will be used to read Short values */
f@0 93 ByteBuffer byteBuffer = ByteBuffer.wrap(audioBytes);
f@0 94 /* set the endiannes according to the audio format */
f@0 95 byteBuffer.order(conversionFormat.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
f@0 96
f@0 97 /* make an estimation of the frames in the converted file, based on the original file. This is necessary because *
f@0 98 * convertedFile is a stream pointing to original file and not a file itself. Therefore getFrameLength() returns *
f@0 99 * -1 as t doesn't have any knowledge of the length of the underlying file. So make an estimate of the length *
f@0 100 * of the file after conversion in order to allocate enough space when the ArrayList in newFlechunks is allocated*
f@0 101 * and for the progress of file load. */
f@0 102 float convertedFrameSize = conversionFormat.getFrameSize();
f@0 103 float convertedFrameRate = conversionFormat.getFrameRate();
f@0 104 float convertedNumChannels = conversionFormat.getChannels();
f@0 105
f@0 106 long convertedNumTotalFrames = (long) (
f@0 107 originalNumTotalFrames *
f@0 108 (convertedFrameSize/originalFrameSize) *
f@0 109 (convertedFrameRate/originalFrameRate) *
f@0 110 (convertedNumChannels/originalNumChannels));
f@0 111 long convertedNumTotalBytes = convertedNumTotalFrames * conversionFormat.getFrameSize();
f@0 112
f@0 113 /* creates the first list of chunks with smallest size. Array size = audio frames/num frames of minimum chunk */
f@0 114 WavePeaks newFileChunks = new WavePeaks(maxScaleFactor);
f@0 115 /* first List s for scale factor = 1, that is the finest zoom scale */
f@0 116 newFileChunks.add(1, new ArrayList<Chunk>((int)(convertedNumTotalFrames/minChunkSize) +1));
f@0 117 int numBytesRead = 0;
f@0 118 float totalBytesRead = 0f;
f@0 119 try(BufferedInputStream chunkBufferedAudio = new BufferedInputStream(convertedFile,audioBytes.length)){
f@0 120 while((numBytesRead = chunkBufferedAudio.read(audioBytes)) != -1){
f@0 121 if(isCancelled())
f@0 122 return null;
f@0 123 totalBytesRead += numBytesRead;
f@0 124 /* normalize the progress value to total load */
f@0 125 int progress = (int) ((totalBytesRead/convertedNumTotalBytes)*FILE_LOAD_TOTAL_PROGRESS);
f@0 126 if(progress < FILE_LOAD_TOTAL_PROGRESS)
f@0 127 setProgress(progress);
f@0 128 /* Now read the byte buffer, backed by audioByte, and find min and max. The audio format *
f@0 129 * has been converted to signed 16 bit frames, so it can be read in a Short value */
f@0 130 Short currentMax = Short.MIN_VALUE;
f@0 131 Short currentMin = Short.MAX_VALUE;
f@0 132
f@0 133 /* find maximum and minimum values in this chunk */
f@0 134 byteBuffer.clear();
f@0 135 byteBuffer.limit(numBytesRead);
f@0 136 while(byteBuffer.hasRemaining()){
f@0 137 Short frame = byteBuffer.getShort();
f@0 138 if(frame > currentMax)
f@0 139 currentMax = frame;
f@0 140 if(frame < currentMin)
f@0 141 currentMin = frame;
f@0 142 }
f@0 143 newFileChunks.get(1).add(new Chunk(currentMin, currentMax));
f@0 144 }
f@0 145 }
f@0 146
f@0 147 for(int scaleFactor = 2; scaleFactor <= maxScaleFactor; scaleFactor++){
f@0 148 List<Chunk> previousList = newFileChunks.get(scaleFactor-1);
f@0 149 List<Chunk> newList = new ArrayList<>(previousList.size()/2+1);
f@0 150
f@0 151 for(int i=0; i<previousList.size();i += 2){
f@0 152 /* check if we're at the last array item, which happens when the size is odd *
f@0 153 * In this case we don't merge two items but just take the last item as a new one */
f@0 154 if(i == previousList.size()-1){
f@0 155 newList.add(previousList.get(i));
f@0 156 break; // end of the array anyway
f@0 157 }
f@0 158 newList.add(new Chunk(previousList.get(i),previousList.get(i+1)));
f@0 159 }
f@0 160 newFileChunks.add(scaleFactor, newList);
f@0 161 }
f@0 162
f@0 163 /* open the Sample for playback */
f@1 164 Sample sample = Daw.getSoundEngineFactory().createSample(audioFile.getAbsolutePath());
f@1 165
f@1 166
f@0 167 /* return sample and chunks to the event dispatching thread */
f@0 168 return new ReturnObject(newFileChunks,sample,originalAudioFormat,conversionFormat);
f@0 169 }
f@0 170
f@0 171 protected AudioFormat getConversionFormat(){
f@0 172 return DEFAULT_CONVERSION_FORMAT;
f@0 173 }
f@0 174
f@1 175 /**
f@1 176 *
f@1 177 * An object returned by the AudioLoader. It contains meta data about the sound sample such as wave peaks and format
f@1 178 * as well as the Sample object representing the loaded sample.
f@1 179 *
f@1 180 */
f@0 181 public static class ReturnObject {
f@1 182 public ReturnObject(WavePeaks peaks, Sample s,
f@0 183 AudioFormat originalFormat, AudioFormat conversionFormat) {
f@0 184 super();
f@0 185 this.peaks = peaks;
f@0 186 this.sample = s;
f@0 187 this.originalFormat = originalFormat;
f@0 188 this.conversionFormat = conversionFormat;
f@0 189 }
f@0 190
f@0 191 public WavePeaks peaks;
f@1 192 public Sample sample;
f@0 193 public AudioFormat originalFormat;
f@0 194 public AudioFormat conversionFormat;
f@0 195 }
f@0 196 }