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: 465de817212b
Choose a base ref
...
head repository: GlasgowEmbedded/glasgow
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 5eed704e74f2
Choose a head ref
  • 2 commits
  • 4 files changed
  • 1 contributor

Commits on Sep 30, 2019

  1. Copy the full SHA
    7d1eb4b View commit details
  2. applet.audio.yamaha_opx: add streaming timeout.

    Otherwise people would glitch it and wait for something to happen
    while holding the synthesizer lock.
    whitequark committed Sep 30, 2019
    Copy the full SHA
    5eed704 View commit details
2 changes: 1 addition & 1 deletion software/glasgow/applet/all.py
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@
from .display.pdi import DisplayPDIApplet

from .audio.dac import AudioDACApplet
from .audio.yamaha_opl import AudioYamahaOPLApplet
from .audio.yamaha_opx import AudioYamahaOPxApplet

from .video.rgb_input import VideoRGBInputApplet
from .video.vga_output import VGAOutputApplet
Original file line number Diff line number Diff line change
@@ -629,6 +629,45 @@ async def _check_enable_features(self, address, data):
await super()._check_enable_features(address, data)


class YamahaOPMInterface(YamahaOPxInterface):
chips = ["YM2151/OPM"]

def get_vgm_clock_rate(self, vgm_reader):
return vgm_reader.ym2151_clk, 1

max_master_hz = 4.0e6 # 2.0/3.58/4.0
sample_decoder = YamahaOPLSampleDecoder
channel_count = 2

address_clocks = 12
data_clocks = 68
sample_clocks = 64

_registers = [
0x08, 0x0F, 0x10, 0x11, 0x12, 0x14, 0x18, 0x19, 0x1B,
*range(0x20, 0x28), *range(0x28, 0x30), *range(0x30, 0x38), *range(0x38, 0x40),
*range(0x40, 0x60), *range(0x60, 0x80), *range(0x80, 0xA0), *range(0xA0, 0xC0),
*range(0xC0, 0xE0), *range(0xE0, 0x100)
]

async def _check_enable_features(self, address, data):
if address == 0x01 and data in (0x00, 0x02):
pass # LFO reset
else:
await super()._check_enable_features(address, data)

async def reset(self):
await super().reset()
await self.write_register(0x01, 0x02) # LFO reset
await self.write_register(0x01, 0x00)
await self.write_register(0x14, 0x30) # flag reset
await self.write_register(0x14, 0x00)
for address in range(0x60, 0x80):
await self.write_register(address, 0x7F) # lowest TL
for address in range(0x20, 0x28):
await self.write_register(address, 0xC0) # RL enable


class YamahaVGMStreamPlayer(VGMStreamPlayer):
def __init__(self, reader, opx_iface, clock_rate):
self._reader = reader
@@ -640,6 +679,7 @@ def __init__(self, reader, opx_iface, clock_rate):
async def play(self):
try:
await self._opx_iface.enable()
await self.wait_seconds(1.0)
await self._reader.parse_data(self)
finally:
# Various parts of our stack are not completely synchronized to each other, resulting
@@ -659,6 +699,9 @@ async def record(self, queue, chunk_count=16384):

await queue.put(b"")

async def ym2151_write(self, address, data):
await self._opx_iface.write_register(address, data)

async def ym3526_write(self, address, data):
await self._opx_iface.write_register(address, data)

@@ -742,7 +785,7 @@ async def serve_vgm(self, request):
async with self._lock:
try:
voltage = float(headers["Voltage"])
self._logger.info("setting voltage to %.2f V", voltage)
self._logger.info("web: %s: setting voltage to %.2f V", digest, voltage)
await self._set_voltage(voltage)

except Exception as error:
@@ -780,7 +823,7 @@ async def serve_vgm(self, request):
if play_fut.done() and play_fut.exception():
break

samples = await sample_queue.get()
samples = await asyncio.wait_for(sample_queue.get(), timeout=1.0)
if not samples:
break
await sock.send_bytes(samples)
@@ -792,6 +835,15 @@ async def serve_vgm(self, request):
digest)
await sock.close()

except asyncio.TimeoutError:
self._logger.info("web: %s: timeout streaming",
digest)
await sock.close(code=1002, message="Streaming timeout (glitched too hard?)")

for fut in [play_fut, record_fut]:
if not fut.done():
fut.cancel()

except asyncio.CancelledError:
self._logger.info("web: %s: cancel streaming",
digest)
@@ -824,15 +876,16 @@ async def serve(self, endpoint):
await asyncio.Future()


