Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
/* 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 "audio/mididrv.h"
#include "common/debug.h"
#include "common/scummsys.h"
#include "common/textconsole.h"
#include "zvision/detection.h"
#include "zvision/sound/midi.h"
namespace ZVision {
MidiManager::MidiManager() {
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB);
_driver = MidiDriver::createMidi(dev);
if (_driver->open()) {
warning("Can't open MIDI, no MIDI output!");
_available = false;
} else {
Common::String driverName = MidiDriver::getDeviceString(dev, MidiDriver::DeviceStringType::kDriverName);
Common::String deviceName = MidiDriver::getDeviceString(dev, MidiDriver::DeviceStringType::kDeviceName);
_mt32 = MidiDriver::getMusicType(dev) == MT_MT32;
debugC(1, kDebugSound, "MIDI opened, driver type: %s, device name: %s", driverName.c_str(), deviceName.c_str());
_available = true;
_maxChannels = _driver->MIDI_CHANNEL_COUNT;
}
}
MidiManager::~MidiManager() {
stop();
_driver->close();
delete _driver;
}
void MidiManager::send(uint8 status, uint8 data1, uint8 data2) {
assert(status & 0x80 && "Malformed MIDI status byte");
assert(!(data1 & 0x80) && "Malformed MIDI data byte 1");
assert(!(data2 & 0x80) && "Malformed MIDI data byte 2");
_driver->send(status | (data1 << 8) | (data2 << 16));
}
void MidiManager::stop() {
for (uint8 i = 0; i < 16; i++)
noteOff(i);
}
void MidiManager::noteOn(uint8 channel, uint8 note, uint8 velocity) {
assert(channel <= 15);
_activeChannels[channel].playing = true;
_activeChannels[channel].note = note;
send(0x90 | channel, note, velocity);
debugC(1, kDebugSound, "MIDI note on, channel %d, note %d, velocity %d", channel, note, velocity);
}
void MidiManager::noteOff(uint8 channel) {
assert(channel <= 15);
if (_activeChannels[channel].playing) {
_activeChannels[channel].playing = false;
send(0x80 | channel, _activeChannels[channel].note);
}
}
int8 MidiManager::getFreeChannel() {
uint8 start = _mt32 ? 1 : 0; // MT-32 can be used for MIDI, but does not play anything on MIDI channel 0
for (uint8 i = start; i < 16; i++)
if (!_activeChannels[i].playing)
return i;
return -1;
}
void MidiManager::setVolume(uint8 channel, uint8 volume) {
assert(channel <= 15);
debugC(1, kDebugSound, "MIDI volume out %d", volume >> 1);
send(0xB0 | channel, 0x07, volume >> 1);
}
void MidiManager::setBalance(uint8 channel, int8 balance) {
assert(channel <= 15);
uint8 _balance = (uint8)(balance + 128);
debugC(1, kDebugSound, "MIDI balance out %d", _balance >> 1);
send(0xB0 | channel, 0x08, _balance >> 1);
}
void MidiManager::setPan(uint8 channel, int8 pan) {
assert(channel <= 15);
uint8 _pan = (uint8)(pan + 128);
debugC(1, kDebugSound, "MIDI pan in %d, out %d", pan, _pan >> 1);
send(0xB0 | channel, 0x0A, _pan >> 1);
}
void MidiManager::setProgram(uint8 channel, uint8 prog) {
assert(channel <= 15);
send(0xC0 | channel, prog);
}
} // End of namespace ZVision

View File

@@ -0,0 +1,65 @@
/* 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 ZVISION_MIDI_H
#define ZVISION_MIDI_H
class MidiDriver;
namespace ZVision {
class MidiManager {
public:
MidiManager();
~MidiManager();
void stop();
void noteOn(uint8 channel, uint8 noteNumber, uint8 velocity);
void noteOff(uint8 channel);
void setVolume(uint8 channel, uint8 volume);
void setBalance(uint8 channel, int8 balance);
void setPan(uint8 channel, int8 pan);
void setProgram(uint8 channel, uint8 prog);
int8 getFreeChannel(); // Negative if none available
bool isAvailable() const {
return _available;
}
protected:
bool _available = false;
bool _mt32 = false;
struct chan {
bool playing;
uint8 note;
chan() : playing(false), note(0) {}
};
void send(uint8 status, uint8 data1 = 0x00, uint8 data2 = 0x00);
uint8 _startChannel = 0;
uint8 _maxChannels = 16;
MidiDriver *_driver;
chan _activeChannels[16];
};
}
#endif

View File

@@ -0,0 +1,291 @@
/* 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/debug.h"
#include "zvision/detection.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/sound/volume_manager.h"
namespace ZVision {
// Power law with exponent 1.5.
static constexpr uint8 powerLaw[256] = {
0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4,
4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11,
11, 12, 12, 13, 14, 14, 15, 15, 16, 16, 17, 18, 18, 19, 20, 20,
21, 21, 22, 23, 23, 24, 25, 26, 26, 27, 28, 28, 29, 30, 31, 31,
32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40, 41, 41, 42, 43, 44,
45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57, 58,
59, 60, 61, 62, 63, 64, 65, 65, 66, 67, 68, 69, 70, 71, 72, 73,
74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 88, 89, 90,
91, 92, 93, 94, 95, 96, 97, 98, 99,100,102,103,104,105,106,107,
108,109,110,112,113,114,115,116,117,119,120,121,122,123,124,126,
127,128,129,130,132,133,134,135,136,138,139,140,141,142,144,145,
146,147,149,150,151,152,154,155,156,158,159,160,161,163,164,165,
167,168,169,171,172,173,174,176,177,178,180,181,182,184,185,187,
188,189,191,192,193,195,196,197,199,200,202,203,204,206,207,209,
210,211,213,214,216,217,218,220,221,223,224,226,227,228,230,231,
233,234,236,237,239,240,242,243,245,246,248,249,251,252,254,255
};
static constexpr uint8 logPower[256] = {
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, 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, 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, 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, 0, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8,
8, 9, 9, 10, 10, 11, 11, 12, 13, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 26, 27, 29, 30, 32, 34, 36, 38, 40, 42, 45,
47, 50, 52, 55, 58, 62, 65, 69, 73, 77, 81, 86, 90, 96,101,107,
113,119,126,133,140,148,156,165,174,184,194,205,217,229,241,255
};
// */
static constexpr uint8 logAmplitude[256] = {
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, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5,
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8,
8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 12, 12,
2, 13, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, 18, 18, 19,
19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27, 28, 29,
30, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 45,
46, 47, 48, 50, 51, 52, 54, 55, 57, 58, 60, 62, 63, 65, 67, 69,
71, 73, 75, 77, 79, 81, 83, 86, 88, 90, 93, 96, 98,101,104,107,
110,113,116,119,122,126,129,133,136,140,144,148,152,156,160,165,
169,174,179,184,189,194,200,205,211,217,222,229,235,241,248,255
};
/*/
// Old system; this is wrong, caused bug #7176; cloister fountain (value 50 in-game, 127/255) inaudible
// Using linear volume served as a temporary fix, but causes other sounds not to play at correct amplitudes (e.g. beehive, door singing in ZGI)
static constexpr uint8 logAmplitude[256] = {
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, 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, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,
4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 14,
14, 15, 15, 16, 16, 17, 18, 18, 19, 20, 21, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 36, 37, 38, 40, 41, 43, 45,
46, 48, 50, 52, 53, 55, 57, 60, 62, 64, 67, 69, 72, 74, 77, 80,
83, 86, 89, 92, 96, 99,103,107,111,115,119,123,128,133,137,143,
148,153,159,165,171,177,184,191,198,205,212,220,228,237,245,255
};
// */
/*
Estimated relative amplitude of a point sound source as it circles the listener's head from front to rear, due to ear pinna shape.
Maximum attenuation -5dB when fully to rear. Seems to give a reasonably realistic effect when tested on the Nemesis cloister fountain.
Should be applied AFTER volume profile is applied to script files.
Generating function:
for 0 < theta < 90, amp = 255;
for 90 < theta < 180, amp = 255*10^(1-(cos(2*(theta-90))/4))
where theta is the azimuth, in degrees, of the sound source relative to straight ahead of listener
Source: Own work; crude and naive model that is probably not remotely scientifically accurate, but good enough for a 30-year-old game.
*/
static constexpr uint8 directionalAmplitude[181] = {
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,254,254,253,
252,251,249,248,246,245,243,241,238,236,234,231,228,226,223,220,
217,214,211,208,204,201,198,195,191,188,185,181,178,175,171,168,
165,162,158,155,152,149,146,143,141,138,135,132,130,127,125,122,
120,118,116,113,111,109,108,106,104,102,101, 99, 98, 96, 95, 93,
92, 91, 90, 89, 88, 87, 86, 85, 85, 84, 83, 83, 82, 82, 82, 81,
81, 81, 81, 81, 81
};
VolumeManager::VolumeManager(ZVision *engine, volumeScaling mode) :
_mode(mode) {
}
uint8 VolumeManager::convert(uint8 inputValue) {
return convert(inputValue, _mode);
}
uint8 VolumeManager::convert(uint8 inputValue, Math::Angle azimuth, uint8 directionality) {
return convert(inputValue, _mode, azimuth, directionality);
}
uint8 VolumeManager::convert(uint8 inputValue, volumeScaling &mode, Math::Angle azimuth, uint8 directionality) {
uint8 index = abs(round(azimuth.getDegrees(-180)));
uint32 output = convert(inputValue, mode);
uint32 directionalOutput = (output * directionalAmplitude[index]) * directionality;
directionalOutput /= 0xFF;
output *= (0xFF - directionality);
output = (output + directionalOutput) / 0xFF;
debugC(4, kDebugSound, "Directionally converted output %d", output);
return output;
}
uint8 VolumeManager::convert(uint8 inputValue, volumeScaling &mode) {
if (inputValue > _scriptScale)
inputValue = _scriptScale;
uint32 scaledInput = inputValue * 0xFF;
scaledInput /= _scriptScale;
uint8 output = 0;
switch (mode) {
case kVolumeLogPower:
output = logPower[scaledInput];
break;
case kVolumeLogAmplitude:
output = logAmplitude[scaledInput];
break;
case kVolumePowerLaw:
output = powerLaw[scaledInput];
break;
case kVolumeParabolic:
scaledInput *= scaledInput;
output = scaledInput / 0xFF;
break;
case kVolumeCubic:
scaledInput *= scaledInput * scaledInput;
output = scaledInput / 0xFE01;
break;
case kVolumeQuartic:
scaledInput *= scaledInput;
scaledInput *= scaledInput;
output = scaledInput / 0xFD02FF;
break;
case kVolumeLinear:
default:
output = scaledInput;
break;
}
debugC(4, kDebugSound, "Scripted volume %d, scaled volume %d, converted output %d", inputValue, scaledInput, output);
return output;
}
#if defined(USE_MPEG2) && defined(USE_A52)
double VolumeManager::getVobAmplification(Common::String fileName) const {
// For some reason, we get much lower volume in the hi-res videos than
// in the low-res ones. So we artificially boost the volume. This is an
// approximation, but I've tried to match the old volumes reasonably
// well.
//
// Some of these will cause audio clipping. Hopefully not enough to be
// noticeable.
double amplification = 0.0;
if (fileName == "em00d011.vob") {
// The finale.
amplification = 10.0;
} else if (fileName == "em00d021.vob") {
// Jack's escape and arrival at Flathead Mesa.
amplification = 9.0;
} else if (fileName == "em00d032.vob") {
// The Grand Inquisitor's speech.
amplification = 11.0;
} else if (fileName == "em00d122.vob") {
// Jack orders you to the radio tower.
amplification = 17.0;
} else if (fileName == "em3ed012.vob") {
// The Grand Inquisitor gets the Coconut of Quendor.
amplification = 12.0;
} else if (fileName == "g000d101.vob") {
// Griff gets captured.
amplification = 11.0;
} else if (fileName == "g000d111.vob") {
// Brog gets totemized. The music seems to be mixed much softer
// in this than in the low-resolution version.
amplification = 12.0;
} else if (fileName == "g000d122.vob") {
// Lucy gets captured.
amplification = 14.0;
} else if (fileName == "g000d302.vob") {
// The Grand Inquisitor visits Jack in his cell.
amplification = 13.0;
} else if (fileName == "g000d312.vob") {
// You get captured.
amplification = 14.0;
} else if (fileName == "g000d411.vob") {
// Propaganda On Parade. No need to make it as loud as the
// low-resolution version.
amplification = 11.0;
} else if (fileName == "pe1ed012.vob") {
// Jack lets you in with the lantern.
amplification = 14.0;
} else if (fileName.hasPrefix("pe1ed")) {
// Jack answers the door. Several different ways.
amplification = 17.0;
} else if (fileName == "pe5ed052.vob") {
// You get killed by the guards
amplification = 12.0;
} else if (fileName == "pe6ed012.vob") {
// Jack gets captured by the guards
amplification = 17.0;
} else if (fileName == "pp1ed022.vob") {
// Jack examines the lantern
amplification = 10.0;
} else if (fileName == "qb1ed012.vob") {
// Lucy gets invited to the back room
amplification = 17.0;
} else if (fileName.hasPrefix("qe1ed")) {
// Floyd answers the door. Several different ways.
amplification = 17.0;
} else if (fileName == "qs1ed011.vob") {
// Jack explains the rules of the game.
amplification = 16.0;
} else if (fileName == "qs1ed021.vob") {
// Jack loses the game.
amplification = 14.0;
} else if (fileName == "uc1gd012.vob") {
// Y'Gael appears.
amplification = 12.0;
} else if (fileName == "ue1ud012.vob") {
// Jack gets totemized... or what?
amplification = 12.0;
} else if (fileName == "ue2qd012.vob") {
// Jack agrees to totemization.
amplification = 10.0;
} else if (fileName == "g000d981.vob") {
// The Enterprise logo. Has no low-res version. Its volume is
// louder than the other logo animations.
amplification = 6.2;
} else if (fileName.hasPrefix("g000d")) {
// The Dolby Digital and Activision logos. They have no low-res
// versions, but I've used the low-resolution Activision logo
// (slightly different) as reference.
amplification = 8.5;
}
return amplification;
}
#endif
} // End of namespace ZVision

