├── include ├── tos │ └── .gitignore ├── m68k │ ├── .gitignore │ └── m68kds.h ├── vt │ ├── vt52.h │ ├── ecma48.h │ └── vt-cmd.h ├── audio │ ├── wave-reader.h │ ├── wave-writer.h │ ├── portaudio-writer.h │ ├── reader.h │ ├── writer.h │ ├── alsa-writer.h │ └── audio.h ├── atari │ ├── fdc.h │ ├── rom.h │ ├── m68k.h │ ├── dac.h │ ├── mfp-register.h │ ├── mfp.h │ ├── glue.h │ ├── irq.h │ ├── system-variable.h │ ├── ram.h │ ├── cpu.h │ ├── psg.h │ ├── sound.h │ ├── mmu.h │ ├── mmu-trace.h │ ├── mfp-map.h │ ├── bus.h │ ├── trace.h │ ├── sample.h │ ├── machine.h │ ├── mixer.h │ ├── shifter.h │ └── device.h ├── graph │ ├── svg.h │ └── graph.h ├── system │ ├── unix │ │ ├── remake.h │ │ ├── info.h │ │ ├── print.h │ │ ├── sndh.h │ │ ├── diagnostic.h │ │ ├── memory.h │ │ ├── poll-fifo.h │ │ ├── disassemble.h │ │ ├── text-mode.h │ │ ├── command-mode.h │ │ ├── clock.h │ │ ├── tty.h │ │ ├── file.h │ │ ├── option.h │ │ └── string.h │ └── atari │ │ ├── clock.h │ │ ├── psg.h │ │ ├── option.h │ │ ├── timer.h │ │ ├── model.h │ │ ├── ice_decrunch_inplace.h │ │ └── file.h ├── psgplay │ ├── version.h │ └── psgplay.h ├── test │ ├── maxamp.h │ ├── sndhfrms.h │ ├── sndhtimera.h │ ├── sndhtimerb.h │ ├── sndhtimerc.h │ ├── sndhtimerd.h │ ├── verify.h │ ├── sndhtimer.h │ ├── sndhtimervbl.h │ ├── option.h │ ├── tempo.h │ ├── psgpitch.h │ ├── snd-dma-alt.h │ ├── sndhtimer-verify.h │ ├── report.h │ └── dmapitch.h ├── text │ ├── load.h │ ├── main.h │ ├── mvc.h │ └── mode.h ├── unicode │ ├── atari.h │ └── utf8.h ├── internal │ ├── print.h │ ├── assert.h │ ├── struct.h │ ├── check-compiler.h │ ├── limits.h │ ├── fifo.h │ ├── bit.h │ ├── types.h │ ├── macro.h │ ├── build-assert.h │ └── string.h └── ice │ └── ice.h ├── lib ├── version │ ├── .gitignore │ └── Makefile ├── example │ ├── .gitignore │ ├── example-info.c │ ├── example-play.c │ └── Makefile ├── m68k │ ├── .gitignore │ └── Makefile ├── tos │ ├── tos │ ├── Makefile │ └── reset.S ├── ice │ └── Makefile ├── psgplay │ └── .gitignore ├── graph │ ├── Makefile │ └── graph.c ├── unicode │ └── Makefile ├── vt │ ├── Makefile │ ├── vt52.c │ └── ecma48.c ├── text │ ├── Makefile │ ├── mode.c │ └── load.c ├── internal │ ├── sso.c │ ├── Makefile │ ├── bit.c │ ├── fifo.c │ ├── print.c │ └── string.c ├── audio │ └── Makefile ├── atari │ ├── exception-vector.c │ ├── Makefile │ ├── rom.c │ ├── system-variable.c │ ├── bus.c │ ├── fdc.c │ ├── cpu.c │ ├── mmu-trace.c │ ├── shifter.c │ ├── glue.c │ ├── ram.c │ └── machine.c ├── test │ ├── Makefile │ ├── report.c │ └── verify.c └── Makefile ├── doc ├── psgpitch.png ├── atari-load.png ├── atari-main.png └── Makefile ├── test ├── .gitignore ├── sndhtimera-verify.c ├── sndhtimerb-verify.c ├── sndhtimerc-verify.c ├── sndhtimerd-verify.c ├── sndhtimervbl-verify.c ├── sndhtimera.c ├── sndhtimerb.c ├── sndhtimerc.c ├── sndhtimerd.c ├── sndhtimervbl.c ├── psgpitch.c ├── sndhfrms.c ├── sndhfrms-verify.c ├── archive.suite ├── tempo.c ├── maxamp.c ├── psgpitch-verify.c ├── maxamp-verify.c ├── dmapitch-verify.c ├── tempo-verify.c ├── dmapitch.c └── dmasint-verify.c ├── system ├── Makefile ├── atari │ ├── clock.c │ ├── timer.c │ ├── print.c │ ├── option.c │ ├── ice_decrunch_inplace.c │ ├── psg.c │ ├── Makefile │ └── psgplay.c └── unix │ ├── memory.c │ ├── sndh.c │ ├── print.c │ ├── clock.c │ ├── diagnostic.c │ ├── poll-fifo.c │ ├── info.c │ ├── Makefile │ ├── remake.c │ └── string.c ├── .gitignore ├── .gitmodules ├── script ├── tos ├── pkg ├── sndh.ld ├── test-tune ├── version ├── archive-suite ├── tos.ld └── compile ├── licence └── MIT └── Makefile /include/tos/.gitignore: -------------------------------------------------------------------------------- 1 | /tos.h 2 | -------------------------------------------------------------------------------- /lib/version/.gitignore: -------------------------------------------------------------------------------- 1 | /version.c 2 | -------------------------------------------------------------------------------- /include/m68k/.gitignore: -------------------------------------------------------------------------------- 1 | /m68kdg.h 2 | /m68kops.h 3 | -------------------------------------------------------------------------------- /lib/example/.gitignore: -------------------------------------------------------------------------------- 1 | /example-info 2 | /example-play 3 | -------------------------------------------------------------------------------- /lib/m68k/.gitignore: -------------------------------------------------------------------------------- 1 | /m68kdg 2 | /m68kdt 3 | /m68kmake 4 | /m68kops.c 5 | -------------------------------------------------------------------------------- /lib/tos/tos: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frno7/psgplay/HEAD/lib/tos/tos -------------------------------------------------------------------------------- /doc/psgpitch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frno7/psgplay/HEAD/doc/psgpitch.png -------------------------------------------------------------------------------- /doc/atari-load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frno7/psgplay/HEAD/doc/atari-load.png -------------------------------------------------------------------------------- /doc/atari-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frno7/psgplay/HEAD/doc/atari-main.png -------------------------------------------------------------------------------- /lib/ice/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | ICE_SRC := lib/ice/ice.c 4 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | /*.png 2 | /*.svg 3 | /*.report 4 | /*-verify 5 | /*.sha256 6 | /*.tmp 7 | -------------------------------------------------------------------------------- /lib/psgplay/.gitignore: -------------------------------------------------------------------------------- 1 | /libpsgplay.a 2 | /libpsgplay.so 3 | /libpsgplay.js 4 | /libpsgplay.wasm 5 | /libpsgplay.pc 6 | -------------------------------------------------------------------------------- /system/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | include system/atari/Makefile 4 | include system/unix/Makefile 5 | -------------------------------------------------------------------------------- /lib/graph/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | GRAPH_SRC := $(addprefix lib/graph/, \ 4 | graph.c \ 5 | svg.c) 6 | -------------------------------------------------------------------------------- /lib/unicode/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | UNICODE_SRC := $(addprefix lib/unicode/, \ 4 | atari.c \ 5 | utf8.c) 6 | -------------------------------------------------------------------------------- /lib/vt/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | VT_SRC := \ 4 | lib/vt/ecma48.c \ 5 | lib/vt/vt.c \ 6 | lib/vt/vt52.c 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /version 2 | /psgplay 3 | /PSGPLAY.* 4 | *.sndh 5 | *.wave 6 | *.o 7 | *.o.d 8 | *.elf 9 | *.dylib 10 | /GPATH 11 | /GRTAGS 12 | /GTAGS 13 | -------------------------------------------------------------------------------- /lib/text/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | TEXT_SRC := $(addprefix lib/text/, \ 4 | load.c \ 5 | main.c \ 6 | mode.c) 7 | -------------------------------------------------------------------------------- /lib/internal/sso.c: -------------------------------------------------------------------------------- 1 | /* Test whether the compiler supports scalar storage order. */ 2 | struct __attribute__((__scalar_storage_order__("big-endian"))) { 3 | int n; 4 | } sso; 5 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | .PHONY: install-man 4 | install-man: 5 | $(INSTALL) -d -m 755 $(DESTDIR)$(man1dir) 6 | $(INSTALL) -m 644 doc/psgplay.1 $(DESTDIR)$(man1dir) 7 | -------------------------------------------------------------------------------- /test/sndhtimera-verify.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include "test/report.h" 4 | #include "test/verify.h" 5 | #include "test/sndhtimera.h" 6 | 7 | #include "test/sndhtimer-verify.h" 8 | -------------------------------------------------------------------------------- /test/sndhtimerb-verify.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include "test/report.h" 4 | #include "test/verify.h" 5 | #include "test/sndhtimerb.h" 6 | 7 | #include "test/sndhtimer-verify.h" 8 | -------------------------------------------------------------------------------- /test/sndhtimerc-verify.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include "test/report.h" 4 | #include "test/verify.h" 5 | #include "test/sndhtimerc.h" 6 | 7 | #include "test/sndhtimer-verify.h" 8 | -------------------------------------------------------------------------------- /test/sndhtimerd-verify.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include "test/report.h" 4 | #include "test/verify.h" 5 | #include "test/sndhtimerd.h" 6 | 7 | #include "test/sndhtimer-verify.h" 8 | -------------------------------------------------------------------------------- /test/sndhtimervbl-verify.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include "test/report.h" 4 | #include "test/verify.h" 5 | #include "test/sndhtimervbl.h" 6 | 7 | #include "test/sndhtimer-verify.h" 8 | -------------------------------------------------------------------------------- /include/vt/vt52.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef VT_VT52_H 7 | #define VT_VT52_H 8 | 9 | #include "vt-cmd.h" 10 | 11 | extern const struct vt_cmd vt52; 12 | 13 | #endif /* VT_VT52_H */ 14 | -------------------------------------------------------------------------------- /include/audio/wave-reader.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef PSGPLAY_WAVE_READER_H 4 | #define PSGPLAY_WAVE_READER_H 5 | 6 | #include "audio/reader.h" 7 | 8 | extern const struct audio_reader wave_reader; 9 | 10 | #endif /* PSGPLAY_WAVE_READER_H */ 11 | -------------------------------------------------------------------------------- /include/audio/wave-writer.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef PSGPLAY_WAVE_WRITER_H 4 | #define PSGPLAY_WAVE_WRITER_H 5 | 6 | #include "audio/writer.h" 7 | 8 | extern const struct audio_writer wave_writer; 9 | 10 | #endif /* PSGPLAY_WAVE_WRITER_H */ 11 | -------------------------------------------------------------------------------- /include/vt/ecma48.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef VT_ECMA48_H 7 | #define VT_ECMA48_H 8 | 9 | #include "vt-cmd.h" 10 | 11 | extern const struct vt_cmd ecma48; 12 | 13 | #endif /* VT_ECMA48_H */ 14 | -------------------------------------------------------------------------------- /include/atari/fdc.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_FDC_H 7 | #define ATARI_FDC_H 8 | 9 | #include "atari/bus.h" 10 | 11 | extern const struct device fdc_device; 12 | 13 | #endif /* ATARI_FDC_H */ 14 | -------------------------------------------------------------------------------- /include/m68k/m68kds.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2021 Fredrik Noring 4 | */ 5 | 6 | #ifndef M68KDS_H 7 | #define M68KDS_H 8 | 9 | #include "m68kda.h" 10 | 11 | extern struct m68kda_elements m68kds_motorola; 12 | 13 | #endif /* M68KDS_H */ 14 | -------------------------------------------------------------------------------- /include/atari/rom.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_ROM_H 7 | #define ATARI_ROM_H 8 | 9 | #include "atari/device.h" 10 | 11 | extern const struct device rom_device; 12 | 13 | #endif /* ATARI_ROM_H */ 14 | -------------------------------------------------------------------------------- /test/sndhtimera.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | 5 | #include "test/sndhtimera.h" 6 | 7 | sndh_title("SNDH timer A " XSTR(SNDH_TIMER_FREQUENCY) " Hz"); 8 | sndh_timer(SNDH_TIMER_A, SNDH_TIMER_FREQUENCY); 9 | 10 | #include "test/sndhtimer.h" 11 | -------------------------------------------------------------------------------- /test/sndhtimerb.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | 5 | #include "test/sndhtimerb.h" 6 | 7 | sndh_title("SNDH timer B " XSTR(SNDH_TIMER_FREQUENCY) " Hz"); 8 | sndh_timer(SNDH_TIMER_B, SNDH_TIMER_FREQUENCY); 9 | 10 | #include "test/sndhtimer.h" 11 | -------------------------------------------------------------------------------- /test/sndhtimerc.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | 5 | #include "test/sndhtimerc.h" 6 | 7 | sndh_title("SNDH timer C " XSTR(SNDH_TIMER_FREQUENCY) " Hz"); 8 | sndh_timer(SNDH_TIMER_C, SNDH_TIMER_FREQUENCY); 9 | 10 | #include "test/sndhtimer.h" 11 | -------------------------------------------------------------------------------- /test/sndhtimerd.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | 5 | #include "test/sndhtimerd.h" 6 | 7 | sndh_title("SNDH timer D " XSTR(SNDH_TIMER_FREQUENCY) " Hz"); 8 | sndh_timer(SNDH_TIMER_D, SNDH_TIMER_FREQUENCY); 9 | 10 | #include "test/sndhtimer.h" 11 | -------------------------------------------------------------------------------- /include/audio/portaudio-writer.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef PSGPLAY_PORTAUDIO_WRITER_H 4 | #define PSGPLAY_PORTAUDIO_WRITER_H 5 | 6 | #include "audio/writer.h" 7 | 8 | extern const struct audio_writer portaudio_output; 9 | 10 | #endif /* PSGPLAY_PORTAUDIO_WRITER_H */ 11 | -------------------------------------------------------------------------------- /test/sndhtimervbl.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | 5 | #include "test/sndhtimervbl.h" 6 | 7 | sndh_title("SNDH timer VBL " XSTR(SNDH_TIMER_FREQUENCY) " Hz"); 8 | sndh_timer(SNDH_TIMER_VBL, SNDH_TIMER_FREQUENCY); 9 | 10 | #include "test/sndhtimer.h" 11 | -------------------------------------------------------------------------------- /include/atari/m68k.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_M68K_H 7 | #define ATARI_M68K_H 8 | 9 | int m68k_int_ack_callback(int level); 10 | 11 | void m68k_instruction_callback(int pc); 12 | 13 | #endif /* ATARI_M68K_H */ 14 | -------------------------------------------------------------------------------- /include/atari/dac.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef ATARI_DAC_H 4 | #define ATARI_DAC_H 5 | 6 | #include "internal/types.h" 7 | 8 | struct cf2149_dac { 9 | uint16_t lvl[32][32][32]; 10 | }; 11 | 12 | const struct cf2149_dac *cf2149_atari_st_dac(); 13 | 14 | #endif /* ATARI_DAC_H */ 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "toslibc"] 2 | path = lib/toslibc 3 | url = ../toslibc 4 | [submodule "lib/cf2149"] 5 | path = lib/cf2149 6 | url = ../cf2149 7 | [submodule "lib/cf68901"] 8 | path = lib/cf68901 9 | url = ../cf68901 10 | [submodule "lib/cf300588"] 11 | path = lib/cf300588 12 | url = ../cf300588 13 | -------------------------------------------------------------------------------- /include/graph/svg.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_GRAPH_SVG_H 7 | #define PSGPLAY_GRAPH_SVG_H 8 | 9 | #include "graph/graph.h" 10 | 11 | extern const struct graph_encoder_module svg_encoder; 12 | 13 | #endif /* PSGPLAY_GRAPH_SVG_H */ 14 | -------------------------------------------------------------------------------- /include/system/unix/remake.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2020 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_UNIX_REMAKE_H 7 | #define PSGPLAY_SYSTEM_UNIX_REMAKE_H 8 | 9 | void remake_header(const void *data, size_t size); 10 | 11 | #endif /* PSGPLAY_SYSTEM_UNIX_REMAKE_H */ 12 | -------------------------------------------------------------------------------- /include/atari/mfp-register.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_MFP_REGISTER_H 7 | #define ATARI_MFP_REGISTER_H 8 | 9 | #include "cf68901/module/cf68901.h" 10 | 11 | #define MFP_TIMER_FREQUENCY 2457600 /* Hz */ 12 | 13 | #endif /* ATARI_MFP_REGISTER_H */ 14 | -------------------------------------------------------------------------------- /include/system/unix/info.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_UNIX_INFO_H 7 | #define PSGPLAY_SYSTEM_UNIX_INFO_H 8 | 9 | #include "system/unix/file.h" 10 | 11 | void sndh_print(struct file file); 12 | 13 | #endif /* PSGPLAY_SYSTEM_UNIX_INFO_H */ 14 | -------------------------------------------------------------------------------- /include/psgplay/version.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef PSGPLAY_VERSION_H 4 | #define PSGPLAY_VERSION_H 5 | 6 | /** 7 | * psgplay_version - return PSG play version 8 | * 9 | * Return: version string of the PSG play library 10 | */ 11 | const char *psgplay_version(void); 12 | 13 | #endif /* PSGPLAY_VERSION_H */ 14 | -------------------------------------------------------------------------------- /include/test/maxamp.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_MAXAMP_H 7 | #define PSGPLAY_TEST_MAXAMP_H 8 | 9 | #define tune_value_time_names(t) \ 10 | t(0, 4, "PSG and DMA maximum amplitude square waves") 11 | 12 | #endif /* PSGPLAY_TEST_MAXAMP_H */ 13 | -------------------------------------------------------------------------------- /include/text/load.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEXT_LOAD_H 7 | #define PSGPLAY_TEXT_LOAD_H 8 | 9 | #include "internal/types.h" 10 | 11 | #include "text/mode.h" 12 | 13 | extern const struct text_mode text_mode_load; 14 | 15 | #endif /* PSGPLAY_TEXT_LOAD_H */ 16 | -------------------------------------------------------------------------------- /include/text/main.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEXT_MAIN_H 7 | #define PSGPLAY_TEXT_MAIN_H 8 | 9 | #include "internal/types.h" 10 | 11 | #include "text/mode.h" 12 | 13 | extern const struct text_mode text_mode_main; 14 | 15 | #endif /* PSGPLAY_TEXT_MAIN_H */ 16 | -------------------------------------------------------------------------------- /include/atari/mfp.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_MFP_H 7 | #define ATARI_MFP_H 8 | 9 | #include "bus.h" 10 | 11 | u32 mfp_irq_vector(void); 12 | 13 | void dma_sound_active(bool level); 14 | 15 | extern const struct device mfp_device; 16 | 17 | #endif /* ATARI_MFP_H */ 18 | -------------------------------------------------------------------------------- /include/atari/glue.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_GLUE_H 7 | #define ATARI_GLUE_H 8 | 9 | #include "atari/device.h" 10 | 11 | void glue_irq_set(int irq); 12 | void glue_irq_clr(int irq); 13 | 14 | extern const struct device glue_device; 15 | 16 | #endif /* ATARI_GLUE_H */ 17 | -------------------------------------------------------------------------------- /include/system/atari/clock.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_ATARI_CLOCK_H 7 | #define PSGPLAY_SYSTEM_ATARI_CLOCK_H 8 | 9 | #include "internal/types.h" 10 | 11 | void clock_tick(void); 12 | 13 | u64 clock_ms(void); 14 | 15 | #endif /* PSGPLAY_SYSTEM_ATARI_CLOCK_H */ 16 | -------------------------------------------------------------------------------- /include/system/unix/print.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_UNIX_PRINT_H 7 | #define PSGPLAY_SYSTEM_UNIX_PRINT_H 8 | 9 | #include 10 | 11 | void pr_mem(FILE *f, const void *data, size_t size, size_t offset); 12 | 13 | #endif /* PSGPLAY_SYSTEM_UNIX_PRINT_H */ 14 | -------------------------------------------------------------------------------- /include/system/unix/sndh.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_UNIX_SNDH_H 7 | #define PSGPLAY_SYSTEM_UNIX_SNDH_H 8 | 9 | #include 10 | #include 11 | 12 | struct file sndh_read_file(const char *path); 13 | 14 | #endif /* PSGPLAY_SYSTEM_UNIX_SNDH_H */ 15 | -------------------------------------------------------------------------------- /include/atari/irq.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_IRQ_H 7 | #define ATARI_IRQ_H 8 | 9 | #include "m68k/m68k.h" 10 | 11 | #include "atari/bus.h" 12 | 13 | #define IRQ_HBL M68K_IRQ_2 14 | #define IRQ_VBL M68K_IRQ_4 15 | #define IRQ_MFP M68K_IRQ_6 16 | 17 | #endif /* ATARI_IRQ_H */ 18 | -------------------------------------------------------------------------------- /include/system/unix/diagnostic.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_UNIX_DIAGNOSTIC_H 7 | #define PSGPLAY_SYSTEM_UNIX_DIAGNOSTIC_H 8 | 9 | #include "system/unix/file.h" 10 | 11 | void sndh_diagnostic(struct file file); 12 | 13 | #endif /* PSGPLAY_SYSTEM_UNIX_DIAGNOSTIC_H */ 14 | -------------------------------------------------------------------------------- /system/atari/clock.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2020 Fredrik Noring 4 | */ 5 | 6 | #include 7 | 8 | #include "system/atari/clock.h" 9 | 10 | static u64 now; 11 | 12 | void clock_tick(void) 13 | { 14 | now += 5; /* 200 Hz clock tick is 5 ms */ 15 | } 16 | 17 | u64 clock_ms(void) 18 | { 19 | return now; 20 | } 21 | -------------------------------------------------------------------------------- /include/system/atari/psg.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_ATARI_PSG_H 7 | #define PSGPLAY_SYSTEM_ATARI_PSG_H 8 | 9 | #include "internal/types.h" 10 | 11 | void psg_init(void); 12 | 13 | u8 psg_mute(void); 14 | 15 | void psg_unmute(u8 iomix); 16 | 17 | #endif /* PSGPLAY_SYSTEM_ATARI_PSG_H */ 18 | -------------------------------------------------------------------------------- /lib/audio/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | ifeq (1,$(ALSA)) 4 | HAVE_CFLAGS += -DHAVE_ALSA 5 | endif 6 | 7 | ifeq (1,$(PORTAUDIO)) 8 | HAVE_CFLAGS += -DHAVE_PORTAUDIO 9 | endif 10 | 11 | AUDIO_SRC := $(addprefix lib/audio/, \ 12 | alsa-writer.c \ 13 | portaudio-writer.c \ 14 | wave-reader.c \ 15 | wave-writer.c \ 16 | audio.c) 17 | -------------------------------------------------------------------------------- /include/atari/system-variable.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_SYSTEM_VARIABLE_H 7 | #define ATARI_SYSTEM_VARIABLE_H 8 | 9 | #include "internal/types.h" 10 | 11 | #include "toslibc/tos/system-variable.h" 12 | 13 | const char *system_variable_label(u32 address); 14 | 15 | #endif /* ATARI_SYSTEM_VARIABLE_H */ 16 | -------------------------------------------------------------------------------- /system/atari/timer.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2020 Fredrik Noring 4 | */ 5 | 6 | #include 7 | 8 | #include 9 | 10 | DEFINE_XBRA("PSGP", timer_c); 11 | 12 | void timer_init(bool (*f)(uint32_t vector, 13 | struct xbra_regs *regs, void *arg), void *arg) 14 | { 15 | atexit(timer_c_exit); 16 | 17 | timer_c_init(69, f, NULL, arg); 18 | } 19 | -------------------------------------------------------------------------------- /include/system/unix/memory.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef PSGPLAY_SYSTEM_UNIX_MEMORY_H 4 | #define PSGPLAY_SYSTEM_UNIX_MEMORY_H 5 | 6 | #include 7 | 8 | void *xmalloc(size_t size); 9 | 10 | void *zalloc(size_t size); 11 | 12 | void *xrealloc(void *ptr, size_t size); 13 | 14 | void *xmemdup(const void *ptr, size_t size); 15 | 16 | #endif /* PSGPLAY_SYSTEM_UNIX_MEMORY_H */ 17 | -------------------------------------------------------------------------------- /include/system/atari/option.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_ATARI_OPTION_H 7 | #define PSGPLAY_SYSTEM_ATARI_OPTION_H 8 | 9 | extern const char *progname; 10 | 11 | struct options { 12 | const char *input; 13 | }; 14 | 15 | struct options *parse_options(int argc, char **argv); 16 | 17 | #endif /* PSGPLAY_SYSTEM_ATARI_OPTION_H */ 18 | -------------------------------------------------------------------------------- /script/tos: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -o pipefail 5 | 6 | if [ $# != 2 ] 7 | then 8 | echo "usage: $0 " >&2 9 | exit 1 10 | fi 11 | 12 | src="$1" 13 | dst="$2" 14 | 15 | hex() { 16 | od -A n -vt x1 "$dst".tmp 20 | hex <"$src" >>"$dst".tmp 21 | echo '};' >>"$dst".tmp 22 | mv "$dst".tmp "$dst" 23 | -------------------------------------------------------------------------------- /include/system/atari/timer.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_ATARI_TIMER_H 7 | #define PSGPLAY_SYSTEM_ATARI_TIMER_H 8 | 9 | #include 10 | 11 | #include "internal/types.h" 12 | 13 | void timer_init(bool (*f)(uint32_t vector, 14 | struct xbra_regs *regs, void *arg), void *arg); 15 | 16 | #endif /* PSGPLAY_SYSTEM_ATARI_TIMER_H */ 17 | -------------------------------------------------------------------------------- /include/atari/ram.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_RAM_H 7 | #define ATARI_RAM_H 8 | 9 | #include "atari/bus.h" 10 | 11 | struct ram_map_ro { 12 | size_t size; 13 | uint32_t addr; 14 | const void *p; 15 | }; 16 | 17 | struct ram_map_ro ram_map_ro(uint32_t size, uint32_t addr); 18 | 19 | extern const struct device ram_device; 20 | 21 | #endif /* ATARI_RAM_H */ 22 | -------------------------------------------------------------------------------- /include/test/sndhfrms.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_SNDHFRMS_H 7 | #define PSGPLAY_TEST_SNDHFRMS_H 8 | 9 | #include "toslibc/asm/machine.h" 10 | 11 | #include "internal/macro.h" 12 | 13 | #define tune_value_frames_names(t) \ 14 | t(0, 1, "FRMS 1 frame") \ 15 | t(0, 1, "FRMS 1 frame indefinite init") 16 | 17 | #endif /* PSGPLAY_TEST_SNDHFRMS_H */ 18 | -------------------------------------------------------------------------------- /include/atari/cpu.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_CPU_H 7 | #define ATARI_CPU_H 8 | 9 | #include "atari/device.h" 10 | 11 | void m68k_instruction_callback(int pc); 12 | 13 | void cpu_instruction_callback(void (*cb)(uint32_t pc, void *arg), void *arg); 14 | 15 | u64 cpu_cycles_run(void); 16 | 17 | extern const struct device cpu_device; 18 | 19 | #endif /* ATARI_CPU_H */ 20 | -------------------------------------------------------------------------------- /include/system/unix/poll-fifo.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef SYSTEM_UNIX_POLL_INFO_H 7 | #define SYSTEM_UNIX_POLL_INFO_H 8 | 9 | #include "internal/fifo.h" 10 | 11 | struct poll_fifo { 12 | int fd; 13 | struct fifo *in; 14 | struct fifo *out; 15 | }; 16 | 17 | bool poll_fifo(const struct poll_fifo *pfs, size_t n, int timeout); 18 | 19 | #endif /* SYSTEM_UNIX_POLL_INFO_H */ 20 | -------------------------------------------------------------------------------- /include/test/sndhtimera.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_SNDHTIMERA_H 7 | #define PSGPLAY_TEST_SNDHTIMERA_H 8 | 9 | #include "internal/macro.h" 10 | 11 | #define SNDH_TIMER_FREQUENCY 16 12 | 13 | #define tune_value_time_names(t) \ 14 | t(SNDH_TIMER_FREQUENCY, 63, "SNDH timer A " XSTR(SNDH_TIMER_FREQUENCY) " Hz") 15 | 16 | #endif /* PSGPLAY_TEST_SNDHTIMERA_H */ 17 | -------------------------------------------------------------------------------- /include/test/sndhtimerb.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_SNDHTIMERB_H 7 | #define PSGPLAY_TEST_SNDHTIMERB_H 8 | 9 | #include "internal/macro.h" 10 | 11 | #define SNDH_TIMER_FREQUENCY 75 12 | 13 | #define tune_value_time_names(t) \ 14 | t(SNDH_TIMER_FREQUENCY, 63, "SNDH timer B " XSTR(SNDH_TIMER_FREQUENCY) " Hz") 15 | 16 | #endif /* PSGPLAY_TEST_SNDHTIMERB_H */ 17 | -------------------------------------------------------------------------------- /include/test/sndhtimerc.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_SNDHTIMERC_H 7 | #define PSGPLAY_TEST_SNDHTIMERC_H 8 | 9 | #include "internal/macro.h" 10 | 11 | #define SNDH_TIMER_FREQUENCY 200 12 | 13 | #define tune_value_time_names(t) \ 14 | t(SNDH_TIMER_FREQUENCY, 63, "SNDH timer C " XSTR(SNDH_TIMER_FREQUENCY) " Hz") 15 | 16 | #endif /* PSGPLAY_TEST_SNDHTIMERC_H */ 17 | -------------------------------------------------------------------------------- /include/test/sndhtimerd.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_SNDHTIMERD_H 7 | #define PSGPLAY_TEST_SNDHTIMERD_H 8 | 9 | #include "internal/macro.h" 10 | 11 | #define SNDH_TIMER_FREQUENCY 480 12 | 13 | #define tune_value_time_names(t) \ 14 | t(SNDH_TIMER_FREQUENCY, 63, "SNDH timer D " XSTR(SNDH_TIMER_FREQUENCY) " Hz") 15 | 16 | #endif /* PSGPLAY_TEST_SNDHTIMERD_H */ 17 | -------------------------------------------------------------------------------- /include/unicode/atari.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef PSGPLAY_UNICODE_ATARI_ST_H 4 | #define PSGPLAY_UNICODE_ATARI_ST_H 5 | 6 | #include "internal/types.h" 7 | 8 | #include "unicode/utf8.h" 9 | 10 | unicode_t charset_atari_st_to_utf32(u8 c, void *arg); 11 | 12 | u8 utf32_to_charset_atari_st(unicode_t u, void *arg); 13 | 14 | bool utf8_valid_in_atari_st(const u8 *u, size_t length); 15 | 16 | #endif /* PSGPLAY_UNICODE_ATARI_ST_H */ 17 | -------------------------------------------------------------------------------- /lib/atari/exception-vector.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include "atari/exception-vector.h" 7 | 8 | const char *exception_vector_description(u32 address) 9 | { 10 | switch (address >> 2) { 11 | #define EXCEPTION_VECTOR_DESCRIPTION(vector_, description_) \ 12 | case vector_: return description_; 13 | EXCEPTION_VECTOR(EXCEPTION_VECTOR_DESCRIPTION) 14 | default: return ""; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /script/pkg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -o pipefail 5 | 6 | gen_pkg() 7 | { 8 | echo "prefix=$prefix 9 | libdir=$libdir 10 | includedir=$includedir 11 | 12 | Name: psgplay 13 | Description: PSG play is a music player for Atari ST YM2149 SNDH files 14 | Version: ${PSGPLAY_VERSION_MINOR}"' 15 | 16 | Libs: -L${libdir} -lpsgplay 17 | Cflags: -I${includedir}' 18 | } 19 | 20 | if [ $# = 1 ] 21 | then 22 | gen_pkg >"$1".tmp 23 | mv "$1".tmp "$1" 24 | else 25 | gen_pkg 26 | fi 27 | -------------------------------------------------------------------------------- /script/sndh.ld: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | 3 | OUTPUT_FORMAT("binary") 4 | 5 | SECTIONS 6 | { 7 | .text : ALIGN(4) { 8 | *(.text) 9 | *(.text.*) 10 | } 11 | 12 | .rodata : ALIGN(4) { 13 | *(.rodata) 14 | *(.rodata.*) 15 | } 16 | 17 | .data : ALIGN(4) { 18 | *(.data) 19 | *(.data.*) 20 | } 21 | 22 | .bss : ALIGN(4) { 23 | *(.bss) 24 | *(.bss.*) 25 | } 26 | 27 | /DISCARD/ : { 28 | *(.comment) 29 | *(.debug*) 30 | *(.note.*) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /include/system/unix/disassemble.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_UNIX_DISASSEMBLE_H 7 | #define PSGPLAY_SYSTEM_UNIX_DISASSEMBLE_H 8 | 9 | #include "system/unix/file.h" 10 | #include "system/unix/option.h" 11 | 12 | void sndh_disassemble(struct options *options, struct file file); 13 | 14 | void sndh_trace(struct options *options, struct file file); 15 | 16 | #endif /* PSGPLAY_SYSTEM_UNIX_DISASSEMBLE_H */ 17 | -------------------------------------------------------------------------------- /include/test/verify.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_VERIFY_H 7 | #define PSGPLAY_TEST_VERIFY_H 8 | 9 | #include "audio/audio.h" 10 | 11 | #include "test/option.h" 12 | 13 | #define verify_assert(expr) if (!(expr)) 14 | 15 | const char *verify(const struct audio *audio, const struct options *options); 16 | 17 | const char *flags(const struct options *options); 18 | 19 | #endif /* PSGPLAY_TEST_VERIFY_H */ 20 | -------------------------------------------------------------------------------- /include/atari/psg.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_PSG_H 7 | #define ATARI_PSG_H 8 | 9 | #include "toslibc/asm/machine.h" 10 | 11 | #include "atari/bus.h" 12 | #include "atari/sample.h" 13 | 14 | #define PSG_FREQUENCY (ATARI_STE_EXT_OSC / ATARI_STE_SND_PSG_CLK_DIV) 15 | 16 | extern const struct device psg_device; 17 | 18 | void psg_sample(psg_sample_f sample, void *sample_arg); 19 | 20 | #endif /* ATARI_PSG_H */ 21 | -------------------------------------------------------------------------------- /include/audio/reader.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_AUDIO_READER_H 7 | #define PSGPLAY_AUDIO_READER_H 8 | 9 | #include "audio/audio.h" 10 | 11 | struct audio_reader { 12 | void *(*open)(const char *path); 13 | struct audio_format (*format)(void *arg); 14 | size_t (*sample)(struct audio_sample *samples, size_t count, void *arg); 15 | void (*close)(void *arg); 16 | }; 17 | 18 | #endif /* PSGPLAY_AUDIO_READER_H */ 19 | -------------------------------------------------------------------------------- /include/atari/sound.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2020 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_SOUND_H 7 | #define ATARI_SOUND_H 8 | 9 | #include "atari/bus.h" 10 | #include "atari/sample.h" 11 | 12 | extern const struct device sound_device; 13 | 14 | void sound_sample(sound_sample_f sample, void *sample_arg); 15 | 16 | void record_sample(record_sample_f record, void *record_arg); 17 | 18 | void sound_check(u32 bus_address); 19 | 20 | #endif /* ATARI_SOUND_H */ 21 | -------------------------------------------------------------------------------- /include/system/atari/model.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_ATARI_MODEL_H 7 | #define PSGPLAY_SYSTEM_ATARI_MODEL_H 8 | 9 | #include "psgplay/sndh.h" 10 | 11 | #include "text/mvc.h" 12 | 13 | void model_timer(struct text_sndh *sndh); 14 | 15 | void model_update(struct text_state *model, const struct text_state *ctrl, 16 | struct text_sndh *sndh, u64 timestamp); 17 | 18 | #endif /* PSGPLAY_SYSTEM_ATARI_MODEL_H */ 19 | -------------------------------------------------------------------------------- /system/atari/print.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "internal/macro.h" 9 | #include "internal/print.h" 10 | 11 | #include "system/atari/option.h" 12 | 13 | void NORETURN pr_bug(const char *file, int line, 14 | const char *func, const char *expr) 15 | { 16 | printf("%s: BUG: %s:%d: %s: %s\n", 17 | progname, file, line, func, expr); 18 | 19 | gemdos_cconin(); 20 | 21 | exit(EXIT_FAILURE); 22 | } 23 | -------------------------------------------------------------------------------- /script/test-tune: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -o pipefail 5 | 6 | list_file_tunes() 7 | { 8 | local f="$(echo "$f" | sed 's/\..*$//')" 9 | 10 | cat "$1" | 11 | sed '/^ t(.*)[ \\]*$/!d' | 12 | sed 's/^[^"]*"//;s/".*$//' | 13 | awk '{ print "'"$f"'" " " NR " " $0 }' 14 | } 15 | 16 | list_all_tunes() 17 | { 18 | for f in "$@" 19 | do 20 | list_file_tunes "$f" 21 | done 22 | } 23 | 24 | cmd_list() 25 | { 26 | list_all_tunes "$@" | sed 's/ /-/;s/ .*$//' 27 | } 28 | 29 | cmd="$1" 30 | shift 31 | cmd_"$cmd" "$@" 32 | -------------------------------------------------------------------------------- /include/system/unix/text-mode.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_UNIX_TEXT_MODE_H 7 | #define PSGPLAY_SYSTEM_UNIX_TEXT_MODE_H 8 | 9 | #include "internal/types.h" 10 | 11 | #include "audio/writer.h" 12 | 13 | #include "system/unix/file.h" 14 | #include "system/unix/option.h" 15 | 16 | void text_replay(const struct options *options, struct file file, 17 | const struct audio_writer *output); 18 | 19 | #endif /* PSGPLAY_SYSTEM_UNIX_TEXT_MODE_H */ 20 | -------------------------------------------------------------------------------- /lib/version/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | PSGPLAY_VERSION := $(shell script/version) 4 | PSGPLAY_VERSION_MINOR := $(shell echo '$(PSGPLAY_VERSION)' | sed 's/-.*$$//') 5 | PSGPLAY_VERSION_MAJOR := $(shell echo '$(PSGPLAY_VERSION)' | sed 's/\..*$$//') 6 | 7 | export PSGPLAY_VERSION_MINOR PSGPLAY_VERSION_MAJOR 8 | 9 | VERSION_SRC = lib/version/version.c 10 | 11 | .PHONY: $(shell script/version --check $(VERSION_SRC)) 12 | $(VERSION_SRC): 13 | $(QUIET_GEN)script/version $@ 14 | 15 | OTHER_CLEAN += $(VERSION_SRC) 16 | -------------------------------------------------------------------------------- /include/system/unix/command-mode.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_UNIX_COMMAND_MODE_H 7 | #define PSGPLAY_SYSTEM_UNIX_COMMAND_MODE_H 8 | 9 | #include "internal/types.h" 10 | 11 | #include "audio/writer.h" 12 | 13 | #include "system/unix/file.h" 14 | #include "system/unix/option.h" 15 | 16 | void command_replay(const struct options *options, struct file file, 17 | const struct audio_writer *output); 18 | 19 | #endif /* PSGPLAY_SYSTEM_UNIX_COMMAND_MODE_H */ 20 | -------------------------------------------------------------------------------- /include/audio/writer.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_WRITER_H 7 | #define PSGPLAY_WRITER_H 8 | 9 | #include "internal/types.h" 10 | 11 | struct audio_writer { 12 | void *(*open)(const char *output, int frequency, 13 | bool nonblocking, size_t sample_length); 14 | bool (*sample)(s16 left, s16 right, void *arg); 15 | bool (*pause)(void *arg); 16 | bool (*resume)(void *arg); 17 | void (*flush)(void *arg); 18 | void (*drop)(void *arg); 19 | void (*close)(void *arg); 20 | }; 21 | 22 | #endif /* PSGPLAY_WRITER_H */ 23 | -------------------------------------------------------------------------------- /include/system/unix/clock.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef SYSTEM_UNIX_CLOCK_H 7 | #define SYSTEM_UNIX_CLOCK_H 8 | 9 | #include "internal/types.h" 10 | 11 | void clock_init(void); 12 | 13 | void clock_suspend(void); 14 | 15 | void clock_resume(void); 16 | 17 | void clock_update(void); 18 | 19 | u32 clock_s(void); 20 | 21 | u64 clock_ms(void); 22 | 23 | void clock_request_s(const u32 timestamp); 24 | 25 | void clock_request_ms(const u64 timestamp); 26 | 27 | int clock_poll(void); 28 | 29 | #endif /* SYSTEM_UNIX_CLOCK_H */ 30 | -------------------------------------------------------------------------------- /include/atari/mmu.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2020 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_MMU_H 7 | #define ATARI_MMU_H 8 | 9 | #include "internal/types.h" 10 | 11 | u8 dma_read_memory_8(u32 bus_address); 12 | 13 | u16 dma_read_memory_16(u32 bus_address); 14 | 15 | u8 probe_read_memory_8(u32 bus_address); 16 | 17 | u16 probe_read_memory_16(u32 bus_address); 18 | 19 | void probe_copy_memory_8(void *buffer, u32 bus_address, size_t byte_count); 20 | 21 | void probe_copy_memory_16(void *buffer, u32 bus_address, size_t word_count); 22 | 23 | #endif /* ATARI_MMU_H */ 24 | -------------------------------------------------------------------------------- /include/atari/mmu-trace.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_MMU_TRACE_H 7 | #define ATARI_MMU_TRACE_H 8 | 9 | #include "internal/types.h" 10 | 11 | #include "atari/device.h" 12 | 13 | void mmu_trace_rd_u8(u32 dev_address, u32 value, const struct device *bd); 14 | void mmu_trace_rd_u16(u32 dev_address, u32 value, const struct device *bd); 15 | void mmu_trace_wr_u8(u32 dev_address, u32 value, const struct device *bd); 16 | void mmu_trace_wr_u16(u32 dev_address, u32 value, const struct device *bd); 17 | 18 | #endif /* ATARI_MMU_TRACE_H */ 19 | -------------------------------------------------------------------------------- /include/atari/mfp-map.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_MFP_MAP_H 7 | #define ATARI_MFP_MAP_H 8 | 9 | #include "mfp-register.h" 10 | 11 | #define MFP_BUS_ADDRESS 0xfffa00 12 | 13 | struct mfp_map { 14 | #define MFP_REG_MAP(register_, symbol_, label_, description_) \ 15 | u8 : 8; \ 16 | __volatile__ struct cf68901_##symbol_ symbol_; 17 | CF68901_REGISTERS(MFP_REG_MAP) 18 | }; 19 | 20 | static inline struct mfp_map *mfp_map(void) 21 | { 22 | return (struct mfp_map *)MFP_BUS_ADDRESS; 23 | } 24 | 25 | #endif /* ATARI_MFP_MAP_H */ 26 | -------------------------------------------------------------------------------- /include/test/sndhtimer.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_TIMER_SNDH_H 7 | #define PSGPLAY_TEST_TIMER_SNDH_H 8 | 9 | sndh_tune_value_time_names(double, tune_value_time_names); 10 | 11 | #include "test/snd-dma-alt.h" 12 | 13 | static struct snd_dma_alt_sample sample[1]; 14 | 15 | void sndh_init(int tune) 16 | { 17 | snd_dma_alt_init(sample); 18 | } 19 | 20 | void sndh_play() 21 | { 22 | snd_dma_alt_play(sample); 23 | } 24 | 25 | void sndh_exit() 26 | { 27 | snd_dma_alt_exit(sample); 28 | } 29 | 30 | #endif /* PSGPLAY_TEST_TIMER_SNDH_H */ 31 | -------------------------------------------------------------------------------- /include/test/sndhtimervbl.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_SNDHTIMERVBL_H 7 | #define PSGPLAY_TEST_SNDHTIMERVBL_H 8 | 9 | #include "toslibc/asm/machine.h" 10 | 11 | #include "internal/macro.h" 12 | 13 | #define SNDH_TIMER_FREQUENCY 50 14 | 15 | #define TIMER_FREQUENCY ((double)ATARI_STE_PAL_MCLK / \ 16 | (ATARI_STE_CPU_CLK_DIV * ATARI_STE_CYCLES_PER_VBL_PAL)) 17 | 18 | #define tune_value_time_names(t) \ 19 | t(TIMER_FREQUENCY, 63, "SNDH timer VBL " XSTR(SNDH_TIMER_FREQUENCY) " Hz") 20 | 21 | #endif /* PSGPLAY_TEST_SNDHTIMERVBL_H */ 22 | -------------------------------------------------------------------------------- /test/psgpitch.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | 6 | #include "test/psgpitch.h" 7 | 8 | sndh_title("PSG pitch"); 9 | sndh_tune_value_time_names(int, tune_value_time_names); 10 | 11 | void sndh_init(int tune) 12 | { 13 | const int pitch = sndh_tune_select_value(tune); 14 | 15 | snd_psg_wr_iomix(SND_PSG_IOMIX_OFF); 16 | snd_psg_wr_freq_a(pitch); 17 | snd_psg_wr_level_a(SND_PSG_LEVEL_MAX); 18 | } 19 | 20 | void sndh_play() 21 | { 22 | snd_psg_wr_iomix(SND_PSG_IOMIX_TONE_A); 23 | } 24 | 25 | void sndh_exit() 26 | { 27 | snd_psg_wr_iomix(SND_PSG_IOMIX_OFF); 28 | } 29 | -------------------------------------------------------------------------------- /include/audio/alsa-writer.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef PSGPLAY_ALSA_WRITER_H 4 | #define PSGPLAY_ALSA_WRITER_H 5 | 6 | #include "audio/writer.h" 7 | 8 | extern const struct audio_writer alsa_writer; 9 | 10 | /** 11 | * alsa_writer_handle - determine ALSA output handle 12 | * @output: output string 13 | * 14 | * Note: @output must be valid during the lifetime of the returned string. 15 | * 16 | * Return: %"default" if @output is %NULL, ALSA handle if @output has 17 | * %"alsa:" prefix, otherwise %NULL. 18 | */ 19 | const char *alsa_writer_handle(const char *output); 20 | 21 | #endif /* PSGPLAY_ALSA_WRITER_H */ 22 | -------------------------------------------------------------------------------- /include/atari/bus.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_BUS_H 7 | #define ATARI_BUS_H 8 | 9 | #include "internal/types.h" 10 | 11 | #include "atari/machine.h" 12 | #include "atari/device.h" 13 | 14 | u8 bus_error_rd_u8(const struct device *device, u32 address); 15 | u16 bus_error_rd_u16(const struct device *device, u32 address); 16 | void bus_error_wr_u8(const struct device *device, u32 address, u8 data); 17 | void bus_error_wr_u16(const struct device *device, u32 address, u16 data); 18 | 19 | extern const struct device bus_device_error; 20 | 21 | #endif /* ATARI_BUS_H */ 22 | -------------------------------------------------------------------------------- /lib/atari/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | lib/atari/ram.c lib/atari/rom.c: $(LIBPSGPLAY_TOS_HEADER) 4 | 5 | ATARI_SRC := $(addprefix lib/atari/, \ 6 | bus.c \ 7 | cpu.c \ 8 | dac.c \ 9 | device.c \ 10 | exception-vector.c \ 11 | fdc.c \ 12 | glue.c \ 13 | machine.c \ 14 | mfp.c \ 15 | mixer.c \ 16 | mmu.c \ 17 | mmu-trace.c \ 18 | psg.c \ 19 | ram.c \ 20 | rom.c \ 21 | shifter.c \ 22 | sound.c \ 23 | system-variable.c) \ 24 | $(CF2149_SRC) \ 25 | $(CF68901_SRC) \ 26 | $(CF300588_SOUND_SRC) 27 | -------------------------------------------------------------------------------- /lib/graph/graph.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #include 7 | 8 | #include "system/unix/memory.h" 9 | 10 | #include "graph/graph.h" 11 | 12 | struct graph_encoder *graph_encoder_init(struct graph_bounds bounds, 13 | struct graph_encoder_cb cb, const struct graph_encoder_module *module) 14 | { 15 | struct graph_encoder *encoder = zalloc(sizeof(*encoder)); 16 | 17 | encoder->bounds = bounds; 18 | encoder->cb = cb; 19 | encoder->module = module; 20 | 21 | return encoder; 22 | } 23 | 24 | void graph_encoder_free(struct graph_encoder * const encoder) 25 | { 26 | free(encoder); 27 | } 28 | -------------------------------------------------------------------------------- /include/test/option.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_OPTION_H 7 | #define PSGPLAY_TEST_OPTION_H 8 | 9 | #include 10 | 11 | extern const char *progname; 12 | 13 | struct options { 14 | char name[64]; 15 | 16 | int verbose; 17 | 18 | const char *command; 19 | const char *input; 20 | const char *output; 21 | 22 | int track; 23 | }; 24 | 25 | int option_verbosity(void); 26 | 27 | int track_from_path(const char *path); 28 | 29 | void name_from_input(); 30 | 31 | struct options *parse_options(int argc, char **argv); 32 | 33 | #endif /* PSGPLAY_TEST_OPTION_H */ 34 | -------------------------------------------------------------------------------- /lib/internal/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | INTERNAL_SRC := \ 4 | lib/internal/bit.c \ 5 | lib/internal/fifo.c \ 6 | lib/internal/print.c \ 7 | lib/internal/string.c 8 | 9 | ALL_OBJ += lib/internal/sso.o 10 | 11 | ifneq (clean,$(MAKECMDGOALS)) 12 | 13 | # Test whether the compiler supports scalar storage order. 14 | HAVE_SSO := $(shell $(HOST_CC) $(HOST_CFLAGS) -c -o lib/internal/sso.o lib/internal/sso.c 2>&1 && echo 1) 15 | 16 | ifeq (1,$(HAVE_SSO)) 17 | HAVE_CFLAGS += -DHAVE_SSO 18 | else 19 | $(warning WARNING: Disassembler disabled: The C compiler does not support __attribute__((__scalar_storage_order__("big-endian")))) 20 | endif 21 | 22 | endif 23 | -------------------------------------------------------------------------------- /include/atari/trace.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2021 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_TRACE_H 7 | #define ATARI_TRACE_H 8 | 9 | #include 10 | 11 | #define TRACE_DEVICE(dev) \ 12 | dev(all, ALL, 0) \ 13 | dev(cpu, CPU, 1) \ 14 | dev(reg, REG, 2) 15 | 16 | enum trace_device { 17 | #define TRACE_DEVICE_ENUM(symbol_, label_, id_) \ 18 | TRACE_DEVICE_##label_ = !id_ ? -1 : 1 << (id_ - 1), 19 | TRACE_DEVICE(TRACE_DEVICE_ENUM) 20 | }; 21 | 22 | #define TRACE_ENABLE(trace_mode_, label_) \ 23 | ((trace_mode_)->m & TRACE_DEVICE_ ## label_) 24 | 25 | struct trace_mode { 26 | uint32_t m; 27 | }; 28 | 29 | #endif /* ATARI_TRACE_H */ 30 | -------------------------------------------------------------------------------- /include/system/atari/ice_decrunch_inplace.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef PSGPLAY_SYSTEM_ATARI_ICE_H 4 | #define PSGPLAY_SYSTEM_ATARI_ICE_H 5 | 6 | #include 7 | #include 8 | 9 | #include "internal/types.h" 10 | 11 | struct ice_decrunch_inplace { 12 | u32 d[8]; 13 | void *a[7]; 14 | u32 d0; 15 | u8 save[120]; 16 | size_t size; 17 | }; 18 | 19 | struct ice_decrunch_inplace ice_decrunch_inplace_init( 20 | const void *data, size_t size); 21 | 22 | bool ice_decrunch_inplace_partial(void *data, 23 | struct ice_decrunch_inplace *context, size_t *offset); 24 | 25 | void ice_decrunch_inplace_all(void *data, size_t size); 26 | 27 | #endif /* PSGPLAY_SYSTEM_ATARI_ICE_H */ 28 | -------------------------------------------------------------------------------- /include/system/unix/tty.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_UNIX_TTY_H 7 | #define PSGPLAY_SYSTEM_UNIX_TTY_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "system/unix/file.h" 14 | 15 | struct tty_size { 16 | int rows; 17 | int cols; 18 | }; 19 | 20 | struct tty_events { 21 | void (*resize)(struct tty_size size, void *arg); 22 | void (*suspend)(void *arg); 23 | void (*resume)(void *arg); 24 | void *arg; 25 | }; 26 | 27 | struct tty_size tty_size(void); 28 | 29 | bool tty_present(void); 30 | 31 | bool tty_init(const struct tty_events *events); 32 | 33 | void tty_exit(void); 34 | 35 | #endif /* PSGPLAY_SYSTEM_UNIX_TTY_H */ 36 | -------------------------------------------------------------------------------- /include/test/tempo.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_TEMPO_H 7 | #define PSGPLAY_TEST_TEMPO_H 8 | 9 | struct timer_preset { 10 | int ctrl; 11 | int divisor; 12 | int count; 13 | }; 14 | 15 | #define T(c, d, n) { .ctrl = (c), .divisor = (d), .count = (n) } 16 | 17 | #define tune_value_time_names(t) \ 18 | t(T(7, 200, 11), 63, "1117 Hz timer A") \ 19 | t(T(6, 100, 17), 63, "1445 Hz timer A") \ 20 | t(T(5, 64, 19), 63, "2021 Hz timer A") \ 21 | t(T(4, 50, 23), 63, "2137 Hz timer A") \ 22 | t(T(3, 16, 47), 63, "3268 Hz timer A") \ 23 | t(T(2, 10, 53), 63, "4636 Hz timer A") \ 24 | t(T(1, 4, 73), 63, "8416 Hz timer A") 25 | 26 | #endif /* PSGPLAY_TEST_TEMPO_H */ 27 | -------------------------------------------------------------------------------- /system/atari/option.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "internal/macro.h" 13 | 14 | #include "system/atari/option.h" 15 | 16 | static struct options option; 17 | 18 | static void help(void) 19 | { 20 | printf("Usage: %s \r\n", progname); 21 | } 22 | 23 | static void NORETURN help_exit(int code) 24 | { 25 | help(); 26 | 27 | gemdos_cconin(); 28 | 29 | exit(code); 30 | } 31 | 32 | struct options *parse_options(int argc, char **argv) 33 | { 34 | if (argc != 2 || argv[1][0] == '-') 35 | help_exit(EXIT_FAILURE); 36 | 37 | option.input = argv[1]; 38 | 39 | return &option; 40 | } 41 | -------------------------------------------------------------------------------- /lib/test/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | PSGPLAY_LIB_TEST_CFLAGS = $(BASIC_HOST_CFLAGS) $(HOST_CFLAGS) 4 | 5 | PSGPLAY_LIB_TEST_VERIFY := lib/example/example-info 6 | 7 | PSGPLAY_LIB_TEST_SRC := \ 8 | $(AUDIO_SRC) \ 9 | $(GRAPH_SRC) \ 10 | $(IN_SRC) \ 11 | lib/internal/print.c \ 12 | lib/internal/string.c \ 13 | lib/test/option.c \ 14 | lib/test/report.c \ 15 | lib/test/verify.c \ 16 | system/unix/file.c \ 17 | system/unix/memory.c \ 18 | system/unix/print.c \ 19 | system/unix/string.c 20 | 21 | PSGPLAY_LIB_TEST_OBJ := $(PSGPLAY_LIB_TEST_SRC:%.c=%.o) 22 | 23 | ALL_OBJ += $(PSGPLAY_LIB_TEST_OBJ) 24 | 25 | $(PSGPLAY_LIB_TEST_OBJ): %.o: %.c 26 | $(QUIET_CC)$(HOST_CC) $(PSGPLAY_LIB_TEST_CFLAGS) -c -o $@ $< 27 | -------------------------------------------------------------------------------- /system/unix/memory.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | 6 | #include "internal/print.h" 7 | #include "internal/types.h" 8 | 9 | #include "system/unix/memory.h" 10 | 11 | void *xmalloc(size_t size) 12 | { 13 | void *p = malloc(size); 14 | 15 | if (!p) 16 | pr_fatal_errno("malloc"); 17 | 18 | return p; 19 | } 20 | 21 | void *zalloc(size_t size) 22 | { 23 | void *p = xmalloc(size); 24 | 25 | memset(p, 0, size); 26 | 27 | return p; 28 | } 29 | 30 | void *xrealloc(void *ptr, size_t size) 31 | { 32 | void *p = realloc(ptr, size); 33 | 34 | if (!p) 35 | pr_fatal_errno("realloc"); 36 | 37 | return p; 38 | } 39 | 40 | void *xmemdup(const void *ptr, size_t size) 41 | { 42 | void *p = xmalloc(size); 43 | 44 | memcpy(p, ptr, size); 45 | 46 | return p; 47 | } 48 | -------------------------------------------------------------------------------- /include/text/mvc.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEXT_MVC_H 7 | #define PSGPLAY_TEXT_MVC_H 8 | 9 | struct text_mode; 10 | 11 | struct text_state { 12 | const struct text_mode *mode; 13 | 14 | const char *path; 15 | int progress; 16 | 17 | int cursor; 18 | int track; 19 | enum { 20 | TRACK_STOP = 0, 21 | TRACK_PLAY, 22 | TRACK_PAUSE, 23 | TRACK_RESTART, 24 | } op; 25 | struct text_mixer { 26 | int volume; 27 | } mixer; 28 | u64 timestamp; 29 | u64 pause_offset; 30 | u64 pause_timestamp; 31 | 32 | bool redraw; 33 | bool quit; 34 | }; 35 | 36 | struct text_sndh { 37 | char title[40]; 38 | int subtune_count; 39 | 40 | const char *path; 41 | size_t size; 42 | const void *data; 43 | }; 44 | 45 | #endif /* PSGPLAY_TEXT_MVC_H */ 46 | -------------------------------------------------------------------------------- /test/sndhfrms.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | 6 | #include "test/sndhfrms.h" 7 | 8 | sndh_title("FRMS"); 9 | sndh_tune_value_frames_names(int, tune_value_frames_names); 10 | sndh_timer(SNDH_TIMER_C, 200); 11 | 12 | static void idle() 13 | { 14 | __asm__ __volatile__ ("stop #0x2200" : : : "cc"); 15 | } 16 | 17 | static void idle_indefinitely() 18 | { 19 | for (;;) 20 | idle(); 21 | } 22 | 23 | void sndh_init(int tune) 24 | { 25 | if (tune == 2) 26 | idle_indefinitely(); 27 | 28 | snd_psg_wr_iomix(SND_PSG_IOMIX_OFF); 29 | snd_psg_wr_freq_a(440); 30 | snd_psg_wr_level_a(SND_PSG_LEVEL_MAX); 31 | } 32 | 33 | void sndh_play() 34 | { 35 | snd_psg_wr_iomix(SND_PSG_IOMIX_TONE_A); 36 | } 37 | 38 | void sndh_exit() 39 | { 40 | snd_psg_wr_iomix(SND_PSG_IOMIX_OFF); 41 | } 42 | 43 | -------------------------------------------------------------------------------- /script/version: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -o pipefail 5 | 6 | if [ -f version ] 7 | then 8 | ver="$(cat version)" 9 | elif [ -e .git ] 10 | then 11 | mod=$([ -z "$(git status -suno)" ] || echo "+") 12 | tag="$(git describe --always --tags)" 13 | ver="$(echo "$tag" | sed 's/^v//')$mod" 14 | else 15 | ver="" 16 | fi 17 | 18 | if [ $# = 0 ] 19 | then 20 | echo "$ver" 21 | exit 22 | fi 23 | 24 | if [ x"$1" = x--check ] 25 | then 26 | check=1 27 | shift 28 | fi 29 | 30 | [ $# = 1 ] 31 | f="$1" 32 | 33 | src=$(echo "#include "'"'"psgplay/version.h"'"'" 34 | 35 | const char *psgplay_version(void) { return "'"'"$ver"'"'"; }") 36 | 37 | if [ ! -f "$f" ] || ! echo "$src" | cmp --quiet - "$f" 38 | then 39 | if [ x"$check" = x1 ] 40 | then 41 | echo "$f" 42 | else 43 | echo "$src" >"$f".tmp 44 | mv "$f".tmp "$f" 45 | fi 46 | else 47 | : 48 | fi 49 | -------------------------------------------------------------------------------- /include/internal/print.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef INTERNAL_PRINT_H 4 | #define INTERNAL_PRINT_H 5 | 6 | #include 7 | #include 8 | 9 | #include "internal/macro.h" 10 | 11 | void pr_warn(const char *fmt, ...) 12 | __attribute__((format(printf, 1, 2))); 13 | 14 | void pr_warn_errno(const char *s); 15 | 16 | void pr_errno(const char *s); 17 | 18 | void pr_error(const char *fmt, ...) 19 | __attribute__((format(printf, 1, 2))); 20 | 21 | void NORETURN pr_fatal_error(const char *fmt, ...) 22 | __attribute__((format(printf, 1, 2))); 23 | 24 | void NORETURN pr_fatal_errno(const char *s); 25 | 26 | void pr_bug_warn(const char *file, int line, 27 | const char *func, const char *fmt, ...); 28 | 29 | void NORETURN pr_bug(const char *file, int line, 30 | const char *func, const char *expr); 31 | 32 | #endif /* INTERNAL_PRINT_H */ 33 | -------------------------------------------------------------------------------- /lib/example/example-info.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Simple example on how to display SNDH tags using sndh_for_each_tag. 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include "internal/print.h" 10 | 11 | #include "psgplay/sndh.h" 12 | 13 | #include "system/unix/file.h" 14 | #include "system/unix/sndh.h" 15 | 16 | int main(int argc, char *argv[]) 17 | { 18 | if (argc <= 1) { 19 | fprintf(stderr, "usage: example-info ...\n"); 20 | 21 | return EXIT_FAILURE; 22 | } 23 | 24 | for (int arg = 1; arg < argc; arg++) { 25 | const char *path = argv[arg]; 26 | struct file f = sndh_read_file(path); 27 | 28 | if (!file_valid(f)) 29 | pr_fatal_errno(path); 30 | 31 | sndh_for_each_tag (f.data, f.size) 32 | printf("%s %s\n", sndh_tag_name, sndh_tag_value); 33 | 34 | file_free(f); 35 | } 36 | 37 | return EXIT_SUCCESS; 38 | } 39 | -------------------------------------------------------------------------------- /include/atari/sample.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_SAMPLE_H 7 | #define ATARI_SAMPLE_H 8 | 9 | #include "internal/types.h" 10 | 11 | struct cf2149_ac; 12 | 13 | typedef void (*psg_sample_f)( 14 | const struct cf2149_ac *sample, size_t count, void *arg); 15 | 16 | struct sound_sample { 17 | s16 left; 18 | s16 right; 19 | }; 20 | 21 | typedef void (*sound_sample_f)( 22 | const struct sound_sample *sample, size_t count, void *arg); 23 | 24 | struct mixer_sample { 25 | struct { 26 | s8 main; 27 | s8 left; 28 | s8 right; 29 | } volume; 30 | struct { 31 | s8 bass; 32 | s8 treble; 33 | } tone; 34 | bool mix; 35 | }; 36 | 37 | typedef void (*mixer_sample_f)( 38 | const struct mixer_sample *sample, size_t count, void *arg); 39 | 40 | typedef void (*record_sample_f)(uint64_t cycle, void *arg); 41 | 42 | #endif /* ATARI_SAMPLE_H */ 43 | -------------------------------------------------------------------------------- /lib/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | -include lib/toslibc/lib/Makefile 4 | -include lib/toslibc/tool/Makefile 5 | -include lib/toslibc/script/Makefile 6 | ifndef TOSLINK 7 | $(warning WARNING: Missing toslibc Git submodule; clone with the "--recurse-submodules" option, or do "git submodule update --init --recursive") 8 | endif 9 | 10 | include lib/cf2149/module/Makefile 11 | include lib/cf68901/module/Makefile 12 | include lib/cf300588/module/Makefile 13 | 14 | include lib/version/Makefile 15 | include lib/tos/Makefile 16 | include lib/ice/Makefile 17 | include lib/internal/Makefile 18 | include lib/m68k/Makefile 19 | include lib/atari/Makefile 20 | 21 | include lib/psgplay/Makefile 22 | 23 | include lib/example/Makefile 24 | 25 | include lib/audio/Makefile 26 | include lib/graph/Makefile 27 | include lib/vt/Makefile 28 | include lib/unicode/Makefile 29 | include lib/text/Makefile 30 | include lib/test/Makefile 31 | -------------------------------------------------------------------------------- /script/archive-suite: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -o pipefail 5 | 6 | tune_count() 7 | { 8 | "$cmd" --quiet --info "$1" | awk ' 9 | BEGIN { n=1 } 10 | $1 == "tag" && $2 == "field" && $3 == "##" { n=$4; exit } 11 | END { print n } 12 | ' 13 | } 14 | 15 | cmd_enum() 16 | { 17 | local cmd="$1" 18 | local archive_dir="$2" 19 | local archive_suite="$3" 20 | 21 | while read n file 22 | do 23 | echo "$(tune_count "${archive_dir}/$file") $file" 24 | done <"${archive_suite}" | sort -k2 >"${archive_suite}".tmp 25 | 26 | mv "${archive_suite}".tmp "${archive_suite}" 27 | } 28 | 29 | cmd_tune-seq() 30 | { 31 | local n="$1" 32 | local archive_suite="$2" 33 | 34 | seq $(sed -n "$n"'{s/ .*$//;p;q}' <"${archive_suite}") 35 | } 36 | 37 | cmd_name() 38 | { 39 | local n="$1" 40 | local archive_suite="$2" 41 | 42 | sed -n "$n"'{s/[0-9]\+ \+//;p;q}' <"${archive_suite}" 43 | } 44 | 45 | cmd="$1" 46 | shift 47 | cmd_"$cmd" "$@" 48 | -------------------------------------------------------------------------------- /include/internal/assert.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef INTERNAL_ASSERT_H 4 | #define INTERNAL_ASSERT_H 5 | 6 | #include 7 | 8 | #include "internal/build-assert.h" 9 | #include "internal/print.h" 10 | 11 | /* Macro definitions from the Linux kernel. */ 12 | 13 | #define BUG_ON(expr) \ 14 | do { \ 15 | if (expr) \ 16 | pr_bug(__FILE__, __LINE__, __func__, #expr); \ 17 | } while (0) 18 | 19 | #define BUG() \ 20 | do { \ 21 | pr_bug(__FILE__, __LINE__, __func__, "fatal error"); \ 22 | } while (0) 23 | 24 | #define WARN(format...) \ 25 | do { \ 26 | pr_bug_warn(__FILE__, __LINE__, __func__, format); \ 27 | } while (0) 28 | 29 | #define WARN_ONCE(format...) \ 30 | do { \ 31 | static bool warned__; \ 32 | if (!warned__) \ 33 | WARN(format); \ 34 | warned__ = true; \ 35 | } while (0) 36 | 37 | #endif /* INTERNAL_ASSERT_H */ 38 | -------------------------------------------------------------------------------- /lib/atari/rom.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include "atari/bus.h" 7 | #include "atari/device.h" 8 | #include "atari/machine.h" 9 | #include "atari/rom.h" 10 | 11 | #include "tos/tos.h" 12 | 13 | static u8 rom_rd_u8(const struct device *device, u32 dev_address) 14 | { 15 | return dev_address + 1 <= sizeof(tos) ? tos[dev_address] : 0; 16 | } 17 | 18 | static u16 rom_rd_u16(const struct device *device, u32 dev_address) 19 | { 20 | return dev_address + 2 <= sizeof(tos) ? 21 | (tos[dev_address] << 8) | tos[dev_address + 1] : 0; 22 | } 23 | 24 | const struct device rom_device = { 25 | .name = "rom", 26 | .clk = { 27 | .frequency = CPU_FREQUENCY, 28 | .divisor = 1 29 | }, 30 | .bus = { 31 | .address = 0xe00000, 32 | .size = 256 * 1024, 33 | }, 34 | .rd_u8 = rom_rd_u8, 35 | .rd_u16 = rom_rd_u16, 36 | .wr_u8 = bus_error_wr_u8, 37 | .wr_u16 = bus_error_wr_u16, 38 | }; 39 | -------------------------------------------------------------------------------- /test/sndhfrms-verify.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include "test/report.h" 4 | #include "test/verify.h" 5 | #include "test/sndhfrms.h" 6 | 7 | test_value_frames_names(int, tune_value_frames_names); 8 | 9 | void report(struct strbuf *sb, const struct audio *audio, 10 | const struct options *options) 11 | { 12 | const struct test_wave_deviation wave_deviation = 13 | test_wave_deviation(audio); 14 | 15 | report_input(sb, audio, test_name(options), options); 16 | 17 | /* One interrupt is half a period, so multiply with 0.5 accordingly. */ 18 | report_wave_estimate(sb, audio->format, wave_deviation, 19 | 0.5 * 96); 20 | } 21 | 22 | const char *verify(const struct audio *audio, const struct options *options) 23 | { 24 | /* 25 | * 1 frame with a 200 Hz timer and 44100 kHz 26 | * sampling rate is 220.5 samples. 27 | */ 28 | verify_assert (audio->format.sample_count == 221) 29 | return "sample duration"; 30 | 31 | return NULL; 32 | } 33 | -------------------------------------------------------------------------------- /script/tos.ld: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | 3 | OUTPUT_FORMAT("binary") 4 | 5 | ENTRY(_rom) 6 | 7 | MEMORY 8 | { 9 | sys(rwx) : ORIGIN = 0, LENGTH = 0x40000 /* 256 KiB */ 10 | rom(rx) : ORIGIN = 0xe00000, LENGTH = 1M 11 | } 12 | 13 | SECTIONS 14 | { 15 | .exception_vector : { 16 | . += 1K; 17 | } >sys 18 | 19 | .system_variable : { 20 | . += 1K; 21 | } >sys 22 | 23 | .stack : ALIGN(4) { 24 | _usp_bottom = .; 25 | . += 96K; 26 | _usp_top = .; 27 | _ssp_bottom = .; 28 | . += 96K; 29 | _ssp_top = .; 30 | . += 4K; 31 | } >sys 32 | 33 | .bss : ALIGN(4) { 34 | *(.bss) 35 | *(.bss.*) 36 | } >sys 37 | 38 | .text : ALIGN(4) { 39 | *(.text) 40 | *(.text.*) 41 | } >rom 42 | 43 | .rodata : ALIGN(4) { 44 | *(.rodata) 45 | *(.rodata.*) 46 | } >rom = 0 47 | 48 | .data : ALIGN(4) { 49 | *(.data) 50 | *(.data.*) 51 | } >rom = 0 52 | 53 | /DISCARD/ : { 54 | *(.comment) 55 | *(.debug*) 56 | *(.note.*) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /include/test/psgpitch.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_PSGPITCH_H 7 | #define PSGPLAY_TEST_PSGPITCH_H 8 | 9 | #define tune_value_time_names(t) \ 10 | t( 440, 63, "PSG square wave A4 pitch standard A440 440 Hz") \ 11 | t( 33, 63, "PSG square wave C1 double low C 33 Hz") \ 12 | t( 65, 63, "PSG square wave C2 low C (cello) 65 Hz") \ 13 | t( 131, 63, "PSG square wave C3 tenor C (organ) 131 Hz") \ 14 | t( 262, 63, "PSG square wave C4 middle C 262 Hz") \ 15 | t( 523, 63, "PSG square wave C5 treble C 523 Hz") \ 16 | t( 1047, 63, "PSG square wave C6 high C (soprano) 1047 Hz") \ 17 | t( 2093, 63, "PSG square wave C7 double high C 2093 Hz") \ 18 | t( 4186, 63, "PSG square wave C8 triple high C 4186 Hz") \ 19 | t( 8372, 63, "PSG square wave C9 quadruple high C 8372 Hz") \ 20 | t(16744, 63, "PSG square wave C10 quintuple high C 16744 Hz") 21 | 22 | #endif /* PSGPLAY_TEST_PSGPITCH_H */ 23 | -------------------------------------------------------------------------------- /system/unix/sndh.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include "internal/print.h" 10 | 11 | #include "ice/ice.h" 12 | 13 | #include "system/unix/file.h" 14 | #include "system/unix/memory.h" 15 | #include "system/unix/sndh.h" 16 | 17 | struct file sndh_read_file(const char *path) 18 | { 19 | struct file file = file_read_or_stdin(path); 20 | 21 | if (!file_valid(file)) 22 | return file; 23 | 24 | if (ice_identify(file.data, file.size)) { 25 | const size_t s = ice_decrunched_size(file.data, file.size); 26 | void *b = xmalloc(s); 27 | 28 | if (ice_decrunch(b, file.data, file.size) == -1) { 29 | pr_error("%s: ICE decrunch failed\n", file.path); 30 | 31 | free(b); 32 | file_free(file); 33 | errno = ENOEXEC; 34 | 35 | return (struct file) { }; 36 | } 37 | 38 | free(file.data); 39 | file.size = s; 40 | file.data = b; 41 | } 42 | 43 | return file; 44 | } 45 | -------------------------------------------------------------------------------- /test/archive.suite: -------------------------------------------------------------------------------- 1 | 1 505/Posh-Part_1.sndh 2 | 1 505/Relix/BRN_2010.sndh 3 | 1 505/Replay.sndh 4 | 1 Count_Zero/Kohl_Ohr_Schock.sndh 5 | 1 Crazy_Q/Punk_Ass_MF.sndh 6 | 1 Cube/Blink.sndh 7 | 1 Cube/Blipblop_Memories.sndh 8 | 1 Cube/Threshold.sndh 9 | 1 Damo/DMA/Deep_Blue.sndh 10 | 1 Dma-Sc/DMA/ATARI-su-ba-ra-shii.sndh 11 | 1 Dma-Sc/DMA/Its_A_Girl_2.sndh 12 | 1 Dubmood/Zabutoms_Bice4.sndh 13 | 1 Floopy/Metal Planet.sndh 14 | 1 gwEm/DMA/Stardust_Memories.sndh 15 | 1 Lotek_Style/Artefakt.sndh 16 | 4 Mad_Max/Games/Astaroth.sndh 17 | 7 Mad_Max/Games/Lethal_Xcess_(STe).sndh 18 | 7 Mad_Max/Games/Lethal_Xcess_(ST).sndh 19 | 5 Mad_Max/Games/Thalion_Intro.sndh 20 | 9 Mad_Max/Games/Wings_Of_Death.sndh 21 | 9 Mad_Max/Games/Wings_of_Death_STe.sndh 22 | 1 Modmate/jamtrack_(ode_to_scavy).sndh 23 | 1 Stu/Megablast.sndh 24 | 1 Tao/Steps/Just_Feel_It.sndh 25 | 1 Tao/Steps/Ride_The_Sky.sndh 26 | 1 Tao/Steps/Rise.sndh 27 | 1 Tao/TSD_STe/Intensity.sndh 28 | 1 Timbral/DMA/So_Alone_(Live_ver).sndh 29 | 1 Yerzmyey/HappyHardcorew.sndh 30 | -------------------------------------------------------------------------------- /include/internal/struct.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef INTERNAL_STRUCT_H 4 | #define INTERNAL_STRUCT_H 5 | 6 | #include "internal/build-assert.h" 7 | 8 | #define sizeof_member(type, member) sizeof(((type *)0)->member) 9 | 10 | #define BE_STORAGE __attribute__(( __scalar_storage_order__("big-endian") )) 11 | #define LE_STORAGE __attribute__(( __scalar_storage_order__("little-endian") )) 12 | 13 | /* Macro definitions from the Linux kernel. */ 14 | 15 | /** 16 | * container_of - cast a member of a structure out to the containing structure 17 | * @ptr: the pointer to the member 18 | * @type: the type of the container struct this is embedded in 19 | * @member: the name of the member within the struct 20 | */ 21 | #define container_of(ptr, type, member) ({ \ 22 | void *__mptr = (void *)(ptr); \ 23 | BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \ 24 | !__same_type(*(ptr), void), \ 25 | "pointer type mismatch in container_of()"); \ 26 | ((type *)(__mptr - offsetof(type, member))); }) 27 | 28 | #endif /* INTERNAL_STRUCT_H */ 29 | -------------------------------------------------------------------------------- /system/atari/ice_decrunch_inplace.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include "ice/ice.h" 7 | 8 | #include "system/atari/ice_decrunch_inplace.h" 9 | 10 | bool ice_decrunch_inplace_(void *data, struct ice_decrunch_inplace *context); 11 | 12 | struct ice_decrunch_inplace ice_decrunch_inplace_init( 13 | const void *data, size_t size) 14 | { 15 | return (struct ice_decrunch_inplace) { 16 | .size = ice_decrunched_size(data, size) 17 | }; 18 | } 19 | 20 | bool ice_decrunch_inplace_partial(void *data, 21 | struct ice_decrunch_inplace *context, size_t *offset) 22 | { 23 | const bool more = ice_decrunch_inplace_(data, context); 24 | const size_t n = more ? context->a[6] - context->a[4] : 0; 25 | 26 | if (offset) 27 | *offset = context->size - n; 28 | 29 | return more; 30 | } 31 | 32 | void ice_decrunch_inplace_all(void *data, size_t size) 33 | { 34 | struct ice_decrunch_inplace context = 35 | ice_decrunch_inplace_init(data, size); 36 | 37 | do { } while (ice_decrunch_inplace_partial(data, &context, NULL)); 38 | } 39 | -------------------------------------------------------------------------------- /lib/internal/bit.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2021 Fredrik Noring 4 | */ 5 | 6 | #include "internal/bit.h" 7 | 8 | uint16_t bitrev16(uint16_t x) 9 | { 10 | uint16_t r = 0; 11 | 12 | for (int i = 0; i < 16; i++, x >>= 1) 13 | r |= (x & 1) << (15 - i); 14 | 15 | return r; 16 | } 17 | 18 | uint16_t bitpop16(uint16_t x) 19 | { 20 | uint16_t n = 0; 21 | 22 | while (x) { 23 | x &= x - 1; 24 | n++; 25 | } 26 | 27 | return n; 28 | } 29 | 30 | uint16_t bitcompress16(uint16_t x, uint16_t m) 31 | { 32 | uint16_t r = 0; 33 | 34 | for (uint16_t s = 0; m; x >>= 1, m >>= 1) { 35 | const uint16_t b = m & 1; 36 | 37 | r |= (x & b) << s; 38 | s += b; 39 | } 40 | 41 | return r; 42 | } 43 | 44 | uint16_t bitexpand16(uint16_t x, uint16_t m) 45 | { 46 | uint16_t r = 0; 47 | 48 | for (uint16_t s = 0; m; s++, m >>= 1) { 49 | const uint16_t b = m & 1; 50 | 51 | r |= (x & b) << s; 52 | x >>= b; 53 | } 54 | 55 | return r; 56 | } 57 | 58 | uint16_t bitsuccessor16(uint16_t x, uint16_t m) 59 | { 60 | return bitexpand16(bitcompress16(x, m) + 1, m) | (x & ~m); 61 | } 62 | -------------------------------------------------------------------------------- /lib/text/mode.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include "psgplay/sndh.h" 10 | 11 | #include "unicode/utf8.h" 12 | 13 | #include "text/mode.h" 14 | 15 | unicode_t fifo_utf32(struct fifo_utf32 *ffu) 16 | { 17 | const unicode_t t = utf8_to_utf32_next(&ffu->uua); 18 | 19 | if (t) 20 | return t; 21 | 22 | char c; 23 | 24 | if (!fifo_read(&ffu->fifo, &c, sizeof(c))) 25 | return 0; 26 | 27 | return utf8_to_utf32_first(&ffu->uua, c); 28 | } 29 | 30 | struct text_sndh text_sndh_init(const char *title, 31 | const char *path, const void *data, size_t size) 32 | { 33 | struct text_sndh sndh = { 34 | .path = path, 35 | .size = size, 36 | .data = data, 37 | }; 38 | 39 | if (!sndh_tag_title(sndh.title, sizeof(sndh.title), data, size)) { 40 | strncpy(sndh.title, title, sizeof(sndh.title) - 1); 41 | sndh.title[sizeof(sndh.title) - 1] = '\0'; 42 | } 43 | 44 | if (!sndh_tag_subtune_count(&sndh.subtune_count, data, size)) 45 | sndh.subtune_count = 1; 46 | 47 | return sndh; 48 | } 49 | -------------------------------------------------------------------------------- /include/internal/check-compiler.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef INTERNAL_CHECK_COMPILER_H 4 | #define INTERNAL_CHECK_COMPILER_H 5 | 6 | #if defined(__m68k__) && defined(__GNUC__) && defined(__linux__) 7 | /* 8 | * Although m68k-linux-gcc with -march=68000 restricts itself to plain 9 | * 68000 instructions, it will occasionally emit unaligned 16- and 32-bit 10 | * memory access instructions which cause address exceptions on 68000 11 | * hardware. The reason is that Linux requires 68020+. 12 | * 13 | * m68k/GCC maintainers have indicated that they will not fix this bug, 14 | * and m68k-linux-gcc does not reject the -march=68000 option with an 15 | * error despite incompatibility with 68000 hardware which is why this 16 | * is tested here with the C preprocessor. 17 | * 18 | * The recommended compiler for 68000 hardware is m68k-elf-gcc. 19 | * 20 | * GCC bug 98627: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98627 21 | */ 22 | #error "m68k-linux-gcc always emits 68020, please use m68k-elf-gcc instead" 23 | #error "See GCC bug 98627: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98627" 24 | #endif 25 | 26 | #endif /* INTERNAL_CHECK_COMPILER_H */ 27 | -------------------------------------------------------------------------------- /include/vt/vt-cmd.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef VT_CMD_H 7 | #define VT_CMD_H 8 | 9 | /* FIXME */ 10 | struct vt_text { 11 | const char *s; 12 | char b[16]; 13 | }; 14 | 15 | /** 16 | * struct vt_cmd - virtual terminal command 17 | * @clear: clear entire screen 18 | * @hide: cursor hide 19 | * @normal: normal foreground and background colours (nonreverse) 20 | * @position: cursor position 21 | * @reset: all attributes off 22 | * @reverse: exchange foreground and background colours 23 | * @show: cursor show 24 | * @deescape: convert escape sequences to characters 25 | */ 26 | struct vt_cmd { 27 | struct vt_text (*clear)(void); 28 | struct vt_text (*hide)(void); 29 | struct vt_text (*normal)(void); 30 | struct vt_text (*position)(int row, int col); 31 | struct vt_text (*reset)(void); 32 | struct vt_text (*reverse)(void); 33 | struct vt_text (*show)(void); 34 | struct vt_text (*deescape)(const char *s); 35 | }; 36 | 37 | #define vt_text(t) (t.s ? t.s : t.b) 38 | 39 | struct vt_text vt_text_combine(const struct vt_text a, const struct vt_text b); 40 | 41 | #endif /* VT_CMD_H */ 42 | -------------------------------------------------------------------------------- /include/system/unix/file.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_UNIX_FILE_H 7 | #define PSGPLAY_SYSTEM_UNIX_FILE_H 8 | 9 | #include 10 | #include 11 | 12 | /** 13 | * struct file - file container 14 | * @path: path of file 15 | * @size: size in bytes of file 16 | * @data: contents of file, always NUL terminated 17 | */ 18 | struct file { 19 | char * path; 20 | size_t size; 21 | void * data; 22 | }; 23 | 24 | struct file file_read(const char *path); 25 | 26 | struct file file_read_or_stdin(const char *path); 27 | 28 | struct file file_read_fd(int fd, const char *path); 29 | 30 | bool file_write(const char *path, void *buf, size_t nbyte); 31 | 32 | void file_free(struct file f); 33 | 34 | bool file_valid(struct file f); 35 | 36 | int xopen(const char *path, int oflag, ...); 37 | 38 | int xclose(int fd); 39 | 40 | ssize_t xread(int fd, void *buf, size_t nbyte); 41 | 42 | ssize_t xwrite(int fd, const void *buf, size_t nbyte); 43 | 44 | void file_nonblocking(int fd); 45 | 46 | const char *file_basename(const char *path); 47 | 48 | #endif /* PSGPLAY_SYSTEM_UNIX_FILE_H */ 49 | -------------------------------------------------------------------------------- /include/test/snd-dma-alt.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_SND_DMA_ALT_H 7 | #define PSGPLAY_TEST_SND_DMA_ALT_H 8 | 9 | #include 10 | 11 | #include 12 | 13 | struct snd_dma_alt_sample { 14 | union { 15 | struct { 16 | int8_t left; 17 | int8_t right; 18 | }; 19 | int16_t u16; 20 | }; 21 | }; 22 | 23 | static inline void snd_dma_alt_init(struct snd_dma_alt_sample *sample) 24 | { 25 | snd_dma_wr_base(&sample[0]); 26 | snd_dma_wr_end(&sample[1]); 27 | 28 | snd_dma_wrs_mode({ 29 | .format = SND_DMA_MODE_FORMAT_STEREO8, 30 | .rate = SND_DMA_MODE_FREQUENCY_50066 31 | }); 32 | } 33 | 34 | static inline void snd_dma_alt_play(struct snd_dma_alt_sample *sample) 35 | { 36 | if (!sample[0].u16) { 37 | sample[0].left = 0x7f; 38 | sample[0].right = 0x7f; 39 | 40 | snd_dma_wrs_ctrl({ .play_repeat = true, .play = true }); 41 | } else 42 | sample[0].u16 = ~sample[0].u16; 43 | } 44 | 45 | static inline void snd_dma_alt_exit(struct snd_dma_alt_sample *sample) 46 | { 47 | snd_dma_wrs_ctrl({ .play = false }); 48 | } 49 | 50 | #endif /* PSGPLAY_TEST_SND_DMA_ALT_H */ 51 | -------------------------------------------------------------------------------- /test/tempo.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "test/snd-dma-alt.h" 9 | #include "test/tempo.h" 10 | 11 | sndh_title("Tempo"); 12 | sndh_tune_value_time_names(struct timer_preset, tune_value_time_names); 13 | 14 | static struct snd_dma_alt_sample sample[1]; 15 | 16 | static INTERRUPT void timer_a_play() 17 | { 18 | snd_dma_alt_play(sample); 19 | 20 | mfp_clrs_isra({ .timer_a = true }); 21 | } 22 | 23 | void sndh_init(int tune) 24 | { 25 | const struct timer_preset preset = sndh_tune_select_value(tune); 26 | 27 | snd_dma_alt_init(sample); 28 | 29 | iowr32((uint32_t)timer_a_play, 0x134); 30 | 31 | mfp_wrs_tacr({ .ctrl = preset.ctrl }); 32 | mfp_wrs_tadr({ .count = preset.count }); 33 | mfp_clrs_ier({ .timer_a = true }); 34 | mfp_clrs_ipr({ .timer_a = true }); 35 | mfp_clrs_isr({ .timer_a = true }); 36 | mfp_sets_imr({ .timer_a = true }); 37 | } 38 | 39 | void sndh_play() 40 | { 41 | if (!mfp_rd_ier().timer_a) 42 | mfp_sets_ier({ .timer_a = true }); 43 | } 44 | 45 | void sndh_exit() 46 | { 47 | snd_dma_alt_exit(sample); 48 | 49 | // FIXME: Restore vectors and MFP 50 | } 51 | -------------------------------------------------------------------------------- /system/atari/psg.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "atari/psg.h" 13 | 14 | #include "system/atari/psg.h" 15 | 16 | #include "cf2149/module/cf2149.h" 17 | 18 | static bool key_click; 19 | 20 | static void save_disable_key_click(void) 21 | { 22 | key_click = __system_variables->conterm.key_click; 23 | 24 | __system_variables->conterm.key_click = false; 25 | } 26 | 27 | static void restore_key_click(void) 28 | { 29 | __system_variables->conterm.key_click = key_click; 30 | } 31 | 32 | static void psg_exit(void) 33 | { 34 | psg_mute(); 35 | 36 | xbios_supexec(restore_key_click); 37 | } 38 | 39 | void psg_init(void) 40 | { 41 | xbios_supexec(save_disable_key_click); 42 | 43 | atexit(psg_exit); 44 | } 45 | 46 | u8 psg_mute(void) 47 | { 48 | const u8 iomix = xbios_giaccess(0, CF2149_REG_IOMIX); 49 | 50 | xbios_giaccess(0x3f | iomix, XBIOS_GIACCESS_SET | CF2149_REG_IOMIX); 51 | 52 | return iomix; 53 | } 54 | 55 | void psg_unmute(u8 iomix) 56 | { 57 | xbios_giaccess(iomix, XBIOS_GIACCESS_SET | CF2149_REG_IOMIX); 58 | } 59 | -------------------------------------------------------------------------------- /system/unix/print.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "internal/compare.h" 9 | #include "internal/macro.h" 10 | #include "internal/print.h" 11 | #include "internal/types.h" 12 | 13 | #include "system/unix/print.h" 14 | 15 | static void pr_printables(FILE *f, 16 | size_t offset, size_t columns, size_t size, const u8 *b) 17 | { 18 | const size_t d = size - offset; 19 | for (size_t i = 0; i < (d < columns ? columns - d : 0); i++) 20 | fprintf(f, " "); 21 | fprintf(f, " "); 22 | 23 | for (size_t i = 0; i < min(columns, size - offset); i++) 24 | fprintf(f, "%c", isprint(b[offset + i]) ? b[offset + i] : '.'); 25 | } 26 | 27 | void pr_mem(FILE *f, const void *data, size_t size, size_t offset) 28 | { 29 | const int columns = 16; 30 | const u8 *b = data; 31 | 32 | for (size_t i = 0; i < size; i++) { 33 | char address[32]; 34 | 35 | sprintf(address, "\n\t%06zx ", offset + i); 36 | 37 | fprintf(f, "%s%02x", 38 | !i ? &address[1] : i % columns == 0 ? &address[0] : " ", 39 | b[i]); 40 | 41 | if ((i + 1) % columns == 0 || i + 1 == size) 42 | pr_printables(f, i - (i % columns), columns, size, b); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/atari/system-variable.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include "internal/macro.h" 7 | 8 | #include "atari/system-variable.h" 9 | 10 | struct system_variable { 11 | u32 address; 12 | u32 count; 13 | u32 size; 14 | const char *label; 15 | const char *description; 16 | }; 17 | 18 | static bool valid_system_variable(u32 address, 19 | const struct system_variable *sv) 20 | { 21 | return sv->address <= address && 22 | address < sv->address + sv->count * sv->size; 23 | } 24 | 25 | const char *system_variable_label(u32 address) 26 | { 27 | static const struct system_variable list[] = { 28 | #define SYSTEM_VARIABLE_DESCRIPTION( \ 29 | address_, count_, size_, label_, type_, description_) \ 30 | { address_, count_, size_, #label_, description_ }, 31 | SYSTEM_VARIABLE(SYSTEM_VARIABLE_DESCRIPTION) 32 | }; 33 | const size_t n = ARRAY_SIZE(list); 34 | const size_t s = list[0].address; 35 | const size_t e = list[n - 1].address + 36 | list[n - 1].count * list[n - 1].size; 37 | 38 | if (s <= address && address < e) 39 | for (size_t i = 0; i < n; i++) 40 | if (valid_system_variable(address, &list[i])) 41 | return list[i].label; 42 | 43 | return ""; 44 | } 45 | -------------------------------------------------------------------------------- /include/internal/limits.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1 2 | 3 | #ifndef INTERNAL_LIMITS_H 4 | #define INTERNAL_LIMITS_H 5 | 6 | #include "internal/types.h" 7 | 8 | #define USHRT_MAX ((unsigned short)~0U) 9 | #define SHRT_MAX ((short)(USHRT_MAX >> 1)) 10 | #define SHRT_MIN ((short)(-SHRT_MAX - 1)) 11 | #define INT_MAX ((int)(~0U >> 1)) 12 | #define INT_MIN (-INT_MAX - 1) 13 | #define UINT_MAX (~0U) 14 | #define LONG_MAX ((long)(~0UL >> 1)) 15 | #define LONG_MIN (-LONG_MAX - 1) 16 | #define ULONG_MAX (~0UL) 17 | #define LLONG_MAX ((long long)(~0ULL >> 1)) 18 | #define LLONG_MIN (-LLONG_MAX - 1) 19 | #define ULLONG_MAX (~0ULL) 20 | #define SIZE_MAX (~(size_t)0) 21 | #define PHYS_ADDR_MAX (~(phys_addr_t)0) 22 | 23 | #define U8_MAX ((uint8_t)~0U) 24 | #define S8_MAX ((int8_t)(U8_MAX >> 1)) 25 | #define S8_MIN ((int8_t)(-S8_MAX - 1)) 26 | #define U16_MAX ((uint16_t)~0U) 27 | #define S16_MAX ((int16_t)(U16_MAX >> 1)) 28 | #define S16_MIN ((int16_t)(-S16_MAX - 1)) 29 | #define U32_MAX ((uint32_t)~0U) 30 | #define S32_MAX ((int32_t)(U32_MAX >> 1)) 31 | #define S32_MIN ((int32_t)(-S32_MAX - 1)) 32 | #define U64_MAX ((uint64_t)~0ULL) 33 | #define S64_MAX ((int64_t)(U64_MAX >> 1)) 34 | #define S64_MIN ((int64_t)(-S64_MAX - 1)) 35 | 36 | #endif /* INTERNAL_LIMITS_H */ 37 | -------------------------------------------------------------------------------- /include/system/atari/file.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_ATARI_FILE_H 7 | #define PSGPLAY_SYSTEM_ATARI_FILE_H 8 | 9 | #include 10 | #include 11 | 12 | #include "system/atari/ice_decrunch_inplace.h" 13 | 14 | ssize_t fsize(int fd); 15 | 16 | /** 17 | * struct file - file container 18 | * @path: path of file 19 | * @size: size in bytes of file 20 | * @data: contents of file, always NUL terminated 21 | */ 22 | struct file { 23 | const char *path; 24 | size_t size; 25 | void *data; 26 | }; 27 | 28 | struct file_cursor { 29 | struct file *file; 30 | size_t file_offset; 31 | size_t file_size; 32 | int fd; 33 | struct { 34 | bool crunched; 35 | size_t offset; 36 | struct ice_decrunch_inplace context; 37 | } ice; 38 | }; 39 | 40 | bool file_valid(struct file *file); 41 | 42 | bool sndh_read_first(const char *path, struct file *file, 43 | struct file_cursor *file_cursor); 44 | 45 | bool sndh_read_next(struct file_cursor *file_cursor); 46 | 47 | bool sndh_read_finished(struct file_cursor *file_cursor); 48 | 49 | int sndh_read_progress(struct file_cursor *file_cursor); 50 | 51 | const char *file_basename(const char *path); 52 | 53 | #endif /* PSGPLAY_SYSTEM_ATARI_FILE_H */ 54 | -------------------------------------------------------------------------------- /include/atari/machine.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_MACHINE_H 7 | #define ATARI_MACHINE_H 8 | 9 | #include "toslibc/asm/machine.h" 10 | 11 | #include "internal/types.h" 12 | 13 | #include "atari/sample.h" 14 | 15 | #define CPU_FREQUENCY (ATARI_STE_PAL_MCLK / ATARI_STE_CPU_CLK_DIV) 16 | 17 | #define MACHINE_PROGRAM 0x40000 /* 256 KiB */ 18 | #define MACHINE_RUN_SLICE 10000 19 | 20 | struct machine_registers { 21 | u32 d[8]; /* Data registers */ 22 | u32 a[8]; /* Address registers */ 23 | }; 24 | 25 | struct machine_ports { 26 | psg_sample_f psg_sample; 27 | sound_sample_f sound_sample; 28 | mixer_sample_f mixer_sample; 29 | record_sample_f record_sample; 30 | void *arg; 31 | }; 32 | 33 | struct machine { 34 | void (*init)(const void *prg, size_t size, size_t offset, 35 | const struct machine_registers *regs, 36 | const struct machine_ports *ports); 37 | bool (*run)(void); 38 | }; 39 | 40 | u64 cycle_transform(u64 to_frequency, u64 from_frequency, u64 cycle); 41 | 42 | u64 cycle_transform_align(u64 to_frequency, u64 from_frequency, u64 cycle); 43 | 44 | u64 machine_cycle(void); 45 | 46 | void machine_execute(void); 47 | 48 | extern const struct machine atari_st; 49 | 50 | #endif /* ATARI_MACHINE_H */ 51 | -------------------------------------------------------------------------------- /lib/tos/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | TOS_CFLAGS += $(BASIC_TARGET_CFLAGS) -march=68000 -fno-PIC -nostdlib \ 4 | -isystem lib/toslibc/include/toslibc -D_TOSLIBC_SOURCE 5 | 6 | TOS_LDFLAGS += --orphan-handling=error --discard-all -nostdlib \ 7 | --no-relax -no-pie --script=script/tos.ld 8 | 9 | ALL_OBJ += lib/tos/reset.o lib/tos/sndh.o 10 | 11 | ifdef TARGET_CC 12 | 13 | lib/tos/reset.o: lib/tos/reset.S 14 | $(QUIET_AS)$(TARGET_CC) $(TOS_CFLAGS) $(TARGET_CFLAGS) -c -o $@ $< 15 | 16 | lib/tos/sndh.o: lib/tos/sndh.c 17 | $(QUIET_CC)$(TARGET_CC) $(TOS_CFLAGS) $(TARGET_CFLAGS) -c -o $@ $< 18 | 19 | TOS_TOSLIBC_OBJ += $(addprefix $(TOSLIBC_lib_dir), \ 20 | __mulsi3.o \ 21 | __udivmodsi4.o \ 22 | __udivsi3.o) 23 | 24 | lib/tos/tos: $(TOS_TOSLIBC_OBJ) 25 | lib/tos/tos: script/tos.ld script/tos 26 | lib/tos/tos: lib/tos/reset.o lib/tos/sndh.o 27 | $(QUIET_LINK)$(TARGET_LD) $(TOS_LDFLAGS) $(TARGET_LDFLAGS) \ 28 | -o $@ lib/tos/reset.o lib/tos/sndh.o $(TOS_TOSLIBC_OBJ) 29 | @chmod a-x $@ 30 | 31 | else 32 | 33 | # Use precompiled TOS when TARGET_CC is undefined. 34 | lib/tos/tos: 35 | @touch $@ 36 | 37 | endif 38 | 39 | LIBPSGPLAY_TOS_HEADER = include/tos/tos.h 40 | 41 | $(LIBPSGPLAY_TOS_HEADER): lib/tos/tos 42 | $(QUIET_GEN)script/tos $< $@ 43 | 44 | OTHER_CLEAN += $(LIBPSGPLAY_TOS_HEADER) 45 | -------------------------------------------------------------------------------- /include/atari/mixer.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2020 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_MIXER_H 7 | #define ATARI_MIXER_H 8 | 9 | #include "atari/bus.h" 10 | 11 | #define MIXER_MICROWIRE_REGISTERS(reg) \ 12 | reg(0, data, DATA, "Microwire mixer data") \ 13 | reg(1, mask, MASK, "Microwire mixer mask") 14 | 15 | enum mixer_microwire_reg { 16 | #define MIXER_MICROWIRE_REG_ENUM(register_, symbol_, label_, description_)\ 17 | MIXER_MICROWIRE_REG_##label_ = register_, 18 | MIXER_MICROWIRE_REGISTERS(MIXER_MICROWIRE_REG_ENUM) 19 | }; 20 | 21 | #define MIXER_LMC1992_REGISTERS(reg) \ 22 | reg(0, mixer, MIXER, "Mixer") \ 23 | reg(1, bass, BASS, "Bass") \ 24 | reg(2, treble, TREBLE, "Treble") \ 25 | reg(3, volume_main, VOLUME_MAIN, "Main volume") \ 26 | reg(4, volume_right, VOLUME_RIGHT, "Right volume") \ 27 | reg(5, volume_left, VOLUME_LEFT, "Left volume") 28 | 29 | enum mixer_LMC1992_reg { 30 | #define MIXER_LMC1992_REG_ENUM(register_, symbol_, label_, description_)\ 31 | MIXER_LMC1992_REG_##label_ = register_, 32 | MIXER_LMC1992_REGISTERS(MIXER_LMC1992_REG_ENUM) 33 | }; 34 | 35 | extern const struct device mixer_device; 36 | 37 | void mixer_sample(mixer_sample_f sample, void *sample_arg); 38 | 39 | #endif /* ATARI_MIXER_H */ 40 | -------------------------------------------------------------------------------- /lib/vt/vt52.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include "vt/vt52.h" 10 | 11 | #define VT52_STR(s_) (struct vt_text) { .s = s_ } 12 | #define VT52_ESC "\033" 13 | #define VT52_ESC_STR(s_) VT52_STR(VT52_ESC s_) 14 | 15 | /* Proper VT52 */ 16 | static struct vt_text vt52_clear(void) 17 | { 18 | return VT52_STR(VT52_ESC "H" VT52_ESC "J"); 19 | } 20 | 21 | static struct vt_text vt52_position(int row, int col) 22 | { 23 | struct vt_text t; 24 | 25 | t.s = NULL; 26 | snprintf(t.b, sizeof(t.b), VT52_ESC "Y%c%c", row + 32, col + 32); 27 | 28 | return t; 29 | }; 30 | 31 | /* Atari ST extensions */ 32 | static struct vt_text vt52_reset(void) { return VT52_ESC_STR("q"); }; 33 | static struct vt_text vt52_normal(void) { return VT52_ESC_STR("q"); }; 34 | static struct vt_text vt52_reverse(void) { return VT52_ESC_STR("p"); }; 35 | static struct vt_text vt52_hide(void) { return VT52_ESC_STR("f"); }; 36 | static struct vt_text vt52_show(void) { return VT52_ESC_STR("e"); }; 37 | 38 | const struct vt_cmd vt52 = { 39 | .clear = vt52_clear, 40 | .hide = vt52_hide, 41 | .normal = vt52_normal, 42 | .position = vt52_position, 43 | .reset = vt52_reset, 44 | .reverse = vt52_reverse, 45 | .show = vt52_show, 46 | }; 47 | -------------------------------------------------------------------------------- /include/system/unix/option.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_UNIX_OPTION_H 7 | #define PSGPLAY_SYSTEM_UNIX_OPTION_H 8 | 9 | #include 10 | 11 | #include "atari/trace.h" 12 | #include "psgplay/stereo.h" 13 | 14 | #define OPTION_TIME_UNDEFINED -1 15 | #define OPTION_STOP_NEVER -2 16 | 17 | extern const char *progname; 18 | 19 | enum disassemble_type { 20 | DISASSEMBLE_TYPE_NONE, 21 | DISASSEMBLE_TYPE_ALL, 22 | DISASSEMBLE_TYPE_HEADER, 23 | }; 24 | 25 | struct options { 26 | int verbose; 27 | 28 | bool info; 29 | const char *output; 30 | 31 | const char *start; 32 | const char *stop; 33 | const char *length; 34 | 35 | const char *mode; 36 | 37 | int track; 38 | int frequency; 39 | 40 | const char *psg_mix; 41 | struct psgplay_psg_stereo_balance psg_balance; 42 | struct psgplay_psg_stereo_volume psg_volume; 43 | 44 | const char *input; 45 | 46 | struct trace_mode trace; 47 | enum disassemble_type disassemble; 48 | bool disassemble_address; 49 | bool remake_header; 50 | }; 51 | 52 | int option_verbosity(void); 53 | 54 | bool command_mode_option(void); 55 | 56 | bool text_mode_option(void); 57 | 58 | psgplay_digital_to_stereo_cb psg_mix_option(void); 59 | void *psg_mix_arg(void); 60 | 61 | struct options *parse_options(int argc, char **argv); 62 | 63 | #endif /* PSGPLAY_SYSTEM_UNIX_OPTION_H */ 64 | -------------------------------------------------------------------------------- /test/maxamp.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "internal/macro.h" 10 | 11 | #include "test/maxamp.h" 12 | 13 | sndh_title("PSG and DMA maximum amplitude square waves"); 14 | sndh_tune_value_time_names(int, tune_value_time_names); 15 | sndh_timer(SNDH_TIMER_A, 400); 16 | 17 | #define R8(x) x, x, x, x, x, x, x, x, 18 | #define R64(x) R8(x) R8(x) R8(x) R8(x) R8(x) R8(x) R8(x) R8(x) 19 | 20 | static const int8_t samples[] = { R64(+127) R64(-128) }; 21 | 22 | void sndh_init(int tune) 23 | { 24 | snd_psg_wr_iomix(SND_PSG_IOMIX_OFF); 25 | snd_psg_wr_period_a(73); 26 | snd_psg_wr_period_b(103); 27 | snd_psg_wr_period_c(173); 28 | snd_psg_wr_level_a(SND_PSG_LEVEL_MAX); 29 | snd_psg_wr_level_b(SND_PSG_LEVEL_MAX); 30 | snd_psg_wr_level_c(SND_PSG_LEVEL_MAX); 31 | 32 | snd_dma_wr_base(&samples[0]); 33 | snd_dma_wr_end(&samples[ARRAY_SIZE(samples)]); 34 | 35 | snd_dma_wrs_mode({ 36 | .format = SND_DMA_MODE_FORMAT_MONO8, 37 | .rate = SND_DMA_MODE_FREQUENCY_6258 38 | }); 39 | } 40 | 41 | void sndh_play() 42 | { 43 | snd_psg_wr_iomix(SND_PSG_IOMIX_TONE_A & 44 | SND_PSG_IOMIX_TONE_B & 45 | SND_PSG_IOMIX_TONE_C); 46 | 47 | snd_dma_wrs_ctrl({ .play_repeat = true, .play = true }); 48 | } 49 | 50 | void sndh_exit() 51 | { 52 | snd_psg_wr_iomix(SND_PSG_IOMIX_OFF); 53 | 54 | snd_dma_wrs_ctrl({ .play = false }); 55 | } 56 | -------------------------------------------------------------------------------- /lib/atari/bus.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include "internal/assert.h" 7 | #include "internal/build-assert.h" 8 | #include "internal/macro.h" 9 | 10 | #include "m68k/m68kcpu.h" 11 | 12 | #include "atari/bus.h" 13 | #include "atari/machine.h" 14 | 15 | u8 bus_error_rd_u8(const struct device *device, u32 address) 16 | { 17 | /* FIXME: Properly report pr_error("bus error: rd u8 %x\n", device->bus.address + address); */ 18 | 19 | m68k_pulse_bus_error(); 20 | 21 | return 0; 22 | } 23 | 24 | u16 bus_error_rd_u16(const struct device *device, u32 address) 25 | { 26 | /* FIXME: Properly report pr_error("bus error: rd u16 %x\n", device->bus.address + address); */ 27 | 28 | m68k_pulse_bus_error(); 29 | 30 | return 0; 31 | } 32 | 33 | void bus_error_wr_u8(const struct device *device, u32 address, u8 data) 34 | { 35 | /* FIXME: Properly report pr_error("bus error: wr u8 %x %x\n", device->bus.address + address, data); */ 36 | 37 | m68k_pulse_bus_error(); 38 | } 39 | 40 | void bus_error_wr_u16(const struct device *device, u32 address, u16 data) 41 | { 42 | /* FIXME: Properly report pr_error("bus error: wr u16 %x %x\n", device->bus.address + address, data); */ 43 | 44 | m68k_pulse_bus_error(); 45 | } 46 | 47 | const struct device bus_device_error = { 48 | .name = "bus", 49 | .rd_u8 = bus_error_rd_u8, 50 | .rd_u16 = bus_error_rd_u16, 51 | .wr_u8 = bus_error_wr_u8, 52 | .wr_u16 = bus_error_wr_u16, 53 | }; 54 | -------------------------------------------------------------------------------- /licence/MIT: -------------------------------------------------------------------------------- 1 | Valid-License-Identifier: MIT 2 | SPDX-URL: https://spdx.org/licenses/MIT.html 3 | Usage-Guide: 4 | To use the MIT License put the following SPDX tag/value pair into a 5 | comment according to the placement guidelines in the licensing rules 6 | documentation: 7 | SPDX-License-Identifier: MIT 8 | License-Text: 9 | 10 | MIT License 11 | 12 | Copyright (c) 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a 15 | copy of this software and associated documentation files (the "Software"), 16 | to deal in the Software without restriction, including without limitation 17 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 18 | and/or sell copies of the Software, and to permit persons to whom the 19 | Software is furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in 22 | all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 30 | DEALINGS IN THE SOFTWARE. 31 | -------------------------------------------------------------------------------- /include/unicode/utf8.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef PSGPLAY_UNICODE_H 4 | #define PSGPLAY_UNICODE_H 5 | 6 | #include "internal/types.h" 7 | 8 | typedef u32 unicode_t; 9 | 10 | int utf8_to_utf32(unicode_t *u, const u8 *s, size_t insize); 11 | 12 | int utf32_to_utf8(unicode_t u, u8 *s, size_t maxout); 13 | 14 | int utf32_to_utf8_length(unicode_t u); 15 | 16 | ssize_t charset_to_utf8_string_length(const u8 *s, size_t length, 17 | unicode_t (*charset_to_utf32)(u8 c, void *arg), void *arg); 18 | 19 | ssize_t utf8_to_charset_string_length(const u8 *u, size_t length); 20 | 21 | u8 *charset_to_utf8_string(const u8 *s, size_t length, 22 | unicode_t (*charset_to_utf32)(u8 c, void *arg), void *arg); 23 | 24 | u8 *utf8_to_charset_string(const u8 *u, size_t length, 25 | u8 (*utf32_to_charset)(unicode_t u, void *arg), void *arg); 26 | 27 | bool utf8_valid_in_charset_string(const u8 *u, size_t length, 28 | unicode_t (*charset_to_utf32)(u8 c, void *arg), 29 | u8 (*utf32_to_charset)(unicode_t u, void *arg), void *arg); 30 | 31 | struct utf8_to_utf32_adapter { 32 | size_t length; 33 | u8 s[6]; /* Longest UTF-8 sequence */ 34 | }; 35 | 36 | unicode_t utf8_to_utf32_first(struct utf8_to_utf32_adapter *uua, char c); 37 | 38 | unicode_t utf8_to_utf32_next(struct utf8_to_utf32_adapter *uua); 39 | 40 | #define for_each_utf8_to_utf32(symbol, uua, c) \ 41 | for (symbol = utf8_to_unicode_first(uua, c); \ 42 | symbol; \ 43 | symbol = utf8_to_utf32_next(uua)) 44 | 45 | #endif /* PSGPLAY_UNICODE_H */ 46 | -------------------------------------------------------------------------------- /include/text/mode.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEXT_MODE_H 7 | #define PSGPLAY_TEXT_MODE_H 8 | 9 | #include 10 | 11 | #include "internal/types.h" 12 | 13 | #include "text/mvc.h" 14 | 15 | #include "unicode/utf8.h" 16 | 17 | #include "vt/vt.h" 18 | 19 | struct fifo_utf32 { 20 | u8 buffer[64]; 21 | struct fifo fifo; 22 | struct utf8_to_utf32_adapter uua; 23 | }; 24 | 25 | #define DEFINE_FIFO_UTF32(id_) \ 26 | struct fifo_utf32 id_ = INIT_FIFO(id_) 27 | 28 | struct text_mode { 29 | u64 (*view)(struct vt_buffer *vtb, struct text_state *view, 30 | const struct text_state *model, const struct text_sndh *sndh, 31 | u64 timestamp); 32 | void (*ctrl)(const unicode_t key, struct text_state *ctrl, 33 | const struct text_state *model, const struct text_sndh *sndh); 34 | }; 35 | 36 | unicode_t fifo_utf32(struct fifo_utf32 *ffu); 37 | 38 | #define for_each_fifo_utf32(symbol, ffu) \ 39 | for (symbol = fifo_utf32(ffu); \ 40 | symbol; \ 41 | symbol = fifo_utf32(ffu)) 42 | 43 | #define UNICODE_KEYS(k) \ 44 | k(0x2190, ARROW_LEFT) \ 45 | k(0x2191, ARROW_UP) \ 46 | k(0x2192, ARROW_RIGHT) \ 47 | k(0x2193, ARROW_DOWN) 48 | 49 | enum unicode_code { 50 | #define DEFINE_UNICODE_ENUM(code, label) \ 51 | U_##label = code, 52 | UNICODE_KEYS(DEFINE_UNICODE_ENUM) 53 | }; 54 | 55 | struct text_sndh text_sndh_init(const char *title, 56 | const char *path, const void *data, size_t size); 57 | 58 | #endif /* PSGPLAY_TEXT_MODE_H */ 59 | -------------------------------------------------------------------------------- /include/internal/fifo.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef INTERNAL_FIFO_H 7 | #define INTERNAL_FIFO_H 8 | 9 | #include "internal/types.h" 10 | 11 | struct fifo { 12 | size_t size; 13 | size_t index; 14 | size_t capacity; 15 | void *buffer; 16 | }; 17 | 18 | #define DECLARE_FIFO(capacity_) \ 19 | struct { \ 20 | u8 buffer[capacity_]; \ 21 | struct fifo fifo; \ 22 | } 23 | 24 | #define INIT_FIFO(id_) \ 25 | { \ 26 | .fifo = { \ 27 | .capacity = sizeof(id_.buffer), \ 28 | .buffer = &id_.buffer \ 29 | } \ 30 | } 31 | 32 | #define DEFINE_FIFO(id_, capacity_) \ 33 | DECLARE_FIFO(capacity_) id_ = INIT_FIFO(id_) 34 | 35 | static inline size_t fifo_size(const struct fifo *f) 36 | { 37 | return f->size; 38 | } 39 | 40 | static inline bool fifo_empty(const struct fifo *f) 41 | { 42 | return fifo_size(f) == 0; 43 | } 44 | 45 | static inline bool fifo_full(const struct fifo *f) 46 | { 47 | return fifo_size(f) == f->capacity; 48 | } 49 | 50 | static inline size_t fifo_remaining(const struct fifo *f) 51 | { 52 | return f->capacity - fifo_size(f); 53 | } 54 | 55 | size_t fifo_write(struct fifo *f, const void *buf, size_t size); 56 | 57 | size_t fifo_read(struct fifo *f, void *buf, size_t size); 58 | 59 | size_t fifo_peek(struct fifo *f, const void **buf); 60 | 61 | size_t fifo_skip(struct fifo *f, size_t size); 62 | 63 | static inline void fifo_clear(struct fifo *f) 64 | { 65 | f->size = f->index = 0; 66 | } 67 | 68 | #endif /* INTERNAL_FIFO_H */ 69 | -------------------------------------------------------------------------------- /lib/example/example-play.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Simple example on how to play an SNDH file in 44.1 kHz stereo on 4 | * standard output. Pipe output to for example the aplay command 5 | * 6 | * lib/psgplay/example-play example.sndh | aplay -r44100 -c2 -fS16_LE 7 | * 8 | * or -fS16_BE for a big-endian machine. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "internal/macro.h" 16 | #include "internal/print.h" 17 | 18 | #include "psgplay/psgplay.h" 19 | 20 | #include "system/unix/file.h" 21 | #include "system/unix/sndh.h" 22 | 23 | int main(int argc, char *argv[]) 24 | { 25 | if (argc <= 1) { 26 | fprintf(stderr, "usage: example-play ...\n"); 27 | 28 | return EXIT_FAILURE; 29 | } 30 | 31 | for (int arg = 1; arg < argc; arg++) { 32 | const char *path = argv[arg]; 33 | struct file f = sndh_read_file(path); 34 | 35 | if (!file_valid(f)) 36 | pr_fatal_errno(path); 37 | 38 | struct psgplay *pp = psgplay_init(f.data, f.size, 1, 44100); 39 | if (!pp) 40 | pr_fatal_error("%s: Failed to play file\n", path); 41 | 42 | for (;;) { 43 | struct psgplay_stereo buffer[4096]; 44 | 45 | const ssize_t r = psgplay_read_stereo(pp, 46 | buffer, ARRAY_SIZE(buffer)); 47 | 48 | if (r <= 0) 49 | break; 50 | 51 | const ssize_t s = sizeof(struct psgplay_stereo[r]); 52 | const ssize_t w = xwrite(STDOUT_FILENO, buffer, s); 53 | 54 | if (w != s) 55 | break; 56 | } 57 | 58 | psgplay_free(pp); 59 | 60 | file_free(f); 61 | } 62 | 63 | return EXIT_SUCCESS; 64 | } 65 | -------------------------------------------------------------------------------- /include/graph/graph.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_GRAPH_H 7 | #define PSGPLAY_GRAPH_H 8 | 9 | #include "internal/types.h" 10 | 11 | #include "audio/audio.h" 12 | 13 | struct graph_bounds { 14 | double min_x; 15 | double min_y; 16 | double max_x; 17 | double max_y; 18 | }; 19 | 20 | struct graph_encoder_cb { 21 | /** 22 | * f - callback with successively encoded data 23 | * 24 | * @param data encoded data 25 | * @param size size of encoded data 26 | * @param arg argument pointer 27 | * @return true to continue processing, otherwise false 28 | */ 29 | bool (*f)(const void *data, size_t size, void *arg); 30 | void *arg; 31 | }; 32 | 33 | struct graph_encoder { 34 | struct graph_bounds bounds; 35 | struct graph_encoder_cb cb; 36 | const struct graph_encoder_module *module; 37 | }; 38 | 39 | struct graph_encoder_module { 40 | bool (*header)(const struct graph_encoder * const encoder); 41 | bool (*footer)(const struct graph_encoder * const encoder); 42 | bool (*axes)(const struct graph_encoder * const encoder); 43 | bool (*samples)(const struct graph_encoder * const encoder, 44 | struct audio *audio); 45 | bool (*square_wave)(const struct graph_encoder * const encoder, 46 | struct audio_wave wave, int16_t minimum, int16_t maximum); 47 | }; 48 | 49 | struct graph_encoder *graph_encoder_init(struct graph_bounds bounds, 50 | struct graph_encoder_cb cb, const struct graph_encoder_module *module); 51 | 52 | void graph_encoder_free(struct graph_encoder * const encoder); 53 | 54 | #endif /* PSGPLAY_GRAPH_H */ 55 | -------------------------------------------------------------------------------- /include/test/sndhtimer-verify.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_TIMER_SNDH_VERIFY_H 7 | #define PSGPLAY_TEST_TIMER_SNDH_VERIFY_H 8 | 9 | test_value_time_names(double, tune_value_time_names); 10 | 11 | #define TIMER_SNDH_VERIFY_INIT \ 12 | const double timer_frequency = test_value(options); \ 13 | const struct test_wave_deviation wave_deviation = \ 14 | test_wave_deviation(audio) 15 | 16 | void report(struct strbuf *sb, const struct audio *audio, 17 | const struct options *options) 18 | { 19 | TIMER_SNDH_VERIFY_INIT; 20 | 21 | report_input(sb, audio, test_name(options), options); 22 | 23 | sbprintf(sb, "timer frequency %f Hz\n", timer_frequency); 24 | 25 | /* One interrupt is half a period, so multiply with 0.5 accordingly. */ 26 | report_wave_estimate(sb, audio->format, wave_deviation, 27 | 0.5 * timer_frequency); 28 | } 29 | 30 | const char *verify(const struct audio *audio, const struct options *options) 31 | { 32 | TIMER_SNDH_VERIFY_INIT; 33 | 34 | /* One interrupt is half a period, so multiply with 0.5 accordingly. */ 35 | const struct test_wave_error error = test_wave_error( 36 | audio->format, wave_deviation, 37 | 0.5 * timer_frequency); 38 | 39 | verify_assert (audio_duration(audio->format) >= 60.0) 40 | return "sample duration"; 41 | 42 | verify_assert (wave_deviation.deviation.maximum <= 5.0) 43 | return "wave deviation max"; 44 | 45 | verify_assert (error.relative_frequency <= 46 | audio_relative_tolerance(audio->format)) 47 | return "wave error relative frequency"; 48 | 49 | return NULL; 50 | } 51 | 52 | #endif /* PSGPLAY_TEST_TIMER_SNDH_VERIFY_H */ 53 | -------------------------------------------------------------------------------- /lib/example/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | EXAMPLE_CFLAGS = $(BASIC_HOST_CFLAGS) $(HOST_CFLAGS) 4 | 5 | EXAMPLE_INFO := lib/example/example-info 6 | EXAMPLE_PLAY := lib/example/example-play 7 | 8 | EXAMPLE_INFO_SRC := $(EXAMPLE_INFO).c 9 | EXAMPLE_PLAY_SRC := $(EXAMPLE_PLAY).c 10 | 11 | EXAMPLE_INFO_OBJ := $(EXAMPLE_INFO_SRC:%.c=%.o) 12 | EXAMPLE_PLAY_OBJ := $(EXAMPLE_PLAY_SRC:%.c=%.o) 13 | 14 | EXAMPLE_LINK_SRC := \ 15 | lib/ice/ice.c \ 16 | lib/internal/print.c \ 17 | lib/internal/string.c \ 18 | system/unix/file.c \ 19 | system/unix/memory.c \ 20 | system/unix/print.c \ 21 | system/unix/sndh.c \ 22 | system/unix/string.c 23 | 24 | EXAMPLE_SRC := $(EXAMPLE_INFO_SRC) $(EXAMPLE_PLAY_SRC) 25 | EXAMPLE_OBJ := $(EXAMPLE_INFO_OBJ) $(EXAMPLE_PLAY_OBJ) 26 | 27 | EXAMPLE_object = $(addprefix lib/example/,$(subst /,-,$(1:%.c=%.o))) 28 | 29 | define EXAMPLE_link_target 30 | EXAMPLE_LINK_OBJ += $(call EXAMPLE_object,$(1)) 31 | $(call EXAMPLE_object,$(1)): $(1) 32 | $$(QUIET_CC)$$(HOST_CC) $$(EXAMPLE_CFLAGS) -c -o $$@ $$< 33 | endef 34 | 35 | $(foreach f,$(EXAMPLE_LINK_SRC),$(eval $(call EXAMPLE_link_target,$(f)))) 36 | 37 | ALL_OBJ += $(EXAMPLE_OBJ) $(EXAMPLE_LINK_OBJ) 38 | 39 | $(EXAMPLE_OBJ): %.o: %.c 40 | $(QUIET_CC)$(HOST_CC) $(EXAMPLE_CFLAGS) -c -o $@ $< 41 | 42 | $(EXAMPLE_INFO): $(EXAMPLE_INFO_OBJ) $(LIBPSGPLAY_SHARED) 43 | $(EXAMPLE_PLAY): $(EXAMPLE_PLAY_OBJ) $(LIBPSGPLAY_SHARED) 44 | 45 | .PHONY: example 46 | example: $(EXAMPLE_INFO) $(EXAMPLE_PLAY) 47 | 48 | $(EXAMPLE_INFO) $(EXAMPLE_PLAY): $(EXAMPLE_LINK_OBJ) 49 | $(QUIET_LINK)$(HOST_CC) $(EXAMPLE_CFLAGS) -o $@ $^ 50 | 51 | OTHER_CLEAN += $(EXAMPLE_INFO) $(EXAMPLE_PLAY) 52 | -------------------------------------------------------------------------------- /system/unix/clock.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #define _POSIX_C_SOURCE 199309 7 | #include 8 | 9 | #include "internal/compare.h" 10 | #include "internal/types.h" 11 | #include "internal/print.h" 12 | 13 | #include "system/unix/clock.h" 14 | 15 | static u64 start; 16 | static u64 suspend; 17 | static u64 offset; 18 | static u64 now; 19 | 20 | static u64 request; 21 | 22 | static u64 clock_now(void) 23 | { 24 | struct timespec tp; 25 | 26 | if (clock_gettime(CLOCK_MONOTONIC, &tp) == -1) 27 | pr_fatal_errno("clock_now:clock_gettime"); 28 | 29 | return tp.tv_sec * 1000000000 + tp.tv_nsec; 30 | } 31 | 32 | void clock_init(void) 33 | { 34 | start = clock_now(); 35 | } 36 | 37 | void clock_suspend(void) 38 | { 39 | suspend = clock_now(); 40 | } 41 | 42 | void clock_resume(void) 43 | { 44 | offset += clock_now() - suspend; 45 | } 46 | 47 | void clock_update(void) 48 | { 49 | now = clock_now() - start - offset; 50 | } 51 | 52 | u32 clock_s(void) 53 | { 54 | return clock_ms() / 1000; 55 | } 56 | 57 | u64 clock_ms(void) 58 | { 59 | return now / 1000000; 60 | } 61 | 62 | void clock_request_s(const u32 timestamp) 63 | { 64 | if (!timestamp) 65 | return; 66 | 67 | clock_request_ms(1000 * timestamp); 68 | } 69 | 70 | void clock_request_ms(const u64 timestamp) 71 | { 72 | if (!timestamp) 73 | return; 74 | 75 | const u64 t = timestamp * 1000000; 76 | 77 | request = !request ? t : min(request, t); 78 | } 79 | 80 | int clock_poll(void) 81 | { 82 | if (!request) 83 | return -1; 84 | 85 | const u64 r = request; 86 | 87 | request = 0; 88 | 89 | if (r <= now) 90 | return 0; 91 | 92 | return (r - now) / 1000000; 93 | } 94 | -------------------------------------------------------------------------------- /include/system/unix/string.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_SYSTEM_UNIX_STRING_H 7 | #define PSGPLAY_SYSTEM_UNIX_STRING_H 8 | 9 | #include 10 | #include 11 | 12 | char *xstrdup(const char *s); 13 | 14 | char *xstrcat(const char *a, const char *b); 15 | 16 | char *xstrndup(const char *s, size_t n); 17 | 18 | /** 19 | * strrep - substitute each matching substring in a given string 20 | * @s: string to substitute substrings in 21 | * @from: substring to match 22 | * @to: replacement substring 23 | * 24 | * Return: allocated substituted string 25 | */ 26 | char *strrep(const char *s, const char *from, const char *to); /* FIXME */ 27 | 28 | /** 29 | * struct strbuf - string buffer 30 | * @length: length in bytes of @s, excluding any terminating NUL 31 | * @capacity: maximum size in bytes of @s 32 | * @data: contents of string, NUL terminated only if @size > 0 33 | */ 34 | struct strbuf { 35 | size_t length; 36 | size_t capacity; 37 | char *s; 38 | }; 39 | 40 | void sbfree(struct strbuf *sb); 41 | 42 | /** 43 | * sbprintf - formatted output conversion to a string buffer 44 | * @sb: string buffer, can be initialised to zero 45 | * @fmt: a printf family format 46 | * @...: parameters to @fmt 47 | * 48 | * Return: %true if successful, otherwise %false 49 | */ 50 | bool sbprintf(struct strbuf *sb, const char *fmt, ...); 51 | 52 | bool sbmprintf(struct strbuf *sb, size_t margin, const char *fmt, ...); 53 | 54 | bool vsbprintf(struct strbuf *sb, const char *fmt, va_list ap); 55 | 56 | bool vsbmprintf(struct strbuf *sb, size_t margin, const char *fmt, va_list ap); 57 | 58 | #endif /* PSGPLAY_SYSTEM_UNIX_STRING_H */ 59 | -------------------------------------------------------------------------------- /include/internal/bit.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2021 Fredrik Noring 4 | */ 5 | 6 | #ifndef INTERNAL_BIT_H 7 | #define INTERNAL_BIT_H 8 | 9 | #include "internal/types.h" 10 | 11 | /** 12 | * bitrev16 - reverse 16-bit word 13 | * @x: word to reverse 14 | * 15 | * Return: bit-reversed word 16 | */ 17 | uint16_t bitrev16(uint16_t x); 18 | 19 | /** 20 | * bitpop16 - 16-bit word population count 21 | * @x: word to count 22 | * 23 | * Return: number of bits in @x that are 1 24 | */ 25 | uint16_t bitpop16(uint16_t x); 26 | 27 | /** 28 | * bitcompress16 - compress a 16-bit word 29 | * @x: word to compress 30 | * @m: compression selection word 31 | * 32 | * Example: Let @x = abcd efgh ijkl mnop, and 33 | * @m = 0000 1111 0011 1100, then the result is 34 | * 0000 0000 efgh klmn. 35 | * 36 | * Return: bits in @x chosen by bits in @m 37 | */ 38 | uint16_t bitcompress16(uint16_t x, uint16_t m); 39 | 40 | /** 41 | * bitexpand16 - expand a 16-bit word 42 | * @x: word to expand 43 | * @m: expansion selection word 44 | * 45 | * Example: Let @x = 0000 0000 efgh klmn, and 46 | * @m = 0000 1111 0011 1100, then the result is 47 | * 0000 efgh 00kl mn00. 48 | * 49 | * Return: bits in @x chosen by bits in @m 50 | */ 51 | uint16_t bitexpand16(uint16_t x, uint16_t m); 52 | 53 | /** 54 | * bitsuccessor16 - successor to a compressed 16-bit word expanded 55 | * @x: word to add by 1 in compressed form 56 | * @m: compression and expansion of word 57 | * 58 | * Return: word added one in compressed form, and then expanded, with the 59 | * other bits retained 60 | */ 61 | uint16_t bitsuccessor16(uint16_t x, uint16_t m); 62 | 63 | #endif /* INTERNAL_BIT_H */ 64 | -------------------------------------------------------------------------------- /lib/atari/fdc.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include "internal/build-assert.h" 10 | 11 | #include "atari/bus.h" 12 | #include "atari/device.h" 13 | #include "atari/fdc.h" 14 | #include "atari/machine.h" 15 | 16 | static void fdc_event(const struct device *device, 17 | const struct device_cycle mfp_cycle) 18 | { 19 | } 20 | 21 | static u8 fdc_rd_u8(const struct device *device, u32 dev_address) 22 | { 23 | return 0; 24 | } 25 | 26 | static u16 fdc_rd_u16(const struct device *device, u32 dev_address) 27 | { 28 | return fdc_rd_u8(device, dev_address) << 8; 29 | } 30 | 31 | static void fdc_wr_u8(const struct device *device, u32 dev_address, u8 data) 32 | { 33 | } 34 | 35 | static void fdc_wr_u16(const struct device *device, u32 dev_address, u16 data) 36 | { 37 | fdc_wr_u8(device, dev_address, data >> 8); 38 | } 39 | 40 | static size_t fdc_id_u8(const struct device *device, 41 | u32 dev_address, char *buf, size_t size) 42 | { 43 | buf[0] = '\0'; 44 | 45 | return strlen(buf); 46 | } 47 | 48 | static size_t fdc_id_u16(const struct device *device, 49 | u32 dev_address, char *buf, size_t size) 50 | { 51 | return fdc_id_u8(device, dev_address, buf, size); 52 | } 53 | 54 | static void fdc_reset(const struct device *device) 55 | { 56 | } 57 | 58 | const struct device fdc_device = { 59 | .name = "fdc", 60 | .clk = { 61 | .frequency = CPU_FREQUENCY, 62 | .divisor = 1 63 | }, 64 | .bus = { 65 | .address = 0xff8600, 66 | .size = 16, 67 | }, 68 | .reset = fdc_reset, 69 | .event = fdc_event, 70 | .rd_u8 = fdc_rd_u8, 71 | .rd_u16 = fdc_rd_u16, 72 | .wr_u8 = fdc_wr_u8, 73 | .wr_u16 = fdc_wr_u16, 74 | .id_u8 = fdc_id_u8, 75 | .id_u16 = fdc_id_u16, 76 | }; 77 | -------------------------------------------------------------------------------- /include/internal/types.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef INTERNAL_TYPES_H 4 | #define INTERNAL_TYPES_H 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 12 | #define __BITFIELD_FIELD(field, more) \ 13 | field; \ 14 | more 15 | #elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 16 | #define __BITFIELD_FIELD(field, more) \ 17 | more \ 18 | field; 19 | #else 20 | #error "bitfield neither big nor little endian?" 21 | #endif 22 | 23 | typedef int8_t s8; /* FIXME: Deprecate */ 24 | typedef int16_t s16; /* FIXME: Deprecate */ 25 | typedef int32_t s32; /* FIXME: Deprecate */ 26 | typedef int64_t s64; /* FIXME: Deprecate */ 27 | 28 | typedef uint8_t u8; /* FIXME: Deprecate */ 29 | typedef uint16_t u16; /* FIXME: Deprecate */ 30 | typedef uint32_t u32; /* FIXME: Deprecate */ 31 | typedef uint64_t u64; /* FIXME: Deprecate */ 32 | 33 | /* Macro definitions from the Linux kernel. */ 34 | 35 | /* Are two types/vars the same type (ignoring qualifiers)? */ 36 | #ifndef __same_type 37 | # define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b)) 38 | #endif 39 | 40 | /* 41 | * min()/max()/clamp() macros must accomplish three things: 42 | * 43 | * - avoid multiple evaluations of the arguments (so side-effects like 44 | * "x++" happen only once) when non-constant. 45 | * - perform strict type-checking (to generate warnings instead of 46 | * nasty runtime surprises). See the "unnecessary" pointer comparison 47 | * in __typecheck(). 48 | * - retain result as a constant expressions when called with only 49 | * constant expressions (to avoid tripping VLA warnings in stack 50 | * allocation usage). 51 | */ 52 | #define __typecheck(x, y) (!!(sizeof((typeof(x) *)1 == (typeof(y) *)1))) 53 | 54 | #endif /* INTERNAL_TYPES_H */ 55 | -------------------------------------------------------------------------------- /test/psgpitch-verify.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | 5 | #include "toslibc/asm/machine.h" 6 | 7 | #include "test/report.h" 8 | #include "test/verify.h" 9 | #include "test/psgpitch.h" 10 | 11 | test_value_time_names(int, tune_value_time_names); 12 | 13 | static int tone_period(const struct options *options) 14 | { 15 | return (int)round((double)ATARI_STE_EXT_OSC / 16 | (ATARI_STE_SND_PSG_CLK_DIV * 16.0 * test_value(options))); 17 | } 18 | 19 | static double tone_frequency(const struct options *options) 20 | { 21 | return (double)ATARI_STE_EXT_OSC / 22 | (ATARI_STE_SND_PSG_CLK_DIV * 16.0 * tone_period(options)); 23 | } 24 | 25 | void report(struct strbuf *sb, const struct audio *audio, 26 | const struct options *options) 27 | { 28 | const struct test_wave_deviation wave_deviation = 29 | test_wave_deviation(audio); 30 | 31 | report_input(sb, audio, test_name(options), options); 32 | 33 | sbprintf(sb, 34 | "tone clock %d / %d / 16 Hz\n" 35 | "tone period %d cycles\n", 36 | ATARI_STE_EXT_OSC, ATARI_STE_SND_PSG_CLK_DIV, 37 | tone_period(options)); 38 | 39 | report_wave_estimate(sb, audio->format, wave_deviation, 40 | tone_frequency(options)); 41 | } 42 | 43 | const char *verify(const struct audio *audio, const struct options *options) 44 | { 45 | const struct test_wave_deviation wave_deviation = 46 | test_wave_deviation(audio); 47 | const struct test_wave_error error = test_wave_error( 48 | audio->format, wave_deviation, tone_frequency(options)); 49 | 50 | verify_assert (audio_duration(audio->format) >= 60.0) 51 | return "sample duration"; 52 | 53 | verify_assert (wave_deviation.deviation.maximum <= 1.8) 54 | return "wave deviation max"; 55 | 56 | verify_assert (error.relative_frequency <= 57 | audio_relative_tolerance(audio->format)) 58 | return "wave error relative frequency"; 59 | 60 | return NULL; 61 | } 62 | -------------------------------------------------------------------------------- /include/atari/shifter.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_SHIFTER_H 7 | #define ATARI_SHIFTER_H 8 | 9 | #include "atari/bus.h" 10 | 11 | #define SHIFTER_REGISTERS(sh) \ 12 | sh( 0, vbasehi, VBASEHI, "Video display base high byte") \ 13 | sh( 1, vbasemid, VBASEMID, "Video display base mid byte") \ 14 | sh( 2, vcounthi, VCOUNTHI, "Video refresh address high byte") \ 15 | sh( 3, vcountmi, VCOUNTMI, "Video refresh address mid byte") \ 16 | sh( 4, vcountlo, VCOUNTLO, "Video refresh address low byte") \ 17 | sh( 5, synch_m, SYNC_M, "Synchronisation mode") \ 18 | sh( 6, vbaselo, VBASELO, "Video display base low byte") \ 19 | sh( 7, linewid, LINEWID, "Extra words per scanline") \ 20 | sh(32, color_00, COLOR_00, "Palette color 0") \ 21 | sh(33, color_01, COLOR_01, "Palette color 1") \ 22 | sh(34, color_02, COLOR_02, "Palette color 2") \ 23 | sh(35, color_03, COLOR_03, "Palette color 3") \ 24 | sh(36, color_04, COLOR_04, "Palette color 4") \ 25 | sh(37, color_05, COLOR_05, "Palette color 5") \ 26 | sh(38, color_06, COLOR_06, "Palette color 6") \ 27 | sh(39, color_07, COLOR_07, "Palette color 7") \ 28 | sh(40, color_08, COLOR_08, "Palette color 8") \ 29 | sh(41, color_09, COLOR_09, "Palette color 9") \ 30 | sh(42, color_10, COLOR_10, "Palette color 10") \ 31 | sh(43, color_11, COLOR_11, "Palette color 11") \ 32 | sh(44, color_12, COLOR_12, "Palette color 12") \ 33 | sh(45, color_13, COLOR_13, "Palette color 13") \ 34 | sh(46, color_14, COLOR_14, "Palette color 14") \ 35 | sh(47, color_15, COLOR_15, "Palette color 15") \ 36 | sh(48, video_m, VIDEO_M, "Video mode resoluation") \ 37 | sh(50, hscroll, HSCROLL, "Horizontal scroll") 38 | 39 | extern const struct device shifter_device; 40 | 41 | #endif /* ATARI_SHIFTER_H */ 42 | -------------------------------------------------------------------------------- /lib/internal/fifo.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | 8 | #include "internal/compare.h" 9 | #include "internal/fifo.h" 10 | 11 | size_t fifo_write(struct fifo *f, const void *buf, size_t size) 12 | { 13 | const size_t s = min(f->capacity - f->size, size); 14 | const size_t i = f->index + f->size < f->capacity ? 15 | f->index + f->size : f->index + f->size - f->capacity; 16 | const u8 *src = buf; 17 | u8 *dst = f->buffer; 18 | 19 | if (!s) 20 | return 0; 21 | 22 | if (i + s > f->capacity) { 23 | const size_t p = f->capacity - i; 24 | 25 | memcpy(&dst[i], &src[0], p); 26 | memcpy(&dst[0], &src[p], s - p); 27 | } else 28 | memcpy(&dst[i], &src[0], s); 29 | 30 | f->size += s; 31 | 32 | return s; 33 | } 34 | 35 | size_t fifo_read(struct fifo *f, void *buf, size_t size) 36 | { 37 | const size_t s = min(f->size, size); 38 | const u8 *src = f->buffer; 39 | u8 *dst = buf; 40 | 41 | if (!s) 42 | return 0; 43 | 44 | if (f->index + s > f->capacity) { 45 | const size_t p = f->capacity - f->index; 46 | 47 | memcpy(&dst[0], &src[f->index], p); 48 | memcpy(&dst[p], &src[0], s - p); 49 | } else 50 | memcpy(&dst[0], &src[f->index], s); 51 | 52 | f->index += s; 53 | if (f->index >= f->capacity) 54 | f->index -= f->capacity; 55 | 56 | f->size -= s; 57 | 58 | return s; 59 | } 60 | 61 | size_t fifo_peek(struct fifo *f, const void **buf) 62 | { 63 | const u8 *src = f->buffer; 64 | 65 | if (buf != NULL) 66 | *buf = &src[f->index]; 67 | 68 | return f->index + f->size > f->capacity ? 69 | f->capacity - f->index : f->size; 70 | } 71 | 72 | size_t fifo_skip(struct fifo *f, size_t size) 73 | { 74 | const size_t s = min(f->size, size); 75 | 76 | f->index += s; 77 | if (f->index >= f->capacity) 78 | f->index -= f->capacity; 79 | 80 | f->size -= s; 81 | 82 | return s; 83 | } 84 | -------------------------------------------------------------------------------- /test/maxamp-verify.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include "internal/compare.h" 4 | 5 | #include "test/report.h" 6 | #include "test/verify.h" 7 | #include "test/maxamp.h" 8 | 9 | test_value_time_names(int, tune_value_time_names); 10 | 11 | struct minmax { 12 | int16_t minimum; 13 | int16_t maximum; 14 | }; 15 | 16 | static struct minmax minmax(const struct audio *audio) 17 | { 18 | struct minmax mm = { }; 19 | 20 | if (!audio->format.sample_count) 21 | return mm; 22 | 23 | mm.minimum = min(audio->samples[0].left, audio->samples[0].right); 24 | mm.maximum = max(audio->samples[0].left, audio->samples[0].right); 25 | 26 | for (size_t i = 1; i < audio->format.sample_count; i++) { 27 | mm.minimum = min3(mm.minimum, audio->samples[i].left, 28 | audio->samples[i].right); 29 | mm.maximum = max3(mm.maximum, audio->samples[i].left, 30 | audio->samples[i].right); 31 | } 32 | 33 | return mm; 34 | } 35 | 36 | void report(struct strbuf *sb, const struct audio *audio, 37 | const struct options *options) 38 | { 39 | const struct minmax mm = minmax(audio); 40 | 41 | report_input(sb, audio, test_name(options), options); 42 | 43 | sbprintf(sb, 44 | "wave minimum %d\n" 45 | "wave maximum %d\n", 46 | mm.minimum, 47 | mm.maximum); 48 | } 49 | 50 | const char *verify(const struct audio *audio, const struct options *options) 51 | { 52 | const struct minmax mm = minmax(audio); 53 | 54 | verify_assert (audio_duration(audio->format) >= 1.0) 55 | return "sample duration"; 56 | 57 | verify_assert (mm.minimum < -32000 && /* Verify near numerical min */ 58 | mm.minimum > -32760) /* Verify margin to clipping */ 59 | return "sample minimum"; 60 | 61 | verify_assert (mm.maximum > 32000 && /* Verify near numerical max */ 62 | mm.maximum < 32760) /* Verify margin to clipping */ 63 | return "sample maximum"; 64 | 65 | return NULL; 66 | } 67 | -------------------------------------------------------------------------------- /system/unix/diagnostic.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "internal/print.h" 11 | 12 | #include "psgplay/sndh.h" 13 | 14 | #include "system/unix/diagnostic.h" 15 | #include "system/unix/file.h" 16 | #include "system/unix/string.h" 17 | 18 | static void diag_warn(void *arg, const char *fmt, ...) 19 | { 20 | struct file *file = arg; 21 | char msg[1024]; 22 | va_list ap; 23 | 24 | va_start(ap, fmt); 25 | vsnprintf(msg, sizeof(msg), fmt, ap); 26 | pr_warn("%s: %s\n", file->path, msg); 27 | va_end(ap); 28 | } 29 | 30 | static void diag_error(void *arg, const char *fmt, ...) 31 | { 32 | struct file *file = arg; 33 | char msg[1024]; 34 | va_list ap; 35 | 36 | va_start(ap, fmt); 37 | vsnprintf(msg, sizeof(msg), fmt, ap); 38 | pr_error("%s: %s\n", file->path, msg); 39 | va_end(ap); 40 | } 41 | 42 | static bool printable(const char *s) 43 | { 44 | for (size_t i = 0; s[i] != '\0'; i++) { 45 | int c = s[i] & 0xff; 46 | 47 | if (c < 0x20 || 0x7f == c) 48 | return false; 49 | } 50 | 51 | return true; 52 | } 53 | 54 | void sndh_diagnostic(struct file file) 55 | { 56 | const struct sndh_diagnostic diag = { 57 | .warn = diag_warn, 58 | .error = diag_error, 59 | .arg = &file 60 | }; 61 | 62 | sndh_for_each_tag_with_diagnostic (file.data, file.size, &diag) { 63 | const char *name = sndh_tag_name; 64 | const char *value = sndh_tag_value; 65 | const size_t length = strlen(value); 66 | 67 | if (!length) 68 | pr_warn("%s: tag %s empty\n", file.path, name); 69 | if (!printable(value)) 70 | pr_warn("%s: tag %s contains nonprintable characters: %s\n", 71 | file.path, name, value); 72 | if (length && (isspace(value[0]) || isspace(value[length - 1]))) 73 | pr_warn("%s: tag %s malformed whitespace\n", 74 | file.path, name); 75 | 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/atari/cpu.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | * 5 | * FIXME: Supervisor address access 6 | */ 7 | 8 | #include "internal/assert.h" 9 | 10 | #include "atari/cpu.h" 11 | #include "atari/device.h" 12 | #include "atari/machine.h" 13 | 14 | #include "m68k/m68k.h" 15 | #include "m68k/m68kcpu.h" 16 | 17 | static struct { 18 | void (*cb)(uint32_t pc, void *arg); 19 | void *arg; 20 | } instruction_callback; 21 | 22 | bool cpu_execute; 23 | 24 | void m68k_instruction_callback(int pc) 25 | { 26 | #if 0 /* FIXME */ 27 | printf("%08x: %04x %s\n", pc, REG_IR, 28 | m68ki_disassemble_quick(pc, M68K_CPU_TYPE_68000)); 29 | #endif 30 | 31 | if (!instruction_callback.cb) 32 | return; 33 | 34 | instruction_callback.cb(pc, instruction_callback.arg); 35 | } 36 | 37 | void cpu_instruction_callback(void (*cb)(uint32_t pc, void *arg), void *arg) 38 | { 39 | instruction_callback.cb = cb; 40 | instruction_callback.arg = arg; 41 | } 42 | 43 | u64 cpu_cycles_run(void) 44 | { 45 | const int cycles_run = cpu_execute ? m68k_cycles_run() : 0; 46 | 47 | BUG_ON(cycles_run < 0); 48 | 49 | return cycles_run; 50 | } 51 | 52 | static struct device_slice cpu_run(const struct device *device, 53 | struct device_cycle device_cycle, struct device_slice device_slice) 54 | { 55 | cpu_execute = true; 56 | const int s = m68k_execute(device_slice.s); 57 | cpu_execute = false; 58 | 59 | BUG_ON(s < 0); 60 | 61 | return (struct device_slice) { .s = s }; 62 | } 63 | 64 | static void cpu_reset(const struct device *device) 65 | { 66 | instruction_callback.cb = NULL; 67 | instruction_callback.arg = NULL; 68 | 69 | m68k_init(); 70 | m68k_set_cpu_type(M68K_CPU_TYPE_68000); 71 | m68k_pulse_reset(); 72 | } 73 | 74 | const struct device cpu_device = { 75 | .name = "cpu", 76 | .clk = { 77 | .frequency = CPU_FREQUENCY, 78 | .divisor = 1 79 | }, 80 | .run = cpu_run, 81 | .reset = cpu_reset, 82 | }; 83 | -------------------------------------------------------------------------------- /include/ice/ice.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef PSGPLAY_ICE_H 4 | #define PSGPLAY_ICE_H 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #define ICE_HEADER_SIZE 12 11 | 12 | /** 13 | * ice_identify - is data ICE compressed? 14 | * @data: compressed data 15 | * @size: size in bytes of compressed data, or greater 16 | * 17 | * Note that @size must be at least ICE_HEADER_SIZE bytes. 18 | * 19 | * Return: %true if data seems to be ICE compressed, otherwise %false 20 | */ 21 | bool ice_identify(const void *data, size_t size); 22 | 23 | /** 24 | * ice_crunched_size - determine size of ICE compressed data 25 | * @data: compressed data 26 | * @size: size in bytes of compressed data, or greater 27 | * 28 | * Note that @size must be at least ICE_HEADER_SIZE bytes. 29 | * 30 | * Return: size in bytes of compressed data, or zero on failure 31 | */ 32 | size_t ice_crunched_size(const void *data, size_t size); 33 | 34 | /** 35 | * ice_decrunched_size - determine size of ICE decompressed data 36 | * @data: compressed data 37 | * @size: size in bytes of compressed data, or greater 38 | * 39 | * Note that @size must be at least ICE_HEADER_SIZE bytes. 40 | * 41 | * Return: size in bytes of decompressed data, or zero on failure 42 | */ 43 | size_t ice_decrunched_size(const void *data, size_t size); 44 | 45 | /** 46 | * ice_decrunch - ICE decompress data 47 | * @out: decompressed output data 48 | * @in: compressed input data 49 | * @insize: size in bytes of compressed data, or greater 50 | * 51 | * Note that the @out buffer must be large enough to contain all decompressed 52 | * data. Use &ice_decrunched_size to determine the size. 53 | * 54 | * Also note that @in and @out memory buffers must not overlap. 55 | * 56 | * Return: size in bytes of decompressed data, or -1 on failure 57 | */ 58 | ssize_t ice_decrunch(void *out, const void *in, size_t insize); 59 | 60 | #endif /* PSGPLAY_ICE_H */ 61 | -------------------------------------------------------------------------------- /system/atari/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | PRG_CFLAGS += $(BASIC_TARGET_CFLAGS) $(CF2149_CFLAGS) \ 4 | -march=68000 -fno-PIC -nostdlib \ 5 | -ffunction-sections -fdata-sections \ 6 | -Ilib/toslibc/include -isystem lib/toslibc/include/toslibc \ 7 | -D_TOSLIBC_SOURCE 8 | 9 | PRG_LDFLAGS += --relocatable --gc-sections --strip-all --entry _start \ 10 | -L$(TOSLIBC_lib_dir) --script=$(TOSLIBC_SCRIPT_PRG_LD) 11 | 12 | PSGPLAY_TOS := PSGPLAY.TOS 13 | PSGPLAY_PRG_OBJ := system/atari/PSGPLAY.PRG.o 14 | 15 | SYSTEM_ATARI_SRC := \ 16 | lib/internal/fifo.c \ 17 | lib/internal/string.c \ 18 | lib/ice/ice.c \ 19 | lib/psgplay/sndh.c \ 20 | lib/text/load.c \ 21 | lib/text/main.c \ 22 | lib/text/mode.c \ 23 | lib/unicode/atari.c \ 24 | lib/unicode/utf8.c \ 25 | lib/vt/vt52.c \ 26 | lib/vt/vt.c \ 27 | system/atari/clock.c \ 28 | system/atari/file.c \ 29 | system/atari/ice_decrunch_inplace.c \ 30 | system/atari/ice_decrunch_inplace_.S \ 31 | system/atari/model.c \ 32 | system/atari/option.c \ 33 | system/atari/print.c \ 34 | system/atari/psg.c \ 35 | system/atari/psgplay.c \ 36 | system/atari/timer.c 37 | 38 | define SYSTEM_ATARI_target 39 | SYSTEM_ATARI_OBJ += system/atari/$(notdir $(basename $(1))).o 40 | system/atari/$(notdir $(basename $(1))).o: $(1) 41 | $$(QUIET_CC)$$(TARGET_CC) $$(PRG_CFLAGS) $$(TARGET_CFLAGS) -c -o $$@ $$< 42 | endef 43 | 44 | $(foreach f,$(SYSTEM_ATARI_SRC),$(eval $(call SYSTEM_ATARI_target,$(f)))) 45 | 46 | ALL_OBJ += $(SYSTEM_ATARI_OBJ) $(PSGPLAY_PRG_OBJ) 47 | 48 | $(PSGPLAY_PRG_OBJ): $(TOSLIBC) $(TOSLIBC_SCRIPT_PRG_LD) $(SYSTEM_ATARI_OBJ) 49 | $(QUIET_LINK)$(TARGET_LD) $(PRG_LDFLAGS) $(TARGET_LDFLAGS) -o $@ \ 50 | $(SYSTEM_ATARI_OBJ) $(TOSLIBC) 51 | 52 | $(PSGPLAY_TOS): $(TOSLINK) 53 | 54 | $(PSGPLAY_TOS): $(PSGPLAY_PRG_OBJ) 55 | $(QUIET_LINK)$(TOSLINK) -o $@ $< 56 | 57 | OTHER_CLEAN += $(PSGPLAY_TOS) 58 | -------------------------------------------------------------------------------- /include/atari/device.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef ATARI_DEVICE_H 7 | #define ATARI_DEVICE_H 8 | 9 | #include "internal/types.h" 10 | 11 | struct device_cycle { 12 | u64 c; 13 | }; 14 | 15 | struct device_slice { 16 | u64 s; 17 | }; 18 | 19 | struct device_state { 20 | void *internal; 21 | size_t size; 22 | }; 23 | 24 | struct device { 25 | const char *name; 26 | 27 | bool main_bus; 28 | struct { 29 | u32 frequency; 30 | u32 divisor; 31 | } clk; 32 | 33 | struct { 34 | u32 address; 35 | u32 size; 36 | } bus; 37 | 38 | struct device_state state; 39 | 40 | struct device_slice (*run)(const struct device *device, 41 | struct device_cycle, struct device_slice); 42 | void (*reset)(const struct device *device); 43 | void (*event)(const struct device *device, 44 | struct device_cycle device_cycle); 45 | 46 | u8 (*rd_u8)(const struct device *device, u32 dev_address); 47 | u16 (*rd_u16)(const struct device *device, u32 dev_address); 48 | 49 | void (*wr_u8)(const struct device *device, u32 dev_address, u8 data); 50 | void (*wr_u16)(const struct device *device, u32 dev_address, u16 data); 51 | 52 | size_t (*id_u8)(const struct device *device, 53 | u32 dev_address, char *buf, size_t size); 54 | size_t (*id_u16)(const struct device *device, 55 | u32 dev_address, char *buf, size_t size); 56 | }; 57 | 58 | bool valid_device_bus_address(u32 bus_address, const struct device *dev); 59 | 60 | const struct device *device_for_bus_address(u32 bus_address); 61 | 62 | struct device_cycle device_cycle(const struct device *device); 63 | 64 | struct device_cycle device_from_machine_cycle( 65 | const struct device *device, u64 machine_cycle); 66 | 67 | void request_device_event(const struct device *device, 68 | struct device_cycle device_cycle); 69 | 70 | void device_reset(void); 71 | 72 | u64 device_run(u64 machine_cycle, u64 machine_cycle_slice); 73 | 74 | #endif /* ATARI_DEVICE_H */ 75 | -------------------------------------------------------------------------------- /test/dmapitch-verify.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | 5 | #include "toslibc/asm/machine.h" 6 | 7 | #include "test/report.h" 8 | #include "test/verify.h" 9 | #include "test/dmapitch.h" 10 | 11 | test_value_time_names(struct dma_preset, tune_value_time_names); 12 | 13 | static double dma_sound_frequency(const struct options *options) 14 | { 15 | const int d = 1 << (3 - test_value(options).rate); 16 | 17 | return ATARI_STE_EXT_OSC / (double)(ATARI_STE_SND_DMA_CLK_DIV * d); 18 | } 19 | 20 | static int dma_sample_period(const struct options *options) 21 | { 22 | return 2 * test_value(options).halfperiod; 23 | } 24 | 25 | static double dma_sample_frequency(const struct options *options) 26 | { 27 | return dma_sound_frequency(options) / dma_sample_period(options); 28 | } 29 | 30 | void report(struct strbuf *sb, const struct audio *audio, 31 | const struct options *options) 32 | { 33 | const struct test_wave_deviation wave_deviation = 34 | test_wave_deviation(audio); 35 | 36 | report_input(sb, audio, test_name(options), options); 37 | 38 | sbprintf(sb, 39 | "dma sound frequency %f Hz\n" 40 | "dma sample period %d samples\n", 41 | dma_sound_frequency(options), 42 | dma_sample_period(options)); 43 | 44 | report_wave_estimate(sb, audio->format, wave_deviation, 45 | dma_sample_frequency(options)); 46 | } 47 | 48 | const char *verify(const struct audio *audio, const struct options *options) 49 | { 50 | const struct test_wave_deviation wave_deviation = 51 | test_wave_deviation(audio); 52 | const struct test_wave_error error = test_wave_error( 53 | audio->format, wave_deviation, dma_sample_frequency(options)); 54 | 55 | verify_assert (audio_duration(audio->format) >= 60.0) 56 | return "sample duration"; 57 | 58 | verify_assert (wave_deviation.deviation.maximum <= 1.5) 59 | return "wave deviation max"; 60 | 61 | verify_assert (error.relative_frequency <= 62 | audio_relative_tolerance(audio->format)) 63 | return "wave error relative frequency"; 64 | 65 | return NULL; 66 | } 67 | -------------------------------------------------------------------------------- /test/tempo-verify.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include "toslibc/asm/machine.h" 4 | 5 | #include "atari/machine.h" 6 | 7 | #include "test/report.h" 8 | #include "test/verify.h" 9 | #include "test/tempo.h" 10 | 11 | test_value_time_names(struct timer_preset, tune_value_time_names); 12 | 13 | static double timer_frequency(const struct options *options) 14 | { 15 | const struct timer_preset preset = test_value(options); 16 | 17 | return (double)ATARI_MFP_XTAL / (preset.divisor * preset.count); 18 | } 19 | 20 | void report(struct strbuf *sb, const struct audio *audio, 21 | const struct options *options) 22 | { 23 | const struct timer_preset preset = test_value(options); 24 | const struct test_wave_deviation wave_deviation = 25 | test_wave_deviation(audio); 26 | 27 | report_input(sb, audio, test_name(options), options); 28 | 29 | sbprintf(sb, 30 | "timer clock %d Hz\n" 31 | "timer divisor %d cycles\n" 32 | "timer count %d cycles\n" 33 | "timer frequency %f Hz\n", 34 | ATARI_MFP_XTAL, 35 | preset.divisor, 36 | preset.count, 37 | timer_frequency(options)); 38 | 39 | /* One interrupt is half a period, so multiply with 0.5 accordingly. */ 40 | report_wave_estimate(sb, audio->format, wave_deviation, 41 | 0.5 * timer_frequency(options)); 42 | } 43 | 44 | const char *verify(const struct audio *audio, const struct options *options) 45 | { 46 | const struct test_wave_deviation wave_deviation = 47 | test_wave_deviation(audio); 48 | 49 | /* One interrupt is half a period, so multiply with 0.5 accordingly. */ 50 | const struct test_wave_error error = test_wave_error( 51 | audio->format, wave_deviation, 52 | 0.5 * timer_frequency(options)); 53 | 54 | verify_assert (audio_duration(audio->format) >= 60.0) 55 | return "sample duration"; 56 | 57 | verify_assert (wave_deviation.deviation.maximum <= 5.0) 58 | return "wave deviation max"; 59 | 60 | verify_assert (error.relative_frequency <= 61 | audio_relative_tolerance(audio->format)) 62 | return "wave error relative frequency"; 63 | 64 | return NULL; 65 | } 66 | -------------------------------------------------------------------------------- /lib/atari/mmu-trace.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "internal/types.h" 11 | 12 | #include "atari/bus.h" 13 | #include "atari/machine.h" 14 | 15 | static void mmu_trace(const char *op, u32 dev_address, 16 | const char *spacing, int size, u32 value, 17 | size_t (*sh)(const struct device *device, 18 | u32 dev_address, char *buf, size_t size), 19 | const struct device *dev) 20 | { 21 | char description[256]; 22 | 23 | /* FIXME: if (!dev->trace.format) with dev->trace.format(fmt, ...) */ 24 | return; 25 | 26 | if (strcmp(dev->name, "dma") == 0) 27 | goto trace; 28 | if (strcmp(dev->name, "mfp") == 0) 29 | goto trace; 30 | if (strcmp(dev->name, "psg") == 0) 31 | goto trace; 32 | if (dev->bus.address + dev_address < 2048) 33 | goto trace; 34 | 35 | trace: 36 | 37 | if (sh) 38 | sh(dev, dev_address, description, sizeof(description)); 39 | else 40 | description[0] = '\0'; 41 | 42 | if (description[0] != '\0') 43 | printf("%s %8" PRIu64 " %6x: %s %s%.*x %s\n", 44 | dev->name, machine_cycle(), 45 | dev->bus.address + dev_address, 46 | op, spacing, size, value, 47 | description); 48 | else 49 | printf("%s %8" PRIu64 " %6x: %s %s%.*x\n", 50 | dev->name, machine_cycle(), 51 | dev->bus.address + dev_address, 52 | op, spacing, size, value); 53 | } 54 | 55 | void mmu_trace_rd_u8(u32 dev_address, u32 value, const struct device *dev) 56 | { 57 | mmu_trace("rd u8", dev_address, " ", 2, value, dev->id_u8, dev); 58 | } 59 | 60 | void mmu_trace_rd_u16(u32 dev_address, u32 value, const struct device *dev) 61 | { 62 | mmu_trace("rd u16", dev_address, "", 4, value, dev->id_u16, dev); 63 | } 64 | 65 | void mmu_trace_wr_u8(u32 dev_address, u32 value, const struct device *dev) 66 | { 67 | mmu_trace("wr u8", dev_address, " ", 2, value, dev->id_u8, dev); 68 | } 69 | 70 | void mmu_trace_wr_u16(u32 dev_address, u32 value, const struct device *dev) 71 | { 72 | mmu_trace("wr u16", dev_address, "", 4, value, dev->id_u16, dev); 73 | } 74 | -------------------------------------------------------------------------------- /include/internal/macro.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef INTERNAL_MACRO_H 4 | #define INTERNAL_MACRO_H 5 | 6 | /* Macro definitions from the Linux kernel. */ 7 | 8 | #define __ALIGN__MASK(x, mask) (((x) + (mask)) & ~(mask)) 9 | #define __ALIGN_(x, a) __ALIGN__MASK(x, (typeof(x))(a) - 1) 10 | #define ALIGN(x, a) __ALIGN_((x), (a)) 11 | 12 | #define STR(x) #x 13 | #define XSTR(x) STR(x) 14 | 15 | #define CONCAT__(a, b) a ## b 16 | #define CONCATENATE(a, b) CONCAT__(a, b) 17 | 18 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 19 | 20 | /* This counts to 32. Any more, it will return the 33rd argument. */ 21 | #define __COUNT_ARGS__( \ 22 | _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ 23 | _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \ 24 | _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _n, X...) _n 25 | #define COUNT_ARGS(X...) __COUNT_ARGS__(, ##X, \ 26 | 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, \ 27 | 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, \ 28 | 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 29 | 30 | /* Indirect macros required for expanded argument pasting, eg. __LINE__. */ 31 | #define ___PASTE(a,b) a##b 32 | #define __PASTE(a,b) ___PASTE(a,b) 33 | 34 | #define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__) 35 | 36 | /* 37 | * This returns a constant expression while determining if an argument is 38 | * a constant expression, most importantly without evaluating the argument. 39 | * Glory to Martin Uecker 40 | */ 41 | #define __is_constexpr(x) \ 42 | (sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8))) 43 | 44 | #define __no_side_effects(x, y) \ 45 | (__is_constexpr(x) && __is_constexpr(y)) 46 | 47 | #define preserve(x) \ 48 | for (typeof(x) x__ = (x), y__ = 0; !y__; (x) = x__, y__ = !y__) 49 | 50 | #define swap(a, b) \ 51 | do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0) 52 | 53 | #define NORETURN __attribute__((__noreturn__)) 54 | 55 | #define __mode(x) __attribute__((__mode__(x))) 56 | 57 | #endif /* INTERNAL_MACRO_H */ 58 | -------------------------------------------------------------------------------- /lib/internal/print.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "internal/compare.h" 11 | #include "internal/macro.h" 12 | #include "internal/print.h" 13 | #include "internal/types.h" 14 | 15 | const char *progname __attribute__((weak)) = "psgplay"; 16 | 17 | static void report(const char *prefix, const char *suffix, 18 | const char *fmt, va_list ap) 19 | { 20 | char msg[4096]; 21 | 22 | vsnprintf(msg, sizeof(msg), fmt, ap); 23 | 24 | fprintf(stderr, "%s: %s%s%s%s%s", progname, 25 | prefix, prefix[0] ? ": " : "", 26 | suffix, suffix[0] ? ": " : "", 27 | msg); 28 | } 29 | 30 | void pr_warn(const char *fmt, ...) 31 | { 32 | va_list ap; 33 | 34 | va_start(ap, fmt); 35 | report("warning", "", fmt, ap); 36 | va_end(ap); 37 | } 38 | 39 | void pr_warn_errno(const char *s) 40 | { 41 | pr_warn("%s: %s\n", s, strerror(errno)); 42 | } 43 | 44 | void pr_error(const char *fmt, ...) 45 | { 46 | va_list ap; 47 | 48 | va_start(ap, fmt); 49 | report("error", "", fmt, ap); 50 | va_end(ap); 51 | } 52 | 53 | void pr_errno(const char *s) 54 | { 55 | pr_error("%s: %s\n", s, strerror(errno)); 56 | } 57 | 58 | void NORETURN pr_fatal_error(const char *fmt, ...) 59 | { 60 | va_list ap; 61 | 62 | va_start(ap, fmt); 63 | report("error", "", fmt, ap); 64 | va_end(ap); 65 | 66 | exit(EXIT_FAILURE); 67 | } 68 | 69 | void NORETURN pr_fatal_errno(const char *s) 70 | { 71 | pr_fatal_error("%s: %s\n", s, strerror(errno)); 72 | } 73 | 74 | void pr_bug_warn(const char *file, int line, 75 | const char *func, const char *fmt, ...) 76 | { 77 | char prefix[4096]; 78 | va_list ap; 79 | 80 | snprintf(prefix, sizeof(prefix), "%s:%d: %s", file, line, func); 81 | 82 | va_start(ap, fmt); 83 | report("WARNING", prefix, fmt, ap); 84 | va_end(ap); 85 | } 86 | 87 | void NORETURN pr_bug(const char *file, int line, 88 | const char *func, const char *expr) 89 | { 90 | fprintf(stderr, "%s: BUG: %s:%d: %s: %s\n", 91 | progname, file, line, func, expr); 92 | 93 | exit(EXIT_FAILURE); 94 | } 95 | -------------------------------------------------------------------------------- /lib/vt/ecma48.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include "internal/compare.h" 10 | 11 | #include "vt/ecma48.h" 12 | 13 | #define ECMA48_STR(s_) (struct vt_text) { .s = s_ } 14 | #define ECMA48_CSI "\033[" 15 | #define ECMA48_CSI_STR(s_) ECMA48_STR(ECMA48_CSI s_) 16 | #define ECMA48_SGR(n_) ECMA48_CSI #n_ "m" 17 | #define ECMA48_SGR_STR(n_) ECMA48_STR(ECMA48_SGR(n_)) 18 | 19 | #define ECMA48_DEESCAPE(d) \ 20 | d(CUU, ECMA48_CSI "A", "\xE2\x86\x91") /* Up */ \ 21 | d(CUD, ECMA48_CSI "B", "\xE2\x86\x93") /* Down */ \ 22 | d(CUF, ECMA48_CSI "C", "\xE2\x86\x92") /* Forward */ \ 23 | d(CUB, ECMA48_CSI "D", "\xE2\x86\x90") /* Backward */ 24 | 25 | /* Proper ECMA-48 */ 26 | static struct vt_text ecma48_clear(void) { return ECMA48_CSI_STR("2J"); } 27 | static struct vt_text ecma48_normal(void) { return ECMA48_SGR_STR(27); }; 28 | static struct vt_text ecma48_reset(void) { return ECMA48_SGR_STR(0); }; 29 | static struct vt_text ecma48_reverse(void) { return ECMA48_SGR_STR(7); }; 30 | 31 | /* VT320 extensions */ 32 | static struct vt_text vt320_hide(void) { return ECMA48_CSI_STR("?25l"); }; 33 | static struct vt_text vt320_show(void) { return ECMA48_CSI_STR("?25h"); }; 34 | 35 | static struct vt_text ecma48_position(int row, int col) 36 | { 37 | struct vt_text t; 38 | 39 | t.s = NULL; 40 | snprintf(t.b, sizeof(t.b), ECMA48_CSI "%d;%dH", 41 | clamp(row + 1, 1, 1000), 42 | clamp(col + 1, 1, 1000)); 43 | 44 | return t; 45 | }; 46 | 47 | static struct vt_text ecma48_deescape(const char *s) 48 | { 49 | #define ECMA48_DEESCAPE_S(label_, escape_, utf8_) \ 50 | if (strcmp(s, escape_) == 0) \ 51 | return ECMA48_STR(utf8_); 52 | ECMA48_DEESCAPE(ECMA48_DEESCAPE_S) 53 | 54 | return ECMA48_STR(""); 55 | } 56 | 57 | const struct vt_cmd ecma48 = { 58 | .clear = ecma48_clear, 59 | .hide = vt320_hide, 60 | .normal = ecma48_normal, 61 | .position = ecma48_position, 62 | .reset = ecma48_reset, 63 | .reverse = ecma48_reverse, 64 | .show = vt320_show, 65 | .deescape = ecma48_deescape 66 | }; 67 | -------------------------------------------------------------------------------- /include/internal/build-assert.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef INTERNAL_BUILD_ASSERT_H 4 | #define INTERNAL_BUILD_ASSERT_H 5 | 6 | /* Macro definitions from the Linux kernel. */ 7 | 8 | #define __compiletime_error(message) __attribute__((__error__(message))) 9 | 10 | #ifdef __OPTIMIZE__ 11 | # define __compiletime_assert(condition, msg, prefix, suffix) \ 12 | do { \ 13 | extern void prefix ## suffix(void) __compiletime_error(msg); \ 14 | if (!(condition)) \ 15 | prefix ## suffix(); \ 16 | } while (0) 17 | #else 18 | # define __compiletime_assert(condition, msg, prefix, suffix) do { } while (0) 19 | #endif 20 | 21 | #define _compiletime_assert(condition, msg, prefix, suffix) \ 22 | __compiletime_assert(condition, msg, prefix, suffix) 23 | 24 | /** 25 | * compiletime_assert - break build and emit msg if condition is false 26 | * @condition: a compile-time constant condition to check 27 | * @msg: a message to emit if condition is false 28 | * 29 | * In tradition of POSIX assert, this macro will break the build if the 30 | * supplied condition is *false*, emitting the supplied error message if the 31 | * compiler has support to do so. 32 | */ 33 | #define compiletime_assert(condition, msg) \ 34 | _compiletime_assert(condition, msg, __compiletime_assert_, __LINE__) 35 | 36 | /** 37 | * BUILD_BUG_ON_MSG - break compile if a condition is true & emit supplied 38 | * error message. 39 | * @condition: the condition which the compiler should know is false. 40 | * 41 | * See BUILD_BUG_ON for description. 42 | */ 43 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg) 44 | 45 | /** 46 | * BUILD_BUG_ON - break compile if a condition is true. 47 | * @condition: the condition which the compiler should know is false. 48 | * 49 | * If you have some code which relies on certain constants being equal, or 50 | * some other compile-time-evaluated condition, you should use BUILD_BUG_ON to 51 | * detect if someone changes it. 52 | */ 53 | #define BUILD_BUG_ON(condition) \ 54 | BUILD_BUG_ON_MSG(condition, "BUILD_BUG_ON failed: " #condition) 55 | 56 | #endif /* INTERNAL_BUILD_ASSERT_H */ 57 | -------------------------------------------------------------------------------- /system/unix/poll-fifo.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "internal/assert.h" 11 | #include "internal/compare.h" 12 | #include "internal/print.h" 13 | 14 | #include "system/unix/poll-fifo.h" 15 | 16 | static bool fifo_ready_in(struct fifo *f) 17 | { 18 | return f && !fifo_full(f); 19 | } 20 | 21 | static bool fifo_ready_out(struct fifo *f) 22 | { 23 | return f && !fifo_empty(f); 24 | } 25 | 26 | bool poll_fifo(const struct poll_fifo *pfs, size_t n, int timeout) 27 | { 28 | struct pollfd pfds[n]; 29 | 30 | for (size_t i = 0; i < n; i++) 31 | pfds[i] = (struct pollfd) { 32 | .fd = pfs[i].fd, 33 | .events = (fifo_ready_in(pfs[i].in) ? POLLIN : 0) | 34 | (fifo_ready_out(pfs[i].out) ? POLLOUT : 0) 35 | }; 36 | 37 | const int p = poll(pfds, n, timeout); 38 | 39 | if (p == -1) { 40 | if (errno != EINTR) 41 | pr_fatal_errno("poll_fifo"); 42 | 43 | return false; 44 | } 45 | 46 | for (size_t i = 0; i < n; i++) 47 | if (pfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) 48 | pr_fatal_error("poll_fifo\n"); 49 | 50 | for (size_t i = 0; i < n; i++) { 51 | if (pfs[i].in && (pfds[i].revents & POLLIN)) { 52 | u8 buffer[1024]; 53 | 54 | const size_t s = min(sizeof(buffer), 55 | fifo_remaining(pfs[i].in)); 56 | const size_t r = read(pfs[i].fd, buffer, s); 57 | 58 | if (r == -1) { 59 | if (errno != EAGAIN && 60 | errno != EWOULDBLOCK && 61 | errno != EINTR) 62 | pr_fatal_errno("poll_fifo:read"); 63 | } 64 | 65 | const size_t w = fifo_write(pfs[i].in, buffer, r); 66 | 67 | BUG_ON(w != r); 68 | } 69 | 70 | if (pfs[i].out && (pfds[i].revents & POLLOUT)) { 71 | const void *buffer; 72 | const size_t r = fifo_peek(pfs[i].out, &buffer); 73 | const size_t w = write(pfs[i].fd, buffer, r); 74 | 75 | if (w == -1) { 76 | if (errno != EAGAIN && 77 | errno != EWOULDBLOCK && 78 | errno != EINTR) 79 | pr_fatal_errno("poll_fifo:write"); 80 | } else 81 | fifo_skip(pfs[i].out, w); 82 | } 83 | } 84 | 85 | return true; 86 | } 87 | -------------------------------------------------------------------------------- /test/dmapitch.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "test/dmapitch.h" 10 | 11 | sndh_title("DMA pitch"); 12 | sndh_tune_value_time_names(struct dma_preset, tune_value_time_names); 13 | 14 | #define R1(x) x 15 | #define R2(x) x, x 16 | #define R3(x) x, x, x 17 | #define R5(x) x, x, x, x, x 18 | #define R7(x) x, x, x, x, x, x, x 19 | 20 | struct mono { 21 | int8_t mono; 22 | }; 23 | 24 | struct stereo { 25 | int8_t left; 26 | int8_t right; 27 | }; 28 | 29 | #define M(hp) \ 30 | static const struct mono samples1_##hp[2*hp] = { \ 31 | R##hp({ 127}), \ 32 | R##hp({-127}) \ 33 | } 34 | 35 | #define S(hp) \ 36 | static const struct stereo samples2_##hp[2*hp] = { \ 37 | R##hp({R2( 127)}), \ 38 | R##hp({R2(-127)}) \ 39 | } 40 | 41 | #define SM(hp) M(hp); S(hp) 42 | 43 | SM(1); 44 | SM(2); 45 | SM(3); 46 | SM(5); 47 | SM(7); 48 | 49 | void sndh_init(int tune) 50 | { 51 | const struct dma_preset preset = sndh_tune_select_value(tune); 52 | const int8_t *base = NULL; 53 | 54 | #define CASE(c, hp, f) ((c) << 8) | (hp): base = &samples##c##_##hp[0].f 55 | 56 | switch ((preset.channels << 8) | preset.halfperiod) { 57 | case CASE(1, 1, mono); break; 58 | case CASE(1, 2, mono); break; 59 | case CASE(1, 3, mono); break; 60 | case CASE(1, 5, mono); break; 61 | case CASE(1, 7, mono); break; 62 | case CASE(2, 1, left); break; 63 | case CASE(2, 2, left); break; 64 | case CASE(2, 3, left); break; 65 | case CASE(2, 5, left); break; 66 | case CASE(2, 7, left); break; 67 | } 68 | 69 | const int8_t *end = &base[2 * preset.halfperiod * preset.channels]; 70 | 71 | snd_dma_wr_base(base); 72 | snd_dma_wr_end(end); 73 | snd_dma_wrs_mode({ 74 | .format = preset.channels == 1 ? 75 | SND_DMA_MODE_FORMAT_MONO8 : 76 | SND_DMA_MODE_FORMAT_STEREO8, 77 | .rate = preset.rate, 78 | }); 79 | } 80 | 81 | void sndh_play() 82 | { 83 | if (!snd_dma_rd_ctrl().play) 84 | snd_dma_wrs_ctrl({ .play_repeat = true, .play = true }); 85 | } 86 | 87 | void sndh_exit() 88 | { 89 | snd_dma_wrs_ctrl({ .play = false }); 90 | } 91 | -------------------------------------------------------------------------------- /lib/atari/shifter.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include "toslibc/asm/machine.h" 10 | 11 | #include "atari/device.h" 12 | #include "atari/shifter.h" 13 | 14 | static char *shifter_register_name(u32 reg) 15 | { 16 | switch (reg) { 17 | #define SHIFTER_REG_NAME(register_, symbol_, label_, description_) \ 18 | case register_: return #symbol_; 19 | SHIFTER_REGISTERS(SHIFTER_REG_NAME) 20 | default: 21 | return ""; 22 | } 23 | } 24 | 25 | static u8 shifter_rd_u8(const struct device *device, u32 dev_address) 26 | { 27 | return 0; /* FIXME */ 28 | } 29 | 30 | static u16 shifter_rd_u16(const struct device *device, u32 dev_address) 31 | { 32 | return 0; /* FIXME */ 33 | } 34 | 35 | static void shifter_wr_u8(const struct device *device, u32 dev_address, u8 data) 36 | { 37 | /* FIXME */ 38 | } 39 | 40 | static void shifter_wr_u16(const struct device *device, u32 dev_address, u16 data) 41 | { 42 | /* FIXME */ 43 | } 44 | 45 | static size_t shifter_id_u16(const struct device *device, 46 | u32 dev_address, char *buf, size_t size) 47 | { 48 | snprintf(buf, size, "wr %s", shifter_register_name(dev_address / 2)); /* FIXME */ 49 | 50 | return strlen(buf); 51 | } 52 | 53 | static size_t shifter_id_u8(const struct device *device, 54 | u32 dev_address, char *buf, size_t size) 55 | { 56 | snprintf(buf, size, "wr %s", shifter_register_name(dev_address / 2)); /* FIXME */ 57 | 58 | return strlen(buf); 59 | } 60 | 61 | static void shifter_event(const struct device *device, 62 | const struct device_cycle mfp_cycle) 63 | { 64 | } 65 | 66 | static void shifter_reset(const struct device *device) 67 | { 68 | } 69 | 70 | const struct device shifter_device = { 71 | .name = "shifter", 72 | .clk = { 73 | .frequency = ATARI_STE_PAL_MCLK, 74 | .divisor = 1 75 | }, 76 | .bus = { 77 | .address = 0xff8200, 78 | .size = 512, 79 | }, 80 | .reset = shifter_reset, 81 | .event = shifter_event, 82 | .rd_u8 = shifter_rd_u8, 83 | .rd_u16 = shifter_rd_u16, 84 | .wr_u8 = shifter_wr_u8, 85 | .wr_u16 = shifter_wr_u16, 86 | .id_u8 = shifter_id_u8, 87 | .id_u16 = shifter_id_u16, 88 | }; 89 | -------------------------------------------------------------------------------- /system/unix/info.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "internal/print.h" 11 | 12 | #include "psgplay/sndh.h" 13 | 14 | #include "unicode/atari.h" 15 | 16 | #include "system/unix/file.h" 17 | #include "system/unix/info.h" 18 | #include "system/unix/string.h" 19 | 20 | static void print_flags(const char *flags, struct file file) 21 | { 22 | for (size_t i = 0; flags[i] != '\0'; i++) 23 | switch (flags[i]) { 24 | #define PRINT_SNDH_FLAG(c, label, description) \ 25 | case c: printf(" " #label); break; 26 | SNDH_FLAG(PRINT_SNDH_FLAG) 27 | default: 28 | pr_warn("%s: unrecognised flag '%c'\n", 29 | file.path, flags[i]); 30 | } 31 | } 32 | 33 | void sndh_print(struct file file) 34 | { 35 | size_t header_size; 36 | 37 | int time_count = 0; 38 | int subname_count = 0; 39 | int subflag_count = 0; 40 | 41 | printf("path %s\n", file.path); 42 | 43 | sndh_for_each_tag_with_header_size (file.data, file.size, &header_size) { 44 | const char *name = sndh_tag_name; 45 | const char *value = sndh_tag_value; 46 | const size_t length = strlen(value); 47 | 48 | u8 *u = charset_to_utf8_string((const u8 *)value, length, 49 | charset_atari_st_to_utf32, NULL); 50 | if (!u) 51 | continue; 52 | 53 | char *v = strrep((const char *)u, "\n", "\n\t"); 54 | 55 | if (strcmp(name, "TIME") == 0) { 56 | printf("tag field %s %d %s\n", 57 | name, ++time_count, v); 58 | } else if (strcmp(name, "!#SN") == 0) { 59 | printf("tag field %s %d %s\n", 60 | name, ++subname_count, v); 61 | } else if (strcmp(name, "FLAG~") == 0) { 62 | /* FIXME: Document FLAG~ and FLAG in doc/sndhv21.txt */ 63 | printf("tag field FLAG ~ %s", v); 64 | print_flags(v, file); 65 | printf("\n"); 66 | } else if (strcmp(name, "FLAG") == 0) { 67 | printf("tag field %s %d %s", 68 | name, ++subflag_count, v); 69 | print_flags(v, file); 70 | printf("\n"); 71 | } else 72 | printf("tag field %s%s%s\n", 73 | name, name[0] != '\0' ? " " : "", v); 74 | 75 | free(v); 76 | free(u); 77 | } 78 | 79 | printf("header size %zu\n", header_size); 80 | printf("data size %zu\n", file.size - header_size); 81 | } 82 | -------------------------------------------------------------------------------- /system/unix/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | PSGPLAY_CFLAGS = $(BASIC_HOST_CFLAGS) $(HOST_CFLAGS) 4 | 5 | PSGPLAY := psgplay 6 | 7 | SYSTEM_UNIX_SRC := \ 8 | system/unix/clock.c \ 9 | system/unix/command-mode.c \ 10 | system/unix/diagnostic.c \ 11 | system/unix/file.c \ 12 | system/unix/info.c \ 13 | system/unix/memory.c \ 14 | system/unix/option.c \ 15 | system/unix/poll-fifo.c \ 16 | system/unix/print.c \ 17 | system/unix/psgplay.c \ 18 | system/unix/remake.c \ 19 | system/unix/sndh.c \ 20 | system/unix/string.c \ 21 | system/unix/text-mode.c \ 22 | system/unix/tty.c 23 | 24 | ifeq (1,$(HAVE_SSO)) 25 | SYSTEM_UNIX_SRC += system/unix/disassemble.c 26 | system/unix/system-unix-disassemble.o: PSGPLAY_CFLAGS += -Ilib/toslibc/include 27 | endif 28 | 29 | PSGPLAY_SRC := \ 30 | $(AUDIO_SRC) \ 31 | $(DISASSEMBLE_SRC) \ 32 | $(SYSTEM_UNIX_SRC) \ 33 | $(TEXT_SRC) \ 34 | $(UNICODE_SRC) \ 35 | $(VT_SRC) 36 | 37 | PSGPLAY_object = $(addprefix system/unix/,$(subst /,-,$(1:%.c=%.o))) 38 | 39 | define PSGPLAY_target 40 | PSGPLAY_OBJ += $(call PSGPLAY_object,$(1)) 41 | $(call PSGPLAY_object,$(1)): $(1) 42 | $$(QUIET_CC)$$(HOST_CC) $$(PSGPLAY_CFLAGS) -c -o $$@ $$< 43 | endef 44 | 45 | $(foreach f,$(PSGPLAY_SRC),$(eval $(call PSGPLAY_target,$(f)))) 46 | 47 | ALL_OBJ += $(PSGPLAY_OBJ) system/unix/system-unix-disassemble.o 48 | 49 | ifeq (Darwin,$(BUILD_SYSTEM)) 50 | PSGPLAY_LIBS = 51 | else 52 | PSGPLAY_LIBS = -lm 53 | endif 54 | 55 | ifeq (1,$(ALSA)) 56 | PSGPLAY_ALSA_LIB := $(shell pkg-config --silence-errors --libs alsa || echo -lasound) 57 | PSGPLAY_LIBS += $(PSGPLAY_ALSA_LIB) 58 | endif 59 | 60 | ifeq (1,$(PORTAUDIO)) 61 | PSGPLAY_PORTAUDIO_LIB := $(shell pkg-config --libs portaudio-2.0) 62 | PSGPLAY_PORTAUDIO_CFLAGS := $(shell pkg-config --cflags portaudio-2.0) 63 | PSGPLAY_CFLAGS += $(PSGPLAY_PORTAUDIO_CFLAGS) 64 | PSGPLAY_LIBS += $(PSGPLAY_PORTAUDIO_LIB) 65 | endif 66 | 67 | $(PSGPLAY): $(PSGPLAY_OBJ) $(LIBPSGPLAY_STATIC) 68 | $(QUIET_LINK)$(HOST_LD) $(PSGPLAY_CFLAGS) -o $@ $^ $(PSGPLAY_LIBS) 69 | 70 | .PHONY: install-psgplay 71 | install-psgplay: $(PSGPLAY) 72 | $(INSTALL) -d $(DESTDIR)$(bindir) 73 | $(INSTALL) $(PSGPLAY) $(DESTDIR)$(bindir) 74 | 75 | OTHER_CLEAN += $(PSGPLAY) 76 | -------------------------------------------------------------------------------- /include/internal/string.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef INTERNAL_STRING_H 7 | #define INTERNAL_STRING_H 8 | 9 | #include 10 | 11 | bool strtoint(int *n, const char *s, int base); 12 | 13 | /** 14 | * struct string_split - string split cursor 15 | * @length: length of current substring, not necessarily NUL terminated 16 | * @s: pointer to current substring 17 | * @sep: %true if the current substring is the separator, otherwise %false 18 | * @eos: %true if the current substring is the last one, otherwise %false 19 | */ 20 | struct string_split { 21 | size_t length; 22 | const char *s; 23 | bool sep; 24 | bool eos; 25 | }; 26 | 27 | struct string_split first_string_split( 28 | const char *s, const char *sep, 29 | char *(find)(const char *s, const char *sep)); 30 | 31 | struct string_split next_string_split( 32 | struct string_split split, const char *sep, 33 | char *(find)(const char *s, const char *sep)); 34 | 35 | /** 36 | * for_each_string_split - split a string by a separator 37 | * @split: string split cursor 38 | * @str: string to split 39 | * @sep: separator string 40 | * 41 | * Note that all substrings including the separator is provided to the loop 42 | * statement. Hence, concatenating all substrings gives the original string. 43 | * 44 | * The loop statement is always invoked at least once, even when given the 45 | * empty string to split. 46 | * 47 | * Splitting on the empty separator splits every character. 48 | */ 49 | #define for_each_string_split(split, str, sep) \ 50 | for ((split) = first_string_split((str), (sep), strstr); \ 51 | (split).s; \ 52 | (split) = next_string_split((split), (sep), strstr)) 53 | 54 | struct line_column { 55 | int line; 56 | int column; 57 | }; 58 | 59 | /** 60 | * char_line_column - update line and column given a character 61 | * @c: character for update 62 | * @lc: line and column to update 63 | * 64 | * Return: line and column after character update 65 | */ 66 | struct line_column char_line_column(char c, struct line_column lc); 67 | 68 | /** 69 | * string_line_column - update line and column given a string 70 | * @s: string for update 71 | * @lc: line and column to update 72 | * 73 | * Return: line and column after string update 74 | */ 75 | struct line_column string_line_column(const char *s, struct line_column lc); 76 | 77 | #endif /* INTERNAL_STRING_H */ 78 | -------------------------------------------------------------------------------- /lib/internal/string.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "internal/assert.h" 13 | #include "internal/string.h" 14 | #include "internal/types.h" 15 | 16 | bool strtoint(int *n, const char *s, int base) 17 | { 18 | char *e; 19 | 20 | errno = 0; 21 | const long int m = strtol(s, &e, base); 22 | 23 | if (e != &s[strlen(s)] || errno == ERANGE) 24 | return false; 25 | 26 | if (m < INT_MIN || INT_MAX < m) 27 | return false; 28 | 29 | *n = m; 30 | 31 | return true; 32 | } 33 | 34 | struct string_split first_string_split( 35 | const char *s, const char *sep, 36 | char *(find)(const char *s, const char *sep)) 37 | { 38 | if (s[0] == '\0') 39 | return (struct string_split) { 40 | .length = 0, 41 | .s = s, 42 | .eos = true 43 | }; 44 | 45 | if (sep[0] == '\0') 46 | return (struct string_split) { 47 | .length = 1, 48 | .s = s, 49 | .eos = (s[1] == '\0') 50 | }; 51 | 52 | const char *t = find(s, sep); 53 | 54 | if (!t) 55 | return (struct string_split) { 56 | .length = strlen(s), 57 | .s = s, 58 | .eos = true 59 | }; 60 | 61 | const size_t n = t - s; 62 | 63 | if (!n) { 64 | const size_t sep_len = strlen(sep); 65 | 66 | return (struct string_split) { 67 | .length = sep_len, 68 | .s = s, 69 | .sep = true, 70 | .eos = (s[sep_len] == '\0') 71 | }; 72 | } 73 | 74 | return (struct string_split) { 75 | .length = n, 76 | .s = s, 77 | .eos = (s[n] == '\0') 78 | }; 79 | } 80 | 81 | struct string_split next_string_split( 82 | struct string_split split, const char *sep, 83 | char *(find)(const char *s, const char *sep)) 84 | { 85 | return split.eos ? (struct string_split) { } : 86 | first_string_split(&split.s[split.length], sep, find); 87 | } 88 | 89 | struct line_column char_line_column(char c, struct line_column lc) 90 | { 91 | if (c == '\n') { 92 | lc.line++; 93 | lc.column = 1; 94 | } else 95 | lc.column = 1 + (c == '\t' ? 96 | (((lc.column - 1) >> 3) << 3) + 8 : lc.column); 97 | 98 | return lc; 99 | } 100 | 101 | struct line_column string_line_column(const char *s, struct line_column lc) 102 | { 103 | for (size_t i = 0; s[i] != '\0'; i++) 104 | lc = char_line_column(s[i], lc); 105 | 106 | return lc; 107 | } 108 | -------------------------------------------------------------------------------- /lib/tos/reset.S: -------------------------------------------------------------------------------- 1 | /* The Operating System (TOS) */ 2 | 3 | #include 4 | #include 5 | 6 | .equ TOS_VERSION, 0x0162 /* in BCD */ 7 | .equ TOS_RELEASE, 0x01011990 /* in BCD */ 8 | 9 | .equ TOS_REGION_SE, (TOS_COUNTRY_SE << 1) | TOS_PAL 10 | 11 | .text 12 | 13 | /* 14 | * The first 8 bytes of the ROM are mirrored at address zero: 15 | * 16 | * $000 reset: initial supervisor stack pointer (SSP) 17 | * $004 reset: initial program counter (PC) 18 | * 19 | * The SSP is repurposed to contain a bra.s instruction and 20 | * the TOS version. 21 | */ 22 | .globl _rom 23 | _rom: bra.s .reset 24 | .dc.w TOS_VERSION 25 | .dc.l .reset 26 | 27 | .dc.l 0 /* FIXME: Base of OS */ 28 | .dc.l 0 /* FIXME */ 29 | .dc.l 0 /* Reserved */ 30 | .dc.l 0 /* FIXME */ 31 | .dc.l TOS_RELEASE 32 | .dc.w TOS_REGION_SE 33 | .dc.w 0 /* FIXME: TOS release date in GEMDOS format */ 34 | .dc.l 0 /* FIXME */ 35 | .dc.l 0 /* FIXME */ 36 | .dc.l 0 /* FIXME */ 37 | .dc.l 0 /* FIXME */ 38 | 39 | .reset: move #0x2700,sr /* Disable interrupts */ 40 | lea _usp_top,sp 41 | move sp,%usp /* Set a valid user stack pointer */ 42 | lea _ssp_top,sp /* Set a valid supervisor stack pointer */ 43 | move.b #0x40,0xfffa17 /* Set MFP exception vector base, SEI=0 */ 44 | 45 | /* Ignore all exceptions by default */ 46 | move.l #0x8,a1 47 | .loop: move.l #.ignore,(a1)+ 48 | cmp.l #0x13c,a1 49 | bne.s .loop 50 | 51 | /* 52 | * Initially disable noise and tone in IOMIX PSG register as some 53 | * SNDH files are known to inadvertently play sounds otherwise. 54 | */ 55 | move.l #0x07003f00,0xffff8800.w 56 | 57 | /* Install GEMDOS and XBIOS vectors */ 58 | move.l #.gemdos,0x84 59 | move.l #.xbios,0xb8 60 | 61 | /* Install VBL and timer A, B, C and D vectors */ 62 | move.l #vbl_exception,0x70 63 | move.l #timer_a_exception,0x134 64 | move.l #timer_b_exception,0x120 65 | move.l #timer_c_exception,0x114 66 | move.l #timer_d_exception,0x110 67 | 68 | move.l d2,-(sp) /* u32 timer */ 69 | move.l d1,-(sp) /* u32 track */ 70 | move.l a0,-(sp) /* void *sndh */ 71 | move.l d0,-(sp) /* size_t size */ 72 | jsr start 73 | sub.l #16,sp 74 | 75 | .halt: stop #0x2700 76 | bra.s .halt 77 | 78 | .ignore: 79 | rte 80 | 81 | .macro trap name 82 | .\name: 83 | move.l sp,-(sp) 84 | addq.l #6,(sp) /* skip SR and PC saved by the trap */ 85 | jsr \name\()_trap 86 | addq.l #4,sp 87 | rte 88 | .endm 89 | 90 | trap gemdos 91 | trap xbios 92 | -------------------------------------------------------------------------------- /include/test/report.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_REPORT_H 7 | #define PSGPLAY_TEST_REPORT_H 8 | 9 | #include "audio/audio.h" 10 | 11 | #include "system/unix/string.h" 12 | 13 | #include "test/option.h" 14 | 15 | #define TEST_DEFINE_VALUE(value, _, name) value, 16 | 17 | #define test_values(type, entries) \ 18 | const type test_values_[] = { \ 19 | entries(TEST_DEFINE_VALUE) \ 20 | } 21 | 22 | #define TEST_DEFINE_NAME(value, _, name) name, 23 | 24 | #define test_names(entries) \ 25 | const char *test_names_[] = { \ 26 | entries(TEST_DEFINE_NAME) \ 27 | } 28 | 29 | #define test_value_time_names(type, entries) \ 30 | test_values(type, entries); \ 31 | test_names(entries); \ 32 | \ 33 | static inline type test_value(const struct options *options) \ 34 | { \ 35 | return test_values_[options->track - 1]; \ 36 | } \ 37 | \ 38 | static inline const char *test_name(const struct options *options) \ 39 | { \ 40 | return test_names_[options->track - 1]; \ 41 | } 42 | 43 | #define test_value_frames_names(type, entries) \ 44 | test_values(type, entries); \ 45 | test_names(entries); \ 46 | \ 47 | static inline type test_value(const struct options *options) \ 48 | { \ 49 | return test_values_[options->track - 1]; \ 50 | } \ 51 | \ 52 | static inline const char *test_name(const struct options *options) \ 53 | { \ 54 | return test_names_[options->track - 1]; \ 55 | } 56 | 57 | struct test_wave_deviation { 58 | struct audio_wave wave; 59 | struct audio_zero_crossing_periodic_deviation deviation; 60 | }; 61 | 62 | struct test_wave_error { 63 | double absolute_frequency; 64 | double relative_frequency; 65 | }; 66 | 67 | void report(struct strbuf *sb, const struct audio *audio, 68 | const struct options *options); 69 | 70 | void report_input(struct strbuf *sb, const struct audio *audio, 71 | const char *name, const struct options *options); 72 | 73 | struct test_wave_deviation test_wave_deviation(const struct audio *audio); 74 | 75 | struct test_wave_error test_wave_error(struct audio_format audio_format, 76 | struct test_wave_deviation wave_deviation, double reference_frequency); 77 | 78 | void report_wave_estimate(struct strbuf *sb, struct audio_format audio_format, 79 | struct test_wave_deviation wave_deviation, double reference_frequency); 80 | 81 | #endif /* PSGPLAY_TEST_REPORT_H */ 82 | -------------------------------------------------------------------------------- /script/compile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -o pipefail 5 | 6 | compile="$0" 7 | JOBS="$(getconf _NPROCESSORS_ONLN)" 8 | V="${V:+V=1}" 9 | 10 | clean() 11 | { 12 | make clean 13 | 14 | local CLEAN=$(git clean -n -fdx) 15 | if [ -n "$CLEAN" ] 16 | then 17 | echo "$compile: Unclean:" >&2 18 | echo "$CLEAN" >&2 19 | exit 1 20 | fi 21 | } 22 | 23 | make_jv() 24 | { 25 | make -j"$JOBS" $V $@ 26 | } 27 | 28 | which_mac_homebrew_gcc() 29 | { 30 | local prefix="${HOMEBREW_PREFIX:-$(brew --prefix)}" 31 | local path="$(find -L "$prefix/bin" -name 'gcc-[0-9]*' 2>/dev/null | 32 | sort -Vr | head -n1)" 33 | 34 | if [ -z "$path" ] 35 | then 36 | echo "$compile: Error: Homebrew gcc- not found." >&2 37 | exit 1 38 | fi 39 | 40 | echo "$path" 41 | } 42 | 43 | arch() 44 | { 45 | [ -z "$1" ] && exit 46 | 47 | echo "$compile $@ JOBS=$JOBS" $V 48 | clean 49 | 50 | case "$1" in 51 | cross) 52 | arch atari-st web "$(uname -m)" 53 | ;; 54 | atari-st) 55 | m68k-elf-gcc --version | sed -n 1p 56 | make_jv S=1 TARGET_COMPILE=m68k-elf- PSGPLAY.TOS lib/tos/tos 57 | ;; 58 | test) 59 | m68k-elf-gcc --version | sed -n 1p 60 | make_jv S=1 TARGET_COMPILE=m68k-elf- test 61 | ;; 62 | verify) 63 | m68k-elf-gcc --version | sed -n 1p 64 | make_jv S=1 TARGET_COMPILE=m68k-elf- verify 65 | make_jv S=1 TARGET_COMPILE=m68k-elf- report 66 | ;; 67 | mac) 68 | cc --version | sed -n 1p 69 | make_jv psgplay example 70 | ;; 71 | mac-homebrew-gcc-portaudio) 72 | local HOMEBREW_GCC="$(which_mac_homebrew_gcc)" 73 | echo "$HOMEBREW_GCC $("$HOMEBREW_GCC" --version | sed -n 1p)" 74 | make_jv PORTAUDIO=1 CC="$HOMEBREW_GCC" LD=/usr/bin/clang 75 | ;; 76 | web) 77 | emcc --version | sed -n 1p 78 | make_jv HOST_CC=emcc web 79 | ;; 80 | ppc64le |\ 81 | aarch64) 82 | cc --version | sed -n 1p 83 | make_jv ALSA=1 all test 84 | ;; 85 | x86_64) 86 | gcc --version | sed -n 1p 87 | make_jv S=1 ALSA=1 CC=gcc all test 88 | clean 89 | 90 | clang --version | sed -n 1p 91 | make_jv S=1 ALSA=1 CC=clang psgplay 92 | clean 93 | 94 | cc --version | sed -n 1p 95 | make_jv psgplay 96 | clean 97 | 98 | cc --version | sed -n 1p 99 | make_jv ALSA=1 psgplay 100 | ;; 101 | *) 102 | echo "$compile: '$1' is not an arch." >&2 103 | exit 1 104 | esac 105 | 106 | shift 107 | arch "$@" 108 | } 109 | 110 | case "$1" in 111 | arch) 112 | shift 113 | arch "$@" 114 | ;; 115 | *) 116 | echo "$compile: '$1' is not a command." >&2 117 | exit 1 118 | esac 119 | -------------------------------------------------------------------------------- /lib/atari/glue.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | 8 | #include "toslibc/asm/machine.h" 9 | 10 | #include "internal/assert.h" 11 | 12 | #include "atari/bus.h" 13 | #include "atari/glue.h" 14 | #include "atari/irq.h" 15 | #include "atari/m68k.h" 16 | #include "atari/mfp.h" 17 | #include "atari/machine.h" 18 | 19 | #include "m68k/m68k.h" 20 | 21 | static struct device_cycle vbl_cycle; 22 | 23 | static u32 irq_pending; 24 | 25 | void glue_irq_set(int irq) 26 | { 27 | const u32 p = irq_pending; 28 | 29 | BUG_ON(irq < 1 || 7 < irq); 30 | 31 | irq_pending |= 1 << irq; 32 | 33 | if (irq_pending > p) 34 | m68k_set_irq(irq); 35 | } 36 | 37 | void glue_irq_clr(int irq) 38 | { 39 | int i; 40 | 41 | irq_pending &= ~(1u << irq); 42 | 43 | for (i = 7; i > 0; i--) 44 | if (irq_pending & (1u << i)) 45 | break; 46 | 47 | m68k_set_irq(i); 48 | } 49 | 50 | static void request_vbl_event(struct device_cycle device_cycle) 51 | { 52 | const u64 pal_vbl = ATARI_STE_CYCLES_PER_VBL_PAL; 53 | 54 | vbl_cycle.c = device_cycle.c + (pal_vbl - (device_cycle.c % pal_vbl)); 55 | 56 | request_device_event(&glue_device, vbl_cycle); 57 | } 58 | 59 | static void vbl_execute(struct device_cycle device_cycle) 60 | { 61 | if (vbl_cycle.c) 62 | glue_irq_set(IRQ_VBL); 63 | } 64 | 65 | static int glue_hbl(void) 66 | { 67 | glue_irq_clr(IRQ_VBL); 68 | 69 | return 26; /* FIXME: HBL exception vector */ 70 | } 71 | 72 | static int glue_vbl(void) 73 | { 74 | glue_irq_clr(IRQ_VBL); 75 | 76 | return 28; /* FIXME: VBL exception vector */ 77 | } 78 | 79 | static int glue_mfp(void) 80 | { 81 | glue_irq_clr(IRQ_MFP); 82 | 83 | return mfp_irq_vector(); 84 | } 85 | 86 | static void glue_event(const struct device *device, 87 | struct device_cycle device_cycle) 88 | { 89 | if (vbl_cycle.c <= device_cycle.c) 90 | vbl_execute(device_cycle); 91 | 92 | request_vbl_event(device_cycle); 93 | } 94 | 95 | static void glue_reset(const struct device *device) 96 | { 97 | request_vbl_event(device_cycle(&glue_device)); 98 | } 99 | 100 | int m68k_int_ack_callback(int level) 101 | { 102 | switch(level) 103 | { 104 | case IRQ_HBL: return glue_hbl(); 105 | case IRQ_VBL: return glue_vbl(); 106 | case IRQ_MFP: return glue_mfp(); 107 | default: return M68K_INT_ACK_SPURIOUS; 108 | } 109 | } 110 | 111 | const struct device glue_device = { 112 | .name = "glue", 113 | .clk = { 114 | .frequency = CPU_FREQUENCY, 115 | .divisor = 1 116 | }, 117 | .reset = glue_reset, 118 | .event = glue_event, 119 | }; 120 | -------------------------------------------------------------------------------- /lib/atari/ram.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include "internal/compare.h" 10 | 11 | #include "atari/device.h" 12 | #include "atari/exception-vector.h" 13 | #include "atari/machine.h" 14 | #include "atari/ram.h" 15 | #include "atari/sound.h" 16 | #include "atari/system-variable.h" 17 | 18 | #include "tos/tos.h" 19 | 20 | static u8 ram[4 * 1024 * 1024]; /* 4 MiB of RAM */ 21 | 22 | struct ram_map_ro ram_map_ro(uint32_t size, uint32_t addr) 23 | { 24 | if (ARRAY_SIZE(ram) <= addr) 25 | return (struct ram_map_ro) { }; 26 | 27 | return (struct ram_map_ro) { 28 | .size = min_t(uint32_t, size, ARRAY_SIZE(ram) - addr), 29 | .addr = addr, 30 | .p = &ram[addr], 31 | }; 32 | } 33 | 34 | static void ram_reset(const struct device *device) 35 | { 36 | memcpy(&ram[0], tos, 8); /* ROM overlay during reset */ 37 | memset(&ram[8], 0, sizeof(ram) - 8); 38 | } 39 | 40 | static u8 ram_rd_u8(const struct device *device, u32 dev_address) 41 | { 42 | return ram[dev_address]; 43 | } 44 | 45 | static u16 ram_rd_u16(const struct device *device, u32 dev_address) 46 | { 47 | return (ram[dev_address] << 8) | ram[dev_address + 1]; 48 | } 49 | 50 | static void ram_wr_u8(const struct device *device, 51 | u32 dev_address, u8 data) 52 | { 53 | sound_check(dev_address); 54 | 55 | ram[dev_address] = data; 56 | } 57 | 58 | static void ram_wr_u16(const struct device *device, 59 | u32 dev_address, u16 data) 60 | { 61 | sound_check(dev_address); 62 | 63 | ram[dev_address] = data >> 8; 64 | ram[dev_address + 1] = data & 0xff; 65 | } 66 | 67 | static size_t ram_id_u8(const struct device *device, 68 | u32 dev_address, char *buf, size_t size) 69 | { 70 | const char *description = exception_vector_description(dev_address); 71 | 72 | if (description[0] == '\0') 73 | description = system_variable_label(dev_address); 74 | 75 | snprintf(buf, size, "%s", description); 76 | 77 | return strlen(buf); 78 | } 79 | 80 | static size_t ram_id_u16(const struct device *device, 81 | u32 dev_address, char *buf, size_t size) 82 | { 83 | return ram_id_u8(device, dev_address, buf, size); 84 | } 85 | 86 | const struct device ram_device = { 87 | .name = "ram", 88 | .clk = { 89 | .frequency = CPU_FREQUENCY, 90 | .divisor = 1 91 | }, 92 | .main_bus = true, 93 | .bus = { 94 | .address = 0, 95 | .size = sizeof(ram), 96 | }, 97 | .reset = ram_reset, 98 | .rd_u8 = ram_rd_u8, 99 | .rd_u16 = ram_rd_u16, 100 | .wr_u8 = ram_wr_u8, 101 | .wr_u16 = ram_wr_u16, 102 | .id_u8 = ram_id_u8, 103 | .id_u16 = ram_id_u16, 104 | }; 105 | -------------------------------------------------------------------------------- /system/atari/psgplay.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "internal/check-compiler.h" 17 | #include "internal/compare.h" 18 | 19 | #include "psgplay/sndh.h" 20 | 21 | #include "text/mode.h" 22 | #include "text/mvc.h" 23 | 24 | #include "vt/vt52.h" 25 | 26 | #include "system/atari/clock.h" 27 | #include "system/atari/model.h" 28 | #include "system/atari/option.h" 29 | #include "system/atari/psg.h" 30 | #include "system/atari/timer.h" 31 | 32 | const char *progname = "PSGPLAY"; 33 | 34 | bool timer_c(uint32_t vector, struct xbra_regs *regs, void *arg) 35 | { 36 | struct text_sndh *sndh = arg; 37 | 38 | model_timer(sndh); 39 | 40 | clock_tick(); 41 | 42 | return true; 43 | } 44 | 45 | static unicode_t read_key(void) 46 | { 47 | if (!gemdos_cconis()) 48 | return 0; 49 | 50 | const u32 k = gemdos_cnecin(); 51 | 52 | switch ((k >> 16) & 0xff) { 53 | case 0x4b: return U_ARROW_LEFT; 54 | case 0x48: return U_ARROW_UP; 55 | case 0x4d: return U_ARROW_RIGHT; 56 | case 0x50: return U_ARROW_DOWN; 57 | } 58 | 59 | return k & 0xff; 60 | } 61 | 62 | static void write_vt(struct fifo *f) 63 | { 64 | for (;;) { 65 | char s[256]; 66 | const size_t r = fifo_read(f, s, sizeof(s) - 1); 67 | 68 | if (!r) 69 | break; 70 | 71 | s[r] = '\0'; 72 | gemdos_cconws(s); 73 | } 74 | } 75 | 76 | int main(int argc, char *argv[]) 77 | { 78 | struct options *options = parse_options(argc, argv); 79 | 80 | static DEFINE_FIFO(scr_out, 1024); 81 | static DEFINE_VT(scr_vt, 40, 25, &vt52); 82 | 83 | struct text_state model = { .path = options->input }; 84 | struct text_state scr_view = { }; 85 | struct text_state ctrl = { }; 86 | struct text_sndh sndh = { }; 87 | 88 | psg_init(); 89 | timer_init(timer_c, &sndh); 90 | 91 | model_update(&model, &ctrl, &sndh, clock_ms()); 92 | 93 | while (!model.quit) { 94 | vt_write_fifo(&scr_vt.vtb, &scr_out.fifo); 95 | 96 | const unicode_t key = read_key(); 97 | 98 | ctrl = model; 99 | if (model.mode->ctrl) 100 | model.mode->ctrl(key, &ctrl, &model, &sndh); 101 | 102 | model_update(&model, &ctrl, &sndh, clock_ms()); 103 | 104 | if (model.mode->view) 105 | model.mode->view(&scr_vt.vtb, &scr_view, &model, &sndh, clock_ms()); 106 | 107 | write_vt(&scr_out.fifo); 108 | } 109 | 110 | dprintf(STDOUT_FILENO, "%s\r\n", vt_text(vt_reset(&scr_vt.vtb))); 111 | 112 | return EXIT_SUCCESS; 113 | } 114 | -------------------------------------------------------------------------------- /include/test/dmapitch.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_TEST_DMAPITCH_H 7 | #define PSGPLAY_TEST_DMAPITCH_H 8 | 9 | struct dma_preset { 10 | int channels; 11 | int halfperiod; 12 | int rate; 13 | }; 14 | 15 | #define D(c, hp, r) { .channels = (c), .halfperiod = (hp), .rate = (r) } 16 | 17 | #define tune_value_time_names(t) \ 18 | t(D(1, 1, 0), 63, "DMA mono halfperiod 1 at 6258 Hz") \ 19 | t(D(1, 2, 0), 63, "DMA mono halfperiod 2 at 6258 Hz") \ 20 | t(D(1, 3, 0), 63, "DMA mono halfperiod 3 at 6258 Hz") \ 21 | t(D(1, 5, 0), 63, "DMA mono halfperiod 5 at 6258 Hz") \ 22 | t(D(1, 7, 0), 63, "DMA mono halfperiod 7 at 6258 Hz") \ 23 | t(D(1, 1, 1), 63, "DMA mono halfperiod 1 at 12517 Hz") \ 24 | t(D(1, 2, 1), 63, "DMA mono halfperiod 2 at 12517 Hz") \ 25 | t(D(1, 3, 1), 63, "DMA mono halfperiod 3 at 12517 Hz") \ 26 | t(D(1, 5, 1), 63, "DMA mono halfperiod 5 at 12517 Hz") \ 27 | t(D(1, 7, 1), 63, "DMA mono halfperiod 7 at 12517 Hz") \ 28 | t(D(1, 1, 2), 63, "DMA mono halfperiod 1 at 25033 Hz") \ 29 | t(D(1, 2, 2), 63, "DMA mono halfperiod 2 at 25033 Hz") \ 30 | t(D(1, 3, 2), 63, "DMA mono halfperiod 3 at 25033 Hz") \ 31 | t(D(1, 5, 2), 63, "DMA mono halfperiod 5 at 25033 Hz") \ 32 | t(D(1, 7, 2), 63, "DMA mono halfperiod 7 at 25033 Hz") \ 33 | t(D(1, 2, 3), 63, "DMA mono halfperiod 2 at 50066 Hz") \ 34 | t(D(1, 3, 3), 63, "DMA mono halfperiod 3 at 50066 Hz") \ 35 | t(D(1, 5, 3), 63, "DMA mono halfperiod 5 at 50066 Hz") \ 36 | t(D(1, 7, 3), 63, "DMA mono halfperiod 7 at 50066 Hz") \ 37 | t(D(2, 1, 0), 63, "DMA stereo halfperiod 1 at 6258 Hz") \ 38 | t(D(2, 2, 0), 63, "DMA stereo halfperiod 2 at 6258 Hz") \ 39 | t(D(2, 3, 0), 63, "DMA stereo halfperiod 3 at 6258 Hz") \ 40 | t(D(2, 5, 0), 63, "DMA stereo halfperiod 5 at 6258 Hz") \ 41 | t(D(2, 7, 0), 63, "DMA stereo halfperiod 7 at 6258 Hz") \ 42 | t(D(2, 1, 1), 63, "DMA stereo halfperiod 1 at 12517 Hz") \ 43 | t(D(2, 2, 1), 63, "DMA stereo halfperiod 2 at 12517 Hz") \ 44 | t(D(2, 3, 1), 63, "DMA stereo halfperiod 3 at 12517 Hz") \ 45 | t(D(2, 5, 1), 63, "DMA stereo halfperiod 5 at 12517 Hz") \ 46 | t(D(2, 7, 1), 63, "DMA stereo halfperiod 7 at 12517 Hz") \ 47 | t(D(2, 1, 2), 63, "DMA stereo halfperiod 1 at 25033 Hz") \ 48 | t(D(2, 2, 2), 63, "DMA stereo halfperiod 2 at 25033 Hz") \ 49 | t(D(2, 3, 2), 63, "DMA stereo halfperiod 3 at 25033 Hz") \ 50 | t(D(2, 5, 2), 63, "DMA stereo halfperiod 5 at 25033 Hz") \ 51 | t(D(2, 7, 2), 63, "DMA stereo halfperiod 7 at 25033 Hz") \ 52 | t(D(2, 2, 3), 63, "DMA stereo halfperiod 2 at 50066 Hz") \ 53 | t(D(2, 3, 3), 63, "DMA stereo halfperiod 3 at 50066 Hz") \ 54 | t(D(2, 5, 3), 63, "DMA stereo halfperiod 5 at 50066 Hz") \ 55 | t(D(2, 7, 3), 63, "DMA stereo halfperiod 7 at 50066 Hz") 56 | 57 | #endif /* PSGPLAY_TEST_DMAPITCH_H */ 58 | -------------------------------------------------------------------------------- /lib/m68k/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | M68K_BUILD_CFLAGS = $(BASIC_BUILD_CFLAGS) $(BUILD_CFLAGS) 4 | M68K_HOST_CFLAGS = $(BASIC_HOST_CFLAGS) $(HOST_CFLAGS) 5 | 6 | M68KMAKE := lib/m68k/m68kmake 7 | 8 | $(M68KMAKE).o: $(M68KMAKE).c 9 | $(QUIET_CC)$(BUILD_CC) $(M68K_BUILD_CFLAGS) -c -o $@ $< 10 | $(M68KMAKE): $(M68KMAKE).o 11 | $(QUIET_LINK)$(BUILD_CC) $(M68K_BUILD_CFLAGS) -o $@ $^ 12 | 13 | M68K_GEN_H := include/m68k/m68kops.h 14 | M68K_GEN_C := lib/m68k/m68kops.c 15 | 16 | lib/m68k/%ops.c include/m68k/%ops.h: lib/m68k/%_in.c $(M68KMAKE) 17 | $(Q:@=@echo ' GEN '$(M68K_GEN_H) \ 18 | $(M68K_GEN_C);)$(M68KMAKE) . $< 19 | 20 | lib/m68k/m68kcpu.c: $(M68K_GEN_H) 21 | 22 | M68K_SRC := $(M68K_GEN_C) lib/m68k/m68kcpu.c 23 | 24 | M68KDA_SPEC := lib/m68k/m68kda.spec 25 | 26 | M68KDG := lib/m68k/m68kdg 27 | 28 | M68KDG_GEN_H := include/m68k/m68kdg.h 29 | 30 | lib/m68k/m68kda.c: $(M68KDG_GEN_H) 31 | 32 | $(M68KDG_GEN_H): $(M68KDG) $(M68KDA_SPEC) 33 | $(QUIET_GEN)$(M68KDG) -o $@ $(M68KDA_SPEC) 34 | 35 | M68KDG_LINK_SRC := \ 36 | system/unix/file.c \ 37 | system/unix/memory.c \ 38 | system/unix/print.c \ 39 | system/unix/string.c \ 40 | lib/internal/bit.c \ 41 | lib/internal/fifo.c \ 42 | lib/internal/print.c \ 43 | lib/internal/string.c \ 44 | lib/version/version.c 45 | 46 | define M68KDG_LINK_target 47 | M68KDG_LINK_OBJ += lib/m68k/$(basename $(subst /,-,$(1))).o 48 | lib/m68k/$(basename $(subst /,-,$(1))).o: $(1) 49 | $$(QUIET_CC)$$(BUILD_CC) $$(M68K_BUILD_CFLAGS) -c -o $$@ $$< 50 | endef 51 | 52 | $(foreach f,$(M68KDG_LINK_SRC),$(eval $(call M68KDG_LINK_target,$(f)))) 53 | 54 | $(M68KDG).o: $(M68KDG).c 55 | $(QUIET_CC)$(BUILD_CC) $(M68K_BUILD_CFLAGS) -c -o $@ $< 56 | 57 | $(M68KDG): $(M68KDG).o $(M68KDG_LINK_OBJ) 58 | $(QUIET_LINK)$(BUILD_CC) $(M68K_BUILD_CFLAGS) -o $@ $(M68KDG).o $(M68KDG_LINK_OBJ) 59 | 60 | M68KDT := lib/m68k/m68kdt 61 | 62 | M68KDA_SRC := lib/m68k/m68kds.c lib/m68k/m68kda.c 63 | M68KDT_SRC := lib/m68k/m68kdt.c 64 | 65 | M68KDA_OBJ := $(M68KDA_SRC:%.c=%.o) 66 | M68KDT_OBJ := $(M68KDT_SRC:%.c=%.o) 67 | 68 | ALL_OBJ += $(M68KDG).o $(M68KMAKE).o \ 69 | $(M68KDA_OBJ) $(M68KDT_OBJ) $(M68KDG_LINK_OBJ) 70 | 71 | ifeq (1,$(HAVE_SSO)) 72 | 73 | M68K_SRC += $(M68KDA_SRC) 74 | M68K_OBJ := $(M68K_SRC:%.c=%.o) 75 | 76 | lib/m68k/m68kdt.c: $(M68KDG_GEN_H) 77 | 78 | $(M68K_OBJ) $(M68KDT_OBJ): %.o : %.c 79 | $(QUIET_CC)$(HOST_CC) $(M68K_HOST_CFLAGS) -c -o $@ $< 80 | 81 | $(M68KDT): $(M68KDT_OBJ) $(M68KDG_LINK_OBJ) $(M68KDA_OBJ) 82 | $(QUIET_LINK)$(HOST_CC) $(M68K_HOST_CFLAGS) -o $@ \ 83 | $(M68KDT_OBJ) $(M68KDG_LINK_OBJ) $(M68KDA_OBJ) 84 | 85 | .PHONY: test-m68kdt 86 | test-m68kdt: $(M68KDT) 87 | $(QUIET_TEST)$(M68KDT) $(subst @,,$(V:1=-v)) 88 | 89 | endif 90 | 91 | OTHER_CLEAN += $(M68K_GEN_H) $(M68K_GEN_C) \ 92 | $(M68KMAKE) $(M68KDG_GEN_H) $(M68KDG) $(M68KDT) 93 | -------------------------------------------------------------------------------- /lib/atari/machine.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include "internal/assert.h" 10 | #include "internal/build-assert.h" 11 | #include "internal/compare.h" 12 | #include "internal/macro.h" 13 | 14 | #include "atari/cpu.h" 15 | #include "atari/device.h" 16 | #include "atari/glue.h" 17 | #include "atari/machine.h" 18 | #include "atari/mixer.h" 19 | #include "atari/psg.h" 20 | #include "atari/ram.h" 21 | #include "atari/sound.h" 22 | 23 | #include "m68k/m68k.h" 24 | #include "m68k/m68kcpu.h" 25 | 26 | #define MACHINE_REGISTERS(reg) \ 27 | reg(0, d, D) \ 28 | reg(1, d, D) \ 29 | reg(2, d, D) \ 30 | reg(3, d, D) \ 31 | reg(4, d, D) \ 32 | reg(5, d, D) \ 33 | reg(6, d, D) \ 34 | reg(7, d, D) \ 35 | reg(0, a, A) \ 36 | reg(1, a, A) \ 37 | reg(2, a, A) \ 38 | reg(3, a, A) \ 39 | reg(4, a, A) \ 40 | reg(5, a, A) \ 41 | reg(6, a, A) \ 42 | reg(7, a, A) 43 | 44 | static u64 cycle; 45 | 46 | u64 cycle_transform(u64 to_frequency, u64 from_frequency, u64 cycle) 47 | { 48 | const u64 q = cycle / from_frequency; 49 | const u64 r = cycle % from_frequency; 50 | 51 | return q * to_frequency + (r * to_frequency) / from_frequency; 52 | } 53 | 54 | u64 cycle_transform_align(u64 to_frequency, u64 from_frequency, u64 cycle) 55 | { 56 | const u64 q = cycle / from_frequency; 57 | const u64 r = cycle % from_frequency; 58 | 59 | return q * to_frequency + 60 | (r * to_frequency + from_frequency - 1) / from_frequency; 61 | } 62 | 63 | u64 machine_cycle(void) 64 | { 65 | return cycle + cpu_cycles_run(); 66 | } 67 | 68 | static void atari_st_init(const void *prg, size_t size, size_t offset, 69 | const struct machine_registers *regs, 70 | const struct machine_ports *ports) 71 | { 72 | const u8 *p = prg; 73 | 74 | cycle = 0; 75 | m68k_clear_timeslice(); 76 | 77 | if (offset + size >= ram_device.bus.size) 78 | pr_fatal_error("Program at %zu bytes too large for %u bytes\n", 79 | size, ram_device.bus.size); 80 | 81 | device_reset(); 82 | 83 | #define MACHINE_REGISTER_SET(index_, field_, label_) \ 84 | m68k_set_reg(M68K_REG_##label_##index_, regs->field_[index_]); 85 | MACHINE_REGISTERS(MACHINE_REGISTER_SET) 86 | 87 | for (size_t i = 0; i < size; i++) 88 | ram_device.wr_u8(&ram_device, offset + i, p[i]); 89 | 90 | psg_sample(ports->psg_sample, ports->arg); 91 | sound_sample(ports->sound_sample, ports->arg); 92 | mixer_sample(ports->mixer_sample, ports->arg); 93 | record_sample(ports->record_sample, ports->arg); 94 | } 95 | 96 | static bool atari_st_run(void) 97 | { 98 | cycle += device_run(cycle, MACHINE_RUN_SLICE); 99 | 100 | return true; 101 | } 102 | 103 | const struct machine atari_st = { 104 | .init = atari_st_init, 105 | .run = atari_st_run, 106 | }; 107 | -------------------------------------------------------------------------------- /include/audio/audio.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2025 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_AUDIO_H 7 | #define PSGPLAY_AUDIO_H 8 | 9 | #include "internal/types.h" 10 | 11 | struct audio_format { 12 | const char *path; 13 | int frequency; 14 | size_t sample_count; 15 | }; 16 | 17 | struct audio_sample { 18 | int16_t left; 19 | int16_t right; 20 | }; 21 | 22 | struct audio { 23 | struct audio_format format; 24 | struct audio_sample samples[]; 25 | }; 26 | 27 | struct audio_meter { 28 | struct audio_meter_channel { 29 | int16_t minimum; 30 | int16_t maximum; 31 | int16_t average; 32 | } left, right; 33 | }; 34 | 35 | struct audio_map_cb { 36 | struct audio_sample (*f)(struct audio_sample sample, void *arg); 37 | void *arg; 38 | }; 39 | 40 | struct audio_zero_crossing { 41 | size_t index; 42 | bool neg_to_pos; 43 | }; 44 | 45 | struct audio_zero_crossing_cb { 46 | bool (*f)(size_t index, struct audio_sample a, 47 | struct audio_sample b, void *arg); 48 | void *arg; 49 | }; 50 | 51 | struct audio_zero_crossing_periodic { 52 | size_t count; 53 | struct audio_zero_crossing first; 54 | struct audio_zero_crossing last; 55 | }; 56 | 57 | struct audio_zero_crossing_periodic_deviation { 58 | size_t count; 59 | double maximum; 60 | }; 61 | 62 | struct audio_wave { 63 | double period; 64 | double phase; 65 | }; 66 | 67 | struct audio *audio_alloc(struct audio_format format); 68 | 69 | void audio_free(struct audio *audio); 70 | 71 | struct audio *audio_read_wave(const char *path); 72 | 73 | struct audio *audio_range(const struct audio *audio, size_t lo, size_t hi); 74 | 75 | struct audio_meter audio_meter(const struct audio *audio); 76 | 77 | struct audio *audio_map(const struct audio *audio, struct audio_map_cb cb); 78 | 79 | struct audio *audio_normalise(const struct audio *audio, float peak); 80 | 81 | bool audio_zero_crossing(const struct audio *audio, 82 | struct audio_zero_crossing_cb cb); 83 | 84 | struct audio_zero_crossing_periodic audio_zero_crossing_periodic( 85 | const struct audio *audio); 86 | 87 | struct audio_zero_crossing_periodic_deviation 88 | audio_zero_crossing_periodic_deviation(const struct audio *audio, 89 | struct audio_wave wave); 90 | 91 | struct audio_wave audio_wave_estimate(struct audio_zero_crossing_periodic zcp); 92 | 93 | static inline double audio_frequency_from_period(double period, 94 | double sampling_frequency) 95 | { 96 | return period ? sampling_frequency / period : 0.0; 97 | } 98 | 99 | static inline double audio_duration(const struct audio_format format) 100 | { 101 | return format.sample_count / (double)format.frequency; 102 | } 103 | 104 | static inline double audio_relative_tolerance(const struct audio_format format) 105 | { 106 | /* 107 | * Max 1+1 sample error in first and last zero crossing, 108 | * with 10 % margin. 109 | */ 110 | const double max_sample_error = 2.0 * 1.1; 111 | 112 | return max_sample_error / format.sample_count; 113 | } 114 | 115 | #endif /* PSGPLAY_AUDIO_H */ 116 | -------------------------------------------------------------------------------- /system/unix/remake.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "internal/print.h" 12 | 13 | #include "psgplay/sndh.h" 14 | 15 | #include "system/unix/memory.h" 16 | #include "system/unix/remake.h" 17 | #include "system/unix/string.h" 18 | 19 | static char *escape(const char *s) 20 | { 21 | const size_t len = strlen(s); 22 | char *e = xmalloc(4 * len + 1); /* "\xXX" is the longest expansion */ 23 | size_t i, j; 24 | 25 | for (i = j = 0; s[i] != '\0'; i++) 26 | if (s[i] == '\t') { 27 | e[j++] = '\\'; 28 | e[j++] = 't'; 29 | } else if (s[i] == '\r') { 30 | e[j++] = '\\'; 31 | e[j++] = 'r'; 32 | } else if (s[i] == '\n') { 33 | e[j++] = '\\'; 34 | e[j++] = 'n'; 35 | } else if (s[i] == '\\' || s[i] == '"') { 36 | e[j++] = '\\'; 37 | e[j++] = s[i]; 38 | } else if (isprint(s[i])) { 39 | e[j++] = s[i]; 40 | } else 41 | j += sprintf(&e[j], "\\x%02x", (uint8_t)s[i]); 42 | 43 | e[j] = '\0'; 44 | 45 | return e; 46 | } 47 | 48 | static const char *trim(char *s) 49 | { 50 | return s; 51 | 52 | const size_t len = strlen(s); 53 | 54 | if (!len) 55 | return s; 56 | 57 | for (char *e = &s[len - 1]; e != s; e--) 58 | if (isspace(*e)) 59 | *e = '\0'; 60 | else 61 | break; 62 | 63 | while (isspace(*s)) 64 | s++; 65 | 66 | return s; 67 | } 68 | 69 | static void print_substrings_tag(const char *tag, 70 | const char *label, const char *prefix, 71 | const void *data, size_t size) 72 | { 73 | int subtune_count; 74 | 75 | printf("\teven\n%s:\n\tdc.b\t'%s'\n", label, tag); 76 | 77 | if (!sndh_tag_subtune_count(&subtune_count, data, size)) 78 | subtune_count = 1; 79 | 80 | for (int i = 0; i < subtune_count; i++) 81 | printf("\tdc.w\t%s%d-%s\n", prefix, 1 + i, label); 82 | } 83 | 84 | void remake_header(const void *data, size_t size) 85 | { 86 | printf("\tdc.b\t'SNDH'\n"); 87 | 88 | int time_count = 0; 89 | int subtitle_count = 0; 90 | int subflag_count = 0; 91 | 92 | sndh_for_each_tag (data, size) { 93 | const char *name = sndh_tag_name; 94 | char *value = escape(sndh_tag_value); 95 | const char *v = trim(value); 96 | 97 | if (strcmp(name, "TIME") == 0) { 98 | if (!time_count++) 99 | printf("\teven\n\tdc.b\t'TIME'\n"); 100 | printf("\tdc.w\t\%d\n", sndh_tag_integer); 101 | } else if (strcmp(name, "!#SN") == 0) { 102 | if (!subtitle_count++) 103 | print_substrings_tag(name, 104 | ".subtitles", ".st", data, size); 105 | printf(".st%d:\tdc.b\t'%s',0\n", subtitle_count, v); 106 | } else if (strcmp(name, "FLAG") == 0) { 107 | if (!subflag_count++) 108 | print_substrings_tag(name, 109 | ".subflags", ".sf", data, size); 110 | printf(".sf%d:\tdc.b\t'%s',0\n", subflag_count, v); 111 | } else if (strcmp(name, "##") == 0) { 112 | printf("\tdc.b\t'%s%02d',0\n", name, sndh_tag_integer); 113 | } else 114 | printf("\tdc.b\t'%s%s',0\n", name, v); 115 | 116 | free(value); 117 | } 118 | 119 | printf("\teven\n"); 120 | printf("\tdc.b\t'HDNS'\n"); 121 | } 122 | -------------------------------------------------------------------------------- /include/psgplay/psgplay.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #ifndef PSGPLAY_H 7 | #define PSGPLAY_H 8 | 9 | #include 10 | #include 11 | 12 | #include "digital.h" 13 | #include "stereo.h" 14 | 15 | /** 16 | * psgplay_init - initialise PSG play 17 | * @data: SNDH data, must not be in compressed form 18 | * @size: SNDH size in octets 19 | * @track: subtune to play 20 | * @frequency: stereo sample frequency in Hz, or zero for digital reading 21 | * 22 | * Use psgplay_read_stereo() if @frequency is nonzero, otherwise use 23 | * psgplay_read_digital(). 24 | * 25 | * Return: PSG play object, which must be freed with psgplay_free(), 26 | * or %NULL on failure 27 | */ 28 | struct psgplay *psgplay_init(const void *data, size_t size, 29 | int track, int frequency); 30 | 31 | /** 32 | * psgplay_free - free a PSG play object previously initialised 33 | * @pp: PSG play object to free 34 | */ 35 | void psgplay_free(struct psgplay *pp); 36 | 37 | /** 38 | * psgplay_stop - stop a PSG play object previously initialised 39 | * @pp: PSG play object to stop 40 | * 41 | * Calling psgplay_stop() is optional, but it will allow PSG play to fade out 42 | * stereo samples, which prevents a sharp and often audible cut-off noise. 43 | * 44 | * psgplay_stop() is mostly intended for interactive use, when the stop 45 | * condition is not known in advance. 46 | * 47 | * Note: psgplay_read_stereo() will continue to read samples for about 10 ms, 48 | * after which it will permanently return zero to indicate that it has finished 49 | * fading out and there are no more samples to be read. 50 | * 51 | * See also psgplay_stop_at_time() and psgplay_stop_digital_at_sample(). 52 | * 53 | * The cut-off noise is due to the YM2149 PSG unipolar signal being 54 | * transformed into a bipolar signal for stereo sample mixing. PSG play 55 | * automatically fade in stereo samples for the first 10 ms. 56 | */ 57 | void psgplay_stop(struct psgplay *pp); 58 | 59 | /** 60 | * psgplay_stop_at_time - stop PSG play after a given time 61 | * @pp: PSG play object to stop 62 | * @time: time in seconds, from the start of the SNDH tune, to stop at 63 | * 64 | * Calling psgplay_stop_at_time() is optional, but it will allow PSG play 65 | * to prefade out stereo samples, which prevents a sharp and often audible 66 | * cut-off noise. 67 | * 68 | * Note: psgplay_read_stereo() will permanently return zero to indicate that 69 | * it has finished fading out and there are no more samples to be read. 70 | * 71 | * See also psgplay_stop() and psgplay_stop_digital_at_sample(). 72 | * 73 | * The cut-off noise is due to the YM2149 PSG unipolar signal being 74 | * transformed into a bipolar signal for stereo sample mixing. PSG play 75 | * automatically fade in stereo samples for the first 10 ms. 76 | */ 77 | void psgplay_stop_at_time(struct psgplay *pp, float time); 78 | 79 | /** 80 | * psgplay_instruction_callback - invoke callback for every CPU instruction 81 | * @pp: PSG play object 82 | * @cb: callback 83 | * @arg: optional argument supplied to @cb, can be %NULL 84 | */ 85 | void psgplay_instruction_callback(struct psgplay *pp, 86 | void (*cb)(uint32_t pc, void *arg), void *arg); 87 | 88 | #endif /* PSGPLAY_H */ 89 | -------------------------------------------------------------------------------- /system/unix/string.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "internal/assert.h" 11 | #include "internal/print.h" 12 | #include "internal/string.h" 13 | 14 | #include "system/unix/memory.h" 15 | #include "system/unix/string.h" 16 | 17 | char *xstrdup(const char *s) 18 | { 19 | void *t = strdup(s); 20 | 21 | if (!t) 22 | pr_fatal_errno("strdup"); 23 | 24 | return t; 25 | } 26 | 27 | char *xstrcat(const char *a, const char *b) 28 | { 29 | const size_t alen = strlen(a); 30 | const size_t blen = strlen(b); 31 | char *s = xmalloc(alen + blen + 1); 32 | 33 | memcpy(&s[0], a, alen); 34 | memcpy(&s[alen], b, blen); 35 | s[alen + blen] = '\0'; 36 | 37 | return s; 38 | } 39 | 40 | char *xstrndup(const char *s, size_t n) 41 | { 42 | void *t = strndup(s, n); 43 | 44 | if (!t) 45 | pr_fatal_errno("strndup"); 46 | 47 | return t; 48 | } 49 | 50 | char *strrep(const char *s, const char *from, const char *to) 51 | { 52 | const size_t s_len = strlen(s); 53 | const size_t from_len = strlen(from); 54 | const size_t to_len = strlen(to); 55 | struct string_split split; 56 | 57 | size_t n = 0; 58 | for_each_string_split (split, s, from) 59 | if (split.sep) 60 | n++; 61 | 62 | const size_t t_len = to_len < from_len ? 63 | s_len - (from_len - to_len) * n : 64 | s_len + (to_len - from_len) * n; 65 | 66 | char *t = xmalloc(t_len + 1); 67 | 68 | size_t i = 0; 69 | for_each_string_split (split, s, from) 70 | if (split.sep) { 71 | memcpy(&t[i], to, to_len); 72 | i += to_len; 73 | } else { 74 | memcpy(&t[i], split.s, split.length); 75 | i += split.length; 76 | } 77 | 78 | BUG_ON(i != t_len); 79 | 80 | t[i] = '\0'; 81 | 82 | return t; 83 | } 84 | 85 | void sbfree(struct strbuf *sb) 86 | { 87 | free(sb->s); 88 | 89 | *sb = (struct strbuf) { }; 90 | } 91 | 92 | bool sbprintf(struct strbuf *sb, const char *fmt, ...) 93 | { 94 | va_list ap; 95 | 96 | va_start(ap, fmt); 97 | const int length = vsbprintf(sb, fmt, ap); 98 | va_end(ap); 99 | 100 | return length; 101 | } 102 | 103 | bool sbmprintf(struct strbuf *sb, size_t margin, const char *fmt, ...) 104 | { 105 | va_list ap; 106 | 107 | va_start(ap, fmt); 108 | const int length = vsbmprintf(sb, margin, fmt, ap); 109 | va_end(ap); 110 | 111 | return length; 112 | } 113 | 114 | bool vsbprintf(struct strbuf *sb, const char *fmt, va_list ap) 115 | { 116 | return vsbmprintf(sb, 4096, fmt, ap); 117 | } 118 | 119 | bool vsbmprintf(struct strbuf *sb, size_t margin, const char *fmt, va_list ap) 120 | { 121 | if (!margin) 122 | return false; 123 | 124 | if (sb->capacity < sb->length + margin) { 125 | const size_t capacity = sb->length + 2 * margin; 126 | char *s = realloc(sb->s, capacity); 127 | 128 | if (!s) 129 | return false; 130 | 131 | sb->capacity = capacity; 132 | sb->s = s; 133 | } 134 | 135 | const int length = vsnprintf(&sb->s[sb->length], 136 | sb->capacity - sb->length, fmt, ap); 137 | 138 | if (length < 0 || sb->capacity <= sb->length + length + 1) { 139 | sb->s[sb->length] = '\0'; 140 | return false; 141 | } 142 | 143 | sb->length += length; 144 | 145 | return true; 146 | } 147 | -------------------------------------------------------------------------------- /test/dmasint-verify.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include "toslibc/asm/machine.h" 4 | 5 | #include "atari/machine.h" 6 | 7 | #include "test/report.h" 8 | #include "test/verify.h" 9 | #include "test/dmasint.h" 10 | 11 | test_value_time_names(struct dma_preset, tune_value_time_names); 12 | 13 | static double dma_sound_frequency(const struct options *options) 14 | { 15 | const int d = 1 << (3 - test_value(options).rate); 16 | 17 | return ATARI_STE_EXT_OSC / (double)(ATARI_STE_SND_DMA_CLK_DIV * d); 18 | } 19 | 20 | static int dma_sample_period(const struct options *options) 21 | { 22 | return 2 * test_value(options).halfperiod; 23 | } 24 | 25 | struct zero_crossing { 26 | const struct audio *audio; 27 | const double period; 28 | 29 | size_t first; 30 | size_t last; 31 | size_t count; 32 | 33 | size_t x, y, z; 34 | 35 | const char *error; 36 | }; 37 | 38 | static int diff(size_t i, size_t m, const struct audio *audio) 39 | { 40 | return m <= i && i + m < audio->format.sample_count ? 41 | audio->samples[i - m].left + audio->samples[i - m].right - 42 | audio->samples[i + m].left - audio->samples[i + m].right : 0; 43 | } 44 | 45 | static bool zero_crossing(size_t i, struct audio_sample a, 46 | struct audio_sample b, void *arg) 47 | { 48 | struct zero_crossing *zc = arg; 49 | 50 | const int d = diff(i, 10, zc->audio); 51 | 52 | if (d < 8000) 53 | return true; 54 | else if (d < 16000) zc->x++; 55 | else if (d < 32000) zc->y++; 56 | else zc->z++; 57 | 58 | if (zc->count) { 59 | const size_t p = i - zc->last; 60 | 61 | if (p < zc->period - 2 || 62 | p > zc->period + 2) 63 | zc->error = "period malfunction"; 64 | } else 65 | zc->first = i; 66 | zc->last = i; 67 | zc->count++; 68 | 69 | return true; 70 | } 71 | 72 | static struct zero_crossing measure(const struct audio *audio, 73 | const struct options *options) 74 | { 75 | struct audio *norm = audio_normalise(audio, 0.8f); 76 | struct zero_crossing zc = { 77 | .audio = audio, 78 | .period = audio->format.frequency * 79 | dma_sample_period(options) / 80 | dma_sound_frequency(options), 81 | }; 82 | 83 | audio_zero_crossing(norm, (struct audio_zero_crossing_cb) { 84 | .f = zero_crossing, 85 | .arg = &zc, 86 | }); 87 | 88 | audio_free(norm); 89 | 90 | return zc; 91 | } 92 | 93 | void report(struct strbuf *sb, const struct audio *audio, 94 | const struct options *options) 95 | { 96 | const struct zero_crossing zc = measure(audio, options); 97 | 98 | report_input(sb, audio, test_name(options), options); 99 | 100 | sbprintf(sb, 101 | "dma sound frequency %f Hz\n" 102 | "dma sample period %d samples\n" 103 | "dma sample crossings %zu %zu %zu\n", 104 | dma_sound_frequency(options), 105 | dma_sample_period(options), 106 | zc.x, zc.y, zc.z); 107 | } 108 | 109 | const char *verify(const struct audio *audio, const struct options *options) 110 | { 111 | const struct zero_crossing zc = measure(audio, options); 112 | 113 | verify_assert (audio_duration(audio->format) >= 1.0) 114 | return "sample duration"; 115 | 116 | verify_assert (zc.x == test_value(options).count[0]) 117 | return "x count"; 118 | 119 | verify_assert (zc.y == test_value(options).count[1]) 120 | return "y count"; 121 | 122 | verify_assert (zc.z == test_value(options).count[2]) 123 | return "z count"; 124 | 125 | return zc.error; 126 | } 127 | -------------------------------------------------------------------------------- /lib/test/report.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "internal/print.h" 8 | 9 | #include "audio/audio.h" 10 | #include "system/unix/file.h" 11 | 12 | #include "test/option.h" 13 | #include "test/report.h" 14 | 15 | void report_input(struct strbuf *sb, const struct audio *audio, 16 | const char *name, const struct options *options) 17 | { 18 | sbprintf(sb, 19 | "path %s\n" 20 | "name %s\n" 21 | "index %d\n" 22 | "title %s\n" 23 | "sample count %zu samples\n" 24 | "sample duration %.1f s\n" 25 | "sample frequency %d Hz\n", 26 | options->input, 27 | options->name, 28 | options->track, 29 | name, 30 | audio->format.sample_count, 31 | audio_duration(audio->format), 32 | audio->format.frequency); 33 | } 34 | 35 | struct test_wave_deviation test_wave_deviation(const struct audio *audio) 36 | { 37 | struct audio *norm = audio_normalise(audio, 0.8f); 38 | 39 | const struct audio_zero_crossing_periodic zcp = 40 | audio_zero_crossing_periodic(norm); 41 | const struct audio_wave wave = audio_wave_estimate(zcp); 42 | const struct audio_zero_crossing_periodic_deviation deviation = 43 | audio_zero_crossing_periodic_deviation(norm, wave); 44 | 45 | audio_free(norm); 46 | 47 | return (struct test_wave_deviation) { 48 | .wave = wave, 49 | .deviation = deviation, 50 | }; 51 | } 52 | 53 | struct test_wave_error test_wave_error(struct audio_format audio_format, 54 | struct test_wave_deviation wave_deviation, double reference_frequency) 55 | { 56 | const double absolute_frequency = audio_frequency_from_period( 57 | wave_deviation.wave.period, audio_format.frequency) - 58 | reference_frequency; 59 | 60 | return (struct test_wave_error) { 61 | .absolute_frequency = absolute_frequency, 62 | .relative_frequency = 63 | fabs(absolute_frequency) / reference_frequency, 64 | }; 65 | } 66 | 67 | void report_wave_estimate(struct strbuf *sb, struct audio_format audio_format, 68 | struct test_wave_deviation wave_deviation, double reference_frequency) 69 | { 70 | const double wave_frequency = audio_frequency_from_period( 71 | wave_deviation.wave.period, audio_format.frequency); 72 | const double wave_error_total_count = audio_format.sample_count * 73 | fabs(wave_frequency / reference_frequency - 1.0); 74 | const double wave_error_total_time = 75 | wave_error_total_count / audio_format.frequency; 76 | const struct test_wave_error error = test_wave_error( 77 | audio_format, wave_deviation, reference_frequency); 78 | 79 | sbprintf(sb, 80 | "wave reference frequency %f Hz\n" 81 | "wave period %f samples\n" 82 | "wave frequency %f Hz\n" 83 | "wave phase %f samples\n" 84 | "wave zero crossing count %zu\n" 85 | "wave zero crossing deviation max %f samples\n" 86 | "wave error total count %.3f samples\n" 87 | "wave error total time %.3e s\n" 88 | "wave error absolute frequency %f Hz\n" 89 | "wave error relative frequency %.2e\n" 90 | "wave error relative tolerance %.2e\n", 91 | reference_frequency, 92 | wave_deviation.wave.period, 93 | wave_frequency, 94 | wave_deviation.wave.phase, 95 | wave_deviation.deviation.count, 96 | wave_deviation.deviation.maximum, 97 | wave_error_total_count, 98 | wave_error_total_time, 99 | error.absolute_frequency, 100 | error.relative_frequency, 101 | audio_relative_tolerance(audio_format)); 102 | } 103 | -------------------------------------------------------------------------------- /lib/text/load.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /* 3 | * Copyright (C) 2019 Fredrik Noring 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "psgplay/sndh.h" 12 | 13 | #include "text/load.h" 14 | #include "text/mode.h" 15 | 16 | static const u32 logo[] = { 17 | 0b00000111111011111110011111100000, 18 | 0b00000110011011000000011001100000, 19 | 0b00000110011011111110011000000000, 20 | 0b00000111111000000110111011100000, 21 | 0b00001110000011100110111001100000, 22 | 0b00001110000011100110111001100000, 23 | 0b00001110000011111110111111100000, 24 | 0b00000000000000000000000000000000, 25 | 0b00000000001100000000000000000000, 26 | 0b00000000001100000000000000000000, 27 | 0b00111111101100111111101100011000, 28 | 0b00110001101100000001101110011000, 29 | 0b00110001101110111111101110011000, 30 | 0b00111111101110111001101111111000, 31 | 0b00111000001110111111100000011000, 32 | 0b00111000000000000000001111111000, 33 | }; 34 | 35 | static void vt_printf_centre(struct vt_buffer *vtb, int row, 36 | struct vt_attr attr, const char *fmt, ...) 37 | { 38 | char msg[64]; 39 | va_list ap; 40 | 41 | va_start(ap, fmt); 42 | 43 | vsnprintf(msg, sizeof(msg), fmt, ap); 44 | 45 | const int offset = (39 - strlen(msg)) / 2; 46 | 47 | for (int i = 0; msg[i]; i++) 48 | vt_putc(vtb, row, offset + i, msg[i], attr); 49 | 50 | va_end(ap); 51 | } 52 | 53 | static void load_bar(struct vt_buffer *vtb, struct text_state *view, 54 | const struct text_state *model, const struct text_sndh *sndh, 55 | u64 timestamp) 56 | { 57 | const size_t progress = (27 * model->progress) / 100; 58 | 59 | for (size_t col = 0; col < 27; col++) 60 | vt_putc(vtb, ARRAY_SIZE(logo) + 4, 6 + col, '-', 61 | col + 1 <= progress ? vt_attr_reverse : vt_attr_normal); 62 | } 63 | 64 | static void load_init(struct vt_buffer *vtb, struct text_state *view, 65 | const struct text_state *model, const struct text_sndh *sndh, 66 | u64 timestamp) 67 | { 68 | vt_clear(vtb); 69 | 70 | for (size_t row = 0; row < ARRAY_SIZE(logo); row++) 71 | for (size_t col = 0; col < 32; col++) 72 | if (logo[row] & (0x80000000 >> col)) 73 | vt_putc_reverse(vtb, row, 4 + col, ' '); 74 | 75 | vt_printf_centre(vtb, ARRAY_SIZE(logo) + 2, 76 | vt_attr_normal, "Loading %s", sndh->title); 77 | 78 | load_bar(vtb, view, model, sndh, timestamp); 79 | } 80 | 81 | static u64 load_view(struct vt_buffer *vtb, struct text_state *view, 82 | const struct text_state *model, const struct text_sndh *sndh, 83 | u64 timestamp) 84 | { 85 | if (view->mode != model->mode) { 86 | view->mode = model->mode; 87 | view->progress = 0; 88 | 89 | load_init(vtb, view, model, sndh, timestamp); 90 | } 91 | 92 | if (view->progress != model->progress) { 93 | load_bar(vtb, view, model, sndh, timestamp); 94 | 95 | view->progress = model->progress; 96 | } 97 | 98 | return 0; 99 | } 100 | 101 | static void load_ctrl(const unicode_t key, struct text_state *ctrl, 102 | const struct text_state *model, const struct text_sndh *sndh) 103 | { 104 | ctrl->redraw = false; 105 | 106 | switch (key) { 107 | case 0: 108 | break; 109 | case '\014': /* ^L */ 110 | ctrl->redraw = true; 111 | break; 112 | case 'q': 113 | case '\033': /* Escape */ 114 | ctrl->quit = true; 115 | break; 116 | } 117 | } 118 | 119 | const struct text_mode text_mode_load = { 120 | .view = load_view, 121 | .ctrl = load_ctrl, 122 | }; 123 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | # 3 | # See the file INSTALL for installation instructions. 4 | 5 | prefix = $(HOME)/.local/usr 6 | datarootdir = $(prefix)/share 7 | exec_prefix = $(prefix) 8 | bindir = $(exec_prefix)/bin 9 | mandir = $(datarootdir)/man 10 | man1dir = $(mandir)/man1 11 | includedir = $(prefix)/include 12 | libdir = $(exec_prefix)/lib 13 | pkgconfigdir = $(libdir)/pkgconfig 14 | 15 | export prefix includedir libdir 16 | 17 | BUILD_SYSTEM := $(shell uname -s) 18 | 19 | LD = $(CC) 20 | 21 | ifdef BUILD_COMPILE 22 | BUILD_CC = $(BUILD_COMPILE)gcc 23 | else 24 | BUILD_CC = $(CC) 25 | endif 26 | 27 | ifdef HOST_COMPILE 28 | HOST_CC = $(HOST_COMPILE)gcc 29 | HOST_LD = $(HOST_COMPILE)gcc 30 | HOST_AR = $(HOST_COMPILE)ar 31 | else 32 | HOST_CC = $(CC) 33 | HOST_LD = $(LD) 34 | ifeq (Darwin,$(BUILD_SYSTEM)) 35 | HOST_AR = libtool 36 | else 37 | HOST_AR = $(AR) 38 | endif 39 | endif 40 | 41 | ifdef TARGET_COMPILE 42 | TARGET_CC = $(TARGET_COMPILE)gcc 43 | TARGET_LD = $(TARGET_COMPILE)ld 44 | TARGET_AR = $(TARGET_COMPILE)ar 45 | endif 46 | 47 | CFLAGS = -g 48 | BUILD_CFLAGS = $(CFLAGS) 49 | HOST_CFLAGS = $(CFLAGS) 50 | TARGET_CFLAGS = $(CFLAGS) 51 | 52 | ifeq (Darwin,$(BUILD_SYSTEM)) 53 | HOST_ARFLAGS = -static -o 54 | SHLIB_EXT = dylib 55 | else 56 | HOST_ARFLAGS = rcs 57 | SHLIB_EXT = so 58 | endif 59 | 60 | INSTALL = install 61 | 62 | ifeq (1,$(S)) 63 | S_CFLAGS = -fsanitize=address -fsanitize=leak -fsanitize=undefined \ 64 | -fsanitize-address-use-after-scope 65 | endif 66 | 67 | # Suppress false array-bounds warning "array subscript 0 is outside 68 | # array bounds" and "source object is likely at address zero" for 69 | # the io{rd,wr}{8,16,32} family of functions. Confer 70 | # . 71 | TARGET_CFLAGS_FIXES = --param=min-pagesize=0 72 | 73 | DEP_CFLAGS = -Wp,-MD,$(@D)/$(@F).d -MT $(@D)/$(@F) 74 | BASIC_CFLAGS = -O2 -Wall -D_GNU_SOURCE $(HAVE_CFLAGS) $(DEP_CFLAGS) 75 | BASIC_BUILD_CFLAGS = -Iinclude $(S_CFLAGS) $(BASIC_CFLAGS) 76 | BASIC_HOST_CFLAGS = -Iinclude $(S_CFLAGS) $(BASIC_CFLAGS) 77 | BASIC_TARGET_CFLAGS = -Iinclude $(TARGET_CFLAGS_FIXES) $(BASIC_CFLAGS) 78 | 79 | .PHONY: all 80 | all: 81 | 82 | include doc/Makefile 83 | include lib/Makefile 84 | include system/Makefile 85 | include test/Makefile 86 | 87 | ALL_DEP = $(sort $(ALL_OBJ:%=%.d)) 88 | 89 | all: $(PSGPLAY) 90 | all: $(LIBPSGPLAY_STATIC) $(LIBPSGPLAY_SHARED) $(LIBPSGPLAY_PC) 91 | all: $(EXAMPLE_INFO) $(EXAMPLE_PLAY) 92 | 93 | ifdef TARGET_CC 94 | all: $(PSGPLAY_TOS) 95 | endif 96 | 97 | .PHONY: install 98 | install: install-psgplay install-man install-lib 99 | 100 | .PHONY: test 101 | test: test-m68kdt 102 | ifdef TARGET_CC 103 | test: test-svg 104 | endif 105 | 106 | .PHONY: version 107 | version: 108 | @script/version 109 | 110 | .PHONY: gtags 111 | gtags: 112 | $(QUIET_GEN)gtags 113 | OTHER_CLEAN += GPATH GRTAGS GTAGS 114 | 115 | .PHONY: clean 116 | clean: 117 | $(QUIET_RM)$(RM) $(ALL_OBJ) $(ALL_DEP) $(OTHER_CLEAN) 118 | 119 | V = @ 120 | Q = $(V:1=) 121 | QUIET_AR = $(Q:@=@echo ' AR '$@;) 122 | QUIET_AS = $(Q:@=@echo ' AS '$@;) 123 | QUIET_CC = $(Q:@=@echo ' CC '$@;) 124 | QUIET_GEN = $(Q:@=@echo ' GEN '$@;) 125 | QUIET_LINK = $(Q:@=@echo ' LD '$@;) 126 | QUIET_RM = $(Q:@=@echo ' RM '$@;) 127 | QUIET_CHECK = $(Q:@=@echo ' CHECK '$@;) 128 | QUIET_TEST = $(Q:@=@echo ' TEST '$@;) 129 | QUIET_VERIFY = $(Q:@=@echo ' VERIFY '$@;) 130 | 131 | $(eval -include $(ALL_DEP)) 132 | -------------------------------------------------------------------------------- /lib/test/verify.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "internal/print.h" 9 | 10 | #include "system/unix/file.h" 11 | #include "system/unix/memory.h" 12 | 13 | #include "audio/audio.h" 14 | #include "graph/graph.h" 15 | #include "graph/svg.h" 16 | 17 | #include "test/option.h" 18 | #include "test/report.h" 19 | #include "test/verify.h" 20 | 21 | const char *progname; 22 | 23 | static bool encode_file(const void *data, size_t size, void *arg) 24 | { 25 | struct strbuf *sb = arg; 26 | 27 | sbprintf(sb, "%*s", (int)size, data); 28 | 29 | return true; 30 | } 31 | 32 | static void graph(struct strbuf *sb, const struct audio *audio, 33 | const struct options *options) 34 | { 35 | struct audio *cut = audio_range(audio, 0, 250); 36 | struct audio *norm = audio_normalise(cut, 0.8f); 37 | const struct audio_meter meter = audio_meter(norm); 38 | const struct audio_zero_crossing_periodic zcp = 39 | audio_zero_crossing_periodic(norm); 40 | const struct audio_wave wave = audio_wave_estimate(zcp); 41 | 42 | struct graph_encoder *encoder = graph_encoder_init( 43 | (struct graph_bounds) { 44 | .min_x = 0, 45 | .min_y = 0, 46 | .max_x = 1000, 47 | .max_y = 200, 48 | }, 49 | (struct graph_encoder_cb) { 50 | .f = encode_file, 51 | .arg = sb, 52 | }, 53 | &svg_encoder); 54 | 55 | encoder->module->header(encoder); 56 | encoder->module->axes(encoder); 57 | encoder->module->square_wave(encoder, 58 | wave, meter.left.minimum, meter.left.maximum); 59 | encoder->module->samples(encoder, norm); 60 | encoder->module->footer(encoder); 61 | 62 | graph_encoder_free(encoder); 63 | 64 | audio_free(norm); 65 | audio_free(cut); 66 | } 67 | 68 | __attribute__((weak)) const char *flags(const struct options *options) 69 | { 70 | return ""; 71 | } 72 | 73 | int main(int argc, char *argv[]) 74 | { 75 | struct options *options = parse_options(argc, argv); 76 | 77 | if (strcmp(options->command, "flags") == 0) { 78 | puts(flags(options)); 79 | 80 | return EXIT_SUCCESS; 81 | } 82 | 83 | if (!options->input) 84 | pr_fatal_error("missing input WAVE file\n"); 85 | 86 | name_from_input(options); 87 | 88 | if (!options->track) 89 | options->track = track_from_path(options->input); 90 | if (!options->track) 91 | pr_fatal_error("%s: track not in file name and not given with --track\n", 92 | options->input); 93 | 94 | struct audio *audio = audio_read_wave(options->input); 95 | 96 | /* FIXME: Avoid trimming first and last second with --no-fade option. */ 97 | struct audio *trim = NULL; 98 | if (audio->format.sample_count < 3 * audio->format.frequency) 99 | trim = audio_range(audio, 0, audio->format.sample_count); 100 | else 101 | trim = audio_range(audio, 102 | audio->format.frequency, 103 | audio->format.sample_count - audio->format.frequency); 104 | 105 | struct strbuf sb = { }; 106 | 107 | if (strcmp(options->command, "verify") == 0) { 108 | const char *error = verify(trim, options); 109 | 110 | if (error) { 111 | report(&sb, trim, options); 112 | 113 | pr_fatal_error("%s\n%s%s: error: verify: %s\n", 114 | options->input, sb.s, options->input, error); 115 | } 116 | } else if (strcmp(options->command, "graph") == 0) { 117 | graph(&sb, trim, options); 118 | } else if (strcmp(options->command, "report") == 0) { 119 | report(&sb, trim, options); 120 | } else 121 | pr_fatal_error("%s: unknown command\n", options->command); 122 | 123 | if (options->output && sb.length && 124 | !file_write(options->output, sb.s, sb.length)) 125 | pr_fatal_errno(options->output); 126 | 127 | sbfree(&sb); 128 | 129 | audio_free(trim); 130 | audio_free(audio); 131 | 132 | return EXIT_SUCCESS; 133 | } 134 | --------------------------------------------------------------------------------