aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/com/jsyn/unitgen/GrainFarm.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/jsyn/unitgen/GrainFarm.java')
-rw-r--r--src/main/java/com/jsyn/unitgen/GrainFarm.java178
1 files changed, 178 insertions, 0 deletions
diff --git a/src/main/java/com/jsyn/unitgen/GrainFarm.java b/src/main/java/com/jsyn/unitgen/GrainFarm.java
new file mode 100644
index 0000000..78179bc
--- /dev/null
+++ b/src/main/java/com/jsyn/unitgen/GrainFarm.java
@@ -0,0 +1,178 @@
+/*
+ * 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.unitgen;
+
+import com.jsyn.ports.UnitInputPort;
+import com.jsyn.ports.UnitOutputPort;
+import com.jsyn.util.PseudoRandom;
+
+/**
+ * A unit generator that generates a cloud of sound using multiple Grains. Special thanks to my
+ * friend Ross Bencina for his excellent article on Granular Synthesis. Several of his ideas are
+ * reflected in this architecture. "Implementing Real-Time Granular Synthesis" by Ross Bencina,
+ * Audio Anecdotes III, 2001.
+ *
+ * <pre><code>
+ synth.add( sampleGrainFarm = new GrainFarm() );
+ grainFarm.allocate( NUM_GRAINS );
+</code></pre>
+ *
+ * @author Phil Burk (C) 2011 Mobileer Inc
+ * @see Grain
+ * @see GrainSourceSine
+ * @see RaisedCosineEnvelope
+ */
+public class GrainFarm extends UnitGenerator implements UnitSource {
+ /** A scaler for playback rate. Nominally 1.0. */
+ public UnitInputPort rate;
+ public UnitInputPort rateRange;
+ public UnitInputPort amplitude;
+ public UnitInputPort amplitudeRange;
+ public UnitInputPort density;
+ public UnitInputPort duration;
+ public UnitInputPort durationRange;
+ public UnitOutputPort output;
+
+ PseudoRandom randomizer;
+ private GrainState[] states;
+ private double countScaler = 1.0;
+ private final GrainScheduler scheduler = new StochasticGrainScheduler();
+
+ public GrainFarm() {
+ randomizer = new PseudoRandom();
+ addPort(rate = new UnitInputPort("Rate", 1.0));
+ addPort(amplitude = new UnitInputPort("Amplitude", 1.0));
+ addPort(duration = new UnitInputPort("Duration", 0.01));
+ addPort(rateRange = new UnitInputPort("RateRange", 0.0));
+ addPort(amplitudeRange = new UnitInputPort("AmplitudeRange", 0.0));
+ addPort(durationRange = new UnitInputPort("DurationRange", 0.0));
+ addPort(density = new UnitInputPort("Density", 0.1));
+ addPort(output = new UnitOutputPort());
+ }
+
+ private class GrainState {
+ Grain grain;
+ int countdown;
+ double lastDuration;
+ final static int STATE_IDLE = 0;
+ final static int STATE_GAP = 1;
+ final static int STATE_RUNNING = 2;
+ int state = STATE_IDLE;
+ private double gapError;
+
+ public double next(int i) {
+ double output = 0.0;
+ if (state == STATE_RUNNING) {
+ if (grain.hasMoreValues()) {
+ output = grain.next();
+ } else {
+ startGap(i);
+ }
+ } else if (state == STATE_GAP) {
+ if (countdown > 0) {
+ countdown -= 1;
+ } else if (countdown == 0) {
+ state = STATE_RUNNING;
+ grain.reset();
+
+ setupGrain(grain, i);
+
+ double dur = nextDuration(i);
+ grain.setDuration(dur);
+ }
+ } else if (state == STATE_IDLE) {
+ nextDuration(i); // initialize lastDuration
+ startGap(i);
+ }
+ return output;
+ }
+
+ private double nextDuration(int i) {
+ double dur = duration.getValues()[i];
+ dur = scheduler.nextDuration(dur);
+ lastDuration = dur;
+ return dur;
+ }
+
+ private void startGap(int i) {
+ state = STATE_GAP;
+ double dens = density.getValues()[i];
+ double gap = scheduler.nextGap(lastDuration, dens) * getFrameRate();
+ gap += gapError;
+ countdown = (int) gap;
+ gapError = gap - countdown;
+ }
+ }
+
+ public void setGrainArray(Grain[] grains) {
+ countScaler = 1.0 / grains.length;
+ states = new GrainState[grains.length];
+ for (int i = 0; i < states.length; i++) {
+ states[i] = new GrainState();
+ states[i].grain = grains[i];
+ grains[i].setFrameRate(getSynthesisEngine().getFrameRate());
+ }
+ }
+
+ public void setupGrain(Grain grain, int i) {
+ double temp = rate.getValues()[i] * calculateOctaveScaler(rateRange.getValues()[i]);
+ grain.setRate(temp);
+
+ // Scale the amplitude range so that we never go above
+ // original amplitude.
+ double base = amplitude.getValues()[i];
+ double offset = base * Math.random() * amplitudeRange.getValues()[i];
+ grain.setAmplitude(base - offset);
+ }
+
+ public void allocate(int numGrains) {
+ Grain[] grainArray = new Grain[numGrains];
+ for (int i = 0; i < numGrains; i++) {
+ Grain grain = new Grain(new GrainSourceSine(), new RaisedCosineEnvelope());
+ grainArray[i] = grain;
+ }
+ setGrainArray(grainArray);
+ }
+
+ @Override
+ public UnitOutputPort getOutput() {
+ return output;
+ }
+
+ private double calculateOctaveScaler(double rangeValue) {
+ double octaveRange = 0.5 * randomizer.nextRandomDouble() * rangeValue;
+ return Math.pow(2.0, octaveRange);
+ }
+
+ @Override
+ public void generate(int start, int limit) {
+ double[] outputs = output.getValues();
+ double[] amplitudes = amplitude.getValues();
+ // double frp = getSynthesisEngine().getFramePeriod();
+ for (int i = start; i < limit; i++) {
+ double result = 0.0;
+
+ // Mix the grains together.
+ for (GrainState grainState : states) {
+ result += grainState.next(i);
+ }
+
+ outputs[i] = result * amplitudes[i] * countScaler;
+ }
+
+ }
+}