summaryrefslogtreecommitdiffstats
path: root/audio/asap/sap2ntsc.c
blob: a1ca5340d7b53f500fa4c0acf0399579e5567c8d (plain)
/*
 * sap2ntsc.c - convert PAL SAP files to NTSC
 *
 * Copyright (C) 2012-2019  Piotr Fusik
 *
 * This file is part of ASAP (Another Slight Atari Player),
 * see http://asap.sourceforge.net
 *
 * ASAP is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 *
 * ASAP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ASAP; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* public API doesn't allow us to set NTSC or FASTPLAY */
#include "asap.c"

static void fatal_error(const char *format, ...)
{
	va_list args;
	va_start(args, format);
	fprintf(stderr, "sap2ntsc: ");
	vfprintf(stderr, format, args);
	fputc('\n', stderr);
	va_end(args);
	exit(1);
}

static void process_file(const char *filename)
{
	/* check filename */
	const char *ext = strrchr(filename, '.');
	if (ext == NULL || strcasecmp(ext, ".sap") != 0)
		fatal_error("%s: filename must be *.sap", filename);

	/* read file */
	FILE *fp = fopen(filename, "rb");
	if (fp == NULL)
		fatal_error("cannot open %s", filename);
	static unsigned char module[ASAPInfo_MAX_MODULE_LENGTH];
	int module_len = fread(module, 1, sizeof(module), fp);
	fclose(fp);

	/* parse file */
	ASAPInfo *info = ASAPInfo_New();
	if (info == NULL)
		fatal_error("out of memory");
	if (!ASAPInfo_Load(info, filename, module, module_len))
		fatal_error("%s: unsupported file", filename);

	/* check if conversion possible */
	if (ASAPInfo_IsNtsc(info))
		fatal_error("%s: is already NTSC", filename);
	if (ASAPInfo_GetPlayerRateScanlines(info) != 312)
		fatal_error("%s: uses FASTPLAY", filename);

	/* do the conversion */
	ASAPWriter *writer = ASAPWriter_New();
	if (writer == NULL)
		fatal_error("out of memory");
	info->ntsc = true;
	info->fastplay = 262;
	for (int i = 0; i < ASAPInfo_GetSongs(info); i++) {
		int duration = ASAPInfo_GetDuration(info, i);
		if (duration > 0)
			ASAPInfo_SetDuration(info, i, (int) (duration * (1773447 / 1789772.5 * 262 / 312)));
	}
	static unsigned char output[ASAPInfo_MAX_MODULE_LENGTH];
	ASAPWriter_SetOutput(writer, output, 0, sizeof(output));
	int output_len = ASAPWriter_Write(writer, filename, info, module, module_len, true);
	ASAPWriter_Delete(writer);
	if (output_len < 0)
		fatal_error("%s: conversion error", filename);

	/* write file */
	fp = fopen(filename, "wb");
	if (fp == NULL)
		fatal_error("cannot write %s", filename);
	if (fwrite(output, output_len, 1, fp) != 1) {
		fclose(fp);
		remove(filename); /* "unlink" is less portable */
		fatal_error("%s: write error", filename);
	}
	fclose(fp);

	/* print summary */
	printf("%s: ", filename);
	int warnings = 0;

	/* issue a warning for samples - they may break on NTSC */
	/* TYPE S has FASTPLAY!=312, so it has been rejected earlier */
	if (ASAPInfo_GetTypeLetter(info) == 'D') {
		printf("WARNING: TYPE D");
		warnings++;
	}
	
	/* issue a warning if the 6502 code possibly reads the PAL/NTSC flag of GTIA
	   (LDA/LDX/LDY $D014, e.g. Ghostbusters.sap).
	   This is just a guess - false positives are possible and other code may be used for NTSC detection. */
	for (int i = 0; i < module_len - 2; i++) {
		if (module[i] >= 0xac && module[i] <= 0xae && module[i + 1] == 0x14 && module[i + 2] == 0xd0) {
			if (warnings++ > 0)
				printf(", ");
			printf("WARNING: possible PAL/NTSC detection code");
			break;
		}
	}

	ASAPInfo_Delete(info);
	if (warnings == 0)
		printf("ok");
	printf("\n");
}

int main(int argc, char **argv)
{
	bool usage = true;
	for (int i = 1; i < argc; i++) {
		const char *arg = argv[i];
		if (strcmp(arg, "--help") == 0) {
			usage = true;
			break;
		}
		if (strcmp(arg, "--version") == 0) {
			printf("sap2ntsc " ASAPInfo_VERSION "\n");
			return 0;
		}
		process_file(arg);
		usage = false;
	}
	if (usage) {
		printf(
			"Usage: sap2ntsc FILE.sap...\n"
			"Replaces FILE.sap with an NTSC version\n"
		);
	}
	return 0;
}