////////////////////////////////////////////////////////////////////////////////
// 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.sound;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.androidtransfuse.event.EventObserver;
import org.apache.commons.io.FileUtils;
import com.teotigraphix.caustk.application.Dispatcher;
import com.teotigraphix.caustk.application.IDispatcher;
import com.teotigraphix.caustk.controller.ICaustkController;
import com.teotigraphix.caustk.controller.SubControllerBase;
import com.teotigraphix.caustk.controller.SubControllerModel;
import com.teotigraphix.caustk.core.CausticException;
import com.teotigraphix.caustk.core.osc.RackMessage;
import com.teotigraphix.caustk.library.LibraryScene;
import com.teotigraphix.caustk.library.SoundMixerState;
import com.teotigraphix.caustk.tone.BasslineTone;
import com.teotigraphix.caustk.tone.BeatboxTone;
import com.teotigraphix.caustk.tone.EightBitSynth;
import com.teotigraphix.caustk.tone.FMSynthTone;
import com.teotigraphix.caustk.tone.ModularTone;
import com.teotigraphix.caustk.tone.OrganTone;
import com.teotigraphix.caustk.tone.PCMSynthTone;
import com.teotigraphix.caustk.tone.PadSynthTone;
import com.teotigraphix.caustk.tone.SubSynthTone;
import com.teotigraphix.caustk.tone.Tone;
import com.teotigraphix.caustk.tone.ToneDescriptor;
import com.teotigraphix.caustk.tone.ToneType;
import com.teotigraphix.caustk.tone.ToneUtils;
import com.teotigraphix.caustk.tone.VocoderTone;
import com.teotigraphix.caustk.utils.RuntimeUtils;
public class SoundSource extends SubControllerBase implements ISoundSource {
private int maxNumTones = 14;
@Override
protected Class<? extends SubControllerModel> getModelType() {
return SoundSourceModel.class;
}
SoundSourceModel getModel() {
return (SoundSourceModel)getInternalModel();
}
//--------------------------------------------------------------------------
// Public Property API
//--------------------------------------------------------------------------
//----------------------------------
// dispatcher
//----------------------------------
private final IDispatcher dispatcher;
@Override
public IDispatcher getDispatcher() {
return dispatcher;
}
//----------------------------------
// transpose
//----------------------------------
private int transpose;
@Override
public int getTranspose() {
return transpose;
}
@Override
public void setTranspose(int value) {
transpose = value;
}
//----------------------------------
// tones
//----------------------------------
@Override
public int getToneCount() {
return getModel().getTones().size();
}
@Override
public Collection<Tone> getTones() {
return Collections.unmodifiableCollection(getModel().getTones().values());
}
@Override
public Tone getTone(int index) {
return getModel().getTones().get(index);
}
@Override
public Tone getToneByName(String value) {
for (Tone tone : getModel().getTones().values()) {
if (tone.getName().equals(value))
return tone;
}
return null;
}
public SoundSource(ICaustkController controller) {
super(controller);
dispatcher = new Dispatcher();
getDispatcher().register(OnSoundSourceInitialValue.class,
new EventObserver<OnSoundSourceInitialValue>() {
@Override
public void trigger(OnSoundSourceInitialValue object) {
System.out.println("Original value:" + object.getValue());
}
});
}
//--------------------------------------------------------------------------
// Public Method API
//--------------------------------------------------------------------------
@Override
public List<Tone> findToneStartsWith(String name) {
List<Tone> result = new ArrayList<Tone>();
for (Tone tone : getTones()) {
if (tone.getName().startsWith(name))
result.add(tone);
}
return result;
}
@Override
public void createScene(LibraryScene scene) throws CausticException {
// make tones
for (ToneDescriptor descriptor : scene.getSoundSourceState().getDescriptors().values()) {
createTone(descriptor);
}
SoundMixerState mixerState = scene.getSoundMixerState();
SoundMixerModel model = getController().getSerializeService().fromString(
mixerState.getData(), SoundMixerModel.class);
model.update();
}
@SuppressWarnings("unchecked")
@Override
public <T extends Tone> T createTone(String data) throws CausticException {
Tone tone = null;
try {
tone = getController().getSerializeService().fromString(data,
ToneUtils.getToneClass(data));
} catch (IOException e) {
e.printStackTrace();
}
int index = nextIndex();
tone.setIndex(index);
RackMessage.CREATE.send(getController(), tone.getToneType().getValue(), tone.getName(),
index);
toneAdd(index, tone);
return (T)tone;
}
@SuppressWarnings("unchecked")
@Override
public <T extends Tone> T createTone(int index, String name, Class<? extends Tone> toneClass)
throws CausticException {
T tone = null;
try {
Constructor<? extends Tone> constructor = toneClass
.getConstructor(ICaustkController.class);
tone = (T)constructor.newInstance(getController());
initializeTone(tone, name, tone.getToneType(), index);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
SoundSourceUtils.setup(toneClass.cast(tone));
RackMessage.CREATE.send(getController(), tone.getToneType().getValue(), tone.getName(),
tone.getIndex());
toneAdd(index, tone);
return tone;
}
@Override
public <T extends Tone> T createTone(String name, Class<? extends Tone> toneClass)
throws CausticException {
return createTone(nextIndex(), name, toneClass);
}
@Override
public Tone createTone(String name, ToneType toneType) throws CausticException {
return createTone(new ToneDescriptor(nextIndex(), name, toneType));
}
@Override
public Tone createTone(int index, String name, ToneType toneType) throws CausticException {
return createTone(new ToneDescriptor(index, name, toneType));
}
@Override
public Tone createTone(ToneDescriptor descriptor) throws CausticException {
Tone tone = createSynthChannel(nextIndex(), descriptor.getName(), descriptor.getToneType());
return tone;
}
@Override
public void destroyTone(int index) {
destroyTone(getTone(index));
}
public void destroyTone(Tone tone) {
int index = tone.getIndex();
RackMessage.REMOVE.send(getController(), index);
toneRemove(tone);
}
@Override
public void clearAndReset() {
getController().getDispatcher().trigger(new OnSoundSourceClear());
ArrayList<Tone> remove = new ArrayList<Tone>(getModel().getTones().values());
for (Tone tone : remove) {
//RackMessage.REMOVE.send(getController(), tone.getIndex());
toneRemove(tone);
}
RackMessage.BLANKRACK.send(getController());
getController().getDispatcher().trigger(new OnSoundSourceReset());
}
//--------------------------------------------------------------------------
// Protected Method API
//--------------------------------------------------------------------------
Tone createSynthChannel(int index, String toneName, ToneType toneType) throws CausticException {
if (index > 13)
throw new CausticException("Only 14 machines allowed in a rack");
if (getModel().getTones().containsKey(index))
throw new CausticException("{" + index + "} tone is already defined");
RackMessage.CREATE.send(getController(), toneType.getValue(), toneName, index);
Tone tone = null;
switch (toneType) {
case Bassline:
tone = new BasslineTone(getController());
initializeTone(tone, toneName, toneType, index);
SoundSourceUtils.setup((BasslineTone)tone);
break;
case Beatbox:
tone = new BeatboxTone(getController());
initializeTone(tone, toneName, toneType, index);
SoundSourceUtils.setup((BeatboxTone)tone);
break;
case PCMSynth:
tone = new PCMSynthTone(getController());
initializeTone(tone, toneName, toneType, index);
SoundSourceUtils.setup((PCMSynthTone)tone);
break;
case SubSynth:
tone = new SubSynthTone(getController());
initializeTone(tone, toneName, toneType, index);
SoundSourceUtils.setup((SubSynthTone)tone);
break;
case PadSynth:
tone = new PadSynthTone(getController());
initializeTone(tone, toneName, toneType, index);
SoundSourceUtils.setup((PadSynthTone)tone);
break;
case Organ:
tone = new OrganTone(getController());
initializeTone(tone, toneName, toneType, index);
SoundSourceUtils.setup((OrganTone)tone);
break;
case Vocoder:
tone = new VocoderTone(getController());
initializeTone(tone, toneName, toneType, index);
SoundSourceUtils.setup((VocoderTone)tone);
break;
case EightBitSynth:
tone = new EightBitSynth(getController());
initializeTone(tone, toneName, toneType, index);
SoundSourceUtils.setup((EightBitSynth)tone);
break;
case Modular:
tone = new ModularTone(getController());
initializeTone(tone, toneName, toneType, index);
SoundSourceUtils.setup((ModularTone)tone);
break;
case FMSynth:
tone = new FMSynthTone(getController());
initializeTone(tone, toneName, toneType, index);
SoundSourceUtils.setup((FMSynthTone)tone);
break;
default:
break;
}
toneAdd(index, tone);
return tone;
}
private void initializeTone(Tone tone, String toneName, ToneType toneType, int index) {
tone.setId(UUID.randomUUID());
tone.setName(toneName);
tone.setIndex(index);
}
private void toneAdd(int index, Tone tone) {
getModel().getTones().put(index, tone);
getController().getDispatcher().trigger(new OnSoundSourceToneAdd(tone));
}
private void toneRemove(Tone tone) {
getModel().getTones().remove(tone.getIndex());
getController().getDispatcher().trigger(new OnSoundSourceToneRemove(tone));
}
//--------------------------------------------------------------------------
// Public Observer API
//--------------------------------------------------------------------------
public static class OnSoundSourceToneAdd {
private Tone tone;
public Tone getTone() {
return tone;
}
public OnSoundSourceToneAdd(Tone tone) {
this.tone = tone;
}
}
public static class OnSoundSourceToneRemove {
private Tone tone;
public Tone getTone() {
return tone;
}
public OnSoundSourceToneRemove(Tone tone) {
this.tone = tone;
}
}
public static class OnSoundSourceInitialValue {
private Object value;
public Object getValue() {
return value;
}
public OnSoundSourceInitialValue(Object value) {
this.value = value;
}
}
public static class OnSoundSourceInitialValueReset {
}
public static class OnSoundSourceClear {
}
public static class OnSoundSourceReset {
}
//--------------------------------------------------------------------------
// Public Observer
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Private Methods
//--------------------------------------------------------------------------
private int nextIndex() {
int index = 0;
for (index = 0; index < 15; index++) {
if (!getModel().getTones().containsKey(index))
break;
}
return index;
}
@Override
public void loadSong(File causticFile) throws CausticException {
clearAndReset();
RackMessage.LOAD_SONG.send(getController(), causticFile.getAbsolutePath());
restore();
getController().getSoundMixer().restore();
getDispatcher().trigger(new OnSoundSourceSongLoad());
}
@Override
public File saveSong(String name) {
RackMessage.SAVE_SONG.send(getController(), name);
return RuntimeUtils.getCausticSongFile(name);
}
@Override
public File saveSongAs(File file) throws IOException {
File song = saveSong(file.getName().replace(".caustic", ""));
FileUtils.copyFileToDirectory(song, file.getParentFile(), true);
song.delete();
return file;
}
@Override
public void restore() {
for (int i = 0; i < maxNumTones; i++) {
String name = RackMessage.QUERY_MACHINE_NAME.queryString(getController(), i);
String type = RackMessage.QUERY_MACHINE_TYPE.queryString(getController(), i);
if (name == null || name.equals(""))
continue;
ToneType toneType = ToneType.fromString(type);
Tone tone = null;
try {
tone = createTone(i, name, toneType);
} catch (CausticException e) {
e.printStackTrace();
}
tone.restore();
}
}
}