diff options
Diffstat (limited to 'src/com/jsyn/util')
28 files changed, 0 insertions, 4110 deletions
diff --git a/src/com/jsyn/util/AudioSampleLoader.java b/src/com/jsyn/util/AudioSampleLoader.java deleted file mode 100644 index b665933..0000000 --- a/src/com/jsyn/util/AudioSampleLoader.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2009 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import com.jsyn.data.FloatSample; - -public interface AudioSampleLoader { - /** - * Load a FloatSample from a File object. - */ - public FloatSample loadFloatSample(File fileIn) throws IOException; - - /** - * Load a FloatSample from an InputStream. This is handy when loading Resources from a JAR file. - */ - public FloatSample loadFloatSample(InputStream inputStream) throws IOException; - - /** - * Load a FloatSample from a URL.. This is handy when loading Resources from a website. - */ - public FloatSample loadFloatSample(URL url) throws IOException; - -} diff --git a/src/com/jsyn/util/AudioStreamReader.java b/src/com/jsyn/util/AudioStreamReader.java deleted file mode 100644 index 5a725c3..0000000 --- a/src/com/jsyn/util/AudioStreamReader.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2010 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import com.jsyn.Synthesizer; -import com.jsyn.io.AudioFifo; -import com.jsyn.io.AudioInputStream; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.unitgen.MonoStreamWriter; -import com.jsyn.unitgen.StereoStreamWriter; -import com.jsyn.unitgen.UnitStreamWriter; - -/** - * Reads audio signals from the background engine to a foreground application through an AudioFifo. - * Connect to the input port returned by getInput(). - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class AudioStreamReader implements AudioInputStream { - private UnitStreamWriter streamWriter; - private AudioFifo fifo; - - public AudioStreamReader(Synthesizer synth, int samplesPerFrame) { - if (samplesPerFrame == 1) { - streamWriter = new MonoStreamWriter(); - } else if (samplesPerFrame == 2) { - streamWriter = new StereoStreamWriter(); - } else { - throw new IllegalArgumentException("Only 1 or 2 samplesPerFrame supported."); - } - synth.add(streamWriter); - - fifo = new AudioFifo(); - fifo.setWriteWaitEnabled(!synth.isRealTime()); - fifo.setReadWaitEnabled(true); - fifo.allocate(32 * 1024); - streamWriter.setOutputStream(fifo); - streamWriter.start(); - } - - public UnitInputPort getInput() { - return streamWriter.input; - } - - /** How many values are available to read without blocking? */ - @Override - public int available() { - return fifo.available(); - } - - @Override - public void close() { - fifo.close(); - } - - @Override - public double read() { - return fifo.read(); - } - - @Override - public int read(double[] buffer) { - return fifo.read(buffer); - } - - @Override - public int read(double[] buffer, int start, int count) { - return fifo.read(buffer, start, count); - } - -} diff --git a/src/com/jsyn/util/AutoCorrelator.java b/src/com/jsyn/util/AutoCorrelator.java deleted file mode 100644 index 2996036..0000000 --- a/src/com/jsyn/util/AutoCorrelator.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright 2004 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -/** - * Calculate period of a repeated waveform in an array. This algorithm is based on a normalized - * auto-correlation function as dewscribed in: "A Smarter Way to Find Pitch" by Philip McLeod and - * Geoff Wyvill - * - * @author (C) 2004 Mobileer, PROPRIETARY and CONFIDENTIAL - */ -public class AutoCorrelator implements SignalCorrelator { - // A higher number will reject suboctaves more. - private static final float SUB_OCTAVE_REJECTION_FACTOR = 0.0005f; - // We can focus our analysis on the maxima - private static final int STATE_SEEKING_NEGATIVE = 0; - private static final int STATE_SEEKING_POSITIVE = 1; - private static final int STATE_SEEKING_MAXIMUM = 2; - private static final int[] tauAdvanceByState = { - 4, 2, 1 - }; - private int state; - - private float[] buffer; - // double buffer the diffs so we can view them - private float[] diffs; - private float[] diffs1; - private float[] diffs2; - private int cursor = -1; - private int tau; - - private float sumProducts; - private float sumSquares; - private float localMaximum; - private int localPosition; - private float bestMaximum; - private int bestPosition; - private int peakCounter; - // This factor was found empirically to reduce a systematic offset in the pitch. - private float pitchCorrectionFactor = 0.99988f; - - // Results of analysis. - private double period; - private double confidence; - private int minPeriod = 2; - private boolean bufferValid; - private double previousSample = 0.0; - private int maxWindowSize; - private float noiseThreshold = 0.001f; - - public AutoCorrelator(int numFrames) { - buffer = new float[numFrames]; - maxWindowSize = buffer.length / 2; - diffs1 = new float[2 + numFrames / 2]; - diffs2 = new float[diffs1.length]; - diffs = diffs1; - period = minPeriod; - reset(); - } - - // Scan assuming we will not wrap around the buffer. - private void rawDeltaScan(int last1, int last2, int count, int stride) { - for (int k = 0; k < count; k += stride) { - float d1 = buffer[last1 - k]; - float d2 = buffer[last2 - k]; - sumProducts += d1 * d2; - sumSquares += ((d1 * d1) + (d2 * d2)); - } - } - - // Do correlation when we know the splitLast will wrap around. - private void splitDeltaScan(int last1, int splitLast, int count, int stride) { - int c1 = splitLast; - rawDeltaScan(last1, splitLast, c1, stride); - rawDeltaScan(last1 - c1, buffer.length - 1, count - c1, stride); - } - - private void checkDeltaScan(int last1, int last2, int count, int stride) { - if (count > last2) { - int c1 = last2; - // Use recursion with reverse indexes to handle a double split. - checkDeltaScan(last2, last1, c1, stride); - checkDeltaScan(buffer.length - 1, last1 - c1, count - c1, stride); - } else if (count > last1) { - splitDeltaScan(last2, last1, count, stride); - } else { - rawDeltaScan(last1, last2, count, stride); - } - } - - // Perform correlation. Handle circular buffer wrap around. - // Normalized square difference function between -1.0 and +1.0. - private float topScan(int last1, int tau, int count, int stride) { - final float minimumResult = 0.00000001f; - - int last2 = last1 - tau; - if (last2 < 0) { - last2 += buffer.length; - } - sumProducts = 0.0f; - sumSquares = 0.0f; - checkDeltaScan(last1, last2, count, stride); - // Prevent divide by zero. - if (sumSquares < minimumResult) { - return minimumResult; - } - float correction = (float) Math.pow(pitchCorrectionFactor, tau); - float result = (float) (2.0 * sumProducts / sumSquares) * correction; - - return result; - } - - // Prepare for a new calculation. - private void reset() { - switchDiffs(); - int i = 0; - for (; i < minPeriod; i++) { - diffs[i] = 1.0f; - } - for (; i < diffs.length; i++) { - diffs[i] = 0.0f; - } - tau = minPeriod; - state = STATE_SEEKING_NEGATIVE; - peakCounter = 0; - bestMaximum = -1.0f; - bestPosition = -1; - } - - // Analyze new diff result. Incremental peak detection. - private void nextPeakAnalysis(int index) { - // Scale low frequency correlation down to reduce suboctave matching. - // Note that this has a side effect of reducing confidence value for low frequency sounds. - float value = diffs[index] * (1.0f - (index * SUB_OCTAVE_REJECTION_FACTOR)); - switch (state) { - case STATE_SEEKING_NEGATIVE: - if (value < -0.01f) { - state = STATE_SEEKING_POSITIVE; - } - break; - case STATE_SEEKING_POSITIVE: - if (value > 0.2f) { - state = STATE_SEEKING_MAXIMUM; - localMaximum = value; - localPosition = index; - } - break; - case STATE_SEEKING_MAXIMUM: - if (value > localMaximum) { - localMaximum = value; - localPosition = index; - } else if (value < -0.1f) { - peakCounter += 1; - if (localMaximum > bestMaximum) { - bestMaximum = localMaximum; - bestPosition = localPosition; - } - state = STATE_SEEKING_POSITIVE; - } - break; - } - } - - /** - * Generate interpolated maximum from index of absolute maximum using three point analysis. - */ - private double findPreciseMaximum(int indexMax) { - if (indexMax < 3) { - return 3.0; - } - if (indexMax == (diffs.length - 1)) { - return indexMax; - } - // Get 3 adjacent values. - double d1 = diffs[indexMax - 1]; - double d2 = diffs[indexMax]; - double d3 = diffs[indexMax + 1]; - - return interpolatePeak(d1, d2, d3) + indexMax; - } - - // Use quadratic fit to return offset between -0.5 and +0.5 from center. - protected static double interpolatePeak(double d1, double d2, double d3) { - return 0.5 * (d1 - d3) / (d1 - (2.0 * d2) + d3); - } - - // Calculate a little more for each sample. - // This spreads the CPU load out more evenly. - private boolean incrementalAnalysis() { - boolean updated = false; - if (bufferValid) { - // int windowSize = maxWindowSize; - // Interpolate between tau and maxWindowsSize based on confidence. - // If confidence is low then use bigger window. - int windowSize = (int) ((tau * confidence) + (maxWindowSize * (1.0 - confidence))); - - int stride = 1; - // int stride = (windowSize / 32) + 1; - - diffs[tau] = topScan(cursor, tau, windowSize, stride); - - // Check to see if the signal is strong enough to analyze. - // Look at sumPeriods on first correlation. - if ((tau == minPeriod) && (sumProducts < noiseThreshold)) { - // Update if we are dropping to zero confidence. - boolean result = (confidence > 0.0); - confidence = 0.0; - return result; - } - - nextPeakAnalysis(tau); - - // Reuse calculated values if we are not near a peak. - tau += 1; - int advance = tauAdvanceByState[state] - 1; - while ((advance > 0) && (tau < diffs.length)) { - diffs[tau] = diffs[tau - 1]; - tau++; - advance--; - } - - if ((peakCounter >= 4) || (tau >= maxWindowSize)) { - if (bestMaximum > 0.0) { - period = findPreciseMaximum(bestPosition); - // clip into range 0.0 to 1.0, low values are really bogus - confidence = (bestMaximum < 0.0) ? 0.0 : bestMaximum; - } else { - confidence = 0.0; - } - updated = true; - reset(); - } - } - return updated; - } - - @Override - public float[] getDiffs() { - // Return diffs that are not currently being used - return (diffs == diffs1) ? diffs2 : diffs1; - } - - private void switchDiffs() { - diffs = (diffs == diffs1) ? diffs2 : diffs1; - } - - @Override - public boolean addSample(double value) { - double average = (value + previousSample) * 0.5; - previousSample = value; - - cursor += 1; - if (cursor == buffer.length) { - cursor = 0; - bufferValid = true; - } - buffer[cursor] = (float) average; - - return incrementalAnalysis(); - } - - @Override - public double getPeriod() { - return period; - } - - @Override - public double getConfidence() { - return confidence; - } - - public float getPitchCorrectionFactor() { - return pitchCorrectionFactor; - } - - public void setPitchCorrectionFactor(float pitchCorrectionFactor) { - this.pitchCorrectionFactor = pitchCorrectionFactor; - } -} diff --git a/src/com/jsyn/util/Instrument.java b/src/com/jsyn/util/Instrument.java deleted file mode 100644 index 8a53304..0000000 --- a/src/com/jsyn/util/Instrument.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2011 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import com.softsynth.shared.time.TimeStamp; - -/** - * A note player that references one or more voices by a noteNumber. This is similar to the MIDI - * protocol that references voices by an integer pitch or keyIndex. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public interface Instrument { - // This will be applied to the voice when noteOn is called. - void usePreset(int presetIndex, TimeStamp timeStamp); - - public void noteOn(int tag, double frequency, double amplitude, TimeStamp timeStamp); - - public void noteOff(int tag, TimeStamp timeStamp); - - public void setPort(int tag, String portName, double value, TimeStamp timeStamp); - - public void allNotesOff(TimeStamp timeStamp); -} diff --git a/src/com/jsyn/util/InstrumentLibrary.java b/src/com/jsyn/util/InstrumentLibrary.java deleted file mode 100644 index 65113dc..0000000 --- a/src/com/jsyn/util/InstrumentLibrary.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2011 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import com.jsyn.swing.InstrumentBrowser; - -/** - * A library of instruments that can be used to play notes. - * - * @author Phil Burk (C) 2011 Mobileer Inc - * @see InstrumentBrowser - */ - -public interface InstrumentLibrary { - public String getName(); - - public VoiceDescription[] getVoiceDescriptions(); -} diff --git a/src/com/jsyn/util/JavaSoundSampleLoader.java b/src/com/jsyn/util/JavaSoundSampleLoader.java deleted file mode 100644 index 56a654e..0000000 --- a/src/com/jsyn/util/JavaSoundSampleLoader.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2011 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioInputStream; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.UnsupportedAudioFileException; - -import com.jsyn.data.FloatSample; - -/** - * Internal class for loading audio samples. Use SampleLoader instead. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -class JavaSoundSampleLoader implements AudioSampleLoader { - /** - * Load a FloatSample from a File object. - */ - @Override - public FloatSample loadFloatSample(File fileIn) throws IOException { - try { - return loadFloatSample(AudioSystem.getAudioInputStream(fileIn)); - } catch (UnsupportedAudioFileException e) { - throw new IOException(e); - } - } - - /** - * Load a FloatSample from an InputStream. This is handy when loading Resources from a JAR file. - */ - @Override - public FloatSample loadFloatSample(InputStream inputStream) throws IOException { - try { - return loadFloatSample(AudioSystem.getAudioInputStream(inputStream)); - } catch (UnsupportedAudioFileException e) { - throw new IOException(e); - } - } - - /** - * Load a FloatSample from a URL.. This is handy when loading Resources from a website. - */ - @Override - public FloatSample loadFloatSample(URL url) throws IOException { - try { - return loadFloatSample(AudioSystem.getAudioInputStream(url)); - } catch (UnsupportedAudioFileException e) { - throw new IOException(e); - } - } - - private FloatSample loadFloatSample(javax.sound.sampled.AudioInputStream audioInputStream) - throws IOException, UnsupportedAudioFileException { - float[] floatData = null; - FloatSample sample = null; - int bytesPerFrame = audioInputStream.getFormat().getFrameSize(); - if (bytesPerFrame == AudioSystem.NOT_SPECIFIED) { - // some audio formats may have unspecified frame size - // in that case we may read any amount of bytes - bytesPerFrame = 1; - } - AudioFormat format = audioInputStream.getFormat(); - if (format.getEncoding() == AudioFormat.Encoding.PCM_SIGNED) { - floatData = loadSignedPCM(audioInputStream); - } - sample = new FloatSample(floatData, format.getChannels()); - sample.setFrameRate(format.getFrameRate()); - return sample; - } - - private float[] loadSignedPCM(AudioInputStream audioInputStream) throws IOException, - UnsupportedAudioFileException { - int totalSamplesRead = 0; - AudioFormat format = audioInputStream.getFormat(); - int numFrames = (int) audioInputStream.getFrameLength(); - int numSamples = format.getChannels() * numFrames; - float[] data = new float[numSamples]; - final int bytesPerFrame = format.getFrameSize(); - // Set an arbitrary buffer size of 1024 frames. - int numBytes = 1024 * bytesPerFrame; - byte[] audioBytes = new byte[numBytes]; - int numBytesRead = 0; - int numFramesRead = 0; - // Try to read numBytes bytes from the file. - while ((numBytesRead = audioInputStream.read(audioBytes)) != -1) { - int bytesRemainder = numBytesRead % bytesPerFrame; - if (bytesRemainder != 0) { - // TODO Read until you get enough data. - throw new IOException("Read partial block of sample data!"); - } - - if (audioInputStream.getFormat().getSampleSizeInBits() == 16) { - if (format.isBigEndian()) { - SampleLoader.decodeBigI16ToF32(audioBytes, 0, numBytesRead, data, - totalSamplesRead); - } else { - SampleLoader.decodeLittleI16ToF32(audioBytes, 0, numBytesRead, data, - totalSamplesRead); - } - } else if (audioInputStream.getFormat().getSampleSizeInBits() == 24) { - if (format.isBigEndian()) { - SampleLoader.decodeBigI24ToF32(audioBytes, 0, numBytesRead, data, - totalSamplesRead); - } else { - SampleLoader.decodeLittleI24ToF32(audioBytes, 0, numBytesRead, data, - totalSamplesRead); - } - } else if (audioInputStream.getFormat().getSampleSizeInBits() == 32) { - if (format.isBigEndian()) { - SampleLoader.decodeBigI32ToF32(audioBytes, 0, numBytesRead, data, - totalSamplesRead); - } else { - SampleLoader.decodeLittleI32ToF32(audioBytes, 0, numBytesRead, data, - totalSamplesRead); - } - } else { - throw new UnsupportedAudioFileException( - "Only 16, 24 or 32 bit PCM samples supported."); - } - - // Calculate the number of frames actually read. - numFramesRead = numBytesRead / bytesPerFrame; - totalSamplesRead += numFramesRead * format.getChannels(); - } - return data; - } - -} diff --git a/src/com/jsyn/util/JavaTools.java b/src/com/jsyn/util/JavaTools.java deleted file mode 100644 index 65e3dd1..0000000 --- a/src/com/jsyn/util/JavaTools.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2009 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -public class JavaTools { - - @SuppressWarnings("rawtypes") - public static Class loadClass(String className, boolean verbose) { - Class newClass = null; - try { - newClass = Class.forName(className); - } catch (Throwable e) { - if (verbose) - System.out.println("Caught " + e); - } - if (newClass == null) { - try { - ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); - newClass = Class.forName(className, true, systemLoader); - } catch (Throwable e) { - if (verbose) - System.out.println("Caught " + e); - } - } - return newClass; - } - - /** - * First try Class.forName(). If this fails, try Class.forName() using - * ClassLoader.getSystemClassLoader(). - * - * @return Class or null - */ - @SuppressWarnings("rawtypes") - public static Class loadClass(String className) { - /** - * First try Class.forName(). If this fails, try Class.forName() using - * ClassLoader.getSystemClassLoader(). - * - * @return Class or null - */ - return loadClass(className, true); - } - -} diff --git a/src/com/jsyn/util/MultiChannelSynthesizer.java b/src/com/jsyn/util/MultiChannelSynthesizer.java deleted file mode 100644 index b84d753..0000000 --- a/src/com/jsyn/util/MultiChannelSynthesizer.java +++ /dev/null @@ -1,404 +0,0 @@ -/* - * Copyright 2016 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import com.jsyn.Synthesizer; -import com.jsyn.engine.SynthesisEngine; -import com.jsyn.midi.MidiConstants; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.unitgen.ExponentialRamp; -import com.jsyn.unitgen.LinearRamp; -import com.jsyn.unitgen.Multiply; -import com.jsyn.unitgen.Pan; -import com.jsyn.unitgen.PowerOfTwo; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.TwoInDualOut; -import com.jsyn.unitgen.UnitGenerator; -import com.jsyn.unitgen.UnitOscillator; -import com.jsyn.unitgen.UnitVoice; -import com.softsynth.math.AudioMath; -import com.softsynth.shared.time.TimeStamp; - -/** - * General purpose synthesizer with "channels" - * that could be used to implement a MIDI synthesizer. - * - * Each channel has: - * <pre><code> - * lfo -> pitchToLinear -> [VOICES] -> volume* -> panner - * bend --/ - * </code></pre> - * - * Note: this class is experimental and subject to change. - * - * @author Phil Burk (C) 2016 Mobileer Inc - */ -public class MultiChannelSynthesizer { - private Synthesizer synth; - private TwoInDualOut outputUnit; - private ChannelContext[] channels; - private final static int MAX_VELOCITY = 127; - private double mMasterAmplitude = 0.25; - - private class ChannelGroupContext { - private VoiceDescription voiceDescription; - private UnitVoice[] voices; - private VoiceAllocator allocator; - - ChannelGroupContext(int numVoices, VoiceDescription voiceDescription) { - this.voiceDescription = voiceDescription; - - voices = new UnitVoice[numVoices]; - for (int i = 0; i < numVoices; i++) { - UnitVoice voice = voiceDescription.createUnitVoice(); - UnitGenerator ugen = voice.getUnitGenerator(); - synth.add(ugen); - voices[i] = voice; - - } - allocator = new VoiceAllocator(voices); - } - } - - private class ChannelContext { - private UnitOscillator lfo; - private PowerOfTwo pitchToLinear; - private LinearRamp timbreRamp; - private LinearRamp pressureRamp; - private ExponentialRamp volumeRamp; - private Multiply volumeMultiplier; - private Pan panner; - private double vibratoRate = 5.0; - private double bendRangeOctaves = 2.0 / 12.0; - private int presetIndex; - private ChannelGroupContext groupContext; - VoiceOperation voiceOperation = new VoiceOperation() { - @Override - public void operate (UnitVoice voice) { - voice.usePreset(presetIndex); - connectVoice(voice); - } - }; - - void setup(ChannelGroupContext groupContext) { - this.groupContext = groupContext; - synth.add(pitchToLinear = new PowerOfTwo()); - synth.add(lfo = new SineOscillator()); // TODO use a MorphingOscillator or switch - // between S&H etc. - // Use a ramp to smooth out the timbre changes. - // This helps reduce pops from changing filter cutoff too abruptly. - synth.add(timbreRamp = new LinearRamp()); - timbreRamp.time.set(0.02); - synth.add(pressureRamp = new LinearRamp()); - pressureRamp.time.set(0.02); - synth.add(volumeRamp = new ExponentialRamp()); - volumeRamp.input.set(1.0); - volumeRamp.time.set(0.02); - synth.add(volumeMultiplier = new Multiply()); - synth.add(panner = new Pan()); - - pitchToLinear.input.setValueAdded(true); // so we can sum pitch bend - lfo.output.connect(pitchToLinear.input); - lfo.amplitude.set(0.0); - lfo.frequency.set(vibratoRate); - - volumeRamp.output.connect(volumeMultiplier.inputB); - volumeMultiplier.output.connect(panner.input); - panner.output.connect(0, outputUnit.inputA, 0); // Use MultiPassthrough - panner.output.connect(1, outputUnit.inputB, 0); - } - - private void connectVoice(UnitVoice voice) { - UnitGenerator ugen = voice.getUnitGenerator(); - // Hook up some channel controllers to standard ports on the voice. - UnitInputPort freqMod = (UnitInputPort) ugen - .getPortByName(UnitGenerator.PORT_NAME_FREQUENCY_SCALER); - if (freqMod != null) { - freqMod.disconnectAll(); - pitchToLinear.output.connect(freqMod); - } - UnitInputPort timbrePort = (UnitInputPort) ugen - .getPortByName(UnitGenerator.PORT_NAME_TIMBRE); - if (timbrePort != null) { - timbrePort.disconnectAll(); - timbreRamp.output.connect(timbrePort); - timbreRamp.input.setup(timbrePort); - } - UnitInputPort pressurePort = (UnitInputPort) ugen - .getPortByName(UnitGenerator.PORT_NAME_PRESSURE); - if (pressurePort != null) { - pressurePort.disconnectAll(); - pressureRamp.output.connect(pressurePort); - pressureRamp.input.setup(pressurePort); - } - voice.getOutput().disconnectAll(); - voice.getOutput().connect(volumeMultiplier.inputA); // mono mix all the voices - } - - void programChange(int program) { - int programWrapped = program % groupContext.voiceDescription.getPresetCount(); - String name = groupContext.voiceDescription.getPresetNames()[programWrapped]; - //System.out.println("Preset[" + program + "] = " + name); - presetIndex = programWrapped; - } - - void noteOff(int noteNumber, double amplitude) { - groupContext.allocator.noteOff(noteNumber, synth.createTimeStamp()); - } - - void noteOff(int noteNumber, double amplitude, TimeStamp timeStamp) { - groupContext.allocator.noteOff(noteNumber, timeStamp); - } - - void noteOn(int noteNumber, double amplitude) { - noteOn(noteNumber, amplitude, synth.createTimeStamp()); - } - - void noteOn(int noteNumber, double amplitude, TimeStamp timeStamp) { - double frequency = AudioMath.pitchToFrequency(noteNumber); - //System.out.println("noteOn(noteNumber) -> " + frequency + " Hz"); - groupContext.allocator.noteOn(noteNumber, frequency, amplitude, voiceOperation, timeStamp); - } - - public void setPitchBend(double offset) { - pitchToLinear.input.set(bendRangeOctaves * offset); - } - - public void setBendRange(double semitones) { - bendRangeOctaves = semitones / 12.0; - } - - public void setVibratoDepth(double semitones) { - lfo.amplitude.set(semitones); - } - - public void setVolume(double volume) { - double min = SynthesisEngine.DB96; - double max = 1.0; - double ratio = max / min; - double value = min * Math.pow(ratio, volume); - volumeRamp.input.set(value); - } - - public void setPan(double pan) { - panner.pan.set(pan); - } - - /* - * @param timbre normalized 0 to 1 - */ - public void setTimbre(double timbre) { - double min = timbreRamp.input.getMinimum(); - double max = timbreRamp.input.getMaximum(); - double value = min + (timbre * (max - min)); - timbreRamp.input.set(value); - } - - /* - * @param pressure normalized 0 to 1 - */ - public void setPressure(double pressure) { - double min = pressureRamp.input.getMinimum(); - double max = pressureRamp.input.getMaximum(); - double ratio = max / min; - double value = min * Math.pow(ratio, pressure); - pressureRamp.input.set(value); - } - } - - /** - * Construct a synthesizer with a maximum of 16 channels like MIDI. - */ - public MultiChannelSynthesizer() { - this(MidiConstants.MAX_CHANNELS); - } - - - public MultiChannelSynthesizer(int maxChannels) { - channels = new ChannelContext[maxChannels]; - for (int i = 0; i < channels.length; i++) { - channels[i] = new ChannelContext(); - } - } - - /** - * Specify a VoiceDescription to use with multiple channels. - * - * @param synth - * @param startChannel channel index is zero based - * @param numChannels - * @param voicesPerChannel - * @param voiceDescription - */ - public void setup(Synthesizer synth, int startChannel, int numChannels, int voicesPerChannel, - VoiceDescription voiceDescription) { - this.synth = synth; - if (outputUnit == null) { - synth.add(outputUnit = new TwoInDualOut()); - } - ChannelGroupContext groupContext = new ChannelGroupContext(voicesPerChannel, - voiceDescription); - for (int i = 0; i < numChannels; i++) { - channels[startChannel + i].setup(groupContext); - } - } - - public void programChange(int channel, int program) { - ChannelContext channelContext = channels[channel]; - channelContext.programChange(program); - } - - - /** - * Turn off a note. - * @param channel - * @param noteNumber - * @param velocity between 0 and 127, will be scaled by masterAmplitude - */ - public void noteOff(int channel, int noteNumber, int velocity) { - double amplitude = velocity * (1.0 / MAX_VELOCITY); - noteOff(channel, noteNumber, amplitude); - } - - /** - * Turn off a note. - * @param channel - * @param noteNumber - * @param amplitude between 0 and 1.0, will be scaled by masterAmplitude - */ - public void noteOff(int channel, int noteNumber, double amplitude) { - ChannelContext channelContext = channels[channel]; - channelContext.noteOff(noteNumber, amplitude * mMasterAmplitude); - } - - /** - * Turn off a note. - * @param channel - * @param noteNumber - * @param amplitude between 0 and 1.0, will be scaled by masterAmplitude - */ - public void noteOff(int channel, int noteNumber, double amplitude, TimeStamp timeStamp) { - ChannelContext channelContext = channels[channel]; - channelContext.noteOff(noteNumber, amplitude * mMasterAmplitude, timeStamp); - } - - /** - * Turn on a note. - * @param channel - * @param noteNumber - * @param velocity between 0 and 127, will be scaled by masterAmplitude - */ - public void noteOn(int channel, int noteNumber, int velocity) { - double amplitude = velocity * (1.0 / MAX_VELOCITY); - noteOn(channel, noteNumber, amplitude); - } - - /** - * Turn on a note. - * @param channel - * @param noteNumber - * @param amplitude between 0 and 1.0, will be scaled by masterAmplitude - */ - public void noteOn(int channel, int noteNumber, double amplitude, TimeStamp timeStamp) { - ChannelContext channelContext = channels[channel]; - channelContext.noteOn(noteNumber, amplitude * mMasterAmplitude, timeStamp); - } - - /** - * Turn on a note. - * @param channel - * @param noteNumber - * @param amplitude between 0 and 1.0, will be scaled by masterAmplitude - */ - public void noteOn(int channel, int noteNumber, double amplitude) { - ChannelContext channelContext = channels[channel]; - channelContext.noteOn(noteNumber, amplitude * mMasterAmplitude); - } - - /** - * Set a pitch offset that will be scaled by the range for the channel. - * - * @param channel - * @param offset ranges from -1.0 to +1.0 - */ - public void setPitchBend(int channel, double offset) { - //System.out.println("setPitchBend[" + channel + "] = " + offset); - ChannelContext channelContext = channels[channel]; - channelContext.setPitchBend(offset); - } - - public void setBendRange(int channel, double semitones) { - ChannelContext channelContext = channels[channel]; - channelContext.setBendRange(semitones); - } - - public void setPressure(int channel, double pressure) { - ChannelContext channelContext = channels[channel]; - channelContext.setPressure(pressure); - } - - public void setVibratoDepth(int channel, double semitones) { - ChannelContext channelContext = channels[channel]; - channelContext.setVibratoDepth(semitones); - } - - public void setTimbre(int channel, double timbre) { - ChannelContext channelContext = channels[channel]; - channelContext.setTimbre(timbre); - } - - /** - * Set volume for entire channel. - * - * @param channel - * @param volume normalized between 0.0 and 1.0 - */ - public void setVolume(int channel, double volume) { - ChannelContext channelContext = channels[channel]; - channelContext.setVolume(volume); - } - - /** - * Pan from left to right. - * - * @param channel - * @param pan ranges from -1.0 to +1.0 - */ - public void setPan(int channel, double pan) { - ChannelContext channelContext = channels[channel]; - channelContext.setPan(pan); - } - - /** - * @return stereo output port - */ - public UnitOutputPort getOutput() { - return outputUnit.output; - } - - /** - * Set amplitude for a single voice when the velocity is 127. - * @param masterAmplitude - */ - public void setMasterAmplitude(double masterAmplitude) { - mMasterAmplitude = masterAmplitude; - } - public double getMasterAmplitude() { - return mMasterAmplitude; - } -} diff --git a/src/com/jsyn/util/NumericOutput.java b/src/com/jsyn/util/NumericOutput.java deleted file mode 100644 index 79afaf1..0000000 --- a/src/com/jsyn/util/NumericOutput.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 1999 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -/** - * Formatted numeric output. Convert integers and floats to strings based on field widths and - * desired decimal places. - * - * @author Phil Burk (C) 1999 SoftSynth.com - */ - -public class NumericOutput { - static char digitToChar(int digit) { - if (digit > 9) { - return (char) ('A' + digit - 10); - } else { - return (char) ('0' + digit); - } - } - - public static String integerToString(int n, int width, boolean leadingZeros) { - return integerToString(n, width, leadingZeros, 10); - } - - public static String integerToString(int n, int width) { - return integerToString(n, width, false, 10); - } - - public static String integerToString(int n, int width, boolean leadingZeros, int radix) { - if (width > 32) - width = 32; - StringBuffer buf = new StringBuffer(); - long ln = n; - boolean ifNeg = false; - // only do sign if decimal - if (radix != 10) { - // System.out.println("MASK before : ln = " + ln ); - ln = ln & 0x00000000FFFFFFFFL; - // System.out.println("MASK after : ln = " + ln ); - } else if (ln < 0) { - ifNeg = true; - ln = -ln; - } - if (ln == 0) { - buf.append('0'); - } else { - // System.out.println(" ln = " + ln ); - while (ln > 0) { - int rem = (int) (ln % radix); - buf.append(digitToChar(rem)); - ln = ln / radix; - } - } - if (leadingZeros) { - int pl = width; - if (ifNeg) - pl -= 1; - for (int i = buf.length(); i < pl; i++) - buf.append('0'); - } - if (ifNeg) - buf.append('-'); - // leading spaces - for (int i = buf.length(); i < width; i++) - buf.append(' '); - // reverse buffer to put characters in correct order - buf.reverse(); - - return buf.toString(); - } - - /** - * Convert double to string. - * - * @param width = minimum width of formatted string - * @param places = number of digits displayed after decimal point - */ - public static String doubleToString(double value, int width, int places) { - return doubleToString(value, width, places, false); - } - - /** - * Convert double to string. - * - * @param width = minimum width of formatted string - * @param places = number of digits displayed after decimal point - */ - public static String doubleToString(double value, int width, int places, boolean leadingZeros) { - if (width > 32) - width = 32; - if (places > 16) - places = 16; - - boolean ifNeg = false; - if (value < 0.0) { - ifNeg = true; - value = -value; - } - // round at relevant decimal place - value += 0.5 * Math.pow(10.0, 0 - places); - int ival = (int) Math.floor(value); - // get portion after decimal point as an integer - int fval = (int) ((value - Math.floor(value)) * Math.pow(10.0, places)); - String result = ""; - - result += integerToString(ival, 0, false, 10); - result += "."; - result += integerToString(fval, places, true, 10); - - if (leadingZeros) { - // prepend leading zeros and {-} - int zw = width; - if (ifNeg) - zw -= 1; - while (result.length() < zw) - result = "0" + result; - if (ifNeg) - result = "-" + result; - } else { - // prepend {-} and leading spaces - if (ifNeg) - result = "-" + result; - while (result.length() < width) - result = " " + result; - } - return result; - } - - static void testInteger(int n) { - System.out.println("Test " + n + ", 0x" + Integer.toHexString(n) + ", %" - + Integer.toBinaryString(n)); - System.out.println(" +,8,t,10 = " + integerToString(n, 8, true, 10)); - System.out.println(" +,8,f,10 = " + integerToString(n, 8, false, 10)); - System.out.println(" -,8,t,10 = " + integerToString(-n, 8, true, 10)); - System.out.println(" -,8,f,10 = " + integerToString(-n, 8, false, 10)); - System.out.println(" +,8,t,16 = " + integerToString(n, 8, true, 16)); - System.out.println(" +,8,f,16 = " + integerToString(n, 8, false, 16)); - System.out.println(" -,8,t,16 = " + integerToString(-n, 8, true, 16)); - System.out.println(" -,8,f,16 = " + integerToString(-n, 8, false, 16)); - System.out.println(" +,8,t, 2 = " + integerToString(n, 8, true, 2)); - System.out.println(" +,8,f, 2 = " + integerToString(n, 8, false, 2)); - } - - static void testDouble(double value) { - System.out.println("Test " + value); - System.out.println(" +,5,1 = " + doubleToString(value, 5, 1)); - System.out.println(" -,5,1 = " + doubleToString(-value, 5, 1)); - - System.out.println(" +,14,3 = " + doubleToString(value, 14, 3)); - System.out.println(" -,14,3 = " + doubleToString(-value, 14, 3)); - - System.out.println(" +,6,2,true = " + doubleToString(value, 6, 2, true)); - System.out.println(" -,6,2,true = " + doubleToString(-value, 6, 2, true)); - } - - public static void main(String argv[]) { - System.out.println("Test NumericOutput"); - testInteger(0); - testInteger(1); - testInteger(16); - testInteger(23456); - testInteger(0x23456); - testInteger(0x89ABC); - testDouble(0.0); - testDouble(0.0678); - testDouble(0.1234567); - testDouble(1.234567); - testDouble(12.34567); - testDouble(123.4567); - testDouble(1234.5678); - - } -} diff --git a/src/com/jsyn/util/PolyphonicInstrument.java b/src/com/jsyn/util/PolyphonicInstrument.java deleted file mode 100644 index 08460d0..0000000 --- a/src/com/jsyn/util/PolyphonicInstrument.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2011 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.ports.UnitPort; -import com.jsyn.unitgen.Circuit; -import com.jsyn.unitgen.Multiply; -import com.jsyn.unitgen.PassThrough; -import com.jsyn.unitgen.UnitGenerator; -import com.jsyn.unitgen.UnitSource; -import com.jsyn.unitgen.UnitVoice; -import com.softsynth.shared.time.TimeStamp; - -/** - * The API for this class is likely to change. Please comment on its usefulness. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ - -public class PolyphonicInstrument extends Circuit implements UnitSource, Instrument { - private Multiply mixer; - private UnitVoice[] voices; - private VoiceAllocator voiceAllocator; - public UnitInputPort amplitude; - - public PolyphonicInstrument(UnitVoice[] voices) { - this.voices = voices; - voiceAllocator = new VoiceAllocator(voices); - add(mixer = new Multiply()); - // Mix all the voices to one output. - for (UnitVoice voice : voices) { - UnitGenerator unit = voice.getUnitGenerator(); - boolean wasEnabled = unit.isEnabled(); - // This overrides the enabled property of the voice. - add(unit); - voice.getOutput().connect(mixer.inputA); - // restore - unit.setEnabled(wasEnabled); - } - - addPort(amplitude = mixer.inputB, "Amplitude"); - amplitude.setup(0.0001, 0.4, 2.0); - exportAllInputPorts(); - } - - /** - * Connect a PassThrough unit to the input ports of the voices so that they can be controlled - * together using a single port. Note that this will prevent their individual use. So the - * "Frequency" and "Amplitude" ports are excluded. Note that this method is a bit funky and is - * likely to change. - */ - public void exportAllInputPorts() { - // Iterate through the ports. - for (UnitPort port : voices[0].getUnitGenerator().getPorts()) { - if (port instanceof UnitInputPort) { - UnitInputPort inputPort = (UnitInputPort) port; - String voicePortName = inputPort.getName(); - // FIXME Need better way to identify ports that are per note. - if (!voicePortName.equals("Frequency") && !voicePortName.equals("Amplitude")) { - exportNamedInputPort(voicePortName); - } - } - } - } - - /** - * Create a UnitInputPort for the circuit that is connected to the named port on each voice - * through a PassThrough unit. This allows you to control all of the voices at once. - * - * @param portName - * @see exportAllInputPorts - */ - public void exportNamedInputPort(String portName) { - UnitInputPort voicePort = null; - PassThrough fanout = new PassThrough(); - for (UnitVoice voice : voices) { - voicePort = (UnitInputPort) voice.getUnitGenerator().getPortByName(portName); - fanout.output.connect(voicePort); - } - if (voicePort != null) { - addPort(fanout.input, portName); - fanout.input.setup(voicePort); - } - } - - @Override - public UnitOutputPort getOutput() { - return mixer.output; - } - - @Override - public void usePreset(int presetIndex) { - usePreset(presetIndex, getSynthesisEngine().createTimeStamp()); - } - - // FIXME - no timestamp on UnitVoice - @Override - public void usePreset(int presetIndex, TimeStamp timeStamp) { - // Apply preset to all voices. - for (UnitVoice voice : voices) { - voice.usePreset(presetIndex); - } - // Then copy values from first voice to instrument. - for (UnitPort port : voices[0].getUnitGenerator().getPorts()) { - if (port instanceof UnitInputPort) { - UnitInputPort inputPort = (UnitInputPort) port; - // FIXME Need better way to identify ports that are per note. - UnitInputPort fanPort = (UnitInputPort) getPortByName(inputPort.getName()); - if ((fanPort != null) && (fanPort != amplitude)) { - fanPort.set(inputPort.get()); - } - } - } - } - - @Override - public void noteOn(int tag, double frequency, double amplitude, TimeStamp timeStamp) { - voiceAllocator.noteOn(tag, frequency, amplitude, timeStamp); - } - - @Override - public void noteOff(int tag, TimeStamp timeStamp) { - voiceAllocator.noteOff(tag, timeStamp); - } - - @Override - public void setPort(int tag, String portName, double value, TimeStamp timeStamp) { - voiceAllocator.setPort(tag, portName, value, timeStamp); - } - - @Override - public void allNotesOff(TimeStamp timeStamp) { - voiceAllocator.allNotesOff(timeStamp); - } - - public synchronized boolean isOn(int tag) { - return voiceAllocator.isOn(tag); - } -} diff --git a/src/com/jsyn/util/PseudoRandom.java b/src/com/jsyn/util/PseudoRandom.java deleted file mode 100644 index e92b669..0000000 --- a/src/com/jsyn/util/PseudoRandom.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2009 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * Sep 9, 2009 - * com.jsyn.engine.units.SynthRandom.java - */ - -package com.jsyn.util; - -import java.util.Random; - -/** - * Pseudo-random numbers using predictable and fast linear-congruential method. - * - * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa - * Tolentino. - */ -public class PseudoRandom { - // We must shift 1L or else we get a negative number! - private static final double INT_TO_DOUBLE = (1.0 / (1L << 31)); - private long seed = 99887766; - - /** - * Create an instance of SynthRandom. - */ - public PseudoRandom() { - this(new Random().nextInt()); - } - - /** - * Create an instance of PseudoRandom. - */ - public PseudoRandom(int seed) { - setSeed(seed); - } - - public void setSeed(int seed) { - this.seed = (long) seed; - } - - public int getSeed() { - return (int) seed; - } - - /** - * Returns the next random double from 0.0 to 1.0 - * - * @return value from 0.0 to 1.0 - */ - public double random() { - int positiveInt = nextRandomInteger() & 0x7FFFFFFF; - return positiveInt * INT_TO_DOUBLE; - } - - /** - * Returns the next random double from -1.0 to 1.0 - * - * @return value from -1.0 to 1.0 - */ - public double nextRandomDouble() { - return nextRandomInteger() * INT_TO_DOUBLE; - } - - /** Calculate random 32 bit number using linear-congruential method. */ - public int nextRandomInteger() { - // Use values for 64-bit sequence from MMIX by Donald Knuth. - seed = (seed * 6364136223846793005L) + 1442695040888963407L; - return (int) (seed >> 32); // The higher bits have a longer sequence. - } - - public int choose(int range) { - long positiveInt = nextRandomInteger() & 0x7FFFFFFF; - long temp = positiveInt * range; - return (int) (temp >> 31); - } -} diff --git a/src/com/jsyn/util/RecursiveSequenceGenerator.java b/src/com/jsyn/util/RecursiveSequenceGenerator.java deleted file mode 100644 index 53dbdf9..0000000 --- a/src/com/jsyn/util/RecursiveSequenceGenerator.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 1997 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import java.util.Random; - -/** - * Generate a sequence of integers based on a recursive mining of previous material. Notes are - * generated by one of the following formula: - * - * <pre> - * <code> - * value[n] = value[n-delay] + offset; - * </code> - * </pre> - * - * The parameters delay and offset are randomly generated. This algorithm was first developed in - * 1977 for a class project in FORTRAN. It was ported to Forth for HMSL in the late 80's. It was - * then ported to Java for JSyn in 1997. - * - * @author Phil Burk (C) 1997,2011 Mobileer Inc - */ -public class RecursiveSequenceGenerator { - private int delay = 1; - private int maxValue; - private int maxInterval; - private double desiredDensity = 0.5; - - private int offset; - private int values[]; - private boolean enables[]; - private int cursor; - private int countdown = -1; - private double actualDensity; - private int beatsPerMeasure = 8; - private Random random; - - public RecursiveSequenceGenerator() { - this(25, 7, 64); - } - - public RecursiveSequenceGenerator(int maxValue, int maxInterval, int arraySize) { - values = new int[arraySize]; - enables = new boolean[arraySize]; - this.maxValue = maxValue; - this.maxInterval = maxInterval; - for (int i = 0; i < values.length; i++) { - values[i] = maxValue / 2; - enables[i] = isNextEnabled(false); - } - } - - /** Set density of notes. 0.0 to 1.0 */ - public void setDensity(double density) { - desiredDensity = density; - } - - public double getDensity() { - return desiredDensity; - } - - /** Set maximum for generated value. */ - public void setMaxValue(int maxValue) { - this.maxValue = maxValue; - } - - public int getMaxValue() { - return maxValue; - } - - /** Set maximum for generated value. */ - public void setMaxInterval(int maxInterval) { - this.maxInterval = maxInterval; - } - - public int getMaxInterval() { - return maxInterval; - } - - /* Determine whether next in sequence should occur. */ - public boolean isNextEnabled(boolean preferance) { - /* Calculate note density using low pass IIR filter. */ - double newDensity = (actualDensity * 0.9) + (preferance ? 0.1 : 0.0); - /* Invert enable to push density towards desired level, with hysteresis. */ - if (preferance && (newDensity > ((desiredDensity * 0.7) + 0.3))) - preferance = false; - else if (!preferance && (newDensity < (desiredDensity * 0.7))) - preferance = true; - actualDensity = (actualDensity * 0.9) + (preferance ? 0.1 : 0.0); - return preferance; - } - - public int randomPowerOf2(int maxExp) { - return (1 << (int) (random.nextDouble() * (maxExp + 1))); - } - - /** Random number evenly distributed from -maxInterval to +maxInterval */ - public int randomEvenInterval() { - return (int) (random.nextDouble() * ((maxInterval * 2) + 1)) - maxInterval; - } - - void calcNewOffset() { - offset = randomEvenInterval(); - } - - public void randomize() { - - delay = randomPowerOf2(4); - calcNewOffset(); - // System.out.println("NewSeq: delay = " + delay + ", offset = " + - // offset ); - } - - /** Change parameters based on random countdown. */ - public int next() { - // If this sequence is finished, start a new one. - if (countdown-- < 0) { - randomize(); - countdown = randomPowerOf2(3); - } - return nextValue(); - } - - /** Change parameters using a probability based on beatIndex. */ - public int next(int beatIndex) { - int beatMod = beatIndex % beatsPerMeasure; - switch (beatMod) { - case 0: - if (Math.random() < 0.90) - randomize(); - break; - case 2: - case 6: - if (Math.random() < 0.15) - randomize(); - break; - case 4: - if (Math.random() < 0.30) - randomize(); - break; - default: - if (Math.random() < 0.07) - randomize(); - break; - } - return nextValue(); - } - - /** Generate nextValue based on current delay and offset */ - public int nextValue() { - // Generate index into circular value buffer. - int idx = (cursor - delay); - if (idx < 0) - idx += values.length; - - // Generate new value. Calculate new offset if too high or low. - int nextVal = 0; - int timeout = 100; - while (timeout > 0) { - nextVal = values[idx] + offset; - if ((nextVal >= 0) && (nextVal < maxValue)) - break; - // Prevent endless loops when maxValue changes. - if (nextVal > (maxValue + maxInterval - 1)) { - nextVal = maxValue; - break; - } - calcNewOffset(); - timeout--; - // System.out.println("NextVal = " + nextVal + ", offset = " + - // offset ); - } - if (timeout <= 0) { - System.err.println("RecursiveSequence: nextValue timed out. offset = " + offset); - nextVal = maxValue / 2; - offset = 0; - } - - // Save new value in circular buffer. - values[cursor] = nextVal; - - boolean playIt = enables[cursor] = isNextEnabled(enables[idx]); - cursor++; - if (cursor >= values.length) - cursor = 0; - - // System.out.println("nextVal = " + nextVal ); - - return playIt ? nextVal : -1; - } - - public Random getRandom() { - return random; - } - - public void setRandom(Random random) { - this.random = random; - } - -} diff --git a/src/com/jsyn/util/SampleLoader.java b/src/com/jsyn/util/SampleLoader.java deleted file mode 100644 index 170b4cb..0000000 --- a/src/com/jsyn/util/SampleLoader.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright 2011 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import com.jsyn.data.FloatSample; -import com.jsyn.util.soundfile.CustomSampleLoader; - -/** - * Load a FloatSample from various sources. The default loader uses custom code to load WAV or AIF - * files. Supported data formats are 16, 24 and 32 bit PCM, and 32-bit float. Compressed formats - * such as unsigned 8-bit, uLaw, A-Law and MP3 are not support. If you need to load one of those - * files try setJavaSoundPreferred(true). Or convert it to a supported format using Audacity or Sox - * or some other sample file tool. Here is an example of loading a sample from a file. - * - * <pre> - * <code> - * File sampleFile = new File("guitar.wav"); - * FloatSample sample = SampleLoader.loadFloatSample( sampleFile ); - * </code> - * </pre> - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class SampleLoader { - private static boolean javaSoundPreferred = false; - private static final String JS_LOADER_NAME = "com.jsyn.util.JavaSoundSampleLoader"; - - /** - * Try to create an implementation of AudioSampleLoader. - * - * @return A device supported on this platform. - */ - private static AudioSampleLoader createLoader() { - AudioSampleLoader loader = null; - try { - if (javaSoundPreferred) { - loader = (AudioSampleLoader) JavaTools.loadClass(JS_LOADER_NAME).newInstance(); - } else { - loader = new CustomSampleLoader(); - } - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - return loader; - } - - /** - * Load a FloatSample from a File object. - */ - public static FloatSample loadFloatSample(File fileIn) throws IOException { - AudioSampleLoader loader = SampleLoader.createLoader(); - return loader.loadFloatSample(fileIn); - } - - /** - * Load a FloatSample from an InputStream. This is handy when loading Resources from a JAR file. - */ - public static FloatSample loadFloatSample(InputStream inputStream) throws IOException { - AudioSampleLoader loader = SampleLoader.createLoader(); - return loader.loadFloatSample(inputStream); - } - - /** - * Load a FloatSample from a URL.. This is handy when loading Resources from a website. - */ - public static FloatSample loadFloatSample(URL url) throws IOException { - AudioSampleLoader loader = SampleLoader.createLoader(); - return loader.loadFloatSample(url); - } - - public static boolean isJavaSoundPreferred() { - return javaSoundPreferred; - } - - /** - * If set true then the audio file parser from JavaSound will be used. Note that JavaSound - * cannot load audio files containing floating point data. But it may be able to load some - * compressed data formats such as uLaw. - * - * Note: JavaSound is not supported on Android. - * - * @param javaSoundPreferred - */ - public static void setJavaSoundPreferred(boolean javaSoundPreferred) { - SampleLoader.javaSoundPreferred = javaSoundPreferred; - } - - /** - * Decode 24 bit samples from a BigEndian byte array into a float array. The samples will be - * normalized into the range -1.0 to +1.0. - * - * @param audioBytes raw data from an audio file - * @param offset first element of byte array - * @param numBytes number of bytes to process - * @param data array to be filled with floats - * @param outputOffset first element of float array to be filled - */ - public static void decodeBigI24ToF32(byte[] audioBytes, int offset, int numBytes, float[] data, - int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int hi = ((audioBytes[byteCursor++]) & 0x00FF); - int mid = ((audioBytes[byteCursor++]) & 0x00FF); - int lo = ((audioBytes[byteCursor++]) & 0x00FF); - int value = (hi << 24) | (mid << 16) | (lo << 8); - data[floatCursor++] = value * (1.0f / Integer.MAX_VALUE); - } - } - - public static void decodeBigI16ToF32(byte[] audioBytes, int offset, int numBytes, float[] data, - int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int hi = ((audioBytes[byteCursor++]) & 0x00FF); - int lo = ((audioBytes[byteCursor++]) & 0x00FF); - short value = (short) ((hi << 8) | lo); - data[floatCursor++] = value * (1.0f / 32768); - } - } - - public static void decodeBigF32ToF32(byte[] audioBytes, int offset, int numBytes, float[] data, - int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int bits = audioBytes[byteCursor++]; - bits = (bits << 8) | ((audioBytes[byteCursor++]) & 0x00FF); - bits = (bits << 8) | ((audioBytes[byteCursor++]) & 0x00FF); - bits = (bits << 8) | ((audioBytes[byteCursor++]) & 0x00FF); - data[floatCursor++] = Float.intBitsToFloat(bits); - } - } - - public static void decodeBigI32ToF32(byte[] audioBytes, int offset, int numBytes, float[] data, - int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int value = audioBytes[byteCursor++]; // MSB - value = (value << 8) | ((audioBytes[byteCursor++]) & 0x00FF); - value = (value << 8) | ((audioBytes[byteCursor++]) & 0x00FF); - value = (value << 8) | ((audioBytes[byteCursor++]) & 0x00FF); - data[floatCursor++] = value * (1.0f / Integer.MAX_VALUE); - } - } - - public static void decodeLittleF32ToF32(byte[] audioBytes, int offset, int numBytes, - float[] data, int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int bits = ((audioBytes[byteCursor++]) & 0x00FF); // LSB - bits += ((audioBytes[byteCursor++]) & 0x00FF) << 8; - bits += ((audioBytes[byteCursor++]) & 0x00FF) << 16; - bits += (audioBytes[byteCursor++]) << 24; - data[floatCursor++] = Float.intBitsToFloat(bits); - } - } - - public static void decodeLittleI32ToF32(byte[] audioBytes, int offset, int numBytes, - float[] data, int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int value = ((audioBytes[byteCursor++]) & 0x00FF); - value += ((audioBytes[byteCursor++]) & 0x00FF) << 8; - value += ((audioBytes[byteCursor++]) & 0x00FF) << 16; - value += (audioBytes[byteCursor++]) << 24; - data[floatCursor++] = value * (1.0f / Integer.MAX_VALUE); - } - } - - public static void decodeLittleI24ToF32(byte[] audioBytes, int offset, int numBytes, - float[] data, int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int lo = ((audioBytes[byteCursor++]) & 0x00FF); - int mid = ((audioBytes[byteCursor++]) & 0x00FF); - int hi = ((audioBytes[byteCursor++]) & 0x00FF); - int value = (hi << 24) | (mid << 16) | (lo << 8); - data[floatCursor++] = value * (1.0f / Integer.MAX_VALUE); - } - } - - public static void decodeLittleI16ToF32(byte[] audioBytes, int offset, int numBytes, - float[] data, int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int lo = ((audioBytes[byteCursor++]) & 0x00FF); - int hi = ((audioBytes[byteCursor++]) & 0x00FF); - short value = (short) ((hi << 8) | lo); - float sample = value * (1.0f / 32768); - data[floatCursor++] = sample; - } - } - -} diff --git a/src/com/jsyn/util/SignalCorrelator.java b/src/com/jsyn/util/SignalCorrelator.java deleted file mode 100644 index ebdd46b..0000000 --- a/src/com/jsyn/util/SignalCorrelator.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2011 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -/** - * Interface used to evaluate various algorithms for pitch detection. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public interface SignalCorrelator { - /** - * Add a sample to be analyzed. The samples will generally be held in a circular buffer. - * - * @param value - * @return true if a new period value has been generated - */ - public boolean addSample(double value); - - /** - * @return the estimated period of the waveform in samples - */ - public double getPeriod(); - - /** - * Measure of how confident the analyzer is of the last result. - * - * @return quality of the estimate between 0.0 and 1.0 - */ - public double getConfidence(); - - /** For internal debugging. */ - public float[] getDiffs(); - -} diff --git a/src/com/jsyn/util/StreamingThread.java b/src/com/jsyn/util/StreamingThread.java deleted file mode 100644 index 682f476..0000000 --- a/src/com/jsyn/util/StreamingThread.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2011 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import java.io.IOException; - -import com.jsyn.io.AudioInputStream; -import com.jsyn.io.AudioOutputStream; - -/** - * Read from an AudioInputStream and write to an AudioOutputStream as a background thread. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class StreamingThread extends Thread { - private AudioInputStream inputStream; - private AudioOutputStream outputStream; - private int framesPerBuffer = 1024; - private volatile boolean go = true; - private TransportModel transportModel; - private long framePosition; - private long maxFrames; - private int samplesPerFrame = 1; - - public StreamingThread(AudioInputStream inputStream, AudioOutputStream outputStream) { - this.inputStream = inputStream; - this.outputStream = outputStream; - } - - @Override - public void run() { - double[] buffer = new double[framesPerBuffer * samplesPerFrame]; - try { - transportModel.firePositionChanged(framePosition); - transportModel.fireStateChanged(TransportModel.STATE_RUNNING); - int framesToRead = geteFramesToRead(buffer); - while (go && (framesToRead > 0)) { - int samplesToRead = framesToRead * samplesPerFrame; - while (samplesToRead > 0) { - int samplesRead = inputStream.read(buffer, 0, samplesToRead); - outputStream.write(buffer, 0, samplesRead); - samplesToRead -= samplesRead; - } - framePosition += framesToRead; - transportModel.firePositionChanged(framePosition); - framesToRead = geteFramesToRead(buffer); - } - transportModel.fireStateChanged(TransportModel.STATE_STOPPED); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private int geteFramesToRead(double[] buffer) { - if (maxFrames > 0) { - long numToRead = maxFrames - framePosition; - if (numToRead < 0) { - return 0; - } else if (numToRead > framesPerBuffer) { - numToRead = framesPerBuffer; - } - return (int) numToRead; - } else { - return framesPerBuffer; - } - } - - public int getFramesPerBuffer() { - return framesPerBuffer; - } - - /** - * Only call this before the thread has started. - * - * @param framesPerBuffer - */ - public void setFramesPerBuffer(int framesPerBuffer) { - this.framesPerBuffer = framesPerBuffer; - } - - public void requestStop() { - go = false; - } - - public TransportModel getTransportModel() { - return transportModel; - } - - public void setTransportModel(TransportModel transportModel) { - this.transportModel = transportModel; - } - - /** - * @param maxFrames - */ - public void setMaxFrames(long maxFrames) { - this.maxFrames = maxFrames; - } - - public int getSamplesPerFrame() { - return samplesPerFrame; - } - - public void setSamplesPerFrame(int samplesPerFrame) { - this.samplesPerFrame = samplesPerFrame; - } -} diff --git a/src/com/jsyn/util/TransportListener.java b/src/com/jsyn/util/TransportListener.java deleted file mode 100644 index 3c8b048..0000000 --- a/src/com/jsyn/util/TransportListener.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2009 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -public interface TransportListener { - /** - * @param transportModel - * @param framePosition position in frames - */ - void positionChanged(TransportModel transportModel, long framePosition); - - /** - * @param transportModel - * @param state for example TransportModel.STATE_STOPPED - */ - void stateChanged(TransportModel transportModel, int state); -} diff --git a/src/com/jsyn/util/TransportModel.java b/src/com/jsyn/util/TransportModel.java deleted file mode 100644 index bcc75be..0000000 --- a/src/com/jsyn/util/TransportModel.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2009 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import java.util.concurrent.CopyOnWriteArrayList; - -public class TransportModel { - public static final int STATE_STOPPED = 0; - public static final int STATE_PAUSED = 1; - public static final int STATE_RUNNING = 2; - - private CopyOnWriteArrayList<TransportListener> listeners = new CopyOnWriteArrayList<TransportListener>(); - private int state = STATE_STOPPED; - private long position; - - public void addTransportListener(TransportListener listener) { - listeners.add(listener); - } - - public void removeTransportListener(TransportListener listener) { - listeners.remove(listener); - } - - public void setState(int newState) { - state = newState; - fireStateChanged(newState); - } - - public int getState() { - return state; - } - - public void setPosition(long newPosition) { - position = newPosition; - firePositionChanged(newPosition); - } - - public long getPosition() { - return position; - } - - public void fireStateChanged(int newState) { - for (TransportListener listener : listeners) { - listener.stateChanged(this, newState); - } - } - - public void firePositionChanged(long newPosition) { - for (TransportListener listener : listeners) { - listener.positionChanged(this, newPosition); - } - } -} diff --git a/src/com/jsyn/util/VoiceAllocator.java b/src/com/jsyn/util/VoiceAllocator.java deleted file mode 100644 index f20f7a5..0000000 --- a/src/com/jsyn/util/VoiceAllocator.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright 2011 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import com.jsyn.Synthesizer; -import com.jsyn.unitgen.UnitVoice; -import com.softsynth.shared.time.ScheduledCommand; -import com.softsynth.shared.time.TimeStamp; - -/** - * Allocate voices based on an integer tag. The tag could, for example, be a MIDI note number. Or a - * tag could be an int that always increments. Use the same tag to refer to a voice for noteOn() and - * noteOff(). If no new voices are available then a voice in use will be stolen. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class VoiceAllocator implements Instrument { - private int maxVoices; - private VoiceTracker[] trackers; - private long tick; - private Synthesizer synthesizer; - private static final int UNASSIGNED_PRESET = -1; - private int mPresetIndex = UNASSIGNED_PRESET; - - /** - * Create an allocator for the array of UnitVoices. The array must be full of instantiated - * UnitVoices that are connected to some kind of mixer. - * - * @param voices - */ - public VoiceAllocator(UnitVoice[] voices) { - maxVoices = voices.length; - trackers = new VoiceTracker[maxVoices]; - for (int i = 0; i < maxVoices; i++) { - trackers[i] = new VoiceTracker(); - trackers[i].voice = voices[i]; - } - } - - public Synthesizer getSynthesizer() { - if (synthesizer == null) { - synthesizer = trackers[0].voice.getUnitGenerator().getSynthesizer(); - } - return synthesizer; - } - - private class VoiceTracker { - UnitVoice voice; - int tag = -1; - int presetIndex = UNASSIGNED_PRESET; - long when; - boolean on; - - public void off() { - on = false; - when = tick++; - } - } - - /** - * @return number of UnitVoices passed to the allocator. - */ - public int getVoiceCount() { - return maxVoices; - } - - private VoiceTracker findVoice(int tag) { - for (VoiceTracker tracker : trackers) { - if (tracker.tag == tag) { - return tracker; - } - } - return null; - } - - private VoiceTracker stealVoice() { - VoiceTracker bestOff = null; - VoiceTracker bestOn = null; - for (VoiceTracker tracker : trackers) { - if (tracker.voice == null) { - return tracker; - } - // If we have a bestOff voice then don't even bother with on voices. - else if (bestOff != null) { - // Older off voice? - if (!tracker.on && (tracker.when < bestOff.when)) { - bestOff = tracker; - } - } else if (tracker.on) { - if (bestOn == null) { - bestOn = tracker; - } else if (tracker.when < bestOn.when) { - bestOn = tracker; - } - } else { - bestOff = tracker; - } - } - if (bestOff != null) { - return bestOff; - } else { - return bestOn; - } - } - - /** - * Allocate a Voice associated with this tag. It will first pick a voice already assigned to - * that tag. Next it will pick the oldest voice that is off. Next it will pick the oldest voice - * that is on. If you are using timestamps to play the voice in the future then you should use - * the noteOn() noteOff() and setPort() methods. - * - * @param tag - * @return Voice that is most available. - */ - protected synchronized UnitVoice allocate(int tag) { - VoiceTracker tracker = allocateTracker(tag); - return tracker.voice; - } - - private VoiceTracker allocateTracker(int tag) { - VoiceTracker tracker = findVoice(tag); - if (tracker == null) { - tracker = stealVoice(); - } - tracker.tag = tag; - tracker.when = tick++; - tracker.on = true; - return tracker; - } - - protected synchronized boolean isOn(int tag) { - VoiceTracker tracker = findVoice(tag); - if (tracker != null) { - return tracker.on; - } - return false; - } - - protected synchronized UnitVoice off(int tag) { - VoiceTracker tracker = findVoice(tag); - if (tracker != null) { - tracker.off(); - return tracker.voice; - } - return null; - } - - /** Turn off all the note currently on. */ - @Override - public void allNotesOff(TimeStamp timeStamp) { - getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - for (VoiceTracker tracker : trackers) { - if (tracker.on) { - tracker.voice.noteOff(getSynthesizer().createTimeStamp()); - tracker.off(); - } - } - } - }); - } - - /** - * Play a note on the voice and associate it with the given tag. if needed a new voice will be - * allocated and an old voice may be turned off. - */ - @Override - public void noteOn(final int tag, final double frequency, final double amplitude, - TimeStamp timeStamp) { - getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - VoiceTracker voiceTracker = allocateTracker(tag); - if (voiceTracker.presetIndex != mPresetIndex) { - voiceTracker.voice.usePreset(mPresetIndex); - voiceTracker.presetIndex = mPresetIndex; - } - voiceTracker.voice.noteOn(frequency, amplitude, getSynthesizer().createTimeStamp()); - } - }); - } - - /** - * Play a note on the voice and associate it with the given tag. if needed a new voice will be - * allocated and an old voice may be turned off. - * Apply an operation to the voice. - */ - public void noteOn(final int tag, - final double frequency, - final double amplitude, - final VoiceOperation operation, - TimeStamp timeStamp) { - getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - VoiceTracker voiceTracker = allocateTracker(tag); - operation.operate(voiceTracker.voice); - voiceTracker.voice.noteOn(frequency, amplitude, getSynthesizer().createTimeStamp()); - } - }); - } - - /** Turn off the voice associated with the given tag if allocated. */ - @Override - public void noteOff(final int tag, TimeStamp timeStamp) { - getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - VoiceTracker voiceTracker = findVoice(tag); - if (voiceTracker != null) { - voiceTracker.voice.noteOff(getSynthesizer().createTimeStamp()); - off(tag); - } - } - }); - } - - /** Set a port on the voice associated with the given tag if allocated. */ - @Override - public void setPort(final int tag, final String portName, final double value, - TimeStamp timeStamp) { - getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - VoiceTracker voiceTracker = findVoice(tag); - if (voiceTracker != null) { - voiceTracker.voice.setPort(portName, value, getSynthesizer().createTimeStamp()); - } - } - }); - } - - @Override - public void usePreset(final int presetIndex, TimeStamp timeStamp) { - getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - mPresetIndex = presetIndex; - } - }); - } - -} diff --git a/src/com/jsyn/util/VoiceDescription.java b/src/com/jsyn/util/VoiceDescription.java deleted file mode 100644 index b7be044..0000000 --- a/src/com/jsyn/util/VoiceDescription.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2011 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import com.jsyn.unitgen.UnitVoice; - -/** - * Describe a voice so that a user can pick it out of an InstrumentLibrary. - * - * @author Phil Burk (C) 2011 Mobileer Inc - * @see PolyphonicInstrument - */ -public abstract class VoiceDescription { - private String name; - private String[] presetNames; - - public VoiceDescription(String name, String[] presetNames) { - this.name = name; - this.presetNames = presetNames; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getPresetCount() { - return presetNames.length; - } - - public String[] getPresetNames() { - return presetNames; - } - - public abstract String[] getTags(int presetIndex); - - /** - * Instantiate one of these voices. You may want to call usePreset(n) on the voice after - * instantiating it. - * - * @return a voice - */ - public abstract UnitVoice createUnitVoice(); - - public abstract String getVoiceClassName(); - - @Override - public String toString() { - return name + "[" + getPresetCount() + "]"; - } -} diff --git a/src/com/jsyn/util/VoiceOperation.java b/src/com/jsyn/util/VoiceOperation.java deleted file mode 100644 index cd3b48e..0000000 --- a/src/com/jsyn/util/VoiceOperation.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.jsyn.util; - -import com.jsyn.unitgen.UnitVoice; - -public interface VoiceOperation { - public void operate(UnitVoice voice); -} diff --git a/src/com/jsyn/util/WaveFileWriter.java b/src/com/jsyn/util/WaveFileWriter.java deleted file mode 100644 index 32e9995..0000000 --- a/src/com/jsyn/util/WaveFileWriter.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright 2011 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.RandomAccessFile; - -import com.jsyn.io.AudioOutputStream; - -/** - * Write audio data to a WAV file. - * - * <pre> - * <code> - * WaveFileWriter writer = new WaveFileWriter(file); - * writer.setFrameRate(22050); - * writer.setBitsPerSample(24); - * writer.write(floatArray); - * writer.close(); - * </code> - * </pre> - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class WaveFileWriter implements AudioOutputStream { - private static final short WAVE_FORMAT_PCM = 1; - private OutputStream outputStream; - private long riffSizePosition = 0; - private long dataSizePosition = 0; - private int frameRate = 44100; - private int samplesPerFrame = 1; - private int bitsPerSample = 16; - private int bytesWritten; - private File outputFile; - private boolean headerWritten = false; - private final static int PCM24_MIN = -(1 << 23); - private final static int PCM24_MAX = (1 << 23) - 1; - - /** - * Create a writer that will write to the specified file. - * - * @param outputFile - * @throws FileNotFoundException - */ - public WaveFileWriter(File outputFile) throws FileNotFoundException { - this.outputFile = outputFile; - FileOutputStream fileOut = new FileOutputStream(outputFile); - outputStream = new BufferedOutputStream(fileOut); - } - - /** - * @param frameRate default is 44100 - */ - public void setFrameRate(int frameRate) { - this.frameRate = frameRate; - } - - public int getFrameRate() { - return frameRate; - } - - /** For stereo, set this to 2. Default is 1. */ - public void setSamplesPerFrame(int samplesPerFrame) { - this.samplesPerFrame = samplesPerFrame; - } - - public int getSamplesPerFrame() { - return samplesPerFrame; - } - - /** Only 16 or 24 bit samples supported at the moment. Default is 16. */ - public void setBitsPerSample(int bits) { - if ((bits != 16) && (bits != 24)) { - throw new IllegalArgumentException("Only 16 or 24 bits per sample allowed. Not " + bits); - } - bitsPerSample = bits; - } - - public int getBitsPerSample() { - return bitsPerSample; - } - - @Override - public void close() throws IOException { - outputStream.close(); - fixSizes(); - } - - /** Write entire buffer of audio samples to the WAV file. */ - @Override - public void write(double[] buffer) throws IOException { - write(buffer, 0, buffer.length); - } - - /** Write audio to the WAV file. */ - public void write(float[] buffer) throws IOException { - write(buffer, 0, buffer.length); - } - - /** Write single audio data value to the WAV file. */ - @Override - public void write(double value) throws IOException { - if (!headerWritten) { - writeHeader(); - } - - if (bitsPerSample == 24) { - writePCM24(value); - } else { - writePCM16(value); - } - } - - private void writePCM24(double value) throws IOException { - // Offset before casting so that we can avoid using floor(). - // Also round by adding 0.5 so that very small signals go to zero. - double temp = (PCM24_MAX * value) + 0.5 - PCM24_MIN; - int sample = ((int) temp) + PCM24_MIN; - // clip to 24-bit range - if (sample > PCM24_MAX) { - sample = PCM24_MAX; - } else if (sample < PCM24_MIN) { - sample = PCM24_MIN; - } - // encode as little-endian - writeByte(sample); // little end - writeByte(sample >> 8); // middle - writeByte(sample >> 16); // big end - } - - private void writePCM16(double value) throws IOException { - // Offset before casting so that we can avoid using floor(). - // Also round by adding 0.5 so that very small signals go to zero. - double temp = (Short.MAX_VALUE * value) + 0.5 - Short.MIN_VALUE; - int sample = ((int) temp) + Short.MIN_VALUE; - if (sample > Short.MAX_VALUE) { - sample = Short.MAX_VALUE; - } else if (sample < Short.MIN_VALUE) { - sample = Short.MIN_VALUE; - } - writeByte(sample); // little end - writeByte(sample >> 8); // big end - } - - /** Write audio to the WAV file. */ - @Override - public void write(double[] buffer, int start, int count) throws IOException { - for (int i = 0; i < count; i++) { - write(buffer[start + i]); - } - } - - /** Write audio to the WAV file. */ - public void write(float[] buffer, int start, int count) throws IOException { - for (int i = 0; i < count; i++) { - write(buffer[start + i]); - } - } - - // Write lower 8 bits. Upper bits ignored. - private void writeByte(int b) throws IOException { - outputStream.write(b); - bytesWritten += 1; - } - - /** - * Write a 32 bit integer to the stream in Little Endian format. - */ - public void writeIntLittle(int n) throws IOException { - writeByte(n); - writeByte(n >> 8); - writeByte(n >> 16); - writeByte(n >> 24); - } - - /** - * Write a 16 bit integer to the stream in Little Endian format. - */ - public void writeShortLittle(short n) throws IOException { - writeByte(n); - writeByte(n >> 8); - } - - /** - * Write a simple WAV header for PCM data. - */ - private void writeHeader() throws IOException { - writeRiffHeader(); - writeFormatChunk(); - writeDataChunkHeader(); - outputStream.flush(); - headerWritten = true; - } - - /** - * Write a 'RIFF' file header and a 'WAVE' ID to the WAV file. - */ - private void writeRiffHeader() throws IOException { - writeByte('R'); - writeByte('I'); - writeByte('F'); - writeByte('F'); - riffSizePosition = bytesWritten; - writeIntLittle(Integer.MAX_VALUE); - writeByte('W'); - writeByte('A'); - writeByte('V'); - writeByte('E'); - } - - /** - * Write an 'fmt ' chunk to the WAV file containing the given information. - */ - public void writeFormatChunk() throws IOException { - int bytesPerSample = (bitsPerSample + 7) / 8; - - writeByte('f'); - writeByte('m'); - writeByte('t'); - writeByte(' '); - writeIntLittle(16); // chunk size - writeShortLittle(WAVE_FORMAT_PCM); - writeShortLittle((short) samplesPerFrame); - writeIntLittle(frameRate); - // bytes/second - writeIntLittle(frameRate * samplesPerFrame * bytesPerSample); - // block align - writeShortLittle((short) (samplesPerFrame * bytesPerSample)); - writeShortLittle((short) bitsPerSample); - } - - /** - * Write a 'data' chunk header to the WAV file. This should be followed by call to - * writeShortLittle() to write the data to the chunk. - */ - public void writeDataChunkHeader() throws IOException { - writeByte('d'); - writeByte('a'); - writeByte('t'); - writeByte('a'); - dataSizePosition = bytesWritten; - writeIntLittle(Integer.MAX_VALUE); // size - } - - /** - * Fix RIFF and data chunk sizes based on final size. Assume data chunk is the last chunk. - */ - private void fixSizes() throws IOException { - RandomAccessFile randomFile = new RandomAccessFile(outputFile, "rw"); - try { - // adjust RIFF size - long end = bytesWritten; - int riffSize = (int) (end - riffSizePosition) - 4; - randomFile.seek(riffSizePosition); - writeRandomIntLittle(randomFile, riffSize); - // adjust data size - int dataSize = (int) (end - dataSizePosition) - 4; - randomFile.seek(dataSizePosition); - writeRandomIntLittle(randomFile, dataSize); - } finally { - randomFile.close(); - } - } - - private void writeRandomIntLittle(RandomAccessFile randomFile, int n) throws IOException { - byte[] buffer = new byte[4]; - buffer[0] = (byte) n; - buffer[1] = (byte) (n >> 8); - buffer[2] = (byte) (n >> 16); - buffer[3] = (byte) (n >> 24); - randomFile.write(buffer); - } - -} diff --git a/src/com/jsyn/util/WaveRecorder.java b/src/com/jsyn/util/WaveRecorder.java deleted file mode 100644 index 059863b..0000000 --- a/src/com/jsyn/util/WaveRecorder.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2011 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; - -import com.jsyn.Synthesizer; -import com.jsyn.ports.UnitInputPort; - -/** - * Connect a unit generator to the input. Then start() recording. The signal will be written to a - * WAV format file that can be read by other programs. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class WaveRecorder { - private AudioStreamReader reader; - private WaveFileWriter writer; - private StreamingThread thread; - private Synthesizer synth; - private TransportModel transportModel = new TransportModel(); - private double maxRecordingTime; - - /** - * Create a stereo 16-bit recorder. - * - * @param synth - * @param outputFile - * @throws FileNotFoundException - */ - public WaveRecorder(Synthesizer synth, File outputFile) throws FileNotFoundException { - this(synth, outputFile, 2, 16); - } - - public WaveRecorder(Synthesizer synth, File outputFile, int samplesPerFrame) - throws FileNotFoundException { - this(synth, outputFile, samplesPerFrame, 16); - } - - /** - * @param synth - * @param outputFile - * @param samplesPerFrame 1 for mono, 2 for stereo - * @param bitsPerSample 16 or 24 - * @throws FileNotFoundException - */ - public WaveRecorder(Synthesizer synth, File outputFile, int samplesPerFrame, int bitsPerSample) - throws FileNotFoundException { - this.synth = synth; - reader = new AudioStreamReader(synth, samplesPerFrame); - - writer = new WaveFileWriter(outputFile); - writer.setFrameRate(synth.getFrameRate()); - writer.setSamplesPerFrame(samplesPerFrame); - writer.setBitsPerSample(bitsPerSample); - } - - public UnitInputPort getInput() { - return reader.getInput(); - } - - public void start() { - stop(); - thread = new StreamingThread(reader, writer); - thread.setTransportModel(transportModel); - thread.setSamplesPerFrame(writer.getSamplesPerFrame()); - updateMaxRecordingTime(); - thread.start(); - } - - public void stop() { - if (thread != null) { - thread.requestStop(); - try { - thread.join(500); - } catch (InterruptedException e) { - } - thread = null; - } - } - - /** Close and disconnect any connected inputs. */ - public void close() throws IOException { - stop(); - if (writer != null) { - writer.close(); - writer = null; - } - if (reader != null) { - reader.close(); - for (int i = 0; i < reader.getInput().getNumParts(); i++) { - reader.getInput().disconnectAll(i); - } - reader = null; - } - } - - public void addTransportListener(TransportListener listener) { - transportModel.addTransportListener(listener); - } - - public void removeTransportListener(TransportListener listener) { - transportModel.removeTransportListener(listener); - } - - public void setMaxRecordingTime(double maxRecordingTime) { - this.maxRecordingTime = maxRecordingTime; - updateMaxRecordingTime(); - } - - private void updateMaxRecordingTime() { - StreamingThread streamingThread = thread; - if (streamingThread != null) { - long maxFrames = (long) (maxRecordingTime * synth.getFrameRate()); - streamingThread.setMaxFrames(maxFrames); - } - } -} diff --git a/src/com/jsyn/util/soundfile/AIFFFileParser.java b/src/com/jsyn/util/soundfile/AIFFFileParser.java deleted file mode 100644 index 2b09d78..0000000 --- a/src/com/jsyn/util/soundfile/AIFFFileParser.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2009 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util.soundfile; - -import java.io.EOFException; -import java.io.IOException; - -import com.jsyn.data.FloatSample; -import com.jsyn.data.SampleMarker; -import com.jsyn.util.SampleLoader; - -public class AIFFFileParser extends AudioFileParser { - private static final String SUPPORTED_FORMATS = "Only 16 and 24 bit PCM or 32-bit float AIF files supported."; - static final int AIFF_ID = ('A' << 24) | ('I' << 16) | ('F' << 8) | 'F'; - static final int AIFC_ID = ('A' << 24) | ('I' << 16) | ('F' << 8) | 'C'; - static final int COMM_ID = ('C' << 24) | ('O' << 16) | ('M' << 8) | 'M'; - static final int SSND_ID = ('S' << 24) | ('S' << 16) | ('N' << 8) | 'D'; - static final int MARK_ID = ('M' << 24) | ('A' << 16) | ('R' << 8) | 'K'; - static final int INST_ID = ('I' << 24) | ('N' << 16) | ('S' << 8) | 'T'; - static final int NONE_ID = ('N' << 24) | ('O' << 16) | ('N' << 8) | 'E'; - static final int FL32_ID = ('F' << 24) | ('L' << 16) | ('3' << 8) | '2'; - static final int FL32_ID_LC = ('f' << 24) | ('l' << 16) | ('3' << 8) | '2'; - - int sustainBeginID = -1; - int sustainEndID = -1; - int releaseBeginID = -1; - int releaseEndID = -1; - boolean typeFloat = false; - - @Override - FloatSample finish() throws IOException { - setLoops(); - - if ((byteData == null)) { - throw new IOException("No data found in audio sample."); - } - float[] floatData = new float[numFrames * samplesPerFrame]; - if (bitsPerSample == 16) { - SampleLoader.decodeBigI16ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (bitsPerSample == 24) { - SampleLoader.decodeBigI24ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (bitsPerSample == 32) { - if (typeFloat) { - SampleLoader.decodeBigF32ToF32(byteData, 0, byteData.length, floatData, 0); - } else { - SampleLoader.decodeBigI32ToF32(byteData, 0, byteData.length, floatData, 0); - } - } else { - throw new IOException(SUPPORTED_FORMATS + " size = " + bitsPerSample); - } - - return makeSample(floatData); - } - - double read80BitFloat() throws IOException { - /* - * This is not a full decoding of the 80 bit number but it should suffice for the range we - * expect. - */ - byte[] bytes = new byte[10]; - parser.read(bytes); - int exp = ((bytes[0] & 0x3F) << 8) | (bytes[1] & 0xFF); - int mant = ((bytes[2] & 0xFF) << 16) | ((bytes[3] & 0xFF) << 8) | (bytes[4] & 0xFF); - // System.out.println( "exp = " + exp + ", mant = " + mant ); - return mant / (double) (1 << (22 - exp)); - } - - void parseCOMMChunk(IFFParser parser, int ckSize) throws IOException { - samplesPerFrame = parser.readShortBig(); - numFrames = parser.readIntBig(); - bitsPerSample = parser.readShortBig(); - frameRate = read80BitFloat(); - if (ckSize > 18) { - int format = parser.readIntBig(); - // Validate data format. - if ((format == FL32_ID) || (format == FL32_ID_LC)) { - typeFloat = true; - } else if (format == NONE_ID) { - typeFloat = false; - } else { - throw new IOException(SUPPORTED_FORMATS + " format " + IFFParser.IDToString(format)); - } - } - - bytesPerSample = (bitsPerSample + 7) / 8; - bytesPerFrame = bytesPerSample * samplesPerFrame; - } - - /* parse tuning and multi-sample info */ - @SuppressWarnings("unused") - void parseINSTChunk(IFFParser parser, int ckSize) throws IOException { - int baseNote = parser.readByte(); - int detune = parser.readByte(); - originalPitch = baseNote + (0.01 * detune); - - int lowNote = parser.readByte(); - int highNote = parser.readByte(); - - parser.skip(2); /* lo,hi velocity */ - int gain = parser.readShortBig(); - - int playMode = parser.readShortBig(); /* sustain */ - sustainBeginID = parser.readShortBig(); - sustainEndID = parser.readShortBig(); - - playMode = parser.readShortBig(); /* release */ - releaseBeginID = parser.readShortBig(); - releaseEndID = parser.readShortBig(); - } - - private void setLoops() { - SampleMarker cuePoint = cueMap.get(sustainBeginID); - if (cuePoint != null) { - sustainBegin = cuePoint.position; - } - cuePoint = cueMap.get(sustainEndID); - if (cuePoint != null) { - sustainEnd = cuePoint.position; - } - } - - void parseSSNDChunk(IFFParser parser, int ckSize) throws IOException { - long numRead; - // System.out.println("parseSSNDChunk()"); - int offset = parser.readIntBig(); - parser.readIntBig(); /* blocksize */ - parser.skip(offset); - dataPosition = parser.getOffset(); - int numBytes = ckSize - 8 - offset; - if (ifLoadData) { - byteData = new byte[numBytes]; - numRead = parser.read(byteData); - } else { - numRead = parser.skip(numBytes); - } - if (numRead != numBytes) - throw new EOFException("AIFF data chunk too short!"); - } - - void parseMARKChunk(IFFParser parser, int ckSize) throws IOException { - long startOffset = parser.getOffset(); - int numCuePoints = parser.readShortBig(); - // System.out.println( "parseCueChunk: numCuePoints = " + numCuePoints - // ); - for (int i = 0; i < numCuePoints; i++) { - // Some AIF files have a bogus numCuePoints so check to see if we - // are at end. - long numInMark = parser.getOffset() - startOffset; - if (numInMark >= ckSize) { - System.out.println("Reached end of MARK chunk with bogus numCuePoints = " - + numCuePoints); - break; - } - - int uniqueID = parser.readShortBig(); - int position = parser.readIntBig(); - int len = parser.read(); - String markerName = parseString(parser, len); - if ((len & 1) == 0) { - parser.skip(1); /* skip pad byte */ - } - - SampleMarker cuePoint = findOrCreateCuePoint(uniqueID); - cuePoint.position = position; - cuePoint.name = markerName; - - if (IFFParser.debug) { - System.out.println("AIFF Marker at " + position + ", " + markerName); - } - } - } - - /** - * Called by parse() method to handle FORM chunks in an AIFF specific manner. - * - * @param ckID four byte chunk ID such as 'data' - * @param ckSize size of chunk in bytes - * @exception IOException If parsing fails, or IO error occurs. - */ - @Override - public void handleForm(IFFParser parser, int ckID, int ckSize, int type) throws IOException { - if ((ckID == IFFParser.FORM_ID) && (type != AIFF_ID) && (type != AIFC_ID)) - throw new IOException("Bad AIFF form type = " + IFFParser.IDToString(type)); - } - - /** - * Called by parse() method to handle chunks in an AIFF specific manner. - * - * @param ckID four byte chunk ID such as 'data' - * @param ckSize size of chunk in bytes - * @exception IOException If parsing fails, or IO error occurs. - */ - @Override - public void handleChunk(IFFParser parser, int ckID, int ckSize) throws IOException { - switch (ckID) { - case COMM_ID: - parseCOMMChunk(parser, ckSize); - break; - case SSND_ID: - parseSSNDChunk(parser, ckSize); - break; - case MARK_ID: - parseMARKChunk(parser, ckSize); - break; - case INST_ID: - parseINSTChunk(parser, ckSize); - break; - default: - break; - } - } - -} diff --git a/src/com/jsyn/util/soundfile/AudioFileParser.java b/src/com/jsyn/util/soundfile/AudioFileParser.java deleted file mode 100644 index e7bb066..0000000 --- a/src/com/jsyn/util/soundfile/AudioFileParser.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2001 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util.soundfile; - -import java.io.IOException; -import java.util.HashMap; - -import com.jsyn.data.FloatSample; -import com.jsyn.data.SampleMarker; - -/** - * Base class for various types of audio specific file parsers. - * - * @author (C) 2001 Phil Burk, SoftSynth.com - */ - -abstract class AudioFileParser implements ChunkHandler { - IFFParser parser; - protected byte[] byteData; - boolean ifLoadData = true; /* If true, load sound data into memory. */ - long dataPosition; /* - * Number of bytes from beginning of file where sound data resides. - */ - protected int bitsPerSample; - protected int bytesPerFrame; // in the file - protected int bytesPerSample; // in the file - protected HashMap<Integer, SampleMarker> cueMap = new HashMap<Integer, SampleMarker>(); - protected short samplesPerFrame; - protected double frameRate; - protected int numFrames; - protected double originalPitch = 60.0; - protected int sustainBegin = -1; - protected int sustainEnd = -1; - - public AudioFileParser() { - } - - /** - * @return Number of bytes from beginning of stream where sound data resides. - */ - public long getDataPosition() { - return dataPosition; - } - - /** - * This can be read by another thread when load()ing a sample to determine how many bytes have - * been read so far. - */ - public synchronized long getNumBytesRead() { - IFFParser p = parser; // prevent race - if (p != null) - return p.getOffset(); - else - return 0; - } - - /** - * This can be read by another thread when load()ing a sample to determine how many bytes need - * to be read. - */ - public synchronized long getFileSize() { - IFFParser p = parser; // prevent race - if (p != null) - return p.getFileSize(); - else - return 0; - } - - protected SampleMarker findOrCreateCuePoint(int uniqueID) { - SampleMarker cuePoint = cueMap.get(uniqueID); - if (cuePoint == null) { - cuePoint = new SampleMarker(); - cueMap.put(uniqueID, cuePoint); - } - return cuePoint; - } - - public FloatSample load(IFFParser parser) throws IOException { - this.parser = parser; - parser.parseAfterHead(this); - return finish(); - } - - abstract FloatSample finish() throws IOException; - - FloatSample makeSample(float[] floatData) throws IOException { - FloatSample floatSample = new FloatSample(floatData, samplesPerFrame); - - floatSample.setChannelsPerFrame(samplesPerFrame); - floatSample.setFrameRate(frameRate); - floatSample.setPitch(originalPitch); - - if (sustainBegin >= 0) { - floatSample.setSustainBegin(sustainBegin); - floatSample.setSustainEnd(sustainEnd); - } - - for (SampleMarker marker : cueMap.values()) { - floatSample.addMarker(marker); - } - - /* Set Sustain Loop by assuming first two markers are loop points. */ - if (floatSample.getMarkerCount() >= 2) { - floatSample.setSustainBegin(floatSample.getMarker(0).position); - floatSample.setSustainEnd(floatSample.getMarker(1).position); - } - return floatSample; - } - - protected String parseString(IFFParser parser, int textLength) throws IOException { - byte[] bar = new byte[textLength]; - parser.read(bar); - return new String(bar); - } -} diff --git a/src/com/jsyn/util/soundfile/ChunkHandler.java b/src/com/jsyn/util/soundfile/ChunkHandler.java deleted file mode 100644 index 6dfe26d..0000000 --- a/src/com/jsyn/util/soundfile/ChunkHandler.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 1997 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util.soundfile; - -import java.io.IOException; - -/** - * Handle IFF Chunks as they are parsed from an IFF or RIFF file. - * - * @see IFFParser - * @see AudioSampleAIFF - * @author (C) 1997 Phil Burk, SoftSynth.com - */ -interface ChunkHandler { - /** - * The parser will call this when it encounters a FORM or LIST chunk that contains other chunks. - * This handler can either read the form's chunks, or let the parser find them and call - * handleChunk(). - * - * @param ID a 4 byte identifier such as FORM_ID that identifies the IFF chunk type. - * @param numBytes number of bytes contained in the FORM, not counting the FORM type. - * @param type a 4 byte identifier such as AIFF_ID that identifies the FORM type. - */ - public void handleForm(IFFParser parser, int ID, int numBytes, int type) throws IOException; - - /** - * The parser will call this when it encounters a chunk that is not a FORM or LIST. This handler - * can either read the chunk's, or ignore it. The parser will skip over any unread data. Do NOT - * read past the end of the chunk! - * - * @param ID a 4 byte identifier such as SSND_ID that identifies the IFF chunk type. - * @param numBytes number of bytes contained in the chunk, not counting the ID and size field. - */ - public void handleChunk(IFFParser parser, int ID, int numBytes) throws IOException; -} diff --git a/src/com/jsyn/util/soundfile/CustomSampleLoader.java b/src/com/jsyn/util/soundfile/CustomSampleLoader.java deleted file mode 100644 index 14efde9..0000000 --- a/src/com/jsyn/util/soundfile/CustomSampleLoader.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2009 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util.soundfile; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import com.jsyn.data.FloatSample; -import com.jsyn.util.AudioSampleLoader; - -public class CustomSampleLoader implements AudioSampleLoader { - - @Override - public FloatSample loadFloatSample(File fileIn) throws IOException { - FileInputStream fileStream = new FileInputStream(fileIn); - BufferedInputStream inputStream = new BufferedInputStream(fileStream); - return loadFloatSample(inputStream); - } - - @Override - public FloatSample loadFloatSample(URL url) throws IOException { - InputStream rawStream = url.openStream(); - BufferedInputStream inputStream = new BufferedInputStream(rawStream); - return loadFloatSample(inputStream); - } - - @Override - public FloatSample loadFloatSample(InputStream inputStream) throws IOException { - AudioFileParser fileParser; - IFFParser parser = new IFFParser(inputStream); - parser.readHead(); - if (parser.isRIFF()) { - fileParser = new WAVEFileParser(); - } else if (parser.isIFF()) { - fileParser = new AIFFFileParser(); - } else { - throw new IOException("Unsupported audio file type."); - } - return fileParser.load(parser); - } - -} diff --git a/src/com/jsyn/util/soundfile/IFFParser.java b/src/com/jsyn/util/soundfile/IFFParser.java deleted file mode 100644 index f429657..0000000 --- a/src/com/jsyn/util/soundfile/IFFParser.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright 1997 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util.soundfile; - -import java.io.EOFException; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Parse Electronic Arts style IFF File. IFF is a file format that allows "chunks" of data to be - * placed in a hierarchical file. It was designed by Jerry Morrison at Electronic Arts for the Amiga - * computer and is now used extensively by Apple Computer and other companies. IFF is an open - * standard. - * - * @see RIFFParser - * @see AudioSampleAIFF - * @author (C) 1997 Phil Burk, SoftSynth.com - */ - -class IFFParser extends FilterInputStream { - private long numBytesRead = 0; - private long totalSize = 0; - private int fileId; - static boolean debug = false; - - public static final int RIFF_ID = ('R' << 24) | ('I' << 16) | ('F' << 8) | 'F'; - public static final int LIST_ID = ('L' << 24) | ('I' << 16) | ('S' << 8) | 'T'; - public static final int FORM_ID = ('F' << 24) | ('O' << 16) | ('R' << 8) | 'M'; - - IFFParser(InputStream stream) { - super(stream); - numBytesRead = 0; - } - - /** - * Size of file based on outermost chunk size plus 8. Can be used to report progress when - * loading samples. - * - * @return Number of bytes in outer chunk plus header. - */ - public long getFileSize() { - return totalSize; - } - - /** - * Since IFF files use chunks with explicit size, it is important to keep track of how many - * bytes have been read from the file. Can be used to report progress when loading samples. - * - * @return Number of bytes read from stream, or skipped. - */ - public long getOffset() { - return numBytesRead; - } - - /** @return Next byte from stream. Increment offset by 1. */ - @Override - public int read() throws IOException { - numBytesRead++; - return super.read(); - } - - /** @return Next byte array from stream. Increment offset by len. */ - @Override - public int read(byte[] bar) throws IOException { - return read(bar, 0, bar.length); - } - - /** @return Next byte array from stream. Increment offset by len. */ - @Override - public int read(byte[] bar, int off, int len) throws IOException { - // Reading from a URL can return before all the bytes are available. - // So we keep reading until we get the whole thing. - int cursor = off; - int numLeft = len; - // keep reading data until we get it all - while (numLeft > 0) { - int numRead = super.read(bar, cursor, numLeft); - if (numRead < 0) - return numRead; - cursor += numRead; - numBytesRead += numRead; - numLeft -= numRead; - // System.out.println("read " + numRead + ", cursor = " + cursor + - // ", len = " + len); - } - return cursor - off; - } - - /** @return Skip forward in stream and add numBytes to offset. */ - @Override - public long skip(long numBytes) throws IOException { - numBytesRead += numBytes; - return super.skip(numBytes); - } - - /** Read 32 bit signed integer assuming Big Endian byte order. */ - public int readIntBig() throws IOException { - int result = read() & 0xFF; - result = (result << 8) | (read() & 0xFF); - result = (result << 8) | (read() & 0xFF); - int data = read(); - if (data == -1) - throw new EOFException("readIntBig() - EOF in middle of word at offset " + numBytesRead); - result = (result << 8) | (data & 0xFF); - return result; - } - - /** Read 32 bit signed integer assuming Little Endian byte order. */ - public int readIntLittle() throws IOException { - int result = read() & 0xFF; // LSB - result |= ((read() & 0xFF) << 8); - result |= ((read() & 0xFF) << 16); - int data = read(); - if (data == -1) - throw new EOFException("readIntLittle() - EOF in middle of word at offset " - + numBytesRead); - result |= (data << 24); - return result; - } - - /** Read 16 bit signed short assuming Big Endian byte order. */ - public short readShortBig() throws IOException { - short result = (short) ((read() << 8)); // MSB - int data = read(); - if (data == -1) - throw new EOFException("readShortBig() - EOF in middle of word at offset " - + numBytesRead); - result |= data & 0xFF; - return result; - } - - /** Read 16 bit signed short assuming Little Endian byte order. */ - public short readShortLittle() throws IOException { - short result = (short) (read() & 0xFF); // LSB - int data = read(); // MSB - if (data == -1) - throw new EOFException("readShortLittle() - EOF in middle of word at offset " - + numBytesRead); - result |= data << 8; - return result; - } - - public int readUShortLittle() throws IOException { - return (readShortLittle()) & 0x0000FFFF; - } - - /** Read 8 bit signed byte. */ - public byte readByte() throws IOException { - return (byte) read(); - } - - /** Read 32 bit signed int assuming IFF order. */ - public int readChunkSize() throws IOException { - if (isRIFF()) { - return readIntLittle(); - } - { - return readIntBig(); - } - } - - /** Convert a 4 character IFF ID to a String */ - public static String IDToString(int ID) { - byte bar[] = new byte[4]; - bar[0] = (byte) (ID >> 24); - bar[1] = (byte) (ID >> 16); - bar[2] = (byte) (ID >> 8); - bar[3] = (byte) ID; - return new String(bar); - } - - /** - * Parse the stream after reading the first ID and pass the forms and chunks to the ChunkHandler - */ - public void parseAfterHead(ChunkHandler handler) throws IOException { - int numBytes = readChunkSize(); - totalSize = numBytes + 8; - parseChunk(handler, fileId, numBytes); - if (debug) - System.out.println("parse() ------- end"); - } - - /** - * Parse the FORM and pass the chunks to the ChunkHandler The cursor should be positioned right - * after the type field. - */ - void parseForm(ChunkHandler handler, int ID, int numBytes, int type) throws IOException { - if (debug) { - System.out.println("IFF: parseForm >>>>>>>>>>>>>>>>>> BEGIN"); - } - while (numBytes > 8) { - int ckid = readIntBig(); - int size = readChunkSize(); - numBytes -= 8; - if (debug) { - System.out.println("chunk( " + IDToString(ckid) + ", " + size + " )"); - } - if (size < 0) { - throw new IOException("Bad IFF chunk Size: " + IDToString(ckid) + " = 0x" - + Integer.toHexString(ckid) + ", Size = " + size); - } - parseChunk(handler, ckid, size); - if ((size & 1) == 1) - size++; // even-up - numBytes -= size; - if (debug) { - System.out.println("parseForm: numBytes left in form = " + numBytes); - } - } - if (debug) { - System.out.println("IFF: parseForm <<<<<<<<<<<<<<<<<<<< END"); - } - - if (numBytes > 0) { - System.out.println("IFF Parser detected " + numBytes - + " bytes of garbage at end of FORM."); - skip(numBytes); - } - } - - /* - * Parse one chunk from IFF file. After calling handler, make sure stream is positioned at end - * of chunk. - */ - void parseChunk(ChunkHandler handler, int ckid, int numBytes) throws IOException { - long startOffset, endOffset; - int numRead; - startOffset = getOffset(); - if (isForm(ckid)) { - int type = readIntBig(); - if (debug) - System.out.println("parseChunk: form = " + IDToString(ckid) + ", " + numBytes - + ", " + IDToString(type)); - handler.handleForm(this, ckid, numBytes - 4, type); - endOffset = getOffset(); - numRead = (int) (endOffset - startOffset); - if (numRead < numBytes) - parseForm(handler, ckid, (numBytes - numRead), type); - } else { - handler.handleChunk(this, ckid, numBytes); - } - endOffset = getOffset(); - numRead = (int) (endOffset - startOffset); - if (debug) { - System.out.println("parseChunk: endOffset = " + endOffset); - System.out.println("parseChunk: numRead = " + numRead); - } - if ((numBytes & 1) == 1) - numBytes++; // even-up - if (numRead < numBytes) - skip(numBytes - numRead); - } - - public void readHead() throws IOException { - if (debug) - System.out.println("parse() ------- begin"); - numBytesRead = 0; - fileId = readIntBig(); - } - - public boolean isRIFF() { - return (fileId == RIFF_ID); - } - - public boolean isIFF() { - return (fileId == FORM_ID); - } - - /** - * Does the following chunk ID correspond to a container type like FORM? - */ - public boolean isForm(int ckid) { - if (isRIFF()) { - switch (ckid) { - case LIST_ID: - case RIFF_ID: - return true; - default: - return false; - } - } else { - switch (ckid) { - case LIST_ID: - case FORM_ID: - return true; - default: - return false; - } - } - } - -} diff --git a/src/com/jsyn/util/soundfile/WAVEFileParser.java b/src/com/jsyn/util/soundfile/WAVEFileParser.java deleted file mode 100644 index ec9350c..0000000 --- a/src/com/jsyn/util/soundfile/WAVEFileParser.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Copyright 2009 Phil Burk, Mobileer Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.jsyn.util.soundfile; - -import java.io.EOFException; -import java.io.IOException; - -import com.jsyn.data.FloatSample; -import com.jsyn.data.SampleMarker; -import com.jsyn.util.SampleLoader; - -class WAVEFileParser extends AudioFileParser implements ChunkHandler { - static final short WAVE_FORMAT_PCM = 1; - static final short WAVE_FORMAT_IEEE_FLOAT = 3; - static final short WAVE_FORMAT_EXTENSIBLE = (short) 0xFFFE; - - static final byte[] KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { - 3, 0, 0, 0, 0, 0, 16, 0, -128, 0, 0, -86, 0, 56, -101, 113 - }; - static final byte[] KSDATAFORMAT_SUBTYPE_PCM = { - 1, 0, 0, 0, 0, 0, 16, 0, -128, 0, 0, -86, 0, 56, -101, 113 - }; - - static final int WAVE_ID = ('W' << 24) | ('A' << 16) | ('V' << 8) | 'E'; - static final int FMT_ID = ('f' << 24) | ('m' << 16) | ('t' << 8) | ' '; - static final int DATA_ID = ('d' << 24) | ('a' << 16) | ('t' << 8) | 'a'; - static final int CUE_ID = ('c' << 24) | ('u' << 16) | ('e' << 8) | ' '; - static final int FACT_ID = ('f' << 24) | ('a' << 16) | ('c' << 8) | 't'; - static final int SMPL_ID = ('s' << 24) | ('m' << 16) | ('p' << 8) | 'l'; - static final int LTXT_ID = ('l' << 24) | ('t' << 16) | ('x' << 8) | 't'; - static final int LABL_ID = ('l' << 24) | ('a' << 16) | ('b' << 8) | 'l'; - - int samplesPerBlock = 0; - int blockAlign = 0; - private int numFactSamples = 0; - private short format; - - WAVEFileParser() { - } - - @Override - FloatSample finish() throws IOException { - if ((byteData == null)) { - throw new IOException("No data found in audio sample."); - } - float[] floatData = new float[numFrames * samplesPerFrame]; - if (bitsPerSample == 16) { - SampleLoader.decodeLittleI16ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (bitsPerSample == 24) { - SampleLoader.decodeLittleI24ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (bitsPerSample == 32) { - if (format == WAVE_FORMAT_IEEE_FLOAT) { - SampleLoader.decodeLittleF32ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (format == WAVE_FORMAT_PCM) { - SampleLoader.decodeLittleI32ToF32(byteData, 0, byteData.length, floatData, 0); - } else { - throw new IOException("WAV: Unsupported format = " + format); - } - } else { - throw new IOException("WAV: Unsupported bitsPerSample = " + bitsPerSample); - } - - return makeSample(floatData); - } - - // typedef struct { - // long dwIdentifier; - // long dwPosition; - // ID fccChunk; - // long dwChunkStart; - // long dwBlockStart; - // long dwSampleOffset; - // } CuePoint; - - /* Parse various chunks encountered in WAV file. */ - void parseCueChunk(IFFParser parser, int ckSize) throws IOException { - int numCuePoints = parser.readIntLittle(); - if (IFFParser.debug) { - System.out.println("WAV: numCuePoints = " + numCuePoints); - } - if ((ckSize - 4) != (6 * 4 * numCuePoints)) - throw new EOFException("Cue chunk too short!"); - for (int i = 0; i < numCuePoints; i++) { - int dwName = parser.readIntLittle(); /* dwName */ - int position = parser.readIntLittle(); // dwPosition - parser.skip(3 * 4); // fccChunk, dwChunkStart, dwBlockStart - int sampleOffset = parser.readIntLittle(); // dwPosition - - if (IFFParser.debug) { - System.out.println("WAV: parseCueChunk: #" + i + ", dwPosition = " + position - + ", dwName = " + dwName + ", dwSampleOffset = " + sampleOffset); - } - SampleMarker cuePoint = findOrCreateCuePoint(dwName); - cuePoint.position = position; - } - } - - void parseLablChunk(IFFParser parser, int ckSize) throws IOException { - int dwName = parser.readIntLittle(); - int textLength = (ckSize - 4) - 1; // don't read NUL terminator - String text = parseString(parser, textLength); - if (IFFParser.debug) { - System.out.println("WAV: label id = " + dwName + ", text = " + text); - } - SampleMarker cuePoint = findOrCreateCuePoint(dwName); - cuePoint.name = text; - } - - void parseLtxtChunk(IFFParser parser, int ckSize) throws IOException { - int dwName = parser.readIntLittle(); - int dwSampleLength = parser.readIntLittle(); - parser.skip(4 + (4 * 2)); // purpose through codepage - int textLength = (ckSize - ((4 * 4) + (4 * 2))) - 1; // don't read NUL - // terminator - if (textLength > 0) { - String text = parseString(parser, textLength); - if (IFFParser.debug) { - System.out.println("WAV: ltxt id = " + dwName + ", dwSampleLength = " - + dwSampleLength + ", text = " + text); - } - SampleMarker cuePoint = findOrCreateCuePoint(dwName); - cuePoint.comment = text; - } - } - - void parseFmtChunk(IFFParser parser, int ckSize) throws IOException { - format = parser.readShortLittle(); - samplesPerFrame = parser.readShortLittle(); - frameRate = parser.readIntLittle(); - parser.readIntLittle(); /* skip dwAvgBytesPerSec */ - blockAlign = parser.readShortLittle(); - bitsPerSample = parser.readShortLittle(); - - if (IFFParser.debug) { - System.out.println("WAV: format = 0x" + Integer.toHexString(format)); - System.out.println("WAV: bitsPerSample = " + bitsPerSample); - System.out.println("WAV: samplesPerFrame = " + samplesPerFrame); - } - bytesPerFrame = blockAlign; - bytesPerSample = bytesPerFrame / samplesPerFrame; - samplesPerBlock = (8 * blockAlign) / bitsPerSample; - - if (format == WAVE_FORMAT_EXTENSIBLE) { - int extraSize = parser.readShortLittle(); - short validBitsPerSample = parser.readShortLittle(); - int channelMask = parser.readIntLittle(); - byte[] guid = new byte[16]; - parser.read(guid); - if (IFFParser.debug) { - System.out.println("WAV: extraSize = " + extraSize); - System.out.println("WAV: validBitsPerSample = " + validBitsPerSample); - System.out.println("WAV: channelMask = " + channelMask); - System.out.print("guid = {"); - for (int i = 0; i < guid.length; i++) { - System.out.print(guid[i] + ", "); - } - System.out.println("}"); - } - if (matchBytes(guid, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { - format = WAVE_FORMAT_IEEE_FLOAT; - } else if (matchBytes(guid, KSDATAFORMAT_SUBTYPE_PCM)) { - format = WAVE_FORMAT_PCM; - } - } - if ((format != WAVE_FORMAT_PCM) && (format != WAVE_FORMAT_IEEE_FLOAT)) { - throw new IOException( - "Only WAVE_FORMAT_PCM and WAVE_FORMAT_IEEE_FLOAT supported. format = " + format); - } - if ((bitsPerSample != 16) && (bitsPerSample != 24) && (bitsPerSample != 32)) { - throw new IOException( - "Only 16 and 24 bit PCM or 32-bit float WAV files supported. width = " - + bitsPerSample); - } - } - - private boolean matchBytes(byte[] bar1, byte[] bar2) { - if (bar1.length != bar2.length) - return false; - for (int i = 0; i < bar1.length; i++) { - if (bar1[i] != bar2[i]) - return false; - } - return true; - } - - private int convertByteToFrame(int byteOffset) throws IOException { - if (blockAlign == 0) { - throw new IOException("WAV file has bytesPerBlock = zero"); - } - if (samplesPerFrame == 0) { - throw new IOException("WAV file has samplesPerFrame = zero"); - } - int nFrames = (samplesPerBlock * byteOffset) / (samplesPerFrame * blockAlign); - return nFrames; - } - - private int calculateNumFrames(int numBytes) throws IOException { - int nFrames; - if (numFactSamples > 0) { - // nFrames = numFactSamples / samplesPerFrame; - nFrames = numFactSamples; // FIXME which is right - } else { - nFrames = convertByteToFrame(numBytes); - } - return nFrames; - } - - // Read fraction in range of 0 to 0xFFFFFFFF and - // convert to 0.0 to 1.0 range. - private double readFraction(IFFParser parser) throws IOException { - // Put L at end or we get -1. - long maxFraction = 0x0FFFFFFFFL; - // Get unsigned fraction. Have to fit in long. - long fraction = (parser.readIntLittle()) & maxFraction; - return (double) fraction / (double) maxFraction; - } - - void parseSmplChunk(IFFParser parser, int ckSize) throws IOException { - parser.readIntLittle(); // Manufacturer - parser.readIntLittle(); // Product - parser.readIntLittle(); // Sample Period - int unityNote = parser.readIntLittle(); - double pitchFraction = readFraction(parser); - originalPitch = unityNote + pitchFraction; - - parser.readIntLittle(); // SMPTE Format - parser.readIntLittle(); // SMPTE Offset - int numLoops = parser.readIntLittle(); - parser.readIntLittle(); // Sampler Data - - int lastCueID = Integer.MAX_VALUE; - for (int i = 0; i < numLoops; i++) { - int cueID = parser.readIntLittle(); - parser.readIntLittle(); // type - int loopStartPosition = parser.readIntLittle(); - // Point to sample one after. - int loopEndPosition = parser.readIntLittle() + 1; - // TODO handle fractional loop sizes? - double endFraction = readFraction(parser); - parser.readIntLittle(); // playCount - - // Use lowest numbered cue. - if (cueID < lastCueID) { - sustainBegin = loopStartPosition; - sustainEnd = loopEndPosition; - } - } - } - - void parseFactChunk(IFFParser parser, int ckSize) throws IOException { - numFactSamples = parser.readIntLittle(); - } - - void parseDataChunk(IFFParser parser, int ckSize) throws IOException { - long numRead; - dataPosition = parser.getOffset(); - if (ifLoadData) { - byteData = new byte[ckSize]; - numRead = parser.read(byteData); - } else { - numRead = parser.skip(ckSize); - } - if (numRead != ckSize) { - throw new EOFException("WAV data chunk too short! Read " + numRead + " instead of " - + ckSize); - } - numFrames = calculateNumFrames(ckSize); - } - - @Override - public void handleForm(IFFParser parser, int ckID, int ckSize, int type) throws IOException { - if ((ckID == IFFParser.RIFF_ID) && (type != WAVE_ID)) - throw new IOException("Bad WAV form type = " + IFFParser.IDToString(type)); - } - - /** - * Called by parse() method to handle chunks in a WAV specific manner. - * - * @param ckID four byte chunk ID such as 'data' - * @param ckSize size of chunk in bytes - * @return number of bytes left in chunk - */ - @Override - public void handleChunk(IFFParser parser, int ckID, int ckSize) throws IOException { - switch (ckID) { - case FMT_ID: - parseFmtChunk(parser, ckSize); - break; - case DATA_ID: - parseDataChunk(parser, ckSize); - break; - case CUE_ID: - parseCueChunk(parser, ckSize); - break; - case FACT_ID: - parseFactChunk(parser, ckSize); - break; - case SMPL_ID: - parseSmplChunk(parser, ckSize); - break; - case LABL_ID: - parseLablChunk(parser, ckSize); - break; - case LTXT_ID: - parseLtxtChunk(parser, ckSize); - break; - default: - break; - } - } - - /* - * (non-Javadoc) - * @see com.softsynth.javasonics.util.AudioSampleLoader#isLittleEndian() - */ - boolean isLittleEndian() { - return true; - } - -} |