// SoundUtils.java
// https://www.rgagnon.com/javadetails/java-0632.html
// https://stackoverflow.com/questions/3780406/how-to-play-a-sound-alert-in-a-java-application
// Modified by Andrew Davison, ad@coe.psu.ac.th, March 2023

/* Java sound methods limited to creating SourceDataLines
   which generate sounds by writing bytes to a buffer. No 
   audio files are loaded.

  The sounds are played inside their own thread so that the 
  calling code isn't forced to wait for the sound to finish.

  See the main() test-rig at the end for ways to call the 
  methods.

   Usage:
     > javac SoundUtils.java
     > java SoundUtils

*/


import java.awt.*;
import java.util.*;

import javax.sound.sampled.*;



public class SoundUtils 
{
  private static float SAMPLE_RATE = 8000f;


  public static void listSources()
  // limit the listing to SourceDataLines 
  {
    Line.Info sdl = new Line.Info(SourceDataLine.class);

    System.out.println("Source line Info:");
    Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo();
    int count = 0;
    for (Mixer.Info info : mixerInfos) {
      Mixer m = AudioSystem.getMixer(info);
      if (m.isLineSupported(sdl)) {
        Line.Info[] lineInfos = m.getSourceLineInfo();
        for (Line.Info lineInfo : lineInfos) {
          count++;
          System.out.println(count + ". " + 
                info.getName() + " --- " + lineInfo + "\n");
        }
      }
    }
    if (count == 0)
      System.out.println("-- no information found");
  }  // end of listSources()


  public static void tone(int hz, int msecs) 
  {  tone(hz, msecs, 1.0);  }


  public static void tone(int hz, int msecs, double vol)
  {
    new Thread(() -> {
      tone1(hz, msecs, vol);
    }).start();
  }

  private static void tone1(int hz, int msecs, double vol)
  {
    SourceDataLine sdl = startSDL(SAMPLE_RATE);
    if (sdl == null)
      return;

    sdl.start();
    byte[] buf = new byte[1];
    for (int i=0; i < msecs*8; i++) {
      double angle = i / (SAMPLE_RATE / hz) * 2.0 * Math.PI;
      buf[0] = (byte)(Math.sin(angle) * 127.0 * vol);
      sdl.write(buf,0,1);
    }

    stopSDL(sdl);
  }  // end of tone1()


  private static SourceDataLine startSDL(float sampleRate)
  {
    try { // an 8-bit mono format
      AudioFormat af = new AudioFormat(sampleRate,
                              8,       // sampleSizeInBits
                              1,       // channels
                              true,    // signed
                              false);  // bigEndian

      if (!isSupported(af)) {
        System.err.println("Audioformat not supported: " + af);
        return null;
      }
      SourceDataLine sdl = AudioSystem.getSourceDataLine(af);
      sdl.open(af);
      sdl.start();
      return sdl;
    }
    catch (LineUnavailableException e)
    {  System.err.println(e);
       return null;
    }
  }  // end of startSDL()


  private static boolean isSupported(AudioFormat af)
  {
    DataLine.Info info = 
          new DataLine.Info(SourceDataLine.class, af); 	
    return AudioSystem.isLineSupported(info);
  }


  private static void stopSDL(SourceDataLine sdl)
  {
    sdl.drain();
    sdl.stop();
    sdl.close();
  }


  public static void laser(int repeat)
  {
    new Thread(() -> {
      laser1(repeat);
    }).start();
  }


  private static void laser1(int repeat)
  {
    SourceDataLine sdl = startSDL(SAMPLE_RATE);
    if (sdl == null)
      return;

    byte[] buf = new byte[1];
    int step;

    for (int j=0; j < repeat; j++) {
      step = 10;
      for(int i=0; i < 2000; i++) {
        buf[0] = ((i%step > 0) ? 32 : (byte)0);
        if (i%250 == 0) 
          step += 2;
        sdl.write(buf,0,1);
      }
      pause(200);
    }

    stopSDL(sdl);
  }  // end of laser1()


  public static void warp(int repeat)
  {
    new Thread(() -> {
      warp1(repeat);
    }).start();
  }


  private static void warp1(int repeat)
  {
    SourceDataLine sdl = startSDL(SAMPLE_RATE);
    if (sdl == null)
      return;

    byte[] buf = new byte[1];
    int step;

    for (int j=0; j < repeat; j++) {
      step = 25;
      for (int i=0; i < 2000; i++) {
        if (i < 500) {
          buf[0] = ((i%step > 0) ? 32 : (byte)0);
          if (i%25 == 0) 
            step--;
        }
        else {
          buf[0] = ((i%step > 0) ? 16 : (byte)0);
          if (i%50 == 0) 
            step++;
        }
        sdl.write(buf,0,1);
      }
    }

    stopSDL(sdl);
  }  // end of warp1()


  public static void bang()
  {
    new Thread(() -> {
      bang1();
    }).start();
  }


  private static void bang1()
  {
    SourceDataLine sdl = startSDL(SAMPLE_RATE);
    if (sdl == null)
      return;

    byte[] buf = new byte[1];
    Random r = new Random();
    boolean silence = true;

    for (int i=0 ; i < 8000 ; i++) {
      while(r.nextInt() % 10 != 0) {
          buf[0] =
            silence ? 0 :
              (byte)Math.abs(r.nextInt() %
                  (int)(1.0 + 63.0 * (1.0 + Math.cos(((double)i)
                      * Math.PI / 8000.0))));
          i++;
          sdl.write(buf,0,1);
      }
      silence = !silence;
    }

    stopSDL(sdl);
  }  // end of bang1()


  public static void beep()
  {  Toolkit.getDefaultToolkit().beep();  }



  // ------------------- test rig ------------------


  public static void main(String[] args) 
  {
    listSources();

    System.out.println("beep");
    SoundUtils.beep();
    pause(1000);

    System.out.println("tone(1000, 100)");
    SoundUtils.tone(1000, 100);
    pause(1000);

    System.out.println("one(100, 1000)");
    SoundUtils.tone(100, 1000);
    pause(1000);

    System.out.println("tone(5000, 100)");
    SoundUtils.tone(5000, 100);
    pause(1000);

    System.out.println("tone(400, 500)");
    SoundUtils.tone(400, 500);
    pause(1000);

    System.out.println("tone(400, 500, 0.2)");
    SoundUtils.tone(400, 500, 0.2);
    pause(1000);

    System.out.println("laser(5)");
    SoundUtils.laser(5);
    pause(2000);

    System.out.println("warp(10)");
    SoundUtils.warp(10);
    pause(3000);

    System.out.println("bang");
    SoundUtils.bang();
    pause(1000);
  }  // end of main()


  private static void pause(int ms)
  {
    try {
      Thread.sleep(ms);
    }
    catch(InterruptedException ex) { }
  }  // end of pause()

}  // end of SoundUtils class

