package org.eclipse.ui.views.midi;
import java.io.IOException;
import java.text.MessageFormat;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.Track;
import javax.util.midi.MidiUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CheckboxCellEditor;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
public class MidiViewPage extends ScrolledComposite {
public static final String ICON_PATH = "icons/"; //$NON-NLS-1$
private final Composite content;
private final Sequencer sequencer;
private final Synthesizer synthesizer;
public MidiViewPage(Composite parent, IFile file) throws MidiUnavailableException, InvalidMidiDataException, IOException {
super(parent, SWT.H_SCROLL | SWT.V_SCROLL);
setExpandHorizontal(true);
setExpandVertical(true);
content = new Composite(this, SWT.NONE);
content.setLayout(new GridLayout(1, true));
setContent(content);
sequencer = MidiSystem.getSequencer();
sequencer.open();
synthesizer = MidiSystem.getSynthesizer();
synthesizer.open();
sequencer.getTransmitter().setReceiver(synthesizer.getReceiver());
addTime(content);
addVolume(content);
addTempo(content);
addTracks(content);
for (Control child : content.getChildren()) {
GridData layoutData = new GridData();
layoutData.horizontalAlignment = SWT.FILL;
layoutData.grabExcessHorizontalSpace = true;
child.setLayoutData(layoutData);
}
setFile(file);
}
// File handling
/**
* The open MIDI file.
*/
private IFile file;
public IFile getFile() {
return file;
}
public void setFile(IFile file) throws InvalidMidiDataException, IOException {
this.file = file;
sequencer.setSequence(MidiSystem.getSequence(file.getRawLocation().toFile()));
time.setMaximumValue((int)sequencer.getMicrosecondLength());
tracks.setInput(sequencer.getSequence());
layoutColumns();
setMinSize(content.computeSize(SWT.DEFAULT, SWT.DEFAULT));
}
public void reload() throws InvalidMidiDataException, IOException {
setFile(getFile());
}
public void closeFile() {
pause();
synthesizer.close();
sequencer.close();
content.dispose();
this.dispose();
}
// Playback
private MidiViewToolbarManager.PlaybackAction playbackAction;
public MidiViewToolbarManager.PlaybackAction getPlaybackAction() {
return playbackAction;
}
public void setPlaybackAction(MidiViewToolbarManager.PlaybackAction playbackAction) {
this.playbackAction = playbackAction;
}
public void play() {
if (isFinished()) {
sequencer.setMicrosecondPosition(0);
}
sequencer.start();
getPlaybackAction().setPlaying(true);
Display.getDefault().timerExec(0, new Updater());
}
public void pause() {
if(sequencer.isOpen()){
sequencer.stop();
getPlaybackAction().setPlaying(false);
}
}
public boolean isPlaying() {
return sequencer.isRunning();
}
public void togglePlayback() {
if (isPlaying()) {
pause();
} else {
play();
}
}
// Time
private NumericValueEditor time;
private void addTime(Composite parent) {
time = new NumericValueEditor(parent, "Time", Activator.getImageDescriptor(ICON_PATH + "Time.png"), Activator.getImageDescriptor(ICON_PATH + "Rewind.png"), 0, 0, new ValueHooks() { //$NON-NLS-2$ //$NON-NLS-3$
@Override
public String display(int value) {
return getDuration(value);
}
@Override
public void valueSet(int value) {
sequencer.setMicrosecondPosition(value);
}
private String getDuration(long microseconds) {
long seconds = microseconds / 1000000;
final int secondsInMinute = 60;
return MessageFormat.format("{0}:{1,number,00}", seconds / secondsInMinute, seconds % secondsInMinute);
}
});
}
private boolean isFinished() {
return sequencer.getMicrosecondPosition() >= sequencer.getMicrosecondLength();
}
private class Updater implements Runnable {
@Override
public void run() {
time.setValue((int)sequencer.getMicrosecondPosition(), false);
if (isFinished()) {
pause();
} else {
final int millisecondsPerSecond = 1000;
final int framesPerSecond = 25;
Display.getDefault().timerExec(millisecondsPerSecond / framesPerSecond, this);
}
}
}
// Volume
private static final int MAX_VOLUME = 100;
private void addVolume(Composite parent) {
new NumericValueEditor(parent, "Volume", Activator.getImageDescriptor(ICON_PATH + "Volume.png"), Activator.getImageDescriptor(ICON_PATH + "Reset.png"), MAX_VOLUME, MAX_VOLUME / 2, new ValueHooks() { //$NON-NLS-2$ //$NON-NLS-3$
@Override
public String display(int value) {
return MessageFormat.format("{0}%", value);
}
@Override
public void valueSet(int value) {
int volume = (value * 127) / 100;
MidiUtils.setVolume(synthesizer, volume);
}
});
}
// Tempo
private static final int MAX_TEMPO_FACTOR = 200;
private void addTempo(Composite parent) {
new NumericValueEditor(parent, "Tempo", Activator.getImageDescriptor(ICON_PATH + "Tempo.png"), Activator.getImageDescriptor(ICON_PATH + "Reset.png"), MAX_TEMPO_FACTOR, MAX_TEMPO_FACTOR / 2, new ValueHooks() { //$NON-NLS-2$ //$NON-NLS-3$
@Override
public String display(int value) {
return MessageFormat.format("{0,number,0.00}x", computeFactor(value));
}
@Override
public void valueSet(int value) {
sequencer.setTempoFactor(computeFactor(value));
}
private float computeFactor(int value) {
return value / (float)100;
}
});
}
// Tracks
private TableViewer tracks;
private void addTracks(Composite parent) {
tracks = new TableViewer(parent, SWT.BORDER | SWT.NO_SCROLL|SWT.FULL_SELECTION);
for (TrackColumn trackColumn : TrackColumn.values()) {
TableViewerColumn tableViewerColumn = new TableViewerColumn(tracks, SWT.NONE);
tableViewerColumn.setEditingSupport(new TrackEditingSupport(tracks, trackColumn));
TableColumn tableColumn = tableViewerColumn.getColumn();
tableColumn.setText(trackColumn.name);
tableColumn.setImage(Activator.getImageDescriptor(ICON_PATH + trackColumn.iconFilename).createImage());
tableColumn.setAlignment(SWT.CENTER);
}
Table table = tracks.getTable();
table.setHeaderVisible(true);
table.setLinesVisible(true);
tracks.setContentProvider(new TrackContentProvider());
tracks.setLabelProvider(new TrackLabelProvider());
addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
layoutColumns();
}
});
}
private void layoutColumns() {
layout();
Table table = tracks.getTable();
int widthOfOtherColumns = 0;
for (int i = 1; i < table.getColumnCount(); i++) {
TableColumn column = table.getColumn(i);
column.pack();
widthOfOtherColumns += column.getWidth();
}
table.getColumn(0).setWidth(table.getSize().x - widthOfOtherColumns - table.getBorderWidth() - 2);
}
private enum TrackColumn {
NAME("Track name", "Name.png") { //$NON-NLS-2$
@Override
public String getColumnText(Sequencer sequencer, Track track) {
String result = MidiUtils.getTrackName(track);
return ((result == null) || result.isEmpty() ? "(untitled)" : result);
}
@Override
public CellEditor getCellEditor(Composite parent) {
return null;
}
@Override
public Object getValue(Sequencer sequencer, int trackNumber) {
return null;
}
@Override
public void setValue(Sequencer sequencer, int trackNumber, Object value) {
}
},
MUTE("Mute", "Mute.png") { //$NON-NLS-2$
@Override
public String getColumnText(Sequencer sequencer, Track track) {
return getCheckLabel(sequencer, track);
}
@Override
public CellEditor getCellEditor(Composite parent) {
return new CheckboxCellEditor(parent);
}
@Override
public Object getValue(Sequencer sequencer, int trackNumber) {
return sequencer.getTrackMute(trackNumber);
}
@Override
public void setValue(Sequencer sequencer, int trackNumber, Object value) {
sequencer.setTrackMute(trackNumber, (Boolean)value);
}
},
SOLO("Solo", "Solo.png") { //$NON-NLS-2$
@Override
public String getColumnText(Sequencer sequencer, Track track) {
return getCheckLabel(sequencer, track);
}
@Override
public CellEditor getCellEditor(Composite parent) {
return new CheckboxCellEditor(parent);
}
@Override
public Object getValue(Sequencer sequencer, int trackNumber) {
return sequencer.getTrackSolo(trackNumber);
}
@Override
public void setValue(Sequencer sequencer, int trackNumber, Object value) {
sequencer.setTrackSolo(trackNumber, (Boolean)value);
}
};
private TrackColumn(String name, String iconFilename) {
this.name = name;
this.iconFilename = iconFilename;
}
private final String name;
private final String iconFilename;
public abstract String getColumnText(Sequencer sequencer, Track track);
public abstract CellEditor getCellEditor(Composite parent);
public abstract Object getValue(Sequencer sequencer, int trackNumber);
public abstract void setValue(Sequencer sequencer, int trackNumber, Object value);
protected String getCheckLabel(Sequencer sequencer, Track track) { // XXX workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=285121
return (Boolean)getValue(sequencer, MidiUtils.getTrackNumber(sequencer, track)) ? "✓" : "";
}
}
private class TrackContentProvider implements IStructuredContentProvider {
@Override
public Object[] getElements(Object inputElement) {
return ((Sequence)inputElement).getTracks();
}
@Override
public void dispose() {
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
}
public class TrackLabelProvider extends LabelProvider implements ITableLabelProvider {
@Override
public String getColumnText(Object element, int columnIndex) {
return TrackColumn.values()[columnIndex].getColumnText(sequencer, (Track)element);
}
@Override
public Image getColumnImage(Object element, int columnIndex) {
return null;
}
}
public class TrackEditingSupport extends EditingSupport {
private final TrackColumn column;
private final CellEditor cellEditor;
public TrackEditingSupport(TableViewer viewer, TrackColumn column) {
super(viewer);
this.column = column;
cellEditor = column.getCellEditor(viewer.getTable());
}
@Override
protected CellEditor getCellEditor(Object element) {
return cellEditor;
}
@Override
protected boolean canEdit(Object element) {
return cellEditor != null;
}
@Override
protected Object getValue(Object element) {
int trackNumber = MidiUtils.getTrackNumber(sequencer, (Track)element);
return column.getValue(sequencer, trackNumber);
}
@Override
protected void setValue(Object element, Object value) {
int trackNumber = MidiUtils.getTrackNumber(sequencer, (Track)element);
column.setValue(sequencer, trackNumber, value);
getViewer().update(element, null);
}
}
}