Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: GlasgowEmbedded/glasgow
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: a1cc03333158
Choose a base ref
...
head repository: GlasgowEmbedded/glasgow
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 67cffef62056
Choose a head ref
  • 4 commits
  • 3 files changed
  • 1 contributor

Commits on Sep 28, 2019

  1. Copy the full SHA
    7f2d599 View commit details

Commits on Sep 29, 2019

  1. Copy the full SHA
    6cbb617 View commit details
  2. Copy the full SHA
    88dfe4e View commit details
  3. applet.audio.yamaha_opl: do unsigned→signed conversion in gateware.

    Pretty much everything (WebAudio, samplerate, WAV, ...) uses signed
    samples instead of unsigned, so it's silly that we have to use numpy
    just to shift everything by 0x8000.
    whitequark committed Sep 29, 2019
    Copy the full SHA
    67cffef View commit details
2 changes: 1 addition & 1 deletion software/glasgow/access/direct/demultiplexer.py
Original file line number Diff line number Diff line change
@@ -155,7 +155,7 @@ def __init__(self, device, applet, mux_interface,
break

interfaces = list(config.iterInterfaces())
assert self._pipe_num <= len(interfaces)
assert self._pipe_num < len(interfaces)
interface = interfaces[self._pipe_num]

settings = list(interface.iterSettings())
52 changes: 33 additions & 19 deletions software/glasgow/applet/audio/yamaha_opl/__init__.py
Original file line number Diff line number Diff line change
@@ -310,6 +310,9 @@ def __init__(self, pads, in_fifo, out_fifo, format,

# Audio

data_r = Signal(16)
self.sync += If(dac_bus.stb_sy, data_r.eq(Cat(data_r[1:], dac_bus.mo)))

xfer_o = Signal(16)
if format == "F-3Z-9M-1S-3E":
xfer_i = Record([("z", 3), ("m", 9), ("s", 1), ("e", 3),])
@@ -322,8 +325,9 @@ def __init__(self, pads, in_fifo, out_fifo, format,
else:
assert False

data_r = Signal(16)
self.sync += If(dac_bus.stb_sy, data_r.eq(Cat(data_r[1:], dac_bus.mo)))
xfrm_o = Signal(16)
xfrm_i = Signal(16)
self.comb += xfrm_o.eq(xfrm_i + 0x8000)

self.submodules.data_fsm = FSM()
self.data_fsm.act("WAIT-SH",
@@ -334,10 +338,14 @@ def __init__(self, pads, in_fifo, out_fifo, format,
)
self.data_fsm.act("SAMPLE",
NextValue(xfer_i.raw_bits(), data_r),
NextState("TRANSFORM")
)
self.data_fsm.act("TRANSFORM",
NextValue(xfrm_i, xfer_o),
NextState("SEND-L-BYTE")
)
self.data_fsm.act("SEND-L-BYTE",
in_fifo.din.eq(xfer_o[0:8]),
in_fifo.din.eq(xfrm_o[0:8]),
If(in_fifo.writable,
in_fifo.we.eq(1),
NextState("SEND-H-BYTE")
@@ -346,7 +354,7 @@ def __init__(self, pads, in_fifo, out_fifo, format,
)
)
self.data_fsm.act("SEND-H-BYTE",
in_fifo.din.eq(xfer_o[8:16]),
in_fifo.din.eq(xfrm_o[8:16]),
If(in_fifo.writable,
in_fifo.we.eq(1),
NextState("WAIT-SH")
@@ -641,11 +649,13 @@ async def wait_seconds(self, delay):


class YamahaOPxWebInterface:
def __init__(self, logger, opx_iface):
def __init__(self, logger, opx_iface, set_voltage):
self._logger = logger
self._opx_iface = opx_iface
self._lock = asyncio.Lock()

self._set_voltage = set_voltage

async def serve_index(self, request):
with open(os.path.join(os.path.dirname(__file__), "index.html")) as f:
index_html = f.read()
@@ -663,19 +673,15 @@ def _make_resampler(self, actual, preferred):
async def resample(input_queue, output_queue):
while True:
input_data = await input_queue.get()
input_array = numpy.frombuffer(input_data, dtype="<u2")
output_array = (input_array - 32768).astype(numpy.int16)
if input_data:
await output_queue.put(output_array.tobytes())
await output_queue.put(input_data)
if not input_data:
await output_queue.put(b"")
break
return resample, actual

resampler = samplerate.Resampler()
def resample_worker(input_data, end):
input_array = numpy.frombuffer(input_data, dtype="<u2")
input_array = (input_array.astype(numpy.float32) - 32768) / 32768
input_array = numpy.frombuffer(input_data, dtype="<i2")
input_array = input_array.astype(numpy.float32) / 32768
output_array = resampler.process(
input_array, ratio=preferred / actual, end_of_input=end)
output_array = (output_array * 32768).astype(numpy.int16)
@@ -742,8 +748,11 @@ async def serve_vgm(self, request):
digest, input_rate, preferred_rate, output_rate)

async with self._lock:
self._logger.info("web: %s: start streaming",
digest)
voltage = float(request.headers["X-Voltage"])
self._logger.info("setting voltage to %.2f V", voltage)
await self._set_voltage(voltage)

self._logger.info("web: %s: start streaming", digest)

await self._opx_iface.reset()

@@ -810,7 +819,7 @@ async def serve_vgm(self, request):

return response

async def serve(self):
async def serve(self, endpoint):
app = web.Application()
app.add_routes([
web.get ("/", self.serve_index),
@@ -826,7 +835,7 @@ async def serve(self):
runner = web.AppRunner(app,
access_log_format='%a(%{X-Forwarded-For}i) "%r" %s "%{Referer}i" "%{User-Agent}i"')
await runner.setup()
site = web.TCPSite(runner, "localhost", 8080)
site = web.TCPSite(runner, *endpoint.split(":", 2))
await site.start()
await asyncio.Future()

@@ -844,7 +853,7 @@ class AudioYamahaOPLApplet(GlasgowApplet, name="audio-yamaha-opl"):
The ~CS input should always be grounded, since there is only one chip on the bus in the first
place.
The digital output is losslessly converted to 16-bit unsigned PCM samples. (The Yamaha DACs
The digital output is losslessly converted to 16-bit signed PCM samples. (The Yamaha DACs
only have 16 bit of dynamic range, and there is a direct mapping between the on-wire format
and ordinary 16-bit PCM.)
@@ -939,6 +948,9 @@ def add_interact_arguments(cls, parser):

p_web = p_operation.add_parser(
"web", help="expose Yamaha hardware via a web interface")
p_web.add_argument(
"endpoint", metavar="ENDPOINT", type=str, default="localhost:8080",
help="listen for requests on ENDPOINT (default: %(default)s)")

async def interact(self, device, args, opx_iface):
if args.operation == "convert":
@@ -975,8 +987,10 @@ async def write_pcm(input_queue):
await fut

if args.operation == "web":
web_iface = YamahaOPxWebInterface(self.logger, opx_iface)
await web_iface.serve()
async def set_voltage(voltage):
await device.set_voltage(args.port_spec, voltage)
web_iface = YamahaOPxWebInterface(self.logger, opx_iface, set_voltage=set_voltage)
await web_iface.serve(args.endpoint)

# -------------------------------------------------------------------------------------------------

3 changes: 3 additions & 0 deletions software/glasgow/applet/audio/yamaha_opl/index.html
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ <h1>Yamaha OP* Web Gateway</h1>
<p>This webpage lets you submit commands to a real Yamaha synthesizer and listen to the output. The synthesizer is a <b>shared resource</b> (and isn't very fast, although it does not have to run in real time), and everyone who submits a file is in the same queue, so you might have to wait until it's your turn.</p>
<p>Connected synthesizer: {{chip}}.</p>
<p>Playback support for: {{compat}}.</p>
<p><b>Glitching</b>: undervolt to <input type="number" min="1.65" max="5.50" value="5.00" step="0.01" id="voltage"></p>
<p>Play a <a href="https://vgmrips.net/packs/chips">VGM/VGZ file</a>: <input type="file" id="file" accept=".vgm,.vgz"> <input type="button" id="play" value="Play"> <input type="button" id="replay" value="Replay" disabled> <input type="checkbox" id="loop"> <label for="loop">Loop</label></p>
<p>Export PCM: <input type="button" id="exportFull" value="Export full"> <input type="button" id="exportLoop" value="Export loop"></p>
<p>Status: <span id="chipStatus">no chip</span>, <span id="netStatus">idle</span>, <span id="playStatus">stopped</span>.</p>
@@ -243,6 +244,7 @@ <h1>Yamaha OP* Web Gateway</h1>
document.body.removeChild(a);
}

var voltageSpinner = document.getElementById("voltage");
var playButton = document.getElementById("play");
var replayButton = document.getElementById("replay");
var exportFullButton = document.getElementById("exportFull");
@@ -294,6 +296,7 @@ <h1>Yamaha OP* Web Gateway</h1>
var xhr = new XMLHttpRequest();
xhr.open("POST", "vgm", /*async=*/true);
xhr.setRequestHeader("X-Preferred-Sample-Rate", player.preferredSampleRate());
xhr.setRequestHeader("X-Voltage", voltage.value);
var sampleRate = 0;
var seenBytes = 0;
var totalSamples = 0;