VOGONS


Can anyone help me fix my Adlib(OPL2) emulation?

Topic actions

First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

I understand the principle of the modulators and carriers, but how is the frequency used in this? I assume it's applied to the carrier signal, which might, after the frequency modulation, be modulated by the modulator of the channel? So: modulator -> (FM'ed carrier) -> output?

Also what happens when both the modulator and carrier are put into AM mode? Also, what happens when FM -> AM -> output is set up? Is the set frequency always used during both modulator and carriers set up in FM mode? So (FM using frequency) -> (FM using frequency) -> output? Is the modulator performing frequency or amplitude modulation in all these cases? Is there a difference between the FM-AM and AM-FM modes?

Last edited by superfury on 2018-10-19, 20:44. Edited 2 times in total.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 1 of 112, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie

I have to guess; you want to write your own OPL2 emulator, right?

I know a lot about how it works internally, but I have not finished my own emulator (because I want to make it 100% accurate which may not be possible 😀

I can give you some pointers how to do it, but if you want help, have your emulator on some place like github and you have a suitable open source licence, I could also jump in and code something.

Edit: forgot to actually answer anything. In OPL2, there are two operators per channel. Think of them as sine wave generators for now.

If a channel is set to FM mode, operator 1 is the modulator because its output modulates the frequency (actually phase) of operator 2, and operator 2 is the carrier because its output is sent to speaker. The other mode is called "AM" mode, but it is not amplitude modulation, maybe it is an abbreviation of Additive Modulation, but there is no modulation. In "AM" mode, both operators are carriers because their output is just summed to output. Same thing as having two channels with one operator each, so to speak.

Reply 2 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

As far as I can see each operator has an 'AM' bit at index 0x20-0x35 bit 7. There's also the Additive synthesis vs Frequency modulation at index 0xC0-0xC8 bit 0.

According to Bochs's adlib.txt:

Bytes 20-35 - Amplitude Modulation / Vibrato / Envelope Generator Type /
Keyboard Scaling Rate / Modulator Frequency Multiple

7 6 5 4 3 2 1 0
+-----+-----+-----+-----+-----+-----+-----+-----+
| Amp | Vib | EG | KSR | Modulator Frequency |
| Mod | | Typ | | Multiple |
+-----+-----+-----+-----+-----+-----+-----+-----+

bit 7 - Apply amplitude modulation when set; AM depth is
controlled by the AM-Depth flag in address BD.

And:

Bytes C0-C8 - Feedback / Algorithm

7 6 5 4 3 2 1 0
+-----+-----+-----+-----+-----+-----+-----+-----+
| unused | Feedback | Alg |
| | | |
+-----+-----+-----+-----+-----+-----+-----+-----+
          bit 0    - If set to 0, operator 1 modulates operator 2.  In this
case, operator 2 is the only one producing sound.
If set to 1, both operators produce sound directly.
Complex sounds are more easily created if the algorithm
is set to 0.

My current source code (an adjusted version of fake86's emulation):

/*
Fake86: A portable, open-source 8086 PC emulator.
Copyright (C)2010-2012 Mike Chambers

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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

/* adlib.c: very ugly Adlib OPL2 emulation for Fake86. very much a work in progress. :) */

//#include "config.h"
#include "headers/types.h" //Basic headers!
#include "headers/emu/sound.h" //Basic sound!
#include "headers/support/log.h" //Logging support!
#include "headers/hardware/ports.h" //Basic ports!
#include "headers/support/highrestimer.h" //High resoltion timer support!
#include "headers/emu/emu_misc.h" //Random short support for noise generation!
#include "headers/emu/timers.h" //Timer support for attack/decay!
//#include <stdint.h>
//#include <stdio.h>

#define uint8_t byte
#define uint16_t word

//Are we disabled?
#define __HW_DISABLED 0
//Use the adlib sound? If disabled, only run timers for the CPU. Sound will not actually be heard.
#define __SOUND_ADLIB 1
//What volume, in percent!
#define ADLIB_VOLUME 100.0f

//Maximum DB volume (equalling 100% volume)!
#define __MAX_DB 100.0f

//extern void set_port_write_redirector (uint16_t startport, uint16_t endport, void *callback);
//extern void set_port_read_redirector (uint16_t startport, uint16_t endport, void *callback);

uint16_t baseport = 0x388; //Adlib address(w)/status(r) port, +1=Data port (write only)
float usesamplerate = 14318180.0f/288.0f; //The sample rate to use for output!

uint16_t adlibregmem[0xFF], adlibaddr = 0;

int8_t oplwave[4][256] = {
{
0, 1, 3, 4, 6, 7, 9, 11, 12, 14, 15, 17, 18, 20, 22, 23, 24, 26, 27, 29, 30, 31, 33, 34, 36, 37, 38, 40, 40, 42, 43, 44, 46, 46, 48, 49, 50, 51, 51, 53,
53, 54, 55, 56, 57, 57, 58, 59, 59, 60, 61, 61, 62, 62, 63, 63, 63, 64, 64, 64, 116, 116, 116, 116, 116, 116, 116, 116, 116, 64, 64, 64, 63, 63, 63, 62, 62, 61, 61, 60,
59, 59, 58, 57, 57, 56, 55, 54, 53, 53, 51, 51, 50, 49, 48, 46, 46, 44, 43, 42, 40, 40, 38, 37, 36, 34, 33, 31, 30, 29, 27, 26, 24, 23, 22, 20, 18, 17, 15, 14,
12, 11, 9, 7, 6, 4, 3, 1, 0, -1, -3, -4, -6, -7, -9, -11, -12, -14, -15, -17, -18, -20, -22, -23, -24, -26, -27, -29, -30, -31, -33, -34, -36, -37, -38, -40, -40, -42, -43, -44,
-46, -46, -48, -49, -50, -51, -51, -53, -53, -54, -55, -56, -57, -57, -58, -59, -59, -60, -61, -61, -62, -62, -63, -63, -63, -64, -64, -64, -116, -116, -116, -116, -116, -116, -116, -116, -116, -64, -64, -64,
Show last 354 lines
		-63, -63, -63, -62, -62, -61, -61, -60, -59, -59, -58, -57, -57, -56, -55, -54, -53, -53, -51, -51, -50, -49, -48, -46, -46, -44, -43, -42, -40, -40, -38, -37, -36, -34, -33, -31, -30, -29, -27, -26,
-24, -23, -22, -20, -18, -17, -15, -14, -12, -11, -9, -7, -6, -4, -3, -1
},

{
0, 1, 3, 4, 6, 7, 9, 11, 12, 14, 15, 17, 18, 20, 22, 23, 24, 26, 27, 29,30, 31, 33, 34, 36, 37, 38, 40, 40, 42, 43, 44, 46, 46, 48, 49, 50, 51, 51, 53,
53, 54, 55, 56, 57, 57, 58, 59, 59, 60, 61, 61, 62, 62, 63, 63, 63, 64, 64, 64, 116, 116, 116, 116, 116, 116, 116, 116, 116, 64, 64, 64, 63, 63, 63, 62, 62, 61, 61, 60,
59, 59, 58, 57, 57, 56, 55, 54, 53, 53, 51, 51, 50, 49, 48, 46, 46, 44, 43, 42, 40, 40, 38, 37, 36, 34, 33, 31, 30, 29, 27, 26, 24, 23, 22, 20, 18, 17, 15, 14,
12, 11, 9, 7, 6, 4, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
},


{
0, 1, 3, 4, 6, 7, 9, 11, 12, 14, 15, 17, 18, 20, 22, 23, 24, 26, 27, 29, 30, 31, 33, 34, 36, 37, 38, 40, 40, 42, 43, 44, 46, 46, 48, 49, 50, 51, 51, 53,
53, 54, 55, 56, 57, 57, 58, 59, 59, 60, 61, 61, 62, 62, 63, 63, 63, 64, 64, 64, 116, 116, 116, 116, 116, 116, 116, 116, 116, 64, 64, 64, 63, 63, 63, 62, 62, 61, 61, 60,
59, 59, 58, 57, 57, 56, 55, 54, 53, 53, 51, 51, 50, 49, 48, 46, 46, 44, 43, 42, 40, 40, 38, 37, 36, 34, 33, 31, 30, 29, 27, 26, 24, 23, 22, 20, 18, 17, 15, 14,
12, 11, 9, 7, 6, 4, 3, 1, 0, 1, 3, 4, 6, 7, 9, 11, 12, 14, 15, 17, 18, 20, 22, 23, 24, 26, 27, 29, 30, 31, 33, 34, 36, 37, 38, 40, 40, 42, 43, 44,
46, 46, 48, 49, 50, 51, 51, 53, 53, 54, 55, 56, 57, 57, 58, 59, 59, 60, 61, 61, 62, 62, 63, 63, 63, 64, 64, 64, 116, 116, 116, 116, 116, 116, 116, 116, 116, 64, 64, 64,
63, 63, 63, 62, 62, 61, 61, 60, 59, 59, 58, 57, 57, 56, 55, 54, 53, 53, 51, 51, 50, 49, 48, 46, 46, 44, 43, 42, 40, 40, 38, 37, 36, 34, 33, 31, 30, 29, 27, 26,
24, 23, 22, 20, 18, 17, 15, 14, 12, 11, 9, 7, 6, 4, 3, 1
},


{
0, 1, 3, 4, 6, 7, 9, 11, 12, 14, 15, 17, 18, 20, 22, 23, 24, 26, 27, 29, 30, 31, 33, 34, 36, 37, 38, 40, 40, 42, 43, 44, 46, 46, 48, 49, 50, 51, 51, 53,
53, 54, 55, 56, 57, 57, 58, 59, 59, 60, 61, 61, 62, 62, 63, 63, 63, 64, 64, 64, 116, 116, 116, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 4, 6, 7, 9, 11, 12, 14, 15, 17, 18, 20, 22, 23, 24, 26, 27, 29, 30, 31, 33, 34, 36, 37, 38, 40, 40, 42, 43, 44,
46, 46, 48, 49, 50, 51, 51, 53, 53, 54, 55, 56, 57, 57, 58, 59, 59, 60, 61, 61, 62, 62, 63, 63, 63, 64, 64, 64, 116, 116, 116, 116, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}

};

byte adliboperators[2][9] = { //Groupings of 22 registers! (20,40,60,80,E0)
{ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12 },
{ 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15 }
};

byte adliboperatorsreverse[0x16] = { 0, 1, 2, 0, 1, 2,
255, 255,
3, 4, 5, 3, 4, 5,
255, 255,
6, 7, 8, 6, 7, 8}; //Channel lookup of adlib operators!

byte wavemask = 0; //Wave select mask!

struct structadlibop {
uint8_t wavesel;
uint8_t AM; //Amplitude modulation enabled?
float ModulatorFrequencyMultiple; //What harmonic to sound?
} adlibop[0x10];

struct structadlibchan {
uint16_t freq;
double convfreq;
uint8_t keyon;
uint16_t octave;
uint8_t synthmode; //What synthesizer mode (1=Additive synthesis, 0=Frequency modulation)
} adlibch[0x10];

double attacktable[0x10] = { 1.0003, 1.00025, 1.0002, 1.00015, 1.0001, 1.00009, 1.00008, 1.00007, 1.00006, 1.00005, 1.00004, 1.00003, 1.00002, 1.00001, 1.000005 }; //1.003, 1.05, 1.01, 1.015, 1.02, 1.025, 1.03, 1.035, 1.04, 1.045, 1.05, 1.055, 1.06, 1.065, 1.07, 1.075 };
double decaytable[0x10] = { 0.99999, 0.999985, 0.99998, 0.999975, 0.99997, 0.999965, 0.99996, 0.999955, 0.99995, 0.999945, 0.99994, 0.999935, 0.99994, 0.999925, 0.99992, 0.99991 };
double adlibenv[0x20], adlibdecay[0x20], adlibattack[0x20];
uint8_t adlibdidattack[0x20], adlibpercussion = 0, adlibstatus = 0;

float AMDepth = 0.0f; //AM depth in dB!

uint16_t adlibport = 0x388;

OPTINLINE double dB2factor(double dB, double fMaxLevelDB)
{
return pow(10, ((dB - fMaxLevelDB) / 20));
}

OPTINLINE float calcModulatorFrequencyMultiple(byte data)
{
return 1.0f; //Just ignore for now!
}

void outadlib (uint16_t portnum, uint8_t value) {
if (portnum==adlibport) {
adlibaddr = value;
return;
}
portnum = adlibaddr;
adlibregmem[portnum] = value;
switch (portnum) {
case 0: //Waveform select enable
wavemask = (adlibregmem[0] & 0x20) ? 3 : 0; //Apply waveform mask!
break;
case 4: //timer control
if (value&0x80) {
adlibstatus = 0;
adlibregmem[4] = 0;
}
break;
case 0xBD:
if (value & 0x10) adlibpercussion = 1;
else adlibpercussion = 0;
AMDepth = (value & 0x80) ? dB2factor(4.8f, __MAX_DB) : dB2factor(1.0f, __MAX_DB); //AM depth in dB!
break;
}
if ((portnum >= 0x20) && (portnum <= 0x35)) //Various flags
{
portnum &= 0x1F;
adlibop[portnum].AM = (adlibregmem[0x20 + portnum] & 0x80) ? 1 : 0; //Take the AM bit!
adlibop[portnum].ModulatorFrequencyMultiple = calcModulatorFrequencyMultiple(adlibregmem[0x20 + portnum] & 0xF); //Which harmonic to use?
}
else if ( (portnum >= 0x60) && (portnum <= 0x75) ) { //attack/decay
portnum &= 0x1F;
adlibattack[portnum] = attacktable[15- (value>>4) ]*1.006;
adlibdecay[portnum] = decaytable[value&15];
}
else if ( (portnum >= 0xA0) && (portnum <= 0xB8) ) { //octave, freq, key on
if (portnum & 0x8) return; //Ignore A8-AF!
portnum &= 0x7; //Only 9 channels
if (!adlibch[portnum].keyon && ( (adlibregmem[0xB0+portnum]>>5) &1) ) {
adlibdidattack[adliboperators[0][portnum]] = 0;
adlibdidattack[adliboperators[1][portnum]] = 0;
adlibenv[adliboperators[0][portnum]] = 0.0025;
adlibenv[adliboperators[1][portnum]] = 0.0025;
}
adlibch[portnum].freq = adlibregmem[0xA0+portnum] | ( (adlibregmem[0xB0+portnum]&3) <<8);
adlibch[portnum].convfreq = ( (double) adlibch[portnum].freq * 0.7626459);
adlibch[portnum].keyon = (adlibregmem[0xB0+portnum]>>5) &1;
adlibch[portnum].octave = (adlibregmem[0xB0+portnum]>>2) &7;
}
else if ((portnum >= 0xC0) && (portnum <= 0xC8))
{
portnum &= 7;
adlibch[portnum].synthmode = (adlibregmem[0xC0 + portnum] & 1); //Save the synthesis mode!
}
else if ( (portnum >= 0xE0) && (portnum <= 0xF5) ) { //waveform select
portnum &= 0x1F;
adlibop[portnum].wavesel = value&3;
}
}

uint8_t inadlib (uint16_t portnum) {
if (!adlibregmem[4]) adlibstatus = 0;
else adlibstatus = 0x80;
adlibstatus = adlibstatus + (adlibregmem[4]&1) *0x40 + (adlibregmem[4]&2) *0x10;
return (adlibstatus);
}

uint16_t adlibfreq (uint8_t chan) {
//uint8_t downoct[4] = { 3, 2, 1, 0 };
//uint8_t upoct[3] = { 1, 2, 3 };
if (chan > 8) return (0); //Invalid channel: we only have 9 channels!
uint16_t tmpfreq;
if (!adlibch[chan].keyon) return (0);
tmpfreq = (uint16_t) adlibch[chan].convfreq;
//if (adlibch[chan].octave<4) tmpfreq = tmpfreq>>1;
//if (adlibch[chan].octave>4) tmpfreq = tmpfreq<<1;
switch (adlibch[chan].octave) {
case 0:
tmpfreq = tmpfreq >> 4;
break;
case 1:
tmpfreq = tmpfreq >> 3;
break;
case 2:
tmpfreq = tmpfreq >> 2;
break;
case 3:
tmpfreq = tmpfreq >> 1;
break;
//case 4: tmpfreq = tmpfreq >> 1; break;
case 5:
tmpfreq = tmpfreq << 1;
break;
case 6:
tmpfreq = tmpfreq << 2;
break;
case 7:
tmpfreq = tmpfreq << 3;
}

return (tmpfreq);
}

//Original again!
OPTINLINE char adlibsample (uint8_t curchan) {
static uint64_t adlibstep[0x20];
float fullstep1, fullstep2, tempsample, result, tempstep, effectivemodulator, effectivecarrier;
byte operator1, operator2; //What operator is our modulator and carrier?

if (curchan >= NUMITEMS(adlibch)) return 0; //No sample with invalid channel!
if (adlibpercussion && (curchan >= 6) && (curchan <= 8)) //We're percussion?
{
return 0; //Percussion isn't supported yet!
}

//Determine our active operators!
operator1 = adliboperators[0][curchan]; //First operator to use!
operator2 = adliboperators[1][curchan]; //Second operator to use!

//Determine the type of modulation!
//Operator 1!
effectivemodulator = (adlibfreq(curchan)*adlibop[operator1].ModulatorFrequencyMultiple); //Effective modulator frequency!
fullstep1 = usesamplerate / effectivemodulator;
tempsample = oplwave[adlibop[operator1].wavesel&wavemask][(uint8_t)((double)adlibstep[operator1]++ / ((double)fullstep1 / (double)256))];
if (adlibop[operator1].AM) tempsample *= AMDepth; //Apply AM depth!

//Apply the volume envelope for operator 1!
tempstep = adlibenv[operator1]; //Current volume!
if (tempstep > 1.0f) tempstep = 1.0f; //Limit volume to 100%!
tempsample *= tempstep;
tempsample *= 2.0f;

//Operator 2!
effectivecarrier = (adlibfreq(curchan)*adlibop[operator2].ModulatorFrequencyMultiple); //Effective carrier init!
if (!adlibch[curchan].synthmode) effectivecarrier *= ((float)tempsample/117.0f); //FM using the first operator when needed!
fullstep2 = usesamplerate / effectivecarrier;
result = oplwave[adlibop[operator2].wavesel&wavemask][(uint8_t)((double)adlibstep[operator2]++ / ((double)fullstep2 / (double)256))];
if (adlibop[operator2].AM) result *= AMDepth; //Apply AM depth!

//Apply the volume envelope for operator 2!
tempstep = adlibenv[operator2]; //Current volume!
if (tempstep > 1.0f) tempstep = 1.0f; //Limit volume to 100%!
result *= tempstep;
result *= 2.0f;

//Perform additive synthesis when asked to do so.
if (adlibch[curchan].synthmode) result += tempsample; //Perform additive synthesis when needed!

//Make sure we don't overflow!
if (adlibstep[operator1] > fullstep1) adlibstep[operator1] = 0;
if (adlibstep[operator2] > fullstep2) adlibstep[operator2] = 0;

return (char)(result);
}

OPTINLINE char adlibgensample() {
uint8_t curchan;
char adlibaccum;
adlibaccum = 0;
for (curchan = 0; curchan < 9; curchan++)
{
if (adlibfreq(curchan) != 0)
{
adlibaccum += adlibsample(curchan);
}
}
return (adlibaccum);
}

void tickadlib() {
uint8_t curchan;
for (curchan = 0; curchan<0x16; curchan++)
{
if (adlibfreq(adliboperatorsreverse[curchan]) != 0)
{
if (adlibdidattack[curchan])
{
if (adlibenv[curchan]>0.0001f)
{
adlibenv[curchan] *= adlibdecay[curchan];
}
else
{
adlibenv[curchan] = 0; //Clear: nothing to be heard!
}
}
else
{
adlibenv[curchan] *= adlibattack[curchan];
if (adlibenv[curchan] >= 1.0) adlibdidattack[curchan] = 1;
}
}
}
}

byte adlib_soundGenerator(void* buf, uint_32 length, byte stereo, void *userdata) //Generate a sample!
{
if (stereo) return 0; //We don't support stereo!

byte filled,curchan;
filled = 0; //Default: not filled!
for (curchan=0; curchan<9; curchan++) { //Check for active channels!
if (adlibfreq (curchan) !=0) {
filled = 1; //We're filled!
break; //Stop searching!
}
}

if (!filled) //Not filled?
{
return 0; //Unused channel!
}

uint_32 c;
c = length; //Init c!
char *data_mono;
data_mono = (char *)buf; //The data in correct samples!
for (;;) //Fill it!
{
//Left and right are the same!
*data_mono++ = adlibgensample(); //Generate a mono sample!
if (!--c) return SOUNDHANDLER_RESULT_FILLED; //Next item!
}
}

//Multicall speedup!
#define ADLIBMULTIPLIER 10

void initAdlib()
{
if (__HW_DISABLED) return; //Abort!

//All input!
AMDepth = dB2factor(1.0f, __MAX_DB);

if (__SOUND_ADLIB)
{
if (!addchannel(&adlib_soundGenerator,NULL,"Adlib",usesamplerate,0,0,SMPL8S)) //Start the sound emulation (mono) with automatic samples buffer?
{
dolog("adlib","Error registering sound channel for output!");
}
else
{
setVolume(&adlib_soundGenerator,NULL,ADLIB_VOLUME);
}
}
//dolog("adlib","sound channel added. registering ports...");
//Ignore unregistered channel, we need to be used by software!
register_PORTIN(baseport,&inadlib); //Status port (R)
//All output!
register_PORTOUT(baseport,&outadlib); //Address port (W)
register_PORTOUT(baseport+1,&outadlib); //Data port (W/O)
//dolog("adlib","Registering timer...");
addtimer(usesamplerate,&tickadlib,"AdlibAttackDecay",ADLIBMULTIPLIER,0,NULL); //We run at 49.716Khz, about every 20us.
//dolog("adlib","Ready"); //Ready to run!
}

void doneAdlib()
{
if (__HW_DISABLED) return; //Abort!
removetimer("AdlibAttackDecay"); //Stop the audio channel!
if (__SOUND_ADLIB)
{
removechannel(&adlib_soundGenerator,NULL,0); //Stop the sound emulation?
}
//Unregister the ports!
register_PORTIN(baseport,NULL);
//All output!
register_PORTOUT(baseport,NULL);
register_PORTOUT(baseport+1,NULL);
}

For some reason I get a good tone for a second, after that some strange mix of signals (like a buffer overflow?)? It sounds like the tone (frequency) is changed every 1/8th second on the 'background tone', with the normal tone (the D#) still sounding.

My register settings are:

		adlibsetreg(0x20, 0x01); //Modulator multiple to 1!
adlibsetreg(0x40, 0x10); //Modulator level about 40dB!
adlibsetreg(0x60, 0xF0); //Modulator attack: quick; decay long!
adlibsetreg(0x80, 0x77); //Modulator sustain: medium; release: medium
adlibsetreg(0xA0, 0x98); //Set voice frequency's LSB (it'll be a D#)!
adlibsetreg(0x23, 0x01); //Set the carrier's multiple to 1!
adlibsetreg(0x43, 0x00); //Set the carrier to maximum volume (about 47dB).
adlibsetreg(0x63, 0xF0); //Carrier attack: quick; decay: long!
adlibsetreg(0x83, 0x77); //Carrier sustain: medium; release: medium!
adlibsetreg(0xB0, 0x31); //Turn the voice on; set the octave and freq MSB!

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 3 of 112, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie

Ah, the "Amplitude Modulation" bit is just "tremolo" effect. It does modulate the amplitude of the output, hence the name.

Tremolo is to amplitude like what vibrato is to frequency.

Reply 4 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

What happens when either the carrier or the modulator has the AM bit set to 0? Will Frequency Modulation be applied to the signal, before being passed to the carrier frequency or output (depending on whether it's applied to the modulator or carrier)? As far as I understand, with the AM bit set, the amplitude set in dB will be applied, but what happens when either the modulator or carrier has the AM bit set to 0? What will happen to the generated wave in the modulator/carrier?
Also, what happens when both are in AM mode or both have the AM bit set to 0? Does it only apply to the final stage?

So:
Modulator (AM or FM) -> Carrier (AM or FM) -> output?

What will happen to the output signal and how is the carrier or modulator frequency applied?

So the possibilities are:
Modulator (FM) -> Carrier (FM) -> output
Modulator (FM) -> Carrier (AM) -> output
Modulator (AM) -> Carrier (FM) -> output
Modulator (AM) -> Carrier (AM) -> output

What happens in these four cases? As far as I know the Modulator always Frequency modulates the carrier?

Also I seem to have figured out the strange behaviour: it has something to do with the volume envelope's decay phase? Can anyone see what's going wrong here?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 5 of 112, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie

In general, the AM bit is for each operator, so tremolo is applied to output of each operator, whether the operator is used as a modulator or carrier. I'll try to answer your questions, but they make little sense to me, you seem to confuse some separate things as one.

superfury wrote:

What happens when either the carrier or the modulator has the AM bit set to 0?

[AM bit in operator register base 0x20]
When AM=0, tremolo is not applied to operator output, and if AM=1, tremolo is applied to operator output.

If carrier AM=0, carrier output has no tremolo effect, if carrier AM=1, carrier output has the tremolo effect.
If modulator AM=0, modulator output has no tremolo effect, if modulator AM=1, modulator output has the tremolo effect.

superfury wrote:

Will Frequency Modulation be applied to the signal, before being passed to the carrier frequency or output (depending on whether it's applied to the modulator or carrier)?

[Connection type bit in channel base 0xc0]

When the operators are connected to operate in FM mode, the output of the first operator (called now the modulator) is used to modify (modulate) frequency (phase actually) of the second operator (called now the carrier), and only the output of the second operator (carrier) is sent to speakers.

When the operators are connected to operate in Additive Mode, the output of both operators are just summed together and sent to speakers. In a sense, they both operate as carriers (you can hear both operators, the other is not used to modulate the other).

superfury wrote:

As far as I understand, with the AM bit set, the amplitude set in dB will be applied, but what happens when either the modulator or carrier has the AM bit set to 0? What will happen to the generated wave in the modulator/carrier?
Also, what happens when both are in AM mode or both have the AM bit set to 0? Does it only apply to the final stage?

The operators with AM=0 then has no tremolo effect applied on their output, whether they are used as modulators or carriers.

superfury wrote:
So: Modulator (AM or FM) -> Carrier (AM or FM) -> output? […]
Show full quote

So:
Modulator (AM or FM) -> Carrier (AM or FM) -> output?

What will happen to the output signal and how is the carrier or modulator frequency applied?

So the possibilities are:
Modulator (FM) -> Carrier (FM) -> output
Modulator (FM) -> Carrier (AM) -> output
Modulator (AM) -> Carrier (FM) -> output
Modulator (AM) -> Carrier (AM) -> output

What happens in these four cases? As far as I know the Modulator always Frequency modulates the carrier?

No, more like two possibilites:
Channel is connected to Frequency Modulation : 0 -> [OP1] -> [OP2] -> speaker.
Here nothing modulates operator 1, but its output is used as modulation input on operator 2, and operator 2 output is used as audio source.
So OP1 is modulator, OP2 is carrier.

Channel is connected to Additive Modulation (which has nothing to do with amplitude modulation) :
0 -> [OP1] -> speaker
0 -> [OP2] -> speaker

So nothing modulates operator 1, nothing modulates operator 2, and output of both operators is used as audio source. Both OP1 and OP2 are carrirers.

[in reality, OP1 has feedback capability, so OP1 can modulate itself if it wants. It can use a portion of its output as modulation input.]

superfury wrote:

Also I seem to have figured out the strange behaviour: it has something to do with the volume envelope's decay phase? Can anyone see what's going wrong here?

I'd have to check that at some point.

Reply 6 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

How should I apply the Frequency modulation? The first operator should be correctly calculated (excluding the result from the calcModulatorFrequencyMultiple, which I still don't know how to calculate (effectively a speedup or speeddown of the generated frequency, where 1.0 is unchanged speed, 1.5 is 150%, 0.5 is 50% speed etc.)).

This row contains the actual frequency modulation atm:

if (!adlibch[curchan].synthmode) effectivecarrier *= ((float)tempsample/117.0f); //FM using the first operator when needed!

So the input from the modulator (-117 - 117) is converted into a number between -1 and 1, after which it's applied as a factor to the frequency to use for the carrier.
Is this correct? Or should the modulator wave simply be added to the frequency of the carrier to generate the resulting sample? So the sample can vary at a frequency of 117 below the specified frequency of the carrier up to 117 above the frequency of the carrier?

If the AM depth is either 4.8dB or 1.0dB, I need to know the volume of the full output signal (so the volume of a sinus of -117 to 117). Do you know at what volume (in dB) the Adlib operates? I need this to set the __MAX_DB constant correctly. I've inputted a value of 100dB atm to test with, but I do need the actual scale of the signal in order to apply the dB scale (using the dB2factor function).

Also, how is the AM depth modulated? I assume it's a signal (sinus?) that is multiplied with my AMDepth variable, which is then added to the operator result, in order to apply the tremolo? What's the frequency of the AM modulation signal?

This is my current version (applied calcModulatorFrequencyMultiple to the resulting frequency):

/*
Fake86: A portable, open-source 8086 PC emulator.
Copyright (C)2010-2012 Mike Chambers

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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

/* adlib.c: very ugly Adlib OPL2 emulation for Fake86. very much a work in progress. :) */

//#include "config.h"
#include "headers/types.h" //Basic headers!
#include "headers/emu/sound.h" //Basic sound!
#include "headers/support/log.h" //Logging support!
#include "headers/hardware/ports.h" //Basic ports!
#include "headers/support/highrestimer.h" //High resoltion timer support!
#include "headers/emu/emu_misc.h" //Random short support for noise generation!
#include "headers/emu/timers.h" //Timer support for attack/decay!
//#include <stdint.h>
//#include <stdio.h>

#define uint8_t byte
#define uint16_t word

//Are we disabled?
#define __HW_DISABLED 0
//Use the adlib sound? If disabled, only run timers for the CPU. Sound will not actually be heard.
#define __SOUND_ADLIB 1
//What volume, in percent!
#define ADLIB_VOLUME 100.0f

//Maximum DB volume (equalling 100% volume)!
#define __MAX_DB 100.0f

//extern void set_port_write_redirector (uint16_t startport, uint16_t endport, void *callback);
//extern void set_port_read_redirector (uint16_t startport, uint16_t endport, void *callback);

uint16_t baseport = 0x388; //Adlib address(w)/status(r) port, +1=Data port (write only)
float usesamplerate = 14318180.0f/288.0f; //The sample rate to use for output!

uint16_t adlibregmem[0xFF], adlibaddr = 0;

int8_t oplwave[4][256] = {
{
0, 1, 3, 4, 6, 7, 9, 11, 12, 14, 15, 17, 18, 20, 22, 23, 24, 26, 27, 29, 30, 31, 33, 34, 36, 37, 38, 40, 40, 42, 43, 44, 46, 46, 48, 49, 50, 51, 51, 53,
53, 54, 55, 56, 57, 57, 58, 59, 59, 60, 61, 61, 62, 62, 63, 63, 63, 64, 64, 64, 116, 116, 116, 116, 116, 116, 116, 116, 116, 64, 64, 64, 63, 63, 63, 62, 62, 61, 61, 60,
59, 59, 58, 57, 57, 56, 55, 54, 53, 53, 51, 51, 50, 49, 48, 46, 46, 44, 43, 42, 40, 40, 38, 37, 36, 34, 33, 31, 30, 29, 27, 26, 24, 23, 22, 20, 18, 17, 15, 14,
12, 11, 9, 7, 6, 4, 3, 1, 0, -1, -3, -4, -6, -7, -9, -11, -12, -14, -15, -17, -18, -20, -22, -23, -24, -26, -27, -29, -30, -31, -33, -34, -36, -37, -38, -40, -40, -42, -43, -44,
-46, -46, -48, -49, -50, -51, -51, -53, -53, -54, -55, -56, -57, -57, -58, -59, -59, -60, -61, -61, -62, -62, -63, -63, -63, -64, -64, -64, -116, -116, -116, -116, -116, -116, -116, -116, -116, -64, -64, -64,
Show last 363 lines
		-63, -63, -63, -62, -62, -61, -61, -60, -59, -59, -58, -57, -57, -56, -55, -54, -53, -53, -51, -51, -50, -49, -48, -46, -46, -44, -43, -42, -40, -40, -38, -37, -36, -34, -33, -31, -30, -29, -27, -26,
-24, -23, -22, -20, -18, -17, -15, -14, -12, -11, -9, -7, -6, -4, -3, -1
},

{
0, 1, 3, 4, 6, 7, 9, 11, 12, 14, 15, 17, 18, 20, 22, 23, 24, 26, 27, 29,30, 31, 33, 34, 36, 37, 38, 40, 40, 42, 43, 44, 46, 46, 48, 49, 50, 51, 51, 53,
53, 54, 55, 56, 57, 57, 58, 59, 59, 60, 61, 61, 62, 62, 63, 63, 63, 64, 64, 64, 116, 116, 116, 116, 116, 116, 116, 116, 116, 64, 64, 64, 63, 63, 63, 62, 62, 61, 61, 60,
59, 59, 58, 57, 57, 56, 55, 54, 53, 53, 51, 51, 50, 49, 48, 46, 46, 44, 43, 42, 40, 40, 38, 37, 36, 34, 33, 31, 30, 29, 27, 26, 24, 23, 22, 20, 18, 17, 15, 14,
12, 11, 9, 7, 6, 4, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
},


{
0, 1, 3, 4, 6, 7, 9, 11, 12, 14, 15, 17, 18, 20, 22, 23, 24, 26, 27, 29, 30, 31, 33, 34, 36, 37, 38, 40, 40, 42, 43, 44, 46, 46, 48, 49, 50, 51, 51, 53,
53, 54, 55, 56, 57, 57, 58, 59, 59, 60, 61, 61, 62, 62, 63, 63, 63, 64, 64, 64, 116, 116, 116, 116, 116, 116, 116, 116, 116, 64, 64, 64, 63, 63, 63, 62, 62, 61, 61, 60,
59, 59, 58, 57, 57, 56, 55, 54, 53, 53, 51, 51, 50, 49, 48, 46, 46, 44, 43, 42, 40, 40, 38, 37, 36, 34, 33, 31, 30, 29, 27, 26, 24, 23, 22, 20, 18, 17, 15, 14,
12, 11, 9, 7, 6, 4, 3, 1, 0, 1, 3, 4, 6, 7, 9, 11, 12, 14, 15, 17, 18, 20, 22, 23, 24, 26, 27, 29, 30, 31, 33, 34, 36, 37, 38, 40, 40, 42, 43, 44,
46, 46, 48, 49, 50, 51, 51, 53, 53, 54, 55, 56, 57, 57, 58, 59, 59, 60, 61, 61, 62, 62, 63, 63, 63, 64, 64, 64, 116, 116, 116, 116, 116, 116, 116, 116, 116, 64, 64, 64,
63, 63, 63, 62, 62, 61, 61, 60, 59, 59, 58, 57, 57, 56, 55, 54, 53, 53, 51, 51, 50, 49, 48, 46, 46, 44, 43, 42, 40, 40, 38, 37, 36, 34, 33, 31, 30, 29, 27, 26,
24, 23, 22, 20, 18, 17, 15, 14, 12, 11, 9, 7, 6, 4, 3, 1
},


{
0, 1, 3, 4, 6, 7, 9, 11, 12, 14, 15, 17, 18, 20, 22, 23, 24, 26, 27, 29, 30, 31, 33, 34, 36, 37, 38, 40, 40, 42, 43, 44, 46, 46, 48, 49, 50, 51, 51, 53,
53, 54, 55, 56, 57, 57, 58, 59, 59, 60, 61, 61, 62, 62, 63, 63, 63, 64, 64, 64, 116, 116, 116, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 4, 6, 7, 9, 11, 12, 14, 15, 17, 18, 20, 22, 23, 24, 26, 27, 29, 30, 31, 33, 34, 36, 37, 38, 40, 40, 42, 43, 44,
46, 46, 48, 49, 50, 51, 51, 53, 53, 54, 55, 56, 57, 57, 58, 59, 59, 60, 61, 61, 62, 62, 63, 63, 63, 64, 64, 64, 116, 116, 116, 116, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}

};

byte adliboperators[2][9] = { //Groupings of 22 registers! (20,40,60,80,E0)
{ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12 },
{ 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15 }
};

byte adliboperatorsreverse[0x16] = { 0, 1, 2, 0, 1, 2,
255, 255,
3, 4, 5, 3, 4, 5,
255, 255,
6, 7, 8, 6, 7, 8}; //Channel lookup of adlib operators!

byte wavemask = 0; //Wave select mask!

struct structadlibop {
uint8_t wavesel;
uint8_t AM; //Amplitude modulation enabled?
float ModulatorFrequencyMultiple; //What harmonic to sound?
} adlibop[0x10];

struct structadlibchan {
uint16_t freq;
double convfreq;
uint8_t keyon;
uint16_t octave;
uint8_t synthmode; //What synthesizer mode (1=Additive synthesis, 0=Frequency modulation)
} adlibch[0x10];

double attacktable[0x10] = { 1.0003, 1.00025, 1.0002, 1.00015, 1.0001, 1.00009, 1.00008, 1.00007, 1.00006, 1.00005, 1.00004, 1.00003, 1.00002, 1.00001, 1.000005 }; //1.003, 1.05, 1.01, 1.015, 1.02, 1.025, 1.03, 1.035, 1.04, 1.045, 1.05, 1.055, 1.06, 1.065, 1.07, 1.075 };
double decaytable[0x10] = { 0.99999, 0.999985, 0.99998, 0.999975, 0.99997, 0.999965, 0.99996, 0.999955, 0.99995, 0.999945, 0.99994, 0.999935, 0.99994, 0.999925, 0.99992, 0.99991 };
double adlibenv[0x20], adlibdecay[0x20], adlibattack[0x20];
uint8_t adlibdidattack[0x20], adlibpercussion = 0, adlibstatus = 0;

float AMDepth = 0.0f; //AM depth in dB!

uint16_t adlibport = 0x388;

OPTINLINE double dB2factor(double dB, double fMaxLevelDB)
{
return pow(10, ((dB - fMaxLevelDB) / 20));
}

OPTINLINE float calcModulatorFrequencyMultiple(byte data)
{
switch (data)
{
case 0: return 0.5f;
case 11: return 10.0f;
case 13: return 12.0f;
case 14: return 15.0f;
default: return (float)data; //The same number!
}
}

void outadlib (uint16_t portnum, uint8_t value) {
if (portnum==adlibport) {
adlibaddr = value;
return;
}
portnum = adlibaddr;
adlibregmem[portnum] = value;
switch (portnum) {
case 0: //Waveform select enable
wavemask = (adlibregmem[0] & 0x20) ? 3 : 0; //Apply waveform mask!
break;
case 4: //timer control
if (value&0x80) {
adlibstatus = 0;
adlibregmem[4] = 0;
}
break;
case 0xBD:
if (value & 0x10) adlibpercussion = 1;
else adlibpercussion = 0;
AMDepth = (value & 0x80) ? dB2factor(4.8f, __MAX_DB) : dB2factor(1.0f, __MAX_DB); //AM depth in dB!
break;
}
if ((portnum >= 0x20) && (portnum <= 0x35)) //Various flags
{
portnum &= 0x1F;
adlibop[portnum].AM = (adlibregmem[0x20 + portnum] & 0x80) ? 1 : 0; //Take the AM bit!
adlibop[portnum].ModulatorFrequencyMultiple = calcModulatorFrequencyMultiple(adlibregmem[0x20 + portnum] & 0xF); //Which harmonic to use?
}
else if ( (portnum >= 0x60) && (portnum <= 0x75) ) { //attack/decay
portnum &= 0x1F;
adlibattack[portnum] = attacktable[15- (value>>4) ]*1.006;
adlibdecay[portnum] = decaytable[value&15];
}
else if ( (portnum >= 0xA0) && (portnum <= 0xB8) ) { //octave, freq, key on
if (portnum & 0x8) return; //Ignore A8-AF!
portnum &= 0x7; //Only 9 channels
if (!adlibch[portnum].keyon && ( (adlibregmem[0xB0+portnum]>>5) &1) ) {
adlibdidattack[adliboperators[0][portnum]] = 0;
adlibdidattack[adliboperators[1][portnum]] = 0;
adlibenv[adliboperators[0][portnum]] = 0.0025;
adlibenv[adliboperators[1][portnum]] = 0.0025;
}
adlibch[portnum].freq = adlibregmem[0xA0+portnum] | ( (adlibregmem[0xB0+portnum]&3) <<8);
adlibch[portnum].convfreq = ( (double) adlibch[portnum].freq * 0.7626459);
adlibch[portnum].keyon = (adlibregmem[0xB0+portnum]>>5) &1;
adlibch[portnum].octave = (adlibregmem[0xB0+portnum]>>2) &7;
}
else if ((portnum >= 0xC0) && (portnum <= 0xC8))
{
portnum &= 7;
adlibch[portnum].synthmode = (adlibregmem[0xC0 + portnum] & 1); //Save the synthesis mode!
}
else if ( (portnum >= 0xE0) && (portnum <= 0xF5) ) { //waveform select
portnum &= 0x1F;
adlibop[portnum].wavesel = value&3;
}
}

uint8_t inadlib (uint16_t portnum) {
if (!adlibregmem[4]) adlibstatus = 0;
else adlibstatus = 0x80;
adlibstatus = adlibstatus + (adlibregmem[4]&1) *0x40 + (adlibregmem[4]&2) *0x10;
return (adlibstatus);
}

uint16_t adlibfreq (sbyte operatornumber, uint8_t chan) {
//uint8_t downoct[4] = { 3, 2, 1, 0 };
//uint8_t upoct[3] = { 1, 2, 3 };
if (chan > 8) return (0); //Invalid channel: we only have 9 channels!
uint16_t tmpfreq;
if (!adlibch[chan].keyon) return (0);
tmpfreq = (uint16_t) adlibch[chan].convfreq;
//if (adlibch[chan].octave<4) tmpfreq = tmpfreq>>1;
//if (adlibch[chan].octave>4) tmpfreq = tmpfreq<<1;
switch (adlibch[chan].octave) {
case 0:
tmpfreq = tmpfreq >> 4;
break;
case 1:
tmpfreq = tmpfreq >> 3;
break;
case 2:
tmpfreq = tmpfreq >> 2;
break;
case 3:
tmpfreq = tmpfreq >> 1;
break;
//case 4: tmpfreq = tmpfreq >> 1; break;
case 5:
tmpfreq = tmpfreq << 1;
break;
case 6:
tmpfreq = tmpfreq << 2;
break;
case 7:
tmpfreq = tmpfreq << 3;
}
if (operatornumber != -1) //Apply frequency multiplication factor?
{
tmpfreq *= adlibop[operatornumber].ModulatorFrequencyMultiple; //Apply the frequency multiplication factor!
}
return (tmpfreq);
}

//Original again!
OPTINLINE char adlibsample (uint8_t curchan) {
static uint64_t adlibstep[0x20];
float fullstep1, fullstep2, tempsample, result, tempstep, effectivemodulator, effectivecarrier;
byte operator1, operator2; //What operator is our modulator and carrier?

if (curchan >= NUMITEMS(adlibch)) return 0; //No sample with invalid channel!
if (adlibpercussion && (curchan >= 6) && (curchan <= 8)) //We're percussion?
{
return 0; //Percussion isn't supported yet!
}

//Determine our active operators!
operator1 = adliboperators[0][curchan]; //First operator to use!
operator2 = adliboperators[1][curchan]; //Second operator to use!

//Determine the type of modulation!
//Operator 1!
effectivemodulator = adlibfreq(operator1,curchan); //Effective modulator frequency!
fullstep1 = usesamplerate / effectivemodulator;
tempsample = oplwave[adlibop[operator1].wavesel&wavemask][(uint8_t)((double)adlibstep[operator1]++ / ((double)fullstep1 / (double)256))];
if (adlibop[operator1].AM) tempsample *= AMDepth; //Apply AM depth!

//Apply the volume envelope for operator 1!
tempstep = adlibenv[operator1]; //Current volume!
tempsample *= tempstep;
tempsample *= 2.0f;

//Operator 2!
effectivecarrier = adlibfreq(operator2,curchan); //Effective carrier init!
if (!adlibch[curchan].synthmode) effectivecarrier += tempsample; //FM using the first operator when needed!
fullstep2 = usesamplerate / effectivecarrier;
result = oplwave[adlibop[operator2].wavesel&wavemask][(uint8_t)((double)adlibstep[operator2]++ / ((double)fullstep2 / (double)256))];
if (adlibop[operator2].AM) result *= AMDepth; //Apply AM depth!

//Apply the volume envelope for operator 2!
tempstep = adlibenv[operator2]; //Current volume!
if (tempstep > 1.0f) tempstep = 1.0f; //Limit volume to 100%!
result *= tempstep;
result *= 2.0f;

//Perform additive synthesis when asked to do so.
if (adlibch[curchan].synthmode) result += tempsample; //Perform additive synthesis when needed!

//Make sure we don't overflow!
if (adlibstep[operator1] > fullstep1) adlibstep[operator1] = 0;
if (adlibstep[operator2] > fullstep2) adlibstep[operator2] = 0;

return (char)(result);
}

OPTINLINE char adlibgensample() {
uint8_t curchan;
char adlibaccum;
adlibaccum = 0;
for (curchan = 0; curchan < 9; curchan++)
{
if (adlibfreq(-1,curchan) != 0)
{
adlibaccum += adlibsample(curchan);
}
}
return (adlibaccum);
}

void tickadlib() {
uint8_t curchan;
for (curchan = 0; curchan<0x16; curchan++)
{
if (adlibfreq(-1,adliboperatorsreverse[curchan]) != 0)
{
if (adlibdidattack[curchan])
{
if (adlibenv[curchan]>0.0001f)
{
adlibenv[curchan] *= adlibdecay[curchan];
}
else
{
adlibenv[curchan] = 0; //Clear: nothing to be heard!
}
}
else
{
adlibenv[curchan] *= adlibattack[curchan];
if (adlibenv[curchan] >= 1.0) adlibdidattack[curchan] = 1;
}
}
}
}

byte adlib_soundGenerator(void* buf, uint_32 length, byte stereo, void *userdata) //Generate a sample!
{
if (stereo) return 0; //We don't support stereo!

byte filled,curchan;
filled = 0; //Default: not filled!
for (curchan=0; curchan<9; curchan++) { //Check for active channels!
if (adlibfreq (-1,curchan) !=0) {
filled = 1; //We're filled!
break; //Stop searching!
}
}

if (!filled) //Not filled?
{
return 0; //Unused channel!
}

uint_32 c;
c = length; //Init c!
char *data_mono;
data_mono = (char *)buf; //The data in correct samples!
for (;;) //Fill it!
{
//Left and right are the same!
*data_mono++ = adlibgensample(); //Generate a mono sample!
if (!--c) return SOUNDHANDLER_RESULT_FILLED; //Next item!
}
}

//Multicall speedup!
#define ADLIBMULTIPLIER 0

void initAdlib()
{
if (__HW_DISABLED) return; //Abort!

//All input!
AMDepth = dB2factor(1.0f, __MAX_DB);

if (__SOUND_ADLIB)
{
if (!addchannel(&adlib_soundGenerator,NULL,"Adlib",usesamplerate,0,0,SMPL8S)) //Start the sound emulation (mono) with automatic samples buffer?
{
dolog("adlib","Error registering sound channel for output!");
}
else
{
setVolume(&adlib_soundGenerator,NULL,ADLIB_VOLUME);
}
}
//dolog("adlib","sound channel added. registering ports...");
//Ignore unregistered channel, we need to be used by software!
register_PORTIN(baseport,&inadlib); //Status port (R)
//All output!
register_PORTOUT(baseport,&outadlib); //Address port (W)
register_PORTOUT(baseport+1,&outadlib); //Data port (W/O)
//dolog("adlib","Registering timer...");
addtimer(usesamplerate,&tickadlib,"AdlibAttackDecay",ADLIBMULTIPLIER,0,NULL); //We run at 49.716Khz, about every 20us.
//dolog("adlib","Ready"); //Ready to run!
}

void doneAdlib()
{
if (__HW_DISABLED) return; //Abort!
removetimer("AdlibAttackDecay"); //Stop the audio channel!
if (__SOUND_ADLIB)
{
removechannel(&adlib_soundGenerator,NULL,0); //Stop the sound emulation?
}
//Unregister the ports!
register_PORTIN(baseport,NULL);
//All output!
register_PORTOUT(baseport,NULL);
register_PORTOUT(baseport+1,NULL);
}

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 7 of 112, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie

In FM modulation, the output of modulator is summed to frequency, not multiplied. But in OPL chip, it is phase modulation which is a special case of FM modulation. Basically the same thing mathematically if the modulation is a sine wave (as it usually is).

I don't get your decibel problem. AM depth is still -4.8dB (X*0.576) or -1.0dB (X*0.891) compared to 0dB (X*1) - regardless of actual scale of signal or value of X. You don't need any decibel offset as long as you have correct amplitude on operator output. It should be very near +/-4096.

AM depth is actually triangle wave (in decibel domain, not linear domain), frequency 3.7Hz iirc.

Reply 8 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

So I should FM by adding the output of the modulator (-117 - +117) to the frequency to use for the carrier?

So I should use a triangle wave of 0.576tri(t) or 0.891tri(t) at 3.7Hz and multiply the result with the operator?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 9 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

This is my latest version of my adlib emulation:

Filename
adlib.c
File size
17.32 KiB
Downloads
108 downloads
File comment
My latest adlib emulation, based on fake86 of 20150606_1717.
File license
Fair use/fair dealing exception

I've split operator signal generation to remove the duplicates in the code. Now calcOperator(channel,operator,modulator) calculates the signal of the modulator and carrier and (in the case of the carrier) modulates it. It seems to work, but it still has the problem of going wrong when the volume of the carrier (adlibenv values for the carrier operators) gets to a low point (<0.39%? Maybe because it's below the limits of the char datatype (1/256 of the full volume, linearly calculated? So the carrier signal becomes -1, 0 or 1 depending on the rounding of the volume?)). Do you know why this problem occurs?

Also, what happens to the volume envelope of the modulator signals (index +00, +01, +02, +08, +09, +0A, +10, +11, +12 (added to base 0x20, 0x40, 0x60, 0x80 and 0xE0 in the adlib) for the modulator signal indexes)? Does it keep being 0 when finished with the release phase? Or does it reset and start with the Attack phase when finished with the Release phase? I assume the carrier ADSR will terminate when finished? So it won't restart when it's finished with the Release phase?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 10 of 112, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

So I should FM by adding the output of the modulator (-117 - +117) to the frequency to use for the carrier?

No. Add it to phase, not to frequency. The -117..+117 range is also weird to me, but also other internals are different so as long as the modulator output modulates the carrier phase by same factor it should sound similar.

superfury wrote:

So I should use a triangle wave of 0.576tri(t) or 0.891tri(t) at 3.7Hz and multiply the result with the operator?

No. No tremolo means the multiplier is always 1. With tremolo the multiplier varies from 1 down to 0.576 and back up to 1. But the waveform is logarithmic in PCM domain, because is linear triangular wave in decibel domain. In other words, it attenuates original waveform between 0dB and 4.8dB.

superfury wrote:

So the carrier signal becomes -1, 0 or 1 depending on the rounding of the volume?)). Do you know why this problem occurs?

I think you are correct and 8 bit resolution is not enough.

superfury wrote:

Also, what happens to the volume envelope of the modulator signals (index +00, +01, +02, +08, +09, +0A, +10, +11, +12 (added to base 0x20, 0x40, 0x60, 0x80 and 0xE0 in the adlib) for the modulator signal indexes)? Does it keep being 0 when finished with the release phase? Or does it reset and start with the Attack phase when finished with the Release phase? I assume the carrier ADSR will terminate when finished? So it won't restart when it's finished with the Release phase?

ADSR works identically on both operators. They won't restart automatically.

Anyway, neat emulator. I just have to ask about some implementation details. What kind of research have you made to make this, how have you ended up with 256-entry waveform table and why the output range is +/- 116 instead of something else?

Reply 11 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

It's an adjusted version of the fake86 open source adlib.c. I've currently upgraded it to 16 bit using formulas I'm using to generate a sinus of a frequency (A*sin(2.0*PI*((freq*time)+phase)), where the sinus function is transformed using simple if-else using inversion (=0-result) with/without depending on current phase =(fmod(modf(freq*time),0.5) >= 0.25 becomes 0 with waveform #3, wave 0 is sinus, wave 1 is modf(freq*time)>=0.5 becomes 0, wave 2&3 do inversion. That way, all 4 waveforms are generated using a sinus(2.0*pi*freq*time), which is then reversed and/or silenced upon these if-else constructs.

Latest version of my adlib.c:

/*
Fake86: A portable, open-source 8086 PC emulator.
Copyright (C)2010-2012 Mike Chambers

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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

/* adlib.c: very ugly Adlib OPL2 emulation for Fake86. very much a work in progress. :) */

//#include "config.h"
#include "headers/types.h" //Basic headers!
#include "headers/emu/sound.h" //Basic sound!
#include "headers/support/log.h" //Logging support!
#include "headers/hardware/ports.h" //Basic ports!
#include "headers/support/highrestimer.h" //High resoltion timer support!
#include "headers/emu/emu_misc.h" //Random short support for noise generation!
#include "headers/emu/timers.h" //Timer support for attack/decay!
//#include <stdint.h>
//#include <stdio.h>

#define uint8_t byte
#define uint16_t word

//Are we disabled?
#define __HW_DISABLED 0
//Use the adlib sound? If disabled, only run timers for the CPU. Sound will not actually be heard.
#define __SOUND_ADLIB 1
//What volume, in percent!
#define ADLIB_VOLUME 100.0f

//Maximum DB volume (equalling 100% volume)!
#define __MAX_DB 100.0f

//extern void set_port_write_redirector (uint16_t startport, uint16_t endport, void *callback);
//extern void set_port_read_redirector (uint16_t startport, uint16_t endport, void *callback);

uint16_t baseport = 0x388; //Adlib address(w)/status(r) port, +1=Data port (write only)
float usesamplerate = 14318180.0f/288.0f; //The sample rate to use for output!

uint16_t adlibregmem[0xFF], adlibaddr = 0;

byte adliboperators[2][9] = { //Groupings of 22 registers! (20,40,60,80,E0)
{ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12 },
{ 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15 }
};

byte adliboperatorsreverse[0x16] = { 0, 1, 2, 0, 1, 2,
255, 255,
Show last 387 lines
									3, 4, 5, 3, 4, 5,
255, 255,
6, 7, 8, 6, 7, 8}; //Channel lookup of adlib operators!

byte wavemask = 0; //Wave select mask!

struct structadlibop {
uint8_t wavesel;
uint8_t AM; //Amplitude modulation enabled?
float ModulatorFrequencyMultiple; //What harmonic to sound?
byte ReleaseImmediately; //Release even when the note is still turned on?
} adlibop[0x10];

struct structadlibchan {
uint16_t freq;
double convfreq;
uint8_t keyon;
uint16_t octave;
uint8_t synthmode; //What synthesizer mode (1=Additive synthesis, 0=Frequency modulation)
} adlibch[0x10];

double attacktable[0x10] = { 1.0003, 1.00025, 1.0002, 1.00015, 1.0001, 1.00009, 1.00008, 1.00007, 1.00006, 1.00005, 1.00004, 1.00003, 1.00002, 1.00001, 1.000005 }; //1.003, 1.05, 1.01, 1.015, 1.02, 1.025, 1.03, 1.035, 1.04, 1.045, 1.05, 1.055, 1.06, 1.065, 1.07, 1.075 };
double decaytable[0x10] = { 0.99999, 0.999985, 0.99998, 0.999975, 0.99997, 0.999965, 0.99996, 0.999955, 0.99995, 0.999945, 0.99994, 0.999935, 0.99994, 0.999925, 0.99992, 0.99991 };
double sustaintable[0x10] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 };
double adlibenv[0x20], adlibrelease[0x20], adlibsustain[0x20], adlibdecay[0x20], adlibattack[0x20];
uint8_t adlibenvstatus[0x20], adlibpercussion = 0, adlibstatus = 0;

float adlib_freq0[0x20], adlib_time[0x20]; //The q0quency and current time of an operator!

float AMDepth = 0.0f; //AM depth in dB!

uint16_t adlibport = 0x388;

OPTINLINE double dB2factor(double dB, double fMaxLevelDB)
{
return pow(10, ((dB - fMaxLevelDB) / 20));
}

OPTINLINE float calcModulatorFrequencyMultiple(byte data)
{
switch (data)
{
case 0: return 0.5f;
case 11: return 10.0f;
case 13: return 12.0f;
case 14: return 15.0f;
default: return (float)data; //The same number!
}
}

void outadlib (uint16_t portnum, uint8_t value) {
if (portnum==adlibport) {
adlibaddr = value;
return;
}
portnum = adlibaddr;
adlibregmem[portnum] = value;
lockaudio(); //Lock the audio: we're going to adjust audio information!
switch (portnum) {
case 0: //Waveform select enable
wavemask = (adlibregmem[0] & 0x20) ? 3 : 0; //Apply waveform mask!
break;
case 4: //timer control
if (value&0x80) {
adlibstatus = 0;
adlibregmem[4] = 0;
}
break;
case 0xBD:
if (value & 0x10) adlibpercussion = 1;
else adlibpercussion = 0;
AMDepth = (value & 0x80) ? dB2factor(4.8f, __MAX_DB) : dB2factor(1.0f, __MAX_DB); //AM depth in dB!
break;
}
if ((portnum >= 0x20) && (portnum <= 0x35)) //Various flags
{
portnum &= 0x1F;
adlibop[portnum].AM = (value & 0x80) ? 1 : 0; //Take the AM bit!
adlibop[portnum].ModulatorFrequencyMultiple = calcModulatorFrequencyMultiple(value & 0xF); //Which harmonic to use?
adlibop[portnum].ReleaseImmediately = (value & 0x20) ? 0 : 1; //Release when not sustain until release!
}
else if ( (portnum >= 0x60) && (portnum <= 0x75) ) { //attack/decay
portnum &= 0x1F;
adlibattack[portnum] = attacktable[15- (value>>4) ]*1.006;
adlibdecay[portnum] = decaytable[value&15];
}
else if ((portnum >= 0x80) && (portnum <= 0x95)) //sustain/release
{
portnum &= 0x1F;
adlibsustain[portnum] = sustaintable[15 - (value >> 4)];
adlibrelease[portnum] = decaytable[value & 15];
}
else if ( (portnum >= 0xA0) && (portnum <= 0xB8) )
{ //octave, freq, key on
if ((portnum & 0xF) < 9) //Ignore A9-AF!
{
portnum &= 0xF; //Get the channel to use!
if (!adlibch[portnum].keyon && ((adlibregmem[0xB0 + portnum] >> 5) & 1))
{
adlibenvstatus[adliboperators[0][portnum]] = 0;
adlibenvstatus[adliboperators[1][portnum]] = 0;
adlibenv[adliboperators[0][portnum]] = 0.0025;
adlibenv[adliboperators[1][portnum]] = 0.0025;
adlib_freq0[adliboperators[0][portnum]] = adlib_time[adliboperators[0][portnum]] = 0.0f; //Initialise operator signal!
adlib_freq0[adliboperators[1][portnum]] = adlib_time[adliboperators[1][portnum]] = 0.0f; //Initialise operator signal!
}
adlibch[portnum].freq = adlibregmem[0xA0 + portnum] | ((adlibregmem[0xB0 + portnum] & 3) << 8);
adlibch[portnum].convfreq = ((double)adlibch[portnum].freq * 0.7626459);
adlibch[portnum].keyon = (adlibregmem[0xB0 + portnum] >> 5) & 1;
adlibch[portnum].octave = (adlibregmem[0xB0 + portnum] >> 2) & 7;
}
}
else if ((portnum >= 0xC0) && (portnum <= 0xC8))
{
portnum &= 0xF;
adlibch[portnum].synthmode = (adlibregmem[0xC0 + portnum] & 1); //Save the synthesis mode!
}
else if ( (portnum >= 0xE0) && (portnum <= 0xF5) ) //waveform select
{
portnum &= 0x1F;
adlibop[portnum].wavesel = value&3;
}
unlockaudio(1); //Finished with audio update!
}

uint8_t inadlib (uint16_t portnum) {
if (!adlibregmem[4]) adlibstatus = 0;
else adlibstatus = 0x80;
adlibstatus = adlibstatus + (adlibregmem[4]&1) *0x40 + (adlibregmem[4]&2) *0x10;
return (adlibstatus);
}

uint16_t adlibfreq (sbyte operatornumber, uint8_t chan) {
//uint8_t downoct[4] = { 3, 2, 1, 0 };
//uint8_t upoct[3] = { 1, 2, 3 };
if (chan > 8) return (0); //Invalid channel: we only have 9 channels!
uint16_t tmpfreq;
tmpfreq = (uint16_t) adlibch[chan].convfreq;
//if (adlibch[chan].octave<4) tmpfreq = tmpfreq>>1;
//if (adlibch[chan].octave>4) tmpfreq = tmpfreq<<1;
switch (adlibch[chan].octave) {
case 0:
tmpfreq = tmpfreq >> 4;
break;
case 1:
tmpfreq = tmpfreq >> 3;
break;
case 2:
tmpfreq = tmpfreq >> 2;
break;
case 3:
tmpfreq = tmpfreq >> 1;
break;
//case 4: tmpfreq = tmpfreq >> 1; break;
case 5:
tmpfreq = tmpfreq << 1;
break;
case 6:
tmpfreq = tmpfreq << 2;
break;
case 7:
tmpfreq = tmpfreq << 3;
}
if (operatornumber != -1) //Apply frequency multiplication factor?
{
tmpfreq *= adlibop[operatornumber].ModulatorFrequencyMultiple; //Apply the frequency multiplication factor!
}
return (tmpfreq);
}

float adlib_scaleFactor = (SHRT_MAX - 1.0f);

OPTINLINE float adlibWave(byte signal, const float frequencytime) {
double x;
float result = sinf(2.0f * PI * frequencytime); //The sinus function!
float t = modf(frequencytime, &x);

switch (signal) {
case 0: //SINE?
return result; //SINE!
case 1: // Negative=0?
if (t > 0.5f) return 0.0f; //Negative!
return result; //Positive!
case 3: // Absolute with second half=0?
if (fmod(t,0.5f) > 0.25) return 0.0f; //Are we the second half of the half period? Clear the signal if so!
case 2: // Absolute?
if (result < 0) result = 0 - result; //Make positive!
return result; //Simply absolute!
case 4:
return RandomFloat(-1.0f, 1.0f); //Random noise!
default: //Unknown signal?
return 0.0f;
}
}

OPTINLINE float calcAdlibSignal(byte wave, float phase, float frequency, float *freq0, float *time) //Calculates a signal for input to the adlib synth!
{
const float adlib_sampleLength = 1.0f / usesamplerate;
if (frequency != *freq0) { //Frequency changed?
*time *= (*freq0 / frequency);
}

float result = (adlib_scaleFactor * adlibWave(wave, (frequency * *time)+phase)); //Set the channels!
*time += adlib_sampleLength; //Add 1 sample to the time!

float temp = *time*frequency; //Calculate!
if (temp > 1.0f) {
double d;
*time = modf(temp, &d) / frequency;
}

*freq0 = frequency;
return result; //Give the generated sample!
}

//Calculate an operator signal!
OPTINLINE float calcOperator(byte curchan, byte operator, float modulator)
{
float frequency, result; //What frequency to generate?

//Calculate the frequency to use!
frequency = adlibfreq(operator, curchan); //Effective carrier init!
if (adlibch[curchan].synthmode) modulator = 0.0f; //Don't FM using the first operator when needed, so no PM!
else modulator /= SHRT_MAX; //Make it a value between -1 and 1 for PM!

//Generate the signal!
result = calcAdlibSignal(adlibop[operator].wavesel&wavemask,modulator, frequency, &adlib_freq0[operator], &adlib_time[operator]);
result *= adlibenv[operator]; //Apply current volume of the ADSR envelope!

return result; //Give the result!
}

OPTINLINE short adlibsample (uint8_t curchan) {
float modulatorresult, result; //The operator result and the final result!

if (curchan >= NUMITEMS(adlibch)) return 0; //No sample with invalid channel!
if (adlibpercussion && (curchan >= 6) && (curchan <= 8)) //We're percussion?
{
return 0; //Percussion isn't supported yet!
}

//Determine the type of modulation!
//Operator 1!
modulatorresult = calcOperator(curchan, adliboperators[0][curchan], 0); //Calculate the modulator!
result = calcOperator(curchan, adliboperators[1][curchan], modulatorresult); //Calculate the carrier with applied modulator if needed!

//Perform additive synthesis when asked to do so.
if (adlibch[curchan].synthmode) result += modulatorresult; //Perform additive synthesis when needed!
return (short)result; //Give the result, converted to short!
}

OPTINLINE short adlibgensample() {
uint8_t curchan;
short adlibaccum;
adlibaccum = 0;
for (curchan = 0; curchan < 9; curchan++)
{
if (adlibenv[curchan] != 0.0f) //Anything to sound according to the volume envelope?
{
adlibaccum += adlibsample(curchan);
}
}
return (adlibaccum);
}

void tickadlib() {
uint8_t curop;
for (curop = 0; curop<0x16; curop++)
{
if (adlibenvstatus[curop])
{
if (adlibenvstatus[curop]>=2) //Sustaining/releasing?
{
dosustain: //Entered sustain phase!
if (((!adlibch[adliboperatorsreverse[curop]].keyon) || adlibop[curop].ReleaseImmediately) && (adlibenvstatus[curop] == 2)) //Release entered when sustaining?
{
adlibenvstatus[curop] = 3; //Releasing!
}
if (adlibenvstatus[curop] == 3) //Releasing?
{
adlibenv[curop] *= adlibrelease[curop]; //Release!
}
}
else if (adlibenv[curop]>adlibsustain[curop]) //Decay?
{
adlibenv[curop] *= adlibdecay[curop]; //Decay!
}
else
{
adlibenv[curop] = adlibsustain[curop]; //Sustain level!
adlibenvstatus[curop] = 2; //Enter sustain phase!
goto dosustain; //Do sustain!
}
}
else
{
adlibenv[curop] *= adlibattack[curop]; //Attack!
if (adlibenv[curop] >= 1.0)
{
adlibenv[curop] = 1.0; //We're at 1.0 to start decaying!
adlibenvstatus[curop] = 1; //Enter decay phase!
}
}
}
}

byte adlib_soundGenerator(void* buf, uint_32 length, byte stereo, void *userdata) //Generate a sample!
{
if (stereo) return 0; //We don't support stereo!

byte filled,curchan;
filled = 0; //Default: not filled!
for (curchan=0; curchan<9; curchan++) { //Check for active channels!
if (adlibenv[adliboperators[1][curchan]] !=0.0f) { //Do we generate sound on this channel?
filled = 1; //We're filled!
break; //Stop searching!
}
}

if (!filled) return SOUNDHANDLER_RESULT_NOTFILLED; //Not filled: nothing to sound!

uint_32 c;
c = length; //Init c!
short *data_mono;
data_mono = (short *)buf; //The data in correct samples!
for (;;) //Fill it!
{
//Left and right are the same!
*data_mono++ = adlibgensample(); //Generate a mono sample!
if (!--c) return SOUNDHANDLER_RESULT_FILLED; //Next item!
}
}

//Multicall speedup!
#define ADLIBMULTIPLIER 0

void initAdlib()
{
if (__HW_DISABLED) return; //Abort!

int i;
for (i = 0; i < 9; i++)
{
adlibch[i].keyon = 0; //Initialise key on!
adlibenvstatus[adliboperators[0][i]] = 3; //Initialise to unused!
adlibenvstatus[adliboperators[1][i]] = 3; //Initialise to unused!
}

//All input!
AMDepth = dB2factor(1.0f, __MAX_DB);

if (__SOUND_ADLIB)
{
if (!addchannel(&adlib_soundGenerator,NULL,"Adlib",usesamplerate,0,0,SMPL16S)) //Start the sound emulation (mono) with automatic samples buffer?
{
dolog("adlib","Error registering sound channel for output!");
}
else
{
setVolume(&adlib_soundGenerator,NULL,ADLIB_VOLUME);
}
}
//dolog("adlib","sound channel added. registering ports...");
//Ignore unregistered channel, we need to be used by software!
register_PORTIN(baseport,&inadlib); //Status port (R)
//All output!
register_PORTOUT(baseport,&outadlib); //Address port (W)
register_PORTOUT(baseport+1,&outadlib); //Data port (W/O)
//dolog("adlib","Registering timer...");
addtimer(usesamplerate,&tickadlib,"AdlibAttackDecay",ADLIBMULTIPLIER,0,NULL); //We run at 49.716Khz, about every 20us.
//dolog("adlib","Ready"); //Ready to run!
}

void doneAdlib()
{
if (__HW_DISABLED) return; //Abort!
removetimer("AdlibAttackDecay"); //Stop the audio channel!
if (__SOUND_ADLIB)
{
removechannel(&adlib_soundGenerator,NULL,0); //Stop the sound emulation?
}
//Unregister the ports!
register_PORTIN(baseport,NULL);
//All output!
register_PORTOUT(baseport,NULL);
register_PORTOUT(baseport+1,NULL);
}

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 12 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

This is my current adlib emulation:

/*
Fake86: A portable, open-source 8086 PC emulator.
Copyright (C)2010-2012 Mike Chambers

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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

/* adlib.c: very ugly Adlib OPL2 emulation for Fake86. very much a work in progress. :) */

//#include "config.h"
#include "headers/types.h" //Basic headers!
#include "headers/emu/sound.h" //Basic sound!
#include "headers/support/log.h" //Logging support!
#include "headers/hardware/ports.h" //Basic ports!
#include "headers/support/highrestimer.h" //High resoltion timer support!
#include "headers/emu/emu_misc.h" //Random short support for noise generation!
#include "headers/emu/timers.h" //Timer support for attack/decay!
//#include <stdint.h>
//#include <stdio.h>

#define uint8_t byte
#define uint16_t word

//Are we disabled?
#define __HW_DISABLED 0
//Use the adlib sound? If disabled, only run timers for the CPU. Sound will not actually be heard.
#define __SOUND_ADLIB 1
//What volume, in percent!
#define ADLIB_VOLUME 100.0f

//Maximum DB volume (equalling 100% volume)!
#define __MAX_DB 100.0f

//extern void set_port_write_redirector (uint16_t startport, uint16_t endport, void *callback);
//extern void set_port_read_redirector (uint16_t startport, uint16_t endport, void *callback);

uint16_t baseport = 0x388; //Adlib address(w)/status(r) port, +1=Data port (write only)
float usesamplerate = 14318180.0f/288.0f; //The sample rate to use for output!

uint16_t adlibregmem[0xFF], adlibaddr = 0;

byte adliboperators[2][9] = { //Groupings of 22 registers! (20,40,60,80,E0)
{ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12 },
{ 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15 }
};

byte adliboperatorsreverse[0x16] = { 0, 1, 2, 0, 1, 2,
255, 255,
Show last 401 lines
									3, 4, 5, 3, 4, 5,
255, 255,
6, 7, 8, 6, 7, 8}; //Channel lookup of adlib operators!

static const double feedbacklookup[8] = { 0, PI / 16.0, PI / 8.0, PI / 4.0, PI / 2.0, PI, PI*2.0, PI*4.0 }; //The feedback to use from opl3emu! Seems to be half a sinus wave per number!

byte wavemask = 0; //Wave select mask!

struct structadlibop {
uint8_t wavesel;
uint8_t AM; //Amplitude modulation enabled?
float ModulatorFrequencyMultiple; //What harmonic to sound?
byte ReleaseImmediately; //Release even when the note is still turned on?
} adlibop[0x10];

struct structadlibchan {
uint16_t freq;
double convfreq;
uint8_t keyon;
uint16_t octave;
uint8_t synthmode; //What synthesizer mode (1=Additive synthesis, 0=Frequency modulation)
float feedback; //The feedback strength of the modulator signal.
} adlibch[0x10];

double attacktable[0x10] = { 1.0003, 1.00025, 1.0002, 1.00015, 1.0001, 1.00009, 1.00008, 1.00007, 1.00006, 1.00005, 1.00004, 1.00003, 1.00002, 1.00001, 1.000005 }; //1.003, 1.05, 1.01, 1.015, 1.02, 1.025, 1.03, 1.035, 1.04, 1.045, 1.05, 1.055, 1.06, 1.065, 1.07, 1.075 };
double decaytable[0x10] = { 0.99999, 0.999985, 0.99998, 0.999975, 0.99997, 0.999965, 0.99996, 0.999955, 0.99995, 0.999945, 0.99994, 0.999935, 0.99994, 0.999925, 0.99992, 0.99991 };
double sustaintable[0x10] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 };
double adlibenv[0x20], adlibrelease[0x20], adlibsustain[0x20], adlibdecay[0x20], adlibattack[0x20];
uint8_t adlibenvstatus[0x20], adlibpercussion = 0, adlibstatus = 0;

float adlib_freq0[0x20], adlib_time[0x20]; //The q0quency and current time of an operator!

float AMDepth = 0.0f; //AM depth in dB!

uint16_t adlibport = 0x388;

OPTINLINE double dB2factor(double dB, double fMaxLevelDB)
{
return pow(10, ((dB - fMaxLevelDB) / 20));
}

OPTINLINE float calcModulatorFrequencyMultiple(byte data)
{
switch (data)
{
case 0: return 0.5f;
case 11: return 10.0f;
case 13: return 12.0f;
case 14: return 15.0f;
default: return (float)data; //The same number!
}
}

void outadlib (uint16_t portnum, uint8_t value) {
if (portnum==adlibport) {
adlibaddr = value;
return;
}
portnum = adlibaddr;
adlibregmem[portnum] = value;
lockaudio(); //Lock the audio: we're going to adjust audio information!
switch (portnum) {
case 0: //Waveform select enable
wavemask = (adlibregmem[0] & 0x20) ? 3 : 0; //Apply waveform mask!
break;
case 4: //timer control
if (value&0x80) {
adlibstatus = 0;
adlibregmem[4] = 0;
}
break;
case 0xBD:
if (value & 0x10) adlibpercussion = 1;
else adlibpercussion = 0;
AMDepth = (value & 0x80) ? dB2factor(4.8f, __MAX_DB) : dB2factor(1.0f, __MAX_DB); //AM depth in dB!
break;
}
if ((portnum >= 0x20) && (portnum <= 0x35)) //Various flags
{
portnum &= 0x1F;
adlibop[portnum].AM = (value & 0x80) ? 1 : 0; //Take the AM bit!
adlibop[portnum].ModulatorFrequencyMultiple = calcModulatorFrequencyMultiple(value & 0xF); //Which harmonic to use?
adlibop[portnum].ReleaseImmediately = (value & 0x20) ? 0 : 1; //Release when not sustain until release!
}
else if ( (portnum >= 0x60) && (portnum <= 0x75) ) { //attack/decay
portnum &= 0x1F;
adlibattack[portnum] = attacktable[15- (value>>4) ]*1.006;
adlibdecay[portnum] = decaytable[value&15];
}
else if ((portnum >= 0x80) && (portnum <= 0x95)) //sustain/release
{
portnum &= 0x1F;
adlibsustain[portnum] = sustaintable[15 - (value >> 4)];
adlibrelease[portnum] = decaytable[value & 15];
}
else if ( (portnum >= 0xA0) && (portnum <= 0xB8) )
{ //octave, freq, key on
if ((portnum & 0xF) < 9) //Ignore A9-AF!
{
portnum &= 0xF; //Get the channel to use!
if (!adlibch[portnum].keyon && ((adlibregmem[0xB0 + portnum] >> 5) & 1))
{
adlibenvstatus[adliboperators[0][portnum]] = 0;
adlibenvstatus[adliboperators[1][portnum]] = 0;
adlibenv[adliboperators[0][portnum]] = 0.0025;
adlibenv[adliboperators[1][portnum]] = 0.0025;
adlib_freq0[adliboperators[0][portnum]] = adlib_time[adliboperators[0][portnum]] = 0.0f; //Initialise operator signal!
adlib_freq0[adliboperators[1][portnum]] = adlib_time[adliboperators[1][portnum]] = 0.0f; //Initialise operator signal!
}
adlibch[portnum].freq = adlibregmem[0xA0 + portnum] | ((adlibregmem[0xB0 + portnum] & 3) << 8);
adlibch[portnum].convfreq = ((double)adlibch[portnum].freq * 0.7626459);
adlibch[portnum].keyon = (adlibregmem[0xB0 + portnum] >> 5) & 1;
adlibch[portnum].octave = (adlibregmem[0xB0 + portnum] >> 2) & 7;
}
}
else if ((portnum >= 0xC0) && (portnum <= 0xC8))
{
portnum &= 0xF;
adlibch[portnum].synthmode = (adlibregmem[0xC0 + portnum] & 1); //Save the synthesis mode!
byte feedback;
feedback = (adlibregmem[0xC0 + portnum] >> 1) & 3; //Get the feedback value used!
adlibch[portnum].feedback = feedbacklookup[feedback]; //Convert to a feedback of the modulator signal!
}
else if ( (portnum >= 0xE0) && (portnum <= 0xF5) ) //waveform select
{
portnum &= 0x1F;
adlibop[portnum].wavesel = value&3;
}
unlockaudio(1); //Finished with audio update!
}

uint8_t inadlib (uint16_t portnum) {
if (!adlibregmem[4]) adlibstatus = 0;
else adlibstatus = 0x80;
adlibstatus = adlibstatus + (adlibregmem[4]&1) *0x40 + (adlibregmem[4]&2) *0x10;
return (adlibstatus);
}

uint16_t adlibfreq (sbyte operatornumber, uint8_t chan) {
//uint8_t downoct[4] = { 3, 2, 1, 0 };
//uint8_t upoct[3] = { 1, 2, 3 };
if (chan > 8) return (0); //Invalid channel: we only have 9 channels!
uint16_t tmpfreq;
tmpfreq = (uint16_t) adlibch[chan].convfreq;
//if (adlibch[chan].octave<4) tmpfreq = tmpfreq>>1;
//if (adlibch[chan].octave>4) tmpfreq = tmpfreq<<1;
switch (adlibch[chan].octave) {
case 0:
tmpfreq = tmpfreq >> 4;
break;
case 1:
tmpfreq = tmpfreq >> 3;
break;
case 2:
tmpfreq = tmpfreq >> 2;
break;
case 3:
tmpfreq = tmpfreq >> 1;
break;
//case 4: tmpfreq = tmpfreq >> 1; break;
case 5:
tmpfreq = tmpfreq << 1;
break;
case 6:
tmpfreq = tmpfreq << 2;
break;
case 7:
tmpfreq = tmpfreq << 3;
}
if (operatornumber != -1) //Apply frequency multiplication factor?
{
tmpfreq *= adlibop[operatornumber].ModulatorFrequencyMultiple; //Apply the frequency multiplication factor!
}
return (tmpfreq);
}

float adlib_scaleFactor = (SHRT_MAX - 1.0f);

OPTINLINE float adlibWave(byte signal, const float frequencytime) {
double x;
float result = sinf(2.0f * PI * frequencytime); //The sinus function!
float t = modf(frequencytime, &x);

switch (signal) {
case 0: //SINE?
return result; //SINE!
case 1: // Negative=0?
if (t > 0.5f) return 0.0f; //Negative!
return result; //Positive!
case 3: // Absolute with second half=0?
if (fmod(t,0.5f) > 0.25) return 0.0f; //Are we the second half of the half period? Clear the signal if so!
case 2: // Absolute?
if (result < 0) result = 0 - result; //Make positive!
return result; //Simply absolute!
case 4:
return RandomFloat(-1.0f, 1.0f); //Random noise!
default: //Unknown signal?
return 0.0f;
}
}

OPTINLINE float calcAdlibSignal(byte wave, float phase, float frequency, float *freq0, float *time) //Calculates a signal for input to the adlib synth!
{
const float adlib_sampleLength = 1.0f / usesamplerate;
if (frequency != *freq0) { //Frequency changed?
*time *= (*freq0 / frequency);
}

float result = adlibWave(wave, (frequency * *time)+phase); //Set the channels!
*time += adlib_sampleLength; //Add 1 sample to the time!

float temp = *time*frequency; //Calculate!
if (temp > 1.0f) {
double d;
*time = modf(temp, &d) / frequency;
}

*freq0 = frequency;
return result; //Give the generated sample!
}

//Calculate an operator signal!
OPTINLINE float calcOperator(byte curchan, byte operator, float modulator, float feedback)
{
float frequency, result; //What frequency to generate?

//Calculate the frequency to use!
frequency = adlibfreq(operator, curchan); //Effective carrier init!
if (adlibch[curchan].synthmode) modulator = 0.0f; //Don't FM using the first operator when needed, so no PM!

//Generate the signal!
result = calcAdlibSignal(adlibop[operator].wavesel&wavemask,modulator, frequency, &adlib_freq0[operator], &adlib_time[operator]);
result *= adlibenv[operator]; //Apply current volume of the ADSR envelope!

if (feedback!=0.0f) //We're using feedback?
{
result *= feedback; //Convert to feedback ratio!
return calcOperator(curchan, operator,result, 0); //Apply feedback using our own signal as the modulator!
}

return result; //Give the result!
}

OPTINLINE short adlibsample (uint8_t curchan) {
float modulatorresult, result; //The operator result and the final result!

if (curchan >= NUMITEMS(adlibch)) return 0; //No sample with invalid channel!
if (adlibpercussion && (curchan >= 6) && (curchan <= 8)) //We're percussion?
{
return 0; //Percussion isn't supported yet!
}

//Determine the type of modulation!
//Operator 1!
modulatorresult = calcOperator(curchan, adliboperators[0][curchan], 0,adlibch[curchan].feedback); //Calculate the modulator!
result = calcOperator(curchan, adliboperators[1][curchan], modulatorresult,0); //Calculate the carrier with applied modulator if needed!

//Perform additive synthesis when asked to do so.
if (adlibch[curchan].synthmode) result += modulatorresult; //Perform additive synthesis when needed!

result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale!
return (short)result; //Give the result, converted to short!
}

OPTINLINE short adlibgensample() {
uint8_t curchan;
short adlibaccum;
adlibaccum = 0;
for (curchan = 0; curchan < 9; curchan++)
{
if (adlibenv[curchan] != 0.0f) //Anything to sound according to the volume envelope?
{
adlibaccum += adlibsample(curchan);
}
}
return (adlibaccum);
}

void tickadlib() {
uint8_t curop;
for (curop = 0; curop<0x16; curop++)
{
if (adlibenvstatus[curop])
{
if (adlibenvstatus[curop]>=2) //Sustaining/releasing?
{
dosustain: //Entered sustain phase!
if (((!adlibch[adliboperatorsreverse[curop]].keyon) || adlibop[curop].ReleaseImmediately) && (adlibenvstatus[curop] == 2)) //Release entered when sustaining?
{
adlibenvstatus[curop] = 3; //Releasing!
}
if (adlibenvstatus[curop] == 3) //Releasing?
{
adlibenv[curop] *= adlibrelease[curop]; //Release!
}
}
else if (adlibenv[curop]>adlibsustain[curop]) //Decay?
{
adlibenv[curop] *= adlibdecay[curop]; //Decay!
}
else
{
adlibenv[curop] = adlibsustain[curop]; //Sustain level!
adlibenvstatus[curop] = 2; //Enter sustain phase!
goto dosustain; //Do sustain!
}
}
else
{
adlibenv[curop] *= adlibattack[curop]; //Attack!
if (adlibenv[curop] >= 1.0)
{
adlibenv[curop] = 1.0; //We're at 1.0 to start decaying!
adlibenvstatus[curop] = 1; //Enter decay phase!
}
}
}
}

byte adlib_soundGenerator(void* buf, uint_32 length, byte stereo, void *userdata) //Generate a sample!
{
if (stereo) return 0; //We don't support stereo!

byte filled,curchan;
filled = 0; //Default: not filled!
for (curchan=0; curchan<9; curchan++) { //Check for active channels!
if (adlibenv[adliboperators[1][curchan]] !=0.0f) { //Do we generate sound on this channel?
filled = 1; //We're filled!
break; //Stop searching!
}
}

if (!filled) return SOUNDHANDLER_RESULT_NOTFILLED; //Not filled: nothing to sound!

uint_32 c;
c = length; //Init c!
short *data_mono;
data_mono = (short *)buf; //The data in correct samples!
for (;;) //Fill it!
{
//Left and right are the same!
*data_mono++ = adlibgensample(); //Generate a mono sample!
if (!--c) return SOUNDHANDLER_RESULT_FILLED; //Next item!
}
}

//Multicall speedup!
#define ADLIBMULTIPLIER 0

void initAdlib()
{
if (__HW_DISABLED) return; //Abort!

int i;
for (i = 0; i < 9; i++)
{
adlibch[i].keyon = 0; //Initialise key on!
adlibch[i].feedback = 0.0f; //No feedback!
adlibenvstatus[adliboperators[0][i]] = 3; //Initialise to unused!
adlibenvstatus[adliboperators[1][i]] = 3; //Initialise to unused!
}

//All input!
AMDepth = dB2factor(1.0f, __MAX_DB);

if (__SOUND_ADLIB)
{
if (!addchannel(&adlib_soundGenerator,NULL,"Adlib",usesamplerate,0,0,SMPL16S)) //Start the sound emulation (mono) with automatic samples buffer?
{
dolog("adlib","Error registering sound channel for output!");
}
else
{
setVolume(&adlib_soundGenerator,NULL,ADLIB_VOLUME);
}
}
//dolog("adlib","sound channel added. registering ports...");
//Ignore unregistered channel, we need to be used by software!
register_PORTIN(baseport,&inadlib); //Status port (R)
//All output!
register_PORTOUT(baseport,&outadlib); //Address port (W)
register_PORTOUT(baseport+1,&outadlib); //Data port (W/O)
//dolog("adlib","Registering timer...");
addtimer(usesamplerate,&tickadlib,"AdlibAttackDecay",ADLIBMULTIPLIER,0,NULL); //We run at 49.716Khz, about every 20us.
//dolog("adlib","Ready"); //Ready to run!
}

void doneAdlib()
{
if (__HW_DISABLED) return; //Abort!
removetimer("AdlibAttackDecay"); //Stop the audio channel!
if (__SOUND_ADLIB)
{
removechannel(&adlib_soundGenerator,NULL,0); //Stop the sound emulation?
}
//Unregister the ports!
register_PORTIN(baseport,NULL);
//All output!
register_PORTOUT(baseport,NULL);
register_PORTOUT(baseport+1,NULL);
}

Basic modulation seems to go fine, but I'm still a bit uncertain about the actual calculation of the feedback value. I'm currently just using the multiples of PI (according to opl3emu) to use with FM modulation on the modulator. (modulator = modulator FM (modulator times x*(2*PI*factor)), where factor is a power of 2) So 7 for full modulation, 1 for 1/7 modulation etc. Anyone knows the correct calculation for this? Is it true that values 6&7 have the same result? Phase modulation with factors of multiples of 2 times PI will have no effect, since a shift of 2 times pi and 4 times pi is the same (both 360 degrees)? Since phase modulating with a whole sine wave shifted is the same (for 2 times PI and 4 times PI)? PI will shift half a phase, and PI/2, PI/4, PI/8 and PI/16 will shift parts of the phase?

I also still seem to notice that when a sound finished, volume seems to go up before terminating the volume envelope (which should be impossible with a factor of 0.0-1.0). Anyone can see what's the cause of this?

Edit: The current formula for frequency modulation with(out) feedback is like this:

y(t)=sin (2*pi*((f2*t)+sin(2*pi*((f1*t)+(sin(2*pi*f1*t)*(powerof2*pi))))))

f1 is the modulator frequency, f2 is the carrier frequency, t is time (seconds) and powerof2 is the feedback strength (0, 1/16, 1/8, 1/4, 1/2, 1, 2 or 4. These are the values from the feedback strength lookup table). Anyone can tell me if this is correct?

Last edited by superfury on 2015-06-13, 21:13. Edited 1 time in total.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 13 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

This is my latest version of my adlib emulation:

/*
Fake86: A portable, open-source 8086 PC emulator.
Copyright (C)2010-2012 Mike Chambers

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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

/* adlib.c: very ugly Adlib OPL2 emulation for Fake86. very much a work in progress. :) */

//#include "config.h"
#include "headers/types.h" //Basic headers!
#include "headers/emu/sound.h" //Basic sound!
#include "headers/support/log.h" //Logging support!
#include "headers/hardware/ports.h" //Basic ports!
#include "headers/support/highrestimer.h" //High resoltion timer support!
#include "headers/emu/emu_misc.h" //Random short support for noise generation!
#include "headers/emu/timers.h" //Timer support for attack/decay!
//#include <stdint.h>
//#include <stdio.h>

#define uint8_t byte
#define uint16_t word

//Are we disabled?
#define __HW_DISABLED 0
//Use the adlib sound? If disabled, only run timers for the CPU. Sound will not actually be heard.
#define __SOUND_ADLIB 1
//What volume, in percent!
#define ADLIB_VOLUME 100.0f
//What volume is the minimum volume to be heard!
#define __MIN_VOL (1.0f / SHRT_MAX)

//extern void set_port_write_redirector (uint16_t startport, uint16_t endport, void *callback);
//extern void set_port_read_redirector (uint16_t startport, uint16_t endport, void *callback);

uint16_t baseport = 0x388; //Adlib address(w)/status(r) port, +1=Data port (write only)
float usesamplerate = 14318180.0f/288.0f; //The sample rate to use for output!

uint16_t adlibregmem[0xFF], adlibaddr = 0;

byte adliboperators[2][9] = { //Groupings of 22 registers! (20,40,60,80,E0)
{ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12 },
{ 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15 }
};

byte adliboperatorsreverse[0x16] = { 0, 1, 2, 0, 1, 2,
255, 255,
3, 4, 5, 3, 4, 5,
Show last 431 lines
									255, 255,
6, 7, 8, 6, 7, 8}; //Channel lookup of adlib operators!

static const double feedbacklookup[8] = { 0, PI / 16.0, PI / 8.0, PI / 4.0, PI / 2.0, PI, PI*2.0, PI*4.0 }; //The feedback to use from opl3emu! Seems to be half a sinus wave per number!

byte wavemask = 0; //Wave select mask!

struct structadlibop {
//Effects
uint8_t AM; //Amplitude modulation enabled?
byte ReleaseImmediately; //Release even when the note is still turned on?

//Volume envelope
float attack, decay, sustain, release, volenv; //Volume envelope!
uint8_t volenvstatus;

//Signal generation
uint8_t wavesel;
float freq0, time; //The q0quency and current time of an operator!
float ModulatorFrequencyMultiple; //What harmonic to sound?
} adlibop[0x16];

struct structadlibchan {
uint16_t freq;
double convfreq;
uint8_t keyon;
uint16_t octave;
uint8_t synthmode; //What synthesizer mode (1=Additive synthesis, 0=Frequency modulation)
float feedback; //The feedback strength of the modulator signal.
} adlibch[0x10];

double attacktable[0x10] = { 1.0003, 1.00025, 1.0002, 1.00015, 1.0001, 1.00009, 1.00008, 1.00007, 1.00006, 1.00005, 1.00004, 1.00003, 1.00002, 1.00001, 1.000005 }; //1.003, 1.05, 1.01, 1.015, 1.02, 1.025, 1.03, 1.035, 1.04, 1.045, 1.05, 1.055, 1.06, 1.065, 1.07, 1.075 };
double decaytable[0x10] = { 0.99999, 0.999985, 0.99998, 0.999975, 0.99997, 0.999965, 0.99996, 0.999955, 0.99995, 0.999945, 0.99994, 0.999935, 0.99994, 0.999925, 0.99992, 0.99991 };
double sustaintable[0x10] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 };

