Search code examples
javafunctionenumsargumentsjcombobox

Passing enum values to a function by selecting in a JComboBox


I simply had a brilliant idea but then I realized I'm just too noob in Java to do this. My idea was like this:

We have class called test (by now) that have an enum Tones - as you can see there are frequency values of each tone. Of course we have getChord() function to get these values.

import javax.swing.*;
import java.awt.*;
import java.util.*;

public class test 
{
public enum Tones
{
    C(261.6), CSHARP(277.2), D(293.7), DSHARP(311.2), E(329.6), F(349.6), FSHARP(370), G(391.9), GSHARP(415.3), A(440), B(466.2), H(493.9);

    private double frequencyVal;

    Tones(double frequencyVal)
    {
        this.frequencyVal = frequencyVal;
    }

    public double getChord()
    {
        return frequencyVal;
    }
    };
}

Then we have a class called Chords where JComboBox is. We can simply select a chord from it.

import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import javax.swing.*;


class Chords extends JPanel
{
private JComboBox chooseChord = new JComboBox(new String[]{"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "B", "H"});

public Chords() 
{
    chooseChord.setSelectedItem(null);

    chooseChord.addItemListener(new ItemListener() 
    {
        @Override
        public void itemStateChanged(ItemEvent e) 
        {
            Object item = chooseChord.getSelectedItem();

            if ("A".equals(item)) 
            {

            } 

            else if ("B".equals(item)) 
            {

            }

            else if ("H".equals(item)) 
            {

            }

            else if ("C".equals(item)) 
            {

            }

            else if ("C#".equals(item)) 
            {

            }

            else if ("D".equals(item)) 
            {

            }

            else if ("D#".equals(item)) 
            {

            }

            else if ("E".equals(item)) 
            {

            }

            else if ("F".equals(item)) 
            {

            }

            else if ("F#".equals(item)) 
            {

            }

            else if ("G".equals(item)) 
            {

            }

            else if ("G#".equals(item)) 
            {

            }
        }
    });

    add(chooseChord);
}   
}

That's where the hard part comes in. I hope I will describe it as simple as I can.

By selecting the chord in JComboBox I wanted to select a group of three specific tones from enum. And THEN I wanted these three specific values to be passed to playChord() function which is also in another class (the end of code). I just typed random values of frequency there.

import java.applet.*;
import java.io.*;
import java.net.*;

import javax.sound.sampled.*;

public final class Audio 
{

    public static final int SAMPLE_RATE = 44100;

    private static final int BYTES_PER_SAMPLE = 2;                // 16-bit audio
    private static final int BITS_PER_SAMPLE = 16;                // 16-bit audio
    private static final double MAX_16_BIT = Short.MAX_VALUE;     // 32,767
    private static final int SAMPLE_BUFFER_SIZE = 4096;

    private static SourceDataLine line;   // to play the sound
    private static byte[] buffer;         // our internal buffer
    private static int bufferSize = 0;    

    private static double amplitude, frequency, phase;

    // not-instantiable
    private Audio() { }

    // static initializer
    static { init(); }

    // open up an audio stream
private static void init() 
{

try 
{
    // 44,100 samples per second, 16-bit audio, mono, signed PCM, little Endian
    AudioFormat format = new AudioFormat((float) SAMPLE_RATE, BITS_PER_SAMPLE, 1, true, false);
    DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);

    line = (SourceDataLine) AudioSystem.getLine(info);
    line.open(format, SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE);

    buffer = new byte[SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE/3];
}

catch (Exception e) 
{
    System.out.println(e.getMessage());
    System.exit(1);
}

// no sound gets made before this call
line.start();
}

public void setAmplitude(double a)
{
    amplitude = a;
}

public static void setFrequency(double f)
{
    frequency = f;
}

public void SetPhase(double p)
{
    phase = p;
}

public static double getAmplitude()
{
    return amplitude;
}

public static double getFrequency()
{
    return frequency;
}

public static double getPhase()
{
    return phase;
}

/**
 * Close standard audio.
 */
public static void close() 
{
line.drain();
line.stop();
}

/**
 * Write one sample (between -1.0 and +1.0) to standard audio. If the sample
 * is outside the range, it will be clipped.
 */
public static void play(double in) 
{

// clip if outside [-1, +1]
if (in < -1.0) in = -1.0;
if (in > +1.0) in = +1.0;

// convert to bytes
short s = (short) (MAX_16_BIT * in);
buffer[bufferSize++] = (byte) s;
buffer[bufferSize++] = (byte) (s >> 8);   // little Endian

// send to sound card if buffer is full        
if (bufferSize >= buffer.length) 
{
    line.write(buffer, 0, buffer.length);
    bufferSize = 0;
}
}

/**
 * Write an array of samples (between -1.0 and +1.0) to standard audio. If a     sample
 * is outside the range, it will be clipped.
 */
public static void play(double[] input) 
{
for (int i = 0; i < input.length; i++) 
{
    play(input[i]);
}
}

private static double[] tone(double hz, double duration, double amplitude, double phase) 
{
int N = (int) (Audio.SAMPLE_RATE * duration);
double[] a = new double[N+1];
for (int i = 0; i <= N; i++)
    a[i] = amplitude * Math.sin(2 * Math.PI * i * hz / Audio.SAMPLE_RATE + phase);
return a;
}

public static void playChord() throws LineUnavailableException 
{
double[] a = tone(415.3, 1.0, 1, 0);
double[] b = tone(329.6, 1.0, 1, 0);
double[] c = tone(493.9, 1.0, 1, 0);

for( int i=0; i<a.length; ++ i )
   a[i] = (a[i] + b[i] + c[i]) / 3;

Audio.play(a);
}

public static void playSound() throws LineUnavailableException
{
double[] a = tone(getFrequency(), 1.0, getAmplitude(), getPhase());

for( int i = 0; i < a.length; ++ i )
       a[i] = (a[i]);

Audio.play(a);
}
}

My program assumes that user can select a chord from a list and then just press "Play' button to hear it. That's why I wanted it to be done automatically, because I'm not really sure whether defining 12 separate functions/variables/arrays storing tones values is the best way.

Any clue or help how to achieve that complicated goal? I feel just overwhelmed by it.

EDIT: Implementing Radiodef's solution into my code is kinda difficult for me. I think I understand your code but I can't just implement it into mine. Will you help? My main function looks like this:

import java.awt.*;
import java.awt.event.*;
import javax.sound.sampled.LineUnavailableException;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.event.*;

import java.awt.*;

public class Window extends JPanel implements ActionListener
{
private JMenuBar mainMenu = new JMenuBar();

private Plot plot = new Plot();
private Parameters param = new Parameters();

private JButton playSound = new JButton("Play");
private JButton getSample = new JButton("Save wave");
private JButton getPlot = new JButton("Save plot");

private Chords music = new Chords();

private JPanel mainPanel = new JPanel();
private JPanel subPanel = new JPanel();
private JPanel buttonsPanel = new JPanel();
private JPanel slidersPanel = new JPanel();

private JLabel chord = new JLabel("Chord:");

private JTextField aValue = new JTextField();
private JTextField fValue = new JTextField(); 
private JTextField pValue = new JTextField();

public Window() 
{
    mainPanel.setLayout(new FlowLayout());
    buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.Y_AXIS));
    slidersPanel.setLayout(new BorderLayout());
    subPanel.setLayout(new BorderLayout());

