Skip to content

frno7/psgplay

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

compilation workflow

Atari ST loading screen Atari ST main menu

PSG play is a music player and emulator for the Atari ST Programmable Sound Generator (PSG) YM2149 and files in the SNDH archive.

How to download

Github actions automatically compile and publish archives with PSG play for the Atari ST, as well as Linux and the architecture x86-64, and a wasm web browser library for use with Cowbell. These are built with .github/workflows/compilation.yml.

How to build

This repository has Git submodules so clone it with the --recurse-submodules option, or do git submodule update --init --recursive.

For Linux and Mac OS, do make psgplay to compile psgplay. To use Advanced Linux Sound Architecture (ALSA) and interactive text mode, do make ALSA=1 psgplay. To use PortAudio and interactive text mode, do make PORTAUDIO=1 psgplay.

For Atari ST, do make TARGET_COMPILE=m68k-elf- PSGPLAY.TOS.

For Javascript, Webassembly, and the Emscripten compiler, do make HOST_CC=emcc web. The PSG play library is available with Cowbell, having a particular focus on demoscene music.

The BUILD_CC, HOST_AR, HOST_CC, and TARGET_CC with TARGET_LD Makefile variables can be configured for various compilation settings. The BUILD_CFLAGS, HOST_CFLAGS, TARGET_CFLAGS, and TARGET_LDFLAGS variables are available as well.

Type make install to install everything, by default in ~/.local/usr. Set prefix to change the directory, for example make prefix=$HOME/some/place/else install. More specific subtargets than install are also available, for instance install-lib, install-psgplay, and so on. Set DESTDIR for staged installs.

Review the file INSTALL for installation instructions.

How to use

PSG play options for Linux and Mac OS:

Usage: psgplay [options]... <sndh-file>

General options:

    -h, --help             display this help and exit
    --version              display version and exit
    -v, --verbose          increase verbosity

    -i, --info             display SNDH file info and exit

Play options:

    -o, --output=<file>    write audio output to the given file in WAVE format
                           or to an ALSA handle if prefixed with "alsa:"

    --start=<[mm:]ss.ss>   start playing at the given time
    --stop=<[mm:]ss.ss|auto|never>
                           stop playing at the given time, or automatically
                           if the track has a known duration, or never
    --length=<[mm:]ss.ss>  play for the given duration

    -m, --mode=<command|text>
                           command or interactive text mode

    -t, --track=<num>      set track number
    -f, --frequency=<num>  set audio frequency in Hz (default 44100)

    --psg-mix=<empiric|linear>
                           empiric (default) mixes the three PSG channels as
                           measured on Atari ST hardware; linear sums the
                           channels to produce a cleaner sound
    --psg-balance=<A:B:C>  set balance between -1 (left) and +1 (right) for
                           PSG channels A, B and C. For example -0.4:0:+0.4
                           for stereo effect. Default is 0:0:0 for mono.
    --psg-volume=<A:B:C>   set volume between 0 (off) and +1 (max) for
                           PSG channels A, B and C. For example 0:0:1 to
                           play channel C only. Default is 1:1:1.

Disassembly options:

    --disassemble          disassemble SNDH file and exit; may be combined
                           with the --trace=cpu option for self-modifying code,
                           disassembly of interrupt code, etc.
    --disassemble-header   disassemble SNDH file header and exit
    --disassemble-address  display address column in disassembly
    --remake-header        remake SNDH file header in disassembly

Trace options:

    --trace=<device>,...   trace device operations of SNDH file and exit:
                           all cpu reg

Interactive text mode

PSG play defaults to interactive text mode if it is compiled with ALSA or PortAudio for Linux or Mac OS, or is compiled for Atari ST. If no audio output support is present, PSG play will default to WAVE format output.

For Linux and Mac OS, TTY mode and ECMA-48 are used, including support for job control such as process suspension.

For Atari ST, text mode and VT52 are used. See issues #5 and #6 for ideas about additional serial port and GEM user interfaces.

