import Tone from "../core/Tone"; /** * @class Tone.CtrlMarkov represents a Markov Chain where each call * to Tone.CtrlMarkov.next will move to the next state. If the next * state choice is an array, the next state is chosen randomly with * even probability for all of the choices. For a weighted probability * of the next choices, pass in an object with "state" and "probability" attributes. * The probabilities will be normalized and then chosen. If no next options * are given for the current state, the state will stay there. * @extends {Tone} * @example * var chain = new Tone.CtrlMarkov({ * "beginning" : ["end", "middle"], * "middle" : "end" * }); * chain.value = "beginning"; * chain.next(); //returns "end" or "middle" with 50% probability * * @example * var chain = new Tone.CtrlMarkov({ * "beginning" : [{"value" : "end", "probability" : 0.8}, * {"value" : "middle", "probability" : 0.2}], * "middle" : "end" * }); * chain.value = "beginning"; * chain.next(); //returns "end" with 80% probability or "middle" with 20%. * @param {Object} values An object with the state names as the keys * and the next state(s) as the values. */ Tone.CtrlMarkov = function(values, initial){ Tone.call(this); /** * The Markov values with states as the keys * and next state(s) as the values. * @type {Object} */ this.values = Tone.defaultArg(values, {}); /** * The current state of the Markov values. The next * state will be evaluated and returned when Tone.CtrlMarkov.next * is invoked. * @type {String} */ this.value = Tone.defaultArg(initial, Object.keys(this.values)[0]); }; Tone.extend(Tone.CtrlMarkov); /** * Returns the next state of the Markov values. * @return {String} */ Tone.CtrlMarkov.prototype.next = function(){ if (this.values.hasOwnProperty(this.value)){ var next = this.values[this.value]; if (Tone.isArray(next)){ var distribution = this._getProbDistribution(next); var rand = Math.random(); var total = 0; for (var i = 0; i < distribution.length; i++){ var dist = distribution[i]; if (rand > total && rand < total + dist){ var chosen = next[i]; if (Tone.isObject(chosen)){ this.value = chosen.value; } else { this.value = chosen; } } total += dist; } } else { this.value = next; } } return this.value; }; /** * Choose randomly from an array weighted options in the form * {"state" : string, "probability" : number} or an array of values * @param {Array} options * @return {Array} The randomly selected choice * @private */ Tone.CtrlMarkov.prototype._getProbDistribution = function(options){ var distribution = []; var total = 0; var needsNormalizing = false; for (var i = 0; i < options.length; i++){ var option = options[i]; if (Tone.isObject(option)){ needsNormalizing = true; distribution[i] = option.probability; } else { distribution[i] = 1 / options.length; } total += distribution[i]; } if (needsNormalizing){ //normalize the values for (var j = 0; j < distribution.length; j++){ distribution[j] = distribution[j] / total; } } return distribution; }; /** * Clean up * @return {Tone.CtrlMarkov} this */ Tone.CtrlMarkov.prototype.dispose = function(){ this.values = null; }; export default Tone.CtrlMarkov;