uint8_t adlibpercussion = 0, adlibstatus = 0;

uint16_t adlibport = 0x388;

OPTINLINE double dB2factor(double dB, double fMaxLevelDB)
{
return pow(10, ((dB - fMaxLevelDB) / 20));
}

OPTINLINE float calcModulatorFrequencyMultiple(byte data)
{
switch (data)
{
case 0: return 0.5f;
case 11: return 10.0f;
case 13: return 12.0f;
case 14: return 15.0f;
default: return (float)data; //The same number!
}
}

void outadlib (uint16_t portnum, uint8_t value) {
if (portnum==adlibport) {
adlibaddr = value;
return;
}
portnum = adlibaddr;
adlibregmem[portnum] = value;
lockaudio(); //Lock the audio: we're going to adjust audio information!
switch (portnum) {
case 0: //Waveform select enable
wavemask = (adlibregmem[0] & 0x20) ? 3 : 0; //Apply waveform mask!
break;
case 4: //timer control
if (value&0x80) {
adlibstatus = 0;
adlibregmem[4] = 0;
}
break;
case 0xBD:
if (value & 0x10) adlibpercussion = 1;
else adlibpercussion = 0;
break;
}
if ((portnum >= 0x20) && (portnum <= 0x35)) //Various flags
{
portnum &= 0x1F;
adlibop[portnum].AM = (value & 0x80) ? 1 : 0; //Take the AM bit!
adlibop[portnum].ModulatorFrequencyMultiple = calcModulatorFrequencyMultiple(value & 0xF); //Which harmonic to use?
adlibop[portnum].ReleaseImmediately = (value & 0x20) ? 0 : 1; //Release when not sustain until release!
}
else if ( (portnum >= 0x60) && (portnum <= 0x75) ) { //attack/decay
portnum &= 0x1F;
adlibop[portnum].attack = attacktable[15- (value>>4) ]*1.006;
adlibop[portnum].decay = decaytable[value&15];
}
else if ((portnum >= 0x80) && (portnum <= 0x95)) //sustain/release
{
portnum &= 0x1F;
adlibop[portnum].sustain = sustaintable[15 - (value >> 4)];
adlibop[portnum].release = decaytable[value & 15];
}
else if ( (portnum >= 0xA0) && (portnum <= 0xB8) )
{ //octave, freq, key on
if ((portnum & 0xF) < 9) //Ignore A9-AF!
{
portnum &= 0xF; //Get the channel to use!
if (!adlibch[portnum].keyon && ((adlibregmem[0xB0 + portnum] >> 5) & 1))
{
adlibop[adliboperators[0][portnum]].volenvstatus = 1; //Start attacking!
adlibop[adliboperators[1][portnum]].volenvstatus = 1; //Start attacking!
adlibop[adliboperators[0][portnum]].volenv = 0.0025;
adlibop[adliboperators[1][portnum]].volenv = 0.0025;
adlibop[adliboperators[0][portnum]].freq0 = adlibop[adliboperators[0][portnum]].time = 0.0f; //Initialise operator signal!
adlibop[adliboperators[1][portnum]].freq0 = adlibop[adliboperators[1][portnum]].time = 0.0f; //Initialise operator signal!
}
adlibch[portnum].freq = adlibregmem[0xA0 + portnum] | ((adlibregmem[0xB0 + portnum] & 3) << 8);
adlibch[portnum].convfreq = ((double)adlibch[portnum].freq * 0.7626459);
adlibch[portnum].keyon = (adlibregmem[0xB0 + portnum] >> 5) & 1;
adlibch[portnum].octave = (adlibregmem[0xB0 + portnum] >> 2) & 7;
}
}
else if ((portnum >= 0xC0) && (portnum <= 0xC8))
{
portnum &= 0xF;
adlibch[portnum].synthmode = (adlibregmem[0xC0 + portnum] & 1); //Save the synthesis mode!
byte feedback;
feedback = (adlibregmem[0xC0 + portnum] >> 1) & 3; //Get the feedback value used!
adlibch[portnum].feedback = feedbacklookup[feedback]; //Convert to a feedback of the modulator signal!
}
else if ( (portnum >= 0xE0) && (portnum <= 0xF5) ) //waveform select
{
portnum &= 0x1F;
adlibop[portnum].wavesel = value&3;
}
unlockaudio(1); //Finished with audio update!
}

