Initial commit
This commit is contained in:
340
engines/twine/audio/music.cpp
Normal file
340
engines/twine/audio/music.cpp
Normal file
@@ -0,0 +1,340 @@
|
||||
/* 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 "twine/audio/music.h"
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/midiparser.h"
|
||||
#include "audio/midiplayer.h"
|
||||
#include "backends/audiocd/audiocd.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "twine/resources/hqr.h"
|
||||
#include "twine/resources/resources.h"
|
||||
#include "twine/twine.h"
|
||||
|
||||
namespace TwinE {
|
||||
|
||||
/**
|
||||
* LBA1 default number of tracks
|
||||
* <pre>
|
||||
* TRACK 01 MODE1/2352
|
||||
* INDEX 01 00:00:00
|
||||
* TRACK 02 AUDIO
|
||||
* INDEX 01 10:47:52
|
||||
* TRACK 03 AUDIO
|
||||
* INDEX 01 14:02:01
|
||||
* TRACK 04 AUDIO
|
||||
* INDEX 01 17:02:19
|
||||
* TRACK 05 AUDIO
|
||||
* INDEX 01 19:34:45
|
||||
* TRACK 06 AUDIO
|
||||
* INDEX 01 22:22:34
|
||||
* TRACK 07 AUDIO
|
||||
* INDEX 01 25:09:32
|
||||
* TRACK 08 AUDIO
|
||||
* INDEX 01 26:47:72
|
||||
* TRACK 09 AUDIO
|
||||
* INDEX 01 30:29:07
|
||||
* TRACK 10 AUDIO
|
||||
* INDEX 01 32:04:62
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
TwinEMidiPlayer::TwinEMidiPlayer(TwinEEngine *engine) : _engine(engine) {
|
||||
MidiPlayer::createDriver();
|
||||
|
||||
int ret = _driver->open();
|
||||
if (ret == 0) {
|
||||
if (_nativeMT32) {
|
||||
_driver->sendMT32Reset();
|
||||
} else {
|
||||
_driver->sendGMReset();
|
||||
}
|
||||
_driver->setTimerCallback(this, &timerCallback);
|
||||
}
|
||||
}
|
||||
|
||||
void TwinEMidiPlayer::play(byte *buf, int size, bool loop) {
|
||||
if (_parser == nullptr) {
|
||||
if (_engine->_cfgfile.MidiType == MIDIFILE_DOS) {
|
||||
_parser = MidiParser::createParser_XMIDI();
|
||||
} else {
|
||||
_parser = MidiParser::createParser_SMF();
|
||||
}
|
||||
}
|
||||
|
||||
if (!_parser->loadMusic(buf, size)) {
|
||||
warning("Failed to load midi music");
|
||||
return;
|
||||
}
|
||||
_parser->setTrack(0);
|
||||
_parser->setMidiDriver(this);
|
||||
_parser->setTimerRate(_driver->getBaseTempo());
|
||||
_parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
|
||||
|
||||
syncVolume();
|
||||
debug("play midi with volume: %i", getVolume());
|
||||
|
||||
_isLooping = loop;
|
||||
_isPlaying = true;
|
||||
}
|
||||
|
||||
Music::Music(TwinEEngine *engine) : _engine(engine), _midiPlayer(engine) {
|
||||
}
|
||||
|
||||
int32 Music::getLengthTrackCDR(int track) const {
|
||||
// TODO:
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool Music::playMidi(int32 midiIdx) {
|
||||
const int32 loop = 1;
|
||||
if (_engine->isDotEmuEnhanced() || _engine->isLba1Classic()) {
|
||||
// the midi tracks are stored in the lba1-xx files and the adeline logo jingle
|
||||
// is in lba1-32.xx - while the midiIdx is 31
|
||||
const int32 trackOffset = 1;
|
||||
Common::Path trackName(Common::String::format("lba1-%02i", midiIdx + trackOffset));
|
||||
Audio::SeekableAudioStream *stream = Audio::SeekableAudioStream::openStreamFile(trackName);
|
||||
if (stream != nullptr) {
|
||||
const int volume = _engine->_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
|
||||
_engine->_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, &_midiHandle,
|
||||
Audio::makeLoopingAudioStream(stream, loop), volume);
|
||||
debug("Play midi music track %i", midiIdx);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const char *filename;
|
||||
if (_engine->_cfgfile.MidiType == MIDIFILE_DOS) {
|
||||
filename = Resources::HQR_MIDI_MI_DOS_FILE;
|
||||
} else if (_engine->_cfgfile.MidiType == MIDIFILE_WIN) {
|
||||
filename = Resources::HQR_MIDI_MI_WIN_FILE;
|
||||
} else {
|
||||
debug("midi disabled - skip playing %i", midiIdx);
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 midiSize = HQR::getAllocEntry(&midiPtr, filename, midiIdx);
|
||||
if (midiSize == 0) {
|
||||
debug("Could not find midi file for index %i", midiIdx);
|
||||
return false;
|
||||
}
|
||||
debug("Play midi file for index %i", midiIdx);
|
||||
_midiPlayer.play(midiPtr, midiSize, loop == 0 || loop > 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Music::playTrackCDR(int32 track) {
|
||||
if (_engine->isLBA2()) {
|
||||
static const char *musicTracksLba2[] = {
|
||||
"",
|
||||
"TADPCM1",
|
||||
"TADPCM2",
|
||||
"TADPCM3",
|
||||
"TADPCM4",
|
||||
"TADPCM5",
|
||||
"JADPCM01",
|
||||
"", // Track6.wav
|
||||
"JADPCM02",
|
||||
"JADPCM03",
|
||||
"JADPCM04",
|
||||
"JADPCM05",
|
||||
"JADPCM06",
|
||||
"JADPCM07",
|
||||
"JADPCM08",
|
||||
"JADPCM09",
|
||||
"JADPCM10",
|
||||
"JADPCM11",
|
||||
"JADPCM12",
|
||||
"JADPCM13",
|
||||
"JADPCM14",
|
||||
"JADPCM15",
|
||||
"JADPCM16",
|
||||
"JADPCM17",
|
||||
"JADPCM18",
|
||||
"LOGADPCM"};
|
||||
|
||||
const char *basename = musicTracksLba2[track];
|
||||
Audio::SeekableAudioStream *stream = Audio::SeekableAudioStream::openStreamFile(basename);
|
||||
if (stream != nullptr) {
|
||||
const int volume = _engine->_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
|
||||
_engine->_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, &_midiHandle,
|
||||
Audio::makeLoopingAudioStream(stream, 1), volume);
|
||||
debug(3, "Play audio track %s for track id %i", basename, track);
|
||||
return true;
|
||||
}
|
||||
debug(3, "Failed to find a supported format for audio track: %s", basename);
|
||||
// TODO: are there versions with real audio cd?
|
||||
// us release starting at track 0
|
||||
// other releases at track 6
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioCDManager *cdrom = g_system->getAudioCDManager();
|
||||
int offset = 1;
|
||||
// usually the tracks are starting at track_02.xxx for gog and steam releases.
|
||||
// But some users might have ripped the original cd audio tracks and named them
|
||||
// from track_01.xxx onwards. So we need to check if the first track exists.
|
||||
//
|
||||
// see https://bugs.scummvm.org/ticket/15410 and https://bugs.scummvm.org/ticket/14669
|
||||
if (cdrom->existExtractedCDAudioFiles(1)) {
|
||||
offset = 0;
|
||||
}
|
||||
return cdrom->play(track + offset, 1, 0, 0);
|
||||
}
|
||||
|
||||
bool Music::initCdrom() {
|
||||
AudioCDManager *cdrom = g_system->getAudioCDManager();
|
||||
return cdrom->open();
|
||||
}
|
||||
|
||||
void Music::stopMusic() {
|
||||
stopMusicCD();
|
||||
stopMusicMidi();
|
||||
}
|
||||
|
||||
void Music::stopMusicCD() {
|
||||
AudioCDManager *cdrom = g_system->getAudioCDManager();
|
||||
cdrom->stop();
|
||||
}
|
||||
|
||||
void Music::fadeMusicMidi(uint32 time) {
|
||||
// TODO implement fade out
|
||||
stopMusicMidi();
|
||||
}
|
||||
|
||||
void Music::stopMusicMidi() {
|
||||
if (_engine->isDotEmuEnhanced() || _engine->isLba1Classic() || _engine->isLBA2()) {
|
||||
_engine->_system->getMixer()->stopHandle(_midiHandle);
|
||||
}
|
||||
|
||||
_midiPlayer.stop();
|
||||
free(midiPtr);
|
||||
midiPtr = nullptr;
|
||||
numXmi = -1;
|
||||
}
|
||||
|
||||
bool Music::playMusic(int32 track) {
|
||||
if (track == -1) {
|
||||
stopMusic();
|
||||
return true;
|
||||
}
|
||||
if (!_engine->_cfgfile.Sound) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_engine->isCDROM()) {
|
||||
if (_flagVoiceCD || track < 1 || track > 9) {
|
||||
if (playMidiFile(track)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (playCdTrack(track)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (playMidiFile(track)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
warning("Failed to play track %i", track);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Music::playMidiFile(int32 midiIdx) {
|
||||
if (!_engine->_cfgfile.Sound) {
|
||||
debug("sound disabled - skip playing %i", midiIdx);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_engine->isCDROM()) {
|
||||
stopMusicCD();
|
||||
}
|
||||
|
||||
if (midiIdx != numXmi || !isMidiPlaying()) {
|
||||
stopMusicMidi();
|
||||
numXmi = midiIdx;
|
||||
if (!playMidi(midiIdx)) {
|
||||
return false;
|
||||
}
|
||||
// volumeMidi(100);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int32 Music::getMusicCD() {
|
||||
AudioCDManager *cdrom = g_system->getAudioCDManager();
|
||||
// if (_engine->_system->getMillis() > endMusicCD) {
|
||||
if (!cdrom->isPlaying()) {
|
||||
currentMusicCD = -1;
|
||||
}
|
||||
return currentMusicCD;
|
||||
}
|
||||
|
||||
bool Music::playCdTrack(int32 track) {
|
||||
fadeMusicMidi(1);
|
||||
numXmi = -1;
|
||||
|
||||
if (track != getMusicCD()) {
|
||||
stopMusicCD();
|
||||
// TODO: endMusicCD = _engine->toSeconds(getLengthTrackCDR(track)) / 75 + _engine->toSeconds(1);
|
||||
if (playTrackCDR(track)) {
|
||||
// TODO: endMusicCD += _engine->_system->getMillis();
|
||||
debug("Play cd music track %i", track);
|
||||
currentMusicCD = track;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Music::playAllMusic(int num) {
|
||||
if (num != numXmi || !isMidiPlaying()) {
|
||||
stopMusicMidi();
|
||||
numXmi = num;
|
||||
playMidi(num);
|
||||
// volumeMidi(100);
|
||||
}
|
||||
if (num != getMusicCD()) {
|
||||
stopMusicCD();
|
||||
// TODO: endMusicCD = _engine->toSeconds(getLengthTrackCDR(num)) / 75 + _engine->toSeconds(1);
|
||||
if (playTrackCDR(num)) {
|
||||
// TODO: endMusicCD += _engine->_system->getMillis();
|
||||
currentMusicCD = num;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Music::isMidiPlaying() const {
|
||||
if (_engine->isDotEmuEnhanced() || _engine->isLba1Classic()) {
|
||||
return _engine->_system->getMixer()->isSoundHandleActive(_midiHandle);
|
||||
}
|
||||
|
||||
return _midiPlayer.isPlaying();
|
||||
}
|
||||
|
||||
void Music::musicVolume(int32 volume) {
|
||||
_engine->_system->getMixer()->setVolumeForSoundType(Audio::Mixer::SoundType::kMusicSoundType, volume);
|
||||
_midiPlayer.setVolume(volume);
|
||||
}
|
||||
|
||||
} // namespace TwinE
|
||||
110
engines/twine/audio/music.h
Normal file
110
engines/twine/audio/music.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 TWINE_MUSIC_H
|
||||
#define TWINE_MUSIC_H
|
||||
|
||||
#include "audio/midiplayer.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "common/scummsys.h"
|
||||
|
||||
namespace TwinE {
|
||||
|
||||
class TwinEEngine;
|
||||
|
||||
class TwinEMidiPlayer : public Audio::MidiPlayer {
|
||||
private:
|
||||
TwinEEngine *_engine;
|
||||
public:
|
||||
TwinEMidiPlayer(TwinEEngine *engine);
|
||||
void play(byte *buf, int size, bool loop);
|
||||
};
|
||||
|
||||
class Music {
|
||||
private:
|
||||
TwinEEngine *_engine;
|
||||
TwinEMidiPlayer _midiPlayer;
|
||||
|
||||
void fadeMusicMidi(uint32 time = 1);
|
||||
|
||||
/** Auxiliar midi pointer to */
|
||||
uint8 *midiPtr = nullptr;
|
||||
Audio::SoundHandle _midiHandle;
|
||||
/** Track number of the current playing music */
|
||||
int32 numXmi = -1;
|
||||
int32 currentMusicCD = -1;
|
||||
// int32 endMusicCD = -1;
|
||||
const bool _flagVoiceCD = false;
|
||||
public:
|
||||
// TODO: implement the handling
|
||||
int32 _nextMusic = -1; // lba2: NextMusic
|
||||
int32 _nextMusicTimer; // lba2: NextMusicTimer
|
||||
bool _stopLastMusic = false; // lba2: StopLastMusic
|
||||
private:
|
||||
/** Stop CD music */
|
||||
void stopMusicCD();
|
||||
bool playMidi(int32 midiIdx);
|
||||
int32 getLengthTrackCDR(int track) const;
|
||||
bool playTrackCDR(int32 track);
|
||||
public:
|
||||
Music(TwinEEngine *engine);
|
||||
|
||||
/**
|
||||
* Music volume
|
||||
* @param current volume number
|
||||
*/
|
||||
void musicVolume(int32 volume);
|
||||
|
||||
/**
|
||||
* Play CD music
|
||||
* @param track track number to play
|
||||
*/
|
||||
bool playCdTrack(int32 track);
|
||||
/**
|
||||
* Generic play music, according with settings it plays CD or high quality sounds instead
|
||||
* @param track track number to play
|
||||
*/
|
||||
bool playMusic(int32 track);
|
||||
/**
|
||||
* Play MIDI music
|
||||
* @param midiIdx music index under mini_mi_win.hqr
|
||||
* @note valid indices for lba1 are [1-32]
|
||||
*/
|
||||
bool playMidiFile(int32 midiIdx);
|
||||
|
||||
void playAllMusic(int track);
|
||||
|
||||
/** Stop MIDI music */
|
||||
void stopMusicMidi();
|
||||
|
||||
/** Initialize CD-Rom */
|
||||
bool initCdrom();
|
||||
|
||||
/** Stop MIDI and Track music */
|
||||
void stopMusic();
|
||||
|
||||
bool isMidiPlaying() const;
|
||||
int32 getMusicCD();
|
||||
};
|
||||
|
||||
} // namespace TwinE
|
||||
|
||||
#endif
|
||||
319
engines/twine/audio/sound.cpp
Normal file
319
engines/twine/audio/sound.cpp
Normal file
@@ -0,0 +1,319 @@
|
||||
/* 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 "twine/audio/sound.h"
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
#include "audio/decoders/voc.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/system.h"
|
||||
#include "common/text-to-speech.h"
|
||||
#include "common/types.h"
|
||||
#include "common/util.h"
|
||||
#include "twine/parser/text.h"
|
||||
#include "twine/scene/collision.h"
|
||||
#include "twine/movies.h"
|
||||
#include "twine/scene/grid.h"
|
||||
#include "twine/resources/hqr.h"
|
||||
#include "twine/scene/movements.h"
|
||||
#include "twine/resources/resources.h"
|
||||
#include "twine/shared.h"
|
||||
#include "twine/text.h"
|
||||
#include "twine/twine.h"
|
||||
|
||||
namespace TwinE {
|
||||
|
||||
Sound::Sound(TwinEEngine *engine) : _engine(engine) {
|
||||
}
|
||||
|
||||
Sound::~Sound() {
|
||||
_engine->_system->getMixer()->stopAll();
|
||||
}
|
||||
|
||||
void Sound::startRainSample() {
|
||||
if (!_engine->_cfgfile.Sound) {
|
||||
return;
|
||||
}
|
||||
#if 0
|
||||
const int sample = SAMPLE_RAIN;
|
||||
if (CubeMode == CUBE_EXTERIEUR && !TEMPETE_FINIE && !isSamplePlaying(sample)) {
|
||||
const int rate = 0x1000;
|
||||
const int offset = 300;
|
||||
const int repeat = 0;
|
||||
const int panning = 64;
|
||||
const int volumeRain = 70;
|
||||
// TODO: mixSample(sample, rate, offset, repeat, panning, volumeRain);
|
||||
}
|
||||
|
||||
RestartRainSample = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Sound::setChannelRate(int32 channelIdx, uint32 rate) {
|
||||
if (channelIdx < 0 || channelIdx >= NUM_CHANNELS) {
|
||||
return;
|
||||
}
|
||||
_engine->_system->getMixer()->setChannelRate(_samplesPlaying[channelIdx], rate);
|
||||
}
|
||||
|
||||
void Sound::setChannelBalance(int32 channelIdx, uint8 volumeLeft, uint8 volumeRight) {
|
||||
if (channelIdx < 0 || channelIdx >= NUM_CHANNELS) {
|
||||
return;
|
||||
}
|
||||
Audio::Mixer *mixer = _engine->_system->getMixer();
|
||||
const Audio::SoundHandle &handle = _samplesPlaying[channelIdx];
|
||||
// balance value ranges from -127 to 127
|
||||
int8 balance = (int8)(((int16)volumeRight - (int16)volumeLeft) * 127 / 255);
|
||||
mixer->setChannelBalance(handle, balance);
|
||||
}
|
||||
|
||||
void Sound::setChannelPosition(int32 channelIdx, int32 x, int32 y, int32 z) {
|
||||
if (channelIdx < 0 || channelIdx >= NUM_CHANNELS) {
|
||||
return;
|
||||
}
|
||||
const int32 camX = _engine->_grid->_startCube.x * SIZE_BRICK_XZ;
|
||||
const int32 camY = _engine->_grid->_startCube.y * SIZE_BRICK_Y;
|
||||
const int32 camZ = _engine->_grid->_startCube.z * SIZE_BRICK_XZ;
|
||||
int32 distance = getDistance3D(camX, camY, camZ, x, y, z);
|
||||
distance = boundRuleThree(0, distance, 10000, 255);
|
||||
byte targetVolume = 0;
|
||||
if (distance < Audio::Mixer::kMaxChannelVolume) {
|
||||
targetVolume = Audio::Mixer::kMaxChannelVolume - distance;
|
||||
}
|
||||
_engine->_system->getMixer()->setChannelVolume(_samplesPlaying[channelIdx], targetVolume);
|
||||
}
|
||||
|
||||
void Sound::playFlaSample(int32 index, int16 rate, int32 repeat, uint8 volumeLeft, uint8 volumeRight) {
|
||||
if (!_engine->_cfgfile.Sound) {
|
||||
return;
|
||||
}
|
||||
|
||||
int channelIdx = getFreeSampleChannelIndex();
|
||||
if (channelIdx == -1) {
|
||||
warning("Failed to play fla sample for index: %i - no free channel", index);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8 *sampPtr = nullptr;
|
||||
const int32 sampSize = HQR::getAllocEntry(&sampPtr, Resources::HQR_FLASAMP_FILE, index);
|
||||
if (sampSize == 0) {
|
||||
warning("Failed to load %s", Resources::HQR_FLASAMP_FILE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix incorrect sample files first byte
|
||||
if (*sampPtr != 'C') {
|
||||
_engine->_text->_hasHiddenVox = *sampPtr != '\0';
|
||||
_engine->_text->_voxHiddenIndex++;
|
||||
*sampPtr = 'C';
|
||||
}
|
||||
|
||||
Common::MemoryReadStream *stream = new Common::MemoryReadStream(sampPtr, sampSize, DisposeAfterUse::YES);
|
||||
Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
|
||||
if (playSample(channelIdx, index, audioStream, repeat, Resources::HQR_FLASAMP_FILE)) {
|
||||
setChannelRate(channelIdx, rate);
|
||||
setChannelBalance(channelIdx, volumeLeft, volumeRight);
|
||||
}
|
||||
}
|
||||
|
||||
void Sound::mixSample(int32 index, uint16 pitchbend, int32 repeat, uint8 volumeLeft, uint8 volumeRight) {
|
||||
mixSample3D(index, pitchbend, repeat, {}, -1);
|
||||
int channelIdx = getSampleChannel(index);
|
||||
setChannelBalance(channelIdx, volumeLeft, volumeRight);
|
||||
}
|
||||
|
||||
void Sound::mixSample3D(int32 index, uint16 pitchbend, int32 repeat, const IVec3 &pos, int32 actorIdx) {
|
||||
if (!_engine->_cfgfile.Sound) {
|
||||
return;
|
||||
}
|
||||
|
||||
int channelIdx = getFreeSampleChannelIndex();
|
||||
if (channelIdx == -1) {
|
||||
warning("Failed to play sample for index: %i - no free channel", index);
|
||||
return;
|
||||
}
|
||||
|
||||
if (actorIdx != -1) {
|
||||
// TODO: implement balance
|
||||
setChannelPosition(channelIdx, pos.x, pos.y, pos.z);
|
||||
// save the actor index for the channel so we can check the position
|
||||
_samplesPlayingActors[channelIdx] = actorIdx;
|
||||
} else {
|
||||
_samplesPlayingActors[channelIdx] = -1;
|
||||
}
|
||||
|
||||
uint8 *sampPtr = _engine->_resources->_samplesTable[index];
|
||||
uint32 sampSize = _engine->_resources->_samplesSizeTable[index];
|
||||
Common::MemoryReadStream *stream = new Common::MemoryReadStream(sampPtr, sampSize, DisposeAfterUse::NO);
|
||||
Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
|
||||
playSample(channelIdx, index, audioStream, repeat, Resources::HQR_SAMPLES_FILE, Audio::Mixer::kSFXSoundType);
|
||||
uint32 rate = 11025 + (pitchbend - 0x1000);
|
||||
setChannelRate(channelIdx, rate);
|
||||
}
|
||||
|
||||
bool Sound::playVoxSample(const TextEntry *text) {
|
||||
if (!_engine->_cfgfile.Sound || text == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int channelIdx = getFreeSampleChannelIndex();
|
||||
if (channelIdx == -1) {
|
||||
warning("Failed to play vox sample for index: %i - no free channel", text->index);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_engine->isAndroid()) {
|
||||
Common::Path basename(Common::String::format("%s%03i", _engine->_text->_currentOggBaseFile.c_str(), text->index));
|
||||
Audio::SeekableAudioStream *audioStream = Audio::SeekableAudioStream::openStreamFile(basename);
|
||||
if (audioStream != nullptr) {
|
||||
return playSample(channelIdx, text->index, audioStream, 1, _engine->_text->_currentOggBaseFile.c_str(), Audio::Mixer::kSpeechSoundType);
|
||||
}
|
||||
}
|
||||
|
||||
uint8 *sampPtr = nullptr;
|
||||
int32 sampSize = HQR::getAllocVoxEntry(&sampPtr, _engine->_text->_currentVoxBankFile.c_str(), text->index, _engine->_text->_voxHiddenIndex);
|
||||
if (sampSize == 0) {
|
||||
if (ConfMan.hasKey("tts_narrator") && ConfMan.getBool("tts_narrator")) {
|
||||
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
|
||||
if (ttsMan != nullptr) {
|
||||
ttsMan->stop();
|
||||
return ttsMan->say(text->string);
|
||||
}
|
||||
} else {
|
||||
debug(4, "TTS disabled");
|
||||
}
|
||||
warning("Failed to get vox sample for index: %i", text->index);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fix incorrect sample files first byte
|
||||
if (*sampPtr != 'C') {
|
||||
_engine->_text->_hasHiddenVox = *sampPtr != '\0';
|
||||
_engine->_text->_voxHiddenIndex++;
|
||||
*sampPtr = 'C';
|
||||
}
|
||||
Common::MemoryReadStream *stream = new Common::MemoryReadStream(sampPtr, sampSize, DisposeAfterUse::YES);
|
||||
Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
|
||||
return playSample(channelIdx, text->index, audioStream, 1, _engine->_text->_currentVoxBankFile.c_str(), Audio::Mixer::kSpeechSoundType);
|
||||
}
|
||||
|
||||
bool Sound::playSample(int32 channelIdx, int32 index, Audio::SeekableAudioStream *audioStream, int32 loop, const char *name, Audio::Mixer::SoundType soundType) {
|
||||
if (audioStream == nullptr) {
|
||||
warning("Failed to create audio stream for %s: %i", name, index);
|
||||
return false;
|
||||
}
|
||||
|
||||
// infinite loop
|
||||
if (loop == -1) {
|
||||
loop = 0;
|
||||
}
|
||||
Audio::AudioStream *loopStream = Audio::makeLoopingAudioStream(audioStream, loop);
|
||||
Audio::SoundHandle *handle = &_samplesPlaying[channelIdx];
|
||||
const byte volume = Audio::Mixer::kMaxChannelVolume;
|
||||
_engine->_system->getMixer()->playStream(soundType, handle, loopStream, index, volume);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sound::resumeSamples() {
|
||||
if (!_engine->_cfgfile.Sound) {
|
||||
return;
|
||||
}
|
||||
_engine->_system->getMixer()->pauseAll(false);
|
||||
}
|
||||
|
||||
void Sound::pauseSamples() {
|
||||
if (!_engine->_cfgfile.Sound) {
|
||||
return;
|
||||
}
|
||||
_engine->_system->getMixer()->pauseAll(true);
|
||||
}
|
||||
|
||||
void Sound::stopSamples() { // HQ_StopSample
|
||||
if (!_engine->_cfgfile.Sound) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int channelIdx = 0; channelIdx < NUM_CHANNELS; channelIdx++) {
|
||||
_engine->_system->getMixer()->stopHandle(_samplesPlaying[channelIdx]);
|
||||
}
|
||||
memset(_samplesPlayingActors, -1, sizeof(_samplesPlayingActors));
|
||||
}
|
||||
|
||||
int32 Sound::getActorChannel(int32 actorIdx) {
|
||||
for (int32 channelIdx = 0; channelIdx < NUM_CHANNELS; channelIdx++) {
|
||||
if (_samplesPlayingActors[channelIdx] == actorIdx) {
|
||||
return channelIdx;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32 Sound::getSampleChannel(int32 index) {
|
||||
for (int32 channelIdx = 0; channelIdx < NUM_CHANNELS; channelIdx++) {
|
||||
if (_engine->_system->getMixer()->getSoundID(_samplesPlaying[channelIdx]) == index) {
|
||||
return channelIdx;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Sound::removeChannelWatch(int32 channelIdx) {
|
||||
_samplesPlayingActors[channelIdx] = -1;
|
||||
}
|
||||
|
||||
void Sound::stopSample(int32 index) {
|
||||
if (!_engine->_cfgfile.Sound) {
|
||||
return;
|
||||
}
|
||||
const int32 channelIdx = getSampleChannel(index);
|
||||
if (channelIdx != -1) {
|
||||
_engine->_system->getMixer()->stopID(index);
|
||||
removeChannelWatch(channelIdx);
|
||||
}
|
||||
}
|
||||
|
||||
bool Sound::isChannelPlaying(int32 channelIdx) {
|
||||
if (channelIdx >= 0 && channelIdx < ARRAYSIZE(_samplesPlaying)) {
|
||||
if (_engine->_system->getMixer()->isSoundHandleActive(_samplesPlaying[channelIdx])) {
|
||||
return true;
|
||||
}
|
||||
removeChannelWatch(channelIdx);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 Sound::isSamplePlaying(int32 index) {
|
||||
const int32 channelIdx = getSampleChannel(index);
|
||||
return isChannelPlaying(channelIdx);
|
||||
}
|
||||
|
||||
int32 Sound::getFreeSampleChannelIndex() {
|
||||
for (int channelIdx = 0; channelIdx < NUM_CHANNELS; channelIdx++) {
|
||||
if (!_engine->_system->getMixer()->isSoundHandleActive(_samplesPlaying[channelIdx])) {
|
||||
return channelIdx;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace TwinE
|
||||
138
engines/twine/audio/sound.h
Normal file
138
engines/twine/audio/sound.h
Normal file
@@ -0,0 +1,138 @@
|
||||
/* 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 TWINE_SOUND_H
|
||||
#define TWINE_SOUND_H
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
#include "twine/shared.h"
|
||||
|
||||
namespace TwinE {
|
||||
|
||||
class TextEntry;
|
||||
|
||||
#define NUM_CHANNELS 32
|
||||
|
||||
namespace Samples {
|
||||
enum _Samples {
|
||||
TwinsenHit = 0,
|
||||
SoldierHit = 4,
|
||||
ItemPopup = 11,
|
||||
Explode = 37,
|
||||
BigItemFound = 41,
|
||||
TaskCompleted = 41,
|
||||
Hit = 86,
|
||||
ItemFound = 97,
|
||||
WalkFloorBegin = 126, // BASE_STEP_SOUND
|
||||
WalkFloorRightBegin = 141
|
||||
};
|
||||
}
|
||||
|
||||
class TwinEEngine;
|
||||
class Sound {
|
||||
private:
|
||||
TwinEEngine *_engine;
|
||||
|
||||
/** Samples playing at the same time */
|
||||
Audio::SoundHandle _samplesPlaying[NUM_CHANNELS];
|
||||
|
||||
/** Samples playing at a actors position */
|
||||
int32 _samplesPlayingActors[NUM_CHANNELS]{0};
|
||||
|
||||
bool playSample(int32 channelIdx, int32 index, Audio::SeekableAudioStream *audioStream, int32 loop, const char *name, Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType);
|
||||
|
||||
bool isChannelPlaying(int32 channelIdx);
|
||||
|
||||
/** Find a free channel slot to use */
|
||||
int32 getFreeSampleChannelIndex();
|
||||
|
||||
/** Remove a sample from the channel usage list */
|
||||
void removeChannelWatch(int32 channelIdx);
|
||||
public:
|
||||
int32 _parmSampleVolume = 127;
|
||||
int32 _parmSampleDecalage = 0;
|
||||
int32 _parmSampleFrequence = 0;
|
||||
|
||||
Sound(TwinEEngine *engine);
|
||||
~Sound();
|
||||
|
||||
/** Get the channel where the sample is playing */
|
||||
int32 getSampleChannel(int32 index);
|
||||
|
||||
/**
|
||||
* Play FLA movie samples
|
||||
* @param index sample index under flasamp.hqr file
|
||||
* @param repeat number of times to repeat the sample
|
||||
*/
|
||||
void playFlaSample(int32 index, int16 rate, int32 repeat, uint8 volumeLeft, uint8 volumeRight);
|
||||
|
||||
void setChannelBalance(int32 channelIdx, uint8 volumeLeft, uint8 volumeRight);
|
||||
|
||||
void setChannelRate(int32 channelIdx, uint32 rate);
|
||||
|
||||
/** Update sample position in channel */
|
||||
void setChannelPosition(int32 channelIdx, int32 x, int32 y, int32 z);
|
||||
|
||||
inline void setChannelPosition(int32 channelIdx, const IVec3 &pos) {
|
||||
setChannelPosition(channelIdx, pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play samples
|
||||
* @param index sample index under flasamp.hqr file
|
||||
* @param repeat number of times to repeat the sample
|
||||
* @param pos sound generating entity position
|
||||
* @param actorIdx
|
||||
*/
|
||||
void mixSample3D(int32 index, uint16 pitchbend, int32 repeat, const IVec3 &pos, int32 actorIdx); // HQ_3D_MixSample
|
||||
|
||||
void mixSample(int32 index, uint16 pitchbend, int32 repeat, uint8 volumeLeft, uint8 volumeRight); // HQ_MixSample
|
||||
|
||||
/** Pause samples */
|
||||
void pauseSamples();
|
||||
|
||||
/** Resume samples */
|
||||
void resumeSamples();
|
||||
|
||||
void startRainSample();
|
||||
|
||||
/** Stop samples */
|
||||
void stopSamples();
|
||||
|
||||
/** Get the channel where the actor sample is playing */
|
||||
int32 getActorChannel(int32 actorIdx);
|
||||
|
||||
/** Stops a specific sample */
|
||||
void stopSample(int32 index); // HQ_StopOneSample
|
||||
|
||||
/** Check if a sample is playing */
|
||||
int32 isSamplePlaying(int32 index);
|
||||
|
||||
/** Play VOX sample */
|
||||
bool playVoxSample(const TextEntry *text);
|
||||
};
|
||||
|
||||
} // namespace TwinE
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user