1
2 #include "common.h"
3 #include "Timbre.h"
4 #include "Envelope.h"
5
6 #include "minorGems/util/SimpleVector.h"
7
8
9
10 #include <SDL/SDL.h>
11 #include <SDL/SDL_audio.h>
12
13 #include <math.h>
14 #include <stdlib.h>
15
16 int sampleRate = 22050;
17 //int sampleRate = 11025;
18
19
20
21
22 Image *musicImage = NULL;
23 int w, h;
24
25 // total number of samples played so far
26 int streamSamples = 0;
27
28 // offset into grid at start
29 // for testing
30 int gridStartOffset = 0;
31
32
33
34 // overal loudness of music
35 double musicLoudness = 1.0;
36
37
38
39
40
41
42 // one grid step in seconds
43 double gridStepDuration = 0.25;
44 int gridStepDurationInSamples = (int)( gridStepDuration * sampleRate );
45
46 double entireGridDuraton;
47
48
49 // c
50 double keyFrequency = 261.63;
51
52
53 int numTimbres = 4;
54
55 Timbre *musicTimbres[ 4 ];
56
57 int numEnvelopes = 4;
58
59 Envelope *musicEnvelopes[ 4 ];
60
61
62
63 class Note {
64 public:
65 // index into musicTimbres array
66 int mTimbreNumber;
67
68 // index into musicEnvelopes array
69 int mEnvelopeNumber;
70
71 int mScaleNoteNumber;
72
73 // additional loudness adjustment
74 // places note in stereo space
75 double mLoudnessLeft;
76 double mLoudnessRight;
77
78
79 // start time, in seconds from start of note grid
80 double mStartTime;
81
82 // duration in seconds
83 double mDuration;
84
85 // used when note is currently playing to track progress in note
86 // negative if we should wait before starting to play the note
87 int mCurrentSampleNumber;
88
89 // duration in samples
90 int mNumSamples;
91
92
93 };
94
95
96 // isomorphic to our music image, except only has an entry for each note
97 // start (all others, including grid spots that contain note continuations,
98 // are NULL)
99 // indexed as noteGrid[y][x]
100 Note ***noteGrid;
101
102
103 SimpleVector<Note*> currentlyPlayingNotes;
104
105
106
107 // need to synch these with audio thread
108
setMusicLoudness(double inLoudness)109 void setMusicLoudness( double inLoudness ) {
110 SDL_LockAudio();
111
112 musicLoudness = inLoudness;
113
114 SDL_UnlockAudio();
115 }
116
117
118
restartMusic()119 void restartMusic() {
120 SDL_LockAudio();
121
122 // return to beginning (and forget samples we've played so far)
123 streamSamples = 0;
124
125 // drop all currently-playing notes
126 currentlyPlayingNotes.deleteAll();
127
128 SDL_UnlockAudio();
129 }
130
131
132
133
134 // called by SDL to get more samples
audioCallback(void * inUserData,Uint8 * inStream,int inLengthToFill)135 void audioCallback( void *inUserData, Uint8 *inStream, int inLengthToFill ) {
136
137 // 2 bytes for each channel of stereo sample
138 int numSamples = inLengthToFill / 4;
139
140
141 Sint16 *samplesL = new Sint16[ numSamples ];
142 Sint16 *samplesR = new Sint16[ numSamples ];
143
144 // first, zero-out the buffer to prepare it for our sum of note samples
145 // each sample is 2 bytes
146 memset( samplesL, 0, 2 * numSamples );
147 memset( samplesR, 0, 2 * numSamples );
148
149
150 int i;
151
152
153 // hop through all grid steps that *start* in this stream buffer
154 // add notes that start during this stream buffer
155
156 // how far into stream buffer before we hit our first grid step?
157 int startOfFirstGridStep = streamSamples % gridStepDurationInSamples;
158
159 if( startOfFirstGridStep != 0 ) {
160 startOfFirstGridStep =
161 gridStepDurationInSamples - startOfFirstGridStep;
162 }
163
164
165 // hop from start of grid step to start of next grid step
166 // ignore samples in between, since notes don't start there,
167 // and all we're doing right now is finding notes that start
168 for( i=startOfFirstGridStep;
169 i<numSamples;
170 i += gridStepDurationInSamples ) {
171
172 // start of new grid position
173
174 // check for new notes that are starting
175
176 // map into our music image:
177 int x = ( streamSamples + i ) / gridStepDurationInSamples;
178
179 // wrap in image
180 x = x % w;
181
182 for( int y=0; y<h; y++ ) {
183
184 Note *note = noteGrid[y][x];
185
186 if( note != NULL ) {
187 // new note
188 currentlyPlayingNotes.push_back( note );
189 // start it
190
191 // set a delay for its start based on our position
192 // in this callback buffer
193 note->mCurrentSampleNumber = -i;
194 }
195 }
196 }
197
198 streamSamples += numSamples;
199
200
201 // loop over all current notes and add their samples to buffer
202
203 for( int n=0; n<currentlyPlayingNotes.size(); n++ ) {
204
205 Note *note = *( currentlyPlayingNotes.getElement( n ) );
206
207 int waveTableNumber = note->mScaleNoteNumber;
208 Timbre *timbre = musicTimbres[ note->mTimbreNumber ];
209 int tableLength = timbre->mWaveTableLengths[ waveTableNumber ];
210
211 Sint16 *waveTable = timbre->mWaveTable[ waveTableNumber ];
212
213 Envelope *env = musicEnvelopes[ note->mEnvelopeNumber ];
214 double *envLevels =
215 env->getEnvelope(
216 // index envelope by number of grid steps in note
217 note->mNumSamples / gridStepDurationInSamples );
218
219
220 double noteLoudnessL = note->mLoudnessLeft;
221 double noteLoudnessR = note->mLoudnessRight;
222
223 // do this outside inner loop
224 noteLoudnessL *= musicLoudness;
225 noteLoudnessR *= musicLoudness;
226
227
228 int noteStartInBuffer = 0;
229 int noteEndInBuffer = numSamples;
230
231 if( note->mCurrentSampleNumber < 0 ) {
232 // delay before note starts in this sample buffer
233 noteStartInBuffer = - note->mCurrentSampleNumber;
234
235 // we've taken account of the delay
236 note->mCurrentSampleNumber = 0;
237 }
238
239 char endNote = false;
240
241 int numSamplesLeftInNote =
242 note->mNumSamples - note->mCurrentSampleNumber;
243
244 if( noteStartInBuffer + numSamplesLeftInNote < noteEndInBuffer ) {
245 // note ends before end of buffer
246 noteEndInBuffer = noteStartInBuffer + numSamplesLeftInNote;
247 endNote = true;
248 }
249
250
251 int waveTablePos = note->mCurrentSampleNumber % tableLength;
252
253 int currentSampleNumber = note->mCurrentSampleNumber;
254
255 for( i=noteStartInBuffer; i != noteEndInBuffer; i++ ) {
256 double envelope = envLevels[ currentSampleNumber ];
257
258 double monoSample = envelope *
259 waveTable[ waveTablePos ];
260
261
262 samplesL[i] += (Sint16)( noteLoudnessL * monoSample );
263 samplesR[i] += (Sint16)( noteLoudnessR * monoSample );
264
265 currentSampleNumber ++;
266
267 waveTablePos ++;
268
269 // avoid using mod operator (%) in inner loop
270 // found with profiler
271 if( waveTablePos == tableLength ) {
272 // back to start of table
273 waveTablePos = 0;
274 }
275
276 }
277
278 note->mCurrentSampleNumber += ( noteEndInBuffer - noteStartInBuffer );
279
280 if( endNote ) {
281 // note ended in this buffer
282 currentlyPlayingNotes.deleteElement( n );
283 n--;
284 }
285
286 }
287
288
289 // now copy samples into Uint8 buffer
290 int streamPosition = 0;
291 for( i=0; i != numSamples; i++ ) {
292 Sint16 intSampleL = samplesL[i];
293 Sint16 intSampleR = samplesR[i];
294
295 inStream[ streamPosition ] = (Uint8)( intSampleL & 0xFF );
296 inStream[ streamPosition + 1 ] = (Uint8)( ( intSampleL >> 8 ) & 0xFF );
297
298 inStream[ streamPosition + 2 ] = (Uint8)( intSampleR & 0xFF );
299 inStream[ streamPosition + 3 ] = (Uint8)( ( intSampleR >> 8 ) & 0xFF );
300
301 streamPosition += 4;
302 }
303
304 delete [] samplesL;
305 delete [] samplesR;
306
307 }
308
309
310
311 // limit on n, based on Nyquist, when summing sine components
312 //int nLimit = (int)( sampleRate * M_PI );
313 // actually, this is way too many: it takes forever to compute
314 // use a lower limit instead
315 // This produces fine results (almost perfect square wave)
316 int nLimit = 40;
317
318
319
320 // square wave with period of 2pi
squareWave(double inT)321 double squareWave( double inT ) {
322 double sum = 0;
323
324 for( int n=1; n<nLimit; n+=2 ) {
325 sum += 1.0/n * sin( n * inT );
326 }
327 return sum;
328 }
329
330
331
332 // sawtoot wave with period of 2pi
sawWave(double inT)333 double sawWave( double inT ) {
334 double sum = 0;
335
336 for( int n=1; n<nLimit; n++ ) {
337 sum += 1.0/n * sin( n * inT );
338 }
339 return sum;
340 }
341
342
343 // white noise, ignores inT
whiteNoise(double inT)344 double whiteNoise( double inT ) {
345 return 2.0 * ( rand() / (double)RAND_MAX ) - 1.0;
346 }
347
348
349 // white noise where each sample is averaged with last sample
350 // effectively a low-pass filter
351 double lastSample = 0;
352
smoothedWhiteNoise(double inT)353 double smoothedWhiteNoise( double inT ) {
354 // give double-weight to last sample to make it even smoother
355 lastSample = ( 2 * lastSample + whiteNoise( inT ) ) / 3;
356
357 return lastSample;
358 }
359
360
361
362
loadMusicImage(const char * inTGAFileName)363 void loadMusicImage( const char *inTGAFileName ) {
364
365 musicImage = readTGA( "/usr/local/share/passage/music", inTGAFileName );
366
367 w = musicImage->getWidth();
368 h = musicImage->getHeight();
369
370 // notes are in red and green channel
371 double *redChannel = musicImage->getChannel( 0 );
372 double *greenChannel = musicImage->getChannel( 1 );
373
374
375 entireGridDuraton = gridStepDuration * w;
376
377
378 // jump ahead in stream, if needed
379 streamSamples += gridStartOffset * gridStepDurationInSamples;
380
381
382 // blank line of pixels between timbres
383 int heightPerTimbre = (h+1) / numTimbres - 1;
384
385
386 // find the maximum number of simultaneous notes in the song
387 // take loudness into account
388 double maxNoteLoudnessInAColumn = 0;
389
390 int x, y;
391 for( x=0; x<w; x++ ) {
392 double noteLoudnessInColumnL = 0;
393 double noteLoudnessInColumnR = 0;
394
395 for( y=0; y<h; y++ ) {
396
397 int imageIndex = y * w + x;
398
399 // the note number in our scale
400 // scale starts over for each timbre, with blank line
401 // in between timbres
402 int noteNumber = (h - y - 1) % (heightPerTimbre + 1);
403
404 if( // not blank line between timbres
405 noteNumber < heightPerTimbre &&
406 // tone present in image
407 ( redChannel[ imageIndex ] > 0 ||
408 greenChannel[ imageIndex ] > 0 ) ) {
409
410 noteLoudnessInColumnL += greenChannel[ imageIndex ];
411 noteLoudnessInColumnR += redChannel[ imageIndex ];
412
413 }
414 }
415 // pick loudest channel for this column and compare it to
416 // loudest column/channel seen so far
417 if( maxNoteLoudnessInAColumn < noteLoudnessInColumnL ) {
418 maxNoteLoudnessInAColumn = noteLoudnessInColumnL;
419 }
420 if( maxNoteLoudnessInAColumn < noteLoudnessInColumnR ) {
421 maxNoteLoudnessInAColumn = noteLoudnessInColumnR;
422 }
423
424 }
425
426
427 // divide loudness amoung timbres to avoid clipping
428 double loudnessPerTimbre = 1.0 / maxNoteLoudnessInAColumn;
429
430 // further adjust loudness per channel here as we construct
431 // each timbre.
432 // This is easier than tweaking loundness of a given part by hand
433 // using a painting program
434
435 musicTimbres[0] = new Timbre( sampleRate, 0.6 * loudnessPerTimbre,
436 keyFrequency,
437 heightPerTimbre, sawWave );
438 musicTimbres[1] = new Timbre( sampleRate, loudnessPerTimbre,
439 keyFrequency,
440 heightPerTimbre, sin );
441 musicTimbres[2] = new Timbre( sampleRate, 0.4 * loudnessPerTimbre,
442 keyFrequency / 4,
443 heightPerTimbre, squareWave );
444 musicTimbres[3] = new Timbre( sampleRate, 0.75 * loudnessPerTimbre,
445 keyFrequency / 4,
446 heightPerTimbre, smoothedWhiteNoise );
447
448
449 // next, compute the longest note in the song
450 int maxNoteLength = 0;
451
452 for( y=0; y<h; y++ ) {
453 int currentNoteLength = 0;
454
455 for( x=0; x<w; x++ ) {
456 int imageIndex = y * w + x;
457
458 // the note number in our scale
459 // scale starts over for each timbre, with blank line
460 // in between timbres
461 int noteNumber = (h - y - 1) % (heightPerTimbre + 1);
462
463 if( // not blank line between timbres
464 noteNumber < heightPerTimbre &&
465 // tone present in image
466 ( redChannel[ imageIndex ] > 0 ||
467 greenChannel[ imageIndex ] > 0 ) ) {
468
469 currentNoteLength ++;
470 }
471 else {
472 currentNoteLength = 0;
473 }
474 if( currentNoteLength > maxNoteLength ) {
475 maxNoteLength = currentNoteLength;
476 }
477 }
478 }
479
480 printf( "Max note length in song = %d\n", maxNoteLength );
481
482
483
484 musicEnvelopes[0] = new Envelope( 0.05, 0.7, 0.25, 0.1,
485 maxNoteLength,
486 gridStepDurationInSamples );
487 musicEnvelopes[1] = new Envelope( 0.1, 0.9, 0.0, 0.0,
488 maxNoteLength,
489 gridStepDurationInSamples );
490 musicEnvelopes[2] = new Envelope( 0.25, 0.0, 1.0, 0.1,
491 maxNoteLength,
492 gridStepDurationInSamples );
493 musicEnvelopes[3] = new Envelope( 0.0, 0.2, 0.0, 0.0,
494 maxNoteLength,
495 gridStepDurationInSamples );
496
497
498
499
500 noteGrid = new Note**[ h ];
501
502 for( int y=0; y<h; y++ ) {
503 noteGrid[y] = new Note*[ w ];
504
505 // each row is one pitch for a given instrument
506 // thus, two consecutive pixels should be the same note
507 // handle this by tracking whether a note is playing or not
508 char notePlaying = false;
509 Note *noteStart = NULL;
510 for( int x=0; x<w; x++ ) {
511 int imageIndex = y * w + x;
512
513 // default to NULL
514 noteGrid[y][x] = NULL;
515
516 // the note number in our scale
517 // scale starts over for each timbre, with blank line
518 // in between timbres
519 int noteNumber = (h - y - 1) % (heightPerTimbre + 1);
520
521
522
523 if( // not blank line between timbres
524 noteNumber < heightPerTimbre &&
525 // tone present in image
526 ( redChannel[ imageIndex ] > 0 ||
527 greenChannel[ imageIndex ] > 0 ) ) {
528
529
530 if( notePlaying ) {
531 // part of note that's already playing
532
533 // one more grid step
534 noteStart->mDuration += gridStepDuration;
535 noteStart->mNumSamples += gridStepDurationInSamples;
536
537 }
538 else {
539 // start a new note
540 noteGrid[y][x] = new Note();
541
542 noteGrid[y][x]->mScaleNoteNumber = noteNumber;
543
544 noteGrid[y][x]->mTimbreNumber =
545 y / ( heightPerTimbre + 1 );
546
547 // same as timbre number
548 noteGrid[y][x]->mEnvelopeNumber =
549 noteGrid[y][x]->mTimbreNumber;
550
551 // left loudness from green brightness
552 noteGrid[y][x]->mLoudnessLeft = greenChannel[ imageIndex ];
553
554 // right loudness from red brightness
555 noteGrid[y][x]->mLoudnessRight = redChannel[ imageIndex ];
556
557 noteGrid[y][x]->mStartTime = gridStepDuration * x;
558
559 // one grid step so far
560 noteGrid[y][x]->mDuration = gridStepDuration;
561 noteGrid[y][x]->mNumSamples = gridStepDurationInSamples;
562
563 // track if it needs to be continued
564 notePlaying = true;
565 noteStart = noteGrid[y][x];
566 }
567 }
568 else {
569 // no tone
570
571 if( notePlaying ) {
572 // stop it
573 notePlaying = false;
574 noteStart = NULL;
575 }
576 }
577 }
578 }
579
580
581 }
582
583
584
startMusic(const char * inTGAFileName)585 void startMusic( const char *inTGAFileName ) {
586
587 loadMusicImage( inTGAFileName );
588
589 SDL_AudioSpec audioFormat;
590
591 /* Set 16-bit stereo audio at 22Khz */
592 audioFormat.freq = sampleRate;
593 audioFormat.format = AUDIO_S16;
594 audioFormat.channels = 2;
595 audioFormat.samples = 512; /* A good value for games */
596 audioFormat.callback = audioCallback;
597 audioFormat.userdata = NULL;
598
599 /* Open the audio device and start playing sound! */
600 if( SDL_OpenAudio( &audioFormat, NULL ) < 0 ) {
601 printf( "Unable to open audio: %s\n", SDL_GetError() );
602 }
603
604 // set pause to 0 to start audio
605 SDL_PauseAudio(0);
606
607
608 }
609
610
611
stopMusic()612 void stopMusic() {
613 SDL_CloseAudio();
614
615 if( musicImage != NULL ) {
616 delete musicImage;
617 musicImage = NULL;
618 }
619
620 for( int y=0; y<h; y++ ) {
621
622 for( int x=0; x<w; x++ ) {
623
624 if( noteGrid[y][x] != NULL ) {
625 delete noteGrid[y][x];
626 }
627 }
628 delete [] noteGrid[y];
629 }
630
631 delete [] noteGrid;
632
633
634 int i;
635
636 for( i=0; i<numTimbres; i++ ) {
637 delete musicTimbres[i];
638 }
639 for( i=0; i<numEnvelopes; i++ ) {
640 delete musicEnvelopes[i];
641 }
642
643 }
644
645
646