uint8_t inadlib (uint16_t portnum) {
if (!adlibregmem[4]) adlibstatus = 0;
else adlibstatus = 0x80;
adlibstatus = adlibstatus + (adlibregmem[4]&1) *0x40 + (adlibregmem[4]&2) *0x10;
return (adlibstatus);
}

uint16_t adlibfreq (sbyte operatornumber, uint8_t chan) {
//uint8_t downoct[4] = { 3, 2, 1, 0 };
//uint8_t upoct[3] = { 1, 2, 3 };
if (chan > 8) return (0); //Invalid channel: we only have 9 channels!
uint16_t tmpfreq;
tmpfreq = (uint16_t) adlibch[chan].convfreq;
//if (adlibch[chan].octave<4) tmpfreq = tmpfreq>>1;
//if (adlibch[chan].octave>4) tmpfreq = tmpfreq<<1;
switch (adlibch[chan].octave) {
case 0:
tmpfreq = tmpfreq >> 4;
break;
case 1:
tmpfreq = tmpfreq >> 3;
break;
case 2:
tmpfreq = tmpfreq >> 2;
break;
case 3:
tmpfreq = tmpfreq >> 1;
break;
//case 4: tmpfreq = tmpfreq >> 1; break;
case 5:
tmpfreq = tmpfreq << 1;
break;
case 6:
tmpfreq = tmpfreq << 2;
break;
case 7:
tmpfreq = tmpfreq << 3;
}
if (operatornumber != -1) //Apply frequency multiplication factor?
{
tmpfreq *= adlibop[operatornumber].ModulatorFrequencyMultiple; //Apply the frequency multiplication factor!
}
return (tmpfreq);
}

