Skip to content
Permalink
Browse files
Implement proper pitch slides
  • Loading branch information
binarymaster committed Aug 7, 2017
1 parent 87cf8b1 commit 284aff4a6be591f8d694cfeab876082ba62c8756
Showing with 104 additions and 121 deletions.
  1. +94 −114 src/herad.cpp
  2. +10 −7 src/herad.h
@@ -18,12 +18,18 @@
*
* herad.cpp - Herbulot AdLib Player by Stas'M <binarymaster@mail.ru>
*
* Thanks goes to co-workers:
* - SynaMax (general documentation, reverse-engineering, testing)
* - Jepael (timer code sample, DOS driver shell)
* - opl2 (pitch slides code sample)
*
* REFERENCES:
* http://www.vgmpf.com/Wiki/index.php/HERAD
*
* TODO:
* - Fix transpose issue
* - Fix strange AGD sound
* - Fix splash sound in Gorbi (at 0:23)
* - Fix hiss sound in NewSan (at beginning)
*/

#include <cstring>
@@ -41,19 +47,12 @@ const uint8_t CheradPlayer::slot_offset[HERAD_NUM_VOICES] = {
const uint16_t CheradPlayer::FNum[HERAD_NUM_NOTES] = {
343, 364, 385, 408, 433, 459, 486, 515, 546, 579, 614, 650
};
const uint16_t CheradPlayer::FNum_coarse[HERAD_NUM_NOTES * 5] = {
343, 348, 353, 358, 363,
364, 369, 374, 379, 384,
385, 390, 395, 400, 405,
408, 413, 418, 423, 428,
433, 438, 443, 448, 453,
459, 464, 469, 474, 479,
486, 492, 498, 504, 510,
515, 521, 527, 533, 539,
546, 552, 558, 564, 570,
579, 585, 591, 597, 603,
614, 620, 626, 632, 638,
650, 656, 662, 668, 674
const uint8_t CheradPlayer::fine_bend[HERAD_NUM_NOTES + 1] = {
19, 21, 21, 23, 25, 26, 27, 29, 31, 33, 35, 36, 37
};
const uint8_t CheradPlayer::coarse_bend[10] = {
0, 5, 10, 15, 20,
0, 6, 12, 18, 24
};

CPlayer *CheradPlayer::factory(Copl *newopl)
@@ -707,11 +706,8 @@ void CheradPlayer::rewind(int subsong)
chn[i].playprog = 0;
chn[i].note = 24;
chn[i].keyon = false;
chn[i].bend = 0x40;
chn[i].slide_sign = 0;
chn[i].bend = HERAD_BEND_CENTER;
chn[i].slide_dur = 0;
chn[i].slide_coarse = false;
chn[i].slide_step = 0;
}
if (v2)
{
@@ -827,7 +823,7 @@ void CheradPlayer::ev_noteOn(uint8_t ch, uint8_t note, uint8_t vel)
}
chn[ch].note = note;
chn[ch].keyon = true;
chn[ch].bend = 0x40;
chn[ch].bend = HERAD_BEND_CENTER;
if (v2 && inst[chn[ch].playprog].param.mode == HERAD_INSTMODE_KMAP)
return;
playNote(ch, note, HERAD_NOTE_ON);
@@ -883,85 +879,98 @@ void CheradPlayer::ev_pitchBend(uint8_t ch, uint8_t bend)
playNote(ch, chn[ch].note, HERAD_NOTE_UPDATE);
}

void CheradPlayer::clipNote(uint8_t * note, bool soft)
{
// C2 ~ B9
if (*note < 24)
*note = 24;
if (*note > 119)
*note = (soft ? 119 : 24);
}

