/* This is a modified version of the original opblib.h/opblib.c (https://github.com/Enichan/OPBinaryLib) Basically, I have merged the two files into a stb-style single-file header-only C library. Define OPBLIB_IMPLEMENTATION before you include this file in *one* C/C++ file to create the implementation. I have also replaced the C initializers with initializing functions for compatibility with C++. I place all my changes under the same license as the original code (see below). / Mattias Gustavsson ( mattias@mattiasgustavsson.com ) */ /* // MIT License // // Copyright (c) 2021 Eniko Fox/Emma Maassen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. */ #ifndef opblib_h #define opblib_h //#pragma once #include //#ifdef __cplusplus //extern "C" { //#endif // uncomment this for big endian architecture //#define OPB_BIG_ENDIAN #define OPBERR_LOGGED 1 // an error occurred and what error that was has been sent to OPB_Log #define OPBERR_WRITE_ERROR 2 #define OPBERR_SEEK_ERROR 3 #define OPBERR_TELL_ERROR 4 #define OPBERR_READ_ERROR 5 #define OPBERR_BUFFER_ERROR 6 #define OPBERR_NOT_AN_OPB_FILE 7 #define OPBERR_VERSION_UNSUPPORTED 8 typedef struct OPB_Command { uint16_t Addr; uint8_t Data; double Time; } OPB_Command; typedef enum OPB_Format { OPB_Format_Default, OPB_Format_Raw, } OPB_Format; const char* OPB_GetErrorMessage(int errCode); const char* OPB_GetFormatName(OPB_Format fmt); // Custom write handler of the same form as stdio.h's fwrite for writing to memory // This function should write elementSize * elementCount bytes from buffer to the user-defined context object // Must return elementCount if successful typedef size_t(*OPB_StreamWriter)(const void* buffer, size_t elementSize, size_t elementCount, void* context); // Custom seek handler of the same form as stdio.h's fseek for writing to memory // This function should change the position to write to in the user-defined context object by the number of bytes // Specified by offset, relative to the specified origin which is one of 3 values: // // 1. Beginning of file (same as fseek's SEEK_SET) // 2. Current position of the file pointer (same as fseek's SEEK_CUR) // 3. End of file (same as fseek's SEEK_END) // // Must return 0 if successful typedef int (*OPB_StreamSeeker)(void* context, long offset, int origin); // Custom tell handler of the same form as stdio.h's ftell for writing to memory // This function must return the current write position for the user-defined context object // Must return -1L if unsuccessful typedef long (*OPB_StreamTeller)(void* context); // Custom read handler of the same form as stdio.h's fread for reading from memory // This function should read elementSize * elementCount bytes from the user-defined context object to buffer // Should return number of elements read typedef size_t(*OPB_StreamReader)(void* buffer, size_t elementSize, size_t elementCount, void* context); // Function that receives OPB_Command items read by OPB_BinaryToOpl and OPB_FileToOpl // This is where you copy the OPB_Command items into a data structure or the user-defined context object // Should return 0 if successful. Note that the array for `commandStream` is stack allocated and must be copied! typedef int(*OPB_BufferReceiver)(OPB_Command* commandStream, size_t commandCount, void* context); // OPL command stream to binary. Returns 0 if successful. int OPB_OplToBinary(OPB_Format format, OPB_Command* commandStream, size_t commandCount, OPB_StreamWriter write, OPB_StreamSeeker seek, OPB_StreamTeller tell, void* userData); // OPL command stream to file. Returns 0 if successful. int OPB_OplToFile(OPB_Format format, OPB_Command* commandStream, size_t commandCount, const char* file); // OPB binary to OPL command stream. Returns 0 if successful. int OPB_BinaryToOpl(OPB_StreamReader reader, void* readerData, OPB_BufferReceiver receiver, void* receiverData); // OPB file to OPL command stream. Returns 0 if successful. int OPB_FileToOpl(const char* file, OPB_BufferReceiver receiver, void* receiverData); // OPBLib log function typedef void (*OPB_LogHandler)(const char* s); extern OPB_LogHandler OPB_Log; //#ifdef __cplusplus //} //#endif #endif /* opblib_h */ #ifdef OPBLIB_IMPLEMENTATION #undef OPBLIB_IMPLEMENTATION /* // MIT License // // Copyright (c) 2021 Eniko Fox/Emma Maassen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. */ #ifdef _WIN32 #define _CRT_SECURE_NO_DEPRECATE #endif #include #include #include #include #include #include "opblib.h" #define OPB_HEADER_SIZE 7 // OPBin1\0 const char OPB_Header[OPB_HEADER_SIZE] = { 'O', 'P', 'B', 'i', 'n', '1', '\0' }; #define VECTOR_MIN_CAPACITY 8 #define VECTOR_PTR(vector, index) (void*)((uint8_t*)((vector)->Storage) + (index) * vector->ElementSize) // this only exists to make type declarations clearer #define VectorT(T) Vector typedef struct Vector { size_t Count; size_t Capacity; size_t ElementSize; void* Storage; } Vector; Vector Vector_New(size_t elementSize) { Vector v = { 0 }; v.ElementSize = elementSize; return v; } static void Vector_Free(Vector* v) { if (v->Storage != NULL) { free(v->Storage); } v->Storage = NULL; v->Capacity = 0; v->Count = 0; } static void* Vector_Get(Vector* v, int index) { if (index < 0 || index >= v->Count) { return NULL; } if (v->ElementSize <= 0) { return NULL; } return VECTOR_PTR(v, index); } #define Vector_GetT(T, vector, index) ((T*)Vector_Get(vector, index)) static int Vector_Set(Vector* v, void* item, int index) { if (index < 0 || index >= v->Count) { return -1; } if (v->ElementSize <= 0) { return -1; } memcpy(VECTOR_PTR(v, index), item, v->ElementSize); return 0; } static int Vector_Add(Vector* v, void* item) { if (v->ElementSize <= 0) { return -1; } if (v->Count >= v->Capacity) { size_t newCapacity = v->Capacity * 2; if (newCapacity < VECTOR_MIN_CAPACITY) newCapacity = VECTOR_MIN_CAPACITY; void* newStorage = malloc(newCapacity * v->ElementSize); if (newStorage == NULL) { return -1; } if (v->Storage != NULL) { memcpy(newStorage, v->Storage, v->Count * v->ElementSize); free(v->Storage); } v->Storage = newStorage; v->Capacity = newCapacity; } v->Count++; return Vector_Set(v, item, (size_t)((int)v->Count - 1)); } static int Vector_AddRange(Vector* v, void* items, size_t count) { if (v->ElementSize <= 0) { return -1; } uint8_t* itemBytes = (uint8_t*)items; for (size_t i = 0; i < count; i++, itemBytes += v->ElementSize) { int ret; ret = Vector_Add(v, (void*)itemBytes); if (ret) return ret; } return 0; } typedef int(*VectorSortFunc)(const void* a, const void* b); static void Vector_Clear(Vector* v, bool keepStorage) { v->Count = 0; if (!keepStorage && v->Storage != NULL) { free(v->Storage); v->Storage = NULL; v->Capacity = 0; } } static void Vector_Sort(Vector* v, VectorSortFunc sortFunc) { qsort(v->Storage, v->Count, v->ElementSize, sortFunc); } static const char* GetFilename(const char* path) { const char* lastFwd = strrchr(path, '/'); const char* lastBck = strrchr(path, '\\'); if (lastFwd == NULL && lastBck == NULL) { return path; } return lastFwd > lastBck ? lastFwd + 1 : lastBck + 1; } static const char* GetSourceFilename(void) { return GetFilename(__FILE__); } #define CONCAT_IMPL(x, y) x##y #define MACRO_CONCAT(x, y) CONCAT_IMPL(x, y) #define NUM_CHANNELS 18 #define NUM_TRACKS (NUM_CHANNELS + 1) #define WRITE(buffer, size, count, context) \ if (context->Write(buffer, size, count, context->UserData) != count) { \ Log("OPB write error occurred in '%s' at line %d\n", GetSourceFilename(), __LINE__); \ return OPBERR_WRITE_ERROR; \ } #define WRITE_UINT7(context, value) \ if (WriteUint7(context, value)) { \ Log("OPB write error occurred in '%s' at line %d\n", GetSourceFilename(), __LINE__); \ return OPBERR_WRITE_ERROR; \ } #define SEEK(context, offset, origin) \ if (context->Seek(context->UserData, offset, origin)) { \ Log("OPB seek error occurred in '%s' at line %d\n", GetSourceFilename(), __LINE__); \ return OPBERR_SEEK_ERROR; \ } #define TELL(context, var) \ var = context->Tell(context->UserData); \ if (var == -1L) { \ Log("OPB file position error occurred in '%s' at line %d\n", GetSourceFilename(), __LINE__); \ return OPBERR_TELL_ERROR; \ } #define READ(buffer, size, count, context) \ if (context->Read(buffer, size, count, context->UserData) != count) { \ Log("OPB read error occurred in '%s' at line %d\n", GetSourceFilename(), __LINE__); \ return OPBERR_READ_ERROR; \ } #define READ_UINT7(var, context) \ if ((var = ReadUint7(context)) < 0) { \ Log("OPB read error occurred in '%s' at line %d\n", GetSourceFilename(), __LINE__); \ return OPBERR_READ_ERROR; \ } #define SUBMIT(stream, count, context) \ if (context->Submit(stream, count, context->ReceiverData)) return OPBERR_BUFFER_ERROR typedef struct Context Context; typedef struct Command Command; typedef struct OpbData OpbData; typedef struct Instrument Instrument; struct Context { VectorT(Command) CommandStream; OPB_StreamWriter Write; OPB_StreamSeeker Seek; OPB_StreamTeller Tell; OPB_StreamReader Read; OPB_BufferReceiver Submit; OPB_Format Format; VectorT(OpbData) DataMap; VectorT(Instrument) Instruments; VectorT(Command) Tracks[NUM_TRACKS]; double Time; void* UserData; void* ReceiverData; }; static void Context_Free(Context* context) { if (context->CommandStream.Storage != NULL) { Vector_Free(&context->CommandStream); } if (context->Instruments.Storage != NULL) { Vector_Free(&context->Instruments); } if (context->DataMap.Storage != NULL) { Vector_Free(&context->DataMap); } for (int i = 0; i < NUM_TRACKS; i++) { if (context->Tracks[i].Storage != NULL) { Vector_Free(&context->Tracks[i]); } } } OPB_LogHandler OPB_Log; static inline size_t BufferSize(const char* format, ...) { va_list args; va_start(args, format); size_t result = vsnprintf(NULL, 0, format, args); va_end(args); return (size_t)(result + 1); // safe byte for \0 } static void Log(const char* format, ...) { if (!OPB_Log) return; va_list args; va_start(args, format); size_t size = BufferSize(format, args); va_end(args); if (size == 0) return; va_start(args, format); char* s = NULL; if (size < 0 || (s = (char*)malloc(size)) == NULL) { vprintf(format, args); } else { vsprintf(s, format, args); } va_end(args); if (s != NULL) { OPB_Log(s); free(s); } } OPB_Command MakeCommand(uint16_t Addr, uint8_t Data, double Time) { OPB_Command cmd; cmd.Addr = Addr; cmd.Data = Data; cmd.Time = Time; return cmd; } const char* OPB_GetFormatName(OPB_Format fmt) { switch (fmt) { default: return "Default"; case OPB_Format_Raw: return "Raw"; } } static inline uint32_t FlipEndian32(uint32_t val) { #ifdef OPB_BIG_ENDIAN return val; #else val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF); return (val << 16) | (val >> 16); #endif } static inline uint16_t FlipEndian16(uint16_t val) { #ifdef OPB_BIG_ENDIAN return val; #else return (val << 8) | ((val >> 8) & 0xFF); #endif } static size_t Uint7Size(uint32_t value) { if (value >= 2097152) { return 4; } else if (value >= 16384) { return 3; } else if (value >= 128) { return 2; } else { return 1; } } struct Command { uint16_t Addr; uint8_t Data; double Time; int OrderIndex; int DataIndex; }; struct OpbData { uint32_t Count; uint8_t Args[16]; }; static void OpbData_WriteUint7(OpbData* data, uint32_t value) { if (value >= 2097152) { uint8_t b0 = (value & 0b01111111) | 0b10000000; uint8_t b1 = ((value & 0b011111110000000) >> 7) | 0b10000000; uint8_t b2 = ((value & 0b0111111100000000000000) >> 14) | 0b10000000; uint8_t b3 = (value & 0b11111111000000000000000000000) >> 21; data->Args[data->Count] = b0; data->Count++; data->Args[data->Count] = b1; data->Count++; data->Args[data->Count] = b2; data->Count++; data->Args[data->Count] = b3; data->Count++; } else if (value >= 16384) { uint8_t b0 = (value & 0b01111111) | 0b10000000; uint8_t b1 = ((value & 0b011111110000000) >> 7) | 0b10000000; uint8_t b2 = (value & 0b0111111100000000000000) >> 14; data->Args[data->Count] = b0; data->Count++; data->Args[data->Count] = b1; data->Count++; data->Args[data->Count] = b2; data->Count++; } else if (value >= 128) { uint8_t b0 = (value & 0b01111111) | 0b10000000; uint8_t b1 = (value & 0b011111110000000) >> 7; data->Args[data->Count] = b0; data->Count++; data->Args[data->Count] = b1; data->Count++; } else { uint8_t b0 = value & 0b01111111; data->Args[data->Count] = b0; data->Count++; } } static void OpbData_WriteU8(OpbData* data, uint32_t value) { data->Args[data->Count] = (uint8_t)value; data->Count++; } #define OPB_CMD_SETINSTRUMENT 0xD0 #define OPB_CMD_PLAYINSTRUMENT 0xD1 #define OPB_CMD_NOTEON 0xD7 #define NUM_OPERATORS 36 static int OperatorOffsets[NUM_OPERATORS] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x108, 0x109, 0x10A, 0x10B, 0x10C, 0x10D, 0x110, 0x111, 0x112, 0x113, 0x114, 0x115, }; static int ChannelToOp[NUM_CHANNELS] = { 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32, }; static int ChannelToOffset[NUM_CHANNELS] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108, }; static int RegisterOffsetToChannel(uint32_t offset) { uint32_t baseoff = offset & 0xFF; int chunk = baseoff / 8; int suboff = baseoff % 8; if (chunk >= 3 || suboff >= 6) { return -1; } return chunk * 3 + (suboff % 3) + ((offset & 0x100) != 0 ? NUM_CHANNELS / 2 : 0); } static int RegisterOffsetToOpIndex(uint32_t offset) { uint32_t baseoff = offset & 0xFF; uint32_t suboff = baseoff % 8; if (suboff >= 6) { return -1; } return suboff >= 3; } #define REG_FEEDCONN 0xC0 #define REG_CHARACTER 0x20 #define REG_LEVELS 0x40 #define REG_ATTACK 0x60 #define REG_SUSTAIN 0x80 #define REG_WAVE 0xE0 #define REG_FREQUENCY 0xA0 #define REG_NOTE 0xB0 typedef struct Operator { int16_t Characteristic; int16_t AttackDecay; int16_t SustainRelease; int16_t WaveSelect; } Operator; Operator MakeOperator(int16_t Characteristic, int16_t AttackDecay, int16_t SustainRelease, int16_t WaveSelect) { Operator op; op.Characteristic = Characteristic; op.AttackDecay = AttackDecay; op.SustainRelease = SustainRelease; op.WaveSelect = WaveSelect; return op; } struct Instrument { int16_t FeedConn; Operator Modulator; Operator Carrier; int Index; }; Instrument MakeInstrument(int16_t FeedConn, Operator Modulator, Operator Carrier, int Index) { Instrument instrument; instrument.FeedConn = FeedConn; instrument.Modulator = Modulator; instrument.Carrier = Carrier; instrument.Index = Index; return instrument; } static Context Context_New(void) { Context context = { 0 }; context.CommandStream = Vector_New(sizeof(Command)); context.Instruments = Vector_New(sizeof(Instrument)); context.DataMap = Vector_New(sizeof(OpbData)); for (int i = 0; i < NUM_TRACKS; i++) { context.Tracks[i] = Vector_New(sizeof(Command)); } return context; } static Instrument GetInstrument(Context* context, Command* feedconn, Command* modChar, Command* modAttack, Command* modSustain, Command* modWave, Command* carChar, Command* carAttack, Command* carSustain, Command* carWave) { // find a matching instrument for (int i = 0; i < context->Instruments.Count; i++) { Instrument* instr = Vector_GetT(Instrument, &context->Instruments, i); if ((feedconn == NULL || instr->FeedConn == feedconn->Data) && (modChar == NULL || instr->Modulator.Characteristic == modChar->Data) && (modAttack == NULL || instr->Modulator.AttackDecay == modAttack->Data) && (modSustain == NULL || instr->Modulator.SustainRelease == modSustain->Data) && (modWave == NULL || instr->Modulator.WaveSelect == modWave->Data) && (carChar == NULL || instr->Carrier.Characteristic == carChar->Data) && (carAttack == NULL || instr->Carrier.AttackDecay == carAttack->Data) && (carSustain == NULL || instr->Carrier.SustainRelease == carSustain->Data) && (carWave == NULL || instr->Carrier.WaveSelect == carWave->Data)) { return *instr; } } // no instrument found, create and store new instrument Instrument instr = { feedconn == NULL ? -1 : feedconn->Data, { modChar == NULL ? -1 : modChar->Data, modAttack == NULL ? -1 : modAttack->Data, modSustain == NULL ? -1 : modSustain->Data, modWave == NULL ? -1 : modWave->Data, }, { carChar == NULL ? -1 : carChar->Data, carAttack == NULL ? -1 : carAttack->Data, carSustain == NULL ? -1 : carSustain->Data, carWave == NULL ? -1 : carWave->Data, }, (int)context->Instruments.Count }; Vector_Add(&context->Instruments, &instr); return instr; } static int WriteInstrument(Context* context, const Instrument* instr) { uint8_t feedConn = (uint8_t)(instr->FeedConn >= 0 ? instr->FeedConn : 0); uint8_t modChr = (uint8_t)(instr->Modulator.Characteristic >= 0 ? instr->Modulator.Characteristic : 0); uint8_t modAtk = (uint8_t)(instr->Modulator.AttackDecay >= 0 ? instr->Modulator.AttackDecay : 0); uint8_t modSus = (uint8_t)(instr->Modulator.SustainRelease >= 0 ? instr->Modulator.SustainRelease : 0); uint8_t modWav = (uint8_t)(instr->Modulator.WaveSelect >= 0 ? instr->Modulator.WaveSelect : 0); uint8_t carChr = (uint8_t)(instr->Carrier.Characteristic >= 0 ? instr->Carrier.Characteristic : 0); uint8_t carAtk = (uint8_t)(instr->Carrier.AttackDecay >= 0 ? instr->Carrier.AttackDecay : 0); uint8_t carSus = (uint8_t)(instr->Carrier.SustainRelease >= 0 ? instr->Carrier.SustainRelease : 0); uint8_t carWav = (uint8_t)(instr->Carrier.WaveSelect >= 0 ? instr->Carrier.WaveSelect : 0); WRITE(&feedConn, sizeof(uint8_t), 1, context); WRITE(&modChr, sizeof(uint8_t), 1, context); WRITE(&modAtk, sizeof(uint8_t), 1, context); WRITE(&modSus, sizeof(uint8_t), 1, context); WRITE(&modWav, sizeof(uint8_t), 1, context); WRITE(&carChr, sizeof(uint8_t), 1, context); WRITE(&carAtk, sizeof(uint8_t), 1, context); WRITE(&carSus, sizeof(uint8_t), 1, context); WRITE(&carWav, sizeof(uint8_t), 1, context); return 0; } static int WriteUint7(Context* context, uint32_t value) { if (value >= 2097152) { uint8_t b0 = (value & 0b01111111) | 0b10000000; uint8_t b1 = ((value & 0b011111110000000) >> 7) | 0b10000000; uint8_t b2 = ((value & 0b0111111100000000000000) >> 14) | 0b10000000; uint8_t b3 = (value & 0b11111111000000000000000000000) >> 21; if (context->Write(&b0, sizeof(uint8_t), 1, context->UserData) < 1) return -1; if (context->Write(&b1, sizeof(uint8_t), 1, context->UserData) < 1) return -1; if (context->Write(&b2, sizeof(uint8_t), 1, context->UserData) < 1) return -1; if (context->Write(&b3, sizeof(uint8_t), 1, context->UserData) < 1) return -1; } else if (value >= 16384) { uint8_t b0 = (value & 0b01111111) | 0b10000000; uint8_t b1 = ((value & 0b011111110000000) >> 7) | 0b10000000; uint8_t b2 = (value & 0b0111111100000000000000) >> 14; if (context->Write(&b0, sizeof(uint8_t), 1, context->UserData) < 1) return -1; if (context->Write(&b1, sizeof(uint8_t), 1, context->UserData) < 1) return -1; if (context->Write(&b2, sizeof(uint8_t), 1, context->UserData) < 1) return -1; } else if (value >= 128) { uint8_t b0 = (value & 0b01111111) | 0b10000000; uint8_t b1 = (value & 0b011111110000000) >> 7; if (context->Write(&b0, sizeof(uint8_t), 1, context->UserData) < 1) return -1; if (context->Write(&b1, sizeof(uint8_t), 1, context->UserData) < 1) return -1; } else { uint8_t b0 = value & 0b01111111; if (context->Write(&b0, sizeof(uint8_t), 1, context->UserData) < 1) return -1; } return 0; } // returns channel for note event or -1 if not a note event static int IsNoteEvent(int addr) { int baseAddr = addr & 0xFF; if (baseAddr >= 0xB0 && baseAddr <= 0xB8) { return (baseAddr - 0xB0) * ((addr & 0x100) == 0 ? 1 : 2); } else if (baseAddr >= OPB_CMD_NOTEON && baseAddr < OPB_CMD_NOTEON + NUM_CHANNELS / 2) { return (baseAddr - OPB_CMD_NOTEON) * ((addr & 0x100) == 0 ? 1 : 2); } return -1; } static bool IsChannelNoteEvent(int addr, int channel) { return (addr == 0xB0 + (channel % 9) + (channel >= 9 ? 0x100 : 0)) || (addr == OPB_CMD_NOTEON + (channel % 9) + (channel >= 9 ? 0x100 : 0)); } static int ChannelFromRegister(int reg) { int baseReg = reg & 0xFF; if ((baseReg >= 0x20 && baseReg <= 0x95) || (baseReg >= 0xE0 && baseReg <= 0xF5)) { int offset = baseReg % 0x20; if (offset < 0 || offset >= 0x16) { return -1; } if ((reg & 0x100) != 0) { offset |= 0x100; } int ch; if ((ch = RegisterOffsetToChannel(offset)) >= 0) { return ch; } } else if ((baseReg >= 0xA0 && baseReg <= 0xB8) || (baseReg >= 0xC0 && baseReg <= 0xC8)) { int ch = baseReg % 0x10; if (ch < 0 || ch >= 0x9) { return -1; } if ((reg & 0x100) != 0) { ch += 9; } return ch; } return -1; } // 0 for modulator, 1 for carrier, -1 otherwise static int RegisterToOpIndex(int reg) { int baseReg = reg & 0xFF; if ((baseReg >= 0x20 && baseReg <= 0x95) || (baseReg >= 0xE0 && baseReg <= 0xF5)) { int offset = baseReg % 0x20; if (offset < 0 || offset >= 0x16) { return -1; } int op; if ((op = RegisterOffsetToOpIndex(offset)) >= 0) { return op; } } return -1; } static void SeparateTracks(Context* context) { for (int i = 0; i < context->CommandStream.Count; i++) { Command* cmd = Vector_GetT(Command, &context->CommandStream, i); int channel = ChannelFromRegister(cmd->Addr); if (channel < 0) channel = NUM_TRACKS - 1; Vector_Add(&context->Tracks[channel], cmd); } } static int CountInstrumentChanges(Command* feedconn, Command* modChar, Command* modAttack, Command* modSustain, Command* modWave, Command* carChar, Command* carAttack, Command* carSustain, Command* carWave) { int count = 0; if (feedconn != NULL) count++; if (modChar != NULL) count++; if (modAttack != NULL) count++; if (modSustain != NULL) count++; if (modWave != NULL) count++; if (carChar != NULL) count++; if (carAttack != NULL) count++; if (carSustain != NULL) count++; if (carWave != NULL) count++; return count; } static int ProcessRange(Context* context, int channel, double time, Command* commands, int cmdCount, Vector* range, int _debug_start, int _debug_end // these last two are only for logging in case of error ) { for (int i = 0; i < cmdCount; i++) { Command* cmd = commands + i; if (cmd->Time != time) { int timeMs = (int)(time * 1000); Log("A timing error occurred at %d ms on channel %d in range %d-%d\n", timeMs, channel, _debug_start, _debug_end); return OPBERR_LOGGED; } } Command* modChar = NULL, * modLevel = NULL, * modAttack = NULL, * modSustain = NULL, * modWave = NULL; Command* carChar = NULL, * carLevel = NULL, * carAttack = NULL, * carSustain = NULL, * carWave = NULL; Command* freq = NULL, * note = NULL, * feedconn = NULL; for (int i = 0; i < cmdCount; i++) { Command* cmd = commands + i; int baseAddr = cmd->Addr & 0xFF; int op; if ((op = RegisterToOpIndex(cmd->Addr)) > -1) { // command affects modulator or carrier if (op == 0) { if (baseAddr >= 0x20 && baseAddr <= 0x35) modChar = cmd; else if (baseAddr >= 0x40 && baseAddr <= 0x55) modLevel = cmd; else if (baseAddr >= 0x60 && baseAddr <= 0x75) modAttack = cmd; else if (baseAddr >= 0x80 && baseAddr <= 0x95) modSustain = cmd; else if (baseAddr >= 0xE0 && baseAddr <= 0xF5) modWave = cmd; } else { if (baseAddr >= 0x20 && baseAddr <= 0x35) carChar = cmd; else if (baseAddr >= 0x40 && baseAddr <= 0x55) carLevel = cmd; else if (baseAddr >= 0x60 && baseAddr <= 0x75) carAttack = cmd; else if (baseAddr >= 0x80 && baseAddr <= 0x95) carSustain = cmd; else if (baseAddr >= 0xE0 && baseAddr <= 0xF5) carWave = cmd; } } else { if (baseAddr >= 0xA0 && baseAddr <= 0xA8) freq = cmd; else if (baseAddr >= 0xB0 && baseAddr <= 0xB8) { if (note != NULL) { int timeMs = (int)(time * 1000); Log("A decoding error occurred at %d ms on channel %d in range %d-%d\n", timeMs, channel, _debug_start, _debug_end); return OPBERR_LOGGED; } note = cmd; } else if (baseAddr >= 0xC0 && baseAddr <= 0xC8) feedconn = cmd; else { Vector_Add(range, cmd); } } } // combine instrument data int instrChanges; if ((instrChanges = CountInstrumentChanges(feedconn, modChar, modAttack, modSustain, modWave, carChar, carAttack, carSustain, carWave)) > 0) { Instrument instr = GetInstrument(context, feedconn, modChar, modAttack, modSustain, modWave, carChar, carAttack, carSustain, carWave); size_t size = Uint7Size(instr.Index) + 3; if (modLevel != NULL) { size++; instrChanges++; } if (carLevel != NULL) { size++; instrChanges++; } // combine with frequency and note command if present if (freq != NULL && note != NULL) { size += 2; instrChanges += 2; } if ((int)size < instrChanges * 2) { OpbData data = { 0 }; OpbData_WriteUint7(&data, instr.Index); uint8_t channelMask = channel | (modLevel != NULL ? 0b00100000 : 0) | (carLevel != NULL ? 0b01000000 : 0) | (feedconn != NULL ? 0b10000000 : 0); OpbData_WriteU8(&data, channelMask); int mask = (modChar != NULL ? 0b00000001 : 0) | (modAttack != NULL ? 0b00000010 : 0) | (modSustain != NULL ? 0b00000100 : 0) | (modWave != NULL ? 0b00001000 : 0) | (carChar != NULL ? 0b00010000 : 0) | (carAttack != NULL ? 0b00100000 : 0) | (carSustain != NULL ? 0b01000000 : 0) | (carWave != NULL ? 0b10000000 : 0); OpbData_WriteU8(&data, mask); // instrument command is 0xD0 int reg = OPB_CMD_SETINSTRUMENT; if (freq != NULL && note != NULL) { OpbData_WriteU8(&data, freq->Data); OpbData_WriteU8(&data, note->Data); // play command is 0xD1 reg = OPB_CMD_PLAYINSTRUMENT; } if (modLevel != NULL) OpbData_WriteU8(&data, modLevel->Data); if (carLevel != NULL) OpbData_WriteU8(&data, carLevel->Data); int opbIndex = (int32_t)context->DataMap.Count + 1; Vector_Add(&context->DataMap, &data); Command cmd = { (uint16_t)(reg + (channel >= 9 ? 0x100 : 0)), // register 0, // data time, commands[0].OrderIndex, opbIndex }; Vector_Add(range, &cmd); feedconn = modChar = modLevel = modAttack = modSustain = modWave = carChar = carLevel = carAttack = carSustain = carWave = NULL; if (freq != NULL && note != NULL) { freq = note = NULL; } } } // combine frequency/note and modulator and carrier level data if (freq != NULL && note != NULL) { // note on command is 0xD7 through 0xDF (and 0x1D7 through 0x1DF for channels 10-18) int reg = OPB_CMD_NOTEON + (channel % 9) + (channel >= 9 ? 0x100 : 0); OpbData data = { 0 }; OpbData_WriteU8(&data, freq->Data); int noteLevels = note->Data & 0b00111111; // encode modulator and carrier levels data in the note data's upper 2 (unused) bits if (modLevel != NULL) { noteLevels |= 0b01000000; } if (carLevel != NULL) { noteLevels |= 0b10000000; } OpbData_WriteU8(&data, noteLevels); if (modLevel != NULL) { OpbData_WriteU8(&data, modLevel->Data); } if (carLevel != NULL) { OpbData_WriteU8(&data, carLevel->Data); } int opbIndex = (int32_t)context->DataMap.Count + 1; Vector_Add(&context->DataMap, &data); Command cmd = { (uint16_t)reg, // register 0, // data time, note->OrderIndex, opbIndex }; Vector_Add(range, &cmd); freq = note = modLevel = carLevel = NULL; } if (modChar != NULL) Vector_Add(range, modChar); if (modLevel != NULL) Vector_Add(range, modLevel); if (modAttack != NULL) Vector_Add(range, modAttack); if (modSustain != NULL) Vector_Add(range, modSustain); if (modWave != NULL) Vector_Add(range, modWave); if (carChar != NULL) Vector_Add(range, carChar); if (carLevel != NULL) Vector_Add(range, carLevel); if (carAttack != NULL) Vector_Add(range, carAttack); if (carSustain != NULL) Vector_Add(range, carSustain); if (carWave != NULL) Vector_Add(range, carWave); if (feedconn != NULL) Vector_Add(range, feedconn); if (freq != NULL) Vector_Add(range, freq); if (note != NULL) Vector_Add(range, note); return 0; } static int ProcessTrack(Context* context, int channel, Vector* chOut) { Vector* commands = &context->Tracks[channel]; if (commands->Count == 0) { return 0; } int lastOrder = Vector_GetT(Command, commands, 0)->OrderIndex; int i = 0; while (i < commands->Count) { double time = Vector_GetT(Command, commands, i)->Time; int start = i; // sequences must be all in the same time block and in order // sequences are capped by a note command (write to register B0-B8 or 1B0-1B8) while (i < commands->Count && Vector_GetT(Command, commands, i)->Time <= time && (Vector_GetT(Command, commands, i)->OrderIndex - lastOrder) <= 1) { Command* cmd = Vector_GetT(Command, commands, i); lastOrder = cmd->OrderIndex; i++; if (IsChannelNoteEvent(cmd->Addr, channel)) { break; } } int end = i; VectorT(Command) range = Vector_New(sizeof(Command)); int ret = ProcessRange(context, channel, time, Vector_GetT(Command, commands, start), end - start, &range, start, end); if (ret) { Vector_Free(&range); return ret; } Vector_AddRange(chOut, range.Storage, range.Count); Vector_Free(&range); if (i < commands->Count) { lastOrder = Vector_GetT(Command, commands, i)->OrderIndex; } } return 0; } static int WriteChunk(Context* context, double elapsed, int start, int count) { uint32_t elapsedMs = (uint32_t)((elapsed * 1000) + 0.5); int loCount = 0; int hiCount = 0; for (int i = start; i < start + count; i++) { Command* cmd = Vector_GetT(Command, &context->CommandStream, i); if ((cmd->Addr & 0x100) == 0) { loCount++; } else { hiCount++; } } // write header WRITE_UINT7(context, elapsedMs); WRITE_UINT7(context, loCount); WRITE_UINT7(context, hiCount); // write low and high register writes bool isLow = true; while (true) { for (int i = start; i < start + count; i++) { Command* cmd = Vector_GetT(Command, &context->CommandStream, i); if (((cmd->Addr & 0x100) == 0) == isLow) { uint8_t baseAddr = cmd->Addr & 0xFF; WRITE(&baseAddr, sizeof(uint8_t), 1, context); if (cmd->DataIndex) { // opb command OpbData* data = Vector_GetT(OpbData, &context->DataMap, cmd->DataIndex - 1); WRITE(data->Args, sizeof(uint8_t), data->Count, context); } else { // regular write WRITE(&(cmd->Data), sizeof(uint8_t), 1, context); } } } if (!isLow) { break; } isLow = !isLow; } return 0; } static int SortCommands(const void* a, const void* b) { return ((Command*)a)->OrderIndex - ((Command*)b)->OrderIndex; } static int ConvertToOpb(Context* context) { if (context->Format < OPB_Format_Default || context->Format > OPB_Format_Raw) { context->Format = OPB_Format_Default; } WRITE(OPB_Header, sizeof(char), OPB_HEADER_SIZE, context); Log("OPB format %d (%s)\n", context->Format, OPB_GetFormatName(context->Format)); uint8_t fmt = (uint8_t)context->Format; WRITE(&fmt, sizeof(uint8_t), 1, context); if (context->Format == OPB_Format_Raw) { Log("Writing raw OPL data stream\n"); double lastTime = 0.0; for (int i = 0; i < context->CommandStream.Count; i++) { Command* cmd = Vector_GetT(Command, &context->CommandStream, i); uint16_t elapsed = FlipEndian16((uint16_t)((cmd->Time - lastTime) * 1000.0)); uint16_t addr = FlipEndian16(cmd->Addr); WRITE(&elapsed, sizeof(uint16_t), 1, context); WRITE(&addr, sizeof(uint16_t), 1, context); WRITE(&(cmd->Data), sizeof(uint8_t), 1, context); lastTime = cmd->Time; } return 0; } // separate command stream into tracks Log("Separating OPL data stream into channels\n"); SeparateTracks(context); // process each track into its own output vector VectorT(Command) chOut[NUM_TRACKS]; for (int i = 0; i < NUM_TRACKS; i++) { Log("Processing channel %d\n", i); chOut[i] = Vector_New(sizeof(Command)); int ret = ProcessTrack(context, i, chOut + i); if (ret) { for (int j = 0; j < NUM_TRACKS; j++) { Vector_Free(chOut + j); } return ret; } } // combine all output back into command stream Log("Combining processed data into linear stream\n"); Vector_Clear(&context->CommandStream, true); for (int i = 0; i < NUM_TRACKS; i++) { Vector_AddRange(&context->CommandStream, chOut[i].Storage, chOut[i].Count); } for (int j = 0; j < NUM_TRACKS; j++) { Vector_Free(chOut + j); } // sort by received order Vector_Sort(&context->CommandStream, SortCommands); // write instruments table SEEK(context, 12, SEEK_CUR); // skip header Log("Writing instrument table\n"); for (int i = 0; i < context->Instruments.Count; i++) { int ret = WriteInstrument(context, Vector_GetT(Instrument, &context->Instruments, i)); if (ret) return ret; } // write chunks { int chunks = 0; double lastTime = 0; int i = 0; Log("Writing chunks\n"); while (i < context->CommandStream.Count) { double chunkTime = Vector_GetT(Command, &context->CommandStream, i)->Time; int start = i; while (i < context->CommandStream.Count && Vector_GetT(Command, &context->CommandStream, i)->Time <= chunkTime) { i++; } int end = i; int ret = WriteChunk(context, chunkTime - lastTime, start, end - start); if (ret) return ret; chunks++; lastTime = chunkTime; } // write header Log("Writing header\n"); long fpos; TELL(context, fpos); uint32_t length = FlipEndian32(fpos); uint32_t instrCount = FlipEndian32((uint32_t)context->Instruments.Count); uint32_t chunkCount = FlipEndian32(chunks); SEEK(context, OPB_HEADER_SIZE + 1, SEEK_SET); WRITE(&length, sizeof(uint32_t), 1, context); WRITE(&instrCount, sizeof(uint32_t), 1, context); WRITE(&chunkCount, sizeof(uint32_t), 1, context); } return 0; } static size_t WriteToFile(const void* buffer, size_t elementSize, size_t elementCount, void* context) { return fwrite(buffer, elementSize, elementCount, (FILE*)context); } static int SeekInFile(void* context, long offset, int origin) { return fseek((FILE*)context, offset, origin); } static long TellInFile(void* context) { return ftell((FILE*)context); } int OPB_OplToFile(OPB_Format format, OPB_Command* commandStream, size_t commandCount, const char* file) { FILE* outFile; if ((outFile = fopen(file, "wb")) == NULL) { Log("Couldn't open file '%s' for writing\n", file); return OPBERR_LOGGED; } int ret = OPB_OplToBinary(format, commandStream, commandCount, WriteToFile, SeekInFile, TellInFile, outFile); if (fclose(outFile)) { Log("Error while closing file '%s'\n", file); return OPBERR_LOGGED; } return ret; } int OPB_OplToBinary(OPB_Format format, OPB_Command* commandStream, size_t commandCount, OPB_StreamWriter write, OPB_StreamSeeker seek, OPB_StreamTeller tell, void* userData) { Context context = Context_New(); context.Write = write; context.Seek = seek; context.Tell = tell; context.UserData = userData; context.Format = format; // convert stream to internal format for (int i = 0; i < commandCount; i++) { const OPB_Command* src = commandStream + i; Command cmd = { src->Addr, // OPL register src->Data, // OPL data src->Time, // Time in seconds i, // Stream index 0 // Data index }; Vector_Add(&context.CommandStream, &cmd); } int ret = ConvertToOpb(&context); Context_Free(&context); if (ret) { Log("%s\n", OPB_GetErrorMessage(ret)); } return ret; } static int ReadInstrument(Context* context, Instrument* instr) { uint8_t buffer[9]; READ(buffer, sizeof(uint8_t), 9, context); *instr = MakeInstrument( buffer[0], // feedconn MakeOperator( buffer[1], // modulator characteristic buffer[2], // modulator attack/decay buffer[3], // modulator sustain/release buffer[4] // modulator wave select ), MakeOperator( buffer[5], // carrier characteristic buffer[6], // carrier attack/decay buffer[7], // carrier sustain/release buffer[8] // carrier wave select ), (int)context->Instruments.Count // instrument index ); return 0; } static int ReadUint7(Context* context) { uint8_t b0 = 0, b1 = 0, b2 = 0, b3 = 0; if (context->Read(&b0, sizeof(uint8_t), 1, context->UserData) != 1) return -1; if (b0 >= 128) { b0 &= 0b01111111; if (context->Read(&b1, sizeof(uint8_t), 1, context->UserData) != 1) return -1; if (b1 >= 128) { b1 &= 0b01111111; if (context->Read(&b2, sizeof(uint8_t), 1, context->UserData) != 1) return -1; if (b2 >= 128) { b2 &= 0b01111111; if (context->Read(&b3, sizeof(uint8_t), 1, context->UserData) != 1) return -1; } } } return b0 | (b1 << 7) | (b2 << 14) | (b3 << 21); } #define DEFAULT_READBUFFER_SIZE 256 static inline int AddToBuffer(Context* context, OPB_Command* buffer, int* index, OPB_Command cmd) { buffer[*index] = cmd; (*index)++; if (*index >= DEFAULT_READBUFFER_SIZE) { SUBMIT(buffer, DEFAULT_READBUFFER_SIZE, context); *index = 0; } return 0; } #define ADD_TO_BUFFER_IMPL(retvar, context, buffer, index, command) \ { int retvar; \ if ((retvar = AddToBuffer(context, buffer, bufferIndex, command))) return retvar; } #define ADD_TO_BUFFER(context, buffer, index, ...) ADD_TO_BUFFER_IMPL(MACRO_CONCAT(__ret, __LINE__), context, buffer, index, __VA_ARGS__) static int ReadCommand(Context* context, OPB_Command* buffer, int* bufferIndex, int mask) { uint8_t baseAddr; READ(&baseAddr, sizeof(uint8_t), 1, context); int addr = baseAddr | mask; switch (baseAddr) { default: { uint8_t data; READ(&data, sizeof(uint8_t), 1, context); ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)addr, data, context->Time )); break; } case OPB_CMD_PLAYINSTRUMENT: case OPB_CMD_SETINSTRUMENT: { int instrIndex; READ_UINT7(instrIndex, context); uint8_t channelMask[2]; READ(channelMask, sizeof(uint8_t), 2, context); int channel = channelMask[0]; bool modLvl = (channel & 0b00100000) != 0; bool carLvl = (channel & 0b01000000) != 0; bool feedconn = (channel & 0b10000000) != 0; channel &= 0b00011111; if (channel < 0 || channel >= NUM_CHANNELS) { Log("Error reading OPB command: channel %d out of range\n", channel); return OPBERR_LOGGED; } int chmask = channelMask[1]; bool modChr = (chmask & 0b00000001) != 0; bool modAtk = (chmask & 0b00000010) != 0; bool modSus = (chmask & 0b00000100) != 0; bool modWav = (chmask & 0b00001000) != 0; bool carChr = (chmask & 0b00010000) != 0; bool carAtk = (chmask & 0b00100000) != 0; bool carSus = (chmask & 0b01000000) != 0; bool carWav = (chmask & 0b10000000) != 0; uint8_t freq = 0, note = 0; bool isPlay = baseAddr == OPB_CMD_PLAYINSTRUMENT; if (isPlay) { READ(&freq, sizeof(uint8_t), 1, context); READ(¬e, sizeof(uint8_t), 1, context); } uint8_t modLvlData = 0, carLvlData = 0; if (modLvl) READ(&modLvlData, sizeof(uint8_t), 1, context); if (carLvl) READ(&carLvlData, sizeof(uint8_t), 1, context); if (instrIndex < 0 || instrIndex >= context->Instruments.Count) { Log("Error reading OPB command: instrument %d out of range\n", instrIndex); return OPBERR_LOGGED; } Instrument* instr = Vector_GetT(Instrument, &context->Instruments, instrIndex); int conn = ChannelToOffset[channel]; int mod = OperatorOffsets[ChannelToOp[channel]]; int car = mod + 3; int playOffset = ChannelToOffset[channel]; if (feedconn) ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(REG_FEEDCONN + conn), (uint8_t)instr->FeedConn, context->Time )); if (modChr) ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(REG_CHARACTER + mod), (uint8_t)instr->Modulator.Characteristic, context->Time )); if (modLvl) ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(REG_LEVELS + mod), modLvlData, context->Time )); if (modAtk) ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(REG_ATTACK + mod), (uint8_t)instr->Modulator.AttackDecay, context->Time )); if (modSus) ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(REG_SUSTAIN + mod), (uint8_t)instr->Modulator.SustainRelease, context->Time )); if (modWav) ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(REG_WAVE + mod), (uint8_t)instr->Modulator.WaveSelect, context->Time )); if (carChr) ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(REG_CHARACTER + car), (uint8_t)instr->Carrier.Characteristic, context->Time )); if (carLvl) ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(REG_LEVELS + car), carLvlData, context->Time )); if (carAtk) ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(REG_ATTACK + car), (uint8_t)instr->Carrier.AttackDecay, context->Time )); if (carSus) ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(REG_SUSTAIN + car), (uint8_t)instr->Carrier.SustainRelease, context->Time )); if (carWav) ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(REG_WAVE + car), (uint8_t)instr->Carrier.WaveSelect, context->Time )); if (isPlay) { ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(REG_FREQUENCY + playOffset), freq, context->Time )); ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(REG_NOTE + playOffset), note, context->Time )); } break; } case OPB_CMD_NOTEON: case OPB_CMD_NOTEON + 1: case OPB_CMD_NOTEON + 2: case OPB_CMD_NOTEON + 3: case OPB_CMD_NOTEON + 4: case OPB_CMD_NOTEON + 5: case OPB_CMD_NOTEON + 6: case OPB_CMD_NOTEON + 7: case OPB_CMD_NOTEON + 8: { int channel = (baseAddr - 0xD7) + (mask != 0 ? 9 : 0); if (channel < 0 || channel >= NUM_CHANNELS) { Log("Error reading OPB command: channel %d out of range\n", channel); return OPBERR_LOGGED; } uint8_t freqNote[2]; READ(freqNote, sizeof(uint8_t), 2, context); uint8_t freq = freqNote[0]; uint8_t note = freqNote[1]; ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(addr - (OPB_CMD_NOTEON - REG_FREQUENCY)), freq, context->Time )); ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)(addr - (OPB_CMD_NOTEON - REG_NOTE)), (uint8_t)(note & 0b00111111), context->Time )); if ((note & 0b01000000) != 0) { // set modulator volume uint8_t vol; READ(&vol, sizeof(uint8_t), 1, context); int reg = REG_LEVELS + OperatorOffsets[ChannelToOp[channel]]; ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)reg, vol, context->Time )); } if ((note & 0b10000000) != 0) { // set carrier volume uint8_t vol; READ(&vol, sizeof(uint8_t), 1, context); int reg = REG_LEVELS + 3 + OperatorOffsets[ChannelToOp[channel]]; ADD_TO_BUFFER(context, buffer, bufferIndex, MakeCommand( (uint16_t)reg, vol, context->Time )); } break; } } return 0; } static int ReadChunk(Context* context, OPB_Command* buffer, int* bufferIndex) { int elapsed, loCount, hiCount; READ_UINT7(elapsed, context); READ_UINT7(loCount, context); READ_UINT7(hiCount, context); context->Time += elapsed / 1000.0; for (int i = 0; i < loCount; i++) { int ret = ReadCommand(context, buffer, bufferIndex, 0x0); if (ret) return ret; } for (int i = 0; i < hiCount; i++) { int ret = ReadCommand(context, buffer, bufferIndex, 0x100); if (ret) return ret; } return 0; } static int ReadOpbDefault(Context* context) { uint32_t header[3]; READ(header, sizeof(uint32_t), 3, context); for (int i = 0; i < 3; i++) header[i] = FlipEndian32(header[i]); uint32_t instrumentCount = header[1]; uint32_t chunkCount = header[2]; for (uint32_t i = 0; i < instrumentCount; i++) { Instrument instr; int ret = ReadInstrument(context, &instr); if (ret) return ret; Vector_Add(&context->Instruments, &instr); } OPB_Command buffer[DEFAULT_READBUFFER_SIZE]; int bufferIndex = 0; for (uint32_t i = 0; i < chunkCount; i++) { int ret = ReadChunk(context, buffer, &bufferIndex); if (ret) return ret; } if (bufferIndex > 0) { SUBMIT(buffer, bufferIndex, context); } return 0; } #define RAW_READBUFFER_SIZE 256 #define RAW_ENTRY_SIZE 5 static int ReadOpbRaw(Context* context) { double time = 0; uint8_t buffer[RAW_READBUFFER_SIZE * RAW_ENTRY_SIZE]; OPB_Command commandStream[RAW_READBUFFER_SIZE]; size_t itemsRead; while ((itemsRead = context->Read(buffer, RAW_ENTRY_SIZE, RAW_READBUFFER_SIZE, context->UserData)) > 0) { uint8_t* value = buffer; for (int i = 0; i < itemsRead; i++, value += RAW_ENTRY_SIZE) { uint16_t elapsed = (value[0] << 8) | value[1]; uint16_t addr = (value[2] << 8) | value[3]; uint8_t data = value[4]; time += elapsed / 1000.0; OPB_Command cmd = { addr, data, time }; commandStream[i] = cmd; } SUBMIT(commandStream, itemsRead, context); } return 0; } static int ConvertFromOpb(Context* context) { char id[OPB_HEADER_SIZE + 1] = { 0 }; READ(id, sizeof(char), OPB_HEADER_SIZE, context); if (strncmp(id, "OPBin", 5)) { return OPBERR_NOT_AN_OPB_FILE; } switch (id[5]) { case '1': break; default: return OPBERR_VERSION_UNSUPPORTED; } if (id[6] != '\0') { return OPBERR_NOT_AN_OPB_FILE; } uint8_t fmt; READ(&fmt, sizeof(uint8_t), 1, context); switch (fmt) { default: Log("Error reading OPB file: unknown format %d\n", fmt); return OPBERR_LOGGED; case OPB_Format_Default: return ReadOpbDefault(context); case OPB_Format_Raw: return ReadOpbRaw(context); } } static size_t ReadFromFile(void* buffer, size_t elementSize, size_t elementCount, void* context) { return fread(buffer, elementSize, elementCount, (FILE*)context); } int OPB_FileToOpl(const char* file, OPB_BufferReceiver receiver, void* receiverData) { FILE* inFile; if ((inFile = fopen(file, "rb")) == NULL) { Log("Couldn't open file '%s' for reading\n", file); return OPBERR_LOGGED; } int ret = OPB_BinaryToOpl(ReadFromFile, inFile, receiver, receiverData); fclose(inFile); return ret; } int OPB_BinaryToOpl(OPB_StreamReader reader, void* readerData, OPB_BufferReceiver receiver, void* receiverData) { Context context = { 0 }; context.Read = reader; context.Submit = receiver; context.UserData = readerData; context.ReceiverData = receiverData; context.Instruments = Vector_New(sizeof(Instrument)); int ret = ConvertFromOpb(&context); Context_Free(&context); if (ret) { Log("%s\n", OPB_GetErrorMessage(ret)); } return ret; } const char* OPB_GetErrorMessage(int errCode) { switch (errCode) { case OPBERR_WRITE_ERROR: return "A write error occurred while converting OPB"; break; case OPBERR_SEEK_ERROR: return "A seek error occurred while converting OPB"; break; case OPBERR_TELL_ERROR: return "A file position error occurred while converting OPB"; break; case OPBERR_READ_ERROR: return "A read error occurred while converting OPB"; break; case OPBERR_BUFFER_ERROR: return "A buffer error occurred while converting OPB"; break; case OPBERR_NOT_AN_OPB_FILE: return "Couldn't parse OPB file; not a valid OPB file"; break; case OPBERR_VERSION_UNSUPPORTED: return "Couldn't parse OPB file; invalid version or version unsupported"; break; default: return "Unknown OPB error"; } } #endif /* OPBLIB_IMPLEMENTATION */