////////////////////////////////////////////////////////////////////////////////
// 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 java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.teotigraphix.caustk.application.IDispatcher;
import com.teotigraphix.caustk.controller.ICaustkController;
import com.teotigraphix.caustk.core.CausticException;
import com.teotigraphix.caustk.library.LibraryPhrase;
import com.teotigraphix.caustk.service.ISerialize;
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, ChannelPhrase channel) throws CausticException {
int startMeasure = getNextMeasure();
return addPhraseAt(startMeasure, numLoops, channel);
}
/**
* 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, ChannelPhrase channelPhrase)
throws CausticException {
if (contains(startMeasure))
throw new CausticException("Track contain phrase at start: " + startMeasure);
final int duration = channelPhrase.getLength() * numLoops;
int endMeasure = startMeasure + duration;
if (contains(startMeasure, endMeasure))
throw new CausticException("Track contain phrase at end: " + endMeasure);
TrackItem item = new TrackItem();
item.setTrackIndex(getIndex());
item.setNumMeasures(channelPhrase.getLength());
item.setPhraseId(channelPhrase.getId());
item.setStartMeasure(startMeasure);
item.setBankIndex(channelPhrase.getBankIndex());
item.setPatternIndex(channelPhrase.getPatternIndex());
item.setEndMeasure(startMeasure + duration);
item.setNumLoops(numLoops);
items.put(startMeasure, item);
getDispatcher().trigger(new OnTrackPhraseAdd(this, item));
return item;
}
public TrackItem getTrackItem(int measure) {
return items.get(measure);
}
public TrackItem findTrackItem(int startMeasure, int length) {
for (int i = startMeasure; i < startMeasure + length; i++) {
for (TrackItem item : items.values()) {
if (items.containsKey(i))
return item;
}
}
return null;
}
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 List<TrackItem> getItemsOnMeasure(int measure) {
List<TrackItem> result = new ArrayList<TrackItem>();
for (TrackItem item : items.values()) {
if (item.contains(measure))
result.add(item);
}
return result;
}
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 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() {
}
@Override
public void wakeup(ICaustkController controller) {
//initialize();
for (TrackItem item : items.values()) {
item.wakeup(controller);
}
}
}