#include "music.h" //event times & durations are absolute integer milliseconds //--------------------------------------------------------------------------------------------------------------------------------------------- musicscore::musicscore() { timeframe=2000; flake.loadImage("flake.png"); flake.setAnchorPercent(0.5,0.5); missedLast=false; } 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){ //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]=notes.begin()->second; for (iter = notes.begin(); iter != notes.end(); iter++) { float songPos=((float)iter->first)/songDuration; if ((iter->second->num/5!=lastNote->num/5)||(iter->first-lastTime>((songPos*threshEnd)+((1.0f-songPos)*threshStart)))) { flakes[iter->first]=iter->second; } lastNote=iter->second; lastTime=iter->first; } } void musicscore::setTimeframe(int millis) {timeframe=millis;} void musicscore::draw(levelscore *levels) { ofEnableAlphaBlending(); int scoreStart=ofGetElapsedTimeMillis()-startTime; 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)); ofRect(thisnote*widthStep,ofGetHeight()-(thisstart*heightStep),widthStep,-(thislength*heightStep)); } //draw flakes for (iter = flakes.lower_bound(scoreStart-200); iter != flakes.upper_bound(scoreEnd); ++iter) { //extra 200ms for flake to leave screen 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)); flake.draw((((thisnote/5)*5)+3.5f)*widthStep,ofGetHeight()-(thisstart*heightStep),flake.getWidth()/2,flake.getHeight()/2); } //check for unactivated flakes within this segment: is there a more efficient way? //is it the number of flakes they can lose per segment? missedFlakes=0; missedLast=false; for (iter = flakes.lower_bound(levels->getLowerBound(levels->getLevel(scoreStart))); iter != flakes.upper_bound(scoreStart); ++iter){ if (!iter->second->activated) { missedFlakes++; } missedLast=!iter->second->activated; } 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) { iter->second->activate(); } } //--------------------------------------------------------------------------------------------------------------------------------------------- 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=200; } 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; } void song::play() { backing.play(); melody.play(); startTime=ofGetElapsedTimeMillis(); notes.startTime=startTime; isPlaying=true; notes.makeFlakes(fThreshStart,fThreshEnd); } void song::stop() { backing.stop(); melody.stop(); isPlaying=false; } void song::preRoll(long preroll) { startTime=ofGetElapsedTimeMillis()+preroll; notes.startTime=startTime; isPreroll=true; isPlaying=true; notes.makeFlakes(fThreshStart,fThreshEnd); } void song::draw(){ int songTime=ofGetElapsedTimeMillis()-startTime; if (isPlaying) { if (isPreroll) { if (startTimelevels.getLives(songTime)) { //work out score stop(); } } } else melody.setVolume(1.0f); notes.draw(&levels); } ofDrawBitmapString(ofToString((float)songTime/1000.0f,3)+" "+ofToString(levels.getLevel(songTime))+" "+ofToString(notes.missedFlakes)+" of "+ofToString(levels.getLives(songTime)),10,ofGetHeight()-15); } void song::playerControl(int key){ notes.playerControl(key,keyThresh); }