package apes.plugins;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;
import apes.interfaces.AudioFormatPlugin;
import apes.models.FileStatus;
import apes.models.InternalFormat;
import apes.models.Tags;
import apes.views.ProgressView;
/**
* Module used for converting .wav-files to the internal format and converting
* the internal format to .wav-files. NOTE! This file has no support for more
* chunks than the necessary. If you want to add support. Read here:
* <ul>
* <li>http://www.sonicspot.com/guide/wavefiles.html</li>
* <li>http://ccrma.stanford.edu/courses/422/projects/WaveFormat/</li>
* </ul>
*
* @author Simon Holm
*/
public class WaveFileFormat implements AudioFormatPlugin
{
/**
* Magic number of samples to read and write.
*/
private final static int IO_CHUNK_SIZE = 100000;
/**
* Returns the name of the plugin.
*
* @return The name.
*/
public String getName()
{
return "Wave";
}
/**
* Returns the description map.
*
* @return Description map.
*/
public Map<String, String> getDescriptions()
{
Map<String, String> map = new HashMap<String, String>();
map.put("en", "Support for .wav files.");
map.put("sv", "Stöd för .wav filer.");
return map;
}
/**
* Returns the extension of the file format
*
* @return extension
*/
public String getExtension()
{
return "wav";
}
/**
* Converts a file from the internal file format to .wav and stores it on the
* disk
*
* @param internalFormat The file to be converted.
* @param path The path to the folder were the file should be saved.
* @param fileName The name of the file to be saved.
* @throws Exception
*/
public void exportFile(InternalFormat internalFormat, String path, String name) throws IOException
{
exportFile(internalFormat, new File(path, name));
}
/**
* Converts a file from the internal file format to .wav and stores it on the
* disk
*
* @param internalFormat The file to be converted.
* @param file File to write to
* @throws Exception
*/
public void exportFile(InternalFormat internalFormat, File file) throws IOException
{
exportFile(internalFormat, file, 0, internalFormat.getSampleAmount());
}
/**
* @param internalFormat The file to be converted.
* @param file File to write to
* @param start Start of interval to copy from in internal format in samples
* @param stop End of interval to copy from in internal format in samples
* @throws Exception
*/
public void exportFile(InternalFormat internalFormat, File file, long startS, long stopS) throws IOException
{
ByteBuffer data; // contians data to be exported
// TODO; Add better support for different headers
byte[] chunkID = { 'R', 'I', 'F', 'F' };
int chunkSize;
byte[] format = { 'W', 'A', 'V', 'E' };
int subchunk1ID = 0x666d7420; // fmt
int subchunk1Size = 16;
short audioFormat = 1;
short numChannels = (short)internalFormat.getNumChannels();
int sampleRate = internalFormat.getSampleRate();
int byteRate = sampleRate * numChannels * (internalFormat.bytesPerSample);
short blockAlign = (short)(numChannels * (internalFormat.bytesPerSample));
short bitsPerSample = (short)internalFormat.bitsPerSample;
byte[] subchunk2ID = { 'd', 'a', 't', 'a' };
int subchunk2Size;
long numSamples = stopS - startS;
subchunk2Size = (int)(numSamples * numChannels * internalFormat.bytesPerSample);
chunkSize = 4 + (8 + subchunk1Size) + (8 + subchunk2Size);
data = ByteBuffer.wrap(new byte[44]);
// Start copy data
data.order(ByteOrder.BIG_ENDIAN);
data.put(chunkID);
data.order(ByteOrder.LITTLE_ENDIAN);
data.putInt(chunkSize);
data.order(ByteOrder.BIG_ENDIAN);
data.put(format);
data.putInt(subchunk1ID);
data.order(ByteOrder.LITTLE_ENDIAN);
data.putInt(subchunk1Size);
data.putShort(audioFormat);
data.putShort(numChannels);
data.putInt(sampleRate);
data.putInt(byteRate);
data.putShort(blockAlign);
data.putShort(bitsPerSample);
data.order(ByteOrder.BIG_ENDIAN);
data.put(subchunk2ID);
data.order(ByteOrder.LITTLE_ENDIAN);
data.putInt(subchunk2Size);
FileOutputStream fStream = new FileOutputStream(file);
fStream.write(data.array());
int written = 0;
while(written < numSamples)
while(written < numSamples)
{
byte[] bytes = internalFormat.getChunk(startS + written, IO_CHUNK_SIZE);
if(bytes == null)
bytes = internalFormat.getChunk(startS + written, (int)(numSamples - written));
written += IO_CHUNK_SIZE;
fStream.write(bytes);
}
fStream.close();
}
// TODO: Create a more detailed description of exception
/**
* Imports a wave file, converts it to the internal format and returns it.
*
* @param path The path to the folder with the file to be loaded.
* @param filename The name of the file to be imported.
* @return Returns the file converted to the internal format
* @throws Exception Will throw an exception if something bad happens
*/
// TODO: Rewrite
public InternalFormat importFile(String path, String filename) throws IOException
{
ProgressView progress = ProgressView.getInstance();
// ByteBuffer buffer = FileHandler.loadFile( path, filename );
// buffer.order( ByteOrder.LITTLE_ENDIAN );
File file = new File(path, filename);
FileInputStream fStream = new FileInputStream(file);
DataInputStream dStream = new DataInputStream(fStream);
// Wave do not contain any tags
Tags tag = null;
dStream.skip(16);
// 4 little
int subChunk1Size = bigToLittleEndian(dStream.readInt());
dStream.skip(2);
// 2 little
int numChannels = bigToLittleEndian(dStream.readShort());
// 4 little
int sampleRate = bigToLittleEndian(dStream.readInt());
dStream.skip(6);
// 2 little
int bitsPerSample = bigToLittleEndian(dStream.readShort());
// Because subChunk1Size contains "16 + extra format bytes".
dStream.skip(subChunk1Size - 16);
dStream.skip(4);
// 4 little
int subChunk2Size = bigToLittleEndian(dStream.readInt());
InternalFormat internalFormat = new InternalFormat(tag, sampleRate, numChannels, bitsPerSample);
internalFormat.setFileStatus(new FileStatus(path, filename));
int written = 0;
byte b[] = new byte[IO_CHUNK_SIZE];
while(written < subChunk2Size)
{
int percent = Math.round(written / (float)subChunk2Size * 100);
progress.setValue(percent);
int read = dStream.read(b);
if(read < IO_CHUNK_SIZE)
{
byte[] bTemp = new byte[read];
System.arraycopy(b, 0, bTemp, 0, read);
b = bTemp;
}
internalFormat.insertSamples(written, b);
written += b.length;
}
dStream.close();
return internalFormat;
}
private static int bigToLittleEndian(int bigendian)
{
ByteBuffer buf = ByteBuffer.allocate(4);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putInt(bigendian);
buf.order(ByteOrder.LITTLE_ENDIAN);
return buf.getInt(0);
}
private static int bigToLittleEndian(short bigendian)
{
ByteBuffer buf = ByteBuffer.allocate(2);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(bigendian);
buf.order(ByteOrder.LITTLE_ENDIAN);
return buf.getShort(0);
}
}