mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
1317 lines
33 KiB
C++
1317 lines
33 KiB
C++
/* 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 "sci/sound/drivers/mididriver.h"
|
|
#include "sci/sound/drivers/macmixer.h"
|
|
#include "sci/resource/resource.h"
|
|
|
|
#include "audio/mixer.h"
|
|
#include "audio/mods/paula.h"
|
|
#include "common/array.h"
|
|
#include "common/debug-channels.h"
|
|
#include "common/hashmap.h"
|
|
#include "common/memstream.h"
|
|
#include "common/mutex.h"
|
|
#include "common/stream.h"
|
|
#include "common/textconsole.h"
|
|
#include "common/util.h"
|
|
|
|
namespace Sci {
|
|
|
|
class MidiPlayer_AmigaMac1 : public MidiPlayer {
|
|
public:
|
|
enum {
|
|
kVoices = 4,
|
|
kFreqTableSize = 56,
|
|
kBaseFreq = 60
|
|
};
|
|
|
|
enum kEnvState {
|
|
kEnvStateAttack,
|
|
kEnvStateDecay,
|
|
kEnvStateSustain,
|
|
kEnvStateRelease
|
|
};
|
|
|
|
MidiPlayer_AmigaMac1(SciVersion version, Audio::Mixer *mixer, uint extraSamples, bool wantSignedSamples, Common::Mutex &mutex);
|
|
virtual ~MidiPlayer_AmigaMac1();
|
|
|
|
// MidiPlayer
|
|
void close() override;
|
|
void send(uint32 b) override;
|
|
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override;
|
|
uint32 getBaseTempo() override { return (1000000 + kBaseFreq / 2) / kBaseFreq; }
|
|
byte getPlayId() const override { return 0x06; }
|
|
int getPolyphony() const override { return kVoices; }
|
|
bool hasRhythmChannel() const override { return false; }
|
|
void setVolume(byte volume) override;
|
|
int getVolume() override;
|
|
void playSwitch(bool play) override;
|
|
|
|
protected:
|
|
struct Wave {
|
|
Wave() : name(), phase1Start(0), phase1End(0), phase2Start(0), phase2End(0),
|
|
nativeNote(0), freqTable(nullptr), samples(nullptr), size(0) {}
|
|
|
|
~Wave() {
|
|
delete[] samples;
|
|
}
|
|
|
|
char name[9];
|
|
uint16 phase1Start, phase1End;
|
|
uint16 phase2Start, phase2End;
|
|
uint16 nativeNote;
|
|
|
|
// This table contains frequency data for about one octave with 3 pitch bend positions between semitones
|
|
// On Mac, this table contains a fixed-point source-samples-per-output-sample value
|
|
// On Amiga, this table contains how many clock cycles each source sample should be output
|
|
const uint32 *freqTable;
|
|
const byte *samples;
|
|
uint32 size;
|
|
};
|
|
|
|
struct NoteRange {
|
|
NoteRange() : startNote(0), endNote(0), wave(nullptr), transpose(0), attackSpeed(0),
|
|
attackTarget(0), decaySpeed(0), decayTarget(0), releaseSpeed(0), fixedNote(0),
|
|
loop(false) {}
|
|
|
|
int16 startNote;
|
|
int16 endNote;
|
|
|
|
const Wave *wave;
|
|
|
|
int16 transpose;
|
|
|
|
byte attackSpeed;
|
|
byte attackTarget;
|
|
byte decaySpeed;
|
|
byte decayTarget;
|
|
byte releaseSpeed;
|
|
|
|
int16 fixedNote;
|
|
bool loop;
|
|
};
|
|
|
|
struct Instrument {
|
|
Instrument() : name() {}
|
|
|
|
char name[9];
|
|
Common::Array<NoteRange> noteRange;
|
|
};
|
|
|
|
Common::Array<const Instrument *> _instruments;
|
|
typedef Common::HashMap<uint32, const Wave *> WaveMap;
|
|
WaveMap _waves;
|
|
typedef Common::HashMap<uint32, const uint32 *> FreqTableMap;
|
|
FreqTableMap _freqTables;
|
|
|
|
bool _playSwitch;
|
|
uint _masterVolume;
|
|
|
|
Audio::Mixer *_mixer;
|
|
Audio::SoundHandle _mixerSoundHandle;
|
|
Common::TimerManager::TimerProc _timerProc;
|
|
void *_timerParam;
|
|
bool _isOpen;
|
|
|
|
uint32 *loadFreqTable(Common::SeekableReadStream &stream);
|
|
const Wave *loadWave(Common::SeekableReadStream &stream, bool isEarlyPatch);
|
|
bool loadInstruments(Common::SeekableReadStream &patch, bool isEarlyPatch);
|
|
void freeInstruments();
|
|
void distributeVoices();
|
|
void onTimer();
|
|
|
|
class Channel;
|
|
class Voice {
|
|
public:
|
|
Voice(MidiPlayer_AmigaMac1 &driver, byte id) :
|
|
_channel(nullptr),
|
|
_note(-1),
|
|
_velocity(0),
|
|
_isReleased(false),
|
|
_isSustained(false),
|
|
_ticks(0),
|
|
_releaseTicks(0),
|
|
_envState(kEnvStateAttack),
|
|
_envCurVel(0),
|
|
_envCntDown(0),
|
|
_noteRange(nullptr),
|
|
_wave(nullptr),
|
|
_freqTable(nullptr),
|
|
_id(id),
|
|
_driver(driver) {}
|
|
|
|
virtual ~Voice() {}
|
|
|
|
void noteOn(int8 note, int8 velocity);
|
|
void noteOff();
|
|
|
|
virtual void play(int8 note, int8 velocity) = 0;
|
|
virtual void stop() = 0;
|
|
virtual void setVolume(byte volume) = 0;
|
|
virtual bool calcVoiceStep() = 0;
|
|
|
|
void calcMixVelocity();
|
|
void processEnvelope();
|
|
|
|
Channel *_channel;
|
|
int8 _note;
|
|
byte _velocity;
|
|
bool _isReleased;
|
|
bool _isSustained;
|
|
uint16 _ticks;
|
|
uint16 _releaseTicks;
|
|
|
|
kEnvState _envState;
|
|
int8 _envCurVel;
|
|
byte _envCntDown;
|
|
|
|
const NoteRange *_noteRange;
|
|
const Wave *_wave;
|
|
const uint32 *_freqTable;
|
|
const byte _id;
|
|
|
|
private:
|
|
MidiPlayer_AmigaMac1 &_driver;
|
|
};
|
|
|
|
Common::Array<Voice *> _voices;
|
|
typedef Common::Array<Voice *>::const_iterator VoiceIt;
|
|
|
|
class Channel {
|
|
public:
|
|
Channel(MidiPlayer_AmigaMac1 &driver) :
|
|
_patch(0),
|
|
_pitch(0x2000),
|
|
_hold(false),
|
|
_pan(64),
|
|
_volume(63),
|
|
_lastVoiceIt(driver._voices.begin()),
|
|
_extraVoices(0),
|
|
_driver(driver) {}
|
|
|
|
void noteOn(int8 note, int8 velocity);
|
|
void noteOff(int8 note);
|
|
|
|
Voice *findVoice();
|
|
void voiceMapping(byte voices);
|
|
void assignVoices(byte voices);
|
|
void releaseVoices(byte voices);
|
|
void changePatch(int8 patch);
|
|
void holdPedal(int8 pedal);
|
|
void setPitchWheel(uint16 pitch);
|
|
|
|
int8 _patch;
|
|
uint16 _pitch;
|
|
bool _hold;
|
|
int8 _pan;
|
|
int8 _volume;
|
|
VoiceIt _lastVoiceIt;
|
|
byte _extraVoices;
|
|
|
|
private:
|
|
MidiPlayer_AmigaMac1 &_driver;
|
|
};
|
|
|
|
Common::Array<Channel *> _channels;
|
|
typedef Common::Array<Channel *>::const_iterator ChanIt;
|
|
|
|
static const byte _envSpeedToStep[32];
|
|
static const byte _envSpeedToSkip[32];
|
|
static const byte _velocityMap[64];
|
|
|
|
const uint _extraSamples;
|
|
const bool _wantSignedSamples;
|
|
|
|
Common::Mutex &_mixMutex;
|
|
Common::Mutex _timerMutex;
|
|
};
|
|
|
|
MidiPlayer_AmigaMac1::MidiPlayer_AmigaMac1(SciVersion version, Audio::Mixer *mixer, uint extraSamples, bool wantSignedSamples, Common::Mutex &mutex) :
|
|
MidiPlayer(version),
|
|
_playSwitch(true),
|
|
_masterVolume(15),
|
|
_mixer(mixer),
|
|
_mixerSoundHandle(),
|
|
_timerProc(),
|
|
_timerParam(nullptr),
|
|
_isOpen(false),
|
|
_extraSamples(extraSamples),
|
|
_wantSignedSamples(wantSignedSamples),
|
|
_mixMutex(mutex) {
|
|
|
|
assert(_extraSamples > 0);
|
|
}
|
|
|
|
MidiPlayer_AmigaMac1::~MidiPlayer_AmigaMac1() {
|
|
close();
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::close() {
|
|
if (!_isOpen)
|
|
return;
|
|
|
|
_mixer->stopHandle(_mixerSoundHandle);
|
|
|
|
for (ChanIt c = _channels.begin(); c != _channels.end(); ++c)
|
|
delete *c;
|
|
_channels.clear();
|
|
|
|
for (VoiceIt v = _voices.begin(); v != _voices.end(); ++v)
|
|
delete *v;
|
|
_voices.clear();
|
|
|
|
freeInstruments();
|
|
|
|
_isOpen = false;
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
|
|
Common::StackLock lock(_timerMutex);
|
|
_timerProc = timer_proc;
|
|
_timerParam = timer_param;
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::setVolume(byte volume) {
|
|
Common::StackLock lock(_mixMutex);
|
|
_masterVolume = volume;
|
|
}
|
|
|
|
int MidiPlayer_AmigaMac1::getVolume() {
|
|
Common::StackLock lock(_mixMutex);
|
|
return _masterVolume;
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::playSwitch(bool play) {
|
|
Common::StackLock lock(_mixMutex);
|
|
_playSwitch = play;
|
|
}
|
|
|
|
uint32 *MidiPlayer_AmigaMac1::loadFreqTable(Common::SeekableReadStream &stream) {
|
|
uint32 *freqTable = new ufrac_t[kFreqTableSize];
|
|
|
|
for (uint i = 0; i < kFreqTableSize; ++i)
|
|
freqTable[i] = stream.readUint32BE();
|
|
|
|
return freqTable;
|
|
}
|
|
|
|
const MidiPlayer_AmigaMac1::Wave *MidiPlayer_AmigaMac1::loadWave(Common::SeekableReadStream &stream, bool isEarlyPatch) {
|
|
Wave *wave = new Wave();
|
|
|
|
stream.read(wave->name, 8);
|
|
wave->name[8] = 0;
|
|
|
|
bool isSigned = true;
|
|
if (!isEarlyPatch)
|
|
isSigned = stream.readUint16BE();
|
|
|
|
wave->phase1Start = stream.readUint16BE();
|
|
wave->phase1End = stream.readUint16BE();
|
|
wave->phase2Start = stream.readUint16BE();
|
|
wave->phase2End = stream.readUint16BE();
|
|
wave->nativeNote = stream.readUint16BE();
|
|
const uint32 freqTableOffset = stream.readUint32BE();
|
|
|
|
// Sanity checks of segment offsets
|
|
if ((wave->phase2End & ~1) > wave->phase1End || wave->phase1Start > wave->phase1End || wave->phase2Start > wave->phase2End)
|
|
error("MidiPlayer_AmigaMac1: Invalid segment offsets found for wave '%s'", wave->name);
|
|
|
|
// On Mac, 1480 additional samples are present, rounded up to the next word boundary
|
|
// This allows for a maximum step of 8 during sample generation without bounds checking
|
|
// On Amiga, 224 additional samples are present
|
|
wave->size = ((wave->phase1End + 1) + _extraSamples + 1) & ~1;
|
|
byte *samples = new byte[wave->size];
|
|
stream.read(samples, wave->size);
|
|
wave->samples = samples;
|
|
|
|
if (_wantSignedSamples && !isSigned) {
|
|
// The original code uses a signed 16-bit type here, while some samples
|
|
// exceed INT_MAX in size. In this case, it will change one "random" byte
|
|
// in memory and then stop converting. We simulate this behaviour here, minus
|
|
// the memory corruption.
|
|
// The "maincrnh" instrument in Castle of Dr. Brain has an incorrect signedness
|
|
// flag, but is not actually converted because of its size.
|
|
|
|
if (wave->phase1End + _extraSamples <= 0x8000) {
|
|
for (uint32 i = 0; i < wave->size; ++i)
|
|
samples[i] -= 0x80;
|
|
} else {
|
|
debugC(kDebugLevelSound, "MidiPlayer_AmigaMac1: Skipping sign conversion for wave '%s' of size %d bytes", wave->name, wave->size);
|
|
}
|
|
}
|
|
|
|
if (!_freqTables.contains(freqTableOffset)) {
|
|
stream.seek(freqTableOffset);
|
|
_freqTables[freqTableOffset] = loadFreqTable(stream);
|
|
}
|
|
|
|
wave->freqTable = _freqTables[freqTableOffset];
|
|
return wave;
|
|
}
|
|
|
|
bool MidiPlayer_AmigaMac1::loadInstruments(Common::SeekableReadStream &patch, bool isEarlyPatch) {
|
|
_instruments.resize(128);
|
|
|
|
for (uint patchIdx = 0; patchIdx < 128; ++patchIdx) {
|
|
patch.seek(patchIdx * 4);
|
|
uint32 offset = patch.readUint32BE();
|
|
|
|
if (offset == 0)
|
|
continue;
|
|
|
|
Instrument *instrument = new Instrument();
|
|
|
|
patch.seek(offset);
|
|
patch.read(instrument->name, 8);
|
|
instrument->name[8] = 0;
|
|
patch.skip(2); // Unknown
|
|
|
|
debugC(kDebugLevelSound, "Instrument[%d]: '%s'", patchIdx, instrument->name);
|
|
|
|
while (1) {
|
|
NoteRange noteRange;
|
|
|
|
noteRange.startNote = patch.readUint16BE();
|
|
|
|
if (patch.err() || patch.eos()) {
|
|
if (_instruments[patchIdx] != instrument) {
|
|
delete instrument;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (noteRange.startNote == -1)
|
|
break;
|
|
|
|
noteRange.endNote = patch.readUint16BE();
|
|
|
|
const uint32 waveOffset = patch.readUint32BE();
|
|
|
|
noteRange.transpose = patch.readSint16BE();
|
|
|
|
noteRange.attackSpeed = patch.readByte();
|
|
noteRange.attackTarget = patch.readByte();
|
|
noteRange.decaySpeed = patch.readByte();
|
|
noteRange.decayTarget = patch.readByte();
|
|
noteRange.releaseSpeed = patch.readByte();
|
|
|
|
patch.skip(1); // Probably releaseTarget, unused
|
|
noteRange.fixedNote = patch.readSint16BE();
|
|
noteRange.loop = !patch.readUint16BE();
|
|
|
|
int32 nextNoteRangePos = patch.pos();
|
|
|
|
if (!_waves.contains(waveOffset)) {
|
|
patch.seek(waveOffset);
|
|
_waves[waveOffset] = loadWave(patch, isEarlyPatch);
|
|
}
|
|
|
|
noteRange.wave = _waves[waveOffset];
|
|
|
|
debugC(kDebugLevelSound, "\tNotes %d-%d", noteRange.startNote, noteRange.endNote);
|
|
debugC(kDebugLevelSound, "\t\tWave: '%s'", noteRange.wave->name);
|
|
debugC(kDebugLevelSound, "\t\t\tSegment 1: %d-%d", noteRange.wave->phase1Start, noteRange.wave->phase1End);
|
|
debugC(kDebugLevelSound, "\t\t\tSegment 2: %d-%d", noteRange.wave->phase2Start, noteRange.wave->phase2End);
|
|
debugC(kDebugLevelSound, "\t\tTranspose = %d, Fixed note = %d, Loop = %d", noteRange.transpose, noteRange.fixedNote, noteRange.loop);
|
|
debugC(kDebugLevelSound, "\t\tAttack: %d delta, %d target", noteRange.attackSpeed, noteRange.attackTarget);
|
|
debugC(kDebugLevelSound, "\t\tDecay: %d delta, %d target", noteRange.decaySpeed, noteRange.decayTarget);
|
|
debugC(kDebugLevelSound, "\t\tRelease: %d delta, %d target", noteRange.releaseSpeed, 0);
|
|
debugC(kDebugLevelSound, "\t\tRelease: %d delta, %d target", noteRange.releaseSpeed, 0);
|
|
|
|
instrument->noteRange.push_back(noteRange);
|
|
|
|
_instruments[patchIdx] = instrument;
|
|
patch.seek(nextNoteRangePos);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::freeInstruments() {
|
|
for (WaveMap::iterator it = _waves.begin(); it != _waves.end(); ++it)
|
|
delete it->_value;
|
|
_waves.clear();
|
|
|
|
for (FreqTableMap::iterator it = _freqTables.begin(); it != _freqTables.end(); ++it)
|
|
delete[] it->_value;
|
|
_freqTables.clear();
|
|
|
|
for (Common::Array<const Instrument *>::iterator it = _instruments.begin(); it != _instruments.end(); ++it)
|
|
delete *it;
|
|
_instruments.clear();
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::onTimer() {
|
|
_mixMutex.unlock();
|
|
_timerMutex.lock();
|
|
|
|
if (_timerProc)
|
|
(*_timerProc)(_timerParam);
|
|
|
|
_timerMutex.unlock();
|
|
_mixMutex.lock();
|
|
|
|
for (VoiceIt it = _voices.begin(); it != _voices.end(); ++it) {
|
|
Voice *v = *it;
|
|
if (v->_note != -1) {
|
|
++v->_ticks;
|
|
if (v->_isReleased)
|
|
++v->_releaseTicks;
|
|
v->processEnvelope();
|
|
v->calcMixVelocity();
|
|
}
|
|
}
|
|
}
|
|
|
|
MidiPlayer_AmigaMac1::Voice *MidiPlayer_AmigaMac1::Channel::findVoice() {
|
|
assert(_lastVoiceIt != _driver._voices.end());
|
|
|
|
VoiceIt voiceIt = _lastVoiceIt;
|
|
uint16 maxTicks = 0;
|
|
VoiceIt maxTicksVoiceIt = _driver._voices.end();
|
|
|
|
do {
|
|
++voiceIt;
|
|
|
|
if (voiceIt == _driver._voices.end())
|
|
voiceIt = _driver._voices.begin();
|
|
|
|
Voice *v = *voiceIt;
|
|
|
|
if (v->_channel == this) {
|
|
if (v->_note == -1) {
|
|
_lastVoiceIt = voiceIt;
|
|
return v;
|
|
}
|
|
|
|
uint16 ticks;
|
|
|
|
if (v->_releaseTicks != 0)
|
|
ticks = v->_releaseTicks + 0x8000;
|
|
else
|
|
ticks = v->_ticks;
|
|
|
|
if (ticks >= maxTicks) {
|
|
maxTicks = ticks;
|
|
maxTicksVoiceIt = voiceIt;
|
|
}
|
|
}
|
|
} while (voiceIt != _lastVoiceIt);
|
|
|
|
if (maxTicksVoiceIt != _driver._voices.end()) {
|
|
(*maxTicksVoiceIt)->noteOff();
|
|
_lastVoiceIt = maxTicksVoiceIt;
|
|
return *maxTicksVoiceIt;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::Channel::voiceMapping(byte voices) {
|
|
int curVoices = 0;
|
|
|
|
for (VoiceIt it = _driver._voices.begin(); it != _driver._voices.end(); ++it)
|
|
if ((*it)->_channel == this)
|
|
curVoices++;
|
|
|
|
curVoices += _extraVoices;
|
|
|
|
if (curVoices < voices)
|
|
assignVoices(voices - curVoices);
|
|
else if (curVoices > voices) {
|
|
releaseVoices(curVoices - voices);
|
|
_driver.distributeVoices();
|
|
}
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::Channel::assignVoices(byte voices) {
|
|
for (VoiceIt it = _driver._voices.begin(); it != _driver._voices.end(); ++it) {
|
|
Voice *v = *it;
|
|
|
|
if (!v->_channel) {
|
|
v->_channel = this;
|
|
|
|
if (v->_note != -1)
|
|
v->noteOff();
|
|
|
|
if (--voices == 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
_extraVoices += voices;
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::Channel::releaseVoices(byte voices) {
|
|
if (_extraVoices >= voices) {
|
|
_extraVoices -= voices;
|
|
return;
|
|
}
|
|
|
|
voices -= _extraVoices;
|
|
_extraVoices = 0;
|
|
|
|
for (VoiceIt it = _driver._voices.begin(); it != _driver._voices.end(); ++it) {
|
|
Voice *v = *it;
|
|
|
|
if ((v->_channel == this) && (v->_note == -1)) {
|
|
v->_channel = nullptr;
|
|
if (--voices == 0)
|
|
return;
|
|
}
|
|
}
|
|
|
|
do {
|
|
uint16 maxTicks = 0;
|
|
Voice *maxTicksVoice = _driver._voices[0];
|
|
|
|
for (VoiceIt it = _driver._voices.begin(); it != _driver._voices.end(); ++it) {
|
|
Voice *v = *it;
|
|
|
|
if (v->_channel == this) {
|
|
// The original code seems to be broken here. It reads a word value from
|
|
// byte array _voiceSustained.
|
|
uint16 ticks = v->_releaseTicks;
|
|
if (ticks > 0)
|
|
ticks += 0x8000;
|
|
else
|
|
ticks = v->_ticks;
|
|
|
|
if (ticks >= maxTicks) {
|
|
maxTicks = ticks;
|
|
maxTicksVoice = v;
|
|
}
|
|
}
|
|
}
|
|
maxTicksVoice->_isSustained = false;
|
|
maxTicksVoice->noteOff();
|
|
maxTicksVoice->_channel = nullptr;
|
|
} while (--voices > 0);
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::distributeVoices() {
|
|
int freeVoices = 0;
|
|
|
|
for (VoiceIt it = _voices.begin(); it != _voices.end(); ++it)
|
|
if (!(*it)->_channel)
|
|
freeVoices++;
|
|
|
|
if (freeVoices == 0)
|
|
return;
|
|
|
|
for (ChanIt it = _channels.begin(); it != _channels.end(); ++it) {
|
|
Channel *channel = *it;
|
|
|
|
if (channel->_extraVoices != 0) {
|
|
if (channel->_extraVoices >= freeVoices) {
|
|
channel->_extraVoices -= freeVoices;
|
|
channel->assignVoices(freeVoices);
|
|
return;
|
|
} else {
|
|
freeVoices -= channel->_extraVoices;
|
|
const byte extraVoices = channel->_extraVoices;
|
|
channel->_extraVoices = 0;
|
|
channel->assignVoices(extraVoices);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::Voice::noteOn(int8 note, int8 velocity) {
|
|
_isReleased = false;
|
|
_envCurVel = 0;
|
|
_envState = kEnvStateAttack;
|
|
_envCntDown = 0;
|
|
_ticks = 0;
|
|
_releaseTicks = 0;
|
|
|
|
const int8 patchId = _channel->_patch;
|
|
|
|
// Check for valid patch
|
|
if (patchId < 0 || (uint)patchId >= _driver._instruments.size() || !_driver._instruments[patchId])
|
|
return;
|
|
|
|
const Instrument *ins = _driver._instruments[patchId];
|
|
|
|
// Each patch links to one or more waves, where each wave is assigned to a range of notes.
|
|
Common::Array<NoteRange>::const_iterator noteRange;
|
|
for (noteRange = ins->noteRange.begin(); noteRange != ins->noteRange.end(); ++noteRange) {
|
|
if (noteRange->startNote <= note && note <= noteRange->endNote)
|
|
break;
|
|
}
|
|
|
|
// Abort if this note has no wave assigned to it
|
|
if (noteRange == ins->noteRange.end())
|
|
return;
|
|
|
|
const Wave *wave = noteRange->wave;
|
|
const uint32 *freqTable = wave->freqTable;
|
|
|
|
_noteRange = noteRange;
|
|
_wave = wave;
|
|
_freqTable = freqTable;
|
|
|
|
play(note, velocity);
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::Voice::noteOff() {
|
|
stop();
|
|
_velocity = 0;
|
|
_note = -1;
|
|
_isSustained = false;
|
|
_isReleased = false;
|
|
_envState = kEnvStateAttack;
|
|
_envCntDown = 0;
|
|
_ticks = 0;
|
|
_releaseTicks = 0;
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::Voice::processEnvelope() {
|
|
if (!_noteRange->loop) {
|
|
_envCurVel = _noteRange->attackTarget;
|
|
return;
|
|
}
|
|
|
|
if (_isReleased)
|
|
_envState = kEnvStateRelease;
|
|
|
|
switch(_envState) {
|
|
case kEnvStateAttack: {
|
|
if (_envCntDown != 0) {
|
|
--_envCntDown;
|
|
return;
|
|
}
|
|
_envCntDown = _envSpeedToSkip[_noteRange->attackSpeed];
|
|
_envCurVel += _envSpeedToStep[_noteRange->attackSpeed];
|
|
if (_envCurVel >= _noteRange->attackTarget) {
|
|
_envCurVel = _noteRange->attackTarget;
|
|
_envState = kEnvStateDecay;
|
|
}
|
|
break;
|
|
}
|
|
case kEnvStateDecay: {
|
|
if (_envCntDown != 0) {
|
|
--_envCntDown;
|
|
return;
|
|
}
|
|
_envCntDown = _envSpeedToSkip[_noteRange->decaySpeed];
|
|
_envCurVel -= _envSpeedToStep[_noteRange->decaySpeed];
|
|
if (_envCurVel <= _noteRange->decayTarget) {
|
|
_envCurVel = _noteRange->decayTarget;
|
|
_envState = kEnvStateSustain;
|
|
}
|
|
break;
|
|
}
|
|
case kEnvStateSustain:
|
|
_envCurVel = _noteRange->decayTarget;
|
|
break;
|
|
case kEnvStateRelease: {
|
|
if (_envCntDown != 0) {
|
|
--_envCntDown;
|
|
return;
|
|
}
|
|
_envCntDown = _envSpeedToSkip[_noteRange->releaseSpeed];
|
|
_envCurVel -= _envSpeedToStep[_noteRange->releaseSpeed];
|
|
if (_envCurVel <= 0)
|
|
noteOff();
|
|
}
|
|
}
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::Voice::calcMixVelocity() {
|
|
byte voiceVelocity = _velocity;
|
|
|
|
if (_channel->_volume != 0) {
|
|
if (voiceVelocity != 0) {
|
|
voiceVelocity = voiceVelocity * _channel->_volume / 63;
|
|
if (_envCurVel != 0) {
|
|
voiceVelocity = voiceVelocity * _envCurVel / 63;
|
|
if (_driver._masterVolume != 0) {
|
|
voiceVelocity = voiceVelocity * (_driver._masterVolume << 2) / 63;
|
|
if (voiceVelocity == 0)
|
|
++voiceVelocity;
|
|
} else {
|
|
voiceVelocity = 0;
|
|
}
|
|
} else {
|
|
voiceVelocity = 0;
|
|
}
|
|
}
|
|
} else {
|
|
voiceVelocity = 0;
|
|
}
|
|
|
|
if (!_driver._playSwitch)
|
|
voiceVelocity = 0;
|
|
|
|
setVolume(voiceVelocity);
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::Channel::noteOn(int8 note, int8 velocity) {
|
|
if (velocity == 0) {
|
|
noteOff(note);
|
|
return;
|
|
}
|
|
|
|
for (VoiceIt it = _driver._voices.begin(); it != _driver._voices.end(); ++it) {
|
|
Voice *v = *it;
|
|
|
|
if (v->_channel == this && v->_note == note) {
|
|
v->_isSustained = false;
|
|
v->noteOff();
|
|
v->noteOn(note, velocity);
|
|
return;
|
|
}
|
|
}
|
|
|
|
Voice *v = findVoice();
|
|
if (v)
|
|
v->noteOn(note, velocity);
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::Channel::noteOff(int8 note) {
|
|
for (VoiceIt it = _driver._voices.begin(); it != _driver._voices.end(); ++it) {
|
|
Voice *v = *it;
|
|
|
|
if (v->_channel == this && v->_note == note) {
|
|
if (_hold)
|
|
v->_isSustained = true;
|
|
else {
|
|
v->_isReleased = true;
|
|
v->_envCntDown = 0;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::Channel::changePatch(int8 patch) {
|
|
_patch = patch;
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::Channel::holdPedal(int8 pedal) {
|
|
_hold = pedal;
|
|
|
|
if (pedal != 0)
|
|
return;
|
|
|
|
for (VoiceIt it = _driver._voices.begin(); it != _driver._voices.end(); ++it) {
|
|
Voice *v = *it;
|
|
|
|
if (v->_channel == this && v->_isSustained) {
|
|
v->_isSustained = false;
|
|
v->_isReleased = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::Channel::setPitchWheel(uint16 pitch) {
|
|
_pitch = pitch;
|
|
|
|
for (VoiceIt it = _driver._voices.begin(); it != _driver._voices.end(); ++it) {
|
|
Voice *v = *it;
|
|
|
|
if (v->_note != -1 && v->_channel == this)
|
|
v->calcVoiceStep();
|
|
}
|
|
}
|
|
|
|
void MidiPlayer_AmigaMac1::send(uint32 b) {
|
|
Common::StackLock lock(_mixMutex);
|
|
|
|
const byte command = b & 0xf0;
|
|
Channel *channel = _channels[b & 0xf];
|
|
const byte op1 = (b >> 8) & 0xff;
|
|
byte op2 = (b >> 16) & 0xff;
|
|
|
|
switch(command) {
|
|
case 0x80:
|
|
channel->noteOff(op1);
|
|
break;
|
|
case 0x90:
|
|
channel->noteOn(op1, op2);
|
|
break;
|
|
case 0xb0:
|
|
switch (op1) {
|
|
case 0x07:
|
|
if (op2 != 0) {
|
|
op2 >>= 1;
|
|
if (op2 == 0)
|
|
++op2;
|
|
}
|
|
channel->_volume = op2;
|
|
break;
|
|
case 0x0a:
|
|
channel->_pan = op2;
|
|
break;
|
|
case 0x40:
|
|
channel->holdPedal(op2);
|
|
break;
|
|
case 0x4b:
|
|
channel->voiceMapping(op2);
|
|
break;
|
|
case 0x7b:
|
|
for (VoiceIt it = _voices.begin(); it != _voices.end(); ++it) {
|
|
Voice *v = *it;
|
|
|
|
if (v->_channel == channel && v->_note != -1)
|
|
v->noteOff();
|
|
}
|
|
}
|
|
break;
|
|
case 0xc0:
|
|
channel->changePatch(op1);
|
|
break;
|
|
case 0xe0:
|
|
channel->setPitchWheel((op2 << 7) | op1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
const byte MidiPlayer_AmigaMac1::_envSpeedToStep[32] = {
|
|
0x40, 0x32, 0x24, 0x18, 0x14, 0x0f, 0x0d, 0x0b, 0x09, 0x08, 0x07, 0x06, 0x05, 0x0a, 0x04, 0x03,
|
|
0x05, 0x02, 0x03, 0x0b, 0x05, 0x09, 0x09, 0x01, 0x02, 0x03, 0x07, 0x05, 0x04, 0x03, 0x03, 0x02
|
|
};
|
|
|
|
const byte MidiPlayer_AmigaMac1::_envSpeedToSkip[32] = {
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
|
|
0x01, 0x00, 0x01, 0x07, 0x02, 0x05, 0x07, 0x00, 0x01, 0x02, 0x08, 0x08, 0x08, 0x09, 0x0e, 0x0b
|
|
};
|
|
|
|
const byte MidiPlayer_AmigaMac1::_velocityMap[64] = {
|
|
0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
|
|
0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
|
|
0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2a,
|
|
0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x34, 0x35, 0x37, 0x39, 0x3a, 0x3c, 0x3e, 0x40
|
|
};
|
|
|
|
class MidiPlayer_Mac1 : public Mixer_Mac<MidiPlayer_Mac1>, public MidiPlayer_AmigaMac1 {
|
|
public:
|
|
MidiPlayer_Mac1(SciVersion version, Audio::Mixer *mixer, Mixer_Mac<MidiPlayer_Mac1>::Mode mode);
|
|
|
|
// MidiPlayer
|
|
int open(ResourceManager *resMan) override;
|
|
|
|
// MidiDriver
|
|
void close() override;
|
|
|
|
// Mixer_Mac
|
|
static int8 applyChannelVolume(byte velocity, byte sample);
|
|
void interrupt() { onTimer(); }
|
|
void onChannelFinished(uint channel);
|
|
|
|
private:
|
|
static int euclDivide(int x, int y);
|
|
|
|
class MacVoice : public MidiPlayer_AmigaMac1::Voice {
|
|
public:
|
|
MacVoice(MidiPlayer_Mac1 &driver, byte id) :
|
|
MidiPlayer_AmigaMac1::Voice(driver, id),
|
|
_macDriver(driver) {}
|
|
|
|
private:
|
|
void play(int8 note, int8 velocity) override;
|
|
void stop() override;
|
|
void setVolume(byte volume) override;
|
|
bool calcVoiceStep() override;
|
|
|
|
ufrac_t calcStep(int8 note);
|
|
|
|
MidiPlayer_Mac1 &_macDriver;
|
|
};
|
|
};
|
|
|
|
MidiPlayer_Mac1::MidiPlayer_Mac1(SciVersion version, Audio::Mixer *mixer, Mixer_Mac<MidiPlayer_Mac1>::Mode mode) :
|
|
Mixer_Mac<MidiPlayer_Mac1>(mode),
|
|
MidiPlayer_AmigaMac1(version, mixer, 1480, false, _mutex) {}
|
|
|
|
int MidiPlayer_Mac1::open(ResourceManager *resMan) {
|
|
if (_isOpen)
|
|
return MidiDriver::MERR_ALREADY_OPEN;
|
|
|
|
const Resource *patch = resMan->findResource(ResourceId(kResourceTypePatch, 7), false);
|
|
if (!patch) {
|
|
warning("MidiPlayer_Mac1: Failed to open patch 7");
|
|
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
|
}
|
|
|
|
Common::MemoryReadStream stream(patch->toStream());
|
|
if (!loadInstruments(stream, false)) {
|
|
freeInstruments();
|
|
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
|
}
|
|
|
|
for (byte vi = 0; vi < kVoices; ++vi)
|
|
_voices.push_back(new MacVoice(*this, vi));
|
|
|
|
for (byte ci = 0; ci < MIDI_CHANNELS; ++ci)
|
|
_channels.push_back(new MidiPlayer_AmigaMac1::Channel(*this));
|
|
|
|
startMixer();
|
|
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO);
|
|
|
|
_isOpen = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void MidiPlayer_Mac1::close() {
|
|
MidiPlayer_AmigaMac1::close();
|
|
stopMixer();
|
|
}
|
|
|
|
void MidiPlayer_Mac1::onChannelFinished(uint channel) {
|
|
_voices[channel]->noteOff();
|
|
}
|
|
|
|
void MidiPlayer_Mac1::MacVoice::play(int8 note, int8 velocity) {
|
|
if (velocity != 0)
|
|
velocity = _velocityMap[velocity >> 1];
|
|
|
|
_velocity = velocity;
|
|
_note = note;
|
|
|
|
if (!calcVoiceStep()) {
|
|
_note = -1;
|
|
return;
|
|
}
|
|
|
|
_macDriver.setChannelVolume(_id, 0);
|
|
|
|
uint16 endOffset = _wave->phase2End;
|
|
|
|
if (endOffset == 0)
|
|
endOffset = _wave->phase1End;
|
|
|
|
uint16 loopLength = 0;
|
|
|
|
if (_wave->phase2End != 0 && _noteRange->loop)
|
|
loopLength = endOffset - _wave->phase2Start + 1;
|
|
|
|
_macDriver.setChannelData(_id, _wave->samples, _wave->phase1Start, endOffset, loopLength);
|
|
}
|
|
|
|
void MidiPlayer_Mac1::MacVoice::stop() {
|
|
_macDriver.resetChannel(_id);
|
|
}
|
|
|
|
void MidiPlayer_Mac1::MacVoice::setVolume(byte volume) {
|
|
_macDriver.setChannelVolume(_id, volume);
|
|
_macDriver.setChannelPan(_id, _channel->_pan);
|
|
}
|
|
|
|
ufrac_t MidiPlayer_Mac1::MacVoice::calcStep(int8 note) {
|
|
uint16 noteAdj = note + 127 - _wave->nativeNote;
|
|
uint16 pitch = _channel->_pitch;
|
|
pitch /= 170;
|
|
noteAdj += (pitch >> 2) - 12;
|
|
uint octaveRsh = 0;
|
|
|
|
if (noteAdj < 255)
|
|
octaveRsh = 21 - (noteAdj + 9) / 12;
|
|
|
|
noteAdj = (noteAdj + 9) % 12;
|
|
|
|
const uint freqTableIndex = (noteAdj << 2) + (pitch & 3);
|
|
assert(freqTableIndex + 8 < kFreqTableSize);
|
|
ufrac_t step = (ufrac_t)_freqTable[freqTableIndex + 4];
|
|
|
|
int16 transpose = _noteRange->transpose;
|
|
if (transpose > 0) {
|
|
ufrac_t delta = (ufrac_t)_freqTable[freqTableIndex + 8] - step;
|
|
delta >>= 4;
|
|
delta >>= octaveRsh;
|
|
delta *= transpose;
|
|
step >>= octaveRsh;
|
|
step += delta;
|
|
} else if (transpose < 0) {
|
|
ufrac_t delta = step - (ufrac_t)_freqTable[freqTableIndex];
|
|
delta >>= 4;
|
|
delta >>= octaveRsh;
|
|
delta *= -transpose;
|
|
step >>= octaveRsh;
|
|
step -= delta;
|
|
} else {
|
|
step >>= octaveRsh;
|
|
}
|
|
|
|
return step;
|
|
}
|
|
|
|
bool MidiPlayer_Mac1::MacVoice::calcVoiceStep() {
|
|
int8 note = _note;
|
|
|
|
int16 fixedNote = _noteRange->fixedNote;
|
|
if (fixedNote != -1)
|
|
note = fixedNote;
|
|
|
|
ufrac_t step = calcStep(note);
|
|
if (step == (ufrac_t)-1)
|
|
return false;
|
|
|
|
_macDriver.setChannelStep(_id, step);
|
|
|
|
return true;
|
|
}
|
|
|
|
int MidiPlayer_Mac1::euclDivide(int x, int y) {
|
|
// Assumes y > 0
|
|
if (x % y < 0)
|
|
return x / y - 1;
|
|
else
|
|
return x / y;
|
|
}
|
|
|
|
int8 MidiPlayer_Mac1::applyChannelVolume(byte volume, byte sample) {
|
|
return euclDivide((sample - 0x80) * volume, 63);
|
|
}
|
|
|
|
class MidiPlayer_Amiga1 : public Audio::Paula, public MidiPlayer_AmigaMac1 {
|
|
public:
|
|
MidiPlayer_Amiga1(SciVersion version, Audio::Mixer *mixer);
|
|
|
|
// MidiPlayer
|
|
int open(ResourceManager *resMan) override;
|
|
|
|
// MidiDriver
|
|
void close() override;
|
|
|
|
// Paula
|
|
void interrupt() override;
|
|
|
|
private:
|
|
class AmigaVoice : public MidiPlayer_AmigaMac1::Voice {
|
|
public:
|
|
AmigaVoice(MidiPlayer_Amiga1 &driver, uint id) :
|
|
MidiPlayer_AmigaMac1::Voice(driver, id),
|
|
_amigaDriver(driver) {}
|
|
|
|
void play(int8 note, int8 velocity) override;
|
|
void stop() override;
|
|
void setVolume(byte volume) override;
|
|
bool calcVoiceStep() override;
|
|
|
|
private:
|
|
uint16 calcPeriod(int8 note);
|
|
|
|
MidiPlayer_Amiga1 &_amigaDriver;
|
|
};
|
|
|
|
bool _isSci1Ega;
|
|
|
|
static const byte _velocityMapSci1Ega[64];
|
|
};
|
|
|
|
MidiPlayer_Amiga1::MidiPlayer_Amiga1(SciVersion version, Audio::Mixer *mixer) :
|
|
Paula(true, mixer->getOutputRate(), (mixer->getOutputRate() + kBaseFreq / 2) / kBaseFreq, kFilterModeA500),
|
|
MidiPlayer_AmigaMac1(version, mixer, 224, true, _mutex),
|
|
_isSci1Ega(false) {}
|
|
|
|
int MidiPlayer_Amiga1::open(ResourceManager *resMan) {
|
|
if (_isOpen)
|
|
return MidiDriver::MERR_ALREADY_OPEN;
|
|
|
|
const Resource *patch = resMan->findResource(ResourceId(kResourceTypePatch, 9), false);
|
|
|
|
if (!patch) {
|
|
patch = resMan->findResource(ResourceId(kResourceTypePatch, 5), false);
|
|
|
|
if (!patch) {
|
|
warning("MidiPlayer_Amiga1: Failed to open patch");
|
|
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
|
}
|
|
|
|
_isSci1Ega = true;
|
|
}
|
|
|
|
// SCI1 EGA banks start with a uint32 patch size, skip it
|
|
Common::MemoryReadStream stream(patch->toStream(_isSci1Ega ? 4 : 0));
|
|
if (!loadInstruments(stream, _isSci1Ega)) {
|
|
freeInstruments();
|
|
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
|
}
|
|
|
|
for (byte vi = 0; vi < kVoices; ++vi)
|
|
_voices.push_back(new AmigaVoice(*this, vi));
|
|
|
|
for (byte ci = 0; ci < MIDI_CHANNELS; ++ci)
|
|
_channels.push_back(new MidiPlayer_AmigaMac1::Channel(*this));
|
|
|
|
startPaula();
|
|
// Enable reverse stereo to counteract Audio::Paula's reverse stereo
|
|
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO, false, true);
|
|
|
|
_isOpen = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void MidiPlayer_Amiga1::close() {
|
|
MidiPlayer_AmigaMac1::close();
|
|
stopPaula();
|
|
}
|
|
|
|
void MidiPlayer_Amiga1::interrupt() {
|
|
// In the original driver, the interrupt handlers for each voice
|
|
// call voiceOff when non-looping samples are finished.
|
|
for (uint vi = 0; vi < kVoices; ++vi) {
|
|
if (_voices[vi]->_note != -1 && !_voices[vi]->_noteRange->loop && getChannelDmaCount(vi) > 0)
|
|
_voices[vi]->noteOff();
|
|
}
|
|
|
|
onTimer();
|
|
}
|
|
|
|
void MidiPlayer_Amiga1::AmigaVoice::play(int8 note, int8 velocity) {
|
|
if (velocity != 0) {
|
|
if (_amigaDriver._isSci1Ega)
|
|
velocity = _velocityMapSci1Ega[velocity >> 1];
|
|
else
|
|
velocity = _velocityMap[velocity >> 1];
|
|
}
|
|
|
|
_velocity = velocity;
|
|
_note = note;
|
|
|
|
if (!calcVoiceStep()) {
|
|
_note = -1;
|
|
return;
|
|
}
|
|
|
|
_amigaDriver.setChannelVolume(_id, 0);
|
|
|
|
// The original driver uses double buffering to play the samples. We will instead
|
|
// play the data directly. The original driver might be OB1 in end offsets and
|
|
// loop sizes on occasion. Currently, this behavior isn't taken into account, and
|
|
// the samples are played according to the meta data in the sound bank.
|
|
const int8 *samples = (const int8 *)_wave->samples;
|
|
uint16 phase1Start = _wave->phase1Start;
|
|
uint16 phase1End = _wave->phase1End;
|
|
uint16 phase2Start = _wave->phase2Start;
|
|
uint16 phase2End = _wave->phase2End;
|
|
bool loop = _noteRange->loop;
|
|
|
|
uint16 endOffset = phase2End;
|
|
|
|
if (endOffset == 0)
|
|
endOffset = phase1End;
|
|
|
|
// Paula consumes one word at a time
|
|
phase1Start &= 0xfffe;
|
|
phase2Start &= 0xfffe;
|
|
|
|
// If endOffset is odd, the sample byte at endOffset is played, otherwise it isn't
|
|
endOffset = (endOffset + 1) & 0xfffe;
|
|
|
|
int phase1Len = endOffset - phase1Start;
|
|
int phase2Len = endOffset - phase2Start;
|
|
|
|
// The original driver delays the voice start for two MIDI ticks, possibly
|
|
// due to DMA requirements
|
|
if (phase2End == 0 || !loop) {
|
|
// Non-looping
|
|
_amigaDriver.setChannelData(_id, samples + phase1Start, nullptr, phase1Len, 0);
|
|
} else {
|
|
// Looping
|
|
_amigaDriver.setChannelData(_id, samples + phase1Start, samples + phase2Start, phase1Len, phase2Len);
|
|
}
|
|
}
|
|
|
|
void MidiPlayer_Amiga1::AmigaVoice::stop() {
|
|
_amigaDriver.clearVoice(_id);
|
|
}
|
|
|
|
void MidiPlayer_Amiga1::AmigaVoice::setVolume(byte volume) {
|
|
_amigaDriver.setChannelVolume(_id, volume);
|
|
}
|
|
|
|
uint16 MidiPlayer_Amiga1::AmigaVoice::calcPeriod(int8 note) {
|
|
uint16 noteAdj = note + 127 - _wave->nativeNote;
|
|
uint16 pitch = _channel->_pitch;
|
|
pitch /= 170;
|
|
noteAdj += (pitch >> 2) - 12;
|
|
|
|
// SCI1 EGA is off by one note
|
|
if (_amigaDriver._isSci1Ega)
|
|
++noteAdj;
|
|
|
|
const uint octaveRsh = noteAdj / 12;
|
|
noteAdj %= 12;
|
|
|
|
const uint freqTableIndex = (noteAdj << 2) + (pitch & 3);
|
|
assert(freqTableIndex + 8 < kFreqTableSize);
|
|
uint32 period = _freqTable[freqTableIndex + 4];
|
|
|
|
int16 transpose = _noteRange->transpose;
|
|
if (transpose > 0) {
|
|
uint32 delta = period - _freqTable[freqTableIndex + 8];
|
|
delta >>= 4;
|
|
delta *= transpose;
|
|
period -= delta;
|
|
} else if (transpose < 0) {
|
|
uint32 delta = _freqTable[freqTableIndex] - period;
|
|
delta >>= 4;
|
|
delta *= -transpose;
|
|
period += delta;
|
|
}
|
|
|
|
period >>= octaveRsh;
|
|
|
|
if (period < 0x7c || period > 0xffff)
|
|
return (uint16)-1;
|
|
|
|
return period;
|
|
}
|
|
|
|
bool MidiPlayer_Amiga1::AmigaVoice::calcVoiceStep() {
|
|
int8 note = _note;
|
|
|
|
int16 fixedNote = _noteRange->fixedNote;
|
|
if (fixedNote != -1)
|
|
note = fixedNote;
|
|
|
|
uint16 period = calcPeriod(note);
|
|
if (period == (uint16)-1)
|
|
return false;
|
|
|
|
// Audio::Paula uses int16 instead of uint16?
|
|
_amigaDriver.setChannelPeriod(_id, period);
|
|
|
|
return true;
|
|
}
|
|
|
|
const byte MidiPlayer_Amiga1::_velocityMapSci1Ega[64] = {
|
|
0x01, 0x04, 0x07, 0x0a, 0x0c, 0x0f, 0x11, 0x15, 0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x21, 0x22, 0x23,
|
|
0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x30, 0x31, 0x31,
|
|
0x32, 0x32, 0x33, 0x33, 0x34, 0x34, 0x35, 0x35, 0x36, 0x36, 0x37, 0x37, 0x38, 0x38, 0x38, 0x39,
|
|
0x39, 0x39, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b, 0x3c, 0x3d, 0x3e, 0x3e, 0x3f, 0x3f, 0x40, 0x40
|
|
};
|
|
|
|
MidiPlayer *MidiPlayer_AmigaMac1_create(SciVersion version, Common::Platform platform) {
|
|
if (platform == Common::kPlatformMacintosh)
|
|
return new MidiPlayer_Mac1(version, g_system->getMixer(), Mixer_Mac<MidiPlayer_Mac1>::kModeHqStereo);
|
|
else
|
|
return new MidiPlayer_Amiga1(version, g_system->getMixer());
|
|
}
|
|
|
|
} // End of namespace Sci
|