/* * 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. * *

   synth.add( sampleGrainFarm = new GrainFarm() );
   grainFarm.allocate( NUM_GRAINS );
* * @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; } } }