/* * 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 #include #include #include #include #include #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); } }