    buttonsPanel.add(chord);
    buttonsPanel.add(music);
    buttonsPanel.add(Box.createRigidArea(new Dimension(0,10)));
    buttonsPanel.add(playSound);
    buttonsPanel.add(Box.createRigidArea(new Dimension(0,10)));
    buttonsPanel.add(getSample);
    buttonsPanel.add(Box.createRigidArea(new Dimension(0,10)));
    buttonsPanel.add(getPlot);
    buttonsPanel.setBorder(BorderFactory.createTitledBorder("Menu"));

    JMenu langMenu = new JMenu("Language");

    param.addAmplitudeListener(new ChangeListener()
    {
        public void stateChanged(ChangeEvent a)
        {
            int ampValue = param.getAmplitudeValue();
            aValue.setText(String.valueOf(ampValue));
        }
    }
    );


    param.addFrequencyListener(new ChangeListener()
    {
        public void stateChanged(ChangeEvent f)
        {
            double frValue = param.getFrequencyValue();
            fValue.setText(String.valueOf(frValue));
        }
    }
    );


    param.addPhaseListener(new ChangeListener()
    {
        public void stateChanged(ChangeEvent p)
        {
            double phValue = param.getPhaseValue();
            pValue.setText(String.valueOf(phValue));
        }
    }
    );

    playSound.addActionListener(this);
    getPlot.addActionListener(this);
    getSample.addActionListener(this);

    mainMenu.add(langMenu);
    slidersPanel.add(param);
    subPanel.add(buttonsPanel, BorderLayout.NORTH);
    subPanel.add(slidersPanel, BorderLayout.SOUTH);
    mainPanel.add(subPanel);
    mainPanel.add(plot);
    add(mainPanel);


}

public void actionPerformed(ActionEvent a)
{
    Object button = a.getSource();

    if(button==playSound)
        try 
        {
            Audio.playChord(frequencies);
        } 

        catch (LineUnavailableException e) 
        {
            System.out.println("Error");
        }
}

public JMenuBar getmainMenu()
{
    return mainMenu;
}

private static void GUI()
{
    Window mainPanel = new Window();
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(mainPanel);
    frame.setJMenuBar(mainPanel.getmainMenu());
    frame.pack();
    frame.setVisible(true);
    Menu theme = new Menu();
    theme.setVisible(true);
    theme.pack();
}