class AudioYamahaOPLApplet(GlasgowApplet, name="audio-yamaha-opl"):
class AudioYamahaOPxApplet(GlasgowApplet, name="audio-yamaha-opx"):
logger = logging.getLogger(__name__)
help = "drive and record Yamaha OPL* FM synthesizers"
help = "drive and record Yamaha OP* FM synthesizers"
description = """
Send commands and record digital output from Yamaha OPL* series FM synthesizers. The supported
chips are:
Send commands and record digital output from Yamaha OP* series FM synthesizers.
The supported chips are:
* YM3526 (OPL)
* YM3812 (OPL2)
* YMF262 (OPL3)
* YM2151 (OPM)
The ~CS input should always be grounded, since there is only one chip on the bus in the first
place.
@@ -848,24 +901,25 @@ class AudioYamahaOPLApplet(GlasgowApplet, name="audio-yamaha-opl"):
"""

__pin_sets = ("d", "a")
__pins = ("clk_m", "rd", "wr",
"clk_sy", "sh", "mo")
__pins = ("wr", "rd", "clk_m",
"sh", "mo", "clk_sy")

@classmethod
def add_build_arguments(cls, parser, access):
super().add_build_arguments(parser, access)

access.add_pin_set_argument(parser, "d", width=8, default=True)
access.add_pin_argument(parser, "clk_m", default=True)
access.add_pin_set_argument(parser, "a", width=2, default=True)
access.add_pin_argument(parser, "rd", default=True)
access.add_pin_argument(parser, "wr", default=True)
access.add_pin_argument(parser, "clk_sy", default=True)
access.add_pin_argument(parser, "rd", default=True)
access.add_pin_argument(parser, "clk_m", default=True)
access.add_pin_argument(parser, "sh", default=True)
access.add_pin_argument(parser, "mo", default=True)
access.add_pin_argument(parser, "clk_sy", default=True)

parser.add_argument(
"-d", "--device", metavar="DEVICE", choices=["OPL", "OPL2", "OPL3"], required=True,
"-d", "--device", metavar="DEVICE", choices=["OPL", "OPL2", "OPL3", "OPM"],
required=True,
help="synthesizer family")
parser.add_argument(
"-o", "--overclock", metavar="FACTOR", type=float, default=1.0,
@@ -879,6 +933,8 @@ def _device_iface_cls(args):
return YamahaOPL2Interface
if args.device == "OPL3":
return YamahaOPL3Interface
if args.device == "OPM":
return YamahaOPMInterface
assert False

def build(self, target, args):
@@ -973,11 +1029,15 @@ async def set_voltage(voltage):

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

class AudioYamahaOPLAppletTestCase(GlasgowAppletTestCase, applet=AudioYamahaOPLApplet):
class AudioYamahaOPxAppletTestCase(GlasgowAppletTestCase, applet=AudioYamahaOPxApplet):
@synthesis_test
def test_build_opl2(self):
self.assertBuilds(args=["--device", "OPL2"])

@synthesis_test
def test_build_opl3(self):
self.assertBuilds(args=["--device", "OPL3"])

@synthesis_test
def test_build_opm(self):
self.assertBuilds(args=["--device", "OPM"])
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
<body style="width:50em">
<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>The exported PCM is a lossless conversion from the DAC bitstream output of the synthesier. It is resampled for streaming via WebAudio. WebAudio playback occasionally results in clicks, stutter, etc., which are not present in exported files.</p>
<p>The exported PCM is a lossless conversion from the DAC bitstream output of the synthesizer. It is resampled for streaming via WebAudio. WebAudio playback occasionally results in clicks, stutter, etc., which are not present in exported files.</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>
@@ -19,7 +19,7 @@ <h1>Yamaha OP* Web Gateway</h1>
Pull requests welcome.
-->
<script type="text/javascript">
var BUFFER_AT_LEAST = 16;
var BUFFER_AT_LEAST = 8;

function PCMPlayer() {
this.sampleRate = undefined;
7 changes: 6 additions & 1 deletion software/glasgow/protocol/vgm.py
Original file line number Diff line number Diff line change
@@ -11,6 +11,9 @@


class VGMStreamPlayer:
async def ym2151_write(self, address, data):
raise NotImplementedError("VGMStream.ym2151_write not implemented")

async def ym3526_write(self, address, data):
raise NotImplementedError("VGMStream.ym3526_write not implemented")

@@ -158,7 +161,9 @@ def chips(self):
async def parse_data(self, player):
while True:
command = self._read0("B")
if command == 0x5A:
if command == 0x54:
await player.ym2151_write(*self._read("BB"))
elif command == 0x5A:
await player.ym3812_write(*self._read("BB"))
elif command == 0x5B:
await player.ym3526_write(*self._read("BB"))