aboutsummaryrefslogtreecommitdiff
path: root/engines/scumm/player_apple2.h
blob: b4a7d409fbed18d99cfa3756b1db703ec06eca49 (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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
/* 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 SCUMM_PLAYER_APPLEII_H
#define SCUMM_PLAYER_APPLEII_H

#include "common/mutex.h"
#include "common/scummsys.h"
#include "common/memstream.h"
#include "scumm/music.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "audio/softsynth/sid.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;
};

class Player_AppleII : public Audio::AudioStream, public MusicEngine {
public:
	Player_AppleII(ScummEngine *scumm, Audio::Mixer *mixer);
	virtual ~Player_AppleII();

	virtual void setMusicVolume(int vol) { _sampleConverter.setMusicVolume(vol); }
	void setSampleRate(int rate) {
		_sampleRate = rate;
		_sampleConverter.setSampleRate(rate); 
	}
	virtual void startSound(int sound);
	virtual void stopSound(int sound);
	virtual void stopAllSounds();
	virtual int  getSoundStatus(int sound) const;
	virtual int  getMusicTimer();

	// AudioStream API
	int readBuffer(int16 *buffer, const int numSamples);
	bool isStereo() const { return false; }
	bool endOfData() const { return false; }
	int getRate() const { return _sampleRate; }

public:
	void speakerToggle();
	void generateSamples(int cycles);
	void wait(int interval, int count);

private:
	// sound number
	int _soundNr;
	// type of sound
	int _type;
	// number of loops left
	int _loop;
	// global sound param list
	const byte *_params;
	// speaker toggle state (0 / 1)
	byte _speakerState;
	// sound function
	AppleII_SoundFunction *_soundFunc;
	// cycle to sample converter
	SampleConverter _sampleConverter;

private:
	ScummEngine *_vm;
	Audio::Mixer *_mixer;
	Audio::SoundHandle _soundHandle;
	int _sampleRate;
	Common::Mutex _mutex;

private:
	void resetState();
	bool updateSound();
};

} // End of namespace Scumm

#endif