/*
* Created on February 11, 2007
*
* Copyright (c) 2007 Jens Gulden
*
* http://www.frinika.com
*
* This file is part of Frinika.
*
* Frinika 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 2 of the License, or
* (at your option) any later version.
* Frinika 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 Frinika; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.frinika.sequencer.gui.menu.midi;
import com.frinika.global.FrinikaConfig;
import static com.frinika.localization.CurrentLocale.getMessage;
import com.frinika.gui.AbstractDialog;
import com.frinika.gui.OptionsEditor;
import com.frinika.tootX.midi.MidiInDeviceManager;
import com.frinika.project.ProjectContainer;
import com.frinika.project.gui.ProjectFrame;
import com.frinika.sequencer.SongPositionListener;
import com.frinika.sequencer.SwingSongPositionListenerWrapper;
import com.frinika.sequencer.gui.TimeFormat;
import com.frinika.sequencer.gui.TimeSelector;
import com.frinika.sequencer.gui.selection.SelectionContainer;
import com.frinika.sequencer.gui.selection.SelectionListener;
import com.frinika.sequencer.midi.MidiMessageListener;
import com.frinika.sequencer.model.MidiLane;
import java.awt.Font;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.ShortMessage;
import java.util.*;
/**
* GUI-dialog of a MidiStepRecordAction.
*
* Unlike other dialogs used in this package, this dialog is non-modal, thus
* once the dialog is opened, it remains 'floating' above the main window while
* elements in the main-window remain editable.
*
* (Created with NetBeans 5.5 gui-editor, see corresponding .form file.)
*
* @see MidiStepRecordAction
* @author Jens Gulden
*/
public class MidiStepRecordActionDialog extends AbstractDialog implements OptionsEditor, SongPositionListener, SelectionListener, MidiMessageListener {
public final static long AUTO_RECORD_DELAY_INTERVAL = 1500; // ms after no new notes have been etered to commit an auto-record
private final static Font BUFFER_TEXT_FIELD_FONT_NORMAL = new Font("DialogInput", Font.PLAIN, 12);
private final static Font BUFFER_TEXT_FIELD_FONT_ITALICS = new Font("DialogInput", Font.ITALIC, 12);
private MidiStepRecordAction action;
private ProjectFrame frame;
private TimeSelector positionTimeSelector;
private TimeSelector stepTimeSelector;
//private boolean autoRecord = false;
private boolean bufferDirty = false;
private MidiLane monitoredLane = null; // reference for attaching this as MidiMessageListener
private Collection<Integer> currentlyPressedNotes = new HashSet<Integer>();
private AutoRecordThread autoRecordThread = null;
/** Creates new form MidiStepRecordActionDialog */
public MidiStepRecordActionDialog(ProjectFrame frame, MidiStepRecordAction action) {
super(frame, getMessage("sequencer.midi.step_record"), false); // non-modal
this.frame = frame;
this.action = action;
MidiInDeviceManager.open(FrinikaConfig.getMidiInDeviceList());
ProjectContainer project = frame.getProjectContainer();
initComponents();
positionTimeSelector = new TimeSelector(frame.getProjectContainer(), TimeFormat.BAR_BEAT_TICK);
stepTimeSelector = new TimeSelector(frame.getProjectContainer(), TimeFormat.NOTE_LENGTH, true);
stepTimeSelector.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
MidiStepRecordActionDialog.this.action.step = stepTimeSelector.getTicks();
}
});
positionTimeSelectorPanel.add(positionTimeSelector);
stepPanel.add(stepTimeSelector);
bufferTextField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
setBufferDirty(true);
}
});
/*// close on esc:
final String ESC_CANCEL = "esc-cancel";
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ESC_CANCEL);
getRootPane().getActionMap().put(ESC_CANCEL, new AbstractAction() {
public void actionPerformed(ActionEvent event) {
close();
}
});*/
project.getSequencer().addSongPositionListener(new SwingSongPositionListenerWrapper(this));
project.getSequencer().addMidiMessageListener(this);
project.getMidiSelection().addSelectionListener(this);
this.getRootPane().setDefaultButton(recordButton);
pack();
refresh();
}
public void refresh() {
action.position = frame.getProjectContainer().getSequencer().getTickPosition();
stepTimeSelector.setTicks(action.step);
positionTimeSelector.setTicks(action.position);
lengthDiffSpinner.setValue(action.lengthDiff);
velocitySpinner.setValue(action.velocity);
autoRecordCheckBox.setSelected(action.autoRecord);
refreshAutoRecord();
// this MidiListener is for the vertical virtual keyboard of the pianoroll
refreshPart();
}
private void refreshPart() {
if (monitoredLane != null) {
monitoredLane.removeMidiMessageListener(this);
}
action.part = frame.getProjectContainer().getMidiSelection().getMidiPart();
if (action.part != null) {
monitoredLane = (MidiLane)action.part.getLane();
if (monitoredLane != null) {
monitoredLane.addMidiMessageListener(this);
}
}
}
public void update() {
action.step = stepTimeSelector.getTicks();
action.position = positionTimeSelector.getTicks();
action.lengthDiff = (Integer)lengthDiffSpinner.getValue();
action.velocity = (Integer)velocitySpinner.getValue();
}
@Override
public void hide() {
if (monitoredLane != null) {
monitoredLane.removeMidiMessageListener(this);
}
super.hide();
}
/**
* Moves note from temporary buffer to track/lane.
*/
void record() {
//MidiPart part = frame.getProjectContainer().getMidiSelection().getMidiPart();
refreshPart();
String buffer = getBuffer();
String actuallyRecorded = action.stepRecord(buffer);
if (actuallyRecorded != null) {
setBuffer(actuallyRecorded);
setBufferDirty(false);
currentlyPressedNotes.clear();
}
}
void clear() {
setBuffer("");
}
void undo() {
frame.getProjectContainer().getEditHistoryContainer().getUndoMenuItem().doClick();
}
void close() {
hide();
}
/**
* Implementation of MidiMessageListener.midiMessage()
*/
public void midiMessage(MidiMessage message) {
if (message instanceof ShortMessage) {
ShortMessage shm = (ShortMessage)message;
int cmd = shm.getCommand();
if ( cmd == ShortMessage.NOTE_ON || cmd == ShortMessage.NOTE_OFF ) {
int velocity = shm.getData2();
if ( (velocity != 0) && (cmd == ShortMessage.NOTE_ON) ) {
if (this.isVisible()) {
if (currentlyPressedNotes.isEmpty()) {
clear();
}
int note = shm.getData1();
currentlyPressedNotes.add(note);
this.addToBuffer(note);
if ( action.autoRecord ) {
startAutoRecordInterval();
}
}
} else {
if (this.isVisible()) {
int note = shm.getData1();
currentlyPressedNotes.remove(note);
if ( ! currentlyPressedNotes.isEmpty() ) {
this.removeFromBuffer(note); // last single one remains
}
}
}
}
}
}
public void selectionChanged(SelectionContainer selection) {
refreshPart(); // to replug input device if necessary
}
public void notifyTickPosition(long tick) {
action.position = tick;
positionTimeSelector.setTicks(tick);
}
public boolean requiresNotificationOnEachTick() {
return false;
}
public void setBuffer(String s) {
setBufferDirty(true);
bufferTextField.setText(s);
}
public String getBuffer() {
return bufferTextField.getText();
}
public void addToBuffer(String s) {
String buffer = getBuffer();
if ( buffer.toLowerCase().indexOf( s.toLowerCase() ) == -1 ) { // not yet there
if (buffer.length() > 0) {
buffer += " ";
}
buffer += s;
setBuffer(buffer);
}
}
public void removeFromBuffer(String s) {
String buffer = getBuffer() + " ";
s = s + " ";
int i = buffer.toLowerCase().indexOf( s.toLowerCase() );
if ( i != -1 ) {
buffer = buffer.substring(0, i) + buffer.substring(i + s.length());
setBuffer(buffer.trim());
}
}
public void addToBuffer(int note) {
addToBuffer( MidiStepRecordAction.formatNote(note) );
}
public void removeFromBuffer(int note) {
removeFromBuffer( MidiStepRecordAction.formatNote(note) );
}
private void setBufferDirty(boolean dirty) {
if ( dirty == this.bufferDirty ) return;
this.bufferDirty = dirty;
if ( dirty ) {
bufferTextField.setFont(BUFFER_TEXT_FIELD_FONT_NORMAL);
} else { // not dirty: italic font signalizes 'committed'
bufferTextField.setFont(BUFFER_TEXT_FIELD_FONT_ITALICS);
bufferTextField.selectAll(); // so user can continue typing next note(s) without clearing the textfield
}
bufferTextField.repaint();
}
/*private void clear() {
}*/
private void refreshAutoRecord() {
action.autoRecord = autoRecordCheckBox.isSelected();
if ( action.autoRecord && this.bufferDirty ) {
record(); // initial one right at the time when checkbox set
}
//recordButton.setEnabled( ! autoRecord );
}
private synchronized void startAutoRecordInterval() {
// wait a second, if no new notes, then auto-record
long now = System.currentTimeMillis();
long end = now + AUTO_RECORD_DELAY_INTERVAL; // TODO: configurable by user
if (autoRecordThread == null) {
autoRecordThread = new AutoRecordThread();
autoRecordThread.endTime = end;
autoRecordThread.start();
} else {
autoRecordThread.endTime = end;
}
}
private synchronized void stopAutoRecordInterval() {
autoRecordThread = null;
}
// --- inner class ---
private class AutoRecordThread extends Thread {
long endTime;
@Override
public void run() {
try {
while ( (autoRecordThread == this) && (System.currentTimeMillis() < endTime) ) { // endTime might get increased while looping
Thread.sleep(50);
}
if (autoRecordThread == this) { // not stopped?
recordButton.doClick(); // do auto-record
}
} catch (InterruptedException ex) {
//ex.printStackTrace();
//nop
}
stopAutoRecordInterval();
// Thread exit
}
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
mainPanel = new javax.swing.JPanel();
jLabel1 = new javax.swing.JLabel();
stepPanel = new javax.swing.JPanel();
jLabel4 = new javax.swing.JLabel();
positionTimeSelectorPanel = new javax.swing.JPanel();
jLabel2 = new javax.swing.JLabel();
lengthDiffSpinner = new javax.swing.JSpinner();
jLabel3 = new javax.swing.JLabel();
velocitySpinner = new javax.swing.JSpinner();
bufferTextField = new javax.swing.JTextField();
buttonsPanel = new javax.swing.JPanel();
autoRecordCheckBox = new javax.swing.JCheckBox();
recordButton = new javax.swing.JButton();
undoButton = new javax.swing.JButton();
clearButton = new javax.swing.JButton();
closeButton = new javax.swing.JButton();
getContentPane().setLayout(new java.awt.GridBagLayout());
mainPanel.setLayout(new java.awt.GridBagLayout());
mainPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
jLabel1.setText("Step");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
mainPanel.add(jLabel1, gridBagConstraints);
stepPanel.setLayout(new java.awt.BorderLayout());
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridheight = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 8);
mainPanel.add(stepPanel, gridBagConstraints);
jLabel4.setText("Position");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
mainPanel.add(jLabel4, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
mainPanel.add(positionTimeSelectorPanel, gridBagConstraints);
jLabel2.setText("Length rel.");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
mainPanel.add(jLabel2, gridBagConstraints);
lengthDiffSpinner.setModel(new javax.swing.SpinnerNumberModel(action.lengthDiff, -999, 999, 1));
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
mainPanel.add(lengthDiffSpinner, gridBagConstraints);
jLabel3.setText("Velocity");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
mainPanel.add(jLabel3, gridBagConstraints);
velocitySpinner.setModel(new javax.swing.SpinnerNumberModel(action.velocity, 1, 127, 1));
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
mainPanel.add(velocitySpinner, gridBagConstraints);
bufferTextField.setFont(BUFFER_TEXT_FIELD_FONT_NORMAL);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new java.awt.Insets(10, 3, 10, 3);
mainPanel.add(bufferTextField, gridBagConstraints);
buttonsPanel.setLayout(new java.awt.GridBagLayout());
autoRecordCheckBox.setText("auto step");
autoRecordCheckBox.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
autoRecordCheckBox.setMargin(new java.awt.Insets(0, 0, 0, 0));
autoRecordCheckBox.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
autoRecordCheckBoxStateChanged(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(3, 5, 3, 3);
buttonsPanel.add(autoRecordCheckBox, gridBagConstraints);
recordButton.setForeground(java.awt.Color.red);
recordButton.setMnemonic('P');
recordButton.setText(" Step ");
recordButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
recordButtonActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
buttonsPanel.add(recordButton, gridBagConstraints);
undoButton.setMnemonic('U');
undoButton.setText(" Undo ");
undoButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
undoButtonActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 3);
buttonsPanel.add(undoButton, gridBagConstraints);
clearButton.setMnemonic('C');
clearButton.setText(" Clear ");
clearButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
clearButtonActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 3);
buttonsPanel.add(clearButton, gridBagConstraints);
closeButton.setMnemonic((char)27);
closeButton.setText(" Close ");
closeButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
closeButtonActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
buttonsPanel.add(closeButton, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
mainPanel.add(buttonsPanel, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.ipadx = 5;
gridBagConstraints.ipady = 5;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
getContentPane().add(mainPanel, gridBagConstraints);
pack();
}// </editor-fold>//GEN-END:initComponents
private void clearButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clearButtonActionPerformed
stopAutoRecordInterval();
clear();
}//GEN-LAST:event_clearButtonActionPerformed
private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed
stopAutoRecordInterval();
close();
}//GEN-LAST:event_closeButtonActionPerformed
private void recordButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_recordButtonActionPerformed
stopAutoRecordInterval();
record();
}//GEN-LAST:event_recordButtonActionPerformed
private void undoButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_undoButtonActionPerformed
stopAutoRecordInterval();
undo();
}//GEN-LAST:event_undoButtonActionPerformed
private void autoRecordCheckBoxStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_autoRecordCheckBoxStateChanged
refreshAutoRecord();
}//GEN-LAST:event_autoRecordCheckBoxStateChanged
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JCheckBox autoRecordCheckBox;
private javax.swing.JTextField bufferTextField;
private javax.swing.JPanel buttonsPanel;
private javax.swing.JButton clearButton;
private javax.swing.JButton closeButton;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JLabel jLabel3;
private javax.swing.JLabel jLabel4;
private javax.swing.JSpinner lengthDiffSpinner;
private javax.swing.JPanel mainPanel;
private javax.swing.JPanel positionTimeSelectorPanel;
private javax.swing.JButton recordButton;
private javax.swing.JPanel stepPanel;
private javax.swing.JButton undoButton;
private javax.swing.JSpinner velocitySpinner;
// End of variables declaration//GEN-END:variables
}