]> 4ch.mooo.com Git - 16.git/blob - 16/adplug/adplug/src/surroundopl.cpp
wwww~
[16.git] / 16 / adplug / adplug / src / surroundopl.cpp
1 /*
2  * Adplug - Replayer for many OPL2/OPL3 audio file formats.
3  * Copyright (C) 1999 - 2010 Simon Peter, <dn.tlp@gmx.net>, et al.
4  * 
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  * 
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  * 
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  * surroundopl.cpp - Wrapper class to provide a surround/harmonic effect
20  *   for another OPL emulator, by Adam Nielsen <malvineous@shikadi.net>
21  *
22  * Stereo harmonic algorithm by Adam Nielsen <malvineous@shikadi.net>
23  * Please give credit if you use this algorithm elsewhere :-)
24  */
25
26 #include <math.h> // for pow()
27 #include "surroundopl.h"
28 #include "debug.h"
29
30 CSurroundopl::CSurroundopl(Copl *a, Copl *b, bool use16bit)
31         : use16bit(use16bit),
32                 bufsize(4096),
33                 a(a), b(b)
34 {
35         currType = TYPE_OPL2;
36         this->lbuf = new short[this->bufsize];
37         this->rbuf = new short[this->bufsize];
38 };
39
40 CSurroundopl::~CSurroundopl()
41 {
42         delete[] this->rbuf;
43         delete[] this->lbuf;
44         delete a;
45         delete b;
46 }
47
48 void CSurroundopl::update(short *buf, int samples)
49 {
50         if (samples * 2 > this->bufsize) {
51                 // Need to realloc the buffer
52                 delete[] this->rbuf;
53                 delete[] this->lbuf;
54                 this->bufsize = samples * 2;
55                 this->lbuf = new short[this->bufsize];
56                 this->rbuf = new short[this->bufsize];
57         }
58
59         a->update(this->lbuf, samples);
60         b->update(this->rbuf, samples);
61
62         // Copy the two mono OPL buffers into the stereo buffer
63         for (int i = 0; i < samples; i++) {
64                 if (this->use16bit) {
65                         buf[i * 2] = this->lbuf[i];
66                         buf[i * 2 + 1] = this->rbuf[i];
67                 } else {
68                         ((char *)buf)[i * 2] = ((char *)this->lbuf)[i];
69                         ((char *)buf)[i * 2 + 1] = ((char *)this->rbuf)[i];
70                 }
71         }
72
73 }
74
75 // template methods
76 void CSurroundopl::write(int reg, int val)
77 {
78         a->write(reg, val);
79
80         // Transpose the other channel to produce the harmonic effect
81
82         int iChannel = -1;
83         int iRegister = reg; // temp
84         int iValue = val; // temp
85         if ((iRegister >> 4 == 0xA) || (iRegister >> 4 == 0xB)) iChannel = iRegister & 0x0F;
86
87         // Remember the FM state, so that the harmonic effect can access
88         // previously assigned register values.
89         /*if (((iRegister >> 4 == 0xB) && (iValue & 0x20) && !(this->iFMReg[iRegister] & 0x20)) ||
90                 (iRegister == 0xBD) && (
91                         ((iValue & 0x01) && !(this->iFMReg[0xBD] & 0x01))
92                 )) {
93                 this->iFMReg[iRegister] = iValue;
94         }*/
95         this->iFMReg[iRegister] = iValue;
96
97         if ((iChannel >= 0)) {// && (i == 1)) {
98                 uint8_t iBlock = (this->iFMReg[0xB0 + iChannel] >> 2) & 0x07;
99                 uint16_t iFNum = ((this->iFMReg[0xB0 + iChannel] & 0x03) << 8) | this->iFMReg[0xA0 + iChannel];
100                 //double dbOriginalFreq = 50000.0 * (double)iFNum * pow(2, iBlock - 20);
101                 double dbOriginalFreq = 49716.0 * (double)iFNum * pow(2, iBlock - 20);
102
103                 uint8_t iNewBlock = iBlock;
104                 uint16_t iNewFNum;
105
106                 // Adjust the frequency and calculate the new FNum
107                 //double dbNewFNum = (dbOriginalFreq+(dbOriginalFreq/FREQ_OFFSET)) / (50000.0 * pow(2, iNewBlock - 20));
108                 //#define calcFNum() ((dbOriginalFreq+(dbOriginalFreq/FREQ_OFFSET)) / (50000.0 * pow(2, iNewBlock - 20)))
109                 #define calcFNum() ((dbOriginalFreq+(dbOriginalFreq/FREQ_OFFSET)) / (49716.0 * pow(2, iNewBlock - 20)))
110                 double dbNewFNum = calcFNum();
111
112                 // Make sure it's in range for the OPL chip
113                 if (dbNewFNum > 1023 - NEWBLOCK_LIMIT) {
114                         // It's too high, so move up one block (octave) and recalculate
115
116                         if (iNewBlock > 6) {
117                                 // Uh oh, we're already at the highest octave!
118                                 AdPlug_LogWrite("OPL WARN: FNum %d/B#%d would need block 8+ after being transposed (new FNum is %d)\n",
119                                         iFNum, iBlock, (int)dbNewFNum);
120                                 // The best we can do here is to just play the same note out of the second OPL, so at least it shouldn't
121                                 // sound *too* bad (hopefully it will just miss out on the nice harmonic.)
122                                 iNewBlock = iBlock;
123                                 iNewFNum = iFNum;
124                         } else {
125                                 iNewBlock++;
126                                 iNewFNum = (uint16_t)calcFNum();
127                         }
128                 } else if (dbNewFNum < 0 + NEWBLOCK_LIMIT) {
129                         // It's too low, so move down one block (octave) and recalculate
130
131                         if (iNewBlock == 0) {
132                                 // Uh oh, we're already at the lowest octave!
133                                 AdPlug_LogWrite("OPL WARN: FNum %d/B#%d would need block -1 after being transposed (new FNum is %d)!\n",
134                                         iFNum, iBlock, (int)dbNewFNum);
135                                 // The best we can do here is to just play the same note out of the second OPL, so at least it shouldn't
136                                 // sound *too* bad (hopefully it will just miss out on the nice harmonic.)
137                                 iNewBlock = iBlock;
138                                 iNewFNum = iFNum;
139                         } else {
140                                 iNewBlock--;
141                                 iNewFNum = (uint16_t)calcFNum();
142                         }
143                 } else {
144                         // Original calculation is within range, use that
145                         iNewFNum = (uint16_t)dbNewFNum;
146                 }
147
148                 // Sanity check
149                 if (iNewFNum > 1023) {
150                         // Uh oh, the new FNum is still out of range! (This shouldn't happen)
151                         AdPlug_LogWrite("OPL ERR: Original note (FNum %d/B#%d is still out of range after change to FNum %d/B#%d!\n",
152                                 iFNum, iBlock, iNewFNum, iNewBlock);
153                         // The best we can do here is to just play the same note out of the second OPL, so at least it shouldn't
154                         // sound *too* bad (hopefully it will just miss out on the nice harmonic.)
155                         iNewBlock = iBlock;
156                         iNewFNum = iFNum;
157                 }
158
159                 if ((iRegister >= 0xB0) && (iRegister <= 0xB8)) {
160
161                         // Overwrite the supplied value with the new F-Number and Block.
162                         iValue = (iValue & ~0x1F) | (iNewBlock << 2) | ((iNewFNum >> 8) & 0x03);
163
164                         this->iCurrentTweakedBlock[iChannel] = iNewBlock; // save it so we don't have to update register 0xB0 later on
165                         this->iCurrentFNum[iChannel] = iNewFNum;
166
167                         if (this->iTweakedFMReg[0xA0 + iChannel] != (iNewFNum & 0xFF)) {
168                                 // Need to write out low bits
169                                 uint8_t iAdditionalReg = 0xA0 + iChannel;
170                                 uint8_t iAdditionalValue = iNewFNum & 0xFF;
171                                 b->write(iAdditionalReg, iAdditionalValue);
172                                 this->iTweakedFMReg[iAdditionalReg] = iAdditionalValue;
173                         }
174                 } else if ((iRegister >= 0xA0) && (iRegister <= 0xA8)) {
175
176                         // Overwrite the supplied value with the new F-Number.
177                         iValue = iNewFNum & 0xFF;
178
179                         // See if we need to update the block number, which is stored in a different register
180                         uint8_t iNewB0Value = (this->iFMReg[0xB0 + iChannel] & ~0x1F) | (iNewBlock << 2) | ((iNewFNum >> 8) & 0x03);
181                         if (
182                                 (iNewB0Value & 0x20) && // but only update if there's a note currently playing (otherwise we can just wait
183                                 (this->iTweakedFMReg[0xB0 + iChannel] != iNewB0Value)   // until the next noteon and update it then)
184                         ) {
185                                 AdPlug_LogWrite("OPL INFO: CH%d - FNum %d/B#%d -> FNum %d/B#%d == keyon register update!\n",
186                                         iChannel, iFNum, iBlock, iNewFNum, iNewBlock);
187                                         // The note is already playing, so we need to adjust the upper bits too
188                                         uint8_t iAdditionalReg = 0xB0 + iChannel;
189                                         b->write(iAdditionalReg, iNewB0Value);
190                                         this->iTweakedFMReg[iAdditionalReg] = iNewB0Value;
191                         } // else the note is not playing, the upper bits will be set when the note is next played
192
193                 } // if (register 0xB0 or 0xA0)
194
195         } // if (a register we're interested in)
196
197         // Now write to the original register with a possibly modified value
198         b->write(iRegister, iValue);
199         this->iTweakedFMReg[iRegister] = iValue;
200
201 };
202
203 void CSurroundopl::init() {};