void VideoManager::loadVideo(char* filename)
{
	FILE* fp = NULL;
	if ( fopen_s(&fp, filename, "rb") != 0 )
	{
		al_trace("Movie %s is missing.\r\n", filename);
		return; // do NOT summon error mode for this because the application might in debugging/lite mode
	}

	// get the length of the video
	fseek(fp, 0, SEEK_END);
    long fsize = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	
	// load the entire video into an unreasonably large memory buffer!
	videoBuffer = malloc(fsize);
	fread(videoBuffer, fsize, 1, fp);
	fclose(fp);

	// sanity check the buffer contents or else APEG may hang
	if ( ((char*)videoBuffer)[0] == 'O' && ((char*)videoBuffer)[1] == 'g' && ((char*)videoBuffer)[2] == 'g' && ((char*)videoBuffer)[3] == 'S' )
	{
		cmov = apeg_open_memory_stream(videoBuffer, fsize);
	}
	else
	{
		al_trace("Video stream did not seem to contain Ogg data.\r\n");
		unloadVideo();
	}
}
void VideoManager::loadVideoAtCurrentStep()
{
	char filename[256] = "DATA/video/";
	if ( script[currentStep].filename[0] == '*' )
	{
		strcat_s(filename, 256, script[currentStep-1].filename); // check for videos named '*'. I think the original game used this to denote "play that video again"
	}
	else
	{
		strcat_s(filename, 256, script[currentStep].filename);
	}
	strcat_s(filename, 256, ".ogg");

	// default case: show a placeholder texture
	unloadVideo();
	clear_to_color(frameData, makecol(255,255,255));
	textprintf_centre(frameData, font, 160, 90, makecol(0,0,0), "%s", script[currentStep].filename);

	loadVideo(filename);
	if ( cmov == NULL )
	{
		al_trace("Movie %s did not open for whatever reason.\r\n", filename);
		return;
	}

	if ( apeg_advance_stream(cmov, true) != APEG_OK)
	{
		al_trace("Video problem! Breakpoint!\r\n"); // doesn't really matter if it fails
	}
	blit(cmov->bitmap, frameData, 0, 0, 0, 0, 320, 192);
}
Beispiel #3
0
void print_disassembly(const word scommand)
{
    script_command s_c = command_list[scommand];
    
    if(s_c.args == 2)
    {
        al_trace("%14s: ", s_c.name);
        
        if(s_c.arg1_type == 0)
            al_trace("%10s (val = %9d), ", varToString(sarg1), get_register(sarg1));
        else
            al_trace("%10s (val = %9d), ", "immediate", sarg1);
            
        if(s_c.arg2_type == 0)
            al_trace("%10s (val = %9d)\n", varToString(sarg2), get_register(sarg2));
        else
            al_trace("%10s (val = %9d)\n", "immediate", sarg2);
    }
    else if(s_c.args == 1)
    {
        al_trace("%14s: ", s_c.name);
        
        if(s_c.arg1_type == 0)
            al_trace("%10s (val = %9d)\n", varToString(sarg1), get_register(sarg1));
        else
            al_trace("%10s (val = %9d)\n", "immediate", sarg1);
    }
    else
        al_trace("%14s\n", s_c.name);
        
}
Beispiel #4
0
void gamedata::set_dcounter(short change, byte c)
{
#ifdef DEBUG_GD_COUNTERS

    if(c==0)
        al_trace("Changing D counter %i from %i to %i\n", c, _dcounter[c], change);
        
#endif
        
    if(c>=32)  // Sanity check
        return;
        
    if(game!=NULL)
    {
        int ringID=current_item_id(itype_ring, true);
        _dcounter[c]=change;
        
        if(ringID!=current_item_id(itype_ring, true))
            ringcolor(false);
    }
    else
        _dcounter[c]=change;
        
    return;
}
Beispiel #5
0
void gamedata::change_HCpieces(short p)
{
#ifdef DEBUG_GD_HCP
    al_trace("Changing HCP by %d to %d\n",p, get_generic(0));
#endif
    change_generic(p, 0);
    return;
}
void renderStepZoneDMX(int player)
{
	int startColumn = player == 0 ? 0 : 4;
	if ( gs.isSingles() )
	{
		if ( gs.player[0].centerLeft )
		{
			startColumn = 0;
		}
		else if ( gs.player[0].centerRight )
		{
			startColumn = 4;
		}
		else
		{
			startColumn = 2; // center play
		}
	}
	int endColumn = gs.isDoubles ? 8 : startColumn+4;

	for ( int i = startColumn; i < endColumn; i++ )
	{
		int x = getColumnOffsetX_DMX(i);
		int blink = gs.player[player].stepZoneBlinkTimer > 0 ? 0 : 1;	// pick the state of the step zone
		int hitcolor = 0x7F000000;
		int outlineColor = 0xFFB4B4B4;
		const int fadeOutLength = 100;
		if ( blink == 1 )
		{
			static int colors[3] = { makeacol(41, 239, 115, 255), makeacol(239, 101, 190, 255), makeacol(76, 0, 190, 255) };
			int stageColor = gs.player[player].stagesLevels[gs.currentStage] % 10;
			outlineColor = colors[stageColor];
		}
 
		if ( gs.player[player].laneFlareColors[i] == 2 )
		{
			blink = 2; // currently holding a hold note in this column
		}

		int color = getColorOfColumn(i);
		if ( im.isKeyDown(i) )
		{
			hitcolor = color == 0 ? 0xEECC0000 : 0xEE0000CC;
		}
		else if ( im.getReleaseLength(i) <= fadeOutLength )
		{
			int a = getValueFromRange(0xFF, 0x7F, im.getReleaseLength(i) * 100/ fadeOutLength);
			if ( i ==4 )
			{
				al_trace("%d\r\n", a);
			}
			hitcolor = color == 0 ? makeacol32(0xFF, 0, 0, a) : makeacol32(0, 0, 0xFF, a);
		}
		renderStepLaneDMX(x, hitcolor, outlineColor);
		masked_blit(m_stepZoneSourceDMX[color], rm.m_backbuf, blink*34, 0, x, (gs.player[player].isColumnReversed(i) ? DMX_STEP_ZONE_REV_Y-34 : DMX_STEP_ZONE_Y), 34, 38);
	}
}
Beispiel #7
0
void gamedata::set_HCpieces(byte p)
{
#ifdef DEBUG_GD_HCP
    al_trace("Setting HCP to %i\n",p);
#endif
    
    set_generic(p, 0);
    return;
}
bool extioManager::attemptConnection(const char* port)
{
	isConnected = false;

	hSerial = CreateFile(port,
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);

	if(hSerial==INVALID_HANDLE_VALUE)
	{
		//al_trace("The IO board isn't on %s\n", port);
		return false; // it's okay - we'll try another port
	}

	//If connected we try to set the comm parameters
	DCB dcbSerialParams = {0};

	if ( !GetCommState(hSerial, &dcbSerialParams) )
	{
		al_trace("extioManager: failed to GET serial parameters!");
		return false;
	}

	//Define serial connection parameters for the arduino board
	dcbSerialParams.BaudRate = CBR_9600;
	dcbSerialParams.ByteSize = 8;
	dcbSerialParams.StopBits = ONESTOPBIT;
	dcbSerialParams.Parity = NOPARITY;

	if( !SetCommState(hSerial, &dcbSerialParams) )
	{
		al_trace("extioManager: failed to SET serial parameters!");
		return false;
	}

	isConnected = true;
	return true;
}
void VideoManager::update(UTIME dt)
{
	if ( isStopped || haxNoVideos )
	{
		return;
	}
	currentTime += dt;

	// check for a script step advance
	if ( (script[currentStep+1].timing != -1) && currentTime/10 >= script[currentStep+1].timing/3 ) // because there are 300 ticks per second
	{
		currentStep++;
		loadVideoAtCurrentStep();
	}
	// check for the movie ending and loop the whole thing after 5 seconds
	if ( script[currentStep+1].timing == -1 && currentStep > 1 && (currentTime/10 + 1500 >= script[currentStep].timing/3) )
	{
		currentStep = 1;
		currentTime = 0;
		al_trace("Restarting video script.\r\n");
		loadVideoAtCurrentStep();
	}

	if ( cmov != NULL )
	{
		if ( apeg_advance_stream(cmov, true) != APEG_OK)
		{
			al_trace("Video problem! Breakpoint!\r\n"); // doesn't really matter if it fails
		}
		if( cmov->frame_updated > 0 && cmov->bitmap != NULL )
		{
			//stretch_blit(cmov->bitmap, frameData, 0, 0, cmov->w, cmov->h, 0, 0, 320, 192);
			blit(cmov->bitmap, frameData, 0, 0, 0, 0, 320, 192);
		}
	}
}
// NOTE: currently only used for calculating the end time of hold notes
UTIME getMillisecondsAtBeat(float targetBeat, std::vector<struct BEAT_NOTE> *chart, int startIndex, UTIME startTime, float currentTimePerBeat)
{
	UTIME retval = startTime;
	float msPerBeat = currentTimePerBeat;
	float lastBeatProcessed = chart->at(startIndex).beat;
	float overshoot = 0;
	unsigned int i = 0;

	if ( lastBeatProcessed > targetBeat )
	{
		al_trace("Searching for a past beat. Set breakpoint.\r\n");
	}

	for ( i = startIndex; i < chart->size(); i++ )
	{
		// check for finishing
		if ( targetBeat == chart->at(i).beat )
		{
			break;
		}
		if ( targetBeat < chart->at(i).beat )
		{
			overshoot = chart->at(i).beat - targetBeat; // fix a bug where not taking this into account was causing holds to run until the next note!
			break;
		}

		if ( chart->at(i).type == BPM_CHANGE )
		{
			retval += ((chart->at(i).beat - lastBeatProcessed) * msPerBeat);
			msPerBeat = BPM_TO_MSEC(chart->at(i).param);
			lastBeatProcessed = chart->at(i).beat;
		}
		if ( chart->at(i).type == SCROLL_STOP )
		{
			retval += chart->at(i).param;
		}
	}

	if ( i == chart->size() )
	{
		i--;
	}

	retval += ((chart->at(i).beat - lastBeatProcessed - overshoot) * msPerBeat);
	return retval;
}
Beispiel #11
0
void gamedata::change_maxcounter(short change, byte c)
{
#ifdef DEBUG_GD_COUNTERS
    al_trace("Changing max counter %i from %i by +%i\n", c, _maxcounter[c], change);
#endif
    
    if(c==2)
    {
        change_maxbombs(change);
        return;
    }
    
    if(c>=32)  // Sanity check
        return;
        
    _maxcounter[c]=zc_max(0, _maxcounter[c]+change);
    return;
}
Beispiel #12
0
int Win32Data::zqSetCustomCallbackProc(HWND hWnd)
{
    Win32Mutex mutex;
    mutex.Lock();
    
    memset((void*)&win32data, 0, sizeof(Win32Data));
    win32data.hasFocus = true;
    
    win32data.hWnd = hWnd;
    
    if(win32data.hWnd)
        win32data.isValid = true;
    else return -1;
    
    hAllegroProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC);
    
    if(!hAllegroProc)
    {
        win32data.isValid = false;
        return -1;
    }
    
    win32data.hInstance = ::GetWindowLongPtr(hWnd, GWLP_HINSTANCE);
    
    if(!win32data.hInstance)
    {
        win32data.isValid = false;
        return -1;
    }
    
    win32data.hId = ::GetWindowLongPtr(hWnd, GWLP_ID);
    
    //assign a new callback
    LONG_PTR pcb = ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)Win32Data::zqWindowsProc);
    
    if(!pcb)
    {
        al_trace("Error: Cannot Set Windows Procedure.");
        win32data.isValid = false;
        return -1;
    }
    
    return 0;
}
Beispiel #13
0
void gamedata::set_maxcounter(word change, byte c)
{
#ifdef DEBUG_GD_COUNTERS

    if(c==0) al_trace("Changing max counter %i from %i to %i\n", c, _maxcounter[c], change);
    
#endif
    
    if(c==2)
    {
        set_maxbombs(change);
        return;
    }
    
    if(c>=32)  // Sanity check
        return;
        
    _maxcounter[c]=change;
    return;
}
Beispiel #14
0
void gamedata::change_counter(short change, byte c)
{
#ifdef DEBUG_GD_COUNTERS
    al_trace("Changing counter %i from %i by %i\n", c, _counter[c], change);
#endif
    
    if(c>=32)  // Sanity check
        return;
        
    if(game!=NULL)
    {
        int ringID=current_item_id(itype_ring, true);
        _counter[c]=vbound(_counter[c]+change, 0, _maxcounter[c]);
        
        if(ringID!=current_item_id(itype_ring, true))
            ringcolor(false);
    }
    else
        _counter[c]=vbound(_counter[c]+change, 0, _maxcounter[c]);
        
    return;
}
void AnalyticsManager::logEventToServer(const char* serverURL, const char* eventCategory, const char* eventType, const char* eventLabel, int eventValue)
{
	CURL* curl = curl_easy_init();
	CURLcode res;

	// construct the postfields using much string math
	char postfields[512] = "";
	sprintf_s(postfields, 512, "v=%d&tid=%s&an=%s&cid=%s&t=%s&ec=%s&ea=%s&debug=%d", 1, TRACKING_ID, TRACKING_APP_NAME, clientID, "event", eventCategory, eventType, isDebugMode);
	if ( eventLabel[0] != 0 )
	{
		char* tempEncode = curl_easy_escape(curl, eventLabel, strlen(eventLabel));
		strcat_s(postfields, 512, "&el=");
		strcat_s(postfields, 512, tempEncode);
		curl_free(tempEncode);
	}
	if ( eventValue != -1 )
	{
		char tempEncode[128] = "";
		_itoa_s(eventValue, tempEncode, 64, 10);
		strcat_s(postfields, 512, "&ev=");
		strcat_s(postfields, 512, tempEncode);
	}

	// post the data using curl
	if ( curl )
	{
		// post to Google Analytics first
	    curl_easy_setopt(curl, CURLOPT_URL, serverURL);
		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields); 
		res = curl_easy_perform(curl);
		if (res != CURLE_OK)
		{
			al_trace("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
		}
		
	    curl_easy_cleanup(curl);
	}
}
Beispiel #16
0
void gamedata::set_counter(word change, byte c)
{
#ifdef DEBUG_GD_COUNTERS
    al_trace("Changing counter %i from %i to %i\n", c, _counter[c], change);
#endif
    
    if(c>=32)  // Sanity check
        return;
        
    if(game!=NULL)
    {
        int ringID=current_item_id(itype_ring, true);
        _counter[c]=zc_max(change, 0);
        
        // ringcolor is very slow, so make sure the ring has actually changed
        if(ringID!=current_item_id(itype_ring, true))
            ringcolor(false);
    }
    else
        _counter[c]=zc_max(change, 0);
        
    return;
}
bool extioManager::updateInitialize(UTIME dt)
{
	if ( !isConnected )
	{
		comPort++;
		if ( comPort < 10 )
		{
			char port[] = "\\\\.\\COM0";

			port[7] = comPort + '0';
			attemptConnection(port);
			connectionState = 1;
		}
		else
		{
			al_trace("Can't find the IO board on any com port.\r\n");
			comPort = -1;
			connectionState = 0;
			return false;
		}

		powerOnTime = 0;
	}
	else
	{
		powerOnTime += dt;

		if ( connectionState == 1 )
		{
			if ( powerOnTime >= ARDUINO_WAIT_TIME )
			{
				WriteData("DMX", PACKET_SIZE);
				connectionState = 2;
			}
		}
		else if ( connectionState == 2 )
		{
			if ( isPacketReady() )
			{
				ReadData(inputBuffer, PACKET_SIZE);
				if ( inputBuffer[0] == 'O' && inputBuffer[1] == 'K' && inputBuffer[2] == '!' )
				{
					isTalking = true;
					connectionState = 3;
					al_trace("Handshake accepted! Found the IO board on port %d.\r\n", comPort);
					WriteData("I", 1); // begin the input request loop
				}
				else
				{
					// abort - try another port
					powerOnTime = 0;
					isConnected = false;
					connectionState = 0;
					al_trace("The handshake was incorrect on port %d. Checking other ports.\r\n", comPort);
				}
			}
			else if ( powerOnTime >= ARDUINO_TIMEOUT )
			{
				// abort - try another port
				powerOnTime = 0;
				isConnected = false;
				connectionState = 0;
				al_trace("IO board timeout on port %d. Checking other ports.\r\n", comPort);
			}
		}
		else if ( connectionState == 3 )
		{
			al_trace("Do not call updateInitialize() after success!\r\n");
		}
	}

	return true;
}
void processChartString(std::vector<struct BEAT_NOTE> *chart, char* string, int side)
{
	int currentIndex = 0;
	int beatDivision = 2; // 1/8 beats are the default in DWI Files
	float totalBeatsProcessed = 0.0;
	float prevBeatProcessed = 0.0; // crtical for hold notes
	float beatOfHold[4] = { -1, -1, -1, -1 };

	// when side == 1 then add 4 to every column for the right half of a doubles chart
	int doublesFactor = side == 1 ? 4 : 0;
	
	while ( string[currentIndex] != 0 )
	{
		unsigned char ch = string[currentIndex];
		int columns[4] = { -1, -1, -1, -1 };

		// punctuation has various meanings
		if ( ch == '0' )
		{
			prevBeatProcessed = totalBeatsProcessed;
			totalBeatsProcessed += 1.0/beatDivision;
			currentIndex++;
			continue;
		}
		else if ( ch == '\r' || ch == '\n' )
		{
			currentIndex++;
			continue; // not needed, but nice for debugging below
		}
		else if ( ch == '(' )
		{
			beatDivision = 4; // parens mean 1/16th notes
			currentIndex++;
			continue;
		}
		else if ( ch == '[' )
		{
			beatDivision = 6; // brackets mean 1/24th notes
			currentIndex++;
			continue;
		}
		else if ( ch == '{' )
		{
			beatDivision = 16; // curly braces mean 1/64th notes
			currentIndex++;
			continue;
		}
		else if ( ch == '`' ) // == 96
		{
			beatDivision = 48; // this `00000' nonsense means 192nd notes
			currentIndex++;
			continue;
		}
		else if ( ch == ')' || ch == ']' || ch == '}' || ch == 250 || ch == 39 )
		{
			beatDivision = 2; // back to 1/8th notes
			currentIndex++;
			continue;
		}
		else if ( ch == '!' )
		{
			// what is to the right of the bang?
			currentIndex++; // skip over the bang
			columns[0] = getColumn(string[currentIndex], 0);
			columns[1] = getColumn(string[currentIndex], 1);

			if ( columns[0] != -1 && beatOfHold[columns[0]] == -1 )
			{
				beatOfHold[columns[0]] = prevBeatProcessed; // store when the hold starts
			}
			if ( columns[1] != -1 && beatOfHold[columns[1]] == -1 )
			{
				beatOfHold[columns[1]] = prevBeatProcessed; // store when the hold starts
			}
		}

		// look for regular notes
		else if ( (ch >= '0' && ch <= '9') || ch == 'A' || ch == 'B' )
		{
			columns[0] = getColumn(ch, 0);
			columns[1] = getColumn(ch, 1);
		}
		else if ( ch == '<' ) // this special character means "tie columns together" to make triples and quads
		{
			int currentColumn = 0;
			currentIndex++;
			bool startedHolds = false;

			while ( string[currentIndex] != '>' )
			{
				ch = string[currentIndex];
				if ( startedHolds == false )
				{
					if ( ch == '!' )
					{
						startedHolds = true; // a triple is starting a hold
						currentIndex++;
						continue;
					}
					if ( getColumn(ch, 0) != -1 )
					{
						columns[currentColumn] = getColumn(ch, 0);
						currentColumn = currentColumn == 3 ? 3 : currentColumn + 1;
					}
					if ( getColumn(ch, 1) != -1 )
					{
						columns[currentColumn] = getColumn(ch, 1);
						currentColumn = currentColumn == 3 ? 3 : currentColumn + 1;
					}
				}
				else
				{
					/* TODO: fix this crazy corner case
					// this hold is string from inside a < > combination tag, so use the current time
					int col0 = getColumn(ch, 0);
					int col1 = getColumn(ch, 1);

					if ( col0 != -1 && beatOfHold[col0] == -1 )
					{
						beatOfHold[col0] = totalBeatsProcessed; // store when the hold starts
					}
					if ( col1 != -1 && beatOfHold[col1] == -1 )
					{
						beatOfHold[col1] = totalBeatsProcessed; // store when the hold starts
					}
					//*/
				}
				currentIndex++;
			}
		}
		else
		{
			al_trace("Unknown symbol: %c", ch);
		}

		// create a freeze note? the end of a hold is signaled by a tap note in a column that was marked as held on a previous pass
		if ( ch != '!' )
		{
			for ( int d = 0; d < 4; d++ )
			{
				if ( columns[d] != -1 && beatOfHold[columns[d]] != -1 )
				{
					struct BEAT_NOTE a;
					a.beat = beatOfHold[columns[d]];
					a.column = columns[d] + doublesFactor;
					a.type = HOLD_START;
					a.param = totalBeatsProcessed;

					beatOfHold[columns[d]] = -1; // clear the hold
					columns[d] = -1; // clear the tap which ended this hold
					chart->push_back(a); 
				}
			}
		}

		// this can happen if a hold ends - sort all the -1 columns to the end of the list
		// for example {-1, 2, -1, -1} becomes {2, -1, -1, -1}
		if ( columns[0] == -1 && (columns[1] != -1 || columns[2] != -1 || columns[3] != -1) )
		{
			columns[0] = columns[1];
			columns[1] = columns[2];
			columns[2] = columns[3];
			columns[3] = -1;
		}
		if ( columns[1] == -1 && (columns[2] != -1 || columns[3] != -1) )
		{
			columns[1] = columns[2];
			columns[2] = columns[3];
			columns[3] = -1;
		}
		if ( columns[2] == -1 && columns[3] != -1 )
		{
			columns[2] = columns[3];
			columns[3] = -1;
		}

		// create any notes that need to be made
		if ( string[currentIndex-1] != '!' ) // this doesn't count as a note, nor should it increment time
		{
			for ( int d = 0; d < 4; d++ )
			{
				if ( columns[d] != -1 )
				{
					struct BEAT_NOTE a;
					a.beat = totalBeatsProcessed;
					a.column = columns[d] + doublesFactor;
					a.type = TAP;
					chart->push_back(a);
				}
			}

			prevBeatProcessed = totalBeatsProcessed;
			totalBeatsProcessed += 1.0/beatDivision;
		}
		currentIndex++;
	}

	// finally, put an end-of-song marker here
	struct BEAT_NOTE endmarker;
	endmarker.beat = totalBeatsProcessed;
	endmarker.type = END_SONG;
	chart->push_back(endmarker);
}
int readDWI(std::vector<struct ARROW> *chart, std::vector<struct FREEZE> *holds, int songID, int chartType)
{
	char filename[] = "DATA/dwis/101.dwi";
	FILE* fp = NULL;
	std::vector<struct BEAT_NOTE> beats;

	// hopefully open the target DWI file
	filename[10] = (songID/100 % 10) + '0';
	filename[11] = (songID/10 % 10) + '0';
	filename[12] = (songID % 10) + '0';

	if ( fopen_s(&fp, filename, "rt") != 0 )
	{
		// as a fall back, open a memory dump of a chart (legacy chart data)
		return readDMXSQ(chart, holds, songID, chartType);
	}

	// fix any potential bugs regarding data from one chart accidentally merging into the next chart
	chart->clear();
	holds->clear();
	int gap = 0;
	float timePerBeat = 400.0; // 150 BPM

	// start reading tags
	readNextTag(fp);
	while ( tagName[0] != 0 )
	{
		if ( _strcmpi("SINGLE", tagName) == 0 )
		{
			if ( _strcmpi("BASIC", tagValue) == 0 && chartType == SINGLE_MILD )
			{
				processChartString(&beats, tagLeftSide, 0);
			}
			else if ( _strcmpi("ANOTHER", tagValue) == 0 && chartType == SINGLE_WILD )
			{
				processChartString(&beats, tagLeftSide, 0);
			}
			else if ( _strcmpi("MANIAC", tagValue) == 0 && chartType == SINGLE_ANOTHER )
			{
				processChartString(&beats, tagLeftSide, 0);
			}
		}
		if ( _strcmpi("DOUBLE", tagName) == 0 )
		{
			if ( _strcmpi("BASIC", tagValue) == 0 && chartType == DOUBLE_MILD )
			{
				processChartString(&beats, tagLeftSide, 0);
				processChartString(&beats, tagRightSide, 1);
			}
			else if ( _strcmpi("ANOTHER", tagValue) == 0 && chartType == DOUBLE_WILD )
			{
				processChartString(&beats, tagLeftSide, 0);
				processChartString(&beats, tagRightSide, 1);
			}
			else if ( _strcmpi("MANIAC", tagValue) == 0 && chartType == DOUBLE_ANOTHER )
			{
				processChartString(&beats, tagLeftSide, 0);
				processChartString(&beats, tagRightSide, 1);
			}
		}
		if ( _strcmpi("BPM", tagName) == 0 )
		{
			float bpm = atof(tagValue);
			timePerBeat = BPM_TO_MSEC(bpm);

			// set the initial scroll rate // WHY DOES THIS MAKE SONG NOT END? affects end of song marker somehow?
			struct ARROW a;
			a.timing = 0;
			a.color = bpm;
			a.type = BPM_CHANGE;
			a.judgement = UNSET;
			chart->push_back(a);
		}
		if ( _strcmpi("GAP", tagName) == 0 )
		{
			gap = atoi(tagValue);
			al_trace("gap = %d\r\n", gap);
		}
		if ( _strcmpi("CHANGEBPM", tagName) == 0 ) // #CHANGEBPM:992.000=95.000,1016.000=190.000;
		{
			char* token, *next;
			token = strtok_s(tagValue, ",;", &next);

			while ( token != NULL )
			{
				char leftSide[32], rightSide[32];
				char* equalsPos = strchr(token, '=');
				if ( equalsPos == NULL )
				{
					continue;
				}
				strncpy_s(leftSide, token, equalsPos - token);
				strcpy_s(rightSide, equalsPos+1);

				struct BEAT_NOTE b;
				b.beat = atof(leftSide)/4.0;
				b.param = atof(rightSide);
				b.type = BPM_CHANGE;
				beats.push_back(b);

				token = strtok_s(NULL, ",;", &next);
			}
		}
		if ( _strcmpi("FREEZE", tagName) == 0 ) // #FREEZE:668.000=327.000,1292.000=967.000;
		{
			char* token, *next;
			token = strtok_s(tagValue, ",;", &next);

			while ( token != NULL )
			{
				char leftSide[32], rightSide[32];
				char* equalsPos = strchr(token, '=');
				if ( equalsPos == NULL )
				{
					continue;
				}
				strncpy_s(leftSide, token, equalsPos - token);
				strcpy_s(rightSide, equalsPos+1);

				struct BEAT_NOTE b;
				b.beat = atof(leftSide)/4.0;
				b.param = atof(rightSide);
				b.type = SCROLL_STOP;
				beats.push_back(b);
				
				token = strtok_s(NULL, ",;", &next);
			}
		}

		// next loop
		readNextTag(fp);
	}

	sort(beats.begin(), beats.end(), sortNoteFunction);

	// for each item in the beats vector, create a struct ARROW (real chart object) and translate 'DWI beats' to milliseconds
	int numNotes = beats.size();
	float currentTime = gap;
	float lastBeatProcessed = 0.0;

	for ( int i = 0; i < numNotes; i++ )
	{
		struct ARROW a;
		struct FREEZE f;
		float beatsDifference = beats[i].beat - lastBeatProcessed;
		lastBeatProcessed = beats[i].beat;

		currentTime += timePerBeat*beatsDifference;

		if (currentTime < 0)
		{
			continue; // uh-oh;
		}
		al_trace("%f\r\n", currentTime);

		switch ( beats[i].type )
		{
		case TAP:
			a.timing = currentTime;
			a.color = calculateArrowColor(a.timing, timePerBeat);
			a.type = TAP;
			a.columns[0] = beats[i].column;
			a.judgement = UNSET;
			chart->push_back(a);
			break;
		case HOLD_START:
			f.startTime = currentTime;
			f.columns[0] = beats[i].column;
			f.endTime1 = getMillisecondsAtBeat(beats[i].param, &beats, i, currentTime, timePerBeat); //it ends at beat beats[i].param
			holds->push_back(f); 
			break;
		case BPM_CHANGE:
			a.timing = currentTime;
			a.color = beats[i].param;
			a.type = BPM_CHANGE;
			a.judgement = UNSET;
			chart->push_back(a);

			timePerBeat = BPM_TO_MSEC(beats[i].param); // new tempo! the length of a beat has henceforth and immediately changed
			break;
		case SCROLL_STOP:
			a.timing = currentTime;
			a.color = beats[i].param;
			a.type = SCROLL_STOP;
			chart->push_back(a);

			currentTime += beats[i].param; // advance the time
			break;
		case END_SONG:
			a.timing = currentTime + 1000; // TODO: something better than this, maybe check the mp3?
			a.type = END_SONG;
			chart->push_back(a);
			break;
		default:
			al_trace("IMPOSSIBLE NOTE TYPE IN readDWI() %d\r\n", beats[i].type);
		}
	}

	fclose(fp);

	// count the maximum score that this chart is worth. it is needed while the chart is being played
	int maxScore = 0;
	for ( size_t i = 0; i < chart->size(); i++ )
	{
		if ( chart->at(i).type == TAP || chart->at(i).type == JUMP )
		{
			maxScore += 2;
		}
	}
	maxScore += holds->size()*2;
	return maxScore;
}