mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 13:50:13 +00:00
276 lines
11 KiB
C++
276 lines
11 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/>.
|
|
*
|
|
*/
|
|
|
|
#ifndef AGI_SOUND_2GS_H
|
|
#define AGI_SOUND_2GS_H
|
|
|
|
#include "common/frac.h"
|
|
#include "audio/audiostream.h"
|
|
|
|
namespace Agi {
|
|
|
|
// Sample data in SIERRASTANDARD files is in unsigned 8-bit format. A zero
|
|
// occurring in the sample data causes the ES5503 wavetable sound chip in
|
|
// Apple IIGS to halt the corresponding oscillator immediately. We preprocess
|
|
// the sample data by converting it to signed values and the instruments by
|
|
// detecting prematurely stopping samples beforehand.
|
|
//
|
|
// Note: None of the tested SIERRASTANDARD files have zeroes in them. So in
|
|
// practice there is no need to check for them. However, they still do exist
|
|
// in the sample resources.
|
|
#define ZERO_OFFSET 0x80
|
|
|
|
// Apple IIGS envelope update frequency defaults to 100Hz. It can be changed,
|
|
// so there might be differences per game, for example.
|
|
#define ENVELOPE_COEF 100 / _sampleRate
|
|
|
|
// MIDI player commands
|
|
#define MIDI_NOTE_OFF 0x8
|
|
#define MIDI_NOTE_ON 0x9
|
|
#define MIDI_CONTROLLER 0xB
|
|
#define MIDI_PROGRAM_CHANGE 0xC
|
|
#define MIDI_PITCH_WHEEL 0xE
|
|
|
|
#define MIDI_STOP_SEQUENCE 0xFC
|
|
#define MIDI_TIMER_SYNC 0xF8
|
|
|
|
// Size of the SIERRASTANDARD file (i.e. the wave file i.e. the sample data used by the instruments).
|
|
#define SIERRASTANDARD_SIZE 65536
|
|
|
|
// Maximum number of instruments in an Apple IIGS instrument set.
|
|
// Chosen empirically based on Apple IIGS AGI game data, increase if needed.
|
|
#define MAX_INSTRUMENTS 28
|
|
|
|
// The MIDI player allocates one generator for each note it starts to play.
|
|
// Here the maximum number of generators is defined. Feel free to increase
|
|
// this if it does not seem to be enough.
|
|
#define MAX_GENERATORS 16
|
|
|
|
#define ENVELOPE_SEGMENT_COUNT 8
|
|
#define MAX_OSCILLATOR_WAVES 127 // Maximum is one for every MIDI key
|
|
|
|
struct IIgsInstrumentHeader {
|
|
struct {
|
|
frac_t bp; ///< Envelope segment breakpoint
|
|
frac_t inc; ///< Envelope segment velocity
|
|
} env[ENVELOPE_SEGMENT_COUNT];
|
|
uint8 seg; ///< Envelope release segment
|
|
uint8 bend; ///< Maximum range for pitch bend
|
|
uint8 vibDepth; ///< Vibrato depth
|
|
uint8 vibSpeed; ///< Vibrato speed
|
|
uint8 waveCount[2]; ///< Wave count for both generators
|
|
struct {
|
|
uint8 key; ///< Highest MIDI key to use this wave
|
|
uint32 offset; ///< Offset of wave data, relative to base
|
|
uint32 size; ///< Wave size
|
|
bool halt; ///< Oscillator halted?
|
|
bool loop; ///< Loop mode?
|
|
bool swap; ///< Swap mode?
|
|
bool rightChannel; ///< Output channel (left / right)
|
|
int16 tune; ///< Fine tune in semitones (8.8 fixed point)
|
|
} wave[2][MAX_OSCILLATOR_WAVES];
|
|
|
|
int8 *wavetableBase; ///< Base of wave data
|
|
|
|
/**
|
|
* Read an Apple IIGS instrument header from the given stream.
|
|
* @param stream The source stream from which to read the data.
|
|
* @param ignoreAddr Should we ignore wave infos' wave address variable's value?
|
|
* @return True if successful, false otherwise.
|
|
*/
|
|
bool read(Common::SeekableReadStream &stream, bool ignoreAddr = false);
|
|
bool finalize(int8 *wavetable, uint32 wavetableSize);
|
|
};
|
|
|
|
struct IIgsSampleHeader {
|
|
uint16 type;
|
|
uint8 pitch; ///< Logarithmic, base is 2**(1/12), unknown multiplier (Possibly in range 1040-1080)
|
|
uint8 unknownByte_Ofs3; // 0x7F in Gold Rush's sound resource 60, 0 in all others.
|
|
uint8 volume; ///< Current guess: Logarithmic in 6 dB steps
|
|
uint8 unknownByte_Ofs5; ///< 0 in all tested samples.
|
|
uint16 instrumentSize; ///< 44 in all tested samples. A guess.
|
|
uint16 sampleSize; ///< Accurate in all tested samples excluding Manhunter I's sound resource 16.
|
|
IIgsInstrumentHeader instrument;
|
|
|
|
/**
|
|
* Read an Apple IIGS AGI sample header from the given stream.
|
|
* @param stream The source stream from which to read the data.
|
|
* @return True if successful, false otherwise.
|
|
*/
|
|
bool read(Common::SeekableReadStream &stream);
|
|
bool finalize(int8 *sampleData);
|
|
};
|
|
|
|
class IIgsGenerator {
|
|
public:
|
|
IIgsGenerator() : curInstrument(nullptr), key(-1), channel(-1) {
|
|
memset(&osc, 0, sizeof(osc));
|
|
seg = 0;
|
|
a = 0;
|
|
velocity = 0;
|
|
}
|
|
|
|
const IIgsInstrumentHeader *curInstrument; ///< Currently used instrument
|
|
int key; ///< MIDI key
|
|
int velocity; ///< MIDI velocity (& channel volume)
|
|
int channel; ///< MIDI channel
|
|
struct {
|
|
int8 *base; ///< Sample base pointer
|
|
uint size; ///< Sample size
|
|
frac_t p; ///< Sample pointer
|
|
frac_t pd; ///< Sample pointer delta
|
|
bool halt; ///< Is oscillator halted?
|
|
bool loop; ///< Is looping enabled?
|
|
bool swap; ///< Is swapping enabled?
|
|
bool rightChannel; ///< Output channel (left / right)
|
|
} osc[2];
|
|
int seg; ///< Current envelope segment
|
|
frac_t a; ///< Current envelope amplitude
|
|
};
|
|
|
|
class IIgsMidi : public AgiSound {
|
|
public:
|
|
IIgsMidi(uint8 *data, uint32 len, int resnum);
|
|
~IIgsMidi() override { if (_data != NULL) free(_data); }
|
|
uint16 type() override { return _type; }
|
|
virtual const uint8 *getPtr() { return _ptr; }
|
|
virtual void setPtr(const uint8 *ptr) { _ptr = ptr; }
|
|
virtual void rewind() { _ptr = _data + 2; _ticks = 0; }
|
|
protected:
|
|
uint8 *_data; ///< Raw sound resource data
|
|
const uint8 *_ptr; ///< Pointer to the current position in the MIDI data
|
|
uint32 _len; ///< Length of the raw sound resource
|
|
uint16 _type; ///< Sound resource type
|
|
public:
|
|
uint _ticks; ///< MIDI song position in ticks (1/60ths of a second)
|
|
};
|
|
|
|
class IIgsSample : public AgiSound {
|
|
public:
|
|
IIgsSample(uint8 *data, uint32 len, int16 resourceNr);
|
|
~IIgsSample() override { delete[] _sample; }
|
|
uint16 type() override { return _header.type; }
|
|
const IIgsSampleHeader &getHeader() const { return _header; }
|
|
protected:
|
|
IIgsSampleHeader _header; ///< Apple IIGS AGI sample header
|
|
int8 *_sample; ///< Sample data (8-bit signed format)
|
|
};
|
|
|
|
/** Apple IIGS MIDI program change to instrument number mapping. */
|
|
struct IIgsMidiProgramMapping {
|
|
byte midiProgToInst[44]; ///< Lookup table for the MIDI program number to instrument number mapping
|
|
byte undefinedInst; ///< The undefined instrument number
|
|
|
|
// Maps the MIDI program number to an instrument number
|
|
byte map(uint midiProg) const {
|
|
return midiProg < ARRAYSIZE(midiProgToInst) ? midiProgToInst[midiProg] : undefinedInst;
|
|
}
|
|
};
|
|
|
|
/** Apple IIGS AGI instrument set information. */
|
|
struct IIgsInstrumentSetInfo {
|
|
uint byteCount; ///< Length of the whole instrument set in bytes
|
|
uint instCount; ///< Amount of instrument in the set
|
|
const char *md5; ///< MD5 hex digest of the whole instrument set
|
|
const char *waveFileMd5; ///< MD5 hex digest of the wave file (i.e. the sample data used by the instruments)
|
|
const IIgsMidiProgramMapping *progToInst; ///< Program change to instrument number mapping
|
|
};
|
|
|
|
/** Apple IIGS AGI executable file information. */
|
|
struct IIgsExeInfo {
|
|
enum AgiGameID gameid; ///< Game ID
|
|
const char *exePrefix; ///< Prefix of the Apple IIGS AGI executable (e.g. "SQ", "PQ", "KQ4" etc)
|
|
uint agiVer; ///< Apple IIGS AGI version number, not strictly needed
|
|
uint exeSize; ///< Size of the Apple IIGS AGI executable file in bytes
|
|
uint instSetStart; ///< Starting offset of the instrument set inside the executable file
|
|
const IIgsInstrumentSetInfo *instSet; ///< Information about the used instrument set
|
|
};
|
|
|
|
class IIgsMidiChannel {
|
|
public:
|
|
IIgsMidiChannel() : _instrument(NULL), _volume(127) {}
|
|
void setInstrument(const IIgsInstrumentHeader *instrument) { _instrument = instrument; }
|
|
const IIgsInstrumentHeader *getInstrument() { return _instrument; }
|
|
void setVolume(int volume) { _volume = volume; }
|
|
int getVolume() { return _volume; }
|
|
private:
|
|
const IIgsInstrumentHeader *_instrument; ///< Instrument used on this MIDI channel
|
|
int _volume; ///< MIDI controller number 7 (Volume)
|
|
};
|
|
|
|
class SoundGen2GS : public SoundGen, public Audio::AudioStream {
|
|
public:
|
|
SoundGen2GS(AgiBase *vm, Audio::Mixer *pMixer);
|
|
~SoundGen2GS() override;
|
|
|
|
void play(int resnum) override;
|
|
void stop(void) override;
|
|
|
|
int readBuffer(int16 *buffer, const int numSamples) override;
|
|
|
|
bool isStereo() const override { return true; }
|
|
bool endOfData() const override { return false; }
|
|
int getRate() const override { return _sampleRate; }
|
|
|
|
private:
|
|
// Loader methods
|
|
bool loadInstruments();
|
|
bool loadInstrumentHeaders(Common::String &exePath, const IIgsExeInfo &exeInfo);
|
|
bool loadWaveFile(Common::String &wavePath, const IIgsExeInfo &exeInfo);
|
|
|
|
const IIgsExeInfo *getIIgsExeInfo(enum AgiGameID gameid) const;
|
|
void setProgramChangeMapping(const IIgsMidiProgramMapping *mapping);
|
|
|
|
// Player methods
|
|
void advancePlayer(); ///< Advance the player
|
|
void advanceMidiPlayer(); ///< Advance MIDI player
|
|
uint generateOutput(); ///< Fill the output buffer
|
|
|
|
void haltGenerators(); ///< Halt all generators
|
|
uint activeGenerators(); ///< How many generators are active?
|
|
|
|
void midiNoteOff(int channel, int note, int velocity);
|
|
void midiNoteOn(int channel, int note, int velocity);
|
|
double midiKeyToFreq(int key, double finetune);
|
|
IIgsInstrumentHeader *getInstrument(uint8 program) { return &_instruments[_progToInst->map(program)]; }
|
|
IIgsGenerator *allocateGenerator() { IIgsGenerator *g = &_generators[_nextGen++]; _nextGen %= 16; return g; }
|
|
|
|
bool _disableMidi; ///< Disable MIDI if loading instruments fail
|
|
int _playingSound; ///< Resource number for the currently playing sound
|
|
bool _playing; ///< True when the resource is still playing
|
|
|
|
IIgsGenerator _generators[MAX_GENERATORS]; ///< IIGS sound generators that are used to play single notes
|
|
uint _nextGen; ///< Next generator available for allocation
|
|
IIgsMidiChannel _channels[16]; ///< MIDI channels
|
|
Common::Array<IIgsInstrumentHeader> _instruments; ///< Instrument data
|
|
const IIgsMidiProgramMapping *_progToInst; ///< MIDI program number to instrument mapping
|
|
int8 *_wavetable; ///< Sample data used by the instruments
|
|
uint _ticks; ///< MIDI ticks (60Hz)
|
|
int16 *_out; ///< Output buffer
|
|
uint _outSize; ///< Output buffer size
|
|
|
|
static const int kSfxMidiChannel = 15; ///< MIDI channel used for playing sample resources
|
|
};
|
|
|
|
} // End of namespace Agi
|
|
|
|
#endif /* AGI_SOUND_2GS_H */
|