summaryrefslogtreecommitdiff
path: root/plugins/adplug/adplug/surroundopl.cpp
blob: 14e67e21fba4164ff85cda9eeabdbd41bab3ff8a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/*
 * Adplug - Replayer for many OPL2/OPL3 audio file formats.
 * Copyright (C) 1999 - 2010 Simon Peter, <dn.tlp@gmx.net>, et al.
 * 
 * This library 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 library 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 library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * surroundopl.cpp - Wrapper class to provide a surround/harmonic effect
 *   for another OPL emulator, by Adam Nielsen <malvineous@shikadi.net>
 *
 * Stereo harmonic algorithm by Adam Nielsen <malvineous@shikadi.net>
 * Please give credit if you use this algorithm elsewhere :-)
 */

#include <math.h> // for pow()
#include "surroundopl.h"
#include "debug.h"

CSurroundopl::CSurroundopl(Copl *a, Copl *b, bool use16bit)
	: use16bit(use16bit),
		bufsize(4096),
		a(a), b(b)
{
	currType = TYPE_OPL2;
	this->lbuf = new short[this->bufsize];
	this->rbuf = new short[this->bufsize];
};

CSurroundopl::~CSurroundopl()
{
	delete[] this->rbuf;
	delete[] this->lbuf;
	delete a;
	delete b;
}

void CSurroundopl::update(short *buf, int samples)
{
	if (samples * 2 > this->bufsize) {
		// Need to realloc the buffer
		delete[] this->rbuf;
		delete[] this->lbuf;
		this->bufsize = samples * 2;
		this->lbuf = new short[this->bufsize];
		this->rbuf = new short[this->bufsize];
	}

	a->update(this->lbuf, samples);
	b->update(this->rbuf, samples);

	// Copy the two mono OPL buffers into the stereo buffer
	for (int i = 0; i < samples; i++) {
		if (this->use16bit) {
			buf[i * 2] = this->lbuf[i];
			buf[i * 2 + 1] = this->rbuf[i];
		} else {
			((char *)buf)[i * 2] = ((char *)this->lbuf)[i];
			((char *)buf)[i * 2 + 1] = ((char *)this->rbuf)[i];
		}
	}

}