The currently playing tune is indicated in reverse video. A cursor is shown with >. Keyboard controls:

  • escape or q to quit;
  • s to stop;
  • p or spacebar to pause;
  • 1, 2, ..., 9 to play tunes 1, 2, ..., 9;
  • - to decrease volume;
  • + to increase volume;
  • < to play the previous tune;
  • > to play the next tune;
  • k or up arrow to move the cursor up;
  • j or down arrow to move the cursor down;
  • return to play the tune at the cursor.

Command mode

PSG play runs in command mode if it is not compiled with ALSA for Linux, or with PortAudio for Linux or Mac OS, or the options -o, --output, --start, --stop, --length, --disassemble or --trace are given. Atari ST does not support command mode.

Library form

PSG play is compiled into the static library lib/psgplay/psgplay.a and the shared library lib/psgplay/psgplay.so. The application programming interface (API) is documented in include/psgplay/psgplay.h, include/psgplay/stereo.h, include/psgplay/sndh.h and include/psgplay/version.h.

The library also supplies an unaltered 250 kHz digital form for custom analogue filters and mixers. This digital interface is documented in include/psgplay/digital.h.

There are two simple examples on how to use the PSG play library:

Disassembly

PSG play can disassemble SNDH files. This can be used to debug, update metadata and reassemble SNDH files. The --disassemble option can be used for code inspection. The --disassemble-header option is the safest choice when updating SNDH metadata, because most of the code is retained with dc.b data bytes for exact reassembly.

The disassembly is guided by instruction reachability from the init, play, and exit entry points, to separate executable instructions from data. To deal with interrupt code and self-modifying code, use both the --disassemble and the --trace=cpu options. The disassembly will then print what the processor actually executed in memory, which may have been modified by the program itself, rather than the contents of the SNDH file. The tracing execution length can be set with the --length option.

The --remake-header option can be used to repair broken SNDH metadata such as missing tags, excessive whitespace, etc. It can also be used to update or add new metadata, by editing the produced assembly source code in an editor.

Excerpt of disassembly with the --disassemble option:

init:
	bra.w	_init				; init
exit:
	bra.w	_exit				; exit
play:
	bra.w	_play				; play
sndh:
	dc.b	$53,$4e,$44,$48,$43,$4f,$4d,$4d	; SNDHCOMM
	dc.b	$4d,$61,$64,$20,$4d,$61,$78,$00	; Mad Max.
	dc.b	$52,$49,$50,$50,$47,$72,$61,$7a	; RIPPGraz
	dc.b	$65,$79,$20,$2f,$20,$50,$48,$46	; ey / PHF
	dc.b	$00,$43,$4f,$4e,$56,$47,$72,$61	; .CONVGra
	dc.b	$7a,$65,$79,$20,$2f,$20,$50,$48	; zey / PH
	dc.b	$46,$00,$54,$49,$54,$4c,$57,$61	; F.TITLWa
	dc.b	$72,$70,$00,$23,$23,$30,$38,$00	; rp.##08.
	dc.b	$54,$43,$35,$30,$00,$00        	; TC50..
_init:
	lea	_9a(pc),a0			; init
	lea	_9e(pc),a1			; init
	move.l	a0,(a1)				; init
	subi.w	#1,d0				; init
	lea	_b4a(pc),a0			; init
	lea	_ac(pc),a1			; init
	move.l	d0,d1				; init
	asl.w	#3,d1				; init
	movea.l	(a1,d1.w),a2			; init
	addq.w	#6,d1				; init
	move.w	(a1,d1.w),d0			; init
	lea	_b4a(pc),a0			; init
	adda.l	a2,a0				; init
	lea	_ec(pc),a1			; init
	move.l	a0,56(a1)			; init
	move.l	a0,108(a1)			; init
	clr.w	2104(a1)			; init
	bsr.w	_ec				; init
	lea	_f4(pc),a0			; init
	lea	_9e(pc),a1			; init
	move.l	a0,(a1)				; init
_9a:
	rts					; init
_exit:
	rts					; exit
_9e:
	dc.b	$00,$00,$00,$00
_play:
	movea.l	_9e(pc),a0			; play
	jsr	(a0)				; play
	rts					; play
	...

Excerpt of disassembly with the --disassemble-header option:

init:
	bra.w	_init
exit:
	bra.w	_exit
