1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
|
#include "music.h"
//event times & durations are absolute integer milliseconds
//---------------------------------------------------------------------------------------------------------------------------------------------
note::note(int n,int v,int d){
num=n;
velocity=v;
duration=d;
activated=false;
}
//---------------------------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------------------------------------
musicscore::musicscore() {
timeframe=2000;
flake.loadImage("flake.png");
flake.setAnchorPercent(0.5,0.5);
}
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<int,note*> 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<XML.getNumTags("TrackChunk");i++) {
XML.pushTag("TrackChunk",i);
for (int i=0;i<XML.getNumTags("Event");i++) {
time+=(SPT*XML.getAttribute("Event", "DeltaTimeTicks",0,i));
if (XML.getAttribute("Event", "Label","",i)=="TempoSet") {
string data=XML.getAttribute("Event", "Data","",i);
char* endptr;
int d1=strtoul(data.substr(6,2).c_str(),&endptr,16);
int d2=strtoul(data.substr(9,2).c_str(),&endptr,16);
int d3=strtoul(data.substr(12,2).c_str(),&endptr,16);
int MPQN=(d1<<16)+(d2<<8)+d3;
SPT =(MPQN/1000000.0f)/TPQN;
//printf("Tempo change: seconds per tick now: %f\n",SPT);
}
if (XML.getAttribute("Event", "Label","",i)=="NoteOn"||XML.getAttribute("Event", "Label","",i)=="NoteOff") {
string data=XML.getAttribute("Event", "Data","",i);
char* endptr;
int d1=strtoul(data.substr(0,2).c_str(),&endptr,16);
int d2=strtoul(data.substr(3,2).c_str(),&endptr,16);
int id=strtoul(XML.getAttribute("Event", "Id","",i).c_str(),&endptr,16);
if (id==128||id==144) events[(int)(time*1000.0f)]=new note(d1,d2,id); //noteon/off
}
}
XML.popTag();
}
}
XML.popTag();
}
//iterate events and compute durations now the absolute times are established: extract to notes
map<int,note*>::iterator iter1;
map<int,note*>::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;
iter1->second->updown=iter1->second->num<n?-1:iter1->second->num==n?0:1;
n=iter1->second->num;
notes[iter1->first]=iter1->second;
printf("%i: noteon %i %i %i\n",iter1->first,iter1->second->num,iter1->second->duration,iter1->second->updown);
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);
//decimate notes to generate flakes that can be interacted with
int noteThresh=1000;
note *lastNote=notes.begin()->second;
int lastTime=0;
stars[notes.begin()->first]=notes.begin()->second;
for (iter1 = notes.begin(); iter1 != notes.end(); iter1++) {
if ((iter1->second->num/5!=lastNote->num/5)||(iter1->first-lastTime>noteThresh)) {
stars[iter1->first]=iter1->second;
}
lastNote=iter1->second;
lastTime=iter1->first;
}
interactionThresh=200; //how long player has to respond
missedTime=-1;
}
void musicscore::setTimeframe(int millis) {timeframe=millis;}
void musicscore::draw() {
ofEnableAlphaBlending();
int scoreStart=ofGetElapsedTimeMillis()-startTime;
int scoreEnd=scoreStart+timeframe;
//temporary drawing method 46h - 52h
int numnotes=16;
int firstnote=70;
float widthStep=((float)ofGetWidth())/numnotes;
float heightStep=((float)ofGetHeight())/timeframe;
map<int,note*>::iterator iter;
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));
//different methods for generating flakes
//ideally theres a variable clumping factor, this means pre-processing the flakes though
//ofSetColor(ofColor::fromHsb(((float)thisnote*255)/numnotes,200,255));
//flake.draw((thisnote+0.5f)*widthStep,ofGetHeight()-(thisstart*heightStep));
//flake.draw((iter->second->updown*ofGetWidth()*0.33)+(ofGetWidth()*0.5),ofGetHeight()-(thisstart*heightStep));
//flake.draw((((thisnote/5)*5)+3.5f)*widthStep,ofGetHeight()-(thisstart*heightStep));
}
for (iter = stars.lower_bound(scoreStart-200); iter != stars.upper_bound(scoreEnd); ++iter) { //added extra 200ms for flake to leave screen
int thisnote=iter->second->num-firstnote;
int thisstart=iter->first-scoreStart;
int thislength=iter->second->duration;
// check player interaction
if (thisstart<interactionThresh) {
//this star needs to be interacted with
if (((thisnote/5)+1)==playerKey) {
//success!
iter->second->playerActivated();
}
}
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 stars. must be a better way
missedTime=-1;
for (iter = stars.upper_bound(scoreStart); iter != stars.lower_bound(0); --iter) {
if (!iter->second->activated) missedTime=scoreStart-iter->first;
else break;
}
ofDisableAlphaBlending();
}
void musicscore::playerControl(int key){
//0-3 - 0 is off
playerKey=key;
}
//---------------------------------------------------------------------------------------------------------------------------------------------
song::song(string backfile,string melfile,string notefile) {
backing.loadSound(backfile);
melody.loadSound(melfile);
notes.parseMidi(notefile);
isPlaying=false;
missedInterval=5000;
}
void song::setTimeframe(int millis) {notes.setTimeframe(millis);}
void song::play() {
backing.play();
melody.play();
startTime=ofGetElapsedTimeMillis();
notes.startTime=startTime;
isPlaying=true;
}
void song::stop() {
backing.stop();
melody.stop();
isPlaying=false;
}
void song::preRoll(long preroll) {
startTime=ofGetElapsedTimeMillis()+preroll;
notes.startTime=startTime;
isPreroll=true;
isPlaying=true;
}
void song::draw(){
//how to deal with end/ track length/ part of game?
if (isPreroll) {
if (startTime<ofGetElapsedTimeMillis()) {
backing.play();
melody.play();
isPreroll=false;
}
}
if (notes.missedTime>0) {
if (notes.missedTime>missedInterval) stop();
else melody.setVolume(0.0f); //1.0f-((float)notes.missedTime/(float)missedInterval));
}
else melody.setVolume(1.0f);
notes.draw();
}
void song::playerControl(int key){
//0-3 - 0 is off
notes.playerControl(key);
}
|