/* For license see LICENSE.txt and COPYING.txt in the root directory */ package com.nerdscentral.audio.sound; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.Clip; import javax.sound.sampled.LineEvent; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.Mixer; import javax.sound.sampled.UnsupportedAudioFileException; import com.nerdscentral.audio.Messages; import com.nerdscentral.audio.core.SFConstants; import com.nerdscentral.audio.core.SFData; import com.nerdscentral.audio.core.SFSignal; import com.nerdscentral.audio.io.SFWavFileException; import com.nerdscentral.audio.io.SFWavSubsystem; import com.nerdscentral.sython.Caster; import com.nerdscentral.sython.SFMaths; import com.nerdscentral.sython.SFPL_RuntimeException; /** * A place to collection functions for SonicField. * * @author a1t * */ public final class SF2JavaSound { private final static int WRITE_BUFFER_SIZE = 8096; public final static List<String> getMixers() { Mixer.Info[] aInfos = AudioSystem.getMixerInfo(); List<String> ret = new ArrayList<>(); for (int i = 0; i < aInfos.length; i++) { ret.add(aInfos[i].getName()); } return ret; } public final static Mixer getMixer(String name) { Mixer.Info[] aInfos = AudioSystem.getMixerInfo(); Mixer ret = null; for (int i = 0; i < aInfos.length; i++) { if (aInfos[i].getName().equals(name)) { ret = AudioSystem.getMixer(aInfos[i]); } } return ret; } public final static SFLineListener playFile(String fileName, Mixer mixer) throws UnsupportedAudioFileException, IOException, LineUnavailableException { InputStream in = new FileInputStream(fileName); BufferedInputStream bin = new BufferedInputStream(in); AudioInputStream ain = AudioSystem.getAudioInputStream(bin); try (Clip clip = AudioSystem.getClip(mixer.getMixerInfo());) { clip.open(ain); SFLineListener ret; clip.addLineListener(ret = new SFLineListener() { private volatile boolean stopped = false; @Override public boolean hasStopped() { return this.stopped; } @Override public void update(LineEvent arg0) { if (arg0.getType() == LineEvent.Type.STOP) this.stopped = true; } }); clip.start(); return ret; } } public final static List<SFSignal> readFile(String fileName) throws SFPL_RuntimeException, UnsupportedAudioFileException, IOException { InputStream in = new FileInputStream(fileName); BufferedInputStream bin = new BufferedInputStream(in); try (AudioInputStream ain = AudioSystem.getAudioInputStream(bin);) { return getChannels(ain); } } public final static List<SFSignal> getChannels(AudioInputStream ain) throws SFPL_RuntimeException, IOException { AudioFormat af = ain.getFormat(); int nChannels = af.getChannels(); AudioFormat outDataFormat = new AudioFormat((float) SFConstants.SAMPLE_RATE, 32, nChannels, true, true); if (AudioSystem.isConversionSupported(outDataFormat, ain.getFormat())) { try (AudioInputStream interalAs = AudioSystem.getAudioInputStream(outDataFormat, ain);) { double frameCount = ain.getFrameLength(); frameCount = frameCount * SFConstants.SAMPLE_RATE / ain.getFormat().getFrameRate(); float[][] data = new float[nChannels][(int) SFMaths.ceil(frameCount)]; int expectedBuffSize = 4 * nChannels; byte[] buff = new byte[expectedBuffSize]; for (int pos = 0; pos < frameCount; ++pos) { if (expectedBuffSize != interalAs.read(buff)) { throw new SFPL_RuntimeException(Messages.getString("SF2JavaSound.1")); //$NON-NLS-1$ } int bufferPointer = 0; for (int channel = 0; channel < nChannels; ++channel) { int sample = 0; sample |= buff[bufferPointer++] & 255; sample <<= 8; sample |= buff[bufferPointer++] & 255; sample <<= 8; sample |= buff[bufferPointer++] & 255; sample <<= 8; sample |= buff[bufferPointer++] & 255; data[channel][pos] = (((float) sample) / ((float) Integer.MAX_VALUE)); } } List<SFSignal> ret = new ArrayList<>(nChannels); for (int c = 0; c < nChannels; ++c) { ret.add(SFData.build(data[c])); } return ret; } } throw new SFPL_RuntimeException(Messages.getString("SF2JavaSound.0")); //$NON-NLS-1$ } public static void WriteWav(String fileName, List<Object> channels, boolean hiRes) throws IOException, SFWavFileException, SFPL_RuntimeException { int sampleRate = (int) SFConstants.SAMPLE_RATE; // Samples per second int nChannels = channels.size(); if (nChannels == 0) throw new SFPL_RuntimeException(Messages.getString("SF2JavaSound.2")); //$NON-NLS-1$ // Calculate the number of frames required for specified duration, if // channels are not // the same length then the short channels will be zero'ed at the ends int numFrames = 0; for (int channelRR = 0; channelRR < nChannels; ++channelRR) { SFSignal data = Caster.makeSFSignal(channels.get(0)); int l = data.getLength(); if (l > numFrames) numFrames = l; } // Create a wav file with the name specified as the first argument SFWavSubsystem wavFile = SFWavSubsystem.newWavFile(new File(fileName), nChannels, numFrames, hiRes ? 32 : 16, sampleRate); // Create a buffer of 100 frames double[][] buffer = new double[nChannels][WRITE_BUFFER_SIZE]; // Initialise a local frame counter int frameCounter = 0; List<SFSignal> datas = new ArrayList<>(); for (int c = 0; c < channels.size(); ++c) { datas.add(Caster.makeSFSignal(channels.get(c))); } // Loop until all frames written while (frameCounter < numFrames) { // Determine how many frames to write, up to a maximum of the buffer // size long remaining = wavFile.getFramesRemaining(); int toWrite = (remaining > WRITE_BUFFER_SIZE) ? WRITE_BUFFER_SIZE : (int) remaining; // Fill the buffer, one tone per channel for (int s = 0; s < toWrite; s++, frameCounter++) { for (int channelRR = 0; channelRR < nChannels; ++channelRR) { SFSignal d = datas.get(channelRR); double dd = d.getLength() > frameCounter ? d.getSample(frameCounter) : 0; buffer[channelRR][s] = dd; } } // Write the buffer wavFile.writeFrames(buffer, toWrite); } // Close the wavFile wavFile.close(); } }