Initial commit
This commit is contained in:
316
engines/sky/music/adlibchannel.cpp
Normal file
316
engines/sky/music/adlibchannel.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
/* 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 "common/endian.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "audio/fmopl.h"
|
||||
#include "sky/music/adlibchannel.h"
|
||||
#include "sky/sky.h"
|
||||
|
||||
namespace Sky {
|
||||
|
||||
AdLibChannel::AdLibChannel(OPL::OPL *opl, uint8 *pMusicData, uint16 startOfData) {
|
||||
_opl = opl;
|
||||
_musicData = pMusicData;
|
||||
_channelData.loopPoint = startOfData;
|
||||
_channelData.eventDataPtr = startOfData;
|
||||
_channelData.channelActive = true;
|
||||
_channelData.tremoVibro = 0;
|
||||
_channelData.assignedInstrument = 0xFF;
|
||||
_channelData.channelVolume = 0x7F;
|
||||
_channelData.nextEventTime = getNextEventTime();
|
||||
|
||||
_channelData.adlibChannelNumber = _channelData.lastCommand = _channelData.note =
|
||||
_channelData.adlibReg1 = _channelData.adlibReg2 = _channelData.freqOffset = 0;
|
||||
_channelData.frequency = 0;
|
||||
_channelData.instrumentData = NULL;
|
||||
|
||||
_musicVolume = 128;
|
||||
|
||||
uint16 instrumentDataLoc;
|
||||
|
||||
if (SkyEngine::_systemVars->gameVersion == 109) {
|
||||
//instrumentDataLoc = (_musicData[0x11D0] << 8) | _musicData[0x11CF];
|
||||
//_frequenceTable = (uint16 *)(_musicData + 0x835);
|
||||
//_registerTable = _musicData + 0xE35;
|
||||
//_opOutputTable = _musicData + 0xE47;
|
||||
//_adlibRegMirror = _musicData + 0xF4A;
|
||||
|
||||
instrumentDataLoc = READ_LE_UINT16(_musicData + 0x1204);
|
||||
_frequenceTable = (uint16 *)(_musicData + 0x868);
|
||||
_registerTable = _musicData + 0xE68;
|
||||
_opOutputTable = _musicData + 0xE7A;
|
||||
_adlibRegMirror = _musicData + 0xF7D;
|
||||
} else if (SkyEngine::_systemVars->gameVersion == 267) {
|
||||
instrumentDataLoc = READ_LE_UINT16(_musicData + 0x11FB);
|
||||
_frequenceTable = (uint16 *)(_musicData + 0x7F4);
|
||||
_registerTable = _musicData + 0xDF4;
|
||||
_opOutputTable = _musicData + 0xE06;
|
||||
_adlibRegMirror = _musicData + 0xF55;
|
||||
} else {
|
||||
instrumentDataLoc = READ_LE_UINT16(_musicData + 0x1205);
|
||||
_frequenceTable = (uint16 *)(_musicData + 0x7FE);
|
||||
_registerTable = _musicData + 0xDFE;
|
||||
_opOutputTable = _musicData + 0xE10;
|
||||
_adlibRegMirror = _musicData + 0xF5F;
|
||||
}
|
||||
|
||||
_instrumentMap = _musicData+instrumentDataLoc;
|
||||
_instruments = (InstrumentStruct *)(_instrumentMap+0x80);
|
||||
}
|
||||
|
||||
AdLibChannel::~AdLibChannel() {
|
||||
stopNote();
|
||||
}
|
||||
|
||||
bool AdLibChannel::isActive() {
|
||||
return _channelData.channelActive;
|
||||
}
|
||||
|
||||
void AdLibChannel::updateVolume(uint16 pVolume) {
|
||||
_musicVolume = pVolume;
|
||||
}
|
||||
|
||||
/* This class uses the same area for the register mirror as the original
|
||||
asm driver did (_musicData[0xF5F..0x105E]), so the cache is indeed shared
|
||||
by all instances of the class.
|
||||
*/
|
||||
void AdLibChannel::setRegister(uint8 regNum, uint8 value) {
|
||||
if (_adlibRegMirror[regNum] != value) {
|
||||
_opl->writeReg(regNum, value);
|
||||
_adlibRegMirror[regNum] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void AdLibChannel::stopNote() {
|
||||
if (_channelData.note & 0x20) {
|
||||
_channelData.note &= ~0x20;
|
||||
setRegister(0xB0 | _channelData.adlibChannelNumber, _channelData.note);
|
||||
}
|
||||
}
|
||||
|
||||
int32 AdLibChannel::getNextEventTime() {
|
||||
int32 retV = 0;
|
||||
uint8 cnt, lVal = 0;
|
||||
for (cnt = 0; cnt < 4; cnt++) {
|
||||
lVal = _musicData[_channelData.eventDataPtr];
|
||||
_channelData.eventDataPtr++;
|
||||
retV = (retV << 7) | (lVal & 0x7F);
|
||||
if (!(lVal & 0x80))
|
||||
break;
|
||||
}
|
||||
if (lVal & 0x80) {
|
||||
return -1; // should never happen
|
||||
} else
|
||||
return retV;
|
||||
}
|
||||
|
||||
uint8 AdLibChannel::process(uint16 aktTime) {
|
||||
if (!_channelData.channelActive) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8 returnVal = 0;
|
||||
|
||||
_channelData.nextEventTime -= aktTime;
|
||||
uint8 opcode;
|
||||
while ((_channelData.nextEventTime < 0) && (_channelData.channelActive)) {
|
||||
opcode = _musicData[_channelData.eventDataPtr];
|
||||
_channelData.eventDataPtr++;
|
||||
if (opcode & 0x80) {
|
||||
if (opcode == 0xFF) {
|
||||
// dummy opcode
|
||||
} else if (opcode >= 0x90) {
|
||||
switch (opcode&0xF) {
|
||||
case 0: com90_caseNoteOff(); break;
|
||||
case 1: com90_stopChannel(); break;
|
||||
case 2: com90_setupInstrument(); break;
|
||||
case 3:
|
||||
returnVal = com90_updateTempo();
|
||||
break;
|
||||
case 5: com90_getFreqOffset(); break;
|
||||
case 6: com90_getChannelVolume(); break;
|
||||
case 7: com90_getTremoVibro(); break;
|
||||
case 8: com90_loopMusic(); break;
|
||||
case 9: com90_keyOff(); break;
|
||||
case 12: com90_setLoopPoint(); break;
|
||||
|
||||
default:
|
||||
error("AdLibChannel: Unknown music opcode 0x%02X", opcode);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// new adlib channel assignment
|
||||
_channelData.adlibChannelNumber = opcode & 0xF;
|
||||
_channelData.adlibReg1 = _registerTable[((opcode & 0xF) << 1) | 0];
|
||||
_channelData.adlibReg2 = _registerTable[((opcode & 0xF) << 1) | 1];
|
||||
}
|
||||
} else {
|
||||
_channelData.lastCommand = opcode;
|
||||
stopNote();
|
||||
// not sure why this "if" is necessary...either a bug in my
|
||||
// code or a bug in the music data (section 1, music 2)
|
||||
if (_channelData.instrumentData || _channelData.tremoVibro) {
|
||||
setupInstrument(opcode);
|
||||
|
||||
opcode = _musicData[_channelData.eventDataPtr];
|
||||
_channelData.eventDataPtr++;
|
||||
setupChannelVolume(opcode);
|
||||
} else
|
||||
_channelData.eventDataPtr++;
|
||||
}
|
||||
if (_channelData.channelActive)
|
||||
_channelData.nextEventTime += getNextEventTime();
|
||||
}
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
void AdLibChannel::setupInstrument(uint8 opcode) {
|
||||
uint16 nextNote;
|
||||
if (_channelData.tremoVibro) {
|
||||
uint8 newInstrument = _instrumentMap[opcode];
|
||||
if (newInstrument != _channelData.assignedInstrument) {
|
||||
_channelData.assignedInstrument = newInstrument;
|
||||
_channelData.instrumentData = _instruments + newInstrument;
|
||||
adlibSetupInstrument();
|
||||
}
|
||||
_channelData.lastCommand = _channelData.instrumentData->bindedEffect;
|
||||
nextNote = getNextNote(_channelData.lastCommand);
|
||||
} else {
|
||||
nextNote = getNextNote(opcode - 0x18 + _channelData.instrumentData->bindedEffect);
|
||||
}
|
||||
_channelData.frequency = nextNote;
|
||||
setRegister(0xA0 | _channelData.adlibChannelNumber, (uint8)nextNote);
|
||||
setRegister(0xB0 | _channelData.adlibChannelNumber, (uint8)((nextNote >> 8) | 0x20));
|
||||
_channelData.note = (uint8)((nextNote >> 8) | 0x20);
|
||||
}
|
||||
|
||||
void AdLibChannel::setupChannelVolume(uint8 volume) {
|
||||
uint8 resultOp;
|
||||
uint32 resVol = ((volume + 1) * (_channelData.instrumentData->totOutLev_Op2 + 1)) << 1;
|
||||
resVol &= 0xFFFF;
|
||||
resVol *= (_channelData.channelVolume + 1) << 1;
|
||||
resVol >>= 8;
|
||||
resVol *= _musicVolume << 1;
|
||||
resVol >>= 16;
|
||||
assert(resVol < 0x81);
|
||||
resultOp = ((_channelData.instrumentData->scalingLevel << 6) & 0xC0) | _opOutputTable[resVol];
|
||||
setRegister(0x40 | _channelData.adlibReg2, resultOp);
|
||||
if (_channelData.instrumentData->feedBack & 1) {
|
||||
resVol = ((volume + 1) * (_channelData.instrumentData->totOutLev_Op1 + 1)) << 1;
|
||||
resVol &= 0xFFFF;
|
||||
resVol *= (_channelData.channelVolume + 1) << 1;
|
||||
resVol >>= 8;
|
||||
resVol *= _musicVolume << 1;
|
||||
resVol >>= 16;
|
||||
} else
|
||||
resVol = _channelData.instrumentData->totOutLev_Op1;
|
||||
assert(resVol < 0x81);
|
||||
resultOp = ((_channelData.instrumentData->scalingLevel << 2) & 0xC0) | _opOutputTable[resVol];
|
||||
setRegister(0x40 | _channelData.adlibReg1, resultOp);
|
||||
}
|
||||
|
||||
void AdLibChannel::adlibSetupInstrument() {
|
||||
setRegister(0x60 | _channelData.adlibReg1, _channelData.instrumentData->ad_Op1);
|
||||
setRegister(0x60 | _channelData.adlibReg2, _channelData.instrumentData->ad_Op2);
|
||||
setRegister(0x80 | _channelData.adlibReg1, _channelData.instrumentData->sr_Op1);
|
||||
setRegister(0x80 | _channelData.adlibReg2, _channelData.instrumentData->sr_Op2);
|
||||
setRegister(0xE0 | _channelData.adlibReg1, _channelData.instrumentData->waveSelect_Op1);
|
||||
setRegister(0xE0 | _channelData.adlibReg2, _channelData.instrumentData->waveSelect_Op2);
|
||||
setRegister(0xC0 | _channelData.adlibChannelNumber, _channelData.instrumentData->feedBack);
|
||||
setRegister(0x20 | _channelData.adlibReg1, _channelData.instrumentData->ampMod_Op1);
|
||||
setRegister(0x20 | _channelData.adlibReg2, _channelData.instrumentData->ampMod_Op2);
|
||||
}
|
||||
|
||||
uint16 AdLibChannel::getNextNote(uint8 param) {
|
||||
int16 freqIndex = ((int16)_channelData.freqOffset) - 0x40;
|
||||
if (freqIndex >= 0x3F)
|
||||
freqIndex++;
|
||||
freqIndex *= 2;
|
||||
freqIndex += param << 6;
|
||||
uint16 freqData = FROM_LE_16(_frequenceTable[freqIndex % 0x300]);
|
||||
if ((freqIndex % 0x300 >= 0x1C0) || (freqIndex / 0x300 > 0)) {
|
||||
return (((freqIndex / 0x300) - 1) << 10) + (freqData & 0x7FF);
|
||||
} else {
|
||||
// looks like a bug. dunno why. It's what the ASM code says.
|
||||
return (uint16)(((int16)freqData) >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
//- command 90h routines
|
||||
|
||||
void AdLibChannel::com90_caseNoteOff() {
|
||||
if (_musicData[_channelData.eventDataPtr] == _channelData.lastCommand)
|
||||
stopNote();
|
||||
_channelData.eventDataPtr++;
|
||||
}
|
||||
|
||||
void AdLibChannel::com90_stopChannel() {
|
||||
stopNote();
|
||||
_channelData.channelActive = false;
|
||||
}
|
||||
|
||||
void AdLibChannel::com90_setupInstrument() {
|
||||
_channelData.channelVolume = 0x7F;
|
||||
_channelData.freqOffset = 0x40;
|
||||
_channelData.assignedInstrument = _musicData[_channelData.eventDataPtr];
|
||||
_channelData.eventDataPtr++;
|
||||
_channelData.instrumentData = _instruments + _channelData.assignedInstrument;
|
||||
adlibSetupInstrument();
|
||||
}
|
||||
|
||||
uint8 AdLibChannel::com90_updateTempo() {
|
||||
return _musicData[_channelData.eventDataPtr++];
|
||||
}
|
||||
|
||||
void AdLibChannel::com90_getFreqOffset() {
|
||||
_channelData.freqOffset = _musicData[_channelData.eventDataPtr++];
|
||||
if (_channelData.note & 0x20) {
|
||||
uint16 nextNote = getNextNote(
|
||||
_channelData.lastCommand - 0x18 + _channelData.instrumentData->bindedEffect);
|
||||
setRegister(0xA0 | _channelData.adlibChannelNumber, (uint8)nextNote);
|
||||
setRegister(0xB0 | _channelData.adlibChannelNumber, (uint8)((nextNote >> 8) | 0x20));
|
||||
_channelData.note = (uint8)(nextNote >> 8) | 0x20;
|
||||
}
|
||||
}
|
||||
|
||||
void AdLibChannel::com90_getChannelVolume() {
|
||||
_channelData.channelVolume = _musicData[_channelData.eventDataPtr++];
|
||||
}
|
||||
|
||||
void AdLibChannel::com90_getTremoVibro() {
|
||||
_channelData.tremoVibro = _musicData[_channelData.eventDataPtr++];
|
||||
}
|
||||
|
||||
void AdLibChannel::com90_loopMusic() {
|
||||
_channelData.eventDataPtr = _channelData.loopPoint;
|
||||
}
|
||||
|
||||
void AdLibChannel::com90_keyOff() {
|
||||
stopNote();
|
||||
}
|
||||
|
||||
void AdLibChannel::com90_setLoopPoint() {
|
||||
_channelData.loopPoint = _channelData.eventDataPtr;
|
||||
}
|
||||
|
||||
} // End of namespace Sky
|
||||
110
engines/sky/music/adlibchannel.h
Normal file
110
engines/sky/music/adlibchannel.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SKY_MUSIC_ADLIBCHANNEL_H
|
||||
#define SKY_MUSIC_ADLIBCHANNEL_H
|
||||
|
||||
#include "sky/music/musicbase.h"
|
||||
|
||||
namespace OPL {
|
||||
class OPL;
|
||||
}
|
||||
|
||||
namespace Sky {
|
||||
|
||||
typedef struct {
|
||||
uint8 ad_Op1, ad_Op2;
|
||||
uint8 sr_Op1, sr_Op2;
|
||||
uint8 ampMod_Op1, ampMod_Op2;
|
||||
uint8 waveSelect_Op1, waveSelect_Op2;
|
||||
uint8 bindedEffect;
|
||||
uint8 feedBack;
|
||||
uint8 totOutLev_Op1, totOutLev_Op2;
|
||||
uint8 scalingLevel;
|
||||
uint8 pad1, pad2, pad3;
|
||||
} InstrumentStruct;
|
||||
|
||||
typedef struct {
|
||||
uint16 eventDataPtr;
|
||||
int32 nextEventTime;
|
||||
uint16 loopPoint;
|
||||
uint8 adlibChannelNumber;
|
||||
uint8 lastCommand;
|
||||
bool channelActive;
|
||||
uint8 note;
|
||||
uint8 adlibReg1, adlibReg2;
|
||||
InstrumentStruct *instrumentData;
|
||||
uint8 assignedInstrument;
|
||||
uint8 channelVolume;
|
||||
uint8 padding; // field_12 / not used by original driver
|
||||
uint8 tremoVibro;
|
||||
uint8 freqOffset;
|
||||
uint16 frequency;
|
||||
} AdLibChannelType;
|
||||
|
||||
class AdLibChannel : public ChannelBase {
|
||||
public:
|
||||
AdLibChannel (OPL::OPL *opl, uint8 *pMusicData, uint16 startOfData);
|
||||
~AdLibChannel() override;
|
||||
uint8 process(uint16 aktTime) override;
|
||||
void updateVolume(uint16 pVolume) override;
|
||||
bool isActive() override;
|
||||
private:
|
||||
OPL::OPL *_opl;
|
||||
uint8 *_musicData;
|
||||
uint16 _musicVolume;
|
||||
AdLibChannelType _channelData;
|
||||
|
||||
InstrumentStruct *_instruments;
|
||||
uint16 *_frequenceTable;
|
||||
uint8 *_instrumentMap;
|
||||
uint8 *_registerTable, *_opOutputTable;
|
||||
uint8 *_adlibRegMirror;
|
||||
|
||||
// normal subs
|
||||
void setRegister(uint8 regNum, uint8 value);
|
||||
int32 getNextEventTime();
|
||||
uint16 getNextNote(uint8 param);
|
||||
void adlibSetupInstrument();
|
||||
void setupInstrument(uint8 opcode);
|
||||
void setupChannelVolume(uint8 volume);
|
||||
void stopNote();
|
||||
|
||||
// Streamfunctions from Command90hTable
|
||||
void com90_caseNoteOff(); // 0
|
||||
void com90_stopChannel(); // 1
|
||||
void com90_setupInstrument(); // 2
|
||||
uint8 com90_updateTempo(); // 3
|
||||
//void com90_dummy(); // 4
|
||||
void com90_getFreqOffset(); // 5
|
||||
void com90_getChannelVolume(); // 6
|
||||
void com90_getTremoVibro(); // 7
|
||||
void com90_loopMusic(); // 8
|
||||
void com90_keyOff(); // 9
|
||||
//void com90_error(); // 10
|
||||
//void com90_doLodsb(); // 11
|
||||
void com90_setLoopPoint(); // 12
|
||||
//void com90_do_two_Lodsb(); // 13
|
||||
};
|
||||
|
||||
} // End of namespace Sky
|
||||
|
||||
#endif //ADLIBCHANNEL_H
|
||||
98
engines/sky/music/adlibmusic.cpp
Normal file
98
engines/sky/music/adlibmusic.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/* 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 "common/endian.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "sky/music/adlibmusic.h"
|
||||
#include "sky/music/adlibchannel.h"
|
||||
#include "audio/fmopl.h"
|
||||
#include "sky/sky.h"
|
||||
|
||||
namespace Audio {
|
||||
class Mixer;
|
||||
}
|
||||
|
||||
namespace Sky {
|
||||
|
||||
AdLibMusic::AdLibMusic(Audio::Mixer *pMixer, Disk *pDisk) : MusicBase(pMixer, pDisk) {
|
||||
_driverFileBase = 60202;
|
||||
|
||||
_opl = OPL::Config::create();
|
||||
if (!_opl || !_opl->init())
|
||||
error("Failed to create OPL");
|
||||
|
||||
_opl->start(new Common::Functor0Mem<void, AdLibMusic>(this, &AdLibMusic::onTimer), 50);
|
||||
}
|
||||
|
||||
AdLibMusic::~AdLibMusic() {
|
||||
delete _opl;
|
||||
}
|
||||
|
||||
void AdLibMusic::onTimer() {
|
||||
if (_musicData != NULL)
|
||||
pollMusic();
|
||||
}
|
||||
|
||||
void AdLibMusic::setupPointers() {
|
||||
if (SkyEngine::_systemVars->gameVersion == 109) {
|
||||
// disk demo uses a different AdLib driver version, some offsets have changed
|
||||
//_musicDataLoc = (_musicData[0x11CC] << 8) | _musicData[0x11CB];
|
||||
//_initSequence = _musicData + 0xEC8;
|
||||
|
||||
_musicDataLoc = READ_LE_UINT16(_musicData + 0x1200);
|
||||
_initSequence = _musicData + 0xEFB;
|
||||
} else if (SkyEngine::_systemVars->gameVersion == 267) {
|
||||
_musicDataLoc = READ_LE_UINT16(_musicData + 0x11F7);
|
||||
_initSequence = _musicData + 0xE87;
|
||||
} else {
|
||||
_musicDataLoc = READ_LE_UINT16(_musicData + 0x1201);
|
||||
_initSequence = _musicData + 0xE91;
|
||||
}
|
||||
}
|
||||
|
||||
void AdLibMusic::setupChannels(uint8 *channelData) {
|
||||
_numberOfChannels = channelData[0];
|
||||
channelData++;
|
||||
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++) {
|
||||
uint16 chDataStart = READ_LE_UINT16((uint16 *)channelData + cnt) + _musicDataLoc;
|
||||
_channels[cnt] = new AdLibChannel(_opl, _musicData, chDataStart);
|
||||
// also set proper volume - Fixes bug where intro music plays at full volume
|
||||
_channels[cnt]->updateVolume(_musicVolume);
|
||||
}
|
||||
}
|
||||
|
||||
void AdLibMusic::startDriver() {
|
||||
uint16 cnt = 0;
|
||||
while (_initSequence[cnt] || _initSequence[cnt + 1]) {
|
||||
_opl->writeReg(_initSequence[cnt], _initSequence[cnt + 1]);
|
||||
cnt += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void AdLibMusic::setVolume(uint16 param) {
|
||||
_musicVolume = param;
|
||||
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++)
|
||||
_channels[cnt]->updateVolume(_musicVolume);
|
||||
}
|
||||
|
||||
} // End of namespace Sky
|
||||
60
engines/sky/music/adlibmusic.h
Normal file
60
engines/sky/music/adlibmusic.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SKY_MUSIC_ADLIBMUSIC_H
|
||||
#define SKY_MUSIC_ADLIBMUSIC_H
|
||||
|
||||
#include "sky/music/musicbase.h"
|
||||
|
||||
namespace Audio {
|
||||
class Mixer;
|
||||
}
|
||||
|
||||
namespace OPL {
|
||||
class OPL;
|
||||
}
|
||||
|
||||
namespace Sky {
|
||||
|
||||
class AdLibMusic : public MusicBase {
|
||||
public:
|
||||
AdLibMusic(Audio::Mixer *pMixer, Disk *pDisk);
|
||||
~AdLibMusic() override;
|
||||
|
||||
// AudioStream API
|
||||
void setVolume(uint16 param) override;
|
||||
|
||||
private:
|
||||
OPL::OPL *_opl;
|
||||
uint8 *_initSequence;
|
||||
uint32 _sampleRate;
|
||||
void setupPointers() override;
|
||||
void setupChannels(uint8 *channelData) override;
|
||||
void startDriver() override;
|
||||
|
||||
void premixerCall(int16 *buf, uint len);
|
||||
|
||||
void onTimer();
|
||||
};
|
||||
|
||||
} // End of namespace Sky
|
||||
|
||||
#endif //ADLIBMUSIC_H
|
||||
195
engines/sky/music/gmchannel.cpp
Normal file
195
engines/sky/music/gmchannel.cpp
Normal file
@@ -0,0 +1,195 @@
|
||||
/* 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 "gmchannel.h"
|
||||
#include "common/util.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "audio/mididrv.h"
|
||||
|
||||
namespace Sky {
|
||||
|
||||
GmChannel::GmChannel(uint8 *pMusicData, uint16 startOfData, MidiDriver *pMidiDrv, const byte *pInstMap, const byte *veloTab) {
|
||||
_musicData = pMusicData;
|
||||
_midiDrv = pMidiDrv;
|
||||
_channelData.midiChannelNumber = 0;
|
||||
_channelData.loopPoint = startOfData;
|
||||
_channelData.eventDataPtr = startOfData;
|
||||
_channelData.channelActive = true;
|
||||
_channelData.nextEventTime = getNextEventTime();
|
||||
_instMap = pInstMap;
|
||||
_veloTab = veloTab;
|
||||
|
||||
_musicVolume = 0x7F;
|
||||
_currentChannelVolume = 0x7F;
|
||||
}
|
||||
|
||||
GmChannel::~GmChannel() {
|
||||
stopNote();
|
||||
}
|
||||
|
||||
bool GmChannel::isActive() {
|
||||
return _channelData.channelActive;
|
||||
}
|
||||
|
||||
void GmChannel::updateVolume(uint16 pVolume) {
|
||||
_musicVolume = pVolume;
|
||||
if (_musicVolume > 0)
|
||||
_musicVolume = (_musicVolume * 2) / 3 + 43;
|
||||
|
||||
uint8 newVol = (_currentChannelVolume * _musicVolume) >> 7;
|
||||
_midiDrv->send((0xB0 | _channelData.midiChannelNumber) | 0x700 | (newVol << 16));
|
||||
}
|
||||
|
||||
void GmChannel::stopNote() {
|
||||
// All Notes Off
|
||||
_midiDrv->send((0xB0 | _channelData.midiChannelNumber) | 0x7B00 | 0 | 0x79000000);
|
||||
// Reset the Pitch Wheel. See bug #1742.
|
||||
_midiDrv->send((0xE0 | _channelData.midiChannelNumber) | 0x400000);
|
||||
}
|
||||
|
||||
int32 GmChannel::getNextEventTime() {
|
||||
int32 retV = 0;
|
||||
uint8 cnt, lVal = 0;
|
||||
for (cnt = 0; cnt < 4; cnt++) {
|
||||
lVal = _musicData[_channelData.eventDataPtr];
|
||||
_channelData.eventDataPtr++;
|
||||
retV = (retV << 7) | (lVal & 0x7F);
|
||||
if (!(lVal & 0x80))
|
||||
break;
|
||||
}
|
||||
if (lVal & 0x80) { // should never happen
|
||||
return -1;
|
||||
} else
|
||||
return retV;
|
||||
}
|
||||
|
||||
uint8 GmChannel::process(uint16 aktTime) {
|
||||
if (!_channelData.channelActive)
|
||||
return 0;
|
||||
|
||||
uint8 returnVal = 0;
|
||||
|
||||
_channelData.nextEventTime -= aktTime;
|
||||
uint8 opcode;
|
||||
|
||||
while ((_channelData.nextEventTime < 0) && (_channelData.channelActive)) {
|
||||
opcode = _musicData[_channelData.eventDataPtr];
|
||||
_channelData.eventDataPtr++;
|
||||
if (opcode&0x80) {
|
||||
if (opcode == 0xFF) {
|
||||
// dummy opcode
|
||||
} else if (opcode >= 0x90) {
|
||||
switch (opcode&0xF) {
|
||||
case 0: com90_caseNoteOff(); break;
|
||||
case 1: com90_stopChannel(); break;
|
||||
case 2: com90_setupInstrument(); break;
|
||||
case 3:
|
||||
returnVal = com90_updateTempo();
|
||||
break;
|
||||
case 5: com90_getPitch(); break;
|
||||
case 6: com90_getChannelVolume(); break;
|
||||
case 8: com90_loopMusic(); break;
|
||||
case 9: com90_keyOff(); break;
|
||||
case 11: com90_getChannelPanValue(); break;
|
||||
case 12: com90_setLoopPoint(); break;
|
||||
case 13: com90_getChannelControl(); break;
|
||||
|
||||
default:
|
||||
error("GmChannel: Unknown music opcode 0x%02X", opcode);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// new midi channel assignment
|
||||
_channelData.midiChannelNumber = opcode&0xF;
|
||||
}
|
||||
} else {
|
||||
_channelData.note = opcode;
|
||||
byte velocity = _musicData[_channelData.eventDataPtr];
|
||||
if (_veloTab)
|
||||
velocity = _veloTab[velocity];
|
||||
_channelData.eventDataPtr++;
|
||||
_midiDrv->send((0x90 | _channelData.midiChannelNumber) | (opcode << 8) | (velocity << 16));
|
||||
}
|
||||
if (_channelData.channelActive)
|
||||
_channelData.nextEventTime += getNextEventTime();
|
||||
}
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
//- command 90h routines
|
||||
|
||||
void GmChannel::com90_caseNoteOff() {
|
||||
_midiDrv->send((0x90 | _channelData.midiChannelNumber) | (_musicData[_channelData.eventDataPtr] << 8));
|
||||
_channelData.eventDataPtr++;
|
||||
}
|
||||
|
||||
void GmChannel::com90_stopChannel() {
|
||||
stopNote();
|
||||
_channelData.channelActive = false;
|
||||
}
|
||||
|
||||
void GmChannel::com90_setupInstrument() {
|
||||
byte instrument = _musicData[_channelData.eventDataPtr];
|
||||
if (_instMap)
|
||||
instrument = _instMap[instrument];
|
||||
_midiDrv->send((0xC0 | _channelData.midiChannelNumber) | (instrument << 8));
|
||||
_channelData.eventDataPtr++;
|
||||
}
|
||||
|
||||
uint8 GmChannel::com90_updateTempo() {
|
||||
return _musicData[_channelData.eventDataPtr++];
|
||||
}
|
||||
|
||||
void GmChannel::com90_getPitch() {
|
||||
_midiDrv->send((0xE0 | _channelData.midiChannelNumber) | 0 | (_musicData[_channelData.eventDataPtr] << 16));
|
||||
_channelData.eventDataPtr++;
|
||||
}
|
||||
|
||||
void GmChannel::com90_getChannelVolume() {
|
||||
_currentChannelVolume = _musicData[_channelData.eventDataPtr++];
|
||||
uint8 newVol = (uint8)((_currentChannelVolume * _musicVolume) >> 7);
|
||||
_midiDrv->send((0xB0 | _channelData.midiChannelNumber) | 0x700 | (newVol << 16));
|
||||
}
|
||||
|
||||
void GmChannel::com90_loopMusic() {
|
||||
_channelData.eventDataPtr = _channelData.loopPoint;
|
||||
}
|
||||
|
||||
void GmChannel::com90_keyOff() {
|
||||
_midiDrv->send((0x90 | _channelData.midiChannelNumber) | (_channelData.note << 8) | 0);
|
||||
}
|
||||
|
||||
void GmChannel::com90_setLoopPoint() {
|
||||
_channelData.loopPoint = _channelData.eventDataPtr;
|
||||
}
|
||||
|
||||
void GmChannel::com90_getChannelPanValue() {
|
||||
_midiDrv->send((0xB0 | _channelData.midiChannelNumber) | 0x0A00 | (_musicData[_channelData.eventDataPtr] << 16));
|
||||
_channelData.eventDataPtr++;
|
||||
}
|
||||
|
||||
void GmChannel::com90_getChannelControl() {
|
||||
uint8 conNum = _musicData[_channelData.eventDataPtr++];
|
||||
uint8 conDat = _musicData[_channelData.eventDataPtr++];
|
||||
_midiDrv->send((0xB0 | _channelData.midiChannelNumber) | (conNum << 8) | (conDat << 16));
|
||||
}
|
||||
|
||||
} // End of namespace Sky
|
||||
82
engines/sky/music/gmchannel.h
Normal file
82
engines/sky/music/gmchannel.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SKY_MUSIC_GMCHANNEL_H
|
||||
#define SKY_MUSIC_GMCHANNEL_H
|
||||
|
||||
#include "sky/music/musicbase.h"
|
||||
|
||||
class MidiDriver;
|
||||
|
||||
namespace Sky {
|
||||
|
||||
typedef struct {
|
||||
uint16 eventDataPtr;
|
||||
int32 nextEventTime;
|
||||
uint16 loopPoint;
|
||||
uint8 midiChannelNumber;
|
||||
uint8 note;
|
||||
bool channelActive;
|
||||
} MidiChannelType;
|
||||
|
||||
class GmChannel : public ChannelBase {
|
||||
public:
|
||||
GmChannel(uint8 *pMusicData, uint16 startOfData, MidiDriver *pMidiDrv, const byte *pInstMap, const byte *veloTab);
|
||||
~GmChannel() override;
|
||||
virtual void stopNote();
|
||||
uint8 process(uint16 aktTime) override;
|
||||
void updateVolume(uint16 pVolume) override;
|
||||
bool isActive() override;
|
||||
private:
|
||||
const byte *_instMap;
|
||||
const byte *_veloTab;
|
||||
MidiDriver *_midiDrv;
|
||||
uint8 *_musicData;
|
||||
uint16 _musicVolume;
|
||||
MidiChannelType _channelData;
|
||||
uint8 _currentChannelVolume;
|
||||
//- normal subs
|
||||
void setRegister(uint8 regNum, uint8 value);
|
||||
int32 getNextEventTime();
|
||||
uint16 getNextNote(uint8 param);
|
||||
void adlibSetupInstrument();
|
||||
void setupInstrument(uint8 opcode);
|
||||
void setupChannelVolume(uint8 volume);
|
||||
//- Streamfunctions from Command90hTable
|
||||
void com90_caseNoteOff(); // 0
|
||||
void com90_stopChannel(); // 1
|
||||
void com90_setupInstrument(); // 2
|
||||
uint8 com90_updateTempo(); // 3
|
||||
//void com90_dummy(); // 4
|
||||
void com90_getPitch(); // 5
|
||||
void com90_getChannelVolume(); // 6
|
||||
//void com90_skipTremoVibro(); // 7
|
||||
void com90_loopMusic(); // 8
|
||||
void com90_keyOff(); // 9
|
||||
//void com90_error(); // 10
|
||||
void com90_getChannelPanValue(); // 11
|
||||
void com90_setLoopPoint(); // 12
|
||||
void com90_getChannelControl(); // 13
|
||||
};
|
||||
|
||||
} // End of namespace Sky
|
||||
|
||||
#endif //SKYGMCHANNEL_H
|
||||
120
engines/sky/music/gmmusic.cpp
Normal file
120
engines/sky/music/gmmusic.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
/* 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 "sky/music/gmmusic.h"
|
||||
#include "sky/music/gmchannel.h"
|
||||
#include "sky/sky.h"
|
||||
#include "common/util.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "audio/mididrv.h"
|
||||
|
||||
namespace Sky {
|
||||
|
||||
void GmMusic::passTimerFunc(void *param) {
|
||||
((GmMusic *)param)->timerCall();
|
||||
}
|
||||
|
||||
GmMusic::GmMusic(MidiDriver *pMidiDrv, Audio::Mixer *pMixer, Disk *pDisk) : MusicBase(pMixer, pDisk) {
|
||||
_driverFileBase = 60200;
|
||||
_midiDrv = pMidiDrv;
|
||||
int midiRes = _midiDrv->open();
|
||||
if (midiRes != 0)
|
||||
error("Can't open midi device. Errorcode: %d", midiRes);
|
||||
_timerCount = 0;
|
||||
_midiDrv->setTimerCallback(this, passTimerFunc);
|
||||
_midiDrv->sendGMReset();
|
||||
}
|
||||
|
||||
GmMusic::~GmMusic() {
|
||||
_midiDrv->setTimerCallback(NULL, NULL);
|
||||
if (_currentMusic)
|
||||
stopMusic();
|
||||
// Send All Sound Off and All Notes Off (for external synths)
|
||||
for (int i = 0; i < 16; i++) {
|
||||
_midiDrv->send((120 << 8) | 0xB0 | i);
|
||||
_midiDrv->send((123 << 8) | 0xB0 | i);
|
||||
}
|
||||
_midiDrv->close();
|
||||
delete _midiDrv;
|
||||
}
|
||||
|
||||
void GmMusic::setVolume(uint16 param) {
|
||||
_musicVolume = param;
|
||||
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++)
|
||||
_channels[cnt]->updateVolume(_musicVolume);
|
||||
}
|
||||
|
||||
void GmMusic::timerCall() {
|
||||
_timerCount += _midiDrv->getBaseTempo();
|
||||
if (_timerCount > (1000 * 1000 / 50)) {
|
||||
// call pollMusic() 50 times per second
|
||||
_timerCount -= 1000 * 1000 / 50;
|
||||
if (_musicData != NULL)
|
||||
pollMusic();
|
||||
}
|
||||
}
|
||||
|
||||
void GmMusic::setupPointers() {
|
||||
if (SkyEngine::_systemVars->gameVersion == 109) {
|
||||
_musicDataLoc = READ_LE_UINT16(_musicData + 0x79B);
|
||||
_sysExSequence = _musicData + 0x1EF2;
|
||||
} else {
|
||||
_musicDataLoc = READ_LE_UINT16(_musicData + 0x7DC);
|
||||
_sysExSequence = READ_LE_UINT16(_musicData + 0x7E0) + _musicData;
|
||||
}
|
||||
}
|
||||
|
||||
void GmMusic::setupChannels(uint8 *channelData) {
|
||||
_numberOfChannels = channelData[0];
|
||||
channelData++;
|
||||
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++) {
|
||||
uint16 chDataStart = READ_LE_UINT16((uint16 *)channelData + cnt) + _musicDataLoc;
|
||||
_channels[cnt] = new GmChannel(_musicData, chDataStart, _midiDrv, MidiDriver::_mt32ToGm, _veloTab);
|
||||
_channels[cnt]->updateVolume(_musicVolume);
|
||||
}
|
||||
}
|
||||
|
||||
void GmMusic::startDriver() {
|
||||
// Send GM System On to reset channel parameters etc.
|
||||
uint8 sysEx[] = { 0x7e, 0x7f, 0x09, 0x01 };
|
||||
_midiDrv->sysEx(sysEx, sizeof(sysEx));
|
||||
//_midiDrv->send(0xFF); //ALSA can't handle this.
|
||||
// skip all sysEx as it can't be handled anyways.
|
||||
}
|
||||
|
||||
const byte GmMusic::_veloTab[128] = {
|
||||
0x00, 0x40, 0x41, 0x41, 0x42, 0x42, 0x43, 0x43, 0x44, 0x44,
|
||||
0x45, 0x45, 0x46, 0x46, 0x47, 0x47, 0x48, 0x48, 0x49, 0x49,
|
||||
0x4A, 0x4A, 0x4B, 0x4B, 0x4C, 0x4C, 0x4D, 0x4D, 0x4E, 0x4E,
|
||||
0x4F, 0x4F, 0x50, 0x50, 0x51, 0x51, 0x52, 0x52, 0x53, 0x53,
|
||||
0x54, 0x54, 0x55, 0x55, 0x56, 0x56, 0x57, 0x57, 0x58, 0x58,
|
||||
0x59, 0x59, 0x5A, 0x5A, 0x5B, 0x5B, 0x5C, 0x5C, 0x5D, 0x5D,
|
||||
0x5E, 0x5E, 0x5F, 0x5F, 0x60, 0x60, 0x61, 0x61, 0x62, 0x62,
|
||||
0x63, 0x63, 0x64, 0x64, 0x65, 0x65, 0x66, 0x66, 0x67, 0x67,
|
||||
0x68, 0x68, 0x69, 0x69, 0x6A, 0x6A, 0x6B, 0x6B, 0x6C, 0x6C,
|
||||
0x6D, 0x6D, 0x6E, 0x6E, 0x6F, 0x6F, 0x70, 0x70, 0x71, 0x71,
|
||||
0x72, 0x72, 0x73, 0x73, 0x74, 0x74, 0x75, 0x75, 0x76, 0x76,
|
||||
0x77, 0x77, 0x78, 0x78, 0x79, 0x79, 0x7A, 0x7A, 0x7B, 0x7B,
|
||||
0x7C, 0x7C, 0x7D, 0x7D, 0x7E, 0x7E, 0x7F, 0x7F
|
||||
};
|
||||
|
||||
} // End of namespace Sky
|
||||
52
engines/sky/music/gmmusic.h
Normal file
52
engines/sky/music/gmmusic.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SKY_MUSIC_GMMUSIC_H
|
||||
#define SKY_MUSIC_GMMUSIC_H
|
||||
|
||||
#include "sky/music/musicbase.h"
|
||||
|
||||
class MidiDriver;
|
||||
|
||||
namespace Sky {
|
||||
|
||||
class GmMusic : public MusicBase {
|
||||
public:
|
||||
GmMusic(MidiDriver *pMidiDrv, Audio::Mixer *pMixer, Disk *pDisk);
|
||||
~GmMusic() override;
|
||||
void setVolume(uint16 param) override;
|
||||
private:
|
||||
static void passTimerFunc(void *param);
|
||||
void timerCall();
|
||||
|
||||
uint32 _timerCount;
|
||||
uint8 *_sysExSequence;
|
||||
MidiDriver *_midiDrv;
|
||||
static const byte _veloTab[128];
|
||||
|
||||
void setupPointers() override;
|
||||
void setupChannels(uint8 *channelData) override;
|
||||
void startDriver() override;
|
||||
};
|
||||
|
||||
} // End of namespace Sky
|
||||
|
||||
#endif //GMMUSIC_H
|
||||
176
engines/sky/music/mt32music.cpp
Normal file
176
engines/sky/music/mt32music.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
/* 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 "sky/music/mt32music.h"
|
||||
#include "sky/music/gmchannel.h"
|
||||
#include "common/util.h"
|
||||
#include "common/system.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "audio/mididrv.h"
|
||||
|
||||
namespace Sky {
|
||||
|
||||
void MT32Music::passTimerFunc(void *param) {
|
||||
((MT32Music *)param)->timerCall();
|
||||
}
|
||||
|
||||
MT32Music::MT32Music(MidiDriver *pMidiDrv, Audio::Mixer *pMixer, Disk *pDisk) : MusicBase(pMixer, pDisk) {
|
||||
_driverFileBase = 60200;
|
||||
_midiDrv = pMidiDrv;
|
||||
int midiRes = _midiDrv->open();
|
||||
if (midiRes != 0)
|
||||
error("Can't open midi device. Errorcode: %d",midiRes);
|
||||
_timerCount = 0;
|
||||
_midiDrv->setTimerCallback(this, passTimerFunc);
|
||||
_midiDrv->sendMT32Reset();
|
||||
}
|
||||
|
||||
MT32Music::~MT32Music() {
|
||||
_midiDrv->close();
|
||||
_midiDrv->setTimerCallback(NULL, NULL);
|
||||
delete _midiDrv;
|
||||
}
|
||||
|
||||
void MT32Music::timerCall() {
|
||||
_timerCount += _midiDrv->getBaseTempo();
|
||||
if (_timerCount > (1000000 / 50)) {
|
||||
// call pollMusic() 50 times per second
|
||||
_timerCount -= 1000000 / 50;
|
||||
if (_musicData != NULL)
|
||||
pollMusic();
|
||||
}
|
||||
}
|
||||
|
||||
void MT32Music::setVolume(uint16 volume) {
|
||||
uint8 sysEx[10] = "\x41\x10\x16\x12\x10\x00\x16\x00\x00";
|
||||
_musicVolume = volume;
|
||||
sysEx[7] = (volume > 100) ? 100 : (uint8)volume;
|
||||
sysEx[8] = 0x00;
|
||||
for (uint8 cnt = 4; cnt < 8; cnt++)
|
||||
sysEx[8] -= sysEx[cnt];
|
||||
sysEx[8] &= 0x7F;
|
||||
_midiDrv->sysEx(sysEx, 9);
|
||||
}
|
||||
|
||||
void MT32Music::setupPointers() {
|
||||
_musicDataLoc = READ_LE_UINT16(_musicData + 0x7DC);
|
||||
_sysExSequence = READ_LE_UINT16(_musicData + 0x7E0) + _musicData;
|
||||
}
|
||||
|
||||
void MT32Music::setupChannels(uint8 *channelData) {
|
||||
_numberOfChannels = channelData[0];
|
||||
channelData++;
|
||||
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++) {
|
||||
uint16 chDataStart = READ_LE_UINT16((uint16 *)channelData + cnt) + _musicDataLoc;
|
||||
_channels[cnt] = new GmChannel(_musicData, chDataStart, _midiDrv, NULL, NULL);
|
||||
_channels[cnt]->updateVolume(_musicVolume);
|
||||
}
|
||||
}
|
||||
|
||||
bool MT32Music::processPatchSysEx(uint8 *sysExData) {
|
||||
uint8 sysExBuf[15];
|
||||
uint8 crc = 0;
|
||||
if (sysExData[0] & 0x80)
|
||||
return false;
|
||||
|
||||
// decompress data from stream
|
||||
sysExBuf[ 0] = 0x41;
|
||||
sysExBuf[ 1] = 0x10;
|
||||
sysExBuf[ 2] = 0x16;
|
||||
sysExBuf[ 3] = 0x12;
|
||||
sysExBuf[ 4] = 0x5;
|
||||
sysExBuf[ 5] = sysExData[0] >> 4; // patch offset part 1
|
||||
sysExBuf[ 6] = (sysExData[0] & 0xF) << 3; // patch offset part 2
|
||||
sysExBuf[ 7] = sysExData[1] >> 6; // timbre group
|
||||
sysExBuf[ 8] = sysExData[1] & 0x3F; // timbre num
|
||||
sysExBuf[ 9] = sysExData[2] & 0x3F; // key shift
|
||||
sysExBuf[10] = sysExData[3] & 0x7F; // fine tune
|
||||
sysExBuf[11] = sysExData[4] & 0x7F; // bender range
|
||||
sysExBuf[12] = sysExData[2] >> 6; // assign mode
|
||||
sysExBuf[13] = sysExData[3] >> 7; // reverb switch
|
||||
for (uint8 cnt = 4; cnt < 14; cnt++)
|
||||
crc -= sysExBuf[cnt];
|
||||
sysExBuf[14] = crc & 0x7F; // crc
|
||||
_midiDrv->sysEx(sysExBuf, 15);
|
||||
// We delay the time it takes to send the sysEx plus an
|
||||
// additional 40ms, which is required for MT-32 rev00,
|
||||
// to assure no buffer overflow or missing bytes
|
||||
g_system->delayMillis(17 * 1000 / 3125 + 40);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MT32Music::startDriver() {
|
||||
// setup timbres and patches using SysEx data
|
||||
uint8* sysExData = _sysExSequence;
|
||||
uint8 timbreNum = sysExData[0];
|
||||
uint8 cnt, crc;
|
||||
sysExData++;
|
||||
uint8 sendBuf[256];
|
||||
uint8 len;
|
||||
sendBuf[0] = 0x41;
|
||||
sendBuf[1] = 0x10;
|
||||
sendBuf[2] = 0x16;
|
||||
sendBuf[3] = 0x12;
|
||||
for (cnt = 0; cnt < timbreNum; cnt++) {
|
||||
len = 7;
|
||||
crc = 0;
|
||||
// Timbre address
|
||||
sendBuf[4] = 0x8 | (sysExData[0] >> 6);
|
||||
sendBuf[5] = (sysExData[0] & 0x3F) << 1;
|
||||
sendBuf[6] = 0xA;
|
||||
sysExData++;
|
||||
crc -= sendBuf[4] + sendBuf[5] + sendBuf[6];
|
||||
uint8 dataLen = sysExData[0];
|
||||
sysExData++;
|
||||
// Timbre data:
|
||||
do {
|
||||
uint8 rlVal = 1;
|
||||
uint8 codeVal = sysExData[0];
|
||||
sysExData++;
|
||||
|
||||
if (codeVal & 0x80) {
|
||||
codeVal &= 0x7F;
|
||||
rlVal = sysExData[0];
|
||||
sysExData++;
|
||||
dataLen--;
|
||||
}
|
||||
for (uint8 cnt2 = 0; cnt2 < rlVal; cnt2++) {
|
||||
sendBuf[len] = codeVal;
|
||||
len++;
|
||||
crc -= codeVal;
|
||||
}
|
||||
dataLen--;
|
||||
} while (dataLen > 0);
|
||||
sendBuf[len] = crc & 0x7F;
|
||||
len++;
|
||||
_midiDrv->sysEx(sendBuf, len);
|
||||
// We delay the time it takes to send the sysEx plus an
|
||||
// additional 40ms, which is required for MT-32 rev00,
|
||||
// to assure no buffer overflow or missing bytes
|
||||
g_system->delayMillis((len + 2) * 1000 / 3125 + 40);
|
||||
}
|
||||
|
||||
while (processPatchSysEx(sysExData))
|
||||
sysExData += 5;
|
||||
}
|
||||
|
||||
} // End of namespace Sky
|
||||
52
engines/sky/music/mt32music.h
Normal file
52
engines/sky/music/mt32music.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SKY_MUSIC_MT32MUSIC_H
|
||||
#define SKY_MUSIC_MT32MUSIC_H
|
||||
|
||||
#include "sky/music/musicbase.h"
|
||||
|
||||
class MidiDriver;
|
||||
|
||||
namespace Sky {
|
||||
|
||||
class MT32Music : public MusicBase {
|
||||
public:
|
||||
MT32Music(MidiDriver *pMidiDrv, Audio::Mixer *pMixer, Disk *pDisk);
|
||||
~MT32Music() override;
|
||||
private:
|
||||
static void passTimerFunc(void *param);
|
||||
void timerCall();
|
||||
bool processPatchSysEx(uint8 *sysExData);
|
||||
void setVolume(uint16 volume) override;
|
||||
|
||||
uint32 _timerCount;
|
||||
uint8 *_sysExSequence;
|
||||
MidiDriver *_midiDrv;
|
||||
|
||||
void setupPointers() override;
|
||||
void setupChannels(uint8 *channelData) override;
|
||||
void startDriver() override;
|
||||
};
|
||||
|
||||
} // End of namespace Sky
|
||||
|
||||
#endif //MT32MUSIC_H
|
||||
197
engines/sky/music/musicbase.cpp
Normal file
197
engines/sky/music/musicbase.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
/* 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 "sky/music/musicbase.h"
|
||||
#include "sky/disk.h"
|
||||
#include "common/util.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "audio/audiostream.h"
|
||||
|
||||
namespace Sky {
|
||||
|
||||
MusicBase::MusicBase(Audio::Mixer *pMixer, Disk *pDisk) {
|
||||
_musicData = NULL;
|
||||
_mixer = pMixer;
|
||||
_skyDisk = pDisk;
|
||||
_currentMusic = 0;
|
||||
_musicVolume = 127;
|
||||
_numberOfChannels = _currentMusic = 0;
|
||||
}
|
||||
|
||||
MusicBase::~MusicBase() {
|
||||
stopMusic();
|
||||
free(_musicData);
|
||||
}
|
||||
|
||||
void MusicBase::loadSection(uint8 pSection) {
|
||||
if (_currentMusic)
|
||||
stopMusicInternal();
|
||||
Common::StackLock lock(_mutex);
|
||||
free(_musicData);
|
||||
_currentSection = pSection;
|
||||
_musicData = _skyDisk->loadFile(_driverFileBase + FILES_PER_SECTION * pSection);
|
||||
|
||||
_musicTempo0 = 0x78; // init constants taken from idb file, area ~0x1060
|
||||
_musicTempo1 = 0xC0;
|
||||
_onNextPoll.musicToProcess = 0;
|
||||
_onNextPoll.stream = nullptr;
|
||||
_tempo = _aktTime = 0x10001;
|
||||
_numberOfChannels = _currentMusic = 0;
|
||||
setupPointers();
|
||||
startDriver();
|
||||
}
|
||||
|
||||
bool MusicBase::musicIsPlaying() {
|
||||
if (_mixer->isSoundHandleActive(_musicHandle))
|
||||
return true;
|
||||
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++)
|
||||
if (_channels[cnt]->isActive())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void MusicBase::stopMusic() {
|
||||
stopMusicInternal();
|
||||
}
|
||||
|
||||
void MusicBase::stopMusicInternal() {
|
||||
_mixer->stopHandle(_musicHandle);
|
||||
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++)
|
||||
delete _channels[cnt];
|
||||
_numberOfChannels = 0;
|
||||
}
|
||||
|
||||
void MusicBase::updateTempo() {
|
||||
uint16 tempoMul = _musicTempo0 * _musicTempo1;
|
||||
uint16 divisor = 0x4446390/ 23864;
|
||||
_tempo = (tempoMul / divisor) << 16;
|
||||
_tempo |= (((tempoMul % divisor) << 16) | (tempoMul / divisor)) / divisor;
|
||||
}
|
||||
|
||||
void MusicBase::loadNewMusic() {
|
||||
uint16 musicPos;
|
||||
if (_onNextPoll.musicToProcess > _musicData[_musicDataLoc]) {
|
||||
error("Music %d requested but doesn't exist in file.", _onNextPoll.musicToProcess);
|
||||
return;
|
||||
}
|
||||
if (_currentMusic != 0)
|
||||
stopMusicInternal();
|
||||
|
||||
_currentMusic = _onNextPoll.musicToProcess;
|
||||
|
||||
if (_currentMusic == 0)
|
||||
return;
|
||||
|
||||
// Try playing digital audio first (from the Music Enhancement Project).
|
||||
// TODO: This always prefers digital music over the MIDI music types!
|
||||
Audio::SeekableAudioStream *stream = _onNextPoll.stream;
|
||||
if (stream) {
|
||||
_onNextPoll.stream = nullptr;
|
||||
|
||||
// not all tracks should loop
|
||||
bool loops = true;
|
||||
uint8 section = _currentSection;
|
||||
uint8 song = _currentMusic;
|
||||
if ((section == 0 && song == 1)
|
||||
|| (section == 1 && song == 1) || (section == 1 && song == 4)
|
||||
|| (section == 2 && song == 1) || (section == 2 && song == 4)
|
||||
|| (section == 4 && song == 2) || (section == 4 && song == 3)
|
||||
|| (section == 4 && song == 5) || (section == 4 && song == 6)
|
||||
|| (section == 4 && song == 11) || (section == 5 && song == 1)
|
||||
|| (section == 5 && song == 3) || (section == 5 && song == 4))
|
||||
loops = false;
|
||||
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, Audio::makeLoopingAudioStream(stream, loops ? 0 : 1));
|
||||
return;
|
||||
}
|
||||
|
||||
// no digital audio, resort to MIDI playback
|
||||
musicPos = READ_LE_UINT16(_musicData + _musicDataLoc + 1);
|
||||
musicPos += _musicDataLoc + ((_currentMusic - 1) << 1);
|
||||
musicPos = READ_LE_UINT16(_musicData + musicPos) + _musicDataLoc;
|
||||
|
||||
_musicTempo0 = _musicData[musicPos];
|
||||
_musicTempo1 = _musicData[musicPos+1];
|
||||
|
||||
setupChannels(_musicData + musicPos + 2);
|
||||
|
||||
updateTempo();
|
||||
}
|
||||
|
||||
void MusicBase::pollMusic() {
|
||||
Common::StackLock lock(_mutex);
|
||||
uint8 newTempo;
|
||||
if (_onNextPoll.musicToProcess != _currentMusic)
|
||||
loadNewMusic();
|
||||
|
||||
_aktTime += _tempo;
|
||||
|
||||
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++) {
|
||||
newTempo = _channels[cnt]->process((uint16)(_aktTime >> 16));
|
||||
if (newTempo) {
|
||||
_musicTempo1 = newTempo;
|
||||
updateTempo();
|
||||
}
|
||||
}
|
||||
_aktTime &= 0xFFFF;
|
||||
}
|
||||
|
||||
void MusicBase::startMusic(uint16 param) {
|
||||
uint8 song = param & 0xF;
|
||||
|
||||
_onNextPoll.musicToProcess = song;
|
||||
delete _onNextPoll.stream;
|
||||
_onNextPoll.stream = nullptr;
|
||||
|
||||
if (song == 0)
|
||||
return;
|
||||
|
||||
// Load digital audio now if available
|
||||
// This avoids doing it in the audio thread
|
||||
uint8 section = _currentSection;
|
||||
// handle duplicates
|
||||
if ((section == 2 && song == 1) || (section == 5 && song == 1)) {
|
||||
section = 1;
|
||||
song = 1;
|
||||
} else if ((section == 2 && song == 4) || (section == 5 && song == 4)) {
|
||||
section = 1;
|
||||
song = 4;
|
||||
} else if (section == 5 && song == 6) {
|
||||
section = 4;
|
||||
song = 4;
|
||||
}
|
||||
|
||||
Common::Path trackName(Common::String::format("music_%d%02d", section, song));
|
||||
_onNextPoll.stream = Audio::SeekableAudioStream::openStreamFile(trackName);
|
||||
}
|
||||
|
||||
uint8 MusicBase::giveVolume() {
|
||||
return (uint8)_musicVolume;
|
||||
}
|
||||
|
||||
uint8 MusicBase::giveCurrentMusic() {
|
||||
return _currentMusic;
|
||||
}
|
||||
|
||||
} // End of namespace Sky
|
||||
100
engines/sky/music/musicbase.h
Normal file
100
engines/sky/music/musicbase.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SKY_MUSIC_MUSICBASE_H
|
||||
#define SKY_MUSIC_MUSICBASE_H
|
||||
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/mutex.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
|
||||
namespace Audio {
|
||||
class SeekableAudioStream;
|
||||
}
|
||||
|
||||
namespace Sky {
|
||||
|
||||
class Disk;
|
||||
|
||||
#define FILES_PER_SECTION 4
|
||||
|
||||
typedef struct {
|
||||
uint8 musicToProcess;
|
||||
Audio::SeekableAudioStream *stream;
|
||||
} Actions;
|
||||
|
||||
class ChannelBase {
|
||||
public:
|
||||
virtual ~ChannelBase() {}
|
||||
virtual uint8 process(uint16 aktTime) = 0;
|
||||
virtual void updateVolume(uint16 pVolume) = 0;
|
||||
virtual bool isActive() = 0;
|
||||
private:
|
||||
};
|
||||
|
||||
class MusicBase {
|
||||
public:
|
||||
MusicBase(Audio::Mixer *pMixer, Disk *pDisk);
|
||||
virtual ~MusicBase();
|
||||
void loadSection(uint8 pSection);
|
||||
void startMusic(uint16 param);
|
||||
void stopMusic();
|
||||
bool musicIsPlaying();
|
||||
uint8 giveVolume();
|
||||
uint8 giveCurrentMusic();
|
||||
virtual void setVolume(uint16 param) = 0;
|
||||
|
||||
protected:
|
||||
|
||||
Audio::Mixer *_mixer;
|
||||
Disk *_skyDisk;
|
||||
uint8 *_musicData;
|
||||
|
||||
uint16 _musicDataLoc;
|
||||
uint16 _driverFileBase;
|
||||
|
||||
uint16 _musicVolume, _numberOfChannels;
|
||||
uint8 _currentMusic, _currentSection;
|
||||
uint8 _musicTempo0; // can be changed by music stream
|
||||
uint8 _musicTempo1; // given once per music
|
||||
uint32 _tempo; // calculated from musicTempo0 and musicTempo1
|
||||
uint32 _aktTime;
|
||||
Actions _onNextPoll;
|
||||
ChannelBase *_channels[10];
|
||||
Common::Mutex _mutex;
|
||||
Audio::SoundHandle _musicHandle;
|
||||
|
||||
virtual void setupPointers() = 0;
|
||||
virtual void setupChannels(uint8 *channelData) = 0;
|
||||
virtual void startDriver() = 0;
|
||||
|
||||
void updateTempo();
|
||||
void loadNewMusic();
|
||||
void pollMusic();
|
||||
|
||||
void stopMusicInternal();
|
||||
};
|
||||
|
||||
} // End of namespace Sky
|
||||
|
||||
#endif //MUSICBASE_H
|
||||
Reference in New Issue
Block a user