/*
* Copyright 2015, RagingGoblin <http://raginggoblin.wordpress.com>
*
* This file is part of SpeechLess.
*
* SpeechLess is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SpeechLess 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 SpeechLess. If not, see <http://www.gnu.org/licenses/>.
*/
package raging.goblin.speechless.ui;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.sound.sampled.AudioInputStream;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import lombok.Getter;
import marytts.LocalMaryInterface;
import marytts.util.data.audio.AudioPlayer;
import raging.goblin.speechless.Messages;
import raging.goblin.speechless.speech.SoundEffect;
import raging.goblin.speechless.speech.Voice;
import com.jgoodies.forms.factories.FormFactory;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.ColumnSpec;
import com.jgoodies.forms.layout.FormLayout;
import com.jgoodies.forms.layout.RowSpec;
public class VoiceConfigurationDialog extends JDialog {
private static final Messages MESSAGES = Messages.getInstance();
private JComboBox<Voice> voicesBox;
private List<EffectPanel> effectPanels = new ArrayList<>();
@Getter
private boolean okPressed;
public VoiceConfigurationDialog(JFrame parent) {
super(parent, MESSAGES.get("voice_config_title"), true);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setSize(950, 850);
ScreenPositioner.centerOnScreen(this);
initActionsPanel();
initConfigPanel();
}
private void initActionsPanel() {
JPanel actionPanel = new JPanel();
getContentPane().add(actionPanel, BorderLayout.SOUTH);
FormLayout layout = new FormLayout(new ColumnSpec[] { ColumnSpec.decode("default:grow"),
FormFactory.RELATED_GAP_COLSPEC, FormFactory.DEFAULT_COLSPEC, FormFactory.RELATED_GAP_COLSPEC,
FormFactory.DEFAULT_COLSPEC, FormFactory.RELATED_GAP_COLSPEC, ColumnSpec.decode("max(40dlu;default)"),
FormFactory.RELATED_GAP_COLSPEC, FormFactory.DEFAULT_COLSPEC, FormFactory.RELATED_GAP_COLSPEC,
ColumnSpec.decode("max(40dlu;default)"), FormFactory.RELATED_GAP_COLSPEC,
ColumnSpec.decode("max(5dlu;default)"), }, new RowSpec[] { FormFactory.DEFAULT_ROWSPEC,
FormFactory.RELATED_GAP_ROWSPEC, RowSpec.decode("max(5dlu;default)"), });
actionPanel.setLayout(layout);
JButton btnCancel = new JButton(MESSAGES.get("cancel"));
btnCancel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
});
actionPanel.add(btnCancel, "7, 1, 2, 1");
JButton btnDefaults = new JButton(MESSAGES.get("load_defaults"));
btnDefaults.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setDefaults();
}
});
actionPanel.add(btnDefaults, "3, 1");
JButton btnOk = new JButton(MESSAGES.get("ok"));
btnOk.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
saveConfiguration();
okPressed = true;
setVisible(false);
}
});
actionPanel.add(btnOk, "11, 1, 2, 1");
}
private void initConfigPanel() {
JPanel configPanel = new JPanel();
getContentPane().add(configPanel, BorderLayout.CENTER);
configPanel.setLayout(new FormLayout(new ColumnSpec[] {
ColumnSpec.decode("max(61dlu;min):grow"), ColumnSpec.decode("max(140dlu;default)"),
FormFactory.RELATED_GAP_COLSPEC, ColumnSpec.decode("200px"), FormFactory.RELATED_GAP_COLSPEC,
ColumnSpec.decode("max(30dlu;default)"),
FormFactory.RELATED_GAP_COLSPEC, FormFactory.DEFAULT_COLSPEC, FormFactory.RELATED_GAP_COLSPEC,
ColumnSpec.decode("default:grow"), FormFactory.RELATED_GAP_COLSPEC, FormFactory.DEFAULT_COLSPEC,
FormFactory.RELATED_GAP_COLSPEC, ColumnSpec.decode("default:grow"),
FormFactory.RELATED_GAP_COLSPEC,
ColumnSpec.decode("max(30dlu;default)"), FormFactory.RELATED_GAP_COLSPEC,
ColumnSpec.decode("max(30dlu;default)"), },
new RowSpec[] { FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC,
FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC,
FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC, FormFactory.DEFAULT_ROWSPEC,
FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC,
FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC,
FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC,
FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC,
FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC,
FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC,
FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC,
FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC,
FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC,
FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC,
FormFactory.DEFAULT_ROWSPEC, }));
StringSeparator stringSeparator = new StringSeparator(MESSAGES.get("voice"));
configPanel.add(stringSeparator, "1, 2, 18, 1");
voicesBox = new JComboBox<>();
Voice.getAllVoices().stream().forEach(v -> voicesBox.addItem(v));
voicesBox.setSelectedItem(Voice.getSelectedVoice());
configPanel.add(voicesBox, "2, 4, 13, 1");
JButton descriptionVoiceButton = new JButton(Icon.getIcon("/icons/help.png"));
descriptionVoiceButton.addActionListener(a -> JOptionPane.showMessageDialog(VoiceConfigurationDialog.this,
((Voice) voicesBox.getSelectedItem()).getDescription(), ((Voice) voicesBox.getSelectedItem()).getName(),
JOptionPane.INFORMATION_MESSAGE));
configPanel.add(descriptionVoiceButton, "18, 4, center, center");
StringSeparator dimensionsSeparator = new StringSeparator(MESSAGES.get("effects"));
configPanel.add(dimensionsSeparator, "1, 6, 18, 1");
configPanel.add(new JLabel(MESSAGES.get("name")), "2, 8, 1, 1");
configPanel.add(new JLabel(MESSAGES.get("enabled")), "4, 8, 1, 1");
configPanel.add(new JLabel(MESSAGES.get("parameters")), "6, 8, 8, 1");
configPanel.add(new JLabel(" "), "2, 10, 1, 1");
configPanel.add(new JSeparator(), "2, 11, 17, 1");
for (int i = 0; i < SoundEffect.getAllSoundEffects().size(); i++) {
EffectPanel effectPanel = new EffectPanel(SoundEffect.getAllSoundEffects().get(i));
configPanel.add(effectPanel, String.format("2, %d, 17, 1", i * 2 + 12));
effectPanels.add(effectPanel);
if (i < SoundEffect.getAllSoundEffects().size() - 1) {
configPanel.add(new JSeparator(), String.format("2, %d, 17, 1", i * 2 + 13));
}
}
configPanel.add(new StringSeparator(MESSAGES.get("preview")), "1, 34, 18, 1");
JTextField previewField = new JTextField(MESSAGES.get("preview_text"));
configPanel.add(previewField, "2, 36, 13, 1");
JButton previewButton = new JButton(Icon.getIcon("/icons/control_play.png"));
previewButton.addActionListener(a -> {
try {
LocalMaryInterface marytts = new LocalMaryInterface();
marytts.setVoice(((Voice) voicesBox.getSelectedItem()).getName());
String effectsString = effectPanels.stream().filter(ep -> ep.isEnabled()).map(ep -> ep.getEffectString())
.collect(Collectors.joining("+"));
marytts.setAudioEffects(effectsString);
AudioInputStream audio = marytts.generateAudio(previewField.getText().toLowerCase());
AudioPlayer player = new AudioPlayer(audio);
player.start();
player.join();
} catch (Exception e) {
JOptionPane.showMessageDialog(VoiceConfigurationDialog.this, MESSAGES.get("init_marytts_error"),
MESSAGES.get("error"), JOptionPane.ERROR_MESSAGE);
}
});
configPanel.add(previewButton, "18, 36, center, center");
}
private void setDefaults() {
voicesBox.setSelectedItem(Voice.getDefaultVoice());
effectPanels.stream().forEach(ep -> ep.setDefaults());
}
private void saveConfiguration() {
Voice.setSelectedVoice(((Voice) voicesBox.getSelectedItem()).getName());
effectPanels.stream().forEach(ep -> ep.saveEffect());
}
private class EffectPanel extends JPanel {
private SoundEffect soundEffect;
private JCheckBox enabledBox;
private List<LevelPanel> levelPanels = new ArrayList<>();
public EffectPanel(SoundEffect soundEffect) {
this.soundEffect = soundEffect;
initPanel();
}
public void setDefaults() {
enabledBox.setSelected(false);
levelPanels.stream().forEach(lp -> lp.setDefaults());
}
public void saveEffect() {
soundEffect.setEnabled(enabledBox.isSelected());
levelPanels.stream().forEach(lp -> lp.saveValue());
}
public String getEffectString() {
List<Double> values = levelPanels.stream().map(lp -> ((Double) lp.getSpinner().getValue()))
.collect(Collectors.toList());
return String.format(soundEffect.getFormat(), values.toArray());
}
@Override
public boolean isEnabled() {
return enabledBox.isSelected();
}
private void initPanel() {
setLayout(new FormLayout(new ColumnSpec[] {
ColumnSpec.decode("max(140dlu;default)"), FormFactory.RELATED_GAP_COLSPEC, ColumnSpec.decode("200px"),
FormFactory.RELATED_GAP_COLSPEC, ColumnSpec.decode("max(30dlu;default)"),
FormFactory.RELATED_GAP_COLSPEC, FormFactory.DEFAULT_COLSPEC, FormFactory.RELATED_GAP_COLSPEC,
ColumnSpec.decode("default:grow"), FormFactory.RELATED_GAP_COLSPEC, FormFactory.DEFAULT_COLSPEC,
FormFactory.RELATED_GAP_COLSPEC, ColumnSpec.decode("default:grow"), FormFactory.RELATED_GAP_COLSPEC,
ColumnSpec.decode("max(30dlu;default)"), FormFactory.RELATED_GAP_COLSPEC,
ColumnSpec.decode("max(30dlu;default)"), },
new RowSpec[] { FormFactory.DEFAULT_ROWSPEC }));
JLabel l = new JLabel(soundEffect.getName());
add(l, "1, 1, left, top");
enabledBox = new JCheckBox();
enabledBox.setSelected(soundEffect.isEnabled());
add(enabledBox, "3, 1, left, top");
JButton helpButton = new JButton(Icon.getIcon("/icons/help.png"));
helpButton.addActionListener(a -> JOptionPane.showMessageDialog(VoiceConfigurationDialog.this,
soundEffect.getHelpText(), soundEffect.getName(), JOptionPane.INFORMATION_MESSAGE));
add(helpButton, "17, 1, center, top");
JPanel levelsPanel = new JPanel();
levelsPanel.setLayout(new BoxLayout(levelsPanel, BoxLayout.PAGE_AXIS));
List<String> levelKeys = soundEffect.getLevelKeys();
for (int i = 0; i < soundEffect.getLevelKeys().size(); i++) {
LevelPanel levelPanel = new LevelPanel(soundEffect, levelKeys.get(i),
soundEffect.getLevel(levelKeys.get(i)), soundEffect.getDefaultLevels().get(levelKeys.get(i)));
levelsPanel.add(levelPanel);
levelPanels.add(levelPanel);
}
CellConstraints cc = new CellConstraints();
add(levelsPanel, cc.xyw(5, 1, 9, "fill, top"));
levelPanels.stream().forEach(lp -> lp.getSpinner().setEnabled(enabledBox.isSelected()));
enabledBox.addChangeListener(c -> levelPanels.stream().forEach(
lp -> lp.getSpinner().setEnabled(enabledBox.isSelected())));
}
}
private class LevelPanel extends JPanel {
private SoundEffect soundEffect;
private JSpinner levelSpinner;
private List<Double> defaultValues;
private String name;
public LevelPanel(SoundEffect soundEffect, String name, Double currentValue, List<Double> defaultValues) {
this.soundEffect = soundEffect;
this.defaultValues = defaultValues;
this.name = name;
this.setLayout(new BorderLayout());
JLabel nameLabel = new JLabel(name);
nameLabel.setVerticalAlignment(SwingConstants.TOP);
add(nameLabel, BorderLayout.WEST);
levelSpinner = new JSpinner(new SpinnerNumberModel(currentValue, defaultValues.get(1), defaultValues.get(2),
defaultValues.get(3)));
levelSpinner.setValue(soundEffect.getLevel(name));
add(levelSpinner, BorderLayout.EAST);
}
public void setDefaults() {
levelSpinner.setValue(defaultValues.get(0));
}
public void saveValue() {
soundEffect.setLevel(name, (Double) levelSpinner.getValue());
}
public JSpinner getSpinner() {
return levelSpinner;
}
}
}