float adlib_scaleFactor = (SHRT_MAX - 1.0f);

OPTINLINE float adlibWave(byte signal, const float frequencytime) {
double x;
float result = sinf(2.0f * PI * frequencytime); //The sinus function!
float t = modf(frequencytime, &x);

switch (signal) {
case 0: //SINE?
return result; //SINE!
case 1: // Negative=0?
if (t > 0.5f) return 0.0f; //Negative!
return result; //Positive!
case 3: // Absolute with second half=0?
if (fmod(t,0.5f) > 0.25) return 0.0f; //Are we the second half of the half period? Clear the signal if so!
case 2: // Absolute?
if (result < 0) result = 0 - result; //Make positive!
return result; //Simply absolute!
case 4:
return RandomFloat(-1.0f, 1.0f); //Random noise!
default: //Unknown signal?
return 0.0f;
}
}

OPTINLINE float calcAdlibSignal(byte wave, float phase, float frequency, float *freq0, float *time) //Calculates a signal for input to the adlib synth!
{
const float adlib_sampleLength = 1.0f / usesamplerate;
if (frequency != *freq0) { //Frequency changed?
*time *= (*freq0 / frequency);
}

float result = adlibWave(wave, (frequency * *time)+phase); //Set the channels!
*time += adlib_sampleLength; //Add 1 sample to the time!

float temp = *time*frequency; //Calculate!
if (temp > 1.0f) {
double d;
*time = modf(temp, &d) / frequency;
}

*freq0 = frequency;
return result; //Give the generated sample!
}

