summaryrefslogtreecommitdiff
path: root/src/player/snd_fm.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/snd_fm.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/snd_fm.c')
-rw-r--r--src/player/snd_fm.c385
1 files changed, 385 insertions, 0 deletions
diff --git a/src/player/snd_fm.c b/src/player/snd_fm.c
new file mode 100644
index 0000000..4ee6db8
--- /dev/null
+++ b/src/player/snd_fm.c
@@ -0,0 +1,385 @@
+/*
+ * Schism Tracker - a cross-platform Impulse Tracker clone
+ * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
+ * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
+ * copyright (c) 2009 Storlek & Mrs. Brisby
+ * copyright (c) 2010-2012 Storlek
+ * URL: http://schismtracker.org/
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "fmopl.h"
+#include "snd_fm.h"
+
+#define MAX_VOICES 256 /* Must not be less than the setting in sndfile.h */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define OPLNew(x,r) ym3812_init(x, r)
+#define OPLResetChip ym3812_reset_chip
+#define OPLWrite ym3812_write
+#define OPLUpdateOne ym3812_update_one
+#define OPLClose ym3812_shutdown
+
+/* Mostly pulled from my posterior. Original value was 2000, but Manwe says that's too quiet.
+It'd help if this was at all connected to the song's mixing volume...
+*/
+#define OPL_VOLUME 5000
+
+/*
+The documentation in this file regarding the output ports,
+including the comment "Don't ask me why", are attributed
+to Jeffrey S. Lee's article:
+ Programming the AdLib/Sound Blaster
+ FM Music Chips
+ Version 2.0 (24 Feb 1992)
+*/
+
+static const int oplbase = 0x388;
+
+// OPL info
+static struct OPL* opl = NULL;
+static UINT32 oplretval = 0,
+ oplregno = 0;
+static UINT32 fm_active = 0;
+
+extern int fnumToMilliHertz(unsigned int fnum, unsigned int block,
+ unsigned int conversionFactor);
+
+extern void milliHertzToFnum(unsigned int milliHertz,
+ unsigned int *fnum, unsigned int *block, unsigned int conversionFactor);
+
+
+static void Fmdrv_Outportb(unsigned port, unsigned value)
+{
+ if (opl == NULL ||
+ ((int) port) < oplbase ||
+ ((int) port) >= oplbase + 4)
+ return;
+
+ unsigned ind = port - oplbase;
+ OPLWrite(opl, ind, value);
+
+ if (ind & 1) {
+ if (oplregno == 4) {
+ if (value == 0x80)
+ oplretval = 0x02;
+ else if (value == 0x21)
+ oplretval = 0xC0;
+ }
+ }
+ else
+ oplregno = value;
+}
+
+
+static unsigned char Fmdrv_Inportb(unsigned port)
+{
+ return (((int) port) >= oplbase &&
+ ((int) port) < oplbase + 4) ? oplretval : 0;
+}
+
+
+void Fmdrv_Init(int mixfreq)
+{
+ if (opl != NULL) {
+ OPLClose(opl);
+ opl = NULL;
+ }
+ //Clock for frequency 49716Hz. Mixfreq is used for output mix frequency.
+ opl = OPLNew(1789776 * 2, mixfreq);
+ OPLResetChip(opl);
+ OPL_Detect();
+}
+
+
+void Fmdrv_MixTo(int *target, int count)
+{
+ static short *buf = NULL;
+ static int buf_size = 0;
+
+ if (!fm_active)
+ return;
+
+ if (buf_size != count * 2) {
+ int before = buf_size;
+ buf_size = sizeof(short) * count;
+
+ if (before) {
+ buf = (short *) realloc(buf, buf_size);
+ }
+ else {
+ buf = (short *) malloc(buf_size);
+ }
+ }
+
+ memset(buf, 0, count * 2);
+ OPLUpdateOne(opl, buf, count);
+
+ /*
+ static int counter = 0;
+
+ for(int a = 0; a < count; ++a)
+ buf[a] = ((counter++) & 0x100) ? -10000 : 10000;
+ */
+
+ for (int a = 0; a < count; ++a) {
+ target[a * 2 + 0] += buf[a] * OPL_VOLUME;
+ target[a * 2 + 1] += buf[a] * OPL_VOLUME;
+ }
+}
+
+
+/***************************************/
+
+
+static const char PortBases[9] = {0, 1, 2, 8, 9, 10, 16, 17, 18};
+static signed char Pans[MAX_VOICES];
+static const unsigned char *Dtab[MAX_VOICES] = {NULL};
+
+
+static int SetBase(int c)
+{
+ return c % 9;
+}
+
+
+static void OPL_Byte(unsigned char idx, unsigned char data)
+{
+ //register int a;
+ Fmdrv_Outportb(oplbase, idx); // for(a = 0; a < 6; a++) Fmdrv_Inportb(oplbase);
+ Fmdrv_Outportb(oplbase + 1, data); // for(a = 0; a < 35; a++) Fmdrv_Inportb(oplbase);
+}
+
+
+void OPL_NoteOff(int c)
+{
+ c = SetBase(c);
+
+ if (c<9) {
+ /* KEYON_BLOCK+c seems to not work alone?? */
+ OPL_Byte(KEYON_BLOCK + c, 0);
+ //OPL_Byte(KSL_LEVEL + Ope, 0xFF);
+ //OPL_Byte(KSL_LEVEL + 3 + Ope, 0xFF);
+ }
+}
+
+
+/* OPL_NoteOn changes the frequency on specified
+ channel and guarantees the key is on. (Doesn't
+ retrig, just turns the note on and sets freq.)
+ If keyoff is nonzero, doesn't even set the note on.
+ Could be used for pitch bending also. */
+void OPL_HertzTouch(int c, int milliHertz, int keyoff)
+{
+ c = SetBase(c);
+
+ if (c >= 9)
+ return;
+
+ fm_active = 1;
+
+/*
+ Bytes A0-B8 - Octave / F-Number / Key-On
+
+ 7 6 5 4 3 2 1 0
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | F-Number (least significant byte) | (A0-A8)
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | Unused | Key | Octave | F-Number | (B0-B8)
+ | | On | | most sig. |
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+*/
+ unsigned int outfnum;
+ unsigned int outblock;
+ const int conversion_factor = 49716; // Frequency of OPL.
+ milliHertzToFnum(milliHertz, &outfnum, &outblock, conversion_factor);
+ OPL_Byte(0xA0 + c, outfnum & 255); // F-Number low 8 bits
+ OPL_Byte(0xB0 + c, (keyoff ? 0 : 0x20) // Key on
+ | ((outfnum >> 8) & 3) // F-number high 2 bits
+ | (outblock << 2)
+ );
+
+}
+
+
+void OPL_Touch(int c, const unsigned char *D, unsigned vol)
+{
+ if (!D) {
+ if (c < MAX_VOICES)
+ D = Dtab[c];
+ if (!D)
+ return;
+ }
+
+//fprintf(stderr, "OPL_Touch(%d, %p:%02X.%02X.%02X.%02X-%02X.%02X.%02X.%02X-%02X.%02X.%02X, %d)\n",
+// c, D,D[0],D[1],D[2],D[3],D[4],D[5],D[6],D[7],D[8],D[9],D[10], Vol);
+
+ Dtab[c] = D;
+
+ c = SetBase(c);
+
+ if (c >= 9)
+ return;
+
+ int Ope = PortBases[c];
+
+/*
+ Bytes 40-55 - Level Key Scaling / Total Level
+
+ 7 6 5 4 3 2 1 0
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | Scaling | Total Level |
+ | Level | 24 12 6 3 1.5 .75 | <-- dB
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ bits 7-6 - causes output levels to decrease as the frequency
+ rises:
+ 00 - no change
+ 10 - 1.5 dB/8ve
+ 01 - 3 dB/8ve
+ 11 - 6 dB/8ve
+ bits 5-0 - controls the total output level of the operator.
+ all bits CLEAR is loudest; all bits SET is the
+ softest. Don't ask me why.
+*/
+ OPL_Byte(KSL_LEVEL + Ope, (D[2] & KSL_MASK) |
+ // (63 + (d[2] & 63) * vol / 63 - vol) - old formula
+ // (63 - ((63 - (d[2] & 63)) * vol ) / 63) - older formula
+ // (63 - ((63 - (d[2] & 63)) * vol + 32) / 64) - revised formula, like ST3
+ (((int)(D[2] & 63) - 63) * vol + 63 * 64 - 32) / 64 // - optimized revised formula
+ );
+
+ OPL_Byte(KSL_LEVEL + 3 + Ope, (D[3] & KSL_MASK) |
+ (((int)(D[3] & 63) - 63) * vol + 63 * 64 - 32) / 64
+ );
+
+ /* 2008-09-27 Bisqwit:
+ * Did tests in ST3: The value poked
+ * to 0x43, minus from 63, is:
+ *
+ * OplVol 63 47 31
+ * SmpVol
+ * 64 63 47 31
+ * 32 32 24 15
+ * 16 16 12 8
+ *
+ * This seems to clearly indicate that the value
+ * poked is calculated with 63 - round(oplvol*smpvol/64.0).
+ *
+ * Also, from the documentation we can deduce that
+ * the maximum volume to be set is 47.25 dB and that
+ * each increase by 1 corresponds to 0.75 dB.
+ *
+ * Since we know that 6 dB is equivalent to a doubling
+ * of the volume, we can deduce that an increase or
+ * decrease by 8 will double / halve the volume.
+ *
+ */
+}
+
+
+void OPL_Pan(int c, signed char val)
+{
+ Pans[c] = val;
+ /* Doesn't happen immediately! */
+}
+
+
+void OPL_Patch(int c, const unsigned char *D)
+{
+//fprintf(stderr, "OPL_Patch(%d, %p:%02X.%02X.%02X.%02X-%02X.%02X.%02X.%02X-%02X.%02X.%02X)\n",
+// c, D,D[0],D[1],D[2],D[3],D[4],D[5],D[6],D[7],D[8],D[9],D[10]);
+ Dtab[c] = D;
+
+ c = SetBase(c);
+ if(c >= 9)return;
+
+ int Ope = PortBases[c];
+
+ OPL_Byte(AM_VIB+ Ope, D[0]);
+ OPL_Byte(ATTACK_DECAY+ Ope, D[4]);
+ OPL_Byte(SUSTAIN_RELEASE+ Ope, D[6]);
+ OPL_Byte(WAVE_SELECT+ Ope, D[8]&3);// 6 high bits used elsewhere
+
+ OPL_Byte(AM_VIB+ 3+Ope, D[1]);
+ OPL_Byte(ATTACK_DECAY+ 3+Ope, D[5]);
+ OPL_Byte(SUSTAIN_RELEASE+3+Ope, D[7]);
+ OPL_Byte(WAVE_SELECT+ 3+Ope, D[9]&3);// 6 high bits used elsewhere
+
+ /* feedback, additive synthesis and Panning... */
+ OPL_Byte(FEEDBACK_CONNECTION+c,
+ (D[10] & ~STEREO_BITS)
+ | (Pans[c]<-32 ? VOICE_TO_LEFT
+ : Pans[c]>32 ? VOICE_TO_RIGHT
+ : (VOICE_TO_LEFT | VOICE_TO_RIGHT)
+ ));
+}
+
+
+void OPL_Reset(void)
+{
+//fprintf(stderr, "OPL_Reset\n");
+ int a;
+
+ for(a = 0; a < 244; a++)
+ OPL_Byte(a, 0);
+
+ for(a = 0; a < MAX_VOICES; ++a)
+ Dtab[a] = NULL;
+
+ OPL_Byte(TEST_REGISTER, ENABLE_WAVE_SELECT);
+
+ fm_active = 0;
+}
+
+
+int OPL_Detect(void)
+{
+ SetBase(0);
+
+ /* Reset timers 1 and 2 */
+ OPL_Byte(TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK);
+
+ /* Reset the IRQ of the FM chip */
+ OPL_Byte(TIMER_CONTROL_REGISTER, IRQ_RESET);
+
+ unsigned char ST1 = Fmdrv_Inportb(oplbase); /* Status register */
+
+ OPL_Byte(TIMER1_REGISTER, 255);
+ OPL_Byte(TIMER_CONTROL_REGISTER, TIMER2_MASK | TIMER1_START);
+
+ /*_asm xor cx,cx;P1:_asm loop P1*/
+ unsigned char ST2 = Fmdrv_Inportb(oplbase);
+
+ OPL_Byte(TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK);
+ OPL_Byte(TIMER_CONTROL_REGISTER, IRQ_RESET);
+
+ int OPLMode = (ST2 & 0xE0) == 0xC0 && !(ST1 & 0xE0);
+
+ if (!OPLMode)
+ return -1;
+
+ return 0;
+}
+
+
+void OPL_Close(void)
+{
+ OPL_Reset();
+}
+
© All Rights Reserved