├── .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 |
--------------------------------------------------------------------------------