/* * 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;
}
}