//////////////////////////////////////////////////////////////////////////////// // Copyright 2013 Michael Schmalle - Teoti Graphix, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License // // Author: Michael Schmalle, Principal Architect // mschmalle at teotigraphix dot com //////////////////////////////////////////////////////////////////////////////// package com.teotigraphix.caustk.pattern; import java.util.Collection; import com.sun.jna.Memory; import com.teotigraphix.caustk.application.IDispatcher; import com.teotigraphix.caustk.controller.ICaustkController; import com.teotigraphix.caustk.controller.IControllerComponent; import com.teotigraphix.caustk.controller.command.CommandUtils; import com.teotigraphix.caustk.controller.command.UndoCommand; import com.teotigraphix.caustk.core.CausticException; import com.teotigraphix.caustk.sequencer.SystemSequencer; import com.teotigraphix.caustk.system.TemporaryMemory; import com.teotigraphix.caustk.tone.BeatboxTone; import com.teotigraphix.caustk.tone.Tone; import com.teotigraphix.caustk.tone.ToneDescriptor; import com.teotigraphix.caustk.tone.components.PatternSequencerComponent; public class PatternManager implements IControllerComponent, IPatternManager { //-------------------------------------------------------------------------- // Public OSC Command API //-------------------------------------------------------------------------- /** * Queues the next pattern in the current bank, does not commit. * <p> * This pattern will be in the {@link ControllerAPI#getPendingPattern()}. * * @see OnPatternSequencerPatternChangePending * @see UndoCommand */ public static final String COMMAND_PATTERN_NEXT = "pattern_sequencer/pattern_next"; /** * Commits and plays the next queued pattern. * * @see PatternSequencerPatternPlayCommand * @see UndoCommand */ public static final String COMMAND_PATTERN_PLAY = "pattern_sequencer/pattern_play"; /** * Queues, commits and plays the next pattern synchronously. * * @see PatternSequencerPatternCommand * @see UndoCommand */ public static final String COMMAND_PATTERN = "pattern_sequencer/pattern"; public final IDispatcher getDispatcher() { return controller.getDispatcher(); } private ICaustkController controller; //---------------------------------- // bank & pattern //---------------------------------- private int nextPattern = -1; @Override public void setNextPattern(int pattern) { if (pattern == nextPattern) return; // if the sequencer is locked, return // the sequencer is locked when we are in the last beat of the pattern if (getPattern() != null) { // int beat = controller.getSystemSequencer().getCurrentBeat() + 1; // int measure = controller.getSystemSequencer().getCurrentMeasure() + 1; // int position = getPattern().getSelectedPart().getPhrase().getPosition(); // // if (beat % 4 == 1 && position == measure - 1) // last beat, last measure // // throw new RuntimeException("Pattern change locked"); } updateName(pattern); nextPattern = pattern; pendingPattern = controller.getMemoryManager().getTemporaryMemory().copyPattern(pattern); getDispatcher().trigger(new OnPatternSequencerPatternChangePending()); // if the sequencer is not playing, advance automatically. if (!controller.getSystemSequencer().isPlaying()) { playNextPattern(); } } private void updateName(int pattern) { // int bank = pattern / 16; // int index = pattern % 16; //@SuppressWarnings("unused") //String text = PatternUtils.toString(bank, index); // systemController.setDisplay(text, "main"); } @Override public void playNextPattern() { if (pendingPattern == null) return; // !!! There has to be a commitPattern() method on the temp memory // Because we want to pre setup the pattern but not actually change // it until the sequencer finishes the current pattern's length measures // TEMP for now we just do this right here until we sync into the // core's beat and measure callbacks controller.getMemoryManager().getTemporaryMemory().commit(); setPattern(pendingPattern); } @Override public Pattern playPattern(int pattern) { setNextPattern(pattern); playNextPattern(); return getPattern(); } //---------------------------------- // pattern //---------------------------------- // We should not have this ref, we should proxy straight back to // the temp memory that actually owns the instance. This way there // is no mistake when saving happens or changing, there is nothing to clean // up, only in the temp memory area private Pattern pattern; private Pattern pendingPattern; @Override public Pattern getPattern() { return pattern; } private void setPattern(Pattern value) { pendingPattern = null; Pattern oldPattern = pattern; pattern = value; getDispatcher().trigger(new OnPatternSequencerPatternChange(pattern, oldPattern)); } @Override public Pattern getPendingPattern() { return pendingPattern; } //-------------------------------------------------------------------------- // Constructor //-------------------------------------------------------------------------- public PatternManager(ICaustkController controller) { this.controller = controller; } //----------------------------------------------------------------------------- // CONFIGURATION private static PatternLocation pattern1 = new PatternLocation(0, 1); private static PatternLocation pattern2 = new PatternLocation(0, 2); private static PatternLocation currentPatternLocation = pattern1; private static PatternLocation nextPatternLocation = pattern2; /** * @see SystemSequencer#setNextPattern(int) * @see TemporaryMemory#copyPattern(int) * @see Memory#copyPattern(int) */ @Override public void configure(Pattern pattern) { // copies serialized data from disk into the pattern model. // nothing happens anywhere except inside the Pattern right now, // this the 'configure', when 'commit' is called, all settings // of the Pattern model that apply to global devices get instantly applied try { configureParts(pattern); } catch (CausticException e) { e.printStackTrace(); } queueNext(pattern); configurePartData(pattern); configureProperteis(pattern); } protected void configureProperteis(Pattern pattern) { } protected void configureParts(Pattern pattern) throws CausticException { Collection<Tone> tones = controller.getSoundSource().getTones(); if (tones.size() == 0) { // initialize the pattern set for (ToneDescriptor descriptor : pattern.getPatternItem().getToneSet().getDescriptors()) { Tone tone = controller.getSoundSource().createTone(descriptor); Part part = null; if (tone instanceof BeatboxTone) { part = new RhythmPart(pattern, tone); } else { part = new SynthPart(pattern, tone); } pattern.addPart(part); } } else { // Tones have already been created for (Tone tone : controller.getSoundSource().getTones()) { Part part = null; if (tone instanceof BeatboxTone) { part = new RhythmPart(pattern, tone); } else { part = new SynthPart(pattern, tone); } pattern.addPart(part); } } for (Part part : pattern.getParts()) { controller.getMemoryManager().getSelectedMemoryBank().copyPhrase(part, 0); controller.getMemoryManager().getSelectedMemoryBank().copyPatch(part, 0); } } protected void configurePartData(Pattern pattern) { for (Part part : pattern.getParts()) { configurePart(part); } } protected void configurePart(Part part) { if (part.isRhythm()) { } else { } } /** * @see SystemSequencer#playNextPattern() * @see TemporaryMemory#commit() */ @Override public void commit(Pattern pattern) { for (Part part : pattern.getParts()) { part.getPatch().commit(); part.getPhrase().commit(); } commitPropertySettings(pattern); } protected void commitPropertySettings(Pattern pattern) { // set the default tempo/bpm if (pattern.getPatternItem() != null) { pattern.setTempo(pattern.getPatternItem().getTempo()); } for (Part part : pattern.getParts()) { PatternSequencerComponent component = part.getTone().getComponent( PatternSequencerComponent.class); component.clearIndex(currentPatternLocation.pattern); changePosition(nextPatternLocation, part); } if (pattern.getPatternItem() != null) { pattern.setLength(pattern.getPatternItem().getLength()); } PatternLocation lastPatternLocation = currentPatternLocation; currentPatternLocation = nextPatternLocation; nextPatternLocation = lastPatternLocation; pattern.setSelectedPart(pattern.getPart(0)); } private void queueNext(Pattern pattern) { // add the phrase data for (Part part : pattern.getParts()) { // switch to edit changePosition(nextPatternLocation, part); part.getPhrase().configure(); // switch back to current changePosition(currentPatternLocation, part); } } private void changePosition(PatternLocation location, Part part) { PatternSequencerComponent component = part.getTone().getComponent( PatternSequencerComponent.class); component.setSelectedPattern(location.bank, location.pattern); } static class PatternLocation { private int bank; private int pattern; public PatternLocation(int bank, int pattern) { this.bank = bank; this.pattern = pattern; } } public static class OnPatternSequencerPatternChangePending { } public static class OnPatternSequencerPatternChange { private Pattern pattern; public final Pattern getPattern() { return pattern; } private Pattern oldPattern; public final Pattern getOldPattern() { return oldPattern; } public OnPatternSequencerPatternChange(Pattern pattern, Pattern oldPattern) { this.pattern = pattern; this.oldPattern = oldPattern; } } public static class PatternSequencerPatternNextCommand extends UndoCommand { int last = -1; @Override protected void doExecute() { // we have to use the pending pattern since this command sets // the next pending pattern and has NOT committed yet Pattern pattern = getContext().getComponent(IPatternManager.class).getPendingPattern(); if (pattern != null) last = pattern.getIndex(); int index = CommandUtils.getInteger(getContext(), 0); getContext().getComponent(IPatternManager.class).setNextPattern(index); } @Override protected void undoExecute() { if (last != -1) getContext().getComponent(IPatternManager.class).setNextPattern(last); } } public static class PatternSequencerPatternPlayCommand extends UndoCommand { @Override protected void doExecute() { getContext().getComponent(IPatternManager.class).playNextPattern(); } @Override protected void undoExecute() { // TODO What is the undo, save last pattern and instantly change back // no queing? its more like if the sequencer is playing, this // undo does not apply, will get to it later } } public static class PatternSequencerPatternCommand extends UndoCommand { int last = -1; @Override protected void doExecute() { Pattern pattern = getContext().getComponent(IPatternManager.class).getPattern(); if (pattern != null) last = pattern.getIndex(); int index = CommandUtils.getInteger(getContext(), 0); getContext().getComponent(IPatternManager.class).playPattern(index); } @Override protected void undoExecute() { if (last != -1) getContext().getComponent(IPatternManager.class).playPattern(last); } } }