-
Notifications
You must be signed in to change notification settings - Fork 1
/
WaveHC.cpp
425 lines (376 loc) · 11 KB
/
WaveHC.cpp
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
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
/*
William Greiman's modified version of Ladyada's wave shield library
I have made many changes that may have introduced bugs. Major changes are:
optimized DAC macros to allow 44.1 k 16-bit files
use of FatReader to read FAT32 and FAT16 files
modified readwavhack to be readWaveData
use standard SD and SDHC flash cards.
skip non-data chunks after fmt chunk
allow 18 byte format chunk if no compression
play stereo as mono by interleaving channels
change method of reading fmt chunk - use union of structs
*/
#include <string.h>
#include <avr/interrupt.h>
#include <mcpDac.h>
#include <WaveHC.h>
#include <WaveUtil.h>
// verify program assumptions
#if PLAYBUFFLEN != 256 && PLAYBUFFLEN != 512
#error PLAYBUFFLEN must be 256 or 512
#endif // PLAYBUFFLEN
WaveHC *playing = 0;
uint8_t buffer1[PLAYBUFFLEN];
uint8_t buffer2[PLAYBUFFLEN];
uint8_t *playend; // end position for current buffer
uint8_t *playpos; // position of next sample
uint8_t *sdbuff; // SD fill buffer
uint8_t *sdend; // end of data in sd buffer
// status of sd
#define SD_READY 1 // buffer is ready to be played
#define SD_FILLING 2 // buffer is being filled from DS
#define SD_END_FILE 3 // reached end of file
uint8_t sdstatus = 0;
//------------------------------------------------------------------------------
// timer interrupt for DAC
ISR(TIMER1_COMPA_vect) {
if (!playing) return;
if (playpos >= playend) {
if (sdstatus == SD_READY) {
// swap double buffers
playpos = sdbuff;
playend = sdend;
sdbuff = sdbuff != buffer1 ? buffer1 : buffer2;
sdstatus = SD_FILLING;
// interrupt to call SD reader
TIMSK1 |= _BV(OCIE1B);
}
else if (sdstatus == SD_END_FILE) {
playing->stop();
return;
}
else {
// count overrun error if not at end of file
if (playing->remainingBytesInChunk) {
playing->errors++;
}
return;
}
}
uint8_t dh, dl;
if (playing->BitsPerSample == 16) {
// 16-bit is signed
dh = 0X80 ^ playpos[1];
dl = playpos[0];
playpos += 2;
}
else {
// 8-bit is unsigned
dh = playpos[0];
dl = 0;
playpos++;
}
#if DVOLUME
uint16_t tmp = (dh << 8) | dl;
tmp >>= playing->volume;
dh = tmp >> 8;
dl = tmp;
#endif //DVOLUME
// dac chip select low
mcpDacCsLow();
// send DAC config bits
mcpDacSdiLow();
mcpDacSckPulse(); // DAC A
mcpDacSckPulse(); // unbuffered
mcpDacSdiHigh();
mcpDacSckPulse(); // 1X gain
mcpDacSckPulse(); // no SHDN
// send high 8 bits
mcpDacSendBit(dh, 7);
mcpDacSendBit(dh, 6);
mcpDacSendBit(dh, 5);
mcpDacSendBit(dh, 4);
mcpDacSendBit(dh, 3);
mcpDacSendBit(dh, 2);
mcpDacSendBit(dh, 1);
mcpDacSendBit(dh, 0);
// send low 4 bits
mcpDacSendBit(dl, 7);
mcpDacSendBit(dl, 6);
mcpDacSendBit(dl, 5);
mcpDacSendBit(dl, 4);
// chip select high - done
mcpDacCsHigh();
}
//------------------------------------------------------------------------------
// this is the interrupt that fills the playbuffer
ISR(TIMER1_COMPB_vect) {
// turn off calling interrupt
TIMSK1 &= ~_BV(OCIE1B);
if (sdstatus != SD_FILLING) return;
// enable interrupts while reading the SD
sei();
int16_t read = playing->readWaveData(sdbuff, PLAYBUFFLEN);
cli();
if (read > 0) {
sdend = sdbuff + read;
sdstatus = SD_READY;
}
else {
sdend = sdbuff;
sdstatus = SD_END_FILE;
}
}
//------------------------------------------------------------------------------
/** create an instance of WaveHC. */
WaveHC::WaveHC(void) {
fd = 0;
}
//------------------------------------------------------------------------------
/**
* Read a wave file's metadata and initialize member variables.
*
* \param[in] f A open FatReader instance for the wave file.
*
* \return The value one, true, is returned for success and
* the value zero, false, is returned for failure. Reasons
* for failure include I/O error, an invalid wave file or a wave
* file with features that WaveHC does not support.
*/
uint8_t WaveHC::create(FatReader &f) {
// 18 byte buffer
// can use this since Arduino and RIFF are Little Endian
union {
struct {
char id[4];
uint32_t size;
char data[4];
} riff; // riff chunk
struct {
uint16_t compress;
uint16_t channels;
uint32_t sampleRate;
uint32_t bytesPerSecond;
uint16_t blockAlign;
uint16_t bitsPerSample;
uint16_t extraBytes;
} fmt; // fmt data
} buf;
#if OPTIMIZE_CONTIGUOUS
// set optimized read for contiguous files
f.optimizeContiguous();
#endif // OPTIMIZE_CONTIGUOUS
// must start with WAVE header
if (f.read(&buf, 12) != 12
|| strncmp(buf.riff.id, "RIFF", 4)
|| strncmp(buf.riff.data, "WAVE", 4)) {
return false;
}
// next chunk must be fmt
if (f.read(&buf, 8) != 8
|| strncmp(buf.riff.id, "fmt ", 4)) {
return false;
}
// fmt chunk size must be 16 or 18
uint16_t size = buf.riff.size;
if (size == 16 || size == 18) {
if (f.read(&buf, size) != (int16_t)size) {
return false;
}
}
else {
// compressed data - force error
buf.fmt.compress = 0;
}
if (buf.fmt.compress != 1 || (size == 18 && buf.fmt.extraBytes != 0)) {
putstring_nl("Compression not supported");
return false;
}
Channels = buf.fmt.channels;
if (Channels > 2) {
putstring_nl("Not mono/stereo!");
return false;
}
else if (Channels > 1) {
putstring_nl(" Warning stereo file!");
}
BitsPerSample = buf.fmt.bitsPerSample;
if (BitsPerSample > 16) {
putstring_nl("More than 16 bits per sample!");
return false;
}
dwSamplesPerSec = buf.fmt.sampleRate;
uint32_t clockRate = dwSamplesPerSec*Channels;
uint32_t byteRate = clockRate*BitsPerSample/8;
#if RATE_ERROR_LEVEL > 0
if (clockRate > MAX_CLOCK_RATE
|| byteRate > MAX_BYTE_RATE) {
putstring_nl("Sample rate too high!");
if (RATE_ERROR_LEVEL > 1) {
return false;
}
}
else if (byteRate > 44100 && !f.isContiguous()) {
putstring_nl("High rate fragmented file!");
if (RATE_ERROR_LEVEL > 1) {
return false;
}
}
#endif // RATE_ERROR_LEVEL > 0
fd = &f;
errors = 0;
isplaying = 0;
remainingBytesInChunk = 0;
#if DVOLUME
volume = 0;
#endif //DVOLUME
// position to data
return readWaveData(0, 0) < 0 ? false: true;
}
//------------------------------------------------------------------------------
/**
* Returns true if the player is paused else false.
*/
uint8_t WaveHC::isPaused(void) {
cli();
uint8_t rtn = isplaying && !(TIMSK1 & _BV(OCIE1A));
sei();
return rtn;
}
//------------------------------------------------------------------------------
/**
* Pause the player.
*/
void WaveHC::pause(void) {
cli();
TIMSK1 &= ~_BV(OCIE1A); //disable DAC interrupt
sei();
fd->volume()->rawDevice()->readEnd(); // redo any partial read on resume
}
//------------------------------------------------------------------------------
/**
* Play a wave file.
*
* WaveHC::create() must be called before a file can be played.
*
* Check the member variable WaveHC::isplaying to monitor the status
* of the player.
*/
void WaveHC::play(void) {
// setup the interrupt as necessary
int16_t read;
playing = this;
// fill the play buffer
read = readWaveData(buffer1, PLAYBUFFLEN);
if (read <= 0) return;
playpos = buffer1;
playend = buffer1 + read;
// fill the second buffer
read = readWaveData(buffer2, PLAYBUFFLEN);
if (read < 0) return;
sdbuff = buffer2;
sdend = sdbuff + read;
sdstatus = SD_READY;
// its official!
isplaying = 1;
// Setup mode for DAC ports
mcpDacInit();
// Set up timer one
// Normal operation - no pwm not connected to pins
TCCR1A = 0;
// no prescaling, CTC mode
TCCR1B = _BV(WGM12) | _BV(CS10);
// Sample rate - play stereo interleaved
OCR1A = F_CPU / (dwSamplesPerSec*Channels);
// SD fill interrupt happens at TCNT1 == 1
OCR1B = 1;
// Enable timer interrupt for DAC ISR
TIMSK1 |= _BV(OCIE1A);
}
//------------------------------------------------------------------------------
/** Read wave data.
*
* Not for use in applications. Must be public so SD read ISR can access it.
* Insures SD sectors are aligned with buffers.
*/
int16_t WaveHC::readWaveData(uint8_t *buff, uint16_t len) {
if (remainingBytesInChunk == 0) {
struct {
char id[4];
uint32_t size;
} header;
while (1) {
if (fd->read(&header, 8) != 8) return -1;
if (!strncmp(header.id, "data", 4)) {
remainingBytesInChunk = header.size;
break;
}
// if not "data" then skip it!
if (!fd->seekCur(header.size)) {
return -1;
}
}
}
// make sure buffers are aligned on SD sectors
uint16_t maxLen = PLAYBUFFLEN - fd->readPosition() % PLAYBUFFLEN;
if (len > maxLen) len = maxLen;
if (len > remainingBytesInChunk) {
len = remainingBytesInChunk;
}
int16_t ret = fd->read(buff, len);
if (ret > 0) remainingBytesInChunk -= ret;
return ret;
}
//------------------------------------------------------------------------------
/** Resume a paused player. */
void WaveHC::resume(void) {
cli();
// enable DAC interrupt
if(isplaying) TIMSK1 |= _BV(OCIE1A);
sei();
}
//------------------------------------------------------------------------------
/**
* Reposition a wave file.
*
* \param[in] pos seek will attempt to position the file near \a pos.
* \a pos is the byte number from the beginning of file.
*/
void WaveHC::seek(uint32_t pos) {
// make sure buffer fill interrupt doesn't happen
cli();
if (fd) {
pos -= pos % PLAYBUFFLEN;
if (pos < PLAYBUFFLEN) pos = PLAYBUFFLEN; //don't play metadata
uint32_t maxPos = fd->readPosition() + remainingBytesInChunk;
if (maxPos > fd->fileSize()) maxPos = fd->fileSize();
if (pos > maxPos) pos = maxPos;
if (fd->seekSet(pos)) {
// assumes a lot about the wave file
remainingBytesInChunk = maxPos - pos;
}
}
sei();
}
//------------------------------------------------------------------------------
/** Set the player's sample rate.
*
* \param[in] samplerate The new sample rate in samples per second.
* No checks are done on the input parameter.
*/
void WaveHC::setSampleRate(uint32_t samplerate) {
if (samplerate < 500) samplerate = 500;
if (samplerate > 50000) samplerate = 50000;
// from ladayada's library.
cli();
while (TCNT0 != 0);
OCR1A = F_CPU / samplerate;
sei();
}
//------------------------------------------------------------------------------
/** Stop the player. */
void WaveHC::stop(void) {
TIMSK1 &= ~_BV(OCIE1A); // turn off interrupt
playing->isplaying = 0;
playing = 0;
}