Package com.meapsoft.composers

Source Code of com.meapsoft.composers.HMMComposer

/*
*  Copyright 2006-2007 Columbia University.
*
*  This file is part of MEAPsoft.
*
*  MEAPsoft is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License version 2 as
*  published by the Free Software Foundation.
*
*  MEAPsoft 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 MEAPsoft; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
*  02110-1301 USA
*
*  See the file "COPYING" for the text of the license.
*/

package com.meapsoft.composers;

import gnu.getopt.Getopt;

import java.util.Arrays;
import java.util.Collections;
import java.util.Random;

import com.meapsoft.DSP;
import com.meapsoft.EDLChunk;
import com.meapsoft.EDLFile;
import com.meapsoft.FeatChunk;
import com.meapsoft.FeatFile;

/**
* Program that learns a simple fully connected hidden Markov model from a
* FeatFile and generates chunk sequences from it.
*
* This doesn't produce very compelling compositions because the markov
* assumption (that the currect chunk only depends on the previous chunk) is not
* at all valid for most music which has a more complex structure. Later
* versions will support more constrained HMM topologies that should make for
* more interesting compositions.
*
* All about HMMs: http://en.wikipedia.org/wiki/Hidden_Markov_model
*
* @author Ron Weiss (ronw@ee.columbia.edu)
*/
public class HMMComposer extends VQComposer
{
  public static String oldDesc = "HMMComposer uses a features file to train a simple statistical model of a song and uses it to randomly generate a new sequence of chunks.  This works best when used with chunks created by the beat detector.";

  private int sequenceLength = 50;

  // prior probability of starting in a given state
  private double[] startProbs;

  // probability of transitioning from one state to another
  private double[][] transitionMatrix;

  //@mike: empty constructor just to sniff this guy
  //please don't call this constructor, it will not work
  public HMMComposer()
  {
    initNameAndDescription("HMM Composer", new String(oldDesc));
  }
 
  public HMMComposer(FeatFile trainFN, EDLFile outFN)
  {
    super(trainFN, outFN);
  }

  public void printUsageAndExit()
  {
    System.out
        .println("Usage: HMMComposer [-options] features.feat \n\n"
            + "  where options include:\n"
            + "    -o output_file   the file to write the output to (defaults to "
            + outFileName
            + ")\n"
            + "    -g               debug mode\n"
            + "    -q codebook_size number of states in the HMM (defaults to "
            + cbSize
            + ")\n"
            + "    -b nbeats        number of beats each HMM state should contain (defaults to "
            + beatsPerCodeword
            + ")\n"
            + "    -s sequence_len  length of chunk sequence to generate (defaults to "
            + sequenceLength + ").");
    printCommandLineOptions('i');
    printCommandLineOptions('d');
    printCommandLineOptions('c');
    System.out.println();
    System.exit(0);
  }

  public HMMComposer(String[] args)
  {
    // java demands that we do this
    super(null, null);

    if (args.length == 0)
      printUsageAndExit();

    // Vector features = new Vector();

    // Parse arguments
    String argString = "o:c:q:i:gd:s:b:";
    featdim = parseFeatDim(args, argString);
    dist = parseChunkDist(args, argString, featdim);
    parseCommands(args, argString);

    Getopt opt = new Getopt("HMMComposer", args, argString);
    opt.setOpterr(false);

    int c = -1;
    while ((c = opt.getopt()) != -1)
    {
      switch (c)
      {
      case 'o':
        outFileName = opt.getOptarg();
        break;
      case 'g':
        debug = true;
        break;
      case 'q':
        cbSize = Integer.parseInt(opt.getOptarg());
        break;
      case 'b':
        beatsPerCodeword = Integer.parseInt(opt.getOptarg());
        break;
      case 's':
        sequenceLength = Integer.parseInt(opt.getOptarg());
        break;
      case 'c': // already handled above
        break;
      case 'd': // already handled above
        break;
      case 'i': // already handled above
        break;
      case '?':
        printUsageAndExit();
        break;
      default:
        System.out.print("getopt() returned " + c + "\n");
      }
    }

    // parse arguments
    int ind = opt.getOptind();
    if (ind > args.length)
      printUsageAndExit();

    trainFile = new FeatFile(args[args.length - 1]);
    outFile = new EDLFile(outFileName);

    System.out.println("Composing " + outFileName + " from "
        + args[args.length - 1] + ".");
  }

