import javax.sound.midi.*;
import java.io.*;

public class Sound {

 int note[], t0[], t1[], atime = 0, count = 0, program = 1;
 boolean percussion;
 static int Q = 96, Percussion_ch = 9, next_ch = 0, V = 95;
 static float BPM = 160f;

 public Sound(int n, boolean prcs) { note=new int[n]; t0=new int[n]; t1=new int[n]; percussion = prcs; }
 private void add(int n, int l) {
  if(count < note.length) {
  note[count] = n; t0[count] = atime; t1[count] = atime = atime+l; count++;
  }
 }
 private void msg1(Track trk, int cmd, int ch, int v1, int v2, int t) {
  try {
   ShortMessage msg = new ShortMessage();
   msg.setMessage(cmd, ch, v1, v2); trk.add(new MidiEvent(msg, t));
  } catch(Exception e) { }
 }
 public void apply(Track trk, int pg, int ch, int v) {
  msg1(trk, ShortMessage.PROGRAM_CHANGE, ch, pg, 0, 0);
  for(int i = 0; i < count; ++i)
  if(note[i] >= 0) {
   msg1(trk, ShortMessage.NOTE_ON, ch, note[i], v, t0[i]);
   msg1(trk, ShortMessage.NOTE_OFF, ch, note[i], v, t1[i]);
  }
 }

 private static int getNextChannel() {
  if (next_ch != 9) {
   int next = next_ch;
   next_ch++;
   return next;
  } else {
   next_ch = 11;
   return 10;
  }
 }

  public String toString() {
   return "#<sound>";
  }

  static { Subr.def("Sound", "makeMelody", "make-melody", 0); }
  public static Sound makeMelody() {
    return new Sound(100, false); //////////////////// n=100
  }

  static { Subr.def("Sound", "makeRyhthm", "make-ryhthm", 0); }
  public static Sound makeRyhthm() {
    return new Sound(100, true); //////////////////// n=100
  }

  static { Subr.def("Sound", "addSound", "add-sound", 1, true); }
  public static Symbol addSound(Sound m, List args) {
   while (args != List.nil) {
    List elem = (List) args.car;
    int n = ((Number) elem.car).intValue();
    int l = ((Number) ((List) elem.cdr).car).intValue();
    m.add(n, l);
    args = (List) args.cdr;
   }
   return Symbol.intern("ok");
  }

  static { Subr.def("Sound", "playMusic", "play-music", 0, true); }
  public static Symbol playMusic(List args) throws Exception{
    Sequencer seqr = MidiSystem.getSequencer(); seqr.open();
    Synthesizer synth = MidiSystem.getSynthesizer(); synth.open();
    seqr.getTransmitter().setReceiver(synth.getReceiver());
    Sequence seq = new Sequence(Sequence.PPQ, Q);
    while (args != List.nil) {
     Sound snd = (Sound) args.car;
     if (snd.percussion) {
      snd.apply(seq.createTrack(), 0, Percussion_ch, V);
     } else {
      snd.apply(seq.createTrack(), snd.program, Sound.getNextChannel(), V);
     }
     args = (List) args.cdr;
    }
    seqr.setTempoInBPM(BPM);
    seqr.setSequence(seq); seqr.start();
    while(seqr.isRunning()) Thread.sleep(100);
    return Symbol.intern("done");
  }

  static { Subr.def("Sound", "chInstr", "change-instr", 2); }
  public static Symbol chInstr(Sound m, int instr){
    m.program = instr;
    return Symbol.intern("ok");
  }

  static { Subr.def("Sound", "readMidi", "read-midi", 1); }
  public static File readMidi(String fname) {
    File midiFile;
    try {
      midiFile = new File(fname);
    } catch (Exception e) {
      throw Eval.error("cannot read image file");
    }
    if (midiFile == null)
      throw Eval.error("cannot read image file");
    return midiFile;
  }

  static { Subr.def("Sound", "playMidi", "play-midi", 1); }
  public static Symbol playMidi(File f) throws Exception{
    int Q = 96;
    Sequencer seqr = MidiSystem.getSequencer(); seqr.open();
    Synthesizer synth = MidiSystem.getSynthesizer(); synth.open();
    seqr.getTransmitter().setReceiver(synth.getReceiver());
    Sequence  seq  = MidiSystem.getSequence(f);
    seqr.setSequence(seq); seqr.start();
    while(seqr.isRunning()) Thread.sleep(100);
    return Symbol.intern("done");
  }


  static void init() {}

}