//Calculate an operator signal!
OPTINLINE float calcOperator(byte curchan, byte operator, float modulator, float feedback)
{
float frequency, result; //What frequency to generate?

//Calculate the frequency to use!
frequency = adlibfreq(operator, curchan); //Effective carrier init!
if (adlibch[curchan].synthmode) modulator = 0.0f; //Don't FM using the first operator when needed, so no PM!

//Generate the signal!
result = calcAdlibSignal(adlibop[operator].wavesel&wavemask,modulator, frequency, &adlibop[operator].freq0, &adlibop[operator].time);
result *= adlibop[operator].volenv; //Apply current volume of the ADSR envelope!

if (feedback!=0.0f) //We're using feedback?
{
result *= feedback; //Convert to feedback ratio!
return calcOperator(curchan, operator,result, 0); //Apply feedback using our own signal as the modulator!
}

return result; //Give the result!
}

OPTINLINE short adlibsample (uint8_t curchan) {
float modulatorresult, result; //The operator result and the final result!

if (curchan >= NUMITEMS(adlibch)) return 0; //No sample with invalid channel!
if (adlibpercussion && (curchan >= 6) && (curchan <= 8)) //We're percussion?
{
return 0; //Percussion isn't supported yet!
}

//Determine the type of modulation!
//Operator 1!
modulatorresult = calcOperator(curchan, adliboperators[0][curchan], 0,adlibch[curchan].feedback); //Calculate the modulator!
result = calcOperator(curchan, adliboperators[1][curchan], modulatorresult,0); //Calculate the carrier with applied modulator if needed!

//Perform additive synthesis when asked to do so.
if (adlibch[curchan].synthmode) result += modulatorresult; //Perform additive synthesis when needed!

result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale!
return (short)result; //Give the result, converted to short!
}

