aboutsummaryrefslogtreecommitdiff
path: root/engines/agi/sound_2gs.h
blob: 49a375cdbc32a13fc6bb064bc568fdcd42da49eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
/* 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.
 *
 */

#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() { if (_data != NULL) free(_data); }
	virtual uint16 type() { 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() { delete[] _sample; }
	virtual uint16 type() { 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();

	void play(int resnum);
	void stop(void);

	int readBuffer(int16 *buffer, const int numSamples);

	bool isStereo() const { return true; }
	bool endOfData() const { return false; }
	int getRate() const { 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 */