DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/mt32/LA32Ramp.cpp
00001 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
00002  * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
00003  *
00004  *  This program is free software: you can redistribute it and/or modify
00005  *  it under the terms of the GNU Lesser General Public License as published by
00006  *  the Free Software Foundation, either version 2.1 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU Lesser General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU Lesser General Public License
00015  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
00016  */
00017 
00018 /*
00019 Some notes on this class:
00020 
00021 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.
00022 The LA-32 provides this feature for amplitude and filter cutoff values.
00023 
00024 The 8095 starts ramps on the LA-32 by setting two values in memory-mapped registers:
00025 
00026 (1) The target value (between 0 and 255) for the ramp to end on. This is represented by the "target" argument to startRamp().
00027 (2) The speed at which that value should be approached. This is represented by the "increment" argument to startRamp().
00028 
00029 Once the ramp target value has been hit, the LA-32 raises an interrupt.
00030 
00031 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.
00032 
00033 Our handling of the "target" and "increment" values is based on sample analysis and a little guesswork.
00034 Here's what we're pretty confident about:
00035  - The most significant bit of "increment" indicates the direction that the LA32's current internal value ("current" in our emulation) should change in.
00036    Set means downward, clear means upward.
00037  - The lower 7 bits of "increment" indicate how quickly "current" should be changed.
00038  - If "increment" is 0, no change to "current" is made and no interrupt is raised. [SEMI-CONFIRMED by sample analysis]
00039  - Otherwise, if the MSb is set:
00040     - If "current" already corresponds to a value <= "target", "current" is set immediately to the equivalent of "target" and an interrupt is raised.
00041     - 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.
00042  - Otherwise (the MSb is unset):
00043     - If "current" already corresponds to a value >= "target", "current" is set immediately to the equivalent of "target" and an interrupt is raised.
00044     - 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.
00045 
00046 We haven't fully explored:
00047  - Values when ramping between levels (though this is probably correct).
00048  - Transition timing (may not be 100% accurate, especially for very fast ramps).
00049 */
00050 #include <cmath>
00051 
00052 #include "mt32emu.h"
00053 #include "LA32Ramp.h"
00054 #include "mmath.h"
00055 
00056 namespace MT32Emu {
00057 
00058 // SEMI-CONFIRMED from sample analysis.
00059 const int TARGET_MULT = 0x40000;
00060 const unsigned int MAX_CURRENT = 0xFF * TARGET_MULT;
00061 
00062 // We simulate the delay in handling "target was reached" interrupts by waiting
00063 // this many samples before setting interruptRaised.
00064 // FIXME: This should vary with the sample rate, but doesn't.
00065 // SEMI-CONFIRMED: Since this involves asynchronous activity between the LA32
00066 // and the 8095, a good value is hard to pin down.
00067 // This one matches observed behaviour on a few digital captures I had handy,
00068 // and should be double-checked. We may also need a more sophisticated delay
00069 // scheme eventually.
00070 const int INTERRUPT_TIME = 7;
00071 
00072 LA32Ramp::LA32Ramp() :
00073         current(0),
00074         largeTarget(0),
00075         largeIncrement(0),
00076         interruptCountdown(0),
00077         interruptRaised(false) {
00078 }
00079 
00080 void LA32Ramp::startRamp(Bit8u target, Bit8u increment) {
00081         // CONFIRMED: From sample analysis, this appears to be very accurate.
00082         if (increment == 0) {
00083                 largeIncrement = 0;
00084         } else {
00085                 // Three bits in the fractional part, no need to interpolate
00086                 // (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f)
00087                 Bit32u expArg = increment & 0x7F;
00088                 largeIncrement = 8191 - Tables::getInstance().exp9[~(expArg << 6) & 511];
00089                 largeIncrement <<= expArg >> 3;
00090                 largeIncrement += 64;
00091                 largeIncrement >>= 9;
00092         }
00093         descending = (increment & 0x80) != 0;
00094         if (descending) {
00095                 // CONFIRMED: From sample analysis, descending increments are slightly faster
00096                 largeIncrement++;
00097         }
00098 
00099         largeTarget = target * TARGET_MULT;
00100         interruptCountdown = 0;
00101         interruptRaised = false;
00102 }
00103 
00104 Bit32u LA32Ramp::nextValue() {
00105         if (interruptCountdown > 0) {
00106                 if (--interruptCountdown == 0) {
00107                         interruptRaised = true;
00108                 }
00109         } else if (largeIncrement != 0) {
00110                 // 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).
00111                 if (descending) {
00112                         // Lowering current value
00113                         if (largeIncrement > current) {
00114                                 current = largeTarget;
00115                                 interruptCountdown = INTERRUPT_TIME;
00116                         } else {
00117                                 current -= largeIncrement;
00118                                 if (current <= largeTarget) {
00119                                         current = largeTarget;
00120                                         interruptCountdown = INTERRUPT_TIME;
00121                                 }
00122                         }
00123                 } else {
00124                         // Raising current value
00125                         if (MAX_CURRENT - current < largeIncrement) {
00126                                 current = largeTarget;
00127                                 interruptCountdown = INTERRUPT_TIME;
00128                         } else {
00129                                 current += largeIncrement;
00130                                 if (current >= largeTarget) {
00131                                         current = largeTarget;
00132                                         interruptCountdown = INTERRUPT_TIME;
00133                                 }
00134                         }
00135                 }
00136         }
00137         return current;
00138 }
00139 
00140 bool LA32Ramp::checkInterrupt() {
00141         bool wasRaised = interruptRaised;
00142         interruptRaised = false;
00143         return wasRaised;
00144 }
00145 
00146 void LA32Ramp::reset() {
00147         current = 0;
00148         largeTarget = 0;
00149         largeIncrement = 0;
00150         descending = false;
00151         interruptCountdown = 0;
00152         interruptRaised = false;
00153 }
00154 
00155 
00156 void LA32Ramp::saveState( std::ostream &stream )
00157 {
00158         stream.write(reinterpret_cast<const char*>(&current), sizeof(current) );
00159         stream.write(reinterpret_cast<const char*>(&largeTarget), sizeof(largeTarget) );
00160         stream.write(reinterpret_cast<const char*>(&largeIncrement), sizeof(largeIncrement) );
00161         stream.write(reinterpret_cast<const char*>(&descending), sizeof(descending) );
00162         stream.write(reinterpret_cast<const char*>(&interruptCountdown), sizeof(interruptCountdown) );
00163         stream.write(reinterpret_cast<const char*>(&interruptRaised), sizeof(interruptRaised) );
00164 }
00165 
00166 
00167 void LA32Ramp::loadState( std::istream &stream )
00168 {
00169         stream.read(reinterpret_cast<char*>(&current), sizeof(current) );
00170         stream.read(reinterpret_cast<char*>(&largeTarget), sizeof(largeTarget) );
00171         stream.read(reinterpret_cast<char*>(&largeIncrement), sizeof(largeIncrement) );
00172         stream.read(reinterpret_cast<char*>(&descending), sizeof(descending) );
00173         stream.read(reinterpret_cast<char*>(&interruptCountdown), sizeof(interruptCountdown) );
00174         stream.read(reinterpret_cast<char*>(&interruptRaised), sizeof(interruptRaised) );
00175 }
00176 
00177 }