Skip to content

Commit

Permalink
Allow loading custom waveforms via sysex
Browse files Browse the repository at this point in the history
  • Loading branch information
tstirrat committed Sep 26, 2024
1 parent 31bca4f commit bfb0323
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 65 deletions.
10 changes: 10 additions & 0 deletions Source/io/midi.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "midi.h"
#include "../mGB.h"
#include "midi_asm.h"
#include "midi_sysex.h"
#include "serial.h"

uint8_t statusByte;
Expand All @@ -17,8 +18,17 @@ void updateMidiBuffer(void) {

uint8_t byte = serialBuffer[serialBufferReadPosition];

if (sysexBytesCount) {
captureSysexByte(byte);
return;
}

// STATUS BYTE
if (byte & MIDI_STATUS_BIT) {
if (byte == MIDI_STATUS_SYSEX) {
sysexBytesCount = 1;
return;
}
if ((byte & MIDI_STATUS_SYSTEM) == MIDI_STATUS_SYSTEM) {
return;
}
Expand Down
1 change: 1 addition & 0 deletions Source/io/midi.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <gbdk/platform.h>
#include <stdbool.h>

#define MIDI_STATUS_BIT 0x80
#define MIDI_STATUS_NOTE_ON 0x09
Expand Down
84 changes: 84 additions & 0 deletions Source/io/midi_sysex.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include "midi_sysex.h"
#include "../mGB.h"

uint8_t sysexBytesCount;
sysex_payload_t sysexPayload;
uint8_t sysexBuffer[24];

// adapted from
// https://github.com/FortySevenEffects/arduino_midi_library/blob/2d64cc3c2ff85bbee654a7054e36c59694d8d8e4/src/MIDI.cpp#L87
// (MIT licensed)
// Reduced parameter count to improve perf

/*!
\brief Decode System Exclusive messages.
SysEx messages are encoded to guarantee transmission of data bytes higher
than 127 without breaking the MIDI protocol. Use this static method to
reassemble your received message.
\param outData The output buffer where to
store the decrypted message.
\param inLength The length of the input
buffer.
\return The length of the output buffer.
@see encodeSysEx @see getSysExArrayLength
Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com
*/
static uint8_t decodeSysEx(uint8_t *outData, uint8_t inLength) {
uint8_t count = 0;
uint8_t msbStorage = 0;
uint8_t byteIndex = 0;

for (uint8_t i = 0; i < inLength; ++i) {
if ((i % 8) == 0) {
msbStorage = sysexBuffer[i];
byteIndex = 6;
} else {
const uint8_t body = sysexBuffer[i];
const uint8_t msb = (((msbStorage >> byteIndex) & 1) << 7);
byteIndex--;
outData[count++] = msb | body;
}
}
return count;
}

void captureSysexByte(uint8_t byte) {
sysexBytesCount++;
systemIdle = false;

const uint8_t bufferIndex = sysexBytesCount - SYSEX_HEADER_SIZE;

if (byte == SYSEX_EOF) {
// EMU_printf("sysex EOF: %#02x\n", byte);
sysexPayload.size = decodeSysEx(&sysexPayload.data[0], bufferIndex);
sysexPayload.ready = true;
sysexBytesCount = 0;
return;
}

switch (sysexBytesCount) {
case 1:
// EMU_printf("sysex status byte: %#02x\n", byte);
// ignored in payload
break;
case 2:
// EMU_printf("sysex id: %#02x\n", byte);
sysexPayload.id = byte;
break;
case 3:
// EMU_printf("sysex device_id: %#02x\n", byte);
sysexPayload.device_id = byte;
break;
case 4:
// EMU_printf("sysex channel: %#02x\n", byte);
sysexPayload.channel = byte;
break;

default:
// EMU_printf("sysex data: %#02x\n", byte);
sysexBuffer[bufferIndex] = byte;
break;
}
}
46 changes: 46 additions & 0 deletions Source/io/midi_sysex.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#pragma once

#include <gbdk/platform.h>
#include <stdbool.h>

#define MIDI_STATUS_SYSEX 0xF0
#define SYSEX_UNIVERSAL_REALTIME 0x7F
#define SYSEX_UNIVERSAL_NON_REALTIME 0x7E
#define SYSEX_NON_COMMERCIAL 0x7D
#define SYSEX_EOF 0xF7

#define SYSEX_MGB_ID 0x69

// The size of the SysEx message header before the payload begins
#define SYSEX_HEADER_SIZE 5

extern uint8_t sysexBytesCount;

typedef struct sysex_payload {
// the sysex message id (or manufacturer id)
uint8_t id;
// the payload device id (0xBB for mGB)
uint8_t device_id;
// the device or channel id
uint8_t channel;
// the size of the data
uint8_t size;
// the data payload (24 raw sysex bytes = 21 (3*7) + 3 header blocks)
uint8_t data[21];
// true if the payload has finished reading from the buffer (has seen an EOF)
bool ready;
} sysex_payload_t;

// A SysEx payload, captures up to 16 bytes of sysex data. Currently this is
// used to modify the active WAV channel waveform.
extern sysex_payload_t sysexPayload;

// The buffer to hold the sysexBytes before decoding
// contains enough space to hold 16 bytes = MSB, 7 bytes, MSB, 7 bytes, MSB, 2
// bytes = 19 bytes
extern uint8_t sysexBuffer[24];

// Reads SysEx messages (F0) up until a SysEx EOF packet
// (F7). Data is stored in `sysexPayload` and will set `sysexPayload.ready` when
// it sees the EOF
void captureSysexByte(uint8_t byte);
87 changes: 62 additions & 25 deletions Source/io/sram.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
#include "../mGB.h"
#include "../screen/main.h"
#include "../synth/data.h"
#include "../synth/wav.h"
#include <gbdk/platform.h>
#include <string.h>

void saveDataSet(uint8_t synth) {
// EMU_printf("saveDataSet(synth %d)\n", synth);
Expand Down Expand Up @@ -80,41 +82,76 @@ void loadDataSet(uint8_t synth) {
DISABLE_RAM_MBC1;
}

void checkMemory(void) {
// Init V1 SRAM data (`saveData`)
static void initV1(void) {
for (x = 0; x != 128; x += 8) {
l = 0;
for (i = 0; i < 7; i++) {
saveData[(x + l)] = dataSet[i];
l++;
}
l = 0;
for (i = 7; i != 13; i++) {
saveData[(x + 128U + l)] = dataSet[i];
l++;
}
l = 0;
for (i = 13; i != 20; i++) {
saveData[(x + 256U + l)] = dataSet[i];
l++;
}
l = 0;
for (i = 20; i != 24; i++) {
saveData[(x + 384U + l)] = dataSet[i];
l++;
}
}
saveData[SRAM_SENTINEL_INDEX] = SRAM_INITIALIZED;
sram_version = 1;
}

// Init V2 SRAM data (`sram_wavData`)
static void initV2(void) {
memcpy(&sram_wavData, &wavData, sizeof(sram_wavData));
sram_version = 2;
}

// load the SRAM `sram_wavData` data into the runtime `wavData`
static void sramLoadAllWavData(void) {
memcpy(&wavData, &sram_wavData, sizeof(wavData));
}

void initMemory(void) {
ENABLE_RAM_MBC1;

// fixes some SRAM Bugs
SWITCH_ROM(1);
SWITCH_RAM(0);
// end fix

if (saveData[512] != SRAM_INITIALIZED) {
for (x = 0; x != 128; x += 8) {
l = 0;
for (i = 0; i < 7; i++) {
saveData[(x + l)] = dataSet[i];
l++;
}
l = 0;
for (i = 7; i != 13; i++) {
saveData[(x + 128U + l)] = dataSet[i];
l++;
}
l = 0;
for (i = 13; i != 20; i++) {
saveData[(x + 256U + l)] = dataSet[i];
l++;
}
l = 0;
for (i = 20; i != 24; i++) {
saveData[(x + 384U + l)] = dataSet[i];
l++;
}
}
saveData[512] = SRAM_INITIALIZED;
if (sram_version == 2) {
// do nothing
} else if (saveData[SRAM_SENTINEL_INDEX] == SRAM_INITIALIZED) {
// legacy SRAM format (== V1)
initV2();
} else {
// no SRAM initialized
saveData[SRAM_SENTINEL_INDEX] = SRAM_INITIALIZED;
initV1();
initV2();
}

sramLoadAllWavData();
DISABLE_RAM_MBC1;

for (j = 0; j != 24; j++)
dataSetSnap[j] = dataSet[j];
}

void sramStoreWaveform(uint8_t index) {
const uint8_t offset = index * WAVEFORM_LENGTH;

ENABLE_RAM_MBC1;
memcpy(&sram_wavData[offset], &wavData[offset], WAVEFORM_LENGTH);
DISABLE_RAM_MBC1;
}
19 changes: 17 additions & 2 deletions Source/io/sram.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,26 @@

#include <gbdk/platform.h>

// the position in SRAM where the Version sentinel sits
#define SRAM_SENTINEL_INDEX 512

// SRAM sentinel value to determine if anything is initialized
#define SRAM_INITIALIZED 0xF7

// 512 + sentinel
extern uint8_t saveData[513U];
extern volatile uint8_t saveData[513];

// SRAM version, so that we can determine if a partial migration/init is needed
extern volatile uint8_t sram_version;

// SRAM stored wav data, can be modified at runtime
extern volatile uint8_t sram_wavData[256];

void saveDataSet(uint8_t synth);
void loadDataSet(uint8_t synth);
void checkMemory(void);

// initialises the SRAM
void initMemory(void);

// Copies a wave from `wavData` into SRAM stored `sram_wavData`
void sramStoreWaveform(uint8_t index);
8 changes: 7 additions & 1 deletion Source/io/sram/sram.b0.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@

#include <gbdk/platform.h>

uint8_t saveData[513];
uint8_t saveData[513];

// SRAM version
uint8_t sram_version;

// SRAM stored wav data, can be modified at runtime
uint8_t sram_wavData[256];
2 changes: 1 addition & 1 deletion Source/mGB.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void main(void) {

CRITICAL {
cpu_fast();
checkMemory();
initMemory();
displaySetup();
initMainScreen();
setSoundDefaults();
Expand Down
1 change: 1 addition & 0 deletions Source/mGB.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

// #include <gbdk/emu_debug.h>
#include <gbdk/platform.h>
#include <stdbool.h>

Expand Down
1 change: 1 addition & 0 deletions Source/synth/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ void updateSynths(void) {
updateVibratoPosition(NOI);

updateWavSweep();
updateWavSysex();
}

inline void setOutputSwitch(void) {
Expand Down
Loading

0 comments on commit bfb0323

Please sign in to comment.