aboutsummaryrefslogtreecommitdiff
path: root/audio/softsynth/mt32/LA32Ramp.cpp
diff options
context:
space:
mode:
authorMatthew Hoops2012-03-20 14:18:57 -0400
committerMatthew Hoops2012-03-20 14:49:16 -0400
commit71756bdf4eae5ba9cc3f329b85e894f04640aaef (patch)
tree40d464262da107ab5eed82f198685209161ebac1 /audio/softsynth/mt32/LA32Ramp.cpp
parent03eba05b09e5c9e5a351f8111185934b92a3fed3 (diff)
parent3c3576a224b92c703b4e8ea20008ac8a069980dd (diff)
downloadscummvm-rg350-71756bdf4eae5ba9cc3f329b85e894f04640aaef.tar.gz
scummvm-rg350-71756bdf4eae5ba9cc3f329b85e894f04640aaef.tar.bz2
scummvm-rg350-71756bdf4eae5ba9cc3f329b85e894f04640aaef.zip
Merge remote branch 'upstream/master' into pegasus
Diffstat (limited to 'audio/softsynth/mt32/LA32Ramp.cpp')
-rw-r--r--audio/softsynth/mt32/LA32Ramp.cpp150
1 files changed, 150 insertions, 0 deletions
diff --git a/audio/softsynth/mt32/LA32Ramp.cpp b/audio/softsynth/mt32/LA32Ramp.cpp
new file mode 100644
index 0000000000..9f1f01c3c2
--- /dev/null
+++ b/audio/softsynth/mt32/LA32Ramp.cpp
@@ -0,0 +1,150 @@
+/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
+ * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+Some notes on this class:
+
+This emulates the LA-32's implementation of "ramps". A ramp in this context is a smooth transition from one value to another, handled entirely within the LA-32.
+The LA-32 provides this feature for amplitude and filter cutoff values.
+
+The 8095 starts ramps on the LA-32 by setting two values in memory-mapped registers:
+
+(1) The target value (between 0 and 255) for the ramp to end on. This is represented by the "target" argument to startRamp().
+(2) The speed at which that value should be approached. This is represented by the "increment" argument to startRamp().
+
+Once the ramp target value has been hit, the LA-32 raises an interrupt.
+
+Note that the starting point of the ramp is whatever internal value the LA-32 had when the registers were set. This is usually the end point of a previously completed ramp.
+
+Our handling of the "target" and "increment" values is based on sample analysis and a little guesswork.
+Here's what we're pretty confident about:
+ - The most significant bit of "increment" indicates the direction that the LA32's current internal value ("current" in our emulation) should change in.
+ Set means downward, clear means upward.
+ - The lower 7 bits of "increment" indicate how quickly "current" should be changed.
+ - If "increment" is 0, no change to "current" is made and no interrupt is raised. [SEMI-CONFIRMED by sample analysis]
+ - Otherwise, if the MSb is set:
+ - If "current" already corresponds to a value <= "target", "current" is set immediately to the equivalent of "target" and an interrupt is raised.
+ - Otherwise, "current" is gradually reduced (at a rate determined by the lower 7 bits of "increment"), and once it reaches the equivalent of "target" an interrupt is raised.
+ - Otherwise (the MSb is unset):
+ - If "current" already corresponds to a value >= "target", "current" is set immediately to the equivalent of "target" and an interrupt is raised.
+ - Otherwise, "current" is gradually increased (at a rate determined by the lower 7 bits of "increment"), and once it reaches the equivalent of "target" an interrupt is raised.
+
+We haven't fully explored:
+ - Values when ramping between levels (though this is probably correct).
+ - Transition timing (may not be 100% accurate, especially for very fast ramps).
+*/
+//#include <cmath>
+
+#include "mt32emu.h"
+#include "LA32Ramp.h"
+#include "mmath.h"
+
+namespace MT32Emu {
+
+// SEMI-CONFIRMED from sample analysis.
+const int TARGET_MULT = 0x40000;
+const unsigned int MAX_CURRENT = 0xFF * TARGET_MULT;
+
+// We simulate the delay in handling "target was reached" interrupts by waiting
+// this many samples before setting interruptRaised.
+// FIXME: This should vary with the sample rate, but doesn't.
+// SEMI-CONFIRMED: Since this involves asynchronous activity between the LA32
+// and the 8095, a good value is hard to pin down.
+// This one matches observed behaviour on a few digital captures I had handy,
+// and should be double-checked. We may also need a more sophisticated delay
+// scheme eventually.
+const int INTERRUPT_TIME = 7;
+
+LA32Ramp::LA32Ramp() :
+ current(0),
+ largeTarget(0),
+ largeIncrement(0),
+ interruptCountdown(0),
+ interruptRaised(false) {
+}
+
+void LA32Ramp::startRamp(Bit8u target, Bit8u increment) {
+ // CONFIRMED: From sample analysis, this appears to be very accurate.
+ // FIXME: We could use a table for this in future
+ if (increment == 0) {
+ largeIncrement = 0;
+ } else {
+ largeIncrement = (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f);
+ }
+ descending = (increment & 0x80) != 0;
+ if (descending) {
+ // CONFIRMED: From sample analysis, descending increments are slightly faster
+ largeIncrement++;
+ }
+
+ largeTarget = target * TARGET_MULT;
+ interruptCountdown = 0;
+ interruptRaised = false;
+}
+
+Bit32u LA32Ramp::nextValue() {
+ if (interruptCountdown > 0) {
+ if (--interruptCountdown == 0) {
+ interruptRaised = true;
+ }
+ } else if (largeIncrement != 0) {
+ // CONFIRMED from sample analysis: When increment is 0, the LA32 does *not* change the current value at all (and of course doesn't fire an interrupt).
+ if (descending) {
+ // Lowering current value
+ if (largeIncrement > current) {
+ current = largeTarget;
+ interruptCountdown = INTERRUPT_TIME;
+ } else {
+ current -= largeIncrement;
+ if (current <= largeTarget) {
+ current = largeTarget;
+ interruptCountdown = INTERRUPT_TIME;
+ }
+ }
+ } else {
+ // Raising current value
+ if (MAX_CURRENT - current < largeIncrement) {
+ current = largeTarget;
+ interruptCountdown = INTERRUPT_TIME;
+ } else {
+ current += largeIncrement;
+ if (current >= largeTarget) {
+ current = largeTarget;
+ interruptCountdown = INTERRUPT_TIME;
+ }
+ }
+ }
+ }
+ return current;
+}
+
+bool LA32Ramp::checkInterrupt() {
+ bool wasRaised = interruptRaised;
+ interruptRaised = false;
+ return wasRaised;
+}
+
+void LA32Ramp::reset() {
+ current = 0;
+ largeTarget = 0;
+ largeIncrement = 0;
+ descending = false;
+ interruptCountdown = 0;
+ interruptRaised = false;
+}
+
+}