//////////////////////////////////////////////////////////////////////////////// // 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.project; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; import java.util.Map; import java.util.UUID; import com.teotigraphix.caustic.core.CausticException; import com.teotigraphix.caustic.core.IDispatcher; import com.teotigraphix.caustic.internal.utils.PatternUtils; import com.teotigraphix.caustk.controller.ICaustkController; import com.teotigraphix.caustk.controller.ISerialize; import com.teotigraphix.caustk.library.LibraryPhrase; import com.teotigraphix.caustk.tone.Tone; /* What is a Track? - a channel in a rack (machine) which pattern and automation state is saved over time. A track has; - index, the current Tone index - name, the current Tone name, can be set different than the tone name temp - current beat, measure proxied from the parent Song - soft transient reference to its containing song - patterns, Map<Integer, TrackPattern> NO REFERENCE data structure - add/remove/get TrackPattern API - some type of listener API */ public class Track implements ISerialize { // the phrase map is keyed on the measure start Map<Integer, TrackItem> items = new HashMap<Integer, TrackItem>(); // A01->TrackPhrase Map<String, TrackPhrase> registry = new HashMap<String, TrackPhrase>(); //---------------------------------- // dispatcher //---------------------------------- private transient IDispatcher dispatcher; /** * The {@link TrackSong#getDispatcher()}. */ public final IDispatcher getDispatcher() { return dispatcher; } public final void setDispatcher(IDispatcher value) { dispatcher = value; } //---------------------------------- // index //---------------------------------- private int index; /** * The index within the core rack. */ public final int getIndex() { return index; } public final void setIndex(int value) { index = value; } public Tone getTone(ICaustkController controller) { return TrackUtils.getTone(controller, this); } //-------------------------------------------------------------------------- // Constructor //-------------------------------------------------------------------------- public Track() { initialize(); } public TrackPhrase copyTrackPhrase(LibraryPhrase libraryPhrase) { // TrackItem are unique and hold the start and end measures // within a Track. The TrackPhrase is created to hold the referenced note data // and pointer back to the original library phrase // ALL TrackPhrase are copied from a source LibraryPhrase. // will always create a new instance and get a bank/patter slot off the stack BankPatternSlot slot = queue.pop(); TrackPhrase trackPhrase = new TrackPhrase(); trackPhrase.setId(UUID.randomUUID()); trackPhrase.setBankIndex(slot.getBank()); trackPhrase.setPatternIndex(slot.getPattern()); trackPhrase.setNumMeasures(libraryPhrase.getLength()); trackPhrase.setNoteData(libraryPhrase.getNoteData()); String patternName = PatternUtils.toString(trackPhrase.getBankIndex(), trackPhrase.getPatternIndex()); registry.put(patternName, trackPhrase); return trackPhrase; } public void deleteTrackPhrase(TrackPhrase trackPhrase) { String patternName = PatternUtils.toString(trackPhrase.getBankIndex(), trackPhrase.getPatternIndex()); TrackPhrase phrase = registry.get(patternName); if (phrase == null) { return; } registry.remove(patternName); // remove all TrackItem ? } @SuppressWarnings("unused") private TrackItem findTrackItem(LibraryPhrase libraryPhrase) { TrackItem trackItem = items.get(libraryPhrase.getId()); return trackItem; } @SuppressWarnings("unused") private TrackPhrase findTrackPhraseById(UUID id) { for (TrackPhrase trackPhrase : registry.values()) { if (trackPhrase.getId().equals(id)) return trackPhrase; } return null; } private transient Deque<BankPatternSlot> queue; public TrackItem addPhrase(int numLoops, TrackPhrase trackPhrase) throws CausticException { int startMeasure = getNextMeasure(); return addPhraseAt(startMeasure, numLoops, trackPhrase); } /** * Returns the valid next 'last' measure. * <p> * This measure is for patterns being appended to the end of the current * last pattern's measure. */ private int getNextMeasure() { int next = 0; for (TrackItem item : items.values()) { if (item.getEndMeasure() > next) next = item.getEndMeasure(); } return next; } /** * Adds a {@link TrackPhrase} to the {@link Track} by creating a * {@link TrackItem} reference. * * @param startMeasure The measure the phrase starts on. * @param numLoops The number of times the phrase is repeated. The * {@link TrackPhrase#getNumMeasures()} is used to calculate the * number of total measures. * @param trackPhrase The phrase reference which will already have been * created and registered with the Track. This phrase will have * an index of 0-63. * @throws CausticException The start or measure span is occupied */ public TrackItem addPhraseAt(int startMeasure, int numLoops, TrackPhrase trackPhrase) throws CausticException { if (contains(startMeasure)) throw new CausticException("Track contain phrase at start: " + startMeasure); final int duration = trackPhrase.getNumMeasures() * numLoops; int endMeasure = startMeasure + duration; if (contains(startMeasure, endMeasure)) throw new CausticException("Track contain phrase at end: " + endMeasure); TrackItem item = new TrackItem(); item.setTrackPhraseId(trackPhrase.getId()); item.setStartMeasure(startMeasure); item.setEndMeasure(startMeasure + duration); item.setNumLoops(numLoops); items.put(startMeasure, item); getDispatcher().trigger(new OnTrackPhraseAdd(this, item)); return item; } private boolean contains(int startMeasure, int endMeasure) { // for (int measure = startMeasure; measure < endMeasure; measure++) { // for (TrackItem item : items.values()) { // if (item.containsInSpan(measure)) // return true; // } // } return false; } public boolean contains(int measure) { for (TrackItem item : items.values()) { if (item.contains(measure)) return true; } return false; } public void removePhrase(int startMeasure) throws CausticException { if (!items.containsKey(startMeasure)) throw new CausticException("Patterns does not contain phrase at: " + startMeasure); TrackItem item = items.remove(startMeasure); BankPatternSlot slot = new BankPatternSlot(item.getBankIndex(), item.getPatternIndex()); queue.push(slot); //getDispatcher().trigger(new OnTrackPhraseRemove(this, trackPhrase)); } public void _addPhrase(int startMeasure, int numMeasures, TrackPhrase trackPhrase) throws CausticException { // if (map.containsKey(startMeasure)) // throw new CausticException("Patterns contain phrase at: " + startMeasure); // // trackPhrase.setStartMeasure(startMeasure); // trackPhrase.setEndMeasure(startMeasure + numMeasures); // map.put(startMeasure, trackPhrase); // getDispatcher().trigger(new OnTrackPhraseAdd(this, trackPhrase)); } public void _removePhrase(TrackPhrase trackPhrase) throws CausticException { // int measure = trackPhrase.getStartMeasure(); // if (!map.containsKey(measure)) // throw new CausticException("Patterns does not contain phrase at: " + measure); // map.remove(measure); // getDispatcher().trigger(new OnTrackPhraseRemove(this, trackPhrase)); } public static class TrackEvent { private Track track; public final Track getTrack() { return track; } public TrackEvent(Track track) { this.track = track; } } public static class OnTrackPhraseAdd extends TrackEvent { private TrackItem trackItem; public OnTrackPhraseAdd(Track track, TrackItem trackItem) { super(track); this.trackItem = trackItem; } public final TrackItem getItem() { return trackItem; } } public static class OnTrackPhraseRemove extends TrackEvent { private TrackItem trackItem; public OnTrackPhraseRemove(Track track, TrackItem trackItem) { super(track); this.trackItem = trackItem; } public final TrackItem getItem() { return trackItem; } } public void clearPhrases() throws CausticException { // Collection<TrackPhrase> removed = new ArrayList<TrackPhrase>(map.values()); // Iterator<TrackPhrase> i = removed.iterator(); // while (i.hasNext()) { // TrackPhrase trackPhrase = (TrackPhrase)i.next(); // removePhrase(trackPhrase); // } } class BankPatternSlot { private int bank; private int pattern; public final int getBank() { return bank; } public final int getPattern() { return pattern; } public BankPatternSlot(int bank, int pattern) { this.bank = bank; this.pattern = pattern; } @Override public String toString() { return "[" + bank + ":" + pattern + "]"; } } private void initialize() { queue = new ArrayDeque<BankPatternSlot>(); for (int i = 0; i < 4; i++) { for (int j = 0; j < 16; j++) { TrackPhrase phrase = findPhrase(i, j); if (phrase == null) { queue.addLast(new BankPatternSlot(i, j)); } else { } } } } private TrackPhrase findPhrase(int bankIndex, int patternIndex) { for (TrackPhrase trackPhrase : registry.values()) { if (trackPhrase.getBankIndex() == bankIndex && trackPhrase.getPatternIndex() == patternIndex) { return trackPhrase; } } return null; } @Override public void sleep() { // TODO Auto-generated method stub } @Override public void wakeup(ICaustkController controller) { initialize(); } }