From d1996d82793d7049115f5280ba8651b818e52e3c Mon Sep 17 00:00:00 2001 From: Einar Pehrson Date: Mon, 26 May 2003 13:21:26 +0000 Subject: [PATCH] no message --- MooGUI.java | 12 +- MooInstrumentList.java | 4 +- MooMenu.java | 118 +++++++++++--------- MooNote.java | 2 +- MooNoteElement.java | 88 +-------------- MooToolbar.java | 17 ++- MooTrackTitle.java | 8 +- MooTrackView.java | 107 ++++++++---------- Moosique.java | 245 ++++++++++++++++++++++++++++++----------- To Do.txt | 17 +-- 10 files changed, 331 insertions(+), 287 deletions(-) diff --git a/MooGUI.java b/MooGUI.java index 3ecb308..57759c9 100644 --- a/MooGUI.java +++ b/MooGUI.java @@ -19,6 +19,7 @@ public class MooGUI extends JFrame { private MooView view; private JLabel statusBar; private java.util.Timer timer; + private boolean updateView = true; public static final int statusResetDelay = 3000; public static final Font FONT = new Font("Helvetica", Font.PLAIN, 10); public static final Color bgColor = new Color(192, 224, 255); @@ -119,8 +120,10 @@ public class MooGUI extends JFrame { * Changes the sequence of the GUI. * @param sequence the MIDI sequence to visualize */ - public void setSequence(Sequence sequence) { + public void setSequence(Sequence sequence, File file) { seq = sequence; + if (file != null) setTitle("Moosique - " + file.getName()); + else setTitle("Moosique"); view.setTracks(seq.getTracks(), true); toolbar.resetProgInd(); } @@ -138,8 +141,8 @@ public class MooGUI extends JFrame { * Calls on the main view to update the track views, * and on the toolbar to update the progress indicator. */ - public void update(long tickPosition){ - view.update(tickPosition); + public synchronized void update(long tickPosition){ + if (updateView) view.update(tickPosition); toolbar.updateProgInd(tickPosition); } @@ -155,6 +158,9 @@ public class MooGUI extends JFrame { return octaveAction; } + /** + * Advances the current progress counter by printing a "." to the System output. + */ private void advanceStatus() { System.out.print("."); } diff --git a/MooInstrumentList.java b/MooInstrumentList.java index 34ff4da..91f8eea 100644 --- a/MooInstrumentList.java +++ b/MooInstrumentList.java @@ -24,7 +24,7 @@ public class MooInstrumentList extends JComboBox implements ActionListener { public MooInstrumentList(int chan, int listType, ShortMessage programMsg) { super(instruments[listType]); programChangeMessage = programMsg; - setSelectedIndex(programChangeMessage.getData1()); + if (programChangeMessage != null) setSelectedIndex(programChangeMessage.getData1()); setChannel(chan); setFont(Moosique.getGUI().FONT); addActionListener(this); @@ -36,7 +36,7 @@ public class MooInstrumentList extends JComboBox implements ActionListener { public void setChannel(int chan) { channel = chan; try {programChangeMessage.setMessage(programChangeMessage.getCommand(), chan, programChangeMessage.getData1(), 0);} - catch (InvalidMidiDataException e) {} + catch (Exception e) {} } /** diff --git a/MooMenu.java b/MooMenu.java index 25cab5c..a7fe834 100644 --- a/MooMenu.java +++ b/MooMenu.java @@ -1,9 +1,10 @@ import javax.sound.midi.*; import javax.swing.*; import javax.swing.filechooser.*; +import java.awt.*; import java.awt.event.*; import java.io.*; -import java.awt.*; +import java.util.*; /** * Moosiques GUI representing a menubar, menus and menuitems @@ -12,36 +13,49 @@ import java.awt.*; */ public class MooMenu extends JMenuBar implements ActionListener { - private JMenu file, edit, keyboard, playback, music, help; + private JMenu file, reopen, edit, keyboard, playback, music, help; private JFileChooser chooser; private File directory; - private String[] openedFiles; + private ArrayList recentFiles; /** * Creates the menu bar. */ public MooMenu() { + // Fetches last work directory, and recent files. + // directory = Moosique.getRecentDirectory(); + ArrayList recentFiles = new ArrayList(); // ... = Moosique.getRecentFiles(); + + // Creates the sub-menus and their items. file = createMenu("File", KeyEvent.VK_F); add(file); - addItem(file, "New", KeyEvent.VK_N, ActionEvent.CTRL_MASK); - addItem(file, "Open...", KeyEvent.VK_O, ActionEvent.CTRL_MASK); - addItem(file, "Save", KeyEvent.VK_S, ActionEvent.CTRL_MASK); - addItem(file, "Save as...", KeyEvent.VK_A); + addItem(file, "New", KeyEvent.VK_N, true); + addItem(file, "Open...", KeyEvent.VK_O, true); + reopen = createMenu("Reopen", KeyEvent.VK_R); + Iterator it = recentFiles.iterator(); + while (it.hasNext()) { + JMenuItem recentFile = new JMenuItem((String)it.next()); + recentFile.addActionListener(this); + reopen.add(recentFile); + } + file.add(reopen); + addItem(file, "Save", KeyEvent.VK_S, true); + addItem(file, "Save as...", KeyEvent.VK_A, false); file.addSeparator(); - addItem(file, "Exit", KeyEvent.VK_Q, ActionEvent.CTRL_MASK); + addItem(file, "Exit", KeyEvent.VK_Q, true); edit = createMenu("Edit", KeyEvent.VK_E); add(edit); - addItem(edit, "Copy", KeyEvent.VK_C, ActionEvent.CTRL_MASK); - addItem(edit, "Cut", KeyEvent.VK_X, ActionEvent.CTRL_MASK); - addItem(edit, "Paste", KeyEvent.VK_V, ActionEvent.CTRL_MASK); + addItem(edit, "Copy", KeyEvent.VK_C, true); + addItem(edit, "Cut", KeyEvent.VK_X, true); + addItem(edit, "Paste", KeyEvent.VK_V, true); edit.addSeparator(); - addItem(edit, "Select all", KeyEvent.VK_E, ActionEvent.CTRL_MASK); - addItem(edit, "Invert selection", KeyEvent.VK_I, ActionEvent.CTRL_MASK); + addItem(edit, "Select all", KeyEvent.VK_E, true); + addItem(edit, "Invert selection", KeyEvent.VK_I, true); edit.addSeparator(); - addItem(edit, "Preferences...", KeyEvent.VK_P, ActionEvent.CTRL_MASK); + addItem(edit, "Preferences...", KeyEvent.VK_P, true); playback = createMenu("Playback", KeyEvent.VK_P); add(playback); @@ -50,34 +64,34 @@ public class MooMenu extends JMenuBar implements ActionListener { addItem(playback, "Pause", "F7", KeyEvent.VK_A); addItem(playback, "Stop", "F6", KeyEvent.VK_S); playback.addSeparator(); - addItem(playback, "Set position...", KeyEvent.VK_E); + addItem(playback, "Set position...", KeyEvent.VK_E, false); playback.addSeparator(); keyboard = createMenu("Set keyboard octave", KeyEvent.VK_K); edit.add(keyboard); - for (int i = 9; i >= 0; i--) addItem(keyboard, "Octave " + i, i + 48); + for (int i = 9; i >= 0; i--) addItem(keyboard, "Octave " + i, i + 48, false); music = createMenu("Music", KeyEvent.VK_M); add(music); - addItem(music, "Add track...", KeyEvent.VK_A, ActionEvent.CTRL_MASK); - addItem(music, "Delete track...", KeyEvent.VK_D, ActionEvent.CTRL_MASK); - addItem(music, "Copy track...", KeyEvent.VK_Y, ActionEvent.CTRL_MASK); - addItem(music, "Move track...", KeyEvent.VK_M, ActionEvent.CTRL_MASK); + addItem(music, "Add track...", KeyEvent.VK_A, true); + addItem(music, "Delete track...", KeyEvent.VK_D, true); + addItem(music, "Copy track...", KeyEvent.VK_Y, true); + addItem(music, "Move track...", KeyEvent.VK_M, true); music.addSeparator(); - addItem(music, "Insert measure...", KeyEvent.VK_I); - addItem(music, "Delete measure...", KeyEvent.VK_E); + addItem(music, "Insert measure...", KeyEvent.VK_I, false); + addItem(music, "Delete measure...", KeyEvent.VK_E, false); music.addSeparator(); - addItem(music, "Set time signature...", KeyEvent.VK_S); - addItem(music, "Set tempo...", KeyEvent.VK_M); - addItem(music, "Scale velocity...", KeyEvent.VK_V); - addItem(music, "Transpose...", KeyEvent.VK_T); + addItem(music, "Set time signature...", KeyEvent.VK_S, false); + addItem(music, "Set tempo...", KeyEvent.VK_M, false); + addItem(music, "Scale velocity...", KeyEvent.VK_V, false); + addItem(music, "Transpose...", KeyEvent.VK_T, false); help = createMenu("Help", KeyEvent.VK_L); add(help); - + addItem(help, "User manual", "F1", KeyEvent.VK_M); help.addSeparator(); - addItem(help, "About", KeyEvent.VK_A); + addItem(help, "About", KeyEvent.VK_A, false); } /** * Creats a menu in the menubar. @@ -95,11 +109,15 @@ public class MooMenu extends JMenuBar implements ActionListener { * Creats a menuitem in the menu. * @param menu The menu where to add the menuitem. * @param name The name of the menuitem. + * @param mnemonic The keyboard mnemonic used to access this menuitem + * @param accelerate whether to create a keyboard accelerator for this item * @return item The menuitem created. */ - private JMenuItem addItem(JMenu menu, String name, int mnemonic) { + private JMenuItem addItem(JMenu menu, String name, int mnemonic, boolean accelerate) { JMenuItem item = new JMenuItem(name); item.addActionListener(this); + item.setMnemonic(mnemonic); + if (accelerate) item.setAccelerator(KeyStroke.getKeyStroke(mnemonic, ActionEvent.CTRL_MASK)); menu.add(item); return item; } @@ -109,34 +127,18 @@ public class MooMenu extends JMenuBar implements ActionListener { * @param menu The menu to where to add the menuitem. * @param name The name of the menuitem. * @param key The keystroke to access this menuitem. + * @param mnemonic The keyboard mnemonic used to access this menuitem * @return item The menuitem created. */ private JMenuItem addItem(JMenu menu, String name, String key, int mnemonic) { JMenuItem item = new JMenuItem(name); - item.setAccelerator(KeyStroke.getKeyStroke(key)); item.setMnemonic(mnemonic); + item.setAccelerator(KeyStroke.getKeyStroke(key)); item.addActionListener(this); menu.add(item); return item; } - - /** - * Creats a menuitem in the menu. - * @param menu The menu to where to add the menuitem. - * @param name The name of the menuitem. - * @param key The keystroke to access this menuitem. - * @param mask The keyboard mask. - * @return item The menuitem created. - */ - private JMenuItem addItem(JMenu menu, String name, int key, int mask) { - JMenuItem item = new JMenuItem(name); - item.setAccelerator(KeyStroke.getKeyStroke(key, mask)); - item.setMnemonic(key); - item.addActionListener(this); - menu.add(item); - return item; - } - + private boolean isMidiFile(File f) { if(f != null) { String extension = f.getName().substring(f.getName().lastIndexOf('.') + 1).toLowerCase().trim(); @@ -168,8 +170,19 @@ public class MooMenu extends JMenuBar implements ActionListener { File file = chooser.getSelectedFile(); if(returnVal == JFileChooser.APPROVE_OPTION && isMidiFile(file)) { directory = file.getParentFile(); - if (!Moosique.promptOnUnsavedChanges()) - Moosique.load(file); + if (!Moosique.promptOnUnsavedChanges()) { + if (Moosique.load(file)) { + // Adds an item for this file to the reopen menu. + String filename = file.getAbsolutePath(); + JMenuItem recentFile = new JMenuItem(filename); + for (int i = 0; i < reopen.getMenuComponentCount(); i++) { + if (filename.equals(((JMenuItem)reopen.getMenuComponent(i)).getText())) + reopen.remove(i); + } + reopen.insert(recentFile, 0); + if (reopen.getMenuComponentCount() > 5) reopen.remove(5); + } + } } } else if (command == "Save") { if (!Moosique.save()) showSaveAsDialog(); @@ -233,6 +246,9 @@ public class MooMenu extends JMenuBar implements ActionListener { "About Moosique", JOptionPane.INFORMATION_MESSAGE, new ImageIcon(Moosique.getGUI().logo)); + } else if (reopen.isMenuComponent((JMenuItem)e.getSource())) { + String recentFile = ((JMenuItem)e.getSource()).getText(); + if (!Moosique.promptOnUnsavedChanges()) Moosique.load(new File(recentFile)); } } @@ -270,6 +286,4 @@ public class MooMenu extends JMenuBar implements ActionListener { return "MIDI files"; } } - - } diff --git a/MooNote.java b/MooNote.java index 5628b1d..2a82b6d 100644 --- a/MooNote.java +++ b/MooNote.java @@ -1,7 +1,7 @@ import javax.sound.midi.*; /** - * Functional representation of a MIDI note, which adds functionality to the existent MidiEvent class. + * Functional representation of a MIDI note, which adds functionality to the existing MidiEvent class. * Also provides a reference to the corresponding NoteOff event. * * @author Einar Pehrson diff --git a/MooNoteElement.java b/MooNoteElement.java index beed5c4..42955a8 100644 --- a/MooNoteElement.java +++ b/MooNoteElement.java @@ -13,10 +13,6 @@ public class MooNoteElement extends JPanel implements Comparable{ private MooTrackView mtv; private MooNote note; - private JPopupMenu popup; - private JMenu popupTranspUp, popupTranspDown; - private JMenuItem popupRemove, popupProp; - private JMenuItem[] popupTranspUpItems, popupTranspDownItems; private Rectangle pitchRect, veloRect; private String notePitch, noteVelocity; private boolean selected = false, leftMouseButtonPressed = false, mouseIn = false; @@ -41,15 +37,6 @@ public class MooNoteElement extends JPanel implements Comparable{ // Defines coordinates. pitchRect = new Rectangle(0, 0, 15, 10); veloRect = new Rectangle(20, 0, 40, 10); - - // Creates pop-up menu. - popup = new JPopupMenu(); - popupProp = addMenuItem(popup, "Preferences..."); - popupRemove = addMenuItem(popup, "Remove"); - popupTranspUpItems = new JMenuItem[12]; - popupTranspDownItems = new JMenuItem[12]; - popupTranspUp = createTransposeMenu(popup, popupTranspUpItems, "note up"); - popupTranspDown = createTransposeMenu(popup, popupTranspDownItems, "note down"); } /** @@ -61,7 +48,7 @@ public class MooNoteElement extends JPanel implements Comparable{ } /** - * Compares the note of this element to that of another note. + * Compares the note of this element to that of another element. * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object */ public int compareTo(Object o) { @@ -73,7 +60,7 @@ public class MooNoteElement extends JPanel implements Comparable{ */ public void select() { selected = true; - mtv.selectNote(this); + Moosique.selectNote(this); setBackground(invBgColor); textColor = Color.white; repaint(); @@ -173,40 +160,6 @@ public class MooNoteElement extends JPanel implements Comparable{ mtv.layoutElement(this,true); } - /** - * Adds a menu item with the given command to the given popup menu. - */ - private JMenuItem addMenuItem(JPopupMenu menu, String command) { - JMenuItem item = new JMenuItem(command); - item.addActionListener(new PopupListener()); - menu.add(item); - return item; - } - - /** - * Adds a menu item with the given command to the given menu. - */ - private JMenuItem addMenuItem(JMenu menu, String command) { - JMenuItem item = new JMenuItem(command); - item.addActionListener(new PopupListener()); - menu.add(item); - return item; - } - - /** - * Creates a transpose sub menu with the given title in the given popup menu, - * inserting the items into the given array. - */ - private JMenu createTransposeMenu(JPopupMenu menu, JMenuItem[] items, String title) { - JMenu trans = new JMenu("Transpose " + title); - menu.add(trans); - items[0] = addMenuItem(trans, "One octave"); - for (int i = 1; i < 12; i++) { - items[i] = addMenuItem(trans, (i) + " halftones"); - } - return trans; - } - /** * Listener that checks the mouse actions on this element. */ @@ -274,9 +227,8 @@ public class MooNoteElement extends JPanel implements Comparable{ public void mouseReleased(MouseEvent e) { if (!maybeShowPopup(e) && !mouseIn) { int y = e.getY(); - if (y < 0) mtv.maybeMoveSelectedNotes((int)Math.floor((double)y / MooTrackView.NOTE_HEIGHT) * MooTrackView.NOTE_HEIGHT); - if (y > getHeight()) mtv.maybeMoveSelectedNotes((int)Math.ceil(((double)y - getHeight()) / MooTrackView.NOTE_HEIGHT) * MooTrackView.NOTE_HEIGHT); - + if (y < 0) mtv.moveSelectedNotes((Moosique.getSequence().getResolution() / 4)* (int)Math.floor((double)y / MooTrackView.NOTE_HEIGHT)); + if (y > getHeight()) mtv.moveSelectedNotes((Moosique.getSequence().getResolution() / 4) * (int)Math.ceil(((double)y - getHeight()) / MooTrackView.NOTE_HEIGHT)); } } @@ -285,38 +237,8 @@ public class MooNoteElement extends JPanel implements Comparable{ */ private boolean maybeShowPopup(MouseEvent e) { if (!e.isPopupTrigger()) return false; - if (!e.isControlDown()) { - if (!selected || mtv.isTheOnlySelected((MooNoteElement)e.getComponent())) popup.show(e.getComponent(), e.getX(), e.getY()); - else mtv.showSelectionPopup(e.getComponent(), e.getX(), e.getY()); - } + if (!e.isControlDown()) mtv.showSelectionPopup(e.getComponent(), e.getX(), e.getY()); return true; } } - - /** - * Takes the appropriate action when a user selects an item on the popup menu. - */ - class PopupListener implements ActionListener { - public void actionPerformed(ActionEvent e) { - Object source = e.getSource(); - if (source == popupProp) { - System.out.println("Duration: " + note.getDuration()); - new MooDialog(note); - System.out.println("Duration: " + note.getDuration()); - newLayout(); - update(); - } else if (source == popupRemove) { - remove(); - } else if (source == popupTranspUpItems[0]) { - transpose(12); - } else if (source == popupTranspDownItems[0]) { - transpose(-12); - } else { - for (int i = 1; i < 12; i++) { - if (source == popupTranspUpItems[i]) transpose(i); - else if (source == popupTranspDownItems[i]) transpose(-i); - } - } - } - } } diff --git a/MooToolbar.java b/MooToolbar.java index 5be8fdb..bca50ef 100644 --- a/MooToolbar.java +++ b/MooToolbar.java @@ -1,7 +1,7 @@ +import javax.sound.midi.*; import javax.swing.*; import java.awt.*; import java.awt.event.*; -import javax.sound.midi.*; /** * The application's toolbar, with the most frequently used commands. @@ -75,17 +75,16 @@ public class MooToolbar extends JToolBar { * @param tickPosition the tick position to visualize */ public void updateProgInd(long tickPosition) { - ticksPerBeat = Moosique.getSequence().getResolution(); + System.out.print("Updating to " + tickPosition + " = "); if (tickPosition == 0) { resetProgInd(); + System.out.println("1:1:1"); } else { - // Otherwise, calculates the current song position in measures, beats and ticks. - long measures = tickPosition / (beatsPerMeasure * ticksPerBeat); - long beats = (tickPosition - measures * beatsPerMeasure * ticksPerBeat) / ticksPerBeat; - long ticks = tickPosition - measures * beatsPerMeasure * ticksPerBeat - beats * ticksPerBeat; - measuresValue.setText(Long.toString(1 + measures)); - beatsValue.setText(Long.toString(1 + beats)); - ticksValue.setText(Long.toString(1 + ticks)); + int[] position = Moosique.getPositionForTicks(tickPosition); + System.out.println("" + position[0] + ":" + position[1] + ":" + position[2]); + measuresValue.setText(Integer.toString(position[0])); + beatsValue.setText(Long.toString(position[1])); + ticksValue.setText(Long.toString(position[2])); } } diff --git a/MooTrackTitle.java b/MooTrackTitle.java index 6a3e1b5..4822dca 100644 --- a/MooTrackTitle.java +++ b/MooTrackTitle.java @@ -131,11 +131,13 @@ public class MooTrackTitle extends JPanel { } /** - * Checks if the focus is lost. + * When the title field loses focus, updates the corresponding MidiEvent. */ class TitleFocusListener extends FocusAdapter { public void focusLost(FocusEvent e) { - // Update the MidiEvent containing the title of this track + try { + trackNameMessage.setMessage(3, title.getText().getBytes(), title.getText().length()); + } catch (InvalidMidiDataException ex) {} } } @@ -150,7 +152,7 @@ public class MooTrackTitle extends JPanel { MidiEvent me; MooNote mn; instruments.setChannel(channel); - // Query the user before rechannelling??? + // Prompt the user before rechannelling??? for (int j = 0; j < track.size(); j++) { me = track.get(j); if (me instanceof MooNote){ diff --git a/MooTrackView.java b/MooTrackView.java index 916e8fc..1c7abe4 100644 --- a/MooTrackView.java +++ b/MooTrackView.java @@ -18,20 +18,20 @@ public class MooTrackView extends JPanel { private MooKeyboard keyboard; private JPopupMenu popup, selPopup; - private JMenu selPopupTranspUp, selPopupTranspDown; - private JMenuItem popupAdd, popupPaste; + private JMenu popupAdd, selPopupTranspUp, selPopupTranspDown; + private JMenuItem popupAddItemsCustom, popupAddItemsLast, popupPaste; + private JMenuItem popupAddItemsWhole, popupAddItemsHalf, popupAddItemsQuarter, popupAddItemsEighth, popupAddItemsSixteenth; private JMenuItem selPopupCopy, selPopupCut, selPopupRemove; private JMenuItem[] selPopupTranspUpItems, selPopupTranspDownItems; private ArrayList coords; - private TreeSet selection; private Insets insets; private Rectangle box; - private int ticksPerSixteenth, popupY = 0; + private int ticksPerSixteenth, popupY = 0, lastNoteLength = 2; private boolean leftMouseButtonPressed = false; private static boolean snapToSixteenths = true; - protected static int viewLength = 0; - protected static int extraHeight = 0; + private static int viewLength = 0; + private static int extraHeight = 0; public static final int NOTE_HEIGHT = 10, NOTE_WIDTH = 40, VIEW_WIDTH = 200; /** @@ -48,7 +48,6 @@ public class MooTrackView extends JPanel { ticksPerSixteenth = Moosique.getSequence().getResolution() / 4; insets = getInsets(); coords = new ArrayList(track.size() / 2); - selection = new TreeSet(); // Configures panel setBackground(Color.white); @@ -76,7 +75,16 @@ public class MooTrackView extends JPanel { // Creates panel pop-up menu. popup = new JPopupMenu(); - popupAdd = addMenuItem(popup, "Add note..."); + popupAdd = new JMenu("Add note"); + popup.add(popupAdd); + popupAddItemsCustom = addMenuItem(popupAdd, "Custom..."); + popupAddItemsLast = addMenuItem(popupAdd, "As last added"); + popupAdd.addSeparator(); + popupAddItemsWhole = addMenuItem(popupAdd, "Whole"); + popupAddItemsHalf = addMenuItem(popupAdd, "Half"); + popupAddItemsQuarter = addMenuItem(popupAdd, "Quarter"); + popupAddItemsEighth = addMenuItem(popupAdd, "Eighth"); + popupAddItemsSixteenth = addMenuItem(popupAdd, "Sixteenth"); popupPaste = addMenuItem(popup, "Paste"); // Creates selection pop-up menu. @@ -209,10 +217,11 @@ public class MooTrackView extends JPanel { /** * Adds a standard note to this track. */ - private void addStandardNote() { + private void addNoteAtClickY(int length) { + lastNoteLength = length; int row = (popupY - insets.top) / NOTE_HEIGHT; long timestamp = (long)(ticksPerSixteenth * row); - addNote(new MooNote(title.getChannel(), 60, 100, timestamp, Moosique.getSequence().getResolution() / 4)); + addNote(new MooNote(title.getChannel(), 60, 100, timestamp, ticksPerSixteenth * length)); } /** @@ -230,49 +239,11 @@ public class MooTrackView extends JPanel { repaint(); } - /** - * Selects the given note - * @param the note to select - */ - public void selectNote(MooNoteElement elem) { - selection.add(elem); - } - - /** - * Deselects the given note - * @param the note to deselect - */ - public void deselectNote(MooNoteElement elem) { - selection.remove(elem); - } - - /** - * Deselects all notes. - */ - public void deselectAllNotes() { - Iterator it = selection.iterator(); - while(it.hasNext()) { - ((MooNoteElement)it.next()).deselect(); - } - selection.clear(); - } - - /** - * Determines if the given MooNoteElement is the only one in the track view that is selected. - * @return if the given element is the only selected one - */ - public boolean isTheOnlySelected(MooNoteElement elem) { - Iterator it = selection.iterator(); - while(it.hasNext()) { - if (!it.next().equals(elem)) return false; - } - return true; - } - /** * Copies the current selection. */ public void copySelectedNotes() { + TreeSet selection = Moosique.getSelection(); ArrayList copyBuffer = new ArrayList(selection.size()); Iterator it = selection.iterator(); while(it.hasNext()) { @@ -306,6 +277,7 @@ public class MooTrackView extends JPanel { mn.setChannel(title.getChannel()); addNote(mn); } + Moosique.setEdited(); } } @@ -313,22 +285,26 @@ public class MooTrackView extends JPanel { * Removes the current selection. */ public void removeSelectedNotes() { + TreeSet selection = Moosique.getSelection(); Iterator it = selection.iterator(); while(it.hasNext()) { removeNote((MooNoteElement)it.next()); } selection.clear(); + Moosique.setEdited(); } /** * Transposes all selected notes the given number of halftones. */ private void transposeSelectedNotes(int halftones) { + TreeSet selection = Moosique.getSelection(); Iterator it = selection.iterator(); while(it.hasNext()) { MooNoteElement elem = (MooNoteElement)it.next(); elem.transpose(halftones); } + Moosique.setEdited(); } /** @@ -336,6 +312,7 @@ public class MooTrackView extends JPanel { * @param ticks the number of ticks to move the selection. */ public void moveSelectedNotes(int ticks) { + TreeSet selection = Moosique.getSelection(); if (ticks < 0) { // If the selection should be moved upwards, traverses the list in the natural order. Iterator it = selection.iterator(); @@ -354,14 +331,7 @@ public class MooTrackView extends JPanel { layoutElement(elem, true); } } - } - - /** - * Moves the current selection, depending on the given delta y. - * @param y the number of pixels the selection is moved. - */ - public void maybeMoveSelectedNotes(int y) { - moveSelectedNotes(ticksPerSixteenth * (y / NOTE_HEIGHT)); + Moosique.setEdited(); } /** @@ -456,10 +426,10 @@ public class MooTrackView extends JPanel { */ public void mouseClicked(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) { - deselectAllNotes(); + Moosique.deselectAllNotes(); if (e.getClickCount() == 2) { popupY = e.getY(); - addStandardNote(); + addNoteAtClickY(lastNoteLength); } } } @@ -500,8 +470,23 @@ public class MooTrackView extends JPanel { public void actionPerformed(ActionEvent e) { Object source = e.getSource(); // Handling panel popup actions. - if (source == popupAdd) { - addStandardNote(); + if (source == popupAddItemsLast) { + addNoteAtClickY(lastNoteLength); + } else if (source == popupAddItemsCustom) { + /* Show the user a dialog (identical to note preferences... + then call addNote(new MooNote( + title.getChannel(), pitch, velocity, timestamp, duration)); + */ + } else if (source == popupAddItemsWhole) { + addNoteAtClickY(16); + } else if (source == popupAddItemsHalf) { + addNoteAtClickY(8); + } else if (source == popupAddItemsQuarter) { + addNoteAtClickY(4); + } else if (source == popupAddItemsEighth) { + addNoteAtClickY(2); + } else if (source == popupAddItemsSixteenth) { + addNoteAtClickY(1); } else if (source == popupPaste) { pasteCopiedNotes(); // Handling selection popup actions. diff --git a/Moosique.java b/Moosique.java index e2b7f3e..5ad1afe 100644 --- a/Moosique.java +++ b/Moosique.java @@ -22,6 +22,7 @@ public class Moosique { private static MidiChannel activeChannel; 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; @@ -39,7 +40,7 @@ public class Moosique { * loads a sequence and creates the GUI. */ public static void main (String[] args) { - out("\nMoosique version 1.0\n"); + out("\nMoosique version 1.0\n", true); // Parses command-line arguments. String fileArg = null; @@ -49,17 +50,17 @@ public class Moosique { } // Acquires MIDI devices and connects them. - System.out.print("Initializing MIDI devices."); + out("Initializing MIDI devices.", false); try { // Configures sequencer sequencer = MidiSystem.getSequencer(); - System.out.print("."); + advanceStatus(); sequencer.open(); sequencer.addMetaEventListener(new SongEndListener()); // Configures synthesizer synthesizer = MidiSystem.getSynthesizer(); - System.out.print("."); + advanceStatus(); synthesizer.open(); // Configures receiver, transmitter and channels. @@ -68,22 +69,22 @@ public class Moosique { channels = synthesizer.getChannels(); setActiveChannel(0); } catch (MidiUnavailableException e) { - out("Failed, quitting."); + out("Failed, quitting.", true); System.exit(1); } - out("Done"); + out("Done", true); // Loads user preferences (work directory, last opened files etc). loadPreferences(); //If a filename is given as the command-line argument, attempts to load a sequence from the file. if (fileArg != null) { - System.out.print("Loading MIDI sequence from " + fileArg + "..."); + out("Loading MIDI sequence from " + fileArg + "...", false); if (!load(new File(fileArg))) { - out("Failed, creating new sequence"); + out("Failed, creating new sequence", true); clearSequence(); } else { - out("Done"); + out("Done", true); } } else { // Otherwise creates a new empty one. @@ -92,17 +93,17 @@ public class Moosique { // Builds GUI, unless n-flag is set. if (makeGUI) { - System.out.print("Building GUI."); + out("Building GUI.", false); try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) {} gui = new MooGUI(seq, file); - out("Done"); + out("Done", true); } else { - System.out.print("Playing..."); + out("Playing...", false); play(); while (sequencer.isRunning()) {} - out("Done"); + out("Done", true); quit(); } } @@ -167,17 +168,36 @@ public class Moosique { /** * 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 * @return an array of integers where index 0 is measures, 1 is beats and 2 is ticks. */ - public static int[] getPositionForTicks(long ticks) { - /* - int measures, beats, ticks; - for (int i = 0; i < timeSignatures.length; i++) { - long tick = timeSignatures[i].getTick(); - // Split the ticks in the interval into measures, beats and ticks. + public static int[] getPositionForTicks(long tickPosition) { + int ticksPerBeat = seq.getResolution(), sigs = timeSignatures.size(), beatsPerMeasure = 4; + long measures = 0, beats = 0, ticks = 0; + if (sigs > 1) { + /* + Iterator it = timeSignatures.iterator(); + MidiEvent lastTSEvent = (MidiEvent)it.next(); + if (lastTSEvent.getTick() != 0) tickPos += (int)lastTSEvent.getTick(); + while(it.hasNext()) { + MidiEvent nextTSEvent = (MidiEvent)it.next(); + long tickDiff = nextTSEvent.getTick() - lastTSEvent.getTick(); + ts = decodeTimeSig(((MetaMessage)lastTSEvent.getMessage()).getData()); + beatsPerMeasure = ts[0] * (4 / ts[1]); + tickPos += ((beatsPerMeasure * measures + beats) * res + ticks); + } + */ + } else { + if (sigs == 1) { + MidiEvent TSEvent = (MidiEvent)timeSignatures.get(0); + int[] ts = decodeTimeSig(((MetaMessage)TSEvent.getMessage()).getData()); + beatsPerMeasure = ts[0] * (4 / ts[1]); + } + measures = tickPosition / (beatsPerMeasure * ticksPerBeat); + beats = (tickPosition - measures * beatsPerMeasure * ticksPerBeat) / ticksPerBeat; + ticks = tickPosition - measures * beatsPerMeasure * ticksPerBeat - beats * ticksPerBeat; } - */ - int[] pos = {1, 1, 1}; + int[] pos = {(int)measures + 1, (int)beats + 1, (int)ticks + 1}; return pos; } @@ -228,13 +248,28 @@ public class Moosique { * @return the tick position. */ public static long getTicksForPosition(int measures, int beats, int ticks) { + int res = seq.getResolution(); long tickPos = 0; - /* - for (int i = 0; i < timeSignatures.length; i++) { - long tick = timeSignatures[i].getTick(); - // Add the measures, beats and ticks in the interval. + switch (timeSignatures.size()) { + case 0: + tickPos = (4 * measures + beats) * res + ticks; + case 1: + MidiEvent TSEvent = (MidiEvent)timeSignatures.get(0); + int[] ts = decodeTimeSig(((MetaMessage)TSEvent.getMessage()).getData()); + int beatsPerMeasure = ts[0] * (4 / ts[1]); + tickPos = (beatsPerMeasure * measures + beats) * res + ticks; + default: + Iterator it = timeSignatures.iterator(); + MidiEvent lastTSEvent = (MidiEvent)it.next(); + if (lastTSEvent.getTick() != 0) tickPos += (int)lastTSEvent.getTick(); + while(it.hasNext()) { + MidiEvent nextTSEvent = (MidiEvent)it.next(); + long tickDiff = nextTSEvent.getTick() - lastTSEvent.getTick(); + ts = decodeTimeSig(((MetaMessage)lastTSEvent.getMessage()).getData()); + beatsPerMeasure = ts[0] * (4 / ts[1]); + tickPos += ((beatsPerMeasure * measures + beats) * res + ticks); + } } - */ return tickPos; } @@ -427,7 +462,7 @@ public class Moosique { * @return the tempo in beats per minute */ public static int decodeTempo(byte[] bytes) { - return 60000000 / (bytes[0] * 65536 + bytes[1] * 256 + bytes[2]); // bytes[0] & 0xFF ??? + return 60000000 / (bytes[0] * 65536 + bytes[1] * 256 + bytes[2]); } /** @@ -439,7 +474,7 @@ public class Moosique { public static byte[] encodeTimeSig(int numerator, int denominator) { byte[] b = { (byte)numerator, - (byte)(Math.log(denominator) / Math.log(2)), + (byte)(Math.log(denominator) / Math.log(2)), // logarithm of denominator in base 2 (byte)96, (byte)8 }; @@ -465,6 +500,71 @@ public class Moosique { + /* *** + ** SELECTION METHODS ** + *** */ + + + + + + + + + /** + * Returns the current selection. + * @return the current selection + */ + public static TreeSet getSelection() { + return selection; + } + + /** + * Selects the given note + * @param the note to select + */ + public static void selectNote(MooNoteElement elem) { + selection.add(elem); + } + + /** + * Deselects the given note + * @param the note to deselect + */ + public static void deselectNote(MooNoteElement elem) { + selection.remove(elem); + } + + /** + * Deselects all notes. + */ + public static void deselectAllNotes() { + Iterator it = selection.iterator(); + while(it.hasNext()) { + ((MooNoteElement)it.next()).deselect(); + } + selection.clear(); + } + + /** + * Determines if the given MooNoteElement is the only one in the track view that is selected. + * @return if the given element is the only selected one + */ + public static boolean isTheOnlySelected(MooNoteElement elem) { + Iterator it = selection.iterator(); + while(it.hasNext()) { + if (!it.next().equals(elem)) return false; + } + return true; + } + + + + + + + + /* *** ** PLAYBACK METHODS ** *** */ @@ -524,7 +624,7 @@ public class Moosique { if (sequencer.isRunning()) { sequencer.stop(); } - if (player != null) player.interrupt(); + player = null; } /** @@ -535,7 +635,7 @@ public class Moosique { sequencer.stop(); } sequencer.setTickPosition(editPosition); - if (player != null) player.interrupt(); + player = null; gui.update((long)0); } @@ -566,15 +666,13 @@ public class Moosique { seq = new Sequence(Sequence.PPQ, DEFAULT_RESOLUTION, DEFAULT_TRACKS); Track[] tracks = seq.getTracks(); - // Creates messages for default tempo (120) and time signature (4/4). + // 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) {} - - // Adds them to track 0. tracks[0].add(new MidiEvent(timeSigMsg, (long)0)); tracks[0].add(new MidiEvent(tempoMsg, (long)0)); @@ -582,17 +680,14 @@ public class Moosique { initializeTrack(tracks[1], 0, 24, "Guitar"); initializeTrack(tracks[2], 1, 33, "Bass"); initializeTrack(tracks[3], 9, 0, "Drums"); - - file = null; - emptyTracks = new ArrayList(); - timeSignatures = new ArrayList(); - tempoChanges = new ArrayList(); - trackSolo = new HashMap(); - trackMute = new HashMap(); - copyBuffer = new ArrayList(); } catch (InvalidMidiDataException e) {} + + // Reinitializes sequence variables + file = null; + reinitializeVariables(); + // Sends the sequence to the GUI. - if (gui != null) gui.setSequence(seq); + if (gui != null) gui.setSequence(seq, null); } /** @@ -609,6 +704,9 @@ public class Moosique { titleMsg.setMessage(3, title.getBytes(), title.length()); } catch (InvalidMidiDataException e) {} + // Sends the program change to the channel + getChannel(channel).programChange(program); + // Adds them to the track. track.add(new MidiEvent(programMsg, (long)0)); track.add(new MidiEvent(titleMsg, (long)0)); @@ -691,17 +789,11 @@ public class Moosique { edited = false; Track[] tracks = seq.getTracks(); - emptyTracks = new ArrayList(); - timeSignatures = new ArrayList(); - tempoChanges = new ArrayList(); - trackMute = new HashMap(); - trackSolo = new HashMap(); - copyBuffer = new ArrayList(); + reinitializeVariables(); // Searches track 0 for changes in tempo and time signature. MidiEvent event; MetaMessage metaMsg; - ArrayList ts = new ArrayList(), tc = new ArrayList(); for (int i = 0; i < tracks[0].size(); i++) { event = tracks[0].get(i); if (event.getMessage().getStatus() == MetaMessage.META) { @@ -712,6 +804,9 @@ public class Moosique { } } } + Comparator c = new MidiEventComparator(); + Collections.sort(tempoChanges, c); + Collections.sort(timeSignatures, c); // Converts tracks. for (int i = 0; i < tracks.length; i++) { @@ -719,7 +814,7 @@ public class Moosique { } // Sends sequence to GUI and sequencer, then returns - if (gui != null) gui.setSequence(seq); + if (gui != null) gui.setSequence(seq, file); try { sequencer.setSequence(seq); } catch (InvalidMidiDataException e) {} @@ -737,6 +832,19 @@ public class Moosique { // Math.round(mn.getTick() / ticksPerSixteenth); } + /** + * Reinitializes sequence-specific variables. + */ + private static void reinitializeVariables() { + emptyTracks = new ArrayList(); + timeSignatures = new ArrayList(); + tempoChanges = new ArrayList(); + trackSolo = new HashMap(); + trackMute = new HashMap(); + copyBuffer = new ArrayList(); + selection = new TreeSet(); + } + /** * Loads the user preferences. */ @@ -752,7 +860,7 @@ public class Moosique { } /** - * Prompts the user . + * Prompts the user to save any unsaved changes. */ public static boolean promptOnUnsavedChanges() { if (!edited) return false; @@ -796,8 +904,16 @@ public class Moosique { /** * Prints the given string to the System output. */ - private static void out(String text) { - System.out.println(text); + private static void out(String text, boolean newLine) { + if (newLine) System.out.println(text); + else System.out.print(text); + } + + /** + * Advances the current progress counter by printing a "." to the System output. + */ + private static void advanceStatus() { + out(".", false); } /** @@ -829,28 +945,25 @@ public class Moosique { */ public static class PlayThread extends Thread { public void run() { - // Updates the GUI with the current tick position. - gui.update(sequencer.getTickPosition()); - - // Puts the thread to sleep for as long as it takes - // the sequencer to reach the next sixteenth. - try { - //sleep((long)((15000 / getTempo()) * (tickDiff / ticksPerSixteenth))); - sleep (10); - } catch (InterruptedException e) { - Moosique.stop(); + Thread currentThread = Thread.currentThread(); + while(currentThread == player) { + gui.update(sequencer.getTickPosition()); + try { + sleep(10); + } catch (InterruptedException e) { + Moosique.stop(); + } } } } /** - * A listener for detecting the end of the sequence. + * A listener for detecting the Meta event signifying the end of the sequence. */ public static class SongEndListener implements MetaEventListener { public void meta(MetaMessage event) { if (event.getType() == 47) - // End of sequence stop(); - } + } } } diff --git a/To Do.txt b/To Do.txt index f6b6d37..d655e6d 100644 --- a/To Do.txt +++ b/To Do.txt @@ -11,6 +11,7 @@ Rolle Spara konfiguration BUGGAR +x Exceptions vid inspelning till ny fil. x Play-knappen ändras inte till Pause. x Varför ritar den ut de tomma spåren i en ny fil? Rätt, men hur? x Varför hänger sig Play om man ändrar duration på en not? @@ -27,8 +28,10 @@ x Textf PREFERENCES -x MIDI Devices - Comboboxar över tillgängliga sequencers och synthesizers. -x "Allow smaller note elements than 1/16" - Checkbox mot variabeln MooTrackView.snapToSixteenths +x MIDI Devices Comboboxar över tillgängliga sequencers och synthesizers. +x "Follow song on play" Checkbox mot booleanen MooGUI.updateView +x "Allow smaller note elements than 1/16" Checkbox mot booleanen MooTrackView.snapToSixteenths + Vilka mer booleaner ska man kunna ändra? @@ -40,12 +43,12 @@ SWING Mnemonicsarna är konstiga. MooToolbar + Lägg till: + ! Lablar för tempo och taktart + ! Fler knappar - Preferences, Octave up/down, + ? Volymkontroll Använd Moosique.setVolume(long volume); + ? Progress Bar getTickLength() för max, getTickPosition() för position. Fixa så att allt sitter fast! Med BoxLayout? - Lägg till? - Volymkontroll Använd Moosique.setVolume(long volume); - Progress Bar getTickLength() för max, getTickPosition() för position. - Lablar för tempo och taktart - Fler knappar - Preferences, Octave up/down, MooTrackView Ändra längden på MooNoteElement (JPanel). -- 2.39.2