play:
	bra.w	_play
sndh:
	dc.b	$53,$4e,$44,$48,$43,$4f,$4d,$4d	; SNDHCOMM
	dc.b	$4d,$61,$64,$20,$4d,$61,$78,$00	; Mad Max.
	dc.b	$52,$49,$50,$50,$47,$72,$61,$7a	; RIPPGraz
	dc.b	$65,$79,$20,$2f,$20,$50,$48,$46	; ey / PHF
	dc.b	$00,$43,$4f,$4e,$56,$47,$72,$61	; .CONVGra
	dc.b	$7a,$65,$79,$20,$2f,$20,$50,$48	; zey / PH
	dc.b	$46,$00,$54,$49,$54,$4c,$57,$61	; F.TITLWa
	dc.b	$72,$70,$00,$23,$23,$30,$38,$00	; rp.##08.
	dc.b	$54,$43,$35,$30,$00,$00        	; TC50..
_init:
	dc.b	$41,$fa,$00,$46,$43,$fa,$00,$46
	dc.b	$22,$88,$04,$40,$00,$01,$41,$fa
	dc.b	$0a,$e8,$43,$fa,$00,$46,$22,$00
	dc.b	$e7,$41,$24,$71,$10,$00,$5c,$41
	dc.b	$30,$31,$10,$00,$41,$fa,$0a,$d2
	dc.b	$d1,$ca,$43,$fa,$00,$6e,$23,$48
	dc.b	$00,$38,$23,$48,$00,$6c,$42,$69
	dc.b	$08,$38,$61,$00,$00,$5e,$41,$fa
	dc.b	$00,$62,$43,$fa,$00,$08,$22,$88
	dc.b	$4e,$75
_exit:
	dc.b	$4e,$75,$00,$00,$00,$00
_play:
	dc.b	$20,$7a,$ff,$fa,$4e,$90,$4e,$75
	...

Excerpt of disassembly with the --disassemble-header and --remake-header options (having the missing HDNS tag automatically repaired from the previous excerpt):

init:
	bra.w	_init
exit:
	bra.w	_exit
play:
	bra.w	_play
sndh:
	dc.b	'SNDH'
	dc.b	'COMMMad Max',0
	dc.b	'RIPPGrazey / PHF',0
	dc.b	'CONVGrazey / PHF',0
	dc.b	'TITLWarp',0
	dc.b	'##08',0
	dc.b	'TC50',0
	even
	dc.b	'HDNS'
_init:
	dc.b	$41,$fa,$00,$46,$43,$fa,$00,$46
	dc.b	$22,$88,$04,$40,$00,$01,$41,$fa
	dc.b	$0a,$e8,$43,$fa,$00,$46,$22,$00
	dc.b	$e7,$41,$24,$71,$10,$00,$5c,$41
	dc.b	$30,$31,$10,$00,$41,$fa,$0a,$d2
	dc.b	$d1,$ca,$43,$fa,$00,$6e,$23,$48
	dc.b	$00,$38,$23,$48,$00,$6c,$42,$69
	dc.b	$08,$38,$61,$00,$00,$5e,$41,$fa
	dc.b	$00,$62,$43,$fa,$00,$08,$22,$88
	dc.b	$4e,$75
_exit:
	dc.b	$4e,$75,$00,$00,$00,$00
_play:
	dc.b	$20,$7a,$ff,$fa,$4e,$90,$4e,$75
	...

Disassembly makes it possible to supply bug fixes and metadata updates in source patch form, for quick and easy review, application and SNDH file reassembly:

--- sndh/Mad_Max/Games/Lethal_Xcess_(ST).S.orig	2020-05-22 14:56:20.495508523 +0200
+++ sndh/Mad_Max/Games/Lethal_Xcess_(ST).S.new	2020-05-22 15:23:55.260509689 +0200
@@ -6,13 +6,31 @@
 	bra.w	_play
 sndh:
 	dc.b	'SNDH'
-	dc.b	'TITLLethal Xcess (ST/Falc)',0
-	dc.b	'COMMMad Max',0
+	dc.b	'TITLLethal Xcess (ST)',0
+	dc.b	'COMMJochen Hippel',0
 	dc.b	'RIPPGrazey / PHF',0
 	dc.b	'CONVGrazey / PHF',0
