aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/com/jsyn/util/VoiceAllocator.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/jsyn/util/VoiceAllocator.java')
-rw-r--r--src/main/java/com/jsyn/util/VoiceAllocator.java258
1 files changed, 258 insertions, 0 deletions
diff --git a/src/main/java/com/jsyn/util/VoiceAllocator.java b/src/main/java/com/jsyn/util/VoiceAllocator.java
new file mode 100644
index 0000000..f20f7a5
--- /dev/null
+++ b/src/main/java/com/jsyn/util/VoiceAllocator.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2011 Phil Burk, Mobileer Inc
+ *
+ * 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.
+ */
+
+package com.jsyn.util;
+
+import com.jsyn.Synthesizer;
+import com.jsyn.unitgen.UnitVoice;
+import com.softsynth.shared.time.ScheduledCommand;
+import com.softsynth.shared.time.TimeStamp;
+
+/**
+ * Allocate voices based on an integer tag. The tag could, for example, be a MIDI note number. Or a
+ * tag could be an int that always increments. Use the same tag to refer to a voice for noteOn() and
+ * noteOff(). If no new voices are available then a voice in use will be stolen.
+ *
+ * @author Phil Burk (C) 2011 Mobileer Inc
+ */
+public class VoiceAllocator implements Instrument {
+ private int maxVoices;
+ private VoiceTracker[] trackers;
+ private long tick;
+ private Synthesizer synthesizer;
+ private static final int UNASSIGNED_PRESET = -1;
+ private int mPresetIndex = UNASSIGNED_PRESET;
+
+ /**
+ * Create an allocator for the array of UnitVoices. The array must be full of instantiated
+ * UnitVoices that are connected to some kind of mixer.
+ *
+ * @param voices
+ */
+ public VoiceAllocator(UnitVoice[] voices) {
+ maxVoices = voices.length;
+ trackers = new VoiceTracker[maxVoices];
+ for (int i = 0; i < maxVoices; i++) {
+ trackers[i] = new VoiceTracker();
+ trackers[i].voice = voices[i];
+ }
+ }
+
+ public Synthesizer getSynthesizer() {
+ if (synthesizer == null) {
+ synthesizer = trackers[0].voice.getUnitGenerator().getSynthesizer();
+ }
+ return synthesizer;
+ }
+
+ private class VoiceTracker {
+ UnitVoice voice;
+ int tag = -1;
+ int presetIndex = UNASSIGNED_PRESET;
+ long when;
+ boolean on;
+
+ public void off() {
+ on = false;
+ when = tick++;
+ }
+ }
+
+ /**
+ * @return number of UnitVoices passed to the allocator.
+ */
+ public int getVoiceCount() {
+ return maxVoices;
+ }
+
+ private VoiceTracker findVoice(int tag) {
+ for (VoiceTracker tracker : trackers) {
+ if (tracker.tag == tag) {
+ return tracker;
+ }
+ }
+ return null;
+ }
+
+ private VoiceTracker stealVoice() {
+ VoiceTracker bestOff = null;
+ VoiceTracker bestOn = null;
+ for (VoiceTracker tracker : trackers) {
+ if (tracker.voice == null) {
+ return tracker;
+ }
+ // If we have a bestOff voice then don't even bother with on voices.
+ else if (bestOff != null) {
+ // Older off voice?
+ if (!tracker.on && (tracker.when < bestOff.when)) {
+ bestOff = tracker;
+ }
+ } else if (tracker.on) {
+ if (bestOn == null) {
+ bestOn = tracker;
+ } else if (tracker.when < bestOn.when) {
+ bestOn = tracker;
+ }
+ } else {
+ bestOff = tracker;
+ }
+ }
+ if (bestOff != null) {
+ return bestOff;
+ } else {
+ return bestOn;
+ }
+ }
+
+ /**
+ * Allocate a Voice associated with this tag. It will first pick a voice already assigned to
+ * that tag. Next it will pick the oldest voice that is off. Next it will pick the oldest voice
+ * that is on. If you are using timestamps to play the voice in the future then you should use
+ * the noteOn() noteOff() and setPort() methods.
+ *
+ * @param tag
+ * @return Voice that is most available.
+ */
+ protected synchronized UnitVoice allocate(int tag) {
+ VoiceTracker tracker = allocateTracker(tag);
+ return tracker.voice;
+ }
+
+ private VoiceTracker allocateTracker(int tag) {
+ VoiceTracker tracker = findVoice(tag);
+ if (tracker == null) {
+ tracker = stealVoice();
+ }
+ tracker.tag = tag;
+ tracker.when = tick++;
+ tracker.on = true;
+ return tracker;
+ }
+
+ protected synchronized boolean isOn(int tag) {
+ VoiceTracker tracker = findVoice(tag);
+ if (tracker != null) {
+ return tracker.on;
+ }
+ return false;
+ }
+
+ protected synchronized UnitVoice off(int tag) {
+ VoiceTracker tracker = findVoice(tag);
+ if (tracker != null) {
+ tracker.off();
+ return tracker.voice;
+ }
+ return null;
+ }
+
+ /** Turn off all the note currently on. */
+ @Override
+ public void allNotesOff(TimeStamp timeStamp) {
+ getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() {
+ @Override
+ public void run() {
+ for (VoiceTracker tracker : trackers) {
+ if (tracker.on) {
+ tracker.voice.noteOff(getSynthesizer().createTimeStamp());
+ tracker.off();
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Play a note on the voice and associate it with the given tag. if needed a new voice will be
+ * allocated and an old voice may be turned off.
+ */
+ @Override
+ public void noteOn(final int tag, final double frequency, final double amplitude,
+ TimeStamp timeStamp) {
+ getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() {
+ @Override
+ public void run() {
+ VoiceTracker voiceTracker = allocateTracker(tag);
+ if (voiceTracker.presetIndex != mPresetIndex) {
+ voiceTracker.voice.usePreset(mPresetIndex);
+ voiceTracker.presetIndex = mPresetIndex;
+ }
+ voiceTracker.voice.noteOn(frequency, amplitude, getSynthesizer().createTimeStamp());
+ }
+ });
+ }
+
+ /**
+ * Play a note on the voice and associate it with the given tag. if needed a new voice will be
+ * allocated and an old voice may be turned off.
+ * Apply an operation to the voice.
+ */
+ public void noteOn(final int tag,
+ final double frequency,
+ final double amplitude,
+ final VoiceOperation operation,
+ TimeStamp timeStamp) {
+ getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() {
+ @Override
+ public void run() {
+ VoiceTracker voiceTracker = allocateTracker(tag);
+ operation.operate(voiceTracker.voice);
+ voiceTracker.voice.noteOn(frequency, amplitude, getSynthesizer().createTimeStamp());
+ }
+ });
+ }
+
+ /** Turn off the voice associated with the given tag if allocated. */
+ @Override
+ public void noteOff(final int tag, TimeStamp timeStamp) {
+ getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() {
+ @Override
+ public void run() {
+ VoiceTracker voiceTracker = findVoice(tag);
+ if (voiceTracker != null) {
+ voiceTracker.voice.noteOff(getSynthesizer().createTimeStamp());
+ off(tag);
+ }
+ }
+ });
+ }
+
+ /** Set a port on the voice associated with the given tag if allocated. */
+ @Override
+ public void setPort(final int tag, final String portName, final double value,
+ TimeStamp timeStamp) {
+ getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() {
+ @Override
+ public void run() {
+ VoiceTracker voiceTracker = findVoice(tag);
+ if (voiceTracker != null) {
+ voiceTracker.voice.setPort(portName, value, getSynthesizer().createTimeStamp());
+ }
+ }
+ });
+ }
+
+ @Override
+ public void usePreset(final int presetIndex, TimeStamp timeStamp) {
+ getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() {
+ @Override
+ public void run() {
+ mPresetIndex = presetIndex;
+ }
+ });
+ }
+
+}