/* * Created on Jan 19, 2006 * * Copyright (c) 2005 Peter Johan Salomonsen (http://www.petersalomonsen.com) * * 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.tracker; import static com.frinika.localization.CurrentLocale.getMessage; import java.util.Collection; import java.util.Iterator; import java.util.SortedSet; import java.util.Vector; import javax.swing.table.AbstractTableModel; import com.frinika.project.ProjectContainer; import com.frinika.project.gui.ProjectFrame; import com.frinika.sequencer.FrinikaSequence; import com.frinika.sequencer.FrinikaSequencer; import com.frinika.sequencer.model.ChannelEvent; import com.frinika.sequencer.model.ControllerEvent; import com.frinika.sequencer.model.EditHistoryAction; import com.frinika.sequencer.model.EditHistoryListener; import com.frinika.sequencer.model.MidiPart; import com.frinika.sequencer.model.MovePartEditAction; import com.frinika.sequencer.model.MultiEvent; import com.frinika.sequencer.model.MultiEventChangeRecorder; import com.frinika.sequencer.model.NoteEvent; import com.frinika.sequencer.model.PitchBendEvent; import com.frinika.sequencer.model.SysexEvent; import com.frinika.sequencer.model.util.TimeUtils; /** * @author Peter Johan Salomonsen */ public class TrackerTableModel extends AbstractTableModel implements EditHistoryListener { /** * */ private static final long serialVersionUID = 1L; private static final String[] ColumnNames = { "Time", //"Ch", "Note", "Vel", "Len" }; static final int COLUMN_TIME = 0; // Relative time to rows static final int COLUMN_CHANNEL = -1; // MIDI Channel static final int COLUMN_NOTEORCC = 1; // Note or Control Change or Pitch bend static final int COLUMN_VELORVAL = 2; // Velocity or CC value static final int COLUMN_LEN = 3; // Note length static final int COLUMNS = ColumnNames.length; double ticksPerRow; // Number of ticks per row FrinikaSequence sequence; FrinikaSequencer sequencer; MidiPart midiPart; int columnCount = 1; long startTick = 0; int beatCount = 0; int editVelocity = 100; double editDuration = 1.0; TimeUtils timeUtils; ProjectFrame frame; public TrackerTableModel(ProjectFrame frame) { this.frame = frame; ProjectContainer project = frame.getProjectContainer(); this.sequence = project.getSequence(); this.ticksPerRow = sequence.getResolution() / 4.0; // Default 1/4th notes this.sequencer = project.getSequencer(); timeUtils = new TimeUtils(project); project.getEditHistoryContainer().addEditHistoryListener(this); } public void setMidiPart(MidiPart part) { if(part==null) { startTick = 0; beatCount = 0; midiPart = null; } else { this.midiPart = part; // Make sure that startTick is always on a whole beat this.startTick = (part.getStartTick() - (part.getStartTick() % sequence.getResolution())); this.beatCount = (int)((part.getEndTick() - startTick) / sequence.getResolution()); if(((part.getEndTick() - startTick) % sequence.getResolution())>0) beatCount++; } fireTableDataChanged(); } public void setStartBeat(int startBeat) { this.startTick = startBeat * sequence.getResolution(); fireTableDataChanged(); } public void setBeatCount(int beatCount) { this.beatCount = beatCount; fireTableDataChanged(); } public int getTicksPerRow() { return (int)(sequence.getResolution() / ticksPerRow); } /** * Set number of tracker rows to be showed for one beat * @param rowsPerBeat */ public void setRowsPerBeat(int rowsPerBeat) { this.ticksPerRow = sequence.getResolution() / (double)rowsPerBeat; fireTableDataChanged(); } public int getEditVelocity() { return editVelocity; } public void setEditVelocity(int editVelocity) { this.editVelocity = editVelocity; } public double getEditDuration() { return editDuration; } public void setEditDuration(double editDuration) { this.editDuration = editDuration; } /* (non-Javadoc) * @see javax.swing.table.TableModel#getRowCount() */ public int getRowCount() { return (int)((beatCount * sequence.getResolution()) / ticksPerRow); } /* (non-Javadoc) * @see javax.swing.table.AbstractTableModel#getColumnClass(int) */ public Class< ? > getColumnClass(int columnIndex) { if(columnIndex == 0) return(Integer.class); else switch((columnIndex-1)%COLUMNS) { case COLUMN_TIME: return Double.class; case COLUMN_LEN: return Double.class; default: return Integer.class; } } /* (non-Javadoc) * @see javax.swing.table.AbstractTableModel#getColumnName(int) */ public String getColumnName(int column) { if(column!=0) { return(ColumnNames[(column-1)%COLUMNS]); } else { return("Bar.Beat"); } } /* (non-Javadoc) * @see javax.swing.table.TableModel#getColumnCount() */ public int getColumnCount() { //System.out.println(columnCount); return((columnCount*COLUMNS)+1); } public final long getTickForRow(int row) { return((long)((row*ticksPerRow)+startTick)); } /** * Return row for the given tick * @param tick * @return */ public final int getRowForTick(long tick) { return((int)Math.round((tick-startTick)/ticksPerRow)); } /** * Get the row of the current sequencer position * @return */ public final int getPlayingRow() { return getRowForTick(sequencer.getTickPosition()); } /** * When building the table, the getRowEvents and getCellEvent methods are called repeatedly with the same parameters. * The following private variables is to cache the returned set when the same parameters are repeated. */ private int lastRow=-1; private Collection<MultiEvent> lastRowEvents; /** * Return a subset of MultiEvents for the given table row * @param row * @return */ public final Collection<MultiEvent> getRowEvents(int row) { if(row==lastRow ) { return lastRowEvents; } else { long rowTick = getTickForRow(row); // System.out.println((rowTick-(quantize/2)+" "+(rowTick+(quantize/2)))); SortedSet<MultiEvent> tmpRowEvents = midiPart.getMultiEventSubset( (long)(rowTick-(ticksPerRow/2)), (long)(rowTick+(ticksPerRow/2)) ); // Some multievents might have fixed columns, thus we'll reorder the rowEvent set here Vector<MultiEvent> rowEvents = new Vector<MultiEvent>(); for(MultiEvent multiEvent : tmpRowEvents) { if(multiEvent.getTrackerColumn()!=null) { // If multiEvent has a fixed columnIndex int columnIndex = multiEvent.getTrackerColumn(); while(columnIndex>rowEvents.size()) rowEvents.add(null); if(columnIndex<rowEvents.size() && rowEvents.get(columnIndex)==null) rowEvents.remove(columnIndex); // Depending on the rows per beat setting we'll try to assign a column according to the fixed as good as possible rowEvents.add(columnIndex,multiEvent); } else rowEvents.add(multiEvent); } if((rowEvents.size()+1)>columnCount) { columnCount=rowEvents.size()+1; //System.out.println("colCount"+columnCount); fireTableStructureChanged(); } lastRow = row; lastRowEvents = rowEvents; return rowEvents; } } /** * These two are cache variables to speed up the table rendering */ private int lastCol=-1; private MultiEvent lastCellEvent; /** * Get the MultiEvent for a specific cell * @param row * @param col * @return */ public final MultiEvent getCellEvent(int row,int col) { if(row == lastRow && col == lastCol) { return lastCellEvent; } else { Collection<MultiEvent> rowEvents = getRowEvents(row); Iterator<MultiEvent> it = rowEvents.iterator(); MultiEvent event = null; int c = -1; for(;c<col && it.hasNext();c++) event = it.next(); lastCol = col; if(c==col) { if(event!=null && event.getTrackerColumn()==null) event.setTrackerColumn(col); lastCellEvent = event; return event; } else { lastCellEvent = null; return null; } } } public final int tableColumnToTrackerColumn(int tableColumn) { return (tableColumn-1)/COLUMNS; } public MultiEvent getMultiEventAt(int row, int column) { return getCellEvent(row,tableColumnToTrackerColumn(column)); } /* (non-Javadoc) * @see javax.swing.table.TableModel#getValueAt(int, int) */ public Object getValueAt(int row, int columnIndex) { if(columnIndex == 0) { long tick = getTickForRow(row); double beat = tick/(double)sequence.getResolution(); if(beat % 1 == 0) return(timeUtils.tickToBarBeat(tick)); else return ""; } else { int col = tableColumnToTrackerColumn(columnIndex); int eventCol = (columnIndex-1)%COLUMNS; MultiEvent me = getCellEvent(row,col); if( me!=null ) { if (eventCol == COLUMN_TIME) { long relativeTick = me.getStartTick() - startTick; long rowTick =(long)(row * ticksPerRow); return((relativeTick - rowTick) / ticksPerRow); } else { if (me instanceof ChannelEvent) { ChannelEvent event = (ChannelEvent)me; switch(eventCol) { //case COLUMN_TIME: // long relativeTick = me.getStartTick() - startTick; // long rowTick =(long)(row * ticksPerRow); // return((relativeTick - rowTick) / ticksPerRow); case COLUMN_CHANNEL: return(new Integer(event.getChannel())); case COLUMN_NOTEORCC: if(event instanceof NoteEvent) return ((NoteEvent)event).getNoteName(); else if(event instanceof ControllerEvent) return "CC"+((ControllerEvent)event).getControlNumber(); else if(event instanceof PitchBendEvent) return "PB"; else return null; case COLUMN_VELORVAL: if(event instanceof NoteEvent) return ((NoteEvent)event).getVelocity(); else if(event instanceof ControllerEvent) return ((ControllerEvent)event).getValue(); else if(event instanceof PitchBendEvent) return (((PitchBendEvent)event).getValue() >> 7); else return null; case COLUMN_LEN: if(event instanceof NoteEvent) return ((NoteEvent)event).getDuration() / ticksPerRow; default: return(null); } } else if (me instanceof SysexEvent) { //ChannelEvent event = (ChannelEvent)me; switch(eventCol) { case COLUMN_NOTEORCC: return "SYX"; default: return(null); } } else { return null; } } } else { return null; } } } @Override public void setValueAt(final Object value, final int row, int columnIndex) { int col = tableColumnToTrackerColumn(columnIndex); final int eventCol = (columnIndex-1)%COLUMNS; final MultiEvent me = getCellEvent(row,col); if(me==null) { // A new MultiEvent if(eventCol==COLUMN_NOTEORCC) { Integer val = (Integer)value; if(val>0) { midiPart.getEditHistoryContainer().mark("new note"); NoteEvent event = new NoteEvent(midiPart,getTickForRow(row),(Integer)value,editVelocity,1,(long)(long)(ticksPerRow*getEditDuration())); addMultiEvent(event,col); midiPart.getEditHistoryContainer().notifyEditHistoryListeners(); } else if(val >= -127) { midiPart.getEditHistoryContainer().mark("new control change"); ControllerEvent event = new ControllerEvent(midiPart,getTickForRow(row),-(Integer)value,0); addMultiEvent(event,col); midiPart.getEditHistoryContainer().notifyEditHistoryListeners(); } else if(val == MultiEventCellComponent.EVENT_VALUE_PITCH_BEND) { midiPart.getEditHistoryContainer().mark("new pitch bend"); PitchBendEvent event = new PitchBendEvent(midiPart,getTickForRow(row),0x2000); addMultiEvent(event,col); midiPart.getEditHistoryContainer().notifyEditHistoryListeners(); } else if(val == MultiEventCellComponent.EVENT_VALUE_SYSEX) { // Jens SysexEvent event = new SysexEvent(midiPart,getTickForRow(row)); event.showEditorGUI(frame); if (event.isSuccessfullyParsed()) { midiPart.getEditHistoryContainer().mark(getMessage("sequencer.sysex.new_sysex")); addMultiEvent(event,col); midiPart.getEditHistoryContainer().notifyEditHistoryListeners(); } } } lastRow = -1; fireTableRowsUpdated(row,row); } else { try { switch(eventCol) { case COLUMN_TIME: final long newTick = (long)(getTickForRow(row) + ((Double)value * ticksPerRow)); new MultiEventChangeRecorder("move event",me) { public void doChange(MultiEvent me) { me.setStartTick(newTick); } }; int newRow = getRowForTick(newTick); if(newRow!=row) { lastRow=-1; fireTableRowsUpdated(row,row); if(newRow>=0 && newRow<getRowCount()) fireTableRowsUpdated(newRow,newRow); } break; case COLUMN_NOTEORCC: if(value.equals(MultiEventCellComponent.EVENT_VALUE_DELETE)) { if(me instanceof NoteEvent) midiPart.getEditHistoryContainer().mark("delete note"); else if(me instanceof ControllerEvent) midiPart.getEditHistoryContainer().mark("delete control change"); else if(me instanceof PitchBendEvent) midiPart.getEditHistoryContainer().mark("delete pitch bend"); else if(me instanceof SysexEvent) // Jens midiPart.getEditHistoryContainer().mark(getMessage("sequencer.sysex.delete_sysex")); else midiPart.getEditHistoryContainer().mark("delete event"); midiPart.remove(me); midiPart.getEditHistoryContainer().notifyEditHistoryListeners(); } else if (me instanceof NoteEvent) { new MultiEventChangeRecorder("change note",me) { public void doChange(MultiEvent me) { ((NoteEvent)me).setNote((Integer)value); }}; } else if (me instanceof SysexEvent) { ((SysexEvent)me).showEditorGUI(frame); } break; case COLUMN_VELORVAL: if(me instanceof NoteEvent) { new MultiEventChangeRecorder("change velocity",me) { public void doChange(MultiEvent me) {((NoteEvent)me).setVelocity(((Integer)value).intValue());}}; } else if(me instanceof ControllerEvent) { new MultiEventChangeRecorder("change controller value",me) { public void doChange(MultiEvent me) {((ControllerEvent)me).setValue(((Integer)value).intValue());}}; } else if(me instanceof PitchBendEvent) { new MultiEventChangeRecorder("change pitchbend value",me) { public void doChange(MultiEvent me) {((PitchBendEvent)me).setValue(((Integer)value).intValue() << 7);}}; } break; case COLUMN_LEN: if(me instanceof NoteEvent) new MultiEventChangeRecorder("change duration",me) { public void doChange(MultiEvent me) {((NoteEvent)me).setDuration((long)((Double)value * ticksPerRow));}}; break; } } catch(Exception e) { e.printStackTrace(); } } System.out.println(value+" "+row); } private void addMultiEvent(MultiEvent event,int column) { event.setTrackerColumn(column); midiPart.add(event); } /* (non-Javadoc) * @see javax.swing.table.AbstractTableModel#isCellEditable(int, int) */ public boolean isCellEditable(int rowIndex, int columnIndex) { if(columnIndex != 0) return(true); else return(false); } public void fireSequenceDataChanged(EditHistoryAction[] edithistoryActions) { if(edithistoryActions.length>0 && edithistoryActions[0] instanceof MovePartEditAction && ((MovePartEditAction)edithistoryActions[0]).getPart()==midiPart) this.setMidiPart((MidiPart)((MovePartEditAction)edithistoryActions[0]).getPart()); else fireTableRowsUpdated(0,getRowCount()); } /** * Clean up * */ public void dispose() { midiPart.getEditHistoryContainer().removeEditHistoryListener(this); } }