// template methods
void CSurroundopl::write(int reg, int val)
{
	a->write(reg, val);

	// Transpose the other channel to produce the harmonic effect

	int iChannel = -1;
	int iRegister = reg; // temp
	int iValue = val; // temp
	if ((iRegister >> 4 == 0xA) || (iRegister >> 4 == 0xB)) iChannel = iRegister & 0x0F;

	// Remember the FM state, so that the harmonic effect can access
	// previously assigned register values.
	/*if (((iRegister >> 4 == 0xB) && (iValue & 0x20) && !(this->iFMReg[iRegister] & 0x20)) ||
		(iRegister == 0xBD) && (
			((iValue & 0x01) && !(this->iFMReg[0xBD] & 0x01))
		)) {
		this->iFMReg[iRegister] = iValue;
	}*/
	this->iFMReg[iRegister] = iValue;

	if ((iChannel >= 0)) {// && (i == 1)) {
		uint8_t iBlock = (this->iFMReg[0xB0 + iChannel] >> 2) & 0x07;
		uint16_t iFNum = ((this->iFMReg[0xB0 + iChannel] & 0x03) << 8) | this->iFMReg[0xA0 + iChannel];
		//double dbOriginalFreq = 50000.0 * (double)iFNum * pow(2, iBlock - 20);
		double dbOriginalFreq = 49716.0 * (double)iFNum * pow(2, iBlock - 20);

		uint8_t iNewBlock = iBlock;
		uint16_t iNewFNum;

		// Adjust the frequency and calculate the new FNum
		//double dbNewFNum = (dbOriginalFreq+(dbOriginalFreq/FREQ_OFFSET)) / (50000.0 * pow(2, iNewBlock - 20));
		//#define calcFNum() ((dbOriginalFreq+(dbOriginalFreq/FREQ_OFFSET)) / (50000.0 * pow(2, iNewBlock - 20)))
		#define calcFNum() ((dbOriginalFreq+(dbOriginalFreq/FREQ_OFFSET)) / (49716.0 * pow(2, iNewBlock - 20)))
		double dbNewFNum = calcFNum();

		// Make sure it's in range for the OPL chip
		if (dbNewFNum > 1023 - NEWBLOCK_LIMIT) {
			// It's too high, so move up one block (octave) and recalculate

			if (iNewBlock > 6) {
				// Uh oh, we're already at the highest octave!
				AdPlug_LogWrite("OPL WARN: FNum %d/B#%d would need block 8+ after being transposed (new FNum is %d)\n",
					iFNum, iBlock, (int)dbNewFNum);
				// The best we can do here is to just play the same note out of the second OPL, so at least it shouldn't
				// sound *too* bad (hopefully it will just miss out on the nice harmonic.)
				iNewBlock = iBlock;
				iNewFNum = iFNum;
			} else {
				iNewBlock++;
				iNewFNum = (uint16_t)calcFNum();
			}
		} else if (dbNewFNum < 0 + NEWBLOCK_LIMIT) {
			// It's too low, so move down one block (octave) and recalculate

			if (iNewBlock == 0) {
				// Uh oh, we're already at the lowest octave!
				AdPlug_LogWrite("OPL WARN: FNum %d/B#%d would need block -1 after being transposed (new FNum is %d)!\n",
					iFNum, iBlock, (int)dbNewFNum);
				// The best we can do here is to just play the same note out of the second OPL, so at least it shouldn't
				// sound *too* bad (hopefully it will just miss out on the nice harmonic.)
				iNewBlock = iBlock;
				iNewFNum = iFNum;
			} else {
				iNewBlock--;
				iNewFNum = (uint16_t)calcFNum();
			}
		} else {
			// Original calculation is within range, use that
			iNewFNum = (uint16_t)dbNewFNum;
		}

		// Sanity check
		if (iNewFNum > 1023) {
			// Uh oh, the new FNum is still out of range! (This shouldn't happen)
			AdPlug_LogWrite("OPL ERR: Original note (FNum %d/B#%d is still out of range after change to FNum %d/B#%d!\n",
				iFNum, iBlock, iNewFNum, iNewBlock);
			// The best we can do here is to just play the same note out of the second OPL, so at least it shouldn't
			// sound *too* bad (hopefully it will just miss out on the nice harmonic.)
			iNewBlock = iBlock;
			iNewFNum = iFNum;
		}

		if ((iRegister >= 0xB0) && (iRegister <= 0xB8)) {

			// Overwrite the supplied value with the new F-Number and Block.
			iValue = (iValue & ~0x1F) | (iNewBlock << 2) | ((iNewFNum >> 8) & 0x03);

			this->iCurrentTweakedBlock[iChannel] = iNewBlock; // save it so we don't have to update register 0xB0 later on
			this->iCurrentFNum[iChannel] = iNewFNum;

			if (this->iTweakedFMReg[0xA0 + iChannel] != (iNewFNum & 0xFF)) {
				// Need to write out low bits
				uint8_t iAdditionalReg = 0xA0 + iChannel;
				uint8_t iAdditionalValue = iNewFNum & 0xFF;
				b->write(iAdditionalReg, iAdditionalValue);
				this->iTweakedFMReg[iAdditionalReg] = iAdditionalValue;
			}
		} else if ((iRegister >= 0xA0) && (iRegister <= 0xA8)) {

			// Overwrite the supplied value with the new F-Number.
			iValue = iNewFNum & 0xFF;

			// See if we need to update the block number, which is stored in a different register
			uint8_t iNewB0Value = (this->iFMReg[0xB0 + iChannel] & ~0x1F) | (iNewBlock << 2) | ((iNewFNum >> 8) & 0x03);
			if (
				(iNewB0Value & 0x20) && // but only update if there's a note currently playing (otherwise we can just wait
				(this->iTweakedFMReg[0xB0 + iChannel] != iNewB0Value)   // until the next noteon and update it then)
			) {
				AdPlug_LogWrite("OPL INFO: CH%d - FNum %d/B#%d -> FNum %d/B#%d == keyon register update!\n",
					iChannel, iFNum, iBlock, iNewFNum, iNewBlock);
					// The note is already playing, so we need to adjust the upper bits too
					uint8_t iAdditionalReg = 0xB0 + iChannel;
					b->write(iAdditionalReg, iNewB0Value);
					this->iTweakedFMReg[iAdditionalReg] = iNewB0Value;
			} // else the note is not playing, the upper bits will be set when the note is next played

		} // if (register 0xB0 or 0xA0)

	} // if (a register we're interested in)

	// Now write to the original register with a possibly modified value
	b->write(iRegister, iValue);
	this->iTweakedFMReg[iRegister] = iValue;

};

void CSurroundopl::init() {};