aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/com/jsyn/util/RecursiveSequenceGenerator.java
blob: 0d6e451bd7630bf64704b4acadc25afabaafa1a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
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;
    }

}