#include "music.h" //event times & durations are absolute integer milliseconds int notemap(int n) { //nonlinear mapping of notes to 3 columns - space 5,4,7 - trying 5,5,6 //note drawing 46h - 52h int numnotes=16; int firstnote=70; int note=n-firstnote; if (note<5) return 0; else if (note <10) return 1; else return 2; } //---------------------------------------------------------------------------------------------------------- void lyricscore::draw(){ int scoreTime=ofGetElapsedTimeMillis()-startTime; map::iterator iter; iter=lyrics.upper_bound(scoreTime); if (iter!=lyrics.begin()) { iter--; if ((iter->first+iter->second->duration)>scoreTime) { //outpoint of lyric previous to the one next soonest is afterwards => this lyric is visible int alpha=((iter->first+iter->second->duration)-scoreTime)first+iter->second->duration)-scoreTime))/((float)fadeout))*255.0f):255; ofSetColor(255,255,255,alpha); font.drawString(iter->second->text,(ofGetWidth()/2)-(font.stringWidth(iter->second->text)/2.0f), gridY[1]*ofGetHeight()); } } } //---------------------------------------------------------------------------------------------------------- musicscore::musicscore() { timeframe=5000; missedLast=false; nowpoint=1.0f; missedNote=-1; hitNote=-1; snowflakes.push_back(Puppet()); snowflakes.push_back(Puppet()); snowflakes.push_back(Puppet()); snowflakes[0].load("Snowflake-Blue.xml"); snowflakes[1].load("Snowflake-Purple.xml"); snowflakes[2].load("Snowflake-Green.xml"); } void musicscore::parseMidi(string filename){ // millis = 60000 / (BPM * PPQ) // BPM = 60000000 / MQPN (last 3 bytes of midi tempoSet) // http://www.lastrayofhope.com/2009/12/23/midi-delta-time-ticks-to-seconds/ // presume no change in time signature? //2 passes:: extract notes & set abs times, then scan for float wt=ofGetElapsedTimef(); float BPM=120.0f; //input:: MPQN :: default 500000 float MPQN=60000000.0f/BPM; //unknown:: ticks per quarter note int TPQN=480; //want:: seconds per tick in float float SPT =(MPQN/1000000.0f)/TPQN; float time=0; //counts up in float seconds to avoid rounding errors but converts to millis for map index map events; if( !XML.loadFile(filename) ){ printf("unable to load %s check data/ folder\n",filename.c_str()); }else{ if(XML.pushTag("MidiFile")) { for (int i=0;i::iterator iter1; map::iterator iter2; int n; bool started=false; for (iter1 = events.begin(); iter1 != events.end(); ++iter1) { if (iter1->second->duration==144) { if (!started) { n=iter1->second->num; started=true; } iter1->second->duration=0; iter2=iter1; while (++iter2 != events.end()) { if ((iter1->second->num==iter2->second->num)&&(iter2->second->duration==128)) { iter1->second->duration=iter2->first-iter1->first; n=iter1->second->num; notes[iter1->first]=iter1->second; //printf("%i: noteon %i %i\n",iter1->first,iter1->second->num,iter1->second->duration); break; } } } } iter1 = notes.end(); iter1--; printf("processed %s: length %f, %i notes in %f seconds\n",filename.c_str(),((float)(iter1->first+iter1->second->duration)*.001f),notes.size(),ofGetElapsedTimef()-wt); } void musicscore::makeFlakes(int threshStart,int threshEnd,levelscore *levels){ flakes.clear(); //decimate notes to generate flakes that can be interacted with map::iterator iter; note *lastNote=notes.begin()->second; int lastTime=0; iter = notes.end(); iter--; float songDuration=iter->first; flakes[notes.begin()->first]=new flake(notes.begin()->second->num,notes.begin()->second->velocity,notes.begin()->second->duration); flakes[notes.begin()->first]->puppet=snowflakes[notemap(lastNote->num)]; for (iter = notes.begin(); iter != notes.end(); iter++) { float songPos=((float)iter->first)/songDuration; if ((levels->nextLevelTime(iter->first)>2000)&&((notemap(iter->second->num)!=notemap(lastNote->num))||(iter->first-lastTime>((songPos*threshEnd)+((1.0f-songPos)*threshStart))))) { flakes[iter->first]=new flake(iter->second->num,iter->second->velocity,iter->second->duration); flakes[iter->first]->puppet=snowflakes[notemap(iter->second->num)]; lastNote=iter->second; lastTime=iter->first; } } missedFlake=flakes.end(); missedNote=-1; } void musicscore::setTimeframe(int millis) {timeframe=millis;} void musicscore::setNowpoint(float pct) {nowpoint=pct;} void musicscore::drawNotes(levelscore *levels) { int scoreStart=ofGetElapsedTimeMillis()-startTime-((1.0f-nowpoint)*timeframe); int scoreEnd=scoreStart+timeframe; //note drawing 46h - 52h int numnotes=16; int firstnote=70; float widthStep=((float)ofGetWidth())/numnotes; float heightStep=((float)ofGetHeight())/timeframe; map::iterator iter; //draw notes for reference for (iter = notes.lower_bound(scoreStart); iter != notes.upper_bound(scoreEnd); ++iter) { int thisnote=iter->second->num-firstnote; int thisstart=iter->first-scoreStart; int thislength=iter->second->duration; ofSetColor(ofColor::fromHsb(((float)thisnote*255)/numnotes,200,100),(((float)(thisstart*heightStep))/ofGetHeight()*128)+32); ofRect(thisnote*widthStep,ofGetHeight()-(thisstart*heightStep),widthStep,-(thislength*heightStep)); } } void musicscore::drawFlakes(levelscore *levels,float scale) { ofEnableAlphaBlending(); int scoreStart=ofGetElapsedTimeMillis()-startTime-((1.0f-nowpoint)*timeframe); int scoreEnd=scoreStart+timeframe; //note drawing 46h - 52h int numnotes=16; int firstnote=70; float widthStep=((float)ofGetWidth())/numnotes; float heightStep=((float)ofGetHeight())/timeframe; map::iterator iter; //draw flakes for (iter = flakes.lower_bound(scoreStart); iter != flakes.upper_bound(scoreEnd); iter++) { int thisnote=iter->second->num-firstnote; int thisstart=iter->first-scoreStart; int thislength=iter->second->duration; //if (iter->second->activated) ofSetColor(255,255,255); //else ofSetColor(ofColor::fromHsb(((float)thisnote*255)/numnotes,200,255)); //if (iter->second->activated&&(!iter->second->disintegrated)) iter->second->disintegrate(); ofSetColor(255,255,255); iter->second->draw(gridX[notemap(iter->second->num)+1]*ofGetWidth(),ofGetHeight()-(thisstart*heightStep),scale); //todo - make all drawing resolution independent } //check for unactivated flakes within this segment: is there a more efficient way? //need to know when a flake has just been missed missedFlakes=0; missedLast=false; map::iterator missed=flakes.end(); int scoreTime=ofGetElapsedTimeMillis()-startTime; for (iter = flakes.lower_bound(levels->getLowerBound(levels->getLevel(scoreStart))); iter != flakes.lower_bound(scoreStart); iter++){ if (!iter->second->activated) { missedFlakes++; missed=iter; } missedLast=!iter->second->activated; } //at this point missed points to the latest unactivated flake in the level if there is one if ((missed!=flakes.end())&&(missedFlake!=missed)) { missedFlake=missed; missedNote=notemap(missed->second->num); } else missedNote=-1; ofDisableAlphaBlending(); } void musicscore::playerControl(int key,int threshold){ map::iterator iter; int scoreTime=ofGetElapsedTimeMillis()-startTime; for (iter = flakes.lower_bound(scoreTime-threshold); iter != flakes.upper_bound(scoreTime+threshold); iter++) { if (key==notemap(iter->second->num)) { iter->second->activate(); hitNote=key; } } } //--------------------------------------------------------------------------------------------------------------------------------------------- song::song(string backfile,string melfile,string musfile,string lyricfile,string levelfile) { backing.loadSound(backfile); melody.loadSound(melfile); notes.parseMidi(musfile); lyrics.load(lyricfile); levels.load(levelfile); isPlaying=false; keyThresh=500; notes.setNowpoint(0.8f); } void song::setTimeframe(int millis) {notes.setTimeframe(millis);} void song::setKeythresh(int millis) {keyThresh=millis;} void song::setFlakeThresh(int tS,int tE) { fThreshStart=tS; fThreshEnd=tE; } int song::getCurrentTime(){ return ofGetElapsedTimeMillis()-startTime; } void song::play() { backing.play(); melody.play(); startTime=ofGetElapsedTimeMillis(); notes.start(); lyrics.start(); isPlaying=true; notes.makeFlakes(fThreshStart,fThreshEnd,&levels); } void song::stop() { backing.stop(); melody.stop(); isPlaying=false; } void song::preRoll(long preroll) { startTime=ofGetElapsedTimeMillis()+preroll; notes.start(startTime); lyrics.start(startTime); isPreroll=true; isPlaying=true; notes.makeFlakes(fThreshStart,fThreshEnd,&levels); } void song::drawNotes(){ notes.drawNotes(&levels); } void song::draw(float scale){ int songTime=ofGetElapsedTimeMillis()-startTime; if (isPlaying) { if (isPreroll) { if (startTimelevels.getLives(songTime)) { //work out score stop(); } } } else melody.setVolume(1.0f); notes.drawFlakes(&levels,scale); lyrics.draw(); if (songTime>levels.length) { printf("stopping: %i (%i)\n",songTime,levels.length); stop(); } } ofDrawBitmapString(ofToString((float)songTime/1000.0f,1)+" "+ofToString(levels.getLevel(songTime))+" "+ofToString(notes.missedFlakes)+" of "+ofToString(levels.getLives(songTime)),10,(ofGetHeight()*gridY[1])-3); } void song::playerControl(int key){ notes.playerControl(key,keyThresh); } int song::missedNote(){ return notes.missedNote; } int song::hitNote(){ int n=notes.hitNote; notes.hitNote=-1; return n; } int song::getLevel(long time){ return levels.getLevel(time-startTime); }