/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package com.frinika.project; import com.frinika.sequencer.FrinikaSequencer; import com.frinika.sequencer.FrinikaTrackWrapper; import com.frinika.sequencer.SequencerListener; import com.frinika.sequencer.SongPositionListener; import com.frinika.sequencer.midi.MidiMessageListener; import com.frinika.sequencer.model.ChannelEvent; import com.frinika.sequencer.model.ControllerEvent; import com.frinika.sequencer.model.Lane; import com.frinika.sequencer.model.MidiLane; import com.frinika.sequencer.model.MidiPart; import com.frinika.sequencer.model.MultiEvent; import com.frinika.sequencer.model.NoteEvent; import com.frinika.sequencer.model.PitchBendEvent; import java.util.HashMap; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import javax.sound.midi.MidiMessage; import javax.sound.midi.ShortMessage; /** * * This is added as a message listener to the FrinikaSequencer * * @author pjl */ public class RecordingManager implements SongPositionListener, MidiMessageListener, SequencerListener { MultiPart multiPart; MidiPart lastPart; // The current recording take - will be added to recordingTake when recording is stopped, or in case of a loop - and provided that there are multievents in the take protected Vector<MultiEvent> currentRecordingTake = new Vector<MultiEvent>(); protected HashMap<Integer, NoteEvent> pendingNoteEvents = new HashMap<Integer, NoteEvent>(); protected FrinikaSequencer sequencer; long lastTick = -1; protected Stack stack; protected ProjectContainer project; boolean looped; boolean isDrumTake; public RecordingManager(ProjectContainer proj, int buffSize) { this.project = proj; this.sequencer = proj.getSequencer(); sequencer.addSongPositionListener(this); sequencer.addSequencerListener(this); sequencer.addMidiMessageListener(this); stack = new Stack(buffSize); looped = false; } public boolean requiresNotificationOnEachTick() { return false; } public void midiMessage(MidiMessage message) { if (!sequencer.isRecording()) { return; } long tick = sequencer.getRealTimeTickPosition(); if (message instanceof ShortMessage) { try { ShortMessage shm = (ShortMessage) message; Event event = stack.poke(); event.mess = (ShortMessage) message; event.stamp = tick; } catch (Exception ex) { Logger.getLogger(RecordingManager.class.getName()).log(Level.SEVERE, null, ex); } } } void detachLastTake() { // disable last part if (lastPart != null) { if (lastPart.getLane() != null) { lastPart.removeFromModel(); } } } /** * Add a new take for recording * */ void commitRecordingTake() { if (currentRecordingTake.size() > 0) { System.out.println(" THAT WAS A TAKE "); project.getEditHistoryContainer().mark(" Recording take "); if (!isDrumTake) { detachLastTake(); } boolean flag = false; for (Lane lane : project.getLanes()) { if (!(lane instanceof MidiLane)) { continue; } MidiLane ml = (MidiLane) lane; if (!ml.isRecording()) { continue; } assert (!flag); // fixme for multiple lanes. flag = true; MidiPart part = new MidiPart(ml); for (MultiEvent event : currentRecordingTake) { try { part.add((MultiEvent) event.clone()); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } part.setBoundsFromEvents(); if (lastPart != null) { if (multiPart == null) { multiPart = new MultiPart(); multiPart.add(lastPart); } multiPart.add(part); } lastPart = part; project.getEditHistoryContainer().notifyEditHistoryListeners(); } // recordingTakes.add(currentRecordingTake); // if (recordingTakeDialog != null) { // recordingTakeDialog.notifyNewTake(recordingTakes.size() - 1); // // // If this is a loop then show the dialog // if (recordingTakes.size() > 1) { // recordingTakeDialog.setVisible(true); // } // } } currentRecordingTake.clear(); } public void notifyTickPosition(long tick) { processEvents(); if (tick < lastTick) { looped = true; } if (looped && pendingNoteEvents.isEmpty()) { commitRecordingTake(); looped = false; } lastTick = tick; } void processEvents() { if (currentRecordingTake.size() == 0 && (!stack.isEmpty())) { if (lastPart != null) { project.getEditHistoryContainer().mark(" Recording take "); if (!isDrumTake) { detachLastTake(); } project.getEditHistoryContainer().notifyEditHistoryListeners(); } } Event e = null; while ((e = stack.pop()) != null) { ShortMessage shm = e.mess; long tick = e.stamp; if (shm.getCommand() == ShortMessage.NOTE_ON || shm.getCommand() == ShortMessage.NOTE_OFF) { //Note off if (shm.getCommand() == ShortMessage.NOTE_OFF || shm.getData2() == 0) { // Generate a note event NoteEvent noteEvent = pendingNoteEvents.get(shm.getChannel() << 8 | shm.getData1()); if (noteEvent != null) { long duration = tick - noteEvent.getStartTick(); if (duration < 0) { // PJL if we hold a note after the loop end then correct the tick duration = duration + sequencer.getLoopEndPoint() - sequencer.getLoopStartPoint(); } noteEvent.setDuration(duration); pendingNoteEvents.remove(shm.getChannel() << 8 | shm.getData1()); addEventToRecordingTracks(noteEvent); } } else { //Note on pendingNoteEvents.put(shm.getChannel() << 8 | shm.getData1(), new NoteEvent((FrinikaTrackWrapper) null, tick, shm.getData1(), shm.getData2(), shm.getChannel(), 0)); } } else if (shm.getCommand() == ShortMessage.CONTROL_CHANGE) { addEventToRecordingTracks(new ControllerEvent((FrinikaTrackWrapper) null, tick, shm.getData1(), shm.getData2())); } else if (shm.getCommand() == ShortMessage.PITCH_BEND) { addEventToRecordingTracks(new PitchBendEvent((FrinikaTrackWrapper) null, tick, ((shm.getData1()) | (shm.getData2() << 7)) & 0x7fff)); } } } protected void addEventToRecordingTracks(ChannelEvent event) { currentRecordingTake.add(event); } protected void reset() { notifyTickPosition(-1); // processEvents(); assert (currentRecordingTake.size() == 0); // currentRecordingTake.clear(); lastPart = null; multiPart = null; looped = false; } public void beforeStart() { // recording = sequencer.isRecording(); reset(); for (Lane lane : project.getLanes()) { if (!(lane instanceof MidiLane)) { continue; } MidiLane ml = (MidiLane) lane; if (!ml.isRecording()) { continue; } isDrumTake = ml.isDrumLane(); } debug("Before START"); } public void start() { if (!sequencer.isRecording()) { reset(); } debug("START"); } public void stop() { // recording = false; reset(); debug("STOP"); } void debug(Object str) { System.out.println(" RECORDING: " + str); } protected class Stack { int in; int out; Event stack[]; private int size; Stack(int size) { stack = new Event[size]; for (int i = 0; i < size; i++) { stack[i] = new Event(); } out = size - 1; in = size - 1; this.size = size; } void clear() { out = size - 1; in = size - 1; } Event poke() throws Exception { in = (++in) % size; if (in == out) { throw new Exception("Recorded buffer overflow "); } Event e = stack[in]; return e; } public Event pop() { if (out == in) { return null; } out = (++out) % size; return stack[out]; } public boolean isEmpty() { return in == out; } } protected class Event { public ShortMessage mess; // = new ShortMessage(); public long stamp; MidiLane lane; } }