public class MooDialog extends JDialog {
+ private Container pane;
+ private Track[] tracks;
+
private JLabel labelA, labelB, labelC, labelD, labelE, labelF, labelG, labelH;
private JTextField textFieldA, textFieldB, textFieldC, textFieldD, textFieldE,
textFieldF, textFieldG, textFieldH, textFieldI, textFieldJ,
TRANSPOSE = 11,
SCALE_VELOCITY = 12,
RECORD = 13;
+ public static final String[] noteSizes = {"Whole", "Half", "Quarter", "Eighth", "Sixteenth"};
/**
* Constructor of the dialogs.
public MooDialog(int type) {
super(Moosique.getGUI(), false);
- Container pane = getContentPane();
+ pane = getContentPane();
pane.setLayout(null);
- Track[] tracks = Moosique.getSequence().getTracks();
+ tracks = Moosique.getSequence().getTracks();
switch (type) {
- case ADD_TRACK: makeAddDialog(pane, tracks); break;
- case DELETE_TRACK: makeDelDialog(pane, tracks); break;
- case COPY_TRACK: makeCopyDialog(pane, tracks); break;
- case MOVE_TRACK: makeMoveDialog(pane, tracks); break;
- case SET_POSITION: makeSetPositionDialog(pane); break;
- case MANUAL: makeTextDialog(pane, "Manual.txt"); break;
- case INSERT_MEASURE: makeInsertMeasureDialog(pane); break;
- case DELETE_MEASURE: makeDeleteMeasureDialog(pane); break;
- case SET_TEMPO: makeSetTempoDialog(pane); break;
- case PREFERENCES: makePrefsDialog(pane); break;
- case TRANSPOSE: makeTransposeDialog(pane, tracks); break;
- case SCALE_VELOCITY: makeScaleVelocityDialog(pane, tracks); break;
+ case ADD_TRACK: makeAddDialog(); break;
+ case DELETE_TRACK: makeDelDialog(); break;
+ case COPY_TRACK: makeCopyDialog(); break;
+ case MOVE_TRACK: makeMoveDialog(); break;
+ case SET_POSITION: makeSetPositionDialog(); break;
+ case MANUAL: makeTextDialog("User Manual", "Manual.txt", 30, 95); break;
+ case INSERT_MEASURE: makeInsertMeasureDialog(); break;
+ case DELETE_MEASURE: makeDeleteMeasureDialog(); break;
+ case SET_TEMPO: makeSetTempoDialog(); break;
+ case PREFERENCES: makePrefsDialog(); break;
+ case TRANSPOSE: makeTransposeDialog(); break;
+ case SCALE_VELOCITY: makeScaleVelocityDialog(); break;
+ case RECORD: makeRecordDialog(); break;
}
}
* @param pane The container to put the dialog in.
* @param tracks A array containing miditracks.
*/
- private void makeAddDialog(Container pane, Track[] tracks) {
+ private void makeAddDialog() {
setTitle("Add track");
// create the contents of the dialog and add to container
labelA = new JLabel("Name of track", JLabel.CENTER);
pane.add(labelB);
// list of where to add track
trackListA = new JComboBox();
- for (int i = 1; i <= tracks.length; i++) trackListA.addItem("Track " + i);
+ for (int i = 1; i < tracks.length; i++) trackListA.addItem("Track " + i);
pane.add(trackListA);
// ok and cancel button
cancelButton = new JButton("Cancel");
* @param pane The container to put the dialog in.
* @param tracks A array containing miditracks.
*/
- private void makeDelDialog(Container pane, Track[] tracks) {
+ private void makeDelDialog() {
setTitle("Delete track");
// create the contents of the dialog and add to container
labelB = new JLabel("Delete track", JLabel.CENTER);
* @param pane The container to put the dialog in.
* @param tracks A array containing miditracks.
*/
- private void makeCopyDialog(Container pane, Track[] tracks) {
+ private void makeCopyDialog() {
setTitle("Copy Track");
// create the contents of the dialog and add to container
labelA = new JLabel("Track to copy", JLabel.CENTER);
* @param pane The container to put the dialog in.
* @param tracks A array containing miditracks.
*/
- private void makeMoveDialog(Container pane, Track[] tracks) {
+ private void makeMoveDialog() {
setTitle("Move track");
// create the contents of the dialog and add to container
labelA = new JLabel("Track to move", JLabel.CENTER);
* @param pane The container to put the dialog in.
* @param tracks A array containing miditracks.
*/
- private void makeSetPositionDialog(Container pane) {
+ private void makeSetPositionDialog() {
setTitle("Set edit position");
// create the contents of the dialog and add to container
* Builds the insert measure popupdialog.
* @param pane The container to put the dialog in.
*/
- private void makeInsertMeasureDialog(Container pane){
+ private void makeInsertMeasureDialog(){
setTitle("Insert Measure");
// create the contents of the dialog and add to container
labelA = new JLabel("Insert at:", JLabel.RIGHT);
* Builds the delete measure popupdialog.
* @param pane The container to put the dialog in.
*/
- private void makeDeleteMeasureDialog(Container pane) {
+ private void makeDeleteMeasureDialog() {
setTitle("Delete Measure");
// create the contents of the dialog and add to container
labelA = new JLabel("Delete at:", JLabel.RIGHT);
* Builds the set tempo dialog.
* @param pane The container to put the dialog in.
*/
- private void makeSetTempoDialog(Container pane) {
+ private void makeSetTempoDialog() {
setTitle("Set tempo");
// create contents of dialog and add to container
// track edit-intervall labels
* @param pane The container to put the dialog in.
* @param tracks A array containing miditracks.
*/
- private void makeTransposeDialog(Container pane, Track[] tracks) {
+ private void makeTransposeDialog() {
setTitle("Transpose");
// create contents of dialog and add to container
// track edit-intervall labels
* @param pane The container to put the dialog in.
* @param tracks A array containing miditracks.
*/
- private void makeScaleVelocityDialog(Container pane, Track[] tracks) {
+ private void makeScaleVelocityDialog() {
setTitle("Scale velocity");
// create contents of dialog and add to container
// track edit-intervall labels
}
/** creates the "User manual dialog" that displays a textfile
*/
- private void makeTextDialog(Container pane, String filename) {
- setTitle("User Manual");
+ private void makeTextDialog(String title, String filename, int rows, int columns) {
+ setTitle(title);
pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
File manual = new File(filename);
String s;
br.read(chars, 0, (int)manual.length());
s = new String(chars);
} catch (Exception ex) {
- s = "Manual not found";
+ s = "File not found";
}
- JTextArea text = new JTextArea(s, 30, 95);
+ JTextArea text = new JTextArea(s, rows, columns);
text.setLineWrap(true);
text.setWrapStyleWord(true);
text.setEnabled(false);
setVisible(true);
}
- private void makePrefsDialog(Container pane) {
+ private void makePrefsDialog() {
/*
MidiDevice.Info[] devInfo = MidiSystem.getMidiDeviceInfo();
for (int i = 0; i < devInfo.length; i++) {
*/
}
- private void makeRecordDialog(Container pane) {
- /* Show a dialog with:
- "Track" combo box,
- "Channel" field? (disabled?,
- "Quantize" pane with
- "Quantize" checkbox,
- "Resolution" combo box,
- "Location" checkboxes and
- "Duration" checkboxes
- "Start Recording" button.
-
- Moosique.record(track);
- */
+ private void makeRecordDialog() {
+ setTitle("Record");
+
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+
+ // Creating track combo box,
+ panel.add(new Label("Track: "));
+ trackListA = new JComboBox();
+ for (int i = 1; i < tracks.length; i++) trackListA.addItem("Track " + i);
+ trackListA.setFont(Moosique.getGUI().FONT);
+ panel.add(trackListA);
+
+ // Creating channel combo box
+ panel.add(new Label("Channel: "));
+ JComboBox channelList = new JComboBox();
+ for (int i = 1; i <= 16; i++) channelList.addItem("Channel " + i);
+ channelList.setFont(Moosique.getGUI().FONT);
+ panel.add(channelList);
+
+ // Creating quantize pane
+ JLayeredPane quantizePane = new JLayeredPane();
+ quantizePane.setBorder(BorderFactory.createTitledBorder("Quantize"));
+ JCheckBox quantizeBox = new JCheckBox("Quantize");
+ quantizePane.add(quantizeBox);
+ JLabel quantizeSizeLabel = new JLabel("Resolution");
+ JComboBox quantizeSize = new JComboBox(noteSizes);
+ quantizeSize.setSelectedIndex(4);
+ quantizeSize.setEnabled(false);
+ quantizeSize.setFont(Moosique.getGUI().FONT);
+ quantizeSizeLabel.setLabelFor(quantizeSize);
+ quantizePane.add(quantizeSizeLabel);
+ quantizePane.add(quantizeSize);
+ JCheckBox quantizeLocationBox = new JCheckBox("Affect location", true);
+ quantizeLocationBox.setEnabled(false);
+ quantizePane.add(quantizeLocationBox);
+ JCheckBox quantizeDurationBox = new JCheckBox("Affect duration", true);
+ quantizeDurationBox.setEnabled(false);
+ quantizePane.add(quantizeDurationBox);
+ panel.add(quantizePane);
+
+ // Creating buttons
+ final String btnString1 = "Record!";
+ final String btnString2 = "Cancel";
+ Object[] options = {btnString1, btnString2};
+
+ // Creating option pane
+ optionPane = new JOptionPane(panel,
+ JOptionPane.QUESTION_MESSAGE,
+ JOptionPane.YES_NO_OPTION,
+ null,
+ options,
+ options[0]);
+ optionPane.addPropertyChangeListener(new PropertyChangeListener() {
+ public void propertyChange(PropertyChangeEvent e) {
+ String prop = e.getPropertyName();
+
+ if (isVisible() && (e.getSource() == optionPane) &&
+ (prop.equals(JOptionPane.VALUE_PROPERTY) || prop.equals(JOptionPane.INPUT_VALUE_PROPERTY))) {
+ Object value = optionPane.getValue();
+ if (value == JOptionPane.UNINITIALIZED_VALUE) return;
+ optionPane.setValue(JOptionPane.UNINITIALIZED_VALUE);
+
+ if (value.equals(btnString1)) {
+ // boolean[] quantizers = {quantize, location, duration};
+ Track track = tracks[trackListA.getSelectedIndex() + 1];
+ int channel = Moosique.getGUI().getView().getTrackView(track).getTitle().getChannel();
+ boolean[] quantizers = {false, false, false};
+ int resolution = Moosique.SIXTEENTH_NOTE;
+ Moosique.record(track, channel, quantizers, resolution);
+ }
+ setVisible(false);
+ }
+ }
+ });
+ setContentPane(optionPane);
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+ setResizable(false);
+ pack();
+ setLocationRelativeTo(Moosique.getGUI());
+ setVisible(true);
}
private MooNote note;
*/
public MooDialog(MooNote mn) {
super(Moosique.getGUI(), "Note properties", true);
+
+ note = mn;
+
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(3,2));
- note = mn;
+
pitch = new JTextField(new Integer(note.getPitch()).toString(),3);
panel.add(new Label("Pitch: "));
panel.add(pitch);
}
});
pack();
+ setLocationRelativeTo(Moosique.getGUI());
setVisible(true);
}
}
private JMenu popupAdd, selPopupTranspUp, selPopupTranspDown;
private JMenuItem popupAddItemsCustom, popupAddItemsLast, popupPaste;
private JMenuItem popupAddItemsWhole, popupAddItemsHalf, popupAddItemsQuarter, popupAddItemsEighth, popupAddItemsSixteenth;
- private JMenuItem selPopupCopy, selPopupCut, selPopupRemove;
+ private JMenuItem selPopupProps, selPopupCopy, selPopupCut, selPopupRemove;
private JMenuItem[] selPopupTranspUpItems, selPopupTranspDownItems;
private ArrayList coords;
// Creates selection pop-up menu.
selPopup = new JPopupMenu();
+ selPopupProps = addMenuItem(selPopup, "Properties...");
selPopupCopy = addMenuItem(selPopup, "Copy selection");
selPopupCut = addMenuItem(selPopup, "Cut selection");
selPopupRemove = addMenuItem(selPopup, "Remove selection");
* @param y the y-coordinate in which to display the menu
*/
public void showSelectionPopup(Component c, int x, int y) {
+ // Determines whether the "Properties" item should be available.
+ selPopupProps.setEnabled(Moosique.getSelection().size() == 1);
selPopup.show(c, x, y);
}
} else if (source == popupPaste) {
pasteCopiedNotes();
// Handling selection popup actions.
+ } else if (source == selPopupProps) {
+ new MooDialog(((MooNoteElement)Moosique.getSelection().first()).getNote());
} else if (source == selPopupCopy) {
copySelectedNotes();
} else if (source == selPopupCut) {
/**
* Creates an musical ruler depending on the timesignature
*/
-
public MooViewCounter (MetaMessage[] timeSigs) {
- int timeSig1 = 4, timeSig2 = 4; // ...for now
+ // ...for now
+ int timeSig1 = 4, timeSig2 = 4;
+ calculateLineSpecs(timeSig1, timeSig2);
setBackground(Moosique.getGUI().bgColor);
setPreferredSize(new Dimension(35, 200 * CELL_HEIGHT));
+ }
- switch (timeSig2) {
- case 16: measure = timeSig1; // 1/16
- break;
- case 8: measure = timeSig1 * 2; // 1/16
- halfBeat = measure / timeSig1; // 1/8
- break;
- case 4: measure = timeSig1 * 4; // 1/16
- halfBeat = beat / 2; // 1/8
- beat = measure / timeSig1; // 1/4
- break;
- case 2: measure = timeSig1 * 8; // 1/16
+ private void calculateLineSpecs(int ts1, int ts2) {
+ switch (ts2) {
+ case 16: measure = ts1; // 1/16
+ break;
+ case 8: measure = ts1 * 2; // 1/16
+ halfBeat = measure / ts1; // 1/8
+ break;
+ case 2: measure = ts1 * 8; // 1/16
halfBeat = beat / 2; // 1/8
beat = halfNote / 2; // 1/4
- halfNote = measure / timeSig1; // 1/2
- break;
- case 1: measure = timeSig1 * 16; // 1/16
+ halfNote = measure / ts1; // 1/2
+ break;
+ case 1: measure = ts1 * 16; // 1/16
halfBeat = beat / 2; // 1/8
beat = halfNote / 2; // 1/4
halfNote = measure / 2; // 1/2
- break;
+ break;
+ default: measure = ts1 * 4; // 1/16
+ halfBeat = beat / 2; // 1/8
+ beat = measure / ts1; // 1/4
+ break;
}
-
}
/**
if (!(g instanceof Graphics2D)) return;
Graphics2D g2 = (Graphics2D)g;
g2.setColor(Color.black);
+
+
+ /* Using time signature...
+ for (int i = 0, tick = getTicksForPosition(i, 0, 0); tick < Moosique.getSequence().getTickLength(); i++) {
+ int[] ts = getTimeSig(tick);
+ calculateLineSpecs(ts[0], ts[1]);
+ g2.drawLine(0, c * CELL_HEIGHT, 5, c * CELL_HEIGHT); // 1/16
+ g2.drawLine(0, c * CELL_HEIGHT * halfBeat, 10, c * CELL_HEIGHT * halfBeat); // 1/8
+ g2.drawLine(0, c * CELL_HEIGHT * beat, 15, c * CELL_HEIGHT * beat); // 1/4
+ g2.drawLine(0, c * CELL_HEIGHT * halfNote, 20, c * CELL_HEIGHT * halfNote); // 1/2
+ g2.drawLine(0, c * CELL_HEIGHT * measure, 30, c * CELL_HEIGHT * measure); // 1/1
+ }
+ */
+
for (int c = 0; c < 200; c++) {
g2.drawLine(0, c * CELL_HEIGHT, 5, c * CELL_HEIGHT); // 1/16
g2.drawLine(0, c * CELL_HEIGHT * halfBeat, 10, c * CELL_HEIGHT * halfBeat); // 1/8
public class Moosique {
+ // GUI and MIDI device variables
private static MooGUI gui;
private static Sequence seq;
private static Sequencer sequencer;
private static Receiver receiver;
private static MidiChannel[] channels;
private static MidiChannel activeChannel;
+
+ // Recording variables
private static Track recordTrack = null;
private static MooTrackView recordTrackView = null;
+ private static boolean[] quantizers = {false, false, false};
+ private static int quantizeResolution;
+ // Collections
private static ArrayList copyBuffer, emptyTracks, timeSignatures, tempoChanges;
private static TreeSet selection;
private static Map trackMute = new HashMap();
private static Map trackSolo = new HashMap();
- private static Thread player;
+ // Various...
private static File file = null;
private static long editPosition;
+ private static Thread player;
+
+ // Preferences
private static boolean makeGUI = true, initSound = true, edited = false, drawEmptyTracks = false;
+
+ // Constants
public static final int DEFAULT_RESOLUTION = 96, DEFAULT_TRACKS = 4;
public static final int WHOLE_NOTE = 16, HALF_NOTE = 8, QUARTER_NOTE = 4, EIGHTH_NOTE = 2, SIXTEENTH_NOTE = 1;
editPosition = ticks;
}
- /**
- * Sets the current editing position of the sequencer.
- * @param ticks the tick position
- */
- public static void setTempo(int bpm) {
- // tempoMsg
- }
-
- /**
- * Sets the current editing position of the sequencer.
- * @param ticks the tick position
- */
- public static void setTimeSig(int bpm) {
- // timeSigMsg
- }
-
/**
* Sets the solo setting of the given track.
* @param on true for solo, false for not
recordTrackView.disableKeyboardRecording();
sequencer.stopRecording();
sequencer.recordDisable(recordTrack);
-// if (quantize) recordTrackView.placeNewNotes(quantize(
-// convertTrack(recordTrack), SIXTEENTH_NOTE, true, true));
-// else recordTrackView.placeNewNotes(Moosique.convertTrack(recordTrack));
+ if (quantizers[0]) recordTrackView.placeNewNotes(quantize(
+ convertTrack(recordTrack), quantizeResolution, quantizers[1], quantizers[2]));
+ else recordTrackView.placeNewNotes(Moosique.convertTrack(recordTrack));
}
}
/**
* Enables recording to the given track.
+ * @param track the track in which to store the recorded data
+ * @param tempo the channel from which to record data
+ * @param quantizers an array of booleans where 0 = quantize?, 1 = location, 2 = duration
+ * @param resolution the note size to round each note to
+ * @return if the recording was initialised successfully
*/
- public static boolean record(Track track) {
-/* try {
+ public static boolean record(Track track, int channel, boolean[] quants, int resolution) {
+ try {
sequencer.recordEnable(track, channel);
sequencer.startRecording();
} catch(Exception e) {
+ e.printStackTrace();
return false;
}
+ quantizers = quants;
+ quantizeResolution = resolution;
+ recordTrack = track;
recordTrackView = gui.getView().getTrackView(track);
recordTrackView.enableKeyboardRecording();
Moosique.setEdited();
-*/
return true;
}
return decodeTempo(((MetaMessage)tempoEvent.getMessage()).getData());
}
+ /**
+ * Sets the tempo at the given tick position.
+ * @param ticks the tick position
+ */
+ public static void setTempo(long tick, int bpm) {
+ // Checks for a tempo event at the given tick position.
+ MidiEvent tempoEvent = null;
+ Iterator it = tempoChanges.iterator();
+ while(it.hasNext()) {
+ MidiEvent nextTempoEvent = (MidiEvent)it.next();
+ if (nextTempoEvent.getTick() == tick) {
+ tempoEvent = nextTempoEvent;
+ break;
+ }
+ }
+
+ // If none was found, creates and adds a new one.
+ if (tempoEvent == null) {
+ tempoEvent = new MidiEvent(new MetaMessage(), tick);
+ (seq.getTracks())[0].add(tempoEvent);
+ tempoChanges.add(tempoEvent);
+ Collections.sort(tempoChanges, new MidiEventComparator());
+ }
+
+ // Sets the tempo of the event (found or created).
+ try {
+ ((MetaMessage)tempoEvent.getMessage()).setMessage(81, encodeTempo(bpm), 3);
+ } catch (InvalidMidiDataException e) {}
+ }
+
/**
* Returns the byte array for the given time signature.
* @param numerator the numerator of the time signature
if (timeSignatures.size() > 1) {
for (int i = 1; i < timeSignatures.size(); i++) {
MidiEvent nextTimeSigEvent = (MidiEvent)timeSignatures.get(i);
- if (nextTimeSigEvent.getTick() < tick && nextTimeSigEvent.getTick() > timeSigEvent.getTick())
+ if (nextTimeSigEvent.getTick() <= tick && nextTimeSigEvent.getTick() > timeSigEvent.getTick())
timeSigEvent = nextTimeSigEvent;
}
}
return decodeTimeSig(((MetaMessage)timeSigEvent.getMessage()).getData());
}
+ /**
+ * Sets the time signature at the given tick position.
+ * @param ticks the tick position
+ */
+ public static void setTimeSig(long tick, int numerator, int denominator) {
+ // Checks for a time signature event at the given tick position.
+ MidiEvent timeSigEvent = null;
+ Iterator it = timeSignatures.iterator();
+ while(it.hasNext()) {
+ MidiEvent nextTimeSigEvent = (MidiEvent)it.next();
+ if (nextTimeSigEvent.getTick() == tick) {
+ timeSigEvent = nextTimeSigEvent;
+ break;
+ }
+ }
+
+ // If none was found, creates and adds a new one.
+ if (timeSigEvent == null) {
+ timeSigEvent = new MidiEvent(new MetaMessage(), tick);
+ (seq.getTracks())[0].add(timeSigEvent);
+ timeSignatures.add(timeSigEvent);
+ Collections.sort(timeSignatures, new MidiEventComparator());
+ }
+
+ // Sets the time signature of the event (found or created).
+ try {
+ ((MetaMessage)timeSigEvent.getMessage()).setMessage(88, encodeTimeSig(numerator, denominator), 4);
+ } catch (InvalidMidiDataException e) {}
+ }
+
/**
* Calculates the position (measures, beats, ticks) in the current sequence for the given tick position.
* @param tickPosition the tick position for which to calculate the position
* Replaces the current sequence with a new one, holding three empty tracks.
*/
public static void clearSequence() {
+ // Reinitializes sequence variables
+ file = null;
+ reinitializeLists();
+
try {
// Creates a new sequence.
seq = new Sequence(Sequence.PPQ, DEFAULT_RESOLUTION, DEFAULT_TRACKS);
Track[] tracks = seq.getTracks();
- // Creates messages for default tempo (120) and time signature (4/4), and adds them to track 0.
- MetaMessage timeSigMsg = new MetaMessage();
- MetaMessage tempoMsg = new MetaMessage();
- try {
- timeSigMsg.setMessage(88, encodeTimeSig(4, 4), 4);
- tempoMsg.setMessage(81, encodeTempo(120), 3);
- } catch (InvalidMidiDataException e) {}
- tracks[0].add(new MidiEvent(timeSigMsg, (long)0));
- tracks[0].add(new MidiEvent(tempoMsg, (long)0));
+ // Sets default tempo (120) and time signature (4/4) at the beginning of the sequence.
+ setTempo(0, 120);
+ setTimeSig(0, 4, 4);
// Sets program and title for the tracks.
initializeTrack(tracks[1], 0, 24, "Guitar");
initializeTrack(tracks[3], 9, 0, "Drums");
} catch (InvalidMidiDataException e) {}
- // Reinitializes sequence variables
- file = null;
- reinitializeLists();
-
// Sends the sequence to the GUI.
if (gui != null) gui.setSequence(seq, null);
}
\f
AKTIVITET
Björn Menyn, dialogerna
-Einar Inspelning
+Einar Inspelning, MooViewCounter
Mike
Rolle Spara konfiguration
Arbetskatalog
x Varför hänger sig Play när man spelar in eller ändrar längd på en not?
x Kopiera/flytta spår, möjligt?
-\f
-ANNAT
-x Fixa InstrumentList.
-x Textfält som gör att man bara kan skriva in siffror? (MooNoteProp)
-
\f
PREFERENCES
\f
SWING
MooDialog
- Fixa en Record-dialog.
-
- MooView
- Lägg till en tom panel för att fylla ut skärmen. Med BoxLayout?
+ Textfält som gör att man bara kan skriva in siffror? (NoteProp)
MooMenu
Mnemonicsarna är konstiga.
(Omöjligt att följa strecken till högra änden av skärmen.)
Highlighta noter som spelas? (Enligt kravspec.)
+ MooView
+ Lägg till en tom panel för att fylla ut skärmen. Med BoxLayout?
+
\f
jar cmf manif Moosique.jar *.class *.java midi\*.mid images\*.gif Manual.txt
manif: Main-Class: Moosique