frno7 / psgplay

PSG play is a music player and emulator for the Atari ST Programmable Sound Generator (PSG) YM2149.
24 stars 4 forks source link
atari atari-st music psg sndh ym2149

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 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.

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.

Review the file INSTALL for installation instructions.

The package media-sound/psgplay is available for Gentoo Linux.

Github actions automatically compile and publish archives with PSG play for the Atari ST, as well as Linux and the architectures ppc64le, aarch64 and x86-64. These are built with .github/workflows/compilation.yml.

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

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

Tracing 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 for Linux, or is compiled for Atari ST. Mac OS does not support interactive text mode, only command mode with WAVE format output, as described in issue #8.

For Linux, 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:

Command mode

PSG play runs in command mode if it is not compiled with ALSA for Linux, or is compiled for 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

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 issues #9 and #10, DMA sound and LMC1992 for tone control specific to Atari STE and related hardware are not yet fully emulated by PSG play.