1 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
2  * Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
3  *
4  *  This program is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU Lesser General Public License as published by
6  *  the Free Software Foundation, either version 2.1 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU Lesser General Public License for more details.
13  *
14  *  You should have received a copy of the GNU Lesser General Public License
15  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /*
19 Some notes on this class:
20 
21 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.
22 The LA-32 provides this feature for amplitude and filter cutoff values.
23 
24 The 8095 starts ramps on the LA-32 by setting two values in memory-mapped registers:
25 
26 (1) The target value (between 0 and 255) for the ramp to end on. This is represented by the "target" argument to startRamp().
27 (2) The speed at which that value should be approached. This is represented by the "increment" argument to startRamp().
28 
29 Once the ramp target value has been hit, the LA-32 raises an interrupt.
30 
31 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.
32 
33 Our handling of the "target" and "increment" values is based on sample analysis and a little guesswork.
34 Here's what we're pretty confident about:
35  - The most significant bit of "increment" indicates the direction that the LA32's current internal value ("current" in our emulation) should change in.
36    Set means downward, clear means upward.
37  - The lower 7 bits of "increment" indicate how quickly "current" should be changed.
38  - If "increment" is 0, no change to "current" is made and no interrupt is raised. [SEMI-CONFIRMED by sample analysis]
39  - Otherwise, if the MSb is set:
40     - If "current" already corresponds to a value <= "target", "current" is set immediately to the equivalent of "target" and an interrupt is raised.
41     - 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.
42  - Otherwise (the MSb is unset):
43     - If "current" already corresponds to a value >= "target", "current" is set immediately to the equivalent of "target" and an interrupt is raised.
44     - 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.
45 
46 We haven't fully explored:
47  - Values when ramping between levels (though this is probably correct).
48  - Transition timing (may not be 100% accurate, especially for very fast ramps).
49 */
50 
51 #include "internals.h"
52 
53 #include "LA32Ramp.h"
54 #include "Tables.h"
55 
56 namespace MT32Emu {
57 
58 // SEMI-CONFIRMED from sample analysis.
59 const unsigned int TARGET_SHIFTS = 18;
60 const unsigned int MAX_CURRENT = 0xFF << TARGET_SHIFTS;
61 
62 // We simulate the delay in handling "target was reached" interrupts by waiting
63 // this many samples before setting interruptRaised.
64 // FIXME: This should vary with the sample rate, but doesn't.
65 // SEMI-CONFIRMED: Since this involves asynchronous activity between the LA32
66 // and the 8095, a good value is hard to pin down.
67 // This one matches observed behaviour on a few digital captures I had handy,
68 // and should be double-checked. We may also need a more sophisticated delay
69 // scheme eventually.
70 const int INTERRUPT_TIME = 7;
71 
LA32Ramp()72 LA32Ramp::LA32Ramp() :
73 	current(0),
74 	largeTarget(0),
75 	largeIncrement(0),
76 	interruptCountdown(0),
77 	interruptRaised(false) {
78 }
79 
startRamp(Bit8u target,Bit8u increment)80 void LA32Ramp::startRamp(Bit8u target, Bit8u increment) {
81 	// CONFIRMED: From sample analysis, this appears to be very accurate.
82 	if (increment == 0) {
83 		largeIncrement = 0;
84 	} else {
85 		// Three bits in the fractional part, no need to interpolate
86 		// (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f)
87 		Bit32u expArg = increment & 0x7F;
88 		largeIncrement = 8191 - Tables::getInstance().exp9[~(expArg << 6) & 511];
89 		largeIncrement <<= expArg >> 3;
90 		largeIncrement += 64;
91 		largeIncrement >>= 9;
92 	}
93 	descending = (increment & 0x80) != 0;
94 	if (descending) {
95 		// CONFIRMED: From sample analysis, descending increments are slightly faster
96 		largeIncrement++;
97 	}
98 
99 	largeTarget = target << TARGET_SHIFTS;
100 	interruptCountdown = 0;
101 	interruptRaised = false;
102 }
103 
nextValue()104 Bit32u LA32Ramp::nextValue() {
105 	if (interruptCountdown > 0) {
106 		if (--interruptCountdown == 0) {
107 			interruptRaised = true;
108 		}
109 	} else if (largeIncrement != 0) {
110 		// 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).
111 		if (descending) {
112 			// Lowering current value
113 			if (largeIncrement > current) {
114 				current = largeTarget;
115 				interruptCountdown = INTERRUPT_TIME;
116 			} else {
117 				current -= largeIncrement;
118 				if (current <= largeTarget) {
119 					current = largeTarget;
120 					interruptCountdown = INTERRUPT_TIME;
121 				}
122 			}
123 		} else {
124 			// Raising current value
125 			if (MAX_CURRENT - current < largeIncrement) {
126 				current = largeTarget;
127 				interruptCountdown = INTERRUPT_TIME;
128 			} else {
129 				current += largeIncrement;
130 				if (current >= largeTarget) {
131 					current = largeTarget;
132 					interruptCountdown = INTERRUPT_TIME;
133 				}
134 			}
135 		}
136 	}
137 	return current;
138 }
139 
checkInterrupt()140 bool LA32Ramp::checkInterrupt() {
141 	bool wasRaised = interruptRaised;
142 	interruptRaised = false;
143 	return wasRaised;
144 }
145 
reset()146 void LA32Ramp::reset() {
147 	current = 0;
148 	largeTarget = 0;
149 	largeIncrement = 0;
150 	descending = false;
151 	interruptCountdown = 0;
152 	interruptRaised = false;
153 }
154 
155 // This is actually beyond the LA32 ramp interface.
156 // Instead of polling the current value, MCU receives an interrupt when a ramp completes.
157 // However, this is a simple way to work around the specific behaviour of TVA
158 // when in sustain phase which one normally wants to avoid.
159 // See TVA::recalcSustain() for details.
isBelowCurrent(Bit8u target) const160 bool LA32Ramp::isBelowCurrent(Bit8u target) const {
161 	return Bit32u(target << TARGET_SHIFTS) < current;
162 }
163 
164 } // namespace MT32Emu
165