summaryrefslogtreecommitdiff
path: root/plugins/adplug/adplug/surroundopl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/adplug/adplug/surroundopl.cpp')
-rw-r--r--plugins/adplug/adplug/surroundopl.cpp203
1 files changed, 203 insertions, 0 deletions
diff --git a/plugins/adplug/adplug/surroundopl.cpp b/plugins/adplug/adplug/surroundopl.cpp
new file mode 100644
index 00000000..14e67e21
--- /dev/null
+++ b/plugins/adplug/adplug/surroundopl.cpp
@@ -0,0 +1,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() {};