diff --git a/.gitignore b/.gitignore index ae39754..ccbd7a5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.ppm *.png *.out +*.pcm res/mai.data tools/ttf-convert tools/ttf-bitmap diff --git a/Makefile b/Makefile index 3ac891c..b831005 100644 --- a/Makefile +++ b/Makefile @@ -92,14 +92,23 @@ wordle/wordle.o: wordle/word_list.hpp wordle/wordle.elf: wordle/main_saturn.o wordle/wordle.o wordle/draw.o sh/lib1funcs.o res/dejavusansmono.font.bin.o common/keyboard.o common/draw_font.o common/palette.o -scsp/sine-44100-s16be-1ch.pcm: +# 88200 bytes +scsp/sine-44100-s16be-1ch-1sec.pcm: sox \ -r 44100 -e signed-integer -b 16 -c 1 -n -B \ $@.raw \ synth 1 sin 440 vol -10dB mv $@.raw $@ -scsp/slot.elf: scsp/slot.o scsp/sine-44100-s16be-1ch.pcm.o +# 200 bytes +scsp/sine-44100-s16be-1ch-100sample.pcm: + sox \ + -r 44100 -e signed-integer -b 16 -c 1 -n -B \ + $@.raw \ + synth 100s sin 440 vol -10dB + mv $@.raw $@ + +scsp/slot.elf: scsp/slot.o scsp/sine-44100-s16be-1ch-1sec.pcm.o m68k: @@ -110,6 +119,8 @@ scsp/sound_cpu__slot.elf: scsp/sound_cpu__slot.o m68k/slot.bin.o scsp/sound_cpu__interrupt.elf: scsp/sound_cpu__interrupt.o m68k/interrupt.bin.o sh/lib1funcs.o res/sperrypc.font.bin.o common/draw_font.o common/palette.o +scsp/fm.elf: scsp/fm.o res/nec.bitmap.bin.o sh/lib1funcs.o saturn/start.o scsp/sine-44100-s16be-1ch-100sample.pcm.o + res/sperrypc.bitmap.bin: tools/ttf-bitmap ./tools/ttf-bitmap 20 7f res/Bm437_SperryPC_CGA.otb $@ diff --git a/common/intback.hpp b/common/intback.hpp index ad69f12..f222b5e 100644 --- a/common/intback.hpp +++ b/common/intback.hpp @@ -1,5 +1,7 @@ +#pragma once + namespace intback { - enum intback_fsm { + enum fsm : uint8_t { PORT_STATUS = 0, PERIPHERAL_ID, DATA1, @@ -9,7 +11,7 @@ namespace intback { FSM_NEXT }; - struct intback_state { + struct state { uint8_t fsm; uint8_t controller_ix; uint8_t port_ix; @@ -21,11 +23,12 @@ namespace intback { uint8_t kbd_bits; }; - typedef void (*keyboard_func_ptr)(const enum keysym k, uint8_t kbd_bits); + typedef void (*keyboard_func_ptr)(uint8_t keysym, uint8_t kbd_bits); + typedef void (*digital_func_ptr)(uint8_t fsm_state, uint8_t data); - static intback_state state; + static struct state state; - inline void keyboard_fsm(keyboard_func_ptr callback) + inline void fsm(digital_func_ptr digital_cb, keyboard_func_ptr keyboard_cb) { if ((smpc.reg.SR & SR__PDL) != 0) { // PDL == 1; 1st peripheral data @@ -65,18 +68,16 @@ namespace intback { state.data_size = PERIPHERAL_ID__DATA_SIZE(oreg); break; case DATA1: + if (digital_cb != nullptr) digital_cb(state.fsm, oreg); break; case DATA2: + if (digital_cb != nullptr) digital_cb(state.fsm, oreg); break; case DATA3: state.kbd_bits = oreg & 0b1111; break; case DATA4: - { - uint32_t keysym = oreg; - enum keysym k = scancode_to_keysym(keysym); - callback(k, state.kbd_bits); - } + if (keyboard_cb != nullptr) keyboard_cb(state.kbd_bits, oreg); break; default: break; diff --git a/common/string.hpp b/common/string.hpp new file mode 100644 index 0000000..e3b90af --- /dev/null +++ b/common/string.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace string { + template + inline void hex(T * c, uint32_t len, uint32_t n) + { + while (len > 0) { + uint32_t nib = n & 0xf; + n = n >> 4; + if (nib > 9) { + nib += (97 - 10); + } else { + nib += (48 - 0); + } + + c[--len] = nib; + } + } +} diff --git a/editor/main_saturn.cpp b/editor/main_saturn.cpp index 735da08..0a8be84 100644 --- a/editor/main_saturn.cpp +++ b/editor/main_saturn.cpp @@ -166,8 +166,10 @@ inline void keyboard_regular_key(const enum keysym k) } } -void keyboard_callback(const enum keysym k, uint8_t kbd_bits) +void keyboard_callback(uint8_t kbd_bits, uint8_t scancode) { + enum keysym k = scancode_to_keysym(scancode); + if (KEYBOARD__MAKE(kbd_bits)) { switch (k) { case keysym::LEFT_SHIFT : modifier_state |= MODIFIER_LEFT_SHIFT; break; @@ -200,7 +202,7 @@ void smpc_int(void) scu.reg.IST &= ~(IST__SMPC); scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN); - intback::keyboard_fsm(keyboard_callback); + intback::fsm(nullptr, keyboard_callback); } constexpr int32_t plane_a = 2; diff --git a/m68k/Makefile b/m68k/Makefile index fbbd87c..bea722a 100644 --- a/m68k/Makefile +++ b/m68k/Makefile @@ -9,6 +9,6 @@ include $(LIB)/m68k/common.mk %.pcm.o: %.pcm $(BUILD_BINARY_O) -slot.elf: slot.o sine-44100-s16be-1ch.pcm.o +slot.elf: slot.o sine-44100-s16be-1ch-1sec.pcm.o interrupt.elf: interrupt.o jojo-11025-s16be-1ch.pcm.o diff --git a/m68k/interrupt.cpp b/m68k/interrupt.cpp index 2c253ac..c43c7e9 100644 --- a/m68k/interrupt.cpp +++ b/m68k/interrupt.cpp @@ -37,8 +37,7 @@ void auto_vector_1(void) slot.LSA = 0; // loop start address (samples) slot.LEA = frame_size; // loop end address (samples) slot.EG = EG__AR(0x1f) | EG__EGHOLD; // d2r d1r ho ar krs dl rr - slot.VOLUME = 0; // stwinh sdir tl - slot.FM = 0; // mdl mdxsl mdysl + slot.FM = 0; // stwinh sdir tl mdl mdxsl mdysl slot.PITCH = PITCH__OCT(-2) | PITCH__FNS(0); // oct fns slot.LFO = 0; // lfof plfows slot.MIXER = MIXER__DISDL(0b101); // disdl dipan efsdl efpan @@ -63,8 +62,7 @@ void main() slot.LSA = 0; slot.LEA = 0; slot.EG = 0; - slot.VOLUME = 0; - slot.FM = 0; + slot.FM = 0; // 32-bit access slot.PITCH = 0; slot.LFO = 0; slot.MIXER = 0; diff --git a/m68k/slot.cpp b/m68k/slot.cpp index c71302a..c798dcc 100644 --- a/m68k/slot.cpp +++ b/m68k/slot.cpp @@ -21,8 +21,7 @@ void main() slot.LSA = 0; // loop start address (samples) slot.LEA = 44100; // loop end address (samples) slot.EG = EG__AR(0x1f) | EG__EGHOLD; // d2r d1r ho ar krs dl rr - slot.VOLUME = 0; // stwinh sdir tl - slot.FM = 0; // mdl mdxsl mdysl + slot.FM = 0; // stwinh sdir tl mdl mdxsl mdysl slot.PITCH = PITCH__OCT(0) | PITCH__FNS(0); // oct fns slot.LFO = 0; // lfof plfows slot.MIXER = MIXER__DISDL(0b101); // disdl dipan efsdl efpan diff --git a/scsp/fm.cpp b/scsp/fm.cpp new file mode 100644 index 0000000..3b04c7d --- /dev/null +++ b/scsp/fm.cpp @@ -0,0 +1,753 @@ +#include +#include + +#include "vdp2.h" +#include "smpc.h" +#include "scu.h" +#include "sh2.h" +#include "scsp.h" + +#include "../common/copy.hpp" +#include "../common/intback.hpp" +#include "../common/vdp2_func.hpp" +#include "../common/string.hpp" + +extern void * _sine_start __asm("_binary_scsp_sine_44100_s16be_1ch_100sample_pcm_start"); +extern void * _sine_size __asm("_binary_scsp_sine_44100_s16be_1ch_100sample_pcm_size"); + +struct mask_bit { + uint32_t mask; + uint32_t bit; +}; + +template +constexpr inline std::remove_volatile_t get(const typename T::reg_type r) +{ + return (r >> T::bit) & T::mask; +} + +template +constexpr inline void set(typename T::reg_type& r, uint32_t n) +{ + r = (r & ~(T::mask << T::bit)) | ((n & T::mask) << T::bit); +} + +template +constexpr inline void incdec(typename T::reg_type& r, int32_t n) +{ + int32_t ni = static_cast(get(r)) + n; + set(r, ni); +} + + +#define BITS(N, T, M, B) \ + struct N { using reg_type = T; \ + static constexpr uint32_t mask = M; \ + static constexpr uint32_t bit = B; } + +namespace loop { + BITS(kyonex, reg16, 0b1, 12); + BITS(kyonb, reg16, 0b1, 11); + BITS(sbctl, reg16, 0b11, 9); + BITS(ssctl, reg16, 0b11, 7); + BITS(lpctl, reg16, 0b11, 5); + BITS(pcm8b, reg16, 0b1, 4); +} + +namespace sa { + BITS(sa, reg32, 0xf'ffff, 0); +} + +namespace lsa { + BITS(lsa, reg16, 0xffff, 0); +} + +namespace lea { + BITS(lea, reg16, 0xffff, 0); +} + +namespace eg { + BITS(d2r, reg32, 0x1f, 11 + 16); + BITS(d1r, reg32, 0x1f, 6 + 16); + BITS(eghold, reg32, 0b1, 5 + 16); + BITS(ar, reg32, 0x1f, 0 + 16); + BITS(lpslnk, reg32, 0b1, 14); + BITS(krs, reg32, 0xf, 10); + BITS(dl, reg32, 0x1f, 5); + BITS(rr, reg32, 0x1f, 0); +} + +namespace fm { + BITS(stwinh, reg32, 0b1, 9 + 16); + BITS(sdir, reg32, 0b1, 8 + 16); + BITS(tl, reg32, 0xff, 0 + 16); + BITS(mdl, reg32, 0xf, 12); + BITS(mdxsl, reg32, 0x3f, 6); + BITS(mdysl, reg32, 0x3f, 0); +} + +namespace pitch { + BITS(oct, reg16, 0xf, 11); + BITS(fns, reg16, 0x3ff, 0); +} + +namespace lfo { + BITS(lfore, reg16, 0b1, 15); + BITS(lfof, reg16, 0x1f, 10); + BITS(plfows, reg16, 0b11, 8); + BITS(plfos, reg16, 0b111, 5); + BITS(alfows, reg16, 0b11, 3); + BITS(alfos, reg16, 0b111, 0); +} + +namespace mixer { + BITS(isel, reg32, 0b1111, 3 + 16); + BITS(imxl, reg32, 0b111, 0 + 16); + BITS(disdl, reg32, 0b111, 13); + BITS(dipan, reg32, 0b11111, 8); + BITS(efsdl, reg32, 0b111, 5); + BITS(efpan, reg32, 0b11111, 0); +} + +enum class scsp_name : uint8_t { + kyonex, + kyonb, + sbctl, + ssctl, + lpctl, + pcm8b, + + sa, + + lsa, + + lea, + + d2r, + d1r, + eghold, + ar, + lpslnk, + krs, + dl, + rr, + + stwinh, + sdir, + tl, + mdl, + mdxsl, + mdysl, + + oct, + fns, + + lfore, + lfof, + plfows, + plfos, + alfows, + alfos, + + isel, + imxl, + disdl, + dipan, + efsdl, + efpan, + + FIRST = kyonex, + LAST = efpan, +}; + +std::optional grid[7][8] = { + // 0 1 2 3 4 5 6 7 + {scsp_name::kyonex, scsp_name::kyonb, scsp_name::sbctl, std::nullopt, scsp_name::ssctl, std::nullopt, scsp_name::lpctl, scsp_name::pcm8b}, + {scsp_name::sa, std::nullopt, std::nullopt, scsp_name::lsa, std::nullopt, std::nullopt, scsp_name::lea, std::nullopt}, + {scsp_name::d2r, scsp_name::d1r, scsp_name::eghold, scsp_name::ar, scsp_name::lpslnk, scsp_name::krs, scsp_name::dl, scsp_name::rr}, + {scsp_name::stwinh, std::nullopt, scsp_name::sdir, scsp_name::tl, scsp_name::mdl, scsp_name::mdxsl, std::nullopt, scsp_name::mdysl}, + {scsp_name::oct, std::nullopt, std::nullopt, std::nullopt, scsp_name::fns, std::nullopt, std::nullopt, std::nullopt}, + {scsp_name::lfore, scsp_name::lfof, scsp_name::plfows, std::nullopt, scsp_name::plfos, scsp_name::alfows, std::nullopt, scsp_name::alfos}, + {scsp_name::isel, scsp_name::imxl, std::nullopt, scsp_name::disdl, scsp_name::dipan, scsp_name::efsdl, std::nullopt, scsp_name::efpan}, +}; + +uint32_t get_reg(scsp_slot& slot, scsp_name reg_name) +{ + switch (reg_name) { + case scsp_name::kyonex: return get(slot.LOOP); break; + case scsp_name::kyonb: return get(slot.LOOP); break; + case scsp_name::sbctl: return get(slot.LOOP); break; + case scsp_name::ssctl: return get(slot.LOOP); break; + case scsp_name::lpctl: return get(slot.LOOP); break; + case scsp_name::pcm8b: return get(slot.LOOP); break; + + case scsp_name::sa: return get(slot.SA); break; + + case scsp_name::lsa: return get(slot.LSA); break; + + case scsp_name::lea: return get(slot.LEA); break; + + case scsp_name::d2r: return get(slot.EG); break; + case scsp_name::d1r: return get(slot.EG); break; + case scsp_name::eghold: return get(slot.EG); break; + case scsp_name::ar: return get(slot.EG); break; + case scsp_name::lpslnk: return get(slot.EG); break; + case scsp_name::krs: return get(slot.EG); break; + case scsp_name::dl: return get(slot.EG); break; + case scsp_name::rr: return get(slot.EG); break; + + case scsp_name::stwinh: return get(slot.FM); break; + case scsp_name::sdir: return get(slot.FM); break; + case scsp_name::tl: return get(slot.FM); break; + case scsp_name::mdl: return get(slot.FM); break; + case scsp_name::mdxsl: return get(slot.FM); break; + case scsp_name::mdysl: return get(slot.FM); break; + + case scsp_name::oct: return get(slot.PITCH); break; + case scsp_name::fns: return get(slot.PITCH); break; + + case scsp_name::lfore: return get(slot.LFO); break; + case scsp_name::lfof: return get(slot.LFO); break; + case scsp_name::plfows: return get(slot.LFO); break; + case scsp_name::plfos: return get(slot.LFO); break; + case scsp_name::alfows: return get(slot.LFO); break; + case scsp_name::alfos: return get(slot.LFO); break; + + case scsp_name::isel: return get(slot.MIXER); break; + case scsp_name::imxl: return get(slot.MIXER); break; + case scsp_name::disdl: return get(slot.MIXER); break; + case scsp_name::dipan: return get(slot.MIXER); break; + case scsp_name::efsdl: return get(slot.MIXER); break; + case scsp_name::efpan: return get(slot.MIXER); break; + } + while (1) {} +} + +void incdec_reg(scsp_slot& slot, scsp_name reg_name, int32_t dir) +{ + switch (reg_name) { + case scsp_name::kyonex: return set(slot.LOOP, 1); break; + case scsp_name::kyonb: return incdec(slot.LOOP, dir); break; + case scsp_name::sbctl: return incdec(slot.LOOP, dir); break; + case scsp_name::ssctl: return incdec(slot.LOOP, dir); break; + case scsp_name::lpctl: return incdec(slot.LOOP, dir); break; + case scsp_name::pcm8b: return incdec(slot.LOOP, dir); break; + + case scsp_name::sa: return incdec(slot.SA, dir); break; + + case scsp_name::lsa: return incdec(slot.LSA, dir); break; + + case scsp_name::lea: return incdec(slot.LEA, dir); break; + + case scsp_name::d2r: return incdec(slot.EG, dir); break; + case scsp_name::d1r: return incdec(slot.EG, dir); break; + case scsp_name::eghold: return incdec(slot.EG, dir); break; + case scsp_name::ar: return incdec(slot.EG, dir); break; + case scsp_name::lpslnk: return incdec(slot.EG, dir); break; + case scsp_name::krs: return incdec(slot.EG, dir); break; + case scsp_name::dl: return incdec(slot.EG, dir); break; + case scsp_name::rr: return incdec(slot.EG, dir); break; + + case scsp_name::stwinh: return incdec(slot.FM, dir); break; + case scsp_name::sdir: return incdec(slot.FM, dir); break; + case scsp_name::tl: return incdec(slot.FM, dir); break; + case scsp_name::mdl: return incdec(slot.FM, dir); break; + case scsp_name::mdxsl: return incdec(slot.FM, dir); break; + case scsp_name::mdysl: return incdec(slot.FM, dir); break; + + case scsp_name::oct: return incdec(slot.PITCH, dir); break; + case scsp_name::fns: return incdec(slot.PITCH, dir); break; + + case scsp_name::lfore: return incdec(slot.LFO, dir); break; + case scsp_name::lfof: return incdec(slot.LFO, dir); break; + case scsp_name::plfows: return incdec(slot.LFO, dir); break; + case scsp_name::plfos: return incdec(slot.LFO, dir); break; + case scsp_name::alfows: return incdec(slot.LFO, dir); break; + case scsp_name::alfos: return incdec(slot.LFO, dir); break; + + case scsp_name::isel: return incdec(slot.MIXER, dir); break; + case scsp_name::imxl: return incdec(slot.MIXER, dir); break; + case scsp_name::disdl: return incdec(slot.MIXER, dir); break; + case scsp_name::dipan: return incdec(slot.MIXER, dir); break; + case scsp_name::efsdl: return incdec(slot.MIXER, dir); break; + case scsp_name::efpan: return incdec(slot.MIXER, dir); break; + } + while (1) {} +} + +struct label_value { + uint8_t name[7]; + uint8_t len; + struct { + int8_t y; + int8_t x; + } label; + struct { + int8_t y; + int8_t x; + } value; +}; + +template +constexpr typename std::underlying_type::type u(E e) noexcept +{ + return static_cast::type>(e); +} + +constexpr inline int32_t clz(uint32_t v) +{ + if (v == 0) return 32; + int32_t n = 0; + if ((v & 0xFFFF0000) == 0) { n = n + 16; v = v << 16; } + if ((v & 0xFF000000) == 0) { n = n + 8; v = v << 8; } + if ((v & 0xF0000000) == 0) { n = n + 4; v = v << 4; } + if ((v & 0xC0000000) == 0) { n = n + 2; v = v << 2; } + if ((v & 0x80000000) == 0) { n = n + 1; } + return n; +} + +constexpr inline int32_t hl(const uint32_t v) +{ + // return: number digits in v when represented as a base16 string + int32_t n = 32 - clz(v); + n = (n + 3) & ~0x03; // round up to nearest multiple of 4 + return n >> 2; // divide by 4 +} + +label_value label_value_table[] = { + [u(scsp_name::kyonex)] = { "KX", hl(loop::kyonex::mask), {1, 2}, {2, 2} }, + [u(scsp_name::kyonb)] = { "KB", hl(loop::kyonb::mask), {1, 7}, {2, 7} }, + [u(scsp_name::sbctl)] = { "SBCTL", hl(loop::sbctl::mask), {1, 12}, {2, 12} }, + [u(scsp_name::ssctl)] = { "SSCTL", hl(loop::ssctl::mask), {1, 20}, {2, 20} }, + [u(scsp_name::lpctl)] = { "LPCTL", hl(loop::lpctl::mask), {1, 28}, {2, 28} }, + [u(scsp_name::pcm8b)] = { "8B", hl(loop::pcm8b::mask), {1, 36}, {2, 36} }, + + [u(scsp_name::sa)] = { "SA", hl(sa::sa::mask), {4, 2}, {4, 5} }, + + [u(scsp_name::lsa)] = { "LSA", hl(lsa::lsa::mask), {4, 15}, {4, 19} }, + + [u(scsp_name::lea)] = { "LEA", hl(lea::lea::mask), {4, 28}, {4, 32} }, + + [u(scsp_name::d2r)] = { "D2R", hl(eg::d2r::mask), {6, 2}, {7, 2} }, + [u(scsp_name::d1r)] = { "D1R", hl(eg::d1r::mask), {6, 7}, {7, 7} }, + [u(scsp_name::eghold)] = { "HO", hl(eg::eghold::mask), {6, 12}, {7, 12} }, + [u(scsp_name::ar)] = { "AR", hl(eg::ar::mask), {6, 16}, {7, 16} }, + [u(scsp_name::lpslnk)] = { "LS", hl(eg::lpslnk::mask), {6, 20}, {7, 20} }, + [u(scsp_name::krs)] = { "KRS", hl(eg::krs::mask), {6, 24}, {7, 24} }, + [u(scsp_name::dl)] = { "DL", hl(eg::dl::mask), {6, 29}, {7, 29} }, + [u(scsp_name::rr)] = { "RR", hl(eg::rr::mask), {6, 33}, {7, 33} }, + + [u(scsp_name::stwinh)] = { "STWINH", hl(fm::stwinh::mask), {9, 2}, {10, 2} }, + [u(scsp_name::sdir)] = { "SDIR", hl(fm::sdir::mask), {9, 10}, {10, 10} }, + [u(scsp_name::tl)] = { "TL", hl(fm::tl::mask), {9, 16}, {10, 16} }, + [u(scsp_name::mdl)] = { "MDL", hl(fm::mdl::mask), {9, 20}, {10, 20} }, + [u(scsp_name::mdxsl)] = { "MDXSL", hl(fm::mdxsl::mask), {9, 25}, {10, 25} }, + [u(scsp_name::mdysl)] = { "MDYSL", hl(fm::mdysl::mask), {9, 32}, {10, 32} }, + + [u(scsp_name::oct)] = { "OCT", hl(pitch::oct::mask), {12, 11}, {12, 15} }, + [u(scsp_name::fns)] = { "FNS", hl(pitch::fns::mask), {12, 21}, {12, 25} }, + + [u(scsp_name::lfore)] = { "LFORE", hl(lfo::lfore::mask), {14, 2}, {15, 2} }, + [u(scsp_name::lfof)] = { "LFOF", hl(lfo::lfof::mask), {14, 8}, {15, 8} }, + [u(scsp_name::plfows)] = { "PLFOWS", hl(lfo::plfows::mask), {14, 13}, {15, 13} }, + [u(scsp_name::plfos)] = { "PLFOS", hl(lfo::plfos::mask), {14, 20}, {15, 20} }, + [u(scsp_name::alfows)] = { "ALFOWS", hl(lfo::alfows::mask), {14, 26}, {15, 26} }, + [u(scsp_name::alfos)] = { "ALFOS", hl(lfo::alfos::mask), {14, 33}, {15, 33} }, + + [u(scsp_name::isel)] = { "ISEL", hl(mixer::isel::mask), {17, 2}, {18, 2} }, + [u(scsp_name::imxl)] = { "IMXL", hl(mixer::imxl::mask), {17, 8}, {18, 8} }, + [u(scsp_name::disdl)] = { "DISDL", hl(mixer::disdl::mask), {17, 14}, {18, 14} }, + [u(scsp_name::dipan)] = { "DIPAN", hl(mixer::dipan::mask), {17, 20}, {18, 20} }, + [u(scsp_name::efsdl)] = { "EFSDL", hl(mixer::efsdl::mask), {17, 26}, {18, 26} }, + [u(scsp_name::efpan)] = { "EFPAN", hl(mixer::efpan::mask), {17, 32}, {18, 32} }, +}; + +extern void * _nec_bitmap_start __asm("_binary_res_nec_bitmap_bin_start"); + +constexpr inline uint16_t rgb15(int32_t r, int32_t g, int32_t b) +{ + return ((b & 31) << 10) | ((g & 31) << 5) | ((r & 31) << 0); +} + +void palette_data() +{ + vdp2.cram.u16[1 + 0 ] = rgb15( 0, 0, 0); + vdp2.cram.u16[2 + 0 ] = rgb15(31, 31, 31); + + vdp2.cram.u16[1 + 16] = rgb15(31, 31, 31); + vdp2.cram.u16[2 + 16] = rgb15( 0, 0, 0); + + vdp2.cram.u16[1 + 32] = rgb15(10, 10, 10); + vdp2.cram.u16[2 + 32] = rgb15(31, 31, 31); +} + +namespace pix_fmt_4bpp +{ + constexpr inline uint32_t + bit(uint8_t n, int32_t i) + { + i &= 7; + auto b = (n >> (7 - i)) & 1; + return ((b + 1) << ((7 - i) * 4)); + } + + constexpr inline uint32_t + bits(uint8_t n) + { + return + bit(n, 0) | bit(n, 1) | bit(n, 2) | bit(n, 3) + | bit(n, 4) | bit(n, 5) | bit(n, 6) | bit(n, 7); + } + + static_assert(bits(0b1100'1110) == 0x2211'2221); + static_assert(bits(0b1010'0101) == 0x2121'1212); + static_assert(bits(0b1000'0000) == 0x2111'1111); +} + +void cell_data() +{ + const uint8_t * normal = reinterpret_cast(&_nec_bitmap_start); + + for (int ix = 0; ix <= (0x7f - 0x20); ix++) { + for (int y = 0; y < 8; y++) { + const uint8_t row_n = normal[ix * 8 + y]; + vdp2.vram.u32[ 0 + (ix * 8) + y] = pix_fmt_4bpp::bits(row_n); + } + } +} + +struct count_flop { + s8 count; + u8 flop; + u8 das; + u8 repeat; +}; + +struct input { + count_flop right; + count_flop left; + count_flop down; + count_flop up; + count_flop start; + count_flop a; + count_flop b; + count_flop c; + count_flop r; + count_flop x; + count_flop y; + count_flop z; + count_flop l; +}; + +constexpr int input_arr = 10; +constexpr int input_das = 20; +constexpr int input_debounce = 2; + +static inline void +input_count(count_flop& button, uint32_t input, uint32_t mask) +{ + if ((input & mask) == 0) { + if (button.count < input_debounce) + button.count += 1; + else + button.das += 1; + } else { + if (button.count == 0) { + button.flop = 0; + button.das = 0; + button.repeat = 0; + } + else if (button.count > 0) + button.count -= 1; + } +} + +static inline int32_t +input_flopped(count_flop& button) +{ + if (button.count == input_debounce && button.flop == 0) { + button.flop = 1; + return 1; + } else if (button.flop == 1 && button.das == input_das && button.repeat == 0) { + button.repeat = 1; + button.das = 0; + return 2; + } else if (button.repeat == 1 && (button.das == input_arr)) { + button.das = 0; + return 2; + } else { + return 0; + } +} + +struct cursor { + int8_t x; + int8_t y; + + void left() { do { x = (x - 1) & 7; } while (!grid[y][x]); } + void right() { do { x = (x + 1) & 7; } while (!grid[y][x]); } + void up() + { + y -= 1; + if (y < 0) y = 6; + while (!grid[y][x]) { x--; }; + } + void down() + { + y += 1; + if (y > 6) y = 0; + while (!grid[y][x]) { x--; }; + } +}; + +struct state { + uint8_t slot_ix; + struct input input; + struct cursor cursor; +}; + +static struct state state = { 0 }; + +void digital_callback(uint8_t fsm_state, uint8_t data) +{ + switch (fsm_state) { + case intback::DATA1: + input_count(state.input.right, data, DIGITAL__1__RIGHT); + input_count(state.input.left, data, DIGITAL__1__LEFT); + input_count(state.input.down, data, DIGITAL__1__DOWN); + input_count(state.input.up, data, DIGITAL__1__UP); + input_count(state.input.start, data, DIGITAL__1__START); + input_count(state.input.a, data, DIGITAL__1__A); + input_count(state.input.c, data, DIGITAL__1__C); + input_count(state.input.b, data, DIGITAL__1__B); + break; + case intback::DATA2: + input_count(state.input.r, data, DIGITAL__2__R); + input_count(state.input.x, data, DIGITAL__2__X); + input_count(state.input.y, data, DIGITAL__2__Y); + input_count(state.input.z, data, DIGITAL__2__Z); + input_count(state.input.l, data, DIGITAL__2__L); + break; + default: break; + } +} + +extern "C" +void smpc_int(void) __attribute__ ((interrupt_handler)); +void smpc_int(void) +{ + scu.reg.IST &= ~(IST__SMPC); + scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN); + + intback::fsm(digital_callback, nullptr); +} + +constexpr int32_t plane_a = 2; +constexpr inline int32_t plane_offset(int32_t n) { return n * 0x2000; } + +constexpr int32_t page_size = 64 * 64 * 2; // N0PNB__1WORD (16-bit) +constexpr int32_t plane_size = page_size * 1; + +constexpr int32_t cell_size = (8 * 8) / 2; // N0CHCN__16_COLOR (4-bit) +constexpr int32_t character_size = cell_size * (1 * 1); // N0CHSZ__1x1_CELL +constexpr int32_t page_width = 64; + +static int plane_ix = 0; + +inline void +set_char(int32_t x, int32_t y, uint8_t palette, uint8_t c) +{ + const auto ix = (plane_offset(plane_a + plane_ix) / 2) + (y * page_width) + x; + vdp2.vram.u16[ix] = + PATTERN_NAME_TABLE_1WORD__PALETTE(palette) + | PATTERN_NAME_TABLE_1WORD__CHARACTER((c - 0x20)); +} + +void render() +{ + // 012345678901234 + uint8_t label[] = "scsp.reg.slot[ ]"; + string::hex(&label[14], 2, state.slot_ix); + for (uint32_t i = 0; label[i] != 0; i++) { + set_char(0 + i, 1, 0, label[i]); + } + + constexpr int32_t y_offset = 3; + + for (uint32_t f_ix = u(scsp_name::FIRST); f_ix <= u(scsp_name::LAST); f_ix++) { + label_value& lv = label_value_table[f_ix]; + for (uint32_t i = 0; lv.name[i] != 0; i++) { + set_char(lv.label.x + i, lv.label.y + y_offset, 0, lv.name[i]); + } + + uint8_t buf[lv.len]; + scsp_name name = static_cast(f_ix); + uint32_t value = get_reg(scsp.reg.slot[state.slot_ix], name); + string::hex(buf, lv.len, value); + bool selected = !(!grid[state.cursor.y][state.cursor.x]) && name == *grid[state.cursor.y][state.cursor.x]; + uint32_t palette = selected ? 1 : 0; + for (uint32_t i = 0; i < lv.len; i++) { + set_char(lv.value.x + i, lv.value.y + y_offset, palette, buf[i]); + } + } +} + +namespace event { + inline bool prev_slot() { return input_flopped(state.input.l) == 1; } + inline bool next_slot() { return input_flopped(state.input.r) == 1; } + inline bool cursor_left() { return input_flopped(state.input.left ) >= 1; } + inline bool cursor_right() { return input_flopped(state.input.right) >= 1; } + inline bool cursor_up() { return input_flopped(state.input.up ) >= 1; } + inline bool cursor_down() { return input_flopped(state.input.down ) >= 1; } + inline bool cursor_dec() { return input_flopped(state.input.x ) >= 1; } + inline bool cursor_inc() { return input_flopped(state.input.y ) >= 1; } +} + +void update() +{ + if (event::prev_slot()) + state.slot_ix = (state.slot_ix - 1) & 31; + if (event::next_slot()) + state.slot_ix = (state.slot_ix + 1) & 31; + if (event::cursor_left()) state.cursor.left(); + if (event::cursor_right()) state.cursor.right(); + if (event::cursor_up()) state.cursor.up(); + if (event::cursor_down()) state.cursor.down(); + if (grid[state.cursor.y][state.cursor.x]) { + if (event::cursor_inc()) incdec_reg(scsp.reg.slot[state.slot_ix], *grid[state.cursor.y][state.cursor.x], +1); + if (event::cursor_dec()) incdec_reg(scsp.reg.slot[state.slot_ix], *grid[state.cursor.y][state.cursor.x], -1); + } +} + +extern "C" +void v_blank_in_int(void) __attribute__ ((interrupt_handler)); +void v_blank_in_int() +{ + scu.reg.IST &= ~(IST__V_BLANK_IN); + scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN); + + // flip planes; + vdp2.reg.MPABN0 = MPABN0__N0MPB(0) | MPABN0__N0MPA(plane_a + plane_ix); + plane_ix = !plane_ix; + + // wait at least 300us, as specified in the SMPC manual. + // It appears reading FRC.H is mandatory and *must* occur before FRC.L on real + // hardware. + while ((sh2.reg.FTCSR & FTCSR__OVF) == 0 && sh2.reg.FRC.H == 0 && sh2.reg.FRC.L < 63); + + if ((vdp2.reg.TVSTAT & TVSTAT__VBLANK) != 0) { + // on real hardware, SF contains uninitialized garbage bits other than the + // lsb. + while ((smpc.reg.SF & 1) != 0); + + smpc.reg.SF = 0; + + smpc.reg.IREG[0].val = INTBACK__IREG0__STATUS_DISABLE; + smpc.reg.IREG[1].val = ( INTBACK__IREG1__PERIPHERAL_DATA_ENABLE + | INTBACK__IREG1__PORT2_15BYTE + | INTBACK__IREG1__PORT1_15BYTE + ); + smpc.reg.IREG[2].val = INTBACK__IREG2__MAGIC; + + smpc.reg.COMREG = COMREG__INTBACK; + } + + update(); + render(); +} + +void init_slots() +{ + while ((smpc.reg.SF & 1) != 0); + smpc.reg.SF = 1; + smpc.reg.COMREG = COMREG__SNDON; + while (smpc.reg.OREG[31].val != 0b00000110); + + for (long i = 0; i < 807; i++) { asm volatile ("nop"); } // wait for (way) more than 30µs + + scsp.reg.ctrl.MIXER = MIXER__MEM4MB | MIXER__MVOL(0); + + const uint32_t * buf = reinterpret_cast(&_sine_start); + const uint32_t size = reinterpret_cast(&_sine_size); + copy(&scsp.ram.u32[0], buf, size); + + for (int i = 0; i < 32; i++) { + scsp_slot& slot = scsp.reg.slot[i]; + // start address (bytes) + slot.SA = SA__KYONB | SA__LPCTL__NORMAL | SA__SA(0); // kx kb sbctl[1:0] ssctl[1:0] lpctl[1:0] 8b sa[19:0] + slot.LSA = 0; // loop start address (samples) + slot.LEA = 100; // loop end address (samples) + slot.EG = EG__EGHOLD; // d2r d1r ho ar krs dl rr + slot.FM = 0; // stwinh sdir tl mdl mdxsl mdysl + slot.PITCH = PITCH__OCT(0) | PITCH__FNS(0); // oct fns + slot.LFO = 0; // lfof plfows + slot.MIXER = MIXER__DISDL(0b101); // disdl dipan efsdl efpan + } + + scsp.reg.ctrl.MIXER = MIXER__MEM4MB | MIXER__MVOL(0xf); +} + +void main() +{ + init_slots(); + + v_blank_in(); + + // DISP: Please make sure to change this bit from 0 to 1 during V blank. + vdp2.reg.TVMD = ( TVMD__DISP | TVMD__LSMD__NON_INTERLACE + | TVMD__VRESO__240 | TVMD__HRESO__NORMAL_320); + + /* set the color mode to 5bits per channel, 1024 colors */ + vdp2.reg.RAMCTL = RAMCTL__CRMD__RGB_5BIT_1024; + + /* enable display of NBG0 */ + vdp2.reg.BGON = BGON__N0ON; + + /* set character format for NBG0 to palettized 16 color + set enable "cell format" for NBG0 + set character size for NBG0 to 1x1 cell */ + vdp2.reg.CHCTLA = CHCTLA__N0CHCN__16_COLOR + | CHCTLA__N0BMEN__CELL_FORMAT + | CHCTLA__N0CHSZ__1x1_CELL; + /* "Note: In color RAM modes 0 and 2, 2048-color becomes 1024-color" */ + + /* use 1-word (16-bit) pattern names */ + vdp2.reg.PNCN0 = PNCN0__N0PNB__1WORD; + + /* plane size */ + vdp2.reg.PLSZ = PLSZ__N0PLSZ__1x1; + + /* map plane offset + 1-word: value of bit 6-0 * 0x2000 + 2-word: value of bit 5-0 * 0x4000 + */ + vdp2.reg.MPOFN = MPOFN__N0MP(0); // bits 8~6 + vdp2.reg.MPABN0 = MPABN0__N0MPB(0) | MPABN0__N0MPA(plane_a); // bits 5~0 + vdp2.reg.MPCDN0 = MPABN0__N0MPD(0) | MPABN0__N0MPC(0); // bits 5~0 + + // zeroize character/cell data from 0 up to plane_a_offset + fill(&vdp2.vram.u32[(0 / 4)], 0, plane_offset(plane_a)); + + // zeroize plane_a; `0` is the ascii 0x20 ("space") which doubles as + // "transparency" character. + fill(&vdp2.vram.u32[(plane_offset(plane_a) / 4)], 0, plane_size * 2); + + palette_data(); + cell_data(); + + // free-running timer + sh2.reg.TCR = TCR__CKS__INTERNAL_DIV128; + sh2.reg.FTCSR = 0; + + // initialize smpc + smpc.reg.DDR1 = 0; // INPUT + smpc.reg.DDR2 = 0; // INPUT + smpc.reg.IOSEL = 0; // SMPC control + smpc.reg.EXLE = 0; // + + sh2_vec[SCU_VEC__SMPC] = (u32)(&smpc_int); + sh2_vec[SCU_VEC__V_BLANK_IN] = (u32)(&v_blank_in_int); + + scu.reg.IST = 0; + scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN); +} diff --git a/scsp/sine-44100-s16be-1ch.pcm b/scsp/sine-44100-s16be-1ch.pcm deleted file mode 100644 index 6d40564..0000000 Binary files a/scsp/sine-44100-s16be-1ch.pcm and /dev/null differ diff --git a/scsp/slot.cpp b/scsp/slot.cpp index 91d9fc4..900a477 100644 --- a/scsp/slot.cpp +++ b/scsp/slot.cpp @@ -4,7 +4,7 @@ #include "../common/copy.hpp" -extern void * _sine_start __asm("_binary_scsp_sine_44100_s16be_1ch_pcm_start"); +extern void * _sine_start __asm("_binary_scsp_sine_44100_s16be_1ch_1sec_pcm_start"); void main() { @@ -26,8 +26,7 @@ void main() slot.LSA = 0; // loop start address (samples) slot.LEA = 44100; // loop end address (samples) slot.EG = EG__EGHOLD; // d2r d1r ho ar krs dl rr - slot.VOLUME = 0; // stwinh sdir tl - slot.FM = 0; // mdl mdxsl mdysl + slot.FM = 0; // stwinh sdir tl mdl mdxsl mdysl slot.PITCH = PITCH__OCT(0) | PITCH__FNS(0); // oct fns slot.LFO = 0; // lfof plfows slot.MIXER = MIXER__DISDL(0b101); // disdl dipan efsdl efpan diff --git a/scsp/sound_cpu__interrupt.cpp b/scsp/sound_cpu__interrupt.cpp index eeaf710..33c8f86 100644 --- a/scsp/sound_cpu__interrupt.cpp +++ b/scsp/sound_cpu__interrupt.cpp @@ -12,6 +12,7 @@ #include "../common/draw_font.hpp" #include "../common/palette.hpp" #include "../common/vdp2_func.hpp" +#include "../common/string.hpp" extern void * _m68k_start __asm("_binary_m68k_interrupt_bin_start"); extern void * _m68k_size __asm("_binary_m68k_interrupt_bin_size"); @@ -24,27 +25,6 @@ struct draw_state { static struct draw_state draw_state; -uint32_t print_hex(uint8_t * c, uint32_t len, uint32_t n) -{ - uint32_t ret = 0; - - while (len > 0) { - uint32_t nib = n & 0xf; - n = n >> 4; - - if (nib > 9) { - nib += (97 - 10); - } else { - nib += (48 - 0); - } - - c[--len] = nib; - - ret++; - } - return ret; -} - int32_t draw_string(const uint8_t * string, const uint32_t length, const int32_t x, @@ -171,7 +151,7 @@ inline void draw_label(const uint8_t(&label)[size], int32_t advance, int32_t & r advance = (advance * 8) << 6; advance += draw_string(label, (sizeof (label)) - 1, advance, row); uint8_t v[n]; - print_hex(v, (sizeof (v)), value); + string::hex(v, (sizeof (v)), value); draw_string(v, (sizeof (v)), advance, row); row++; diff --git a/smpc/input_keyboard.cpp b/smpc/input_keyboard.cpp index 0ca61d7..8f94d27 100644 --- a/smpc/input_keyboard.cpp +++ b/smpc/input_keyboard.cpp @@ -10,6 +10,7 @@ #include "../common/draw_font.hpp" #include "../common/palette.hpp" #include "../common/vdp2_func.hpp" +#include "../common/string.hpp" /* begin font */ @@ -149,27 +150,6 @@ static xy foo[2] = { {200, 100} }; -uint32_t print_hex(char16_t * c, uint32_t len, uint32_t n) -{ - uint32_t ret = 0; - - while (len > 0) { - uint32_t nib = n & 0xf; - n = n >> 4; - - if (nib > 9) { - nib += (97 - 10); - } else { - nib += (48 - 0); - } - - c[--len] = nib; - - ret++; - } - return ret; -} - static struct draw_font::state font_state; static uint32_t global_cmd_ix = 0; @@ -240,7 +220,7 @@ void smpc_int(void) { static int32_t y = 50 << 6; if (kbd_bits & 0b1000) { - print_hex(str_num, 2, keysym); + string::hex(str_num, 2, keysym); enum keysym k = scancode_to_keysym(keysym); int32_t c = keysym_to_char(k, false); @@ -268,7 +248,7 @@ void smpc_int(void) { draw_font::horizontal_string(font_state, cmd_ix, // modified &str_num[0], - 2, + 2, qx, qy); } diff --git a/wordle/main_saturn.cpp b/wordle/main_saturn.cpp index 813a6f0..5952c7b 100644 --- a/wordle/main_saturn.cpp +++ b/wordle/main_saturn.cpp @@ -42,8 +42,10 @@ uint32_t xorshift32(struct xorshift32_state *state) static xorshift32_state random_state = { 0x12345678 }; static uint32_t frame_count = 0; -void keyboard_callback(const enum keysym k, uint8_t kbd_bits) +void keyboard_callback(uint8_t kbd_bits, uint8_t scancode) { + enum keysym k = scancode_to_keysym(scancode); + if (!KEYBOARD__MAKE(kbd_bits)) return; @@ -71,7 +73,7 @@ void smpc_int(void) scu.reg.IST &= ~(IST__SMPC); scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN); - intback::keyboard_fsm(keyboard_callback); + intback::fsm(nullptr, keyboard_callback); } // rendering @@ -140,9 +142,9 @@ void v_blank_in_int() smpc.reg.IREG[0].val = INTBACK__IREG0__STATUS_DISABLE; smpc.reg.IREG[1].val = ( INTBACK__IREG1__PERIPHERAL_DATA_ENABLE - | INTBACK__IREG1__PORT2_15BYTE - | INTBACK__IREG1__PORT1_15BYTE - ); + | INTBACK__IREG1__PORT2_15BYTE + | INTBACK__IREG1__PORT1_15BYTE + ); smpc.reg.IREG[2].val = INTBACK__IREG2__MAGIC; smpc.reg.COMREG = COMREG__INTBACK;