573 lines
13 KiB
C++
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
|