View File

@@ -0,0 +1,66 @@
/* 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/scummsys.h"
#include "math/angle.h"
#include "zvision/zvision.h"
#ifndef ZVISION_VOLUME_MANAGER
#define ZVISION_VOLUME_MANAGER
namespace ZVision {
enum volumeScaling {
kVolumeLinear,
kVolumePowerLaw,
kVolumeParabolic,
kVolumeCubic,
kVolumeQuartic,
kVolumeLogPower,
kVolumeLogAmplitude
};
class VolumeManager {
public:
VolumeManager(ZVision *engine, volumeScaling mode);
~VolumeManager() {};
volumeScaling getMode() const {
return _mode;
}
void setMode(volumeScaling mode) {
_mode = mode;
}
uint8 convert(uint8 inputValue);
uint8 convert(uint8 inputValue, volumeScaling &mode);
uint8 convert(uint8 inputValue, Math::Angle azimuth, uint8 directionality = 255);
uint8 convert(uint8 inputValue, volumeScaling &mode, Math::Angle azimuth, uint8 directionality = 255);
#if defined(USE_MPEG2) && defined(USE_A52)
double getVobAmplification(Common::String fileName) const;
#endif
private:
uint _scriptScale = 100; // Z-Vision scripts internally use a volume scale of 0-100; ScummVM uses a scale of 0-255.
volumeScaling _mode = kVolumeLinear;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,278 @@
/* 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 "audio/audiostream.h"
#include "audio/decoders/raw.h"
#include "common/bufferedstream.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/tokenizer.h"
#include "common/util.h"
#include "zvision/zvision.h"
#include "zvision/file/file_manager.h"
#include "zvision/sound/zork_raw.h"
namespace ZVision {
const int16 RawChunkStream::_stepAdjustmentTable[8] = { -1, -1, -1, 1, 4, 7, 10, 12};
const int32 RawChunkStream::_amplitudeLookupTable[89] = {
0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E,
0x0010, 0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001C, 0x001F,
0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042,
0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F,
0x009D, 0x00AD, 0x00BE, 0x00D1, 0x00E6, 0x00FD, 0x0117, 0x0133,
0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292,
0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583,
0x0610, 0x06AB, 0x0756, 0x0812, 0x08E0, 0x09C3, 0x0ABD, 0x0BD0,
0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954,
0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B,
0x3BB9, 0x41B2, 0x4844, 0x4F7E, 0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF
};
RawChunkStream::RawChunkStream(bool stereo) {
if (stereo)
_stereo = 1;
else
_stereo = 0;
init();
}
void RawChunkStream::init() {
_lastSample[0].index = 0;
_lastSample[0].sample = 0;
_lastSample[1].index = 0;
_lastSample[1].sample = 0;
}
RawChunkStream::RawChunk RawChunkStream::readNextChunk(Common::SeekableReadStream *stream) {
RawChunk tmp;
tmp.size = 0;
tmp.data = NULL;
if (!stream || stream->size() == 0 || stream->eos())
return tmp;
tmp.size = (stream->size() - stream->pos()) * 2;
tmp.data = (int16 *)calloc(tmp.size, 1);
readBuffer(tmp.data, stream, stream->size() - stream->pos());
return tmp;
}
int RawChunkStream::readBuffer(int16 *buffer, Common::SeekableReadStream *stream, const int numSamples) {
int32 bytesRead = 0;
// 0: Left, 1: Right
uint channel = 0;
while (bytesRead < numSamples) {
byte encodedSample = stream->readByte();
if (stream->eos()) {
return bytesRead;
}
bytesRead++;
int16 index = _lastSample[channel].index;
uint32 lookUpSample = _amplitudeLookupTable[index];
int32 sample = 0;
if (encodedSample & 0x40)
sample += lookUpSample;
if (encodedSample & 0x20)
sample += lookUpSample >> 1;
if (encodedSample & 0x10)
sample += lookUpSample >> 2;
if (encodedSample & 8)
sample += lookUpSample >> 3;
if (encodedSample & 4)
sample += lookUpSample >> 4;
if (encodedSample & 2)
sample += lookUpSample >> 5;
if (encodedSample & 1)
sample += lookUpSample >> 6;
if (encodedSample & 0x80)
sample = -sample;
sample += _lastSample[channel].sample;
sample = CLIP<int32>(sample, -32768, 32767);
buffer[bytesRead - 1] = (int16)sample;
index += _stepAdjustmentTable[(encodedSample >> 4) & 7];
index = CLIP<int16>(index, 0, 88);
_lastSample[channel].sample = sample;
_lastSample[channel].index = index;
// Increment and wrap the channel
channel = (channel + 1) & _stereo;
}
return bytesRead;
}
const SoundParams RawZorkStream::_zNemSoundParamLookupTable[32] = {
{'0', 0x1F40, false, false, false},
{'1', 0x1F40, true, false, false},
{'2', 0x1F40, false, false, true},
{'3', 0x1F40, true, false, true},
{'4', 0x2B11, false, false, false},
{'5', 0x2B11, true, false, false},
{'6', 0x2B11, false, false, true},
{'7', 0x2B11, true, false, true},
{'8', 0x5622, false, false, false},
{'9', 0x5622, true, false, false},
{'a', 0x5622, false, false, true},
{'b', 0x5622, true, false, true},
{'c', 0xAC44, false, false, false},
{'d', 0xAC44, true, false, false},
{'e', 0xAC44, false, false, true},
{'f', 0xAC44, true, false, true},
{'g', 0x1F40, false, true, false},
{'h', 0x1F40, true, true, false},
{'j', 0x1F40, false, true, true},
{'k', 0x1F40, true, true, true},
{'l', 0x2B11, false, true, false},
{'m', 0x2B11, true, true, false},
{'n', 0x2B11, false, true, true},
{'p', 0x2B11, true, true, true},
{'q', 0x5622, false, true, false},
{'r', 0x5622, true, true, false},
{'s', 0x5622, false, true, true},
{'t', 0x5622, true, true, true},
{'u', 0xAC44, false, true, false},
{'v', 0xAC44, true, true, false},
{'w', 0xAC44, false, true, true},
{'x', 0xAC44, true, true, true}
};
const SoundParams RawZorkStream::_zgiSoundParamLookupTable[24] = {
{'4', 0x2B11, false, false, false},
{'5', 0x2B11, true, false, false},
{'6', 0x2B11, false, false, true},
{'7', 0x2B11, true, false, true},
{'8', 0x5622, false, false, false},
{'9', 0x5622, true, false, false},
{'a', 0x5622, false, false, true},
{'b', 0x5622, true, false, true},
{'c', 0xAC44, false, false, false},
{'d', 0xAC44, true, false, false},
{'e', 0xAC44, false, false, true},
{'f', 0xAC44, true, false, true},
{'g', 0x2B11, false, true, false},
{'h', 0x2B11, true, true, false},
{'j', 0x2B11, false, true, true},
{'k', 0x2B11, true, true, true},
{'m', 0x5622, false, true, false},
{'n', 0x5622, true, true, false},
{'p', 0x5622, false, true, true},
{'q', 0x5622, true, true, true},
{'r', 0xAC44, false, true, false},
{'s', 0xAC44, true, true, false},
{'t', 0xAC44, false, true, true},
{'u', 0xAC44, true, true, true}
};
RawZorkStream::RawZorkStream(uint32 rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream)
: _rate(rate),
_stereo(0),
_stream(stream, disposeStream),
_endOfData(false),
_streamReader(stereo) {
if (stereo)
_stereo = 1;
// Calculate the total playtime of the stream
if (stereo)
_playtime = Audio::Timestamp(0, _stream->size() / 2, rate);
else
_playtime = Audio::Timestamp(0, _stream->size(), rate);
}
int RawZorkStream::readBuffer(int16 *buffer, const int numSamples) {
int32 bytesRead = _streamReader.readBuffer(buffer, _stream.get(), numSamples);
if (_stream->eos())
_endOfData = true;
return bytesRead;
}
bool RawZorkStream::rewind() {
_stream->seek(0, 0);
_stream->clearErr();
_endOfData = false;
_streamReader.init();
return true;
}
Audio::RewindableAudioStream *makeRawZorkStream(Common::SeekableReadStream *stream,
int rate,
bool stereo,
DisposeAfterUse::Flag disposeAfterUse) {
if (stereo)
assert(stream->size() % 2 == 0);
return new RawZorkStream(rate, stereo, disposeAfterUse, stream);
}
Audio::RewindableAudioStream *makeRawZorkStream(const Common::Path &filePath, ZVision *engine) {
Common::String baseName = filePath.baseName();
Common::File *file = engine->getFileManager()->open(filePath);
const SoundParams *soundParams = NULL;
if (engine->getGameId() == GID_NEMESIS) {
for (int i = 0; i < 32; ++i) {
if (RawZorkStream::_zNemSoundParamLookupTable[i].identifier == (baseName[6]))
soundParams = &RawZorkStream::_zNemSoundParamLookupTable[i];
}
} else if (engine->getGameId() == GID_GRANDINQUISITOR) {
for (int i = 0; i < 24; ++i) {
if (RawZorkStream::_zgiSoundParamLookupTable[i].identifier == (baseName[7]))
soundParams = &RawZorkStream::_zgiSoundParamLookupTable[i];
}
}
if (soundParams == NULL)
return NULL;
if (soundParams->packed) {
return makeRawZorkStream(wrapBufferedSeekableReadStream(file, 2048, DisposeAfterUse::YES), soundParams->rate, soundParams->stereo, DisposeAfterUse::YES);
} else {
byte flags = 0;
if (soundParams->bits16)
flags |= Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
if (soundParams->stereo)
flags |= Audio::FLAG_STEREO;
return Audio::makeRawStream(file, soundParams->rate, flags, DisposeAfterUse::YES);
}
}
} // End of namespace ZVision

View File

@@ -0,0 +1,141 @@
/* 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 ZVISION_ZORK_RAW_H
#define ZVISION_ZORK_RAW_H
#include "audio/audiostream.h"
namespace Common {
class SeekableReadStream;
}
namespace ZVision {
class ZVision;
struct SoundParams {
char identifier;
uint32 rate;
bool stereo;
bool packed;
bool bits16;
};
/**
* This is a ADPCM stream-reader, this class holds context for multi-chunk reading and no buffers.
*/
class RawChunkStream {
public:
RawChunkStream(bool stereo);
~RawChunkStream() {
}
private:
uint _stereo;
/**
* Holds the frequency and index from the last sample
* 0 holds the left channel, 1 holds the right channel
*/
struct {
int32 sample;
int16 index;
} _lastSample[2];
static const int16 _stepAdjustmentTable[8];
static const int32 _amplitudeLookupTable[89];
public:
struct RawChunk {
int16 *data;
uint32 size;
};
void init();
//Read next audio portion in new stream (needed for avi), return structure with buffer
RawChunk readNextChunk(Common::SeekableReadStream *stream);
//Read numSamples from stream to buffer
int readBuffer(int16 *buffer, Common::SeekableReadStream *stream, const int numSamples);
};
/**
* This is a stream, which allows for playing raw ADPCM data from a stream.
*/
class RawZorkStream : public Audio::RewindableAudioStream {
public:
RawZorkStream(uint32 rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream);
~RawZorkStream() override {
}
public:
static const SoundParams _zNemSoundParamLookupTable[32];
static const SoundParams _zgiSoundParamLookupTable[24];
private:
const int _rate; // Sample rate of stream
Audio::Timestamp _playtime; // Calculated total play time
Common::DisposablePtr<Common::SeekableReadStream> _stream; // Stream to read data from
bool _endOfData; // Whether the stream end has been reached
uint _stereo;
RawChunkStream _streamReader;
public:
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override {
return _stereo;
}
bool endOfData() const override {
return _endOfData;
}
int getRate() const override {
return _rate;
}
Audio::Timestamp getLength() const {
return _playtime;
}
bool rewind() override;
};
/**
* Creates an audio stream, which plays from the given stream.
*
* @param stream Stream object to play from.
* @param rate Rate of the sound data.
* @param dispose AfterUse Whether to delete the stream after use.
* @return The new SeekableAudioStream (or 0 on failure).
*/
Audio::RewindableAudioStream *makeRawZorkStream(Common::SeekableReadStream *stream,
int rate,
bool stereo,
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
Audio::RewindableAudioStream *makeRawZorkStream(const Common::Path &filePath, ZVision *engine);
} // End of namespace ZVision
#endif