hxtools/smm/bsvplay.c

228 lines
6.6 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2002-2010,2020 Jan Engelhardt
/*
* bsvplay.c - BASICA binary music format interpreter
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libHX/init.h>
#include <libHX/option.h>
#include "pcspkr.h"
#define TICKSPERSEC 1086
/*
* based on findings from pvt collection
* entertan.exe 1989 (size:29527 md5:13caa8100e15ad65d900f6a12b9ddf24)
* ihold.exe 1989 (size:21317 md5:5cf7aef915539c60a9a98c96a3a8aefb)
* maplleaf.exe 1989 (size:27865 md5:f234797cbd1dba47d90da8a94dcb088f)
* mnty.exe 1989 (size:19441 md5:507997fa70b89f9a9ed5430e833cb6c0)
* willtell.exe 1989 (size:33799 md5:5d5405a5ff063476deec618dcd3d990d)
*/
struct bsv_insn {
uint16_t divisor, duration, af_pause;
};
/*
* based on findings in FOGDOS.118, FOGDOS.119, FOGDOS.120:
* pianoman.com 1986-05-06 01:36:02 (md5:cba939868e35b2742984246aa9317984) (Neil J. Rubenking)
* entertan.mus 1984-12-06 03:51:24 (size:24280 md5:994a5130d91b5b1395e4f9653f06c2e9)
* (Scott Joplin - The Entertainer)
* ihold.mus 1986-05-03 18:10:36 (size:17440 md5:d1ed36909bd4db1e4903181e57486c19)
* (Tom Lehrer - I hold your hand in mine)
* maplleaf.mus 1984-12-07 03:56:00 (size:23080 md5:f070f2b417065c322434ea2af613d464)
* (Scott Joplin - The maple leaf rag)
* mntpythn.mus 1984-12-29 20:19:40 (size:18700 md5:e10ba993a9d3179af0b890f453ddc93f)
* (John Philip Sousa - The liberty bell)
* willtell.mus 1985-01-10 15:23:26 (size:40105 md5:bd0e216ce89080a1e633465f57516b6a)
* (Gioachino Rossini - William Tell)
*/
struct pianoman_insn {
/* 0..255 */
uint8_t octave;
/* c(1), c#(2), d(3), d#(4), e(5), f(6), f#(7), g(8), g#(9),
* a(10), a#(11), b(12), rest(13) */
uint8_t note;
/* 0..9 */
uint8_t staccato;
/* 1280=whole note, 640=half, etc. */
uint16_t len;
} __attribute__((packed));
static struct pcspkr pcsp = {
.sample_rate = 48000,
.prop_square = 1,
.prop_sine = 1,
.volume = 0.1,
};
static unsigned int filter_lo = 0, filter_hi = ~0U;
static unsigned int tick_groupsize, tick_filter;
static unsigned int no_zero_ticks, flg_pianoman;
static void parse_basica(int fd)
{
unsigned int count = 0, ticks = 0;
struct bsv_insn tone;
while (read(fd, &tone, sizeof(struct bsv_insn)) ==
sizeof(struct bsv_insn))
{
long frequency = 0x1234DD / tone.divisor;
bool silenced;
++count;
silenced = frequency < filter_lo || frequency > filter_hi;
if (tick_groupsize != 0)
silenced |= (count % tick_groupsize) != tick_filter;
fprintf(stderr, "(%5u) %5hu %5ldHz%c %5hu %5hu\n",
count, tone.divisor, frequency,
silenced ? '*' : ' ', tone.duration,
tone.af_pause);
/*
* It seems that in the sample BSV executables from 1989
* calculate the cpu speed and then do around 1086 ticks/sec.
* entertan.exe: 199335 / 183s = 1089
* ihold.exe: 73248 / 68s = 1077
* maplleaf.exe: 170568 / 157s = 1086
* mnty.exe: 119680 / 110s = 1088
* willtell.exe: 225350 / 206s = 1093
*/
ticks += tone.duration + tone.af_pause;
if (silenced && no_zero_ticks)
;
else if (silenced)
pcspkr_silence(&pcsp, (tone.duration + tone.af_pause) *
pcsp.sample_rate / TICKSPERSEC);
else
pcspkr_output(&pcsp, frequency,
tone.duration * pcsp.sample_rate / TICKSPERSEC,
tone.af_pause * pcsp.sample_rate / TICKSPERSEC);
}
fprintf(stderr, "Total ticks: %u\n", ticks);
}
static void parse_pianoman(int fd)
{
unsigned int count = 0, ticks = 0;
struct pianoman_insn tone;
double notemap[255*12];
for (int n = 0; n < sizeof(notemap); ++n)
notemap[n] = 440.0 * pow(2, (n - 45) / 12.0);
while (read(fd, &tone, sizeof(tone)) == sizeof(tone)) {
int frequency = notemap[tone.octave*12+tone.note];
unsigned int af_pause = tone.len * tone.staccato / 10;
unsigned int duration = tone.len - af_pause;
bool silenced;
++count;
silenced = tone.note == 13;
if (tick_groupsize != 0)
silenced |= (count % tick_groupsize) != tick_filter;
fprintf(stderr, "(%5u) O%uN%02u %5dHz%c %5u %5u\n",
count, tone.octave, tone.note, frequency,
silenced ? '*' : ' ', duration,
af_pause);
/*
* Pianoman runs through
* entertan.mus: 194240/182s=1067
* ihold.mus: 69760/ 66s=1056
* maplleaf.mus: 138480/130s=1065
* mntpythn.mus: 112200/105s=1068
* willtell.mus: 169407/159s=1065
*/
ticks += tone.len;
if (silenced && no_zero_ticks)
;
else if (silenced)
pcspkr_silence(&pcsp, (duration + af_pause) *
pcsp.sample_rate / TICKSPERSEC);
else
pcspkr_output(&pcsp, frequency,
duration * pcsp.sample_rate / TICKSPERSEC,
af_pause * pcsp.sample_rate / TICKSPERSEC);
}
fprintf(stderr, "Total ticks: %u\n", ticks);
}
static void parse_file(const char *file)
{
int fd;
if (strcmp(file, "-") == 0)
fd = STDIN_FILENO;
else
fd = open(file, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Could not open %s: %s\n", file, strerror(errno));
return;
}
if (flg_pianoman)
parse_pianoman(fd);
else
parse_basica(fd);
close(fd);
}
int main(int argc, const char **argv)
{
static const struct HXoption options_table[] = {
{.sh = 'H', .type = HXTYPE_UINT, .ptr = &filter_hi,
.help = "High frequency cutoff (low-pass filter)"},
{.sh = 'L', .type = HXTYPE_UINT, .ptr = &filter_lo,
.help = "Low frequency cutoff (high-pass filter)"},
{.sh = 'M', .type = HXTYPE_UINT, .ptr = &tick_groupsize,
.help = "Size of a tick block"},
{.sh = 'T', .type = HXTYPE_UINT, .ptr = &tick_filter,
.help = "Play only this tick in a tick block"},
{.sh = 'Z', .type = HXTYPE_NONE, .ptr = &no_zero_ticks,
.help = "Skip over silenced ticks"},
{.sh = 'i', .type = HXTYPE_DOUBLE, .ptr = &pcsp.prop_sine,
.help = "Proportion of sine-wave calculation mixed in"},
{.sh = 'q', .type = HXTYPE_DOUBLE, .ptr = &pcsp.prop_square,
.help = "Proportion of square-wave calculation mixed in"},
{.sh = 'r', .type = HXTYPE_UINT, .ptr = &pcsp.sample_rate,
.help = "Sample rate (default: 48000)"},
{.ln = "pianoman", .type = HXTYPE_NONE, .ptr = &flg_pianoman,
.help = "Assume input is in Pianoman .MUS file"},
HXOPT_AUTOHELP,
HXOPT_TABLEEND,
};
int ret;
if ((ret = HX_init()) <= 0) {
fprintf(stderr, "HX_init: %s\n", strerror(-ret));
abort();
}
if (HX_getopt(options_table, &argc, &argv, HXOPT_USAGEONERR) !=
HXOPT_ERR_SUCCESS) {
HX_exit();
return EXIT_FAILURE;
}
pcsp.file_ptr = fdopen(STDOUT_FILENO, "wb");
if (argc == 1)
parse_file("-");
else
while (--argc > 0)
parse_file(*++argv);
HX_exit();
return EXIT_SUCCESS;
}