/* * 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: * *
 * 
 * value[n] = value[n-delay] + offset;
 * 
 * 
* * 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(); // System.out.println("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--; // System.out.println("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; // System.out.println("nextVal = " + nextVal ); return playIt ? nextVal : -1; } public Random getRandom() { return random; } public void setRandom(Random random) { this.random = random; } }