#include "musicPlayer.h" #include "common.h" #include "Timbre.h" #include "Envelope.h" #include "Song.h" #include "minorGems/util/SimpleVector.h" #include "minorGems/util/log/AppLog.h" #include "minorGems/system/Time.h" #include #include #include #include // smoothly fade in particular tracks based on track fade level // low value plays only first track... high value plays all tracks extern double musicTrackFadeLevel; // whether note is currently on and playing or not // toggle these to control music as it plays char noteToggles[PARTS][S][N][N]; int partLengths[PARTS]; // phrase position within each part int partPositions[PARTS]; double partLoudness[PARTS]; double partStereo[PARTS]; // last column of notes that sounded within each current tone matrix int lastNoteColumnPlayed; int sampleRate = 22050; //int sampleRate = 11025; // 16x16 tone matrix used in each phrase of each part int w = N; int h = N; // total number of samples played so far int streamSamples = 0; // offset into grid at start // for testing int gridStartOffset = 0; // overal loudness of music double musicLoudness = 1.0; // one grid step in seconds double gridStepDuration = 0.5; int gridStepDurationInSamples = (int)( gridStepDuration * sampleRate ); void setSpeed( int inSpeed ) { SDL_LockAudio(); //double oldDuration = gridStepDuration; switch( inSpeed ) { case 0: gridStepDuration = 1; break; case 1: gridStepDuration = 0.5; break; case 2: gridStepDuration = 0.25; break; } //double speedMultiple = gridStepDuration / oldDuration; gridStepDurationInSamples = (int)( gridStepDuration * sampleRate ); // jump in stream to maintain our current grid location // otherwise, we're in danger of playing the same note twice simultaneously SDL_UnlockAudio(); } //double entireGridDuraton; // c double v13KeyFrequency = 261.63; // actually, can't support high notes in the key of c w/out round-off errors // because this is a wavetable implementation, and when the tables get short, // the errors get huge // THIS, however, evenly divides our sample rate (22050) // which means that we get perfect, whole-number wave tables at octaves double v14KeyFrequency = 172.265625; double keyFrequency = v13KeyFrequency; // sets v13 frequency by default, UNTIL a timbre is changed (because only // a v14 game would do that). // Thus, v13 music, when we're player, still sounds right. int numTimbres = PARTS; Timbre *musicTimbres[ PARTS ]; int numEnvelopes = PARTS; Envelope *musicEnvelopes[ PARTS ]; // waiting for destruction SimpleVector oldTimbres; SimpleVector oldEnvelopes; class Note { public: // index into musicTimbres array int mTimbreNumber; // pointer to actual Timbre used when note was last sounded // (so it doesn't change out from under note, in middle of note) Timbre *mTimbrePointer; // index into musicEnvelopes array int mEnvelopeNumber; // pointer to actual Envelope, for same reason as above Envelope *mEnvelopePointer; int mScaleNoteNumber; // additional loudness adjustment // places note in stereo space double mLoudnessLeft; double mLoudnessRight; // start time, in seconds from start of note grid //double mStartTime; // duration in seconds //double mDuration; // used when note is currently playing to track progress in note // negative if we should wait before starting to play the note int mCurrentSampleNumber; // duration in samples, set each time the note is playe // incase speed changes int mNumSamples; // set once int mNumGridSteps; // carry the grid step duration with us as we play, in case // it is changed before we're done playing int mOurGridStepDurationInSamples; Note *copy() { Note *note = new Note(); note->mTimbreNumber = mTimbreNumber; note->mTimbrePointer = mTimbrePointer; note->mEnvelopeNumber = mEnvelopeNumber; note->mEnvelopePointer = mEnvelopePointer; note->mScaleNoteNumber = mScaleNoteNumber; note->mLoudnessLeft = mLoudnessLeft; note->mLoudnessRight = mLoudnessRight; note->mCurrentSampleNumber = mCurrentSampleNumber; note->mNumSamples = mNumSamples; note->mNumGridSteps = mNumGridSteps; note->mOurGridStepDurationInSamples = mOurGridStepDurationInSamples; return note; } }; // all possible notes in a 16x16 phrase grid // indexed as noteGrid[part][y][x] Note *noteGrid[PARTS][N][N]; SimpleVector currentlyPlayingNotes; // need to synch these with audio thread void setMusicLoudness( double inLoudness ) { SDL_LockAudio(); musicLoudness = inLoudness; SDL_UnlockAudio(); } double getMusicLoudness() { return musicLoudness; } void restartMusic() { SDL_LockAudio(); // return to beginning (and forget samples we've played so far) streamSamples = 0; // drop all currently-playing notes currentlyPlayingNotes.deleteAll(); SDL_UnlockAudio(); } // called by SDL to get more samples void audioCallback( void *inUserData, Uint8 *inStream, int inLengthToFill ) { // 2 bytes for each channel of stereo sample int numSamples = inLengthToFill / 4; Sint16 *samplesL = new Sint16[ numSamples ]; Sint16 *samplesR = new Sint16[ numSamples ]; // first, zero-out the buffer to prepare it for our sum of note samples // each sample is 2 bytes memset( samplesL, 0, 2 * numSamples ); memset( samplesR, 0, 2 * numSamples ); int i; // hop through all grid steps that *start* in this stream buffer // add notes that start during this stream buffer // how far into stream buffer before we hit our first grid step? int startOfFirstGridStep = streamSamples % gridStepDurationInSamples; if( startOfFirstGridStep != 0 ) { startOfFirstGridStep = gridStepDurationInSamples - startOfFirstGridStep; } // hop from start of grid step to start of next grid step // ignore samples in between, since notes don't start there, // and all we're doing right now is finding notes that start for( i=startOfFirstGridStep; i 0 ) { // step in part partPositions[si] = (x / w) % ( partLengths[si] ); // step in tone matrix for that part-step int matrixX = x % w; lastNoteColumnPlayed = matrixX; for( int y=0; ycopy(); currentlyPlayingNotes.push_back( note ); // save pointer to active envelope and timbre // when this note began note->mTimbrePointer = musicTimbres[ note->mTimbreNumber ]; note->mEnvelopePointer = musicEnvelopes[ note->mEnvelopeNumber ]; note->mTimbrePointer->mActiveNoteCount ++; note->mEnvelopePointer->mActiveNoteCount ++; // tweak loudness based on part loudness and stereo note->mLoudnessRight *= partLoudness[ si ]; note->mLoudnessLeft *= partLoudness[ si ]; // constant power rule double p = M_PI * partStereo[ si ] / 2; note->mLoudnessRight *= sin( p ); note->mLoudnessLeft *= cos( p ); // start it // base these on our // envelope (instead of currently set duration in // player, which may have been updated before // the envelopes were properly updated due to thread // interleaving issues) int envGridStepDuration = musicEnvelopes[ note->mEnvelopeNumber ]-> mGridStepDurationInSamples; // compute length note->mNumSamples = note->mNumGridSteps * envGridStepDuration; note->mOurGridStepDurationInSamples = envGridStepDuration; // set a delay for its start based on our position // in this callback buffer note->mCurrentSampleNumber = -i; } } } else { partPositions[si] = 0; } } } streamSamples += numSamples; // loop over all current notes and add their samples to buffer for( int n=0; nmScaleNoteNumber; //Timbre *timbre = musicTimbres[ note->mTimbreNumber ]; Timbre *timbre = note->mTimbrePointer; int tableLength = timbre->mWaveTableLengths[ waveTableNumber ]; Sint16 *waveTable = timbre->mWaveTable[ waveTableNumber ]; //Envelope *env = musicEnvelopes[ note->mEnvelopeNumber ]; Envelope *env = note->mEnvelopePointer; double *envLevels = env->getEnvelope( // index envelope by number of grid steps in note note->mNumSamples / note->mOurGridStepDurationInSamples ); double noteLoudnessL = note->mLoudnessLeft; double noteLoudnessR = note->mLoudnessRight; // do this outside inner loop noteLoudnessL *= musicLoudness; noteLoudnessR *= musicLoudness; // factor in externally-set track fade level // level from 0..(numTimbres) double trackFadeInLevel = musicTrackFadeLevel * (numTimbres); // level for this track based on trackFadeInLevel double thisTrackLevel; if( trackFadeInLevel >= note->mTimbreNumber + 1 ) { // full volume thisTrackLevel = 1.0; } else if( trackFadeInLevel > note->mTimbreNumber ) { // linear fade in for this track thisTrackLevel = trackFadeInLevel - (int)trackFadeInLevel; } else { // track silent thisTrackLevel = 0; } noteLoudnessL *= thisTrackLevel; noteLoudnessR *= thisTrackLevel; int noteStartInBuffer = 0; int noteEndInBuffer = numSamples; if( note->mCurrentSampleNumber < 0 ) { // delay before note starts in this sample buffer noteStartInBuffer = - note->mCurrentSampleNumber; // we've taken account of the delay note->mCurrentSampleNumber = 0; } char endNote = false; int numSamplesLeftInNote = note->mNumSamples - note->mCurrentSampleNumber; if( noteStartInBuffer + numSamplesLeftInNote < noteEndInBuffer ) { // note ends before end of buffer noteEndInBuffer = noteStartInBuffer + numSamplesLeftInNote; endNote = true; } int waveTablePos = note->mCurrentSampleNumber % tableLength; int currentSampleNumber = note->mCurrentSampleNumber; for( i=noteStartInBuffer; i != noteEndInBuffer; i++ ) { double envelope = envLevels[ currentSampleNumber ]; double monoSample = envelope * waveTable[ waveTablePos ]; samplesL[i] += (Sint16)( noteLoudnessL * monoSample ); samplesR[i] += (Sint16)( noteLoudnessR * monoSample ); currentSampleNumber ++; waveTablePos ++; // avoid using mod operator (%) in inner loop // found with profiler if( waveTablePos == tableLength ) { // back to start of table waveTablePos = 0; } } note->mCurrentSampleNumber += ( noteEndInBuffer - noteStartInBuffer ); if( endNote ) { // note ended in this buffer currentlyPlayingNotes.deleteElement( n ); n--; // note not using these anymore note->mTimbrePointer->mActiveNoteCount --; note->mEnvelopePointer->mActiveNoteCount --; if( note->mTimbrePointer->mActiveNoteCount == 0 && musicTimbres[ note->mTimbreNumber ] != note->mTimbrePointer ) { // this timbre is no longer used oldTimbres.deleteElementEqualTo( note->mTimbrePointer ); delete note->mTimbrePointer; } if( note->mEnvelopePointer->mActiveNoteCount == 0 && musicEnvelopes[ note->mEnvelopeNumber ] != note->mEnvelopePointer ) { // this envelope is no longer used oldEnvelopes.deleteElementEqualTo( note->mEnvelopePointer ); delete note->mEnvelopePointer; } // this was a copy delete note; } } // now copy samples into Uint8 buffer int streamPosition = 0; for( i=0; i != numSamples; i++ ) { Sint16 intSampleL = samplesL[i]; Sint16 intSampleR = samplesR[i]; inStream[ streamPosition ] = (Uint8)( intSampleL & 0xFF ); inStream[ streamPosition + 1 ] = (Uint8)( ( intSampleL >> 8 ) & 0xFF ); inStream[ streamPosition + 2 ] = (Uint8)( intSampleR & 0xFF ); inStream[ streamPosition + 3 ] = (Uint8)( ( intSampleR >> 8 ) & 0xFF ); streamPosition += 4; } delete [] samplesL; delete [] samplesR; } // limit on n, based on Nyquist, when summing sine components //int nLimit = (int)( sampleRate * M_PI ); // actually, this is way too many: it takes forever to compute // use a lower limit instead // This produces fine results (almost perfect square wave) int nLimit = 40; // square wave with period of 2pi double squareWave( double inT ) { double sum = 0; for( int n=1; nmActiveNoteCount > 0 ) { // save old one, because some currently-playing notes are using it! oldTimbres.push_back( musicTimbres[inTimbreNumber] ); } else { delete musicTimbres[inTimbreNumber]; } musicTimbres[inTimbreNumber] = newTimbre; SDL_UnlockAudio(); } void setScale( char inToneOn[12] ) { setTimbreScale( inToneOn ); } void setEnvelope( int inTimbreNumber, double inAttack, double inHold, double inRelease ) { if( inAttack + inHold + inRelease > 1.0 ) { AppLog::error( "Attack + Hold + Release in specified envelope too long" ); if( inAttack > 1 ) { inAttack = 1; } inHold = 1 - inAttack; inRelease = 0; } int maxNoteLength = 3; Envelope *newEnvelope = new Envelope( inAttack, inHold, inRelease, maxNoteLength, maxNoteLength, gridStepDurationInSamples ); // replace it SDL_LockAudio(); if( musicEnvelopes[inTimbreNumber]->mActiveNoteCount > 0 ) { // save old one, because some currently-playing notes are using it! oldEnvelopes.push_back( musicEnvelopes[inTimbreNumber] ); } else { delete musicEnvelopes[inTimbreNumber]; } musicEnvelopes[inTimbreNumber] = newEnvelope; SDL_UnlockAudio(); } void setDefaultMusicSounds() { // back to v13 default keyFrequency = v13KeyFrequency; gridStepDuration = 0.5; gridStepDurationInSamples = (int)( gridStepDuration * sampleRate ); setDefaultScale(); SDL_LockAudio(); for( int i=0; imActiveNoteCount > 0 ) { // save old one, because some currently-playing notes // are using it! oldTimbres.push_back( musicTimbres[i] ); } else { delete musicTimbres[i]; } } if( musicEnvelopes[i] != NULL ) { if( musicEnvelopes[i]->mActiveNoteCount > 0 ) { // save old one, because some currently-playing notes // are using it! oldEnvelopes.push_back( musicEnvelopes[i] ); } else { delete musicEnvelopes[i]; } } } int heightPerTimbre = h; // possible for all notes in a column to be on at user's request // and notes are 3 long at max (decays), so consider overlap double maxNoteLoudnessInAColumn = h * 3; // divide loudness amoung timbres to avoid clipping double loudnessPerTimbre = 1.0 / maxNoteLoudnessInAColumn; // further adjust loudness per channel here as we construct // each timbre. //double t = Time::getCurrentTime(); // load defaults into first 3 banks. // thus, music sent from v13 and earlier (if we'r Player) will // play correctly musicTimbres[0] = new Timbre( sampleRate, 1.0 * loudnessPerTimbre, keyFrequency / 4, heightPerTimbre, sawWave ); musicTimbres[1] = new Timbre( sampleRate, 0.75 * loudnessPerTimbre, keyFrequency / 2, heightPerTimbre, harmonicSaw ); // last timbre has one extra note at top (top row of grid) musicTimbres[2] = new Timbre( sampleRate, 0.65 * loudnessPerTimbre, keyFrequency, heightPerTimbre + 1, harmonicSine ); for( int i=3; ilogPrintf( Log::INFO_LEVEL, "Max note length in song = %d\n", maxNoteLength ); // load defaults into first 3 banks. // thus, music sent from v13 and earlier (if we'r Player) will // play correctly musicEnvelopes[0] = new Envelope( 0.02, 0.98, 0, 0, maxNoteLength, maxNoteLength, gridStepDurationInSamples ); musicEnvelopes[1] = new Envelope( 0.15, 0.85, 0.0, 0.0, maxNoteLength, maxNoteLength, gridStepDurationInSamples ); musicEnvelopes[2] = new Envelope( 0.01, 0.99, 0.0, 0.0, maxNoteLength, maxNoteLength, gridStepDurationInSamples ); for( int i=3; ilogPrintf( Log::INFO_LEVEL, "Height in grid per timbre = %d\n", heightPerTimbre ); // nullify so they are not added to old list by setDefaultMusicSounds for( int i=0; imScaleNoteNumber = noteNumber; noteGrid[si][y][x]->mTimbreNumber = si; noteGrid[si][y][x]->mTimbrePointer = musicTimbres[ si ]; // same as timbre number noteGrid[si][y][x]->mEnvelopeNumber = noteGrid[si][y][x]->mTimbreNumber; noteGrid[si][y][x]->mEnvelopePointer = musicEnvelopes[ si ]; // left loudness fixed noteGrid[si][y][x]->mLoudnessLeft = leftLoudness[ noteGrid[si][y][x]->mTimbreNumber ]; // right loudness fixed noteGrid[si][y][x]->mLoudnessRight = rightLoudness[ noteGrid[si][y][x]->mTimbreNumber ]; //noteGrid[si][y][x]->mStartTime = gridStepDuration * x; // three grid steps long, for overlap //noteGrid[si][y][x]->mDuration = // gridStepDuration * 3; noteGrid[si][y][x]->mNumGridSteps = 3; // set this when the note is played //noteGrid[si][y][x]->mNumSamples = // gridStepDurationInSamples * 3; } } } for( int i=0; ilogPrintf( Log::ERROR_LEVEL, "Unable to open audio: %s\n", SDL_GetError() ); } // set pause to 0 to start audio SDL_PauseAudio(0); } void stopMusic() { SDL_CloseAudio(); for( int i=0; i