Skip to content

Commit

Permalink
Prototype YM control in MIDI
Browse files Browse the repository at this point in the history
  • Loading branch information
indigodarkwolf committed Jul 18, 2021
1 parent 388d8ef commit f84755b
Show file tree
Hide file tree
Showing 13 changed files with 1,333 additions and 562 deletions.
2 changes: 1 addition & 1 deletion build/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ $(BOX16_OBJDIR)/%.o: $(BOX16_SRCDIR)/%.cpp | $(BOX16_OBJDIRS)
$(BOX16_OBJDIRS):
mkdir -p $@

$(OUTDIR)/box16: $(NFD_OBJS) $(LPNG_OBJS) $(RTMIDI_OBJS) $(YMFM_OBJS) $(BOX16_OBJS)
$(OUTDIR)/box16: $(BOX16_OBJS) $(NFD_OBJS) $(LPNG_OBJS) $(RTMIDI_OBJS) $(YMFM_OBJS)
mkdir -p $(OUTDIR)
g++ $^ -o $@ $(BOX16_LDFLAGS) $(NFD_LDFLAGS)
cp $(REPODIR)/resources/*.png $(OUTDIR)/
Expand Down
4 changes: 4 additions & 0 deletions build/vs2019/box16.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ xcopy $(VendorDir)\glew-2.2.0\bin\Release\x64\*.dll $(OutDir) /E /I /F /Y
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\src\audio.cpp" />
<ClCompile Include="..\..\src\bitutils.cpp" />
<ClCompile Include="..\..\src\compat\compat.cpp" />
<ClCompile Include="..\..\src\compat\getopt.cpp" />
<ClCompile Include="..\..\src\cpu\fake6502.cpp" />
Expand Down Expand Up @@ -245,6 +246,7 @@ xcopy $(VendorDir)\glew-2.2.0\bin\Release\x64\*.dll $(OutDir) /E /I /F /Y
<ClCompile Include="..\..\src\overlay\ram_dump.cpp" />
<ClCompile Include="..\..\src\overlay\util.cpp" />
<ClCompile Include="..\..\src\overlay\vram_dump.cpp" />
<ClCompile Include="..\..\src\overlay\ym2151_overlay.cpp" />
<ClCompile Include="..\..\src\ps2.cpp" />
<ClCompile Include="..\..\src\rtc.cpp" />
<ClCompile Include="..\..\src\sdl_events.cpp" />
Expand All @@ -264,6 +266,7 @@ xcopy $(VendorDir)\glew-2.2.0\bin\Release\x64\*.dll $(OutDir) /E /I /F /Y
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\src\audio.h" />
<ClInclude Include="..\..\src\bitutils.h" />
<ClInclude Include="..\..\src\compat\compat.h" />
<ClInclude Include="..\..\src\compat\dirent.h" />
<ClInclude Include="..\..\src\compat\getopt.h" />
Expand Down Expand Up @@ -304,6 +307,7 @@ xcopy $(VendorDir)\glew-2.2.0\bin\Release\x64\*.dll $(OutDir) /E /I /F /Y
<ClInclude Include="..\..\src\overlay\ram_dump.h" />
<ClInclude Include="..\..\src\overlay\util.h" />
<ClInclude Include="..\..\src\overlay\vram_dump.h" />
<ClInclude Include="..\..\src\overlay\ym2151_overlay.h" />
<ClInclude Include="..\..\src\ps2.h" />
<ClInclude Include="..\..\src\ring_buffer.h" />
<ClInclude Include="..\..\src\rom_symbols.h" />
Expand Down
12 changes: 12 additions & 0 deletions build/vs2019/box16.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@
<ClCompile Include="..\..\src\imgui\imgui_tables.cpp">
<Filter>Source Files\imgui</Filter>
</ClCompile>
<ClCompile Include="..\..\src\bitutils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\overlay\ym2151_overlay.cpp">
<Filter>Source Files\overlay</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\src\compat\compat.h">
Expand Down Expand Up @@ -385,6 +391,12 @@
<ClInclude Include="..\..\vendor\lodepng\lodepng.h">
<Filter>Vendor Source Files\lodepng</Filter>
</ClInclude>
<ClInclude Include="..\..\src\bitutils.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="..\..\src\overlay\ym2151_overlay.h">
<Filter>Source Files\overlay</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\..\src\cpu\65c02.opcodes">
Expand Down
1 change: 1 addition & 0 deletions src/bitutils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "bitutils.h"
27 changes: 27 additions & 0 deletions src/bitutils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once
#if !defined(BITUTILS_H)
# define BITUTILS_H

template <uint8_t msb, uint8_t lsb = msb>
uint8_t get_bit_field(const uint8_t value)
{
static_assert(msb >= lsb);
constexpr const uint8_t mask = (2 << (msb - lsb)) - 1;
return (value >> lsb) & mask;
}

template <uint8_t msb, uint8_t lsb = msb>
uint8_t set_bit_field(const uint8_t src, const uint8_t value)
{
static_assert(msb >= lsb);
constexpr const uint8_t mask = (2 << (msb - lsb)) - 1;
return (src & ~(mask << lsb)) | (value << lsb);
}

template <typename T>
constexpr T bit_set_or_res(const T val, const T mask, bool cond)
{
return cond ? (val | mask) : (val & ~mask);
}

#endif
164 changes: 150 additions & 14 deletions src/midi.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#include "midi.h"

#include <cstring>
#include "math.h"
#include "RtMidi.h"

#include "vera/vera_psg.h"
#include "math.h"
#include <cstring>
#include <unordered_map>

#include "vera/vera_psg.h"
#include "ym2151/ym2151.h"

#define MAX_MIDI_KEYS (128)

Expand Down Expand Up @@ -37,10 +37,15 @@ struct psg_midi_mapping {
midi_channel channel;
};

struct ym_midi_mapping {
midi_channel channel;
};

const midi_port_descriptor INVALID_MIDI_PORT{ RtMidi::Api::NUM_APIS, 0xffff };
const midi_channel INVALID_MIDI_CHANNEL{ INVALID_MIDI_PORT, 0xff };

static psg_midi_mapping Psg_midi_mappings[PSG_NUM_CHANNELS];
static ym_midi_mapping Ym_midi_mappings[MAX_YM2151_VOICES];

static RtMidiIn Midi_in_api;

Expand Down Expand Up @@ -181,6 +186,18 @@ static uint16_t Psg_frequency_table[MAX_MIDI_KEYS] = {
33672,
};

midi_ym_patch_entry Ym_default_patch[] = {
{ 0x20, 0xc0 }, // YM_R_L_FB_CONN_OFFSET
{ 0x58, 0x01 }, // YM_DT1_MUL_OFFSET voice 4 slot 3
{ 0x68, 0x00 }, // YM_TL_OFFSET voice 4 slot 1
{ 0x70, 0x00 }, // YM_TL_OFFSET voice 4 slot 2
{ 0x78, 0x00 }, // YM_TL_OFFSET voice 4 slot 3
{ 0x60, 0x00 }, // YM_TL_OFFSET voice 4 slot 4
{ 0x98, 0x1f }, // YM_KS_AR_OFFSET voice 4 slot 3
{ 0xb8, 0x0d }, // YM_A_D1R_OFFSET voice 4 slot 3
{ 0xf8, 0xf6 } // YM_D1L_RR_OFFSET voice 4 slot 3
};

// -------------------
//
// midi_port_descriptor
Expand Down Expand Up @@ -258,6 +275,48 @@ static uint8_t alloc_psg_voice()
return INVALID_VOICE;
}

// -------------------
//
// ym helpers
//
// -------------------

static uint8_t alloc_ym_voice()
{
for (uint8_t i = 0; i < MAX_YM2151_VOICES; ++i) {
if (Ym_midi_mappings[i].channel == INVALID_MIDI_CHANNEL) {
return i;
}
}
return INVALID_VOICE;
}

static void apply_ym_patch(uint8_t ym_channel, midi_ym_patch_entry *patch_data, int num_entries)
{
for (int i = 0; i < num_entries; ++i) {
YM_write(0, patch_data[i].addr + ym_channel);
YM_write(1, patch_data[i].value);
}
}

uint8_t midi_ym_note(uint8_t midikey)
{
const uint8_t chroma = (midikey + 11) % 12;
return chroma + (chroma / 3);
}

uint8_t midi_ym_octave(uint8_t midikey)
{
if (midikey < 13) {
return 0;
}
const uint8_t result = (midikey - 13) / 12;
if (result > 7) {
return 7;
}
return result;
}

// -------------------
//
// midi message helpers
Expand Down Expand Up @@ -290,10 +349,16 @@ static uint16_t get_bent_frequency(int keynum, int bend)
return (uint16_t)(f0 + ((diff * bend) >> 13));
}

static uint8_t get_velocitated_volume(int volume, int velocity)
static uint8_t get_psg_velocitated_volume(int volume, int velocity)
{
const float vv = sqrtf((float)(volume * velocity) / (127.0f * 127.0f));
return (uint8_t)(vv * 63.0f);
}

static uint8_t get_ym_velocitated_volume(int volume, int velocity)
{
const int vv = (const int)sqrtf((float)(volume * velocity)); // max 7 bits
return (uint8_t)(vv >> 1);
const int vv = (const int)sqrtf((float)(volume * velocity) / (127.0f * 127.0f));
return (uint8_t)(vv * 127.0f);
}

static void note_off(open_midi_port &port, uint8_t channel, int keynum, int velocity)
Expand All @@ -309,7 +374,8 @@ static void note_off(open_midi_port &port, uint8_t channel, int keynum, int velo
psg_set_channel_volume(key.voice, 0);
break;
case midi_playback_device::ym2151:
// TODO: Implement me.
YM_key_on(key.voice, false, false, false, false);
Ym_midi_mappings[key.voice].channel = INVALID_MIDI_CHANNEL;
break;
default:
break;
Expand Down Expand Up @@ -349,10 +415,27 @@ static void note_on(open_midi_port &port, uint8_t channel, int keynum, int veloc
psg_set_channel_pulse_width(key.voice, (uint8_t)(settings.modulation >> 1));
psg_set_channel_left(key.voice, settings.pan < 96);
psg_set_channel_right(key.voice, settings.pan > 32);
psg_set_channel_volume(key.voice, settings.use_velocity ? get_velocitated_volume(settings.volume, velocity) : (uint8_t)(settings.volume >> 1));
psg_set_channel_volume(key.voice, settings.use_velocity ? get_psg_velocitated_volume(settings.volume, velocity) : (uint8_t)(settings.volume >> 1));
break;
case midi_playback_device::ym2151:
// TODO: Implement me.
if (key.voice == INVALID_VOICE) {
key.voice = alloc_ym_voice();
}
if (key.voice == INVALID_VOICE) {
return;
}
Ym_midi_mappings[key.voice].channel = { port.descriptor, channel };
apply_ym_patch(key.voice, settings.device.ym2151.patch_bytes, settings.device.ym2151.patch_size);
for (int i = 0; i < 4; ++i) {
const uint8_t tl = YM_get_operator_total_level(key.voice, i);
const uint8_t vv = settings.use_velocity ? (get_psg_velocitated_volume(settings.volume, velocity) << 1) : (uint8_t)(settings.volume);
const uint8_t fv = 127 - (uint8_t)(((uint16_t)vv * (uint16_t)(127 - tl)) >> 7);
YM_set_operator_total_level(key.voice, i, fv);
}
YM_set_voice_key_fraction(key.voice, settings.pitch_bend >> 8);
YM_set_voice_octave(key.voice, midi_ym_octave(keynum));
YM_set_voice_note(key.voice, midi_ym_note(keynum));
YM_key_on(key.voice);
break;
default:
break;
Expand Down Expand Up @@ -643,7 +726,12 @@ static void pitch_bend(open_midi_port &port, uint8_t channel, int bend)
}
break;
case midi_playback_device::ym2151:
// TODO: Implement me.
for (int i = 0; i < MAX_MIDI_KEYS; ++i) {
const midi_key &key = port.channels[channel].keys_on[i];
if (key.voice != INVALID_VOICE) {
YM_set_voice_key_fraction(key.voice, bend >> 8);
}
}
break;
default:
break;
Expand Down Expand Up @@ -716,6 +804,9 @@ void midi_init()
for (uint8_t i = 0; i < PSG_NUM_CHANNELS; ++i) {
Psg_midi_mappings[i].channel = INVALID_MIDI_CHANNEL;
}
for (uint8_t i = 0; i < MAX_YM2151_VOICES; ++i) {
Ym_midi_mappings[i].channel = INVALID_MIDI_CHANNEL;
}
}

void midi_process()
Expand Down Expand Up @@ -826,7 +917,7 @@ void midi_port_set_channel_playback_device(midi_port_descriptor port, uint8_t ch
auto &[port_number, open_port] = *value;

midi_channel_state &state = open_port.channels[channel];
midi_key keys_on[MAX_MIDI_KEYS];
midi_key keys_on[MAX_MIDI_KEYS];
memcpy(keys_on, state.keys_on, sizeof(midi_key) * MAX_MIDI_KEYS);

for (uint8_t i = 0; i < MAX_MIDI_KEYS; ++i) {
Expand All @@ -844,6 +935,11 @@ void midi_port_set_channel_playback_device(midi_port_descriptor port, uint8_t ch
}
}
}

if (d == midi_playback_device::ym2151) {
memcpy(state.settings.device.ym2151.patch_bytes, Ym_default_patch, sizeof(Ym_default_patch));
state.settings.device.ym2151.patch_size = sizeof(Ym_default_patch) / sizeof(Ym_default_patch[0]);
}
}
}
}
Expand All @@ -855,7 +951,7 @@ void midi_port_set_channel_use_velocity(midi_port_descriptor port, uint8_t chann
if (value != Open_midi_ports.end()) {
auto &[port_number, open_port] = *value;

midi_channel_state &state = open_port.channels[channel];
midi_channel_state &state = open_port.channels[channel];
state.settings.use_velocity = use_velocity;
}
}
Expand All @@ -876,7 +972,47 @@ void midi_port_set_channel_psg_waveform(midi_port_descriptor port, uint8_t chann
}
}

state.settings.device.psg.waveform = waveform;
state.settings.device.psg.waveform = waveform;
}
}
}

void midi_port_set_channel_ym2151_patch_byte(midi_port_descriptor port, uint8_t channel, uint8_t addr, uint8_t data)
{
if (channel < MAX_MIDI_CHANNELS) {
auto value = Open_midi_ports.find(port);
if (value != Open_midi_ports.end()) {
auto &[port_number, open_port] = *value;

midi_channel_state &state = open_port.channels[channel];

midi_ym2151_settings &settings = state.settings.device.ym2151;
for (int i = 0; i < settings.patch_size; ++i) {
if (settings.patch_bytes[i].addr == addr) {
settings.patch_bytes[i].value = data;
return;
}
}
settings.patch_bytes[settings.patch_size].addr = addr;
settings.patch_bytes[settings.patch_size].value = data;
++settings.patch_size;
}
}
}

void midi_port_get_channel_ym2151_patch(midi_port_descriptor port, uint8_t channel, uint8_t *bytes)
{
if (channel < MAX_MIDI_CHANNELS) {
auto value = Open_midi_ports.find(port);
if (value != Open_midi_ports.end()) {
auto &[port_number, open_port] = *value;

midi_channel_state &state = open_port.channels[channel];

midi_ym2151_settings &settings = state.settings.device.ym2151;
for (int i = 0; i < settings.patch_size; ++i) {
bytes[settings.patch_bytes[i].addr] = settings.patch_bytes[i].value;
}
}
}
}
9 changes: 9 additions & 0 deletions src/midi.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,14 @@ struct midi_psg_settings {
uint8_t waveform = 0;
};

struct midi_ym_patch_entry {
uint8_t addr;
uint8_t value;
};

struct midi_ym2151_settings {
midi_ym_patch_entry patch_bytes[256];
int patch_size = 0;
};

struct midi_channel_settings {
Expand Down Expand Up @@ -73,5 +80,7 @@ void midi_port_set_channel_playback_device(midi_port_descriptor port, uint8_t ch
void midi_port_set_channel_use_velocity(midi_port_descriptor port, uint8_t channel, bool use_velocity);

void midi_port_set_channel_psg_waveform(midi_port_descriptor port, uint8_t channel, uint8_t waveform);
void midi_port_set_channel_ym2151_patch_byte(midi_port_descriptor port, uint8_t channel, uint8_t addr, uint8_t value);
void midi_port_get_channel_ym2151_patch(midi_port_descriptor port, uint8_t channel, uint8_t *bytes);

#endif
Loading

0 comments on commit f84755b

Please sign in to comment.