////////////////////////////////////////////////////////////////////////////////
// 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.system.bank;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.teotigraphix.caustic.core.IMemento;
import com.teotigraphix.caustic.internal.utils.PatternUtils;
import com.teotigraphix.caustic.osc.OutputPanelMessage;
import com.teotigraphix.caustic.osc.PatternSequencerMessage;
import com.teotigraphix.caustic.osc.RackMessage;
import com.teotigraphix.caustk.controller.ICaustkController;
import com.teotigraphix.caustk.tone.BasslineTone;
import com.teotigraphix.caustk.tone.BeatboxTone;
import com.teotigraphix.caustk.tone.PCMSynthTone;
import com.teotigraphix.caustk.tone.SubSynthTone;
import com.teotigraphix.caustk.tone.Tone;
import com.teotigraphix.caustk.tone.ToneDescriptor;
import com.teotigraphix.caustk.tone.ToneType;
/**
* The {@link MemoryLoader} will load a .caustic file from disk load it into
* memory unserializing all machine patterns into banks of presets.
* <p>
* Future versions could actually load new banks from the server at runtime, but
* the user could not be in the middle of writing a song, the rack needs to be
* reinitialized during this type of loading operations.
*/
public class MemoryLoader {
private Map<Integer, MemoryDescriptor> map = new HashMap<Integer, MemoryDescriptor>();
private ICaustkController controller;
private OnPatchCopy onPatchCopyListener;
public ICaustkController getController() {
return controller;
}
//--------------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------------
public MemoryLoader(ICaustkController controller) {
this.controller = controller;
}
/**
* Returns the {@link MemoryDescriptor} at the specified index.
*
* @param index The index of the descriptor.
*/
public MemoryDescriptor getDescriptor(int index) {
return map.get(index);
}
/**
* Loads a <code>.caustic</code> file from disk using the information
* provided in the {@link MemoryDescriptor}.
* <p>
* This method will populate the descriptor's public API with data
* unserialized from the <code>.caustic</code> song file.
*
* @param descriptor The {@link MemoryDescriptor} used to load the bank.
* @throws IOException
*/
public void load(MemoryDescriptor descriptor, List<ToneDescriptor> tones) throws IOException {
// if the file is not null, this descriptor has not been created
// the patterns, phrases and patches are not in the collections, load it
if (descriptor instanceof CausticFileMemoryDescriptor) {
loadDescriptor((CausticFileMemoryDescriptor)descriptor, tones);
}
addDescriptor(descriptor);
}
private void loadDescriptor(CausticFileMemoryDescriptor descriptor, List<ToneDescriptor> tones)
throws IOException {
File file = descriptor.getFile();
if (!file.exists())
throw new IllegalArgumentException("MemoryBank not found; " + file.getPath());
// clear the rack of any data
// XXX Should I do this here? seems out of place
RackMessage.BLANKRACK.send(controller);
// load the file into the rack.
RackMessage.LOAD_SONG.send(controller, descriptor.getFile().getPath());
// XXX Can I do better than this?
if (tones == null) {
tones = new ArrayList<ToneDescriptor>();
// pre load the descriptors
for (int i = 0; i < 6; i++) {
String name = RackMessage.QUERY_MACHINE_NAME.queryString(controller, i);
String type = RackMessage.QUERY_MACHINE_TYPE.queryString(controller, i);
if (name != null && !name.isEmpty() && type != null && !type.isEmpty()) {
tones.add(new ToneDescriptor(i, name, ToneType.fromString(type)));
}
}
}
List<PatternItem> patterns = new ArrayList<PatternItem>();
List<PhraseItem> phrases = new ArrayList<PhraseItem>();
List<PatchItem> patches = new ArrayList<PatchItem>();
// patternIndex[0-63] [PatternData][PhraseData]
// each pattern is the 'same' pattern in each machine
// for each tone. All the machine patterns get added to
// the main Pattern as phrases, total possible phrases for
// a Pattern is 6, 2 phrases each for the BL1
Map<String, PatternItem> map = new HashMap<String, PatternItem>();
// now we loop through all patterns
for (ToneDescriptor toneDescriptor : tones) {
int toneIndex = toneDescriptor.getIndex();
String name = RackMessage.QUERY_MACHINE_NAME.queryString(controller, toneIndex);
String type = RackMessage.QUERY_MACHINE_TYPE.queryString(controller, toneIndex);
ToneType toneType = ToneType.fromString(type);
if (toneDescriptor.getToneType() != toneType)
throw new RuntimeException("Tone ToneType not equal to loaded tone");
//------------------------------------------------------------------
// Load Patch
//------------------------------------------------------------------
// create the Patch for the Tone preset
PatchItem item = createPatch(name, toneIndex, toneType);
patches.add(item);
// get the patterns
String result = PatternSequencerMessage.QUERY_PATTERNS_WITH_DATA.queryString(
controller, toneIndex);
for (String patternName : result.split(" ")) {
int bankIndex = PatternUtils.toBank(patternName);
int patternIndex = PatternUtils.toPattern(patternName);
// set the current bank and pattern of the machine to query
// the string pattern data
PatternSequencerMessage.BANK.send(controller, toneIndex, bankIndex);
PatternSequencerMessage.PATTERN.send(controller, toneIndex, patternIndex);
PatternItem pattern = null;
//----------------------------------------------------------------
// Load Pattern
//----------------------------------------------------------------
if (descriptor.fullLoad) {
// load one phrase per pattern; load ALL patterns
// as caustic machine patterns
int length = (int)PatternSequencerMessage.NUM_MEASURES.query(controller,
toneIndex);
int tempo = (int)OutputPanelMessage.BPM.query(controller);
pattern = new PatternItem(patternName, length, tempo);
pattern.setIndex(toneIndex);
patterns.add(pattern);
} else {
// add the list of tone patterns to the map
pattern = findPattern(map, patternName, toneIndex);
if (!patterns.contains(pattern))
patterns.add(pattern);
}
//----------------------------------------------------------------
// Load Phrase
//----------------------------------------------------------------
// save pattern properties
PhraseItem phraseItem = createPhrase(pattern, toneIndex, patches.get(toneIndex));
phrases.add(phraseItem);
pattern.addPhraseItem(phraseItem);
}
}
descriptor.setToneDescriptors(tones);
descriptor.setPatternItems(patterns);
descriptor.setPhraseItems(phrases);
descriptor.setPatchItems(patches);
}
private PatchItem createPatch(String name, int toneIndex, ToneType machineType)
throws IOException {
String id = UUID.randomUUID().toString();
PatchItem item = new PatchItem(name, id);
item.setIndex(toneIndex);
IMemento memento = copyData(toneIndex, machineType, id, name);
item.setMemento(memento);
return item;
}
private PatternItem findPattern(Map<String, PatternItem> map, String patternName, int toneIndex) {
// add the list of tone patterns to the map
PatternItem pattern = map.get(patternName);
if (pattern == null) {
int length = (int)PatternSequencerMessage.NUM_MEASURES.query(controller, toneIndex);
int tempo = (int)OutputPanelMessage.BPM.query(controller);
pattern = new PatternItem(patternName, length, tempo);
map.put(patternName, pattern);
}
return pattern;
}
private PhraseItem createPhrase(PatternItem pattern, int toneIndex, PatchItem patch) {
// return the pattern data as a string
String data = PatternSequencerMessage.QUERY_NOTE_DATA.queryString(controller, toneIndex);
PhraseItem phraseItem = new PhraseItem(data, pattern.getLength(), patch);
phraseItem.setIndex(toneIndex);
return phraseItem;
}
private void addDescriptor(MemoryDescriptor descriptor) {
descriptor.setIndex(map.size());
map.put(descriptor.getIndex(), descriptor);
}
//--------------------------------------------------------------------------
//
//--------------------------------------------------------------------------
public IMemento copyData(int machineIndex, ToneType machineType, String id, String name)
throws IOException {
throw new RuntimeException("FIX SINCE YOU MESSED IT UP");
// Machine machine = createMachine(machineIndex, machineType, id, name);
//
// IMemento memento = XMLMemento.createWriteRoot("patch");
//
// switch (machineType) {
// case BASSLINE:
// copyBassline(controller, (Bassline)machine, memento);
// break;
// case BEATBOX:
// copyBeatbox(controller, (Beatbox)machine, memento);
// break;
// case PCMSYNTH:
// copyPCMSynth(controller, (PCMSynth)machine, memento);
// break;
// case SUBSYNTH:
// copySubSynth(controller, (SubSynth)machine, memento);
// break;
// }
//
// copyMixerChannel(controller, machine, memento);
// copyEffectChannel(controller, machine, memento);
//
// machine.copy(memento);
//
// if (onPatchCopyListener != null)
// onPatchCopyListener.onPatchCopy(machine);
//
// machine.setEngine(null);
//
// return memento;
}
void copyBassline(ICaustkController controller, BasslineTone tone, IMemento memento) {
//machine.getSequencer().restore();
tone.getOsc1().restore();
tone.getVolume().restore();
tone.getFilter().restore();
tone.getLFO1().restore();
tone.getDistortion().restore();
}
void copySubSynth(ICaustkController controller, SubSynthTone tone, IMemento memento) {
//machine.getSequencer().restore();
tone.getFilter().restore();
tone.getLFO1().restore();
tone.getLFO2().restore();
tone.getOsc1().restore();
tone.getOsc2().restore();
tone.getVolume().restore();
}
void copyPCMSynth(ICaustkController controller, PCMSynthTone tone, IMemento memento) {
//machine.getSequencer().restore();
tone.getFilter().restore();
tone.getLFO1().restore();
tone.getTuner().restore();
tone.getSampler().restore();
tone.getVolume().restore();
}
void copyBeatbox(ICaustkController controller, BeatboxTone tone, IMemento memento) {
//machine.getSequencer().restore();
tone.getSampler().restore();
tone.getVolume().restore();
}
private void copyMixerChannel(ICaustkController controller, Tone machine, IMemento memento) {
MixerPanel panel = (MixerPanel)controller.getFactory().createMixerPanel();
panel.setEngine(controller);
// panel.addMachine(machine);
MixerDelay delay = (MixerDelay)controller.getFactory().createMixerEffect(panel,
MixerEffectType.DELAY);
MixerReverb reverb = (MixerReverb)controller.getFactory().createMixerEffect(panel,
MixerEffectType.REVERB);
delay.setDevice(panel);
reverb.setDevice(panel);
panel.setDelay(delay);
panel.setReverb(reverb);
panel.restore();
// panel.copyChannel(machine, memento);
}
private void copyEffectChannel(ICaustkController controller, Tone machine, IMemento memento) {
EffectsRack effectsRack = (EffectsRack)controller.getFactory().createEffectRack();
effectsRack.setEngine(controller);
// effectsRack.addMachine(machine);
effectsRack.restore();
// effectsRack.copyChannel(machine, memento);
}
public void setOnPatchCopyListener(OnPatchCopy l) {
onPatchCopyListener = l;
}
public interface OnPatchCopy {
void onPatchCopy(IMachine machine);
}
}