diff -Nru kodi-audiodecoder-asap-20.2.0/audiodecoder.asap/addon.xml.in kodi-audiodecoder-asap-20.3.0/audiodecoder.asap/addon.xml.in --- kodi-audiodecoder-asap-20.2.0/audiodecoder.asap/addon.xml.in 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/audiodecoder.asap/addon.xml.in 2013-05-31 22:59:22.000000000 +0000 @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ @@ -63,14 +63,24 @@ ASAP - Another Slight Atari Player ASAP - Another Slight Atari Player ASAP - Another Slight Atari Player + ASAP - Otro Reproductor Ligero de Atari + ASAP - Another Slight Atari Player + ASAP - Another Slight Atari Player ASAP - Another Slight Atari Player + ASAP - Another Slight Atari Player ASAP - Another Slight Atari Player + ASAP (Another Slight Atari Player) ASAP - 另一个轻量级雅达利播放器 ASAP (Another Slight Atari Player) afspiller i Kodi og konverterer 8-bit Atari-musik ( *.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) på moderne computere og mobile enheder. ASAP (Another Slight Atari Player) konvertiert 8-Bit-Atari-Musik (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) zum nötigen Format und zur Wiedergabe in Kodi auf modernen Computern und Mobilgeräten. ASAP (Another Slight Atari Player) plays on Kodi and converts 8-bit Atari music (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) on modern computers and mobile devices. + ASAP (Another Slight Atari Player - Otro Reproductor Ligero de Atari) reproduce en Kodi y convierte musica de Atari de 8-bits (extensiones sap, cmc, mpt, rmt, tmc, ...) en ordenadores modernos y dispositivos móviles. + ASAP (Another Slight Atari Player) toistaa ja muuntaa Kodissa 8-bittisiä Atari-musiikkitiedostoja (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) nykyaikaisilla tietokoneilla ja mobillilaitteilla. + ASAP (Another Slight Atari Player) se izvodi na Kodiju i pretvara 8-bitnu Atari glazbu (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) na modernim računalima i mobilnim uređajima. ASAP(Another Slight Atari Player)는 Kodi에서 재생되며 최신 컴퓨터 및 모바일 장치에서 8비트 Atari 음악(*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...)을 변환합니다. + ASAP (Another Slight Atari Player) odtwarza na Kodi i konwertuje 8-bitową muzykę Atari (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) na nowoczesnych komputerach i urządzeniach mobilnych. ASAP (Another Slight Atari Player) воспроизводит в Kodi и конвертирует 8-битную музыку Atari (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) на современных компьютерах и мобильных устройствах. + ASAP (Another Slight Atari Player) prehráva a konvertuje na Kodi 8-bitovú hudbu Atari (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) na moderných počítačoch a mobilných zariadeniach. ASAP(Another Slight Atari Player,另一个轻量级雅达利播放器)在 Kodi 上播放,并在现代计算机和移动设备上转换8位雅达利音乐(*.sap、*.cmc、*.mpt、*.rmt、*.tmc等)。 diff -Nru kodi-audiodecoder-asap-20.2.0/audiodecoder.asap/resources/language/resource.language.es_es/strings.po kodi-audiodecoder-asap-20.3.0/audiodecoder.asap/resources/language/resource.language.es_es/strings.po --- kodi-audiodecoder-asap-20.2.0/audiodecoder.asap/resources/language/resource.language.es_es/strings.po 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/audiodecoder.asap/resources/language/resource.language.es_es/strings.po 2013-05-31 22:59:22.000000000 +0000 @@ -5,21 +5,22 @@ msgid "" msgstr "" "Project-Id-Version: KODI Addons\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/audiodecoder.asap/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" +"PO-Revision-Date: 2022-01-18 15:13+0000\n" +"Last-Translator: Alfonso Cachero \n" +"Language-Team: Spanish (Spain) \n" "Language: es_es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.10.1\n" msgctxt "Addon Summary" msgid "ASAP - Another Slight Atari Player" -msgstr "" +msgstr "ASAP - Otro Reproductor Ligero de Atari" msgctxt "Addon Description" msgid "ASAP (Another Slight Atari Player) plays on Kodi and converts 8-bit Atari music (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) on modern computers and mobile devices." -msgstr "" +msgstr "ASAP (Another Slight Atari Player - Otro Reproductor Ligero de Atari) reproduce en Kodi y convierte musica de Atari de 8-bits (extensiones sap, cmc, mpt, rmt, tmc, ...) en ordenadores modernos y dispositivos móviles." diff -Nru kodi-audiodecoder-asap-20.2.0/audiodecoder.asap/resources/language/resource.language.fi_fi/strings.po kodi-audiodecoder-asap-20.3.0/audiodecoder.asap/resources/language/resource.language.fi_fi/strings.po --- kodi-audiodecoder-asap-20.2.0/audiodecoder.asap/resources/language/resource.language.fi_fi/strings.po 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/audiodecoder.asap/resources/language/resource.language.fi_fi/strings.po 2013-05-31 22:59:22.000000000 +0000 @@ -5,21 +5,22 @@ msgid "" msgstr "" "Project-Id-Version: KODI Addons\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/audiodecoder.asap/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" +"PO-Revision-Date: 2022-03-19 06:44+0000\n" +"Last-Translator: Oskari Lavinto \n" +"Language-Team: Finnish \n" "Language: fi_fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.11.2\n" msgctxt "Addon Summary" msgid "ASAP - Another Slight Atari Player" -msgstr "" +msgstr "ASAP - Another Slight Atari Player" msgctxt "Addon Description" msgid "ASAP (Another Slight Atari Player) plays on Kodi and converts 8-bit Atari music (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) on modern computers and mobile devices." -msgstr "" +msgstr "ASAP (Another Slight Atari Player) toistaa ja muuntaa Kodissa 8-bittisiä Atari-musiikkitiedostoja (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) nykyaikaisilla tietokoneilla ja mobillilaitteilla." diff -Nru kodi-audiodecoder-asap-20.2.0/audiodecoder.asap/resources/language/resource.language.hr_hr/strings.po kodi-audiodecoder-asap-20.3.0/audiodecoder.asap/resources/language/resource.language.hr_hr/strings.po --- kodi-audiodecoder-asap-20.2.0/audiodecoder.asap/resources/language/resource.language.hr_hr/strings.po 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/audiodecoder.asap/resources/language/resource.language.hr_hr/strings.po 2013-05-31 22:59:22.000000000 +0000 @@ -5,21 +5,22 @@ msgid "" msgstr "" "Project-Id-Version: KODI Addons\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/audiodecoder.asap/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" +"PO-Revision-Date: 2022-02-15 12:13+0000\n" +"Last-Translator: gogogogi \n" +"Language-Team: Croatian \n" "Language: hr_hr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.10.1\n" msgctxt "Addon Summary" msgid "ASAP - Another Slight Atari Player" -msgstr "" +msgstr "ASAP - Another Slight Atari Player" msgctxt "Addon Description" msgid "ASAP (Another Slight Atari Player) plays on Kodi and converts 8-bit Atari music (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) on modern computers and mobile devices." -msgstr "" +msgstr "ASAP (Another Slight Atari Player) se izvodi na Kodiju i pretvara 8-bitnu Atari glazbu (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) na modernim računalima i mobilnim uređajima." diff -Nru kodi-audiodecoder-asap-20.2.0/audiodecoder.asap/resources/language/resource.language.pl_pl/strings.po kodi-audiodecoder-asap-20.3.0/audiodecoder.asap/resources/language/resource.language.pl_pl/strings.po --- kodi-audiodecoder-asap-20.2.0/audiodecoder.asap/resources/language/resource.language.pl_pl/strings.po 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/audiodecoder.asap/resources/language/resource.language.pl_pl/strings.po 2013-05-31 22:59:22.000000000 +0000 @@ -5,21 +5,22 @@ msgid "" msgstr "" "Project-Id-Version: KODI Addons\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/audiodecoder.asap/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" +"PO-Revision-Date: 2022-02-08 06:57+0000\n" +"Last-Translator: Marek Adamski \n" +"Language-Team: Polish \n" "Language: pl_pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.10.1\n" msgctxt "Addon Summary" msgid "ASAP - Another Slight Atari Player" -msgstr "" +msgstr "ASAP - Another Slight Atari Player" msgctxt "Addon Description" msgid "ASAP (Another Slight Atari Player) plays on Kodi and converts 8-bit Atari music (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) on modern computers and mobile devices." -msgstr "" +msgstr "ASAP (Another Slight Atari Player) odtwarza na Kodi i konwertuje 8-bitową muzykę Atari (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) na nowoczesnych komputerach i urządzeniach mobilnych." diff -Nru kodi-audiodecoder-asap-20.2.0/audiodecoder.asap/resources/language/resource.language.sk_sk/strings.po kodi-audiodecoder-asap-20.3.0/audiodecoder.asap/resources/language/resource.language.sk_sk/strings.po --- kodi-audiodecoder-asap-20.2.0/audiodecoder.asap/resources/language/resource.language.sk_sk/strings.po 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/audiodecoder.asap/resources/language/resource.language.sk_sk/strings.po 2013-05-31 22:59:22.000000000 +0000 @@ -5,21 +5,22 @@ msgid "" msgstr "" "Project-Id-Version: KODI Addons\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/audiodecoder.asap/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" +"PO-Revision-Date: 2022-02-08 06:57+0000\n" +"Last-Translator: Patrik Špaňo \n" +"Language-Team: Slovak \n" "Language: sk_sk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"X-Generator: Weblate 4.10.1\n" msgctxt "Addon Summary" msgid "ASAP - Another Slight Atari Player" -msgstr "" +msgstr "ASAP (Another Slight Atari Player)" msgctxt "Addon Description" msgid "ASAP (Another Slight Atari Player) plays on Kodi and converts 8-bit Atari music (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) on modern computers and mobile devices." -msgstr "" +msgstr "ASAP (Another Slight Atari Player) prehráva a konvertuje na Kodi 8-bitovú hudbu Atari (*.sap, *.cmc, *.mpt, *.rmt, *.tmc, ...) na moderných počítačoch a mobilných zariadeniach." diff -Nru kodi-audiodecoder-asap-20.2.0/azure-pipelines.yml kodi-audiodecoder-asap-20.3.0/azure-pipelines.yml --- kodi-audiodecoder-asap-20.2.0/azure-pipelines.yml 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/azure-pipelines.yml 2013-05-31 22:59:22.000000000 +0000 @@ -16,25 +16,25 @@ - job: Windows pool: - vmImage: 'VS2017-Win2016' + vmImage: 'windows-2022' strategy: matrix: Win32: - GENERATOR: "Visual Studio 15 2017" + GENERATOR: "Visual Studio 17 2022" ARCHITECTURE: Win32 CONFIGURATION: Release Win64: - GENERATOR: "Visual Studio 15 2017" + GENERATOR: "Visual Studio 17 2022" ARCHITECTURE: x64 CONFIGURATION: Release Win64-UWP: - GENERATOR: "Visual Studio 15 2017" + GENERATOR: "Visual Studio 17 2022" ARCHITECTURE: x64 CONFIGURATION: Release WINSTORE: -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0.17763.0" ARM64-UWP: - GENERATOR: "Visual Studio 15 2017" + GENERATOR: "Visual Studio 17 2022" ARCHITECTURE: ARM64 CONFIGURATION: Release WINSTORE: -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0.17763.0" diff -Nru kodi-audiodecoder-asap-20.2.0/debian/changelog kodi-audiodecoder-asap-20.3.0/debian/changelog --- kodi-audiodecoder-asap-20.2.0/debian/changelog 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/debian/changelog 2013-05-31 22:59:22.000000000 +0000 @@ -1,4 +1,4 @@ -kodi-audiodecoder-asap (6:20.2.0-1~focal) focal; urgency=low +kodi-audiodecoder-asap (6:20.3.0-1~focal) focal; urgency=low [ kodi ] * autogenerated dummy changelog diff -Nru kodi-audiodecoder-asap-20.2.0/debian/copyright kodi-audiodecoder-asap-20.3.0/debian/copyright --- kodi-audiodecoder-asap-20.2.0/debian/copyright 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/debian/copyright 2013-05-31 22:59:22.000000000 +0000 @@ -3,7 +3,7 @@ Source: https://github.com/xbmc/audiodecoder.asap Files: * -Copyright: 2005-2021 Team Kodi +Copyright: 2005-2022 Team Kodi License: GPL-2+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff -Nru kodi-audiodecoder-asap-20.2.0/.github/workflows/build.yml kodi-audiodecoder-asap-20.3.0/.github/workflows/build.yml --- kodi-audiodecoder-asap-20.2.0/.github/workflows/build.yml 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/.github/workflows/build.yml 2013-05-31 22:59:22.000000000 +0000 @@ -11,17 +11,17 @@ matrix: include: - name: "Debian package test" - os: ubuntu-18.04 + os: ubuntu-20.04 CC: gcc CXX: g++ DEBIAN_BUILD: true - #- os: ubuntu-18.04 + #- os: ubuntu-20.04 #CC: gcc #CXX: g++ - #- os: ubuntu-18.04 + #- os: ubuntu-20.04 #CC: clang #CXX: clang++ - #- os: macos-10.15 + #- os: macos-11 steps: - name: Install needed ubuntu depends env: diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/aatr.ci kodi-audiodecoder-asap-20.3.0/lib/asap-code/aatr.ci --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/aatr.ci 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/aatr.ci 2013-05-31 22:59:22.000000000 +0000 @@ -1,6 +1,6 @@ // aatr.ci - another ATR file extractor // -// Copyright (C) 2012-2019 Piotr Fusik +// Copyright (C) 2012-2021 Piotr Fusik // // This file is part of ASAP (Another Slight Atari Player), // see http://asap.sourceforge.net @@ -341,21 +341,18 @@ int totalRead = 0; int bytesPerSector = Disk.GetBytesPerSector(); while (length > 0) { - int got; - for (;;) { - got = Sector[bytesPerSector - 1]; - if (FileType == 0) { - // DOS 1 - if (got < 128) - got = 125; - else - got -= 128; - } - else if (got > bytesPerSector) - return -1; - got -= SectorOffset; - if (got > 0) - break; + int got = Sector[bytesPerSector - 1]; + if (FileType == 0) { + // DOS 1 + if (got < 128) + got = 125; + else + got -= 128; + } + else if (got > bytesPerSector) + return -1; + got -= SectorOffset; + if (got <= 0) { if (!Disk.ReadSector(NextSector, Sector, bytesPerSector)) return totalRead; SectorOffset = 0; @@ -364,14 +361,16 @@ sectorHi &= 3; NextSector = sectorHi << 8 | Sector[bytesPerSector - 2]; } - if (got > length) - got = length; - if (buffer != null) - Sector.CopyTo(SectorOffset, buffer, offset + totalRead, got); - Position += got; - SectorOffset += got; - totalRead += got; - length -= got; + else { + if (got > length) + got = length; + if (buffer != null) + Sector.CopyTo(SectorOffset, buffer, offset + totalRead, got); + Position += got; + SectorOffset += got; + totalRead += got; + length -= got; + } } return totalRead; } diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/asap.c kodi-audiodecoder-asap-20.3.0/lib/asap-code/asap.c --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/asap.c 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/asap.c 2013-05-31 22:59:22.000000000 +0000 @@ -1,4 +1,5 @@ // Generated automatically with "cito". Do not edit. +#include #include #include #include "asap.h" @@ -248,14 +249,6 @@ static bool ASAPInfo_CheckValidChar(int c); -static bool ASAPInfo_CheckValidText(const char *s); - -static bool ASAPInfo_CheckTwoDateDigits(const ASAPInfo *self, int i); - -static int ASAPInfo_CheckDate(const ASAPInfo *self); - -static int ASAPInfo_GetTwoDateDigits(const ASAPInfo *self, int i); - static int ASAPInfo_GetWord(uint8_t const *array, int i); static bool ASAPInfo_ParseModule(ASAPInfo *self, uint8_t const *module, int moduleLen); @@ -306,11 +299,13 @@ static bool ASAPInfo_ParseFc(ASAPInfo *self, uint8_t const *module, int moduleLen); +static char *ASAPInfo_ParseText(uint8_t const *module, int i, int argEnd); + static bool ASAPInfo_HasStringAt(uint8_t const *module, int moduleIndex, const char *s); -static int ASAPInfo_ParseDec(const char *s, int minVal, int maxVal); +static int ASAPInfo_ParseDec(uint8_t const *module, int i, int argEnd, int minVal, int maxVal); -static int ASAPInfo_ParseHex(const char *s); +static int ASAPInfo_ParseHex(uint8_t const *module, int i, int argEnd); static bool ASAPInfo_ValidateSap(uint8_t const *module, int moduleLen); @@ -324,6 +319,12 @@ static int ASAPInfo_GuessPackedExt(uint8_t const *module, int moduleLen); +static bool ASAPInfo_CheckValidText(const char *s); + +static int ASAPInfo_CheckDate(const ASAPInfo *self); + +static int ASAPInfo_GetTwoDateDigits(const ASAPInfo *self, int i); + static int ASAPInfo_GetRmtSapOffset(const ASAPInfo *self, uint8_t const *module, int moduleLen); static ASAPModuleType ASAPInfo_GetOriginalModuleType(const ASAPInfo *self, uint8_t const *module, int moduleLen); @@ -476,7 +477,7 @@ static int FlashPackItem_WriteValueTo(const FlashPackItem *self, uint8_t *buffer, int index); struct FlashPack { - int memory[65536]; + int16_t memory[65536]; uint8_t compressed[65536]; int compressedLength; FlashPackItem items[64]; @@ -2236,7 +2237,7 @@ static void ASAP_Call6502(ASAP *self, int addr) { self->cpu.memory[53760] = 32; - self->cpu.memory[53761] = (uint8_t) (addr & 255); + self->cpu.memory[53761] = (uint8_t) addr; self->cpu.memory[53762] = (uint8_t) (addr >> 8); self->cpu.memory[53763] = 210; self->cpu.pc = 53760; @@ -2266,7 +2267,7 @@ self->cpu.memory[53764] = 152; self->cpu.memory[53765] = 72; self->cpu.memory[53766] = 32; - self->cpu.memory[53767] = (uint8_t) (player & 255); + self->cpu.memory[53767] = (uint8_t) player; self->cpu.memory[53768] = (uint8_t) (player >> 8); self->cpu.memory[53769] = 104; self->cpu.memory[53770] = 168; @@ -2280,9 +2281,9 @@ case ASAPModuleType_SAP_S: ; int i = self->cpu.memory[69] - 1; - self->cpu.memory[69] = (uint8_t) (i & 255); + self->cpu.memory[69] = (uint8_t) i; if (i == 0) - self->cpu.memory[45179] = (uint8_t) ((self->cpu.memory[45179] + 1) & 255); + self->cpu.memory[45179] = (uint8_t) (self->cpu.memory[45179] + 1); break; case ASAPModuleType_DLT: ASAP_Call6502(self, player + 259); @@ -2535,10 +2536,10 @@ static void ASAP_PutLittleEndian(uint8_t *buffer, int offset, int value) { - buffer[offset] = (uint8_t) (value & 255); - buffer[offset + 1] = (uint8_t) (value >> 8 & 255); - buffer[offset + 2] = (uint8_t) (value >> 16 & 255); - buffer[offset + 3] = (uint8_t) (value >> 24 & 255); + buffer[offset] = (uint8_t) value; + buffer[offset + 1] = (uint8_t) (value >> 8); + buffer[offset + 2] = (uint8_t) (value >> 16); + buffer[offset + 3] = (uint8_t) (value >> 24); } static void ASAP_PutLittleEndians(uint8_t *buffer, int offset, int value1, int value2) @@ -2609,7 +2610,7 @@ { if (self->silenceCycles > 0 && self->silenceCyclesCounter <= 0) return 0; - int blockShift = ASAPInfo_GetChannels(&self->moduleInfo) - 1 + (format != ASAPSampleFormat_U8 ? 1 : 0); + int blockShift = ASAPInfo_GetChannels(&self->moduleInfo) - (format == ASAPSampleFormat_U8 ? 1 : 0); int bufferBlocks = bufferLen >> blockShift; if (self->currentDuration > 0) { int totalBlocks = ASAP_MillisecondsToBlocks(self->currentDuration); @@ -2776,242 +2777,6 @@ return true; } -static bool ASAPInfo_CheckValidText(const char *s) -{ - int n = (int) strlen(s); - if (n > 127) - return false; - for (int i = 0; i < n; i++) { - if (!ASAPInfo_CheckValidChar(s[i])) - return false; - } - return true; -} - -const char *ASAPInfo_GetAuthor(const ASAPInfo *self) -{ - return self->author; -} - -bool ASAPInfo_SetAuthor(ASAPInfo *self, const char *value) -{ - if (!ASAPInfo_CheckValidText(value)) - return false; - CiString_Assign(&self->author, strdup(value)); - return true; -} - -const char *ASAPInfo_GetTitle(const ASAPInfo *self) -{ - return self->title; -} - -bool ASAPInfo_SetTitle(ASAPInfo *self, const char *value) -{ - if (!ASAPInfo_CheckValidText(value)) - return false; - CiString_Assign(&self->title, strdup(value)); - return true; -} - -const char *ASAPInfo_GetTitleOrFilename(const ASAPInfo *self) -{ - return self->title[0] != '\0' ? self->title : self->filename; -} - -const char *ASAPInfo_GetDate(const ASAPInfo *self) -{ - return self->date; -} - -bool ASAPInfo_SetDate(ASAPInfo *self, const char *value) -{ - if (!ASAPInfo_CheckValidText(value)) - return false; - CiString_Assign(&self->date, strdup(value)); - return true; -} - -static bool ASAPInfo_CheckTwoDateDigits(const ASAPInfo *self, int i) -{ - int d1 = self->date[i]; - int d2 = self->date[i + 1]; - return d1 >= 48 && d1 <= 57 && d2 >= 48 && d2 <= 57; -} - -static int ASAPInfo_CheckDate(const ASAPInfo *self) -{ - int n = (int) strlen(self->date); - switch (n) { - case 10: - if (!ASAPInfo_CheckTwoDateDigits(self, 0) || self->date[2] != '/') - return -1; - case 7: - if (!ASAPInfo_CheckTwoDateDigits(self, n - 7) || self->date[n - 5] != '/') - return -1; - case 4: - if (!ASAPInfo_CheckTwoDateDigits(self, n - 4) || !ASAPInfo_CheckTwoDateDigits(self, n - 2)) - return -1; - return n; - default: - return -1; - } -} - -static int ASAPInfo_GetTwoDateDigits(const ASAPInfo *self, int i) -{ - return (self->date[i] - 48) * 10 + self->date[i + 1] - 48; -} - -int ASAPInfo_GetYear(const ASAPInfo *self) -{ - int n = ASAPInfo_CheckDate(self); - if (n < 0) - return -1; - return ASAPInfo_GetTwoDateDigits(self, n - 4) * 100 + ASAPInfo_GetTwoDateDigits(self, n - 2); -} - -int ASAPInfo_GetMonth(const ASAPInfo *self) -{ - int n = ASAPInfo_CheckDate(self); - if (n < 7) - return -1; - return ASAPInfo_GetTwoDateDigits(self, n - 7); -} - -int ASAPInfo_GetDayOfMonth(const ASAPInfo *self) -{ - int n = ASAPInfo_CheckDate(self); - if (n != 10) - return -1; - return ASAPInfo_GetTwoDateDigits(self, 0); -} - -int ASAPInfo_GetChannels(const ASAPInfo *self) -{ - return self->channels; -} - -int ASAPInfo_GetSongs(const ASAPInfo *self) -{ - return self->songs; -} - -int ASAPInfo_GetDefaultSong(const ASAPInfo *self) -{ - return self->defaultSong; -} - -bool ASAPInfo_SetDefaultSong(ASAPInfo *self, int song) -{ - if (song < 0 || song >= self->songs) - return false; - self->defaultSong = song; - return true; -} - -int ASAPInfo_GetDuration(const ASAPInfo *self, int song) -{ - return self->durations[song]; -} - -bool ASAPInfo_SetDuration(ASAPInfo *self, int song, int duration) -{ - if (song < 0 || song >= self->songs) - return false; - self->durations[song] = duration; - return true; -} - -bool ASAPInfo_GetLoop(const ASAPInfo *self, int song) -{ - return self->loops[song]; -} - -bool ASAPInfo_SetLoop(ASAPInfo *self, int song, bool loop) -{ - if (song < 0 || song >= self->songs) - return false; - self->loops[song] = loop; - return true; -} - -bool ASAPInfo_IsNtsc(const ASAPInfo *self) -{ - return self->ntsc; -} - -int ASAPInfo_GetTypeLetter(const ASAPInfo *self) -{ - switch (self->type) { - case ASAPModuleType_SAP_B: - return 66; - case ASAPModuleType_SAP_C: - return 67; - case ASAPModuleType_SAP_D: - return 68; - case ASAPModuleType_SAP_S: - return 83; - default: - return 0; - } -} - -int ASAPInfo_GetPlayerRateScanlines(const ASAPInfo *self) -{ - return self->fastplay; -} - -int ASAPInfo_GetPlayerRateHz(const ASAPInfo *self) -{ - int scanlineClock = self->ntsc ? 15699 : 15556; - return (scanlineClock + (self->fastplay >> 1)) / self->fastplay; -} - -int ASAPInfo_GetMusicAddress(const ASAPInfo *self) -{ - return self->music; -} - -bool ASAPInfo_SetMusicAddress(ASAPInfo *self, int address) -{ - if (address < 0 || address >= 65535) - return false; - self->music = address; - return true; -} - -int ASAPInfo_GetInitAddress(const ASAPInfo *self) -{ - return self->init; -} - -int ASAPInfo_GetPlayerAddress(const ASAPInfo *self) -{ - return self->player; -} - -int ASAPInfo_GetCovoxAddress(const ASAPInfo *self) -{ - return self->covoxAddr; -} - -int ASAPInfo_GetSapHeaderLength(const ASAPInfo *self) -{ - return self->headerLen; -} - -int ASAPInfo_GetInstrumentNamesOffset(const ASAPInfo *self, uint8_t const *module, int moduleLen) -{ - if (self->type != ASAPModuleType_RMT) - return -1; - for (int offset = ASAPInfo_GetWord(module, 4) - ASAPInfo_GetWord(module, 2) + 12; offset < moduleLen; offset++) { - if (module[offset - 1] == 0) - return offset; - } - return -1; -} - static int ASAPInfo_GetWord(uint8_t const *array, int i) { return array[i] + (array[i + 1] << 8); @@ -3266,7 +3031,9 @@ seen[i] = 2; for (int patternRows = module[462]; --patternRows >= 0;) { for (ch = 3; ch >= 0; ch--) { - if (patternOffset[ch] == 0 || --blankRowsCounter[ch] >= 0) + if (patternOffset[ch] == 0) + continue; + if (--blankRowsCounter[ch] >= 0) continue; for (;;) { i = module[patternOffset[ch]++]; @@ -3878,36 +3645,37 @@ self->loops[self->songs] = true; while (!ASAPInfo_IsFcSongEnd(module, trackPos)) { for (int n = 0; n < 3; n++) { - int trackCmd = ASAPInfo_GetFcTrackCommand(module, trackPos, n); - if (trackCmd != 255 && patternDelay[n]-- <= 0) { - while (trackPos[n] < 256) { - trackCmd = ASAPInfo_GetFcTrackCommand(module, trackPos, n); - if (trackCmd < 64) { - int patternCmd = module[patternOffsets[trackCmd] + patternPos[n]++]; - if (patternCmd < 64) { - patternDelay[n] = noteDuration[n]; - break; - } - else if (patternCmd < 96) - noteDuration[n] = patternCmd - 64; - else if (patternCmd == 255) { - patternDelay[n] = 0; - noteDuration[n] = 0; - patternPos[n] = 0; - trackPos[n]++; - } - } - else if (trackCmd == 64) - trackPos[n] += 2; - else if (trackCmd == 254) { - self->loops[self->songs] = false; + if (ASAPInfo_GetFcTrackCommand(module, trackPos, n) == 255) + continue; + if (patternDelay[n]-- > 0) + continue; + while (trackPos[n] < 256) { + int trackCmd = ASAPInfo_GetFcTrackCommand(module, trackPos, n); + if (trackCmd < 64) { + int patternCmd = module[patternOffsets[trackCmd] + patternPos[n]++]; + if (patternCmd < 64) { + patternDelay[n] = noteDuration[n]; break; } - else if (trackCmd == 255) - break; - else + else if (patternCmd < 96) + noteDuration[n] = patternCmd - 64; + else if (patternCmd == 255) { + patternDelay[n] = 0; + noteDuration[n] = 0; + patternPos[n] = 0; trackPos[n]++; + } + } + else if (trackCmd == 64) + trackPos[n] += 2; + else if (trackCmd == 254) { + self->loops[self->songs] = false; + break; } + else if (trackCmd == 255) + break; + else + trackPos[n]++; } } if (ASAPInfo_IsFcSongEnd(module, trackPos)) @@ -3929,6 +3697,15 @@ return true; } +static char *ASAPInfo_ParseText(uint8_t const *module, int i, int argEnd) +{ + if (i < 0 || argEnd - i < 2 || module[i] != 34 || module[argEnd - 1] != 34) + return strdup(""); + if (module[i + 1] == 60 && module[i + 2] == 63 && module[i + 3] == 62) + return strdup(""); + return CiString_Substring((const char *) module + i + 1, argEnd - i - 2); +} + static bool ASAPInfo_HasStringAt(uint8_t const *module, int moduleIndex, const char *s) { int n = (int) strlen(s); @@ -3938,12 +3715,13 @@ return true; } -static int ASAPInfo_ParseDec(const char *s, int minVal, int maxVal) +static int ASAPInfo_ParseDec(uint8_t const *module, int i, int argEnd, int minVal, int maxVal) { + if (i < 0) + return -1; int r = 0; - int len = (int) strlen(s); - for (int i = 0; i < len; i++) { - int c = s[i]; + while (i < argEnd) { + int c = module[i++]; if (c < 48 || c > 57) return -1; r = r * 10 + c - 48; @@ -3955,14 +3733,15 @@ return r; } -static int ASAPInfo_ParseHex(const char *s) +static int ASAPInfo_ParseHex(uint8_t const *module, int i, int argEnd) { + if (i < 0) + return -1; int r = 0; - int len = (int) strlen(s); - for (int i = 0; i < len; i++) { + while (i < argEnd) { + int c = module[i++]; if (r > 4095) return -1; - int c = s[i]; r <<= 4; if (c >= 48 && c <= 57) r += c - 48; @@ -4003,7 +3782,7 @@ } int tagLen = moduleIndex - lineStart; int argStart = -1; - int argLen = -1; + int argEnd = -1; for (;;) { int c = module[moduleIndex]; if (c > 32) { @@ -4011,11 +3790,11 @@ return false; if (argStart < 0) argStart = moduleIndex; - argLen = -1; + argEnd = -1; } else { - if (argLen < 0) - argLen = moduleIndex - argStart; + if (argEnd < 0) + argEnd = moduleIndex; if (c == 10) break; } @@ -4024,99 +3803,70 @@ } if (++moduleIndex + 6 >= moduleLen) return false; - if (tagLen <= 8) { - char *tag = CiString_Substring((const char *) module + lineStart, tagLen); - if (argStart >= 0 && argLen <= 129) { - char *arg = CiString_Substring((const char *) module + argStart, argLen); - if (argLen >= 3 && arg[0] == '\"' && arg[argLen - 1] == '\"' && strcmp(arg, "\"\"") != 0) { - if (strcmp(tag, "AUTHOR") == 0) - CiString_Assign(&self->author, CiString_Substring(arg + 1, argLen - 2)); - else if (strcmp(tag, "NAME") == 0) - CiString_Assign(&self->title, CiString_Substring(arg + 1, argLen - 2)); - else if (strcmp(tag, "DATE") == 0) - CiString_Assign(&self->date, CiString_Substring(arg + 1, argLen - 2)); - } - else if (strcmp(tag, "SONGS") == 0) { - if ((self->songs = ASAPInfo_ParseDec(arg, 1, 32)) == -1) { - free(arg); - free(tag); - return false; - } - } - else if (strcmp(tag, "DEFSONG") == 0) { - if ((self->defaultSong = ASAPInfo_ParseDec(arg, 0, 31)) == -1) { - free(arg); - free(tag); - return false; - } - } - else if (strcmp(tag, "TIME") == 0) { - if (durationIndex >= 32) { - free(arg); - free(tag); - return false; - } - if (argLen > 5 && ASAPInfo_HasStringAt(module, argStart + argLen - 5, " LOOP")) { - self->loops[durationIndex] = true; - arg[argLen - 5] = '\0'; - } - if ((self->durations[durationIndex++] = ASAPInfo_ParseDuration(arg)) == -1) { - free(arg); - free(tag); - return false; - } - } - else if (strcmp(tag, "TYPE") == 0) - type = arg[0]; - else if (strcmp(tag, "FASTPLAY") == 0) { - if ((self->fastplay = ASAPInfo_ParseDec(arg, 1, 32767)) == -1) { - free(arg); - free(tag); - return false; - } - } - else if (strcmp(tag, "MUSIC") == 0) { - if ((self->music = ASAPInfo_ParseHex(arg)) == -1) { - free(arg); - free(tag); - return false; - } - } - else if (strcmp(tag, "INIT") == 0) { - if ((self->init = ASAPInfo_ParseHex(arg)) == -1) { - free(arg); - free(tag); - return false; - } - } - else if (strcmp(tag, "PLAYER") == 0) { - if ((self->player = ASAPInfo_ParseHex(arg)) == -1) { - free(arg); - free(tag); - return false; - } - } - else if (strcmp(tag, "COVOX") == 0) { - if ((self->covoxAddr = ASAPInfo_ParseHex(arg)) == -1) { - free(arg); - free(tag); - return false; - } - if (self->covoxAddr != 54784) { - free(arg); - free(tag); - return false; - } - self->channels = 2; + if (tagLen == 6 && memcmp(module + lineStart, "AUTHOR", 6) == 0) + CiString_Assign(&self->author, ASAPInfo_ParseText(module, argStart, argEnd)); + else if (tagLen == 4 && memcmp(module + lineStart, "NAME", 4) == 0) + CiString_Assign(&self->title, ASAPInfo_ParseText(module, argStart, argEnd)); + else if (tagLen == 4 && memcmp(module + lineStart, "DATE", 4) == 0) + CiString_Assign(&self->date, ASAPInfo_ParseText(module, argStart, argEnd)); + else if (tagLen == 4 && memcmp(module + lineStart, "TIME", 4) == 0) { + if (durationIndex >= 32) + return false; + if (argStart < 0) + return false; + if (argEnd - argStart > 5 && ASAPInfo_HasStringAt(module, argEnd - 5, " LOOP")) { + self->loops[durationIndex] = true; + argEnd -= 5; + } + { + char *arg = CiString_Substring((const char *) module + argStart, argEnd - argStart); + if ((self->durations[durationIndex++] = ASAPInfo_ParseDuration(arg)) == -1) { + free(arg); + return false; } free(arg); } - else if (strcmp(tag, "STEREO") == 0) - self->channels = 2; - else if (strcmp(tag, "NTSC") == 0) - self->ntsc = true; - free(tag); } + else if (tagLen == 5 && memcmp(module + lineStart, "SONGS", 5) == 0) { + if ((self->songs = ASAPInfo_ParseDec(module, argStart, argEnd, 1, 32)) == -1) + return false; + } + else if (tagLen == 7 && memcmp(module + lineStart, "DEFSONG", 7) == 0) { + if ((self->defaultSong = ASAPInfo_ParseDec(module, argStart, argEnd, 0, 31)) == -1) + return false; + } + else if (tagLen == 4 && memcmp(module + lineStart, "TYPE", 4) == 0) { + if (argStart < 0) + return false; + type = module[argStart]; + } + else if (tagLen == 8 && memcmp(module + lineStart, "FASTPLAY", 8) == 0) { + if ((self->fastplay = ASAPInfo_ParseDec(module, argStart, argEnd, 1, 32767)) == -1) + return false; + } + else if (tagLen == 5 && memcmp(module + lineStart, "MUSIC", 5) == 0) { + if ((self->music = ASAPInfo_ParseHex(module, argStart, argEnd)) == -1) + return false; + } + else if (tagLen == 4 && memcmp(module + lineStart, "INIT", 4) == 0) { + if ((self->init = ASAPInfo_ParseHex(module, argStart, argEnd)) == -1) + return false; + } + else if (tagLen == 6 && memcmp(module + lineStart, "PLAYER", 6) == 0) { + if ((self->player = ASAPInfo_ParseHex(module, argStart, argEnd)) == -1) + return false; + } + else if (tagLen == 5 && memcmp(module + lineStart, "COVOX", 5) == 0) { + if ((self->covoxAddr = ASAPInfo_ParseHex(module, argStart, argEnd)) == -1) + return false; + if (self->covoxAddr != 54784) + return false; + self->channels = 2; + } + else if (tagLen == 6 && memcmp(module + lineStart, "STEREO", 6) == 0) + self->channels = 2; + else if (tagLen == 4 && memcmp(module + lineStart, "NTSC", 4) == 0) + self->ntsc = true; } if (self->defaultSong >= self->songs) return false; @@ -4303,6 +4053,239 @@ } } +static bool ASAPInfo_CheckValidText(const char *s) +{ + int n = (int) strlen(s); + if (n > 127) + return false; + for (int i = 0; i < n; i++) { + if (!ASAPInfo_CheckValidChar(s[i])) + return false; + } + return true; +} + +const char *ASAPInfo_GetAuthor(const ASAPInfo *self) +{ + return self->author; +} + +bool ASAPInfo_SetAuthor(ASAPInfo *self, const char *value) +{ + if (!ASAPInfo_CheckValidText(value)) + return false; + CiString_Assign(&self->author, strdup(value)); + return true; +} + +const char *ASAPInfo_GetTitle(const ASAPInfo *self) +{ + return self->title; +} + +bool ASAPInfo_SetTitle(ASAPInfo *self, const char *value) +{ + if (!ASAPInfo_CheckValidText(value)) + return false; + CiString_Assign(&self->title, strdup(value)); + return true; +} + +const char *ASAPInfo_GetTitleOrFilename(const ASAPInfo *self) +{ + return self->title[0] != '\0' ? self->title : self->filename; +} + +const char *ASAPInfo_GetDate(const ASAPInfo *self) +{ + return self->date; +} + +bool ASAPInfo_SetDate(ASAPInfo *self, const char *value) +{ + if (!ASAPInfo_CheckValidText(value)) + return false; + CiString_Assign(&self->date, strdup(value)); + return true; +} + +static int ASAPInfo_CheckDate(const ASAPInfo *self) +{ + int n = (int) strlen(self->date); + switch (n) { + case 4: + case 7: + case 10: + break; + default: + return -1; + } + for (int i = 0; i < n; i++) { + int c = self->date[i]; + if (i == n - 5 || i == n - 8) { + if (c != 47) + return -1; + } + else if (c < 48 || c > 57) + return -1; + } + return n; +} + +static int ASAPInfo_GetTwoDateDigits(const ASAPInfo *self, int i) +{ + return (self->date[i] - 48) * 10 + self->date[i + 1] - 48; +} + +int ASAPInfo_GetYear(const ASAPInfo *self) +{ + int n = ASAPInfo_CheckDate(self); + if (n < 0) + return -1; + return ASAPInfo_GetTwoDateDigits(self, n - 4) * 100 + ASAPInfo_GetTwoDateDigits(self, n - 2); +} + +int ASAPInfo_GetMonth(const ASAPInfo *self) +{ + int n = ASAPInfo_CheckDate(self); + if (n < 7) + return -1; + return ASAPInfo_GetTwoDateDigits(self, n - 7); +} + +int ASAPInfo_GetDayOfMonth(const ASAPInfo *self) +{ + int n = ASAPInfo_CheckDate(self); + if (n != 10) + return -1; + return ASAPInfo_GetTwoDateDigits(self, 0); +} + +int ASAPInfo_GetChannels(const ASAPInfo *self) +{ + return self->channels; +} + +int ASAPInfo_GetSongs(const ASAPInfo *self) +{ + return self->songs; +} + +int ASAPInfo_GetDefaultSong(const ASAPInfo *self) +{ + return self->defaultSong; +} + +bool ASAPInfo_SetDefaultSong(ASAPInfo *self, int song) +{ + if (song < 0 || song >= self->songs) + return false; + self->defaultSong = song; + return true; +} + +int ASAPInfo_GetDuration(const ASAPInfo *self, int song) +{ + return self->durations[song]; +} + +bool ASAPInfo_SetDuration(ASAPInfo *self, int song, int duration) +{ + if (song < 0 || song >= self->songs) + return false; + self->durations[song] = duration; + return true; +} + +bool ASAPInfo_GetLoop(const ASAPInfo *self, int song) +{ + return self->loops[song]; +} + +bool ASAPInfo_SetLoop(ASAPInfo *self, int song, bool loop) +{ + if (song < 0 || song >= self->songs) + return false; + self->loops[song] = loop; + return true; +} + +bool ASAPInfo_IsNtsc(const ASAPInfo *self) +{ + return self->ntsc; +} + +int ASAPInfo_GetTypeLetter(const ASAPInfo *self) +{ + switch (self->type) { + case ASAPModuleType_SAP_B: + return 66; + case ASAPModuleType_SAP_C: + return 67; + case ASAPModuleType_SAP_D: + return 68; + case ASAPModuleType_SAP_S: + return 83; + default: + return 0; + } +} + +int ASAPInfo_GetPlayerRateScanlines(const ASAPInfo *self) +{ + return self->fastplay; +} + +int ASAPInfo_GetPlayerRateHz(const ASAPInfo *self) +{ + int scanlineClock = self->ntsc ? 15699 : 15556; + return (scanlineClock + (self->fastplay >> 1)) / self->fastplay; +} + +int ASAPInfo_GetMusicAddress(const ASAPInfo *self) +{ + return self->music; +} + +bool ASAPInfo_SetMusicAddress(ASAPInfo *self, int address) +{ + if (address < 0 || address >= 65535) + return false; + self->music = address; + return true; +} + +int ASAPInfo_GetInitAddress(const ASAPInfo *self) +{ + return self->init; +} + +int ASAPInfo_GetPlayerAddress(const ASAPInfo *self) +{ + return self->player; +} + +int ASAPInfo_GetCovoxAddress(const ASAPInfo *self) +{ + return self->covoxAddr; +} + +int ASAPInfo_GetSapHeaderLength(const ASAPInfo *self) +{ + return self->headerLen; +} + +int ASAPInfo_GetInstrumentNamesOffset(const ASAPInfo *self, uint8_t const *module, int moduleLen) +{ + if (self->type != ASAPModuleType_RMT) + return -1; + for (int offset = ASAPInfo_GetWord(module, 4) - ASAPInfo_GetWord(module, 2) + 12; offset < moduleLen; offset++) { + if (module[offset - 1] == 0) + return offset; + } + return -1; +} + const char *ASAPInfo_GetExtDescription(const char *ext) { switch (ASAPInfo_PackExt(ext)) { @@ -4317,7 +4300,7 @@ case 7564643: return "Stereo Double CMC"; case 6516068: - return "DoublePlay CMC"; + return "CMC DoublePlay"; case 7629924: return "Delta Music Composer"; case 7630957: @@ -5066,10 +5049,11 @@ break; case ASAPModuleType_TMC: ; + int perFrame = module[37]; static const int TMC_PLAYER_OFFSET[4] = { 3, -9, -10, -10 }; - int player2 = player + TMC_PLAYER_OFFSET[module[37] - 1]; + int player2 = player + TMC_PLAYER_OFFSET[perFrame - 1]; static const int TMC_INIT_OFFSET[4] = { -14, -16, -17, -17 }; - startAddr = player2 + TMC_INIT_OFFSET[module[37] - 1]; + startAddr = player2 + TMC_INIT_OFFSET[perFrame - 1]; if (ASAPInfo_GetSongs(info) != 1) startAddr -= 3; if (!ASAPWriter_WriteExecutableHeader(self, initAndPlayer, info, 66, startAddr, player2)) @@ -5110,7 +5094,7 @@ if (!ASAPWriter_WriteByte(self, 96)) return false; } - switch (module[37]) { + switch (perFrame) { case 2: if (!ASAPWriter_WriteByte(self, 6)) return false; @@ -5163,7 +5147,7 @@ return false; if (!ASAPWriter_WriteByte(self, 160)) return false; - if (!ASAPWriter_WriteByte(self, module[37])) + if (!ASAPWriter_WriteByte(self, perFrame)) return false; if (!ASAPWriter_WriteByte(self, 132)) return false; @@ -5174,6 +5158,8 @@ if (!ASAPWriter_WriteByte(self, 3)) return false; break; + default: + break; } if (!ASAPWriter_WriteBytes(self, playerRoutine, 6, playerLastByte - player + 7)) return false; @@ -6188,7 +6174,7 @@ self->vdi |= 8; continue; default: - continue; + assert(false); } switch (data) { case 1: @@ -6446,6 +6432,8 @@ case 254: self->nz = Cpu6502_Increment(self, addr); break; + default: + assert(false); } } } @@ -6470,7 +6458,7 @@ ; int value = self->value - 128; buffer[index] = 0; - buffer[index + 1] = (uint8_t) (value & 255); + buffer[index + 1] = (uint8_t) value; buffer[index + 2] = (uint8_t) (value >> 8); return 3; default: @@ -6500,8 +6488,10 @@ int flags = 1; do { flags <<= 1; - if (index < self->itemsCount && self->items[index++].type != FlashPackItemType_LITERAL) - flags++; + if (index < self->itemsCount) { + if (self->items[index++].type != FlashPackItemType_LITERAL) + flags++; + } } while (flags < 256); return flags & 255; @@ -7111,6 +7101,8 @@ self->reloadCycles1 = data + 4; PokeyChannel_MuteUltrasound(&self->channels[1], cycle); break; + default: + assert(false); } PokeyChannel_MuteUltrasound(&self->channels[0], cycle); break; @@ -7133,6 +7125,8 @@ case 80: self->channels[1].periodCycles = self->channels[0].audf + (data << 8) + 7; break; + default: + assert(false); } PokeyChannel_MuteUltrasound(&self->channels[1], cycle); break; @@ -7161,6 +7155,8 @@ self->reloadCycles3 = data + 4; PokeyChannel_MuteUltrasound(&self->channels[3], cycle); break; + default: + assert(false); } PokeyChannel_MuteUltrasound(&self->channels[2], cycle); break; @@ -7183,6 +7179,8 @@ case 40: self->channels[3].periodCycles = self->channels[2].audf + (data << 8) + 7; break; + default: + assert(false); } PokeyChannel_MuteUltrasound(&self->channels[3], cycle); break; @@ -7214,6 +7212,8 @@ self->channels[1].periodCycles = self->channels[0].audf + (self->channels[1].audf << 8) + 7; self->reloadCycles1 = self->channels[0].audf + 4; break; + default: + assert(false); } PokeyChannel_MuteUltrasound(&self->channels[0], cycle); PokeyChannel_MuteUltrasound(&self->channels[1], cycle); @@ -7236,6 +7236,8 @@ self->channels[3].periodCycles = self->channels[2].audf + (self->channels[3].audf << 8) + 7; self->reloadCycles3 = self->channels[2].audf + 4; break; + default: + assert(false); } PokeyChannel_MuteUltrasound(&self->channels[2], cycle); PokeyChannel_MuteUltrasound(&self->channels[3], cycle); @@ -7312,12 +7314,12 @@ buffer[bufferOffset++] = (uint8_t) ((sample >> 8) + 128); break; case ASAPSampleFormat_S16_L_E: - buffer[bufferOffset++] = (uint8_t) (sample & 255); - buffer[bufferOffset++] = (uint8_t) (sample >> 8 & 255); + buffer[bufferOffset++] = (uint8_t) sample; + buffer[bufferOffset++] = (uint8_t) (sample >> 8); break; case ASAPSampleFormat_S16_B_E: - buffer[bufferOffset++] = (uint8_t) (sample >> 8 & 255); - buffer[bufferOffset++] = (uint8_t) (sample & 255); + buffer[bufferOffset++] = (uint8_t) (sample >> 8); + buffer[bufferOffset++] = (uint8_t) sample; break; } return bufferOffset; @@ -7333,12 +7335,12 @@ int reg = 511; for (int i = 0; i < 511; i++) { reg = (((reg >> 5 ^ reg) & 1) << 8) + (reg >> 1); - self->poly9Lookup[i] = (uint8_t) (reg & 255); + self->poly9Lookup[i] = (uint8_t) reg; } reg = 131071; for (int i = 0; i < 16385; i++) { reg = (((reg >> 5 ^ reg) & 255) << 9) + (reg >> 8); - self->poly17Lookup[i] = (uint8_t) (reg >> 1 & 255); + self->poly17Lookup[i] = (uint8_t) (reg >> 1); } } @@ -7407,16 +7409,18 @@ samplesEnd = i + blocks; else blocks = samplesEnd - i; - for (; i < samplesEnd; i++) { - bufferOffset = Pokey_StoreSample(&self->basePokey, buffer, bufferOffset, i, format); - if (self->extraPokeyMask != 0) - bufferOffset = Pokey_StoreSample(&self->extraPokey, buffer, bufferOffset, i, format); - } - if (i == self->readySamplesEnd) { - Pokey_AccumulateTrailing(&self->basePokey, i); - Pokey_AccumulateTrailing(&self->extraPokey, i); + if (blocks > 0) { + for (; i < samplesEnd; i++) { + bufferOffset = Pokey_StoreSample(&self->basePokey, buffer, bufferOffset, i, format); + if (self->extraPokeyMask != 0) + bufferOffset = Pokey_StoreSample(&self->extraPokey, buffer, bufferOffset, i, format); + } + if (i == self->readySamplesEnd) { + Pokey_AccumulateTrailing(&self->basePokey, i); + Pokey_AccumulateTrailing(&self->extraPokey, i); + } + self->readySamplesStart = i; } - self->readySamplesStart = i; return blocks; } diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/asap.ci kodi-audiodecoder-asap-20.3.0/lib/asap-code/asap.ci --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/asap.ci 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/asap.ci 2013-05-31 22:59:22.000000000 +0000 @@ -1,6 +1,6 @@ // asap.ci - emulation engine // -// Copyright (C) 2010-2019 Piotr Fusik +// Copyright (C) 2010-2021 Piotr Fusik // // This file is part of ASAP (Another Slight Atari Player), // see http://asap.sourceforge.net @@ -504,6 +504,7 @@ PutLittleEndian(buffer, offset + 4, value2); } +#if !OPENCL static int PutWavMetadata(byte[]! buffer, int offset, int fourCC, string value) { int len = value.Length; @@ -519,6 +520,7 @@ } return offset; } +#endif /// Fills leading bytes of the specified buffer with WAV file header. /// Returns the number of changed bytes. @@ -547,6 +549,7 @@ buffer[34] = 8 << use16bit; buffer[35] = 0; int i = 36; +#if !OPENCL if (metadata) { int year = ModuleInfo.GetYear(); if (ModuleInfo.GetTitle().Length > 0 || ModuleInfo.GetAuthor().Length > 0 || year > 0) { @@ -566,6 +569,7 @@ PutLittleEndians(buffer, 36, FourCC("LIST"), i - 44); } } +#endif PutLittleEndians(buffer, 0, FourCC("RIFF"), i + nBytes); PutLittleEndians(buffer, i, FourCC("data"), nBytes); return i + 8; @@ -575,7 +579,7 @@ { if (SilenceCycles > 0 && SilenceCyclesCounter <= 0) return 0; - int blockShift = (ModuleInfo.GetChannels() - 1) + (format != ASAPSampleFormat.U8 ? 1 : 0); + int blockShift = ModuleInfo.GetChannels() - (format == ASAPSampleFormat.U8 ? 1 : 0); int bufferBlocks = bufferLen >> blockShift; if (CurrentDuration > 0) { int totalBlocks = MillisecondsToBlocks(CurrentDuration); diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/asapconv.c kodi-audiodecoder-asap-20.3.0/lib/asap-code/asapconv.c --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/asapconv.c 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/asapconv.c 2013-05-31 22:59:22.000000000 +0000 @@ -1,7 +1,7 @@ /* * asapconv.c - converter of ASAP-supported formats * - * Copyright (C) 2005-2019 Piotr Fusik + * Copyright (C) 2005-2020 Piotr Fusik * * This file is part of ASAP (Another Slight Atari Player), * see http://asap.sourceforge.net @@ -250,9 +250,9 @@ fatal_error("-s and %%s are mutually exclusive"); if (++current_song >= ASAPInfo_GetSongs(info)) return NULL; - sprintf(song_tag, "%d", current_song + 1); file_per_song = true; } + sprintf(song_tag, "%d", current_song + 1); tag = song_tag; break; case '%': diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/asap.h kodi-audiodecoder-asap-20.3.0/lib/asap-code/asap.h --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/asap.h 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/asap.h 2013-05-31 22:59:22.000000000 +0000 @@ -137,27 +137,27 @@ /** * ASAP version - minor part. */ -#define ASAPInfo_VERSION_MINOR 0 +#define ASAPInfo_VERSION_MINOR 2 /** * ASAP version - micro part. */ -#define ASAPInfo_VERSION_MICRO 1 +#define ASAPInfo_VERSION_MICRO 0 /** * ASAP version as a string. */ -#define ASAPInfo_VERSION "5.0.1" +#define ASAPInfo_VERSION "5.2.0" /** * Years ASAP was created in. */ -#define ASAPInfo_YEARS "2005-2020" +#define ASAPInfo_YEARS "2005-2021" /** * Short credits for ASAP. */ -#define ASAPInfo_CREDITS "Another Slight Atari Player (C) 2005-2020 Piotr Fusik\nCMC, MPT, TMC, TM2 players (C) 1994-2005 Marcin Lewandowski\nRMT player (C) 2002-2005 Radek Sterba\nDLT player (C) 2009 Marek Konopka\nCMS player (C) 1999 David Spilka\nFC player (C) 2011 Jerzy Kut\n" +#define ASAPInfo_CREDITS "Another Slight Atari Player (C) 2005-2021 Piotr Fusik\nCMC, MPT, TMC, TM2 players (C) 1994-2005 Marcin Lewandowski\nRMT player (C) 2002-2005 Radek Sterba\nDLT player (C) 2009 Marek Konopka\nCMS player (C) 1999 David Spilka\nFC player (C) 2011 Jerzy Kut\n" /** * Short license notice. @@ -182,6 +182,35 @@ #define ASAPInfo_MAX_SONGS 32 /** + * Returns the number of milliseconds represented by the given string. + * @param s Time in the "mm:ss.xxx" format. + */ +int ASAPInfo_ParseDuration(const char *s); + +/** + * Checks whether the filename represents a module type supported by ASAP. + * Returns true if the filename is supported by ASAP. + * @param filename Filename to check the extension of. + */ +bool ASAPInfo_IsOurFile(const char *filename); + +/** + * Checks whether the filename extension represents a module type supported by ASAP. + * Returns true if the filename extension is supported by ASAP. + * @param ext Filename extension without the leading dot. + */ +bool ASAPInfo_IsOurExt(const char *ext); + +/** + * Loads file information. + * @param self This ASAPInfo. + * @param filename Filename, used to determine the format. + * @param module Contents of the file. + * @param moduleLen Length of the file. + */ +bool ASAPInfo_Load(ASAPInfo *self, const char *filename, uint8_t const *module, int moduleLen); + +/** * Returns author's name. * A nickname may be included in parentheses after the real name. * Multiple authors are separated with " & ". @@ -224,28 +253,30 @@ /** * Returns music creation date. - * Some of the possible formats are: + * + *

