diff options
author | RubbaBoy <[email protected]> | 2020-07-06 02:33:28 -0400 |
---|---|---|
committer | Phil Burk <[email protected]> | 2020-10-30 11:19:34 -0700 |
commit | 46888fae6eb7b1dd386f7af7d101ead99ae61981 (patch) | |
tree | 8969bbfd68d2fb5c0d8b86da49ec2eca230a72ab /src/main/java/com/jsyn/data | |
parent | c51e92e813dd481603de078f0778e1f75db2ab05 (diff) |
Restructured project, added gradle, JUnit, logger, and more
Added Gradle (and removed ant), modernized testing via the JUnit framework, moved standalone examples from the tests directory to a separate module, removed sparsely used Java logger and replaced it with SLF4J. More work could be done, however this is a great start to greatly improving the health of the codebase.
Diffstat (limited to 'src/main/java/com/jsyn/data')
-rw-r--r-- | src/main/java/com/jsyn/data/AudioSample.java | 108 | ||||
-rw-r--r-- | src/main/java/com/jsyn/data/DoubleTable.java | 109 | ||||
-rw-r--r-- | src/main/java/com/jsyn/data/FloatSample.java | 164 | ||||
-rw-r--r-- | src/main/java/com/jsyn/data/Function.java | 35 | ||||
-rw-r--r-- | src/main/java/com/jsyn/data/HammingWindow.java | 41 | ||||
-rw-r--r-- | src/main/java/com/jsyn/data/HannWindow.java | 36 | ||||
-rw-r--r-- | src/main/java/com/jsyn/data/SampleMarker.java | 30 | ||||
-rw-r--r-- | src/main/java/com/jsyn/data/SegmentedEnvelope.java | 125 | ||||
-rw-r--r-- | src/main/java/com/jsyn/data/SequentialData.java | 96 | ||||
-rw-r--r-- | src/main/java/com/jsyn/data/SequentialDataCommon.java | 136 | ||||
-rw-r--r-- | src/main/java/com/jsyn/data/ShortSample.java | 123 | ||||
-rw-r--r-- | src/main/java/com/jsyn/data/SpectralWindow.java | 21 | ||||
-rw-r--r-- | src/main/java/com/jsyn/data/SpectralWindowFactory.java | 55 | ||||
-rw-r--r-- | src/main/java/com/jsyn/data/Spectrum.java | 97 |
14 files changed, 1176 insertions, 0 deletions
diff --git a/src/main/java/com/jsyn/data/AudioSample.java b/src/main/java/com/jsyn/data/AudioSample.java new file mode 100644 index 0000000..dcbbae5 --- /dev/null +++ b/src/main/java/com/jsyn/data/AudioSample.java @@ -0,0 +1,108 @@ +/* + * 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.data; + +import java.util.ArrayList; + +/** + * Base class for FloatSample and ShortSample. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public abstract class AudioSample extends SequentialDataCommon { + protected int numFrames; + protected int channelsPerFrame = 1; + private double frameRate = 44100.0; + private double pitch; + private ArrayList<SampleMarker> markers; + + public abstract void allocate(int numFrames, int channelsPerFrame); + + @Override + public double getRateScaler(int index, double synthesisRate) { + return 1.0; + } + + public double getFrameRate() { + return frameRate; + } + + public void setFrameRate(double f) { + this.frameRate = f; + } + + @Override + public int getNumFrames() { + return numFrames; + } + + @Override + public int getChannelsPerFrame() { + return channelsPerFrame; + } + + public void setChannelsPerFrame(int channelsPerFrame) { + this.channelsPerFrame = channelsPerFrame; + } + + /** + * Set the recorded pitch as a fractional MIDI semitone value where 60 is Middle C. + * + * @param pitch + */ + public void setPitch(double pitch) { + this.pitch = pitch; + } + + public double getPitch() { + return pitch; + } + + public int getMarkerCount() { + if (markers == null) + return 0; + else + return markers.size(); + } + + public SampleMarker getMarker(int index) { + if (markers == null) + return null; + else + return markers.get(index); + } + + /** + * Add a marker that will be stored sorted by position. This is normally used internally by the + * SampleLoader. + * + * @param marker + */ + public void addMarker(SampleMarker marker) { + if (markers == null) + markers = new ArrayList<SampleMarker>(); + int idx = markers.size(); + for (int k = 0; k < markers.size(); k++) { + SampleMarker cue = markers.get(k); + if (cue.position > marker.position) { + idx = k; + break; + } + } + markers.add(idx, marker); + } +} diff --git a/src/main/java/com/jsyn/data/DoubleTable.java b/src/main/java/com/jsyn/data/DoubleTable.java new file mode 100644 index 0000000..ca64c94 --- /dev/null +++ b/src/main/java/com/jsyn/data/DoubleTable.java @@ -0,0 +1,109 @@ +/* + * 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.data; + +import com.jsyn.exceptions.ChannelMismatchException; + +/** + * Evaluate a Function by interpolating between the values in a table. This can be used for + * wavetable lookup or waveshaping. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class DoubleTable implements Function { + private double[] table; + + public DoubleTable(int numFrames) { + allocate(numFrames); + } + + public DoubleTable(double[] data) { + allocate(data.length); + write(data); + } + + public DoubleTable(ShortSample shortSample) { + if (shortSample.getChannelsPerFrame() != 1) { + throw new ChannelMismatchException("DoubleTable can only be built from mono samples."); + } + short[] buffer = new short[256]; + int framesLeft = shortSample.getNumFrames(); + allocate(framesLeft); + int cursor = 0; + while (framesLeft > 0) { + int numTransfer = framesLeft; + if (numTransfer > buffer.length) { + numTransfer = buffer.length; + } + shortSample.read(cursor, buffer, 0, numTransfer); + write(cursor, buffer, 0, numTransfer); + cursor += numTransfer; + framesLeft -= numTransfer; + } + } + + public void allocate(int numFrames) { + table = new double[numFrames]; + } + + public int length() { + return table.length; + } + + public void write(double[] data) { + write(0, data, 0, data.length); + } + + public void write(int startFrame, short[] data, int startIndex, int numFrames) { + for (int i = 0; i < numFrames; i++) { + table[startFrame + i] = data[startIndex + i] * (1.0 / 32768.0); + } + } + + public void write(int startFrame, double[] data, int startIndex, int numFrames) { + for (int i = 0; i < numFrames; i++) { + table[startFrame + i] = data[startIndex + i]; + } + } + + /** + * Treat the double array as a lookup table with a domain of -1.0 to 1.0. If the input is out of + * range then the output will clip to the end values. + * + * @param input + * @return interpolated value from table + */ + @Override + public double evaluate(double input) { + double interp; + if (input < -1.0) { + interp = table[0]; + } else if (input < 1.0) { + double fractionalIndex = (table.length - 1) * (input - (-1.0)) / 2.0; + // We don't need floor() because fractionalIndex >= 0.0 + int index = (int) fractionalIndex; + double fraction = fractionalIndex - index; + + double s1 = table[index]; + double s2 = table[index + 1]; + interp = ((s2 - s1) * fraction) + s1; + } else { + interp = table[table.length - 1]; + } + return interp; + } +} diff --git a/src/main/java/com/jsyn/data/FloatSample.java b/src/main/java/com/jsyn/data/FloatSample.java new file mode 100644 index 0000000..2d8c973 --- /dev/null +++ b/src/main/java/com/jsyn/data/FloatSample.java @@ -0,0 +1,164 @@ +/* + * 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.data; + +import com.jsyn.unitgen.FixedRateMonoReader; +import com.jsyn.unitgen.FixedRateStereoReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.unitgen.VariableRateStereoReader; +import com.jsyn.util.SampleLoader; + +/** + * Store multi-channel floating point audio data in an interleaved buffer. The values are stored as + * 32-bit floats. You can play samples using one of the readers, for example VariableRateMonoReader. + * + * @author Phil Burk (C) 2010 Mobileer Inc + * @see SampleLoader + * @see FixedRateMonoReader + * @see FixedRateStereoReader + * @see VariableRateMonoReader + * @see VariableRateStereoReader + */ +public class FloatSample extends AudioSample implements Function { + private float[] buffer; + + public FloatSample() { + } + + /** Constructor for mono samples. */ + public FloatSample(int numFrames) { + this(numFrames, 1); + } + + /** Constructor for mono samples with data. */ + public FloatSample(float[] data) { + this(data.length, 1); + write(data); + } + + /** Constructor for multi-channel samples with data. */ + public FloatSample(float[] data, int channelsPerFrame) { + this(data.length / channelsPerFrame, channelsPerFrame); + write(data); + } + + /** + * Create a silent sample with enough memory to hold the audio data. The number of sample + * numbers in the array will be numFrames*channelsPerFrame. + * + * @param numFrames number of sample groups. A stereo frame contains 2 samples. + * @param channelsPerFrame 1 for mono, 2 for stereo + */ + public FloatSample(int numFrames, int channelsPerFrame) { + allocate(numFrames, channelsPerFrame); + } + + /** + * Allocate memory to hold the audio data. The number of sample numbers in the array will be + * numFrames*channelsPerFrame. + * + * @param numFrames number of sample groups. A stereo frame contains 2 samples. + * @param channelsPerFrame 1 for mono, 2 for stereo + */ + @Override + public void allocate(int numFrames, int channelsPerFrame) { + buffer = new float[numFrames * channelsPerFrame]; + this.numFrames = numFrames; + this.channelsPerFrame = channelsPerFrame; + } + + /** + * Note that in a stereo sample, a frame has two values. + * + * @param startFrame index of frame in the sample + * @param data data to be written + * @param startIndex index of first value in array + * @param numFrames + */ + public void write(int startFrame, float[] data, int startIndex, int numFrames) { + int numSamplesToWrite = numFrames * channelsPerFrame; + int firstSampleIndexToWrite = startFrame * channelsPerFrame; + System.arraycopy(data, startIndex, buffer, firstSampleIndexToWrite, numSamplesToWrite); + } + + /** + * Note that in a stereo sample, a frame has two values. + * + * @param startFrame index of frame in the sample + * @param data array to receive the data from the sample + * @param startIndex index of first location in array to start filling + * @param numFrames + */ + public void read(int startFrame, float[] data, int startIndex, int numFrames) { + int numSamplesToRead = numFrames * channelsPerFrame; + int firstSampleIndexToRead = startFrame * channelsPerFrame; + System.arraycopy(buffer, firstSampleIndexToRead, data, startIndex, numSamplesToRead); + } + + /** + * Write the entire array to the sample. The sample data must have already been allocated with + * enough room to contain the data. + * + * @param data + */ + public void write(float[] data) { + write(0, data, 0, data.length / getChannelsPerFrame()); + } + + public void read(float[] data) { + read(0, data, 0, data.length / getChannelsPerFrame()); + } + + @Override + public double readDouble(int index) { + return buffer[index]; + } + + @Override + public void writeDouble(int index, double value) { + buffer[index] = (float) value; + } + + /** + * Interpolate between two adjacent samples. + * Note that this will only work for mono, single channel samples. + * + * @param fractionalIndex must be >=0 and < (size-1) + */ + public double interpolate(double fractionalIndex) { + int index = (int) fractionalIndex; + float phase = (float) (fractionalIndex - index); + float source = buffer[index]; + float target = buffer[index + 1]; + return ((target - source) * phase) + source; + } + + /** + * Note that this will only work for mono, single channel samples. + */ + @Override + public double evaluate(double input) { + // Input ranges from -1 to +1 + // Map it to range of sample with guard point. + double normalizedInput = (input + 1.0) * 0.5; + // Clip so it does not go out of range of the sample. + if (normalizedInput < 0.0) normalizedInput = 0.0; + else if (normalizedInput > 1.0) normalizedInput = 1.0; + double fractionalIndex = (getNumFrames() - 1.01) * normalizedInput; + return interpolate(fractionalIndex); + } +} diff --git a/src/main/java/com/jsyn/data/Function.java b/src/main/java/com/jsyn/data/Function.java new file mode 100644 index 0000000..c0e6566 --- /dev/null +++ b/src/main/java/com/jsyn/data/Function.java @@ -0,0 +1,35 @@ +/* + * 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.data; + +import com.jsyn.unitgen.FunctionEvaluator; +import com.jsyn.unitgen.FunctionOscillator; + +/** + * @author Phil Burk (C) 2010 Mobileer Inc + * @see FunctionEvaluator + * @see FunctionOscillator + */ +public interface Function { + /** + * Convert an input value to an output value. + * + * @param input + * @return generated value + */ + public double evaluate(double input); +} diff --git a/src/main/java/com/jsyn/data/HammingWindow.java b/src/main/java/com/jsyn/data/HammingWindow.java new file mode 100644 index 0000000..d8e1238 --- /dev/null +++ b/src/main/java/com/jsyn/data/HammingWindow.java @@ -0,0 +1,41 @@ +/* + * 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.data; + +public class HammingWindow implements SpectralWindow { + private double[] data; + + /** Construct a generalized Hamming Window */ + public HammingWindow(int length, double alpha, double beta) { + data = new double[length]; + double scaler = 2.0 * Math.PI / (length - 1); + for (int i = 0; i < length; i++) { + data[i] = alpha - (beta * (Math.cos(i * scaler))); + } + } + + /** Traditional Hamming Window with alpha = 25/46 and beta = 21/46 */ + public HammingWindow(int length) { + this(length, 25.0 / 46.0, 21.0 / 46.0); + } + + @Override + public double get(int index) { + return data[index]; + } + +} diff --git a/src/main/java/com/jsyn/data/HannWindow.java b/src/main/java/com/jsyn/data/HannWindow.java new file mode 100644 index 0000000..878d07c --- /dev/null +++ b/src/main/java/com/jsyn/data/HannWindow.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013 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.data; + +import com.jsyn.unitgen.SpectralFFT; +import com.jsyn.unitgen.SpectralIFFT; + +/** + * A HammingWindow with alpha and beta = 0.5. + * + * @author Phil Burk (C) 2013 Mobileer Inc + * @see SpectralWindow + * @see SpectralFFT + * @see SpectralIFFT + */ +public class HannWindow extends HammingWindow { + + public HannWindow(int length) { + super(length, 0.5, 0.5); + } + +} diff --git a/src/main/java/com/jsyn/data/SampleMarker.java b/src/main/java/com/jsyn/data/SampleMarker.java new file mode 100644 index 0000000..d3db1d4 --- /dev/null +++ b/src/main/java/com/jsyn/data/SampleMarker.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012 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.data; + +/** + * A marker for an audio sample. + * + * @author (C) 2012 Phil Burk, Mobileer Inc + */ + +public class SampleMarker { + /** Sample frame index. */ + public int position; + public String name; + public String comment; +} diff --git a/src/main/java/com/jsyn/data/SegmentedEnvelope.java b/src/main/java/com/jsyn/data/SegmentedEnvelope.java new file mode 100644 index 0000000..efdfd89 --- /dev/null +++ b/src/main/java/com/jsyn/data/SegmentedEnvelope.java @@ -0,0 +1,125 @@ +/* + * 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.data; + +import com.jsyn.unitgen.EnvelopeDAHDSR; +import com.jsyn.unitgen.VariableRateMonoReader; + +/** + * Store an envelope as a series of line segments. Each line is described as a duration and a target + * value. The envelope can be played using a {@link VariableRateMonoReader}. Here is an example that + * generates an envelope that looks like a traditional ADSR envelope. + * + * <pre> + * <code> + * // Create an amplitude envelope and fill it with data. + * double[] ampData = { + * 0.02, 0.9, // duration,value pair 0, "attack" + * 0.10, 0.5, // pair 1, "decay" + * 0.50, 0.0 // pair 2, "release" + * }; + * SegmentedEnvelope ampEnvelope = new SegmentedEnvelope( ampData ); + * + * // Hang at end of decay segment to provide a "sustain" segment. + * ampEnvelope.setSustainBegin( 1 ); + * ampEnvelope.setSustainEnd( 1 ); + * + * // Play the envelope using queueOn so that it uses the sustain and release information. + * synth.add( ampEnv = new VariableRateMonoReader() ); + * ampEnv.dataQueue.queueOn( ampEnvelope ); + * </code> + * </pre> + * + * As an alternative you could use an {@link EnvelopeDAHDSR}. + * + * @author Phil Burk (C) 2010 Mobileer Inc + * @see VariableRateMonoReader + * @see EnvelopeDAHDSR + */ +public class SegmentedEnvelope extends SequentialDataCommon { + private double[] buffer; + + public SegmentedEnvelope(int maxFrames) { + allocate(maxFrames); + } + + public SegmentedEnvelope(double[] pairs) { + this(pairs.length / 2); + write(pairs); + } + + public void allocate(int maxFrames) { + buffer = new double[maxFrames * 2]; + this.maxFrames = maxFrames; + this.numFrames = 0; + } + + /** + * Write frames of envelope data. A frame consists of a duration and a value. + * + * @param startFrame Index of frame in envelope to write to. + * @param data Pairs of duration and value. + * @param startIndex Index of frame in data[] to read from. + * @param numToWrite Number of frames (pairs) to write. + */ + public void write(int startFrame, double[] data, int startIndex, int numToWrite) { + System.arraycopy(data, startIndex * 2, buffer, startFrame * 2, numToWrite * 2); + if ((startFrame + numToWrite) > numFrames) { + numFrames = startFrame + numToWrite; + } + } + + public void read(int startFrame, double[] data, int startIndex, int numToRead) { + System.arraycopy(buffer, startFrame * 2, data, startIndex * 2, numToRead * 2); + } + + public void write(double[] data) { + write(0, data, 0, data.length / 2); + } + + public void read(double[] data) { + read(0, data, 0, data.length / 2); + } + + /** Read the value of an envelope, not the duration. */ + @Override + public double readDouble(int index) { + return buffer[(index * 2) + 1]; + } + + @Override + public void writeDouble(int index, double value) { + buffer[(index * 2) + 1] = value; + if ((index + 1) > numFrames) { + numFrames = index + 1; + } + } + + @Override + public double getRateScaler(int index, double synthesisPeriod) { + double duration = buffer[index * 2]; + if (duration < synthesisPeriod) { + duration = synthesisPeriod; + } + return 1.0 / duration; + } + + @Override + public int getChannelsPerFrame() { + return 1; + } +} diff --git a/src/main/java/com/jsyn/data/SequentialData.java b/src/main/java/com/jsyn/data/SequentialData.java new file mode 100644 index 0000000..f567493 --- /dev/null +++ b/src/main/java/com/jsyn/data/SequentialData.java @@ -0,0 +1,96 @@ +/* + * 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.data; + +import com.jsyn.unitgen.FixedRateMonoReader; +import com.jsyn.unitgen.FixedRateMonoWriter; +import com.jsyn.unitgen.FixedRateStereoReader; +import com.jsyn.unitgen.FixedRateStereoWriter; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.unitgen.VariableRateStereoReader; + +/** + * Interface for objects that can be read and/or written by index. The index is not stored + * internally so they can be shared by multiple readers. + * + * @author Phil Burk (C) 2010 Mobileer Inc + * @see FixedRateMonoReader + * @see FixedRateStereoReader + * @see FixedRateMonoWriter + * @see FixedRateStereoWriter + * @see VariableRateMonoReader + * @see VariableRateStereoReader + */ +public interface SequentialData { + /** + * Write a value at the given index. + * + * @param index sample index is ((frameIndex * channelsPerFrame) + channelIndex) + * @param value the value to be written + */ + void writeDouble(int index, double value); + + /** + * Read a value from the sample independently from the internal storage format. + * + * @param index sample index is ((frameIndex * channelsPerFrame) + channelIndex) + */ + double readDouble(int index); + + /*** + * @return Beginning of sustain loop or -1 if no loop. + */ + public int getSustainBegin(); + + /** + * SustainEnd value is the frame index of the frame just past the end of the loop. The number of + * frames included in the loop is (SustainEnd - SustainBegin). + * + * @return End of sustain loop or -1 if no loop. + */ + public int getSustainEnd(); + + /*** + * @return Beginning of release loop or -1 if no loop. + */ + public int getReleaseBegin(); + + /*** + * @return End of release loop or -1 if no loop. + */ + public int getReleaseEnd(); + + /** + * Get rate to play the data. In an envelope this correspond to the inverse of the frame + * duration and would vary frame to frame. For an audio sample it is 1.0. + * + * @param index + * @param synthesisRate + * @return rate to scale the playback speed. + */ + double getRateScaler(int index, double synthesisRate); + + /** + * @return For a stereo sample, return 2. + */ + int getChannelsPerFrame(); + + /** + * @return The number of valid frames that can be read. + */ + int getNumFrames(); +} diff --git a/src/main/java/com/jsyn/data/SequentialDataCommon.java b/src/main/java/com/jsyn/data/SequentialDataCommon.java new file mode 100644 index 0000000..5cc51df --- /dev/null +++ b/src/main/java/com/jsyn/data/SequentialDataCommon.java @@ -0,0 +1,136 @@ +/* + * 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.data; + +/** + * Abstract base class for envelopes and samples that adds sustain and release loops. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public abstract class SequentialDataCommon implements SequentialData { + protected int numFrames; + protected int maxFrames; + private int sustainBegin = -1; + private int sustainEnd = -1; + private int releaseBegin = -1; + private int releaseEnd = -1; + + @Override + public abstract void writeDouble(int index, double value); + + @Override + public abstract double readDouble(int index); + + @Override + public abstract double getRateScaler(int index, double synthesisRate); + + @Override + public abstract int getChannelsPerFrame(); + + /** + * @return Maximum number of frames of data. + */ + public int getMaxFrames() { + return maxFrames; + } + + /** + * Set number of frames of data. Input will be clipped to maxFrames. This is useful when + * changing the contents of a sample or envelope. + */ + public void setNumFrames(int numFrames) { + if (numFrames > maxFrames) + numFrames = maxFrames; + this.numFrames = numFrames; + } + + @Override + public int getNumFrames() { + return numFrames; + } + + // JavaDocs will be copied from SequentialData + + @Override + public int getSustainBegin() { + return this.sustainBegin; + } + + @Override + public int getSustainEnd() { + return this.sustainEnd; + } + + @Override + public int getReleaseBegin() { + return this.releaseBegin; + } + + @Override + public int getReleaseEnd() { + return this.releaseEnd; + } + + /** + * Set beginning of a sustain loop. When UnitDataQueuePort.queueOn() is called, + * if the loop is set then the attack portion will be queued followed by this sustain + * region using queueLoop(). + * The number of frames in the loop will be (SustainEnd - SustainBegin). + * <p> + * For a steady sustain level, like in an ADSR envelope, set SustainBegin and + * SustainEnd to the same frame. + * <p> + * For a sustain that is modulated, include two or more frames in the loop. + * + * @param sustainBegin + */ + public void setSustainBegin(int sustainBegin) { + this.sustainBegin = sustainBegin; + } + + /** + * SustainEnd value is the frame index of the frame just past the end of the loop. + * The number of frames included in the loop is (SustainEnd - SustainBegin). + * + * @param sustainEnd + */ + public void setSustainEnd(int sustainEnd) { + this.sustainEnd = sustainEnd; + } + + /** + * The release loop behaves like the sustain loop but it is triggered + * by UnitDataQueuePort.queueOff(). + * + * @param releaseBegin + */ + public void setReleaseBegin(int releaseBegin) { + this.releaseBegin = releaseBegin; + } + + /** + * ReleaseEnd value is the frame index of the frame just past the end of the loop. + * The number of frames included in the loop is (ReleaseEnd - ReleaseBegin). + * + * @param releaseEnd + */ + + public void setReleaseEnd(int releaseEnd) { + this.releaseEnd = releaseEnd; + } + +} diff --git a/src/main/java/com/jsyn/data/ShortSample.java b/src/main/java/com/jsyn/data/ShortSample.java new file mode 100644 index 0000000..4a4110e --- /dev/null +++ b/src/main/java/com/jsyn/data/ShortSample.java @@ -0,0 +1,123 @@ +/* + * 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.data; + +import com.jsyn.engine.SynthesisEngine; +import com.jsyn.unitgen.FixedRateMonoReader; +import com.jsyn.unitgen.FixedRateStereoReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.unitgen.VariableRateStereoReader; +import com.jsyn.util.SampleLoader; + +/** + * Store multi-channel short audio data in an interleaved buffer. + * + * @author Phil Burk (C) 2010 Mobileer Inc + * @see SampleLoader + * @see FixedRateMonoReader + * @see FixedRateStereoReader + * @see VariableRateMonoReader + * @see VariableRateStereoReader + */ +public class ShortSample extends AudioSample { + private short[] buffer; + + public ShortSample() { + } + + public ShortSample(int numFrames, int channelsPerFrame) { + allocate(numFrames, channelsPerFrame); + } + + /** Constructor for mono samples with data. */ + public ShortSample(short[] data) { + this(data.length, 1); + write(data); + } + + /** Constructor for multi-channel samples with data. */ + public ShortSample(short[] data, int channelsPerFrame) { + this(data.length / channelsPerFrame, channelsPerFrame); + write(data); + } + + @Override + public void allocate(int numFrames, int channelsPerFrame) { + buffer = new short[numFrames * channelsPerFrame]; + this.numFrames = numFrames; + this.channelsPerFrame = channelsPerFrame; + } + + /** + * Note that in a stereo sample, a frame has two values. + * + * @param startFrame index of frame in the sample + * @param data data to be written + * @param startIndex index of first value in array + * @param numFrames + */ + public void write(int startFrame, short[] data, int startIndex, int numFrames) { + int numSamplesToWrite = numFrames * channelsPerFrame; + int firstSampleIndexToWrite = startFrame * channelsPerFrame; + System.arraycopy(data, startIndex, buffer, firstSampleIndexToWrite, numSamplesToWrite); + } + + /** + * Note that in a stereo sample, a frame has two values. + * + * @param startFrame index of frame in the sample + * @param data array to receive the data from the sample + * @param startIndex index of first location in array to start filling + * @param numFrames + */ + public void read(int startFrame, short[] data, int startIndex, int numFrames) { + int numSamplesToRead = numFrames * channelsPerFrame; + int firstSampleIndexToRead = startFrame * channelsPerFrame; + System.arraycopy(buffer, firstSampleIndexToRead, data, startIndex, numSamplesToRead); + } + + public void write(short[] data) { + write(0, data, 0, data.length); + } + + public void read(short[] data) { + read(0, data, 0, data.length); + } + + public short readShort(int index) { + return buffer[index]; + } + + public void writeShort(int index, short value) { + buffer[index] = value; + } + + /** Read a sample converted to a double in the range -1.0 to almost 1.0. */ + @Override + public double readDouble(int index) { + return SynthesisEngine.convertShortToDouble(buffer[index]); + } + + /** + * Write a double that will be clipped to the range -1.0 to almost 1.0 and converted to a short. + */ + @Override + public void writeDouble(int index, double value) { + buffer[index] = SynthesisEngine.convertDoubleToShort(value); + } + +} diff --git a/src/main/java/com/jsyn/data/SpectralWindow.java b/src/main/java/com/jsyn/data/SpectralWindow.java new file mode 100644 index 0000000..0fcfac4 --- /dev/null +++ b/src/main/java/com/jsyn/data/SpectralWindow.java @@ -0,0 +1,21 @@ +/* + * 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.data; + +public interface SpectralWindow { + public double get(int index); +} diff --git a/src/main/java/com/jsyn/data/SpectralWindowFactory.java b/src/main/java/com/jsyn/data/SpectralWindowFactory.java new file mode 100644 index 0000000..01cced6 --- /dev/null +++ b/src/main/java/com/jsyn/data/SpectralWindowFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013 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.data; + +import com.jsyn.unitgen.SpectralFFT; +import com.jsyn.unitgen.SpectralFilter; +import com.jsyn.unitgen.SpectralIFFT; + +/** + * Create shared windows as needed for use with FFTs and IFFTs. + * + * @author Phil Burk (C) 2013 Mobileer Inc + * @see SpectralWindow + * @see SpectralFFT + * @see SpectralIFFT + * @see SpectralFilter + */ +public class SpectralWindowFactory { + private static final int NUM_WINDOWS = 16; + private static final int MIN_SIZE_LOG_2 = 2; + private static HammingWindow[] hammingWindows = new HammingWindow[NUM_WINDOWS]; + private static HannWindow[] hannWindows = new HannWindow[NUM_WINDOWS]; + + /** @return a shared standard HammingWindow */ + public static HammingWindow getHammingWindow(int sizeLog2) { + int index = sizeLog2 - MIN_SIZE_LOG_2; + if (hammingWindows[index] == null) { + hammingWindows[index] = new HammingWindow(1 << sizeLog2); + } + return hammingWindows[index]; + } + + /** @return a shared HannWindow */ + public static HannWindow getHannWindow(int sizeLog2) { + int index = sizeLog2 - MIN_SIZE_LOG_2; + if (hannWindows[index] == null) { + hannWindows[index] = new HannWindow(1 << sizeLog2); + } + return hannWindows[index]; + } +} diff --git a/src/main/java/com/jsyn/data/Spectrum.java b/src/main/java/com/jsyn/data/Spectrum.java new file mode 100644 index 0000000..66e4ee4 --- /dev/null +++ b/src/main/java/com/jsyn/data/Spectrum.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013 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.data; + +import com.jsyn.unitgen.SpectralFFT; +import com.jsyn.unitgen.SpectralIFFT; +import com.jsyn.unitgen.SpectralProcessor; + +/** + * Complex spectrum with real and imaginary parts. The frequency associated with each bin of the + * spectrum is: + * + * <pre> + * frequency = binIndex * sampleRate / size + * </pre> + * + * Note that the upper half of the spectrum is above the Nyquist frequency. Those frequencies are + * mirrored around the Nyquist frequency. Note that this spectral API is experimental and may change + * at any time. + * + * @author Phil Burk (C) 2013 Mobileer Inc + * @version 016 + * @see SpectralFFT + * @see SpectralIFFT + * @see SpectralProcessor + */ +public class Spectrum { + private double[] real; + private double[] imaginary; + public static final int DEFAULT_SIZE_LOG_2 = 9; + public static final int DEFAULT_SIZE = 1 << DEFAULT_SIZE_LOG_2; + + public Spectrum() { + this(DEFAULT_SIZE); + } + + public Spectrum(int size) { + setSize(size); + } + + public double[] getReal() { + return real; + } + + public double[] getImaginary() { + return imaginary; + } + + /** + * If you change the size of the spectrum then the real and imaginary arrays will be + * reallocated. + * + * @param size + */ + public void setSize(int size) { + if ((real == null) || (real.length != size)) { + real = new double[size]; + imaginary = new double[size]; + } + } + + public int size() { + return real.length; + } + + /** + * Copy this spectrum to another spectrum of the same length. + * + * @param destination + */ + public void copyTo(Spectrum destination) { + assert (size() == destination.size()); + System.arraycopy(real, 0, destination.real, 0, real.length); + System.arraycopy(imaginary, 0, destination.imaginary, 0, imaginary.length); + } + + public void clear() { + for (int i = 0; i < real.length; i++) { + real[i] = 0.0; + imaginary[i] = 0.0; + } + } +} |