├── .editorconfig ├── .github └── workflows │ └── ccpp.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── TODO ├── default.nix ├── docs ├── common.txt ├── ff.txt ├── p.txt ├── p86.txt ├── pmb.txt ├── pmd.txt ├── ppc.txt ├── pps.txt ├── pvi.txt └── pzi.txt ├── examples ├── .editorconfig ├── M │ ├── PAT1_P.M2 │ └── RAVETUNE.M2 ├── MML │ └── ravetune.mml └── P86 │ └── RC1.P86 ├── include ├── CMakeLists.txt ├── common.h ├── p86.h └── ppc.h ├── shell.nix ├── src ├── CMakeLists.txt ├── common.c └── p86.c ├── tools ├── CMakeLists.txt ├── loadtest.c ├── p86create.c └── p86extract.c └── watcom.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | max_line_length = 120 10 | 11 | [{Makefile,**.c,**.h}] 12 | indent_style = tab 13 | indent_size = 2 14 | 15 | [**.nix] 16 | indent_style = space 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /.github/workflows/ccpp.yml: -------------------------------------------------------------------------------- 1 | name: Project CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | defaults: 12 | run: 13 | shell: bash 14 | 15 | jobs: 16 | normal: 17 | strategy: 18 | matrix: 19 | config: 20 | - { name: 'Windows', os: windows-latest } 21 | - { name: 'macOS', os: macos-latest } 22 | - { name: 'Ubuntu', os: ubuntu-latest } 23 | fail-fast: false 24 | 25 | name: ${{ matrix.config.name }} 26 | runs-on: ${{ matrix.config.os }} 27 | 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v2 31 | 32 | - name: Configure 33 | run: cmake -B ${PWD}/build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${PWD}/install 34 | 35 | - name: Build 36 | run: cmake --build ${PWD}/build --config Debug --parallel 2 37 | 38 | - name: Install 39 | run: cmake --install ${PWD}/build --config Debug 40 | 41 | - name: Test 42 | run: | 43 | export PATH=${PWD}/install/bin:${PATH} 44 | if [ '${{ matrix.config.name }}' == 'Windows' ]; then 45 | function print_hash() { 46 | CertUtil -hashfile "$1" 47 | } 48 | elif [ '${{ matrix.config.name }}' == 'macOS' ]; then 49 | function print_hash() { 50 | shasum -a 256 "$1" 51 | } 52 | else 53 | function print_hash() { 54 | sha256sum "$1" 55 | } 56 | fi 57 | export -f print_hash 58 | 59 | mkdir test 60 | cd test 61 | 62 | loadtest ../examples/P86/RC1.P86 63 | mv TEST_MEM.P86 LOADTEST_MEM.P86 64 | mv TEST_FIL.P86 LOADTEST_FIL.P86 65 | 66 | p86extract LOADTEST_MEM.P86 67 | p86create TEST_*.RAW 68 | mv TEST.P86 P86CREATE.P86 69 | 70 | print_hash ../examples/P86/RC1.P86 71 | print_hash LOADTEST_MEM.P86 72 | print_hash LOADTEST_FIL.P86 73 | print_hash P86CREATE.P86 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | libopenpmd 2 | 3 | local/ 4 | build/ 5 | 6 | *.swp 7 | 8 | ## 9 | 10 | # Prerequisites 11 | *.d 12 | 13 | # Object files 14 | *.o 15 | *.ko 16 | *.obj 17 | *.elf 18 | 19 | # Linker output 20 | *.ilk 21 | *.map 22 | *.exp 23 | 24 | # Precompiled Headers 25 | *.gch 26 | *.pch 27 | 28 | # Libraries 29 | *.lib 30 | *.a 31 | *.la 32 | *.lo 33 | 34 | # Shared objects (inc. Windows DLLs) 35 | *.dll 36 | *.so 37 | *.so.* 38 | *.dylib 39 | 40 | # Executables 41 | *.exe 42 | *.out 43 | *.app 44 | *.i*86 45 | *.x86_64 46 | *.hex 47 | 48 | # Debug files 49 | *.dSYM/ 50 | *.su 51 | *.idb 52 | *.pdb 53 | 54 | # Kernel Module Compile Results 55 | *.mod* 56 | *.cmd 57 | .tmp_versions/ 58 | modules.order 59 | Module.symvers 60 | Mkfile.old 61 | dkms.conf 62 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/adpcm"] 2 | path = submodules/adpcm 3 | url = https://github.com/superctr/adpcm 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.0) 2 | project (libopenpmd C) 3 | 4 | if (${CMAKE_VERSION} VERSION_GREATER "3.0.2") 5 | cmake_policy (SET CMP0054 NEW) 6 | endif () 7 | 8 | set (CMAKE_C_STANDARD 90) 9 | set (CMAKE_C_STANDARD_REQUIRED TRUE) 10 | set (CMAKE_C_EXTENSIONS OFF) 11 | 12 | if (MSVC) 13 | # warning level 4 and all warnings as errors 14 | # add_compile_options (/W4 /WX) 15 | # Shut up MSVC about its "better", non-portable functions 16 | add_definitions (/D_CRT_SECURE_NO_WARNINGS) 17 | elseif (${CMAKE_C_COMPILER_ID} MATCHES "GNU|\\.*Clang|\\.*LLVM") 18 | # lots of warnings and all warnings as errors 19 | add_compile_options (-Wall -Wextra -pedantic -Werror -pedantic-errors) 20 | endif () 21 | 22 | if (NOT CMAKE_BUILD_TYPE) 23 | set (CMAKE_BUILD_TYPE Debug) 24 | endif () 25 | string (TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_UPPER) 26 | 27 | if (CMAKE_BUILD_TYPE_UPPER STREQUAL "DEBUG") 28 | if (MSVC) 29 | add_definitions (/DDEBUG) 30 | else () 31 | add_definitions (-DDEBUG) 32 | endif () 33 | endif () 34 | 35 | if (CMAKE_SYSTEM_NAME STREQUAL "DOS" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "I86") 36 | set (PREFER_LOWMEM TRUE) 37 | else () 38 | set (PREFER_LOWMEM FALSE) 39 | endif () 40 | option (USE_LOWMEM 41 | "Use code that's better suited for low-memory environments" 42 | ${PREFER_LOWMEM} 43 | ) 44 | message (STATUS "USE_LOWMEM: ${USE_LOWMEM}") 45 | 46 | if (USE_LOWMEM) 47 | add_definitions (-DUSE_LOWMEM) 48 | endif () 49 | 50 | include (GNUInstallDirs) 51 | 52 | add_subdirectory (include) 53 | add_subdirectory (src) 54 | add_subdirectory (tools) 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libopenpmd 2 | 3 | A WIP project to write an open-source C library capable of parsing both the MML and the compiled versions of the PMD 4 | sound driver's music files. 5 | 6 | The aim is to write a stripped-down, cross-platform - Windows, macOS, Linux and DOS on actual PC-98s - library for 7 | parsing and handling PMD files. This goal is being approached by doing a from-the-ground-up, hopefully clean effort to 8 | understand and document the binary formats based on my own experiments with the official manual and compiler with the 9 | help of prior documentation efforts. 10 | 11 | ## Roadmap 12 | 13 | - [ ] - Not started 14 | - [D] - Not working 15 | - [C] - Can fully parse existing file 16 | - [B] - Can properly modify & overwrite existing file 17 | - [A] - Can generate file from scratch, either empty or from supplied data 18 | - [x] - Implemented to a satisfactory degree 19 | 20 | --- 21 | 22 | * [ ] Handle PMD-related bank formats 23 | * [A] P86 24 | * [ ] PPS 25 | * [ ] PPC 26 | * [ ] PVI 27 | * [ ] PZI 28 | * [ ] FF 29 | * ... Other, non-PC-98 bank formats 30 | * [ ] Handle PC-98 PMD modules 31 | * [ ] Handle PC-98 MML files 32 | * -> A PC-98 PMD compiler (reasonably accurate to the original one) & decompiler 33 | 34 | ### Further ideas 35 | 36 | * Support for non-PC-98 module & MML formats? 37 | * IBM PC 38 | * FM Towns 39 | * X68000 40 | * PX 41 | * An expanded PMD syntax & custom PMD version? 42 | * A PMD module space optimiser? (e.g. loop analysis & unrolling) 43 | * A new PMD player??? 44 | 45 | ## Current status 46 | 47 | I'm redoing the codebase. I'll start with smaller, more urgent goals like the various sample / patch bank formats to get 48 | a better feel for the language & platform limitations. 49 | 50 | Currently writing code to parse, load, create & modify P86 banks. Figuring out what the API should look like as I go, 51 | depending on what I need to implement everything to a satisfactory degree. From there it's just a matter of copy-pasting 52 | function declarations & reimplementing them according to the formats, at least for the simple banks. 53 | 54 | ## Credits 55 | 56 | - [@kajaponn], for writing the original PMD driver and all of the original tools. 57 | - [@ValleyBell], for their [documentation of the PMD format]. 58 | - [@AnhanLi], for inspiring me with their [format analysis earlier this year] to pursue this project. 59 | - [@NoyemiK], for her .M(2) file that I may include as an example. 60 | - [@Blargzargo], for his independent [documentation of PMD and MML]. 61 | - [@Ravancloak], for her example P86 bank. 62 | 63 | [@kajaponn]: https://twitter.com/kajaponn 64 | [@ValleyBell]: https://github.com/ValleyBell 65 | [@AnhanLi]: https://twitter.com/AnhanLi 66 | [@NoyemiK]: https://github.com/NoyemiK 67 | [@Blargzargo]: https://www.youtube.com/channel/UCDZR3q3anQ9boE6IAvorz8Q 68 | [@Ravancloak]: https://ravancloak.bandcamp.com/ 69 | 70 | [documentation of the PMD format]: https://raw.githubusercontent.com/ValleyBell/MidiConverters/master/pmd_SeqFormat.txt 71 | [format analysis earlier this year]: https://lithcore.cn/2318 72 | [documentation of PMD and MML]: https://pastebin.com/raw/FP5q8zgC 73 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | todo: 2 | - rewrite P86_{Im,Ex}portFile to be cleaner 3 | - implement p86_struct P86_ImportData 4 | - implement boolean P86_ExportData 5 | - stdint.h wrapper: use if available, fallback header with typedefs if fully C89-compliant (== no stdint.h) 6 | - CMake code for ADPCM library (just ADPCM-B part for encoding/decoding PPC sample data) 7 | 8 | maybe: 9 | - make file loading code 16-bit real mode compatible (reference files / file offsets instead of loading everything?) 10 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import { }; 3 | in { 4 | default = pkgs.callPackage ( 5 | 6 | { stdenv 7 | , lib 8 | , cmake 9 | , toilet 10 | , valgrind 11 | }: 12 | 13 | stdenv.mkDerivation rec { 14 | pname = "libopenpmd"; 15 | version = "0.0.0-local"; 16 | 17 | src = ./.; 18 | 19 | postUnpack = '' 20 | rm -rf $sourceRoot/{.git,build}/ 21 | ''; 22 | 23 | nativeBuildInputs = [ cmake toilet valgrind ]; 24 | 25 | cmakeBuildType = "Debug"; 26 | 27 | cmakeFlags = [ 28 | # "-DUSE_LOWMEM=ON" 29 | ]; 30 | 31 | doCheck = true; 32 | 33 | checkPhase = '' 34 | print() { 35 | toilet -f wideterm -F border $* 36 | } 37 | run_test() { 38 | valgrind --leak-check=full --track-origins=yes -s $* 2>&1 | tee log 39 | grep -q 'ERROR SUMMARY: 0 errors' log || exit 1 40 | } 41 | compare_two_hashes() { 42 | hash1="$(sha256sum "$1")" 43 | hash2="$(sha256sum "$2")" 44 | 45 | echo $hash1 46 | echo $hash2 47 | if [[ "$(echo "$hash1" | cut -d' ' -f1)" != "$(echo "$hash2" | cut -d' ' -f1)" ]]; then 48 | echo "Hashes don't match!" 49 | exit 1 50 | fi 51 | } 52 | 53 | print LOADTEST 54 | run_test ./tools/loadtest ../examples/P86/RC1.P86 55 | 56 | print COMPARING ORIGINAL AGAINST EXPORT TESTS 57 | compare_two_hashes ../examples/P86/RC1.P86 TEST_MEM.P86 58 | compare_two_hashes ../examples/P86/RC1.P86 TEST_FIL.P86 59 | 60 | print P86EXTRACT 61 | run_test ./tools/p86extract TEST_MEM.P86 62 | 63 | print P86CREATE 64 | run_test ./tools/p86create TEST_***.RAW 65 | 66 | # print COMPARING HASHES 67 | # compare_two_hashes TEST_MEM.P86 TEST.P86 68 | ''; 69 | } 70 | 71 | ) { }; 72 | } 73 | -------------------------------------------------------------------------------- /docs/common.txt: -------------------------------------------------------------------------------- 1 | ======================================== 2 | Common 3 | ======================================== 4 | 5 | ADPCM RAM address -> file offset to sample data: 6 | (address_this - address_start) [address samples start being written to, 7 | usually preceeded by bank identification data] 8 | * 0x20 [1 "address" corresponds to 32b of ADPCM data] 9 | + file_header_size 10 | 11 | -------------------------------------------------------------------------------- /docs/ff.txt: -------------------------------------------------------------------------------- 1 | ======================================= 2 | Body 3 | ======================================= 4 | 5 | 0x0000 1..256 * { 6 | 7 | 25B - Instrument Register Data. refer to YM2608 manual, "2-2-2: Parameters and channel registers: $30-$B6" 8 | - 4x DT/MULTI 9 | - 4x TL 10 | - 4x KS/AR 11 | - 4x AM/DR 12 | - 4x SR 13 | - 4x SL/RR 14 | - FB/Algorithm 15 | 7B - Instrument Label 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /docs/p.txt: -------------------------------------------------------------------------------- 1 | ======================================= 2 | Header 3 | ======================================= 4 | 5 | 0x0000 - 0x03FF 256 * { 6 | Sample start (uint32_t) 7 | - 0x00000000 = unused 8 | } 9 | 10 | 11 | ======================================= 12 | Body 13 | ======================================= 14 | 15 | Stream of Sample Data { 16 | 17 | MSM6258 ADPCM encoding 18 | nibble-swapped VOX / Dialogic ADPCM 19 | Mono 20 | Sample rate? 21 | 16000Hz seems fine 22 | 23 | } 24 | -------------------------------------------------------------------------------- /docs/p86.txt: -------------------------------------------------------------------------------- 1 | ======================================= 2 | Header 3 | ======================================= 4 | 5 | 0x0000 Identifier (12b) 6 | "PCM86 DATA(\n)(\0)" 7 | 0x000C Targeted P86DRV version (1b) 8 | version . 9 | 0x000D File Length (3b) 10 | 0x0010 - 0x060F 256 * { 11 | 12 | Pointer to Sample Data Start (3b) 13 | Length of Sample Data (3b) 14 | 15 | (0x000000 0x000000 -> no sample for this instrument ID) 16 | 17 | } 18 | 19 | 20 | ======================================= 21 | Body 22 | ======================================= 23 | 24 | Stream of Sample Data { 25 | 26 | 8-Bit Signed 27 | Mono 28 | 16540Hz 29 | (above sample rate according to KAJA's documentation 30 | any sample rate possible, for different base note & octave) 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /docs/pmb.txt: -------------------------------------------------------------------------------- 1 | ======================================= 2 | Header 3 | ======================================= 4 | 5 | 0x0000 - 0x0007 Bank name? (8b) 6 | - "xenon" (\0-padding) in Xenon's bank 7 | - all \0's in Desire's banks 8 | 0x0008 - 0x1007 32 * { 9 | Sample name? (16b) 10 | - maybe 8b name, 8b padding - see body blocks for reasoning 11 | ??? (16b) 12 | - 0x007f pattern in Xenon & most of Desire 13 | - weird, steadily rising values in the first entry of Desire's *3* PMB banks 14 | Sample id? (32b) 15 | - maybe 1b ID, 31b padding 16 | - ID part 0 when entry unset 17 | - 0x00 padding in Xenon & some parts of Desire 18 | - 0x000000 in some of Desire's banks 19 | - 0x000000 in some of Desire's banks' first entries 20 | ??? (64b) 21 | - 0x007f 0x7f00 0x007f 0x0000 pattern in Xenon 22 | - 0x007f 0x607e 0x407e 0x0000 pattern in Desire 23 | } 24 | 25 | 26 | ======================================= 27 | Body 28 | ======================================= 29 | 30 | for every set sample from header { 31 | Sample name (8b) 32 | Sample id (4b) 33 | Sample length (4b) 34 | ??? (16b) 35 | - 0x0000 0x0000 0x0000 0x0000 0x0620 0x0000 0x003c 0x0000 in Xenon's bank 36 | - 0x1090 0x0000 0x0001 0x0000 0x03f5 0x0000 0x0024 0x0000 in Desire bank 2, first sample (0x1008) 37 | - 0x1579 0x0000 0x0001 0x0000 0x07a8 0xfc14 0x0028 0x0000 in Desire bank 2, second sample (0x20b9) 38 | - 0x184d 0x0000 0x0001 0x0000 0x07a8 0x0000 0x003c 0x0000 in Desire bank 2, third sample (0x3653) 39 | 40 | Stream of Sample Data { 41 | 42 | RF5C68 encoding (refer to manual, section "Example of waveform data format") 43 | - 8-bit 44 | - 0x00 -> (unused) 45 | - 0x01 - 0x7F -> -1 - -127 (negative amplitude peak) 46 | - 0x80 -> 0 (middle point) 47 | - 0x81 - 0xFE -> 1 - 127 (positive amplitude peak) 48 | - 0xFF -> ("loop stop data": jump to loop start point) 49 | Mono 50 | Sample rate? 51 | - the usual 16000Hz seems close enough 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/pmd.txt: -------------------------------------------------------------------------------- 1 | ======================================= 2 | Header 3 | ======================================= 4 | 5 | (offsets relative to version byte, +0x01) 6 | 7 | 0x0000 Version (1b) { 8 | 9 | 0x00 = NEC PC-98 / Fujitsu FM Towns 10 | 0x01 = Sharp X68000 11 | 0x02 = IBM PC/AT 12 | 13 | } 14 | 15 | NEC PC-98 { 16 | 17 | 0x0001 Offset FM1 Sequence Data (1h) 18 | 0x0003 Offset FM2 Sequence Data (1h) 19 | 0x0005 Offset FM3 Sequence Data (1h) 20 | 0x0007 Offset FM4 Sequence Data (1h) 21 | 0x0009 Offset FM5 Sequence Data (1h) 22 | 0x000B Offset FM6 Sequence Data (1h) 23 | 0x000D Offset SSG1 Sequence Data (1h) 24 | 0x000F Offset SSG2 Sequence Data (1h) 25 | 0x0011 Offset SSG3 Sequence Data (1h) 26 | 0x0013 Offset ADPCM Sequence Data (1h) 27 | 0x0015 Offset Rhythm Sequence Data (1h) 28 | 0x0017 Offset Rhythm Pattern Data (1h) 29 | 0x0019 Offset Instrument Data (1h) 30 | 31 | } Fujitsu FM Towns { 32 | 33 | 0x0001 Offset FM1 Sequence Data (1h) 34 | 0x0003 Offset FM2 Sequence Data (1h) 35 | 0x0005 Offset FM3 Sequence Data (1h) 36 | 0x0007 Offset FM4 Sequence Data (1h) 37 | 0x0009 Offset FM5 Sequence Data (1h) 38 | 0x000B Offset FM6 Sequence Data (1h) 39 | 0x000D Offset Dummy Sequence Data (1h) 40 | 0x000F Offset Dummy Sequence Data (1h) 41 | 0x0011 Offset Dummy Sequence Data (1h) 42 | 0x0013 Offset PCM1 Sequence Data (1h) 43 | 0x0015 Offset PCM2 Sequence Data (1h) 44 | 0x0017 Offset Dummy Pattern Data (for extra data section) (1h) 45 | 0x0019 Offset Instrument Data (1h) 46 | 47 | } Sharp X68000 { 48 | 49 | 0x0001 Offset FM1 Sequence Data (1h) 50 | 0x0003 Offset FM2 Sequence Data (1h) 51 | 0x0005 Offset FM3 Sequence Data (1h) 52 | 0x0007 Offset FM4 Sequence Data (1h) 53 | 0x0009 Offset FM5 Sequence Data (1h) 54 | 0x000B Offset FM6 Sequence Data (1h) 55 | 0x000D Offset FM7 Sequence Data (1h) 56 | 0x000F Offset FM8 Sequence Data (1h) 57 | 0x0011 Offset Dummy Sequence Data (1h) 58 | 0x0013 Offset PCM Sequence Data (1h) 59 | 0x0015 Offset Dummy Sequence Data (1h) 60 | 0x0017 Offset Dummy Rhythm Pattern Data (offset to next short) (1h) 61 | 0x0019 Offset Instrument Data (1h) 62 | 63 | } IBM PC/AT { 64 | 65 | 0x0001 Offset FM1 Sequence Data (1h) 66 | 0x0003 Offset FM2 Sequence Data (1h) 67 | 0x0005 Offset FM3 Sequence Data (1h) 68 | 0x0007 Offset FM4 Sequence Data (1h) 69 | 0x0009 Offset FM5 Sequence Data (1h) 70 | 0x000B Offset FM6 Sequence Data (1h) 71 | 0x000D Offset FM7 Sequence Data (1h) 72 | 0x000F Offset FM8 Sequence Data (1h) 73 | 0x0011 Offset FM9 Sequence Data (1h) 74 | 0x0013 Offset Dummy Sequence Data (1h) 75 | 0x0015 Offset Dummy Sequence Data (1h) 76 | 0x0017 Offset Dummy Pattern Data (for extra data section) (1h) 77 | 0x0019 Offset Instrument Data (1h) 78 | 79 | } 80 | 81 | 82 | ======================================= 83 | Sequence Data Section Commands 84 | ======================================= 85 | 86 | FF XX - @ XX 87 | -> §6.1.1 88 | FE XX - q XX 89 | -> §4.13 90 | FD XX - V XX 91 | -> §5.2 92 | FC FF XX - t XX 93 | -> §11.1 94 | FC FE XX - T +/- XX (signed) 95 | -> §11.2 96 | FC FD XX - t +/- XX (signed) 97 | -> §11.1 98 | FC XX - T XX 99 | -> §11.2 100 | FB - & 101 | -> §4.10 102 | FA XXXX - D XXXX (signed) 103 | -> §7.1 104 | F9 XXXX - [ 105 | (XXXX: absolute pointer to corresponding ]) 106 | -> §10.1 107 | F8 XX YY ZZZZ - ]XX 108 | ( YY: YY =/= XX -> loop interrupted via : 109 | ZZZZ: absolute pointer to corresponding [) 110 | -> §10.1 111 | F7 XXXX - : 112 | (XXXX: absolute pointer to corresponding ]) 113 | -> §10.1 114 | F6 - L 115 | -> §10.2 116 | F5 XX - _ XX (signed) 117 | -> §4.14 118 | F4 - ) 119 | -> §5.5 120 | F3 - ( 121 | -> §5.5 122 | F2 XX YY ZZ WW - MA XX, YY, ZZ (signed), WW 123 | -> §9.1 124 | F1 XX - *A XX 125 | -> §9.3 126 | F0 XX YY ZZ WW - E XX, YY (signed), ZZ, WW 127 | -> §8.1 128 | EF XX YY - y XX, YY 129 | -> §15.1 130 | EE XX - w XX 131 | -> §6.6 132 | ED XX - P XX (P0 = 0x00, P1 = 0x07, P2 = 0x38, P3 = 0x3f) 133 | -> §6.5 134 | EC XX - p XX 135 | -> §13.1 136 | EB XX - (RSS Shot/Dump Control 137 | bitorder: \b \s \c \h \t \i unused +p) 138 | -> §14.1 139 | EA XX - (RSS Individual Volume Setting 140 | XX >> 5, meaning of value: unused b s c h t i unused 141 | XX & 0x1F: value) 142 | -> §14.3 143 | E9 XX - (RSS Output Position Setting 144 | XX >> 5: see EA 145 | XX & 0x1F: unused r l m unused unused unused unused) 146 | -> §14.4 147 | E8 XX - \V XX 148 | -> §14.2 149 | E7 XX - __ XX (signed) 150 | -> §4.14 151 | E6 XX - \V +/- XX (signed) 152 | -> §14.2 153 | E5 XX YY - (RSS Individual Volume Setting +/- 154 | XX: see EB 155 | YY = value (signed)) 156 | -> §14.3 157 | E4 XX - #D XX 158 | -> §9.13 159 | E3 XX - )% XX 160 | -> §5.5 161 | E2 XX - (% XX 162 | -> §5.5 163 | E1 XX - H (XX & 0x0F), (XX >> 8) 164 | -> §9.10 165 | E0 XX - # ((XX >> 3) & 0x01), (XX & 0x07) 166 | -> §9.11 167 | DF XX - C XX (#Zenlen XX -> C XX on Channel G) 168 | -> §4.11 169 | DE XX - )^ XX 170 | -> §5.5 171 | DD XX - (^ XX 172 | -> §5.5 173 | DC XX - ~ XX 174 | -> §15.9 175 | DB XX - ~ +/- XX (signed) 176 | -> §15.9 177 | DA XX YY ZZ - { XX YY }%ZZ 178 | (XX, YY: resolved as note+octave - see section Notes) 179 | -> §4.3 180 | D9 XX - #w XX 181 | -> §9.12 182 | D8 XX - (XX < 0x80): #a XX 183 | (else): #p XX (signed) 184 | -> §9.12 185 | D7 XX - #f XX 186 | -> §9.12 187 | D6 XX YY - MDA XX, YY 188 | (see B7) 189 | -> §9.7 190 | D5 XXXX - DD +/- XXXX 191 | -> §7.1 192 | D4 XX - n XX 193 | -> §15.6 194 | D3 XX - N XX 195 | -> §15.5 196 | D2 XX - F XX 197 | -> §15.3 198 | D1 - $$$ UNKNOWN / UNUSED $$$ 199 | D0 XX - w XX 200 | -> §6.6 201 | CF XX - s (XX >> 4) 202 | -> §6.2 203 | CE YYYY ZZZZ WWWW - @ $$, YYYY (signed), ZZZZ (signed), WWWW (signed) 204 | (see FF, PCM Channels Case of @. follows FF) 205 | -> §6.1.5 206 | CD XX YY ZZ WW VV - E XX, YY, ZZ, (WW & 0x0F), (WW >> 4), VV 207 | -> §8.1 208 | CC XX - DX XX 209 | (#Detune Extend adds this to G-I) 210 | -> §7.3 211 | CB XX - MWA XX 212 | -> §9.2 213 | CA XX - MXA XX 214 | (#LFOSpeed adds this to every channels sans K) 215 | -> §9.5 216 | C9 XX - EX XX 217 | -> §8.2 218 | C8 XX YYYY - sd XX, YYYY (signed) 219 | -> §7.2 220 | C7 XX YYYY - sdd XX, YYYY (signed) 221 | -> §7.2 222 | C6 XXXX YYYY ZZZZ - #FM3Extend 223 | (XXXX to ZZZZ: offsets of sequence data for extension channels, 0x0000 = none. 224 | occurs at start of A) 225 | -> §2.20 226 | C5 XX - MMA XX 227 | -> §9.4 228 | C4 XX - (~XX % 32 == 0): Q (~XX >> 5) 229 | (else): Q% (~XX) 230 | -> 4.12 231 | C3 XX YY - px XX (signed), YY 232 | -> §13.2 233 | C2 XX - MA XX 234 | -> §9.1 235 | C1 - && 236 | -> §4.10 237 | C0 FF XX - DF XX 238 | -> §15.4 239 | C0 FE XX - DF +/- XX (signed) 240 | -> §15.4 241 | C0 FD XX - DS XX 242 | -> §15.4 243 | C0 FC XX - DS +/- XX (signed) 244 | -> §15.4 245 | C0 FB XX - DP XX 246 | -> §15.4 247 | C0 FA XX - DP +/- XX (signed) 248 | -> §15.4 249 | C0 F9 XX - DR XX 250 | -> §15.4 251 | C0 F8 XX - DR +/- XX (signed) 252 | -> §15.4 253 | C0 F7 XX - A XX 254 | -> §15.10 255 | C0 XX - m XX 256 | -> §15.7 257 | BF XX YY ZZ WW - MB XX, YY, ZZ, WW 258 | -> §9.1 259 | BE XX - *B XX 260 | -> §9.3 261 | BD XX YY B7 ZZ - MDB XX, YY (signed), ZZ 262 | -> §9.7 263 | BD XX YY - MDB XX, YY (signed) 264 | -> §9.7 265 | BC XX - MWB XX 266 | -> §9.2 267 | BB XX - MXB XX 268 | (#LFOSpeed adds this to every channels sans K) 269 | -> §9.5 270 | BA XX - MMB XX 271 | -> §9.4 272 | B9 XX - MB XX 273 | -> §9.1 274 | B8 XX YY - (high nibble 0xF): O XX, YY (signed) 275 | (else): O XX, YY 276 | -> §6.3 277 | B7 XX - MDA $$, $$, XX 278 | (see D6, number3 part of command) 279 | -> §9.7 280 | B6 XX - (XX < 0x80): FB XX 281 | (else): FB +/- XX (signed, 0x80 = +0, 0xFF = -1) 282 | -> §6.4 283 | B5 XX YY - sk XX, YY 284 | -> §12.3 285 | B4 XXXX YYYY ZZZZ WWWW 286 | VVVV UUUU TTTT SSSS - #PPZExtend 287 | (XXXX to SSSS: offsets of sequence data for PPZ channels, 0x0000 = none. 288 | occurs at start of J) 289 | -> §2.25 290 | B3 XX - q $$-XX 291 | (see FE, number2 part of command) 292 | -> §4.13 293 | B2 XX - _M XX (signed) 294 | -> §4.16 295 | B1 XX - q $$-$$, XX 296 | (see FE & B3, number3 part of command) 297 | -> §4.13 298 | 80 - (End Track) 299 | 300 | Notes (0x00 - 0x7F) { 301 | 302 | XF YY - r%YY (X: 0-7 legal, only 0 possible) 303 | XY ZZ - (octave+note)%ZZ (X: octave = X+1, Y: note = {c,c#,d,…,a,a#,b}[Y]. e.g. 0x35: o4 f) 304 | 305 | } 306 | 307 | 308 | ======================================= 309 | Rhythm Sequence Data Section Commands 310 | ======================================= 311 | 312 | { 313 | 314 | 00-7F -> XX - R XX ( 315 | PMDMML_MAN specifies 0-255, only 0-127 are actually usable 316 | compiler digests 128-255 but they're interpreted sequence data section commands 317 | ) 318 | 80-FF -> XX - Sequence Data Section Commands [XX] 319 | 320 | } 321 | 322 | 323 | ======================================= 324 | Rhythm Pattern Data Section 325 | ======================================= 326 | 327 | offset table: 328 | R0 -> XXXX 329 | R1 -> YYYY 330 | etc 331 | 332 | older versions: if R0 points at very next short, == empty 333 | 334 | not fully verified / missing clarity { 335 | 336 | FF - Pattern over, return 337 | 80-BF -> XX YY ZZ - Play samples for %ZZ (format: (XXYY AND 3FFF) -> @ 338 | e.g. 80 82 -> 8082 AND 3FFF -> 82 hex -> 130 dec 339 | @130 = @128 + @2 -> Hi-Hat Close + Snare Drum 1 340 | 0F XX - r%XX 341 | 342 | } 343 | 344 | 345 | Version-dependent / not guaranteed to exist { 346 | 347 | (This is very likely read backwards from the Instrument Data Section) 348 | 349 | Extra data type 0x48 OR NEC PC-98, missing clarity { 350 | 351 | 4b - Song length (including pre-Master Loop) 352 | 4b - Loop length (only Master Loop Start - End) 353 | 354 | } 355 | 356 | 1h - Offset to Extra Data Table 357 | 1b - Extra Data Table version 358 | FE 359 | 360 | } 361 | 362 | 363 | ======================================= 364 | Instrument Data Section 365 | ======================================= 366 | 367 | 1b - @ XX 368 | 25b - Eaw register data, see chip-specific manual for details 369 | OPN-family: see YM2608 manual, 2-2-2: Parameters and channel registers: $30-$B6 370 | (OP order: 1->3->2->4!) 371 | - 4x DT/MULTI 372 | - 4x TL 373 | - 4x KS/AR 374 | - 4x AM/DR 375 | - 4x SR 376 | - 4x SL/RR 377 | - FB/Algorithm 378 | OPM: see YM2151 manual, Figure 2.3 a), Address Map (1): WRITE MODE 379 | - 4x DT1/MUL 380 | - 4x TL 381 | - 4x KS/AR 382 | - 4x AMS-EN/D1R 383 | - 4x DT2/D2R 384 | - 4x D1L/RR 385 | - RL/FB/CONECT 386 | OPL: see YM3812 manual, 2-7 Address Map 387 | - 2x AM/VIB/EG-TYP/KSR/MULTI 388 | - 2x KSL/TL 389 | - 2x AR/DR 390 | - 2x SL/RR 391 | - FB/C 392 | 393 | version-dependent / not guaranteed to exist (missing if FE subsection in previous section is missing) { 394 | 395 | section end: 396 | 00 FF 397 | 398 | } 399 | 400 | 401 | ======================================= 402 | Extra Data Table Section 403 | ======================================= 404 | 405 | (strings are C-style, NUL-terminated) 406 | 407 | V4.8+ { 408 | 409 | 1h - Offset PPZ filename string 410 | 411 | } 412 | 413 | V4.3+ { 414 | 415 | 1h - Offset PPS filename strings (2 filenames: seperator 0xFE) 416 | 417 | } 418 | 419 | 1h - Offset main sample bank filename string 420 | - NEC PC-98: PPC/P86 421 | - Fujitsu FM Towns: PMB 422 | 1h - Offset title string 423 | 1h - Offset composer string 424 | 1h - Offset arranger string 425 | 426 | [0 .. 128] { 427 | 428 | 1h - Memo 429 | 430 | } 431 | 432 | section end: 433 | 0x0000 434 | -------------------------------------------------------------------------------- /docs/ppc.txt: -------------------------------------------------------------------------------- 1 | ======================================== 2 | General 3 | ======================================== 4 | 5 | ADPCM RAM addresses: see docs/common.txt { 6 | 7 | address_start = 0x0026 8 | file_header_size = 0x0420 9 | 10 | } 11 | 12 | 13 | ======================================== 14 | Header 15 | ======================================== 16 | 17 | 0x0000 Identifier (30b) 18 | "ADPCM DATA for PMD ver.4.4- " 19 | 0x001E Address of End of Data (32B blocks) in ADPCM RAM (1h) 20 | File Size == address -> file offset 21 | 22 | 0x0020 - 0x041F 256 * { 23 | 24 | Start of Sample (32b blocks) in ADPCM RAM (1h) 25 | End of Sample (32b blocks) in ADPCM RAM (1h) 26 | 27 | (0x0000 0x0000 -> no sample for this instrument ID) 28 | 29 | } 30 | 31 | 32 | ======================================== 33 | Body 34 | ======================================== 35 | 36 | Stream of Sample Data { 37 | 38 | Yamaha ADPCM-B encoding (4-Bit Signed ADPCM) 39 | Mono 40 | 16kHz 41 | (above sample rate according to KAJA's documentation 42 | any sample rate possible, for different base note & octave) 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /docs/pps.txt: -------------------------------------------------------------------------------- 1 | ======================================= 2 | Header 3 | ======================================= 4 | 5 | 0x0000 - 0x0053 14 * { 6 | 7 | Pointer to Sample Data Start (1h) 8 | Length of Sample Data (1h) 9 | "Pitch"(?) (1b) 10 | Volume Reduction (1b) 11 | 12 | } 13 | 14 | (0x0000 0x0000 [0x00 0x00] -> no sample for this instrument ID) 15 | 16 | } 17 | 18 | 19 | ======================================= 20 | Body 21 | ======================================= 22 | 23 | Stream of Sample Data { 24 | 25 | 4-Bit Unsigned 26 | (afaict) 27 | Mono 28 | 16Hz 29 | (based on tests, maybe alternatively 8kHz) 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /docs/pvi.txt: -------------------------------------------------------------------------------- 1 | ======================================= 2 | General 3 | ======================================= 4 | 5 | ADPCM RAM addresses: see docs/common.txt { 6 | 7 | address_start = 0x0000 8 | file_header_size = 0x0210 9 | 10 | } 11 | 12 | ======================================= 13 | Header 14 | ======================================= 15 | 16 | 0x0000 Identifier (4b) 17 | "PVI2" 18 | 0x0004 - 0x0007 Unknown Settings (4b) 19 | Unknown mappings PCME switches <-> Values 20 | "0x10 0x00 0x10 0x02" in all example files 21 | First 1h may be Start Address in ADPCM RAM? 22 | 0x0008 - 0x0009 "Delta-N" playback frequency (1h) 23 | Default 0x49BA "== 16kHz"?? 24 | 0x000A Unknown (1b) 25 | RAM type? (Docs indicate values 0 or 8, all example files have value 0x02?) 26 | 0x000B Amount of defined Samples (1b) 27 | 0x000C - 0x000F Unknown (4b) 28 | Padding? 29 | "0x00 0x00 0x00 0x00" in all example files 30 | 0x0010 - 0x020F 128 * { 31 | 32 | Start of Sample (32b blocks) in ADPCM RAM (1h) 33 | End of Sample (32b blocks) in ADPCM RAM (1h) 34 | 35 | (0x0000 0x0000 -> no sample for this instrument ID) 36 | 37 | } 38 | 39 | 40 | ======================================= 41 | Body 42 | ======================================= 43 | 44 | Stream of Sample Data { 45 | 46 | Yamaha ADPCM-B encoding (4-Bit Signed ADPCM) 47 | Mono 48 | Sample rate as specified earlier 49 | (examples i have seems Stereo or 32kHz despite "16kHz" playback frequency setting?) 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /docs/pzi.txt: -------------------------------------------------------------------------------- 1 | ======================================= 2 | Header 3 | ======================================= 4 | 5 | 0x0000 Identifier (4b) 6 | "PZI1" 7 | 0x0004 - 0x001F Unknown (28b) 8 | Part of identifier? Settings? 9 | All (\0)s in all example files 10 | 0x0020 - 0x091F 128 * { 11 | 12 | Start of Sample after header (2h) 13 | Length of Sample (2h) 14 | Offset of loop start from sample start (2h) 15 | Offset of loop end from sample start (2h) 16 | Sample rate (1h) 17 | 18 | (0xFFFFFFFF 0xFFFFFFFF loop offsets -> no loop information) 19 | 20 | } 21 | 22 | 23 | ======================================= 24 | Body 25 | ======================================= 26 | 27 | Stream of Sample Data { 28 | 29 | Unsigned 8-Bit 30 | Mono 31 | Sample rate as specified in header 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /examples/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | -------------------------------------------------------------------------------- /examples/M/PAT1_P.M2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OPNA2608/libopenpmd/37aaec23e42dd014db1d0251d651e917d5b3889f/examples/M/PAT1_P.M2 -------------------------------------------------------------------------------- /examples/M/RAVETUNE.M2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OPNA2608/libopenpmd/37aaec23e42dd014db1d0251d651e917d5b3889f/examples/M/RAVETUNE.M2 -------------------------------------------------------------------------------- /examples/MML/ravetune.mml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OPNA2608/libopenpmd/37aaec23e42dd014db1d0251d651e917d5b3889f/examples/MML/ravetune.mml -------------------------------------------------------------------------------- /examples/P86/RC1.P86: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OPNA2608/libopenpmd/37aaec23e42dd014db1d0251d651e917d5b3889f/examples/P86/RC1.P86 -------------------------------------------------------------------------------- /include/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE) 2 | 3 | set (HEADERS 4 | common.h 5 | p86.h 6 | ) 7 | 8 | install (FILES ${HEADERS} 9 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/openpmd/" 10 | ) 11 | -------------------------------------------------------------------------------- /include/common.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_H 2 | #define COMMON_H 3 | 4 | /* boolean for C89 */ 5 | typedef enum {false, true} boolean; 6 | 7 | /* 8 | * Wrap unused function arguments. Only works in debug builds. 9 | * Should not be usable in release code because it defeats the whole point of the warning flags 10 | * but it's useful when testing WIP code. 11 | */ 12 | #ifdef DEBUG 13 | #define UNUSED(x) (void)(x); 14 | #else 15 | #define UNUSED(x) 16 | #endif 17 | 18 | #define PMD_ERRMAXSIZE 1024 19 | #define PMD_BUFFERSIZE 4096 20 | 21 | extern const char* pmd_error_malloc; 22 | extern const char* pmd_error_eof; 23 | extern const char* pmd_error_write; 24 | 25 | void PMD_SetError (const char* errorMsg, ...); 26 | 27 | const char* PMD_GetError (); 28 | 29 | #define MALLOC_CHECK(varname, size) \ 30 | varname = malloc (size); if (varname == NULL) 31 | 32 | #endif /* COMMON_H */ 33 | -------------------------------------------------------------------------------- /include/p86.h: -------------------------------------------------------------------------------- 1 | #ifndef P86_H 2 | #define P86_H 3 | 4 | #include 5 | #include "common.h" 6 | 7 | typedef enum { 8 | MEM, FIL 9 | } p86_sample_type; 10 | 11 | typedef struct { 12 | signed char* data; 13 | } p86_sample_mem; 14 | 15 | typedef struct { 16 | FILE* file; 17 | long fileStart; 18 | unsigned long offset; 19 | } p86_sample_fil; 20 | 21 | typedef union { 22 | p86_sample_mem mem; 23 | p86_sample_fil fil; 24 | } p86_sample_data; 25 | 26 | typedef struct { 27 | unsigned char id; 28 | p86_sample_type type; 29 | unsigned long length; 30 | p86_sample_data typeData; 31 | } p86_sample; 32 | 33 | typedef struct { 34 | unsigned char version; 35 | p86_sample* samples[256]; 36 | } p86_struct; 37 | 38 | extern const char P86_MAGIC[12]; 39 | extern const unsigned short P86_HEADERLENGTH; 40 | extern const unsigned long P86_LENGTHMAX; 41 | 42 | /* 43 | * Reads a P86 file from FILE pointer p86File. 44 | * 45 | * Used implementation (Memory or File) depends on the configure-time option USE_LOWMEM. 46 | */ 47 | p86_struct* P86_ImportFile (FILE* p86File); 48 | 49 | /* 50 | * Loads a P86 file from FILE pointer p86File by fully loading its contents into memory. 51 | * 52 | * On a system with minimal amounts of memory, P86_ImportFile_File may be preferred. 53 | */ 54 | p86_struct* P86_ImportFile_Memory (FILE* p86File); 55 | 56 | /* 57 | * Loads a P86 file from FILE pointer p86File by keeping parsing crucial bank data & keeping FILE references. 58 | * You are responsible for keeping the handed-in FILE* opened while its contents are needed! 59 | * 60 | * On a system with modern amounts of memory, P86_ImportFile_Memory may be preferred. 61 | */ 62 | p86_struct* P86_ImportFile_File (FILE* p86File); 63 | 64 | /* 65 | * TODO Implement 66 | * 67 | * Reads a P86 file from void pointer p86Data and parses its contents into a P86 struct. 68 | */ 69 | p86_struct* P86_ImportData (void* p86Data); 70 | 71 | /* 72 | * Writes a P86 file from p86 pointer p86 to FILE pointer p86File. 73 | * 74 | * Possible return values: 75 | * * 0 - No Error 76 | * * 1 - Failed to write to file 77 | * * 2 - Failed to read from file (for slim pmd_sample loading) 78 | * * 3 - file read buffer allocation failure 79 | */ 80 | int P86_ExportFile (const p86_struct* p86, FILE* p86File); 81 | 82 | /* 83 | * TODO Implement 84 | * 85 | * Writes a P86 file from p86 pointer p86 to voidpointer p86Data. 86 | */ 87 | int P86_ExportData (p86_struct* p86, void* p86Data); 88 | 89 | /* 90 | * Creates a new blank P86 bank in memory. 91 | */ 92 | p86_struct* P86_New (); 93 | 94 | /* 95 | * Free a P86 struct. 96 | */ 97 | void P86_Free (p86_struct* p86); 98 | 99 | /* 100 | * Free a P86 sample struct. 101 | */ 102 | void P86_FreeSample (p86_sample* sample); 103 | 104 | /* 105 | * Validates a p86 struct. 106 | * 107 | * Possible return values: 108 | * * 0 - No Error 109 | * * 1 - Some error (TODO more specific error codes!) 110 | */ 111 | int P86_Validate (const p86_struct* p86); 112 | 113 | /* 114 | * Map sample data to specified ID. 115 | * 116 | * Possible return values: 117 | * * 0 - No Error 118 | * * 1 - Sample data buffer allocation failure 119 | * * 2 - p86_sample struct allocation failure 120 | */ 121 | int P86_SetSample (p86_struct* p86, unsigned char id, unsigned long length, signed char* data); 122 | 123 | /* 124 | * Get a pointer to the sample data with the specified ID. 125 | * Returns NULL if ID is not set. 126 | */ 127 | const p86_sample* P86_GetSample (p86_struct* p86, unsigned char id); 128 | 129 | /* 130 | * Map sample data to next free ID. 131 | * 132 | * Possible return values: 133 | * * 0 - No Error 134 | * * 1 - All bank IDs are already in use 135 | */ 136 | int P86_AddSample (p86_struct* p86, unsigned long length, signed char* data); 137 | 138 | /* 139 | * Unmap sample data at specified ID. 140 | * 141 | * TODO Currently cannot fail, maybe make void instead? 142 | * 143 | * Possible return values: 144 | * * 0 - No Error 145 | * * 1 - Some error 146 | */ 147 | int P86_UnsetSample (p86_struct* p86, unsigned char id); 148 | 149 | /* 150 | * Unmap sample data at specified ID and shift all IDs afterwards. 151 | * 152 | * Possible return values: 153 | * * 0 - No Error 154 | * * 1 - p86_sample struct allocation failure 155 | */ 156 | int P86_RemoveSample (p86_struct* p86, unsigned char id); 157 | 158 | /* 159 | * Switches the sample data between two IDs. 160 | */ 161 | int P86_SwitchSample (p86_struct* p86, unsigned char from, unsigned char to); 162 | 163 | /* 164 | * Checks if ID id is set (length != 0). 165 | */ 166 | int P86_IsSet (const p86_struct* p86, unsigned char id); 167 | 168 | /* 169 | * Prints details about P86 data. 170 | */ 171 | void P86_Print (const p86_struct* p86); 172 | 173 | /* 174 | * Prints the string representation ("major.minor") of a P86 version struct into the supplied buf variable. 175 | * 176 | * Returns the return value of sprintf. 177 | */ 178 | int P86_GetVersionString (const unsigned char* version, char* buf); 179 | 180 | /* 181 | * Calculates the raw size needed for a p86_struct p86 if it were to be written to a file. 182 | * This may exceed actual P86 size limitations with future bank optimisations. 183 | * (Only data type limits - unsigned long - are checked against) 184 | */ 185 | unsigned long P86_GetTotalLength (const p86_struct* p86); 186 | 187 | #endif /* P86_H */ 188 | -------------------------------------------------------------------------------- /include/ppc.h: -------------------------------------------------------------------------------- 1 | #ifndef PPC_H 2 | #define PPC_H 3 | 4 | #include 5 | #include "common.h" 6 | 7 | typedef struct { 8 | unsigned char id; 9 | unsigned long length; 10 | adpcm_stream* data; 11 | } ppc_sample; 12 | 13 | typedef struct { 14 | /* quasi-fixed version */ 15 | ppc_sample* samples[256]; 16 | } ppc_struct; 17 | 18 | extern char PPC_MAGIC[31]; 19 | extern unsigned short PPC_HEADERLENGTH; 20 | extern unsigned long PPC_LENGTHMAX; 21 | 22 | /* 23 | * Reads a PPC file from FILE pointer ppcFile and parses its contents into a PPC struct. 24 | */ 25 | ppc_struct PPC_ImportFile (FILE* ppcFile); 26 | 27 | /* 28 | * TODO Implement 29 | * 30 | * Reads a PPC file from void pointer ppcData and parses its contents into a PPC struct. 31 | */ 32 | ppc_struct PPC_ImportData (void* ppcData); 33 | 34 | /* 35 | * Writes a PPC file from ppc pointer ppc to FILE pointer ppcFile. 36 | */ 37 | int PPC_ExportFile (ppc_struct* ppc, FILE* ppcFile); 38 | 39 | /* 40 | * TODO Implement 41 | * 42 | * Writes a PPC file from ppc pointer ppc to voidpointer ppcData. 43 | */ 44 | int PPC_ExportData (ppc_struct* ppc, void* ppcData); 45 | 46 | /* 47 | * Free a PPC struct. 48 | */ 49 | int PPC_Free (ppc_struct* ppc); 50 | 51 | /* 52 | * Validates a ppc struct. 53 | */ 54 | int PPC_Validate (ppc_struct* ppc); 55 | 56 | /* 57 | * Map sample data to specified ID. 58 | */ 59 | int PPC_SetSample (ppc_struct* ppc, unsigned char id, unsigned long length, signed char* data); 60 | 61 | /* 62 | * Map sample data to next free ID. 63 | */ 64 | int PPC_AddSample (ppc_struct* ppc, unsigned long length, signed char* data); 65 | 66 | /* 67 | * Unmap sample data at specified ID. 68 | */ 69 | int PPC_UnsetSample (ppc_struct* ppc, unsigned char id); 70 | 71 | /* 72 | * Unmap sample data at specified ID and shift all IDs afterwards. 73 | */ 74 | int PPC_RemoveSample (ppc_struct* ppc, unsigned char id); 75 | 76 | /* 77 | * Switches the sample data between two IDs. 78 | */ 79 | int PPC_SwitchSample (ppc_struct* ppc, unsigned char from, unsigned char to); 80 | 81 | /* 82 | * Checks if ID id is set (length != 0). 83 | */ 84 | int PPC_IsSet (ppc_struct* ppc, unsigned char id); 85 | 86 | /* 87 | * Prints details about PPC data. 88 | */ 89 | void PPC_Print (ppc_struct* ppc); 90 | 91 | /* 92 | * Prints the string representation ("major.minor") of a PPC version struct into the supplied buf variable. 93 | * The value returned by this is fixed to 4.4, see PPC_MAGIC. 94 | * 95 | * Returns the return value of sprintf. 96 | */ 97 | int PPC_GetVersionString (unsigned char* version, char* buf); 98 | 99 | /* 100 | * Calculates the total size needed for a ppc_struct ppc if it were to be written to a file. 101 | */ 102 | unsigned long PPC_GetTotalLength (ppc_struct* ppc); 103 | 104 | #endif /* PPC_H */ 105 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { } }: 2 | 3 | with pkgs; 4 | 5 | mkShell { 6 | buildInputs = [ 7 | cmake 8 | gcc 9 | editorconfig-checker 10 | valgrind 11 | gdb 12 | ]; 13 | 14 | shellHook = '' 15 | unset CC 16 | unset CXX 17 | export VERBOSE=1 18 | ''; 19 | } 20 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (LIBOPENPMD_SOURCES 2 | common.c 3 | p86.c 4 | ) 5 | 6 | add_library (openpmd ${LIBOPENPMD_SOURCES}) 7 | target_include_directories (openpmd PUBLIC ${INCLUDE_PATH}) 8 | install (TARGETS openpmd 9 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 10 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 11 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 12 | ) 13 | -------------------------------------------------------------------------------- /src/common.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include 3 | #include 4 | #include 5 | 6 | static char pmd_error[PMD_ERRMAXSIZE]; 7 | 8 | /* TODO enum of error format strings? */ 9 | const char* pmd_error_malloc = "Failed to allocate memory for %s (length: %luB)"; 10 | const char* pmd_error_eof = "Reached end of file"; 11 | const char* pmd_error_write = "Failed to write to file"; 12 | 13 | void PMD_SetError (const char* errorMsg, ...) { 14 | va_list fArgs; 15 | va_start (fArgs, errorMsg); 16 | vsprintf (pmd_error, errorMsg, fArgs); 17 | va_end (fArgs); 18 | } 19 | 20 | const char* PMD_GetError () { 21 | return pmd_error; 22 | } 23 | -------------------------------------------------------------------------------- /src/p86.c: -------------------------------------------------------------------------------- 1 | #include "p86.h" 2 | #include 3 | #include 4 | #include 5 | 6 | const char P86_MAGIC[12] = "PCM86 DATA\n\0"; 7 | const unsigned short P86_HEADERLENGTH = 0x0610; 8 | const unsigned long P86_LENGTHMAX = 0xFFFFFF; 9 | 10 | p86_struct* P86_ImportFile (FILE* p86File) { 11 | return 12 | #ifdef USE_LOWMEM 13 | P86_ImportFile_File 14 | #else 15 | P86_ImportFile_Memory 16 | #endif 17 | (p86File); 18 | } 19 | 20 | #define READ_CHECK(var, elemsize, writecounter) \ 21 | locReadCounter = fread (var, elemsize, writecounter, p86File); \ 22 | if (locReadCounter != writecounter) 23 | p86_struct* P86_ImportFile_Memory (FILE* p86File) { 24 | size_t locReadCounter; 25 | unsigned int i, j; 26 | long curpos; 27 | p86_struct* parsedData; 28 | p86_sample* parsedSample; 29 | signed char* buffer; 30 | unsigned long sample_start = 0; 31 | unsigned long sample_length = 0; 32 | unsigned long parsedLength = 0; 33 | long startpos = ftell (p86File); 34 | 35 | MALLOC_CHECK (parsedData, sizeof (p86_struct)) { 36 | PMD_SetError (pmd_error_malloc, "imported p86_struct", sizeof (p86_struct)); 37 | return NULL; 38 | } 39 | 40 | /* TODO use a separate buffer? */ 41 | MALLOC_CHECK (buffer, 13) { 42 | free (parsedData); 43 | PMD_SetError (pmd_error_malloc, "P86 file magic", 13); 44 | return NULL; 45 | } 46 | READ_CHECK (buffer, sizeof (char), 12) { 47 | free (buffer); 48 | free (parsedData); 49 | PMD_SetError (pmd_error_eof); 50 | return NULL; 51 | } 52 | if (memcmp (P86_MAGIC, (char*)buffer, 12) != 0) { 53 | free (buffer); 54 | free (parsedData); 55 | PMD_SetError ("P86 file header mismatch"); 56 | return NULL; 57 | } 58 | free (buffer); 59 | 60 | parsedData->version = fgetc (p86File); 61 | 62 | READ_CHECK (&parsedLength, 3, 1) { 63 | free (parsedData); 64 | PMD_SetError (pmd_error_eof); 65 | return NULL; 66 | } 67 | 68 | for (i = 0; i <= 255; ++i) { 69 | MALLOC_CHECK (parsedSample, sizeof (p86_sample)) { 70 | for (j = --i; j > i; --j) { 71 | P86_FreeSample (parsedData->samples[j]); 72 | } 73 | free (parsedData); 74 | PMD_SetError (pmd_error_malloc, "imported p86_sample struct", sizeof (p86_sample)); 75 | return NULL; 76 | } 77 | parsedSample->id = i; 78 | 79 | READ_CHECK (&sample_start, 3, 1) { 80 | free (parsedSample); 81 | for (j = --i; j > i; --j) { 82 | P86_FreeSample (parsedData->samples[j]); 83 | } 84 | free (parsedData); 85 | PMD_SetError (pmd_error_eof); 86 | return NULL; 87 | } 88 | READ_CHECK (&sample_length, 3, 1) { 89 | free (parsedSample); 90 | for (j = --i; j > i; --j) { 91 | P86_FreeSample (parsedData->samples[j]); 92 | } 93 | free (parsedData); 94 | PMD_SetError (pmd_error_eof); 95 | return NULL; 96 | } 97 | parsedSample->length = sample_length; 98 | 99 | if (sample_length > 0) { 100 | MALLOC_CHECK (buffer, sample_length) { 101 | free (parsedSample); 102 | for (j = --i; j > i; --j) { 103 | P86_FreeSample (parsedData->samples[j]); 104 | } 105 | free (parsedData); 106 | PMD_SetError (pmd_error_malloc, "imported sample data", sample_length); 107 | return NULL; 108 | } 109 | curpos = ftell (p86File); 110 | fseek (p86File, startpos + sample_start, SEEK_SET); 111 | READ_CHECK (buffer, sizeof (char), sample_length) { 112 | free (parsedSample); 113 | for (j = --i; j > i; --j) { 114 | P86_FreeSample (parsedData->samples[j]); 115 | } 116 | free (parsedData); 117 | PMD_SetError (pmd_error_eof); 118 | return NULL; 119 | } 120 | parsedSample->type = MEM; 121 | parsedSample->typeData.mem.data = buffer; 122 | fseek (p86File, curpos, SEEK_SET); 123 | } 124 | parsedData->samples[i] = parsedSample; 125 | } 126 | 127 | if (P86_Validate (parsedData) != 0) { 128 | P86_Free (parsedData); 129 | /* propagating validation error */ 130 | return NULL; 131 | } 132 | /* extra validation */ 133 | if (parsedLength != P86_GetTotalLength (parsedData)) { 134 | P86_Free (parsedData); 135 | PMD_SetError ("Parsed total length does not match specified total length"); 136 | return NULL; 137 | } 138 | P86_Print (parsedData); 139 | 140 | return parsedData; 141 | } 142 | 143 | p86_struct* P86_ImportFile_File (FILE* p86File) { 144 | size_t locReadCounter; 145 | unsigned int i, j; 146 | char* buffer; 147 | p86_struct* parsedData; 148 | p86_sample* parsedSample; 149 | unsigned long sample_start = 0; 150 | unsigned long sample_length = 0; 151 | unsigned long parsedLength = 0; 152 | long startpos = ftell (p86File); 153 | 154 | MALLOC_CHECK (parsedData, sizeof (p86_struct)) { 155 | PMD_SetError (pmd_error_malloc, "imported p86_struct", sizeof (p86_struct)); 156 | return NULL; 157 | } 158 | 159 | /* TODO use a separate buffer? */ 160 | MALLOC_CHECK (buffer, 13) { 161 | free (parsedData); 162 | PMD_SetError (pmd_error_malloc, "P86 file magic", 13); 163 | return NULL; 164 | } 165 | READ_CHECK (buffer, sizeof (char), 12) { 166 | free (buffer); 167 | free (parsedData); 168 | PMD_SetError (pmd_error_eof); 169 | return NULL; 170 | } 171 | if (memcmp (P86_MAGIC, (char*)buffer, 12) != 0) { 172 | free (buffer); 173 | free (parsedData); 174 | PMD_SetError ("P86 file header mismatch"); 175 | return NULL; 176 | } 177 | free (buffer); 178 | 179 | parsedData->version = fgetc (p86File); 180 | 181 | READ_CHECK (&parsedLength, 3, 1) { 182 | free (parsedData); 183 | PMD_SetError (pmd_error_eof); 184 | return NULL; 185 | } 186 | 187 | for (i = 0; i <= 255; ++i) { 188 | MALLOC_CHECK (parsedSample, sizeof (p86_sample)) { 189 | for (j = --i; j > i; --j) { 190 | P86_FreeSample (parsedData->samples[j]); 191 | } 192 | free (parsedData); 193 | PMD_SetError (pmd_error_malloc, "imported p86_sample struct", sizeof (p86_sample)); 194 | return NULL; 195 | } 196 | parsedSample->id = i; 197 | 198 | READ_CHECK (&sample_start, 3, 1) { 199 | free (parsedSample); 200 | for (j = --i; j > i; --j) { 201 | P86_FreeSample (parsedData->samples[j]); 202 | } 203 | free (parsedData); 204 | PMD_SetError (pmd_error_eof); 205 | return NULL; 206 | } 207 | READ_CHECK (&sample_length, 3, 1) { 208 | free (parsedSample); 209 | for (j = --i; j > i; --j) { 210 | P86_FreeSample (parsedData->samples[j]); 211 | } 212 | free (parsedData); 213 | PMD_SetError (pmd_error_eof); 214 | return NULL; 215 | } 216 | parsedSample->length = sample_length; 217 | 218 | parsedSample->type = FIL; 219 | parsedSample->typeData.fil.file = p86File; 220 | parsedSample->typeData.fil.fileStart = startpos; 221 | parsedSample->typeData.fil.offset = sample_start; 222 | 223 | parsedData->samples[i] = parsedSample; 224 | } 225 | 226 | if (P86_Validate (parsedData) != 0) { 227 | 228 | P86_Free (parsedData); 229 | /* propagating validation error */ 230 | return NULL; 231 | } 232 | 233 | /* extra validation */ 234 | if (parsedLength != P86_GetTotalLength (parsedData)) { 235 | P86_Free (parsedData); 236 | PMD_SetError ("Parsed total length does not match specified total length"); 237 | return NULL; 238 | } 239 | P86_Print (parsedData); 240 | 241 | return parsedData; 242 | } 243 | #undef READ_CHECK 244 | 245 | #define WRITE_CHECK(file, data, elemsize, writecounter) \ 246 | fwrite (data, elemsize, writecounter, file); \ 247 | if (ferror (file)) { \ 248 | PMD_SetError (pmd_error_write); \ 249 | return 1; \ 250 | } 251 | int P86_ExportFile (const p86_struct* p86, FILE* p86File) { 252 | unsigned long start, length, startWrite, lengthWrite; 253 | size_t filReadCounter, locReadCounter, readAmount; 254 | unsigned int i; 255 | signed char* buffer = NULL; /* Conditionally initialised */ 256 | 257 | P86_Validate (p86); 258 | P86_Print (p86); 259 | 260 | WRITE_CHECK (p86File, P86_MAGIC, sizeof (char), 12); 261 | WRITE_CHECK (p86File, "\x11", sizeof (char), 1); 262 | 263 | length = P86_GetTotalLength (p86); 264 | WRITE_CHECK (p86File, &length, sizeof (char), 3); 265 | 266 | start = P86_HEADERLENGTH; 267 | for (i = 0; i <= 255; ++i) { 268 | length = p86->samples[i]->length; 269 | if (length > 0) { 270 | startWrite = start; 271 | lengthWrite = length; 272 | start += length; 273 | } else { 274 | startWrite = 0; 275 | lengthWrite = 0; 276 | } 277 | WRITE_CHECK (p86File, &startWrite, sizeof (char), 3); 278 | WRITE_CHECK (p86File, &lengthWrite, sizeof (char), 3); 279 | } 280 | for (i = 0; i <= 255; ++i) { 281 | length = p86->samples[i]->length; 282 | if (length > 0) { 283 | switch (p86->samples[i]->type) { 284 | case MEM: 285 | WRITE_CHECK (p86File, p86->samples[i]->typeData.mem.data, sizeof (char), length); 286 | break; 287 | case FIL: 288 | if (buffer == NULL) { 289 | MALLOC_CHECK (buffer, PMD_BUFFERSIZE) { 290 | PMD_SetError (pmd_error_malloc, "file read buffer"); 291 | return 3; 292 | } 293 | } 294 | fseek (p86->samples[i]->typeData.fil.file, 295 | p86->samples[i]->typeData.fil.fileStart + p86->samples[i]->typeData.fil.offset, SEEK_SET); 296 | filReadCounter = length; 297 | while (filReadCounter != 0) { 298 | if (filReadCounter < PMD_BUFFERSIZE) { 299 | readAmount = filReadCounter; 300 | } else { 301 | readAmount = PMD_BUFFERSIZE; 302 | } 303 | locReadCounter = fread (buffer, sizeof (char), readAmount, p86->samples[i]->typeData.fil.file); 304 | if (locReadCounter != readAmount) { 305 | PMD_SetError (pmd_error_eof); 306 | return 2; 307 | } 308 | WRITE_CHECK (p86File, buffer, sizeof (char), locReadCounter); 309 | filReadCounter -= locReadCounter; 310 | } 311 | break; 312 | } 313 | } 314 | } 315 | 316 | if (buffer != NULL) { 317 | free (buffer); 318 | } 319 | 320 | return 0; 321 | } 322 | #undef WRITE_CHECK 323 | 324 | p86_struct* P86_New () { 325 | unsigned int i, j; 326 | p86_sample* newSample; 327 | p86_struct* newData; 328 | 329 | MALLOC_CHECK (newData, sizeof (p86_struct)) { 330 | PMD_SetError (pmd_error_malloc, "blank p86_struct instance", sizeof (p86_struct)); 331 | return NULL; 332 | } 333 | newData->version = '\x11'; 334 | 335 | for (i = 0; i <= 255; ++i) { 336 | MALLOC_CHECK (newSample, sizeof (p86_sample)) { 337 | for (j = --i; j > i; --j) { 338 | P86_FreeSample (newData->samples[j]); 339 | } 340 | free (newData); 341 | PMD_SetError (pmd_error_malloc, "blank p86_sample instance", sizeof (p86_sample)); 342 | return NULL; 343 | } 344 | newSample->id = i; 345 | newSample->type = MEM; 346 | newSample->length = 0; 347 | newData->samples[i] = newSample; 348 | } 349 | 350 | P86_Print (newData); 351 | 352 | return newData; 353 | } 354 | 355 | void P86_Free (p86_struct* p86) { 356 | unsigned int i; 357 | 358 | for (i = 0; i <= 255; ++i) { 359 | P86_FreeSample (p86->samples[i]); 360 | } 361 | free (p86); 362 | } 363 | 364 | void P86_FreeSample (p86_sample* sample) { 365 | if (sample->length > 0) { 366 | if (sample->type == MEM) { 367 | free (sample->typeData.mem.data); 368 | } else { 369 | /* TODO handle FILE*s here */ 370 | } 371 | } 372 | free (sample); 373 | } 374 | 375 | #define INVALID() \ 376 | printf ("%s\n", p86_invalid); \ 377 | return 1; 378 | int P86_Validate (const p86_struct* p86) { 379 | const char* p86_error_length_sample = "Sample at ID #%03u (%luB) exceeds per-sample length limit (%luB)"; 380 | const char* p86_error_length_total = "Total length (%luB) exceeds maximum length limit (%luB)"; 381 | const char* p86_invalid = "Data failed validation check! Check PMD_GetError() for more details."; 382 | unsigned int i; 383 | unsigned long totalSize; 384 | 385 | printf ("Checking sample lengths.\n"); 386 | for (i = 0; i <= 255; ++i) { 387 | if (p86->samples[i]->length > P86_LENGTHMAX) { 388 | PMD_SetError (p86_error_length_sample, i, p86->samples[i]->length, P86_LENGTHMAX); 389 | INVALID(); 390 | } 391 | } 392 | 393 | printf ("Checking total length.\n"); 394 | totalSize = P86_GetTotalLength (p86); 395 | if (totalSize > P86_LENGTHMAX) { 396 | PMD_SetError (p86_error_length_total, totalSize, P86_LENGTHMAX); 397 | INVALID(); 398 | } 399 | 400 | printf ("Data is valid!\n"); 401 | return 0; 402 | } 403 | #undef INVALID 404 | 405 | int P86_SetSample (p86_struct* p86, unsigned char id, unsigned long length, signed char* data) { 406 | signed char* buffer; 407 | p86_sample* newSample; 408 | 409 | MALLOC_CHECK (newSample, sizeof (p86_sample)) { 410 | PMD_SetError (pmd_error_malloc, "p86_sample struct", sizeof (p86_sample)); 411 | return 2; 412 | } 413 | MALLOC_CHECK (buffer, length) { 414 | free (newSample); 415 | PMD_SetError (pmd_error_malloc, "sample data buffer", length); 416 | return 1; 417 | } 418 | 419 | newSample->id = id; 420 | newSample->length = length; 421 | 422 | newSample->type = MEM; 423 | memcpy (buffer, data, length); 424 | newSample->typeData.mem.data = buffer; 425 | 426 | P86_FreeSample (p86->samples[id]); 427 | p86->samples[id] = newSample; 428 | 429 | P86_Print (p86); 430 | 431 | return 0; 432 | } 433 | 434 | const p86_sample* P86_GetSample (p86_struct* p86, unsigned char id) { 435 | const char* p86_error_id_missing = "Requested sample ID is not set"; 436 | 437 | if (P86_IsSet (p86, id) > 0) { 438 | PMD_SetError (p86_error_id_missing); 439 | return NULL; 440 | } 441 | return p86->samples[id]; 442 | } 443 | 444 | int P86_AddSample (p86_struct* p86, unsigned long length, signed char* data) { 445 | const char* p86_error_bank_full = "All bank IDs are already in use"; 446 | unsigned int i; 447 | 448 | for (i = 0; i <= 255; ++i) { 449 | if (P86_IsSet (p86, i) > 0) { 450 | printf ("Mapping sample to ID #%03u.\n", i); 451 | return P86_SetSample (p86, i, length, data); 452 | } 453 | } 454 | 455 | PMD_SetError (p86_error_bank_full); 456 | return 1; 457 | } 458 | 459 | int P86_UnsetSample (p86_struct* p86, unsigned char id) { 460 | if (P86_IsSet (p86, id) == 0) { 461 | if (p86->samples[id]->type == MEM) { 462 | free (p86->samples[id]->typeData.mem.data); 463 | } else { 464 | /* TODO handle FILE*s here */ 465 | } 466 | p86->samples[id]->length = 0; 467 | } 468 | 469 | P86_Print (p86); 470 | 471 | return 0; 472 | } 473 | 474 | int P86_RemoveSample (p86_struct* p86, unsigned char id) { 475 | unsigned int i; 476 | p86_sample* newSample; 477 | 478 | P86_UnsetSample (p86, id); 479 | 480 | for (i = id; i < 255; ++i) { 481 | p86->samples[i] = p86->samples[i+1]; 482 | --p86->samples[i]->id; 483 | } 484 | 485 | MALLOC_CHECK (newSample, sizeof (p86_sample)) { 486 | PMD_SetError (pmd_error_malloc, "p86_sample struct", sizeof (p86_sample)); 487 | return 1; 488 | } 489 | newSample->id = 255; 490 | newSample->length = 0; 491 | newSample->type = MEM; 492 | newSample->typeData.mem.data = NULL; 493 | 494 | p86->samples[255] = newSample; 495 | 496 | P86_Print (p86); 497 | 498 | return 0; 499 | } 500 | 501 | int P86_SwitchSamples (p86_struct* p86, unsigned char from, unsigned char to) { 502 | unsigned long tempLength; 503 | p86_sample_type tempType; 504 | p86_sample_data tempData; 505 | 506 | tempLength = p86->samples[to]->length; 507 | tempType = p86->samples[to]->type; 508 | tempData = p86->samples[to]->typeData; 509 | 510 | p86->samples[to]->length = p86->samples[from]->length; 511 | p86->samples[to]->type = p86->samples[from]->type; 512 | p86->samples[to]->typeData = p86->samples[from]->typeData; 513 | 514 | p86->samples[from]->length = tempLength; 515 | p86->samples[from]->type = tempType; 516 | p86->samples[from]->typeData = tempData; 517 | 518 | P86_Print (p86); 519 | 520 | return 0; 521 | } 522 | 523 | int P86_IsSet (const p86_struct* p86, unsigned char id) { 524 | return (p86->samples[id]->length > 0) ? 0 : 1; 525 | } 526 | 527 | void P86_Print (const p86_struct* p86) { 528 | char versionString[6]; 529 | unsigned int i; 530 | 531 | P86_GetVersionString (&p86->version, versionString); 532 | printf ("P86DRV version: %s\n", versionString); 533 | 534 | for (i = 0; i <= 255; ++i) { 535 | if (P86_IsSet (p86, i) == 0) { 536 | printf ("Sample #%03u set, Length: %lu.\n", i, p86->samples[i]->length); 537 | } 538 | } 539 | 540 | printf ("Total length: %lu.\n", P86_GetTotalLength (p86)); 541 | } 542 | 543 | int P86_GetVersionString (const unsigned char* version, char* buf) { 544 | return sprintf (buf, "%u.%u", *version >> 4, *version & 0x0F); 545 | } 546 | 547 | unsigned long P86_GetTotalLength (const p86_struct* p86) { 548 | const char* p86_error_length_overflow = "Overflow in total length calculation, current sum (%luB) " 549 | "+ current sample #%03u (%luB) exceeds data type limit (%luB)"; 550 | int i; 551 | unsigned long sum = P86_HEADERLENGTH; 552 | for (i = 0; i <= 255; ++i) { 553 | if (p86->samples[i]->length > 0) { 554 | if (sum > (ULONG_MAX - p86->samples[i]->length)) { 555 | PMD_SetError (p86_error_length_overflow, sum, i, p86->samples[i]->length, ULONG_MAX); 556 | return 0; 557 | } 558 | sum += p86->samples[i]->length; 559 | } 560 | } 561 | return sum; 562 | } 563 | -------------------------------------------------------------------------------- /tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable (loadtest loadtest.c) 2 | target_link_libraries (loadtest PRIVATE openpmd) 3 | target_include_directories (loadtest PUBLIC ${INCLUDE_PATH}) 4 | install (TARGETS loadtest 5 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 6 | ) 7 | 8 | add_executable (p86create p86create.c) 9 | target_link_libraries (p86create PRIVATE openpmd) 10 | target_include_directories (p86create PUBLIC ${INCLUDE_PATH}) 11 | install (TARGETS p86create 12 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 13 | ) 14 | 15 | add_executable (p86extract p86extract.c) 16 | target_link_libraries (p86extract PRIVATE openpmd) 17 | target_include_directories (p86extract PUBLIC ${INCLUDE_PATH}) 18 | install (TARGETS p86extract 19 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 20 | ) 21 | -------------------------------------------------------------------------------- /tools/loadtest.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "p86.h" 3 | 4 | /* 5 | * Possible return values: 6 | * * 0 - No Error 7 | * * 1 - Could not open file 8 | * * 2 - Export function failed, see PMD_GetError() for details 9 | */ 10 | int export (const p86_struct* data, const char* outPath) { 11 | FILE* outFile; 12 | 13 | printf ("Testing exporting to %s...\n", outPath); 14 | 15 | outFile = fopen (outPath, "wb"); 16 | if (outFile == NULL) { 17 | printf ("ERROR: Could not open file %s.\n", outPath); 18 | return 1; 19 | } 20 | 21 | if (P86_ExportFile (data, outFile) != 0) { 22 | fclose (outFile); 23 | printf ("ERROR: %s\n", PMD_GetError()); 24 | return 2; 25 | } 26 | 27 | fclose (outFile); 28 | return 0; 29 | } 30 | 31 | int main (int argc, char* argv[]) { 32 | char* p86FileInput; 33 | const char* p86FileOutputMem = "TEST_MEM.P86"; 34 | const char* p86FileOutputFil = "TEST_FIL.P86"; 35 | FILE* inpFile; 36 | p86_struct* parsedData; 37 | boolean error = false; 38 | 39 | if (argc < 2) { 40 | printf ("Usage: /path/to/input.86\n"); 41 | return 1; 42 | } else { 43 | p86FileInput = argv[1]; 44 | } 45 | 46 | inpFile = fopen (p86FileInput, "rb"); 47 | if (inpFile == NULL) { 48 | printf ("ERROR: Could not open file %s.\n", p86FileInput); 49 | return 1; 50 | } 51 | 52 | /* P86_ImportFile_Memory */ 53 | 54 | printf ("Testing memory-heavy importing of %s...\n", p86FileInput); 55 | parsedData = P86_ImportFile_Memory (inpFile); 56 | if (parsedData != NULL) { 57 | if (export (parsedData, p86FileOutputMem) == 0) { 58 | printf ("Success!\n"); 59 | } else { 60 | printf ("Export failed: %s\n", PMD_GetError()); 61 | error = true; 62 | } 63 | P86_Free (parsedData); 64 | } else { 65 | printf ("Import failed: %s\n", PMD_GetError()); 66 | error = true; 67 | } 68 | 69 | fseek (inpFile, 0, SEEK_SET); 70 | 71 | /* P86_ImportFile_File */ 72 | 73 | printf ("Testing memory-light importing of %s...\n", p86FileInput); 74 | parsedData = P86_ImportFile_File (inpFile); 75 | if (parsedData != NULL) { 76 | if (export (parsedData, p86FileOutputFil) == 0) { 77 | printf ("Success!\n"); 78 | } else { 79 | printf ("Export failed: %s\n", PMD_GetError()); 80 | error = true; 81 | } 82 | P86_Free (parsedData); 83 | } else { 84 | printf ("Import failed: %s\n", PMD_GetError()); 85 | error = true; 86 | } 87 | 88 | fclose (inpFile); 89 | 90 | printf ("Loadtest done.\n"); 91 | return !error ? 0 : 1; 92 | } 93 | -------------------------------------------------------------------------------- /tools/p86create.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "p86.h" 3 | 4 | #define OUTPUT_DEFAULT "TEST.P86" 5 | 6 | int main (int argc, char* argv[]) { 7 | size_t readAmount; 8 | int i; 9 | FILE* fileHandle; 10 | p86_struct* newBank; 11 | unsigned long sampleSize; 12 | signed char* sampleBuffer; 13 | 14 | if (argc < 2) { 15 | printf ("Usage: %s sample1.raw sample2.raw ...\n\n", argv[0]); 16 | printf ("Sample files must fit internal P86 format & size limitations:\n"); 17 | printf ("\tEncoding: 8-Bit Signed\n"); 18 | printf ("\tPolyphony: Mono\n"); 19 | printf ("\tSample rate: 16540Hz (recommended)\n"); 20 | printf ("\tMax size: 16775663 Bytes (all samples added together)\n"); 21 | return 1; 22 | } 23 | 24 | newBank = P86_New (); 25 | if (newBank == NULL) { 26 | printf ("ERROR: %s.\n", PMD_GetError()); 27 | return 1; 28 | } 29 | 30 | for (i = 1; i < argc; ++i) { 31 | printf ("%s...\n", argv[i]); 32 | fileHandle = fopen (argv[i], "rb"); 33 | if (fileHandle == NULL) { 34 | printf ("ERROR: Could not open file %s.\n", argv[i]); 35 | perror (NULL); 36 | P86_Free (newBank); 37 | return 1; 38 | } 39 | fseek (fileHandle, 0, SEEK_END); 40 | sampleSize = ftell (fileHandle); 41 | fseek (fileHandle, 0, SEEK_SET); 42 | 43 | MALLOC_CHECK (sampleBuffer, sampleSize) { 44 | PMD_SetError (pmd_error_malloc, argv[1], sampleSize); 45 | P86_Free (newBank); 46 | } 47 | 48 | readAmount = fread (sampleBuffer, sizeof (char), sampleSize, fileHandle); 49 | if (readAmount < sampleSize) { 50 | printf ("ERROR: Failed to read %lu bytes from %s.\n", sampleSize, argv[i]); 51 | free (sampleBuffer); 52 | fclose (fileHandle); 53 | P86_Free (newBank); 54 | return 1; 55 | } 56 | 57 | P86_AddSample (newBank, sampleSize, sampleBuffer); 58 | 59 | free (sampleBuffer); 60 | fclose (fileHandle); 61 | } 62 | 63 | printf ("Exporting to %s.\n", OUTPUT_DEFAULT); 64 | fileHandle = fopen (OUTPUT_DEFAULT, "wb"); 65 | P86_ExportFile (newBank, fileHandle); 66 | fclose (fileHandle); 67 | 68 | P86_Free (newBank); 69 | 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /tools/p86extract.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "p86.h" 3 | 4 | #define OUTPUT_PREFIX "TEST_" 5 | 6 | int main (int argc, char* argv[]) { 7 | int i; 8 | char* fileName; 9 | FILE* fileHandle; 10 | p86_struct* p86Bank; 11 | const p86_sample* p86Sample; 12 | 13 | if (argc < 2) { 14 | printf ("Usage: %s bank.P86\n\n", argv[0]); 15 | printf ("Extracted samples have the following raw format:\n"); 16 | printf ("\tEncoding: 8-Bit Signed\n"); 17 | printf ("\tPolyphony: Mono\n"); 18 | printf ("\tSample rate: 16540Hz (usually)\n"); 19 | return 1; 20 | } 21 | 22 | fileName = argv[1]; 23 | fileHandle = fopen (fileName, "rb"); 24 | if (fileHandle == NULL) { 25 | printf ("ERROR: Could not open file %s.\n", fileName); 26 | perror (NULL); 27 | return 1; 28 | } 29 | 30 | p86Bank = P86_ImportFile (fileHandle); 31 | fclose (fileHandle); 32 | 33 | if (p86Bank == NULL) { 34 | printf ("ERROR: %s\n", PMD_GetError()); 35 | return 1; 36 | } 37 | 38 | for (i = 0; i < 255; ++i) { 39 | if (P86_IsSet (p86Bank, i) == 0) { 40 | printf ("%03d...\n", i); 41 | 42 | p86Sample = P86_GetSample (p86Bank, i); 43 | if (p86Sample == NULL) { 44 | printf ("ERROR: Failed to acquire copy of sample data %03d.\n", i); 45 | P86_Free (p86Bank); 46 | return 1; 47 | } 48 | 49 | sprintf (fileName, "%s%03d.RAW", OUTPUT_PREFIX, i); 50 | fileHandle = fopen (fileName, "wb"); 51 | if (fileHandle == NULL) { 52 | printf ("ERROR: Could not open file %s.\n", fileName); 53 | perror (NULL); 54 | P86_Free (p86Bank); 55 | return 1; 56 | } 57 | 58 | fwrite (p86Sample->typeData.mem.data, p86Sample->length, sizeof (char), fileHandle); 59 | if (ferror (fileHandle)) { 60 | printf ("ERROR: Error occurred while writing to file.\n"); 61 | fclose (fileHandle); 62 | P86_Free (p86Bank); 63 | return 1; 64 | } 65 | 66 | fclose (fileHandle); 67 | printf ("Exported sample %s.\n", fileName); 68 | } 69 | } 70 | 71 | P86_Free (p86Bank); 72 | 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /watcom.txt: -------------------------------------------------------------------------------- 1 | mkdir build 2 | cd build 3 | unset CC 4 | unset CXX 5 | 6 | # 16-bit DOS: export PROCESSOR=I86 7 | # 32-bit DOS w/ DOS4/GW: export PROCESSOR=X86 8 | export PROCESSOR=I86 9 | 10 | cmake -G "Watcom WMake" -DCMAKE_SYSTEM_NAME=DOS -DCMAKE_SYSTEM_PROCESSOR=${PROCESSOR} .. 11 | wmake 12 | 13 | ## For running 32-bit on PC-98: 14 | 15 | # Copy ${WATCOM}/binw/dos4gw.exe along with the binaries 16 | set DOS16M=1 @2M-4M 17 | 18 | # Prepend every binary you want to run with "DOS4GW.exe ", i.e. for loadtest: 19 | DOS4GW.EXE LOADTEST.EXE TEST.P86 20 | --------------------------------------------------------------------------------