Skip to content

Commit

Permalink
Allow smaller buffers to be used when generating OPL samples
Browse files Browse the repository at this point in the history
Also restore the old delay function in demo.py which uses smaller buffers
for more accurate delays.
  • Loading branch information
Malvineous committed May 5, 2012
1 parent b6fa051 commit 5f90299
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 9 deletions.
37 changes: 32 additions & 5 deletions demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
# synth is in OPL2 or OPL3 mode - it will always work.
num_channels = 2

# How many samples to synthesise at a time. The DOSBox synth will only work
# if this is set to 512.
# How many samples to synthesise at a time. Higher values will reduce CPU
# usage but increase lag.
synth_size = 512

# An OPL helper class which handles the delay between notes and buffering
Expand All @@ -59,10 +59,10 @@ def writeReg(self, reg, value):

def wait(self, ticks):
# Rather than calculating the exact number of samples we need to generate,
# we just keep generating 512 samples at a time (as required by the DOSBox
# synth) until we've waited as close as possible to the requested delay.
# we just keep generating 512 samples at a time until we've waited as close
# as possible to the requested delay.
# This does mean we might wait for up to 511/freq samples too little (at
# 48kHz that's a worst-case of 1.06ms too short) but no human will notice
# 48kHz that's a worst-case of 10.6ms too short) but nobody should notice
# and it saves enough CPU time and complexity to be worthwhile.
self.delay += ticks * freq / self.ticksPerSecond
while self.delay > synth_size:
Expand All @@ -71,6 +71,28 @@ def wait(self, ticks):
stream.write(self.pyaudio_buf)
self.delay -= synth_size

# This is an alternate way of calculating the delay. It has slightly higher
# CPU usage but provides more accurate delays (+/- 0.04ms).
# To use it, rename the function to "wait" and rename the other "wait"
# function to something else.
def wait2(self, ticks):
# Figure out how many samples we need to get to obtain the delay
fill = ticks * freq / self.ticksPerSecond
tail = fill % synth_size
if tail:
buf_tail = bytearray(tail * sample_size * num_channels)
# Fill the buffer in 512-sample lots until full
cur = self.buf
while fill > 1: # DOSBox synth can't generate < 2 samples
if fill < synth_size:
# Resize the buffer for the last bit
cur = buf_tail
self.opl.getSamples(cur)
pyaudio_buf = buffer(cur)
stream.write(pyaudio_buf)
fill -= synth_size


## Main code begins ##

# Require a filename to be given on the command line
Expand Down Expand Up @@ -104,6 +126,10 @@ def wait(self, ticks):
rate = freq,
output = True)

# At this point we have to hope PyAudio has got us the audio format we
# requested. It doesn't always, but it lacks functions for us to check.
# This means we could end up outputting data in the wrong format...

# Set up the OPL synth
oplStream = OPLStream(freq, ticksPerSecond)

Expand Down Expand Up @@ -133,6 +159,7 @@ def wait(self, ticks):
## First, override the panning register to make the sound come out of both
## channels. This is required if playing OPL2 data, as these registers are
## unused on the OPL2 so are usually set to zero, silencing the channel.
## You won't need this if you're playing native OPL3 data.
#if reg & 0xC0 == 0xC0:
# val = val | 0x30
## Write the same value to the same register on the upper register set
Expand Down
6 changes: 3 additions & 3 deletions pyopl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ PyObject *opl_getSamples(PyObject *self, PyObject *args)

int samples = o->sh->pybuf.len / SAMPLE_SIZE / o->sh->channels;
if (samples > 512) {
PyErr_SetString(PyExc_ValueError, "buffer too large (must be 512 samples)");
PyErr_SetString(PyExc_ValueError, "buffer too large (max 512 samples)");
return NULL;
}
if (samples < 512) {
PyErr_SetString(PyExc_ValueError, "buffer too small (must be 512 samples)");
if (samples < 2) {
PyErr_SetString(PyExc_ValueError, "buffer too small (min 2 samples)");
return NULL;
}

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()

setup(name='PyOPL',
version='1.1',
version='1.2',
description='OPL2/3 Adlib emulation',
author='Adam Nielsen',
author_email='malvineous@shikadi.net',
Expand Down

0 comments on commit 5f90299

Please sign in to comment.