diff options
author | Phil Burk <[email protected]> | 2014-12-30 16:53:03 -0800 |
---|---|---|
committer | Phil Burk <[email protected]> | 2014-12-30 16:53:03 -0800 |
commit | 534969d42ca5168d645678345cd21242fe41f389 (patch) | |
tree | e8f5d1cba1ec57685e76ceb923d8da25a7846cfb /src/com/jsyn/util/VoiceAllocator.java | |
parent | a4d8ca95178d2e3acfc3299a4b73e84c2646d24e (diff) |
Initial commit of code.
Diffstat (limited to 'src/com/jsyn/util/VoiceAllocator.java')
-rw-r--r-- | src/com/jsyn/util/VoiceAllocator.java | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/src/com/jsyn/util/VoiceAllocator.java b/src/com/jsyn/util/VoiceAllocator.java new file mode 100644 index 0000000..af37b91 --- /dev/null +++ b/src/com/jsyn/util/VoiceAllocator.java @@ -0,0 +1,238 @@ +/* + * 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 int presetIndex = -1; + + /** + * 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 = -1; + 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 (presetIndex != voiceTracker.presetIndex) { + voiceTracker.voice.usePreset(presetIndex); + } + 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() { + for (VoiceTracker tracker : trackers) { + tracker.voice.usePreset(presetIndex); + } + } + }); + } + +} |