public static void main(String[] args) 
{
    SwingUtilities.invokeLater(new Runnable() 
    {
         public void run()
         {
            GUI();
         }
      }
      );
}
}

This is the place when I define buttons actionlisteners. Since my reputation is too low so far to post images I can't show you full GUI. Posting all the classes would be too difficult.


Solution

  • One thing you need to do is modify the playChord method so it accepts your array of frequencies (which you've retrieved from the user somehow). You might use something like the following. This is pretty much the same thing you are doing already, except using an array instead of the hardcoded a, b and c.

    public static void playChord(double[] frequencies)
        throws LineUnavailableException 
    {
        double[] buffer = tone(frequencies[0], 1.0, 1, 0);
    
        for(int i = 1; i < frequencies.length; ++i) {
            double[] harmonic = tone(frequencies[i], 1.0, 1, 0);
    
            for(int i = 0; i < buffer.length; ++i) {
                buffer[i] += harmonic[i];
            }
        }
    
        for(int i = 0; i < buffer.length; ++i) {
            buffer[i] /= frequencies.length;
        }
    
        Audio.play(buffer);
    }
    

    So, moving along. I'm not sure if you meant that the user is supposed to select a note, which then plays the 3 corresponding notes of a chord. (For example the user selects C which plays the notes C, E and G of a major triad.)

    tone box

    import java.util.*;
    import javax.swing.*;
    import java.awt.BorderLayout;
    
    class ToneBox implements Runnable {
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new ToneBox());
        }
    
        enum Tone {
            C(261.6),
            CSHARP(277.2),
            D(293.7),
            DSHARP(311.2),
            E(329.6),
            F(349.6),
            FSHARP(370),
            G(391.9),
            GSHARP(415.3),
            A(440),
            ASHARP(466.2), // note: there was an error here
            B(493.9);      // A -> A# -> B, not A -> B -> H
    
            final double hz;
    
            Tone(double hz) {
                this.hz = hz;
            }
    
            double[] triad() {
                List<Tone> values = Arrays.asList(Tone.values());
                double[] chord = new double[3];
    
                int myIndex = values.indexOf(this);
    
                // form a triad using the intervals
                // as relative indexes in to the list
                chord[0] = this.hz;
                chord[1] = values.get((myIndex + 4) % values.size()).hz;
                chord[2] = values.get((myIndex + 7) % values.size()).hz;
    
                return chord;
            }
    
            // override toString, which JComboBox
            // uses to display, so we can put
            // Tone objects in the box directly
    
            @Override
            public String toString() {
                return this.name().replace("SHARP", "#");
            }
        }
    
        @Override
        public void run() {
            final JFrame   frame = new JFrame();
            final JPanel content = new JPanel(new BorderLayout());
            final JComboBox  box = new JComboBox(Tone.values());
            final JButton button = new JButton("Play");
    
            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent ae) {
                    // overriding toString makes it much
                    // easier for us to retrieve the
                    // enum values from the JComboBox
    
                    Tone tone = (Tone)box.getSelectedItem();
                    double[] chord = tone.triad();
    
                    JOptionPane.showMessageDialog(null,
                        "Chord selected: " + Arrays.toString(chord)
                    );
                }
            });
    
            content.add(box, BorderLayout.CENTER);
            content.add(button, BorderLayout.SOUTH);
    
            frame.setContentPane(content);
            frame.pack();
            frame.setResizable(false);
            frame.setLocationRelativeTo(null);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        }
    }
    

    If your idea was that the user selects the notes in the chord individually, you might also consider using a JList for this kind of thing. Just a suggestion. JList allows the user to select multiple items.

    tone list

    import java.util.*;
    import javax.swing.*;
    import java.awt.BorderLayout;
    
    class ToneList implements Runnable {
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new ToneList());
        }
    
        enum Tone {…} // same as above
    
        @Override
        public void run() {
            final JFrame   frame = new JFrame();
            final JPanel content = new JPanel(new BorderLayout());
            final JList     list = new JList(Tone.values());
            final JButton button = new JButton("Play");
    
            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent ae) {
                    Object[] values = list.getSelectedValues();
                    double[] chord  = new double[values.length];
    
                    for(int i = 0; i < chord.length; ++i) {
                        Tone tone = (Tone)values[i];
                        chord[i]  = tone.hz;
                    }
    
                    JOptionPane.showMessageDialog(null,
                        "Chord selected: " + Arrays.toString(chord)
                    );
                }
            });
    
            content.add(list, BorderLayout.CENTER);
            content.add(button, BorderLayout.SOUTH);
    
            frame.setContentPane(content);
            frame.pack();
            frame.setResizable(false);
            frame.setLocationRelativeTo(null);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        }
    }
    

    I hope this gives you some ideas to fill in what you're missing.