Last active
January 22, 2021 02:22
-
-
Save whitequark/7cd3766b4269837a961a1ac64cfabdcd to your computer and use it in GitHub Desktop.
JT51 + CXXRTL + VGM = <3 <3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Step 1: Obtain Yosys from git. | |
// Step 2: Download jt51 from https://github.com/jotego/jt51. | |
// Step 3: Build as follows: | |
// $ yosys jt51/hdl/*.v -b 'cxxrtl -header' -o jt51_core.cc | |
// $ CFLAGS="-fbracket-depth=2048 -I$(yosys-config --datdir/include)" | |
// $ clang++ -O3 $CFLAGS jt51_core.cc jt51_player.cc -o jt51_play | |
// Step 4: Convert as follows: | |
// $ python3 vgm2tsv.py music.vgm music.tsv | |
// Step 5: Play as follows (assuming YM2151 clocked at 4 MHz): | |
// $ ./jt51_play music.tsv music.wav 4000000 | |
// Step 6: Wait. There's going to be a lot of waiting. | |
// Step 7: Enjoy! | |
#include <fstream> | |
#include <iostream> | |
#include <backends/cxxrtl/cxxrtl_vcd.h> | |
#include "jt51_core.h" | |
int main(int argc, char **argv) { | |
if (argc != 4) { | |
std::cerr << "usage: " << argv[0] << " <TSV-FILE> <WAV-FILE> <CLOCK-RATE>" << std::endl; | |
return 1; | |
} | |
std::ifstream tsv_file(argv[1]); | |
std::ofstream wav_file(argv[2], std::ofstream::binary); | |
unsigned clock_rate = std::stol(argv[3]); | |
unsigned sample_clocks = clock_rate / 48000; | |
std::vector<std::tuple<unsigned, uint8_t, uint8_t>> music; | |
while (!tsv_file.eof()) { | |
unsigned delay, address, data; | |
tsv_file >> delay >> address >> data; | |
music.push_back(std::make_tuple(delay, (uint8_t)address, (uint8_t)data)); | |
} | |
wav_file.write("RIFF\xff\xff\xff\xff" | |
"WAVE", 12); | |
wav_file.write("fmt \x10\x00\x00\x00" | |
"\x01\x00\x02\x00\x80\xbb\x00\x00\x00" | |
"\xee\x02\x00\x04\x00\x10\x00", 24); | |
wav_file.write("data\xff\xff\xff\xff", 8); | |
cxxrtl_design::p_jt51 top; | |
top.p_rst .set(1u); | |
top.p_clk .set(0u); | |
top.p_cen .set(1u); | |
top.p_cen__p1.set(1u); | |
top.p_din .set(0u); | |
top.p_a0 .set(0u); | |
top.p_cs__n .set(0u); | |
top.p_wr__n .set(1u); | |
top.step(); | |
#ifdef VCD | |
std::ofstream vcd_file("jt51.vcd"); | |
cxxrtl::vcd_writer vcd; | |
vcd.timescale(1, "ns"); | |
cxxrtl::debug_items debug; | |
top.debug_info(debug); | |
auto traces = { | |
"rst", "clk", "cen", "cen_p1", | |
"cs_n", "wr_n", "a0", "din", | |
"busy", "sample", "xleft", "xright", | |
}; | |
for (auto name : traces) | |
vcd.add(name, debug.at(name)); | |
vcd.sample(0); | |
#endif | |
unsigned clocks = 0; | |
unsigned timer = 2000; | |
size_t instrn = 0; | |
int state = 0; | |
while (instrn < music.size()) { | |
if (timer != 0) | |
timer--; | |
else if (clocks % 32 == 0) { | |
switch (state) { | |
case 0: | |
top.p_rst .set(0u); | |
state = 1; | |
break; | |
case 1: | |
top.p_a0 .set(0u); | |
top.p_din .set(std::get<1>(music[instrn])); | |
top.p_wr__n.set(0u); | |
state++; | |
break; | |
case 2: | |
top.p_wr__n.set(1u); | |
state++; | |
break; | |
case 3: | |
top.p_a0 .set(1u); | |
top.p_din .set(std::get<2>(music[instrn])); | |
top.p_wr__n.set(0u); | |
state++; | |
break; | |
case 4: | |
top.p_wr__n.set(1u); | |
timer = ((uint64_t)std::get<0>(music[++instrn])) * clock_rate / 1000000; | |
state = 1; | |
break; | |
} | |
} | |
if (clocks % sample_clocks == 0) { | |
uint16_t left = top.p_xleft .get<uint16_t>(); | |
uint16_t right = top.p_xright.get<uint16_t>(); | |
wav_file.write((char*)&left, sizeof(left)); | |
wav_file.write((char*)&right, sizeof(right)); | |
} | |
if (clocks % clock_rate == 0) | |
std::cerr << "."; // one second | |
clocks++; | |
top.p_cen__p1.set<bool>(!top.p_cen__p1.get<bool>()); | |
top.p_clk.set(0u); | |
top.step(); | |
#ifdef VCD | |
vcd.sample(250 * clocks); | |
#endif | |
top.p_clk.set(1u); | |
top.step(); | |
#ifdef VCD | |
vcd.sample(250 * clocks + 125); | |
#endif | |
#ifdef VCD | |
vcd_file << vcd.buffer; | |
vcd.buffer.clear(); | |
#endif | |
} | |
uint32_t data_size = wav_file.tellp(); | |
data_size -= 8; // RIFF header | |
wav_file.seekp(4); | |
wav_file.write((char*)&data_size, sizeof(data_size)); | |
data_size -= 36; // WAVE header | |
wav_file.seekp(40); | |
wav_file.write((char*)&data_size, sizeof(data_size)); | |
std::cerr << std::endl; | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import gzip | |
import asyncio | |
from glasgow.protocol import vgm | |
class TSVStreamPlayer(vgm.VGMStreamPlayer): | |
def __init__(self, file): | |
self.file = file | |
async def ym2151_write(self, address, data): | |
self.file.write(f"0\t{address}\t{data}\n") | |
async def wait_seconds(self, duration): | |
self.file.write(f"{duration*1e6:.0f}\t0\t0\n") | |
reader = vgm.VGMStreamReader(gzip.GzipFile(sys.argv[1], "rb")) | |
player = TSVStreamPlayer(open(sys.argv[2], "wt")) | |
asyncio.run(reader.parse_data(player)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment