aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/com/jsyn/util/RecursiveSequenceGenerator.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/jsyn/util/RecursiveSequenceGenerator.java')
-rw-r--r--src/main/java/com/jsyn/util/RecursiveSequenceGenerator.java214
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;
+ }
+
+}