jm.music.tools.AdaptiveMatrix Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmusic Show documentation
Show all versions of jmusic Show documentation
JMusic - Java Music Library
The newest version!
/*
Copyright (C) 2000 Andrew Sorensen & Andrew Brown
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or any
later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package jm.music.tools;
import java.io.*;
import java.util.Enumeration;
import java.util.Hashtable;
/**
* AdaptiveMatrix is responsible for hold Markov information in an internal
* matrix which may be written to disk as a serialized java object
* The matrix itself will contain two arrays. The first array will
* contain historic information. The second array will contain
* weightings. These arrays will both allow for n level markov
* Matrix's.
*
* @author Andrew Sorensen
* @version 1.0, Sun Feb 25 18:43:35 2001
*/
public final class AdaptiveMatrix {
//------------------------------------------
// Attributes
//------------------------------------------
/**
* The depth of the markov array (ie The number
* of prior states to use in this matrix).
*/
private int depth;
/**
* The Markov matrix stored in a Hashtable
* Prefixe is stored as a string of numbers
* equal to the depth attribute
* Postfix is an double[1000] - the indexes
* of this double are not known inside
* adaptive matrix but should be know by
* whatever generative class uses
* AdaptiveMatrix.
*/
private Hashtable weightMatrix;
/**
* The counts matrix stores the Markov matrix
* in a count format.
* This needs to be converted to the weightings
* matrix before any number generation can happen
*/
private Hashtable countMatrix;
/**
* The index range to use for the matrix.
* This value is very important as it defines
* the values for the table. (i.e the index range
* to use for a pitch table is 127 - the range
* of values which could be used for a given pitch)
*/
private int indexRange;
//--------------------------------------------
// Constructors
//--------------------------------------------
/**
* This constructor is used when the Matrix
* is being created for the first time. The score parameter is the
* jMusic score to used in building the matrix.
* The depth attribute is the number of prior states
* to use in creating the Matrix. The fileName is
* a string containing the name of the file to use
* for storing a copy of this matrix. This contructor
* should be used when a Matrix is being created for the
* first time. For matrix's that have already been
* written to disk the AdaptiveMatrix(fileName) constructor
* should be used.
*
* @param numArray are the values to use in the matriX
* @param depth the number of prior states.
* @param indexRange the range of numbers to use for indexing
*/
public AdaptiveMatrix(int[] numArray, int depth, int indexRange) {
this.countMatrix = new Hashtable();
this.weightMatrix = new Hashtable();
this.depth = depth;
this.indexRange = indexRange;
this.calcCount(numArray);
this.calcWeight();
}
/**
* This constructor is used when the matrix is being
* read from an existing file. The file is read by default
* upon calling this constructor which then assigns all
* instance variables to those of the AdaptiveMatrix being
* read back from disk.
*
* @param fileName the fileName to read an existing Matrix from
*/
public AdaptiveMatrix(String fileName) {
read(fileName);
}
//----------------------------------------------------
// Public Methods
//----------------------------------------------------
/**
* update an existing AdaptiveMatrix with new data
*
* @param numArray are the valuew to use in the matrix
*/
public void update(int[] numArray) {
this.calcCount(numArray);
this.calcWeight();
}
/**
* Generate returns an array of generated index values
*
* @param length the number of indexes to return. The
* seed data is included in the length value.
* @param seed the data to use as a starting point
* @return an array of indexes equal to the length
* required.
*/
public int[] generate(int length, int[] seed) {
//Check to make sure seed is the correct depth
if (seed.length != this.depth) {
System.err.println("[WARNING] Wrong seed length for this Matrix depth");
return null;
}
int[] array = new int[length];
String seedString = "";
int[] seedbak = new int[seed.length];
//change seed into a string and add seed to the outgoing array
for (int i = 0; i < seed.length; i++) {
array[i] = seed[i];
seedbak[i] = seed[i];
seedString += seed[i] + " "; //create the prefix
}
String bak = seedString;
//Check that this seed is available from the matrix
if (!weightMatrix.containsKey(seedString)) {
System.err.println("[WARNING] This seed is unavailable .. try another");
return null;
}
//Calculate the new index values
for (int i = seed.length; i < array.length; i++) {
if (!weightMatrix.containsKey(seedString)) { //If there are no other choices
seedString = bak;//select the original seed;
seed = seedbak;
}
double[] tmp = (double[]) weightMatrix.get(seedString);
seedString = ""; //reset the prefix
for (int k = 1; k < seed.length; k++) {
seedString += seed[k] + " ";
seed[k - 1] = seed[k];
}
double rand = Math.random();
double count = 0.0;
for (int j = 0; j < tmp.length; j++) {
count += tmp[j];
if (count > rand) {
array[i] = j;
seedString += j + " ";
seed[depth - 1] = j;
break;
}
}
}
return array;
}
/**
* Read the matrix to file
*/
public void read(String fileName) {
AdaptiveMatrix am = null;
try {
File file = new File(fileName);
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
am = (AdaptiveMatrix) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
this.depth = am.getDepth();
this.indexRange = am.getIndexRange();
this.countMatrix = am.getCountMatrix();
this.weightMatrix = am.getWeightMatrix();
}
/**
* Saves the matrix to file
*/
public void write(String fileName) {
try {
File file = new File(fileName);
FileOutputStream fis = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fis);
oos.writeObject(this);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* A simple print method for displaying the contents
* of a matrix.
*/
public void print() {
System.out.println();
System.out.println("MATRIX");
System.out.println("----------------");
Enumeration enum1 = this.weightMatrix.keys();
while (enum1.hasMoreElements()) {
String prefix = (String) enum1.nextElement();
double[] postfix = (double[]) this.weightMatrix.get(prefix);
System.out.print(prefix + "\t: ");
for (int i = 0; i < postfix.length; i++) {
System.out.print(" " + postfix[i]);
}
System.out.println();
}
}
/**
* get matrix's depth
*
* @return depth the number prior states
*/
public int getDepth() {
return this.depth;
}
/**
* retrieve the matrix
*
* @return Hashtable
*/
public Hashtable getWeightMatrix() {
return this.weightMatrix;
}
/**
* retrieve the count matrix
*
* @return Hashtable
*/
public Hashtable getCountMatrix() {
return this.countMatrix;
}
/**
* retrieve the index range
*
* @return indexRange
*/
public int getIndexRange() {
return this.indexRange;
}
//-------------------------------------------------------
// Private Methods
//-------------------------------------------------------
/**
* calcCount takes an array of double values and
* calculates how many unique prefixes exist in the list
* (based on the depth of the matrix) and how many
* times each number inside the index range occurs.
* The numbers in numArray MUST fall within the 1000
* range. These numbers are treated as indexes not
* as values as such.
* The prefixes are stored as Strings so that they
* are more easily checked for "equality".
*
* @param numArray a number of ints which must fall within
* the index range of this Matrix.
*/
private void calcCount(int[] numArray) {
for (int i = this.depth - 1; i < numArray.length - 1; i++) {
String prefix = "";
int[] post = new int[indexRange];
for (int j = 0, k = this.depth - 1; j < this.depth; j++, k--) {
prefix += numArray[i - k] + " ";
}
if (this.countMatrix.containsKey(prefix)) {
int[] postfix = (int[]) countMatrix.get(prefix);
postfix[numArray[i + 1]]++;
countMatrix.put(prefix, postfix);
} else {
post[numArray[i + 1]]++;
countMatrix.put(prefix, post);
}
}
}
/**
* This method takes the counts established in the
* createMatrix method and turns these into percentage
* weightings based on a division between an individual index count
* and the total index count for that row.
*/
private void calcWeight() {
Enumeration enum1 = this.countMatrix.keys();
while (enum1.hasMoreElements()) {
String prefix = (String) enum1.nextElement();
int[] postfix = (int[]) this.countMatrix.get(prefix);
int count = 0;
for (int i = 0; i < postfix.length; i++) {
count += postfix[i];
}
double[] postfix2 = new double[indexRange];
for (int i = 0; i < postfix.length; i++) {
postfix2[i] = (double) postfix[i] / (double) count;
}
this.weightMatrix.put(prefix, postfix2);
}
}
}