This repository contains MadHelix itself, along with compiled libraries with its dependencies.

[[ 🗃 ^ZEkyo madhelix ]] :: [📥 Inbox] [📤 Outbox] [🐤 Followers] [🤝 Collaborators] [🛠 Commits]

Clone

HTTPS: git clone https://vervis.peers.community/repos/ZEkyo

SSH: git clone USERNAME@vervis.peers.community:ZEkyo

Branches

Tags

v0.1.0 :: src / org / ultrasonicmadness / madhelix /

MadHelix.java

/*
 * UltrasonicHelix is a Java Swing-based GUI frontend for SoundHelix.
 * Copyright (C) 2018 UltrasonicMadness
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 only,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.ultrasonicmadness.madhelix;

// Assorted imports
import java.io.File;
import java.net.URL;
import java.util.Random;

// SoundHelix
import com.soundhelix.component.player.impl.MidiPlayer;
import com.soundhelix.misc.SongContext;
import com.soundhelix.util.SongUtils;

// AWT widgets
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;

// Swing widgets
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;

// Other Swing modules
import javax.swing.SwingUtilities;

// UltrasonicHelix imports
import org.ultrasonicmadness.madhelix.dialogs.AboutBox;
import org.ultrasonicmadness.madhelix.utils.PathUtils;

public class MadHelix extends JFrame {
    
    // SoundHelix
    private final File stylesDir = new File(PathUtils.getStylesDirPath()); // Styles directory
    private SongContext currentSong; // Current song being played
    private boolean startHelix = false; // If this is set to true, SoundHelix will be called to play the specified music and it will be set back to false.
    private boolean helixPlaying = false; // Set to true while SoundHelix is playing music, false otherwise.
    
    // MIDI file output
    private final File midiOutDir = new File(PathUtils.getMidiDirPath()); // MIDI file output directory
    private boolean exportMidi = true;
    
    // Main 3 panels: status, options, controls
    private JPanel statusPanel = new JPanel();
    private JPanel optionsPanel = new JPanel();
    private JPanel controlsPanel = new JPanel();
    
    // Components
    private JPanel mainPanel = new JPanel();
    private AboutBox aboutBox = new AboutBox(this);
    
    // Status labels (spaces included so that the layout works)
    private JLabel playStatus = new JLabel(" ");
    private JLabel playStatusInfo = new JLabel(" ");
    private JLabel styleStatus = new JLabel(" ");
    private JLabel styleStatusInfo = new JLabel(" ");
    
    // Labels and data entry fields
    private JLabel songNameLabel = new JLabel("Song name");
    private JTextField songNameEntry = new JTextField(24);
    private JLabel styleLabel = new JLabel("Style");
    private JComboBox<String> styleChoice = new JComboBox<>();
    private JCheckBox fileExportCheckBox = new JCheckBox("Export files when playing", exportMidi);
    
    // Control buttons
    private JButton playButton = new JButton("\u25B6");
    private JButton stopButton = new JButton("\u25A0");
    private JButton aboutButton = new JButton("About");
    
    // SoundHelix thread.
    private Thread soundHelix = new Thread(new Runnable() {
        public void run() {
            // This thread runs as long as UltrasonicHelix does.
            while (true) {
                if (startHelix) {
                    // Since this command has been started, set this to false so it isn't started repeatedly.
                    startHelix = false;
                    playSong();
                    
                } else {
                    // Without this, the thread does not respond to startHelix being set to true.
                    // There is probably a much better way to do this.
                    System.out.print("");
                }
            }
        }
        
        private void playSong() {
            helixPlaying = true;
            
            setStatus("Loading SoundHelix");
            
            // The buttons don't function at this time, so disable them while SoundHelix loads.
            setControlsEnabled(false);
            
            String styleName = getCurrentStyleName();
            String songName = getCurrentSongName();
            
            File styleFile = new File(stylesDir.toString() + File.separator +
                    styleName + ".xml");
            
            try {
                // If a song name is specified, use it, otherwise generate a random number and use it.
                if (songName != null && !songName.equals("")) {
                    currentSong = SongUtils.generateSong(
                            styleFile.toURI().toURL(), songName);
                } else {
                    // This is the same way SoundHelix uses the random number generator.
                    currentSong = SongUtils.generateSong(
                            styleFile.toURI().toURL(), new Random().nextLong());
                }
                
                if (exportMidi) {
                    
                    try {
                        MidiPlayer midiPlayer = (MidiPlayer)currentSong.getPlayer();
                        
                        midiPlayer.setMidiFilename(midiOutDir + File.separator + 
                                currentSong.getSongName() + "-" + styleName +
                                ".mid");
                    } catch (Exception x) {
                        JOptionPane.showMessageDialog(null, x.getMessage().toString(),
                                "Could not set MIDI filename.", JOptionPane.ERROR_MESSAGE);
                    }
                }
                
                // Update song and style information
                setStatus("Playing",
                        currentSong.getSongName(), "Style", styleName);
                
                // Now SoundHelix has loaded, enable the buttons again
                setControlsEnabled(true);
                
                // Play the song
                currentSong.getPlayer().play(currentSong);
            } catch (Exception x) { // In case SoundHelix encounters a problem
                JOptionPane.showMessageDialog(null, x.getMessage().toString(),
                        "Exception occurred", JOptionPane.ERROR_MESSAGE);
                
                // In case an error has occured, enable the buttons again
                setControlsEnabled(true);
            } catch (NoClassDefFoundError r) { // In case SoundHelix.jar is not found
                JOptionPane.showMessageDialog(null,
                        "Cannot find module " + r.getMessage(), "Error",
                        JOptionPane.ERROR_MESSAGE);
                
                // In case an error has occured, enable the buttons again
                setControlsEnabled(true);
            }
            
            // The song has finished at this point, so update variables and status accordingly.
            helixPlaying = false;
            setStatus("Ready");
        }
    });
    
    public MadHelix() {
        initMadHelix();
    }
    
    private void initMadHelix() {
        // Start SoundHelix thread
        soundHelix.setDaemon(true); // Closes SoundHelix if UltrasonicHelix is closed.
        soundHelix.start();
        
        addComponents();
        
        // Set up main window
        this.setTitle("MadHelix");
        this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        this.setResizable(false);
        this.pack();
        this.setVisible(true);
    }
    
    private void determineStatus() {
        // Assume program is ready , then check for errors
        setStatus("Ready");
        
        // If no styles are found, disable the buttons and display an alert.
        if (stylesDir.list() == null || stylesDir.list().length == 0) {
            setStatus("No styles available.");
            setControlsEnabled(false);
            
            JOptionPane.showMessageDialog(null,
                "No .xml files found in the styles directory",
                "Error",
                JOptionPane.ERROR_MESSAGE
            );
        }
    }
    
    private void addComponents() {
        // Set panel layout
        mainPanel.setLayout(new GridBagLayout());
        
        // Set up constraints
        GridBagConstraints mainConstraints = new GridBagConstraints();
        mainConstraints.fill = GridBagConstraints.HORIZONTAL;
        
        addStatusLabels();
        addOptions();
        addControls();
        
        // Add panels
        mainConstraints.gridy = 0;
        mainPanel.add(statusPanel, mainConstraints);
        
        mainConstraints.gridy = 1;
        mainPanel.add(controlsPanel, mainConstraints);
        
        mainConstraints.gridy = 2;
        mainPanel.add(optionsPanel, mainConstraints);
        
        this.add(mainPanel);
    }
    
    private void addStatusLabels() {
        determineStatus();
        
        // Set up layout
        statusPanel.setLayout(new GridBagLayout());
        
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.insets = new Insets(2, 2, 2, 2);
        constraints.fill = GridBagConstraints.HORIZONTAL;

        // Add the labels to the panel
        constraints.gridy = 0;
        constraints.weightx = 0;
        statusPanel.add(playStatus, constraints);

        constraints.weightx = 1;
        statusPanel.add(playStatusInfo, constraints);

        constraints.gridy = 1;
        constraints.weightx = 0;
        statusPanel.add(styleStatus, constraints);

        constraints.weightx = 1;
        statusPanel.add(styleStatusInfo, constraints);
    }
    
    // Set status to the four passed strings
    private void setStatus(String newPlayStatus, String newPlayStatusInfo,
        String newStyleStatus, String newStyleStatusInfo) {
        
        // Set all label text to the passed strings.
        playStatus.setText(newPlayStatus);
        playStatusInfo.setText(newPlayStatusInfo);
        styleStatus.setText(newStyleStatus);
        styleStatusInfo.setText(newStyleStatusInfo);
    }
    
    // Set status to the passed string. The others are reset to blank.
    private void setStatus(String newPlayStatus) {
        setStatus(newPlayStatus, " ", " ", " ");
    }
    
    private void addOptions() {
        // Get list of styles for the combo box.
        String[] styleList = PathUtils.getStyles();
        
        // Add styles to style choice combo box
        for (String style : styleList) {        
            styleChoice.addItem(style);
        }
        
        fileExportCheckBox.addActionListener(ev -> {
            exportMidi = fileExportCheckBox.isSelected();
        });
        
        fileExportCheckBox.setToolTipText("The files are exported to the midi directory " + 
                "in the same location as the JAR application.");
        
        // Set up layout
        optionsPanel.setLayout(new GridBagLayout());
        
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.insets = new Insets(2, 2, 2, 2);
        
        // Add song information
        constraints.gridy = 0;
        optionsPanel.add(songNameLabel, constraints);
        optionsPanel.add(songNameEntry, constraints);
        
        // Add style label
        constraints.gridy = 1;
        optionsPanel.add(styleLabel, constraints);
        optionsPanel.add(styleChoice, constraints);
        
        // Add file export checkbox
        constraints.gridy = 2;
        constraints.gridwidth = 2;
        optionsPanel.add(fileExportCheckBox, constraints);
    }
    
    private void addControls() {
        // Set up layout
        controlsPanel.setLayout(new GridBagLayout());
        
        playButton.addActionListener(ev -> {
            playSong();
        });
        
        stopButton.addActionListener(ev -> {
            stopSong();
        });
        
        aboutButton.addActionListener(ev -> {
            aboutBox.setVisible(true);
        });
        
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.insets = new Insets(2, 2, 2, 2);
        constraints.fill = GridBagConstraints.HORIZONTAL;
        
        constraints.weightx = 0;
        controlsPanel.add(playButton, constraints);
        controlsPanel.add(stopButton, constraints);
        
        // Add a blank panel to separate player controls and the about button
        constraints.weightx = 1;
        controlsPanel.add(new JPanel(), constraints);
        
        constraints.weightx = 0;
        controlsPanel.add(aboutButton, constraints);
    }
    
    private void setControlsEnabled(boolean enabled) {
        playButton.setEnabled(enabled);
        stopButton.setEnabled(enabled);
    }
    
    // The current song name entered in the text box. This may be different from the name of the song currently playing.
    private String getCurrentSongName() {
        return songNameEntry.getText();
    }
    
    // Refer to the comment above regarding song names, the same applies here with styles.
    private String getCurrentStyleName() {
        return styleChoice.getSelectedItem().toString();
    }
    
    private void playSong() {
        stopSong(); // Without this, the next song won't play until the last one is finished.
        startHelix = true; // Signals to the SoundHelix thread to start
    }
    
    private void stopSong() {
        if (helixPlaying) {
            currentSong.getPlayer().abortPlay();
        }
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new MadHelix();
            }
        });
    }
}

[See repo JSON]