Files
scummvm-cursorfix/engines/ultima/nuvie/sound/adplug/mid.cpp
2026-02-02 04:50:13 +01:00

573 lines
13 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/files/u6_lib_n.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/sound/origin_fx_adib_driver.h"
#include "ultima/nuvie/sound/adplug/mid.h"
namespace Ultima {
namespace Nuvie {
//#define TESTING
#ifdef TESTING
#define midiprintf debug
#else
void CmidPlayer::midiprintf(const char *format, ...) {
}
#endif
#define LUCAS_STYLE 1
#define CMF_STYLE 2
#define MIDI_STYLE 4
#define SIERRA_STYLE 8
// AdLib melodic and rhythm mode defines
#define ADLIB_MELODIC 1
#define ADLIB_RYTHM 0
// File types
#define FILE_LUCAS 1
#define FILE_MIDI 2
#define FILE_CMF 3
#define FILE_SIERRA 4
#define FILE_ADVSIERRA 5
#define FILE_OLDLUCAS 6
CPlayer *CmidPlayer::factory(Copl *newopl) {
return new CmidPlayer(newopl);
}
CmidPlayer::CmidPlayer(Copl *newopl)
: CPlayer(newopl), author(&emptystr), title(&emptystr), remarks(&emptystr),
emptystr('\0'), flen(0), data(0) {
origin_fx_driver = new OriginFXAdLibDriver(Game::get_game()->get_config(), newopl);
}
CmidPlayer::~CmidPlayer() {
if (data)
delete [] data;
delete origin_fx_driver;
}
unsigned char CmidPlayer::datalook(long pos_) {
if (pos_ < 0 || pos_ >= flen) return 0;
return data[pos_];
}
unsigned long CmidPlayer::getnexti(unsigned long num) {
unsigned long v = 0;
unsigned long i;
for (i = 0; i < num; i++) {
v += (datalook(pos) << (8 * i));
pos++;
}
return v;
}
unsigned long CmidPlayer::getnext(unsigned long num) {
unsigned long v = 0;
unsigned long i;
for (i = 0; i < num; i++) {
v <<= 8;
v += datalook(pos);
pos++;
}
return v;
}
unsigned long CmidPlayer::getval() {
int v = 0;
unsigned char b;
b = (unsigned char)getnext(1);
v = b & 0x7f;
while ((b & 0x80) != 0) {
b = (unsigned char)getnext(1);
v = (v << 7) + (b & 0x7F);
}
return v;
}
bool CmidPlayer::load(const Common::Path &filename) {
return false;
}
bool CmidPlayer::load(const Common::Path &filename, int song_index) {
U6Lib_n f;
f.open(filename, 4, NUVIE_GAME_MD);
//binistream *f = fp.open(filename); if(!f) return false;
int good;
flen = f.get_item_size(song_index);
data = new unsigned char [flen];
f.get_item(song_index, data);
//f->readString((char *)data, flen);
//f->readString((char *)s, 6);
good = 0;
subsongs = 0;
switch (data[0]) {
case 'A':
if (data[1] == 'D' && data[2] == 'L') good = FILE_LUCAS;
break;
case 'M':
if (data[1] == 'T' && data[2] == 'h' && data[3] == 'd') good = FILE_MIDI;
break;
case 'C':
if (data[1] == 'T' && data[2] == 'M' && data[3] == 'F') good = FILE_CMF;
break;
break;
default:
if (data[4] == 'A' && data[5] == 'D') good = FILE_OLDLUCAS;
break;
}
if (good != 0)
subsongs = 1;
else {
delete [] data;
data = nullptr;
return false;
}
type = good;
//f->seek(0);
rewind(0);
return true;
}
void CmidPlayer::interrupt_vector() {
origin_fx_driver->interrupt_vector();
}
bool CmidPlayer::update() {
const uint8 adlib_chan_tbl[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10,
10, 18, 11, 0, 12, 13, 17, 13, 16, 13, 14, 13, 13, 15,
13, 19, 0, 0, 0, 0, 21, 0, 0, 0, 26, 26, 25, 20, 20,
0, 0, 21, 21, 22, 23, 0, 0, 24, 0, 20, 0
};
const uint8 adlib_note_tbl[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48,
48, 48, 48, 0, 48, 42, 71, 42, 71, 47, 71, 47, 52, 79,
52, 77, 0, 0, 0, 0, 71, 0, 0, 0, 72, 79, 79, 64, 58,
0, 0, 89, 84, 48, 72, 0, 0, 36, 0, 96, 0
};
//long w,v,note,vel,ctrl,nv,x,l,lnum;
long w, v, note, vel, ctrl, x, l, lnum;
int i = 0, j, c;
//int on,onl,numchan;
int ret;
int current_status[16];
for (i = 0; i < 16; i++)
current_status[i] = 0;
if (doing == 1) {
// just get the first wait and ignore it :>
for (curtrack = 0; curtrack < 16; curtrack++)
if (track[curtrack].on) {
pos = track[curtrack].pos;
if (type != FILE_SIERRA && type != FILE_ADVSIERRA)
track[curtrack].iwait += getval();
else
track[curtrack].iwait += getnext(1);
track[curtrack].pos = pos;
}
doing = 0;
}
iwait = 0;
ret = 1;
while (iwait == 0 && ret == 1) {
for (curtrack = 0; curtrack < 16; curtrack++)
if (track[curtrack].on && track[curtrack].iwait == 0 &&
track[curtrack].pos < track[curtrack].tend) {
pos = track[curtrack].pos;
v = getnext(1);
// This is to do implied MIDI events. aka 'Running Status'
if (v < 0x80) {
v = track[curtrack].pv;
debug("Running status [%2X]", (unsigned int)v);
pos--;
} else {
if (v >= 0xf0 && v < 0xf9) {
track[curtrack].pv = 0; //reset running status.
} else if (v < 0xf0) {
track[curtrack].pv = (unsigned char)v;
}
// if v > 0xf9 then current running status is maintained.
}
c = v & 0x0f;
midiprintf("[%2X]", (unsigned int)v);
if (v == 0xfe)
midiprintf("pos=%d", (int)pos);
current_status[curtrack] = v;
switch (v & 0xf0) {
case 0x80: /*note off*/
midiprintf("Trk%02d: Note Off\n", curtrack);
note = getnext(1);
vel = getnext(1);
origin_fx_driver->play_note(c, note, 0);
break;
case 0x90: /*note on*/
// doing=0;
midiprintf("Trk%02d: Note On\n", curtrack);
note = getnext(1);
vel = getnext(1);
if (c == 9) {
if (adlib_chan_tbl[note] != 0) {
origin_fx_driver->play_note(adlib_chan_tbl[note] - 1, adlib_note_tbl[note], vel);
}
} else {
origin_fx_driver->play_note(c, note, vel);
}
break;
case 0xa0: /*key after touch */
note = getnext(1);
vel = getnext(1);
break;
case 0xb0: /*control change .. pitch bend? */
ctrl = getnext(1);
vel = getnext(1);
origin_fx_driver->control_mode_change(c, ctrl, vel);
break;
case 0xc0: /*patch change*/
x = getnext(1);
origin_fx_driver->program_change(c, x);
break;
case 0xd0: /*chanel touch*/
x = getnext(1);
break;
case 0xe0: /*pitch wheel*/
x = getnext(1);
l = getnext(1);
origin_fx_driver->pitch_bend(c, x, l);
break;
case 0xf0:
switch (v) {
case 0xf0:
case 0xf7: /*sysex*/
l = getval();
if (datalook(pos + l) == 0xf7)
i = 1;
midiprintf("{%d}", (int)l);
midiprintf("\n");
if (datalook(pos) == 0x7d &&
datalook(pos + 1) == 0x10 &&
datalook(pos + 2) < 16) {
adlib_style = LUCAS_STYLE | MIDI_STYLE;
for (i = 0; i < l; i++) {
midiprintf("%x ", datalook(pos + i));
if ((i - 3) % 10 == 0) midiprintf("\n");
}
midiprintf("\n");
getnext(1);
getnext(1);
c = getnext(1);
getnext(1);
// getnext(22); //temp
i = (getnext(1) << 4) + getnext(1);
//if ((i&1)==1) ch[c].ins[10]=1;
midiprintf("\n%d: ", c);
getnext(l - 26);
} else {
midiprintf("\n");
for (j = 0; j < l; j++)
midiprintf("%2X ", (unsigned int)getnext(1));
}
midiprintf("\n");
if (i == 1)
getnext(1);
break;
case 0xf1:
break;
case 0xf2:
getnext(2);
break;
case 0xf3:
getnext(1);
break;
case 0xf4:
break;
case 0xf5:
break;
case 0xf6: /*something*/
case 0xf8:
case 0xfa:
case 0xfb:
case 0xfc:
break;
case 0xfe:
i = getnext(1);
//debug("FE %02X pos=%d\n",i, (int)pos);//(unsigned int)getnext(1),(unsigned int)getnext(1));
getnext(2);
if (i == 0) {
//debug(" %02X",(unsigned int)getnext(1));
//getnext(1);
}
//debug("\n");
if (i != 3) {
origin_fx_driver->control_mode_change(c, 0x7b, 0);
}
break;
case 0xfd:
break;
case 0xff:
v = getnext(1);
l = getval();
midiprintf("\n");
midiprintf("{%X_%X}", (unsigned int)v, (int)l);
if (v == 0x51) {
lnum = getnext(l);
msqtr = lnum; /*set tempo*/
midiprintf("Set Tempo (qtr=%ld)", msqtr);
} else if (v == 0x3) {
midiprintf("Track Name: ");
for (i = 0; i < l; i++)
midiprintf("%c", (unsigned char)getnext(1));
} else if (v == 0x6) {
debugN("Marker: ");
for (i = 0; i < l; i++) {
//midiprintf ("%c",(unsigned char)getnext(1));
debugN("%c", (unsigned char)getnext(1));
}
debug("%s", "");
} else {
for (i = 0; i < l; i++)
midiprintf("%2X ", (unsigned int)getnext(1));
}
break;
}
break;
default:
midiprintf("! v = %d", (int)v); /* if we get down here, a error occurred */
break;
}
if (pos < track[curtrack].tend) {
if (type != FILE_SIERRA && type != FILE_ADVSIERRA)
w = getval();
else
w = getnext(1);
track[curtrack].iwait = w;
/*
if (w!=0)
{
midiprintf("\n<%d>",w);
f =
((float)w/(float)deltas)*((float)msqtr/(float)1000000);
if (doing==1) f=0; //not playing yet. don't wait yet
}
*/
} else
track[curtrack].iwait = 0;
track[curtrack].pos = pos;
}
/*
for(i=0;i<16;i++)
{
if(current_status[i] == 0)
debug("--");
else
debug("%02X", current_status[i]);
debug(" ");
}
debug("\n");
*/
ret = 0; //end of song.
iwait = 0;
for (curtrack = 0; curtrack < 16; curtrack++)
if (track[curtrack].on == 1 &&
track[curtrack].pos < track[curtrack].tend)
ret = 1; //not yet..
if (ret == 1) {
iwait = 0xffffff; // bigger than any wait can be!
for (curtrack = 0; curtrack < 16; curtrack++)
if (track[curtrack].on == 1 &&
track[curtrack].pos < track[curtrack].tend &&
track[curtrack].iwait < iwait)
iwait = track[curtrack].iwait;
}
}
if (iwait != 0 && ret == 1) {
for (curtrack = 0; curtrack < 16; curtrack++)
if (track[curtrack].on)
track[curtrack].iwait -= iwait;
fwait = 1.0f / (((float)iwait / (float)deltas) * ((float)msqtr / (float)1000000));
} else
fwait = 50; // 1/50th of a second
midiprintf("\n");
for (i = 0; i < 16; i++)
if (track[i].on) {
if (track[i].pos < track[i].tend)
;//midiprintf ("<%d:%d>",(int)i,(int)track[i].iwait);
else
midiprintf("stop");
}
// FIXME: current_status is unused?
(void)current_status[0];
if (ret)
return true;
else
return false;
}
float CmidPlayer::getrefresh() {
return (fwait > 0.01f ? fwait : 0.01f);
}
void CmidPlayer::rewind(int subsong) {
long i;
pos = 0;
tins = 0;
adlib_style = MIDI_STYLE | CMF_STYLE;
adlib_mode = ADLIB_MELODIC;
/* General init */
for (i = 0; i < 9; i++) {
chp[i][0] = -1;
chp[i][2] = 0;
}
deltas = 250; // just a number, not a standard
msqtr = 500000;
fwait = 123; // gotta be a small thing.. sorta like nothing
iwait = 0;
subsongs = 1;
for (i = 0; i < 16; i++) {
track[i].tend = 0;
track[i].spos = 0;
track[i].pos = 0;
track[i].iwait = 0;
track[i].on = 0;
track[i].pv = 0;
}
curtrack = 0;
/* specific to file-type init */
pos = 0;
i = getnext(1);
switch (type) {
case FILE_MIDI:
if (type != FILE_LUCAS)
tins = 128;
getnext(9); /*skip header*/
track_count = getnext(2); //total number of tracks.
deltas = getnext(2);
midiprintf("deltas:%ld\n", deltas);
load_ultima_midi_tracks();
break;
}
/* Common::sprintf_s(info,"%s\r\nTicks/Quarter Note: %ld\r\n",info,deltas);
Common::sprintf_s(info,"%sms/Quarter Note: %ld",info,msqtr); */
for (i = 0; i < 16; i++)
if (track[i].on) {
track[i].pos = track[i].spos;
track[i].pv = 0;
track[i].iwait = 0;
}
doing = 1;
origin_fx_driver->init();
}
void CmidPlayer::load_ultima_midi_tracks() {
for (curtrack = 0; curtrack < track_count; curtrack++) {
getnext(4); //skip MTrk
track[curtrack].on = 1;
track[curtrack].tend = getnext(4);
track[curtrack].tend += pos;
track[curtrack].spos = pos;
pos = track[curtrack].tend;
midiprintf("tracklen:%ld\n", track[curtrack].tend - track[curtrack].spos);
}
}
Std::string CmidPlayer::gettype() {
switch (type) {
case FILE_LUCAS:
return Std::string("LucasArts AdLib MIDI");
case FILE_MIDI:
return Std::string("General MIDI");
case FILE_CMF:
return Std::string("Creative Music Format (CMF MIDI)");
case FILE_OLDLUCAS:
return Std::string("Lucasfilm Adlib MIDI");
case FILE_ADVSIERRA:
return Std::string("Sierra On-Line VGA MIDI");
case FILE_SIERRA:
return Std::string("Sierra On-Line EGA MIDI");
default:
return Std::string("MIDI unknown");
}
}
} // End of namespace Nuvie
} // End of namespace Ultima