Some of the possible formats are: *

    *
  • YYYY
  • *
  • MM/YYYY
  • *
  • DD/MM/YYYY
  • *
  • YYYY-YYYY
  • *
- * An empty string means the date is unknown. + *

An empty string means the date is unknown. * @param self This ASAPInfo. */ const char *ASAPInfo_GetDate(const ASAPInfo *self); /** * Sets music creation date. - * Some of the possible formats are: + * + *

Some of the possible formats are: *

    *
  • YYYY
  • *
  • MM/YYYY
  • *
  • DD/MM/YYYY
  • *
  • YYYY-YYYY
  • *
- * An empty string means the date is unknown. + *

An empty string means the date is unknown. * @param self This ASAPInfo. * @param value New music creation date. */ @@ -317,12 +348,12 @@ /** * Returns information whether the specified song loops. - * Returns: + * + *

Returns: *

    *
  • true if the song loops
  • *
  • false if the song stops
  • *
- * * @param self This ASAPInfo. * @param song Song to check for looping, 0-based. */ @@ -330,12 +361,12 @@ /** * Sets information whether the specified song loops. - * Use: + * + *

Use: *

    *
  • true if the song loops
  • *
  • false if the song stops
  • *
- * * @param self This ASAPInfo. * @param song Song to set as looping, 0-based. * @param loop true if the song loops. @@ -403,7 +434,7 @@ int ASAPInfo_GetCovoxAddress(const ASAPInfo *self); /** - * Retturns the length of the SAP header in bytes. + * Returns the length of the SAP header in bytes. * @param self This ASAPInfo. */ int ASAPInfo_GetSapHeaderLength(const ASAPInfo *self); @@ -418,35 +449,6 @@ int ASAPInfo_GetInstrumentNamesOffset(const ASAPInfo *self, uint8_t const *module, int moduleLen); /** - * Returns the number of milliseconds represented by the given string. - * @param s Time in the "mm:ss.xxx" format. - */ -int ASAPInfo_ParseDuration(const char *s); - -/** - * Checks whether the filename represents a module type supported by ASAP. - * Returns true if the filename is supported by ASAP. - * @param filename Filename to check the extension of. - */ -bool ASAPInfo_IsOurFile(const char *filename); - -/** - * Checks whether the filename extension represents a module type supported by ASAP. - * Returns true if the filename extension is supported by ASAP. - * @param ext Filename extension without the leading dot. - */ -bool ASAPInfo_IsOurExt(const char *ext); - -/** - * Loads file information. - * @param self This ASAPInfo. - * @param filename Filename, used to determine the format. - * @param module Contents of the file. - * @param moduleLen Length of the file. - */ -bool ASAPInfo_Load(ASAPInfo *self, const char *filename, uint8_t const *module, int moduleLen); - -/** * Returns human-readable description of the filename extension. * @param ext Filename extension without the leading dot. */ diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/asapinfo.ci kodi-audiodecoder-asap-20.3.0/lib/asap-code/asapinfo.ci --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/asapinfo.ci 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/asapinfo.ci 2013-05-31 22:59:22.000000000 +0000 @@ -1,6 +1,6 @@ // asapinfo.ci - module parser // -// Copyright (C) 2010-2020 Piotr Fusik +// Copyright (C) 2010-2021 Piotr Fusik // // This file is part of ASAP (Another Slight Atari Player), // see http://asap.sourceforge.net @@ -105,18 +105,18 @@ /// ASAP version - major part. public const int VersionMajor = 5; /// ASAP version - minor part. - public const int VersionMinor = 0; + public const int VersionMinor = 2; /// ASAP version - micro part. - public const int VersionMicro = 1; + public const int VersionMicro = 0; /// ASAP version as a string. - public const string Version = VersionMajor + "." + VersionMinor + "." + VersionMicro; + public const string Version = $"{VersionMajor}.{VersionMinor}.{VersionMicro}"; /// Years ASAP was created in. - public const string Years = "2005-2020"; + public const string Years = "2005-2021"; /// Short credits for ASAP. public const string Credits = - "Another Slight Atari Player (C) " + Years + " Piotr Fusik\n" + + $"Another Slight Atari Player (C) {Years} Piotr Fusik\n" + "CMC, MPT, TMC, TM2 players (C) 1994-2005 Marcin Lewandowski\n" + "RMT player (C) 2002-2005 Radek Sterba\n" + "DLT player (C) 2009 Marek Konopka\n" + @@ -141,10 +141,12 @@ /// Maximum number of songs in a file. public const int MaxSongs = 32; +#if !OPENCL string() Filename; string() Author; string() Title; string() Date; +#endif int Channels; int Songs; int DefaultSong; @@ -173,489 +175,214 @@ throw "Invalid character"; } - static void CheckValidText(string s) + internal static int GetWord(byte[] array, int i) => array[i] + (array[i + 1] << 8); + + void ParseModule!(byte[] module, int moduleLen) throws { - int n = s.Length; - if (n > MaxTextLength) - throw "Text too long"; - for (int i = 0; i < n; i++) - CheckValidChar(s[i]); + if ((module[0] != 0xff || module[1] != 0xff) + && (module[0] != 0 || module[1] != 0)) // some CMC and clones start with zeros + throw "Invalid two leading bytes of the module"; + Music = GetWord(module, 2); + int musicLastByte = GetWord(module, 4); + if (Music <= 0xd7ff && musicLastByte >= 0xd000) + throw "Module address conflicts with hardware registers"; + int blockLen = musicLastByte + 1 - Music; + if (6 + blockLen != moduleLen) { + if (Type != ASAPModuleType.Rmt || 11 + blockLen > moduleLen) + throw "Module length doesn't match headers"; + // allow optional info for Raster Music Tracker + int infoAddr = GetWord(module, 6 + blockLen); + if (infoAddr != Music + blockLen) + throw "Invalid address of RMT info"; + int infoLen = GetWord(module, 8 + blockLen) + 1 - infoAddr; + if (10 + blockLen + infoLen != moduleLen) + throw "Invalid RMT info block"; + } } - /// Returns author's name. - /// A nickname may be included in parentheses after the real name. - /// Multiple authors are separated with `" & "`. - /// An empty string means the author is unknown. - public string GetAuthor() => Author; - - /// Sets author's name. - /// A nickname may be included in parentheses after the real name. - /// Multiple authors are separated with `" & "`. - /// An empty string means the author is unknown. - public void SetAuthor!( - /// New author's name for the current music. - string value) - throws + void AddSong!(int playerCalls) { - CheckValidText(value); - Author = value; + long scanlines = playerCalls * Fastplay; + Durations[Songs++] = scanlines * (114000 / 3) / (1773447 / 3); } - /// Returns music title. - /// An empty string means the title is unknown. - public string GetTitle() => Title; + // TODO: enum + 0 + const int SeenThisCall = 1; + const int SeenBefore = 2; + const int SeenRepeat = 3; - /// Sets music title. - /// An empty string means the title is unknown. - public void SetTitle!( - /// New title for the current music. - string value) - throws + void ParseCmcSong!(byte[] module, int pos) { - CheckValidText(value); - Title = value; + int tempo = module[0x19]; + int playerCalls = 0; + int repStartPos = 0; + int repEndPos = 0; + int repTimes = 0; + byte[0x55] seen = 0; + while (pos >= 0 && pos < 0x55) { + if (pos == repEndPos && repTimes > 0) { + for (int i = 0; i < 0x55; i++) + if (seen[i] == SeenThisCall || seen[i] == SeenRepeat) + seen[i] = 0; + repTimes--; + pos = repStartPos; + } + if (seen[pos] != 0) { + if (seen[pos] != SeenThisCall) + Loops[Songs] = true; + break; + } + seen[pos] = SeenThisCall; + int p1 = module[0x206 + pos]; + int p2 = module[0x25b + pos]; + int p3 = module[0x2b0 + pos]; + if (p1 == 0xfe || p2 == 0xfe || p3 == 0xfe) { + pos++; + continue; + } + p1 |= Type == ASAPModuleType.Cms ? 7 : 0xf; + switch (p1) { + case 0x87: // CMS VOLUME + case 0xa7: // CMS MODE + pos++; + break; + case 0x8f: // STOP + pos = -1; + break; + case 0x97: // CMS PAUSE + if (p2 < 128) { + playerCalls += p2; + if (p3 < 128) + playerCalls += p3 * 50; + } + pos++; + break; + case 0x9f: // JUMP + pos = p2; + break; + case 0xaf: // UP + pos -= p2; + break; + case 0xbf: // DOWN + pos += p2; + break; + case 0xcf: // TEMPO + if (p2 < 128) { + tempo = p2; + pos++; + } + else + pos = -1; + break; + case 0xdf: // REPLAY + pos++; + repStartPos = pos; + repEndPos = pos + p2; + repTimes = p3 - 1; + break; + case 0xef: // BREAK + Loops[Songs] = true; + pos = -1; + break; + default: + p2 = repTimes > 0 ? SeenRepeat : SeenBefore; + for (p1 = 0; p1 < 0x55; p1++) + if (seen[p1] == SeenThisCall) + seen[p1] = p2; + playerCalls += tempo * (Type == ASAPModuleType.Cm3 ? 48 : 64); + pos++; + break; + } + } + AddSong(playerCalls); } - /// Returns music title or filename. - /// If title is unknown returns filename without the path or extension. - public string GetTitleOrFilename() => Title.Length > 0 ? Title : Filename; - - /// Returns music creation date. - /// Some of the possible formats are: - /// * YYYY - /// * MM/YYYY - /// * DD/MM/YYYY - /// * YYYY-YYYY - /// - /// An empty string means the date is unknown. - public string GetDate() => Date; + const int CmrBassTableOffset = 0x70f; - /// Sets music creation date. - /// Some of the possible formats are: - /// * YYYY - /// * MM/YYYY - /// * DD/MM/YYYY - /// * YYYY-YYYY - /// - /// An empty string means the date is unknown. - public void SetDate!( - /// New music creation date. - string value) + void ParseCmc!(byte[] module, int moduleLen, ASAPModuleType type) throws { - CheckValidText(value); - Date = value; + if (moduleLen < 0x306) + throw "Module too short"; + Type = type; + ParseModule(module, moduleLen); + int lastPos = 0x54; + while (--lastPos >= 0) { + if (module[0x206 + lastPos] < 0xb0 + || module[0x25b + lastPos] < 0x40 + || module[0x2b0 + lastPos] < 0x40) + break; + if (Channels == 2) { + if (module[0x306 + lastPos] < 0xb0 + || module[0x35b + lastPos] < 0x40 + || module[0x3b0 + lastPos] < 0x40) + break; + } + } + Songs = 0; + ParseCmcSong(module, 0); + for (int pos = 0; pos < lastPos && Songs < MaxSongs; pos++) + if (module[0x206 + pos] == 0x8f || module[0x206 + pos] == 0xef) + ParseCmcSong(module, pos + 1); } - bool CheckTwoDateDigits(int i) + static bool IsDltTrackEmpty(byte[] module, int pos) { - int d1 = Date[i]; - int d2 = Date[i + 1]; - return d1 >= '0' && d1 <= '9' && d2 >= '0' && d2 <= '9'; + return module[0x2006 + pos] >= 0x43 + && module[0x2106 + pos] >= 0x40 + && module[0x2206 + pos] >= 0x40 + && module[0x2306 + pos] >= 0x40; } - int CheckDate() + static bool IsDltPatternEnd(byte[] module, int pos, int i) { - int n = Date.Length; - switch (n) { - case 10: - if (!CheckTwoDateDigits(0) || Date[2] != '/') - return -1; - goto case 7; - case 7: - if (!CheckTwoDateDigits(n - 7) || Date[n - 5] != '/') - return -1; - goto case 4; - case 4: - if (!CheckTwoDateDigits(n - 4) || !CheckTwoDateDigits(n - 2)) - return -1; - return n; - default: - return -1; + for (int ch = 0; ch < 4; ch++) { + int pattern = module[0x2006 + (ch << 8) + pos]; + if (pattern < 64) { + int offset = 6 + (pattern << 7) + (i << 1); + if ((module[offset] & 0x80) == 0 && (module[offset + 1] & 0x80) != 0) + return true; + } } + return false; } - int GetTwoDateDigits(int i) => (Date[i] - '0') * 10 + Date[i + 1] - '0'; + void ParseDltSong!(byte[] module, bool[]! seen, int pos) + { + while (pos < 128 && !seen[pos] && IsDltTrackEmpty(module, pos)) + seen[pos++] = true; + SongPos[Songs] = pos; + int playerCalls = 0; + bool loop = false; + int tempo = 6; + while (pos < 128) { + if (seen[pos]) { + loop = true; + break; + } + seen[pos] = true; + int p1 = module[0x2006 + pos]; + if (p1 == 0x40 || IsDltTrackEmpty(module, pos)) + break; + if (p1 == 0x41) + pos = module[0x2086 + pos]; + else if (p1 == 0x42) + tempo = module[0x2086 + pos++]; + else { + for (int i = 0; i < 64 && !IsDltPatternEnd(module, pos, i); i++) + playerCalls += tempo; + pos++; + } + } + if (playerCalls > 0) { + Loops[Songs] = loop; + AddSong(playerCalls); + } + } - /// Returns music creation year. - /// -1 means the year is unknown. - public int GetYear() - { - int n = CheckDate(); - if (n < 0) - return -1; - return GetTwoDateDigits(n - 4) * 100 + GetTwoDateDigits(n - 2); - } - - /// Returns music creation month (1-12). - /// -1 means the month is unknown. - public int GetMonth() - { - int n = CheckDate(); - if (n < 7) - return -1; - return GetTwoDateDigits(n - 7); - } - - /// Returns day of month of the music creation date. - /// -1 means the day is unknown. - public int GetDayOfMonth() - { - int n = CheckDate(); - if (n != 10) - return -1; - return GetTwoDateDigits(0); - } - - /// Returns 1 for mono or 2 for stereo. - public int GetChannels() => Channels; - - /// Returns number of songs in the file. - public int GetSongs() => Songs; - - /// Returns 0-based index of the "main" song. - /// The specified song should be played by default. - public int GetDefaultSong() => DefaultSong; - - /// Sets the 0-based index of the "main" song. - public void SetDefaultSong!( - /// New default song. - int song) - throws - { - if (song < 0 || song >= Songs) - throw "Song out of range"; - DefaultSong = song; - } - - /// Returns length of the specified song. - /// The length is specified in milliseconds. -1 means the length is indeterminate. - public int GetDuration( - /// Song to get length of, 0-based. - int song) - => Durations[song]; - - /// Sets length of the specified song. - /// The length is specified in milliseconds. -1 means the length is indeterminate. - public void SetDuration!( - /// Song to set length of, 0-based. - int song, - /// New length in milliseconds. - int duration) - throws - { - if (song < 0 || song >= Songs) - throw "Song out of range"; - Durations[song] = duration; - } - - /// Returns information whether the specified song loops. - /// Returns: - /// * `true` if the song loops - /// * `false` if the song stops - public bool GetLoop( - /// Song to check for looping, 0-based. - int song) - => Loops[song]; - - /// Sets information whether the specified song loops. - /// Use: - /// * `true` if the song loops - /// * `false` if the song stops - public void SetLoop!( - /// Song to set as looping, 0-based. - int song, - /// `true` if the song loops. - bool loop) - throws - { - if (song < 0 || song >= Songs) - throw "Song out of range"; - Loops[song] = loop; - } - - /// Returns `true` for NTSC song and `false` for PAL song. - public bool IsNtsc() => Ntsc; - - /// Returns the letter argument for the TYPE SAP tag. - /// Returns zero for non-SAP files. - public int GetTypeLetter() - { - switch (Type) { - case ASAPModuleType.SapB: return 'B'; - case ASAPModuleType.SapC: return 'C'; - case ASAPModuleType.SapD: return 'D'; - case ASAPModuleType.SapS: return 'S'; - default: return 0; - } - } - - /// Returns player routine rate in Atari scanlines. - public int GetPlayerRateScanlines() => Fastplay; - - /// Returns approximate player routine rate in Hz. - public int GetPlayerRateHz() - { - int scanlineClock = Ntsc ? 1789772 / 114 : 1773447 / 114; - return (scanlineClock + (Fastplay >> 1)) / Fastplay; - } - - /// Returns the address of the module. - /// Returns -1 if unknown. - public int GetMusicAddress() => Music; - - /// Causes music to be relocated. - /// Use only with `ASAPWriter.Write`. - public void SetMusicAddress!( - /// New music address. - int address) - throws - { - if (address < 0 || address >= 0xffff) - throw "Invalid music address"; - Music = address; - } - - /// Returns the address of the player initialization routine. - /// Returns -1 if no initialization routine. - public int GetInitAddress() => Init; - - /// Returns the address of the player routine. - public int GetPlayerAddress() => Player; - - /// Returns the address of the COVOX chip. - /// Returns -1 if no COVOX enabled. - public int GetCovoxAddress() => CovoxAddr; - - /// Retturns the length of the SAP header in bytes. - public int GetSapHeaderLength() => HeaderLen; - - /// Returns the offset of instrument names for RMT module. - /// Returns -1 if not an RMT module or RMT module without instrument names. - public int GetInstrumentNamesOffset( - /// Content of the RMT file. - byte[] module, - /// Length of the RMT file. - int moduleLen) - { - if (Type != ASAPModuleType.Rmt) - return -1; - for (int offset = GetWord(module, 4) - GetWord(module, 2) + 12; offset < moduleLen; offset++) { - if (module[offset - 1] == 0) - return offset; - } - return -1; - } - - internal static int GetWord(byte[] array, int i) => array[i] + (array[i + 1] << 8); - - void ParseModule!(byte[] module, int moduleLen) - throws - { - if ((module[0] != 0xff || module[1] != 0xff) - && (module[0] != 0 || module[1] != 0)) // some CMC and clones start with zeros - throw "Invalid two leading bytes of the module"; - Music = GetWord(module, 2); - int musicLastByte = GetWord(module, 4); - if (Music <= 0xd7ff && musicLastByte >= 0xd000) - throw "Module address conflicts with hardware registers"; - int blockLen = musicLastByte + 1 - Music; - if (6 + blockLen != moduleLen) { - if (Type != ASAPModuleType.Rmt || 11 + blockLen > moduleLen) - throw "Module length doesn't match headers"; - // allow optional info for Raster Music Tracker - int infoAddr = GetWord(module, 6 + blockLen); - if (infoAddr != Music + blockLen) - throw "Invalid address of RMT info"; - int infoLen = GetWord(module, 8 + blockLen) + 1 - infoAddr; - if (10 + blockLen + infoLen != moduleLen) - throw "Invalid RMT info block"; - } - } - - void AddSong!(int playerCalls) - { - long scanlines = playerCalls * Fastplay; - Durations[Songs++] = scanlines * (114000 / 3) / (1773447 / 3); - } - - // TODO: enum + 0 - const int SeenThisCall = 1; - const int SeenBefore = 2; - const int SeenRepeat = 3; - - void ParseCmcSong!(byte[] module, int pos) - { - int tempo = module[0x19]; - int playerCalls = 0; - int repStartPos = 0; - int repEndPos = 0; - int repTimes = 0; - byte[0x55] seen = 0; - while (pos >= 0 && pos < 0x55) { - if (pos == repEndPos && repTimes > 0) { - for (int i = 0; i < 0x55; i++) - if (seen[i] == SeenThisCall || seen[i] == SeenRepeat) - seen[i] = 0; - repTimes--; - pos = repStartPos; - } - if (seen[pos] != 0) { - if (seen[pos] != SeenThisCall) - Loops[Songs] = true; - break; - } - seen[pos] = SeenThisCall; - int p1 = module[0x206 + pos]; - int p2 = module[0x25b + pos]; - int p3 = module[0x2b0 + pos]; - if (p1 == 0xfe || p2 == 0xfe || p3 == 0xfe) { - pos++; - continue; - } - p1 |= Type == ASAPModuleType.Cms ? 7 : 0xf; - switch (p1) { - case 0x87: // CMS VOLUME - case 0xa7: // CMS MODE - pos++; - break; - case 0x8f: // STOP - pos = -1; - break; - case 0x97: // CMS PAUSE - if (p2 < 128) { - playerCalls += p2; - if (p3 < 128) - playerCalls += p3 * 50; - } - pos++; - break; - case 0x9f: // JUMP - pos = p2; - break; - case 0xaf: // UP - pos -= p2; - break; - case 0xbf: // DOWN - pos += p2; - break; - case 0xcf: // TEMPO - if (p2 < 128) { - tempo = p2; - pos++; - } - else - pos = -1; - break; - case 0xdf: // REPLAY - pos++; - repStartPos = pos; - repEndPos = pos + p2; - repTimes = p3 - 1; - break; - case 0xef: // BREAK - Loops[Songs] = true; - pos = -1; - break; - default: - p2 = repTimes > 0 ? SeenRepeat : SeenBefore; - for (p1 = 0; p1 < 0x55; p1++) - if (seen[p1] == SeenThisCall) - seen[p1] = p2; - playerCalls += tempo * (Type == ASAPModuleType.Cm3 ? 48 : 64); - pos++; - break; - } - } - AddSong(playerCalls); - } - - const int CmrBassTableOffset = 0x70f; - - void ParseCmc!(byte[] module, int moduleLen, ASAPModuleType type) - throws - { - if (moduleLen < 0x306) - throw "Module too short"; - Type = type; - ParseModule(module, moduleLen); - int lastPos = 0x54; - while (--lastPos >= 0) { - if (module[0x206 + lastPos] < 0xb0 - || module[0x25b + lastPos] < 0x40 - || module[0x2b0 + lastPos] < 0x40) - break; - if (Channels == 2) { - if (module[0x306 + lastPos] < 0xb0 - || module[0x35b + lastPos] < 0x40 - || module[0x3b0 + lastPos] < 0x40) - break; - } - } - Songs = 0; - ParseCmcSong(module, 0); - for (int pos = 0; pos < lastPos && Songs < MaxSongs; pos++) - if (module[0x206 + pos] == 0x8f || module[0x206 + pos] == 0xef) - ParseCmcSong(module, pos + 1); - } - - static bool IsDltTrackEmpty(byte[] module, int pos) - { - return module[0x2006 + pos] >= 0x43 - && module[0x2106 + pos] >= 0x40 - && module[0x2206 + pos] >= 0x40 - && module[0x2306 + pos] >= 0x40; - } - - static bool IsDltPatternEnd(byte[] module, int pos, int i) - { - for (int ch = 0; ch < 4; ch++) { - int pattern = module[0x2006 + (ch << 8) + pos]; - if (pattern < 64) { - int offset = 6 + (pattern << 7) + (i << 1); - if ((module[offset] & 0x80) == 0 && (module[offset + 1] & 0x80) != 0) - return true; - } - } - return false; - } - - void ParseDltSong!(byte[] module, bool[]! seen, int pos) - { - while (pos < 128 && !seen[pos] && IsDltTrackEmpty(module, pos)) - seen[pos++] = true; - SongPos[Songs] = pos; - int playerCalls = 0; - bool loop = false; - int tempo = 6; - while (pos < 128) { - if (seen[pos]) { - loop = true; - break; - } - seen[pos] = true; - int p1 = module[0x2006 + pos]; - if (p1 == 0x40 || IsDltTrackEmpty(module, pos)) - break; - if (p1 == 0x41) - pos = module[0x2086 + pos]; - else if (p1 == 0x42) - tempo = module[0x2086 + pos++]; - else { - for (int i = 0; i < 64 && !IsDltPatternEnd(module, pos, i); i++) - playerCalls += tempo; - pos++; - } - } - if (playerCalls > 0) { - Loops[Songs] = loop; - AddSong(playerCalls); - } - } - - void ParseDlt!(byte[] module, int moduleLen) - throws + void ParseDlt!(byte[] module, int moduleLen) + throws { if (moduleLen != 0x2c06 && moduleLen != 0x2c07) throw "Invalid module length"; @@ -713,7 +440,9 @@ seen[i] = SeenBefore; for (int patternRows = module[0x1ce]; --patternRows >= 0; ) { for (ch = 3; ch >= 0; ch--) { - if (patternOffset[ch] == 0 || --blankRowsCounter[ch] >= 0) + if (patternOffset[ch] == 0) + continue; + if (--blankRowsCounter[ch] >= 0) continue; for (;;) { i = module[patternOffset[ch]++]; @@ -987,6 +716,7 @@ Player = 0x600; if (Songs == 0) throw "No songs found"; +#if !OPENCL byte[MaxTextLength] title; int titleLen; for (titleLen = 0; titleLen < MaxTextLength && 10 + blockLen + titleLen < moduleLen; titleLen++) { @@ -997,6 +727,7 @@ title[titleLen] = IsValidChar(c) ? c : ' '; } Title = Encoding.UTF8.GetString(title, 0, titleLen); +#endif } void ParseTmcSong!(byte[] module, int pos) @@ -1056,6 +787,7 @@ AddSong(frames); } +#if !OPENCL static int ParseTmcTitle(byte[]! title, int titleLen, byte[] module, int moduleOffset) { int lastOffset = moduleOffset + 29; @@ -1098,6 +830,7 @@ } return titleLen; } +#endif void ParseTmc!(byte[] module, int moduleLen) throws @@ -1132,9 +865,11 @@ if (i < 1 || i > 4) throw "Unsupported player call rate"; Fastplay = 312 / i; +#if !OPENCL byte[MaxTextLength] title; int titleLen = ParseTmcTitle(title, 0, module, 6); Title = Encoding.UTF8.GetString(title, 0, titleLen); +#endif } void ParseTm2Song!(byte[] module, int pos) @@ -1161,609 +896,920 @@ for (int ch = 7; ch >= 0; ch--) { if (--blankRows[ch] >= 0) continue; - for (;;) { - int i = module[patternOffset[ch]++]; - if (i == 0) { - patternOffset[ch]++; - break; - } - if (i < 0x40) { - if (module[patternOffset[ch]++] >= 0x80) - patternOffset[ch]++; - break; - } - if (i < 0x80) { - patternOffset[ch]++; - break; - } - if (i == 0x80) { - blankRows[ch] = module[patternOffset[ch]++]; - break; - } - if (i < 0xc0) - break; - if (i < 0xd0) { - tempo = i - 0xbf; - continue; - } - if (i < 0xe0) { - patternOffset[ch]++; - break; + for (;;) { + int i = module[patternOffset[ch]++]; + if (i == 0) { + patternOffset[ch]++; + break; + } + if (i < 0x40) { + if (module[patternOffset[ch]++] >= 0x80) + patternOffset[ch]++; + break; + } + if (i < 0x80) { + patternOffset[ch]++; + break; + } + if (i == 0x80) { + blankRows[ch] = module[patternOffset[ch]++]; + break; + } + if (i < 0xc0) + break; + if (i < 0xd0) { + tempo = i - 0xbf; + continue; + } + if (i < 0xe0) { + patternOffset[ch]++; + break; + } + if (i < 0xf0) { + patternOffset[ch] += 2; + break; + } + if (i < 0xff) { + blankRows[ch] = i - 0xf0; + break; + } + blankRows[ch] = 64; + break; + } + } + playerCalls += tempo; + } + pos += 17; + } + AddSong(playerCalls); + } + + void ParseTm2!(byte[] module, int moduleLen) + throws + { + if (moduleLen < 0x3a4) + throw "Module too short"; + Type = ASAPModuleType.Tm2; + ParseModule(module, moduleLen); + int i = module[0x25]; + if (i < 1 || i > 4) + throw "Unsupported player call rate"; + Fastplay = 312 / i; + Player = 0x800; + if (module[0x1f] != 0) + Channels = 2; + int lastPos = 0xffff; + for (i = 0; i < 0x80; i++) { + int instrAddr = module[0x86 + i] + (module[0x306 + i] << 8); + if (instrAddr != 0 && instrAddr < lastPos) + lastPos = instrAddr; + } + for (i = 0; i < 0x100; i++) { + int patternAddr = module[0x106 + i] + (module[0x206 + i] << 8); + if (patternAddr != 0 && patternAddr < lastPos) + lastPos = patternAddr; + } + lastPos -= GetWord(module, 2) + 0x380; + if (0x386 + lastPos >= moduleLen) + throw "Module too short"; + // skip trailing stop/jump commands + int c; + do { + if (lastPos <= 0) + throw "No songs found"; + lastPos -= 17; + c = module[0x386 + 16 + lastPos]; + } while (c == 0 || c >= 0x80); + Songs = 0; + ParseTm2Song(module, 0); + for (i = 0; i < lastPos && Songs < MaxSongs; i += 17) { + c = module[0x386 + 16 + i]; + if (c == 0 || c >= 0x80) + ParseTm2Song(module, i + 17); + } +#if !OPENCL + byte[MaxTextLength] title; + int titleLen = ParseTmcTitle(title, 0, module, 0x27); + titleLen = ParseTmcTitle(title, titleLen, module, 0x47); + titleLen = ParseTmcTitle(title, titleLen, module, 0x67); + Title = Encoding.UTF8.GetString(title, 0, titleLen); +#endif + } + + static int AfterFF(byte[] module, int moduleLen, int currentOffset) + throws + { + while (currentOffset < moduleLen) { + if (module[currentOffset++] == 0xff) + return currentOffset; + } + throw "Module too short"; + } + + static int GetFcTrackCommand(byte[] module, int[] trackPos, int n) + => module[3 + (n << 8) + trackPos[n]]; + + static bool IsFcSongEnd(byte[] module, int[] trackPos) + { + // stops when one channel (or more) has STOP command or each channel has LOOP command + bool allLoop = true; + for (int n = 0; n < 3; n++) { + if (trackPos[n] >= 0x100) + return true; + switch (GetFcTrackCommand(module, trackPos, n)) { + case 0xfe: // STOP + return true; + case 0xff: // LOOP + break; + default: + allLoop = false; + break; + } + } + return allLoop; + } + + static bool ValidateFc(byte[] module, int moduleLen) + { + if (moduleLen < 0x383) + return false; + if (module[0] != 0x26 || module[1] != 0x23) + return false; + return true; + } + + void ParseFc!(byte[] module, int moduleLen) + throws + { + if (!ValidateFc(module, moduleLen)) + throw "Invalid FC file"; + Type = ASAPModuleType.Fc; + + Player = 0x400; + Music = 0xa00; + Songs = 0; + HeaderLen = -1; + + int[0x40] patternOffsets; + int currentOffset = 0x383; + // patterns + for (int i = 0; i < 0x40; i++) { + patternOffsets[i] = currentOffset; + currentOffset = AfterFF(module, moduleLen, currentOffset); + } + // envelopes + for (int i = 0; i < 0x20; i++) + currentOffset = AfterFF(module, moduleLen, currentOffset); + + for (int pos = 0; pos < 0x100 && Songs < MaxSongs; ) { + int[3] trackPos; + for (int n = 0; n < 3; n++) + trackPos[n] = pos; + int[3] patternDelay = 0; + int[3] noteDuration = 0; + int[3] patternPos = 0; + int playerCalls = 0; + Loops[Songs] = true; + + while (!IsFcSongEnd(module, trackPos)) { + for (int n = 0; n < 3; n++) { + if (GetFcTrackCommand(module, trackPos, n) == 0xff) + continue; + if (patternDelay[n]-- > 0) + continue; + while (trackPos[n] < 0x100) { + int trackCmd = GetFcTrackCommand(module, trackPos, n); + if (trackCmd < 0x40) { + int patternCmd = module[patternOffsets[trackCmd] + patternPos[n]++]; + if (patternCmd < 0x40) { + patternDelay[n] = noteDuration[n]; + break; + } + else if (patternCmd < 0x60) + noteDuration[n] = patternCmd - 0x40; + else if (patternCmd == 0xff) { + patternDelay[n] = 0; + noteDuration[n] = 0; + patternPos[n] = 0; + trackPos[n]++; + } } - if (i < 0xf0) { - patternOffset[ch] += 2; + else if (trackCmd == 0x40) + trackPos[n] += 2; + else if (trackCmd == 0xfe) { + Loops[Songs] = false; break; } - if (i < 0xff) { - blankRows[ch] = i - 0xf0; + else if (trackCmd == 0xff) break; - } - blankRows[ch] = 64; - break; + else + trackPos[n]++; } } - playerCalls += tempo; + if (IsFcSongEnd(module, trackPos)) + break; + playerCalls += module[2]; } - pos += 17; + + pos = -1; + for (int n = 0; n < 3; n++) { + int nxtrkpos = trackPos[n]; + if (patternPos[n] > 0) + nxtrkpos++; + if (pos < nxtrkpos) + pos = nxtrkpos; + } + pos++; + if (pos <= 0x100) + AddSong(playerCalls); } - AddSong(playerCalls); } - void ParseTm2!(byte[] module, int moduleLen) +#if !OPENCL + static string() ParseText(byte[] module, int i, int argEnd) + { + if (i < 0 || argEnd - i < 2 || module[i] != '"' || module[argEnd - 1] != '"') + return ""; // silently ignore malformed AUTHOR/NAME/DATE + if (module[i + 1] == '<' && module[i + 2] == '?' && module[i + 3] == '>') + return ""; + return Encoding.UTF8.GetString(module, i + 1, argEnd - i - 2); + } +#endif + + static bool HasStringAt(byte[] module, int moduleIndex, string s) + { + int n = s.Length; + for (int i = 0; i < n; i++) + if (module[moduleIndex + i] != s[i]) + return false; + return true; + } + + static int ParseDec(byte[] module, int i, int argEnd, int minVal, int maxVal) throws { - if (moduleLen < 0x3a4) - throw "Module too short"; - Type = ASAPModuleType.Tm2; - ParseModule(module, moduleLen); - int i = module[0x25]; - if (i < 1 || i > 4) - throw "Unsupported player call rate"; - Fastplay = 312 / i; - Player = 0x800; - if (module[0x1f] != 0) - Channels = 2; - int lastPos = 0xffff; - for (i = 0; i < 0x80; i++) { - int instrAddr = module[0x86 + i] + (module[0x306 + i] << 8); - if (instrAddr != 0 && instrAddr < lastPos) - lastPos = instrAddr; + if (i < 0) + throw "Missing number"; + int r = 0; + while (i < argEnd) { + int c = module[i++]; + if (c < '0' || c > '9') + throw "Invalid number"; + r = r * 10 + c - '0'; + if (r > maxVal) + throw "Number too big"; } - for (i = 0; i < 0x100; i++) { - int patternAddr = module[0x106 + i] + (module[0x206 + i] << 8); - if (patternAddr != 0 && patternAddr < lastPos) - lastPos = patternAddr; + if (r < minVal) + throw "Number too small"; + return r; + } + + static int ParseHex(byte[] module, int i, int argEnd) + throws + { + if (i < 0) + throw "Missing number"; + int r = 0; + while (i < argEnd) { + int c = module[i++]; + if (r > 0xfff) + throw "Number too big"; + r <<= 4; + if (c >= '0' && c <= '9') + r += c - '0'; + else if (c >= 'A' && c <= 'F') + r += c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + r += c - 'a' + 10; + else + throw "Invalid number"; } - lastPos -= GetWord(module, 2) + 0x380; - if (0x386 + lastPos >= moduleLen) - throw "Module too short"; - // skip trailing stop/jump commands - int c; - do { - if (lastPos <= 0) - throw "No songs found"; - lastPos -= 17; - c = module[0x386 + 16 + lastPos]; - } while (c == 0 || c >= 0x80); - Songs = 0; - ParseTm2Song(module, 0); - for (i = 0; i < lastPos && Songs < MaxSongs; i += 17) { - c = module[0x386 + 16 + i]; - if (c == 0 || c >= 0x80) - ParseTm2Song(module, i + 17); + return r; + } + + /// Returns the number of milliseconds represented by the given string. + public static int ParseDuration( + /// Time in the `"mm:ss.xxx"` format. + string s) + throws + { + DurationParser() parser; + return parser.Parse(s); + } + + static bool ValidateSap(byte[] module, int moduleLen) + => moduleLen >= 30 && HasStringAt(module, 0, "SAP\r\n"); + + void ParseSap!(byte[] module, int moduleLen) + throws + { + if (!ValidateSap(module, moduleLen)) + throw "Invalid SAP file"; + Fastplay = -1; + int type = 0; + int moduleIndex = 5; + int durationIndex = 0; + while (module[moduleIndex] != 0xff) { + // find where the tag ends + int lineStart = moduleIndex; + while (module[moduleIndex] > ' ') { + if (++moduleIndex >= moduleLen) + throw "Invalid SAP file"; + } + int tagLen = moduleIndex - lineStart; + + // find where is the argument, if any + int argStart = -1; + int argEnd = -1; + for (;;) { + int c = module[moduleIndex]; + if (c > ' ') { + CheckValidChar(c); + if (argStart < 0) + argStart = moduleIndex; + argEnd = -1; + } + else { + if (argEnd < 0) + argEnd = moduleIndex; + if (c == '\n') + break; + } + if (++moduleIndex >= moduleLen) + throw "Invalid SAP file"; + } + if (++moduleIndex + 6 >= moduleLen) + throw "Invalid SAP file"; + + // handle tags, ignore unknown + switch (Encoding.UTF8.GetString(module, lineStart, tagLen)) { +#if !OPENCL + case "AUTHOR": + Author = ParseText(module, argStart, argEnd); + break; + case "NAME": + Title = ParseText(module, argStart, argEnd); + break; + case "DATE": + Date = ParseText(module, argStart, argEnd); + break; + case "TIME": + if (durationIndex >= MaxSongs) + throw "Too many TIME tags"; + if (argStart < 0) + throw "Missing TIME argument"; + if (argEnd - argStart > 5 && HasStringAt(module, argEnd - 5, " LOOP")) { + Loops[durationIndex] = true; + argEnd -= 5; + } + { + string() arg = Encoding.UTF8.GetString(module, argStart, argEnd - argStart); + Durations[durationIndex++] = ParseDuration(arg); + } + break; +#endif + case "SONGS": + Songs = ParseDec(module, argStart, argEnd, 1, MaxSongs); + break; + case "DEFSONG": + DefaultSong = ParseDec(module, argStart, argEnd, 0, MaxSongs - 1); + break; + case "TYPE": + if (argStart < 0) + throw "Missing TYPE argument"; + type = module[argStart]; + break; + case "FASTPLAY": + Fastplay = ParseDec(module, argStart, argEnd, 1, 32767); + break; + case "MUSIC": + Music = ParseHex(module, argStart, argEnd); + break; + case "INIT": + Init = ParseHex(module, argStart, argEnd); + break; + case "PLAYER": + Player = ParseHex(module, argStart, argEnd); + break; + case "COVOX": + CovoxAddr = ParseHex(module, argStart, argEnd); + if (CovoxAddr != 0xd600) + throw "COVOX should be D600"; + Channels = 2; + break; + case "STEREO": + Channels = 2; + break; + case "NTSC": + Ntsc = true; + break; + default: + break; + } + } + + if (DefaultSong >= Songs) + throw "DEFSONG too big"; + switch (type) { + case 'B': + if (Player < 0) + throw "Missing PLAYER tag"; + if (Init < 0) + throw "Missing INIT tag"; + Type = ASAPModuleType.SapB; + break; + case 'C': + if (Player < 0) + throw "Missing PLAYER tag"; + if (Music < 0) + throw "Missing MUSIC tag"; + Type = ASAPModuleType.SapC; + break; + case 'D': + if (Init < 0) + throw "Missing INIT tag"; + Type = ASAPModuleType.SapD; + break; + case 'S': + if (Init < 0) + throw "Missing INIT tag"; + Type = ASAPModuleType.SapS; + if (Fastplay < 0) + Fastplay = 78; + break; + default: + throw "Unsupported TYPE"; } - byte[MaxTextLength] title; - int titleLen = ParseTmcTitle(title, 0, module, 0x27); - titleLen = ParseTmcTitle(title, titleLen, module, 0x47); - titleLen = ParseTmcTitle(title, titleLen, module, 0x67); - Title = Encoding.UTF8.GetString(title, 0, titleLen); + if (Fastplay < 0) + Fastplay = Ntsc ? 262 : 312; + if (module[moduleIndex + 1] != 0xff) + throw "Invalid binary header"; + HeaderLen = moduleIndex; } - static int AfterFF(byte[] module, int moduleLen, int currentOffset) - throws + internal static int PackExt(string ext) + => ext.Length == 2 && ext[0] <= 'z' && ext[1] <= 'z' ? ext[0] | ext[1] << 8 | 0x202020 + : ext.Length == 3 && ext[0] <= 'z' && ext[1] <= 'z' && ext[2] <= 'z' ? ext[0] | ext[1] << 8 | ext[2] << 16 | 0x202020 + : 0; + + internal static int GetPackedExt(string filename) { - while (currentOffset < moduleLen) { - if (module[currentOffset++] == 0xff) - return currentOffset; + int ext = 0; + for (int i = filename.Length; --i > 0; ) { + int c = filename[i]; + if (c <= ' ' || c > 'z') + return 0; + if (c == '.') + return ext | 0x202020; + ext = (ext << 8) + c; } - throw "Module too short"; + return 0; } - static int GetFcTrackCommand(byte[] module, int[] trackPos, int n) - => module[3 + (n << 8) + trackPos[n]]; - - static bool IsFcSongEnd(byte[] module, int[] trackPos) + static bool IsOurPackedExt(int ext) { - // stops when one channel (or more) has STOP command or each channel has LOOP command - bool allLoop = true; - for (int n = 0; n < 3; n++) { - if (trackPos[n] >= 0x100) - return true; - switch (GetFcTrackCommand(module, trackPos, n)) { - case 0xfe: // STOP - return true; - case 0xff: // LOOP - break; - default: - allLoop = false; - break; - } + switch (ext) { + case PackExt("SAP"): +#if !ASAP_ONLY_SAP + case PackExt("CMC"): + case PackExt("CM3"): + case PackExt("CMR"): + case PackExt("CMS"): + case PackExt("DMC"): + case PackExt("DLT"): + case PackExt("MPT"): + case PackExt("MPD"): + case PackExt("RMT"): + case PackExt("TMC"): + case PackExt("TM8"): + case PackExt("TM2"): + case PackExt("FC"): +#if EXPERIMENTAL_XEX + case PackExt("XEX"): +#endif +#endif + return true; + default: + return false; } - return allLoop; } - static bool ValidateFc(byte[] module, int moduleLen) + /// Checks whether the filename represents a module type supported by ASAP. + /// Returns `true` if the filename is supported by ASAP. + public static bool IsOurFile( + /// Filename to check the extension of. + string filename) + => IsOurPackedExt(GetPackedExt(filename)); + + /// Checks whether the filename extension represents a module type supported by ASAP. + /// Returns `true` if the filename extension is supported by ASAP. + public static bool IsOurExt( + /// Filename extension without the leading dot. + string ext) + => IsOurPackedExt(PackExt(ext)); + + static int GuessPackedExt(byte[] module, int moduleLen) + throws { - if (moduleLen < 0x383) - return false; - if (module[0] != 0x26 || module[1] != 0x23) - return false; - return true; + if (ValidateSap(module, moduleLen)) + return PackExt("SAP"); + if (ValidateFc(module, moduleLen)) + return PackExt("FC"); + if (ValidateRmt(module, moduleLen)) + return PackExt("RMT"); + throw "Unknown format"; } - void ParseFc!(byte[] module, int moduleLen) + /// Loads file information. + public void Load!( + /// Filename, used to determine the format. + string filename, + /// Contents of the file. + byte[] module, + /// Length of the file. + int moduleLen) throws { - if (!ValidateFc(module, moduleLen)) - throw "Invalid FC file"; - Type = ASAPModuleType.Fc; - - Player = 0x400; - Music = 0xa00; - Songs = 0; - HeaderLen = -1; - - int[0x40] patternOffsets; - int currentOffset = 0x383; - // patterns - for (int i = 0; i < 0x40; i++) { - patternOffsets[i] = currentOffset; - currentOffset = AfterFF(module, moduleLen, currentOffset); - } - // envelopes - for (int i = 0; i < 0x20; i++) - currentOffset = AfterFF(module, moduleLen, currentOffset); - - for (int pos = 0; pos < 0x100 && Songs < MaxSongs; ) { - int[3] trackPos; - for (int n = 0; n < 3; n++) - trackPos[n] = pos; - int[3] patternDelay = 0; - int[3] noteDuration = 0; - int[3] patternPos = 0; - int playerCalls = 0; - Loops[Songs] = true; - - while (!IsFcSongEnd(module, trackPos)) { - for (int n = 0; n < 3; n++) { - int trackCmd = GetFcTrackCommand(module, trackPos, n); - if (trackCmd != 0xff && patternDelay[n]-- <= 0) { //"<" for junks in track - while (trackPos[n] < 0x100) { - trackCmd = GetFcTrackCommand(module, trackPos, n); - if (trackCmd < 0x40) { - int patternCmd = module[patternOffsets[trackCmd] + patternPos[n]++]; - if (patternCmd < 0x40) { - patternDelay[n] = noteDuration[n]; - break; - } - else if (patternCmd < 0x60) - noteDuration[n] = patternCmd - 0x40; - else if (patternCmd == 0xff) { - patternDelay[n] = 0; - noteDuration[n] = 0; - patternPos[n] = 0; - trackPos[n]++; - } - } - else if (trackCmd == 0x40) - trackPos[n] += 2; - else if (trackCmd == 0xfe) { - Loops[Songs] = false; - break; - } - else if (trackCmd == 0xff) - break; - else - trackPos[n]++; - } - } - } - if (IsFcSongEnd(module, trackPos)) + int ext; + if (filename != null) { + int len = filename.Length; + int basename = 0; + ext = -1; + for (int i = len; --i >= 0; ) { + int c = filename[i]; + if (c == '/' || c == '\\') { + basename = i + 1; break; - playerCalls += module[2]; + } + if (c == '.') + ext = i; } + if (ext < 0) + throw "Filename has no extension"; + ext -= basename; + if (ext > MaxTextLength) + ext = MaxTextLength; +#if !OPENCL + Filename = filename.Substring(basename, ext); +#endif + ext = GetPackedExt(filename); + } + else { +#if !OPENCL + Filename = ""; +#endif + ext = GuessPackedExt(module, moduleLen); + } - pos = -1; - for (int n = 0; n < 3; n++) { - int nxtrkpos = trackPos[n]; - if (patternPos[n] > 0) - nxtrkpos++; - if (pos < nxtrkpos) - pos = nxtrkpos; - } - pos++; - if (pos <= 0x100) - AddSong(playerCalls); +#if !OPENCL + Author = ""; + Title = ""; + Date = ""; +#endif + Channels = 1; + Songs = 1; + DefaultSong = 0; + for (int i = 0; i < MaxSongs; i++) { + Durations[i] = -1; + Loops[i] = false; + } + Ntsc = false; + Fastplay = 312; + Music = -1; + Init = -1; + Player = -1; + CovoxAddr = -1; + HeaderLen = 0; + switch (ext) { + case PackExt("SAP"): + ParseSap(module, moduleLen); + return; +#if !ASAP_ONLY_SAP + case PackExt("CMC"): + ParseCmc(module, moduleLen, ASAPModuleType.Cmc); + return; + case PackExt("CM3"): + ParseCmc(module, moduleLen, ASAPModuleType.Cm3); + return; + case PackExt("CMR"): + ParseCmc(module, moduleLen, ASAPModuleType.Cmr); + return; + case PackExt("CMS"): + Channels = 2; + ParseCmc(module, moduleLen, ASAPModuleType.Cms); + return; + case PackExt("DMC"): + Fastplay = 156; + ParseCmc(module, moduleLen, ASAPModuleType.Cmc); + return; + case PackExt("DLT"): + ParseDlt(module, moduleLen); + return; + case PackExt("MPT"): + ParseMpt(module, moduleLen); + return; + case PackExt("MPD"): + Fastplay = 156; + ParseMpt(module, moduleLen); + return; + case PackExt("RMT"): + ParseRmt(module, moduleLen); + return; + case PackExt("TMC"): + case PackExt("TM8"): + ParseTmc(module, moduleLen); + return; + case PackExt("TM2"): + ParseTm2(module, moduleLen); + return; + case PackExt("FC"): + ParseFc(module, moduleLen); + return; +#if EXPERIMENTAL_XEX + case PackExt("XEX"): + if (moduleLen < 7 || module[0] != 0xff || module[1] != 0xff) + throw "Missing 0xff,0xff header"; + Type = ASAPModuleType.Xex; + return; +#endif +#endif + default: + throw "Unknown filename extension"; } } - static bool HasStringAt(byte[] module, int moduleIndex, string s) +#if !OPENCL + + static void CheckValidText(string s) + throws { int n = s.Length; + if (n > MaxTextLength) + throw "Text too long"; for (int i = 0; i < n; i++) - if (module[moduleIndex + i] != s[i]) - return false; - return true; + CheckValidChar(s[i]); } - static int ParseDec(string s, int minVal, int maxVal) + /// Returns author's name. + /// A nickname may be included in parentheses after the real name. + /// Multiple authors are separated with `" & "`. + /// An empty string means the author is unknown. + public string GetAuthor() => Author; + + /// Sets author's name. + /// A nickname may be included in parentheses after the real name. + /// Multiple authors are separated with `" & "`. + /// An empty string means the author is unknown. + public void SetAuthor!( + /// New author's name for the current music. + string value) throws { - int r = 0; - int len = s.Length; - for (int i = 0; i < len; i++) { - int c = s[i]; - if (c < '0' || c > '9') - throw "Invalid number"; - r = r * 10 + c - '0'; - if (r > maxVal) - throw "Number too big"; - } - if (r < minVal) - throw "Number too small"; - return r; + CheckValidText(value); + Author = value; } - static int ParseHex(string s) + /// Returns music title. + /// An empty string means the title is unknown. + public string GetTitle() => Title; + + /// Sets music title. + /// An empty string means the title is unknown. + public void SetTitle!( + /// New title for the current music. + string value) throws { - int r = 0; - int len = s.Length; - for (int i = 0; i < len; i++) { - if (r > 0xfff) - throw "Number too big"; - int c = s[i]; - r <<= 4; - if (c >= '0' && c <= '9') - r += c - '0'; - else if (c >= 'A' && c <= 'F') - r += c - 'A' + 10; - else if (c >= 'a' && c <= 'f') - r += c - 'a' + 10; - else - throw "Invalid number"; - } - return r; + CheckValidText(value); + Title = value; } - /// Returns the number of milliseconds represented by the given string. - public static int ParseDuration( - /// Time in the `"mm:ss.xxx"` format. - string s) + /// Returns music title or filename. + /// If title is unknown returns filename without the path or extension. + public string GetTitleOrFilename() => Title.Length > 0 ? Title : Filename; + + /// Returns music creation date. + /// Some of the possible formats are: + /// * YYYY + /// * MM/YYYY + /// * DD/MM/YYYY + /// * YYYY-YYYY + /// + /// An empty string means the date is unknown. + public string GetDate() => Date; + + /// Sets music creation date. + /// Some of the possible formats are: + /// * YYYY + /// * MM/YYYY + /// * DD/MM/YYYY + /// * YYYY-YYYY + /// + /// An empty string means the date is unknown. + public void SetDate!( + /// New music creation date. + string value) throws { - DurationParser() parser; - return parser.Parse(s); + CheckValidText(value); + Date = value; } - static bool ValidateSap(byte[] module, int moduleLen) - => moduleLen >= 30 && HasStringAt(module, 0, "SAP\r\n"); - - void ParseSap!(byte[] module, int moduleLen) - throws + int CheckDate() { - if (!ValidateSap(module, moduleLen)) - throw "Invalid SAP file"; - Fastplay = -1; - int type = 0; - int moduleIndex = 5; - int durationIndex = 0; - while (module[moduleIndex] != 0xff) { - // find where the tag ends - int lineStart = moduleIndex; - while (module[moduleIndex] > ' ') { - if (++moduleIndex >= moduleLen) - throw "Invalid SAP file"; + int n = Date.Length; + switch (n) { + case 4: // YYYY + case 7: // MM/YYYY + case 10: // DD/MM/YYYY + break; + default: + return -1; + } + for (int i = 0; i < n; i++) { + int c = Date[i]; + if (i == n - 5 || i == n - 8) { + if (c != '/') + return -1; } - int tagLen = moduleIndex - lineStart; + else if (c < '0' || c > '9') + return -1; + } + return n; + } - // find where is the argument, if any - int argStart = -1; - int argLen = -1; - for (;;) { - int c = module[moduleIndex]; - if (c > ' ') { - CheckValidChar(c); - if (argStart < 0) - argStart = moduleIndex; - argLen = -1; - } - else { - if (argLen < 0) - argLen = moduleIndex - argStart; - if (c == '\n') - break; - } - if (++moduleIndex >= moduleLen) - throw "Invalid SAP file"; - } - if (++moduleIndex + 6 >= moduleLen) - throw "Invalid SAP file"; + int GetTwoDateDigits(int i) => (Date[i] - '0') * 10 + Date[i + 1] - '0'; - // handle tags, ignore unknown - const int maxTagLen = "FASTPLAY".Length; - if (tagLen <= maxTagLen) { - string() tag = Encoding.UTF8.GetString(module, lineStart, tagLen); - if (argStart >= 0 && argLen <= MaxTextLength + 2) { - string() arg = Encoding.UTF8.GetString(module, argStart, argLen); - if (argLen >= 3 && arg[0] == '"' && arg[argLen - 1] == '"' - && arg != "\"\"") { - if (tag == "AUTHOR") - Author = arg.Substring(1, argLen - 2); - else if (tag == "NAME") - Title = arg.Substring(1, argLen - 2); - else if (tag == "DATE") - Date = arg.Substring(1, argLen - 2); - } - else if (tag == "SONGS") - Songs = ParseDec(arg, 1, MaxSongs); - else if (tag == "DEFSONG") - DefaultSong = ParseDec(arg, 0, MaxSongs - 1); - else if (tag == "TIME") { - if (durationIndex >= MaxSongs) - throw "Too many TIME tags"; - if (argLen > 5 && HasStringAt(module, argStart + argLen - 5, " LOOP")) { - Loops[durationIndex] = true; - arg = arg.Substring(0, argLen - 5); - } - Durations[durationIndex++] = ParseDuration(arg); - } - else if (tag == "TYPE") - type = arg[0]; - else if (tag == "FASTPLAY") - Fastplay = ParseDec(arg, 1, 32767); - else if (tag == "MUSIC") - Music = ParseHex(arg); - else if (tag == "INIT") - Init = ParseHex(arg); - else if (tag == "PLAYER") - Player = ParseHex(arg); - else if (tag == "COVOX") { - CovoxAddr = ParseHex(arg); - if (CovoxAddr != 0xd600) - throw "COVOX should be D600"; - Channels = 2; - } - } - else if (tag == "STEREO") - Channels = 2; - else if (tag == "NTSC") - Ntsc = true; - } - } + /// Returns music creation year. + /// -1 means the year is unknown. + public int GetYear() + { + int n = CheckDate(); + if (n < 0) + return -1; + return GetTwoDateDigits(n - 4) * 100 + GetTwoDateDigits(n - 2); + } - if (DefaultSong >= Songs) - throw "DEFSONG too big"; - switch (type) { - case 'B': - if (Player < 0) - throw "Missing PLAYER tag"; - if (Init < 0) - throw "Missing INIT tag"; - Type = ASAPModuleType.SapB; - break; - case 'C': - if (Player < 0) - throw "Missing PLAYER tag"; - if (Music < 0) - throw "Missing MUSIC tag"; - Type = ASAPModuleType.SapC; - break; - case 'D': - if (Init < 0) - throw "Missing INIT tag"; - Type = ASAPModuleType.SapD; - break; - case 'S': - if (Init < 0) - throw "Missing INIT tag"; - Type = ASAPModuleType.SapS; - if (Fastplay < 0) - Fastplay = 78; - break; - default: - throw "Unsupported TYPE"; - } - if (Fastplay < 0) - Fastplay = Ntsc ? 262 : 312; - if (module[moduleIndex + 1] != 0xff) - throw "Invalid binary header"; - HeaderLen = moduleIndex; + /// Returns music creation month (1-12). + /// -1 means the month is unknown. + public int GetMonth() + { + int n = CheckDate(); + if (n < 7) + return -1; + return GetTwoDateDigits(n - 7); + } + + /// Returns day of month of the music creation date. + /// -1 means the day is unknown. + public int GetDayOfMonth() + { + int n = CheckDate(); + if (n != 10) + return -1; + return GetTwoDateDigits(0); + } + +#endif + + /// Returns 1 for mono or 2 for stereo. + public int GetChannels() => Channels; + + /// Returns number of songs in the file. + public int GetSongs() => Songs; + + /// Returns 0-based index of the "main" song. + /// The specified song should be played by default. + public int GetDefaultSong() => DefaultSong; + + /// Sets the 0-based index of the "main" song. + public void SetDefaultSong!( + /// New default song. + int song) + throws + { + if (song < 0 || song >= Songs) + throw "Song out of range"; + DefaultSong = song; + } + + /// Returns length of the specified song. + /// The length is specified in milliseconds. -1 means the length is indeterminate. + public int GetDuration( + /// Song to get length of, 0-based. + int song) + => Durations[song]; + + /// Sets length of the specified song. + /// The length is specified in milliseconds. -1 means the length is indeterminate. + public void SetDuration!( + /// Song to set length of, 0-based. + int song, + /// New length in milliseconds. + int duration) + throws + { + if (song < 0 || song >= Songs) + throw "Song out of range"; + Durations[song] = duration; } - internal static int PackExt(string ext) - => ext.Length == 2 && ext[0] <= 'z' && ext[1] <= 'z' ? ext[0] | ext[1] << 8 | 0x202020 - : ext.Length == 3 && ext[0] <= 'z' && ext[1] <= 'z' && ext[2] <= 'z' ? ext[0] | ext[1] << 8 | ext[2] << 16 | 0x202020 - : 0; + /// Returns information whether the specified song loops. + /// Returns: + /// * `true` if the song loops + /// * `false` if the song stops + public bool GetLoop( + /// Song to check for looping, 0-based. + int song) + => Loops[song]; - internal static int GetPackedExt(string filename) + /// Sets information whether the specified song loops. + /// Use: + /// * `true` if the song loops + /// * `false` if the song stops + public void SetLoop!( + /// Song to set as looping, 0-based. + int song, + /// `true` if the song loops. + bool loop) + throws { - int ext = 0; - for (int i = filename.Length; --i > 0; ) { - int c = filename[i]; - if (c <= ' ' || c > 'z') - return 0; - if (c == '.') - return ext | 0x202020; - ext = (ext << 8) + c; - } - return 0; + if (song < 0 || song >= Songs) + throw "Song out of range"; + Loops[song] = loop; } - static bool IsOurPackedExt(int ext) + /// Returns `true` for NTSC song and `false` for PAL song. + public bool IsNtsc() => Ntsc; + + /// Returns the letter argument for the TYPE SAP tag. + /// Returns zero for non-SAP files. + public int GetTypeLetter() { - switch (ext) { - case PackExt("SAP"): -#if !ASAP_ONLY_SAP - case PackExt("CMC"): - case PackExt("CM3"): - case PackExt("CMR"): - case PackExt("CMS"): - case PackExt("DMC"): - case PackExt("DLT"): - case PackExt("MPT"): - case PackExt("MPD"): - case PackExt("RMT"): - case PackExt("TMC"): - case PackExt("TM8"): - case PackExt("TM2"): - case PackExt("FC"): -#if EXPERIMENTAL_XEX - case PackExt("XEX"): -#endif -#endif - return true; - default: - return false; + switch (Type) { + case ASAPModuleType.SapB: return 'B'; + case ASAPModuleType.SapC: return 'C'; + case ASAPModuleType.SapD: return 'D'; + case ASAPModuleType.SapS: return 'S'; + default: return 0; } } - /// Checks whether the filename represents a module type supported by ASAP. - /// Returns `true` if the filename is supported by ASAP. - public static bool IsOurFile( - /// Filename to check the extension of. - string filename) - => IsOurPackedExt(GetPackedExt(filename)); + /// Returns player routine rate in Atari scanlines. + public int GetPlayerRateScanlines() => Fastplay; - /// Checks whether the filename extension represents a module type supported by ASAP. - /// Returns `true` if the filename extension is supported by ASAP. - public static bool IsOurExt( - /// Filename extension without the leading dot. - string ext) - => IsOurPackedExt(PackExt(ext)); + /// Returns approximate player routine rate in Hz. + public int GetPlayerRateHz() + { + int scanlineClock = Ntsc ? 1789772 / 114 : 1773447 / 114; + return (scanlineClock + (Fastplay >> 1)) / Fastplay; + } - static int GuessPackedExt(byte[] module, int moduleLen) + /// Returns the address of the module. + /// Returns -1 if unknown. + public int GetMusicAddress() => Music; + + /// Causes music to be relocated. + /// Use only with `ASAPWriter.Write`. + public void SetMusicAddress!( + /// New music address. + int address) throws { - if (ValidateSap(module, moduleLen)) - return PackExt("SAP"); - if (ValidateFc(module, moduleLen)) - return PackExt("FC"); - if (ValidateRmt(module, moduleLen)) - return PackExt("RMT"); - throw "Unknown format"; + if (address < 0 || address >= 0xffff) + throw "Invalid music address"; + Music = address; } - /// Loads file information. - public void Load!( - /// Filename, used to determine the format. - string filename, - /// Contents of the file. + /// Returns the address of the player initialization routine. + /// Returns -1 if no initialization routine. + public int GetInitAddress() => Init; + + /// Returns the address of the player routine. + public int GetPlayerAddress() => Player; + + /// Returns the address of the COVOX chip. + /// Returns -1 if no COVOX enabled. + public int GetCovoxAddress() => CovoxAddr; + + /// Returns the length of the SAP header in bytes. + public int GetSapHeaderLength() => HeaderLen; + + /// Returns the offset of instrument names for RMT module. + /// Returns -1 if not an RMT module or RMT module without instrument names. + public int GetInstrumentNamesOffset( + /// Content of the RMT file. byte[] module, - /// Length of the file. + /// Length of the RMT file. int moduleLen) - throws { - int ext; - if (filename != null) { - int len = filename.Length; - int basename = 0; - ext = -1; - for (int i = len; --i >= 0; ) { - int c = filename[i]; - if (c == '/' || c == '\\') { - basename = i + 1; - break; - } - if (c == '.') - ext = i; - } - if (ext < 0) - throw "Filename has no extension"; - ext -= basename; - if (ext > MaxTextLength) - ext = MaxTextLength; - Filename = filename.Substring(basename, ext); - ext = GetPackedExt(filename); - } - else { - Filename = ""; - ext = GuessPackedExt(module, moduleLen); - } - - Author = ""; - Title = ""; - Date = ""; - Channels = 1; - Songs = 1; - DefaultSong = 0; - for (int i = 0; i < MaxSongs; i++) { - Durations[i] = -1; - Loops[i] = false; - } - Ntsc = false; - Fastplay = 312; - Music = -1; - Init = -1; - Player = -1; - CovoxAddr = -1; - HeaderLen = 0; - switch (ext) { - case PackExt("SAP"): - ParseSap(module, moduleLen); - return; -#if !ASAP_ONLY_SAP - case PackExt("CMC"): - ParseCmc(module, moduleLen, ASAPModuleType.Cmc); - return; - case PackExt("CM3"): - ParseCmc(module, moduleLen, ASAPModuleType.Cm3); - return; - case PackExt("CMR"): - ParseCmc(module, moduleLen, ASAPModuleType.Cmr); - return; - case PackExt("CMS"): - Channels = 2; - ParseCmc(module, moduleLen, ASAPModuleType.Cms); - return; - case PackExt("DMC"): - Fastplay = 156; - ParseCmc(module, moduleLen, ASAPModuleType.Cmc); - return; - case PackExt("DLT"): - ParseDlt(module, moduleLen); - return; - case PackExt("MPT"): - ParseMpt(module, moduleLen); - return; - case PackExt("MPD"): - Fastplay = 156; - ParseMpt(module, moduleLen); - return; - case PackExt("RMT"): - ParseRmt(module, moduleLen); - return; - case PackExt("TMC"): - case PackExt("TM8"): - ParseTmc(module, moduleLen); - return; - case PackExt("TM2"): - ParseTm2(module, moduleLen); - return; - case PackExt("FC"): - ParseFc(module, moduleLen); - return; -#if EXPERIMENTAL_XEX - case PackExt("XEX"): - if (moduleLen < 7 || module[0] != 0xff || module[1] != 0xff) - throw "Missing 0xff,0xff header"; - Type = ASAPModuleType.Xex; - return; -#endif -#endif - default: - throw "Unknown filename extension"; + if (Type != ASAPModuleType.Rmt) + return -1; + for (int offset = GetWord(module, 4) - GetWord(module, 2) + 12; offset < moduleLen; offset++) { + if (module[offset - 1] == 0) + return offset; } + return -1; } #if !ASAP_ONLY_SAP @@ -1780,7 +1826,7 @@ case PackExt("CM3"): return "CMC \"3/4\""; case PackExt("CMR"): return "CMC \"Rzog\""; case PackExt("CMS"): return "Stereo Double CMC"; - case PackExt("DMC"): return "DoublePlay CMC"; + case PackExt("DMC"): return "CMC DoublePlay"; case PackExt("DLT"): return "Delta Music Composer"; case PackExt("MPT"): return "Music ProTracker"; case PackExt("MPD"): return "MPT DoublePlay"; diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/asap.spec kodi-audiodecoder-asap-20.3.0/lib/asap-code/asap.spec --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/asap.spec 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/asap.spec 2013-05-31 22:59:22.000000000 +0000 @@ -1,5 +1,5 @@ Name: asap -Version: 5.0.1 +Version: 5.2.0 Release: 1 Summary: Player of Atari 8-bit music License: GPLv2+ @@ -22,17 +22,6 @@ %description devel These are the files needed for compiling programs that use libasap. -%package xmms -Summary: ASAP plugin for XMMS -Group: Applications/Multimedia -Requires: xmms -BuildRequires: xmms-devel - -%description xmms -Provides playback of Atari 8-bit music in XMMS. -Supports the following file formats: -SAP, CMC, CM3, CMR, CMS, DMC, DLT, MPT, MPD, RMT, TMC, TM8, TM2, FC. - %package vlc Summary: ASAP plugin for VLC Group: Applications/Multimedia @@ -43,17 +32,26 @@ Provides playback of Atari 8-bit music in VLC. Supports the following file formats: SAP, RMT, FC. +%package xmms2 +Summary: ASAP plugin for XMMS2 +Group: Applications/Multimedia +Requires: xmms2 +BuildRequires: xmms2-devel + +%description xmms2 +Provides playback of Atari 8-bit music (SAP format) in XMMS2. + %global debug_package %{nil} %prep %setup -q %build -make asapconv libasap.a asap-xmms asap-vlc +make asapconv libasap.a asap-vlc asap-xmms2 %install rm -rf $RPM_BUILD_ROOT -make DESTDIR=$RPM_BUILD_ROOT prefix=%{_prefix} install install-xmms install-vlc +make DESTDIR=$RPM_BUILD_ROOT prefix=%{_prefix} libdir=%{_libdir} install install-vlc install-xmms2 %clean rm -rf $RPM_BUILD_ROOT @@ -67,15 +65,25 @@ %{_includedir}/asap.h %{_libdir}/libasap.a -%files xmms -%defattr(-,root,root) -%{_libdir}/xmms/Input/libasap-xmms.so - %files vlc %defattr(-,root,root) %{_libdir}/vlc/plugins/demux/libasap_plugin.so +%files xmms2 +%defattr(-,root,root) +%{_libdir}/xmms2/libxmms_asap.so + %changelog +* Wed Dec 8 2021 Piotr Fusik +- 5.2.0-1 + +* Tue Nov 30 2021 Piotr Fusik +- Added the XMMS2 subpackage +- Removed the XMMS subpackage + +* Fri Jul 9 2021 Piotr Fusik +- 5.1.0-1 + * Sun Jan 19 2020 Piotr Fusik - 5.0.1-1 diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/asapwriter.ci kodi-audiodecoder-asap-20.3.0/lib/asap-code/asapwriter.ci --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/asapwriter.ci 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/asapwriter.ci 2013-05-31 22:59:22.000000000 +0000 @@ -1,6 +1,6 @@ // asapwriter.ci - format conversions // -// Copyright (C) 2011-2019 Piotr Fusik +// Copyright (C) 2011-2020 Piotr Fusik // // This file is part of ASAP (Another Slight Atari Player), // see http://asap.sourceforge.net @@ -540,10 +540,11 @@ WriteBytes(playerRoutine, 2, playerLastByte - player + 7); break; case ASAPModuleType.Tmc: + int perFrame = module[0x25]; const int[4] tmcPlayerOffset = { 3, -9, -10, -10 }; - int player2 = player + tmcPlayerOffset[module[0x25] - 1]; + int player2 = player + tmcPlayerOffset[perFrame - 1]; const int[4] tmcInitOffset = { -14, -16, -17, -17 }; - startAddr = player2 + tmcInitOffset[module[0x25] - 1]; + startAddr = player2 + tmcInitOffset[perFrame - 1]; if (info.GetSongs() != 1) startAddr -= 3; WriteExecutableHeader(initAndPlayer, info, 'B', startAddr, player2); @@ -561,7 +562,7 @@ else { WriteByte(0xa9); WriteByte(0x60); // LDA #$60 } - switch (module[0x25]) { + switch (perFrame) { case 2: WriteByte(0x06); WriteByte(0); // ASL 0 WriteByte(0x4c); WriteWord(player); // JMP PLAYER @@ -578,10 +579,12 @@ WriteByte(0xd0); WriteByte(10); // BNE PLAYER WriteByte(0xc6); WriteByte(0); // DEC 0 WriteByte(0xd0); WriteByte(12); // BNE PLAYER+6 - WriteByte(0xa0); WriteByte(module[0x25]); // LDY #3 + WriteByte(0xa0); WriteByte(perFrame); // LDY #3 WriteByte(0x84); WriteByte(0); // STY 0 WriteByte(0xd0); WriteByte(3); // BNE PLAYER+3 break; + default: + break; } WriteBytes(playerRoutine, 6, playerLastByte - player + 7); break; diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/chksap.pl kodi-audiodecoder-asap-20.3.0/lib/asap-code/chksap.pl --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/chksap.pl 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/chksap.pl 2013-05-31 22:59:22.000000000 +0000 @@ -296,7 +296,7 @@ use Pod::Usage; use strict; -my $VERSION = '5.0.1'; +my $VERSION = '5.2.0'; my $asapscan = File::Spec->rel2abs('asapscan'); my ($check, $fix, $stat) = (0, 0, 0); my ($progress, $time, $overwrite_time, $features, $help, $version) = (0, 0, 0, 0, 0, 0); @@ -480,14 +480,14 @@ } $tags{$tag} = $arg; } - $fatal{'invalid argument of DEFSONG'} = 1 - if exists($tags{'SONGS'}) && $tags{'DEFSONG'} >= $tags{'SONGS'}; - if (@times > ($tags{'SONGS'} || 1)) { + my $songs = $tags{'SONGS'} || 1; + $fatal{'invalid argument of DEFSONG'} = 1 if $tags{'DEFSONG'} >= $songs; + if (@times > $songs) { splice @times, $tags{'SONGS'} || 1; $fixed{'more TIME tags than songs'} = 1; } - elsif (@times < ($tags{'SONGS'} || 1)) { - $fatal{'missing TIME tags'} = 1; + elsif (@times < $songs && !$fix) { + $fixed{'missing TIME tags'} = 1; } if (exists($tags{'TYPE'})) { my $type = $tags{'TYPE'}; @@ -534,21 +534,22 @@ $fixed{'FFFF inside binary part'} = 1; } } - if (%fixed || ($fix && $time && !@times) || $overwrite_time) { + my $fix_time = $overwrite_time || ($time && @times < $songs); + if (%fixed || ($fix && $fix_time)) { if (%fatal) { push @notfixed_messages, - "$fullpath (" . join('; ', sort(keys(%fixed))) . ")\n"; + "$fullpath (" . join('; ', sort(keys(%fatal))) . ")\n"; } else { if ($fix) { - if (($time && !@times) || $overwrite_time) { + if ($fix_time) { my $times = `$asapscan -t $filename`; if (!$times) { $fatal{'error running asapscan'} = 1; } elsif ($times =~ /^(?:TIME \d?\d:\d\d(?:\.\d\d\d?)?(?: LOOP)?\r?\n)+$/s) { my @new_times = $times =~ /\d?\d:\d\d(?:\.\d\d\d?)?(?: LOOP)?/gs; - if (!@times) { + if (@times < @new_times) { @times = @new_times; $fixed{'added TIME tags'} = 1; } diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/cpu6502.ci kodi-audiodecoder-asap-20.3.0/lib/asap-code/cpu6502.ci --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/cpu6502.ci 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/cpu6502.ci 2013-05-31 22:59:22.000000000 +0000 @@ -779,8 +779,8 @@ case 0xf8: // SED Vdi |= DFlag; continue; - default: // we handled all the 256 cases, yet gcc 4.8.3 still complains about the uninitialized `addr` - continue; + default: + assert false; } // Second dispatch on opcode. @@ -1043,6 +1043,8 @@ case 0xfe: // INC abcd,x Nz = Increment(addr); break; + default: + assert false; } } } diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/debian/asap-xmms2.install kodi-audiodecoder-asap-20.3.0/lib/asap-code/debian/asap-xmms2.install --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/debian/asap-xmms2.install 1970-01-01 00:00:00.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/debian/asap-xmms2.install 2013-05-31 22:59:22.000000000 +0000 @@ -0,0 +1 @@ +usr/lib/xmms2/libxmms_asap.so diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/debian/changelog kodi-audiodecoder-asap-20.3.0/lib/asap-code/debian/changelog --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/debian/changelog 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/debian/changelog 2013-05-31 22:59:22.000000000 +0000 @@ -1,3 +1,16 @@ +asap (5.2.0-1) UNRELEASED; urgency=low + + * New release. + * Added XMMS2. + + -- Piotr Fusik Wed, 8 Dec 2021 10:08:56 +0100 + +asap (5.1.0-1) UNRELEASED; urgency=low + + * New release. + + -- Piotr Fusik Fri, 9 Jul 2021 21:01:11 +0200 + asap (5.0.1-1) UNRELEASED; urgency=low * New release. diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/debian/control kodi-audiodecoder-asap-20.3.0/lib/asap-code/debian/control --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/debian/control 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/debian/control 2013-05-31 22:59:22.000000000 +0000 @@ -3,7 +3,7 @@ Section: contrib/sound Priority: optional Standards-Version: 3.9.3 -Build-Depends: debhelper (>= 7), libvlccore-dev +Build-Depends: debhelper (>= 7), libvlccore-dev, xmms2-dev Package: asap Architecture: any @@ -21,3 +21,8 @@ Depends: vlc, ${shlibs:Depends} Description: Provides playback of Atari 8-bit music in VLC. Supports the following file formats: SAP, RMT, FC. + +Package: asap-xmms2 +Architecture: any +Depends: xmms2, ${shlibs:Depends} +Description: Provides playback of Atari 8-bit music (SAP format) in XMMS2. diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/debian/copyright kodi-audiodecoder-asap-20.3.0/lib/asap-code/debian/copyright --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/debian/copyright 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/debian/copyright 2013-05-31 22:59:22.000000000 +0000 @@ -3,5 +3,5 @@ Source: http://asap.sourceforge.net Files: * -Copyright: 2005-2020 Piotr Fusik +Copyright: 2005-2021 Piotr Fusik License: GPL-2+ diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/debian/rules kodi-audiodecoder-asap-20.3.0/lib/asap-code/debian/rules --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/debian/rules 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/debian/rules 2013-05-31 22:59:22.000000000 +0000 @@ -6,10 +6,10 @@ override_dh_auto_clean: override_dh_auto_build: - $(MAKE) asapconv lib asap-vlc + $(MAKE) asapconv lib asap-vlc asap-xmms2 # skip "make check" to avoid dependency on Acid800 override_dh_auto_test: override_dh_auto_install: - $(MAKE) DESTDIR=$$(pwd)/debian/tmp prefix=/usr install install-vlc + $(MAKE) DESTDIR=$$(pwd)/debian/tmp prefix=/usr install install-vlc install-xmms2 diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/flashpack.ci kodi-audiodecoder-asap-20.3.0/lib/asap-code/flashpack.ci --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/flashpack.ci 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/flashpack.ci 2013-05-31 22:59:22.000000000 +0000 @@ -1,6 +1,6 @@ // flashpack.ci - Atari 8-bit executable compression // -// Copyright (C) 2011-2019 Piotr Fusik +// Copyright (C) 2011-2021 Piotr Fusik // // This file is part of ASAP (Another Slight Atari Player), // see http://asap.sourceforge.net @@ -66,7 +66,7 @@ class FlashPack { - int[65536] Memory; // TODO: short + short[65536] Memory; const int MinLoadAddress = 0x2000; // safe for most of Atari DOSes @@ -98,8 +98,10 @@ int flags = 1; do { flags <<= 1; - if (index < ItemsCount && Items[index++].Type != FlashPackItemType.Literal) - flags++; + if (index < ItemsCount) { + if (Items[index++].Type != FlashPackItemType.Literal) + flags++; + } } while (flags < 0x100); return flags & 0xff; } diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/INSTALL kodi-audiodecoder-asap-20.3.0/lib/asap-code/INSTALL --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/INSTALL 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/INSTALL 2013-05-31 22:59:22.000000000 +0000 @@ -96,7 +96,7 @@ You need 'xmms' and 'xmms-devel' packages. Compile: - make asap-xmms + make asap-xmms Install the plugin in your home directory: @@ -110,6 +110,19 @@ remove it in order to avoid conflicts. +Building the XMMS2 plugin on Unix-like systems +---------------------------------------------- + +You need the 'xmms2-devel' package. +Compile: + + make asap-xmms2 + +Install the plugin for all users: + + sudo make install-xmms2 + + Building asapconv, WASAP, Winamp/XMPlay/BASS plugins, RMT DLL, asapscan, shell extension and libasap on Windows --------------------------------------------------------------------- @@ -128,7 +141,7 @@ In addition to the common prerequisites you need: 1. A Microsoft C++ compiler and a Windows SDK. - I use Visual Studio Community 2019. + I use Visual Studio Community 2022. 2. foobar2000 SDK (http://www.foobar2000.org/SDK). Extract it to a new directory called 'foobar2000_SDK' next to (not inside!) the ASAP directory. @@ -173,7 +186,6 @@ make android-debug AndroidASAP integrates with file managers. -It reads phone state only to pause playback on incoming phone calls. Building C# ASAP2WAV and asapplay on Windows @@ -192,8 +204,27 @@ make javascript 'asap2wav.js' is the command-line script. -'asapweb.js' plus 'asap.js' are the browser player -(you'll probably need 'binaryHttpRequest.js' as well). +'asapweb.js' plus 'asap.js' are the browser player. + + +Building Python command-line ASAP2WAV +------------------------------------- + +Open the command prompt in the root directory of ASAP sources and run: + + make python + +'asap2wav.py' together with 'asap.py' are the command-line script. + + +Building Swift command-line ASAP2WAV +------------------------------------ + +Install Swift (https://swift.org/download/). + +Open the command prompt in the root directory of ASAP sources and run: + + make swift Using asapconv diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/java/android/AndroidManifest.xml kodi-audiodecoder-asap-20.3.0/lib/asap-code/java/android/AndroidManifest.xml --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/java/android/AndroidManifest.xml 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/java/android/AndroidManifest.xml 2013-05-31 22:59:22.000000000 +0000 @@ -1,10 +1,10 @@ - + package="net.sf.asap" android:versionCode="520" android:versionName="5.2.0" android:installLocation="auto"> + - + @@ -39,11 +39,6 @@ - - - - - - + diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/java/android/android.mk kodi-audiodecoder-asap-20.3.0/lib/asap-code/java/android/android.mk --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/java/android/android.mk 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/java/android/android.mk 2013-05-31 22:59:22.000000000 +0000 @@ -1,14 +1,12 @@ -ASMA_DIR = ../asma +ASMA_DIR = ../aasma/asma ANDROID_SDK = C:/Users/fox/AppData/Local/Android/Sdk -ANDROID_JAR = $(ANDROID_SDK)/platforms/android-28/android.jar -ANDROID_BUILD_TOOLS = $(ANDROID_SDK)/build-tools/28.0.3 -PROGUARD_JAR = $(ANDROID_SDK)/tools/proguard/lib/proguard.jar +ANDROID_JAR = $(ANDROID_SDK)/platforms/android-31/android.jar +ANDROID_BUILD_TOOLS = $(ANDROID_SDK)/build-tools/30.0.2 JAVA = $(DO)java AAPT = $(ANDROID_BUILD_TOOLS)/aapt -DX = $(DO)java -jar "$(ANDROID_BUILD_TOOLS)/lib/dx.jar" --no-strict -PROGUARD = $(DO)java -jar $(PROGUARD_JAR) -JARSIGNER = $(DO)jarsigner -sigalg SHA1withRSA -digestalg SHA1 +D8 = $(DO)java -cp "$(ANDROID_BUILD_TOOLS)/lib/d8.jar" com.android.tools.r8.D8 +JARSIGNER = $(DO)$(JAVA_SDK)/bin/jarsigner -sigalg SHA1withRSA -digestalg SHA1 ZIPALIGN = $(DO)$(ANDROID_BUILD_TOOLS)/zipalign ADB = $(ANDROID_SDK)/platform-tools/adb ANDROID = $(ANDROID_SDK)/tools/android.bat @@ -28,7 +26,7 @@ ANDROID_RELEASE = release/asap-$(VERSION)-android.apk ANDROID_JAVA_SRC = $(addprefix $(srcdir)java/android/, AATRFileInputStream.java ArchiveSelector.java ArchiveSuggestionsProvider.java BaseSelector.java \ - FileContainer.java FileInfo.java FileSelector.java JavaAATR.java MediaButtonEventReceiver.java Player.java PlayerService.java Util.java ZipInputStream.java) + FileContainer.java FileInfo.java FileSelector.java JavaAATR.java Player.java PlayerService.java Util.java ZipInputStream.java) android-release: $(ANDROID_RELEASE) .PHONY: android-release @@ -85,17 +83,11 @@ CLEAN += java/android/AndroidASAP-unsigned.apk java/android/classes.dex: java/android/classes/net/sf/asap/Player.class - $(DX) --dex --output=$@ java/android/classes + $(D8) --release --output $(@D) --lib $(ANDROID_JAR) `ls java/android/classes/net/sf/asap/*.class` CLEAN += java/android/classes.dex -#java/android/classes.dex: java/android/classes.jar -# $(DX) --dex --output=$@ $< - -java/android/classes.jar: $(srcdir)java/android/proguard.cfg java/android/classes/net/sf/asap/Player.class - $(PROGUARD) -injars java/android/classes -outjars $@ -libraryjars $(ANDROID_JAR) @$< - java/android/classes/net/sf/asap/Player.class: $(ANDROID_JAVA_SRC) java/android/AndroidASAP-resources.apk java/src/net/sf/asap/ASAP.java - $(JAVAC) -d java/android/classes -source 1.7 -target 1.7 -bootclasspath $(ANDROID_JAR) $(ANDROID_JAVA_SRC) java/android/gen/net/sf/asap/R.java java/src/net/sf/asap/*.java + $(JAVAC) -d java/android/classes --release 11 -cp $(ANDROID_JAR) -Xlint:deprecation $(ANDROID_JAVA_SRC) java/android/gen/net/sf/asap/R.java java/src/net/sf/asap/*.java CLEANDIR += java/android/classes # Also generates java/android/gen/net/sf/asap/R.java diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/java/android/Player.java kodi-audiodecoder-asap-20.3.0/lib/asap-code/java/android/Player.java --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/java/android/Player.java 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/java/android/Player.java 2013-05-31 22:59:22.000000000 +0000 @@ -1,7 +1,7 @@ /* * Player.java - ASAP for Android * - * Copyright (C) 2010-2018 Piotr Fusik + * Copyright (C) 2010-2021 Piotr Fusik * * This file is part of ASAP (Another Slight Atari Player), * see http://asap.sourceforge.net @@ -36,7 +36,6 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; -import android.view.View.OnClickListener; import android.widget.MediaController; import android.widget.TextView; @@ -91,17 +90,14 @@ setTag(R.id.song, ""); mediaController.setMediaPlayer(service); - mediaController.setPrevNextListeners(new OnClickListener() { - public void onClick(View v) { service.playNextSong(); } - }, - new OnClickListener() { - public void onClick(View v) { service.playPreviousSong(); } - }); + mediaController.setPrevNextListeners( + v -> service.playNextSong(), + v -> service.playPreviousSong()); } static final String ACTION_SHOW_INFO = "net.sf.asap.action.SHOW_INFO"; - private BroadcastReceiver receiver = new BroadcastReceiver() { + private final BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { showInfo(); @@ -124,12 +120,10 @@ startService(intent); bindService(intent, connection, Context.BIND_AUTO_CREATE); - findViewById(R.id.stop_button).setOnClickListener(new OnClickListener() { - public void onClick(View v) { - if (service != null) - service.stopSelf(); - finish(); - } + findViewById(R.id.stop_button).setOnClickListener(v -> { + if (service != null) + service.stopSelf(); + finish(); }); registerReceiver(receiver, new IntentFilter(ACTION_SHOW_INFO)); diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/java/android/PlayerService.java kodi-audiodecoder-asap-20.3.0/lib/asap-code/java/android/PlayerService.java --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/java/android/PlayerService.java 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/java/android/PlayerService.java 2013-05-31 22:59:22.000000000 +0000 @@ -1,7 +1,7 @@ /* * PlayerService.java - ASAP for Android * - * Copyright (C) 2010-2019 Piotr Fusik + * Copyright (C) 2010-2021 Piotr Fusik * * This file is part of ASAP (Another Slight Atari Player), * see http://asap.sourceforge.net @@ -29,16 +29,20 @@ import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.media.AudioAttributes; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; +import android.media.MediaMetadata; +import android.media.session.MediaSession; +import android.media.session.PlaybackState; import android.net.Uri; import android.os.Binder; import android.os.Build; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.widget.MediaController; @@ -54,17 +58,16 @@ { // User interface ----------------------------------------------------------------------------------------- - private static final int NOTIFICATION_ID = 1; + private static final String ACTION_PLAY = "asap.intent.action.PLAY"; + private static final String ACTION_PAUSE = "asap.intent.action.PAUSE"; + private static final String ACTION_NEXT = "asap.intent.action.NEXT"; + private static final String ACTION_PREVIOUS = "asap.intent.action.PREVIOUS"; private final Handler toastHandler = new Handler(); private void showError(final int messageId) { - toastHandler.post(new Runnable() { - public void run() { - Toast.makeText(PlayerService.this, messageId, Toast.LENGTH_SHORT).show(); - } - }); + toastHandler.post(() -> Toast.makeText(PlayerService.this, messageId, Toast.LENGTH_SHORT).show()); } private void showInfo() @@ -72,25 +75,61 @@ sendBroadcast(new Intent(Player.ACTION_SHOW_INFO)); } - private void showNotification() + private PendingIntent activityIntent; + private MediaSession mediaSession; + + private NotificationManager getNotificationManager() { - PendingIntent intent = PendingIntent.getActivity(this, 0, new Intent(this, Player.class), 0); - Notification.Builder builder = new Notification.Builder(this) - .setSmallIcon(R.drawable.icon) - .setContentTitle(info.getTitleOrFilename()) - .setContentText(info.getAuthor()) - .setContentIntent(intent) - .setOngoing(true); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - builder.setVisibility(Notification.VISIBILITY_PUBLIC); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - final String CHANNEL_ID = "NOW_PLAYING"; + return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + } + + private Notification.Action getNotificationAction(int icon, int titleResource, String action) + { + PendingIntent intent = PendingIntent.getService(this, 0, new Intent(action, null, this, PlayerService.class), 0); + return new Notification.Action(icon, getString(titleResource), intent); + } + + private void showNotification(boolean start) + { + Notification.Builder builder; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + final String CHANNEL_ID = "NOW_PLAYING"; + if (start) { NotificationChannel channel = new NotificationChannel(CHANNEL_ID, getString(R.string.notification_channel), NotificationManager.IMPORTANCE_LOW); - ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).createNotificationChannel(channel); - builder.setChannelId(CHANNEL_ID); + getNotificationManager().createNotificationChannel(channel); } + builder = new Notification.Builder(this, CHANNEL_ID); } - startForeground(NOTIFICATION_ID, builder.getNotification()); + else + builder = new Notification.Builder(this); + Notification notification = builder + .setSmallIcon(R.drawable.icon) + .setContentTitle(info.getTitleOrFilename()) + .setContentText(info.getAuthor()) + .setContentIntent(activityIntent) + .setStyle(new Notification.MediaStyle() + .setMediaSession(mediaSession.getSessionToken()) + .setShowActionsInCompactView(0, 1, 2)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .addAction(getNotificationAction(android.R.drawable.ic_media_previous, R.string.notification_previous, ACTION_PREVIOUS)) + .addAction(isPaused() + ? getNotificationAction(android.R.drawable.ic_media_play, R.string.notification_play, ACTION_PLAY) + : getNotificationAction(android.R.drawable.ic_media_pause, R.string.notification_pause, ACTION_PAUSE)) + .addAction(getNotificationAction(android.R.drawable.ic_media_next, R.string.notification_next, ACTION_NEXT)) + .build(); + final int NOTIFICATION_ID = 1; + if (start) + startForeground(NOTIFICATION_ID, notification); + else + getNotificationManager().notify(NOTIFICATION_ID, notification); + } + + private void setPlaybackState(int state, float speed, long actions) + { + mediaSession.setPlaybackState(new PlaybackState.Builder() + .setState(state, asap.getPosition(), speed) + .setActions(actions) + .build()); } @@ -121,6 +160,17 @@ } } + private boolean setSearchPlaylist(String query) + { + FileInfo[] infos = FileInfo.listIndex(this, query); + if (infos.length == 0) + return false; + playlist.clear(); + for (FileInfo info : infos) + playlist.add(Util.getAsmaUri(info.filename)); + return true; + } + private int getPlaylistIndex() { return playlist.indexOf(uri); @@ -129,18 +179,18 @@ // Playback ----------------------------------------------------------------------------------------------- - private static final int ACTION_STOP = 0; - private static final int ACTION_LOAD = 1; - private static final int ACTION_PLAY = 2; - private static final int ACTION_PAUSE = 3; - private static final int ACTION_NEXT = 4; - private static final int ACTION_PREV = 5; - private int action = ACTION_STOP; + private static final int COMMAND_STOP = 0; + private static final int COMMAND_LOAD = 1; + private static final int COMMAND_PLAY = 2; + private static final int COMMAND_PAUSE = 3; + private static final int COMMAND_NEXT = 4; + private static final int COMMAND_PREV = 5; + private int command = COMMAND_STOP; private Thread thread = null; - private synchronized void setAction(int action) + private synchronized void setCommand(int command) { - this.action = action; + this.command = command; if (thread != null && thread.isAlive()) notify(); else { @@ -152,7 +202,7 @@ private void stop() { if (thread != null) { - setAction(ACTION_STOP); + setCommand(COMMAND_STOP); try { thread.join(); } @@ -212,15 +262,17 @@ } // load into ASAP + int songs; try { asap.load(filename, module, moduleLen); info = asap.getInfo(); + songs = info.getSongs(); switch (song) { case SONG_DEFAULT: song = info.getDefaultSong(); break; case SONG_LAST: - song = info.getSongs() - 1; + song = songs - 1; break; default: break; @@ -232,16 +284,38 @@ return false; } + // put metadata into mediaSession + MediaMetadata.Builder metadata = new MediaMetadata.Builder() + .putString(MediaMetadata.METADATA_KEY_TITLE, info.getTitleOrFilename()); + String author = info.getAuthor(); + if (author.length() > 0) + metadata.putString(MediaMetadata.METADATA_KEY_ARTIST, author); + String date = info.getDate(); + if (date.length() > 0) + metadata.putString(MediaMetadata.METADATA_KEY_DATE, date); + int duration = info.getDuration(song); + if (duration > 0) + metadata.putLong(MediaMetadata.METADATA_KEY_DURATION, duration); + if (songs > 1) { + metadata.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, song + 1); + metadata.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, songs); + } + int year = info.getYear(); + if (year > 0) + metadata.putLong(MediaMetadata.METADATA_KEY_YEAR, year); + mediaSession.setMetadata(metadata.build()); + mediaSession.setActive(true); + return true; } - private synchronized boolean handleLoadAction() + private synchronized boolean handleLoadCommand() { - switch (action) { - case ACTION_LOAD: + switch (command) { + case COMMAND_LOAD: return load(); - case ACTION_NEXT: + case COMMAND_NEXT: if (info != null && song >= 0 && song + 1 < info.getSongs()) song++; else { @@ -255,7 +329,7 @@ } return load(); - case ACTION_PREV: + case COMMAND_PREV: if (song > 0) song--; else { @@ -274,23 +348,37 @@ } } - private boolean handlePlayAction(AudioTrack audioTrack) + private final long COMMON_ACTIONS = PlaybackState.ACTION_PLAY_FROM_SEARCH | PlaybackState.ACTION_SEEK_TO | PlaybackState.ACTION_SKIP_TO_PREVIOUS | PlaybackState.ACTION_SKIP_TO_NEXT; + + private boolean handlePlayCommand(AudioTrack audioTrack) { int pos; synchronized (this) { - if (action == ACTION_PAUSE) { + if (command == COMMAND_PAUSE) { + setPlaybackState(PlaybackState.STATE_PAUSED, 0, PlaybackState.ACTION_PLAY | COMMON_ACTIONS); + showNotification(false); audioTrack.pause(); - while (action == ACTION_PAUSE) { + releaseFocus(); + for (;;) { + if (command == COMMAND_PLAY) { + if (gainFocus()) { + setPlaybackState(PlaybackState.STATE_PLAYING, 1, PlaybackState.ACTION_PAUSE | COMMON_ACTIONS); + showNotification(false); + audioTrack.play(); + break; + } + command = COMMAND_PAUSE; + } + else if (command != COMMAND_PAUSE) + break; try { wait(); } catch (InterruptedException ex) { } } - if (action == ACTION_PLAY) - audioTrack.play(); } - if (action != ACTION_PLAY) { + if (command != COMMAND_PLAY) { audioTrack.stop(); return false; } @@ -299,36 +387,51 @@ seekPosition = -1; } if (pos >= 0) { + if (pos < asap.getPosition()) + setPlaybackState(PlaybackState.STATE_REWINDING, -10, 0); + else + setPlaybackState(PlaybackState.STATE_FAST_FORWARDING, 10, 0); try { asap.seek(pos); } catch (Exception ex) { } + setPlaybackState(PlaybackState.STATE_PLAYING, 1, PlaybackState.ACTION_PAUSE | COMMON_ACTIONS); } return true; } private void playLoop() { - action = ACTION_PLAY; + command = COMMAND_PLAY; seekPosition = -1; + setPlaybackState(PlaybackState.STATE_PLAYING, 1, PlaybackState.ACTION_PAUSE | COMMON_ACTIONS); - int channelConfig = info.getChannels() == 1 ? AudioFormat.CHANNEL_CONFIGURATION_MONO : AudioFormat.CHANNEL_CONFIGURATION_STEREO; + int channelConfig = info.getChannels() == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO; int bufferLen = AudioTrack.getMinBufferSize(ASAP.SAMPLE_RATE, channelConfig, AudioFormat.ENCODING_PCM_16BIT) >> 1; if (bufferLen < 16384) bufferLen = 16384; byte[] byteBuffer = new byte[bufferLen << 1]; short[] shortBuffer = new short[bufferLen]; - AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, ASAP.SAMPLE_RATE, channelConfig, AudioFormat.ENCODING_PCM_16BIT, bufferLen << 1, AudioTrack.MODE_STREAM); + AudioAttributes attributes = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .setUsage(AudioAttributes.USAGE_MEDIA) + .build(); + AudioFormat format = new AudioFormat.Builder() + .setChannelMask(channelConfig) + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .setSampleRate(ASAP.SAMPLE_RATE) + .build(); + AudioTrack audioTrack = new AudioTrack(attributes, format, bufferLen << 1, AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE); audioTrack.play(); - while (handlePlayAction(audioTrack)) { + while (handlePlayCommand(audioTrack)) { bufferLen = asap.generate(byteBuffer, byteBuffer.length, ASAPSampleFormat.S16_L_E) >> 1; for (int i = 0; i < bufferLen; i++) shortBuffer[i] = (short) ((byteBuffer[i << 1] & 0xff) | byteBuffer[i << 1 | 1] << 8); audioTrack.write(shortBuffer, 0, bufferLen); if (bufferLen < shortBuffer.length) - action = ACTION_NEXT; + command = COMMAND_NEXT; } audioTrack.release(); } @@ -337,9 +440,9 @@ { if (!gainFocus()) return; - while (handleLoadAction()) { + while (handleLoadCommand()) { showInfo(); - showNotification(); + showNotification(true); playLoop(); } stopForeground(true); @@ -348,12 +451,12 @@ private boolean isPaused() { - return action == ACTION_PAUSE; + return command == COMMAND_PAUSE; } public boolean isPlaying() { - return action != ACTION_PAUSE; + return command != COMMAND_PAUSE; } public boolean canPause() @@ -378,30 +481,22 @@ public void pause() { - setAction(ACTION_PAUSE); + setCommand(COMMAND_PAUSE); } public void start() { - setAction(ACTION_PLAY); - } - - synchronized void togglePause() - { - if (isPaused()) - start(); - else - pause(); + setCommand(COMMAND_PLAY); } void playNextSong() { - setAction(ACTION_NEXT); + setCommand(COMMAND_NEXT); } void playPreviousSong() { - setAction(ACTION_PREV); + setCommand(COMMAND_PREV); } public int getDuration() @@ -459,52 +554,112 @@ getAudioManager().abandonAudioFocus(this); } - private final BroadcastReceiver headsetReceiver = new BroadcastReceiver() { + private final MediaSession.Callback callback = new MediaSession.Callback() { @Override - public void onReceive(Context context, Intent intent) + public void onPause() + { + pause(); + } + + @Override + public void onPlay() { - if (!isInitialStickyBroadcast() && intent.getIntExtra("state", -1) == 0) { - pause(); - showInfo(); // just to update the MediaController + start(); + } + + @Override + public void onSeekTo(long pos) + { + seekTo((int) pos); + } + + @Override + public void onSkipToNext() + { + playNextSong(); + } + + @Override + public void onSkipToPrevious() + { + playPreviousSong(); + } + + @Override + public void onPlayFromSearch(String query, Bundle extras) + { + if (setSearchPlaylist(query)) { + uri = playlist.get(0); + setCommand(COMMAND_LOAD); } } }; + private final BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) + { + pause(); + showInfo(); // just to update the MediaController + } + }; + @Override - public void onStart(Intent intent, int startId) + public void onCreate() { - super.onStart(intent, startId); - - registerReceiver(headsetReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); - - ComponentName eventReceiver = new ComponentName(getPackageName(), MediaButtonEventReceiver.class.getName()); - getAudioManager().registerMediaButtonEventReceiver(eventReceiver); + activityIntent = PendingIntent.getActivity(this, 0, new Intent(this, Player.class), 0); + mediaSession = new MediaSession(this, "ASAP"); + mediaSession.setCallback(callback); + mediaSession.setSessionActivity(activityIntent); + mediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); + } - song = SONG_DEFAULT; - uri = intent.getData(); - String playlistUri = intent.getStringExtra(EXTRA_PLAYLIST); - if (playlistUri != null) - setPlaylist(Uri.parse(playlistUri), false); - else if (ASAPInfo.isOurFile(uri.toString())) - setPlaylist(Util.getParent(uri), false); - else { - // shuffle - setPlaylist(uri, true); - uri = playlist.get(0); + @Override + public int onStartCommand(Intent intent, int flags, int startId) + { + switch (intent.getAction()) { + case Intent.ACTION_VIEW: + registerReceiver(becomingNoisyReceiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); + song = SONG_DEFAULT; + uri = intent.getData(); + String playlistUri = intent.getStringExtra(EXTRA_PLAYLIST); + if (playlistUri != null) + setPlaylist(Uri.parse(playlistUri), false); + else if (ASAPInfo.isOurFile(uri.toString())) + setPlaylist(Util.getParent(uri), false); + else { + // shuffle + setPlaylist(uri, true); + uri = playlist.get(0); + } + setCommand(COMMAND_LOAD); + break; + case ACTION_PLAY: + start(); + break; + case ACTION_PAUSE: + pause(); + break; + case ACTION_NEXT: + playNextSong(); + break; + case ACTION_PREVIOUS: + playPreviousSong(); + break; + default: + break; } - setAction(ACTION_LOAD); + return START_NOT_STICKY; } @Override public void onDestroy() { - super.onDestroy(); stop(); - ComponentName eventReceiver = new ComponentName(getPackageName(), MediaButtonEventReceiver.class.getName()); - getAudioManager().unregisterMediaButtonEventReceiver(eventReceiver); + unregisterReceiver(becomingNoisyReceiver); - unregisterReceiver(headsetReceiver); + mediaSession.release(); } diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/java/android/res/values/strings.xml kodi-audiodecoder-asap-20.3.0/lib/asap-code/java/android/res/values/strings.xml --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/java/android/res/values/strings.xml 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/java/android/res/values/strings.xml 2013-05-31 22:59:22.000000000 +0000 @@ -16,6 +16,9 @@ Search Toggle details Browse - Player Service - Now Playing + Now Playing + Play + Pause + Next + Previous diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/java/android/Util.java kodi-audiodecoder-asap-20.3.0/lib/asap-code/java/android/Util.java --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/java/android/Util.java 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/java/android/Util.java 2013-05-31 22:59:22.000000000 +0000 @@ -1,7 +1,7 @@ /* * Util.java - ASAP for Android * - * Copyright (C) 2010-2020 Piotr Fusik + * Copyright (C) 2010-2021 Piotr Fusik * * This file is part of ASAP (Another Slight Atari Player), * see http://asap.sourceforge.net @@ -47,6 +47,11 @@ return "asma".equals(uri.getScheme()); } + static Uri getAsmaUri(String path) + { + return Uri.fromParts("asma", path, null); + } + static String getParent(String path) { // nice hack - if there is no slash we return an empty string @@ -71,7 +76,7 @@ static Uri buildUri(Uri baseUri, String relativePath) { if (isAsma(baseUri)) - return Uri.fromParts("asma", relativePath, null); + return getAsmaUri(relativePath); String path = baseUri.getPath(); if (endsWithIgnoreCase(path, ".zip") || endsWithIgnoreCase(path, ".atr")) { String innerPath = baseUri.getFragment(); diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/java/java.mk kodi-audiodecoder-asap-20.3.0/lib/asap-code/java/java.mk --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/java/java.mk 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/java/java.mk 2013-05-31 22:59:22.000000000 +0000 @@ -1,6 +1,8 @@ +JAVA_SDK = "C:/Program Files/Java/jdk-11.0.12" + JAVAC = $(DO)javac -JAR = $(DO)jar -JAVADOC = $(DO)javadoc +JAR = $(DO)$(JAVA_SDK)/bin/jar +JAVADOC = $(DO)$(JAVA_SDK)/bin/javadoc # no user-configurable paths below this line diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/javascript/asap2wav1.js kodi-audiodecoder-asap-20.3.0/lib/asap-code/javascript/asap2wav1.js --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/javascript/asap2wav1.js 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/javascript/asap2wav1.js 2013-05-31 22:59:22.000000000 +0000 @@ -1,7 +1,7 @@ /* * asap2wav1.js - converter of ASAP-supported formats to WAV files * - * Copyright (C) 2009-2011 Piotr Fusik + * Copyright (C) 2009-2020 Piotr Fusik * * This file is part of ASAP (Another Slight Atari Player), * see http://asap.sourceforge.net @@ -21,134 +21,18 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -var usage; +var args; +var driver; +var readBinaryFile; +var BinaryFileOutput; -/*@cc_on -@if (@_jscript_version >= 7) - // JScript .NET - - import System; - import System.IO; - - var arguments = new Array(Environment.GetCommandLineArgs()); - usage = arguments.shift() + " [OPTIONS] INPUTFILE..."; - - function quit(code) - { - Environment.Exit(code); - } - - function readBinaryFile(filename) - { - return File.ReadAllBytes(filename); - } - - class BinaryFileOutput - { - var stream; - - function BinaryFileOutput(filename) - { - this.stream = File.Create(filename); - } - - function write(bytes, len) - { - this.stream.Write(bytes, 0, len); - } - - function close() - { - this.stream.Close(); - } - } -@else @*/ -if (typeof(WScript) == "object") { - // WScript - - var arguments = new Array(); - for (var i = 0; i < WScript.Arguments.length; i++) - arguments[i] = WScript.Arguments(i); - usage = "cscript asap2wav.js [OPTIONS] INPUTFILE..."; - - var print = function(s) - { - WScript.Echo(s); - } - - var quit = function(code) - { - WScript.Quit(code); - } - - var BinaryFile_cp437ToUnicode = [ - 199, 252, 233, 226, 228, 224, 229, 231, 234, 235, 232, 239, 238, 236, 196, 197, - 201, 230, 198, 244, 246, 242, 251, 249, 255, 214, 220, 162, 163, 165, 8359, 402, - 225, 237, 243, 250, 241, 209, 170, 186, 191, 8976, 172, 189, 188, 161, 171, 187, - 9617, 9618, 9619, 9474, 9508, 9569, 9570, 9558, 9557, 9571, 9553, 9559, 9565, 9564, 9563, 9488, - 9492, 9524, 9516, 9500, 9472, 9532, 9566, 9567, 9562, 9556, 9577, 9574, 9568, 9552, 9580, 9575, - 9576, 9572, 9573, 9561, 9560, 9554, 9555, 9579, 9578, 9496, 9484, 9608, 9604, 9612, 9616, 9600, - 945, 223, 915, 960, 931, 963, 181, 964, 934, 920, 937, 948, 8734, 966, 949, 8745, - 8801, 177, 8805, 8804, 8992, 8993, 247, 8776, 176, 8729, 183, 8730, 8319, 178, 9632, 160 - ]; - - var BinaryFile_unicodeToCp437 = new Array(9633); - for (var c = 0; c < 128; c++) { - BinaryFile_unicodeToCp437[c] = c; - BinaryFile_unicodeToCp437[BinaryFile_cp437ToUnicode[c]] = 128 + c; - } - - var createStream = function() - { - var stream = WScript.CreateObject("ADODB.Stream"); - stream.Type = 2; - stream.CharSet = '437'; - stream.Open(); - return stream; - } - - var readBinaryFile = function(filename) - { - var stream = createStream(); - stream.LoadFromFile(filename); - var unicode = stream.ReadText(); - stream.Close(); - var bytes = new Array(unicode.length); - for (var i = 0; i < unicode.length; i++) { - var c = unicode.charCodeAt(i); - bytes[i] = BinaryFile_unicodeToCp437[c]; - } - return bytes; - } - - var BinaryFileOutput = function(filename) - { - this.filename = filename; - this.stream = createStream(); - - this.write = function(bytes, len) - { - for (var i = 0; i < len; i++) { - var c = bytes[i]; - if (c >= 128) - c = BinaryFile_cp437ToUnicode[c - 128]; - this.stream.WriteText(String.fromCharCode(c)); - } - } - - this.close = function() - { - this.stream.SaveToFile(filename, 2); - this.stream.Close(); - } - } -} -else if (typeof(java) == "object") { +if (typeof(java) == "object") { // Rhino - usage = "java -jar js.jar -opt -1 asap2wav.js [OPTIONS] INPUTFILE..."; + args = arguments; + driver = "java -jar rhino-*.jar"; - var readBinaryFile = function(filename) + readBinaryFile = function(filename) { var stream = new java.io.FileInputStream(filename); var bytes = new Array(); @@ -161,7 +45,7 @@ return bytes; } - var BinaryFileOutput = function(filename) + BinaryFileOutput = function(filename) { this.stream = new java.io.BufferedOutputStream(new java.io.FileOutputStream(filename)); @@ -176,81 +60,39 @@ this.stream.close(); } } + + Int32Array.prototype.fill = function(value) + { + for (var i = 0; i < this.length; i++) + this[i] = value; + } } else { - // DMDScript, JaegerMonkey or d8 + // Node - if (typeof(readline) == "undefined") { - var readline = readln; - var write = print; - var args = getenv("ARGS"); - arguments = args == null ? [] : args.split(" "); - usage = "\nset ARGS=[OPTIONS] INPUTFILE\nbase64 INPUTFILE | ds asap2wav.js | base64 -d > OUTFILE"; - } - else if (typeof(write) == "undefined") { - var write = putstr; - usage = "base64 INPUTFILE | js asap2wav.js [OPTIONS] INPUTFILE | base64 -d > OUTFILE"; - } - else - usage = "base64 INPUTFILE | d8 asap2wav.js -- [OPTIONS] INPUTFILE | base64 -d > OUTFILE"; + args = process.argv.slice(2); + driver = "node"; + var print = console.log; - var base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const fs = require("fs"); - var readBinaryFile = function(filename) - { - var bytes = new Array(); - var prev; - var gotBits = 0; - for (;;) { - var line = readline(); - if (line == null || line.length == 0) - break; - for (var i = 0; i < line.length; i++) { - var v = base64chars.indexOf(line.charAt(i)); - if (v >= 0) { - gotBits = (gotBits + 6) & 6; - if (gotBits != 6) - bytes.push((prev << 6 | v) >> gotBits & 0xff); - prev = v; - } - } - } - return bytes; - } + readBinaryFile = fs.readFileSync; - var BinaryFileOutput = function(filename) + BinaryFileOutput = function(filename) { - this.prev = 0; - this.gotBits = 0; + this.fd = fs.openSync(filename, "w"); this.write = function(bytes, len) { - for (var i = 0; i < len; i++) { - this.gotBits += 2; - var v = this.prev << 8 | bytes[i]; - write(base64chars.charAt(v >> this.gotBits & 63)); - if (this.gotBits == 6) { - write(base64chars.charAt(v & 63)); - this.gotBits = 0; - } - this.prev = bytes[i]; - } + fs.writeSync(this.fd, Buffer.from(bytes), 0, len); } this.close = function() { - if (this.gotBits == 2) { - write(base64chars.charAt((this.prev & 3) << 4)); - write("=="); - } - else if (this.gotBits == 4) { - write(base64chars.charAt((this.prev & 15) << 2)); - write("="); - } + fs.closeSync(this.fd); } } } -/*@end @*/ var outputFilename = null; var outputHeader = true; @@ -262,7 +104,7 @@ function printHelp() { print( - "Usage: " + usage + "\n" + + "Usage: " + driver + " asap2wav.js [OPTIONS] INPUTFILE...\n" + "Each INPUTFILE must be in a supported format:\n" + "SAP, CMC, CM3, CMR, CMS, DMC, DLT, MPT, MPD, RMT, TMC, TM8, TM2 or FC.\n" + "Options:\n" + @@ -335,22 +177,22 @@ } var noInputFiles = true; -for (var i = 0; i < arguments.length; i++) { - var arg = arguments[i]; +for (var i = 0; i < args.length; i++) { + var arg = args[i]; if (arg.charAt(0) != "-") { processFile(arg); noInputFiles = false; } else if (arg == "-o") - outputFilename = arguments[++i]; + outputFilename = args[++i]; else if (arg.substring(0, 9) == "--output=") outputFilename = arg.substring(9, arg.length); else if (arg == "-s") - setSong(arguments[++i]); + setSong(args[++i]); else if (arg.substring(0, 7) == "--song=") setSong(arg.substring(7, arg.length)); else if (arg == "-t") - setTime(arguments[++i]); + setTime(args[++i]); else if (arg.substring(0, 7) == "--time=") setTime(arg.substring(7, arg.length)); else if (arg == "-b" || arg == "--byte-samples") @@ -360,7 +202,7 @@ else if (arg == "--raw") outputHeader = false; else if (arg == "-m") - setMuteMask(arguments[++i]); + setMuteMask(args[++i]); else if (arg.substring(0, 7) == "--mute=") setMuteMask(arg.substring(7, arg.length)); else if (arg == "-h" || arg == "--help") { @@ -374,7 +216,5 @@ else throw "unknown option: " + arg; } -if (noInputFiles) { +if (noInputFiles) printHelp(); - quit(1); -} diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/javascript/asapweb.js kodi-audiodecoder-asap-20.3.0/lib/asap-code/javascript/asapweb.js --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/javascript/asapweb.js 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/javascript/asapweb.js 2013-05-31 22:59:22.000000000 +0000 @@ -1,7 +1,7 @@ /* * asapweb.js - pure JavaScript ASAP for web browsers * - * Copyright (C) 2009-2019 Piotr Fusik + * Copyright (C) 2009-2021 Piotr Fusik * * This file is part of ASAP (Another Slight Atari Player), * see http://asap.sourceforge.net @@ -21,7 +21,7 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -var asap = { +const asap = { stop : function() { if (this.processor) { @@ -45,7 +45,7 @@ const buffer = new Uint8Array(new ArrayBuffer(length * channels)); const AudioContext = window.AudioContext || window.webkitAudioContext; - if (typeof(this.context) == "object") + if (this.context) this.context.close(); this.context = new AudioContext({ sampleRate : ASAP.SAMPLE_RATE }); if (typeof(this.onUpdate) == "function") diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/javascript/javascript.mk kodi-audiodecoder-asap-20.3.0/lib/asap-code/javascript/javascript.mk --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/javascript/javascript.mk 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/javascript/javascript.mk 2013-05-31 22:59:22.000000000 +0000 @@ -1,5 +1,3 @@ -JSC = $(DO)jsc -nologo -w:2 -out:$@ $< - # no user-configurable paths below this line ifndef DO @@ -9,10 +7,6 @@ javascript: javascript/asap2wav.js .PHONY: javascript -javascript/asap2wav.exe: javascript/asap2wav.js - $(JSC) -CLEAN += javascript/asap2wav.exe - javascript/asap2wav.js: javascript/asap.js $(srcdir)javascript/asap2wav1.js $(DO)cat $^ > $@ CLEAN += javascript/asap2wav.js diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/javascript/USAGE kodi-audiodecoder-asap-20.3.0/lib/asap-code/javascript/USAGE --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/javascript/USAGE 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/javascript/USAGE 2013-05-31 22:59:22.000000000 +0000 @@ -3,12 +3,8 @@ Enter the following command line to see the syntax: -Windows Script Host: cscript asap2wav.js - Rhino Shell: java -jar js.jar -opt 1 asap2wav.js - V8 Shell: d8 asap2wav.js - JScript .NET: asap2wav - JaegerMonkey: js asap2wav.js - DMDScript: ds asap2wav.js + Node.js: node asap2wav.js +Rhino Shell: java -jar rhino-*.jar asap2wav.js Unless you specify the '-o/--output' option, the output filename will be constructed from the input filename with the extension changed to 'wav'. diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/Makefile kodi-audiodecoder-asap-20.3.0/lib/asap-code/Makefile --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/Makefile 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/Makefile 2013-05-31 22:59:22.000000000 +0000 @@ -1,7 +1,7 @@ prefix := /usr/local srcdir := $(dir $(lastword $(MAKEFILE_LIST))) bindir = $(prefix)/bin -libdir = $(prefix)/lib$(shell test -d $(prefix)/lib64 -a `uname -i` = x86_64 && echo 64) +libdir = $(prefix)/lib CC = gcc CFLAGS = -O2 -Wall CPPFLAGS = @@ -17,10 +17,10 @@ SDL_CFLAGS = `sdl-config --cflags` SDL_LIBS = `sdl-config --libs` SEVENZIP = 7z a -mx=9 -bd -bso0 -MAKEZIP = $(DO)$(RM) $@ && $(SEVENZIP) -tzip $@ $(^:%=./%) # "./" makes 7z don't store paths in the archive +MAKEZIP = $(DO)$(RM) $@ && $(SEVENZIP) -tzip $@ $(patsubst %,./%,$(filter-out win32/signed,$^)) # "./" makes 7z don't store paths in the archive COPY = $(DO)cp $< $@ XASM = $(DO)xasm -q -o $@ $< -OSX_CC = $(DO)gcc -O2 -Wall -o $@ -undefined dynamic_lookup -mmacosx-version-min=10.6 $(INCLUDEOPTS) $(filter %.c,$^) +OSX_CC = $(DO)cc -O2 -Wall -o $@ -undefined dynamic_lookup -mmacosx-version-min=10.6 -arch x86_64 -arch arm64 $(INCLUDEOPTS) $(filter %.c,$^) # no user-configurable paths below this line @@ -132,8 +132,12 @@ include $(srcdir)moc/moc.mk include $(srcdir)vlc/vlc.mk include $(srcdir)xmms/xmms.mk +include $(srcdir)xmms2/xmms2.mk include $(srcdir)csharp/csharp.mk include $(srcdir)java/java.mk include $(srcdir)javascript/javascript.mk +include $(srcdir)opencl/opencl.mk +include $(srcdir)python/python.mk +include $(srcdir)swift/swift.mk include $(srcdir)win32/win32.mk diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/MANIFEST kodi-audiodecoder-asap-20.3.0/lib/asap-code/MANIFEST --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/MANIFEST 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/MANIFEST 2013-05-31 22:59:22.000000000 +0000 @@ -55,6 +55,7 @@ csharp/csharp.mk debian/asap-dev.install debian/asap-vlc.install +debian/asap-xmms2.install debian/asap.install debian/changelog debian/compat @@ -74,7 +75,6 @@ java/android/FileSelector.java java/android/Indexer.java java/android/JavaAATR.java -java/android/MediaButtonEventReceiver.java java/android/Player.java java/android/PlayerService.java java/android/Util.java @@ -99,10 +99,17 @@ javascript/javascript.mk moc/libasap_decoder.c moc/moc.mk +opencl/asap2wav-kernel.cl +opencl/asapcl.cpp +opencl/opencl.mk pokey.ci +python/asap2wav.py +python/python.mk release/release.mk sap2ntsc.c sap2txt.c +swift/main.swift +swift/swift.mk test/RESULTS.xml test/RESULTS.xsl test/antic_nmien.asx @@ -191,3 +198,6 @@ xmms/libasap-xmms.c xmms/libasap-xmms.map xmms/xmms.mk +xmms2/libxmms_asap.c +xmms2/xmms2.mk +xmms2/xmms_configuration.h diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/opencl/asap2wav-kernel.cl kodi-audiodecoder-asap-20.3.0/lib/asap-code/opencl/asap2wav-kernel.cl --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/opencl/asap2wav-kernel.cl 1970-01-01 00:00:00.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/opencl/asap2wav-kernel.cl 2013-05-31 22:59:22.000000000 +0000 @@ -0,0 +1,12 @@ +kernel void asap2wav(constant char *filename, global const uchar *module, int module_len, int song, int duration, global uchar *wav, int wav_len) +{ + ASAP asap; + ASAP_Construct(&asap); + if (!ASAP_Load(&asap, filename, module, module_len) + || !ASAP_PlaySong(&asap, song, duration)) { + wav[0] = '\0'; + return; + } + int header_len = ASAP_GetWavHeader(&asap, wav, ASAPSampleFormat_S16_L_E, false); + ASAP_Generate(&asap, wav + header_len, wav_len - header_len, ASAPSampleFormat_S16_L_E); +} diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/opencl/asapcl.cpp kodi-audiodecoder-asap-20.3.0/lib/asap-code/opencl/asapcl.cpp --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/opencl/asapcl.cpp 1970-01-01 00:00:00.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/opencl/asapcl.cpp 2013-05-31 22:59:22.000000000 +0000 @@ -0,0 +1,181 @@ +/* + * asapcl.cpp - converter of ASAP-supported formats to WAV files + * + * Copyright (C) 2021 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 +#include +#include + +#define CL_TARGET_OPENCL_VERSION 200 +#include + +#include "asap.h" + +void check_error(int err) +{ + if (err != CL_SUCCESS) { + fprintf(stderr, "OpenCL error %d\n", err); + exit(1); + } +} + +int main(int argc, char **argv) +{ + cl_platform_id platform; + cl_uint num; + check_error(clGetPlatformIDs(1, &platform, &num)); + if (num == 0) { + fprintf(stderr, "No OpenCL platforms\n"); + return 1; + } + + cl_device_id device; + check_error(clGetDeviceIDs(platform, CL_DEVICE_TYPE_DEFAULT, 1, &device, &num)); + if (num == 0) { + fprintf(stderr, "No OpenCL device\n"); + return 1; + } + + size_t size; + check_error(clGetDeviceInfo(device, CL_DEVICE_NAME, 0, nullptr, &size)); + char *name = static_cast(malloc(size)); + check_error(clGetDeviceInfo(device, CL_DEVICE_NAME, size, name, nullptr)); + fprintf(stderr, "Running on %s\n", name); + free(name); + + const cl_context_properties properties[] = { + CL_CONTEXT_PLATFORM, (cl_context_properties) platform, 0 + }; + cl_int err; + cl_context context = clCreateContext(properties, 1, &device, nullptr, nullptr, &err); + check_error(err); + + const char *source = +#include "asap-cl.h" + ; + cl_program program = clCreateProgramWithSource(context, 1, &source, nullptr, &err); + check_error(err); + + check_error(clBuildProgram(program, 1, &device, "-cl-std=CL2.0 -cl-opt-disable", nullptr, nullptr)); + + cl_kernel kernel; + check_error(clCreateKernelsInProgram(program, 1, &kernel, nullptr)); + + cl_command_queue queue = clCreateCommandQueueWithProperties(context, device, nullptr, &err); + check_error(err); + + ASAPInfo *info = ASAPInfo_New(); + int exit_code = 0; + + for (int argi = 1; argi < argc; argi++) { + const char *input_file = argv[argi]; + const char *input_dot = strrchr(input_file, '.'); + if (input_dot == nullptr) { + fprintf(stderr, "%s: missing filename extension\n", input_file); + exit_code = 1; + continue; + } + char output_file[FILENAME_MAX]; + if (snprintf(output_file, sizeof(output_file), "%.*s.wav", (int) (input_dot - input_file), input_file) >= static_cast(sizeof(output_file))) { + fprintf(stderr, "%s: filename too long\n", input_file); + exit_code = 1; + continue; + } + + FILE *fp = fopen(input_file, "rb"); + if (fp == NULL) { + fprintf(stderr, "Cannot open %s\n", input_file); + exit_code = 1; + continue; + } + static unsigned char module[ASAPInfo_MAX_MODULE_LENGTH]; + int module_len = fread(module, 1, sizeof(module), fp); + fclose(fp); + + if (!ASAPInfo_Load(info, input_file, module, module_len)) { + fprintf(stderr, "%s: unsupported file\n", input_file); + exit_code = 1; + continue; + } + int channels = ASAPInfo_GetChannels(info); + int song = ASAPInfo_GetDefaultSong(info); + int duration = ASAPInfo_GetDuration(info, song); + if (duration < 0) + duration = 180 * 1000; + + cl_mem filename_buffer = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR | CL_MEM_HOST_NO_ACCESS, strlen(input_file) + 1, const_cast(input_file), &err); + check_error(err); + check_error(clSetKernelArg(kernel, 0, sizeof(filename_buffer), &filename_buffer)); + + cl_mem module_buffer = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR | CL_MEM_HOST_NO_ACCESS, module_len, module, &err); + check_error(err); + check_error(clSetKernelArg(kernel, 1, sizeof(module_buffer), &module_buffer)); + check_error(clSetKernelArg(kernel, 2, sizeof(module_len), &module_len)); + + check_error(clSetKernelArg(kernel, 3, sizeof(song), &song)); + check_error(clSetKernelArg(kernel, 4, sizeof(duration), &duration)); + + int wav_len = 44 + duration * (ASAP_SAMPLE_RATE / 100) / 10 * channels * 2; + cl_mem wav_buffer = clCreateBuffer(context, CL_MEM_WRITE_ONLY | CL_MEM_HOST_READ_ONLY, wav_len, nullptr, &err); + check_error(err); + check_error(clSetKernelArg(kernel, 5, sizeof(wav_buffer), &wav_buffer)); + check_error(clSetKernelArg(kernel, 6, sizeof(wav_len), &wav_len)); + + const size_t one = 1; + check_error(clEnqueueNDRangeKernel(queue, kernel, 1, nullptr, &one, nullptr, 0, nullptr, nullptr)); + + uint8_t *wav = static_cast(malloc(wav_len)); + check_error(clEnqueueReadBuffer(queue, wav_buffer, false, 0, wav_len, wav, 0, nullptr, nullptr)); + + check_error(clFinish(queue)); + + if (wav[0] != 'R') { + fprintf(stderr, "%s: conversion error\n", input_file); + exit_code = 1; + } + else { + fp = fopen(output_file, "wb"); + if (fp == NULL) { + fprintf(stderr, "Cannot create %s\n", output_file); + exit_code = 1; + } + else { + fwrite(wav, 1, wav_len, fp); + fclose(fp); + } + } + + free(wav); + + check_error(clReleaseMemObject(wav_buffer)); + check_error(clReleaseMemObject(module_buffer)); + check_error(clReleaseMemObject(filename_buffer)); + } + + ASAPInfo_Delete(info); + + check_error(clReleaseCommandQueue(queue)); + check_error(clReleaseKernel(kernel)); + check_error(clReleaseProgram(program)); + check_error(clReleaseContext(context)); + return exit_code; +} diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/opencl/opencl.mk kodi-audiodecoder-asap-20.3.0/lib/asap-code/opencl/opencl.mk --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/opencl/opencl.mk 1970-01-01 00:00:00.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/opencl/opencl.mk 2013-05-31 22:59:22.000000000 +0000 @@ -0,0 +1,22 @@ +CXX = g++ +CXXFLAGS = -O2 -Wall + +# no user-configurable paths below this line + +ifndef DO +$(error Use "Makefile" instead of "opencl.mk") +endif + +opencl: opencl/asapcl +.PHONY: opencl + +opencl/asapcl: opencl/asapcl.cpp opencl/asap-cl.h asap.o asap.h + $(DO)$(CXX) $(CXXFLAGS) -o $@ $(INCLUDEOPTS) $< asap.o -lOpenCL + +opencl/asap-cl.h: opencl/asap.cl opencl/asap2wav-kernel.cl + $(DO)(echo 'R"CLC(' && cat $^ && echo ')CLC"') >$@ +CLEAN += opencl/asap-cl.h + +opencl/asap.cl: $(call src,asap.ci asap6502.ci asapinfo.ci cpu6502.ci pokey.ci) $(ASM6502_PLAYERS_OBX) + $(CITO) -D OPENCL +CLEAN += opencl/asap.cl diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/pokey.ci kodi-audiodecoder-asap-20.3.0/lib/asap-code/pokey.ci --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/pokey.ci 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/pokey.ci 2013-05-31 22:59:22.000000000 +0000 @@ -1,6 +1,6 @@ // pokey.ci - POKEY chip emulator // -// Copyright (C) 2010-2019 Piotr Fusik +// Copyright (C) 2010-2021 Piotr Fusik // // This file is part of ASAP (Another Slight Atari Player), // see http://asap.sourceforge.net @@ -319,6 +319,8 @@ ReloadCycles1 = data + 4; Channels[1].MuteUltrasound(cycle); break; + default: + assert false; } Channels[0].MuteUltrasound(cycle); break; @@ -341,6 +343,8 @@ case 0x50: Channels[1].PeriodCycles = Channels[0].Audf + (data << 8) + 7; break; + default: + assert false; } Channels[1].MuteUltrasound(cycle); break; @@ -369,6 +373,8 @@ ReloadCycles3 = data + 4; Channels[3].MuteUltrasound(cycle); break; + default: + assert false; } Channels[2].MuteUltrasound(cycle); break; @@ -391,6 +397,8 @@ case 0x28: Channels[3].PeriodCycles = Channels[2].Audf + (data << 8) + 7; break; + default: + assert false; } Channels[3].MuteUltrasound(cycle); break; @@ -423,6 +431,8 @@ Channels[1].PeriodCycles = Channels[0].Audf + (Channels[1].Audf << 8) + 7; ReloadCycles1 = Channels[0].Audf + 4; break; + default: + assert false; } Channels[0].MuteUltrasound(cycle); Channels[1].MuteUltrasound(cycle); @@ -445,6 +455,8 @@ Channels[3].PeriodCycles = Channels[2].Audf + (Channels[3].Audf << 8) + 7; ReloadCycles3 = Channels[2].Audf + 4; break; + default: + assert false; } Channels[2].MuteUltrasound(cycle); Channels[3].MuteUltrasound(cycle); @@ -680,16 +692,18 @@ samplesEnd = i + blocks; else blocks = samplesEnd - i; - for (; i < samplesEnd; i++) { - bufferOffset = BasePokey.StoreSample(buffer, bufferOffset, i, format); - if (ExtraPokeyMask != 0) - bufferOffset = ExtraPokey.StoreSample(buffer, bufferOffset, i, format); - } - if (i == ReadySamplesEnd) { - BasePokey.AccumulateTrailing(i); - ExtraPokey.AccumulateTrailing(i); + if (blocks > 0) { + for (; i < samplesEnd; i++) { + bufferOffset = BasePokey.StoreSample(buffer, bufferOffset, i, format); + if (ExtraPokeyMask != 0) + bufferOffset = ExtraPokey.StoreSample(buffer, bufferOffset, i, format); + } + if (i == ReadySamplesEnd) { + BasePokey.AccumulateTrailing(i); + ExtraPokey.AccumulateTrailing(i); + } + ReadySamplesStart = i; } - ReadySamplesStart = i; #if APOKEYSND return bufferOffset; #else diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/PORTS.xml kodi-audiodecoder-asap-20.3.0/lib/asap-code/PORTS.xml --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/PORTS.xml 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/PORTS.xml 2013-05-31 22:59:22.000000000 +0000 @@ -35,7 +35,7 @@ Winamp - Linux + Linux player plugin 0.2.0 @@ -583,4 +583,70 @@ no C + + + desktop operating systems + command line + 5.1.0 + sample + 8 or 16-bit, WAV or raw file + yes + no + no + no + yes + yes + no + Python + + + + desktop operating systems + command line + 5.1.0 + sample + 8 or 16-bit, WAV or raw file + yes + no + no + no + yes + yes + no + Swift + + + + desktop operating systems + command line + 5.2.0 + sample + 16-bit WAV file + no + no + no + no + no + no + no + OpenCL + + + Linux + Linux + player plugin + 5.2.0 + stable + 16-bit sound + no + yes + no + no + no + no + no + SAP format only + C + XMMS2 + diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/PORTS.xsl kodi-audiodecoder-asap-20.3.0/lib/asap-code/PORTS.xsl --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/PORTS.xsl 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/PORTS.xsl 2013-05-31 22:59:22.000000000 +0000 @@ -68,8 +68,8 @@ good - bad - partial + partial + bad diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/python/asap2wav.py kodi-audiodecoder-asap-20.3.0/lib/asap-code/python/asap2wav.py --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/python/asap2wav.py 1970-01-01 00:00:00.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/python/asap2wav.py 2013-05-31 22:59:22.000000000 +0000 @@ -0,0 +1,123 @@ +# asap2wav.py - converter of ASAP-supported formats to WAV files +# +# Copyright (C) 2020 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 + +from asap import ASAP, ASAPInfo, ASAPSampleFormat +from pathlib import Path +import sys + +output_filename = None +song = None +duration = None +format = ASAPSampleFormat.S16_L_E +output_header = True +mute_mask = 0 + +def print_help(): + print( + "Usage: python asap2wav.py [OPTIONS] INPUTFILE...\n" + "Each INPUTFILE must be in a supported format:\n" + "SAP, CMC, CM3, CMR, CMS, DMC, DLT, MPT, MPD, RMT, TMC, TM8, TM2 or FC.\n" + "Options:\n" + "-o FILE --output=FILE Set output file name\n" + "-s SONG --song=SONG Select subsong number (zero-based)\n" + "-t TIME --time=TIME Set output length (MM:SS format)\n" + "-b --byte-samples Output 8-bit samples\n" + "-w --word-samples Output 16-bit samples (default)\n" + " --raw Output raw audio (no WAV header)\n" + "-m CHANNELS --mute=CHANNELS Mute POKEY channels (1-8)\n" + "-h --help Display this information\n" + "-v --version Display version information") + +def set_mute_mask(s): + mute_mask = 0 + for c in s: + if "1" <= c <= "8": + mute_mask |= 1 << (int(c) - 1) + +def process_file(input_filename): + with open(input_filename, "rb") as f: module = f.read() + asap = ASAP() + asap.load(input_filename, module, len(module)) + info = asap.get_info() + global output_filename, song, duration, format, output_header, mute_mask + if song is None: + song = info.get_default_song() + if duration is None: + duration = info.get_duration(song) + if duration < 0: + duration = 180_000 + asap.play_song(song, duration) + asap.mute_pokey_channels(mute_mask) + if output_filename is None: + output_filename = Path(input_filename).with_suffix(".wav" if output_header else ".raw") + buffer = bytearray(8192) + with open(output_filename, "wb") as f: + if output_header: + buffer_len = asap.get_wav_header(buffer, format, False) + f.write(buffer[:buffer_len]) + buffer_len = 8192 + while buffer_len == 8192: + buffer_len = asap.generate(buffer, 8192, format) + f.write(buffer[:buffer_len]) + output_filename = None + song = None + duration = None + +args = sys.argv[1:] +no_input_files = True +while (args): + arg = args.pop(0) + if arg[0] != "-": + process_file(arg) + no_input_files = False + elif arg == "-o": + output_filename = args.pop(0) + elif arg.startswith("--output="): + output_filename = arg[9:] + elif arg == "-s": + song = int(args.pop(0)) + elif arg.startswith("--song="): + song = int(arg[7:]) + elif arg == "-t": + duration = ASAPInfo.parse_duration(args.pop(0)) + elif arg.startswith("--time="): + duration = ASAPInfo.parse_duration(arg[7:]) + elif arg == "-b" or arg == "--byte-samples": + format = ASAPSampleFormat.U8 + elif arg == "-w" or arg == "--word-samples": + format = ASAPSampleFormat.S16_L_E + elif arg == "--raw": + output_header = False + elif arg == "-m": + set_mute_mask(args.pop(0)) + elif arg == "--mute=": + set_mute_mask(arg[7:]) + elif arg == "-h" or arg == "--help": + print_help() + no_input_files = False + elif arg == "-v" or arg == "--version": + print("ASAP2WAV (Python)", ASAPInfo.VERSION) + no_input_files = False + else: + raise Exception("unknown option: " + arg) +if no_input_files: + print_help() + sys.exit(1) diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/python/python.mk kodi-audiodecoder-asap-20.3.0/lib/asap-code/python/python.mk --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/python/python.mk 1970-01-01 00:00:00.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/python/python.mk 2013-05-31 22:59:22.000000000 +0000 @@ -0,0 +1,12 @@ +# no user-configurable paths below this line + +ifndef DO +$(error Use "Makefile" instead of "python.mk") +endif + +python: python/asap.py +.PHONY: python + +python/asap.py: $(call src,asap.ci asap6502.ci asapinfo.ci cpu6502.ci pokey.ci) $(ASM6502_PLAYERS_OBX) + $(CITO) +CLEAN += python/asap.py diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/release/release.mk kodi-audiodecoder-asap-20.3.0/lib/asap-code/release/release.mk --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/release/release.mk 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/release/release.mk 2013-05-31 22:59:22.000000000 +0000 @@ -5,7 +5,7 @@ # no user-configurable paths below this line -VERSION = 5.0.1 +VERSION = 5.2.0 ifndef DO $(error Use "Makefile" instead of "release.mk") @@ -14,9 +14,8 @@ dist: \ release/asap-$(VERSION)-android.apk \ release/asap-$(VERSION)-web.zip \ - release/asap-$(VERSION)-win32.msi \ release/asap-$(VERSION)-win32.zip \ - release/asap-$(VERSION)-win64.msi \ + release/signed-msi \ release/foo_asap-$(VERSION).fb2k-component \ srcdist .PHONY: dist @@ -38,14 +37,22 @@ $(MAKEZIP) release/asap-$(VERSION)-win32.zip: release/COPYING.txt \ - $(addprefix win32/,asapconv.exe asapscan.exe wasap.exe in_asap.dll foo_asap.dll apokeysnd.dll xmp-asap.dll bass_asap.dll ASAPShellEx.dll libasap_plugin.dll) + $(addprefix win32/,asapconv.exe asapscan.exe wasap.exe in_asap.dll foo_asap.dll apokeysnd.dll xmp-asap.dll bass_asap.dll ASAPShellEx.dll libasap_plugin.dll signed) $(MAKEZIP) -release/foo_asap-$(VERSION).fb2k-component: win32/foo_asap.dll +release/foo_asap-$(VERSION).fb2k-component: win32/foo_asap.dll win32/signed $(MAKEZIP) release/asap-$(VERSION)-macos.dmg: release/osx/libasap_plugin.dylib release/osx/plugins release/osx/asapconv release/osx/bin +ifdef PORK_CODESIGNING_IDENTITY + codesign --options runtime -f -s $(PORK_CODESIGNING_IDENTITY) release/osx/libasap_plugin.dylib + codesign --options runtime -f -s $(PORK_CODESIGNING_IDENTITY) release/osx/asapconv +endif $(DO)hdiutil create -volname asap-$(VERSION)-macos -srcfolder release/osx -format UDBZ -fs HFS+ -imagekey bzip2-level=3 -ov $@ +ifdef PORK_NOTARIZING_CREDENTIALS + xcrun altool --notarize-app --primary-bundle-id net.sf.asap $(PORK_NOTARIZING_CREDENTIALS) --file $@ \ + | perl -pe 's/^RequestUUID =/xcrun altool $$ENV{PORK_NOTARIZING_CREDENTIALS} --notarization-info/ or next; $$c = $$_; until (/Status: success/) { sleep 20; $$_ = `$$c`; print; } last;' +endif release/osx/libasap_plugin.dylib: libasap_plugin.dylib $(DO)strip -o $@ -x $< && chmod 644 $@ @@ -57,7 +64,7 @@ release/osx/asapconv: $(call src,asapconv.c asap.[ch]) $(OSX_CC) -release/osx/bin: +release/osx/bin: $(DO)ln -s /usr/local/bin $@ deb: @@ -67,20 +74,26 @@ deb64: scp release/asap-$(VERSION).tar.gz vm:. ssh vm 'rm -rf asap-$(VERSION) && tar xf asap-$(VERSION).tar.gz && make -C asap-$(VERSION) deb' - scp vm:asap{,-dev,-vlc}_$(VERSION)-1_amd64.deb release/ + scp vm:asap_$(VERSION)-1_amd64.deb release/ + scp vm:asap-dev_$(VERSION)-1_amd64.deb release/ + scp vm:asap-vlc_$(VERSION)-1_amd64.deb release/ + scp vm:asap-xmms2_$(VERSION)-1_amd64.deb release/ .PHONY: deb64 rpm64: scp release/asap-$(VERSION).tar.gz vm:. ssh vm 'rpmbuild -tb asap-$(VERSION).tar.gz' - scp vm:rpmbuild/RPMS/x86_64/asap{,-devel,-vlc,-xmms}-$(VERSION)-1.x86_64.rpm release/ + scp vm:rpmbuild/RPMS/x86_64/asap-$(VERSION)-1.x86_64.rpm release/ + scp vm:rpmbuild/RPMS/x86_64/asap-devel-$(VERSION)-1.x86_64.rpm release/ + scp vm:rpmbuild/RPMS/x86_64/asap-vlc-$(VERSION)-1.x86_64.rpm release/ + scp vm:rpmbuild/RPMS/x86_64/asap-xmms2-$(VERSION)-1.x86_64.rpm release/ .PHONY: rpm64 -rpm32: - scp release/asap-$(VERSION).tar.gz vm:. - ssh vm 'rpmbuild -tb asap-$(VERSION).tar.gz' - scp vm:rpmbuild/RPMS/i686/asap{,-devel,-vlc,-xmms}-$(VERSION)-1.i686.rpm release/ -.PHONY: rpm32 +mac: + scp release/asap-$(VERSION).tar.gz mac:. + ssh mac 'security unlock-keychain ~/Library/Keychains/login.keychain && rm -rf asap-$(VERSION) && tar xf asap-$(VERSION).tar.gz && make -C asap-$(VERSION) release/asap-$(VERSION)-macos.dmg' + scp mac:asap-$(VERSION)/release/asap-$(VERSION)-macos.dmg release/ +.PHONY: mac release/COPYING.txt: $(srcdir)COPYING $(UNIX2DOS) diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/swift/main.swift kodi-audiodecoder-asap-20.3.0/lib/asap-code/swift/main.swift --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/swift/main.swift 1970-01-01 00:00:00.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/swift/main.swift 2013-05-31 22:59:22.000000000 +0000 @@ -0,0 +1,165 @@ +/* + * main.swift - converter of ASAP-supported formats to WAV files + * + * Copyright (C) 2021 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 + */ + +import Foundation + +var outputFilenameOption : String? +var songOption : Int? +var durationOption : Int? +var format = ASAPSampleFormat.s16LE +var outputHeader = true +var muteMask = 0 + +func printHelp() +{ + print(""" +Usage: asap2wav [OPTIONS] INPUTFILE... +Each INPUTFILE must be in a supported format: +SAP, CMC, CM3, CMR, CMS, DMC, DLT, MPT, MPD, RMT, TMC, TM8, TM2 or FC. +Options: +-o FILE --output=FILE Set output file name +-s SONG --song=SONG Select subsong number (zero-based) +-t TIME --time=TIME Set output length (MM:SS format) +-b --byte-samples Output 8-bit samples +-w --word-samples Output 16-bit samples (default) + --raw Output raw audio (no WAV header) +-m CHANNELS --mute=CHANNELS Mute POKEY channels (1-8) +-h --help Display this information +-v --version Display version information +""") +} + +func getOptionValue(_ i : inout Int, _ option : String) -> String? +{ + let arg = CommandLine.arguments[i] + if arg == "-" + option.prefix(1) { + i += 1 + return CommandLine.arguments[i] + } + let longOption = "--" + option + "=" + if arg.hasPrefix(longOption) { + return String(arg[longOption.endIndex...]) + } + return nil +} + +func processFile(_ inputFilename : String) throws +{ + if let data = FileManager.default.contents(atPath: inputFilename) { + let asap = ASAP() + try asap.load(inputFilename, ArrayRef(Array(data)), data.count) + let info = asap.getInfo()! + let song = songOption ?? info.getDefaultSong() + var duration = durationOption ?? info.getDuration(song) + if duration < 0 { + duration = 180_000 + } + try asap.playSong(song, duration) + asap.mutePokeyChannels(muteMask) + let outputFilename = outputFilenameOption ?? inputFilename.deletingPathExtension.appendingPathExtension(outputHeader ? "wav" : "raw")! + let buffer = ArrayRef(repeating: 0, count: 8192) + if let f = OutputStream(toFileAtPath: outputFilename, append: false) { + f.open() + var ok = true + if outputHeader { + let headerLen = asap.getWavHeader(buffer, format, false) + ok = f.write(buffer.array, maxLength : headerLen) == headerLen + } + while ok { + let bufferLen = asap.generate(buffer, 8192, format) + if bufferLen > 0 { + ok = f.write(buffer.array, maxLength : bufferLen) == bufferLen + } + if bufferLen != 8192 { + break + } + } + f.close() + if !ok { + print(outputFilename, ": error writing file", separator: "") + } + } + else { + print(outputFilename, ": cannot write file", separator: "") + } + } + else { + print(inputFilename, ": cannot open file", separator: "") + } + outputFilenameOption = nil + songOption = nil + durationOption = nil +} + +var noInputFiles = true +var i = 1 +while i < CommandLine.argc { + let arg = CommandLine.arguments[i] + if !arg.hasPrefix("-") { + try processFile(arg) + noInputFiles = false + } + else if let value = getOptionValue(&i, "output") { + outputFilenameOption = value + } + else if let value = getOptionValue(&i, "song") { + songOption = Int(value) + } + else if let value = getOptionValue(&i, "time") { + durationOption = try ASAPInfo.parseDuration(value) + } + else if arg == "-b" || arg == "--byte-samples" { + format = ASAPSampleFormat.u8 + } + else if arg == "-w" || arg == "--word-samples" { + format = ASAPSampleFormat.s16LE + } + else if arg == "--raw" { + outputHeader = false + } + else if let value = getOptionValue(&i, "mute") { + for c in value { + if let i = c.wholeNumberValue { + if i >= 1 && i <= 8 { + muteMask |= 1 << (i - 1) + } + } + } + } + else if arg == "-h" || arg == "--help" { + printHelp() + noInputFiles = false + } + else if arg == "-v" || arg == "--version" { + print("ASAP2WAV (Swift)", ASAPInfo.version) + noInputFiles = false + } + else { + fatalError("unknown option: " + arg) + } + i += 1 +} +if noInputFiles { + printHelp() + exit(1) +} diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/swift/swift.mk kodi-audiodecoder-asap-20.3.0/lib/asap-code/swift/swift.mk --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/swift/swift.mk 1970-01-01 00:00:00.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/swift/swift.mk 2013-05-31 22:59:22.000000000 +0000 @@ -0,0 +1,18 @@ +SWIFTC = $(DO)swiftc -no-color-diagnostics -sdk '$(SDKROOT)' + +# no user-configurable paths below this line + +ifndef DO +$(error Use "Makefile" instead of "swift.mk") +endif + +swift: swift/asap2wav.exe +.PHONY: swift + +swift/asap2wav.exe: swift/main.swift swift/asap.swift + $(SWIFTC) -O -o $@ $^ +CLEAN += swift/asap2wav.exe swift/asap2wav.exp swift/asap2wav.lib + +swift/asap.swift: $(call src,asap.ci asap6502.ci asapinfo.ci cpu6502.ci pokey.ci) $(ASM6502_PLAYERS_OBX) + $(CITO) +CLEAN += swift/asap.swift diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/test/timevsnative.c kodi-audiodecoder-asap-20.3.0/lib/asap-code/test/timevsnative.c --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/test/timevsnative.c 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/test/timevsnative.c 2013-05-31 22:59:22.000000000 +0000 @@ -23,7 +23,7 @@ printf("???"); else { s[len] = '\0'; - printf("%s", (const char *) s); + fputs((const char *) s, stdout); if (ASAPInfo_GetLoop(info, song)) printf(" LOOP"); } @@ -33,38 +33,32 @@ int main(int argc, char *argv[]) { - const char *sap_filename; - FILE *fp; - unsigned char sap[ASAPInfo_MAX_MODULE_LENGTH]; - int sap_len; ASAPInfo *sap_info = ASAPInfo_New(); - int sap_sum; - const char *native_ext; ASAPInfo *native_info = ASAPInfo_New(); if (argc != 2) { printf("Usage: timevsnative FILE.sap\n"); return 1; } - sap_filename = argv[1]; - fp = fopen(sap_filename, "rb"); + const char *sap_filename = argv[1]; + FILE *fp = fopen(sap_filename, "rb"); if (fp == NULL) { fprintf(stderr, "%s: cannot open\n", sap_filename); return 1; } - sap_len = fread(sap, 1, sizeof(sap), fp); + unsigned char sap[ASAPInfo_MAX_MODULE_LENGTH]; + int sap_len = fread(sap, 1, sizeof(sap), fp); fclose(fp); - printf("%s", sap_filename); - sap_sum = print_times(sap_info, sap_filename, sap, sap_len); - native_ext = ASAPInfo_GetOriginalModuleExt(sap_info, sap, sap_len); + fputs(sap_filename, stdout); + int sap_sum = print_times(sap_info, sap_filename, sap, sap_len); + const char *native_ext = ASAPInfo_GetOriginalModuleExt(sap_info, sap, sap_len); if (native_ext != NULL) { ASAPWriter *writer = ASAPWriter_New(); - char native_filename[FILENAME_MAX]; - unsigned char native[ASAPInfo_MAX_MODULE_LENGTH]; - int native_len; printf("\t%s", native_ext); + char native_filename[FILENAME_MAX]; sprintf(native_filename, "%.*s.%s", (int) (strrchr(sap_filename, '.') - sap_filename), sap_filename, native_ext); + unsigned char native[ASAPInfo_MAX_MODULE_LENGTH]; ASAPWriter_SetOutput(writer, native, 0, sizeof(native)); - native_len = ASAPWriter_Write(writer, native_filename, sap_info, sap, sap_len, FALSE); + int native_len = ASAPWriter_Write(writer, native_filename, sap_info, sap, sap_len, false); ASAPWriter_Delete(writer); if (native_len >= 0) { int native_sum = print_times(native_info, native_filename, native, native_len); diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/vlc/vlc.mk kodi-audiodecoder-asap-20.3.0/lib/asap-code/vlc/vlc.mk --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/vlc/vlc.mk 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/vlc/vlc.mk 2013-05-31 22:59:22.000000000 +0000 @@ -5,7 +5,7 @@ # macOS -VLC_OSX_CFLAGS = -std=gnu99 -I../plugins -dynamiclib -flat_namespace +VLC_OSX_CFLAGS = -std=gnu99 -I../vlc-3.0.14/include -dynamiclib -flat_namespace VLC_OSX_PLUGIN_DIR = /Applications/VLC.app/Contents/MacOS/plugins # no user-configurable paths below this line diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/bass/bass_asap.c kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/bass/bass_asap.c --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/bass/bass_asap.c 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/bass/bass_asap.c 2013-05-31 22:59:22.000000000 +0000 @@ -1,7 +1,7 @@ /* * bass_asap.c - ASAP add-on for BASS * - * Copyright (C) 2010-2019 Piotr Fusik + * Copyright (C) 2010-2020 Piotr Fusik * * This file is part of ASAP (Another Slight Atari Player), * see http://asap.sourceforge.net @@ -39,7 +39,7 @@ BASS_CTYPE_MUSIC_ASAP, "ASAP", "*.sap;*.cmc;*.cm3;*.cmr;*.cms;*.dmc;*.dlt;*.mpt;*.mpd;*.rmt;*.tmc;*.tm8;*.tm2;*.fc" }; -static const BASS_PLUGININFO plugininfo = { 0x02040000, 1, &pluginform }; +static const BASS_PLUGININFO plugininfo = { MAKELONG(MAKEWORD(ASAPInfo_VERSION_MINOR, ASAPInfo_VERSION_MAJOR), BASSVERSION), 1, &pluginform }; typedef struct { @@ -52,7 +52,9 @@ static void WINAPI ASAP_Free(void *inst) { - free(inst); + ASAPSTREAM *stream = (ASAPSTREAM *) inst; + ASAP_Delete(stream->asap); + free(stream); } static QWORD WINAPI ASAP_GetLength(void *inst, DWORD mode) @@ -68,9 +70,9 @@ static const char * WINAPI ASAP_GetTags(void *inst, DWORD tags) { - ASAPSTREAM *stream = (ASAPSTREAM *) inst; if (tags != BASS_TAG_ID3) return NULL; + ASAPSTREAM *stream = (ASAPSTREAM *) inst; TAG_ID3 *tag = &stream->tag; memset(tag, 0, sizeof(TAG_ID3)); tag->id[0] = 'T'; @@ -169,6 +171,7 @@ error(BASS_ERROR_MEM); } if (!ASAP_Load(stream->asap, filename, module, module_len)) { + ASAP_Delete(stream->asap); free(stream); error(BASS_ERROR_FILEFORM); } @@ -183,11 +186,13 @@ duration = ASAPInfo_GetDuration(info, song); stream->duration = duration; if (!ASAP_PlaySong(stream->asap, song, duration)) { + ASAP_Delete(stream->asap); free(stream); error(BASS_ERROR_FILEFORM); } HSTREAM handle = bassfunc->CreateStream(ASAP_SAMPLE_RATE, ASAPInfo_GetChannels(info), flags, &StreamProc, stream, &ASAPfuncs); if (handle == 0) { + ASAP_Delete(stream->asap); free(stream); return 0; // CreateStream set the error code } diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/diff-sap.js kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/diff-sap.js --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/diff-sap.js 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/diff-sap.js 2013-05-31 22:59:22.000000000 +0000 @@ -2,21 +2,15 @@ // Get command-line arguments -var sap1; -var sap2; -var names = ""; -switch (WScript.Arguments.length) { -case 4: - names = " /basename:\"" + WScript.Arguments(2) + "\" /minename:\"" + WScript.Arguments(3) + "\""; - // FALLTHROUGH -case 2: - sap1 = WScript.Arguments(0); - sap2 = WScript.Arguments(1); - break; -default: - WScript.Echo("Specify two filenames plus optional two headers"); +var argc = WScript.Arguments.length; +if (argc != 2 && argc != 4) { + WScript.Echo("Specify two filenames and optionally two titles"); WScript.Quit(1); } +var sap1 = WScript.Arguments(0); +var sap2 = WScript.Arguments(1); +var title1 = WScript.Arguments(argc - 2); +var title2 = WScript.Arguments(argc - 1); // Find TortoiseMerge @@ -53,7 +47,7 @@ // Display results -wsh.Run("\"" + tmerge + "\" /base:\"" + txt1 + "\" /mine:\"" + txt2 + "\"" + names, 4, true); +wsh.Run("\"" + tmerge + "\" /base:\"" + txt1 + "\" /mine:\"" + txt2 + "\" /basename:\"" + title1 + "\" /minename:\"" + title2 + "\"", 4, true); // Write back changes diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/foobar2000/foo_asap.cpp kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/foobar2000/foo_asap.cpp --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/foobar2000/foo_asap.cpp 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/foobar2000/foo_asap.cpp 2013-05-31 22:59:22.000000000 +0000 @@ -1,7 +1,7 @@ /* * foo_asap.cpp - ASAP plugin for foobar2000 * - * Copyright (C) 2006-2019 Piotr Fusik + * Copyright (C) 2006-2021 Piotr Fusik * * This file is part of ASAP (Another Slight Atari Player), * see http://asap.sourceforge.net @@ -77,16 +77,13 @@ class input_asap { - static input_asap *head; - input_asap *prev = nullptr; - input_asap *next; service_ptr_t m_file; char *url = nullptr; BYTE module[ASAPInfo_MAX_MODULE_LENGTH]; int module_len; - ASAP *asap; + ASAP * const asap; - int get_song_duration(int song, bool play) + int get_song_duration(int song, bool play) const { const ASAPInfo *info = ASAP_GetInfo(asap); int duration = ASAPInfo_GetDuration(info, song); @@ -115,12 +112,6 @@ public: - static void g_set_mute_mask(int mask) - { - for (input_asap *p = head; p != nullptr; p = p->next) - ASAP_MutePokeyChannels(p->asap, mask); - } - static bool g_is_our_content_type(const char *p_content_type) { return false; @@ -153,25 +144,14 @@ return false; } - input_asap() + input_asap() : asap(ASAP_New()) { - if (head != nullptr) - head->prev = this; - next = head; - head = this; - asap = ASAP_New(); } ~input_asap() { - ASAP_Delete(asap); free(url); - if (prev != nullptr) - prev->next = next; - if (next != nullptr) - next->prev = prev; - if (head == this) - head = next; + ASAP_Delete(asap); } void open(service_ptr_t p_filehint, const char *p_path, t_input_open_reason p_reason, abort_callback &p_abort) @@ -196,17 +176,17 @@ throw exception_io_unsupported_format(); } - t_uint32 get_subsong_count() + t_uint32 get_subsong_count() const { return ASAPInfo_GetSongs(ASAP_GetInfo(asap)); } - t_uint32 get_subsong(t_uint32 p_index) + t_uint32 get_subsong(t_uint32 p_index) const { return p_index; } - void get_info(t_uint32 p_subsong, file_info &p_info, abort_callback &p_abort) + void get_info(t_uint32 p_subsong, file_info &p_info, abort_callback &p_abort) const { int duration = get_song_duration(p_subsong, false); if (duration >= 0) @@ -219,24 +199,25 @@ meta_set(p_info, "date", ASAPInfo_GetDate(info)); } - t_filestats get_file_stats(abort_callback &p_abort) + t_filestats get_file_stats(abort_callback &p_abort) const { return m_file->get_stats(p_abort); } - void decode_initialize(t_uint32 p_subsong, unsigned p_flags, abort_callback &p_abort) + void decode_initialize(t_uint32 p_subsong, unsigned p_flags, abort_callback &p_abort) const { int duration = get_song_duration(p_subsong, true); if (!ASAP_PlaySong(asap, p_subsong, duration)) throw exception_io_unsupported_format(); - ASAP_MutePokeyChannels(asap, mute_mask); const char *filename = url; if (foobar2000_io::_extract_native_path_ptr(filename)) setPlayingSong(filename, p_subsong); } - bool decode_run(audio_chunk &p_chunk, abort_callback &p_abort) + bool decode_run(audio_chunk &p_chunk, abort_callback &p_abort) const { + ASAP_MutePokeyChannels(asap, mute_mask); + int channels = ASAPInfo_GetChannels(ASAP_GetInfo(asap)); int buffered_bytes = BUFFERED_BLOCKS * channels * (BITS_PER_SAMPLE / 8); BYTE buffer[BUFFERED_BLOCKS * 2 * (BITS_PER_SAMPLE / 8)]; @@ -251,32 +232,32 @@ return true; } - void decode_seek(double p_seconds, abort_callback &p_abort) + void decode_seek(double p_seconds, abort_callback &p_abort) const { ASAP_Seek(asap, static_cast(p_seconds * 1000)); } - bool decode_can_seek() + bool decode_can_seek() const { return true; } - bool decode_get_dynamic_info(file_info &p_out, double &p_timestamp_delta) + bool decode_get_dynamic_info(file_info &p_out, double &p_timestamp_delta) const { return false; } - bool decode_get_dynamic_info_track(file_info &p_out, double &p_timestamp_delta) + bool decode_get_dynamic_info_track(file_info &p_out, double &p_timestamp_delta) const { return false; } - void decode_on_idle(abort_callback &p_abort) + void decode_on_idle(abort_callback &p_abort) const { m_file->on_idle(p_abort); } - void retag_set_info(t_uint32 p_subsong, const file_info &p_info, abort_callback &p_abort) + void retag_set_info(t_uint32 p_subsong, const file_info &p_info, abort_callback &p_abort) const { ASAPInfo *info = const_cast(ASAP_GetInfo(asap)); ASAPInfo_SetAuthor(info, empty_if_null(p_info.meta_get("composer", 0))); @@ -301,7 +282,7 @@ m_file->write(output, output_len, p_abort); } - void set_logger(event_logger::ptr ptr) + void set_logger(event_logger::ptr ptr) const { } @@ -310,7 +291,6 @@ typedef input_info_writer interface_info_writer_t; }; -input_asap *input_asap::head = nullptr; static input_factory_t g_input_asap_factory; @@ -371,10 +351,10 @@ class preferences_page_instance_asap : public preferences_page_instance { - HWND m_parent; - HWND m_hWnd; + const HWND m_parent; + const HWND m_hWnd; - int get_time_input() + int get_time_input() const { HWND hDlg = m_hWnd; if (IsDlgButtonChecked(hDlg, IDC_UNLIMITED) == BST_CHECKED) @@ -384,7 +364,7 @@ return static_cast(60 * minutes + seconds); } - int get_silence_input() + int get_silence_input() const { HWND hDlg = m_hWnd; if (IsDlgButtonChecked(hDlg, IDC_SILENCE) != BST_CHECKED) @@ -392,12 +372,12 @@ return GetDlgItemInt(hDlg, IDC_SILSECONDS, NULL, FALSE); } - bool get_loops_input() + bool get_loops_input() const { return IsDlgButtonChecked(m_hWnd, IDC_LOOPS) == BST_CHECKED; } - int get_mute_input() + int get_mute_input() const { HWND hDlg = m_hWnd; int mask = 0; @@ -409,9 +389,9 @@ public: - preferences_page_instance_asap(HWND parent) : m_parent(parent) + preferences_page_instance_asap(HWND parent) : m_parent(parent), + m_hWnd(CreateDialog(core_api::get_my_instance(), MAKEINTRESOURCE(IDD_SETTINGS), parent, ::settings_dialog_proc)) { - m_hWnd = CreateDialog(core_api::get_my_instance(), MAKEINTRESOURCE(IDD_SETTINGS), parent, ::settings_dialog_proc); } t_uint32 get_state() override @@ -436,7 +416,6 @@ silence_seconds = get_silence_input(); play_loops = get_loops_input(); mute_mask = get_mute_input(); - input_asap::g_set_mute_mask(mute_mask); g_callback->on_state_changed(); } @@ -484,32 +463,30 @@ /* File types ------------------------------------------------------------ */ -static const char * const names_and_masks[][2] = { - { "Slight Atari Player", "*.SAP" }, - { "Chaos Music Composer", "*.CMC;*.CM3;*.CMR;*.CMS;*.DMC" }, - { "Delta Music Composer", "*.DLT" }, - { "Music ProTracker", "*.MPT;*.MPD" }, - { "Raster Music Tracker", "*.RMT" }, - { "Theta Music Composer 1.x", "*.TMC;*.TM8" }, - { "Theta Music Composer 2.x", "*.TM2" }, - { "Future Composer", "*.FC" } -}; - -#define N_FILE_TYPES (sizeof(names_and_masks) / sizeof(names_and_masks[0])) - class input_file_type_asap : public service_impl_single_t { + static constexpr const char *names_and_masks[][2] = { + { "Slight Atari Player", "*.SAP" }, + { "Chaos Music Composer", "*.CMC;*.CM3;*.CMR;*.CMS;*.DMC" }, + { "Delta Music Composer", "*.DLT" }, + { "Music ProTracker", "*.MPT;*.MPD" }, + { "Raster Music Tracker", "*.RMT" }, + { "Theta Music Composer 1.x", "*.TMC;*.TM8" }, + { "Theta Music Composer 2.x", "*.TM2" }, + { "Future Composer", "*.FC" } + }; + public: unsigned get_count() override { - return N_FILE_TYPES; + return ARRAYSIZE(names_and_masks); } bool get_name(unsigned idx, pfc::string_base &out) override { - if (idx < N_FILE_TYPES) { - out = ::names_and_masks[idx][0]; + if (idx < ARRAYSIZE(names_and_masks)) { + out = names_and_masks[idx][0]; return true; } return false; @@ -517,8 +494,8 @@ bool get_mask(unsigned idx, pfc::string_base &out) override { - if (idx < N_FILE_TYPES) { - out = ::names_and_masks[idx][1]; + if (idx < ARRAYSIZE(names_and_masks)) { + out = names_and_masks[idx][1]; return true; } return false; @@ -638,8 +615,8 @@ t_size read(void *p_buffer, t_size p_bytes, abort_callback &p_abort) override { - int length = p_bytes < INT_MAX ? (int) p_bytes : INT_MAX; - int result = AATRFileStream_Read(stream, reinterpret_cast(p_buffer), 0, length); + int length = p_bytes < INT_MAX ? static_cast(p_bytes) : INT_MAX; + int result = AATRFileStream_Read(stream, static_cast(p_buffer), 0, length); if (result < 0) throw exception_io_data(); return result; @@ -657,7 +634,7 @@ void seek(t_filesize p_position, abort_callback &p_abort) override { - int position = (int) p_position; + int position = static_cast(p_position); if (position != p_position || !AATRFileStream_SetPosition(stream, position)) throw exception_io_seek_out_of_range(); } diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/gui.rc kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/gui.rc --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/gui.rc 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/gui.rc 2013-05-31 22:59:22.000000000 +0000 @@ -1,7 +1,7 @@ /* * gui.rc - Windows resources * - * Copyright (C) 2005-2019 Piotr Fusik + * Copyright (C) 2005-2020 Piotr Fusik * * This file is part of ASAP (Another Slight Atari Player), * see http://asap.sourceforge.net @@ -60,27 +60,21 @@ } } -#ifdef _WIN64 -#define PROCESSOR_ARCHITECTURE "amd64" -#else -#define PROCESSOR_ARCHITECTURE "X86" -#endif 1 24 { "" "" - "" + "" "" "" - "" + "" "" "" "" "" - "true" + "True/PM" + "PerMonitorV2,PerMonitor" "" "" "" diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/info_dlg.c kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/info_dlg.c --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/info_dlg.c 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/info_dlg.c 2013-05-31 22:59:22.000000000 +0000 @@ -165,9 +165,7 @@ { char buf[16000]; char *p = buf; - const char *ext; - int type; - ext = ASAPInfo_GetOriginalModuleExt(edited_info, saved_module, saved_module_len); + const char *ext = ASAPInfo_GetOriginalModuleExt(edited_info, saved_module, saved_module_len); if (ext != NULL) p += sprintf(p, "Composed in %s\r\n", ASAPInfo_GetExtDescription(ext)); int i = ASAPInfo_GetSongs(edited_info); @@ -179,7 +177,7 @@ } p += sprintf(p, ASAPInfo_GetChannels(edited_info) > 1 ? "STEREO\r\n" : "MONO\r\n"); p += sprintf(p, ASAPInfo_IsNtsc(edited_info) ? "NTSC\r\n" : "PAL\r\n"); - type = ASAPInfo_GetTypeLetter(edited_info); + int type = ASAPInfo_GetTypeLetter(edited_info); if (type != 0) p += sprintf(p, "TYPE %c\r\n", type); p += sprintf(p, "FASTPLAY %d (%d Hz)\r\n", ASAPInfo_GetPlayerRateScanlines(edited_info), ASAPInfo_GetPlayerRateHz(edited_info)); @@ -194,12 +192,11 @@ if (i >= 0) { while (p < buf + sizeof(buf) - 17 && i + 4 < saved_module_len) { int start = saved_module[i] + (saved_module[i + 1] << 8); - int end; if (start == 0xffff) { i += 2; start = saved_module[i] + (saved_module[i + 1] << 8); } - end = saved_module[i + 2] + (saved_module[i + 3] << 8); + int end = saved_module[i + 2] + (saved_module[i + 3] << 8); p += sprintf(p, "LOAD %04X-%04X\r\n", start, end); i += 5 + end - start; } @@ -219,11 +216,9 @@ p = appendStil(p, "Song comment: ", ASTIL_GetSongComment(astil)); for (int i = 0; ; i++) { const ASTILCover *cover = ASTIL_GetCover(astil, i); - int startSeconds; - const char *s; if (cover == NULL) break; - startSeconds = ASTILCover_GetStartSeconds(cover); + int startSeconds = ASTILCover_GetStartSeconds(cover); if (startSeconds >= 0) { int endSeconds = ASTILCover_GetEndSeconds(cover); if (endSeconds >= 0) @@ -233,15 +228,13 @@ } else *p++ = 'C'; - s = ASTILCover_GetTitleAndSource(cover); + const char *s = ASTILCover_GetTitleAndSource(cover); p = appendStil(p, "overs: ", s[0] != '\0' ? s : ""); p = appendStil(p, "by ", ASTILCover_GetArtist(cover)); p = appendStil(p, "Comment: ", ASTILCover_GetComment(cover)); } *p = '\0'; chomp(buf); -#if 1 - /* not compatible with Windows 9x */ if (ASTIL_IsUTF8(astil)) { WCHAR wBuf[16000]; if (MultiByteToWideChar(CP_UTF8, 0, buf, -1, wBuf, 16000) > 0) { @@ -249,7 +242,6 @@ return; } } -#endif SendDlgItemMessage(infoDialog, IDC_STILINFO, WM_SETTEXT, 0, (LPARAM) buf); } @@ -270,33 +262,10 @@ updateStil(); } -static void showEditTip(int nID, LPCTSTR title, LPCTSTR message) +static void showEditTip(int nID, LPCWSTR title, LPCWSTR message) { -#ifndef _UNICODE - -#ifndef EM_SHOWBALLOONTIP -/* missing in MinGW */ -typedef struct -{ - DWORD cbStruct; - LPCWSTR pszTitle; - LPCWSTR pszText; - INT ttiIcon; -} EDITBALLOONTIP; -#define TTI_ERROR 3 -#define EM_SHOWBALLOONTIP 0x1503 -#endif - WCHAR wTitle[64]; - WCHAR wMessage[64]; - EDITBALLOONTIP ebt = { sizeof(EDITBALLOONTIP), wTitle, wMessage, TTI_ERROR }; - if (MultiByteToWideChar(CP_ACP, 0, title, -1, wTitle, 64) <= 0 - || MultiByteToWideChar(CP_ACP, 0, message, -1, wMessage, 64) <= 0 - || !SendDlgItemMessage(infoDialog, nID, EM_SHOWBALLOONTIP, 0, (LPARAM) &ebt)) - -#endif /* _UNICODE */ - - /* Windows before XP don't support balloon tips */ - MessageBox(infoDialog, message, title, MB_OK | MB_ICONERROR); + EDITBALLOONTIP ebt = { sizeof(EDITBALLOONTIP), title, message, TTI_ERROR }; + SendDlgItemMessage(infoDialog, nID, EM_SHOWBALLOONTIP, 0, (LPARAM) &ebt); } static bool isExt(LPCTSTR filename, LPCTSTR ext) @@ -350,7 +319,7 @@ bool ok = func(edited_info, str); updateSaveButtons(mask, ok); if (!ok) - showEditTip(nID, _T("Invalid characters"), _T("Avoid national characters and quotation marks")); + showEditTip(nID, L"Invalid characters", L"Avoid national characters and quotation marks"); } static LRESULT CALLBACK MonthCalWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) @@ -600,7 +569,7 @@ case MAKEWPARAM(IDC_TIME, EN_KILLFOCUS): if ((invalid_fields & INVALID_FIELD_TIME_SHOW) != 0) { invalid_fields &= ~INVALID_FIELD_TIME_SHOW; - showEditTip(IDC_TIME, _T("Invalid format"), _T("Please type MM:SS.mmm")); + showEditTip(IDC_TIME, L"Invalid format", L"Please type MM:SS.mmm"); } return TRUE; case MAKEWPARAM(IDC_LOOP, BN_CLICKED): diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/rmt/apokeysnd_dll.c kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/rmt/apokeysnd_dll.c --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/rmt/apokeysnd_dll.c 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/rmt/apokeysnd_dll.c 2013-05-31 22:59:22.000000000 +0000 @@ -1,7 +1,7 @@ /* * apokeysnd_dll.c - POKEY sound emulator for Raster Music Tracker * - * Copyright (C) 2008-2020 Piotr Fusik + * Copyright (C) 2008-2021 Piotr Fusik * * This file is part of ASAP (Another Slight Atari Player), * see http://asap.sourceforge.net @@ -54,7 +54,7 @@ __declspec(dllexport) void APokeySound_About(const char **name, const char **author, const char **description) { - *name = "Another POKEY Sound Emulator, v5.0.1"; - *author = "Piotr Fusik, (C) 2007-2020"; + *name = "Another POKEY Sound Emulator, v5.2.0"; + *author = "Piotr Fusik, (C) 2007-2021"; *description = "Part of ASAP, http://asap.sourceforge.net"; } diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/setup/asap.wxs kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/setup/asap.wxs --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/setup/asap.wxs 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/setup/asap.wxs 2013-05-31 22:59:22.000000000 +0000 @@ -1,7 +1,6 @@ - @@ -9,7 +8,6 @@ - @@ -246,22 +244,16 @@ - + - - VersionNT >= 600 + - - VersionNT < 600 - - - - VersionNT >= 600 + @@ -300,10 +292,10 @@ - + - + @@ -390,7 +382,7 @@ - + @@ -460,10 +452,10 @@ - + + VersionNT >= 600 - diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/setup/license.rtf kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/setup/license.rtf --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/setup/license.rtf 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/setup/license.rtf 2013-05-31 22:59:22.000000000 +0000 @@ -2,7 +2,7 @@ {\fonttbl {\f0\fswiss\fcharset0 Arial;}} \f0\sa100\fs18\lang1033 -Copyright (C) 2005-2020 - Piotr Fusik +Copyright (C) 2005-2021 - Piotr Fusik \par\ql ASAP is free. You don't have to pay for it, and you can use it diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/shellex/ASAPShellEx.cpp kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/shellex/ASAPShellEx.cpp --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/shellex/ASAPShellEx.cpp 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/shellex/ASAPShellEx.cpp 2013-05-31 22:59:22.000000000 +0000 @@ -1,7 +1,7 @@ /* * ASAPShellEx.cpp - ASAP Column Handler and Property Handler shell extensions * - * Copyright (C) 2010-2019 Piotr Fusik + * Copyright (C) 2010-2021 Piotr Fusik * * This file is part of ASAP (Another Slight Atari Player), * see http://asap.sourceforge.net @@ -21,10 +21,6 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -/* There are two separate implementations for different Windows versions: - Column Handler (IColumnProvider) works in Windows XP and 2003 (probably 2000 too and maybe 98). - Property Handler (IInitializeWithStream+IPropertyStore+IPropertyStoreCapabilities) works in Windows Vista and 7. */ - #include #define _WIN32_IE 0x500 #include @@ -37,7 +33,6 @@ static HINSTANCE g_hDll; static LONG g_cRef = 0; -static enum { WINDOWS_OLD, WINDOWS_XP, WINDOWS_VISTA } g_windowsVer; static void DllAddRef(void) { @@ -53,55 +48,35 @@ static const GUID CLSID_ASAPMetadataHandler = { 0x5ae26367, 0xb5cf, 0x444d, { 0xb1, 0x63, 0x2c, 0xbc, 0x99, 0xb4, 0x12, 0x87 } }; -struct CMyPropertyDef -{ - const SHCOLUMNID scid; - const UINT cChars; - const DWORD csFlags; - const LPCWSTR wszTitle; - - void CopyTo(SHCOLUMNINFO *psci) const - { - psci->scid = this->scid; - psci->vt = VT_LPWSTR; - psci->fmt = LVCFMT_LEFT; - psci->cChars = this->cChars; - psci->csFlags = this->csFlags; - lstrcpyW(psci->wszTitle, this->wszTitle); - lstrcpyW(psci->wszDescription, this->wszTitle); - } -}; - -static const CMyPropertyDef g_propertyDefs[] = { - { { FMTID_SummaryInformation, PIDSI_TITLE }, 25, SHCOLSTATE_TYPE_STR | SHCOLSTATE_SLOW, L"Title" }, - { { FMTID_SummaryInformation, PIDSI_AUTHOR }, 25, SHCOLSTATE_TYPE_STR | SHCOLSTATE_SLOW, L"Author" }, - { { FMTID_MUSIC, PIDSI_ARTIST }, 25, SHCOLSTATE_TYPE_STR | SHCOLSTATE_SLOW, L"Artist" }, - { { FMTID_MUSIC, PIDSI_YEAR }, 4, SHCOLSTATE_TYPE_STR | SHCOLSTATE_SLOW, L"Year" }, - { { FMTID_AudioSummaryInformation, PIDASI_TIMELENGTH }, 8, SHCOLSTATE_TYPE_STR | SHCOLSTATE_SLOW, L"Duration" }, - { { FMTID_AudioSummaryInformation, PIDASI_CHANNEL_COUNT }, 9, SHCOLSTATE_TYPE_INT | SHCOLSTATE_SLOW, L"Channels" }, - { { CLSID_ASAPMetadataHandler, 1 }, 8, SHCOLSTATE_TYPE_INT | SHCOLSTATE_SLOW, L"Subsongs" }, - { { CLSID_ASAPMetadataHandler, 2 }, 8, SHCOLSTATE_TYPE_STR | SHCOLSTATE_SLOW, L"PAL/NTSC" } +static const SHCOLUMNID g_scids[] = { + { FMTID_SummaryInformation, PIDSI_TITLE }, + { FMTID_SummaryInformation, PIDSI_AUTHOR }, + { FMTID_MUSIC, PIDSI_ARTIST }, + { FMTID_MUSIC, PIDSI_YEAR }, + { FMTID_AudioSummaryInformation, PIDASI_TIMELENGTH }, + { FMTID_AudioSummaryInformation, PIDASI_CHANNEL_COUNT }, + { CLSID_ASAPMetadataHandler, 1 }, + { CLSID_ASAPMetadataHandler, 2 } }; -#define N_PROPERTYDEFS ARRAYSIZE(g_propertyDefs) class CMyLock { - const PCRITICAL_SECTION m_pLock; + CRITICAL_SECTION &m_lock; public: - CMyLock(PCRITICAL_SECTION pLock) : m_pLock(pLock) + CMyLock(CRITICAL_SECTION &lck) : m_lock(lck) { - EnterCriticalSection(pLock); + EnterCriticalSection(&lck); } ~CMyLock() { - LeaveCriticalSection(m_pLock); + LeaveCriticalSection(&m_lock); } }; -class CASAPMetadataHandler final : IColumnProvider, IInitializeWithStream, IPropertyStore, IPropertyStoreCapabilities +class CASAPMetadataHandler final : IInitializeWithStream, IPropertyStore, IPropertyStoreCapabilities { LONG m_cRef = 1; CRITICAL_SECTION m_lock; @@ -126,9 +101,11 @@ m_pstream = nullptr; } - int cbFilename = WideCharToMultiByte(CP_ACP, 0, wszFile, -1, nullptr, 0, nullptr, nullptr); + int cbFilename = WideCharToMultiByte(CP_UTF8, 0, wszFile, -1, nullptr, 0, nullptr, nullptr); + if (cbFilename <= 0) + return HRESULT_FROM_WIN32(GetLastError()); char filename[cbFilename]; - if (WideCharToMultiByte(CP_ACP, 0, wszFile, -1, filename, cbFilename, nullptr, nullptr) <= 0) + if (WideCharToMultiByte(CP_UTF8, 0, wszFile, -1, filename, cbFilename, nullptr, nullptr) <= 0) return HRESULT_FROM_WIN32(GetLastError()); byte module[ASAPInfo::maxModuleLength]; @@ -139,7 +116,7 @@ return hr; } else { - HANDLE fh = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr); + HANDLE fh = CreateFileW(wszFile, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr); if (fh == INVALID_HANDLE_VALUE) return HRESULT_FROM_WIN32(GetLastError()); if (!ReadFile(fh, module, ASAPInfo::maxModuleLength, reinterpret_cast(&module_len), nullptr)) { @@ -175,24 +152,26 @@ return S_OK; } + static LPSTR AllocString(const char *s, size_t len) + { + LPSTR result = static_cast(CoTaskMemAlloc(len + 1)); + if (result == nullptr) + return nullptr; + memcpy(result, s, len); + result[len] = '\0'; + return result; + } + static HRESULT GetString(PROPVARIANT *pvarData, const char *s) { - pvarData->vt = VT_BSTR; - // pvarData->bstrVal = A2BSTR(s); - just don't want dependency on ATL - int cch = MultiByteToWideChar(CP_ACP, 0, s, -1, nullptr, 0); - pvarData->bstrVal = SysAllocStringLen(nullptr, cch - 1); - if (pvarData->bstrVal == nullptr) - return E_OUTOFMEMORY; - if (MultiByteToWideChar(CP_ACP, 0, s, -1, pvarData->bstrVal, cch) <= 0) - return HRESULT_FROM_WIN32(GetLastError()); - return S_OK; + pvarData->vt = VT_LPSTR; + pvarData->pszVal = AllocString(s, strlen(s)); + return pvarData->pszVal == NULL ? E_OUTOFMEMORY : S_OK; } - HRESULT GetAuthors(PROPVARIANT *pvarData, bool vista) + HRESULT GetAuthors(PROPVARIANT *pvarData) { const char *author = m_info.getAuthor().data(); - if (!vista) - return GetString(pvarData, author); if (author[0] == '\0') { pvarData->vt = VT_EMPTY; return S_OK; @@ -216,11 +195,9 @@ for (i = 0; ; i++) { const char *e = strstr(s, " & "); size_t len = e != nullptr ? e - s : strlen(s); - pElems[i] = static_cast(CoTaskMemAlloc(len + 1)); + pElems[i] = AllocString(s, len); if (pElems[i] == nullptr) return E_OUTOFMEMORY; - memcpy(pElems[i], s, len); - pElems[i][len] = '\0'; if (e == nullptr) break; s = e + 3; @@ -228,7 +205,7 @@ return S_OK; } - HRESULT GetData(LPCSHCOLUMNID pscid, PROPVARIANT *pvarData, bool vista) + HRESULT GetData(LPCSHCOLUMNID pscid, PROPVARIANT *pvarData) { if (!m_hasInfo) return S_FALSE; @@ -237,11 +214,11 @@ if (pscid->pid == PIDSI_TITLE) return GetString(pvarData, m_info.getTitle().data()); if (pscid->pid == PIDSI_AUTHOR) - return GetAuthors(pvarData, vista); + return GetAuthors(pvarData); } else if (pscid->fmtid == FMTID_MUSIC) { if (pscid->pid == PIDSI_ARTIST) - return GetAuthors(pvarData, vista); + return GetAuthors(pvarData); if (pscid->pid == PIDSI_YEAR) { int year = m_info.getYear(); if (year < 0) { @@ -258,17 +235,9 @@ pvarData->vt = VT_EMPTY; return S_OK; } - if (g_windowsVer == WINDOWS_OLD) { - duration /= 1000; - char timeStr[16]; - sprintf(timeStr, "%02d:%02d:%02d", duration / 3600, duration / 60 % 60, duration % 60); - return GetString(pvarData, timeStr); - } - else { - pvarData->vt = VT_UI8; - pvarData->uhVal.QuadPart = 10000ULL * duration; - return S_OK; - } + pvarData->vt = VT_UI8; + pvarData->uhVal.QuadPart = 10000ULL * duration; + return S_OK; } if (pscid->pid == PIDASI_CHANNEL_COUNT) return GetInt(pvarData, m_info.getChannels()); @@ -354,22 +323,17 @@ STDMETHODIMP QueryInterface(REFIID riid, void **ppv) { - if (riid == IID_IUnknown || riid == IID_IColumnProvider) { - *ppv = static_cast(this); - AddRef(); - return S_OK; - } - if (riid == IID_IInitializeWithStream) { + if (riid == __uuidof(IUnknown) || riid == __uuidof(IInitializeWithStream)) { *ppv = static_cast(this); AddRef(); return S_OK; } - if (riid == IID_IPropertyStore) { + if (riid == __uuidof(IPropertyStore)) { *ppv = static_cast(this); AddRef(); return S_OK; } - if (riid == IID_IPropertyStoreCapabilities) { + if (riid == __uuidof(IPropertyStoreCapabilities)) { *ppv = static_cast(this); AddRef(); return S_OK; @@ -391,49 +355,6 @@ return r; } - // IColumnProvider - - STDMETHODIMP Initialize(LPCSHCOLUMNINIT psci) - { - return S_OK; - } - - STDMETHODIMP GetColumnInfo(DWORD dwIndex, SHCOLUMNINFO *psci) - { - if (dwIndex >= 0 && dwIndex < N_PROPERTYDEFS) { - g_propertyDefs[dwIndex].CopyTo(psci); - return S_OK; - } - return S_FALSE; - } - - STDMETHODIMP GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData) - { - if ((pscd->dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_OFFLINE)) != 0) - return S_FALSE; - char ext[4]; - for (int i = 0; i < 3; i++) { - WCHAR c = pscd->pwszExt[1 + i]; - if (c <= ' ' || c > 'z') - return S_FALSE; - ext[i] = static_cast(c); - } - if (pscd->pwszExt[5] != '\0') - return S_FALSE; - ext[3] = '\0'; - if (!ASAPInfo::isOurExt(ext)) - return S_FALSE; - - CMyLock lck(&m_lock); - if ((pscd->dwFlags & SHCDF_UPDATEITEM) != 0 || lstrcmpW(m_filename, pscd->wszFile) != 0) { - lstrcpyW(m_filename, pscd->wszFile); - HRESULT hr = LoadFile(pscd->wszFile, nullptr, STGM_READ); - if (FAILED(hr)) - return hr; - } - return GetData(pscid, reinterpret_cast(pvarData), false); - } - // IInitializeWithStream STDMETHODIMP Initialize(IStream *pstream, DWORD grfMode) @@ -442,7 +363,7 @@ HRESULT hr = pstream->Stat(&statstg, STATFLAG_DEFAULT); if (FAILED(hr)) return hr; - CMyLock lck(&m_lock); + CMyLock lck(m_lock); hr = LoadFile(statstg.pwcsName, pstream, grfMode); CoTaskMemFree(statstg.pwcsName); return hr; @@ -452,15 +373,15 @@ STDMETHODIMP GetCount(DWORD *cProps) { - CMyLock lck(&m_lock); - return m_hasInfo ? N_PROPERTYDEFS : 0; + CMyLock lck(m_lock); + return m_hasInfo ? ARRAYSIZE(g_scids) : 0; } STDMETHODIMP GetAt(DWORD iProp, PROPERTYKEY *pkey) { - CMyLock lck(&m_lock); - if (m_hasInfo && iProp >= 0 && iProp < N_PROPERTYDEFS) { - *pkey = g_propertyDefs[iProp].scid; + CMyLock lck(m_lock); + if (m_hasInfo && iProp >= 0 && iProp < ARRAYSIZE(g_scids)) { + *pkey = g_scids[iProp]; return S_OK; } return E_INVALIDARG; @@ -468,8 +389,8 @@ STDMETHODIMP GetValue(REFPROPERTYKEY key, PROPVARIANT *pv) { - CMyLock lck(&m_lock); - HRESULT hr = GetData(&key, pv, true); + CMyLock lck(m_lock); + HRESULT hr = GetData(&key, pv); if (hr == S_FALSE) { pv->vt = VT_EMPTY; return S_OK; @@ -479,7 +400,7 @@ STDMETHODIMP SetValue(REFPROPERTYKEY key, REFPROPVARIANT propvar) { - CMyLock lck(&m_lock); + CMyLock lck(m_lock); if (m_pstream == nullptr) return STG_E_ACCESSDENIED; if (key.fmtid == FMTID_SummaryInformation) { @@ -499,7 +420,7 @@ STDMETHODIMP Commit(void) { - CMyLock lck(&m_lock); + CMyLock lck(m_lock); if (m_pstream == nullptr) return STG_E_ACCESSDENIED; LARGE_INTEGER liZero; @@ -562,7 +483,7 @@ STDMETHODIMP QueryInterface(REFIID riid, void **ppv) { - if (riid == IID_IUnknown || riid == IID_IClassFactory) { + if (riid == __uuidof(IUnknown) || riid == __uuidof(IClassFactory)) { *ppv = static_cast(this); DllAddRef(); return S_OK; @@ -608,19 +529,8 @@ STDAPI_(BOOL) __declspec(dllexport) DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { - if (dwReason == DLL_PROCESS_ATTACH) { + if (dwReason == DLL_PROCESS_ATTACH) g_hDll = hInstance; - OSVERSIONINFO osvi; - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - GetVersionEx(&osvi); - g_windowsVer = WINDOWS_OLD; - if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) { - if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion >= 1) - g_windowsVer = WINDOWS_XP; - else if (osvi.dwMajorVersion >= 6) - g_windowsVer = WINDOWS_VISTA; - } - } return TRUE; } @@ -650,28 +560,21 @@ RegCloseKey(hk2); RegCloseKey(hk1); - if (g_windowsVer == WINDOWS_VISTA) { - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\PropertySystem\\PropertyHandlers", 0, KEY_WRITE, &hk1) != ERROR_SUCCESS) + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\PropertySystem\\PropertyHandlers", 0, KEY_WRITE, &hk1) != ERROR_SUCCESS) + return E_FAIL; + for (LPCSTR ext : extensions) { + if (RegCreateKeyEx(hk1, ext, 0, nullptr, 0, KEY_WRITE, nullptr, &hk2, nullptr) != ERROR_SUCCESS) { + RegCloseKey(hk1); return E_FAIL; - for (LPCSTR ext : extensions) { - if (RegCreateKeyEx(hk1, ext, 0, nullptr, 0, KEY_WRITE, nullptr, &hk2, nullptr) != ERROR_SUCCESS) { - RegCloseKey(hk1); - return E_FAIL; - } - if (RegSetString(hk2, nullptr, CLSID_ASAPMetadataHandler_str) != ERROR_SUCCESS) { - RegCloseKey(hk2); - RegCloseKey(hk1); - return E_FAIL; - } - RegCloseKey(hk2); } - RegCloseKey(hk1); - } - else { - if (RegCreateKeyEx(HKEY_CLASSES_ROOT, "Folder\\shellex\\ColumnHandlers\\" CLSID_ASAPMetadataHandler_str, 0, nullptr, 0, KEY_WRITE, nullptr, &hk1, nullptr) != ERROR_SUCCESS) + if (RegSetString(hk2, nullptr, CLSID_ASAPMetadataHandler_str) != ERROR_SUCCESS) { + RegCloseKey(hk2); + RegCloseKey(hk1); return E_FAIL; - RegCloseKey(hk1); + } + RegCloseKey(hk2); } + RegCloseKey(hk1); if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", 0, KEY_SET_VALUE, &hk1) != ERROR_SUCCESS) return E_FAIL; @@ -690,15 +593,11 @@ RegDeleteValue(hk1, CLSID_ASAPMetadataHandler_str); RegCloseKey(hk1); } - if (g_windowsVer == WINDOWS_VISTA) { - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\PropertySystem\\PropertyHandlers", 0, DELETE, &hk1) == ERROR_SUCCESS) { - for (LPCSTR ext : extensions) - RegDeleteKey(hk1, ext); - RegCloseKey(hk1); - } + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\PropertySystem\\PropertyHandlers", 0, DELETE, &hk1) == ERROR_SUCCESS) { + for (LPCSTR ext : extensions) + RegDeleteKey(hk1, ext); + RegCloseKey(hk1); } - else - RegDeleteKey(HKEY_CLASSES_ROOT, "Folder\\shellex\\ColumnHandlers\\" CLSID_ASAPMetadataHandler_str); RegDeleteKey(HKEY_CLASSES_ROOT, "CLSID\\" CLSID_ASAPMetadataHandler_str "\\InProcServer32"); RegDeleteKey(HKEY_CLASSES_ROOT, "CLSID\\" CLSID_ASAPMetadataHandler_str); return S_OK; diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/win32.mk kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/win32.mk --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/win32/win32.mk 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/win32/win32.mk 2013-05-31 22:59:22.000000000 +0000 @@ -21,6 +21,9 @@ CANDLE = $(DO)candle -nologo -o $@ LIGHT = $(DO)light -nologo -o $@ -spdb +# Code signing +DO_SIGN = $(DO)signtool sign -d "ASAP - Another Slight Atari Player $(VERSION)" -n "Open Source Developer, Piotr Fusik" -tr http://time.certum.pl -fd sha256 -td sha256 $^ && touch $@ + # no user-configurable paths below this line ifndef DO @@ -150,7 +153,7 @@ FOOBAR2000_RUNTIME = win32/foobar2000/foobar2000_SDK.lib win32/foobar2000/pfc.lib $(FOOBAR2000_SDK_DIR)/foobar2000/shared/shared.lib win32/foo_asap.dll: $(call src,win32/foobar2000/foo_asap.cpp asap.[ch] astil.[ch] aatr-stdio.[ch] aatr.h win32/info_dlg.[ch] win32/settings_dlg.[ch]) win32/foobar2000/foo_asap.res $(FOOBAR2000_RUNTIME) - $(WIN32_CL) -DFOOBAR2000 -DWIN32 -EHsc -I$(FOOBAR2000_SDK_DIR) comctl32.lib comdlg32.lib ole32.lib shlwapi.lib user32.lib $(WIN32_LINKOPT) + $(WIN32_CL) -DFOOBAR2000 -DWIN32 -EHsc -I$(FOOBAR2000_SDK_DIR) comctl32.lib comdlg32.lib ole32.lib shell32.lib shlwapi.lib user32.lib $(WIN32_LINKOPT) CLEAN += win32/foo_asap.dll win32/foo_asap.exp win32/foo_asap.lib win32/foobar2000/foobar2000_SDK.lib: $(patsubst %,win32/foobar2000/%.obj,component_client abort_callback audio_chunk audio_chunk_channel_config \ @@ -245,7 +248,7 @@ release/asap-$(VERSION)-win32.msi: win32/setup/asap.wixobj \ $(call src,win32/wasap/wasap.ico win32/setup/license.rtf win32/setup/asap-banner.jpg win32/setup/asap-dialog.jpg win32/diff-sap.js win32/shellex/ASAPShellEx.propdesc) \ - $(addprefix win32/,asapconv.exe sap2txt.exe wasap.exe in_asap.dll xmp-asap.dll bass_asap.dll apokeysnd.dll ASAPShellEx.dll foo_asap.dll libasap_plugin.dll) + $(addprefix win32/,asapconv.exe sap2txt.exe wasap.exe in_asap.dll xmp-asap.dll bass_asap.dll apokeysnd.dll ASAPShellEx.dll foo_asap.dll libasap_plugin.dll signed) $(LIGHT) -ext WixUIExtension -sice:ICE69 -b win32 -b release -b $(srcdir)win32/setup -b $(srcdir)win32 $< win32/setup/asap.wixobj: $(srcdir)win32/setup/asap.wxs release/release.mk @@ -254,9 +257,17 @@ release/asap-$(VERSION)-win64.msi: win32/x64/asap.wixobj \ $(call src,win32/wasap/wasap.ico win32/setup/license.rtf win32/setup/asap-banner.jpg win32/setup/asap-dialog.jpg win32/shellex/ASAPShellEx.propdesc) \ - win32/x64/ASAPShellEx.dll win32/x64/libasap_plugin.dll + win32/x64/ASAPShellEx.dll win32/x64/libasap_plugin.dll win32/signed $(LIGHT) -ext WixUIExtension -sice:ICE69 -b win32 -b $(srcdir)/win32/setup -b $(srcdir)win32 $< win32/x64/asap.wixobj: $(srcdir)win32/setup/asap.wxs release/release.mk $(CANDLE) -arch x64 -dVERSION=$(VERSION) $< CLEAN += win32/x64/asap.wixobj + +win32/signed: $(addprefix win32/,asapconv.exe sap2txt.exe wasap.exe in_asap.dll xmp-asap.dll bass_asap.dll apokeysnd.dll ASAPShellEx.dll foo_asap.dll libasap_plugin.dll x64/ASAPShellEx.dll x64/libasap_plugin.dll) + $(DO_SIGN) +CLEAN += win32/signed + +release/signed-msi: release/asap-$(VERSION)-win32.msi release/asap-$(VERSION)-win64.msi + $(DO_SIGN) +CLEAN += release/signed-msi diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/www/contact.xml kodi-audiodecoder-asap-20.3.0/lib/asap-code/www/contact.xml --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/www/contact.xml 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/www/contact.xml 2013-05-31 22:59:22.000000000 +0000 @@ -18,7 +18,7 @@ Windows CE testing. macOS releases. Suggested CMS, CM3, DLT and STIL format support. - Windows Mobile setup. Thorough testing. + Windows Mobile setup. Thorough testing. 6502 routine for playing CMS. 6502 routine for playing RMT. Testing. Windows icons. Testing. @@ -43,7 +43,7 @@

