diff options
Diffstat (limited to 'src/main/java/com/jsyn/util/RecursiveSequenceGenerator.java')
-rw-r--r-- | src/main/java/com/jsyn/util/RecursiveSequenceGenerator.java | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/src/main/java/com/jsyn/util/RecursiveSequenceGenerator.java b/src/main/java/com/jsyn/util/RecursiveSequenceGenerator.java new file mode 100644 index 0000000..0d6e451 --- /dev/null +++ b/src/main/java/com/jsyn/util/RecursiveSequenceGenerator.java @@ -0,0 +1,214 @@ +/* + * 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(); + // LOGGER.debug("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--; + // LOGGER.debug("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; + + // LOGGER.debug("nextVal = " + nextVal ); + + return playIt ? nextVal : -1; + } + + public Random getRandom() { + return random; + } + + public void setRandom(Random random) { + this.random = random; + } + +} |