//////////////////////////////////////////////////////////////////////////////// // Copyright 2011 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 com.teotigraphix.caustk.application.Dispatcher; import com.teotigraphix.caustk.application.IDispatcher; /** * @author Michael Schmalle * @copyright Teoti Graphix, LLC * @since 1.0 */ public class Song { private transient IDispatcher dispatcher; public final IDispatcher getDispatcher() { return dispatcher; } //-------------------------------------------------------------------------- // // ISong API :: Properties // //-------------------------------------------------------------------------- private int bpm = 120; public final int getBPM() { return bpm; } public final void setBPM(int value) { bpm = value; } //---------------------------------- // currentMeasure //---------------------------------- private int currentMeasure = 0; /** * Returns the current measure playing in Song mode. * <p> * Note: The current bar is divisible by 4, the current measure is the sum * of all steps played currently in a song. * </p> * * @return */ public int getCurrentMeasure() { return currentMeasure; } void setCurrentMeasure(int value) { @SuppressWarnings("unused") int last = currentMeasure; currentMeasure = value; // fireMeasureChange(mCurrentMeasure, last); } /** * Returns the actual beat in the current measure. * <p> * Example; measure 4, beat 14 would be beat 2 in the measure (0 index - 3rd * beat in measure). * </p> */ public int getMeasureBeat() { return currentBeat % 4; } //---------------------------------- // currentBeat //---------------------------------- private int currentBeat = 0; /** * Return the ISong current beat. */ public int getCurrentBeat() { return currentBeat; } void setCurrentBeat(int value) { setCurrentBeat(value, false); } void setCurrentBeat(int value, boolean seeking) { int last = currentBeat; currentBeat = value; // fireBeatChange(mCurrentBeat, last); if (last < value) { // forward if (currentBeat == 0) { setCurrentMeasure(0); } else { int remainder = currentBeat % 4; if (seeking) { setCurrentMeasure(currentBeat / 4); } else if (remainder == 0) { setCurrentMeasure(currentMeasure + 1); } } } else if (last > value) { // reverse // if the last beat was a measure change, decrement measure int remainder = last % 4; if (remainder == 0) { setCurrentMeasure(currentMeasure - 1); } } } // the song doesn't know this since it's abstract, the track song // would know it's measures and beats since it adds track patterns // to it's tracks public int getNumBeats() { return -1; } public int getNumMeasures() { return -1; } /** * Returns the total time in seconds. */ public int getTotalTime() { return -1; } public int getCurrentTime() { return -1; } //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- public Song() { dispatcher = new Dispatcher(); } //-------------------------------------------------------------------------- // // ISong API :: Methods // //-------------------------------------------------------------------------- /** * Enables the playhead. * <p> * Calling play dispatches signals without advancing the playhead. This is * useful for starting a song at o beat and 0 measure. * </p> */ public void play() { setCurrentBeat(currentBeat); } /** * Rewinds the playhead to the start of the song, beat 0. */ @SuppressWarnings("unused") public void rewind() { int lastBeat = currentBeat; int lastMeasure = currentMeasure; currentBeat = -1; currentMeasure = -1; // fireBeatChange(mCurrentBeat, lastBeat); // fireMeasureChange(mCurrentMeasure, lastMeasure); } /** * Moves playhead to next beat based on song implementation. */ public void nextBeat() { setCurrentBeat(currentBeat + 1); } /** * Moves playhead to previous beat based on song implementation. */ public void previousBeat() { setCurrentBeat(currentBeat - 1); } /** * Moves playhead to next measure based on song implementation. */ public void nextMeasure() { // TODO use seek() and calc it // think about this though, do you want beat signals or not??? // if so, just loop this, if not use seek() and only he measure // signal will fire // start with a next beat since we might be on a 0 beat of the measure nextBeat(); if (getMeasureBeat() == 0) return; nextBeat(); if (getMeasureBeat() == 0) return; nextBeat(); if (getMeasureBeat() == 0) return; nextBeat(); if (getMeasureBeat() == 0) return; nextBeat(); if (getMeasureBeat() == 0) return; } /** * Moves playhead to previous measure based on song implementation. */ public void previousMeasure() { // start with a previous beat since we might be on a 0 beat of the measure previousBeat(); if (getMeasureBeat() == 0) return; previousBeat(); if (getMeasureBeat() == 0) return; previousBeat(); if (getMeasureBeat() == 0) return; previousBeat(); if (getMeasureBeat() == 0) return; previousBeat(); if (getMeasureBeat() == 0) return; } /** * Moves the playhead a number of beats. * * @param beats The beats to rewind. */ public void seek(int beat) { setCurrentBeat(beat, true); } /** * Moves the playhead to the end of the song data. */ //void forward(); }