  public void setSequenceLength(int len)
  {
    sequenceLength = len;
  }

  private void learnTransitionMatrix(FeatFile trainFile)
  {
    startProbs = new double[cbSize];
    Arrays.fill(startProbs, 0);

    transitionMatrix = new double[cbSize][cbSize];
    for (int x = 0; x < cbSize; x++)
      Arrays.fill(transitionMatrix[x], 0);

    // sort the chunks in order of increasing startTime, while
    // keeping all chunks from the same srcFile together
    trainFile = (FeatFile) trainFile.clone();
        Collections.sort(trainFile.chunks);

    int ndat = trainFile.chunks.size();
    int prevState = -1;
    String lastSrcFile = "";
    for (int n = 0; n < ndat; n++)
    {
      FeatChunk ch = (FeatChunk) trainFile.chunks.get(n);

      int currState = quantizeChunk(ch);

      // is this the beginning of a srcFile?
      if (!lastSrcFile.equals(ch.srcFile))
      {
        lastSrcFile = ch.srcFile;

        startProbs[currState] += 1.0;
      }
      else
        transitionMatrix[prevState][currState] += 1.0;

      prevState = currState;
    }

    // normalize probabilities
    double s = DSP.sum(startProbs);
    for (int x = 0; x < startProbs.length; x++)
      startProbs[x] /= s;

    for (int x = 0; x < transitionMatrix.length; x++)
    {
      s = DSP.sum(transitionMatrix[x]);

      for (int y = 0; y < transitionMatrix[x].length; y++)
        transitionMatrix[x][y] /= s;
    }

    if (debug)
    {
      FeatFile f = new FeatFile("tmp");
      f.chunks = templateChunks;
      DSP.imagesc(f.getFeatures(), "codebook");
      DSP.imagesc(transitionMatrix, "transitionMatrix");
      DSP.imagesc(startProbs, "startProbs");
    }
  }

  private int multinomialSample(double uniformRV, double[] prob)
  {
    if (uniformRV <= prob[0])
      return 0;

    double[] cdf = DSP.cumsum(prob);

    for (int x = 1; x < cdf.length; x++)
      if (uniformRV > cdf[x - 1] && uniformRV <= cdf[x])
        return x;

    return prob.length - 1;
  }

  public EDLFile compose()
  {
    learnCodebook(trainFile);

    learnTransitionMatrix(trainFile);

    // generate a sequence of chunks from the codebook and
    // transition matrix

    Random rand = new Random();
    double currTime = 0;

    // get first chunk from startProbs
    int lastIdx = multinomialSample(rand.nextDouble(), startProbs);
    EDLChunk nc = new EDLChunk((FeatChunk) templateChunks.get(lastIdx),
        currTime);
    outFile.chunks.add(nc);
    currTime += nc.length;
    progress.setValue(progress.getValue() + 1);

    // use transitionMatrix for the remaining chunks
    for (int x = 1; x < sequenceLength; x++)
    {
      int currIdx = multinomialSample(rand.nextDouble(),
          transitionMatrix[lastIdx]);

      nc = new EDLChunk((FeatChunk) templateChunks.get(currIdx), currTime);
      outFile.chunks.add(nc);
      currTime += nc.length;
      progress.setValue(progress.getValue() + 1);

      lastIdx = currIdx;
    }

    return outFile;
  }

  public static void main(String[] args)
  {
    HMMComposer m = new HMMComposer(args);
    long startTime = System.currentTimeMillis();
    m.run();
    System.out.println("Done. Took "
        + ((System.currentTimeMillis() - startTime) / 1000.0) + "s");
    System.exit(0);
  }
}
TOP

Related Classes of com.meapsoft.composers.HMMComposer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.