// ============================================================================
//  Impulse.java
//
//  Audio-impulse generator, for the Vortex Cannon. Requires Java Runtime 1.4 or better.
//
//  Audio code based on AudioSynth tutorial by R.G.Baldwin, www.dickbaldwin.com
//
//  Created by Edwin Wise
//  Copyright (c) 2008 Simulated Reality Systems, LLC. All rights reserved.
// ============================================================================

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.sound.sampled.*;
import java.io.*;
import java.nio.channels.*;
import java.nio.*;
import java.util.*;

// ============================================================================

public class Impulse extends JFrame
{
    // -------------------------------------------------------------------------
    // SYSTEM DATA
    // -------------------------------------------------------------------------
    // Sampling constants:
    final int sampleRate = 22050;       // 8000, 11025, 16000, 22050, 44100 per second
    final int sampleSize = 16;              // 8 or 16 bits
    final int channels = 1;                   // 1 or 2 channels
    final boolean signed = true;           // true or false
    final boolean  bigEndian = true;     // true or false
    
    final int maxValue = 32000;         // Nearly full scale (2^15)-1
    final int minValue = -32000;        // Nearly full scale -(2^15)-1
    final int fullRange = (maxValue - minValue);
    
    // -------------------------------------------------------------------------
    // UI Components:
    final JLabel labelPrepareTime = new JLabel("Preparation time (mS):", JLabel.RIGHT);
    final JTextField textPrepareTime = new JTextField("1000", 5);
    
    final JLabel labelImpulseTime = new JLabel("Impulse time (mS):", JLabel.RIGHT);
    final JTextField textImpulseTime = new JTextField("100", 4);
    final JLabel labelPower = new JLabel("Power Curve:", JLabel.RIGHT); 
    final JTextField textPower = new JTextField("2", 4);    
    final JLabel labelBlank1 = new JLabel("");
    final JRadioButton radioConcave = new JRadioButton("Concave (X^n)", true);
    final JLabel labelBlank2 = new JLabel("");
    final JRadioButton radioConvex = new JRadioButton("Convex (X^(1/n))");
    
    final JLabel labelRestoreTime = new JLabel("Restore time (mS):", JLabel.RIGHT);
    final JTextField textRestoreTime = new JTextField("500", 5);
    
    final JTextField textFileName = new JTextField("impulse", 100);
    final JButton buttonGenerate = new JButton("Generate");
    
    // =========================================================================    
    
    public static void main (String args[]) 
    {
        new Impulse();
    }
    
    // -------------------------------------------------------------------------
    // Constructor, triggerd from main(), creates the application window and
    // wakes it up.
    public Impulse()
    {
        // Manage the radio buttons in a group
        final ButtonGroup buttonGroup = new ButtonGroup();
        buttonGroup.add(radioConcave);
        buttonGroup.add(radioConvex);
        
        // Inputs in a grid at the Center position of the content panel
        final JPanel panelInputs = new JPanel();
        panelInputs.setLayout(new GridLayout(6,2));
        panelInputs.setBorder(BorderFactory.createCompoundBorder(
                            BorderFactory.createEmptyBorder(10, 10, 10, 10), 
                            BorderFactory.createEtchedBorder()));
        panelInputs.add(labelPrepareTime);
        panelInputs.add(textPrepareTime);
        panelInputs.add(labelImpulseTime);
        panelInputs.add(textImpulseTime);
        panelInputs.add(labelPower);
        panelInputs.add(textPower);
        panelInputs.add(labelBlank1);
        panelInputs.add(radioConcave);
        panelInputs.add(labelBlank2);
        panelInputs.add(radioConvex);
        panelInputs.add(labelRestoreTime);
        panelInputs.add(textRestoreTime);
        
        // Filename and Generate button on their own panel at the South position
        final JPanel panelControl = new JPanel();
        panelControl.setLayout(new GridLayout(2,1));
        panelControl.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
        panelControl.add(textFileName);
        panelControl.add(buttonGenerate);
        
        // Anonymous listener for the Generate button
        buttonGenerate.addActionListener(
            new ActionListener()
            {
                public void actionPerformed(ActionEvent e)
                { generate(); }
            }
        );
        
        // Assemble the content pane
        getContentPane().add(panelInputs, BorderLayout.CENTER);
        getContentPane().add(panelControl,BorderLayout.SOUTH);
        
        // Turn on the UI
        setTitle("Impulse");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(getContentPane().getLayout().minimumLayoutSize(getContentPane()));
        setVisible(true);
    }
    
    // -------------------------------------------------------------------------
    // Generate button, make the data and save it to the file.  Does the heavy lifting.
    public void generate()
    {
        // Collect our controlling parameters
        int prepareTime = Integer.parseInt(textPrepareTime.getText());
        int impulseTime = Integer.parseInt(textImpulseTime.getText());
        int restoreTime = Integer.parseInt(textRestoreTime.getText());

        double power = Double.parseDouble(textPower.getText());
        if (radioConvex.isSelected())
        { power = 1.0 / power; }
        power = (double)((short)(power*100)) / 100.0;

        // Build up a nice descriptive name based on the control parameters
        String name = new String(textFileName.getText() + "-" + prepareTime + "." + impulseTime + "." + restoreTime + "-pow" + power + ".au");
        
        // Convert from time in milliseconds (1/1000 second) to samples
        // at the sample rate (samples per second).
        int prepareSamples = (sampleRate * prepareTime) / 1000;
        int impulseSamples = (sampleRate * impulseTime) / 1000;
        int restoreSamples = (sampleRate * restoreTime) / 1000;
        
        int totalSamples = prepareSamples + impulseSamples + restoreSamples;
        int totalSampleSize = totalSamples * channels * (sampleSize/8);
        
        // Set up some destination buffers
        byte audioBuffer[] = new byte[totalSampleSize];
        ByteBuffer byteBuffer = ByteBuffer.wrap(audioBuffer);
        ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
        
        // Generate a synthetic audio impulse in three steps:
        // 1. "Preparation" draws down to -MAX from zero
        for (int cnt=0; cnt<prepareSamples; cnt++)
        {
            shortBuffer.put((short)((cnt*minValue) / prepareSamples));
        }
        
        // 2. "Impulse" follows a power curve from -MAX to +MAX
        for (int cnt=0; cnt<impulseSamples; cnt++)
        {
            double N = (double)(cnt+1) / (double)impulseSamples;
            N = Math.pow(N, power) * fullRange + minValue;
            shortBuffer.put((short)N);
        }
        
        // 3. "Restore" draws down from +MAX to zero
        for (int cnt=0; cnt<restoreSamples; cnt++)
        {
            shortBuffer.put((short)(((restoreSamples-cnt)*maxValue) / restoreSamples));
        }
        
        // Save as an audio file
        try
        {
            InputStream byteArrayInputStream = new ByteArrayInputStream(audioBuffer);
            AudioFormat audioFormat = new AudioFormat(sampleRate, sampleSize, channels, signed, bigEndian);
            AudioInputStream audioInputStream = new AudioInputStream(byteArrayInputStream, audioFormat, totalSamples);
            
            try
            {
                AudioSystem.write( audioInputStream, AudioFileFormat.Type.AU, new File(name));
            } catch (Exception e)
            {
                e.printStackTrace();
                System.exit(0);
            }
        } catch (Exception e)
        {
            e.printStackTrace();
            System.exit(0);
        }
        
        // Feedback
        JOptionPane.showMessageDialog(null, "Generated '"+name+"'");        
    }
}
