summaryrefslogtreecommitdiff
path: root/src/player/opl-util.c
diff options
context:
space:
mode:
authorVito Caputo <vcaputo@pengaru.com>2017-05-26 21:51:04 -0700
committerVito Caputo <vcaputo@pengaru.com>2017-05-26 22:48:09 -0700
commit78f8fce7f286fd0c71774e2567404ed51f24fef3 (patch)
treef3de4987f7a9fc1bc03331e97b65a851b041051a /src/player/opl-util.c
*: initial commit of stripped schism stuff
Forking schism tracker's IT playback stuff into a little playback library for embedding in demos.
Diffstat (limited to 'src/player/opl-util.c')
-rw-r--r--src/player/opl-util.c134
1 files changed, 134 insertions, 0 deletions
diff --git a/src/player/opl-util.c b/src/player/opl-util.c
new file mode 100644
index 0000000..cfb32f7
--- /dev/null
+++ b/src/player/opl-util.c
@@ -0,0 +1,134 @@
+/**
+ * @file opl-util.cpp
+ * @brief Utility functions related to OPL chips.
+ *
+ * Copyright (C) 2010-2013 Adam Nielsen <malvineous@shikadi.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//Stripped down version for Schismtracker, in C.
+
+// this really should be in a header but it's only used in one other file
+int fnumToMilliHertz(unsigned int fnum, unsigned int block,
+ unsigned int conversionFactor);
+void milliHertzToFnum(unsigned int milliHertz,
+ unsigned int *fnum, unsigned int *block, unsigned int conversionFactor);
+
+
+/// Convert the given f-number and block into a note frequency.
+/**
+* @param fnum
+* Input frequency number, between 0 and 1023 inclusive. Values outside this
+* range will cause assertion failures.
+*
+* @param block
+* Input block number, between 0 and 7 inclusive. Values outside this range
+* will cause assertion failures.
+*
+* @param conversionFactor
+* Conversion factor to use. Normally will be 49716 and occasionally 50000.
+*
+* @return The converted frequency in milliHertz.
+*/
+int fnumToMilliHertz(unsigned int fnum, unsigned int block,
+ unsigned int conversionFactor)
+{
+ // Original formula
+ //return 1000 * conversionFactor * (double)fnum * pow(2, (double)((signed)block - 20));
+
+ // More efficient version
+ return (1000ull * conversionFactor * fnum) >> (20 - block);
+}
+/// Convert a frequency into an OPL f-number
+/**
+* @param milliHertz
+* Input frequency.
+*
+* @param fnum
+* Output frequency number for OPL chip. This is a 10-bit number, so it will
+* always be between 0 and 1023 inclusive.
+*
+* @param block
+* Output block number for OPL chip. This is a 3-bit number, so it will
+* always be between 0 and 7 inclusive.
+*
+* @param conversionFactor
+* Conversion factor to use. Normally will be 49716 and occasionally 50000.
+*
+* @post fnum will be set to a value between 0 and 1023 inclusive. block will
+* be set to a value between 0 and 7 inclusive. assert() calls inside this
+* function ensure this will always be the case.
+*
+* @note As the block value increases, the frequency difference between two
+* adjacent fnum values increases. This means the higher the frequency,
+* the less precision is available to represent it. Therefore, converting
+* a value to fnum/block and back to milliHertz is not guaranteed to reproduce
+* the original value.
+*/
+void milliHertzToFnum(unsigned int milliHertz,
+ unsigned int *fnum, unsigned int *block, unsigned int conversionFactor)
+{
+ // Special case to avoid divide by zero
+ if (milliHertz <= 0) {
+ *block = 0; // actually any block will work
+ *fnum = 0;
+ return;
+ }
+
+ // Special case for frequencies too high to produce
+ if (milliHertz > 6208431) {
+ *block = 7;
+ *fnum = 1023;
+ return;
+ }
+
+ /// This formula will provide a pretty good estimate as to the best block to
+ /// use for a given frequency. It tries to use the lowest possible block
+ /// number that is capable of representing the given frequency. This is
+ /// because as the block number increases, the precision decreases (i.e. there
+ /// are larger steps between adjacent note frequencies.) The 6M constant is
+ /// the largest frequency (in milliHertz) that can be represented by the
+ /// block/fnum system.
+ //int invertedBlock = log2(6208431 / milliHertz);
+
+ // Very low frequencies will produce very high inverted block numbers, but
+ // as they can all be covered by inverted block 7 (block 0) we can just clip
+ // the value.
+ //if (invertedBlock > 7) invertedBlock = 7;
+ //*block = 7 - invertedBlock;
+
+ // This is a bit more efficient and doesn't need log2() from math.h
+ if (milliHertz > 3104215) *block = 7;
+ else if (milliHertz > 1552107) *block = 6;
+ else if (milliHertz > 776053) *block = 5;
+ else if (milliHertz > 388026) *block = 4;
+ else if (milliHertz > 194013) *block = 3;
+ else if (milliHertz > 97006) *block = 2;
+ else if (milliHertz > 48503) *block = 1;
+ else *block = 0;
+
+ // Original formula
+ //*fnum = milliHertz * pow(2, 20 - *block) / 1000 / conversionFactor + 0.5;
+
+ // Slightly more efficient version
+ *fnum = ((unsigned long long)milliHertz << (20 - *block)) / (conversionFactor * 1000.0) + 0.5;
+
+ if (*fnum > 1023) {
+ (*block)++;
+ *fnum = ((unsigned long long)milliHertz << (20 - *block)) / (conversionFactor * 1000.0) + 0.5;
+ }
+
+ return;
+}
© All Rights Reserved