/* * JSampler - a java front-end for LinuxSampler * * Copyright (C) 2005-2009 Grigor Iliev * * This file is part of JSampler. * * JSampler is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * JSampler 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 JSampler; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ package org.jsampler.view.std; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Vector; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.DefaultCellEditor; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSlider; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.JToolBar; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumn; import javax.swing.table.JTableHeader; import net.sf.juife.event.TaskEvent; import net.sf.juife.event.TaskListener; import org.jsampler.AudioDeviceModel; import org.jsampler.CC; import org.jsampler.JSPrefs; import org.jsampler.SamplerChannelModel; import org.jsampler.event.EffectSendsAdapter; import org.jsampler.event.EffectSendsEvent; import org.jsampler.event.EffectSendsListener; import org.jsampler.task.Channel; import org.jsampler.view.FxSendTable; import org.linuxsampler.lscp.FxSend; import static org.jsampler.view.std.StdI18n.i18n; /** * * @author Grigor Iliev */ public class JSFxSendsPane extends JPanel implements ListSelectionListener { private SamplerChannelModel channelModel; private final FxSendTable fxSendsTable; protected final Action actionAddFxSend = new AddFxSendAction(); protected final Action actionRemoveFxSend = new RemoveFxSendAction(); private final JComboBox cbMidiCtrls = new JComboBox(); private final JSlider slVolume = StdUtils.createVolumeSlider(); private final JLabel lMidiCtrl = new JLabel(i18n.getLabel("JSFxSendsPane.lMidiCtrl")); private final JLabel lVolume = new JLabel(i18n.getLabel("JSFxSendsPane.lVolume")); private final ChannelRoutingTable channelRoutingTable; private FxSend fxSend = null; private final int[] undefinedControllers = { 3, 9, 14, 15, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 85, 86, 87, 88, 89, 90, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 }; private int lastUsedController = 0; /** * Creates a new instance of JSFxSendsPane. * * @throws IllegalArgumentException If model is null. */ public JSFxSendsPane(SamplerChannelModel model) { if(model == null) throw new IllegalArgumentException("model should be non-null!"); channelModel = model; fxSendsTable = new FxSendTable(channelModel); channelRoutingTable = new ChannelRoutingTable(); setLayout(new BorderLayout()); JPanel rightPane = createRightPane(); JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); splitPane.setLeftComponent(createLeftPane()); splitPane.setRightComponent(rightPane); splitPane.setContinuousLayout(true); add(splitPane); channelModel.addEffectSendsListener(getHandler()); fxSendsTable.getSelectionModel().addListSelectionListener(this); if(channelModel.getChannelInfo().getEngine() == null) { actionAddFxSend.setEnabled(false); } if(channelModel.getFxSendCount() == 0) { actionRemoveFxSend.setEnabled(false); } else { fxSendsTable.setSelectedFxSend(channelModel.getFxSend(0)); } updateFxSend(); Dimension d = getMinimumSize(); int w = d.width > 600 ? d.width : 600; int h = d.height > 300 ? d.height : 300; setPreferredSize(new Dimension(w, h)); splitPane.setDividerLocation(200); } protected JSPrefs preferences() { return CC.getViewConfig().preferences(); } public SamplerChannelModel getChannelModel() { return channelModel; } protected JToolBar createToolBar() { JToolBar tb = new JToolBar(); tb.setMaximumSize(new Dimension(Short.MAX_VALUE, tb.getPreferredSize().height)); tb.setFloatable(false); tb.setAlignmentX(JPanel.RIGHT_ALIGNMENT); tb.add(new JButton(actionAddFxSend)); tb.add(new JButton(actionRemoveFxSend)); return tb; } protected JPanel createLeftPane() { JPanel leftPane = new JPanel(); leftPane.setLayout(new BorderLayout()); leftPane.add(createToolBar(), BorderLayout.NORTH); leftPane.add(new JScrollPane(fxSendsTable)); return leftPane; } protected JPanel createRightPane() { for(int i = 0; i < 128; i++) { String s = "[" + i + "] " + getMidiControllerName(i); cbMidiCtrls.addItem(s); } cbMidiCtrls.setMinimumSize(new Dimension(100, cbMidiCtrls.getMinimumSize().height)); cbMidiCtrls.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(fxSend == null) return; int fxs = fxSend.getFxSendId(); int ctrl = cbMidiCtrls.getSelectedIndex(); if(ctrl == fxSend.getMidiController()) { return; } lastUsedController = ctrl; channelModel.setBackendFxSendMidiController(fxs, ctrl); } }); slVolume.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { setVolume(); } }); JPanel rightPane = new JPanel(); rightPane.setLayout(new BorderLayout()); JPanel p = new JPanel(); p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS)); JPanel p2 = new JPanel(); p2.setLayout(new BoxLayout(p2, BoxLayout.X_AXIS)); p2.add(lVolume); p2.add(Box.createRigidArea(new Dimension(6, 0))); slVolume.setMinimumSize(slVolume.getPreferredSize()); p2.add(slVolume); p2.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); p.add(p2); p2 = new JPanel(); p2.setLayout(new BoxLayout(p2, BoxLayout.X_AXIS)); p2.add(lMidiCtrl); p2.add(Box.createRigidArea(new Dimension(6, 0))); p2.add(cbMidiCtrls); p2.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); //p2.setAlignmentX(LEFT_ALIGNMENT); p.add(p2); rightPane.add(p, BorderLayout.NORTH); rightPane.add(new JScrollPane(channelRoutingTable)); return rightPane; } private String getMidiControllerName(int i) { switch(i) { case 0: return "Bank Select"; case 1: return "Modulation Wheel or Lever"; case 2: return "Breath Controller"; case 4: return "Foot Controller"; case 5: return "Portamento Time"; case 6: return "Data Entry MSB"; case 7: return "Channel Volume"; case 8: return "Balance"; case 10: return "Pan"; case 11: return "Expression Controller"; case 12: return "Effect Control 1"; case 13: return "Effect Control 2"; case 16: return "General Purpose Controller 1"; case 17: return "General Purpose Controller 2"; case 18: return "General Purpose Controller 3"; case 19: return "General Purpose Controller 4"; case 32: return "LSB for Control 0 (Bank Select)"; case 33: return "LSB for Control 1 (Modulation Wheel or Lever)"; case 34: return "LSB for Control 2 (Breath Controller)"; case 35: return "LSB for Control 3 (Undefined)"; case 36: return "LSB for Control 4 (Foot Controller)"; case 37: return "LSB for Control 5 (Portamento Time)"; case 38: return "LSB for Control 6 (Data Entry)"; case 39: return "LSB for Control 7 (Channel Volume)"; case 40: return "LSB for Control 8 (Balance)"; case 41: return "LSB for Control 9 (Undefined)"; case 42: return "LSB for Control 10 (Pan)"; case 43: return "LSB for Control 11 (Expression Controller)"; case 44: return "LSB for Control 12 (Effect control 1)"; case 45: return "LSB for Control 13 (Effect control 2)"; case 46: return "LSB for Control 14 (Undefined)"; case 47: return "LSB for Control 15 (Undefined)"; case 48: return "LSB for Control 16 (General Purpose Controller 1)"; case 49: return "LSB for Control 17 (General Purpose Controller 2)"; case 50: return "LSB for Control 18 (General Purpose Controller 3)"; case 51: return "LSB for Control 19 (General Purpose Controller 4)"; case 52: return "LSB for Control 20 (Undefined)"; case 53: return "LSB for Control 21 (Undefined)"; case 54: return "LSB for Control 22 (Undefined)"; case 55: return "LSB for Control 23 (Undefined)"; case 56: return "LSB for Control 24 (Undefined)"; case 57: return "LSB for Control 25 (Undefined)"; case 58: return "LSB for Control 26 (Undefined)"; case 59: return "LSB for Control 27 (Undefined)"; case 60: return "LSB for Control 28 (Undefined)"; case 61: return "LSB for Control 29 (Undefined)"; case 62: return "LSB for Control 30 (Undefined)"; case 63: return "LSB for Control 31 (Undefined)"; case 64: return "Damper Pedal on/off (Sustain)"; case 65: return "Portamento On/Off"; case 66: return "Sostenuto On/Off"; case 67: return "Soft Pedal On/Off"; case 68: return "Legato Footswitch"; case 69: return "Hold 2"; case 70: return "Sound Controller 1 (default: Sound Variation)"; case 71: return "Sound Controller 2 (default: Timbre/Harmonic Intens.)"; case 72: return "Sound Controller 3 (default: Release Time)"; case 73: return "Sound Controller 4 (default: Attack Time)"; case 74: return "Sound Controller 5 (default: Brightness)"; case 75: return "Sound Controller 6 (default: Decay Time"; case 76: return "Sound Controller 7 (default: Vibrato Rate)"; case 77: return "Sound Controller 8 (default: Vibrato Depth)"; case 78: return "Sound Controller 9 (default: Vibrato Delay)"; case 79: return "Sound Controller 10 (default: undefined)"; case 80: return "General Purpose Controller 5"; case 81: return "General Purpose Controller 6"; case 82: return "General Purpose Controller 7"; case 83: return "General Purpose Controller 8"; case 84: return "Portamento Control"; case 91: return "Effects 1 Depth (default: Reverb Send Level)"; case 92: return "Effects 2 Depth (formerly Tremolo Depth)"; case 93: return "Effects 3 Depth(default: Chorus Send Level)"; case 94: return "Effects 4 Depth (formerly Celeste [Detune] Depth)"; case 95: return "Effects 5 Depth (formerly Phaser Depth)"; case 96: return "Data Increment (Data Entry +1)"; case 97: return "Data Decrement (Data Entry -1)"; case 98: return "Non-Registered Parameter Number (NRPN) - LSB"; case 99: return "Non-Registered Parameter Number (NRPN) - MSB"; case 100: return "Registered Parameter Number (RPN) - LSB"; case 101: return "Registered Parameter Number (RPN) - MSB"; case 120: return "All Sound Off (Channel Mode Message)"; case 121: return "Reset All Controllers (Channel Mode Message)"; case 122: return "Local Control On/Off (Channel Mode Message)"; case 123: return "All Notes Off (Channel Mode Message)"; case 124: return "Omni Mode Off (Channel Mode Message)"; case 125: return "Omni Mode On (Channel Mode Message)"; case 126: return "Mono Mode On (Channel Mode Message)"; case 127: return "Poly Mode On (Channel Mode Message)"; default: return "Undefined"; } } public boolean isUndefinedController(int ctrl) { for(int i = 0; i < undefinedControllers.length; i++) { if(ctrl == undefinedControllers[i]) return true; } return false; } private int findUndefinedController(int ctrl, Vector excludes) { for(int i = 0; i < undefinedControllers.length; i++) { int c = undefinedControllers[i]; if(c > ctrl) { if(!excludes.contains(c)) return c; } } for(int i = 0; i < undefinedControllers.length; i++) { int c = undefinedControllers[i]; if(c < ctrl) { if(!excludes.contains(c)) return c; } else break; } return -1; } public int getUndefinedController() { int c = lastUsedController; if(c > 119) c = 0; FxSend[] fxSends = getChannelModel().getFxSends(); Vector v = new Vector(); for(int i = fxSends.length - 1; i >= 0; i--) { int c2 = fxSends[i].getMidiController(); if(isUndefinedController(c2)) v.add(c2); } int ctrl = findUndefinedController(c, v); return ctrl != -1 ? ctrl : 3; } private void setVolume() { if(fxSend == null || slVolume.getValueIsAdjusting()) return; int i = (int)(fxSend.getLevel() * 100); if(slVolume.getValue() == i) return; float vol = slVolume.getValue(); vol /= 100; channelModel.setBackendFxSendLevel(fxSend.getFxSendId(), vol); } @Override public void valueChanged(ListSelectionEvent e) { if(e.getValueIsAdjusting()) return; fxSend = fxSendsTable.getSelectedFxSend(); actionRemoveFxSend.setEnabled(fxSend != null); updateFxSend(); } private void updateFxSend() { boolean b = (fxSend != null); cbMidiCtrls.setEnabled(b); slVolume.setEnabled(b); channelRoutingTable.setEnabled(b); if(!b) { slVolume.setValue(0); cbMidiCtrls.setSelectedIndex(0); return; } cbMidiCtrls.setSelectedIndex(fxSend.getMidiController()); slVolume.setValue((int)(fxSend.getLevel() * 100)); } class AddFxSendAction extends AbstractAction { private int fxSendId = -1; AddFxSendAction() { super(i18n.getLabel("JSFxSendsPane.AddFxSendAction")); String s = i18n.getLabel("JSFxSendsPane.AddFxSendAction.tt"); putValue(SHORT_DESCRIPTION, s); } @Override public void actionPerformed(ActionEvent e) { int id = channelModel.getChannelId(); int c = getUndefinedController(); lastUsedController = c; final Channel.AddFxSend t = new Channel.AddFxSend(id, c, "New effect send"); t.addTaskListener(new TaskListener() { public void taskPerformed(TaskEvent e) { if(t.doneWithErrors()) { fxSendId = -1; return; } setFxSendId(t.getResult()); } }); CC.getTaskQueue().add(t); } public int getFxSendId() { return fxSendId; } public void setFxSendId(int id) { fxSendId = id; } } class RemoveFxSendAction extends AbstractAction { RemoveFxSendAction() { super(i18n.getLabel("JSFxSendsPane.RemoveFxSendAction")); String s = i18n.getLabel("JSFxSendsPane.RemoveFxSendAction.tt"); putValue(SHORT_DESCRIPTION, s); } @Override public void actionPerformed(ActionEvent e) { FxSend fxs = fxSendsTable.getSelectedFxSend(); if(fxs == null) return; channelModel.removeBackendFxSend(fxs.getFxSendId()); } } class ChannelRoutingTable extends JTable { private String[] columnToolTips = { i18n.getLabel("JSFxSendsPane.ttAudioSrc"), i18n.getLabel("JSFxSendsPane.ttAudioDst"), }; ChannelRoutingTable() { super(new ChannelRoutingTableModel()); JComboBox cb = new JComboBox(); int devId = channelModel.getChannelInfo().getAudioOutputDevice(); AudioDeviceModel adm = CC.getSamplerModel().getAudioDeviceById(devId); int chns; if(adm == null) { chns = channelModel.getChannelInfo().getAudioOutputChannels(); } else { chns = adm.getDeviceInfo().getAudioChannelCount(); } for(Integer i = 0; i < chns; i++) cb.addItem(i); TableColumn column = getColumnModel().getColumn(1); column.setCellEditor(new DefaultCellEditor(cb)); } @Override protected JTableHeader createDefaultTableHeader() { return new JTableHeader(columnModel) { public String getToolTipText(java.awt.event.MouseEvent e) { java.awt.Point p = e.getPoint(); int i = columnModel.getColumnIndexAtX(p.x); i = columnModel.getColumn(i).getModelIndex(); return columnToolTips[i]; } }; } } class ChannelRoutingTableModel extends AbstractTableModel implements ListSelectionListener { private String[] columnNames = { i18n.getLabel("JSFxSendsPane.audioSrc"), i18n.getLabel("JSFxSendsPane.audioDst") }; ChannelRoutingTableModel() { channelModel.addEffectSendsListener(new EffectSendsAdapter() { /** Invoked when an effect send's setting are changed. */ public void effectSendChanged(EffectSendsEvent e) { if(fxSend == null) { fireTableDataChanged(); return; } if(fxSend.equals(e.getFxSend())) { int l; l = e.getFxSend().getAudioOutputRouting().length; fireTableRowsUpdated(0, l - 1); } } }); fxSendsTable.getSelectionModel().addListSelectionListener(this); } @Override public int getColumnCount() { return columnNames.length; } @Override public String getColumnName(int column) { return columnNames[column]; } @Override public int getRowCount() { if(fxSend == null) return 0; return fxSend.getAudioOutputRouting().length; } @Override public Object getValueAt(int row, int column) { switch(column) { case 0: return row; case 1: return fxSend.getAudioOutputRouting()[row]; default: return null; } } @Override public boolean isCellEditable(int row, int column) { switch(column) { case 0: return false; case 1: return true; default: return false; } } @Override public void setValueAt(Object value, int row, int column) { if(column == 0) return; int id = fxSend.getFxSendId(); int src = (Integer)getValueAt(row, 0); int dst = (Integer)value; channelModel.setBackendFxSendAudioOutputChannel(id, src, dst); fxSend.getAudioOutputRouting()[row] = dst; fireTableCellUpdated(row, column); } @Override public void valueChanged(ListSelectionEvent e) { if(e.getValueIsAdjusting()) return; fireTableDataChanged(); } } private final Handler eventHandler = new Handler(); private Handler getHandler() { return eventHandler; } private class Handler implements EffectSendsListener { /** Invoked when a new effect send is added to a sampler channel. */ @Override public void effectSendAdded(EffectSendsEvent e) { FxSend fxs = fxSendsTable.getSelectedFxSend(); if(fxs == null) return; AddFxSendAction a = (AddFxSendAction)actionAddFxSend; if(fxs.getFxSendId() != a.getFxSendId()) return; fxSendsTable.requestFocus(); fxSendsTable.editSelectedFxSend(); a.setFxSendId(-1); } /** Invoked when an effect send is removed from a sampler channel. */ @Override public void effectSendRemoved(EffectSendsEvent e) { if(channelModel.getFxSendCount() == 0) return; int id = e.getFxSend().getFxSendId(); for(FxSend fxs : channelModel.getFxSends()) { if(fxs.getFxSendId() > id) { fxSendsTable.setSelectedFxSend(fxs); return; } } FxSend fxs = channelModel.getFxSend(channelModel.getFxSendCount() - 1); fxSendsTable.setSelectedFxSend(fxs); } /** Invoked when an effect send's setting are changed. */ @Override public void effectSendChanged(EffectSendsEvent e) { if(fxSend == null) return; if(fxSend.equals(e.getFxSend())) { fxSend = e.getFxSend(); updateFxSend(); } } } }