AGI: Implement FR #2813133.
FR #2813133: "AGI: Proper Tandy 3-Voice/IBM PCjr Sound Support". Add proper Tandy music. Heavily based on NAGI source, thus attached its X11 license. To run it now use -e pcjr. Old one is still default for adlib but most likely will be changed in the future. Also lied ground for further separation of different sound generators. svn-id: r49755
This commit is contained in:
parent
85d8b4f5d8
commit
6903fc7a07
6 changed files with 659 additions and 13 deletions
|
@ -583,15 +583,18 @@ void AgiEngine::initialize() {
|
|||
} else if (getPlatform() == Common::kPlatformCoCo3) {
|
||||
_soundemu = SOUND_EMU_COCO3;
|
||||
} else {
|
||||
switch (MidiDriver::detectMusicDriver(MDT_PCSPK | MDT_ADLIB)) {
|
||||
switch (MidiDriver::detectMusicDriver(MDT_PCSPK | MDT_ADLIB | MDT_MIDI)) {
|
||||
case MD_PCSPK:
|
||||
_soundemu = SOUND_EMU_PC;
|
||||
break;
|
||||
case MD_PCJR:
|
||||
_soundemu = SOUND_EMU_PCJR;
|
||||
break;
|
||||
case MD_ADLIB:
|
||||
_soundemu = SOUND_EMU_MIDI;
|
||||
_soundemu = SOUND_EMU_NONE;
|
||||
break;
|
||||
default:
|
||||
_soundemu = SOUND_EMU_NONE;
|
||||
_soundemu = SOUND_EMU_MIDI;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ MODULE_OBJS := \
|
|||
sound.o \
|
||||
sound_2gs.o \
|
||||
sound_midi.o \
|
||||
sound_pcjr.o \
|
||||
sprite.o \
|
||||
text.o \
|
||||
view.o \
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
|
||||
#include "agi/sound_2gs.h"
|
||||
#include "agi/sound_midi.h"
|
||||
#include "agi/sound_pcjr.h"
|
||||
|
||||
namespace Agi {
|
||||
|
||||
|
@ -127,6 +128,15 @@ static int noteToPeriod(int note) {
|
|||
}
|
||||
#endif
|
||||
|
||||
int SoundMgr::readBuffer(int16 *buffer, const int numSamples) {
|
||||
if (_vm->_soundemu == SOUND_EMU_PCJR)
|
||||
_soundGen->premixerCall(buffer, numSamples);
|
||||
else
|
||||
premixerCall(buffer, numSamples / 2);
|
||||
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
void SoundMgr::unloadSound(int resnum) {
|
||||
if (_vm->_game.dirSound[resnum].flags & RES_LOADED) {
|
||||
if (_vm->_game.sounds[resnum]->isPlaying()) {
|
||||
|
@ -174,6 +184,8 @@ void SoundMgr::startSound(int resnum, int flag) {
|
|||
case AGI_SOUND_4CHN:
|
||||
if (_vm->_soundemu == SOUND_EMU_MIDI) {
|
||||
_musicPlayer->playMIDI((MIDISound *)_vm->_game.sounds[resnum]);
|
||||
} else if (_vm->_soundemu == SOUND_EMU_PCJR) {
|
||||
_soundGen->play(resnum, flag);
|
||||
} else {
|
||||
|
||||
PCjrSound *pcjrSound = (PCjrSound *) _vm->_game.sounds[resnum];
|
||||
|
@ -212,7 +224,8 @@ void SoundMgr::stopSound() {
|
|||
debugC(3, kDebugLevelSound, "stopSound() --> %d", _playingSound);
|
||||
|
||||
_endflag = -1;
|
||||
if (_vm->_soundemu != SOUND_EMU_APPLE2GS) {
|
||||
|
||||
if (_vm->_soundemu != SOUND_EMU_APPLE2GS && _vm->_soundemu != SOUND_EMU_PCJR) {
|
||||
for (i = 0; i < NUM_CHANNELS; i++)
|
||||
stopNote(i);
|
||||
}
|
||||
|
@ -229,6 +242,10 @@ void SoundMgr::stopSound() {
|
|||
_musicPlayer->stop();
|
||||
}
|
||||
|
||||
if (_vm->_soundemu == SOUND_EMU_PCJR) {
|
||||
_soundGen->stop();
|
||||
}
|
||||
|
||||
_playingSound = -1;
|
||||
}
|
||||
|
||||
|
@ -261,6 +278,9 @@ int SoundMgr::initSound() {
|
|||
break;
|
||||
case SOUND_EMU_MIDI:
|
||||
break;
|
||||
case SOUND_EMU_PCJR:
|
||||
_soundGen = new SoundGenPCJr(_vm);
|
||||
break;
|
||||
}
|
||||
|
||||
report("Initializing sound:\n");
|
||||
|
@ -281,6 +301,7 @@ int SoundMgr::initSound() {
|
|||
void SoundMgr::deinitSound() {
|
||||
debugC(3, kDebugLevelSound, "()");
|
||||
|
||||
stopSound();
|
||||
_mixer->stopHandle(_soundHandle);
|
||||
}
|
||||
|
||||
|
@ -617,7 +638,7 @@ void SoundMgr::fillAudio(void *udata, int16 *stream, uint len) {
|
|||
data_available -= len;
|
||||
}
|
||||
|
||||
SoundMgr::SoundMgr(AgiBase *agi, Audio::Mixer *pMixer) : _chn() {
|
||||
SoundMgr::SoundMgr(AgiEngine *agi, Audio::Mixer *pMixer) : _chn() {
|
||||
_vm = agi;
|
||||
_mixer = pMixer;
|
||||
_sampleRate = pMixer->getOutputRate();
|
||||
|
@ -630,6 +651,8 @@ SoundMgr::SoundMgr(AgiBase *agi, Audio::Mixer *pMixer) : _chn() {
|
|||
_disabledMidi = false;
|
||||
_useChorus = true; // FIXME: Currently always true?
|
||||
_midiDriver = 0;
|
||||
_musicPlayer = 0;
|
||||
_soundGen = 0;
|
||||
|
||||
_gsSound = new IIgsSoundMgr;
|
||||
|
||||
|
@ -654,6 +677,7 @@ SoundMgr::~SoundMgr() {
|
|||
free(_sndBuffer);
|
||||
delete _gsSound;
|
||||
|
||||
delete _soundGen;
|
||||
delete _musicPlayer;
|
||||
delete _midiDriver;
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace Agi {
|
|||
|
||||
#define SOUND_EMU_NONE 0
|
||||
#define SOUND_EMU_PC 1
|
||||
#define SOUND_EMU_TANDY 2
|
||||
#define SOUND_EMU_PCJR 2
|
||||
#define SOUND_EMU_MAC 3
|
||||
#define SOUND_EMU_AMIGA 4
|
||||
#define SOUND_EMU_APPLE2GS 5
|
||||
|
@ -126,6 +126,17 @@ struct ChannelInfo {
|
|||
|
||||
class SoundMgr;
|
||||
|
||||
class SoundGen {
|
||||
public:
|
||||
SoundGen() {}
|
||||
virtual ~SoundGen() {}
|
||||
|
||||
virtual void play(int resnum, int flag) = 0;
|
||||
virtual void stop(void) = 0;
|
||||
|
||||
virtual void premixerCall(int16 *stream, int len) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* AGI sound resource structure.
|
||||
*/
|
||||
|
@ -167,7 +178,6 @@ protected:
|
|||
};
|
||||
|
||||
class AgiEngine;
|
||||
class AgiBase;
|
||||
class IIgsSoundMgr;
|
||||
class MusicPlayer;
|
||||
|
||||
|
@ -176,15 +186,12 @@ struct IIgsExeInfo;
|
|||
class SoundMgr : public Audio::AudioStream {
|
||||
|
||||
public:
|
||||
SoundMgr(AgiBase *agi, Audio::Mixer *pMixer);
|
||||
SoundMgr(AgiEngine *agi, Audio::Mixer *pMixer);
|
||||
~SoundMgr();
|
||||
virtual void setVolume(uint8 volume);
|
||||
|
||||
// AudioStream API
|
||||
int readBuffer(int16 *buffer, const int numSamples) {
|
||||
premixerCall(buffer, numSamples / 2);
|
||||
return numSamples;
|
||||
}
|
||||
int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
bool isStereo() const {
|
||||
return false;
|
||||
|
@ -200,7 +207,7 @@ public:
|
|||
}
|
||||
|
||||
int _endflag;
|
||||
AgiBase *_vm;
|
||||
AgiEngine *_vm;
|
||||
|
||||
private:
|
||||
Audio::Mixer *_mixer;
|
||||
|
@ -226,6 +233,8 @@ private:
|
|||
void premixerCall(int16 *buf, uint len);
|
||||
void fillAudio(void *udata, int16 *stream, uint len);
|
||||
|
||||
SoundGen *_soundGen;
|
||||
|
||||
public:
|
||||
void unloadSound(int);
|
||||
void playSound();
|
||||
|
|
497
engines/agi/sound_pcjr.cpp
Executable file
497
engines/agi/sound_pcjr.cpp
Executable file
|
@ -0,0 +1,497 @@
|
|||
/* 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 2
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* $URL$
|
||||
* $Id$
|
||||
*
|
||||
*/
|
||||
|
||||
/* Heavily based on code from NAGI
|
||||
*
|
||||
* COPYRIGHT AND PERMISSION NOTICE
|
||||
*
|
||||
* Copyright (c) 2001, 2001, 2002 Nick Sonneveld
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons
|
||||
* to whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
* OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
|
||||
* INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
|
||||
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
||||
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
* Except as contained in this notice, the name of a copyright holder
|
||||
* shall not be used in advertising or otherwise to promote the sale, use
|
||||
* or other dealings in this Software without prior written authorization
|
||||
*
|
||||
*/
|
||||
|
||||
#include "agi/agi.h"
|
||||
#include "agi/sound.h"
|
||||
#include "agi/sound_pcjr.h"
|
||||
|
||||
namespace Agi {
|
||||
|
||||
// "fade out" or possibly "dissolve"
|
||||
// v2.9xx
|
||||
const int8 dissolveDataV2[] = {
|
||||
-2, -3, -2, -1,
|
||||
0x00, 0x00,
|
||||
0x01, 0x01, 0x01, 0x01,
|
||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||
0x04, 0x04, 0x04, 0x04,
|
||||
0x05, 0x05, 0x05, 0x05,
|
||||
0x06, 0x06, 0x06, 0x06, 0x06,
|
||||
0x07, 0x07, 0x07, 0x07,
|
||||
0x08, 0x08, 0x08, 0x08,
|
||||
0x09, 0x09, 0x09, 0x09,
|
||||
0x0A, 0x0A, 0x0A, 0x0A,
|
||||
0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B,
|
||||
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
0x0D,
|
||||
-100
|
||||
};
|
||||
|
||||
// v3
|
||||
const int8 dissolveDataV3[] = {
|
||||
-2, -3, -2, -1,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||
0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x05, 0x05, 0x05, 0x05, 0x05,
|
||||
0x06, 0x06, 0x06, 0x06, 0x06,
|
||||
0x07, 0x07, 0x07, 0x07,
|
||||
0x08, 0x08, 0x08, 0x08,
|
||||
0x09, 0x09, 0x09, 0x09,
|
||||
0x0A, 0x0A, 0x0A, 0x0A,
|
||||
0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B,
|
||||
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
||||
0x0D,
|
||||
-100
|
||||
};
|
||||
|
||||
|
||||
SoundGenPCJr::SoundGenPCJr(AgiEngine *vm) : _vm(vm) {
|
||||
_chanAllocated = 10240; // preallocate something which will most likely fit
|
||||
_chanData = (int16 *)malloc(_chanAllocated << 1);
|
||||
|
||||
// Pick dissolve method
|
||||
//
|
||||
// 0 = no dissolve.. just play for as long as it's meant to be played
|
||||
// this was used in older v2.4 and under games i THINK
|
||||
// 1 = not used
|
||||
// 2 = v2.9+ games used a shorter dissolve
|
||||
// 3 (default) = v3 games used this dissolve pattern.. slightly longer
|
||||
if (_vm->getVersion() >= 0x3000)
|
||||
_dissolveMethod = 3;
|
||||
else if (_vm->getVersion() >= 0x2900)
|
||||
_dissolveMethod = 2;
|
||||
else
|
||||
_dissolveMethod = 0;
|
||||
}
|
||||
|
||||
SoundGenPCJr::~SoundGenPCJr() {
|
||||
free(_chanData);
|
||||
}
|
||||
|
||||
void SoundGenPCJr::play(int resnum, int flag) {
|
||||
PCjrSound *pcjrSound = (PCjrSound *)_vm->_game.sounds[resnum];
|
||||
|
||||
for (int i = 0; i < CHAN_MAX; i++) {
|
||||
_channel[i].data = pcjrSound->getVoicePointer(i % 4);
|
||||
_channel[i].duration = 0;
|
||||
_channel[i].avail = 0xffff;
|
||||
_channel[i].dissolveCount = 0xFFFF;
|
||||
_channel[i].attenuation = 0;
|
||||
_channel[i].attenuationCopy = 0;
|
||||
|
||||
_tchannel[i].avail = 1;
|
||||
_tchannel[i].noteCount = 0;
|
||||
_tchannel[i].freqCount = 250;
|
||||
_tchannel[i].freqCountPrev = -1;
|
||||
_tchannel[i].atten = 0xF; // silence
|
||||
_tchannel[i].genType = kGenTone;
|
||||
_tchannel[i].genTypePrev = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void SoundGenPCJr::stop(void) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CHAN_MAX ; i++) {
|
||||
_channel[i].avail = 0;
|
||||
_tchannel[i].avail = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int SoundGenPCJr::volumeCalc(SndGenChan *chan) {
|
||||
int8 attenuation, dissolveValue;
|
||||
|
||||
const int8 *dissolveData;
|
||||
|
||||
switch (_dissolveMethod) {
|
||||
case 2:
|
||||
dissolveData = dissolveDataV2;
|
||||
break;
|
||||
case 3:
|
||||
default:
|
||||
dissolveData = dissolveDataV3;
|
||||
break;
|
||||
}
|
||||
|
||||
assert(chan);
|
||||
|
||||
attenuation = chan->attenuation;
|
||||
if (attenuation != 0x0F) { // != silence
|
||||
if (chan->dissolveCount != 0xFFFF) {
|
||||
dissolveValue = dissolveData[chan->dissolveCount];
|
||||
if (dissolveValue == -100) { // if at end of list
|
||||
chan->dissolveCount = 0xFFFF;
|
||||
chan->attenuation = chan->attenuationCopy;
|
||||
attenuation = chan->attenuation;
|
||||
} else {
|
||||
chan->dissolveCount++;
|
||||
|
||||
attenuation += dissolveValue;
|
||||
if (attenuation < 0)
|
||||
attenuation = 0;
|
||||
if (attenuation > 0x0F)
|
||||
attenuation = 0x0F;
|
||||
|
||||
chan->attenuationCopy = attenuation;
|
||||
|
||||
attenuation &= 0x0F;
|
||||
attenuation += _vm->getvar(vVolume);
|
||||
if (attenuation > 0x0F)
|
||||
attenuation = 0x0F;
|
||||
}
|
||||
}
|
||||
//if (computer_type == 2) && (attenuation < 8)
|
||||
if (attenuation < 8)
|
||||
attenuation += 2;
|
||||
}
|
||||
|
||||
return attenuation;
|
||||
}
|
||||
|
||||
// read the next channel data.. fill it in *tone
|
||||
// if tone isn't touched.. it should be inited so it just plays silence
|
||||
// return 0 if it's passing more data
|
||||
// return -1 if it's passing nothing (end of data)
|
||||
int SoundGenPCJr::getNextNote(int ch, Tone *tone) {
|
||||
SndGenChan *chan;
|
||||
const byte *data;
|
||||
|
||||
assert(tone);
|
||||
assert(ch < CHAN_MAX);
|
||||
|
||||
if (!_vm->getflag(fSoundOn))
|
||||
return -1;
|
||||
|
||||
chan = &_channel[ch];
|
||||
if (!chan->avail)
|
||||
return -1;
|
||||
|
||||
while ((chan->duration == 0) && (chan->duration != 0xFFFF)) {
|
||||
data = chan->data;
|
||||
|
||||
// read the duration of the note
|
||||
chan->duration = READ_LE_UINT16(data); // duration
|
||||
|
||||
// if it's 0 then it's not going to be played
|
||||
// if it's 0xFFFF then the channel data has finished.
|
||||
if ((chan->duration != 0) && (chan->duration != 0xFFFF)) {
|
||||
// only tone channels dissolve
|
||||
if ((ch != 3) && (_dissolveMethod != 0)) // != noise??
|
||||
chan->dissolveCount = 0;
|
||||
|
||||
// attenuation (volume)
|
||||
chan->attenuation = data[4] & 0xF;
|
||||
|
||||
// frequency
|
||||
if (ch < (CHAN_MAX - 1)) {
|
||||
chan->freqCount = (uint16)data[2] & 0x3F;
|
||||
chan->freqCount <<= 4;
|
||||
chan->freqCount |= data[3] & 0x0F;
|
||||
|
||||
chan->genType = kGenTone;
|
||||
} else {
|
||||
int noiseFreq;
|
||||
|
||||
// check for white noise (1) or periodic (0)
|
||||
chan->genType = (data[3] & 0x04) ? kGenWhite : kGenPeriod;
|
||||
|
||||
noiseFreq = data[3] & 0x03;
|
||||
|
||||
switch (noiseFreq) {
|
||||
case 0:
|
||||
chan->freqCount = 32;
|
||||
break;
|
||||
case 1:
|
||||
chan->freqCount = 64;
|
||||
break;
|
||||
case 2:
|
||||
chan->freqCount = 128;
|
||||
break;
|
||||
case 3:
|
||||
chan->freqCount = _channel[2].freqCount * 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// data now points to the next data seg-a-ment
|
||||
chan->data += 5;
|
||||
}
|
||||
|
||||
if (chan->duration != 0xFFFF) {
|
||||
tone->freqCount = chan->freqCount;
|
||||
tone->atten = volumeCalc(chan); // calc volume, sent vol is different from saved vol
|
||||
tone->type = chan->genType;
|
||||
chan->duration--;
|
||||
} else {
|
||||
// kill channel
|
||||
chan->avail = 0;
|
||||
chan->attenuation = 0x0F; // silent
|
||||
chan->attenuationCopy = 0x0F; // dunno really
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Formulas for noise generator
|
||||
// bit0 = output
|
||||
|
||||
// noise feedback for white noise mode
|
||||
#define FB_WNOISE 0x12000 // bit15.d(16bits) = bit0(out) ^ bit2
|
||||
//#define FB_WNOISE 0x14000 // bit15.d(16bits) = bit0(out) ^ bit1
|
||||
//#define FB_WNOISE 0x28000 // bit16.d(17bits) = bit0(out) ^ bit2 (same to AY-3-8910)
|
||||
//#define FB_WNOISE 0x50000 // bit17.d(18bits) = bit0(out) ^ bit2
|
||||
|
||||
// noise feedback for periodic noise mode
|
||||
// it is correct maybe (it was in the Megadrive sound manual)
|
||||
//#define FB_PNOISE 0x10000 // 16bit rorate
|
||||
#define FB_PNOISE 0x08000
|
||||
|
||||
// noise generator start preset (for periodic noise)
|
||||
#define NG_PRESET 0x0f35
|
||||
|
||||
//#define WAVE_HEIGHT (0x7FFF)
|
||||
|
||||
// Volume table.
|
||||
//
|
||||
// 2dB = 20*log(a/b)
|
||||
// 10^(2/20)*b = a;
|
||||
// value = 0x7fff;
|
||||
// value /= 1.258925411794;
|
||||
const int16 volTable[16] = {
|
||||
32767, 26027, 20674, 16422, 13044, 10361, 8230, 6537, 5193, 4125, 3276, 2602, 2067, 1642, 1304, 0
|
||||
};
|
||||
|
||||
#define FREQ_DIV 111844
|
||||
#define MULT FREQ_DIV
|
||||
|
||||
// fill buff
|
||||
int SoundGenPCJr::chanGen(int chan, int16 *stream, int len) {
|
||||
ToneChan *tpcm;
|
||||
Tone toneNew;
|
||||
int fillSize;
|
||||
int retVal;
|
||||
|
||||
tpcm = &_tchannel[chan];
|
||||
|
||||
retVal = -1;
|
||||
|
||||
while (len > 0) {
|
||||
if (tpcm->noteCount <= 0) {
|
||||
// get new tone data
|
||||
toneNew.freqCount = 0;
|
||||
toneNew.atten = 0xF;
|
||||
toneNew.type = kGenTone;
|
||||
if ((tpcm->avail) && (getNextNote(chan, &toneNew) == 0)) {
|
||||
tpcm->atten = toneNew.atten;
|
||||
tpcm->freqCount = toneNew.freqCount;
|
||||
tpcm->genType = toneNew.type;
|
||||
|
||||
// setup counters 'n stuff
|
||||
// SAMPLE_RATE samples per sec.. tone changes 60 times per sec
|
||||
tpcm->noteCount = SAMPLE_RATE / 60;
|
||||
retVal = 0;
|
||||
} else {
|
||||
// if it doesn't return an
|
||||
tpcm->genType = kGenSilence;
|
||||
tpcm->noteCount = len;
|
||||
tpcm->avail = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// write nothing
|
||||
if ((tpcm->freqCount == 0) || (tpcm->atten == 0xf)) {
|
||||
tpcm->genType = kGenSilence;
|
||||
}
|
||||
|
||||
// find which is smaller.. the buffer or the
|
||||
fillSize = (tpcm->noteCount <= len) ? tpcm->noteCount : len;
|
||||
|
||||
switch (tpcm->genType) {
|
||||
case kGenTone:
|
||||
fillSize = fillSquare(tpcm, stream, fillSize);
|
||||
break;
|
||||
case kGenPeriod:
|
||||
case kGenWhite:
|
||||
fillSize = fillNoise(tpcm, stream, fillSize);
|
||||
break;
|
||||
case kGenSilence:
|
||||
default:
|
||||
// fill with whitespace
|
||||
memset(stream, 0, fillSize * sizeof(int16));
|
||||
break;
|
||||
}
|
||||
|
||||
tpcm->noteCount -= fillSize;
|
||||
stream += fillSize;
|
||||
len -= fillSize;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
int SoundGenPCJr::fillSquare(ToneChan *t, int16 *buf, int len) {
|
||||
int count;
|
||||
|
||||
if (t->genType != t->genTypePrev) {
|
||||
// make sure the freqCount is checked
|
||||
t->freqCountPrev = -1;
|
||||
t->sign = 1;
|
||||
t->genTypePrev = t->genType;
|
||||
}
|
||||
|
||||
if (t->freqCount != t->freqCountPrev) {
|
||||
//t->scale = (int)( (double)t->samp->freq*t->freqCount/FREQ_DIV * MULT + 0.5);
|
||||
t->scale = (SAMPLE_RATE / 2) * t->freqCount;
|
||||
t->count = t->scale;
|
||||
t->freqCountPrev = t->freqCount;
|
||||
}
|
||||
|
||||
count = len;
|
||||
|
||||
while (count > 0) {
|
||||
*(buf++) = t->sign ? volTable[t->atten] : -volTable[t->atten];
|
||||
count--;
|
||||
|
||||
// get next sample
|
||||
t->count -= MULT;
|
||||
while (t->count <= 0) {
|
||||
t->sign ^= 1;
|
||||
t->count += t->scale;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int SoundGenPCJr::fillNoise(ToneChan *t, int16 *buf, int len) {
|
||||
int count;
|
||||
|
||||
if (t->genType != t->genTypePrev) {
|
||||
// make sure the freqCount is checked
|
||||
t->freqCountPrev = -1;
|
||||
t->genTypePrev = t->genType;
|
||||
}
|
||||
|
||||
if (t->freqCount != t->freqCountPrev) {
|
||||
//t->scale = (int)( (double)t->samp->freq*t->freqCount/FREQ_DIV * MULT + 0.5);
|
||||
t->scale = (SAMPLE_RATE / 2) * t->freqCount;
|
||||
t->count = t->scale;
|
||||
t->freqCountPrev = t->freqCount;
|
||||
|
||||
t->feedback = (t->genType == kGenWhite) ? FB_WNOISE : FB_PNOISE;
|
||||
// reset noise shifter
|
||||
t->noiseState = NG_PRESET;
|
||||
t->sign = t->noiseState & 1;
|
||||
}
|
||||
|
||||
count = len;
|
||||
|
||||
while (count > 0) {
|
||||
*(buf++) = t->sign ? volTable[t->atten] : -volTable[t->atten];
|
||||
count--;
|
||||
|
||||
// get next sample
|
||||
t->count -= MULT;
|
||||
while (t->count <= 0) {
|
||||
if (t->noiseState & 1)
|
||||
t->noiseState ^= t->feedback;
|
||||
|
||||
t->noiseState >>= 1;
|
||||
t->sign = t->noiseState & 1;
|
||||
t->count += t->scale;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void SoundGenPCJr::premixerCall(int16 *stream, int len) {
|
||||
int streamCount;
|
||||
int16 *sPtr, *cPtr;
|
||||
|
||||
if (_chanAllocated < len) {
|
||||
free(_chanData);
|
||||
_chanData = (int16 *)malloc(len << 1);
|
||||
_chanAllocated = len;
|
||||
}
|
||||
memset(stream, 0, len << 1);
|
||||
|
||||
assert(stream);
|
||||
|
||||
for (int i = 0; i < CHAN_MAX; i++) {
|
||||
// get channel data(chan.userdata)
|
||||
if (chanGen(i, _chanData, len) == 0) {
|
||||
// divide by number of channels then add to stream
|
||||
streamCount = len;
|
||||
sPtr = stream;
|
||||
cPtr = _chanData;
|
||||
|
||||
while (streamCount--)
|
||||
*(sPtr++) += *(cPtr++) / CHAN_MAX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Agi
|
112
engines/agi/sound_pcjr.h
Executable file
112
engines/agi/sound_pcjr.h
Executable file
|
@ -0,0 +1,112 @@
|
|||
/* 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 2
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* $URL$
|
||||
* $Id$
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AGI_SOUND_PCJR_H
|
||||
#define AGI_SOUND_PCJR_H
|
||||
|
||||
namespace Agi {
|
||||
|
||||
#define CHAN_MAX 4
|
||||
|
||||
#define SAMPLE_RATE 22050
|
||||
|
||||
enum GenType {
|
||||
kGenSilence,
|
||||
kGenTone,
|
||||
kGenPeriod,
|
||||
kGenWhite
|
||||
};
|
||||
|
||||
struct SndGenChan {
|
||||
const byte *data;
|
||||
uint16 duration;
|
||||
uint16 avail; // turned on (1) but when the channel's data runs out, it's set to (0)
|
||||
uint16 dissolveCount;
|
||||
byte attenuation;
|
||||
byte attenuationCopy;
|
||||
|
||||
GenType genType;
|
||||
|
||||
// for the sample mixer
|
||||
int freqCount;
|
||||
};
|
||||
|
||||
struct ToneChan {
|
||||
int avail;
|
||||
|
||||
int noteCount; // length of tone.. duration
|
||||
|
||||
int freqCount;
|
||||
int freqCountPrev;
|
||||
int atten; // volume
|
||||
|
||||
GenType genType;
|
||||
int genTypePrev;
|
||||
|
||||
int count;
|
||||
int scale;
|
||||
int sign;
|
||||
unsigned int noiseState; /* noise generator */
|
||||
int feedback; /* noise feedback mask */
|
||||
};
|
||||
|
||||
struct Tone {
|
||||
int freqCount;
|
||||
int atten;
|
||||
GenType type;
|
||||
};
|
||||
|
||||
class SoundGenPCJr : public SoundGen {
|
||||
public:
|
||||
SoundGenPCJr(AgiEngine *vm);
|
||||
~SoundGenPCJr();
|
||||
|
||||
void play(int resnum, int flag);
|
||||
void stop(void);
|
||||
|
||||
void premixerCall(int16 *stream, int len);
|
||||
|
||||
private:
|
||||
int getNextNote(int ch, Tone *tone);
|
||||
int volumeCalc(SndGenChan *chan);
|
||||
|
||||
int chanGen(int chan, int16 *stream, int len);
|
||||
|
||||
int fillNoise(ToneChan *t, int16 *buf, int len);
|
||||
int fillSquare(ToneChan *t, int16 *buf, int len);
|
||||
|
||||
private:
|
||||
AgiEngine *_vm;
|
||||
SndGenChan _channel[CHAN_MAX];
|
||||
ToneChan _tchannel[CHAN_MAX];
|
||||
int16 *_chanData;
|
||||
int _chanAllocated;
|
||||
|
||||
int _dissolveMethod;
|
||||
};
|
||||
|
||||
} // End of namespace Agi
|
||||
|
||||
#endif /* AGI_SOUND_PCJR_H */
|
Loading…
Add table
Add a link
Reference in a new issue