ASAP is based on a development library, which you can easily embed in your program. It's not only a fully portable C library - it's better than that. Since ASAP is written in Ć, - it is also available as pure C++, Java, C# and JavaScript.

+ it is also available as pure C++, C#, Java, JavaScript, Python, Swift and OpenCL.

Third-party projects using ASAP

The following projects already use ASAP:

@@ -59,5 +59,7 @@
  • mxPlay - audio player for Atari Falcon
  • enotracker - POKEY music editor for PC
  • WebAsap - HTML5/WebAudio port
  • +
  • ZXTune - AY/SID/POKEY player for Windows/macOS/Linux/Android
  • +
  • POKEY2MIDI - converter to MIDI music files
  • diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/www/index.xml kodi-audiodecoder-asap-20.3.0/lib/asap-code/www/index.xml --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/www/index.xml 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/www/index.xml 2013-05-31 22:59:22.000000000 +0000 @@ -33,7 +33,7 @@
    CMS (Stereo Double CMC)
    Stereo CMC.
    -
    DMC (DoublePlay CMC)
    +
    DMC (CMC DoublePlay)
    CMC with 6502 routine executed at double rate of the original CMC.
    DLT (Delta Music Composer)
    diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/www/linux.xml kodi-audiodecoder-asap-20.3.0/lib/asap-code/www/linux.xml --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/www/linux.xml 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/www/linux.xml 2013-05-31 22:59:22.000000000 +0000 @@ -4,23 +4,21 @@ via apt, not snap. Then install the ASAP plugin: .

    On Fedora, install VLC from RPM Fusion, - then install the 64-bit ASAP plugin: - or the 32-bit plugin: .

    + then install the ASAP plugin: .

    VLC on Ubuntu 18.04 -

    XMMS

    -

    XMMS is included in Fedora. - Install the 64-bit ASAP plugin: - or the 32-bit one: .

    - XMMS on Fedora 29 +

    XMMS2

    +

    On Ubuntu install XMMS2 with sudo apt install xmms2. + Then install the ASAP plugin: .

    +

    On Fedora: sudo dnf install xmms2, + then .

    Command-line converter

    For command-line conversions (e.g. to WAV or between SAP and the tracker formats) and editing the metadata, install asapconv:

      -
    • on Ubuntu 64-bit:
    • -
    • on Fedora 64-bit:
    • -
    • on Fedora 32-bit:
    • +
    • on Ubuntu:
    • +
    • on Fedora:

    Run asapconv from the shell to see the syntax. The -o/--output option selects the output format and is mandatory.

    diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/www/news.xml kodi-audiodecoder-asap-20.3.0/lib/asap-code/www/news.xml --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/www/news.xml 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/www/news.xml 2013-05-31 22:59:22.000000000 +0000 @@ -1,4 +1,34 @@  + +
      +
    • Fixed "click" glitches. Depending on the music and the ASAP port, they were happening every dozen of seconds or not at all.
    • +
    • New XMMS2 plugin. Ubuntu and Fedora packages available.
    • +
    • Experimental OpenCL port running on a GPU.
    • +
    • Limited memory allocations on loading SAP files.
    • +
    • foobar2000: Minor stability fix for parallel conversions.
    • +
    +
      +
    • Control playback from the notification and the lock screen.
    • +
    • Voice assistant commands (such as "pause playback", "next song", "seek to one minute"). No voice search yet.
    • +
    • Display metadata and control playback via Bluetooth.
    • +
    • Pause playback on Bluetooth disconnected.
    • +
    • Requires Android 5.0+.
    • +
    +
    + + +
      +
    • Android app includes the current state of the ASMA repo.
    • +
    • Fixed memory leaks in the BASS/AIMP plugin.
    • +
    • Unicode filenames in Windows Explorer.
    • +
    • Fixed "-ft" in chksap.
    • +
    • SAP filenames shown in TortoiseMerge.
    • +
    • Windows and macOS binaries are now signed.
    • +
    • Pure Python and Swift ASAP2WAVs.
    • +
    • JavaScript ASAP2WAV supports Node.js, drops support for DMDScript and JScript.
    • +
    +
    +
    • Android: search by author.
    • diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/www/sap-format.xml kodi-audiodecoder-asap-20.3.0/lib/asap-code/www/sap-format.xml --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/www/sap-format.xml 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/www/sap-format.xml 2013-05-31 22:59:22.000000000 +0000 @@ -6,26 +6,26 @@

      History

      The SAP format was created in the 1990s by Adam Bienias and was intended to be a single format for playback of any Atari 8-bit music on a PC. - Adam Bienias has also created the first player for Windows and later published - its source code. Later, other players appeared.

      + Adam Bienias has also created the first SAP player for Windows and later published + its source code. Other players appeared later.

      At the time of writing, there are five independent players of the SAP format:

      There are many players for different platforms based on either SAP, ASAP or GME. sapemu runs on an Atari XL/XE computer with 128 KB RAM. Windows-only Altirra is a full emulator of Atari computers.

      Concept

      -

      A SAP file contains the original data and code used to playback +

      A SAP file contains the original data and code needed to playback the Atari music. SAP players actually run the program contained in the SAP file on an emulated or real (sapemu) 6502 processor. The program controls the POKEY chip, which generates the sound.

      -

      Identical method is used with platforms other than Atari, +

      Same method is used with platforms other than Atari, for example the SID format for the C64 music.

      Important advantages of SAP over, let's say, MP3, are that SAP files are very small (about 5 KB on average) and play more accurately @@ -47,14 +47,13 @@

      copy /b music.txt+music.bin music.sap

      On macOS or Linux:

      cat music.txt music.bin >music.sap
      +

      Part One - Text Header

      This part consists of tags, one tag per line. - Lines must be terminated by a CR/LF (0x0d, 0x0a) pair - (required by ASAP, other SAP players may be more forgiving). + Lines should be terminated by a CR/LF (0x0d, 0x0a) pair. A line consists of an uppercase tag name and an optional argument, which might be a string, a decimal integer, a hexadecimal integer or a single letter. - The argument should be separated from the tag name with a single space. - Extra whitespace is not allowed.

      + The argument should be separated from the tag name with a single space.

      Example header:

      SAP
       AUTHOR "Jakub Husak"
      @@ -74,7 +73,7 @@
       	described below. Note that format identification in GStreamer
       	requires the AUTHOR tag to be placed right after the "SAP" line.

      The arguments to AUTHOR, NAME and DATE must be wrapped in doublequotes. - Allowed are characters identical in ASCII and ATASCII (ATari ASCII), + Allowed are characters which are identical in ASCII and ATASCII (ATari ASCII), that is: characters with ASCII codes from space to underscore plus all lowercase letters, plus the pipe character (`|`). There are no backquote, tilde nor curly brackets in ATASCII. @@ -120,7 +119,7 @@ DATE "23-26/07/2010" DATE "199?"

      -

      The last example means some unknown year in the 1990s.

      +

      The last example represents some unknown year in the 1990s.

      SONG
      @@ -128,7 +127,7 @@

      Number of subsongs in the file. If there is just one song in the file, it is recommended to omit this tag. ASAP currently limits the number of songs to 32. - The biggest known number of songs in a SAP file is 20.

      + The largest known number of songs in a SAP file is 20.

      DEFSONG
      @@ -162,10 +161,10 @@ A scanline is defined to be 114 Atari clock cycles. FASTPLAY defaults to one frame: 312 scanlines for PAL (about 50 Hz), 262 for NTSC (about 60 Hz). - Most songs don't include this tag. Common values are 156 (twice per frame), + Most songs don't include this tag. Common values are 156 (twice per PAL frame), 104 (three times per frame) and 78 (four times per frame). ASAP 3.0.0 and above supports FASTPLAY up to 32767. - Other SAP players may limit the value to 312.

      + Other SAP players may limit FASTPLAY to 312.

      INIT
      @@ -184,7 +183,8 @@
      PLAYER
      -

      Hexadecimal address of the player routine.

      +

      Hexadecimal address of the player routine, + called with constant frequency (see FASTPLAY).

      COVOX
      @@ -198,11 +198,10 @@
      TIME

      Song duration in minutes, seconds and milliseconds. - In files with subsongs the TIME tag may occur many times - (for example 3 times if there are 3 subsongs). + In files with subsongs, there is a separate TIME tag for every subsong. The optional LOOP modifier specifies that the song starts looping endlessly - at the specified moment. - The exact format of the argument is:

      + at the specified moment. Players can decide to play such songs longer. + The exact format of the TIME argument is:

      • one or two digits specifying minutes
      • colon
      • @@ -215,36 +214,41 @@

        Part Two - Binary Data

        This part contains the player routine and music data in the format - of Atari executables. This format has a two-byte header 0xFF, 0xFF. - The two following bytes are the starting memory address (little endian), - the next two bytes are the ending address. Then data bytes follow. - Usually there are multiple blocks - (the 0xFF/0xFF header is only required for the first block).

        -

        The difference from real Atari executables is that - INIT (0x2e2) and RUN (0x2e0) blocks are not supported.

        + of Atari executables. An Atari executable is a sequence of one or more blocks. + Every block consists of:

        +
          +
        • a header of two 0xFF bytes (required for the first block, + optional for subsequent blocks)
        • +
        • memory address where to load the data (16-bit little endian)
        • +
        • memory address of the last byte (16-bit little endian)
        • +
        • the data of the block (end_address-start_address+1 bytes)
        • +
        +

        In real Atari executables memory locations 0x2E0-0x2E1 specify the run address + and 0x2E2-0x2E3 specify the initialization address. + In the SAP format these are ordinary RAM locations with no special treatment.

        Example:

        ------------------------------------------------------------- FF FF - executable header (always 0xFF, 0xFF) 00 06 - start address of the first block (0x0600) -01 06 - end address of the first block (0x0601) -AB CD - first block data - loaded to 0x0600-0x0601 +02 06 - end address of the first block (0x0602) +AB CD EF - first block data - loaded to 0x0600-0x0602 25 20 - start address of the second block (0x2025) 27 20 - end address of the second block (0x2027) 01 42 A3 - second block data - loaded to 0x2025-0x2027 ------------------------------------------------------------- -

        Some players allow files that end in the middle of start/end address - or block data, but this practice is strongly discouraged.

        +

        Files that end in the middle of block data or the addresses are considered + malformed, but some SAP players accept them.

        Execution Model

        The program from the SAP file is run inside an emulated machine that resembles a limited Atari computer with the 6502 processor, POKEY chip - (at least the audio part) and 64 KB RAM. + (at least its audio part) and 64 KB RAM. Some players include emulation of parts of the ANTIC and GTIA chips, as well as the COVOX expansion. Since SAP files may be played on a real Atari, they should be prepared for the possibility that all the original hardware is present - (for example PIA doing the bankswitching).

        + (for example PIA controlling memory bank switching).

        Memory map:

        0000-CFFF
        @@ -253,7 +257,7 @@
        D000-D0FF
        GTIA chip mirrored 8 times. ASAP maps just the PAL register at D014 for NTSC/PAL detection - and the CONSOL register at D01F for 1-bit sounds, the rest is RAM.
        + and the CONSOL register at D01F for 1-bit sounds. The rest is RAM.
      D100-D1FF
      reserved for parallel devices connected to the Atari, do not use.
      @@ -285,7 +289,7 @@ 0 and 3 for the left channel, 1 and 2 for the right channel.
      D700-D7FF
      -
      reserved for expansions, do not use.
      +
      reserved for hardware expansions, do not use.
      D800-FFFF
      RAM. @@ -294,7 +298,7 @@

      Timing: The main clock is 1773447 Hz (PAL) or 1789772.5 Hz (NTSC). There are 114 cycles per scanline, including 9 cycles memory refresh - not available for the 6502 - same as in the Atari with ANTIC DMA + when the 6502 is stalled - same as in the Atari with ANTIC DMA disabled completely. WSYNC and VCOUNT registers may be used for delays. Note that some SAP players reset VCOUNT at the FASTPLAY rate instead of every frame.

      @@ -312,7 +316,8 @@
      TYPE C
      -

      This type is for easy handling CMC (Chaos Music Composer) music. +

      This type is for convenient handling of music + composed in CMC (Chaos Music Composer) . The player is initialized using the values of MUSIC, PLAYER and DEFSONG as follows:

          lda #$70
      @@ -338,15 +343,15 @@
       		(which is initially clear, allowing interrupts).
       		All 6502 registers (including flags) are saved when calling the PLAYER routine
       		and restored when it returns to the running INIT routine.
      -		In ASAP 2.1.0 and above the PLAYER tag is optional for TYPE D.
      -		First call to PLAYER usually occurs before the first instruction of INIT
      -		gets executed.
      + In ASAP 2.1.0 and above, the PLAYER tag is optional for TYPE D. + Beware that in many players the first call to PLAYER usually occurs + before the first instruction of INIT gets executed.
      TYPE S
      -
      This is a convenience type for SoftSynth music. +
      This is a convenience type for music composed in SoftSynth. The PLAYER tag is not used here, yet some SAP players require it. Instead of the PLAYER routine, at FASTPLAY intervals the memory location 0x45 - is decreased and if reaches zero, the memory location 0xb07b is increased. + is decreased and if reaches zero, the memory location 0xB07B is increased. The default FASTPLAY for this type is 78.
      TYPE R
      diff -Nru kodi-audiodecoder-asap-20.2.0/lib/asap-code/www/web.xml kodi-audiodecoder-asap-20.3.0/lib/asap-code/www/web.xml --- kodi-audiodecoder-asap-20.2.0/lib/asap-code/www/web.xml 2013-05-31 22:59:22.000000000 +0000 +++ kodi-audiodecoder-asap-20.3.0/lib/asap-code/www/web.xml 2013-05-31 22:59:22.000000000 +0000 @@ -4,7 +4,7 @@