A Python wrapper for the AY-3-8910, AY-3-8912 and AY-3-8913 sound chip emulators, featuring real-time audio playback and cycle-accurate synthesis.
A Note on this Project's Origin
This project is primarily the result of a series of experiments using various IA Code Assists for code generation and error handling. Rather than using it on academic examples, it seemed more interesting to apply it to a project that could meet a real practical need.
This, therefore, is the reason for
AY8910's existence: you can dissect the code to see how Gemini and Junie (with my guidance) went about building it, or you can ignore all that and just use this library for your own needs!
This project contains a standalone C++ library for the AY-3-8910 sound chip. It now features a unified API with three main classes:
ay8910: Emulates the 3-channel PSG with 2 I/O ports.ay8912: Emulates the 3-channel PSG with 1 I/O port.ay8913: Emulates the 3-channel PSG with 0 I/O port.
At instantiation, you can choose between three emulation backends:
CAPRICE32(default): The recommended engine. High accuracy, stereo mixing, and integrated live audio.MAME: Based on the MAME implementation.AY_EMUL31: A port of Sergey Bulba's Ay_Emul 3.1.
import ay8910_wrapper as ay
import time
# Initialize an AY-3-8912 using the Caprice32 backend (default)
psg = ay.ay8912(clock=1000000, sample_rate=44100)
psg.set_stereo_mix(255, 13, 170, 170, 13, 255)
# Start live playback!
psg.play()
# Set registers - sound changes immediately
psg.set_register(0, 254) # Tone A Fine
psg.set_register(1, 0) # Tone A Coarse
psg.set_register(7, 0x3E) # Enable Tone A, disable others
psg.set_register(8, 15) # Max volume
time.sleep(1)
psg.stop()# Play a .ym file using the live player
ym_live_player PATH\TO\YM_FILE.YM
# Explicitly choose a backend
ym_live_player PATH\TO\YM_FILE.YM --backend MAME
ym_live_player PATH\TO\YM_FILE.YM --backend AY_EMUL31You can install the library directly from PyPI:
# Base installation (emulator only)
pip install ay8910_wrapper
# Full installation with playback tools and LHA support
pip install ay8910_wrapper[tools]The base installation has no external dependencies. The [tools] option adds lhafile, numpy, and sounddevice which are required for the command-line players and real-time audio output.
For developers who want to compile from source or contribute, please refer to the Contributing Guide.
The simplest way to hear a sound in real-time.
import ay8910_wrapper as ay
import time
# Create an AY-3-8912 (1 I/O port) as used in Amstrad CPC
psg = ay.ay8912(backend=ay.Backend.CAPRICE32, clock=1000000)
# Start live audio
psg.play()
# Set a 440Hz tone on Channel A
# Period = 1000000 / (16 * 440) ≈ 142
psg.set_register(0, 142 & 0xFF)
psg.set_register(1, (142 >> 8) & 0x0F)
psg.set_register(7, 0xFE) # Enable Tone A
psg.set_register(8, 15) # Max volume
time.sleep(1)
psg.stop()Generate samples manually and save them to a file.
import ay8910_wrapper as ay
import wave, struct
psg = ay.ay8910(backend=ay.Backend.MAME, clock=2000000)
# Configure a noise effect (e.g., snare drum)
psg.set_register(6, 15) # Noise period
psg.set_register(7, 0xF7) # Enable Noise on Channel A
psg.set_register(8, 16) # Use envelope
psg.set_register(11, 0) # Envelope period fine
psg.set_register(12, 10) # Envelope period coarse
psg.set_register(13, 0x00) # Decay shape (\___)
# Generate 0.5s of audio at 44100Hz
samples = psg.generate(22050)
with wave.open("noise_effect.wav", "wb") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(44100)
f.writeframes(struct.pack('<' + ('h' * len(samples)), *samples))Simulate specific hardware analog characteristics.
import ay8910_wrapper as ay
# Use MAME backend for advanced hardware flags
psg = ay.ay8910(backend=ay.Backend.MAME, clock=1750000)
# Enable resistor-based output modeling (high accuracy)
psg.set_flags(ay.AY8910_RESISTOR_OUTPUT | ay.AY8910_SINGLE_OUTPUT)
# Set specific load resistors for ZX Spectrum (~1k Ohm)
psg.set_resistors_load(1000.0, 1000.0, 1000.0)
psg.play()
# ... program registers ...For more fine-grained control over the audio stream, use the DirectOutput class directly.
import ay8910_wrapper as ay
from ay8910_wrapper import DirectOutput
# Create any PSG instance
psg = ay.ay8910(backend=ay.Backend.MAME)
# Initialize and start the audio output manually
audio = DirectOutput(psg)
audio.start()
# ... sound generation ...
# Stop playback when done
audio.stop()For a complete list of all available functions, classes, and constants, please see the API Reference.
The project includes several scripts for playing chiptunes and running tests. For a detailed description of how to use them, please see the Scripts and Tools page.
The new architecture allows you to choose the exact chip model and the emulation engine that best fits your needs.
# Initialize an AY-3-8910 (2 I/O ports) using the Caprice32 backend
psg_8910 = ay.ay8910(backend=ay.Backend.CAPRICE32)
# Initialize an AY-3-8912 (1 I/O port) using the MAME backend
psg_8912 = ay.ay8912(backend=ay.Backend.MAME)
# Initialize an AY-3-8913 (0 I/O ports) using the Ay_Emul31 backend
psg_8913 = ay.ay8913(backend=ay.Backend.AY_EMUL31)If you wish to contribute to the project or compile it from source, please refer to our Contributing Guide.
It contains all the necessary information about:
- The development workflow (GitHub Flow)
- Using uv for environment management
- Compilation and test commands
- Continuous integration processes (GitHub Actions)
This project relies on the incredible work of the following open-source projects:
- MAME: The original AY-3-8910 and YM2149 emulation cores were derived from the MAME project. Their commitment to accuracy and historical preservation is a cornerstone of this library.
- Caprice32: The Amstrad CPC-specific PSG emulation logic and amplitude tables were integrated from the Caprice32 project, providing authentic sound for CPC-related audio tasks.
- Ay_Emul: The
ay_emul31engine is based on the work of Sergey Bulba. It's a port of his original Pascal source code (version 3.1) to C++. - Sergey Bulba: Special thanks for the AY/YM amplitude tables used in the Caprice32 and Ay_Emul31 engines, which are essential for reproducing the characteristic sound of these chips.
- Gemini & Junie: This project was built with the assistance of AI, demonstrating the potential of human-AI collaboration in software development.
This project is licensed under the MIT License - see the License for details. The original MAME and Caprice32 cores have their own licensing terms (see LICENSE_MAME.md).