+	dc.b	'YEAR1991',0
 	dc.b	'TC50',0
 	dc.b	'##07',0
 	even
+.subtitles:
+	dc.b	'!#SN'
+	dc.w	.st1-.subtitles
+	dc.w	.st2-.subtitles
+	dc.w	.st3-.subtitles
+	dc.w	.st4-.subtitles
+	dc.w	.st5-.subtitles
+	dc.w	.st6-.subtitles
+	dc.w	.st7-.subtitles
+.st1:	dc.b	'Main Menu',0
+.st2:	dc.b	'Level 1: Ruins of Methallycha 1',0
+.st3:	dc.b	'Level 1: Ruins of Methallycha 2',0
+.st4:	dc.b	'Level 2: Desert of No Return',0
+.st5:	dc.b	'Level 3: The Evil Garden',0
+.st6:	dc.b	'Level 4: Volcanic Plateaus',0
+.st7:	dc.b	'Level 5: Fortress of Methallycha',0
+	even
 	dc.b	'HDNS'
 _init:
 	dc.b	$2f,$00,$41,$fa,$00,$6e,$4a,$50

Test and verification

Various technical aspects such as pitch, tempo, etc. can be tested and verified with quality metrics and audio graphs. The tests are SNDH files compiled from C, so an m68k-elf cross-compiler is required:

  • make -j TARGET_COMPILE=m68k-elf- test compiles all tests.
  • make -j TARGET_COMPILE=m68k-elf- verify compiles and verifies all tests.
  • make -j TARGET_COMPILE=m68k-elf- report compiles and reports quality metrics for all tests.

More specific tests can be compiled, verified and reported. For example verify-psgpitch to verify all psgpitch tests, or verify-psgpitch-3 to verify only the third test, and so on.

Making audio graph and report files:

  • make -j TARGET_COMPILE=m68k-elf- test/psgpitch-1.svg compiles an SVG graph file.
  • make -j TARGET_COMPILE=m68k-elf- test/psgpitch-1.png compiles a PNG graph file.
  • make -j TARGET_COMPILE=m68k-elf- test/psgpitch-1.report compiles a report file.

Example report:

$ make -j TARGET_COMPILE=m68k-elf- report-psgpitch-2
path test/psgpitch-2.wave
name psgpitch
index 2
title PSG square wave C1 double low C 33 Hz
sample count 2686419 samples
sample duration 60.9 s
sample frequency 44100 Hz
tone clock 8010613 / 4 / 16 Hz
tone period 3793 cycles
wave reference frequency 32.999164 Hz
wave period 1336.398010 samples
wave frequency 32.999151 Hz
wave phase 42.000000 samples
wave zero crossing count 4021
wave zero crossing deviation max 0.910448 samples
wave error total count 1.022 samples
wave error total time 2.317e-05 s
wave error absolute frequency -0.000013 Hz
wave error relative frequency 3.80e-07
wave error relative tolerance 7.44e-07

Set the PSGPLAY_TEST Makefile option to run the test suite with a command other than psgplay, for example a command using Hatari instead. Note that the last second is automatically cut from input WAVE files during testing, to avoid artifacts due to audio fading out, etc.

Review the file INSTALL for test and verification instructions.

How it works

The SNDH file format is an Atari ST machine code executable form of music. A substantial part of Atari ST hardware must be emulated to play such files using other kinds of computers. The five most complex parts emulated in software by PSG play are:

The digital emulation is currently fairly accurate, aiming to be within the variation of the compatible models of original Atari hardware. The analogue emulation is currently simpler, aiming to be accurate but also avoid unwanted artifacts such as the high level of noise produced with original Atari hardware.

The YM2149 PSG signal is unipolar, and has to be transformed to a bipolar signal for mixing with stereo samples. To avoid sharp and audible noise when starting and stopping playback, stereo samples fade in and out with a 10 ms logistic sigmoid at start and stop.

As described in the issue #9 the LMC1992 for tone control specific to Atari STE is not yet fully emulated by PSG play.