/*
 * Copyright 2005-2006, Haiku.
 * 
 * Copyright (c) 2002-2004 Matthijs Hollemans
 * Copyright (c) 2002 Jerome Leveque
 * Copyright (c) 2002 Paul Stadler
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Matthijs Hollemans
 *		Jérôme Leveque
 *		Paul Stadler
 */
 
#include <File.h>
#include <List.h>
#include <MidiDefs.h>
#include <MidiStore.h>
#include <stdlib.h>
#include <string.h>
 
#include "debug.h"
 
 
struct BMidiEvent {
	BMidiEvent()
	{
		byte1  = 0;
		byte2  = 0;
		byte3  = 0;
		data   = NULL;
		length = 0;
	}
 
	~BMidiEvent()
	{
		free(data);
	}
 
	uint32 time;    // either ticks or milliseconds
	bool   ticks;   // event is from MIDI file
	uchar  byte1;
	uchar  byte2;
	uchar  byte3;
	void*  data;    // sysex data
	size_t length;  // sysex data size
	int32  tempo;   // beats per minute
};
 
 
static int
compare_events(const void* event1, const void* event2)
{
	BMidiEvent* e1 = *((BMidiEvent**) event1);
	BMidiEvent* e2 = *((BMidiEvent**) event2);
 
	return (e1->time - e2->time);
}
 
 
//	#pragma mark -
 
 
BMidiStore::BMidiStore()
{
	fEvents = new BList;
	fCurrentEvent = 0;
	fStartTime = 0;
	fNeedsSorting = false;
	fBeatsPerMinute = 60;
	fTicksPerBeat = 240;
	fFile = NULL;
	fHookFunc = NULL;
	fLooping = false;
	fPaused = false;
	fFinished = false;
	fInstruments = new bool[128];
}
 
 
BMidiStore::~BMidiStore()
{
	for (int32 t = 0; t < fEvents->CountItems(); ++t) {
		delete EventAt(t);
	}
 
	delete fEvents;
	delete[] fInstruments;
}
 
 
void
BMidiStore::NoteOff(uchar channel, uchar note, uchar velocity,
	uint32 time)
{
	BMidiEvent* event = new BMidiEvent;
	event->time  = time;
	event->ticks = false;
	event->byte1 = B_NOTE_OFF | (channel - 1);
	event->byte2 = note;
	event->byte3 = velocity;
	AddEvent(event);
}
 
 
void
BMidiStore::NoteOn(uchar channel, uchar note,
	uchar velocity, uint32 time)
{
	BMidiEvent* event = new BMidiEvent;
	event->time  = time;
	event->ticks = false;
	event->byte1 = B_NOTE_ON | (channel - 1);
	event->byte2 = note;
	event->byte3 = velocity;
	AddEvent(event);
}
 
 
void
BMidiStore::KeyPressure(uchar channel, uchar note,
	uchar pressure, uint32 time)
{
	BMidiEvent* event = new BMidiEvent;
	event->time  = time;
	event->ticks = false;
	event->byte1 = B_KEY_PRESSURE | (channel - 1);
	event->byte2 = note;
	event->byte3 = pressure;
	AddEvent(event);
}
 
 
void
BMidiStore::ControlChange(uchar channel, uchar controlNumber,
	uchar controlValue, uint32 time)
{
	BMidiEvent* event = new BMidiEvent;
	event->time  = time;
	event->ticks = false;
	event->byte1 = B_CONTROL_CHANGE | (channel - 1);
	event->byte2 = controlNumber;
	event->byte3 = controlValue;
	AddEvent(event);
}
 
 
void
BMidiStore::ProgramChange(uchar channel, uchar programNumber,
	uint32 time)
{
	BMidiEvent* event = new BMidiEvent;
	event->time  = time;
	event->ticks = false;
	event->byte1 = B_PROGRAM_CHANGE | (channel - 1);
	event->byte2 = programNumber;
	AddEvent(event);
}
 
 
void
BMidiStore::ChannelPressure(uchar channel, uchar pressure, uint32 time)
{
	BMidiEvent* event = new BMidiEvent;
	event->time  = time;
	event->ticks = false;
	event->byte1 = B_CHANNEL_PRESSURE | (channel - 1);
	event->byte2 = pressure;
	AddEvent(event);
}
 
 
void
BMidiStore::PitchBend(uchar channel, uchar lsb, uchar msb, uint32 time)
{
	BMidiEvent* event = new BMidiEvent;
	event->time  = time;
	event->ticks = false;
	event->byte1 = B_PITCH_BEND | (channel - 1);
	event->byte2 = lsb;
	event->byte3 = msb;
	AddEvent(event);
}
 
 
void
BMidiStore::SystemExclusive(void* data, size_t length, uint32 time)
{
	BMidiEvent* event = new BMidiEvent;
	event->time   = time;
	event->ticks  = false;
	event->byte1  = B_SYS_EX_START;
	event->data   = malloc(length);
	event->length = length;
	memcpy(event->data, data, length);
	AddEvent(event);
}
 
 
void
BMidiStore::SystemCommon(uchar status, uchar data1,
	uchar data2, uint32 time)
{
	BMidiEvent* event = new BMidiEvent;
	event->time  = time;
	event->ticks = false;
	event->byte1 = status;
	event->byte2 = data1;
	event->byte3 = data2;
	AddEvent(event);
}
 
 
void
BMidiStore::SystemRealTime(uchar status, uint32 time)
{
	BMidiEvent* event = new BMidiEvent;
	event->time  = time;
	event->ticks = false;
	event->byte1 = status;
	AddEvent(event);
}
 
 
void
BMidiStore::TempoChange(int32 beatsPerMinute, uint32 time)
{
	BMidiEvent* event = new BMidiEvent;
	event->time  = time;
	event->ticks = false;
	event->byte1 = 0xFF;
	event->byte2 = 0x51;
	event->byte3 = 0x03;
	event->tempo = beatsPerMinute;
	AddEvent(event);
}
 
 
status_t
BMidiStore::Import(const entry_ref* ref)
{
	memset(fInstruments, 0, 128 * sizeof(bool));
 
	try {
		fFile = new BFile(ref, B_READ_ONLY);
		if (fFile->InitCheck() != B_OK)
			throw fFile->InitCheck();
 
		char fourcc[4];
		ReadFourCC(fourcc);
		if (strncmp(fourcc, "MThd", 4) != 0)
			throw (status_t) B_BAD_MIDI_DATA;
 
		if (Read32Bit() != 6)
			throw (status_t) B_BAD_MIDI_DATA;
 
		fFormat = Read16Bit();
		fNumTracks = Read16Bit();
		fTicksPerBeat = Read16Bit();
 
		if (fTicksPerBeat & 0x8000) {
			// we don't support SMPTE time codes, 
			// only ticks per quarter note
			fTicksPerBeat = 240;
		}
 
		fCurrTrack = 0;
		while (fCurrTrack < fNumTracks) {
			ReadChunk(); 
		}
	} catch (status_t e) {
		delete fFile;
		fFile = NULL;
		return e;
	}
 
	SortEvents(true);
 
	delete fFile;
	fFile = NULL;
	return B_OK;
}
 
 
status_t
BMidiStore::Export(const entry_ref* ref, int32 format)
{
	try {
		fFile = new BFile(ref, B_READ_WRITE);
		if (fFile->InitCheck() != B_OK)
			throw fFile->InitCheck();
 
		SortEvents(true);
 
		WriteFourCC('M', 'T', 'h', 'd');
		Write32Bit(6);
		Write16Bit(0);  // we do only format 0
		Write16Bit(1);
		Write16Bit(fTicksPerBeat);
 
		WriteTrack();
	} catch (status_t e) {
		delete fFile;
		fFile = NULL;
		return e;
	}
 
	delete fFile;
	fFile = NULL;	
	return B_OK;
}
 
 
void
BMidiStore::SortEvents(bool force)
{
	if (force || fNeedsSorting) {
		fEvents->SortItems(compare_events);
		fNeedsSorting = false;
	}
}
 
 
uint32
BMidiStore::CountEvents() const
{
	return fEvents->CountItems();
}
 
 
uint32
BMidiStore::CurrentEvent() const
{
	return fCurrentEvent;
}
 
 
void
BMidiStore::SetCurrentEvent(uint32 eventNumber)
{
	fCurrentEvent = eventNumber;
}
 
 
uint32
BMidiStore::DeltaOfEvent(uint32 eventNumber) const
{
	// Even though the BeBook says that the delta is the time span between
	// an event and the first event in the list, this doesn't appear to be
	// true for events that were captured from other BMidi objects such as
	// BMidiPort. For those events, we return the absolute timestamp. The
	// BeBook is correct for events from MIDI files, though.
 
	BMidiEvent* event = EventAt(eventNumber);
	if (event != NULL)
		return GetEventTime(event);
 
	return 0;
}
 
 
uint32
BMidiStore::EventAtDelta(uint32 time) const
{
	for (int32 t = 0; t < fEvents->CountItems(); ++t) {
		if (GetEventTime(EventAt(t)) >= time)
			return t;
	}
 
	return 0;
}
 
 
uint32
BMidiStore::BeginTime() const
{
	return fStartTime;
}
 
 
void
BMidiStore::SetTempo(int32 beatsPerMinute_)
{
	fBeatsPerMinute = beatsPerMinute_;
}
 
 
int32
BMidiStore::Tempo() const
{
	return fBeatsPerMinute;
}
 
 
void BMidiStore::_ReservedMidiStore1() { }
void BMidiStore::_ReservedMidiStore2() { }
void BMidiStore::_ReservedMidiStore3() { }
 
 
void
BMidiStore::Run()
{
	// This rather compilicated Run() loop is not only used by BMidiStore
	// but also by BMidiSynthFile. The "paused", "finished", and "looping"
	// flags, and the "stop hook" are especially provided for the latter.
 
	fPaused = false;
	fFinished = false;
 
	int32 timeAdjust = 0;
	uint32 baseTime = 0;
	bool firstEvent = true;
	bool resetTime = false;
 
	while (KeepRunning()) {
		if (fPaused) {
			resetTime = true;
			snooze(100000);
			continue;
		}
 
		BMidiEvent* event = EventAt(fCurrentEvent);
 
		if (event == NULL) {
			// no more events
			if (fLooping) {
				resetTime = true;
				fCurrentEvent = 0;
				continue;
			}
			break;
		}
 
		if (firstEvent) {
			fStartTime = B_NOW;
			baseTime = fStartTime;
		} else if (resetTime)
			baseTime = B_NOW;
 
		if (firstEvent || resetTime) {
			timeAdjust = baseTime - GetEventTime(event);
			SprayEvent(event, baseTime);
			firstEvent = false;
			resetTime = false;
		} else
			SprayEvent(event, GetEventTime(event) + timeAdjust);
 
		++fCurrentEvent;
	}
 
	fFinished = true;
	fPaused = false;
 
	if (fHookFunc != NULL)
		(*fHookFunc)(fHookArg);
}
 
 
void
BMidiStore::AddEvent(BMidiEvent* event)
{
	fEvents->AddItem(event);
	fNeedsSorting = true;
}
 
 
void
BMidiStore::SprayEvent(const BMidiEvent* event, uint32 time)
{
	uchar byte1 = event->byte1;
	uchar byte2 = event->byte2;
	uchar byte3 = event->byte3;
 
	switch (byte1 & 0xF0) {
		case B_NOTE_OFF:
			SprayNoteOff((byte1 & 0x0F) + 1, byte2, byte3, time);
			return;
 
		case B_NOTE_ON:
			SprayNoteOn((byte1 & 0x0F) + 1, byte2, byte3, time);
			return;
 
		case B_KEY_PRESSURE:
			SprayKeyPressure((byte1 & 0x0F) + 1, byte2, byte3, time);
			return;
 
		case B_CONTROL_CHANGE:
			SprayControlChange((byte1 & 0x0F) + 1, byte2, byte3, time);
			return;
 
		case B_PROGRAM_CHANGE:
			SprayProgramChange((byte1 & 0x0F) + 1, byte2, time);
			return;
 
		case B_CHANNEL_PRESSURE:
			SprayChannelPressure((byte1 & 0x0F) + 1, byte2, time);
			return;
 
		case B_PITCH_BEND:
			SprayPitchBend((byte1 & 0x0F) + 1, byte2, byte3, time);
			return;
 
		case 0xF0:
			switch (byte1) {
				case B_SYS_EX_START:
					SpraySystemExclusive(event->data, event->length, time);
					return;
 
				case B_MIDI_TIME_CODE:
				case B_SONG_POSITION:
				case B_SONG_SELECT:
				case B_CABLE_MESSAGE:
				case B_TUNE_REQUEST:
				case B_SYS_EX_END:
					SpraySystemCommon(byte1, byte2, byte3, time);
					return;
 
				case B_TIMING_CLOCK:
				case B_START:
				case B_CONTINUE:
				case B_STOP:
				case B_ACTIVE_SENSING:
					SpraySystemRealTime(byte1, time);
					return;
 
				case B_SYSTEM_RESET:
					if (byte2 == 0x51 && byte3 == 0x03) {
						SprayTempoChange(event->tempo, time);
						fBeatsPerMinute = event->tempo;
					} else
						SpraySystemRealTime(byte1, time);
					return;
			}
			return;
	}
}
 
 
BMidiEvent*
BMidiStore::EventAt(int32 index) const
{
	return (BMidiEvent*)fEvents->ItemAt(index);
}
 
 
uint32
BMidiStore::GetEventTime(const BMidiEvent* event) const
{
	if (event->ticks)
		return TicksToMilliseconds(event->time);
 
	return event->time;
}
 
 
uint32
BMidiStore::TicksToMilliseconds(uint32 ticks) const
{
	return ((uint64)ticks * 60000) / (fBeatsPerMinute * fTicksPerBeat);
}
 
 
uint32
BMidiStore::MillisecondsToTicks(uint32 ms) const
{
	return ((uint64)ms * fBeatsPerMinute * fTicksPerBeat) / 60000;
}
 
 
void
BMidiStore::ReadFourCC(char* fourcc)
{
	if (fFile->Read(fourcc, 4) != 4)
		throw (status_t) B_BAD_MIDI_DATA;
}
 
 
void
BMidiStore::WriteFourCC(char a, char b, char c, char d)
{
	char fourcc[4] = { a, b, c, d };
 
	if (fFile->Write(fourcc, 4) != 4)
		throw (status_t) B_ERROR;
}
 
 
uint32
BMidiStore::Read32Bit()
{
	uint8 buf[4];
	if (fFile->Read(buf, 4) != 4)
		throw (status_t) B_BAD_MIDI_DATA;
 
	return (buf[0] << 24L) | (buf[1] << 16L) | (buf[2] << 8L) | buf[3];
}
 
 
void
BMidiStore::Write32Bit(uint32 val)
{
	uint8 buf[4];
	buf[0] = (val >> 24) & 0xFF;
	buf[1] = (val >> 16) & 0xFF;
	buf[2] = (val >>  8) & 0xFF;
	buf[3] =  val        & 0xFF;
 
	if (fFile->Write(buf, 4) != 4)
		throw (status_t) B_ERROR;
}
 
 
uint16
BMidiStore::Read16Bit()
{
	uint8 buf[2];
	if (fFile->Read(buf, 2) != 2)
		throw (status_t) B_BAD_MIDI_DATA;
 
	return (buf[0] << 8) | buf[1];
}
 
 
void
BMidiStore::Write16Bit(uint16 val)
{
	uint8 buf[2];
	buf[0] = (val >> 8) & 0xFF;
	buf[1] = val & 0xFF;
 
	if (fFile->Write(buf, 2) != 2)
		throw (status_t) B_ERROR;
}
 
 
uint8
BMidiStore::PeekByte()
{
	uint8 buf;
	if (fFile->Read(&buf, 1) != 1)
		throw (status_t) B_BAD_MIDI_DATA;
 
	if (fFile->Seek(-1, SEEK_CUR) < 0)
		throw (status_t) B_ERROR;
 
	return buf;
}
 
 
uint8
BMidiStore::NextByte()
{
	uint8 buf;
	if (fFile->Read(&buf, 1) != 1)
		throw (status_t) B_BAD_MIDI_DATA;
 
	--fByteCount;
	return buf;
}
 
 
void
BMidiStore::WriteByte(uint8 val)
{
	if (fFile->Write(&val, 1) != 1)
		throw (status_t) B_ERROR;
 
	++fByteCount;
}
 
 
void
BMidiStore::SkipBytes(uint32 length)
{
	if (fFile->Seek(length, SEEK_CUR) < 0) {
		throw (status_t) B_BAD_MIDI_DATA;
	}
 
	fByteCount -= length;
}
 
 
uint32
BMidiStore::ReadVarLength()
{
	uint32 val;
	uint8 byte;
 
	if ((val = NextByte()) & 0x80) {
		val &= 0x7F;
		do {
			val = (val << 7) + ((byte = NextByte()) & 0x7F);
		} while (byte & 0x80);
	}
 
	return val;
}
 
 
void
BMidiStore::WriteVarLength(uint32 val)
{
	uint32 buffer = val & 0x7F;
 
	while ((val >>= 7) != 0) {
		buffer <<= 8;
		buffer |= ((val & 0x7F) | 0x80);
	}
 
	while (true) {
		WriteByte(buffer);
		if (buffer & 0x80)
			buffer >>= 8;
		else
			break;
	}
}
 
 
void
BMidiStore::ReadChunk()
{
	char fourcc[4];
	ReadFourCC(fourcc);
 
	fByteCount = Read32Bit();
 
	if (strncmp(fourcc, "MTrk", 4) == 0)
		ReadTrack();
	else {
		TRACE(("Skipping '%c%c%c%c' chunk (%" B_PRIu32 " bytes)",
			fourcc[0], fourcc[1], fourcc[2], fourcc[3], fByteCount))
 
		SkipBytes(fByteCount);
	}
}
 
 
void
BMidiStore::ReadTrack()
{
	uint8 status = 0;
	uint8 data1;
	uint8 data2;
	BMidiEvent* event;
 
	fTotalTicks = 0;
 
	while (fByteCount > 0) {
		uint32 ticks = ReadVarLength();
		fTotalTicks += ticks;
 
		if (PeekByte() & 0x80)
			status = NextByte();
 
		switch (status & 0xF0) {
			case B_NOTE_OFF:
			case B_NOTE_ON:
			case B_KEY_PRESSURE:
			case B_CONTROL_CHANGE:
			case B_PITCH_BEND:
				data1 = NextByte();
				data2 = NextByte();
				event = new BMidiEvent;
				event->time  = fTotalTicks;
				event->ticks = true;
				event->byte1 = status;
				event->byte2 = data1;
				event->byte3 = data2;
				AddEvent(event);
				break;
 
			case B_PROGRAM_CHANGE:
			case B_CHANNEL_PRESSURE:
				data1 = NextByte();
				event = new BMidiEvent;
				event->time  = fTotalTicks;
				event->ticks = true;
				event->byte1 = status;
				event->byte2 = data1;
				AddEvent(event);
 
				if ((status & 0xF0) == B_PROGRAM_CHANGE)
					fInstruments[data1] = true;
				break;
 
			case 0xF0:
				switch (status) {
					case B_SYS_EX_START:
						ReadSystemExclusive();
						break;
 
					case B_TUNE_REQUEST:
					case B_SYS_EX_END:
					case B_TIMING_CLOCK:
					case B_START:
					case B_CONTINUE:
					case B_STOP:
					case B_ACTIVE_SENSING:
						event = new BMidiEvent;
						event->time  = fTotalTicks;
						event->ticks = true;
						event->byte1 = status;
						AddEvent(event);
						break;
 
					case B_MIDI_TIME_CODE:
					case B_SONG_SELECT:
					case B_CABLE_MESSAGE:
						data1 = NextByte();
						event = new BMidiEvent;
						event->time  = fTotalTicks;
						event->ticks = true;
						event->byte1 = status;
						event->byte2 = data1;
						AddEvent(event);
						break;
 
					case B_SONG_POSITION:
						data1 = NextByte();
						data2 = NextByte();
						event = new BMidiEvent;
						event->time  = fTotalTicks;
						event->ticks = true;
						event->byte1 = status;
						event->byte2 = data1;
						event->byte3 = data2;
						AddEvent(event);
						break;
 
					case B_SYSTEM_RESET:
						ReadMetaEvent();
						break;
				}
				break;
		}
 
		event = NULL;
	}
		
	++fCurrTrack;
}
 
 
void
BMidiStore::ReadSystemExclusive()
{
	// We do not import sysex's from MIDI files.
 
	SkipBytes(ReadVarLength());
}
 
 
void
BMidiStore::ReadMetaEvent()
{
	// We only import the Tempo Change meta event.
 
	uint8 type = NextByte();	
	uint32 length = ReadVarLength();
 
	if (type == 0x51 && length == 3) {
		uchar data[3];
		data[0] = NextByte();
		data[1] = NextByte();
		data[2] = NextByte();
		uint32 val = (data[0] << 16) | (data[1] << 8) | data[2];
 
		BMidiEvent* event = new BMidiEvent;
		event->time  = fTotalTicks;
		event->ticks = true;
		event->byte1 = 0xFF;
		event->byte2 = 0x51;
		event->byte3 = 0x03;
		event->tempo = 60000000 / val;
		AddEvent(event);
	} else
		SkipBytes(length);
}
 
 
void
BMidiStore::WriteTrack()
{
	WriteFourCC('M', 'T', 'r', 'k');
	off_t lengthPos = fFile->Position();
	Write32Bit(0);
 
	fByteCount = 0;
	uint32 oldTime = 0;
	uint32 newTime;
 
	for (uint32 t = 0; t < CountEvents(); ++t) {
		BMidiEvent* event = EventAt(t);
 
		if (event->ticks)
			newTime = event->time;
		else
			newTime = MillisecondsToTicks(event->time);
 
		if (t == 0)
			WriteVarLength(0);
		else
			WriteVarLength(newTime - oldTime);
 
		oldTime = newTime;
 
		switch (event->byte1 & 0xF0) {
			case B_NOTE_OFF:
			case B_NOTE_ON:
			case B_KEY_PRESSURE:
			case B_CONTROL_CHANGE:
			case B_PITCH_BEND:
				WriteByte(event->byte1);
				WriteByte(event->byte2);
				WriteByte(event->byte3);
				break;
 
			case B_PROGRAM_CHANGE:
			case B_CHANNEL_PRESSURE:
				WriteByte(event->byte1);
				WriteByte(event->byte2);
				break;
 
			case 0xF0:
				switch (event->byte1) {
					case B_SYS_EX_START:
						// We do not export sysex's.
						break;
 
					case B_TUNE_REQUEST:
					case B_SYS_EX_END:
					case B_TIMING_CLOCK:
					case B_START:
					case B_CONTINUE:
					case B_STOP:
					case B_ACTIVE_SENSING:
						WriteByte(event->byte1);
						break;
 
					case B_MIDI_TIME_CODE:
					case B_SONG_SELECT:
					case B_CABLE_MESSAGE:
						WriteByte(event->byte1);
						WriteByte(event->byte2);
						break;
 
					case B_SONG_POSITION:
						WriteByte(event->byte1);
						WriteByte(event->byte2);
						WriteByte(event->byte3);
						break;
 
					case B_SYSTEM_RESET:
						WriteMetaEvent(event);
						break;
				}
				break;
		}
	}
 
	WriteVarLength(0);
	WriteByte(0xFF);   // the end-of-track
	WriteByte(0x2F);   // marker is required
	WriteByte(0x00);  
 
	fFile->Seek(lengthPos, SEEK_SET);
	Write32Bit(fByteCount);
	fFile->Seek(0, SEEK_END);
}
 
 
void
BMidiStore::WriteMetaEvent(BMidiEvent* event)
{
	// We only export the Tempo Change meta event.
 
	if (event->byte2 == 0x51 && event->byte3 == 0x03) {
		uint32 val = 60000000 / event->tempo;
 
		WriteByte(0xFF);
		WriteByte(0x51);
		WriteByte(0x03);
		WriteByte(val >> 16);
		WriteByte(val >> 8);
		WriteByte(val);
	}
}
 

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: time, ticks, tempo.