OPTINLINE short adlibgensample() {
uint8_t curchan;
short adlibaccum;
adlibaccum = 0;
for (curchan = 0; curchan < 9; curchan++)
{
if (adlibop[adliboperators[1][curchan]].volenvstatus) //Are we a running envelope?
{
adlibaccum += adlibsample(curchan);
}
}
return (adlibaccum);
}

float min_vol = __MIN_VOL;

void tickadlib()
{
uint8_t curop;
for (curop = 0; curop < NUMITEMS(adlibop); curop++)
{
if (adliboperatorsreverse[curop] == 0xFF) continue; //Skip invalid operators!
if (adlibop[curop].volenvstatus) //Are we a running envelope?
{
volenv_recheck: //Recheck!
switch (adlibop[curop].volenvstatus)
{
case 1: //Attacking?
adlibop[curop].volenv *= adlibop[curop].attack; //Attack!
if (adlibop[curop].volenv >= 1.0)
{
adlibop[curop].volenv = 1.0; //We're at 1.0 to start decaying!
++adlibop[curop].volenvstatus; //Enter next phase!
goto volenv_recheck;
}
break;
case 2: //Decaying?
adlibop[curop].volenv *= adlibop[curop].decay; //Decay!
if (adlibop[curop].volenv <= adlibop[curop].sustain) //Sustain level reached?
{
adlibop[curop].volenv = adlibop[curop].sustain; //Sustain level!
++adlibop[curop].volenvstatus; //Enter next phase!
goto volenv_recheck;
}
break;
case 3: //Sustaining?
if ((!adlibch[adliboperatorsreverse[curop]].keyon) || adlibop[curop].ReleaseImmediately) //Release entered?
{
++adlibop[curop].volenvstatus; //Enter next phase!
goto volenv_recheck; //Check again!
}
break;
case 4: //Releasing?
adlibop[curop].volenv *= adlibop[curop].release; //Release!
if (adlibop[curop].volenv < min_vol) //Less than the format can provide?
{
adlibop[curop].volenv = 0.0f; //Clear the sound!
adlibop[curop].volenvstatus = 0; //Terminate the signal: we're unused!
}
break;
default: //Unknown volume envelope status?
adlibop[curop].volenvstatus = 0; //Disable this volume envelope!
break;
}
if (adlibop[curop].volenv < min_vol) //Below minimum?
{
adlibop[curop].volenv = 0.0f; //No volume below minimum!
}
}
}
}

byte adlib_soundGenerator(void* buf, uint_32 length, byte stereo, void *userdata) //Generate a sample!
{
if (stereo) return 0; //We don't support stereo!

byte filled,curchan;
filled = 0; //Default: not filled!
for (curchan=0; curchan<9; curchan++) { //Check for active channels!
if (adlibop[adliboperators[1][curchan]].volenvstatus) //Are we a running envelope?
{
filled = 1; //We're filled!
break; //Stop searching!
}
}

if (!filled) return SOUNDHANDLER_RESULT_NOTFILLED; //Not filled: nothing to sound!

uint_32 c;
c = length; //Init c!
short *data_mono;
data_mono = (short *)buf; //The data in correct samples!
for (;;) //Fill it!
{
//Left and right are the same!
*data_mono++ = adlibgensample(); //Generate a mono sample!
if (!--c) return SOUNDHANDLER_RESULT_FILLED; //Next item!
}
}

//Multicall speedup!
#define ADLIBMULTIPLIER 0

void initAdlib()
{
if (__HW_DISABLED) return; //Abort!

int i;
for (i = 0; i < 9; i++)
{
adlibch[i].keyon = 0; //Initialise key on!
adlibch[i].feedback = 0.0f; //No feedback!
adlibch[i].synthmode = 0; //Default synth mode (FM Synthesis)!
}

for (i = 0; i < NUMITEMS(adlibop); i++) //Process all channels!
{
adlibop[i].freq0 = adlibop[i].time = 0.0f; //Initialise the signal!

adlibop[i].attack = attacktable[15] * 1.006;
adlibop[i].decay = decaytable[0];
adlibop[i].sustain = sustaintable[15];
adlibop[i].release = decaytable[0];
adlibop[i].volenvstatus = 0; //Initialise to unused ADSR!
adlibop[i].volenv = 0.0f; //Volume envelope to silence!
adlibop[i].ReleaseImmediately = 1; //Release immediately by default!
}


if (__SOUND_ADLIB)
{
if (!addchannel(&adlib_soundGenerator,NULL,"Adlib",usesamplerate,0,0,SMPL16S)) //Start the sound emulation (mono) with automatic samples buffer?
{
dolog("adlib","Error registering sound channel for output!");
}
else
{
setVolume(&adlib_soundGenerator,NULL,ADLIB_VOLUME);
}
}
//dolog("adlib","sound channel added. registering ports...");
//Ignore unregistered channel, we need to be used by software!
register_PORTIN(baseport,&inadlib); //Status port (R)
//All output!
register_PORTOUT(baseport,&outadlib); //Address port (W)
register_PORTOUT(baseport+1,&outadlib); //Data port (W/O)
//dolog("adlib","Registering timer...");
addtimer(usesamplerate,&tickadlib,"AdlibAttackDecay",ADLIBMULTIPLIER,0,NULL); //We run at 49.716Khz, about every 20us.
//dolog("adlib","Ready"); //Ready to run!
}

void doneAdlib()
{
if (__HW_DISABLED) return; //Abort!
removetimer("AdlibAttackDecay"); //Stop the audio channel!
if (__SOUND_ADLIB)
{
removechannel(&adlib_soundGenerator,NULL,0); //Stop the sound emulation?
}
//Unregister the ports!
register_PORTIN(baseport,NULL);
//All output!
register_PORTOUT(baseport,NULL);
register_PORTOUT(baseport+1,NULL);
}

