/* * Created on Feb 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 static com.frinika.localization.CurrentLocale.getMessage; import com.frinika.gui.OptionsDialog; import com.frinika.project.gui.ProjectFrame; import com.frinika.project.ProjectContainer; import com.frinika.sequencer.gui.partview.PartView; import com.frinika.sequencer.gui.virtualkeyboard.VirtualKeyboard; import com.frinika.sequencer.model.MidiPart; import com.frinika.sequencer.model.MidiLane; import com.frinika.sequencer.model.NoteEvent; import com.frinika.sequencer.model.EditHistoryAction; import java.awt.KeyboardFocusManager; import java.awt.event.ActionEvent; import javax.swing.AbstractAction; import javax.swing.JMenuItem; import java.util.*; /** * Menu-action for non-realtime step-recording. * * @author Jens Gulden */ public class MidiStepRecordAction extends AbstractAction { private final static String actionId = "sequencer.midi.step_record"; long step = 128 / 2; long position; int lengthDiff = -4; int velocity = 100; boolean autoRecord = true; private ProjectFrame frame; private MidiStepRecordActionDialog dialog; MidiPart part; public MidiStepRecordAction(ProjectFrame frame) { super(getMessage(actionId)); this.frame = frame; } public void actionPerformed(ActionEvent e) { if(!(java.awt.EventQueue.getCurrentEvent().getSource() instanceof JMenuItem)) if(!(KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner() instanceof PartView)) return; ProjectContainer project = frame.getProjectContainer(); position = project.getSequencer().getTickPosition(); part = project.getMidiSelection().getMidiPart(); if (dialog == null) { dialog = new MidiStepRecordActionDialog(frame, this); OptionsDialog.centerOnScreen(dialog); } dialog.show(); } /** * Called back from dialog, when "record" is clicked, or triggered by auto-record. */ int[] stepRecord(int[] notes) { MidiPart part = frame.getProjectContainer().getMidiSelection().getMidiPart(); if (part != null) { ProjectContainer project = frame.getProjectContainer(); SortedSet<Integer> inserted = new TreeSet<Integer>(); project.getEditHistoryContainer().mark(getMessage(actionId)); for (int i = 0; i < notes.length; i++) { int n = notes[i]; if (n != -1) { insertNote(n); inserted.add(n); } } // advance step position, undoable final long oldPosition = position; position += step; final long newPosition = position; frame.getProjectContainer().getSequencer().setTickPosition(position); project.getEditHistoryContainer().push(new EditHistoryAction() { public void undo() { frame.getProjectContainer().getSequencer().setTickPosition(oldPosition); } public void redo() { frame.getProjectContainer().getSequencer().setTickPosition(newPosition); } }); project.getEditHistoryContainer().notifyEditHistoryListeners(); int[] result = new int[inserted.size()]; Iterator<Integer> it = inserted.iterator(); for (int i = 0; i < result.length; i++) { result[i] = it.next(); } return result; } else { frame.message("Please select a part to record into."); return null; } } /** * Called back from dialog, when "record" is clicked, or triggered by auto-record. */ String stepRecord(String s) { int[] notes = parseNotes(s); int[] n = stepRecord(notes); if (n != null) { String r = formatNotes(n); return r; } else { return null; } } void insertNote(int note) { NoteEvent n = new NoteEvent(part, this.position, note, this.velocity, ((MidiLane)part.getLane()).getMidiChannel(), this.step + this.lengthDiff); part.add(n); } /** * * @param s * @return array of note numbers, may contain -1 entries to marks unparseable entries */ static int[] parseNotes(String s) { StringTokenizer st = new StringTokenizer(s, " \t\n\r,;:", false); int[] notes = new int[st.countTokens()]; int i = 0; while ( st.hasMoreTokens() ) { notes[ i++ ] = parseNote( st.nextToken() ); } return notes; } static String formatNotes(int[] notes) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < notes.length; i++) { String noteName = formatNote(notes[i]); if (i > 0) { sb.append(' '); } sb.append(noteName); } return sb.toString(); } private final static String NOTES = "c d ef g a b"; public static int parseNote(String s) { // TODO: move to global Tool-class int note; int mod = 0; // -1, 0, +1 int octave; int len = s.length(); if ( (len == 0) || (len > 3) ) return -1; char n = Character.toLowerCase( s.charAt(0) ); note = NOTES.indexOf(n); if (note == -1) return -1; int octavePos = 1; switch (len) { case 1: octave = VirtualKeyboard.Octave; // current octave as set for virtual keyboard via menu break; case 3: char m = s.charAt(1); switch (m) { case '-': break; case '#': mod = 1; break; case 'b': mod = -1; break; default: return -1; // invalid } octavePos = 2; // fallthrough case 2: char oc = s.charAt(octavePos); octave = (int)oc - 48; if (octave < 0 || octave > 9) return -1; break; default: return -1; } int result = note + mod + (octave * 12); return result; } public static String formatNote(int note) { // TODO: move to global Tool-class String s = VirtualKeyboard.getNoteString(note); if (s.charAt(1)=='-') { return new StringBuffer().append(s.charAt(0)).append(s.charAt(2)).toString(); // remove middle '-' (also not required for parsing) } else { return s; } } }