/*
* Play Note (c - channel, note number, note state - see HERAD_NOTE_*)
*/
void CheradPlayer::playNote(uint8_t c, uint8_t note, uint8_t state)
void CheradPlayer::playNote(uint8_t c, int8_t note, uint8_t state)
{
if (inst[chn[c].playprog].param.mc_transpose != 0)
macroTranspose(&note, chn[c].playprog);
uint8_t bend = chn[c].bend;
if (bend != 0x40)
note = (note - 24) & 0xFF;
int8_t oct = note / HERAD_NUM_NOTES;
note %= HERAD_NUM_NOTES;
if (state != HERAD_NOTE_UPDATE)
{
// normalize note and bend
if (bend < 0x40)
if (inst[chn[c].playprog].param.mc_slide_range != 0)
{
while (bend <= 0x20)
chn[c].slide_dur = (state == HERAD_NOTE_ON ? inst[chn[c].playprog].param.mc_slide_dur : 0);
}
}
uint8_t bend = chn[c].bend;
int16_t amount, detune = 0;
uint8_t amount_lo, amount_hi;
if (!(inst[chn[c].playprog].param.mc_slide_coarse & 1))
{ // fine tune
if (bend - HERAD_BEND_CENTER < 0)
{ // slide down
amount = HERAD_BEND_CENTER - bend;
amount_lo = (amount >> 5);
amount_hi = (amount << 3) & 0xFF;
note -= amount_lo;

if (note < 0)
{
note += HERAD_NUM_NOTES;
oct--;
}
if (oct < 0)
{
note--;
bend += 0x20;
note = 0;
oct = 0;
}
detune = ((fine_bend[note] * amount_hi) >> 8) * -1;
}
if (bend > 0x40)
{
while (bend >= 0x60)
else
{ // slide up
amount = bend - HERAD_BEND_CENTER;
amount_lo = (amount >> 5);
amount_hi = (amount << 3) & 0xFF;
note += amount_lo;

if (note >= HERAD_NUM_NOTES)
{
note++;
bend -= 0x20;
note -= HERAD_NUM_NOTES;
oct++;
}
detune = (fine_bend[note + 1] * amount_hi) >> 8;
}
// now bend in range 0x21..0x40..0x5F
}
clipNote(&note);
uint8_t oct = note / HERAD_NUM_NOTES - 2;
uint16_t freq = FNum[note % HERAD_NUM_NOTES];
if (bend != 0x40)
{
uint16_t diff;
int8_t coef = bend - 0x40; // -31..+31
if (note % HERAD_NUM_NOTES == 0 && bend < 0x40)
{
diff = freq - HERAD_FNUM_MIN;
freq += diff * coef / 31;
}
else if (note % HERAD_NUM_NOTES == 11 && bend > 0x40)
{
diff = HERAD_FNUM_MAX - freq;
freq += diff * coef / 31;
}
else
{
if (bend < 0x40)
else
{ // coarse tune
uint8_t offset;
if (bend - HERAD_BEND_CENTER < 0)
{ // slide down
amount = HERAD_BEND_CENTER - bend;
note -= amount / 5;

if (note < 0)
{
diff = freq - FNum[(note - 1) % HERAD_NUM_NOTES];
note += HERAD_NUM_NOTES;
oct--;
}
else
if (oct < 0)
{
diff = FNum[(note + 1) % HERAD_NUM_NOTES] - freq;
note = 0;
oct = 0;
}
freq += diff * coef / 32;
offset = (amount % 5) + (note >= 6 ? 5 : 0);
detune = -1 * coarse_bend[offset];
}
}
if (state != HERAD_NOTE_UPDATE)
{
chn[c].slide_sign = inst[chn[c].playprog].param.mc_slide_range;
if (chn[c].slide_sign != 0)
{
chn[c].slide_dur = (state == HERAD_NOTE_ON ? inst[chn[c].playprog].param.mc_slide_dur : 0);
chn[c].slide_coarse = (inst[chn[c].playprog].param.mc_slide_coarse & 1) > 0;
chn[c].slide_step = 0;
else
{ // slide up
amount = bend - HERAD_BEND_CENTER;
note += amount / 5;

if (note >= HERAD_NUM_NOTES)
{
note -= HERAD_NUM_NOTES;
oct++;
}
offset = (amount % 5) + (note >= 6 ? 5 : 0);
detune = coarse_bend[offset];
}
}
setFreq(c, oct, freq, state != HERAD_NOTE_OFF);
setFreq(c, oct, FNum[note] + detune, state != HERAD_NOTE_OFF);
}

/*
@@ -1164,7 +1173,7 @@ void CheradPlayer::macroFeedback(uint8_t c, uint8_t i, int8_t sens, uint8_t leve
/*
* Macro: Root Note Transpose (note, i - instrument index)
*/
void CheradPlayer::macroTranspose(uint8_t * note, uint8_t i)
void CheradPlayer::macroTranspose(int8_t * note, uint8_t i)
{
uint8_t tran = inst[i].param.mc_transpose;
uint8_t diff = (tran - 0x31) & 0xFF;
@@ -1179,44 +1188,15 @@ void CheradPlayer::macroTranspose(uint8_t * note, uint8_t i)
*/
void CheradPlayer::macroSlide(uint8_t c)
{
if (chn[c].slide_sign < 0)
chn[c].slide_step--;
else
chn[c].slide_step++;
uint8_t note = chn[c].note;
if (inst[chn[c].playprog].param.mc_transpose != 0)
macroTranspose(&note, chn[c].playprog);
clipNote(&note, false);
uint8_t oct = note / HERAD_NUM_NOTES - 2;
uint16_t freq = FNum[note % HERAD_NUM_NOTES];
int32_t scaleX;
int16_t scale_oct;
if (chn[c].slide_coarse)
{
scaleX = ((note % HERAD_NUM_NOTES) * 5) + chn[c].slide_step;
scale_oct = scaleX / (HERAD_NUM_NOTES * 5);
if (oct + scale_oct < 0 || oct + scale_oct > 7)
{
chn[c].slide_dur = 0;
return;
}
oct += scale_oct;
freq = FNum_coarse[scaleX % (HERAD_NUM_NOTES * 5)];
}
else // fine tune
{
scaleX = freq - HERAD_FNUM_MIN + chn[c].slide_step;
scale_oct = scaleX / (HERAD_FNUM_MAX - HERAD_FNUM_MIN + 1);
if (oct + scale_oct < 0 || oct + scale_oct > 7)
{
chn[c].slide_dur = 0;
return;
}
oct += scale_oct;
freq = (scaleX % (HERAD_FNUM_MAX - HERAD_FNUM_MIN + 1)) + HERAD_FNUM_MIN;
}
setFreq(c, oct, freq, chn[c].keyon);
if (!chn[c].slide_dur)
return;

chn[c].slide_dur--;
chn[c].bend += inst[chn[c].playprog].param.mc_slide_range;

if (!(chn[c].note & 0x7F))
return;
playNote(c, chn[c].note, HERAD_NOTE_UPDATE);
}

void CheradPlayer::processEvents()
@@ -18,6 +18,11 @@
*
* herad.h - Herbulot AdLib Player by Stas'M <binarymaster@mail.ru>
*
* Thanks goes to co-workers:
* - SynaMax (general documentation, reverse-engineering, testing)
* - Jepael (timer code sample, DOS driver shell)
* - opl2 (pitch slides code sample)
*
* REFERENCES:
* http://www.vgmpf.com/Wiki/index.php/HERAD
*/
@@ -42,6 +47,7 @@
#define HERAD_INSTMODE_KMAP -1 /* HERAD version 2 keymap */
#define HERAD_FNUM_MIN 325 /* Minimum note frequency number */
#define HERAD_FNUM_MAX 685 /* Maximum note frequency number */
#define HERAD_BEND_CENTER 0x40 /* Pitch bend middle value */
#define HERAD_NOTE_OFF 0
#define HERAD_NOTE_ON 1
#define HERAD_NOTE_UPDATE 2
@@ -122,19 +128,19 @@ class CheradPlayer: public CPlayer
void ev_programChange(uint8_t ch, uint8_t prog);
void ev_aftertouch(uint8_t ch, uint8_t vel);
void ev_pitchBend(uint8_t ch, uint8_t bend);
void clipNote(uint8_t * note, bool soft = false);
void playNote(uint8_t c, uint8_t note, uint8_t state);
void playNote(uint8_t c, int8_t note, uint8_t state);
void setFreq(uint8_t c, uint8_t oct, uint16_t freq, bool on);
void changeProgram(uint8_t c, uint8_t i);
void macroModOutput(uint8_t c, uint8_t i, int8_t sens, uint8_t level);
void macroCarOutput(uint8_t c, uint8_t i, int8_t sens, uint8_t level);
void macroFeedback(uint8_t c, uint8_t i, int8_t sens, uint8_t level);
void macroTranspose(uint8_t * note, uint8_t i);
void macroTranspose(int8_t * note, uint8_t i);
void macroSlide(uint8_t c);

static const uint8_t slot_offset[HERAD_NUM_VOICES];
static const uint16_t FNum[HERAD_NUM_NOTES];
static const uint16_t FNum_coarse[HERAD_NUM_NOTES * 5];
static const uint8_t fine_bend[HERAD_NUM_NOTES + 1];
static const uint8_t coarse_bend[10];

protected:
bool songend;
@@ -169,10 +175,7 @@ class CheradPlayer: public CPlayer
uint8_t note; /* current note */
bool keyon; /* note is active */
uint8_t bend; /* current pitch bend */
int8_t slide_sign; /* pitch slide sign */
uint8_t slide_dur; /* pitch slide duration */
bool slide_coarse; /* pitch slide coarse */
int32_t slide_step; /* pitch slide step */
};
struct herad_inst_data {
int8_t mode; /* instrument mode (see HERAD_INSTMODE_*) */

0 comments on commit 284aff4

Please sign in to comment.