The volume envelope has been fixed and converted into a simple switch statement (smaller volume than 16-bit resolution can render is converted to silence, so values of -1,0 and 1, depending on rounding, won't be given anymore). I've also simplified the volume envelope and combined all operator parameters into the adlibop variables for each operator. Also removed the unused AMDepth parameter.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 14 of 112, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

Basic modulation seems to go fine, but I'm still a bit uncertain about the actual calculation of the feedback value. I'm currently just using the multiples of PI (according to opl3emu) to use with FM modulation on the modulator. (modulator = modulator FM (modulator times x*(2*PI*factor)), where factor is a power of 2) So 7 for full modulation, 1 for 1/7 modulation etc. Anyone knows the correct calculation for this?

Two previous output values of modulator is summed together, and that is shifted to correct position based on selected feedback strength and used directly as phase modulation. However the operator output amplitude and modulation input values have different range in your emulator so you can fix that with a suitable coefficient.

superfury wrote:

Is it true that values 6&7 have the same result? Phase modulation with factors of multiples of 2 times PI will have no effect, since a shift of 2 times pi and 4 times pi is the same (both 360 degrees)? Since phase modulating with a whole sine wave shifted is the same (for 2 times PI and 4 times PI)? PI will shift half a phase, and PI/2, PI/4, PI/8 and PI/16 will shift parts of the phase?

No, values 6 and 7 are different. In your emulator you have 256 entries in the table, and let's say phasemod=A*sin(x). When A=128 modulation is full 360 degrees, right? When A=64 it's only 180 degrees. If A=256 then it´s 720 degrees. So 360 degrees is definitely different from 720.

Reply 15 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

My latest version of my adlib emulation:

/*
Fake86: A portable, open-source 8086 PC emulator.
Copyright (C)2010-2012 Mike Chambers

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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

/* adlib.c: very ugly Adlib OPL2 emulation for Fake86. very much a work in progress. :) */

//#include "config.h"
#include "headers/types.h" //Basic headers!
#include "headers/emu/sound.h" //Basic sound!
#include "headers/support/log.h" //Logging support!
#include "headers/hardware/ports.h" //Basic ports!
#include "headers/support/highrestimer.h" //High resoltion timer support!
#include "headers/emu/emu_misc.h" //Random short support for noise generation!
#include "headers/emu/timers.h" //Timer support for attack/decay!
//#include <stdint.h>
//#include <stdio.h>

#define uint8_t byte
#define uint16_t word

//Are we disabled?
#define __HW_DISABLED 0
//Use the adlib sound? If disabled, only run timers for the CPU. Sound will not actually be heard.
#define __SOUND_ADLIB 1
//What volume, in percent!
#define ADLIB_VOLUME 100.0f
//What volume is the minimum volume to be heard!
#define __MIN_VOL (1.0f / SHRT_MAX)

#define dB2factor(dB, fMaxLevelDB) pow(10, ((dB - fMaxLevelDB) / 20))

//extern void set_port_write_redirector (uint16_t startport, uint16_t endport, void *callback);
//extern void set_port_read_redirector (uint16_t startport, uint16_t endport, void *callback);

uint16_t baseport = 0x388; //Adlib address(w)/status(r) port, +1=Data port (write only)
float usesamplerate = 14318180.0f/288.0f; //The sample rate to use for output!

uint16_t adlibregmem[0xFF], adlibaddr = 0;

byte adliboperators[2][9] = { //Groupings of 22 registers! (20,40,60,80,E0)
{ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12 },
{ 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15 }
};

byte adliboperatorsreverse[0x16] = { 0, 1, 2, 0, 1, 2,
Show last 456 lines
									255, 255,
3, 4, 5, 3, 4, 5,
255, 255,
6, 7, 8, 6, 7, 8}; //Channel lookup of adlib operators!

static const double feedbacklookup[8] = { 0, PI / 16.0, PI / 8.0, PI / 4.0, PI / 2.0, PI, PI*2.0, PI*4.0 }; //The feedback to use from opl3emu! Seems to be half a sinus wave per number!

byte wavemask = 0; //Wave select mask!

struct structadlibop {
//Effects
float outputlevel;
byte ReleaseImmediately; //Release even when the note is still turned on?

//Volume envelope
float attack, decay, sustain, release, volenv; //Volume envelope!
uint8_t volenvstatus;

//Signal generation
uint8_t wavesel;
float freq0, time; //The q0quency and current time of an operator!
float ModulatorFrequencyMultiple; //What harmonic to sound?
} adlibop[0x16];

struct structadlibchan {
uint16_t freq;
double convfreq;
uint8_t keyon;
uint16_t octave;
uint8_t synthmode; //What synthesizer mode (1=Additive synthesis, 0=Frequency modulation)
float feedback; //The feedback strength of the modulator signal.
} adlibch[0x10];

double attacktable[0x10] = { 1.0003, 1.00025, 1.0002, 1.00015, 1.0001, 1.00009, 1.00008, 1.00007, 1.00006, 1.00005, 1.00004, 1.00003, 1.00002, 1.00001, 1.000005 }; //1.003, 1.05, 1.01, 1.015, 1.02, 1.025, 1.03, 1.035, 1.04, 1.045, 1.05, 1.055, 1.06, 1.065, 1.07, 1.075 };
double decaytable[0x10] = { 0.99999, 0.999985, 0.99998, 0.999975, 0.99997, 0.999965, 0.99996, 0.999955, 0.99995, 0.999945, 0.99994, 0.999935, 0.99994, 0.999925, 0.99992, 0.99991 };
double sustaintable[0x10], outputtable[0x40]; //Build using software formulas!

uint8_t adlibpercussion = 0, adlibstatus = 0;

uint16_t adlibport = 0x388;

OPTINLINE float calcModulatorFrequencyMultiple(byte data)
{
switch (data)
{
case 0: return 0.5f;
case 11: return 10.0f;
case 13: return 12.0f;
case 14: return 15.0f;
default: return (float)data; //The same number!
}
}

void outadlib (uint16_t portnum, uint8_t value) {
if (portnum==adlibport) {
adlibaddr = value;
return;
}
portnum = adlibaddr;
adlibregmem[portnum] = value;
lockaudio(); //Lock the audio: we're going to adjust audio information!
switch (portnum) {
case 0: //Waveform select enable
wavemask = (adlibregmem[0] & 0x20) ? 3 : 0; //Apply waveform mask!
break;
case 4: //timer control
if (value&0x80) {
adlibstatus = 0;
adlibregmem[4] = 0;
}
break;
case 0xBD:
if (value & 0x10) adlibpercussion = 1;
else adlibpercussion = 0;
break;
}
if ((portnum >= 0x20) && (portnum <= 0x35)) //Various flags
{
portnum &= 0x1F;
adlibop[portnum].ModulatorFrequencyMultiple = calcModulatorFrequencyMultiple(value & 0xF); //Which harmonic to use?
adlibop[portnum].ReleaseImmediately = (value & 0x20) ? 0 : 1; //Release when not sustain until release!
}
else if ((portnum >= 0x40) && (portnum <= 0x55)) //KSL/Output level
{
portnum &= 0x1F;
adlibop[portnum].outputlevel = outputtable[value & 0x2F]; //Apply output level!
}
else if ( (portnum >= 0x60) && (portnum <= 0x75) ) { //attack/decay
portnum &= 0x1F;
adlibop[portnum].attack = attacktable[15- (value>>4) ]*1.006;
adlibop[portnum].decay = decaytable[value&15];
}
else if ((portnum >= 0x80) && (portnum <= 0x95)) //sustain/release
{
portnum &= 0x1F;
adlibop[portnum].sustain = sustaintable[value >> 4];
adlibop[portnum].release = decaytable[value & 15];
}
else if ( (portnum >= 0xA0) && (portnum <= 0xB8) )
{ //octave, freq, key on
if ((portnum & 0xF) < 9) //Ignore A9-AF!
{
portnum &= 0xF; //Get the channel to use!
if (!adlibch[portnum].keyon && ((adlibregmem[0xB0 + portnum] >> 5) & 1))
{
adlibop[adliboperators[0][portnum]].volenvstatus = 1; //Start attacking!
adlibop[adliboperators[1][portnum]].volenvstatus = 1; //Start attacking!
adlibop[adliboperators[0][portnum]].volenv = 0.0025;
adlibop[adliboperators[1][portnum]].volenv = 0.0025;
adlibop[adliboperators[0][portnum]].freq0 = adlibop[adliboperators[0][portnum]].time = 0.0f; //Initialise operator signal!
adlibop[adliboperators[1][portnum]].freq0 = adlibop[adliboperators[1][portnum]].time = 0.0f; //Initialise operator signal!
}
adlibch[portnum].freq = adlibregmem[0xA0 + portnum] | ((adlibregmem[0xB0 + portnum] & 3) << 8);
adlibch[portnum].convfreq = ((double)adlibch[portnum].freq * 0.7626459);
adlibch[portnum].keyon = (adlibregmem[0xB0 + portnum] >> 5) & 1;
adlibch[portnum].octave = (adlibregmem[0xB0 + portnum] >> 2) & 7;
}
}
else if ((portnum >= 0xC0) && (portnum <= 0xC8))
{
portnum &= 0xF;
adlibch[portnum].synthmode = (adlibregmem[0xC0 + portnum] & 1); //Save the synthesis mode!
byte feedback;
feedback = (adlibregmem[0xC0 + portnum] >> 1) & 3; //Get the feedback value used!
adlibch[portnum].feedback = feedbacklookup[feedback]; //Convert to a feedback of the modulator signal!
}
else if ( (portnum >= 0xE0) && (portnum <= 0xF5) ) //waveform select
{
portnum &= 0x1F;
adlibop[portnum].wavesel = value&3;
}
unlockaudio(1); //Finished with audio update!
}

uint8_t inadlib (uint16_t portnum) {
if (!adlibregmem[4]) adlibstatus = 0;
else adlibstatus = 0x80;
adlibstatus = adlibstatus + (adlibregmem[4]&1) *0x40 + (adlibregmem[4]&2) *0x10;
return (adlibstatus);
}

uint16_t adlibfreq (sbyte operatornumber, uint8_t chan) {
//uint8_t downoct[4] = { 3, 2, 1, 0 };
//uint8_t upoct[3] = { 1, 2, 3 };
if (chan > 8) return (0); //Invalid channel: we only have 9 channels!
uint16_t tmpfreq;
tmpfreq = (uint16_t) adlibch[chan].convfreq;
//if (adlibch[chan].octave<4) tmpfreq = tmpfreq>>1;
//if (adlibch[chan].octave>4) tmpfreq = tmpfreq<<1;
switch (adlibch[chan].octave) {
case 0:
tmpfreq = tmpfreq >> 4;
break;
case 1:
tmpfreq = tmpfreq >> 3;
break;
case 2:
tmpfreq = tmpfreq >> 2;
break;
case 3:
tmpfreq = tmpfreq >> 1;
break;
//case 4: tmpfreq = tmpfreq >> 1; break;
case 5:
tmpfreq = tmpfreq << 1;
break;
case 6:
tmpfreq = tmpfreq << 2;
break;
case 7:
tmpfreq = tmpfreq << 3;
}
if (operatornumber != -1) //Apply frequency multiplication factor?
{
tmpfreq *= adlibop[operatornumber].ModulatorFrequencyMultiple; //Apply the frequency multiplication factor!
}
return (tmpfreq);
}

float adlib_scaleFactor = (SHRT_MAX - 1.0f);

OPTINLINE float adlibWave(byte signal, const float frequencytime) {
double x;
float result = sinf(2.0f * PI * frequencytime); //The sinus function!
float t = modf(frequencytime, &x);

switch (signal) {
case 0: //SINE?
return result; //SINE!
case 1: // Negative=0?
if (t > 0.5f) return 0.0f; //Negative!
return result; //Positive!
case 3: // Absolute with second half=0?
if (fmod(t,0.5f) > 0.25) return 0.0f; //Are we the second half of the half period? Clear the signal if so!
case 2: // Absolute?
if (result < 0) result = 0 - result; //Make positive!
return result; //Simply absolute!
case 4:
return RandomFloat(-1.0f, 1.0f); //Random noise!
default: //Unknown signal?
return 0.0f;
}
}

OPTINLINE float calcAdlibSignal(byte wave, float phase, float frequency, float *freq0, float *time) //Calculates a signal for input to the adlib synth!
{
const float adlib_sampleLength = 1.0f / usesamplerate;
if (frequency != *freq0) { //Frequency changed?
*time *= (*freq0 / frequency);
}

float result = adlibWave(wave, (frequency * *time)+phase); //Set the channels!
*time += adlib_sampleLength; //Add 1 sample to the time!

float temp = *time*frequency; //Calculate!
if (temp > 1.0f) {
double d;
*time = modf(temp, &d) / frequency;
}

*freq0 = frequency;
return result; //Give the generated sample!
}

//Calculate an operator signal!
OPTINLINE float calcOperator(byte curchan, byte operator, float modulator, float feedback)
{
float frequency, result; //What frequency to generate?

//Calculate the frequency to use!
frequency = adlibfreq(operator, curchan); //Effective carrier init!
if (adlibch[curchan].synthmode) modulator = 0.0f; //Don't FM using the first operator when needed, so no PM!

//Generate the signal!
result = calcAdlibSignal(adlibop[operator].wavesel&wavemask,modulator, frequency, &adlibop[operator].freq0, &adlibop[operator].time);
result *= adlibop[operator].volenv; //Apply current volume of the ADSR envelope!
result *= adlibop[operator].outputlevel; //Apply the output level to the operator!

if (feedback!=0.0f) //We're using feedback?
{
result *= feedback; //Convert to feedback ratio!
return calcOperator(curchan, operator,result, 0); //Apply feedback using our own signal as the modulator!
}

return result; //Give the result!
}

OPTINLINE short adlibsample (uint8_t curchan) {
float modulatorresult, result; //The operator result and the final result!

if (curchan >= NUMITEMS(adlibch)) return 0; //No sample with invalid channel!
if (adlibpercussion && (curchan >= 6) && (curchan <= 8)) //We're percussion?
{
return 0; //Percussion isn't supported yet!
}

//Determine the type of modulation!
//Operator 1!
modulatorresult = calcOperator(curchan, adliboperators[0][curchan], 0,adlibch[curchan].feedback); //Calculate the modulator!
result = calcOperator(curchan, adliboperators[1][curchan], modulatorresult,0); //Calculate the carrier with applied modulator if needed!

//Perform additive synthesis when asked to do so.
if (adlibch[curchan].synthmode) result += modulatorresult; //Perform additive synthesis when needed!

result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale!
return (short)result; //Give the result, converted to short!
}

OPTINLINE short adlibgensample() {
uint8_t curchan;
short adlibaccum;
adlibaccum = 0;
for (curchan = 0; curchan < 9; curchan++)
{
if (adlibop[adliboperators[1][curchan]].volenvstatus) //Are we a running envelope?
{
adlibaccum += adlibsample(curchan);
}
}
return (adlibaccum);
}

float min_vol = __MIN_VOL;

void tickadlib()
{
uint8_t curop;
for (curop = 0; curop < NUMITEMS(adlibop); curop++)
{
if (adliboperatorsreverse[curop] == 0xFF) continue; //Skip invalid operators!
if (adlibop[curop].volenvstatus) //Are we a running envelope?
{
volenv_recheck: //Recheck!
switch (adlibop[curop].volenvstatus)
{
case 1: //Attacking?
adlibop[curop].volenv *= adlibop[curop].attack; //Attack!
if (adlibop[curop].volenv >= 1.0)
{
adlibop[curop].volenv = 1.0; //We're at 1.0 to start decaying!
++adlibop[curop].volenvstatus; //Enter next phase!
goto volenv_recheck;
}
break;
case 2: //Decaying?
adlibop[curop].volenv *= adlibop[curop].decay; //Decay!
if (adlibop[curop].volenv <= adlibop[curop].sustain) //Sustain level reached?
{
adlibop[curop].volenv = adlibop[curop].sustain; //Sustain level!
++adlibop[curop].volenvstatus; //Enter next phase!
goto volenv_recheck;
}
break;
case 3: //Sustaining?
if ((!adlibch[adliboperatorsreverse[curop]].keyon) || adlibop[curop].ReleaseImmediately) //Release entered?
{
++adlibop[curop].volenvstatus; //Enter next phase!
goto volenv_recheck; //Check again!
}
break;
case 4: //Releasing?
adlibop[curop].volenv *= adlibop[curop].release; //Release!
if (adlibop[curop].volenv < min_vol) //Less than the format can provide?
{
adlibop[curop].volenv = 0.0f; //Clear the sound!
adlibop[curop].volenvstatus = 0; //Terminate the signal: we're unused!
}
break;
default: //Unknown volume envelope status?
adlibop[curop].volenvstatus = 0; //Disable this volume envelope!
break;
}
if (adlibop[curop].volenv < min_vol) //Below minimum?
{
adlibop[curop].volenv = 0.0f; //No volume below minimum!
}
}
}
}

byte adlib_soundGenerator(void* buf, uint_32 length, byte stereo, void *userdata) //Generate a sample!
{
if (stereo) return 0; //We don't support stereo!

byte filled,curchan;
filled = 0; //Default: not filled!
for (curchan=0; curchan<9; curchan++) { //Check for active channels!
if (adlibop[adliboperators[1][curchan]].volenvstatus) //Are we a running envelope?
{
filled = 1; //We're filled!
break; //Stop searching!
}
}

if (!filled) return SOUNDHANDLER_RESULT_NOTFILLED; //Not filled: nothing to sound!

uint_32 c;
c = length; //Init c!
short *data_mono;
data_mono = (short *)buf; //The data in correct samples!
for (;;) //Fill it!
{
//Left and right are the same!
*data_mono++ = adlibgensample(); //Generate a mono sample!
if (!--c) return SOUNDHANDLER_RESULT_FILLED; //Next item!
}
}

//Multicall speedup!
#define ADLIBMULTIPLIER 0

void initAdlib()
{
if (__HW_DISABLED) return; //Abort!

int i;
for (i = 0; i < 9; i++)
{
adlibch[i].keyon = 0; //Initialise key on!
adlibch[i].feedback = 0.0f; //No feedback!
adlibch[i].synthmode = 0; //Default synth mode (FM Synthesis)!
}

//Build the needed tables!
for (i = 0; i < NUMITEMS(sustaintable); i++)
{
if (i==0xF) sustaintable[i] = dB2factor(93, 93); //Full volume exception with all bits set!
else sustaintable[i] = dB2factor((float)93-(float)(((i & 1) ? 3 : 0) + ((i & 2) ? 6 : 0) + ((i & 4) ? 12 : 0) + ((i & 8) ? 24 : 0)), 93); //Build a sustain table!
}

for (i = 0; i < NUMITEMS(outputtable); i++)
{
outputtable[i] = dB2factor((float)48 - (float)(
((i & 1) ? 0.75:0)+
((i&2)?1.5:0)+
((i&4)?3:0)+
((i&8)?6:0)+
((i&0x10)?12:0)+
((i&0x20)?24:0)
),48.0f);
}

for (i = 0; i < NUMITEMS(adlibop); i++) //Process all channels!
{
adlibop[i].freq0 = adlibop[i].time = 0.0f; //Initialise the signal!

//Apply default ADSR!
adlibop[i].attack = attacktable[15] * 1.006;
adlibop[i].decay = decaytable[0];
adlibop[i].sustain = sustaintable[15];
adlibop[i].release = decaytable[0];

adlibop[i].volenvstatus = 0; //Initialise to unused ADSR!
adlibop[i].volenv = 0.0f; //Volume envelope to silence!
adlibop[i].ReleaseImmediately = 1; //Release immediately by default!

adlibop[i].outputlevel = outputtable[0]; //Apply default output!
}


if (__SOUND_ADLIB)
{
if (!addchannel(&adlib_soundGenerator,NULL,"Adlib",usesamplerate,0,0,SMPL16S)) //Start the sound emulation (mono) with automatic samples buffer?
{
dolog("adlib","Error registering sound channel for output!");
}
else
{
setVolume(&adlib_soundGenerator,NULL,ADLIB_VOLUME);
}
}
//dolog("adlib","sound channel added. registering ports...");
//Ignore unregistered channel, we need to be used by software!
register_PORTIN(baseport,&inadlib); //Status port (R)
//All output!
register_PORTOUT(baseport,&outadlib); //Address port (W)
register_PORTOUT(baseport+1,&outadlib); //Data port (W/O)
//dolog("adlib","Registering timer...");
addtimer(usesamplerate,&tickadlib,"AdlibAttackDecay",ADLIBMULTIPLIER,0,NULL); //We run at 49.716Khz, about every 20us.
//dolog("adlib","Ready"); //Ready to run!
}

void doneAdlib()
{
if (__HW_DISABLED) return; //Abort!
removetimer("AdlibAttackDecay"); //Stop the audio channel!
if (__SOUND_ADLIB)
{
removechannel(&adlib_soundGenerator,NULL,0); //Stop the sound emulation?
}
//Unregister the ports!
register_PORTIN(baseport,NULL);
//All output!
register_PORTOUT(baseport,NULL);
register_PORTOUT(baseport+1,NULL);
}

I've added a sustain level lookup table and a output level lookup table for giving the correct volume.

So the values of 2 times PI and 4 times PI simply double the output frequency (when using the given formula a few posts back, when I input 2 times PI(=feedback 6) and 4 times PI(=feedback 7) compared to PI(=feedback 5), as the feedback (feedback values 6&7), it simply doubles the frequency compared to PI(=feedback value of 5). So 5 gives a signal, 6 gives the signal at double the frequency and 7 gives double the frequency of signal 6. Is this correct? (I'm looking at the graph given to me when I input the formula with Google search, at a modulator and carrier frequency of 1Hz, modulator values 6&7 (2*PI and 4*PI))

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 16 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

My latest source code of my adlib emulation;

/*
Fake86: A portable, open-source 8086 PC emulator.
Copyright (C)2010-2012 Mike Chambers

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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

/* adlib.c: very ugly Adlib OPL2 emulation for Fake86. very much a work in progress. :) */

//#include "config.h"
#include "headers/types.h" //Basic headers!
#include "headers/emu/sound.h" //Basic sound!
#include "headers/support/log.h" //Logging support!
#include "headers/hardware/ports.h" //Basic ports!
#include "headers/support/highrestimer.h" //High resoltion timer support!
#include "headers/emu/emu_misc.h" //Random short support for noise generation!
#include "headers/emu/timers.h" //Timer support for attack/decay!
//#include <stdint.h>
//#include <stdio.h>

#define uint8_t byte
#define uint16_t word

//Are we disabled?
#define __HW_DISABLED 0
//Use the adlib sound? If disabled, only run timers for the CPU. Sound will not actually be heard.
#define __SOUND_ADLIB 1
//What volume, in percent!
#define ADLIB_VOLUME 100.0f
//What volume is the minimum volume to be heard!
#define __MIN_VOL (1.0f / SHRT_MAX)

#define dB2factor(dB, fMaxLevelDB) pow(10, ((dB - fMaxLevelDB) / 20))

//extern void set_port_write_redirector (uint16_t startport, uint16_t endport, void *callback);
//extern void set_port_read_redirector (uint16_t startport, uint16_t endport, void *callback);

uint16_t baseport = 0x388; //Adlib address(w)/status(r) port, +1=Data port (write only)
float usesamplerate = 14318180.0f/288.0f; //The sample rate to use for output!

//Counter info
float counter80 = 0.0f, counter320 = 0.0f; //Counter ticks!
byte timer80=0, timer320=0; //Timer variables for current timer ticks!

//Registers itself
uint16_t adlibregmem[0xFF], adlibaddr = 0;

byte adliboperators[2][9] = { //Groupings of 22 registers! (20,40,60,80,E0)
Show last 535 lines
	{ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12 },
{ 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15 }
};

byte adliboperatorsreverse[0x16] = { 0, 1, 2, 0, 1, 2,
255, 255,
3, 4, 5, 3, 4, 5,
255, 255,
6, 7, 8, 6, 7, 8}; //Channel lookup of adlib operators!

static const double feedbacklookup[8] = { 0, PI / 16.0, PI / 8.0, PI / 4.0, PI / 2.0, PI, PI*2.0, PI*4.0 }; //The feedback to use from opl3emu! Seems to be half a sinus wave per number!

byte wavemask = 0; //Wave select mask!

struct structadlibop {
//Effects
float outputlevel;
byte ReleaseImmediately; //Release even when the note is still turned on?

//Volume envelope
float attack, decay, sustain, release, volenv; //Volume envelope!
uint8_t volenvstatus;

//Signal generation
uint8_t wavesel;
float freq0, time; //The q0quency and current time of an operator!
float ModulatorFrequencyMultiple; //What harmonic to sound?
} adlibop[0x16];

struct structadlibchan {
uint16_t freq;
double convfreq;
uint8_t keyon;
uint16_t octave;
uint8_t synthmode; //What synthesizer mode (1=Additive synthesis, 0=Frequency modulation)
float feedback; //The feedback strength of the modulator signal.
} adlibch[0x10];

double attacktable[0x10] = { 1.0003, 1.00025, 1.0002, 1.00015, 1.0001, 1.00009, 1.00008, 1.00007, 1.00006, 1.00005, 1.00004, 1.00003, 1.00002, 1.00001, 1.000005 }; //1.003, 1.05, 1.01, 1.015, 1.02, 1.025, 1.03, 1.035, 1.04, 1.045, 1.05, 1.055, 1.06, 1.065, 1.07, 1.075 };
double decaytable[0x10] = { 0.99999, 0.999985, 0.99998, 0.999975, 0.99997, 0.999965, 0.99996, 0.999955, 0.99995, 0.999945, 0.99994, 0.999935, 0.99994, 0.999925, 0.99992, 0.99991 };
double sustaintable[0x10], outputtable[0x40]; //Build using software formulas!

uint8_t adlibpercussion = 0, adlibstatus = 0;

uint16_t adlibport = 0x388;

OPTINLINE float calcModulatorFrequencyMultiple(byte data)
{
switch (data)
{
case 0: return 0.5f;
case 11: return 10.0f;
case 13: return 12.0f;
case 14: return 15.0f;
default: return (float)data; //The same number!
}
}

void outadlib (uint16_t portnum, uint8_t value) {
if (portnum==adlibport) {
adlibaddr = value;
return;
}
portnum = adlibaddr;
adlibregmem[portnum] = value;
lockaudio(); //Lock the audio: we're going to adjust audio information!
switch (portnum) {
case 0: //Waveform select enable
wavemask = (adlibregmem[0] & 0x20) ? 3 : 0; //Apply waveform mask!
break;
case 4: //timer control
if (value&0x80) {
adlibstatus &= 0x1F; //Reset status flags needed!
}
if (value&1) //Timer1 enabled?
{
timer80 = adlibregmem[2]; //Reload timer!
}
if (value&2) //Timer2 enabled?
{
timer320 = adlibregmem[3]; //Reload timer!
}
break;
case 0xBD:
if (value & 0x10) adlibpercussion = 1;
else adlibpercussion = 0;
break;
}
if ((portnum >= 0x20) && (portnum <= 0x35)) //Various flags
{
portnum &= 0x1F;
adlibop[portnum].ModulatorFrequencyMultiple = calcModulatorFrequencyMultiple(value & 0xF); //Which harmonic to use?
adlibop[portnum].ReleaseImmediately = (value & 0x20) ? 0 : 1; //Release when not sustain until release!
}
else if ((portnum >= 0x40) && (portnum <= 0x55)) //KSL/Output level
{
portnum &= 0x1F;
adlibop[portnum].outputlevel = outputtable[value & 0x2F]; //Apply output level!
}
else if ( (portnum >= 0x60) && (portnum <= 0x75) ) { //attack/decay
portnum &= 0x1F;
adlibop[portnum].attack = attacktable[15- (value>>4) ]*1.006;
adlibop[portnum].decay = decaytable[value&15];
}
else if ((portnum >= 0x80) && (portnum <= 0x95)) //sustain/release
{
portnum &= 0x1F;
adlibop[portnum].sustain = sustaintable[value >> 4];
adlibop[portnum].release = decaytable[value & 15];
}
else if ( (portnum >= 0xA0) && (portnum <= 0xB8) )
{ //octave, freq, key on
if ((portnum & 0xF) < 9) //Ignore A9-AF!
{
portnum &= 0xF; //Get the channel to use!
if (!adlibch[portnum].keyon && ((adlibregmem[0xB0 + portnum] >> 5) & 1))
{
adlibop[adliboperators[0][portnum]].volenvstatus = 1; //Start attacking!
adlibop[adliboperators[1][portnum]].volenvstatus = 1; //Start attacking!
adlibop[adliboperators[0][portnum]].volenv = 0.0025;
adlibop[adliboperators[1][portnum]].volenv = 0.0025;
adlibop[adliboperators[0][portnum]].freq0 = adlibop[adliboperators[0][portnum]].time = 0.0f; //Initialise operator signal!
adlibop[adliboperators[1][portnum]].freq0 = adlibop[adliboperators[1][portnum]].time = 0.0f; //Initialise operator signal!
}
adlibch[portnum].freq = adlibregmem[0xA0 + portnum] | ((adlibregmem[0xB0 + portnum] & 3) << 8);
adlibch[portnum].convfreq = ((double)adlibch[portnum].freq * 0.7626459);
adlibch[portnum].keyon = (adlibregmem[0xB0 + portnum] >> 5) & 1;
adlibch[portnum].octave = (adlibregmem[0xB0 + portnum] >> 2) & 7;
}
}
else if ((portnum >= 0xC0) && (portnum <= 0xC8))
{
portnum &= 0xF;
adlibch[portnum].synthmode = (adlibregmem[0xC0 + portnum] & 1); //Save the synthesis mode!
byte feedback;
feedback = (adlibregmem[0xC0 + portnum] >> 1) & 3; //Get the feedback value used!
adlibch[portnum].feedback = feedbacklookup[feedback]; //Convert to a feedback of the modulator signal!
}
else if ( (portnum >= 0xE0) && (portnum <= 0xF5) ) //waveform select
{
portnum &= 0x1F;
adlibop[portnum].wavesel = value&3;
}
unlockaudio(1); //Finished with audio update!
}

uint8_t inadlib (uint16_t portnum) {
return adlibstatus; //Give the current status!
}

uint16_t adlibfreq (sbyte operatornumber, uint8_t chan) {
//uint8_t downoct[4] = { 3, 2, 1, 0 };
//uint8_t upoct[3] = { 1, 2, 3 };
if (chan > 8) return (0); //Invalid channel: we only have 9 channels!
uint16_t tmpfreq;
tmpfreq = (uint16_t) adlibch[chan].convfreq;
//if (adlibch[chan].octave<4) tmpfreq = tmpfreq>>1;
//if (adlibch[chan].octave>4) tmpfreq = tmpfreq<<1;
switch (adlibch[chan].octave) {
case 0:
tmpfreq = tmpfreq >> 4;
break;
case 1:
tmpfreq = tmpfreq >> 3;
break;
case 2:
tmpfreq = tmpfreq >> 2;
break;
case 3:
tmpfreq = tmpfreq >> 1;
break;
//case 4: tmpfreq = tmpfreq >> 1; break;
case 5:
tmpfreq = tmpfreq << 1;
break;
case 6:
tmpfreq = tmpfreq << 2;
break;
case 7:
tmpfreq = tmpfreq << 3;
}
if (operatornumber != -1) //Apply frequency multiplication factor?
{
tmpfreq *= adlibop[operatornumber].ModulatorFrequencyMultiple; //Apply the frequency multiplication factor!
}
return (tmpfreq);
}

float adlib_scaleFactor = (SHRT_MAX - 1.0f);

OPTINLINE float adlibWave(byte signal, const float frequencytime) {
double x;
float result = sinf(2.0f * PI * frequencytime); //The sinus function!
float t = modf(frequencytime, &x);

switch (signal) {
case 0: //SINE?
return result; //SINE!
case 1: // Negative=0?
if (t > 0.5f) return 0.0f; //Negative!
return result; //Positive!
case 3: // Absolute with second half=0?
if (fmod(t,0.5f) > 0.25) return 0.0f; //Are we the second half of the half period? Clear the signal if so!
case 2: // Absolute?
if (result < 0) result = 0 - result; //Make positive!
return result; //Simply absolute!
case 4:
return RandomFloat(-1.0f, 1.0f); //Random noise!
default: //Unknown signal?
return 0.0f;
}
}

OPTINLINE float calcAdlibSignal(byte wave, float phase, float frequency, float *freq0, float *time) //Calculates a signal for input to the adlib synth!
{
const float adlib_sampleLength = 1.0f / usesamplerate;
if (frequency != *freq0) { //Frequency changed?
*time *= (*freq0 / frequency);
}

float result = adlibWave(wave, (frequency * *time)+phase); //Set the channels!
*time += adlib_sampleLength; //Add 1 sample to the time!

float temp = *time*frequency; //Calculate!
if (temp > 1.0f) {
double d;
*time = modf(temp, &d) / frequency;
}

*freq0 = frequency;
return result; //Give the generated sample!
}

//Calculate an operator signal!
OPTINLINE float calcOperator(byte curchan, byte operator, float modulator, float feedback)
{
float frequency, result; //What frequency to generate?

//Calculate the frequency to use!
frequency = adlibfreq(operator, curchan); //Effective carrier init!
if (adlibch[curchan].synthmode) modulator = 0.0f; //Don't FM using the first operator when needed, so no PM!

//Generate the signal!
result = calcAdlibSignal(adlibop[operator].wavesel&wavemask,modulator, frequency, &adlibop[operator].freq0, &adlibop[operator].time);
result *= adlibop[operator].volenv; //Apply current volume of the ADSR envelope!
result *= adlibop[operator].outputlevel; //Apply the output level to the operator!

if (feedback!=0.0f) //We're using feedback?
{
result *= feedback; //Convert to feedback ratio!
return calcOperator(curchan, operator,result, 0); //Apply feedback using our own signal as the modulator!
}

return result; //Give the result!
}

OPTINLINE short adlibsample (uint8_t curchan) {
float modulatorresult, result; //The operator result and the final result!

if (curchan >= NUMITEMS(adlibch)) return 0; //No sample with invalid channel!
if (adlibpercussion && (curchan >= 6) && (curchan <= 8)) //We're percussion?
{
return 0; //Percussion isn't supported yet!
}

//Determine the type of modulation!
//Operator 1!
modulatorresult = calcOperator(curchan, adliboperators[0][curchan], 0,adlibch[curchan].feedback); //Calculate the modulator!
result = calcOperator(curchan, adliboperators[1][curchan], modulatorresult,0); //Calculate the carrier with applied modulator if needed!

//Perform additive synthesis when asked to do so.
if (adlibch[curchan].synthmode) result += modulatorresult; //Perform additive synthesis when needed!

result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale!
return (short)result; //Give the result, converted to short!
}

//Timer ticks!

byte ticked80 = 0; //80 ticked?

void tick_adlibtimer()
{
//We don't have any IRQs assigned!
if (adlibregmem[8] & 0x80) //CSM enabled?
{
//Process CSM tick!
}
}

void adlib_timer80() //First timer!
{
ticked80 = 0; //Default: not ticked!
if (adlibregmem[4] & 1) //Timer1 enabled?
{
if (++timer80 == 0) //Overflown?
{
timer80 = adlibregmem[2]; //Reload timer!
if ((~adlibregmem[4]) & 0x40) //Update status?
{
adlibstatus |= 0xC0; //Update status register and set the bits!
}
tick_adlibtimer(); //Tick either timer!
ticked80 = 1; //Ticked 80 clock!
}
}
}

void adlib_timer320() //Second timer!
{
if (adlibregmem[4] & 2) //Timer2 enabled?
{
if (++timer320 == 0) //Overflown?
{
if ((~adlibregmem[4]) & 0x20) //Update status register?
{
adlibstatus |= 0xA0; //Update status register and set the bits!
}
timer320 = adlibregmem[3]; //Reload timer!
if (!ticked80) tick_adlibtimer(); //Tick either if not already ticked!
}
}
ticked80 = 0; //Reset 80 tick!
}

float counter80step = 0.0f; //80us timer tick interval in samples!
float counter320step = 0.0f; //320us timer tick interval in samples!

void adlib_soundtick()
{
counter80 += counter80step; //Tick he 80us counter!
counter320 += counter320step; //Tick the 320us counter!
for (;counter80>=1.0f;) //Ticks left?
{
adlib_timer80(); //Tick 80us timer!
counter80 -= 1.0f; //Decrease counter by 1 tick!
}
for (;counter320>=1.0f;) //Ticks left?
{
adlib_timer320(); //Tick 320us timer!
counter320 -= 1.0f; //Decrease counter by 1 tick!
}
}

OPTINLINE short adlibgensample() {
adlib_soundtick(); //Tick the sound!
uint8_t curchan;
short adlibaccum;
adlibaccum = 0;
for (curchan = 0; curchan < 9; curchan++)
{
if (adlibop[adliboperators[1][curchan]].volenvstatus) //Are we a running envelope?
{
adlibaccum += adlibsample(curchan);
}
}
return (adlibaccum);
}

float min_vol = __MIN_VOL;

void tickadlib()
{
uint8_t curop;
for (curop = 0; curop < NUMITEMS(adlibop); curop++)
{
if (adliboperatorsreverse[curop] == 0xFF) continue; //Skip invalid operators!
if (adlibop[curop].volenvstatus) //Are we a running envelope?
{
volenv_recheck: //Recheck!
switch (adlibop[curop].volenvstatus)
{
case 1: //Attacking?
adlibop[curop].volenv *= adlibop[curop].attack; //Attack!
if (adlibop[curop].volenv >= 1.0)
{
adlibop[curop].volenv = 1.0; //We're at 1.0 to start decaying!
++adlibop[curop].volenvstatus; //Enter next phase!
goto volenv_recheck;
}
break;
case 2: //Decaying?
adlibop[curop].volenv *= adlibop[curop].decay; //Decay!
if (adlibop[curop].volenv <= adlibop[curop].sustain) //Sustain level reached?
{
adlibop[curop].volenv = adlibop[curop].sustain; //Sustain level!
++adlibop[curop].volenvstatus; //Enter next phase!
goto volenv_recheck;
}
break;
case 3: //Sustaining?
if ((!adlibch[adliboperatorsreverse[curop]].keyon) || adlibop[curop].ReleaseImmediately) //Release entered?
{
++adlibop[curop].volenvstatus; //Enter next phase!
goto volenv_recheck; //Check again!
}
break;
case 4: //Releasing?
adlibop[curop].volenv *= adlibop[curop].release; //Release!
if (adlibop[curop].volenv < min_vol) //Less than the format can provide?
{
adlibop[curop].volenv = 0.0f; //Clear the sound!
adlibop[curop].volenvstatus = 0; //Terminate the signal: we're unused!
}
break;
default: //Unknown volume envelope status?
adlibop[curop].volenvstatus = 0; //Disable this volume envelope!
break;
}
if (adlibop[curop].volenv < min_vol) //Below minimum?
{
adlibop[curop].volenv = 0.0f; //No volume below minimum!
}
}
}
}

byte adlib_soundGenerator(void* buf, uint_32 length, byte stereo, void *userdata) //Generate a sample!
{
if (stereo) return 0; //We don't support stereo!

byte filled,curchan;
filled = 0; //Default: not filled!
for (curchan=0; curchan<9; curchan++) { //Check for active channels!
if (adlibop[adliboperators[1][curchan]].volenvstatus) //Are we a running envelope?
{
filled = 1; //We're filled!
break; //Stop searching!
}
}

if (!filled) return SOUNDHANDLER_RESULT_NOTFILLED; //Not filled: nothing to sound!

uint_32 c;
c = length; //Init c!
short *data_mono;
data_mono = (short *)buf; //The data in correct samples!
for (;;) //Fill it!
{
//Left and right are the same!
*data_mono++ = adlibgensample(); //Generate a mono sample!
if (!--c) return SOUNDHANDLER_RESULT_FILLED; //Next item!
}
}

//Multicall speedup!
#define ADLIBMULTIPLIER 0

void initAdlib()
{
if (__HW_DISABLED) return; //Abort!

int i;
for (i = 0; i < 9; i++)
{
adlibch[i].keyon = 0; //Initialise key on!
adlibch[i].feedback = 0.0f; //No feedback!
adlibch[i].synthmode = 0; //Default synth mode (FM Synthesis)!
}

//Build the needed tables!
for (i = 0; i < NUMITEMS(sustaintable); i++)
{
if (i==0xF) sustaintable[i] = dB2factor(93, 93); //Full volume exception with all bits set!
else sustaintable[i] = dB2factor((float)93-(float)(((i & 1) ? 3 : 0) + ((i & 2) ? 6 : 0) + ((i & 4) ? 12 : 0) + ((i & 8) ? 24 : 0)), 93); //Build a sustain table!
}

for (i = 0; i < NUMITEMS(outputtable); i++)
{
outputtable[i] = dB2factor((float)48 - (float)(
((i & 1) ? 0.75:0)+
((i&2)?1.5:0)+
((i&4)?3:0)+
((i&8)?6:0)+
((i&0x10)?12:0)+
((i&0x20)?24:0)
),48.0f);
}

for (i = 0; i < NUMITEMS(adlibop); i++) //Process all channels!
{
adlibop[i].freq0 = adlibop[i].time = 0.0f; //Initialise the signal!

//Apply default ADSR!
adlibop[i].attack = attacktable[15] * 1.006;
adlibop[i].decay = decaytable[0];
adlibop[i].sustain = sustaintable[15];
adlibop[i].release = decaytable[0];

adlibop[i].volenvstatus = 0; //Initialise to unused ADSR!
adlibop[i].volenv = 0.0f; //Volume envelope to silence!
adlibop[i].ReleaseImmediately = 1; //Release immediately by default!

adlibop[i].outputlevel = outputtable[0]; //Apply default output!
}


if (__SOUND_ADLIB)
{
if (!addchannel(&adlib_soundGenerator,NULL,"Adlib",usesamplerate,0,0,SMPL16S)) //Start the sound emulation (mono) with automatic samples buffer?
{
dolog("adlib","Error registering sound channel for output!");
}
else
{
setVolume(&adlib_soundGenerator,NULL,ADLIB_VOLUME);
}
}
//dolog("adlib","sound channel added. registering ports...");
//Ignore unregistered channel, we need to be used by software!
register_PORTIN(baseport,&inadlib); //Status port (R)
//All output!
register_PORTOUT(baseport,&outadlib); //Address port (W)
register_PORTOUT(baseport+1,&outadlib); //Data port (W/O)
//dolog("adlib","Registering timer...");
addtimer(usesamplerate,&tickadlib,"AdlibAttackDecay",ADLIBMULTIPLIER,0,NULL); //We run at 49.716Khz, about every 20us.
counter80step = (1.0f / ((80.0f / 1000000.0f) / (1.0f / (14318180.0f / 288.0f)))); //80us timer tick interval in samples!
counter320step = (1.0f / ((320.0f / 1000000.0f) / (1.0f / (14318180.0f / 288.0f)))); //320us timer tick interval in samples!
//dolog("adlib","Ready"); //Ready to run!
}

void doneAdlib()
{
if (__HW_DISABLED) return; //Abort!
removetimer("AdlibAttackDecay"); //Stop the audio channel!
if (__SOUND_ADLIB)
{
removechannel(&adlib_soundGenerator,NULL,0); //Stop the sound emulation?
}
//Unregister the ports!
register_PORTIN(baseport,NULL);
//All output!
register_PORTOUT(baseport,NULL);
register_PORTOUT(baseport+1,NULL);
}

Little bugfix: the samples to add still (timers) needed a 1/x to convert the ammount of samples per tick to the ammount of samples to add to get a tick every whole value (>=1.0), instead of the ammount of samples per tick added and used as a source.

Also what happens during CSM and rhythm mode (from a signal perspective)? What's the difference with melody mode (in the last 3 channels)?

Edit: Fixed the compiler errors that were in this version.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 17 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

This is my latest signal generation (including feedback):

OPTINLINE float adlibWave(byte signal, const float frequencytime) {
double x;
float result,t;
result = PI2; //Load PI2!
result *= frequencytime; //Apply freqtime!
switch (signal) {
case 0: //SINE?
return sin(result); //The sinus function!
case 0xFF: //Random signal?
return RandomFloat(-1.0f, 1.0f); //Random noise!
default:
t = modf(frequencytime, &x); //Calculate rest for special signal information!
switch (signal) { //What special signal?
case 1: // Negative=0?
if (t > 0.5f) return 0.0f; //Negative!
result = sinf(result); //The sinus function!
return result; //Positive!
case 3: // Absolute with second half=0?
if (fmod(t, 0.5f) > 0.25) return 0.0f; //Are we the second half of the half period? Clear the signal if so!
case 2: // Absolute?
result = sinf(result); //The sinus function!
if (result < 0) result = 0 - result; //Make positive!
return result; //Simply absolute!
default: //Unknown signal?
return 0.0f;
}
}
}

OPTINLINE float calcAdlibSignal(byte wave, float phase, float frequency, float *freq0, float *time) //Calculates a signal for input to the adlib synth!
{
float ftp;
if (frequency != *freq0) { //Frequency changed?
*time *= (*freq0 / frequency);
}

ftp = frequency; //Frequency!
ftp *= *time; //Time!
ftp += phase; //Add phase!
*freq0 = frequency; //Update new frequency!
return adlibWave(wave, ftp); //Give the generated sample!
}

OPTINLINE void incop(byte operator, float frequency)
{
float temp;
double d;
adlibop[operator].time += adlib_sampleLength; //Add 1 sample to the time!

temp = adlibop[operator].time*frequency; //Calculate for overflow!
if (temp >= 1.0f) { //Overflow?
adlibop[operator].time = modf(temp, &d) / frequency;
}
}

//Calculate an operator signal!
OPTINLINE float calcOperator(byte curchan, byte operator, float frequency, float modulator, byte feedback)
{
float result; //Our variables?
//Generate the signal!
Show last 51 lines
	if (feedback) //Apply feedback?
{
modulator = adlibop[operator].lastsignal[0]; //Take the previous last signal!
modulator += adlibop[operator].lastsignal[1]; //Take the last signal!
modulator *= adlibch[curchan].feedback; //Calculate current feedback!
}

//Generate the correct signal!
adlibop[operator].lastsignal[0] = adlibop[operator].lastsignal[1]; //Set last signal #0 to #1(shift)!
result = calcAdlibSignal(adlibop[operator].wavesel&wavemask, modulator, frequency, &adlibop[operator].freq0, &adlibop[operator].time); //Load the current result!

result *= adlibop[operator].outputlevel; //Apply the output level to the operator!
adlibop[operator].lastsignal[1] = result; //The last result including the output level of the envelope!
result *= adlibop[operator].volenv; //Apply current volume of the ADSR envelope!

if (frequency) incop(operator,frequency); //Increase time for the operator!
return result; //Give the result!
}

OPTINLINE short adlibsample(uint8_t curchan) {
const static float adlib_scaleFactor = (SHRT_MAX - 1.0f);
float result; //The operator result and the final result!
byte op1,op2; //The two operators to use!
float op1frequency;
if (curchan >= NUMITEMS(adlibch)) return 0; //No sample with invalid channel!
if (adlibpercussion && (curchan >= 6) && (curchan <= 8)) //We're percussion?
{
return 0; //Percussion isn't supported yet!
}

//Determine the modulator and carrier to use!
op1 = adliboperators[0][curchan]; //First operator number!
op2 = adliboperators[1][curchan]; //Second operator number!
op1frequency = adlibfreq(op1, curchan); //Load the first frequency!

//Operator 1!
//Calculate the frequency to use!
result = calcOperator(curchan, op1, op1frequency, 0.0f,1); //Calculate the modulator for feedback!

if (adlibch[curchan].synthmode) //Additive synthesis?
{
result += calcOperator(curchan, op2, adlibfreq(op2, curchan), 0.0f, 0); //Calculate the carrier without applied modulator additive!
}
else //FM synthesis?
{
result = calcOperator(curchan, op2, adlibfreq(op2, curchan), result, 0); //Calculate the carrier with applied modulator!
}

result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale!
return (short)result; //Give the result, converted to short!
}

Do you know why I'm getting noise through most notes (checking using Supaplex)? This isn't supposed to happen afaik.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 19 of 112, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie

Well, I can spot at least two things making the feedback too high.

First of all, your code does take the feedback before ADSR envelope is applied, so it is not the actual modulator output value that is used elsewhere.

And the ranges are off. If you say your operator output range is -1.0 to +1.0, then if you sum two outputs together, that is already -2.0 to +2.0 range. Then, if the maximum feedback range is +/- 4*pi total, you seem to multiply the +/- 2.0 range with 4*pi so your feedback maximum is actually 8*pi, double what you want.

Also if each operator has range of +/-1, and you multiply that with SHRT_MAX (I assume that is like 32767), then only one operator fits to 16-bit integer, and you have to be able to fit 9 channels there. As I think I previously mentioned already, a single operator has output range between -4085 and +4084, so you can sum 8 operators without fear of overflow. But because you use floats, use floats all the way and then scale the range so that all 9 channels fit into 16-bit integer.

But some of this info wasn't in your post, so I had to look at the github page which of course has older version of this file.