Initial commit
This commit is contained in:
292
engines/scumm/players/player_apple2.h
Normal file
292
engines/scumm/players/player_apple2.h
Normal file
@@ -0,0 +1,292 @@
|
||||
/* 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 SCUMM_PLAYERS_PLAYER_APPLEII_H
|
||||
#define SCUMM_PLAYERS_PLAYER_APPLEII_H
|
||||
|
||||
#include "common/mutex.h"
|
||||
#include "common/scummsys.h"
|
||||
#include "scumm/music.h"
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/mixer.h"
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
class ScummEngine;
|
||||
|
||||
/*
|
||||
* Optimized for use with periodical read/write phases when the buffer
|
||||
* is filled in a write phase and completely read in a read phase.
|
||||
* The growing strategy is optimized for repeated small (e.g. 2 bytes)
|
||||
* single writes resulting in large buffers
|
||||
* (avg.: 4KB, max: 18KB @ 16bit/22.050kHz (MM sound21)).
|
||||
*/
|
||||
class SampleBuffer {
|
||||
public:
|
||||
SampleBuffer() : _data(0) {
|
||||
clear();
|
||||
}
|
||||
|
||||
~SampleBuffer() {
|
||||
free(_data);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
free(_data);
|
||||
_data = 0;
|
||||
_capacity = 0;
|
||||
_writePos = 0;
|
||||
_readPos = 0;
|
||||
}
|
||||
|
||||
void ensureFree(uint32 needed) {
|
||||
// if data was read completely, reset read/write pos to front
|
||||
if ((_writePos != 0) && (_writePos == _readPos)) {
|
||||
_writePos = 0;
|
||||
_readPos = 0;
|
||||
}
|
||||
|
||||
// check for enough space at end of buffer
|
||||
uint32 freeEndCnt = _capacity - _writePos;
|
||||
if (needed <= freeEndCnt)
|
||||
return;
|
||||
|
||||
uint32 avail = availableSize();
|
||||
|
||||
// check for enough space at beginning and end of buffer
|
||||
if (needed <= _readPos + freeEndCnt) {
|
||||
// move unread data to front of buffer
|
||||
memmove(_data, _data + _readPos, avail);
|
||||
_writePos = avail;
|
||||
_readPos = 0;
|
||||
} else { // needs a grow
|
||||
byte *old_data = _data;
|
||||
uint32 new_len = avail + needed;
|
||||
|
||||
_capacity = new_len + 2048;
|
||||
_data = (byte *)malloc(_capacity);
|
||||
|
||||
if (old_data) {
|
||||
// copy old unread data to front of new buffer
|
||||
memcpy(_data, old_data + _readPos, avail);
|
||||
free(old_data);
|
||||
_writePos = avail;
|
||||
_readPos = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32 availableSize() const {
|
||||
if (_readPos >= _writePos)
|
||||
return 0;
|
||||
return _writePos - _readPos;
|
||||
}
|
||||
|
||||
uint32 write(const void *dataPtr, uint32 dataSize) {
|
||||
ensureFree(dataSize);
|
||||
memcpy(_data + _writePos, dataPtr, dataSize);
|
||||
_writePos += dataSize;
|
||||
return dataSize;
|
||||
}
|
||||
|
||||
uint32 read(byte *dataPtr, uint32 dataSize) {
|
||||
uint32 avail = availableSize();
|
||||
if (avail == 0)
|
||||
return 0;
|
||||
if (dataSize > avail)
|
||||
dataSize = avail;
|
||||
memcpy(dataPtr, _data + _readPos, dataSize);
|
||||
_readPos += dataSize;
|
||||
return dataSize;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32 _writePos;
|
||||
uint32 _readPos;
|
||||
uint32 _capacity;
|
||||
byte *_data;
|
||||
};
|
||||
|
||||
// CPU_CLOCK according to AppleWin
|
||||
static const double APPLEII_CPU_CLOCK = 1020484.5; // ~ 1.02 MHz
|
||||
|
||||
/*
|
||||
* Converts the 1-bit speaker state values into audio samples.
|
||||
* This is done by aggregation of the speaker states at each
|
||||
* CPU cycle in a sampling period into an audio sample.
|
||||
*/
|
||||
class SampleConverter {
|
||||
private:
|
||||
void addSampleToBuffer(int sample) {
|
||||
int16 value = sample * _volume / _maxVolume;
|
||||
_buffer.write(&value, sizeof(value));
|
||||
}
|
||||
|
||||
public:
|
||||
SampleConverter() :
|
||||
_cyclesPerSampleFP(0),
|
||||
_missingCyclesFP(0),
|
||||
_sampleCyclesSumFP(0),
|
||||
_volume(_maxVolume)
|
||||
{}
|
||||
|
||||
~SampleConverter() {}
|
||||
|
||||
void reset() {
|
||||
_missingCyclesFP = 0;
|
||||
_sampleCyclesSumFP = 0;
|
||||
_buffer.clear();
|
||||
}
|
||||
|
||||
uint32 availableSize() const {
|
||||
return _buffer.availableSize();
|
||||
}
|
||||
|
||||
void setMusicVolume(int vol) {
|
||||
assert(vol >= 0 && vol <= _maxVolume);
|
||||
_volume = vol;
|
||||
}
|
||||
|
||||
void setSampleRate(int rate) {
|
||||
/* ~46 CPU cycles per sample @ 22.05kHz */
|
||||
_cyclesPerSampleFP = int(APPLEII_CPU_CLOCK * (1 << PREC_SHIFT) / rate);
|
||||
reset();
|
||||
}
|
||||
|
||||
void addCycles(byte level, const int cycles) {
|
||||
/* convert to fixed precision floats */
|
||||
int cyclesFP = cycles << PREC_SHIFT;
|
||||
|
||||
// step 1: if cycles are left from the last call, process them first
|
||||
if (_missingCyclesFP > 0) {
|
||||
int n = (_missingCyclesFP < cyclesFP) ? _missingCyclesFP : cyclesFP;
|
||||
if (level)
|
||||
_sampleCyclesSumFP += n;
|
||||
cyclesFP -= n;
|
||||
_missingCyclesFP -= n;
|
||||
if (_missingCyclesFP == 0) {
|
||||
addSampleToBuffer(2*32767 * _sampleCyclesSumFP / _cyclesPerSampleFP - 32767);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_sampleCyclesSumFP = 0;
|
||||
|
||||
// step 2: process blocks of cycles fitting into a whole sample
|
||||
while (cyclesFP >= _cyclesPerSampleFP) {
|
||||
addSampleToBuffer(level ? 32767 : -32767);
|
||||
cyclesFP -= _cyclesPerSampleFP;
|
||||
}
|
||||
|
||||
// step 3: remember cycles left for next call
|
||||
if (cyclesFP > 0) {
|
||||
_missingCyclesFP = _cyclesPerSampleFP - cyclesFP;
|
||||
if (level)
|
||||
_sampleCyclesSumFP = cyclesFP;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 readSamples(void *buffer, int numSamples) {
|
||||
return _buffer.read((byte *)buffer, numSamples * 2) / 2;
|
||||
}
|
||||
|
||||
private:
|
||||
static const int PREC_SHIFT = 7;
|
||||
|
||||
private:
|
||||
int _cyclesPerSampleFP; /* (fixed precision) */
|
||||
int _missingCyclesFP; /* (fixed precision) */
|
||||
int _sampleCyclesSumFP; /* (fixed precision) */
|
||||
int _volume; /* 0 - 256 */
|
||||
static const int _maxVolume = 256;
|
||||
SampleBuffer _buffer;
|
||||
};
|
||||
|
||||
class Player_AppleII;
|
||||
|
||||
class AppleII_SoundFunction {
|
||||
public:
|
||||
AppleII_SoundFunction() {}
|
||||
virtual ~AppleII_SoundFunction() {}
|
||||
virtual void init(Player_AppleII *player, const byte *params) = 0;
|
||||
/* returns true if finished */
|
||||
virtual bool update() = 0;
|
||||
protected:
|
||||
Player_AppleII *_player = nullptr;
|
||||
};
|
||||
|
||||
class Player_AppleII : public Audio::AudioStream, public MusicEngine {
|
||||
public:
|
||||
Player_AppleII(ScummEngine *scumm, Audio::Mixer *mixer);
|
||||
~Player_AppleII() override;
|
||||
|
||||
void setMusicVolume(int vol) override { _sampleConverter.setMusicVolume(vol); }
|
||||
void setSampleRate(int rate) {
|
||||
_sampleRate = rate;
|
||||
_sampleConverter.setSampleRate(rate);
|
||||
}
|
||||
void startSound(int sound) override;
|
||||
void stopSound(int sound) override;
|
||||
void stopAllSounds() override;
|
||||
int getSoundStatus(int sound) const override;
|
||||
int getMusicTimer() override;
|
||||
|
||||
// AudioStream API
|
||||
int readBuffer(int16 *buffer, const int numSamples) override;
|
||||
bool isStereo() const override { return false; }
|
||||
bool endOfData() const override { return false; }
|
||||
int getRate() const override { return _sampleRate; }
|
||||
|
||||
public:
|
||||
void speakerToggle();
|
||||
void generateSamples(int cycles);
|
||||
void wait(int interval, int count);
|
||||
|
||||
private:
|
||||
// sound number
|
||||
int _soundNr = 0;
|
||||
// type of sound
|
||||
int _type = 0;
|
||||
// number of loops left
|
||||
int _loop = 0;
|
||||
// global sound param list
|
||||
const byte *_params = nullptr;
|
||||
// speaker toggle state (0 / 1)
|
||||
byte _speakerState = 0;
|
||||
// sound function
|
||||
AppleII_SoundFunction *_soundFunc;
|
||||
// cycle to sample converter
|
||||
SampleConverter _sampleConverter;
|
||||
|
||||
ScummEngine *_vm;
|
||||
Audio::Mixer *_mixer;
|
||||
Audio::SoundHandle _soundHandle;
|
||||
int _sampleRate = 0;
|
||||
Common::Mutex _mutex;
|
||||
|
||||
void resetState();
|
||||
bool updateSound();
|
||||
};
|
||||
|
||||
} // End of namespace Scumm
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user