#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(float hOffs){ 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,hOffs+(ofGetHeight()/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; int startnote; if( !XML.loadFile(filename) ){ printf("unable to load %s check data/ folder\n",filename.c_str()); }else{ startnote=XML.getAttribute("MidiFile", "startnote",70,0); 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::printNotes() { map::iterator iter; for (iter = notes.begin(); iter != notes.end(); iter++) { printf("%i: %i, %i for %i\n",iter->first,iter->second->num,iter->second->velocity,iter->second->duration); } } void musicscore::makeRandomNotes(int length,int startFreq,int endFreq){ notes.clear(); int time=startFreq; while (time::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; //((notemap(iter->second->num)!=notemap(lastNote->num))|| if ((levels->nextLevelTime(iter->first)>2000)&&(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; nowpoint=timeframe*0.33f; } void musicscore::drawNotes(float hOffs,levelscore *levels) { int scoreStart=ofGetElapsedTimeMillis()-startTime-nowpoint; int scoreEnd=scoreStart+timeframe; //note drawing 46h - 52h int numnotes=16; int firstnote=70; float widthStep=((float)ofGetHeight())/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(hOffs+thisnote*widthStep,ofGetHeight()-(thisstart*heightStep),widthStep,-(thislength*heightStep)); } //visualise keyThreshold ofSetColor(255,0,0,70); ofRect(hOffs,ofGetHeight()-(nowpoint*heightStep),ofGetHeight(),keyThresh*heightStep); } void musicscore::drawFlakes(float hOffs,levelscore *levels,float scale) { ofEnableAlphaBlending(); int now=ofGetElapsedTimeMillis()-startTime; int screenStart=now-nowpoint; int screenEnd=screenStart+timeframe; //note drawing 46h - 52h int numnotes=16; int firstnote=70; float widthStep=((float)ofGetHeight())/numnotes; float heightStep=((float)ofGetHeight())/timeframe; map::iterator iter; //draw flakes for (iter = flakes.lower_bound(screenStart); iter != flakes.upper_bound(screenEnd); iter++) { int thisnote=iter->second->num-firstnote; int thisstart=iter->first-screenStart; 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]*ofGetHeight())+hOffs,ofGetHeight()-(thisstart*heightStep),scale); //todo - make all drawing resolution independent } //check for unactivated flakes within this level: is there a more efficient way? //need to know when a flake has just been missed //this seems to mess up a little bit when switching levels //also: when the window for hitting flakes gets too big, it messes up missedFlakes=0; missedLast=false; map::iterator missed=flakes.end(); int lvlstart=levels->getLowerBound(levels->getLevel(now)); int window=max(lvlstart,now-keyThresh); for (iter = flakes.lower_bound(lvlstart); iter != flakes.lower_bound(window); 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 or else flakes.end() if ((missed!=flakes.end())&&(missedFlake!=missed)) { missedFlake=missed; missedNote=notemap(missed->second->num); } else missedNote=-1; } void musicscore::playerControl(int key){ map::iterator iter; int now=ofGetElapsedTimeMillis()-startTime; for (iter = flakes.lower_bound(now-keyThresh); iter != flakes.upper_bound(now); iter++) { if (key==notemap(iter->second->num)) { iter->second->activate(); hitNote=key; } } } //--------------------------------------------------------------------------------------------------------------------------------------------- song::song(){ isPractice=true; } song::~song(){ backing.unloadSound(); melody.unloadSound(); } 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; isPractice=false; } void song::setTimeframe(int millis) {notes.setTimeframe(millis);} void song::setKeyThresh(int millis) {notes.keyThresh=millis;} void song::setFlakeThresh(int tS,int tE) { fThreshStart=tS; fThreshEnd=tE; } int song::getCurrentTime(){ return ofGetElapsedTimeMillis()-startTime; } void song::play() { preRoll(0); } void song::stop() { backing.stop(); melody.stop(); isPlaying=false; } void song::preRoll(long preroll) { startTime=ofGetElapsedTimeMillis()+preroll; if (isPractice) { notes.makeRandomNotes(20000,4000,1000); levels.makePractice(20000); } else { lyrics.start(startTime); } notes.makeFlakes(fThreshStart,fThreshEnd,&levels); notes.start(startTime); isPreroll=true; isPlaying=true; gameover=false; } void song::drawNotes(float hOffs){ notes.drawNotes(hOffs,&levels); } string song::getScoreString(){ int songTime=ofGetElapsedTimeMillis()-startTime; return ofToString((float)songTime/1000.0f,1)+": level "+ofToString(levels.getLevel(songTime))+": lives "+ofToString(notes.missedFlakes)+" of "+ofToString(levels.getLives(songTime)); } string song::getName(){ return levels.name; } int song::getLength(){ return levels.length; } bool song::isGameover(){ return gameover; } bool song::isFinished(){ return (ofGetElapsedTimeMillis()-startTime>=levels.length); } void song::draw(float hOffs,float scale){ int songTime=ofGetElapsedTimeMillis()-startTime; if (isPlaying) { if (isPreroll&&(!isPractice)) { if (startTimelevels.getLives(songTime)) { //work out score //stop(); gameover=true; } } } else melody.setVolume(1.0f); if (!isPractice) lyrics.draw(hOffs); if (songTime>levels.length) { printf("stopping: %i (%i)\n",songTime,levels.length); //stop(); gameover=true; } } else { //fade out song + melody :: clean up } } void song::playerControl(int key){ notes.playerControl(key); } 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); }