//////////////////////////////////////////////////////////////////////////////// // 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.sequencer; import au.com.ds.ef.EasyFlow; import au.com.ds.ef.Event; import au.com.ds.ef.FlowBuilder; import au.com.ds.ef.State; import au.com.ds.ef.StatefulContext; import au.com.ds.ef.SyncExecutor; import au.com.ds.ef.call.StateHandler; import com.teotigraphix.caustic.core.IDispatcher; public class PatternFSM { private EasyFlow<Context> flow; private Context context; //-------------------------------------------------------------------------- // States //-------------------------------------------------------------------------- // To : QUEUED_STATE // From : STOPPED_STATE private final State<Context> IDLE = FlowBuilder.state("IDLE"); // To : PLAYING_STATE, UNQUEUED_STATE // From : IDLE private final State<Context> QUEUED_STATE = FlowBuilder.state("QUEUED_STATE"); // To : UNQUEUED_STATE // From : QUEUED_STATE private final State<Context> PLAYING_STATE = FlowBuilder.state("PLAYING_STATE"); // To : QUEUED_STATE, PLAYING_STATE // From : STOPPED_STATE private final State<Context> UNQUEUED_STATE = FlowBuilder.state("UNQUEUED_STATE"); // To : IDLE // From : STOPPED_STATE private final State<Context> STOPPED_STATE = FlowBuilder.state("STOPPED_STATE"); //-------------------------------------------------------------------------- // Transition Events //-------------------------------------------------------------------------- private final Event<Context> onIdle = FlowBuilder.event("onIdle"); private final Event<Context> onQueued = FlowBuilder.event("onQueued"); private final Event<Context> onUnQueued = FlowBuilder.event("onUnQueued"); private final Event<Context> onStop = FlowBuilder.event("onStop"); private final Event<Context> onPlay = FlowBuilder.event("onPlay"); private final IDispatcher dispatcher; private int index; private int bank; /** * The pattern bank in the {@link PatternSequencer}. */ public int getBank() { return bank; } /** * The pattern index in the {@link PatternSequencer}. */ public int getIndex() { return index; } public PatternFSM(IDispatcher dispatcher, int bank, int index) { this.dispatcher = dispatcher; this.bank = bank; this.index = index; initialize(); bind(); context = new Context(); flow.executor(new SyncExecutor()).start(context); } private void initialize() { flow = FlowBuilder.from(IDLE).transit( onQueued.to(QUEUED_STATE).transit( onPlay.to(PLAYING_STATE).transit(onUnQueued.to(UNQUEUED_STATE)), onUnQueued.to(UNQUEUED_STATE).transit( onStop.to(STOPPED_STATE).transit(onIdle.to(IDLE))))); } private void bind() { IDLE.whenEnter(new StateHandler<Context>() { @Override public void call(State<Context> arg0, final Context context) throws Exception { context.idle(); dispatcher.trigger(new OnStateChange(PatternFSM.this, PatternState.IDLE)); } }); QUEUED_STATE.whenEnter(new StateHandler<Context>() { @Override public void call(State<Context> arg0, final Context context) throws Exception { context.isIdle = false; context.isQued = true; dispatcher.trigger(new OnStateChange(PatternFSM.this, PatternState.QUEUED)); } }); PLAYING_STATE.whenEnter(new StateHandler<Context>() { @Override public void call(State<Context> arg0, final Context context) throws Exception { context.isQued = false; context.isPlaying = true; dispatcher.trigger(new OnStateChange(PatternFSM.this, PatternState.PLAYING)); } }); UNQUEUED_STATE.whenEnter(new StateHandler<Context>() { @Override public void call(State<Context> arg0, final Context context) throws Exception { context.isUnQueued = true; context.isQued = false; dispatcher.trigger(new OnStateChange(PatternFSM.this, PatternState.UNQUEUED)); } }); STOPPED_STATE.whenEnter(new StateHandler<Context>() { @Override public void call(State<Context> arg0, final Context context) throws Exception { onIdle.trigger(context); context.measure = 1; dispatcher.trigger(new OnStateChange(PatternFSM.this, PatternState.STOPPED)); } }); } public void nextMeasure() { if (mode == PatternMode.LOOP) { if (context.isQueued()) { onPlay.trigger(context); } else if (context.isUnQueued()) { onStop.trigger(context); } else if (context.isPlaying()) { context.measure++; } } else { // one shot only can play when queued, if playing is stopped if (context.isQueued()) onPlay.trigger(context); else if (context.isPlaying()) { onUnQueued.trigger(context); onStop.trigger(context); } } } public void touch() { if (mode == PatternMode.LOOP) { // if is playing enqueue so the pattern queue will remove // next measure if (context.isPlaying()) { onUnQueued.trigger(context); return; } if (context.isQueued()) onUnQueued.trigger(context); else onQueued.trigger(context); } else if (mode == PatternMode.ONESHOT) { // if the onshot is playing, nothing happens since we are // using mesaures to switch on if (context.isPlaying()) { return; } if (context.isQueued()) { onUnQueued.trigger(context); onStop.trigger(context); } else { onQueued.trigger(context); } } } public static class Context extends StatefulContext { private static final long serialVersionUID = 1L; int length = 1; int measure = 0; boolean isIdle = true; boolean isPlaying = false; boolean isQued = false; boolean isUnQueued = false; boolean isQueued() { return isQued; } void idle() { isIdle = true; isPlaying = false; isQued = false; isUnQueued = false; } boolean isUnQueued() { return isUnQueued; } boolean isPlaying() { return isPlaying; } public boolean isIdle() { return isIdle; } } public boolean isQueued() { return context.isQueued(); } public boolean isUnQueued() { return context.isUnQueued(); } public boolean isPlaying() { return context.isPlaying(); } public boolean isIdle() { return context.isIdle(); } private boolean gate; public boolean isGate() { return gate; } public void setGate(boolean value) { gate = value; } private PatternMode mode = PatternMode.ONESHOT; public PatternMode getMode() { return mode; } public void setMode(PatternMode value) { mode = value; } public enum PatternMode { ONESHOT, LOOP; } public enum PatternState { IDLE, QUEUED, PLAYING, UNQUEUED, STOPPED; } public static class OnStateChange { private final PatternFSM pattern; public PatternFSM getPattern() { return pattern; } private final PatternState patternState; public PatternState getState() { return patternState; } public OnStateChange(PatternFSM pattern, PatternState patternState) { this.pattern = pattern; this.patternState = patternState; } } @Override public String toString() { return "PatternFSM[" + bank + ":" + index + "]"; } }