/** Checks the input lines of the mixer and removes the ones that aren't needed anymore */
 private static void serviceMixer(AudioMixer mixer) {
   List<AudioInput> ais = mixer.getAudioStreams();
   for (int i = ais.size() - 1; i >= 0; i--) {
     AudioInput ai = ais.get(i);
     if (ai.done()) {
       mixer.removeAudioStream(ai);
     }
   }
 }
  public static void main(String[] args) throws Exception {
    int midiDev = -1;
    int audioDev = -1;
    int latencyInMillis = 70;

    // parse arguments
    int argi = 0;
    while (argi < args.length) {
      String arg = args[argi];
      if (arg.equals("-h")) {
        printUsageAndExit();
      } else if (arg.equals("-m")) {
        argi++;
        if (argi >= args.length) {
          printUsageAndExit();
        }
        midiDev = Integer.parseInt(args[argi]);
      } else if (arg.equals("-a")) {
        argi++;
        if (argi >= args.length) {
          printUsageAndExit();
        }
        audioDev = Integer.parseInt(args[argi]);
      } else if (arg.equals("-l")) {
        argi++;
        if (argi >= args.length) {
          printUsageAndExit();
        }
        latencyInMillis = Integer.parseInt(args[argi]);
      } else {
        printUsageAndExit();
      }
      argi++;
    }

    // load samples
    final AudioFileSource[] src = new AudioFileSource[sounds.length];
    for (int i = 0; i < sounds.length; i++) {
      src[i] = new AudioFileSource(new File(sounds[i]));
    }
    // define the first source's audioformat as the master format
    final AudioFormat format = src[0].getFormat();

    // set up mixer
    final AudioMixer mixer = new AudioMixer(format.getChannels(), format.getSampleRate());

    // set up soundcard (sink)
    SoundcardSink sink = new SoundcardSink();
    // open the sink and connect it with the mixer
    sink.open(audioDev, latencyInMillis, format, mixer);
    try {

      // do we want to open a MIDI port?
      MidiIn midi = null;
      if (midiDev >= 0) {
        // start MIDI IN
        midi = new MidiIn();
        midi.setListener(
            new MidiIn.Listener() {
              public void midiInPlayed(int status, int data1, int data2) {
                // only react to NOTE ON messages with velocity > 0
                if (((status & 0xF0) == 0x90) && (data2 > 0)) {
                  AudioFileSource newSrc = src[data1 % src.length].makeClone();
                  mixer.addAudioStream(newSrc);
                  serviceMixer(mixer);
                }
              }

              public void midiInPlayed(byte[] message) {
                // nothing to do for long MIDI messages
              }
            });
        midi.open(midiDev);
      } else {
        Debug.debug("No MIDI.");
      }
      try {

        // start the sink -- from now on, the mixer is polled for new
        // data
        sink.start();

        System.out.println("Press ENTER for a sound, 'q'+ENTER to quit.");
        int currSrc = 0;
        while (true) {
          char c = (char) System.in.read();
          if (c == 'q') {
            break;
          }
          AudioFileSource newSrc = src[(currSrc++) % src.length].makeClone();
          mixer.addAudioStream(newSrc);
          serviceMixer(mixer);
        }
      } finally {
        // clean-up
        if (midi != null) {
          midi.close();
        }
      }
    } finally {
      sink.close();
    }

    Debug.debug("done");
  }