
// NotesSynth.java
// Andrew Davison, April 2005, ad@fivedots.coe.psu.ac.th

/* Aplikace NotesSynth otevře výstupní stereo znaménkovou PCM-linku
   používající 16-bitů na vzorek ve formátu little endian. Pro každý
   vzorek/rámec se používají 4 bajty.

   Metoda sendNote() generujte tóny dané frekvence a amplitudy.
   Tónem je ve skutečnosti sinusová vlna na příslušné frekvence a 
   s danou amplitudou uložena jako vzorky v bufferu bajtů. Aby se 
   tón zahrál, vyprázdní se tento buffer do objektu typu SourceDataLine.

   Metoda play() obsahuje jednoduchou ukázku způsobu použití metody sendNode():
     - generuje sekvenci se vzrustající výškou tónů, která se 9krát opakuje,
       a pokaždé má rychlejší růst a nižší hlasitost.

   Metodu sendNote() lze použít pro generování jednoduchých zvuků za 
   běhu hry, aniž by bylo nutné načíst a přehrát předem připravený zvukový klip.
*/

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


public class NotesSynth  
{
  private static int SAMPLE_RATE = 22050;       // počet vzorků za sekundu
  private static double MAX_AMPLITUDE = 32760;     // maximální hlasitost tónu
                 // je skutečnosti je max. 2^15-1, 32767, protože používáme
                 // 16-bitové znaménkové PCM-kódování

  // frekvenční (výškový) rozsah pro naše tóny
  private static int MIN_FREQ = 250;
  private static int MAX_FREQ = 2000;
    /* Prostřední C (C4) má frekvenci 262 Hz,
       přičemž s každou vyšší oktávnou se násobí dvěma (C5, C6, ...),
       a nižší oktávou se dělí dvěma (C3, C2, ...),
       viz http://www.phys.unsw.edu.au/~jw/notes.html.
    */


  private static AudioFormat format = null;
  private static SourceDataLine line = null;


  public static void main(String[] args) 
  { createOutput();
    play();
    System.exit(0);    // nezbytné pro J2SE 1.4.2 nebo dřívější
  } // konec metody: main()


  private static void createOutput()
  // vytvoří objekt typu SourceDataLine pro výstup generovaných tónů
  {
    /* Vytvoří stereo znaménkovou výstupní PCM-linku používající 16-bitů
       na vzorek ve formátu little endian. */ 

    format =  new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                              SAMPLE_RATE, 16, 2, 4, SAMPLE_RATE, false);
   /*  SAMPLE_RATE   // vzorků za sekundu
       16            // velikost vzorků v bajtech, mezi -2^15 -- 2^15-1
       2             // počet kanálů
       4             // velikost rámce v bajtech (2 bajty na vzorek * 2 kanály)
       SAMPLE_RATE   // stejné jako rámců za sekundu
       false         // little endian
    */

    System.out.println("Audio formát: " + format);

    try {
      DataLine.Info info =
            new DataLine.Info(SourceDataLine.class, format);
      if (!AudioSystem.isLineSupported(info)) {
        System.out.println("Linka nepodporuje: + format);
        System.exit(0);
      }
      line = (SourceDataLine) AudioSystem.getLine(info);
      line.open(format);
    }
    catch (Exception e)
    {  System.out.println( e.getMessage());  
       System.exit(0);
    }
  }  // konec metody: createOutput()


  private static void play()
  // přehraje tóny přes objekt typu SourceDataLine
  {
    // spočítej velikost bufferu pro uložení tónu
    int maxSize = (int) Math.round(
                      (SAMPLE_RATE * format.getFrameSize())/ MIN_FREQ );   
        // rámeček má 4 bajty
    byte[] samples = new byte[maxSize];

    line.start();

    /* Vygeneruj vzrůstající sekvenci opakovanou 9krát, pokaždé
       o něco rychleji a tišeji */
    double volume;
    for (int step = 1; step < 10; step++)
      for (int freq = MIN_FREQ; freq < MAX_FREQ; freq += step) {
         volume = 1.0 - (step/10.0);
         sendNote(freq, volume, samples);
      }

    // posečkej, dokud se nepřehrají všechna data a pak linku uzavři
    line.drain();
    line.stop();
    line.close();
  }  // konec metody: play()


  private static void sendNote(int freq, double volLevel, byte[] samples)
  /* Generujeme jedinou sinusovou s napožadovanou amplitudou a frekvencí,
     což je ekvivalentní jednomu tónu.

     Výška zvuku závisí na frekvenci vlny. Čím vyšší je frekvence, tím vyšší
     je i výška.

     Každž tón je 16-bitový, stereo, little endian -- což odpovídá nastavenému
     formátu
  */
  { if ((volLevel < 0.0) || (volLevel > 1.0)) {
      System.out.println("Hlasitost by měla být mezi 0 a 1, použije se 0.9");
      volLevel = 0.9;
    }
    double amplitude = volLevel * MAX_AMPLITUDE;

    int numSamplesInWave = (int) Math.round( ((double) SAMPLE_RATE)/freq );
    int idx = 0;
    for (int i = 0; i < numSamplesInWave; i++) {
      double sine = Math.sin(((double) i / numSamplesInWave) * 2.0 * Math.PI);
      int sample = (int) (sine * amplitude);
      // levý vzorek stereo signálu
      samples[idx + 0] = (byte) (sample & 0xFF);         // nižší bajt
      samples[idx + 1] = (byte) ((sample >> 8) & 0xFF);  // vyšší bajt
      // pravý vzorek stereo signálu (identický levému)
      samples[idx + 2] = (byte) (sample & 0xFF);
      samples[idx + 3] = (byte) ((sample >> 8) & 0xFF);
      idx += 4;
    }

    // odešli vygenerované vzorky (reprezentujcící jediný tón)
    // line.write(samples, 0, idx);
    int offset = 0;
    while (offset < idx)
      offset += line.write(samples, offset, idx-offset);
  }  // konec metody: sendNote()


} // konec třídy: NotesSynth