687 lines
15 KiB
C++
687 lines
15 KiB
C++
|
#include "Song.h"
|
||
|
#include "Music.h"
|
||
|
#include "TimbreResource.h"
|
||
|
#include "Scale.h"
|
||
|
#include "resourceManager.h"
|
||
|
#include "usageDatabase.h"
|
||
|
#include "uniqueID.h"
|
||
|
|
||
|
#include "imageCache.h"
|
||
|
#include "packSaver.h"
|
||
|
|
||
|
|
||
|
#include "minorGems/util/stringUtils.h"
|
||
|
#include "minorGems/util/log/AppLog.h"
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// static inits
|
||
|
Song *Song::sBlankSong = NULL;
|
||
|
|
||
|
void Song::staticInit() {
|
||
|
sBlankSong = new Song();
|
||
|
// save once
|
||
|
sBlankSong->finishEdit();
|
||
|
}
|
||
|
|
||
|
void Song::staticFree() {
|
||
|
delete sBlankSong;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void Song::setupDefault() {
|
||
|
const char *defaultName = "default";
|
||
|
memcpy( mName, defaultName, strlen( defaultName ) + 1 );
|
||
|
|
||
|
|
||
|
uniqueID blankID = Music::sBlankMusic->getUniqueID();
|
||
|
|
||
|
int x;
|
||
|
int y;
|
||
|
for( y=0; y<SI; y++ ) {
|
||
|
mRowLengths[y] = 0;
|
||
|
mTimbres[y] = TimbreResource::sBlankTimbre->getUniqueID();
|
||
|
for( x=0; x<S; x++ ) {
|
||
|
mPhrases[y][x] = blankID;
|
||
|
}
|
||
|
mRowLoudness[y] = 255;
|
||
|
mRowStereo[y] = 128;
|
||
|
}
|
||
|
|
||
|
mScale = Scale::sBlankScale->getUniqueID();
|
||
|
|
||
|
// normal, default
|
||
|
mSpeed = 1;
|
||
|
|
||
|
makeUniqueID();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
Song::Song() {
|
||
|
setupDefault();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// song loaded from file
|
||
|
Song::Song( uniqueID inID )
|
||
|
: mID( inID ) {
|
||
|
|
||
|
int length;
|
||
|
char fromNetwork;
|
||
|
|
||
|
unsigned char *data = loadResourceData( "song", mID, &length,
|
||
|
&fromNetwork );
|
||
|
|
||
|
if( data != NULL ) {
|
||
|
// save this one
|
||
|
initFromData( data, length, fromNetwork, true );
|
||
|
|
||
|
|
||
|
delete [] data;
|
||
|
data = NULL;
|
||
|
}
|
||
|
else {
|
||
|
setupDefault();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
Song::Song( uniqueID inID, unsigned char *inData, int inLength,
|
||
|
char inFromNetwork, char inSaveWholeSong )
|
||
|
: mID( inID ) {
|
||
|
|
||
|
initFromData( inData, inLength, inFromNetwork, inSaveWholeSong );
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
void Song::initFromData( unsigned char *inData, int inLength,
|
||
|
char inFromNetwork, char inSaveWholeSong ) {
|
||
|
|
||
|
readFromBytes( inData, inLength );
|
||
|
|
||
|
if( inFromNetwork ) {
|
||
|
|
||
|
// phrases might not be present locally, either
|
||
|
|
||
|
// more efficient to load them in a chunk instead of with separate
|
||
|
// messages
|
||
|
|
||
|
|
||
|
uniqueID ids[SI*S];
|
||
|
|
||
|
int i=0;
|
||
|
|
||
|
for( int ty=0; ty<SI; ty++ ) {
|
||
|
for( int tx=0; tx<S; tx++ ) {
|
||
|
ids[i] = mPhrases[ty][tx];
|
||
|
i++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int chunkLengths[SI*S];
|
||
|
char fromNetworkFlags[SI*S];
|
||
|
|
||
|
|
||
|
unsigned char **chunks = loadResourceData( "music",
|
||
|
SI*S,
|
||
|
ids,
|
||
|
chunkLengths,
|
||
|
fromNetworkFlags );
|
||
|
|
||
|
for( int i=0; i<SI*S; i++ ) {
|
||
|
|
||
|
if( chunks[i] != NULL ) {
|
||
|
|
||
|
if( fromNetworkFlags[i] ) {
|
||
|
// make a phrase
|
||
|
Music m( ids[i], chunks[i], chunkLengths[i] );
|
||
|
|
||
|
// force into disk cache, don't remake ID
|
||
|
m.finishEdit( false );
|
||
|
}
|
||
|
|
||
|
|
||
|
delete [] chunks[i];
|
||
|
}
|
||
|
|
||
|
}
|
||
|
delete [] chunks;
|
||
|
|
||
|
|
||
|
|
||
|
i=0;
|
||
|
|
||
|
for( int ty=0; ty<SI; ty++ ) {
|
||
|
ids[i] = mTimbres[ty];
|
||
|
i++;
|
||
|
}
|
||
|
|
||
|
|
||
|
chunks = loadResourceData( "timbre",
|
||
|
SI,
|
||
|
ids,
|
||
|
chunkLengths,
|
||
|
fromNetworkFlags );
|
||
|
|
||
|
for( int i=0; i<SI; i++ ) {
|
||
|
|
||
|
if( chunks[i] != NULL ) {
|
||
|
|
||
|
if( fromNetworkFlags[i] ) {
|
||
|
// make a timbre
|
||
|
TimbreResource t( ids[i], chunks[i], chunkLengths[i] );
|
||
|
|
||
|
// force into disk cache, don't remake ID
|
||
|
t.finishEdit( false );
|
||
|
}
|
||
|
|
||
|
|
||
|
delete [] chunks[i];
|
||
|
}
|
||
|
|
||
|
}
|
||
|
delete [] chunks;
|
||
|
|
||
|
|
||
|
// force scale to be saved
|
||
|
Scale s( mScale );
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
if( inSaveWholeSong ) {
|
||
|
// save song to disk using the ID that we fetched it with
|
||
|
finishEdit( false );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
void Song::editSongPhrase( int inX, int inY, uniqueID inPhraseID ) {
|
||
|
mPhrases[inY][inX] = inPhraseID;
|
||
|
}
|
||
|
|
||
|
|
||
|
void Song::editRowLength( int inY, int inLength ){
|
||
|
mRowLengths[inY] = (unsigned char)inLength;
|
||
|
}
|
||
|
|
||
|
|
||
|
void Song::editTimbre( int inY, uniqueID inTimbreID ) {
|
||
|
mTimbres[inY] = inTimbreID;
|
||
|
}
|
||
|
|
||
|
|
||
|
void Song::editRowLoudness( int inY, int inLoudness ){
|
||
|
mRowLoudness[inY] = (unsigned char)inLoudness;
|
||
|
}
|
||
|
|
||
|
|
||
|
void Song::editRowStereo( int inY, int inStereo ){
|
||
|
mRowStereo[inY] = (unsigned char)inStereo;
|
||
|
}
|
||
|
|
||
|
|
||
|
void Song::editScale( uniqueID inScale ) {
|
||
|
mScale = inScale;
|
||
|
}
|
||
|
|
||
|
|
||
|
void Song::editSpeed( int inSpeed ) {
|
||
|
mSpeed = (unsigned char)inSpeed;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
void Song::editSongName( const char *inName ) {
|
||
|
int newLength = strlen( inName );
|
||
|
if( newLength > 10 ) {
|
||
|
AppLog::getLog()->logPrintf(
|
||
|
Log::ERROR_LEVEL,
|
||
|
"Error: Song name %s is too long (10 max)\n", inName );
|
||
|
}
|
||
|
else {
|
||
|
memcpy( mName, inName, newLength + 1 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
uniqueID Song::getPhrase( int inX, int inY ) {
|
||
|
return mPhrases[inY][inX];
|
||
|
}
|
||
|
|
||
|
|
||
|
int Song::getRowLength( int inY ) {
|
||
|
return mRowLengths[inY];
|
||
|
}
|
||
|
|
||
|
|
||
|
int Song::getRowLoudness( int inY ) {
|
||
|
return mRowLoudness[inY];
|
||
|
}
|
||
|
|
||
|
int Song::getRowStereo( int inY ) {
|
||
|
return mRowStereo[inY];
|
||
|
}
|
||
|
|
||
|
|
||
|
uniqueID Song::getTimbre( int inY ) {
|
||
|
return mTimbres[inY];
|
||
|
}
|
||
|
|
||
|
|
||
|
uniqueID Song::getScale() {
|
||
|
return mScale;
|
||
|
}
|
||
|
|
||
|
|
||
|
int Song::getSpeed() {
|
||
|
return mSpeed;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
char *Song::getSongName() {
|
||
|
return stringDuplicate( mName );
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// finishes the edit, generates a new unique ID, saves result
|
||
|
void Song::finishEdit( char inGenerateNewID ) {
|
||
|
uniqueID oldID = mID;
|
||
|
|
||
|
if( inGenerateNewID ) {
|
||
|
makeUniqueID();
|
||
|
}
|
||
|
|
||
|
|
||
|
if( !equal( oldID, mID ) ||
|
||
|
! resourceExists( "song", mID ) ) {
|
||
|
// change
|
||
|
|
||
|
int numBytes;
|
||
|
unsigned char *bytes = makeBytes( &numBytes );
|
||
|
|
||
|
saveResourceData( "song", mID, mName, bytes, numBytes );
|
||
|
delete [] bytes;
|
||
|
|
||
|
|
||
|
// track usages
|
||
|
addUsage( mID, mScale );
|
||
|
|
||
|
for( int y=0; y<SI; y++ ) {
|
||
|
|
||
|
addUsage( mID, mTimbres[y] );
|
||
|
|
||
|
for( int x=0; x<S; x++ ) {
|
||
|
addUsage( mID, mPhrases[y][x] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void Song::saveToPack() {
|
||
|
for( int py=0; py<SI; py++ ) {
|
||
|
|
||
|
for( int px=0; px<S; px++ ) {
|
||
|
|
||
|
Music m( mPhrases[py][px] );
|
||
|
m.saveToPack();
|
||
|
}
|
||
|
TimbreResource t( mTimbres[py] );
|
||
|
t.saveToPack();
|
||
|
}
|
||
|
Scale s( mScale );
|
||
|
s.saveToPack();
|
||
|
|
||
|
|
||
|
int numBytes;
|
||
|
unsigned char *bytes = makeBytes( &numBytes );
|
||
|
|
||
|
addToPack( "song", mID, mName, bytes, numBytes );
|
||
|
delete [] bytes;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
uniqueID Song::getUniqueID() {
|
||
|
return mID;
|
||
|
}
|
||
|
|
||
|
|
||
|
Image *Song::getImage() {
|
||
|
// compose all phrase images into a sprite with 1 pixel per phrase
|
||
|
|
||
|
// zero to prepare for average
|
||
|
Image *fullImage = new Image( P, P, 3, true );
|
||
|
|
||
|
double *fullC[3] =
|
||
|
{ fullImage->getChannel( 0 ),
|
||
|
fullImage->getChannel( 1 ),
|
||
|
fullImage->getChannel( 2 ) };
|
||
|
|
||
|
|
||
|
|
||
|
for( int py=0; py<SI; py++ ) {
|
||
|
|
||
|
// color by timbre
|
||
|
TimbreResource timbre( getTimbre( py ) );
|
||
|
|
||
|
Color timbreColor = toColor( timbre.getTimbreColor() );
|
||
|
|
||
|
|
||
|
for( int px=0; px<S; px++ ) {
|
||
|
|
||
|
int fullP = (py + 1) * P + (px + 1);
|
||
|
|
||
|
Music m( mPhrases[py][px] );
|
||
|
|
||
|
Image *tImage = m.getImage();
|
||
|
|
||
|
|
||
|
double *tC[4] =
|
||
|
{ tImage->getChannel( 0 ),
|
||
|
tImage->getChannel( 1 ),
|
||
|
tImage->getChannel( 2 ),
|
||
|
tImage->getChannel( 3 ) };
|
||
|
|
||
|
// compute average of all non-trans phrase pixels
|
||
|
|
||
|
for( int c=0; c<3; c++ ) {
|
||
|
// sum first
|
||
|
int numNonTrans = 0;
|
||
|
|
||
|
for( int i=0; i<P*P; i++ ) {
|
||
|
if( tC[3][i] > 0 ) {
|
||
|
fullC[c][fullP] += tC[c][i];
|
||
|
numNonTrans ++;
|
||
|
}
|
||
|
}
|
||
|
if( numNonTrans > 0 ) {
|
||
|
fullC[c][fullP] /= numNonTrans;
|
||
|
fullC[c][fullP] *= (timbreColor)[c];
|
||
|
}
|
||
|
else {
|
||
|
fullC[c][fullP] = 0;
|
||
|
}
|
||
|
}
|
||
|
delete tImage;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fullImage;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
Sprite *Song::getSprite( char inUseTrans, char inCacheOK ) {
|
||
|
// ignore inUseTrans
|
||
|
|
||
|
Image *cachedImage = NULL;
|
||
|
|
||
|
if( inCacheOK ) {
|
||
|
cachedImage = getCachedImage( mID, false );
|
||
|
}
|
||
|
|
||
|
if( cachedImage == NULL ) {
|
||
|
|
||
|
|
||
|
|
||
|
cachedImage = getImage();
|
||
|
|
||
|
if( inCacheOK ) {
|
||
|
addCachedImage( mID, false, cachedImage );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
Sprite *sprite = new Sprite( cachedImage );
|
||
|
if( !inCacheOK ) {
|
||
|
// image not in cache... we must destroy it
|
||
|
delete cachedImage;
|
||
|
}
|
||
|
return sprite;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
const char *Song::getResourceType() {
|
||
|
return "song";
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
Song Song::getDefaultResource() {
|
||
|
return *sBlankSong;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
#include "minorGems/util/SimpleVector.h"
|
||
|
|
||
|
unsigned char *Song::makeBytes( int *outLength ) {
|
||
|
SimpleVector<unsigned char> buffer;
|
||
|
|
||
|
int y, x;
|
||
|
|
||
|
for( y=0; y<SI; y++ ) {
|
||
|
for( x=0; x<S; x++ ) {
|
||
|
buffer.push_back( mPhrases[y][x].bytes, U );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
for( y=0; y<SI; y++ ) {
|
||
|
buffer.push_back( mRowLengths[y] );
|
||
|
}
|
||
|
|
||
|
for( y=0; y<SI; y++ ) {
|
||
|
buffer.push_back( mTimbres[y].bytes, U );
|
||
|
}
|
||
|
|
||
|
for( y=0; y<SI; y++ ) {
|
||
|
buffer.push_back( mRowLoudness[y] );
|
||
|
}
|
||
|
|
||
|
for( y=0; y<SI; y++ ) {
|
||
|
buffer.push_back( mRowStereo[y] );
|
||
|
}
|
||
|
|
||
|
buffer.push_back( mScale.bytes, U );
|
||
|
|
||
|
buffer.push_back( mSpeed );
|
||
|
|
||
|
|
||
|
buffer.push_back( (unsigned char *)mName, strlen( mName ) + 1 );
|
||
|
|
||
|
*outLength = buffer.size();
|
||
|
return buffer.getElementArray();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void Song::readFromBytes( unsigned char *inBytes, int inLength ) {
|
||
|
|
||
|
int numBytesUsed;
|
||
|
char success = readUniqueIDs( (uniqueID *)mPhrases, SI*S,
|
||
|
inBytes, inLength,
|
||
|
&numBytesUsed );
|
||
|
if( success ) {
|
||
|
inBytes = &( inBytes[ numBytesUsed ] );
|
||
|
inLength -= numBytesUsed;
|
||
|
}
|
||
|
|
||
|
if( inLength >= SI ) {
|
||
|
|
||
|
memcpy( (void *)mRowLengths, inBytes, SI );
|
||
|
|
||
|
inBytes = &( inBytes[ SI ] );
|
||
|
inLength -= SI;
|
||
|
}
|
||
|
|
||
|
success = readUniqueIDs( (uniqueID *)mTimbres, SI,
|
||
|
inBytes, inLength,
|
||
|
&numBytesUsed );
|
||
|
|
||
|
if( success ) {
|
||
|
inBytes = &( inBytes[ numBytesUsed ] );
|
||
|
inLength -= numBytesUsed;
|
||
|
}
|
||
|
|
||
|
if( inLength >= SI ) {
|
||
|
|
||
|
memcpy( (void *)mRowLoudness, inBytes, SI );
|
||
|
|
||
|
inBytes = &( inBytes[ SI ] );
|
||
|
inLength -= SI;
|
||
|
}
|
||
|
|
||
|
if( inLength >= SI ) {
|
||
|
|
||
|
memcpy( (void *)mRowStereo, inBytes, SI );
|
||
|
|
||
|
inBytes = &( inBytes[ SI ] );
|
||
|
inLength -= SI;
|
||
|
}
|
||
|
|
||
|
mScale = readUniqueID( inBytes, inLength,
|
||
|
&numBytesUsed );
|
||
|
if( numBytesUsed == U ) {
|
||
|
inBytes = &( inBytes[ numBytesUsed ] );
|
||
|
inLength -= numBytesUsed;
|
||
|
}
|
||
|
|
||
|
if( inLength > 0 ) {
|
||
|
mSpeed = inBytes[0];
|
||
|
inBytes = &( inBytes[ 1 ] );
|
||
|
inLength -= 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// remainder is name
|
||
|
if( inLength <= 11 ) {
|
||
|
memcpy( mName, inBytes, inLength );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void Song::makeUniqueID() {
|
||
|
|
||
|
int numBytes;
|
||
|
unsigned char *bytes = makeBytes( &numBytes );
|
||
|
|
||
|
mID = ::makeUniqueID( bytes, numBytes );
|
||
|
delete [] bytes;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
#include "musicPlayer.h"
|
||
|
|
||
|
extern char noteToggles[PARTS][S][N][N];
|
||
|
|
||
|
extern int partLengths[PARTS];
|
||
|
extern double partLoudness[PARTS];
|
||
|
extern double partStereo[PARTS];
|
||
|
|
||
|
|
||
|
void Song::setInPlayer( Song inOldSong, Song inNewSong, char inForceUpdate ) {
|
||
|
|
||
|
// timbre updates are slow... only do the ones we need
|
||
|
uniqueID oldTimbres[SI];
|
||
|
for( int y=0; y<SI; y++ ) {
|
||
|
oldTimbres[y] = inOldSong.getTimbre( y );
|
||
|
}
|
||
|
|
||
|
// update ALL on a scale change or speed change
|
||
|
uniqueID oldScale = inOldSong.getScale();
|
||
|
uniqueID newScale = inNewSong.getScale();
|
||
|
|
||
|
char scaleChange = ( !equal( oldScale, newScale ) ) || inForceUpdate;
|
||
|
|
||
|
if( scaleChange ) {
|
||
|
Scale s( newScale );
|
||
|
s.setInPlayer();
|
||
|
}
|
||
|
|
||
|
char speedChange = ( inOldSong.getSpeed() != inNewSong.getSpeed() )
|
||
|
|| inForceUpdate;
|
||
|
if( speedChange ) {
|
||
|
setSpeed( inNewSong.getSpeed() );
|
||
|
}
|
||
|
|
||
|
// speed change forces everything to update
|
||
|
|
||
|
for( int y=0; y<SI; y++ ) {
|
||
|
partLengths[y] = inNewSong.getRowLength( y );
|
||
|
|
||
|
|
||
|
TimbreResource t( inNewSong.getTimbre( y ) );
|
||
|
|
||
|
for( int x=0; x<S; x++ ) {
|
||
|
Music m( inNewSong.getPhrase( x, y ) );
|
||
|
|
||
|
if( inNewSong.getRowLength( y ) > x ) {
|
||
|
for( int py=0; py<N; py++ ) {
|
||
|
for( int px=0; px<N; px++ ) {
|
||
|
noteToggles[y][x][py][px] =
|
||
|
m.getNoteOn( px, py );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// turn rest off
|
||
|
for( int py=0; py<N; py++ ) {
|
||
|
for( int px=0; px<N; px++ ) {
|
||
|
noteToggles[y][x][py][px] = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( !equal( t.getUniqueID(), oldTimbres[y] ) ) {
|
||
|
// a true timbre change, set both timbre and envelope
|
||
|
t.setInPlayer( y, true, true );
|
||
|
}
|
||
|
else if( speedChange ) {
|
||
|
// just a speed change, keep timbres
|
||
|
t.setInPlayer( y, false, true );
|
||
|
}
|
||
|
else if( scaleChange ) {
|
||
|
// just a scale change, keep envelopes
|
||
|
t.setInPlayer( y, true, false );
|
||
|
}
|
||
|
|
||
|
|
||
|
partLoudness[y] = inNewSong.getRowLoudness( y ) / 255.0;
|
||
|
partStereo[y] = inNewSong.getRowStereo( y ) / 255.0;
|
||
|
}
|
||
|
|
||
|
}
|