pyayay

PyAYay is a Python extension wrapper for AY-3-8910 sound chip emulator


Keywords
audio-library, emulation-library, pybind11, python3, retrocomputing
License
MIT
Install
pip install pyayay==0.1.1

Documentation

GitHub Actions Workflow Status GitHub Actions Workflow Status PyPI - License
PyPI Python Version from PEP 621 TOML PyPI - Wheel

PyAYay

PyAYay is a Python wrapper for the AY/YM sound chip emulator. Currently it supports only the Ayumi emulator by Peter Sovietov.

Installation

From PyPI

You can install the package from PyPI:

pip install pyayay

Yep, that's it!

From source

git clone https://github.com/ruguevara/pyayay.git
cd pyayay
pip install .

Or install in development mode:

git clone https://github.com/ruguevara/pyayay.git
cd pyayay
pip install -e .

Usage

from pyayay import Ayumi, ChipType, EnvShape

ay = Ayumi(sample_rate=44100, clock=1773400, type=ChipType.AY)
# or just Ayumi() for default values

Set panning for channels, for example in ACB order, and the master volume:

ay.set_pan(0, 0.25)  # A left
ay.set_pan(1, 0.75)  # B right
ay.set_pan(2, 0.5)   # C center

ay.set_master_volume(0.75)

Use setters to set the channel parameters. Do not forget to call set_mixer to enable the channel.

ay.set_mixer(0, True, False, False)  # Tone, Noise and Envelope for A channel
ay.set_tone_period(0, 100)
ay.set_volume(0, 15)

You can set the envelope shape and period, for example:

ay.set_mixer(0, True, False, True)  # Turn on envelope modulation for A channel
ay.set_envelope_shape(EnvShape.UP_DOWN_E)
ay.set_envelope_period(1024)

To generate sound use process_block method:

samples = 44100 * 2  # 2 seconds
outLeft  = np.zeros(samples, dtype=np.float32)
outRight = np.zeros(samples, dtype=np.float32)

ay.process_block(outLeft, outRight, samples)

Examples of using the R0-R13 registers and PSG rendering

You can use AY/YM registers R0-R13 directly:

ay.R[1] = 100
ay.R[0] = 0
ay.R[7] = 0b00111110
ay.R[8] = 15

or, to set number of register at once:

ay.set_registers(
    [1, 0, 7, 8],
    [100, 0, 0b00111110, 15]
)

or, to set some of the registers with the 'mask' array. NOTE that mask is inverted, so 1 means "do not change". This is useful for PSG frame playing.

# R           0  1    2  3  4  5  6  7   8   9  10 11 12 13
R = np.array([0, 100, 0, 0, 0, 0, 0, 62, 15, 0, 0, 0, 0, 0], dtype=np.uint8)
M = np.array([0, 0,   1, 1, 1, 1, 1, 0,  0,  1, 1, 1, 1, 1], dtype=bool)

ay.set_registers_masked(R, M)

And render PSG data from Numpy arrays in one call:

data = np.array([
    [249,   0, 148,   0,  40,   1,   3,  40,  13,  29,  13,  74,   0, 12],
    [249,   0, 158,   4,  40,   1,   3,  56,  13,  15,  13,  74,   0, 12]], dtype=np.uint8)
mask = np.array([
    [0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
    [1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1]], dtype=bool)
fps = 50
ay.render_psg(data, mask, outLeft, outRight, fps)

For more usage examples see tests.

License

We use MIT license, see LICENSE file.