/* | |
* Library to access OPL2/OPL3 hardware (YM3812 / YMF262) | |
* | |
* Copyright (c) 2015, Mateusz Viste | |
* All rights reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions are met: | |
* | |
* 1. Redistributions of source code must retain the above copyright notice, | |
* this list of conditions and the following disclaimer. | |
* | |
* 2. Redistributions in binary form must reproduce the above copyright | |
* notice, this list of conditions and the following disclaimer in the | |
* documentation and/or other materials provided with the distribution. | |
* | |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | |
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
* POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
#ifdef OPL | |
#include <conio.h> /* inp(), out() */ | |
#include <dos.h> | |
#include <stdio.h> | |
#include <stdlib.h> /* calloc() */ | |
#include <string.h> /* memset() */ | |
#include "opl.h" | |
#include "opl-gm.h" | |
struct voicealloc_t { | |
unsigned short priority; | |
signed short timbreid; | |
signed char channel; | |
signed char note; | |
}; | |
struct oplstate_t { | |
signed char notes2voices[16][128]; /* keeps the map of channel:notes -> voice allocations */ | |
unsigned short channelpitch[16]; /* per-channel pitch level */ | |
unsigned short channelvol[16]; /* per-channel pitch level */ | |
struct voicealloc_t voices2notes[18]; /* keeps the map of what voice is playing what note/channel currently */ | |
unsigned char channelprog[16]; /* programs (patches) assigned to channels */ | |
int opl3; /* flag indicating whether or not the sound module is OPL3-compatible or only OPL2 */ | |
}; | |
struct oplstate_t *oplmem = NULL; /* memory area holding all OPL's current states */ | |
static unsigned short freqtable[128] = { /* note # */ | |
345, 365, 387, 410, 435, 460, 488, 517, 547, 580, 615, 651, /* 0 */ | |
690, 731, 774, 820, 869, 921, 975, 517, 547, 580, 615, 651, /* 12 */ | |
690, 731, 774, 820, 869, 921, 975, 517, 547, 580, 615, 651, /* 24 */ | |
690, 731, 774, 820, 869, 921, 975, 517, 547, 580, 615, 651, /* 36 */ | |
690, 731, 774, 820, 869, 921, 975, 517, 547, 580, 615, 651, /* 48 */ | |
690, 731, 774, 820, 869, 921, 975, 517, 547, 580, 615, 651, /* 60 */ | |
690, 731, 774, 820, 869, 921, 975, 517, 547, 580, 615, 651, /* 72 */ | |
690, 731, 774, 820, 869, 921, 975, 517, 547, 580, 615, 651, /* 84 */ | |
690, 731, 774, 820, 869, 921, 975, 517, 547, 580, 615, 651, /* 96 */ | |
690, 731, 774, 820, 869, 921, 975, 517, 547, 580, 615, 651, /* 108 */ | |
690, 731, 774, 820, 869, 921, 975, 517}; /* 120 */ | |
static unsigned char octavetable[128] = { /* note # */ | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */ | |
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, /* 12 */ | |
1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, /* 24 */ | |
2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, /* 36 */ | |
3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, /* 48 */ | |
4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, /* 60 */ | |
5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, /* 72 */ | |
6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, /* 84 */ | |
7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, /* 96 */ | |
8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, /* 108 */ | |
9, 9, 9, 9, 9, 9, 9,10}; /* 120 */ | |
static unsigned short pitchtable[256] = { /* pitch wheel */ | |
29193U,29219U,29246U,29272U,29299U,29325U,29351U,29378U, /* -128 */ | |
29405U,29431U,29458U,29484U,29511U,29538U,29564U,29591U, /* -120 */ | |
29618U,29644U,29671U,29698U,29725U,29752U,29778U,29805U, /* -112 */ | |
29832U,29859U,29886U,29913U,29940U,29967U,29994U,30021U, /* -104 */ | |
30048U,30076U,30103U,30130U,30157U,30184U,30212U,30239U, /* -96 */ | |
30266U,30293U,30321U,30348U,30376U,30403U,30430U,30458U, /* -88 */ | |
30485U,30513U,30541U,30568U,30596U,30623U,30651U,30679U, /* -80 */ | |
30706U,30734U,30762U,30790U,30817U,30845U,30873U,30901U, /* -72 */ | |
30929U,30957U,30985U,31013U,31041U,31069U,31097U,31125U, /* -64 */ | |
31153U,31181U,31209U,31237U,31266U,31294U,31322U,31350U, /* -56 */ | |
31379U,31407U,31435U,31464U,31492U,31521U,31549U,31578U, /* -48 */ | |
31606U,31635U,31663U,31692U,31720U,31749U,31778U,31806U, /* -40 */ | |
31835U,31864U,31893U,31921U,31950U,31979U,32008U,32037U, /* -32 */ | |
32066U,32095U,32124U,32153U,32182U,32211U,32240U,32269U, /* -24 */ | |
32298U,32327U,32357U,32386U,32415U,32444U,32474U,32503U, /* -16 */ | |
32532U,32562U,32591U,32620U,32650U,32679U,32709U,32738U, /* -8 */ | |
32768U,32798U,32827U,32857U,32887U,32916U,32946U,32976U, /* 0 */ | |
33005U,33035U,33065U,33095U,33125U,33155U,33185U,33215U, /* 8 */ | |
33245U,33275U,33305U,33335U,33365U,33395U,33425U,33455U, /* 16 */ | |
33486U,33516U,33546U,33576U,33607U,33637U,33667U,33698U, /* 24 */ | |
33728U,33759U,33789U,33820U,33850U,33881U,33911U,33942U, /* 32 */ | |
33973U,34003U,34034U,34065U,34095U,34126U,34157U,34188U, /* 40 */ | |
34219U,34250U,34281U,34312U,34343U,34374U,34405U,34436U, /* 48 */ | |
34467U,34498U,34529U,34560U,34591U,34623U,34654U,34685U, /* 56 */ | |
34716U,34748U,34779U,34811U,34842U,34874U,34905U,34937U, /* 64 */ | |
34968U,35000U,35031U,35063U,35095U,35126U,35158U,35190U, /* 72 */ | |
35221U,35253U,35285U,35317U,35349U,35381U,35413U,35445U, /* 80 */ | |
35477U,35509U,35541U,35573U,35605U,35637U,35669U,35702U, /* 88 */ | |
35734U,35766U,35798U,35831U,35863U,35895U,35928U,35960U, /* 96 */ | |
35993U,36025U,36058U,36090U,36123U,36155U,36188U,36221U, /* 104 */ | |
36254U,36286U,36319U,36352U,36385U,36417U,36450U,36483U, /* 112 */ | |
36516U,36549U,36582U,36615U,36648U,36681U,36715U,36748U}; /* 120 */ | |
/* tables below provide register offsets for each voice. note, that these are | |
* NOT the registers IDs, but their direct offsets instead - this for simpler | |
* and faster computations. */ | |
const unsigned short op1offsets[18] = {0x00,0x01,0x02,0x08,0x09,0x0a,0x10,0x11,0x12,0x100,0x101,0x102,0x108,0x109,0x10a,0x110,0x111,0x112}; | |
const unsigned short op2offsets[18] = {0x03,0x04,0x05,0x0b,0x0c,0x0d,0x13,0x14,0x15,0x103,0x104,0x105,0x10b,0x10c,0x10d,0x113,0x114,0x115}; | |
/* number of supported voices: 9 by default (OPL2), can go up to 18 (OPL3) */ | |
static int voicescount = 9; | |
/* function used to write into a register 'reg' of the OPL chip located at | |
* port 'port', writing byte 'data' into it. this function supports also OPL3. | |
* to write into the secondary address of an OPL3, just OR your register with | |
* the 0x100 value (the 0x100 flag will be removed then, and the data will be | |
* written into port+3). */ | |
static void oplregwr(unsigned short port, unsigned short reg, unsigned char data) { | |
int i; | |
/* remap 'high' registers to second port (OPL3) */ | |
if ((reg & 0x100) != 0) { | |
reg &= 0xff; | |
port += 2; | |
} | |
/* select the register we want to write to, via the index register */ | |
outp(port, reg); | |
/* OPL2 requires 3.3us to pass before writing to the data port. AdLib | |
* recommends reading 6 times from the index register to make time pass. */ | |
i = 6; | |
while (i--) inp(port); | |
/* write the data to the data register */ | |
outp(port+1, data); | |
/* OPL2 requires 23us to pass before writing to the data port. AdLib | |
* recommends reading 35 times from the index register to make time pass. */ | |
i = 35; | |
while (i--) inp(port); | |
} | |
/* 'volume' is in range 0..127 - take care to change only the 'attenuation' | |
* part of the register, and never touch the KSL bits */ | |
static void calc_vol(unsigned char *regbyte, int volume) { | |
int level; | |
/* invert bits and strip out the KSL header */ | |
level = ~(*regbyte); | |
level &= 0x3f; | |
/* adjust volume */ | |
level = (level * volume) / 127; | |
/* boundaries check */ | |
if (level > 0x3f) level = 0x3f; | |
if (level < 0) level = 0; | |
/* invert the bits, as expected by the OPL registers */ | |
level = ~level; | |
level &= 0x3f; | |
/* final result computation */ | |
*regbyte &= 0xC0; /* zero out all attentuation bits */ | |
*regbyte |= level; /* fill in the new attentuation value */ | |
} | |
/* Initialize hardware upon startup - positive on success, negative otherwise | |
* Returns 0 for OPL2 initialization, or 1 if OPL3 has been detected */ | |
int opl_init(unsigned short port) { | |
int x, y, i; | |
/* make sure we're not inited yet */ | |
if (oplmem != NULL) return(-1); | |
/* detect the hardware and return error if not found */ | |
oplregwr(port, 0x04, 0x60); /* reset both timers by writing 60h to register 4 */ | |
oplregwr(port, 0x04, 0x80); /* enable interrupts by writing 80h to register 4 (must be a separate write from the 1st one) */ | |
x = inp(port) & 0xE0; /* read the status register (port 388h) and store the result */ | |
oplregwr(port, 0x02, 0xff); /* write FFh to register 2 (Timer 1) */ | |
oplregwr(port, 0x04, 0x21); /* start timer 1 by writing 21h to register 4 */ | |
i = 512; | |
while (i--) y = inp(port) & 0xE0; /* delay for at least 80 microseconds (and read the status register) */ | |
oplregwr(port, 0x04, 0x60); /* reset both timers and interrupts (see steps 1 and 2) */ | |
oplregwr(port, 0x04, 0x80); /* reset both timers and interrupts (see steps 1 and 2) */ | |
/* test the stored results of steps 3 and 7 by ANDing them with E0h. The result of step 3 should be */ | |
if (x != 0) return(-1); /* 00h, and the result of step 7 should be C0h. If both are */ | |
if (y != 0xC0) return(-2); /* ok, an AdLib-compatible board is installed in the computer */ | |
/* init memory */ | |
oplmem = calloc(1, sizeof(struct oplstate_t)); | |
if (oplmem == NULL) return(-3); | |
/* is it an OPL3 or just an OPL2? */ | |
if ((inp(port) & 0x06) == 0) oplmem->opl3 = 1; | |
/* init the hardware */ | |
voicescount = 9; /* OPL2 provides 9 melodic voices */ | |
/* enable OPL3 (if detected) and put it into 36 operators mode */ | |
if (oplmem->opl3 != 0) { | |
oplregwr(port, 0x105, 1); /* enable OPL3 mode (36 operators) */ | |
oplregwr(port, 0x104, 0); /* disable four-operator voices */ | |
voicescount = 18; /* OPL3 provides 18 melodic channels */ | |
/* Init the secondary OPL chip | |
* NOTE: this I don't do anymore, it turns my Aztech Waverider mute! */ | |
/* oplregwr(port, 0x101, 0x20); */ /* enable Waveform Select */ | |
/* oplregwr(port, 0x108, 0x40); */ /* turn off CSW mode and activate FM synth mode */ | |
/* oplregwr(port, 0x1BD, 0x00); */ /* set vibrato/tremolo depth to low, set melodic mode */ | |
} | |
oplregwr(port, 0x01, 0x20); /* enable Waveform Select */ | |
oplregwr(port, 0x04, 0x00); /* turn off timers IRQs */ | |
oplregwr(port, 0x08, 0x40); /* turn off CSW mode and activate FM synth mode */ | |
oplregwr(port, 0xBD, 0x00); /* set vibrato/tremolo depth to low, set melodic mode */ | |
for (x = 0; x < voicescount; x++) { | |
oplregwr(port, 0x20 + op1offsets[x], 0x1); /* set the modulator's multiple to 1 */ | |
oplregwr(port, 0x20 + op2offsets[x], 0x1); /* set the modulator's multiple to 1 */ | |
oplregwr(port, 0x40 + op1offsets[x], 0x10); /* set volume of all channels to about 40 dB */ | |
oplregwr(port, 0x40 + op2offsets[x], 0x10); /* set volume of all channels to about 40 dB */ | |
} | |
opl_clear(port); | |
/* all done */ | |
return(oplmem->opl3); | |
} | |
/* close OPL device */ | |
void opl_close(unsigned short port) { | |
int x; | |
/* turns all notes 'off' */ | |
opl_clear(port); | |
/* set volume to lowest level on all voices */ | |
for (x = 0; x < voicescount; x++) { | |
oplregwr(port, 0x40 + op1offsets[x], 0x1f); | |
oplregwr(port, 0x40 + op2offsets[x], 0x1f); | |
} | |
/* if OPL3, switch the chip back into its default OPL2 mode */ | |
if (oplmem->opl3 != 0) oplregwr(port, 0x105, 0); | |
/* free state memory */ | |
free(oplmem); | |
oplmem = NULL; | |
} | |
/* turns off all notes */ | |
void opl_clear(unsigned short port) { | |
int x, y; | |
for (x = 0; x < voicescount; x++) opl_noteoff(port, x); | |
/* reset the percussion bits at the 0xBD register */ | |
oplregwr(port, 0xBD, 0); | |
/* mark all voices as unused */ | |
for (x = 0; x < voicescount; x++) { | |
oplmem->voices2notes[x].channel = -1; | |
oplmem->voices2notes[x].note = -1; | |
oplmem->voices2notes[x].timbreid = -1; | |
} | |
/* mark all notes as unallocated */ | |
for (x = 0; x < 16; x++) { | |
for (y = 0; y < 128; y++) oplmem->notes2voices[x][y] = -1; | |
} | |
/* pre-set emulated channel patches to default GM ids and reset all | |
* per-channel volumes */ | |
for (x = 0; x < 16; x++) { | |
opl_midi_changeprog(x, x); | |
oplmem->channelvol[x] = 127; | |
} | |
} | |
void opl_noteoff(unsigned short port, unsigned short voice) { | |
/* if voice is one of the OPL3 set, adjust it and route over secondary OPL port */ | |
if (voice >= 9) { | |
oplregwr(port, 0x1B0 + voice - 9, 0); | |
} else { | |
oplregwr(port, 0xB0 + voice, 0); | |
} | |
} | |
void opl_noteon(unsigned short port, unsigned short voice, unsigned int note, int pitch) { | |
unsigned int freq = freqtable[note]; | |
unsigned int octave = octavetable[note]; | |
if (pitch != 0) { | |
if (pitch > 127) { | |
pitch = 127; | |
} else if (pitch < -128) { | |
pitch = -128; | |
} | |
freq = ((unsigned long)freq * pitchtable[pitch + 128]) >> 15; | |
if (freq >= 1024) { | |
freq >>= 1; | |
octave++; | |
} | |
} | |
if (octave > 7) octave = 7; | |
/* if voice is one of the OPL3 set, adjust it and route over secondary OPL port */ | |
if (voice >= 9) { | |
voice -= 9; | |
voice |= 0x100; | |
} | |
oplregwr(port, 0xA0 + voice, freq & 0xff); /* set lowfreq */ | |
oplregwr(port, 0xB0 + voice, (freq >> 8) | (octave << 2) | 32); /* KEY ON + hifreq + octave */ | |
} | |
void opl_midi_pitchwheel(unsigned short oplport, int channel, int pitchwheel) { | |
/*int x;*/ | |
/* update the new pitch value for channel (used by newly played notes) */ | |
oplmem->channelpitch[channel] = pitchwheel; | |
/* check all active voices to see who is playing on given channel now, and | |
* recompute all playing notes for this channel with the new pitch TODO */ | |
/*for (x = 0; x < voicescount; x++) { | |
if (oplmem->voices2notes[x].channel != channel) continue; | |
opl_noteon(oplport, x, oplmem->voices2notes[x].note, pitchwheel + gmtimbres[oplmem->voices2notes[x].timbreid].finetune); | |
}*/ | |
} | |
void opl_midi_controller(unsigned short oplport, int channel, int id, int value) { | |
int x; | |
switch (id) { | |
case 11: /* "Expression" (meaning "channel volume") */ | |
oplmem->channelvol[channel] = value; | |
break; | |
case 123: /* 'all notes off' */ | |
case 120: /* 'all sound off' - I map it to 'all notes off' for now, not perfect but better than not handling it at all */ | |
for (x = 0; x < voicescount; x++) { | |
if (oplmem->voices2notes[x].channel != channel) continue; | |
opl_midi_noteoff(oplport, channel, oplmem->voices2notes[x].note); | |
} | |
break; | |
} | |
} | |
/* assign a new instrument to emulated MIDI channel */ | |
void opl_midi_changeprog(int channel, int program) { | |
if (channel == 9) return; /* do not allow to change channel 9, it is for percussions only */ | |
oplmem->channelprog[channel] = program; | |
} | |
void opl_loadinstrument(unsigned short oplport, unsigned short voice, struct timbre_t *timbre) { | |
/* KSL (key level scaling) / attenuation */ | |
oplregwr(oplport, 0x40 + op1offsets[voice], timbre->modulator_40); | |
oplregwr(oplport, 0x40 + op2offsets[voice], timbre->carrier_40 | 0x3f); /* force volume to 0, it will be reajusted during 'note on' */ | |
/* select waveform on both operators */ | |
oplregwr(oplport, 0xE0 + op1offsets[voice], timbre->modulator_E862 >> 24); | |
oplregwr(oplport, 0xE0 + op2offsets[voice], timbre->carrier_E862 >> 24); | |
/* sustain / release */ | |
oplregwr(oplport, 0x80 + op1offsets[voice], (timbre->modulator_E862 >> 16) & 0xff); | |
oplregwr(oplport, 0x80 + op2offsets[voice], (timbre->carrier_E862 >> 16) & 0xff); | |
/* attack rate / decay */ | |
oplregwr(oplport, 0x60 + op1offsets[voice], (timbre->modulator_E862 >> 8) & 0xff); | |
oplregwr(oplport, 0x60 + op2offsets[voice], (timbre->carrier_E862 >> 8) & 0xff); | |
/* AM / vibrato / envelope */ | |
oplregwr(oplport, 0x20 + op1offsets[voice], timbre->modulator_E862 & 0xff); | |
oplregwr(oplport, 0x20 + op2offsets[voice], timbre->carrier_E862 & 0xff); | |
/* feedback / connection */ | |
if (voice >= 9) { | |
voice -= 9; | |
voice |= 0x100; | |
} | |
if (oplmem->opl3 != 0) { /* on OPL3 make sure to enable LEFT/RIGHT unmute bits */ | |
oplregwr(oplport, 0xC0 + voice, timbre->feedconn | 0x30); | |
} else { | |
oplregwr(oplport, 0xC0 + voice, timbre->feedconn); | |
} | |
} | |
/* adjust the volume of the voice (in the usual MIDI range of 0..127) */ | |
static void voicevolume(unsigned short port, unsigned short voice, int program, int volume) { | |
unsigned char carrierval = gmtimbres[program].carrier_40; | |
if (volume == 0) { | |
carrierval |= 0x3f; | |
} else { | |
calc_vol(&carrierval, volume); | |
} | |
oplregwr(port, 0x40 + op2offsets[voice], carrierval); | |
} | |
/* get the id of the instrument that relates to channel/note pair */ | |
static int getinstrument(int channel, int note) { | |
int res; | |
res = oplmem->channelprog[channel]; | |
if (channel == 9) { /* the percussion channel requires special handling */ | |
if ((note < 35) || (note > 86)) return(-1); /* ignore unsupported notes */ | |
res = (128 - 35) + note; | |
} | |
return(res); | |
} | |
void opl_midi_noteon(unsigned short port, int channel, int note, int velocity) { | |
int x, voice = -1; | |
int lowestpriorityvoice = 0; | |
int instrument; | |
/* get the instrument to play */ | |
instrument = getinstrument(channel, note); | |
if (instrument < 0) return; | |
/* if note already playing, then reuse its voice to avoid leaving a stuck voice */ | |
if (oplmem->notes2voices[channel][note] >= 0) { | |
voice = oplmem->notes2voices[channel][note]; | |
} else { | |
/* else find a free voice, possibly with the right timbre, or at least locate the oldest note */ | |
for (x = 0; x < voicescount; x++) { | |
if (oplmem->voices2notes[x].channel < 0) { | |
voice = x; /* preselect this voice, but continue looking */ | |
/* if the instrument is right, do not look further */ | |
if (oplmem->voices2notes[x].timbreid == instrument) { | |
break; | |
} | |
} | |
if (oplmem->voices2notes[x].priority < oplmem->voices2notes[lowestpriorityvoice].priority) lowestpriorityvoice = x; | |
} | |
/* if no free voice available, then abort the oldest one */ | |
if (voice < 0) { | |
voice = lowestpriorityvoice; | |
opl_midi_noteoff(port, oplmem->voices2notes[voice].channel, oplmem->voices2notes[voice].note); | |
} | |
} | |
/* load the proper instrument, if not already good */ | |
if (oplmem->voices2notes[voice].timbreid != instrument) { | |
oplmem->voices2notes[voice].timbreid = instrument; | |
opl_loadinstrument(port, voice, &(gmtimbres[instrument])); | |
} | |
/* update states */ | |
oplmem->voices2notes[voice].channel = channel; | |
oplmem->voices2notes[voice].note = note; | |
oplmem->voices2notes[voice].priority = ((16 - channel) << 8) | 0xff; /* lower channels must have priority */ | |
oplmem->notes2voices[channel][note] = voice; | |
/* set the requested velocity on the voice */ | |
voicevolume(port, voice, oplmem->voices2notes[voice].timbreid, velocity * oplmem->channelvol[channel] / 127); | |
/* trigger NOTE_ON on the OPL, take care to apply the 'finetune' pitch correction, too */ | |
if (channel == 9) { /* percussion channel doesn't provide a real note (FIXME what should I use as the 'note'??) */ | |
opl_noteon(port, voice, 64, oplmem->channelpitch[channel] + gmtimbres[instrument].finetune); | |
} else { | |
opl_noteon(port, voice, note, oplmem->channelpitch[channel] + gmtimbres[instrument].finetune); | |
} | |
/* reajust all priorities */ | |
for (x = 0; x < voicescount; x++) { | |
if (oplmem->voices2notes[x].priority > 0) oplmem->voices2notes[x].priority -= 1; | |
} | |
} | |
void opl_midi_noteoff(unsigned short port, int channel, int note) { | |
int voice = oplmem->notes2voices[channel][note]; | |
if (voice >= 0) { | |
opl_noteoff(port, voice); | |
oplmem->voices2notes[voice].channel = -1; | |
oplmem->voices2notes[voice].note = -1; | |
oplmem->voices2notes[voice].priority = -1; | |
oplmem->notes2voices[channel][note] = -1; | |
} | |
} | |
#endif /* #ifdef OPL */ |