Skip to content

Instantly share code, notes, and snippets.

@whitequark
Last active January 22, 2021 02:22
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save whitequark/7cd3766b4269837a961a1ac64cfabdcd to your computer and use it in GitHub Desktop.
Save whitequark/7cd3766b4269837a961a1ac64cfabdcd to your computer and use it in GitHub Desktop.
JT51 + CXXRTL + VGM = <3 <3
// 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;
}
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