mirror of
https://github.com/ScrelliCopter/VGM-Tools
synced 2025-02-21 04:09:25 +11:00
Compare commits
26 Commits
v0.1.0
...
6510096f90
| Author | SHA1 | Date | |
|---|---|---|---|
| 6510096f90 | |||
| 07ee0b546c | |||
| 454fcd3226 | |||
| c94016e793 | |||
| 172174c837 | |||
| 3fafdfd3f4 | |||
| 6ccf9f6e38 | |||
| b12258970e | |||
| 886966d7dc | |||
| 63aacb9a01 | |||
| 7c66bbab52 | |||
| fbf887b534 | |||
| 0a26bc8998 | |||
| 6d60ea91c0 | |||
| 6164533c1a | |||
| 7c5a577fba | |||
| 7e0740d03a | |||
| c1a5003a82 | |||
| 1969f7ae9a | |||
| 33851696b1 | |||
| 7aff052810 | |||
| 85a3e5089b | |||
| 508f464aa7 | |||
| 86317f57ec | |||
| d0945220a6 | |||
| 45ec9cf2cf |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,6 +1 @@
|
|||||||
*.exe
|
.idea/
|
||||||
*.vgm
|
|
||||||
*.vgz
|
|
||||||
*.log
|
|
||||||
*.pcm
|
|
||||||
*.wav
|
|
||||||
|
|||||||
32
README.md
32
README.md
@@ -1,32 +0,0 @@
|
|||||||
Neo-Geo VGM tools
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
A hodge-podge of tools for working on NG VGM files,
|
|
||||||
these files are provided in the hope that they may be useful to someone.
|
|
||||||
|
|
||||||
Makefiles have currently not been tested but shouldn't be hard to get working.
|
|
||||||
Windows binaries are available under the Releases tab.
|
|
||||||
|
|
||||||
Included tools (sources included).
|
|
||||||
- **adpcm.exe**:
|
|
||||||
ADPCM Type-A to WAV converter.
|
|
||||||
- **adpcmb.exe**:
|
|
||||||
ADPCM Type-B encoding tool that also does decoding to WAV.
|
|
||||||
- **neoadpcmextract.exe**:
|
|
||||||
Scans a .vgm and dumps all ADPCM type A&B data to raw .pcm files.
|
|
||||||
- **autoextract.bat**:
|
|
||||||
Convenience batch script that uses the above tools to dump all samples to WAVs.
|
|
||||||
|
|
||||||
How to use
|
|
||||||
----------
|
|
||||||
Copy your .vgz into this directory, drag it onto autoextract.bat,
|
|
||||||
the script will create a directory of the same name and dump all the converted samples there.
|
|
||||||
|
|
||||||
That's all there is to it.
|
|
||||||
|
|
||||||
TODO
|
|
||||||
----
|
|
||||||
|
|
||||||
- Replace the batch script with something less shite (and actually cross-platform).
|
|
||||||
- Write a makefile for neoadpcmextract.
|
|
||||||
- Make this stuff build on Linux.
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
# Standard makefile to use as a base for DJGPP projects
|
|
||||||
# By MARTINEZ Fabrice aka SNK of SUPREMACY
|
|
||||||
|
|
||||||
# Programs to use during make
|
|
||||||
AR = ar
|
|
||||||
CC = gcc
|
|
||||||
LD = gcc
|
|
||||||
ASM = nasm
|
|
||||||
PACKER = upx
|
|
||||||
|
|
||||||
# Flags for debugging
|
|
||||||
#DEBUG=1
|
|
||||||
#SYMBOLS=1
|
|
||||||
|
|
||||||
# Flags for compilation
|
|
||||||
ASMFLAGS = -f coff
|
|
||||||
ifdef SYMBOLS
|
|
||||||
CFLAGS = -o -mpentium -Wall -Werror -g
|
|
||||||
LDFLAGS = -s
|
|
||||||
else
|
|
||||||
CFLAGS = -fomit-frame-pointer -O3 -mpentium -Werror -Wall \
|
|
||||||
-W -Wno-sign-compare -Wno-unused \
|
|
||||||
-Wpointer-arith -Wbad-function-cast -Wcast-align -Waggregate-return \
|
|
||||||
-pedantic \
|
|
||||||
-Wshadow \
|
|
||||||
-Wstrict-prototypes
|
|
||||||
LDFLAGS =
|
|
||||||
endif
|
|
||||||
CDEFS =
|
|
||||||
ASMDEFS =
|
|
||||||
|
|
||||||
# Object files
|
|
||||||
MAINOBJS =
|
|
||||||
|
|
||||||
# Library files
|
|
||||||
MAINLIBS = obj/adpcm.a
|
|
||||||
|
|
||||||
# Make rules
|
|
||||||
all: adpcm.exe
|
|
||||||
|
|
||||||
adpcm.exe: $(MAINOBJS) $(MAINLIBS)
|
|
||||||
$(LD) $(LDFLAGS) $(MAINOBJS) $(MAINLIBS) -o $@
|
|
||||||
|
|
||||||
src/%.asm:
|
|
||||||
|
|
||||||
obj/%.o: src/%.c
|
|
||||||
$(CC) $(CDEFS) $(CFLAGS) -c $< -o $@
|
|
||||||
|
|
||||||
obj/%.oa: src/%.asm
|
|
||||||
$(ASM) -o $@ $(ASMFLAGS) $(ASMDEFS) $<
|
|
||||||
|
|
||||||
obj/%.a:
|
|
||||||
$(AR) cr $@ $^
|
|
||||||
|
|
||||||
# Rules to manage files
|
|
||||||
pack: adpcm.exe
|
|
||||||
$(PACKER) adpcm.exe
|
|
||||||
|
|
||||||
mkdir:
|
|
||||||
md obj
|
|
||||||
|
|
||||||
clean:
|
|
||||||
del obj\*.o
|
|
||||||
del obj\*.a
|
|
||||||
del obj\*.oa
|
|
||||||
del *.exe
|
|
||||||
|
|
||||||
# Rules to make libraries
|
|
||||||
obj/adpcm.a: obj/adpcm.o
|
|
||||||
# obj/decode.oa \
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
/* neoadpcmextract.cpp
|
|
||||||
Copyright (C) 2017 Nicholas Curtis
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
|
||||||
warranty. In no event will the authors be held liable for any damages
|
|
||||||
arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software
|
|
||||||
in a product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
3. This notice may not be removed or altered from any source distribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <string>
|
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
void DecodeSample ( std::ifstream& a_file, std::vector<uint8_t>& a_out )
|
|
||||||
{
|
|
||||||
// Set up output vector.
|
|
||||||
uint32_t sampLen = 0;
|
|
||||||
a_file.read ( (char*)&sampLen, sizeof(uint32_t) );
|
|
||||||
if ( sampLen < sizeof(uint64_t) )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sampLen -= sizeof(uint64_t);
|
|
||||||
a_out.clear ();
|
|
||||||
a_out.resize ( sampLen );
|
|
||||||
|
|
||||||
// Ignore 8 bytes.
|
|
||||||
uint64_t dummy;
|
|
||||||
a_file.read ( (char*)&dummy, sizeof(uint64_t) );
|
|
||||||
|
|
||||||
// Read adpcm data.
|
|
||||||
a_file.read ( (char*)a_out.data (), sampLen );
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpBytes ( std::string a_path, const std::vector<uint8_t>& a_bytes )
|
|
||||||
{
|
|
||||||
std::ofstream fileOut ( a_path, std::ios::binary );
|
|
||||||
fileOut.write ( (const char*)a_bytes.data (), a_bytes.size () );
|
|
||||||
fileOut.close ();
|
|
||||||
}
|
|
||||||
|
|
||||||
int main ( int argc, char** argv )
|
|
||||||
{
|
|
||||||
if ( argc != 2 )
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open file.
|
|
||||||
std::ifstream file ( argv[1], std::ios::binary );
|
|
||||||
if ( !file.is_open () )
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for pcm headers.
|
|
||||||
std::vector<uint8_t> smpBytes;
|
|
||||||
int smpA = 0, smpB = 0;
|
|
||||||
while ( !file.eof () && !file.fail () )
|
|
||||||
{
|
|
||||||
uint8_t byte;
|
|
||||||
|
|
||||||
file >> byte;
|
|
||||||
if ( byte == 0x67 )
|
|
||||||
{
|
|
||||||
file >> byte;
|
|
||||||
if ( byte == 0x66 )
|
|
||||||
{
|
|
||||||
file >> byte;
|
|
||||||
if ( byte == 0x82 )
|
|
||||||
{
|
|
||||||
std::cout << "ADPCM-A data found at 0x" << std::hex << file.tellg () << std::endl;
|
|
||||||
DecodeSample ( file, smpBytes );
|
|
||||||
std::stringstream path;
|
|
||||||
path << std::hex << "smpa_" << (smpA++) << ".pcm";
|
|
||||||
DumpBytes ( path.str (), smpBytes );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if ( byte == 0x83 )
|
|
||||||
{
|
|
||||||
std::cout << "ADPCM-B data found at 0x" << std::hex << file.tellg () << std::endl;
|
|
||||||
DecodeSample ( file, smpBytes );
|
|
||||||
std::stringstream path;
|
|
||||||
path << std::hex << "smpb_" << (smpB++) << ".pcm";
|
|
||||||
DumpBytes ( path.str (), smpBytes );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file.close ();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
11
neotools/.gitignore
vendored
Normal file
11
neotools/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
*.exe
|
||||||
|
*.vgm
|
||||||
|
*.vgz
|
||||||
|
*.log
|
||||||
|
*.pcm
|
||||||
|
*.wav
|
||||||
|
|
||||||
|
*.o
|
||||||
|
adpcm
|
||||||
|
adpcmb
|
||||||
|
neoadpcmextract
|
||||||
46
neotools/README.md
Normal file
46
neotools/README.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
Neo-Geo VGM tools
|
||||||
|
=================
|
||||||
|
|
||||||
|
A hodge-podge of tools for working on NG VGM files,
|
||||||
|
these files are provided in the hope that they may be useful to someone.
|
||||||
|
|
||||||
|
Included tools (sources included).
|
||||||
|
- **adpcm**:
|
||||||
|
ADPCM Type-A to WAV converter.
|
||||||
|
- **adpcmb**:
|
||||||
|
ADPCM Type-B encoding tool that also does decoding to WAV.
|
||||||
|
- **neoadpcmextract**:
|
||||||
|
Scans a .vgm and dumps all ADPCM type A&B data to raw .pcm files.
|
||||||
|
- **autoextract**:
|
||||||
|
Convenience shell/batch script that uses the above tools to dump all samples to WAVs.
|
||||||
|
|
||||||
|
Building
|
||||||
|
--------
|
||||||
|
Linux:
|
||||||
|
```shell script
|
||||||
|
make -f adpcm.Makefile
|
||||||
|
make -f adpcmb.Makefile
|
||||||
|
make -f neoadpcmextract.Makefile
|
||||||
|
```
|
||||||
|
|
||||||
|
Windows:
|
||||||
|
- The updated Makefiles haven't been tested on Windows yet.
|
||||||
|
- Working Windows binaries are available from the [Releases](https://github.com/ScrelliCopter/VGM-Tools/releases) tab.
|
||||||
|
|
||||||
|
How to use
|
||||||
|
----------
|
||||||
|
Linux:
|
||||||
|
- Run `./autoextract.sh` from this directory with a path to your .vgm or .vgz,
|
||||||
|
a directory for the song will be created containing wav samples.
|
||||||
|
|
||||||
|
Windows:
|
||||||
|
- You will need gzip.exe (provided with the aforementioned release).
|
||||||
|
- Copy your .vgm or .vgz into this directory, drag it onto autoextract.bat,
|
||||||
|
the script will create a directory of the same name and dump all the converted samples there.
|
||||||
|
|
||||||
|
That's all there is to it.
|
||||||
|
|
||||||
|
TODO
|
||||||
|
----
|
||||||
|
|
||||||
|
- Unify all tools into one & obsolete the crappy glue scripts.
|
||||||
34
neotools/adpcm.Makefile
Normal file
34
neotools/adpcm.Makefile
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Standard makefile to use as a base for DJGPP projects (not anymore lol)
|
||||||
|
# By MARTINEZ Fabrice aka SNK of SUPREMACY
|
||||||
|
|
||||||
|
# Programs to use during make
|
||||||
|
LD := $(CC)
|
||||||
|
|
||||||
|
TARGET := adpcm
|
||||||
|
SOURCE := adpcm.c
|
||||||
|
|
||||||
|
# Flags for compilation
|
||||||
|
CFLAGS := -fomit-frame-pointer -O3 -Werror -Wall \
|
||||||
|
-W -Wno-sign-compare -Wno-unused \
|
||||||
|
-Wpointer-arith -Wbad-function-cast -Wcast-align -Waggregate-return \
|
||||||
|
-pedantic \
|
||||||
|
-Wshadow \
|
||||||
|
-Wstrict-prototypes
|
||||||
|
LDFLAGS := -lm
|
||||||
|
|
||||||
|
# Object files
|
||||||
|
OBJECT := $(SOURCE:%.c=%.o)
|
||||||
|
|
||||||
|
# Make rules
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(OBJECT)
|
||||||
|
$(LD) $(CFLAGS) $(LDFLAGS) $^ -o $@
|
||||||
|
|
||||||
|
%.o: %.c
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
# Rules to manage files
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -f $(TARGET) $(OBJECT)
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
#include <process.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <mem.h>
|
#include <stdlib.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <io.h>
|
|
||||||
|
|
||||||
#define BUFFER_SIZE 1024*256
|
#define BUFFER_SIZE 1024*256
|
||||||
#define ADPCMA_VOLUME_RATE 1
|
#define ADPCMA_VOLUME_RATE 1
|
||||||
@@ -70,8 +68,11 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
adpcm_init();
|
adpcm_init();
|
||||||
|
|
||||||
Filelen = filelength(fileno(Fp1));
|
fseek(Fp1, 0, SEEK_END);
|
||||||
|
Filelen = ftell(Fp1);
|
||||||
|
fseek(Fp1, 0, SEEK_SET);
|
||||||
|
|
||||||
*((unsigned int*)(&RiffWave[4])) = Filelen*4 + 0x2C;
|
*((unsigned int*)(&RiffWave[4])) = Filelen*4 + 0x2C;
|
||||||
*((unsigned int*)(&RiffWave[0x28])) = Filelen*4;
|
*((unsigned int*)(&RiffWave[0x28])) = Filelen*4;
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@ CFLAGS = -O3
|
|||||||
SRC = .
|
SRC = .
|
||||||
OBJ = .
|
OBJ = .
|
||||||
|
|
||||||
OUT_OBJ = $(OBJ)/adpcmb.exe
|
OUT_OBJ = $(OBJ)/adpcmb
|
||||||
|
|
||||||
all: $(OUT_OBJ)
|
all: $(OUT_OBJ)
|
||||||
|
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
if ["%~x1"]==[".vgz"] goto vgztopcm else goto vgmtopcm
|
if ["%~x1"]==[".vgz"] goto vgztopcm else goto vgmtopcm
|
||||||
|
|
||||||
:vgmtopcm
|
:vgmtopcm
|
||||||
neoadpcmextract.exe %1
|
neoadpcmextract.exe %1
|
||||||
goto pcmtowav
|
goto pcmtowav
|
||||||
|
|
||||||
:vgztopcm
|
:vgztopcm
|
||||||
copy /y %1 temp.vgm.gz
|
copy /y %1 temp.vgm.gz
|
||||||
gzip.exe -d temp.vgm.gz
|
gzip.exe -d temp.vgm.gz
|
||||||
neoadpcmextract.exe temp.vgm
|
neoadpcmextract.exe temp.vgm
|
||||||
del temp.vgm
|
del temp.vgm
|
||||||
goto pcmtowav
|
goto pcmtowav
|
||||||
|
|
||||||
:pcmtowav
|
:pcmtowav
|
||||||
for /r %%v in (smpa_*.pcm) do adpcm.exe "%%v" "%%v.wav"
|
for /r %%v in (smpa_*.pcm) do adpcm.exe "%%v" "%%v.wav"
|
||||||
for /r %%v in (smpb_*.pcm) do adpcmb.exe -d "%%v" "%%v.wav"
|
for /r %%v in (smpb_*.pcm) do adpcmb.exe -d "%%v" "%%v.wav"
|
||||||
del "*.pcm"
|
del "*.pcm"
|
||||||
mkdir "%~n1"
|
mkdir "%~n1"
|
||||||
move "*.wav" "%~n1"
|
move "*.wav" "%~n1"
|
||||||
20
neotools/autoextract.sh
Executable file
20
neotools/autoextract.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
FILE="$1"
|
||||||
|
NAME="$(basename "$FILE")"
|
||||||
|
WAVDIR="${NAME%%.*}"
|
||||||
|
|
||||||
|
if [ "${NAME##*.}" = "vgz" ]; then
|
||||||
|
cp "$FILE" "temp.vgm.gz"
|
||||||
|
gzip -fd "temp.vgm.gz"
|
||||||
|
FILE="temp.vgm"
|
||||||
|
fi
|
||||||
|
|
||||||
|
./neoadpcmextract "$FILE"
|
||||||
|
mkdir -p "$WAVDIR"
|
||||||
|
for I in smpa_*.pcm; do ./adpcm "$I" "$WAVDIR/${I%%.*}.wav"; done
|
||||||
|
for I in smpb_*.pcm; do ./adpcmb -d "$I" "$WAVDIR/${I%%.*}.wav"; done
|
||||||
|
find . -type f -name "*.pcm" -exec rm -f {} \;
|
||||||
|
|
||||||
|
[ "$FILE" = "temp.vgm" ] && rm -f "temp.vgm"
|
||||||
12
neotools/neoadpcmextract.Makefile
Normal file
12
neotools/neoadpcmextract.Makefile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
TARGET := neoadpcmextract
|
||||||
|
SOURCE := neoadpcmextract.c
|
||||||
|
CFLAGS := -std=c99 -O2 -pipe -Wall -Wextra -pedantic
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(SOURCE)
|
||||||
|
$(CC) $(CFLAGS) $< -o $@
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -f $(TARGET)
|
||||||
97
neotools/neoadpcmextract.c
Normal file
97
neotools/neoadpcmextract.c
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/* neoadpcmextract.cpp
|
||||||
|
Copyright (C) 2017, 2019 Nicholas Curtis
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct { uint8_t* data; size_t size; } Buffer;
|
||||||
|
|
||||||
|
void DecodeSample(FILE* fin, const char* name, Buffer* buf)
|
||||||
|
{
|
||||||
|
// Get sample data length.
|
||||||
|
uint32_t sampLen = 0;
|
||||||
|
fread(&sampLen, sizeof(uint32_t), 1, fin);
|
||||||
|
if (sampLen < sizeof(uint64_t))
|
||||||
|
return;
|
||||||
|
sampLen -= sizeof(uint64_t);
|
||||||
|
|
||||||
|
// Resize buffer if needed.
|
||||||
|
if (!buf->data || buf->size < sampLen)
|
||||||
|
{
|
||||||
|
free(buf->data);
|
||||||
|
buf->data = malloc(sampLen);
|
||||||
|
buf->size = sampLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore 8 bytes.
|
||||||
|
uint64_t dummy;
|
||||||
|
fread(&dummy, sizeof(uint64_t), 1, fin);
|
||||||
|
|
||||||
|
// Read adpcm data.
|
||||||
|
fread(buf->data, sizeof(uint8_t), sampLen, fin);
|
||||||
|
|
||||||
|
FILE* fout = fopen(name, "wb");
|
||||||
|
if (!fout)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fwrite(buf->data, sizeof(uint8_t), sampLen, fout);
|
||||||
|
fclose(fout);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
if (argc != 2)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// Open file.
|
||||||
|
FILE* file = fopen(argv[1], "rb");
|
||||||
|
if (!file)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// Search for pcm headers.
|
||||||
|
Buffer smpBytes = {NULL, 0};
|
||||||
|
char namebuf[32];
|
||||||
|
int smpaCount = 0, smpbCount = 0;
|
||||||
|
while (!feof(file) && !ferror(file))
|
||||||
|
{
|
||||||
|
if (fgetc(file) != 0x67 || fgetc(file) != 0x66)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
uint8_t byte = fgetc(file);
|
||||||
|
if (byte == 0x82)
|
||||||
|
{
|
||||||
|
printf("ADPCM-A data found at 0x%08lX\n", ftell(file));
|
||||||
|
snprintf(namebuf, sizeof(namebuf), "smpa_%x.pcm", smpaCount++);
|
||||||
|
DecodeSample(file, namebuf, &smpBytes);
|
||||||
|
}
|
||||||
|
else if (byte == 0x83)
|
||||||
|
{
|
||||||
|
printf("ADPCM-B data found at 0x%08lX\n", ftell(file));
|
||||||
|
snprintf(namebuf, sizeof(namebuf), "smpb_%x.pcm", smpbCount++);
|
||||||
|
DecodeSample(file, namebuf, &smpBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(smpBytes.data);
|
||||||
|
fclose(file);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
214
spctools/ripsamples.py
Executable file
214
spctools/ripsamples.py
Executable file
@@ -0,0 +1,214 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
''' ripsamples.py -- a python script for mass extracting samples from SPC files.
|
||||||
|
|
||||||
|
Copyright (C) 2018 Nicholas Curtis (a_dinosaur)
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import pathlib
|
||||||
|
import struct
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
# Directory constants.
|
||||||
|
SPCDIR = "./spc"
|
||||||
|
ITDIR = "./it"
|
||||||
|
SMPDIR = "./sample"
|
||||||
|
|
||||||
|
# External programs used by this script.
|
||||||
|
SPC2IT = "spc2it"
|
||||||
|
|
||||||
|
|
||||||
|
class Sample:
|
||||||
|
length = 0
|
||||||
|
loopBeg = 0
|
||||||
|
loopEnd = 0
|
||||||
|
rate = 0
|
||||||
|
|
||||||
|
data = None
|
||||||
|
|
||||||
|
def writesmp(smp, path):
|
||||||
|
with open(path, "wb") as wav:
|
||||||
|
|
||||||
|
# Make sure sample rate is nonzero.
|
||||||
|
#TODO: figure out why this even happens...
|
||||||
|
if smp.rate == 0:
|
||||||
|
smp.rate = 32000
|
||||||
|
#print(path + " may be corrupted...")
|
||||||
|
|
||||||
|
writeLoop = True if smp.loopEnd > smp.loopBeg else False
|
||||||
|
|
||||||
|
# Write RIFF chunk.
|
||||||
|
wav.write(b"RIFF")
|
||||||
|
# Size of entire file following
|
||||||
|
riffSize = 104 if writeLoop else 36
|
||||||
|
wav.write(struct.pack("<I", riffSize + smp.length * 2))
|
||||||
|
wav.write(b"WAVE")
|
||||||
|
|
||||||
|
# Write fmt chunk.
|
||||||
|
wav.write(b"fmt ")
|
||||||
|
wav.write(struct.pack("<I", 16)) # Subchunk size.
|
||||||
|
wav.write(struct.pack("<H", 1)) # Audio format (uncompressed)
|
||||||
|
wav.write(struct.pack("<H", 1)) # Channel count (mono)
|
||||||
|
wav.write(struct.pack("<I", smp.rate)) # Samplerate
|
||||||
|
wav.write(struct.pack("<I", smp.rate * 2 )) # Byte rate (16 bit mono)
|
||||||
|
wav.write(struct.pack("<H", 2)) # Bytes per sample (16 bit mono)
|
||||||
|
wav.write(struct.pack("<H", 16)) # Bits per sample (16 bit)
|
||||||
|
|
||||||
|
# Write sampler chunk (if looped).
|
||||||
|
if writeLoop:
|
||||||
|
wav.write(b"smpl")
|
||||||
|
wav.write(struct.pack("<I", 60)) # Chunk size (36 + loops * 24)
|
||||||
|
wav.write(b"\x00\x00\x00\x00") # Manufacturer
|
||||||
|
wav.write(b"\x00\x00\x00\x00") # Product
|
||||||
|
wav.write(b"\x00\x00\x00\x00") # Sample period
|
||||||
|
wav.write(b"\x00\x00\x00\x00") # MIDI unity note
|
||||||
|
wav.write(b"\x00\x00\x00\x00") # MIDI pitch fraction
|
||||||
|
wav.write(b"\x00\x00\x00\x00") # SMPTE format
|
||||||
|
wav.write(b"\x00\x00\x00\x00") # SMPTE offset
|
||||||
|
wav.write(struct.pack("<I", 1)) # Loop count
|
||||||
|
wav.write(struct.pack("<I", 24)) # Loop data length
|
||||||
|
|
||||||
|
wav.write(struct.pack("<I", 0)) # Cue point ID (none)
|
||||||
|
wav.write(struct.pack("<I", 0)) # Loop type (forward)
|
||||||
|
wav.write(struct.pack("<I", smp.loopBeg)) # Loop start
|
||||||
|
wav.write(struct.pack("<I", smp.loopEnd)) # Loop end
|
||||||
|
wav.write(struct.pack("<I", 0)) # Fraction (none)
|
||||||
|
wav.write(struct.pack("<I", 0)) # Loop count (infinite)
|
||||||
|
|
||||||
|
# Write data chunk.
|
||||||
|
wav.write(b"data")
|
||||||
|
wav.write(struct.pack("<I", smp.length * 2))
|
||||||
|
wav.write(smp.data)
|
||||||
|
|
||||||
|
def readsmp(f, ofs, idx):
|
||||||
|
# List of assumptions made:
|
||||||
|
# - Samples are 16 bit
|
||||||
|
# - Samples are mono
|
||||||
|
# - Samples are signed
|
||||||
|
# - No compression
|
||||||
|
# - No sustain loop
|
||||||
|
# - Loops are forwards
|
||||||
|
# - Global volume is ignored
|
||||||
|
|
||||||
|
f.seek(ofs)
|
||||||
|
if f.read(4) != b"IMPS": return None
|
||||||
|
|
||||||
|
# Skip fname to flags & read.
|
||||||
|
f.seek(ofs + 0x12)
|
||||||
|
flags = int.from_bytes(f.read(1), byteorder="little", signed=False)
|
||||||
|
|
||||||
|
# Read flag values.
|
||||||
|
if not flags & 0b00000001: return None # Check sample data bit.
|
||||||
|
loopBit = True if flags & 0b00010000 else False
|
||||||
|
|
||||||
|
smp = Sample()
|
||||||
|
|
||||||
|
# Read the rest of the header.
|
||||||
|
f.seek(ofs + 0x30)
|
||||||
|
smp.length = int.from_bytes(f.read(4), byteorder="little", signed=False)
|
||||||
|
if loopBit:
|
||||||
|
smp.loopBeg = int.from_bytes(f.read(4), byteorder="little", signed=False)
|
||||||
|
smp.loopEnd = int.from_bytes(f.read(4), byteorder="little", signed=False)
|
||||||
|
else:
|
||||||
|
f.seek(8, 1) # Skip over.
|
||||||
|
smp.loopBeg = 0
|
||||||
|
smp.loopEnd = 0
|
||||||
|
smp.rate = int.from_bytes(f.read(4), byteorder="little", signed=False)
|
||||||
|
f.seek(8, 1) # Skip over sustain shit.
|
||||||
|
|
||||||
|
# Read sample data.
|
||||||
|
dataOfs = int.from_bytes(f.read(4), byteorder="little", signed=False)
|
||||||
|
smp.data = f.read(smp.length * 2)
|
||||||
|
|
||||||
|
# Compute hash of data.
|
||||||
|
#FIXME: This actually generates a butt ton of collisions...
|
||||||
|
# there's got to be a better way!
|
||||||
|
h = hashlib.md5(struct.pack("<pII", smp.data, smp.loopBeg, smp.loopEnd))
|
||||||
|
smp.hash = h.hexdigest()
|
||||||
|
|
||||||
|
return smp
|
||||||
|
|
||||||
|
def readit(path, outpath):
|
||||||
|
with open(path, "r+b") as f:
|
||||||
|
|
||||||
|
# Don't bother scanning non IT files.
|
||||||
|
if f.read(4) != b"IMPM": return
|
||||||
|
|
||||||
|
#print("Song name: " + f.read(26).decode('utf-8'))
|
||||||
|
|
||||||
|
# Need order list size and num instruments to know how far to skip.
|
||||||
|
f.seek(0x20)
|
||||||
|
ordNum = int.from_bytes(f.read(2), byteorder="little", signed=False)
|
||||||
|
insNum = int.from_bytes(f.read(2), byteorder="little", signed=False)
|
||||||
|
smpNum = int.from_bytes(f.read(2), byteorder="little", signed=False)
|
||||||
|
patNum = int.from_bytes(f.read(2), byteorder="little", signed=False)
|
||||||
|
|
||||||
|
# Cus spc2it has a tendency to produce corrupted files...
|
||||||
|
if ordNum > 1024: return
|
||||||
|
if smpNum > 4000: return
|
||||||
|
if insNum > 256: return
|
||||||
|
if patNum > 256: return
|
||||||
|
|
||||||
|
smpOfsTable = 0xC0 + ordNum + insNum * 4
|
||||||
|
|
||||||
|
for i in range(0, smpNum):
|
||||||
|
f.seek(smpOfsTable + i * 4)
|
||||||
|
smpOfs = int.from_bytes(f.read(4), byteorder="little", signed=False)
|
||||||
|
smp = readsmp(f, smpOfs, i + 1)
|
||||||
|
if smp != None:
|
||||||
|
outwav = os.path.join(outpath, smp.hash + ".wav")
|
||||||
|
if not os.path.isfile(outwav):
|
||||||
|
pathlib.Path(outpath).mkdir(parents=True, exist_ok=True)
|
||||||
|
writesmp(smp, outwav)
|
||||||
|
|
||||||
|
def scanit(srcPath, dstPath):
|
||||||
|
for directory, subdirectories, files in os.walk(srcPath):
|
||||||
|
for file in files:
|
||||||
|
if file.endswith(".it"):
|
||||||
|
path = os.path.join(directory, file)
|
||||||
|
outpath = dstPath + path[len(srcPath):-len(file)]
|
||||||
|
readit(path, outpath)
|
||||||
|
|
||||||
|
def scanspc(srcPath, dstPath):
|
||||||
|
for directory, subdirectories, files in os.walk(srcPath):
|
||||||
|
|
||||||
|
# Create output dir for each game.
|
||||||
|
for sub in subdirectories:
|
||||||
|
path = os.path.join(dstPath, sub)
|
||||||
|
pathlib.Path(path).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Convert spc files.
|
||||||
|
for file in files:
|
||||||
|
if file.endswith(".spc"):
|
||||||
|
# Don't convert files that have already been converted.
|
||||||
|
itpath = os.path.join(dstPath + directory[len(srcPath):], file[:-3] + "it")
|
||||||
|
if not os.path.isfile(itpath):
|
||||||
|
path = os.path.join(directory, file)
|
||||||
|
subprocess.call([SPC2IT, path])
|
||||||
|
path = path[:-3] + "it"
|
||||||
|
if os.path.isfile(path):
|
||||||
|
os.rename(path, itpath)
|
||||||
|
|
||||||
|
|
||||||
|
# Actual main stuff.
|
||||||
|
scanspc(SPCDIR, ITDIR)
|
||||||
|
scanit(ITDIR, SMPDIR)
|
||||||
10
spctools/spc2it/.clang-format
Normal file
10
spctools/spc2it/.clang-format
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
AllowShortFunctionsOnASingleLine: Inline
|
||||||
|
BreakConstructorInitializersBeforeComma: true
|
||||||
|
ColumnLimit: 120
|
||||||
|
IndentWidth: 4
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: ForIndentation
|
||||||
|
BreakBeforeBraces: Allman
|
||||||
8
spctools/spc2it/.gitignore
vendored
Normal file
8
spctools/spc2it/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
obj/
|
||||||
|
build/
|
||||||
|
spc2it
|
||||||
|
*.exe
|
||||||
|
*.spc
|
||||||
|
*.it
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
16
spctools/spc2it/CMakeLists.txt
Normal file
16
spctools/spc2it/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
cmake_minimum_required(VERSION 2.8)
|
||||||
|
project(spc2it)
|
||||||
|
set(spc2it_sources
|
||||||
|
emu.c
|
||||||
|
it.c
|
||||||
|
main.c
|
||||||
|
sound.c
|
||||||
|
spc700.c
|
||||||
|
emu.h
|
||||||
|
it.h
|
||||||
|
sneese_spc.h
|
||||||
|
sound.h
|
||||||
|
spc2ittypes.h)
|
||||||
|
|
||||||
|
add_executable(spc2it ${spc2it_sources})
|
||||||
|
target_link_libraries(spc2it m)
|
||||||
42
spctools/spc2it/Makefile
Normal file
42
spctools/spc2it/Makefile
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
TARGET := spc2it
|
||||||
|
SOURCE := emu.c it.c main.c sound.c spc700.c
|
||||||
|
CFLAGS ?= -O2 -pipe
|
||||||
|
|
||||||
|
|
||||||
|
BUILD_CFLAGS := $(CFLAGS)
|
||||||
|
BUILD_LDFLAGS := $(CFLAGS) $(LDFLAGS) -lm
|
||||||
|
PREFIX := /usr/local
|
||||||
|
OBJDIR := obj
|
||||||
|
OBJECTS := $(patsubst %.c,$(OBJDIR)/%.o,$(SOURCE))
|
||||||
|
DEPENDS := $(OBJECTS:%.o=%.d)
|
||||||
|
|
||||||
|
default: all $(TARGET)
|
||||||
|
|
||||||
|
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
||||||
|
$(CC) $(BUILD_CFLAGS) -MMD -c $< -o $@
|
||||||
|
|
||||||
|
$(OBJDIR):
|
||||||
|
mkdir -p $@
|
||||||
|
|
||||||
|
$(TARGET): $(OBJECTS)
|
||||||
|
$(CC) $(BUILD_LDFLAGS) $^ -o $@
|
||||||
|
|
||||||
|
-include $(DEPENDS)
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
|
install: $(TARGET)
|
||||||
|
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||||
|
cp $< $(DESTDIR)$(PREFIX)/bin/$(TARGET)
|
||||||
|
mkdir -p $(DESTDIR)$(PREFIX)/share/man/man1/
|
||||||
|
cp spc2it.1 $(DESTDIR)$(PREFIX)/share/man/man1/
|
||||||
|
|
||||||
|
.PHONY: uninstall
|
||||||
|
uninstall:
|
||||||
|
rm -f $(DESTDIR)$(PREFIX)/bin/$(TARGET)
|
||||||
|
rm -f $(DESTDIR)$(PREFIX)/share/man/man1/spc2it.1
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -f $(TARGET) $(OBJECTS) $(DEPENDS)
|
||||||
49
spctools/spc2it/README.md
Executable file
49
spctools/spc2it/README.md
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
SPC2IT
|
||||||
|
======
|
||||||
|
|
||||||
|
Convert SPC files to IT (Impulse Tracker) files.
|
||||||
|
|
||||||
|
## Compiling
|
||||||
|
|
||||||
|
With GNU make:
|
||||||
|
```bash
|
||||||
|
make -j$(nproc)
|
||||||
|
```
|
||||||
|
|
||||||
|
With CMake:
|
||||||
|
```bash
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||||
|
make -j$(nproc)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install to /usr/local by default
|
||||||
|
sudo make install
|
||||||
|
|
||||||
|
# Install to /usr instead
|
||||||
|
sudo make install PREFIX=/usr
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
Run `spc2it` (or `./sp2it` locally) with no arguments to see the syntax.
|
||||||
|
```
|
||||||
|
SPC2IT - converts SPC700 sound files to the Impulse Tracker format
|
||||||
|
|
||||||
|
Usage: spc2it [options] <filename>
|
||||||
|
Where <filename> is any .spc or .sp# file
|
||||||
|
|
||||||
|
Options: -t x Specify a time limit in seconds [60 default]
|
||||||
|
-d xxxxxxxx Voices to disable (1-8) [none default]
|
||||||
|
-r xxx Specify IT rows per pattern [200 default]
|
||||||
|
```
|
||||||
|
|
||||||
|
## More information
|
||||||
|
|
||||||
|
Cloned from: https://github.com/uyjulian/spc2it
|
||||||
|
|
||||||
|
For more information, read the documentation in `doc/`.
|
||||||
|
Also, see https://www.romhacking.net/forum/index.php?topic=10164.0
|
||||||
160
spctools/spc2it/doc/LICENSE_SNEESE
Normal file
160
spctools/spc2it/doc/LICENSE_SNEESE
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
This license is a free software license, compatible with the GNU General
|
||||||
|
Public License (GPL). It is the minimal set of changes needed to correct
|
||||||
|
the vagueness of the Original Artistic License.
|
||||||
|
|
||||||
|
Text'ified by Randy McDowell (stainless* *at* *users.sourceforge.net).
|
||||||
|
|
||||||
|
|
||||||
|
SNEeSe is copyright (c) 1998-2006, Charles Bilyue'.
|
||||||
|
Portions copyright (c) 1998-2003, Brad Martin.
|
||||||
|
Portions copyright (c) 2003-2004, Daniel Horchner.
|
||||||
|
Portions copyright (c) 2004-2005, Nach. ( http://nsrt.edgeemu.com/ )
|
||||||
|
Unzip Technology, copyright (c) 1998 Gilles Vollant.
|
||||||
|
zlib Technology ( www.gzip.org/zlib/ ), Copyright (c) 1995-2003,
|
||||||
|
Jean-loup Gailly ( jloup* *at* *gzip.org ) and Mark Adler
|
||||||
|
( madler* *at* *alumni.caltech.edu ).
|
||||||
|
JMA Technology, copyright (c) 2004-2005 NSRT Team. ( http://nsrt.edgeemu.com/ )
|
||||||
|
LZMA Technology, copyright (c) 2001-4 Igor Pavlov. ( http://www.7-zip.org )
|
||||||
|
Portions copyright (c) 2002 Andrea Mazzoleni. ( http://advancemame.sf.net )
|
||||||
|
|
||||||
|
|
||||||
|
- The Clarified Artistic License -
|
||||||
|
|
||||||
|
i. Preamble
|
||||||
|
|
||||||
|
The intent of this document is to state the conditions under which a
|
||||||
|
Package may be copied, such that the Copyright Holder maintains some
|
||||||
|
semblance of artistic control over the development of the package, while
|
||||||
|
giving the users of the package the right to use and distribute the
|
||||||
|
Package in a more-or-less customary fashion, plus the right to make
|
||||||
|
reasonable modifications.
|
||||||
|
|
||||||
|
ii. Definitions:
|
||||||
|
|
||||||
|
"Package" refers to the collection of files distributed by the Copyright
|
||||||
|
Holder, and derivatives of that collection of files created through
|
||||||
|
textual modification.
|
||||||
|
|
||||||
|
"Standard Version" refers to such a Package if it has not been modified,
|
||||||
|
or has been modified in accordance with the wishes of the Copyright
|
||||||
|
Holder as specified below.
|
||||||
|
|
||||||
|
"Copyright Holder" is whoever is named in the copyright or copyrights
|
||||||
|
for the package.
|
||||||
|
|
||||||
|
"You" is you, if you're thinking about copying or distributing this
|
||||||
|
Package.
|
||||||
|
|
||||||
|
"Distribution fee" is a fee you charge for providing a copy of this
|
||||||
|
Package to another party.
|
||||||
|
|
||||||
|
"Freely Available" means that no fee is charged for the right to use the
|
||||||
|
item, though there may be fees involved in handling the item. It also
|
||||||
|
means that recipients of the item may redistribute it under the same
|
||||||
|
conditions they received it.
|
||||||
|
|
||||||
|
iii. Context
|
||||||
|
|
||||||
|
1. You may make and give away verbatim copies of the source form of the
|
||||||
|
Standard Version of this Package without restriction, provided that
|
||||||
|
you duplicate all of the original copyright notices and associated
|
||||||
|
disclaimers.
|
||||||
|
|
||||||
|
2. You may apply bug fixes, portability fixes and other modifications
|
||||||
|
derived from the Public Domain, or those made Freely Available, or
|
||||||
|
from the Copyright Holder. A Package modified in such a way shall
|
||||||
|
still be considered the Standard Version.
|
||||||
|
|
||||||
|
3. You may otherwise modify your copy of this Package in any way,
|
||||||
|
provided that you insert a prominent notice in each changed file
|
||||||
|
stating how and when you changed that file, and provided that you do
|
||||||
|
at least ONE of the following:
|
||||||
|
|
||||||
|
a) place your modifications in the Public Domain or otherwise make
|
||||||
|
them Freely Available, such as by posting said modifications to
|
||||||
|
Usenet or an equivalent medium, or placing the modifications on a
|
||||||
|
major network archive site allowing unrestricted access to them,
|
||||||
|
or by allowing the Copyright Holder to include your modifications
|
||||||
|
in the Standard Version of the Package.
|
||||||
|
|
||||||
|
b) use the modified Package only within your corporation or
|
||||||
|
organization.
|
||||||
|
|
||||||
|
c) rename any non-standard executables so the names do not conflict
|
||||||
|
with standard executables, which must also be provided, and
|
||||||
|
provide a separate manual page for each non-standard executable
|
||||||
|
that clearly documents how it differs from the Standard Version.
|
||||||
|
|
||||||
|
d) make other distribution arrangements with the Copyright Holder.
|
||||||
|
|
||||||
|
e) permit and encourge anyone who receives a copy of the modified
|
||||||
|
Package permission to make your modifications Freely Available in
|
||||||
|
some specific way.
|
||||||
|
|
||||||
|
4. You may distribute the programs of this Package in object code or
|
||||||
|
executable form, provided that you do at least ONE of the following:
|
||||||
|
|
||||||
|
a) distribute a Standard Version of the executables and library
|
||||||
|
files, together with instructions (in the manual page or
|
||||||
|
equivalent) on where to get the Standard Version.
|
||||||
|
|
||||||
|
b) accompany the distribution with the machine-readable source of the
|
||||||
|
Package with your modifications.
|
||||||
|
|
||||||
|
c) give non-standard executables non-standard names, and clearly
|
||||||
|
document the differences in manual pages (or equivalent), together
|
||||||
|
with instructions on where to get the Standard Version.
|
||||||
|
|
||||||
|
d) make other distribution arrangements with the Copyright Holder.
|
||||||
|
|
||||||
|
e) offer the machine-readable source of the Package, with your
|
||||||
|
modifications, by mail order.
|
||||||
|
|
||||||
|
5. You may charge a distribution fee for any distribution of this
|
||||||
|
Package. If you offer support for this Package, you may charge any
|
||||||
|
fee you choose for that support. You may not charge a license fee
|
||||||
|
for the right to use this Package itself. You may distribute this
|
||||||
|
Package in aggregate with other (possibly commercial and possibly
|
||||||
|
nonfree) programs as part of a larger (possibly commercial and
|
||||||
|
possibly nonfree) software distribution, and charge license fees for
|
||||||
|
other parts of that software distribution, provided that you do not
|
||||||
|
advertise this Package as a product of your own. If the Package
|
||||||
|
includes an interpreter, You may embed this Package's interpreter
|
||||||
|
within an executable of yours (by linking); this shall be construed
|
||||||
|
as a mere form of aggregation, provided that the complete Standard
|
||||||
|
Version of the interpreter is so embedded.
|
||||||
|
|
||||||
|
6. The scripts and library files supplied as input to or produced as
|
||||||
|
output from the programs of this Package do not automatically fall
|
||||||
|
under the copyright of this Package, but belong to whoever generated
|
||||||
|
them, and may be sold commercially, and may be aggregated with this
|
||||||
|
Package. If such scripts or library files are aggregated with this
|
||||||
|
Package via the so-called "undump" or "unexec" methods of producing a
|
||||||
|
binary executable image, then distribution of such an image shall
|
||||||
|
neither be construed as a distribution of this Package nor shall it
|
||||||
|
fall under the restrictions of Paragraphs 3 and 4, provided that you
|
||||||
|
do not represent such an executable image as a Standard Version of
|
||||||
|
this Package.
|
||||||
|
|
||||||
|
7. C subroutines (or comparably compiled subroutines in other languages)
|
||||||
|
supplied by you and linked into this Package in order to emulate
|
||||||
|
subroutines and variables of the language defined by this Package
|
||||||
|
shall not be considered part of this Package, but are the equivalent
|
||||||
|
of input as in Paragraph 6, provided these subroutines do not change
|
||||||
|
the language in any way that would cause it to fail the regression
|
||||||
|
tests for the language.
|
||||||
|
|
||||||
|
8. Aggregation of the Standard Version of the Package with a commercial
|
||||||
|
distribution is always permitted provided that the use of this
|
||||||
|
Package is embedded; that is, when no overt attempt is made to make
|
||||||
|
this Package's interfaces visible to the end user of the commercial
|
||||||
|
distribution. Such use shall not be construed as a distribution of
|
||||||
|
this Package.
|
||||||
|
|
||||||
|
9. The name of the Copyright Holder may not be used to endorse or
|
||||||
|
promote products derived from this software without specific prior
|
||||||
|
written permission.
|
||||||
|
|
||||||
|
10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
|
||||||
|
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
296
spctools/spc2it/doc/OPENSPC.TXT
Executable file
296
spctools/spc2it/doc/OPENSPC.TXT
Executable file
@@ -0,0 +1,296 @@
|
|||||||
|
This is a modified version of OpenSPC 0.301, improved by Dwedit,
|
||||||
|
with support of fractional update rates to generate better IT files.
|
||||||
|
|
||||||
|
See http://www.romhacking.net/forum/index.php?topic=10164.0
|
||||||
|
|
||||||
|
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
|
||||||
|
|
||||||
|
OpenSPC ver.301
|
||||||
|
|
||||||
|
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
|
||||||
|
|
||||||
|
Intro
|
||||||
|
-----
|
||||||
|
OpenSPC is an SPC Player created using a modified version of SNEeSe's ASM SPC
|
||||||
|
core. The rest was written in C using DJGPP. You can find new versions and
|
||||||
|
a FAQ for OpenSPC at:
|
||||||
|
http://home.gvi.net/~martin
|
||||||
|
|
||||||
|
OpenSPC was created by Butcha, and has been contributed to by Cyber Warrior X,
|
||||||
|
Crono, Kadar, and Cait Sith 2.
|
||||||
|
|
||||||
|
++++++++++++++++
|
||||||
|
Table of Contents
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
1.) What's New
|
||||||
|
2.) Disclaimer and License
|
||||||
|
3.) System Requirements
|
||||||
|
4.) Current Progress
|
||||||
|
5.) Future Progress
|
||||||
|
6.) Configuration File
|
||||||
|
7.) IT Dumping
|
||||||
|
8.) Credits/Special Thanks
|
||||||
|
|
||||||
|
+++++++++++++
|
||||||
|
1.) What's New
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
v0.301 - Relatively small update
|
||||||
|
|
||||||
|
* Added ID support for SPC files (thanks to Cait Sith 2). Displays info for
|
||||||
|
SPC files that contain it, in both the lite version and the full version,
|
||||||
|
non-gui mode.
|
||||||
|
* Changed "SPC->IT conversion" name in IT files to the ID song name if
|
||||||
|
specified in SPC file.
|
||||||
|
* Updated to SNEeSe version .36 SPC code, including all optimizations and
|
||||||
|
bugfixes that were present in it.
|
||||||
|
|
||||||
|
v0.300 - Another big (and overdue) update
|
||||||
|
|
||||||
|
* Prevented notes that are out of IT's range from writing the invalid data -
|
||||||
|
prevents IT corruption, but probably still won't sound quite right.
|
||||||
|
* Checked for invalid loop points supplied by an SPC file (wouldn't think
|
||||||
|
you'd *need* to check... %#!@$).
|
||||||
|
* Updated display screen with new pitch when sliding.
|
||||||
|
* Fixed rare bug which allowed volumes with opposite signs in both channels
|
||||||
|
to cancel each other out when in mono mode.
|
||||||
|
* Corrected bug where if a voice was keyed off at the same time the sample
|
||||||
|
ended, the voice would not get turned off. Prevents the big long strings of
|
||||||
|
note cut commands sometimes seen in the IT files.
|
||||||
|
* Decided to scrap my sound drivers in favor of Allegro's. This means both
|
||||||
|
the Ensoniq SoundScape and ESS AudioDrive should now be supported in addition
|
||||||
|
to all SoundBlaster types. Note only the sound driver, not the mixer, has
|
||||||
|
been replaced.
|
||||||
|
* As a side effect of this, the onscreen display updates slower now, because
|
||||||
|
Allegro requires much more sound data to be mixed and delivered at once.
|
||||||
|
This is much more noticable with frequencies less than 22050 Hz.
|
||||||
|
* Config file rewritten to reflect changes in sound driver. Should still be
|
||||||
|
compatible if you have any old frontends, but I'm not sure.
|
||||||
|
* Corrected a bug in ADSR/GAIN which could cause the envelope height to wrap
|
||||||
|
around infinitely. (Jurassic Park)
|
||||||
|
* Implemented main volume registers
|
||||||
|
* Changed to the newest version of SNEeSe SPC core. TCALL (and therefore
|
||||||
|
Terranigma) now works! Other optimizations and fixes are also present.
|
||||||
|
* Changed to SNEeSe's SPC timer code. Timers are now cycle-exact rather than
|
||||||
|
calculated once every update.
|
||||||
|
* Reimplemented ADSR/GAIN in the style of the timer code, which means it is
|
||||||
|
now cycle-exact. Any song that changed envelope states while monitoring ENVX
|
||||||
|
should now work much better.
|
||||||
|
* Doubled output volume both in the mixer and in IT dumping. This can
|
||||||
|
potentially cause clipping distortion while mixing and maxed-out volumes in
|
||||||
|
IT files if the volume ever gets too high, but it seems SNES games rarely
|
||||||
|
use high volume which is why it sounded so quiet in the first place.
|
||||||
|
* Fixed up the display for text mode. Colors and layout should be identical
|
||||||
|
to the graphics mode, especially if used with an 80x50 screen. Use setgui=2
|
||||||
|
in the config file to activate.
|
||||||
|
* Implemented a command line switch equivalent to the setgui parameter in the
|
||||||
|
config file. (-g x)
|
||||||
|
* Fixed up the colors on the display and allowed the colors to be changed in
|
||||||
|
the config file.
|
||||||
|
* Did a little cleaning and optimization of the source code.
|
||||||
|
|
||||||
|
v0.200 - Lots new in this version
|
||||||
|
|
||||||
|
* Allowed IT dumping with other than 200 rows/pattern (change in CFG file)
|
||||||
|
* Fixed bug with volume and IT dumping (played too loud and would
|
||||||
|
sometimes result in undesired volume column effects being applied)
|
||||||
|
* Fixed segmentation fault bug when a negative volume applied to a channel
|
||||||
|
* Fixed a bug with GAIN linear decrease mode
|
||||||
|
* Fixed file loader bug when filename preceded with "..\" <Doh, stupid me>
|
||||||
|
* Fixed file loader bug when loading files with extension .SP1, .SP2, etc.
|
||||||
|
* Increased number of SPC cycles to execute, while simultaneously
|
||||||
|
implementing system to pause emulation if SPC idle. Should prevent games
|
||||||
|
like Starfox from slowing down and speeding up, and *might* even save some
|
||||||
|
CPU power in other games
|
||||||
|
* Got rid of the "SPC Sample" for samples without lengths. (Thanks to Crono)
|
||||||
|
* Added .IT pattern compression. (Crono)
|
||||||
|
* Reduced memory requirements when *NOT* logging a .IT file (Crono)
|
||||||
|
* Now uses "specific" mixing method for each style of output. (Crono)
|
||||||
|
* Old Soundblaster drivers tweaked, should sound better and can now go up to
|
||||||
|
45454 Hz. May introduce some incompatibility, let me know. (Crono)
|
||||||
|
* Soundblaster Pro drivers tweaked, hopefully sound better. (Crono)
|
||||||
|
* Moved actual emulation stuff from main.c to emu.c. main.c now only
|
||||||
|
contains main() and other graphics-related stuff.
|
||||||
|
* Added support for Ensoniq Soundscape, SoundFX and MediaFX cards (thanks to
|
||||||
|
Kadar)
|
||||||
|
* Added support for non-standard IRQs. Let me know if you still have
|
||||||
|
problems getting it to play on your SB-compatible sound card.
|
||||||
|
* Found and eliminated something WinAmp wasn't liking about the way unlooped
|
||||||
|
samples were saved.
|
||||||
|
* Created 'lite' version! Now capture SPC's to either IT or WAV files
|
||||||
|
direct to disk at maximum speed rather than having to listen to them.
|
||||||
|
Uses command line options to specify parameters. (Replaced 'main.c' with
|
||||||
|
'lite.c' and removed sound drivers in project file)
|
||||||
|
* Added many of the same command line options to the full version. If no
|
||||||
|
options are specified it will default to what's in the OPENSPC.CFG file.
|
||||||
|
* Added a preset time limit and current time indicator to both versions.
|
||||||
|
* Replaced the SNEeSe SPC core with a newer version; had hoped this would fix
|
||||||
|
some SPC bugs, but no luck there. Oh well, hopefully its faster or
|
||||||
|
something.
|
||||||
|
* Broke down and did some SPC debugging myself; managed to get Prince of
|
||||||
|
Persia, WWF, Doom, FF2 (Fabul Castle), SOM2 (Boss Theme), and Simpsons:
|
||||||
|
Bart's Nightmare working a lot better.
|
||||||
|
Details: - all SBC to memory were subtracting in the wrong order
|
||||||
|
- CMP YA, dp didn't complement the carry flag
|
||||||
|
- membit addressing mode operand bits were mapped incorrectly
|
||||||
|
* Corrected some bugs in GAIN Increase and Decrease modes and re-enabled
|
||||||
|
ENVX reporting; made WWF, Doom, and Simpsons sound even better.
|
||||||
|
* Added a reverse stereo option for those of you experiencing swapped sound
|
||||||
|
channels with an SBPro compatible.
|
||||||
|
|
||||||
|
v0.100 - Initial release
|
||||||
|
|
||||||
|
+++++++++++++++++++++++++
|
||||||
|
2.) Disclaimer and License
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
This program is still considered to be in beta status. Neither Butcha nor any
|
||||||
|
of the other contributors are responsible for any undesirable effects of using
|
||||||
|
this program. Also, none of the authors are responsible for the possesion or
|
||||||
|
use of any copyrighted material taken from copyrighted ROMs using this
|
||||||
|
program.
|
||||||
|
|
||||||
|
Please do not redistribute this program without its included documentation.
|
||||||
|
Also, do not package this program with SPC or IT files from copyrighted games.
|
||||||
|
|
||||||
|
++++++++++++++++++++++
|
||||||
|
3.) System Requirements
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
The light version should have very few requirements. Probably a 386 with an
|
||||||
|
FPU could run it, but I can't say for sure. Memory shouldn't be a problem as
|
||||||
|
long as you have some available swap space. The following are some tentative
|
||||||
|
guidelines for the full version:
|
||||||
|
|
||||||
|
Minimum Recommended System:
|
||||||
|
|
||||||
|
- 486/100 processor
|
||||||
|
- 8MB RAM
|
||||||
|
- Sound Blaster or compatible card
|
||||||
|
|
||||||
|
Strongly Recommended System :
|
||||||
|
|
||||||
|
- Pentium processor (P133 or higher)
|
||||||
|
- The more RAM the merrier (especially with Windows 9x)
|
||||||
|
- VGA card (for graphical display)
|
||||||
|
- Sound Blaster 16 or 100% compatible
|
||||||
|
|
||||||
|
+++++++++++++++++++
|
||||||
|
4.) Current Progress
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
The following are implemented in the sound engine:
|
||||||
|
|
||||||
|
- 16bit digital stereo sound
|
||||||
|
- SPC700 Sound CPU (likely still bugs to be found)
|
||||||
|
- FULL ADSR and GAIN volume effects (now cycle-exact!)
|
||||||
|
- Song capture in the IT format
|
||||||
|
- Sound recording to WAV format (lite version only)
|
||||||
|
|
||||||
|
The following are missing in the sound engine:
|
||||||
|
- ECHO
|
||||||
|
- FIR filter
|
||||||
|
- Noise
|
||||||
|
- Modulation
|
||||||
|
|
||||||
|
The following are some known bugs:
|
||||||
|
- If the pitch changes too fast while a note is on, an IT file will not be
|
||||||
|
able to reproduce it fast enough, resulting in a subtle "rounding" effect.
|
||||||
|
(i.e. FF3 Atma Weapon)
|
||||||
|
- A few games' music doesn't like being played with a fixed update-per-second
|
||||||
|
method, resulting in unpredictable effects
|
||||||
|
- Any game that changes the sample location or data while playing will sound
|
||||||
|
strange (Doom, Cannon Fodder). No easy work around this, because ITs always
|
||||||
|
expect the same sample to stay the same.
|
||||||
|
- Dragon Quest seems to have some severe bugs, no idea why
|
||||||
|
|
||||||
|
++++++++++++++++++
|
||||||
|
5.) Future Progress
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
Future progress is completely indefinite at this point, due to my currently
|
||||||
|
attending college at UMR. I haven't had a lot of free time here, and that
|
||||||
|
which I do have I will most likely be devoted to other things. As far as
|
||||||
|
major features missing from the program go, there isn't a whole lot more
|
||||||
|
I can add, because it wouldn't transfer into an IT file anyway (echo, filter).
|
||||||
|
Noise and modulation might be possibilities, I'd have to look more into it. I
|
||||||
|
would like to have a much better display, but I keep saying that and never get
|
||||||
|
around to working on it. Also, there are the above bugs to fix. I would
|
||||||
|
however like to rewrite the IT code to save more data temporarily into a
|
||||||
|
proprietary format, and then convert this data to an IT file. This would make
|
||||||
|
it easier for other people to add support for other filetypes. It could also
|
||||||
|
make the IT code itself more efficient in compressing the resulting file. I
|
||||||
|
have no idea when or if I'll get to this, however. We'll see.
|
||||||
|
|
||||||
|
|
||||||
|
+++++++++++++++++++++
|
||||||
|
6.) Configuration File (not in lite version)
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
Any documentation you need for the configuration file should be found in
|
||||||
|
comments inside the OPENSPC.CFG file itself. If you've let some frontend
|
||||||
|
overwrite it and you can't find any comments, you'll have to restore from the
|
||||||
|
original ZIP. I recommend backing up the config before using any frontends,
|
||||||
|
if the frontend doesn't do so itself. Do NOT delete this file, OpenSPC will
|
||||||
|
NOT regenerate it.
|
||||||
|
|
||||||
|
+++++++++++++
|
||||||
|
7.) IT Dumping
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
IT dumping is done via 16 channels(8 channels each with independent left and
|
||||||
|
right volume control). When you switch on IT dumping, everything you hear in
|
||||||
|
the player is saved into the IT file. This means the IT file starts on the
|
||||||
|
first note of the SPC and ends when you hit a key to end recording. Upon
|
||||||
|
pressing a key, a note cut command is issued across all 16 channels, along
|
||||||
|
with a command to loop back to the beginning of the song. This makes it easy
|
||||||
|
to record simple looping songs-simply hit a key just before the first note of
|
||||||
|
the song plays again, and it should sound pretty decent. However, many songs
|
||||||
|
have an "intro" that they play first, and then go into another section and
|
||||||
|
loop. To make these loop correctly, you will have to manually edit the IT
|
||||||
|
yourself. I will describe how to do it using Impulse Tracker; it is up to you
|
||||||
|
to figure it out if you use a different one, but it shouldn't be much
|
||||||
|
different.
|
||||||
|
|
||||||
|
- First of all, switch on IT dumping and play the song. Stop recording a
|
||||||
|
second or two after you hear it loop.
|
||||||
|
- Open up the resulting IT file in Impulse Tracker.
|
||||||
|
- Find the first note of the section that loops. If it is at the beginning of
|
||||||
|
a pattern, you are incredibly lucky. If not, go to the next pattern and see
|
||||||
|
where it begins with relation to the music(for example, say the pattern
|
||||||
|
begins 5 rows before channel 2 plays a C#5). Also, remember which pattern
|
||||||
|
number it is.
|
||||||
|
- Go find the end of the song. Locate the first note of the looped section,
|
||||||
|
then look forward until you find the spot you remembered(5 rows before
|
||||||
|
channel 2 plays C#5, in our example). Hit ALT-DEL to delete everything in
|
||||||
|
this pattern after that spot. When the pattern is clear after that spot,
|
||||||
|
put the cursor on the effects column of any channel and enter in effect 'B'
|
||||||
|
followed by the number of the pattern you found in the last step(in hex).
|
||||||
|
- Once you've completed this, you'll probably want to delete any extra
|
||||||
|
patterns after this one, along with their respective orders.
|
||||||
|
- Play it. Sound right? Save it. If not you either found the wrong spot or
|
||||||
|
entered in the wrong pattern number.
|
||||||
|
|
||||||
|
It may sound a little complicated or awkward, but its really not that bad when
|
||||||
|
you've done it a few times. I did it 16 times to create the examples on my
|
||||||
|
webpage. If you'd like you can open them up for an example.
|
||||||
|
|
||||||
|
+++++++++++++++++++++++++
|
||||||
|
8.) Credits/Special Thanks
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
Thanks go to:
|
||||||
|
|
||||||
|
- Savoury SnaX and Charles Bilyu‚ for the SPC code used in this project.
|
||||||
|
- Shawn Hargreaves (and others) for the Allegro library
|
||||||
|
- Citizen X for letting CWX see the WinSPC source for ideas
|
||||||
|
- Gary Henderson for helping CWX with SPC Dumping and with Terranigma
|
||||||
|
- The rest of the Snes9x team for their hard work and the release of the
|
||||||
|
Snes9x source code, although this program contains no Snes9x-derived code.
|
||||||
|
- zsKnight and _Demo_ for an awesome emulator and for inventing SPC files
|
||||||
|
(even though I still think I came up with the idea for playing a sound save-
|
||||||
|
state first :)
|
||||||
|
- TheGun and SailorStarFighter for some early betatesting (hi guys :)
|
||||||
|
|
||||||
|
--Butcha
|
||||||
180
spctools/spc2it/doc/Readme.linux
Normal file
180
spctools/spc2it/doc/Readme.linux
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
OpenSPC v.300Linux (based on Lite/Direct To Disk version)
|
||||||
|
Linux port and general code tweaking by Chris Timmerberg (ctimmerb@usa.net)
|
||||||
|
|
||||||
|
Hi! Here's the long-awaited port of OpenSPC to linux. I say linux, primarily
|
||||||
|
because that's all I'm able to test it with (at least presently). If your
|
||||||
|
platform is relatively portable and has a /dev/dsp it may very well work
|
||||||
|
there as well. It may even work with code that pretends to be /dev/dsp, such
|
||||||
|
as I've seen for Os/2. This is completely speculation, I've never tried it.
|
||||||
|
|
||||||
|
Even in the lack of /dev/dsp, you should still be able to compile it almost
|
||||||
|
everywhere there's a gcc or other standard compiler. This will give you full
|
||||||
|
wav/it dumping facilities, as well as the ability to pipe stdout into
|
||||||
|
another player app which handles wav-like data. I've found splay to work
|
||||||
|
fine. Again, I havent tested others. Feel free to do so yourself.
|
||||||
|
|
||||||
|
The tty files were copied from another project and as such contain alot of
|
||||||
|
extra stuff #ifdef'ed which linux doesnt use. I can't say whether or not
|
||||||
|
these other code blocks work on their respective platforms, sorry.
|
||||||
|
|
||||||
|
The binary I send along was built on my 486 running Stampede. It uses no
|
||||||
|
extra libraries beyond Glibc 2.1. Everyone should have this by now. If you
|
||||||
|
dont, feel free to compile your own copy (of the OpenSPC, or of glibc).
|
||||||
|
|
||||||
|
PGCC 1.1.3 was used. The makefile includes -O6 -mpentium optimization levels
|
||||||
|
by default. This seems to generate the best quality code for most people's
|
||||||
|
systems. And no, mpentium DOESNT seem to harm a 486 any. My 486 is doing
|
||||||
|
just lovely playing back the spc's I've tried. I always use at least these
|
||||||
|
options when compiling everything (occasionally a couple extras in
|
||||||
|
addition).
|
||||||
|
|
||||||
|
The overall behavior of this port is somewhat different from the original.
|
||||||
|
Assuming one has enough cpu power (it shouldnt be hard to, my 486 did it)
|
||||||
|
you can dump to several destinations at the same time. This includes wav
|
||||||
|
file, IT file, stdout, and soundcard. The program code sets all output
|
||||||
|
variables to 0. Options you specify for -w -i -p and -l then turn on any of
|
||||||
|
the 4. If none was specified it turns on wav. Each enabled option is then
|
||||||
|
checked for success and if necessary turned off. If no output options remain
|
||||||
|
it finally gives up, and exits.
|
||||||
|
|
||||||
|
Data is written directly to /dev/dsp. The original code made no attempt to
|
||||||
|
buffer writes, and I didn't make any modifications in that area. As far as I
|
||||||
|
can tell, /dev/dsp and/or the soundcard itself have at least a small buffer.
|
||||||
|
This is probably not sufficient to prevent breakups if you're doing
|
||||||
|
something cpu-intense. A buffer may be added eventually...
|
||||||
|
|
||||||
|
Several command line options were added:
|
||||||
|
|
||||||
|
-l enables the audio card output code. As many sound cards do not possess an
|
||||||
|
exact frequency match, the audio output is by default tweaked to match what
|
||||||
|
your soundcard reports it was set to. This does not SEEM TO affect it
|
||||||
|
output, but does affect wav. If your card's status report was in error or
|
||||||
|
your goal was to dump an exact frequency wav you may disable the adjustment
|
||||||
|
with the -a option.
|
||||||
|
|
||||||
|
-p enables piping support. This data is identical to that which is fed to a
|
||||||
|
wav file or /dev/dsp. You may send this directly to an audio player of your
|
||||||
|
choice.
|
||||||
|
|
||||||
|
The -P option to accept input from stdin is wishful thinking for a next
|
||||||
|
version. It'd be nice to be able to zcat, bzcat, or unzip -c compressed spc
|
||||||
|
file(s) into this player, and beats trying to write support code for
|
||||||
|
handling different compression methods.
|
||||||
|
|
||||||
|
Aside from everything mentioned above, the base code remains for the most
|
||||||
|
part, unchanged. I include the two text files from the original dos version,
|
||||||
|
renamed to openspc.dos.txt and source.dos.txt.
|
||||||
|
|
||||||
|
The next version may also include as well as the mentioned earlier buffering
|
||||||
|
system and input pipes, the ability to batch play multiple files, general
|
||||||
|
code cleanup and optimization, and maybe some other sound file formats...
|
||||||
|
Candidates for other formats include gym, cym, nsf, and anything else that
|
||||||
|
isnt supported by other linux players. Within reason, and as long as my 486
|
||||||
|
handles it reliably. :)
|
||||||
|
|
||||||
|
If you want a netscape plugin, write it yourself. I dont do netscape
|
||||||
|
plugins. (Netscape is piggish enough on a 486 without having MORE overhead,
|
||||||
|
thank you very much!!) Meanwhile this existing program SHOULD work as a
|
||||||
|
'helper app' type thingy. No I didnt test that either.
|
||||||
|
|
||||||
|
Misc note: This program uses <__math.h> for the pow2 and __log2 functions.
|
||||||
|
These functions generate inline assembly code, which is both shorter and
|
||||||
|
faster than calling external library functions from linking with the -lm
|
||||||
|
option.
|
||||||
|
|
||||||
|
Using code from the NORMAL <math.h> header I've determined to
|
||||||
|
require an alternate sequence of:
|
||||||
|
|
||||||
|
#define __USE_EXTERN_INLINES 1
|
||||||
|
#define __USE_ISOC9X 1
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
This also changes the function names required, to log2f and __pow2
|
||||||
|
|
||||||
|
Oddly enough, the code generated by this contains many more instructions of
|
||||||
|
overhead.
|
||||||
|
|
||||||
|
<__math.h> generates a warning about __log1p which I dont even use. This
|
||||||
|
warning seems to be due to a bad header file, not my code. The warning is
|
||||||
|
harmless and may be ignored. The other warnings are going to be cleaned up
|
||||||
|
eventually.
|
||||||
|
|
||||||
|
Release notes for version .201Linux:
|
||||||
|
* Overlooked changing a few instances of 'dump' into 'itout'. This caused IT
|
||||||
|
dumping to generate incomplete files.
|
||||||
|
* WAV dumping no longer generates any temp files
|
||||||
|
* Upgraded from PGCC 1.1.1 to 1.1.3, and threw in -ffast-math in
|
||||||
|
compilation options.
|
||||||
|
* Accidentally packed 'source.dos.txt' into the archive twice the first
|
||||||
|
time around. Only need ONE copy of this :)
|
||||||
|
Release notes for version .300Linux:
|
||||||
|
* Downloaded dos version .300 and re-implemented all my changes including
|
||||||
|
stripping _ symbols in .S files, #including <__math.h> in sound.c, and
|
||||||
|
tons of stuff in lite.c
|
||||||
|
* lite.c now features a shutdown function for orderly shutting down.
|
||||||
|
* lite.c has two extra \r characters by popular demand :)
|
||||||
|
* Patched spcmain.S to not use self-modifying code in SPC_READ_RAM_ROM
|
||||||
|
Release notes for version .301Linux:
|
||||||
|
* Features migrated from the .301 dos version finally - id666 tags and new
|
||||||
|
spc core. The id tags allow the player to know how long to play a
|
||||||
|
particular file for.
|
||||||
|
* The code from the dos version's lite.c was WRONG WRONG WRONG and
|
||||||
|
generated really silly play times. So I fixed that and changed it to
|
||||||
|
read the whole id tag header into a 'struct' all in one read.
|
||||||
|
* The code for -t was also slightly incorrect in the dos version and my
|
||||||
|
previous release. We now handle all three cases: seconds only with and
|
||||||
|
without a leading colon, as well as the previous minutes:seconds case.
|
||||||
|
Also properly handles stuff like 90 or :90 or 0:90 (or even 1:90)...
|
||||||
|
* spcmain.S from the dos version included more silly self-modifying code in
|
||||||
|
.301. When will they learn??? .... fixed
|
||||||
|
* Dos version collided with my usage of the -l parameter. I kept mine, -l
|
||||||
|
is still linuxout audio method, and a capitalized -T is used to disable
|
||||||
|
the playback length mentioned in the idtag resulting in unlimited length
|
||||||
|
playbacks.
|
||||||
|
* Changed some of that math.h stuff. Gcc 2.95.2 (and pgcc) seem ok with it
|
||||||
|
the way I have it now, and I didnt feel like playing with it anymore...
|
||||||
|
* Tried to implement a 'large buffer' option to avoid audio breakups. Ended
|
||||||
|
up with WORSE breakups. Someone wanna look that over and see what I
|
||||||
|
missed?
|
||||||
|
* Meanwhile, I upgraded to a nice Trident 4Dwave card and now am running
|
||||||
|
with the Alsa drivers (.58a as of this writing). Program works lovely by
|
||||||
|
default. (At least as long as you've got the oss emulation modules, but
|
||||||
|
you do, dont you?? :) This card at least gives me the exact sample rates
|
||||||
|
I ask for, but I'm sure alot of people out there still have cards which
|
||||||
|
dont so I didnt touch the rate-tweak logic.
|
||||||
|
|
||||||
|
If you want a large buffer and to use native alsa playback, try
|
||||||
|
something like: ./ospc -p CT-1-22-TheHiddenTruth.spc | bag | aplay -m
|
||||||
|
'bag' is a nice buffering utility that I think came with Timidity's
|
||||||
|
tools, and aplay is part of the Alsa stuff. Dont bother trying this
|
||||||
|
approach with certain other play utilities, in particular avoid the one
|
||||||
|
from sox. You can identify this crappy 'play' program by typing 'play'
|
||||||
|
and seeing if it says its a frontend to sox. Sox's play just soaks up
|
||||||
|
tons of cpu and does NOT help (the point of HAVING a buffer is to try
|
||||||
|
to avoid cpu usage spikes from breaking up the audio, after all!!)
|
||||||
|
* The dos version did not yet support the id tag's 'fadeout length' value,
|
||||||
|
and I have not tried to do anything about this yet either. Feel free to
|
||||||
|
do so for me. :)
|
||||||
|
|
||||||
|
I was thinking maybe to use the oss or alsa mixer controls. Note that at
|
||||||
|
least for my trident, the oss-emulation 'main volume' seems to be using
|
||||||
|
one of trident's 32-position ones (despite showing in the oss mixers as a
|
||||||
|
1...100 range), making for rather unsmooth fading if one were to use that.
|
||||||
|
In fact, all of the trident's volume knobs can get a bit confusing to sort
|
||||||
|
out. There are at least several availible from alsa mixers with a full 255
|
||||||
|
position range at least. (I use xamixer. gamix is also nice. Neither is
|
||||||
|
totally perfect though.)...
|
||||||
|
* Some extra keypresses - q and escape quit as well as the previous
|
||||||
|
spacebar. New key functions include p or s to pause. Other activity
|
||||||
|
such as fast forward and rewind seem awfully difficult to implement. Feel
|
||||||
|
free to try doing so yourself as always :) Note that quiting or pausing
|
||||||
|
seem to take place after the buffer is emptied (or at least not QUITE
|
||||||
|
instantly). Good enough though.
|
||||||
|
* If you feel compelled to,
|
||||||
|
make ospcshared
|
||||||
|
cp -a libspc.so /usr/lib/libspc.so.0
|
||||||
|
(or any other suitable directory, or just edit your LD_LIBRARY_PATH)
|
||||||
|
Works here, your milage may vary.
|
||||||
|
* Changed Makefile to use CFLAGS, and included a nice flag setting for
|
||||||
|
optimization which I got from who knows where (probably an old posting to
|
||||||
|
the xmame mailing list).
|
||||||
173
spctools/spc2it/doc/SOURCE.TXT
Executable file
173
spctools/spc2it/doc/SOURCE.TXT
Executable file
@@ -0,0 +1,173 @@
|
|||||||
|
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
|
||||||
|
|
||||||
|
OpenSPC ver.300
|
||||||
|
Source Code
|
||||||
|
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
|
||||||
|
|
||||||
|
/ntro
|
||||||
|
-----
|
||||||
|
OpenSPC is an SPC Player created using a very modified SNEeSe SPC CPU core.
|
||||||
|
It was made in C (although the SPC core is in Assembly) using DJGPP. You can
|
||||||
|
find new versions and a FAQ for OpenSPC at:
|
||||||
|
http://home.gvi.net/~martin
|
||||||
|
|
||||||
|
OpenSPC was created by Butcha, and has been contributed to by Cyber Warrior X,
|
||||||
|
Crono, and Kadar.
|
||||||
|
|
||||||
|
++++++++++++++++
|
||||||
|
Table of Contents
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
1.) Disclaimer
|
||||||
|
2.) License Rights
|
||||||
|
3.) Programming Conventions
|
||||||
|
4.) Brief Outline
|
||||||
|
|
||||||
|
+++++++++++++
|
||||||
|
1.) Disclaimer
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
This program is still considered to be in beta status. Neither Butcha nor any
|
||||||
|
of the other contributors are responsible for any undesirable effects of using
|
||||||
|
this program. Also, none of the authors are responsible for the possesion or
|
||||||
|
use of any copyrighted material taken from copyrighted ROMs using this
|
||||||
|
program.
|
||||||
|
|
||||||
|
+++++++++++++++++
|
||||||
|
2.) License Rights
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
- OpenSPC Program and Code - General
|
||||||
|
----------------------------------
|
||||||
|
Please do not redistribute either of these without their respective
|
||||||
|
documentation. Also, do not package them with either SPC or IT files from
|
||||||
|
copyrighted games.
|
||||||
|
|
||||||
|
- OpenSPC Code - Usage Rights
|
||||||
|
---------------------------
|
||||||
|
If you would like to modify the source to OpenSPC in some way, you may send
|
||||||
|
your version of it to the authors, who will choose whether to include your
|
||||||
|
improvements in future versions. Please do not make a change or improvement
|
||||||
|
and release your own, separate program. However, independent ports of
|
||||||
|
OpenSPC to other platforms are welcome, as long as you let the authors know
|
||||||
|
what you've done. If you would like to use some part of the source in a
|
||||||
|
program of your own design, you are free to do so, provided the primary
|
||||||
|
author is aware of what you are doing. Please give the author(s) credit for
|
||||||
|
any benefits you receive from either the direct use or the examination of this
|
||||||
|
code.
|
||||||
|
|
||||||
|
Blah, legal crap. :( I'm sure none of you out there will try to rip me off...
|
||||||
|
But please read and follow this section anyway.
|
||||||
|
|
||||||
|
+++++++++++++++++++
|
||||||
|
3.) Programming Conventions
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
I have tried to make the source as uniform as possible, to make it easier to
|
||||||
|
read and understand. Also I have tried to comment wherever possible. What
|
||||||
|
follows is a list of different programming conventions used by me and which
|
||||||
|
should be used when modifying the source:
|
||||||
|
|
||||||
|
- Tab Spacing is 8
|
||||||
|
- if, for, and other bracketed statements should take the following form:
|
||||||
|
if(something)
|
||||||
|
{
|
||||||
|
//Do some stuff
|
||||||
|
//Do some more stuff
|
||||||
|
}
|
||||||
|
Here are some examples of what NOT to do:
|
||||||
|
|
||||||
|
if(something){/*Do some stuff*//*Do some
|
||||||
|
more stuff*/}
|
||||||
|
|
||||||
|
if(something)
|
||||||
|
{
|
||||||
|
//Do some stuff
|
||||||
|
//Do some more stuff
|
||||||
|
}
|
||||||
|
if(something){
|
||||||
|
//Do some stuff
|
||||||
|
//Do some more stuff
|
||||||
|
}
|
||||||
|
If there is only one statement within the brackets, they may be omitted.
|
||||||
|
However, the statement should be on the following line and tabbed once:
|
||||||
|
if(something)
|
||||||
|
//Do only one thing
|
||||||
|
|
||||||
|
- Global variables:
|
||||||
|
- Try to use them as little as possible
|
||||||
|
- When you do use them, define them in the .c file whose function they most
|
||||||
|
correspond with. If that .c file is the only one in the project that
|
||||||
|
needs that variable, define it as static. This goes for functions as
|
||||||
|
well.
|
||||||
|
- If a global variable must be seen outside the .c file it is defined in,
|
||||||
|
include it in the .h file associated with that .c file, and give it a name
|
||||||
|
that corresponds with that file. For example, any global variable you
|
||||||
|
will find in the sound.c file is preceded by the letters SND: SNDvoices,
|
||||||
|
SNDsamples, etc. This also goes for functions.
|
||||||
|
|
||||||
|
This is all I can think of right now. For the most part, just observe what
|
||||||
|
is already there and try to make your code look and work similarly.
|
||||||
|
|
||||||
|
++++++++++++++++
|
||||||
|
4.) Brief Outline
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
What follows is a rough outline of how the program works:
|
||||||
|
|
||||||
|
- Startup: display message, read config file, parse command line
|
||||||
|
- Init the SPC: allocate memory, call SNEeSe's Reset command, then load in
|
||||||
|
registers from the input file
|
||||||
|
- Init the mixer: make up some tables, init some variables
|
||||||
|
- If in graphics mode, initialize allegro graphics and fader routine
|
||||||
|
- Init IT dump if capturing to an IT only
|
||||||
|
- Queue the notes that started when state was saved
|
||||||
|
- Initialize the Allegro sound stream to receive data from the mixer
|
||||||
|
- Main loop:
|
||||||
|
- Check to see whether Allegro wants more data
|
||||||
|
- If so, keep emulating and mixing until buffer is full
|
||||||
|
- Otherwise, render the display (if enabled)
|
||||||
|
- Dump the IT pattern buffers to disk if recording
|
||||||
|
- Check current time against time limit
|
||||||
|
- Check to see if we need to toggle voices or exit as a result of keypress
|
||||||
|
- Shut down stuff
|
||||||
|
- If dumping to an IT, figure out what the filename would be and save it.
|
||||||
|
- End program
|
||||||
|
|
||||||
|
Below is an explanation of the processing done when Allegro requests data,
|
||||||
|
found in the function SNDMix() in sound.c:
|
||||||
|
|
||||||
|
- Run the SNEeSe SPC
|
||||||
|
While this is going on, certain actions have been intercepted from SNEeSe
|
||||||
|
control:
|
||||||
|
- (new in v300) Timer control is now cycle-exact, and is handled by the
|
||||||
|
SNEeSe code.
|
||||||
|
- Reads and writes to the DSP are intercepted so as to return current sound
|
||||||
|
data and update sound data when something new is written.
|
||||||
|
- Control returns from the SNEeSe SPC when the number of cycles that should
|
||||||
|
have gone by in one update have been emulated.
|
||||||
|
(2048000 cycles / num_updates_per_second)
|
||||||
|
- Mixer then proceeds to process each of the 8 voices that are currently keyed
|
||||||
|
on. This includes updating the envelope, and detecting pitch and volume
|
||||||
|
changes so as to update the IT, as well as mixing the audio. It also
|
||||||
|
updates a global variable used by the display functions to determine the
|
||||||
|
current level of each voice and both channels.
|
||||||
|
- IT code writes the information recorded as having changed since the last
|
||||||
|
update. It must write it twice, once per each channel, left and right,
|
||||||
|
because the SPC has two independent volume controls rather than a volume and
|
||||||
|
a pan. (The display is somewhat faked... using that method to set the pan
|
||||||
|
doesn't result in a very good recording. Besides, as near as I can tell
|
||||||
|
volume and pan cannot be set simultaneously in an IT file.)
|
||||||
|
- Record IT end of row for this completed update. If we have filled a
|
||||||
|
pattern, use the next buffer. If no other buffer is available, the main
|
||||||
|
loop must be running too slow. Either way, simply overwrite the pattern we
|
||||||
|
just recorded since the data has to go SOMEWHERE. It will skip a pattern
|
||||||
|
though, so if this happens either turn down the sound quality in the config
|
||||||
|
file, or turn up NUM_PATT_BUFS in it.h.
|
||||||
|
- End of update, return from interrupt
|
||||||
|
|
||||||
|
I'm sure a lot of the mixer, envelope, and IT data stuff could use some
|
||||||
|
optimization; if you'd like to do it, as long as it doesn't lose any of the
|
||||||
|
current functionality I'll use it.
|
||||||
|
|
||||||
|
--Butcha
|
||||||
210
spctools/spc2it/emu.c
Normal file
210
spctools/spc2it/emu.c
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
/****************************************************
|
||||||
|
*Part of SPC2IT, read readme.md for more information*
|
||||||
|
****************************************************/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "sound.h"
|
||||||
|
#include "spc2ittypes.h"
|
||||||
|
#include "sneese_spc.h"
|
||||||
|
|
||||||
|
u8 SPC_DSP[256];
|
||||||
|
u8 SPCRAM[65536];
|
||||||
|
u32 Map_Address;
|
||||||
|
u32 SPC_DSP_DATA;
|
||||||
|
u32 Map_Byte;
|
||||||
|
u8 In_CPU;
|
||||||
|
u32 SPC_CPU_cycle_divisor;
|
||||||
|
u32 SPC_CPU_cycle_multiplicand;
|
||||||
|
u32 SPC_CPU_cycles;
|
||||||
|
u32 SPC_CPU_cycles_mul;
|
||||||
|
u32 sound_cycle_latch;
|
||||||
|
|
||||||
|
void (*SPC_Write_DSP_Hook)(u8); // function pointer
|
||||||
|
|
||||||
|
// ID tag stuff
|
||||||
|
s32 SPCtime;
|
||||||
|
SPCFileInformation *SPCInfo;
|
||||||
|
|
||||||
|
static s32 LoadZState(char *fn)
|
||||||
|
{
|
||||||
|
SPCFile *sFile = calloc(1, sizeof(SPCFile));
|
||||||
|
if (sFile == NULL)
|
||||||
|
{
|
||||||
|
printf("Error: could not allocate memory for SPCFile struct\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
FILE *f = fopen(fn, "rb");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
printf("Error: can't open file\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
fread(sFile, sizeof(SPCFile), 1, f);
|
||||||
|
fclose(f);
|
||||||
|
if (strncmp("SNES-SPC700 Sound File Data", sFile->FileTag, 27))
|
||||||
|
{
|
||||||
|
printf("Error: invalid file format\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
memcpy(&active_context->PC.w, sFile->Registers.PC, 2);
|
||||||
|
active_context->YA.b.l = sFile->Registers.A;
|
||||||
|
active_context->X = sFile->Registers.X;
|
||||||
|
active_context->YA.b.h = sFile->Registers.Y;
|
||||||
|
active_context->SP = 0x100 + sFile->Registers.SP;
|
||||||
|
active_context->PSW = sFile->Registers.PSW;
|
||||||
|
memcpy(SPCRAM, sFile->RAM, 65536);
|
||||||
|
memcpy(SPC_DSP, sFile->DSPBuffer, 128);
|
||||||
|
SPCInfo = calloc(1, sizeof(SPCFileInformation));
|
||||||
|
if (SPCInfo == NULL)
|
||||||
|
{
|
||||||
|
printf("Error: could not allocate memory for SPCInfo struct\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
memcpy(SPCInfo, &sFile->Information, sizeof(SPCFileInformation));
|
||||||
|
char songLen[4];
|
||||||
|
strncpy(songLen, SPCInfo->SongLength, 3);
|
||||||
|
if (songLen[0] >= 0)
|
||||||
|
SPCtime = atoi(songLen);
|
||||||
|
else
|
||||||
|
SPCtime = 0;
|
||||||
|
if (0 == (SPC_CTRL & 0x80))
|
||||||
|
active_context->FFC0_Address = SPCRAM;
|
||||||
|
active_context->timers[0].target = (u8)(SPCRAM[0xFA] - 1) + 1;
|
||||||
|
active_context->timers[1].target = (u8)(SPCRAM[0xFB] - 1) + 1;
|
||||||
|
active_context->timers[2].target = (u8)(SPCRAM[0xFC] - 1) + 1;
|
||||||
|
active_context->timers[0].counter = SPCRAM[0xFD] & 0xF;
|
||||||
|
active_context->timers[1].counter = SPCRAM[0xFE] & 0xF;
|
||||||
|
active_context->timers[2].counter = SPCRAM[0xFF] & 0xF;
|
||||||
|
active_context->PORT_R[0] = SPCRAM[0xF4];
|
||||||
|
active_context->PORT_R[1] = SPCRAM[0xF5];
|
||||||
|
active_context->PORT_R[2] = SPCRAM[0xF6];
|
||||||
|
active_context->PORT_R[3] = SPCRAM[0xF7];
|
||||||
|
|
||||||
|
SPC_CPU_cycle_multiplicand = 1;
|
||||||
|
SPC_CPU_cycle_divisor = 1;
|
||||||
|
SPC_CPU_cycles_mul = 0;
|
||||||
|
spc_restore_flags();
|
||||||
|
free(sFile);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUBLIC (non-static) functions
|
||||||
|
|
||||||
|
s32 SPCInit(char *fn)
|
||||||
|
{
|
||||||
|
Reset_SPC();
|
||||||
|
if (LoadZState(fn))
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SPCAddWriteDSPCallback(void (*ToAddCallback)(u8))
|
||||||
|
{
|
||||||
|
SPC_Write_DSP_Hook = ToAddCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from SPC 700 engine
|
||||||
|
|
||||||
|
void DisplaySPC()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void InvalidSPCOpcode()
|
||||||
|
{
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SPC_READ_DSP()
|
||||||
|
{
|
||||||
|
if ((SPC_DSP_ADDR & 0xf) == 8) // ENVX
|
||||||
|
SPC_DSP[SPC_DSP_ADDR] = SNDDoEnv(SPC_DSP_ADDR >> 4) >> 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SPC_WRITE_DSP()
|
||||||
|
{
|
||||||
|
s32 addr_lo = SPC_DSP_ADDR & 0xF, addr_hi = SPC_DSP_ADDR >> 4;
|
||||||
|
switch (addr_lo)
|
||||||
|
{
|
||||||
|
case 3: // Pitch hi
|
||||||
|
SPC_DSP_DATA &= 0x3F;
|
||||||
|
break;
|
||||||
|
case 5: // ADSR1
|
||||||
|
if ((SPC_DSP[0x4C] & (1 << addr_hi)) && ((SPC_DSP_DATA & 0x80) != (SPC_DSP[SPC_DSP_ADDR] & 0x80)))
|
||||||
|
{
|
||||||
|
s32 i;
|
||||||
|
// First of all, in case anything was already
|
||||||
|
// going on, finish it up
|
||||||
|
SNDDoEnv(addr_hi);
|
||||||
|
if (SPC_DSP_DATA & 0x80)
|
||||||
|
{
|
||||||
|
// switch to ADSR--not sure what to do
|
||||||
|
i = SPC_DSP[(addr_hi << 4) + 6];
|
||||||
|
SNDvoices[addr_hi].envstate = ATTACK;
|
||||||
|
SNDvoices[addr_hi].ar = SPC_DSP_DATA & 0xF;
|
||||||
|
SNDvoices[addr_hi].dr = SPC_DSP_DATA >> 4 & 7;
|
||||||
|
SNDvoices[addr_hi].sr = i & 0x1f;
|
||||||
|
SNDvoices[addr_hi].sl = i >> 5;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// switch to a GAIN mode
|
||||||
|
i = SPC_DSP[(addr_hi << 4) + 7];
|
||||||
|
if (i & 0x80)
|
||||||
|
{
|
||||||
|
SNDvoices[addr_hi].envstate = i >> 5;
|
||||||
|
SNDvoices[addr_hi].gn = i & 0x1F;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SNDvoices[addr_hi].envx = (i & 0x7F) << 24;
|
||||||
|
SNDvoices[addr_hi].envstate = DIRECT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 6: // ADSR2
|
||||||
|
// Finish up what was going on
|
||||||
|
SNDDoEnv(addr_hi);
|
||||||
|
SNDvoices[addr_hi].sr = SPC_DSP_DATA & 0x1f;
|
||||||
|
SNDvoices[addr_hi].sl = SPC_DSP_DATA >> 5;
|
||||||
|
break;
|
||||||
|
case 7: // GAIN
|
||||||
|
if ((SPC_DSP[0x4C] & (1 << addr_hi)) && (SPC_DSP_DATA != SPC_DSP[SPC_DSP_ADDR]) &&
|
||||||
|
!(SPC_DSP[(addr_hi << 4) + 5] & 0x80))
|
||||||
|
{
|
||||||
|
if (SPC_DSP_DATA & 0x80)
|
||||||
|
{
|
||||||
|
// Finish up what was going on
|
||||||
|
SNDDoEnv(addr_hi);
|
||||||
|
SNDvoices[addr_hi].envstate = SPC_DSP_DATA >> 5;
|
||||||
|
SNDvoices[addr_hi].gn = SPC_DSP_DATA & 0x1F;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SNDvoices[addr_hi].envx = (SPC_DSP_DATA & 0x7F) << 24;
|
||||||
|
SNDvoices[addr_hi].envstate = DIRECT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// These are general registers
|
||||||
|
case 12: // 0xc
|
||||||
|
switch (addr_hi)
|
||||||
|
{
|
||||||
|
case 4: // Key on
|
||||||
|
SNDNoteOn(SPC_DSP_DATA);
|
||||||
|
SPC_DSP_DATA = SPC_DSP[0x4C];
|
||||||
|
break;
|
||||||
|
case 5: // Key off
|
||||||
|
SNDNoteOff(SPC_DSP_DATA);
|
||||||
|
SPC_DSP_DATA = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SPC_DSP[SPC_DSP_ADDR] = SPC_DSP_DATA;
|
||||||
|
}
|
||||||
23
spctools/spc2it/emu.h
Normal file
23
spctools/spc2it/emu.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/****************************************************
|
||||||
|
*Part of SPC2IT, read readme.md for more information*
|
||||||
|
****************************************************/
|
||||||
|
|
||||||
|
#ifndef EMU_H
|
||||||
|
#define EMU_H
|
||||||
|
#include "spc2ittypes.h"
|
||||||
|
#include "sneese_spc.h"
|
||||||
|
extern u8 SPC_DSP[256];
|
||||||
|
extern u8 SPCRAM[65536];
|
||||||
|
#define TotalCycles (active_context->TotalCycles)
|
||||||
|
extern u32 SPC_DSP_DATA;
|
||||||
|
|
||||||
|
extern s32 SPCtime;
|
||||||
|
extern SPCFileInformation *SPCInfo;
|
||||||
|
extern void (*SPC_Write_DSP_Hook)(u8);
|
||||||
|
|
||||||
|
#define SPCUpdateRate 100
|
||||||
|
|
||||||
|
s32 SPCInit(char *);
|
||||||
|
void SPCAddWriteDSPCallback(void (*ToAddCallback)(u8));
|
||||||
|
|
||||||
|
#endif
|
||||||
546
spctools/spc2it/it.c
Normal file
546
spctools/spc2it/it.c
Normal file
@@ -0,0 +1,546 @@
|
|||||||
|
/****************************************************
|
||||||
|
*Part of SPC2IT, read readme.md for more information*
|
||||||
|
****************************************************/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "it.h"
|
||||||
|
#include "sound.h"
|
||||||
|
#include "emu.h"
|
||||||
|
|
||||||
|
static itdata ITdata[8]; // Temp memory for patterns before going to file
|
||||||
|
static sndsamp *ITSamples[IT_SAMPLE_MAX];
|
||||||
|
static u8 *ITpattbuf[NUM_PATT_BUFS]; // Where patterns are going to be , before writing to file
|
||||||
|
static u8 *ITPatterns;
|
||||||
|
static u32 ITPatternsSize;
|
||||||
|
static s32 ITpattlen[NUM_PATT_BUFS]; // lengths of each pattern
|
||||||
|
static s32 ITcurbuf, ITbufpos, ITcurrow; // Pointers into temp pattern buffers
|
||||||
|
static s32 ITrows; // Number of rows per pattern
|
||||||
|
|
||||||
|
static pcm_t p1, p2;
|
||||||
|
static s32 offset[IT_PATTERN_MAX]; // table of offsets into temp file to each pattern
|
||||||
|
static s32 curpatt; // which pattern we are on in temp file
|
||||||
|
static s32 curoffs; // where we are in file
|
||||||
|
|
||||||
|
static sndsamp *ITAllocateSample(s32 size)
|
||||||
|
{
|
||||||
|
sndsamp *s;
|
||||||
|
if (((s = calloc(1, sizeof(sndsamp))) == NULL) || ((s->buf = calloc(1, size * 2)) == NULL))
|
||||||
|
return (NULL);
|
||||||
|
s->length = size;
|
||||||
|
s->loopto = -1;
|
||||||
|
s->freq = 0;
|
||||||
|
return (s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static s32 ITGetBRRPrediction(u8 filter, pcm_t p1, pcm_t p2)
|
||||||
|
{
|
||||||
|
s32 p;
|
||||||
|
switch (filter)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
p = p1;
|
||||||
|
p -= p1 >> 4;
|
||||||
|
return p;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
p = p1 << 1;
|
||||||
|
p += (-(p1 + (p1 << 1))) >> 5;
|
||||||
|
p -= p2;
|
||||||
|
p += p2 >> 4;
|
||||||
|
return p;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
p = p1 << 1;
|
||||||
|
p += (-(p1 + (p1 << 2) + (p1 << 3))) >> 6;
|
||||||
|
p -= p2;
|
||||||
|
p += (p2 + (p2 << 1)) >> 4;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ITDecodeSampleInternal(s8 s, u8 shift_am, u8 filter)
|
||||||
|
{
|
||||||
|
s32 a;
|
||||||
|
if (shift_am <= 0x0c) // Valid shift count
|
||||||
|
a = ((s < 8 ? s : s - 16) << shift_am) >> 1;
|
||||||
|
else
|
||||||
|
a = s < 8 ? 1 << 11 : (-1) << 11; // Values "invalid" shift counts
|
||||||
|
|
||||||
|
a += ITGetBRRPrediction(filter, p1, p2);
|
||||||
|
|
||||||
|
if (a > 0x7fff)
|
||||||
|
a = 0x7fff;
|
||||||
|
else if (a < -0x8000)
|
||||||
|
a = -0x8000;
|
||||||
|
if (a > 0x3fff)
|
||||||
|
a -= 0x8000;
|
||||||
|
else if (a < -0x4000)
|
||||||
|
a += 0x8000;
|
||||||
|
|
||||||
|
p2 = p1;
|
||||||
|
p1 = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static s32 ITDecodeSample(u16 start, sndsamp **sp)
|
||||||
|
{
|
||||||
|
sndsamp *s;
|
||||||
|
u8 *src;
|
||||||
|
u16 end;
|
||||||
|
u32 brrptr, sampptr = 0;
|
||||||
|
s32 i;
|
||||||
|
src = &SPCRAM[start];
|
||||||
|
for (end = 0; !(src[end] & 1); end += 9)
|
||||||
|
;
|
||||||
|
i = (end + 9) / 9 * 16;
|
||||||
|
*sp = s = ITAllocateSample(i);
|
||||||
|
if (s == NULL)
|
||||||
|
return 1;
|
||||||
|
if (src[end] & 2)
|
||||||
|
s->loopto = 0;
|
||||||
|
for (brrptr = 0; brrptr <= end;)
|
||||||
|
{
|
||||||
|
u8 range = src[brrptr++];
|
||||||
|
u8 filter = (range & 0x0c) >> 2;
|
||||||
|
u8 shift_amount = (range >> 4) & 0x0F;
|
||||||
|
for (i = 0; i < 8; i++, brrptr++)
|
||||||
|
{
|
||||||
|
ITDecodeSampleInternal(src[brrptr] >> 4, shift_amount, filter); // Decode high nybble
|
||||||
|
s->buf[sampptr++] = 2 * p1;
|
||||||
|
ITDecodeSampleInternal(src[brrptr] & 0x0F, shift_amount, filter); // Decode low nybble
|
||||||
|
s->buf[sampptr++] = 2 * p1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ITUpdateSample(s32 s)
|
||||||
|
{
|
||||||
|
s32 i;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
u16 vptr, lptr;
|
||||||
|
} *SRCDIR;
|
||||||
|
i = SPC_DSP[0x5D] << 8; //sample directory table...
|
||||||
|
SRCDIR = (void *)&SPCRAM[i];
|
||||||
|
if (ITDecodeSample(SRCDIR[s].vptr, &ITSamples[s]))
|
||||||
|
return;
|
||||||
|
if (ITSamples[s]->loopto != -1)
|
||||||
|
{
|
||||||
|
ITSamples[s]->loopto = (SRCDIR[s].lptr - SRCDIR[s].vptr) / 9 * 16;
|
||||||
|
if ((ITSamples[s]->loopto > ITSamples[s]->length) || (ITSamples[s]->loopto < 0))
|
||||||
|
ITSamples[s]->loopto = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static s32 ITPitchToNote(s32 pitch, s32 base)
|
||||||
|
{
|
||||||
|
f64 tmp;
|
||||||
|
s32 note;
|
||||||
|
tmp = log2((f64)pitch / (f64)base) * 12 + 60;
|
||||||
|
if (tmp > 127)
|
||||||
|
tmp = 127;
|
||||||
|
else if (tmp < 0)
|
||||||
|
tmp = 0;
|
||||||
|
note = (s32)tmp;
|
||||||
|
if ((s32)(tmp * 2) != (note * 2))
|
||||||
|
note++; // correct rounding
|
||||||
|
return note;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ITWriteDSPCallback(u8 v)
|
||||||
|
{
|
||||||
|
s32 addr_lo = SPC_DSP_ADDR & 0xF;
|
||||||
|
s32 addr_hi = SPC_DSP_ADDR >> 4;
|
||||||
|
if (!(addr_lo == 12))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!(addr_hi == 4))
|
||||||
|
return;
|
||||||
|
|
||||||
|
s32 i, cursamp, pitch;
|
||||||
|
v &= 0xFF; // ext
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
if (v & (1 << i))
|
||||||
|
{
|
||||||
|
cursamp = SPC_DSP[4 + (i << 4)]; // Number of current sample
|
||||||
|
if (cursamp < IT_SAMPLE_MAX) // Only 99 samples supported, sorry
|
||||||
|
{
|
||||||
|
if (ITSamples[cursamp] == NULL)
|
||||||
|
ITUpdateSample(cursamp);
|
||||||
|
pitch = (s32)(*(u16 *)&SPC_DSP[(i << 4) + 0x02]) * 7.8125; // Ext, Get pitch
|
||||||
|
if (ITSamples[cursamp]->freq == 0)
|
||||||
|
ITSamples[cursamp]->freq = pitch;
|
||||||
|
if ((pitch != 0) && (ITSamples[cursamp] != NULL) &&
|
||||||
|
(ITSamples[cursamp]->freq != 0)) // Ext, Sample is actually useful?
|
||||||
|
{
|
||||||
|
ITdata[i].mask |= IT_MASK_NOTE_SAMPLE_ADJUSTVOLUME; // Update note, sample, and adjust the volume.
|
||||||
|
ITdata[i].note = ITPitchToNote(pitch, ITSamples[cursamp]->freq); // change pitch to note
|
||||||
|
ITdata[i].pitch = (s32)(pow(2, ((f64)ITdata[i].note - 60) / 12) *
|
||||||
|
(f64)ITSamples[cursamp]->freq); // needed for pitch slide detection
|
||||||
|
ITdata[i].lvol = 0;
|
||||||
|
ITdata[i].rvol = 0;
|
||||||
|
// IT code will get sample from DSP buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ITSSave(sndsamp *s, FILE *f) // Save sample
|
||||||
|
{
|
||||||
|
s32 loopto = -1;
|
||||||
|
s32 length = 0;
|
||||||
|
s32 freq = 0;
|
||||||
|
s32 ofs = ftell(f);
|
||||||
|
ITFileSample *sHeader = calloc(1, sizeof(ITFileSample));
|
||||||
|
if (sHeader == NULL)
|
||||||
|
{
|
||||||
|
printf("Error: could not allocate memory for ITFileSample struct\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (s != NULL)
|
||||||
|
{
|
||||||
|
loopto = s->loopto;
|
||||||
|
length = s->length;
|
||||||
|
freq = s->freq;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
freq = 8363;
|
||||||
|
loopto = 0;
|
||||||
|
}
|
||||||
|
memcpy(sHeader->magic, "IMPS", 4);
|
||||||
|
if (length)
|
||||||
|
strcpy(sHeader->fileName, "SPC2ITSAMPLE");
|
||||||
|
sHeader->GlobalVolume = 64;
|
||||||
|
sHeader->Flags |= 2; // Bit 1 (16 bit)
|
||||||
|
if (length)
|
||||||
|
sHeader->Flags |= 1; // Bit 0 (sample included with header)
|
||||||
|
sHeader->Volume = 64;
|
||||||
|
if (length)
|
||||||
|
strcpy(sHeader->SampleName, "SPC2ITSAMPLE");
|
||||||
|
sHeader->Convert = 1;
|
||||||
|
sHeader->DefaultPan = 0;
|
||||||
|
sHeader->NumberOfSamples = length;
|
||||||
|
if (loopto != -1)
|
||||||
|
{
|
||||||
|
sHeader->Flags |= 16; // Bit 4 (Use loop)
|
||||||
|
sHeader->LoopBeginning = loopto;
|
||||||
|
sHeader->LoopEnd = length;
|
||||||
|
}
|
||||||
|
sHeader->C5Speed = freq;
|
||||||
|
if (length)
|
||||||
|
sHeader->SampleOffset = ofs + sizeof(ITFileSample);
|
||||||
|
fwrite(sHeader, sizeof(ITFileSample), 1, f);
|
||||||
|
free(sHeader);
|
||||||
|
if (length)
|
||||||
|
fwrite(s->buf, s->length * 2, 1, f); // Write the sample itself... 2x length.
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ITWritePattern(ITPatternInfo *pInfo) {
|
||||||
|
ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Channel;
|
||||||
|
ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Mask;
|
||||||
|
|
||||||
|
if (pInfo->Mask & IT_MASK_NOTE)
|
||||||
|
ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Note;
|
||||||
|
if (pInfo->Mask & IT_MASK_SAMPLE)
|
||||||
|
ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Sample;
|
||||||
|
if (pInfo->Mask & IT_MASK_ADJUSTVOLUME)
|
||||||
|
ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Volume;
|
||||||
|
if (pInfo->Mask & IT_MASK_PITCHSLIDE)
|
||||||
|
{
|
||||||
|
ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Command;
|
||||||
|
ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->CommandValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 ITStart(s32 rows) // Opens up temporary file and inits writing
|
||||||
|
{
|
||||||
|
SPCAddWriteDSPCallback(&ITWriteDSPCallback);
|
||||||
|
s32 i;
|
||||||
|
ITrows = rows;
|
||||||
|
|
||||||
|
for (i = 0; i < NUM_PATT_BUFS; i++)
|
||||||
|
{
|
||||||
|
ITpattbuf[i] = calloc(1, Mem64k - 8); //Don't include the 8 byte header
|
||||||
|
if (ITpattbuf[i] == NULL)
|
||||||
|
{
|
||||||
|
printf("Error: could not allocate memory for IT pattern buffer\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
ITpattlen[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ITPatterns = calloc(1, Mem64k * IT_PATTERN_MAX);
|
||||||
|
if (ITPatterns == NULL)
|
||||||
|
{
|
||||||
|
printf("Error: could not allocate memory for IT pattern storage\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
ITPatternsSize = 0;
|
||||||
|
ITcurbuf = 0;
|
||||||
|
ITbufpos = 0;
|
||||||
|
ITcurrow = 0;
|
||||||
|
|
||||||
|
curoffs = 0;
|
||||||
|
for (i = 0; i < IT_PATTERN_MAX; i++)
|
||||||
|
offset[i] = -1; // -1 means unused pattern
|
||||||
|
curpatt = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
ITdata[i].mask = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 ITUpdate() // Dumps pattern buffers to file
|
||||||
|
{
|
||||||
|
u8 *tmpptr;
|
||||||
|
s32 i;
|
||||||
|
ITFilePattern *pHeader = calloc(1, sizeof(ITFilePattern));
|
||||||
|
if (pHeader == NULL)
|
||||||
|
{
|
||||||
|
printf("Error: could not allocate memory for ITFilePattern struct\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
for (i = 0; i < ITcurbuf; i++)
|
||||||
|
{
|
||||||
|
offset[curpatt] = curoffs;
|
||||||
|
pHeader->Length = ITpattlen[i];
|
||||||
|
pHeader->Rows = ITrows;
|
||||||
|
memcpy(&ITPatterns[ITPatternsSize], pHeader, sizeof(ITFilePattern));
|
||||||
|
ITPatternsSize += sizeof(ITFilePattern);
|
||||||
|
memcpy(&ITPatterns[ITPatternsSize], ITpattbuf[i], ITpattlen[i]);
|
||||||
|
ITPatternsSize += ITpattlen[i];
|
||||||
|
curoffs += ITpattlen[i] + 8;
|
||||||
|
if (curpatt < IT_PATTERN_MAX)
|
||||||
|
curpatt++; // Continue counting if we haven't reached the limit yet
|
||||||
|
}
|
||||||
|
free(pHeader);
|
||||||
|
tmpptr = ITpattbuf[0];
|
||||||
|
ITpattbuf[0] = ITpattbuf[ITcurbuf];
|
||||||
|
ITpattbuf[ITcurbuf] = tmpptr;
|
||||||
|
ITcurbuf = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 ITWrite(char *fn) // Write the final IT file
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
s32 i, t, numsamps, ofs;
|
||||||
|
ITPatternInfo *pInfo = calloc(1, sizeof(ITPatternInfo));
|
||||||
|
if (pInfo == NULL)
|
||||||
|
{
|
||||||
|
printf("Error: could not allocate memory for ITPatternInfo struct\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
// START IT CLEANUP
|
||||||
|
if (fn == NULL)
|
||||||
|
{
|
||||||
|
printf("Error: no IT filename\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
pInfo->Mask = 1;
|
||||||
|
pInfo->Note = 254; //note cut
|
||||||
|
// Stop all notes and loop back to the beginning
|
||||||
|
for (i = 0; i < 15; i++) // Save the last channel to put loop in
|
||||||
|
{
|
||||||
|
pInfo->Channel = (i + 1) | 128; // Channels are 1 based (Channels start at 1, not 0, ITTECH.TXT is WRONG) !!!
|
||||||
|
ITWritePattern(pInfo);
|
||||||
|
}
|
||||||
|
pInfo->Channel = (15 + 1) | 128;
|
||||||
|
pInfo->Mask = 9; // 1001 (note, special command)
|
||||||
|
pInfo->Command = 2; // Effect B: jump to...
|
||||||
|
pInfo->CommandValue = 0; //...order 0 (Loop to beginning)
|
||||||
|
ITWritePattern(pInfo);
|
||||||
|
free(pInfo);
|
||||||
|
while (ITcurrow++ < ITrows)
|
||||||
|
ITpattbuf[ITcurbuf][ITbufpos++] = 0; // end-of-row
|
||||||
|
|
||||||
|
ITpattlen[ITcurbuf++] = ITbufpos;
|
||||||
|
ITUpdate(); // Save the changes we just made
|
||||||
|
// END IT CLEANUP
|
||||||
|
f = fopen(fn, "wb");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
printf("Error: could not open IT file\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
ITFileHeader *fHeader = calloc(1, sizeof(ITFileHeader));
|
||||||
|
if (fHeader == NULL)
|
||||||
|
{
|
||||||
|
printf("Error: could not allocate memory for ITFileHeader struct\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
memcpy(fHeader->magic, "IMPM", 4);
|
||||||
|
if (SPCInfo->SongTitle[0])
|
||||||
|
strncpy(fHeader->songName, SPCInfo->SongTitle, 25);
|
||||||
|
else
|
||||||
|
strcpy(fHeader->songName, "spc2it conversion"); // default string
|
||||||
|
fHeader->OrderNumber = curpatt + 1; // number of orders + terminating order
|
||||||
|
for (numsamps = IT_SAMPLE_MAX; ITSamples[numsamps - 1] == NULL; numsamps--)
|
||||||
|
; // Count the number of samples (the reason of the minus one is because c arrays start at 0)
|
||||||
|
numsamps++;
|
||||||
|
fHeader->SampleNumber = numsamps; // Number of samples
|
||||||
|
fHeader->PatternNumber = curpatt; // Number of patterns
|
||||||
|
fHeader->TrackerCreatorVersion = 0xDAEB; // Created with this tracker version
|
||||||
|
fHeader->TrackerFormatVersion = 0x200; // Compatible with this tracker version
|
||||||
|
fHeader->Flags = 9; // Flags: Stereo, Linear Slides
|
||||||
|
fHeader->GlobalVolume = 128; // Global volume
|
||||||
|
fHeader->MixVolume = 100; // Mix volume
|
||||||
|
fHeader->InitialSpeed = 1; // Initial speed (fastest)
|
||||||
|
fHeader->InitialTempo = (u8)(SPCUpdateRate * 2.5); // Initial tempo (determined by update rate)
|
||||||
|
fHeader->PanningSeperation = 128; // Stereo separation (max)
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
fHeader->ChannelPan[i] = 0; // Channel pan: Set 8 channels to left
|
||||||
|
for (i = 8; i < 16; i++)
|
||||||
|
fHeader->ChannelPan[i] = 64; // Set 8 channels to right
|
||||||
|
for (i = 16; i < 64; i++)
|
||||||
|
fHeader->ChannelPan[i] = 128; // Disable the rest of the channels (Value: +128)
|
||||||
|
for (i = 0; i < 16; i++)
|
||||||
|
fHeader->ChannelVolume[i] = 64; // Channel Vol: set 16 channels loud
|
||||||
|
fwrite(fHeader, sizeof(ITFileHeader), 1, f);
|
||||||
|
free(fHeader);
|
||||||
|
// orders
|
||||||
|
for (i = 0; i < curpatt; i++)
|
||||||
|
fputc(i, f); // Write from 0 to the number of patterns (max: 0xFD)
|
||||||
|
fputc(255, f); // terminating order
|
||||||
|
// Sample offsets
|
||||||
|
ofs = sizeof(ITFileHeader) + (curpatt + 1) + ((numsamps * sizeof(s32)) + (curpatt * sizeof(s32)));
|
||||||
|
for (i = 0; i < numsamps; i++)
|
||||||
|
{
|
||||||
|
fwrite(&ofs, sizeof(s32), 1, f);
|
||||||
|
ofs += sizeof(ITFileSample);
|
||||||
|
if (ITSamples[i] != NULL) // Sample is going to be put in file? Add the length of the sample.
|
||||||
|
ofs += (ITSamples[i]->length * 2);
|
||||||
|
}
|
||||||
|
// Pattern offsets
|
||||||
|
for (i = 0; i < curpatt; i++)
|
||||||
|
{
|
||||||
|
t = offset[i] + ofs;
|
||||||
|
fwrite(&t, sizeof(s32), 1, f);
|
||||||
|
}
|
||||||
|
// samples
|
||||||
|
for (i = 0; i < numsamps; i++)
|
||||||
|
ITSSave(ITSamples[i], f);
|
||||||
|
// patterns
|
||||||
|
fwrite(ITPatterns, ITPatternsSize, 1, f);
|
||||||
|
for (i = 0; i < NUM_PATT_BUFS; i++)
|
||||||
|
free(ITpattbuf[i]);
|
||||||
|
free(ITPatterns);
|
||||||
|
fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ITMix()
|
||||||
|
{
|
||||||
|
s32 envx, pitchslide, lvol = 0, rvol = 0, pitch, temp = 0, voice;
|
||||||
|
ITPatternInfo *pInfo = calloc(1, sizeof(ITPatternInfo));
|
||||||
|
if (pInfo == NULL)
|
||||||
|
{
|
||||||
|
printf("Error: could not allocate memory for ITPatternInfo struct\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
u8 mastervolume = SPC_DSP[0x0C];
|
||||||
|
for (voice = 0; voice < 8; voice++)
|
||||||
|
{
|
||||||
|
if ((SPC_DSP[0x4C] & (1 << voice))) // 0x4C == key on
|
||||||
|
{
|
||||||
|
envx = SNDDoEnv(voice);
|
||||||
|
lvol = (envx >> 24) * (s32)((s8)SPC_DSP[(voice << 4) ]) * mastervolume >> 14; // Ext
|
||||||
|
rvol = (envx >> 24) * (s32)((s8)SPC_DSP[(voice << 4) + 0x01]) * mastervolume >> 14; // Ext
|
||||||
|
// Volume no echo: (s32)((s8)SPC_DSP[(voice << 4) ]) * mastervolume >> 7;
|
||||||
|
|
||||||
|
|
||||||
|
pitch = (s32)(*(u16 *)&SPC_DSP[(voice << 4) + 0x02]) * 7.8125; // This code merges 2 numbers together, high and low 8 bits, to make 16 bits.
|
||||||
|
// adjust for negative volumes
|
||||||
|
if (lvol < 0)
|
||||||
|
lvol = -lvol;
|
||||||
|
if (rvol < 0)
|
||||||
|
rvol = -rvol;
|
||||||
|
// lets see if we need to pitch slide
|
||||||
|
if (pitch && ITdata[voice].pitch)
|
||||||
|
{
|
||||||
|
pitchslide = (s32)(log2((f64)pitch / (f64)ITdata[voice].pitch) * 768.0);
|
||||||
|
if (pitchslide)
|
||||||
|
ITdata[voice].mask |= IT_MASK_PITCHSLIDE; // enable pitch slide
|
||||||
|
}
|
||||||
|
// adjust volume?
|
||||||
|
if ((lvol != ITdata[voice].lvol) || (rvol != ITdata[voice].rvol))
|
||||||
|
{
|
||||||
|
ITdata[voice].mask |= IT_MASK_ADJUSTVOLUME; // Enable adjust volume
|
||||||
|
ITdata[voice].lvol = lvol;
|
||||||
|
ITdata[voice].rvol = rvol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pInfo->Channel = (voice + 1) | 128; //Channels here are 1 based!
|
||||||
|
pInfo->Mask = ITdata[voice].mask;
|
||||||
|
if (ITdata[voice].mask & IT_MASK_NOTE)
|
||||||
|
pInfo->Note = ITdata[voice].note;
|
||||||
|
if (ITdata[voice].mask & IT_MASK_SAMPLE)
|
||||||
|
pInfo->Sample = SPC_DSP[(voice << 4) + 4] + 1;
|
||||||
|
if (ITdata[voice].mask & IT_MASK_ADJUSTVOLUME)
|
||||||
|
pInfo->Volume = (lvol > 64) ? 64 : lvol;
|
||||||
|
if (ITdata[voice].mask & IT_MASK_PITCHSLIDE)
|
||||||
|
{
|
||||||
|
if (pitchslide > 0xF)
|
||||||
|
{
|
||||||
|
temp = pitchslide >> 2;
|
||||||
|
if (temp > 0xF)
|
||||||
|
temp = 0xF;
|
||||||
|
temp |= FINE_SLIDE;
|
||||||
|
ITdata[voice].pitch = (s32)((f64)ITdata[voice].pitch * pow(2, (f64)((temp & 0xF) << 2) / 768.0));
|
||||||
|
pInfo->Command = EFFECT_F;
|
||||||
|
pInfo->CommandValue = temp;
|
||||||
|
}
|
||||||
|
else if (pitchslide > 0)
|
||||||
|
{
|
||||||
|
temp = pitchslide | EXTRA_FINE_SLIDE;
|
||||||
|
ITdata[voice].pitch = (s32)((f64)ITdata[voice].pitch * pow(2, (f64)(temp & 0xF) / 768.0));
|
||||||
|
pInfo->Command = EFFECT_F;
|
||||||
|
pInfo->CommandValue = temp;
|
||||||
|
}
|
||||||
|
else if (pitchslide > -0x10)
|
||||||
|
{
|
||||||
|
temp = (-pitchslide) | EXTRA_FINE_SLIDE;
|
||||||
|
ITdata[voice].pitch = (s32)((f64)ITdata[voice].pitch * pow(2, (f64)(temp & 0xF) / -768.0));
|
||||||
|
pInfo->Command = EFFECT_E;
|
||||||
|
pInfo->CommandValue = temp;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
temp = (-pitchslide) >> 2;
|
||||||
|
if (temp > 0xF)
|
||||||
|
temp = 0xF;
|
||||||
|
temp |= FINE_SLIDE;
|
||||||
|
ITdata[voice].pitch = (s32)((f64)ITdata[voice].pitch * pow(2, (f64)((temp & 0xF) << 2) / -768.0));
|
||||||
|
pInfo->Command = EFFECT_E;
|
||||||
|
pInfo->CommandValue = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ITWritePattern(pInfo); // Write for left channel
|
||||||
|
pInfo->Channel = (voice + 8 + 1) | 128;
|
||||||
|
if (ITdata[voice].mask & IT_MASK_ADJUSTVOLUME)
|
||||||
|
pInfo->Volume = (rvol > 64) ? 64 : rvol;
|
||||||
|
ITWritePattern(pInfo); // Write for right channel
|
||||||
|
|
||||||
|
ITdata[voice].mask = 0; // Clear the mask
|
||||||
|
}
|
||||||
|
ITpattbuf[ITcurbuf][ITbufpos++] = 0; // End-of-row
|
||||||
|
if (++ITcurrow >= ITrows)
|
||||||
|
{
|
||||||
|
ITpattlen[ITcurbuf++] = ITbufpos;
|
||||||
|
ITbufpos = 0; // Reset buffer pos
|
||||||
|
ITcurrow = 0; // Reset current row
|
||||||
|
}
|
||||||
|
free(pInfo);
|
||||||
|
}
|
||||||
33
spctools/spc2it/it.h
Normal file
33
spctools/spc2it/it.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/****************************************************
|
||||||
|
*Part of SPC2IT, read readme.md for more information*
|
||||||
|
****************************************************/
|
||||||
|
|
||||||
|
#ifndef IT_H
|
||||||
|
#define IT_H
|
||||||
|
|
||||||
|
#define NUM_PATT_BUFS 128
|
||||||
|
|
||||||
|
#include "spc2ittypes.h"
|
||||||
|
|
||||||
|
s32 ITStart(s32); // Opens temp file, inits writing
|
||||||
|
s32 ITUpdate(); // Dumps pattern buffers to file
|
||||||
|
s32 ITWrite(char *fn); // Stops recording and writes IT file from temp data
|
||||||
|
void ITMix();
|
||||||
|
|
||||||
|
// Macros
|
||||||
|
|
||||||
|
#define FINE_SLIDE 0xF0
|
||||||
|
#define EXTRA_FINE_SLIDE 0xE0
|
||||||
|
#define EFFECT_F 6
|
||||||
|
#define EFFECT_E 5
|
||||||
|
|
||||||
|
#define IT_PATTERN_MAX 0xFD // The original Impulse Tracker has 200 patterns max
|
||||||
|
#define IT_SAMPLE_MAX 0xFF // The original Impulse Tracker has 99 samples max
|
||||||
|
|
||||||
|
#define IT_MASK_NOTE 1 // 0001 (Note)
|
||||||
|
#define IT_MASK_SAMPLE 2 // 0010 (Sample/instrument marker)
|
||||||
|
#define IT_MASK_ADJUSTVOLUME 4 // 0100 (volume/panning)
|
||||||
|
#define IT_MASK_NOTE_SAMPLE_ADJUSTVOLUME (IT_MASK_NOTE | IT_MASK_SAMPLE | IT_MASK_ADJUSTVOLUME)
|
||||||
|
#define IT_MASK_PITCHSLIDE 8 // 1000 (some special command, we use effect F and effect E)
|
||||||
|
|
||||||
|
#endif
|
||||||
171
spctools/spc2it/main.c
Normal file
171
spctools/spc2it/main.c
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
/****************************************************
|
||||||
|
*Part of SPC2IT, read readme.md for more information*
|
||||||
|
****************************************************/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "sound.h"
|
||||||
|
#include "it.h"
|
||||||
|
#include "emu.h"
|
||||||
|
#include "sneese_spc.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#undef realpath
|
||||||
|
#define realpath(N,R) _fullpath((R),(N),_MAX_PATH)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
size_t u8Size = sizeof(u8);
|
||||||
|
if (!(u8Size == 1))
|
||||||
|
printf("Warning: wrong size u8: %zu \n", u8Size);
|
||||||
|
size_t u16Size = sizeof(u16);
|
||||||
|
if (!(u16Size == 2))
|
||||||
|
printf("Warning: wrong size u16: %zu \n", u16Size);
|
||||||
|
size_t u32Size = sizeof(u32);
|
||||||
|
if (!(u32Size == 4))
|
||||||
|
printf("Warning: wrong size u32: %zu \n", u32Size);
|
||||||
|
size_t u64Size = sizeof(u64);
|
||||||
|
if (!(u64Size == 8))
|
||||||
|
printf("Warning: wrong size u64: %zu \n", u64Size);
|
||||||
|
size_t s8Size = sizeof(s8);
|
||||||
|
if (!(s8Size == 1))
|
||||||
|
printf("Warning: wrong size s8: %zu \n", s8Size);
|
||||||
|
size_t s16Size = sizeof(s16);
|
||||||
|
if (!(s16Size == 2))
|
||||||
|
printf("Warning: wrong size s16: %zu \n", s16Size);
|
||||||
|
size_t s32Size = sizeof(s32);
|
||||||
|
if (!(s32Size == 4))
|
||||||
|
printf("Warning: wrong size s32: %zu \n", s32Size);
|
||||||
|
size_t s64Size = sizeof(s64);
|
||||||
|
if (!(s64Size == 8))
|
||||||
|
printf("Warning: wrong size s64: %zu \n", s64Size);
|
||||||
|
size_t ITFileHeaderSize = sizeof(ITFileHeader);
|
||||||
|
if (!(ITFileHeaderSize == 192))
|
||||||
|
printf("Warning: wrong size ITFileHeader: %zu \n", ITFileHeaderSize);
|
||||||
|
size_t ITFileSampleSize = sizeof(ITFileSample);
|
||||||
|
if (!(ITFileSampleSize == 80))
|
||||||
|
printf("Warning: wrong size ITFileSample: %zu \n", ITFileSampleSize);
|
||||||
|
size_t ITFilePatternSize = sizeof(ITFilePattern);
|
||||||
|
if (!(ITFilePatternSize == 8))
|
||||||
|
printf("Warning: wrong size ITFilePattern: %zu \n", ITFilePatternSize);
|
||||||
|
size_t SPCFileSize = sizeof(SPCFile);
|
||||||
|
if (!(SPCFileSize == 65920))
|
||||||
|
printf("Warning: wrong size SPCFile: %zu \n", SPCFileSize);
|
||||||
|
s32 seconds, limit, ITrows;
|
||||||
|
char fn[PATH_MAX];
|
||||||
|
s32 i;
|
||||||
|
fn[0] = 0;
|
||||||
|
ITrows = 200; // Default 200 IT rows/pattern
|
||||||
|
limit = 0; // Will be set later
|
||||||
|
for (i = 1; i < argc; i++)
|
||||||
|
{
|
||||||
|
if (argv[i][0] == '-')
|
||||||
|
switch (argv[i][1])
|
||||||
|
{
|
||||||
|
case 'r':
|
||||||
|
i++;
|
||||||
|
ITrows = atoi(argv[i]);
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
i++;
|
||||||
|
limit = atoi(argv[i]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf("Warning: unrecognized option '-%c'\n", argv[i][1]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
realpath(argv[i], fn);
|
||||||
|
}
|
||||||
|
if (fn[0] == 0)
|
||||||
|
{
|
||||||
|
printf(" SPC2IT - converts SPC700 sound files to the Impulse Tracker format\n\n");
|
||||||
|
printf(" Usage: spc2it [options] <filename>\n");
|
||||||
|
printf(" Where <filename> is any .spc or .sp# file\n\n");
|
||||||
|
printf(" Options: ");
|
||||||
|
printf("-t x Specify a time limit in seconds [60 default]\n");
|
||||||
|
printf(" -d xxxxxxxx Voices to disable (1-8) [none default]\n");
|
||||||
|
printf(" -r xxx Specify IT rows per pattern [200 default]\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
printf("Filepath: %s\n", fn);
|
||||||
|
if (ITStart(ITrows))
|
||||||
|
{
|
||||||
|
printf("Error: failed to initialize pattern buffers\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (SPCInit(fn)) // Reset SPC and load state
|
||||||
|
{
|
||||||
|
printf("Error: failed to initialize emulation\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (SNDInit())
|
||||||
|
{
|
||||||
|
printf("Error: failed to initialize sound\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!limit) && (SPCtime))
|
||||||
|
limit = SPCtime;
|
||||||
|
else if (!limit)
|
||||||
|
limit = 60;
|
||||||
|
|
||||||
|
printf("Time (seconds): %i\n", limit);
|
||||||
|
|
||||||
|
printf("IT Parameters:\n");
|
||||||
|
printf(" Rows/pattern: %d\n", ITrows);
|
||||||
|
|
||||||
|
printf("ID info:\n");
|
||||||
|
printf(" Song: %s\n", SPCInfo->SongTitle);
|
||||||
|
printf(" Game: %s\n", SPCInfo->GameTitle);
|
||||||
|
printf(" Dumper: %s\n", SPCInfo->DumperName);
|
||||||
|
printf(" Comments: %s\n", SPCInfo->Comment);
|
||||||
|
printf(" Created on: %s\n", SPCInfo->Date);
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
|
SNDNoteOn(SPC_DSP[0x4c]);
|
||||||
|
|
||||||
|
seconds = SNDratecnt = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ITMix();
|
||||||
|
if (ITUpdate())
|
||||||
|
break;
|
||||||
|
SNDratecnt += 1;
|
||||||
|
SPC_START(2048000 / (SPCUpdateRate * 2)); // emulate the SPC700
|
||||||
|
|
||||||
|
if (SNDratecnt >= SPCUpdateRate)
|
||||||
|
{
|
||||||
|
SNDratecnt -= SPCUpdateRate;
|
||||||
|
seconds++; // count number of seconds
|
||||||
|
printf("Progress: %f%%\r", (((f64)seconds / limit) * 100));
|
||||||
|
fflush(stdout);
|
||||||
|
if (seconds == limit)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("\n\nSaving file...\n");
|
||||||
|
for (i = 0; i < PATH_MAX; i++)
|
||||||
|
if (fn[i] == 0)
|
||||||
|
break;
|
||||||
|
for (; i > 0; i--)
|
||||||
|
if (fn[i] == '.')
|
||||||
|
{
|
||||||
|
strcpy(&fn[i + 1], "it");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ITWrite(fn))
|
||||||
|
printf("Error: failed to write %s.\n", fn);
|
||||||
|
else
|
||||||
|
printf("Wrote to %s successfully.\n", fn);
|
||||||
|
Reset_SPC();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
134
spctools/spc2it/sneese_spc.h
Normal file
134
spctools/spc2it/sneese_spc.h
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/****************************************************
|
||||||
|
*Part of SPC2IT, read readme.md for more information*
|
||||||
|
****************************************************/
|
||||||
|
|
||||||
|
/**************************************************************************
|
||||||
|
|
||||||
|
Copyright (c) 2005 Brad Martin.
|
||||||
|
Some portions copyright (c) 1998-2005 Charles Bilyue'.
|
||||||
|
|
||||||
|
This file is part of OpenSPC.
|
||||||
|
|
||||||
|
sneese_spc.h: This file defines the interface between the SNEeSe SPC700
|
||||||
|
core and the associated wrapper files. As the licensing rights for SNEeSe
|
||||||
|
are different from the rest of OpenSPC, none of the files in this directory
|
||||||
|
are LGPL. Although this file was created by me (Brad Martin), it contains
|
||||||
|
some code derived from SNEeSe and therefore falls under its license. See
|
||||||
|
the file './doc/LICENSE_SNEESE' in this directory for more information.
|
||||||
|
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
#if !defined(_SNEESE_SPC_H)
|
||||||
|
#define _SNEESE_SPC_H
|
||||||
|
|
||||||
|
#include "spc2ittypes.h"
|
||||||
|
|
||||||
|
/*========== DEFINES ==========*/
|
||||||
|
|
||||||
|
#define SPC_CTRL (SPCRAM[0xF1])
|
||||||
|
#define SPC_DSP_ADDR (SPCRAM[0xF2])
|
||||||
|
|
||||||
|
/*========== TYPES ==========*/
|
||||||
|
|
||||||
|
typedef union
|
||||||
|
{
|
||||||
|
u16 w;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
u8 l;
|
||||||
|
u8 h;
|
||||||
|
} b;
|
||||||
|
} word_2b;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
u8 B_flag;
|
||||||
|
u8 C_flag;
|
||||||
|
u8 H_flag;
|
||||||
|
u8 I_flag;
|
||||||
|
u8 N_flag;
|
||||||
|
u8 P_flag;
|
||||||
|
u8 PSW;
|
||||||
|
u8 SP;
|
||||||
|
u8 V_flag;
|
||||||
|
u8 X;
|
||||||
|
u8 Z_flag;
|
||||||
|
u8 cycle;
|
||||||
|
u8 data;
|
||||||
|
u8 data2;
|
||||||
|
u8 opcode;
|
||||||
|
u8 offset;
|
||||||
|
|
||||||
|
word_2b PC;
|
||||||
|
word_2b YA;
|
||||||
|
word_2b address;
|
||||||
|
word_2b address2;
|
||||||
|
word_2b data16;
|
||||||
|
word_2b direct_page;
|
||||||
|
|
||||||
|
u32 Cycles;
|
||||||
|
void *FFC0_Address;
|
||||||
|
u32 TotalCycles;
|
||||||
|
s32 WorkCycles;
|
||||||
|
u32 last_cycles;
|
||||||
|
|
||||||
|
u8 PORT_R[4];
|
||||||
|
u8 PORT_W[4];
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
u8 counter;
|
||||||
|
s16 position;
|
||||||
|
s16 target;
|
||||||
|
u32 cycle_latch;
|
||||||
|
} timers[4];
|
||||||
|
} SPC700_CONTEXT;
|
||||||
|
|
||||||
|
/*========== VARIABLES ==========*/
|
||||||
|
|
||||||
|
/* SPCimpl.c variables */
|
||||||
|
extern u8 In_CPU;
|
||||||
|
extern u32 Map_Address;
|
||||||
|
extern u32 Map_Byte;
|
||||||
|
extern u32 SPC_CPU_cycle_divisor;
|
||||||
|
extern u32 SPC_CPU_cycle_multiplicand;
|
||||||
|
extern u32 SPC_CPU_cycles;
|
||||||
|
extern u32 SPC_CPU_cycles_mul;
|
||||||
|
extern u8 SPC_DSP[256];
|
||||||
|
extern u32 SPC_DSP_DATA;
|
||||||
|
extern u8 SPCRAM[65536];
|
||||||
|
extern u32 sound_cycle_latch;
|
||||||
|
|
||||||
|
/* spc700.c variables */
|
||||||
|
extern SPC700_CONTEXT *active_context;
|
||||||
|
|
||||||
|
/*========== MACROS ==========*/
|
||||||
|
|
||||||
|
#define Wrap_SDSP_Cyclecounter()
|
||||||
|
#define update_sound()
|
||||||
|
#define BIT(bit) (1 << (bit))
|
||||||
|
|
||||||
|
/*========== PROCEDURES ==========*/
|
||||||
|
|
||||||
|
/* SPCimpl.c procedures */
|
||||||
|
void DisplaySPC(void);
|
||||||
|
|
||||||
|
void InvalidSPCOpcode(void);
|
||||||
|
|
||||||
|
void SPC_READ_DSP(void);
|
||||||
|
|
||||||
|
void SPC_WRITE_DSP(void);
|
||||||
|
|
||||||
|
/* spc700.c procedures */
|
||||||
|
void Reset_SPC(void);
|
||||||
|
|
||||||
|
u8 SPC_READ_PORT_W(u16 address);
|
||||||
|
|
||||||
|
void SPC_START(u32 cycles);
|
||||||
|
|
||||||
|
void SPC_WRITE_PORT_R(u16 address, u8 data);
|
||||||
|
|
||||||
|
u8 get_SPC_PSW(void);
|
||||||
|
|
||||||
|
void spc_restore_flags(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
263
spctools/spc2it/sound.c
Normal file
263
spctools/spc2it/sound.c
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
/****************************************************
|
||||||
|
*Part of SPC2IT, read readme.md for more information*
|
||||||
|
****************************************************/
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "emu.h"
|
||||||
|
#include "sound.h"
|
||||||
|
|
||||||
|
sndvoice SNDvoices[8];
|
||||||
|
s32 SNDratecnt;
|
||||||
|
|
||||||
|
static const u32 C[0x20] = {
|
||||||
|
0x0, 0x20000, 0x18000, 0x14000, 0x10000, 0xC000, 0xA000, 0x8000, 0x6000, 0x5000, 0x4000,
|
||||||
|
0x3000, 0x2800, 0x2000, 0x1800, 0x1400, 0x1000, 0xC00, 0xA00, 0x800, 0x600, 0x500,
|
||||||
|
0x400, 0x300, 0x280, 0x200, 0x180, 0x140, 0x100, 0xC0, 0x80, 0x40}; // How many cycles till adjust
|
||||||
|
// ADSR/GAIN
|
||||||
|
|
||||||
|
// PUBLIC (non-static) functions:
|
||||||
|
|
||||||
|
s32 SNDDoEnv(s32 voice)
|
||||||
|
{
|
||||||
|
u32 envx, c;
|
||||||
|
envx = SNDvoices[voice].envx;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
u32 cyc = TotalCycles - SNDvoices[voice].envcyc;
|
||||||
|
switch (SNDvoices[voice].envstate)
|
||||||
|
{
|
||||||
|
case ATTACK:
|
||||||
|
c = C[(SNDvoices[voice].ar << 1) + 1];
|
||||||
|
if (c == 0)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc = TotalCycles;
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
}
|
||||||
|
if (cyc > c)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc += c;
|
||||||
|
envx += 0x2000000; // add 1/64th
|
||||||
|
if (envx >= 0x7F000000)
|
||||||
|
{
|
||||||
|
envx = 0x7F000000;
|
||||||
|
if (SNDvoices[voice].sl != 7)
|
||||||
|
SNDvoices[voice].envstate = DECAY;
|
||||||
|
else
|
||||||
|
SNDvoices[voice].envstate = SUSTAIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
break;
|
||||||
|
case DECAY:
|
||||||
|
c = C[(SNDvoices[voice].dr << 1) + 0x10];
|
||||||
|
if (c == 0)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc = TotalCycles;
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
}
|
||||||
|
if (cyc > c)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc += c;
|
||||||
|
envx = (envx >> 8) * 255; // mult by 1-1/256
|
||||||
|
if (envx <= 0x10000000 * (SNDvoices[voice].sl + 1))
|
||||||
|
{
|
||||||
|
envx = 0x10000000 * (SNDvoices[voice].sl + 1);
|
||||||
|
SNDvoices[voice].envstate = SUSTAIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
break;
|
||||||
|
case SUSTAIN:
|
||||||
|
c = C[SNDvoices[voice].sr];
|
||||||
|
if (c == 0)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc = TotalCycles;
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
}
|
||||||
|
if (cyc > c)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc += c;
|
||||||
|
envx = (envx >> 8) * 255; // mult by 1-1/256
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
break;
|
||||||
|
case RELEASE:
|
||||||
|
// says add 1/256?? That won't release, must be subtract.
|
||||||
|
// But how often? Oh well, who cares, I'll just
|
||||||
|
// pick a number. :)
|
||||||
|
c = C[0x1A];
|
||||||
|
if (c == 0)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc = TotalCycles;
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
}
|
||||||
|
if (cyc > c)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc += c;
|
||||||
|
envx -= 0x800000; // sub 1/256th
|
||||||
|
if ((envx == 0) || (envx > 0x7F000000))
|
||||||
|
{
|
||||||
|
SPC_DSP[0x4C] &= ~(1 << voice);
|
||||||
|
return SNDvoices[voice].envx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
break;
|
||||||
|
case INCREASE:
|
||||||
|
c = C[SNDvoices[voice].gn];
|
||||||
|
if (c == 0)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc = TotalCycles;
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
}
|
||||||
|
if (cyc > c)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc += c;
|
||||||
|
envx += 0x2000000; // add 1/64th
|
||||||
|
if (envx > 0x7F000000)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc = TotalCycles;
|
||||||
|
return SNDvoices[voice].envx = 0x7F000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
break;
|
||||||
|
case DECREASE:
|
||||||
|
c = C[SNDvoices[voice].gn];
|
||||||
|
if (c == 0)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc = TotalCycles;
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
}
|
||||||
|
if (cyc > c)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc += c;
|
||||||
|
envx -= 0x2000000; // sub 1/64th
|
||||||
|
if (envx > 0x7F000000) // underflow
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc = TotalCycles;
|
||||||
|
return SNDvoices[voice].envx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
break;
|
||||||
|
case EXP:
|
||||||
|
c = C[SNDvoices[voice].gn];
|
||||||
|
if (c == 0)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc = TotalCycles;
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
}
|
||||||
|
if (cyc > c)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc += c;
|
||||||
|
envx = (envx >> 8) * 255; // mult by 1-1/256
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
break;
|
||||||
|
case BENT:
|
||||||
|
c = C[SNDvoices[voice].gn];
|
||||||
|
if (c == 0)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc = TotalCycles;
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
}
|
||||||
|
if (cyc > c)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc += c;
|
||||||
|
if (envx < 0x60000000)
|
||||||
|
envx += 0x2000000; // add 1/64th
|
||||||
|
else
|
||||||
|
envx += 0x800000; // add 1/256th
|
||||||
|
if (envx > 0x7F000000)
|
||||||
|
{
|
||||||
|
SNDvoices[voice].envcyc = TotalCycles;
|
||||||
|
return SNDvoices[voice].envx = 0x7F000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return SNDvoices[voice].envx = envx;
|
||||||
|
break;
|
||||||
|
case DIRECT:
|
||||||
|
SNDvoices[voice].envcyc = TotalCycles;
|
||||||
|
return envx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SNDNoteOn(u8 v)
|
||||||
|
{
|
||||||
|
s32 i, cursamp, adsr1, adsr2, gain;
|
||||||
|
v &= 0xFF;
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
if (v & (1 << i))
|
||||||
|
{
|
||||||
|
cursamp = SPC_DSP[4 + (i << 4)];
|
||||||
|
if (cursamp < 512)
|
||||||
|
{
|
||||||
|
SPC_DSP[0x4C] |= (1 << i);
|
||||||
|
// figure ADSR/GAIN
|
||||||
|
adsr1 = SPC_DSP[(i << 4) + 5];
|
||||||
|
if (adsr1 & 0x80)
|
||||||
|
{
|
||||||
|
// ADSR mode
|
||||||
|
adsr2 = SPC_DSP[(i << 4) + 6];
|
||||||
|
SNDvoices[i].envx = 0;
|
||||||
|
SNDvoices[i].envcyc = TotalCycles;
|
||||||
|
SNDvoices[i].envstate = ATTACK;
|
||||||
|
SNDvoices[i].ar = adsr1 & 0xF;
|
||||||
|
SNDvoices[i].dr = adsr1 >> 4 & 7;
|
||||||
|
SNDvoices[i].sr = adsr2 & 0x1f;
|
||||||
|
SNDvoices[i].sl = adsr2 >> 5;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// GAIN mode
|
||||||
|
gain = SPC_DSP[(i << 4) + 7];
|
||||||
|
if (gain & 0x80)
|
||||||
|
{
|
||||||
|
SNDvoices[i].envcyc = TotalCycles;
|
||||||
|
SNDvoices[i].envstate = gain >> 5;
|
||||||
|
SNDvoices[i].gn = gain & 0x1F;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SNDvoices[i].envx = (gain & 0x7F) << 24;
|
||||||
|
SNDvoices[i].envstate = DIRECT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (SPC_Write_DSP_Hook)
|
||||||
|
(*SPC_Write_DSP_Hook)(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SNDNoteOff(u8 v)
|
||||||
|
{
|
||||||
|
s32 i;
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
if (v & (1 << i))
|
||||||
|
{
|
||||||
|
SNDDoEnv(i);
|
||||||
|
SNDvoices[i].envstate = RELEASE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 SNDInit()
|
||||||
|
{
|
||||||
|
s32 i;
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
SNDvoices[i].envx = 0;
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
28
spctools/spc2it/sound.h
Normal file
28
spctools/spc2it/sound.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/****************************************************
|
||||||
|
*Part of SPC2IT, read readme.md for more information*
|
||||||
|
****************************************************/
|
||||||
|
|
||||||
|
#ifndef SOUND_H
|
||||||
|
#define SOUND_H
|
||||||
|
|
||||||
|
#include "spc2ittypes.h"
|
||||||
|
|
||||||
|
#define ATTACK 0 // A of ADSR
|
||||||
|
#define DECAY 1 // D of ADSR
|
||||||
|
#define SUSTAIN 2 // S of ADSR
|
||||||
|
#define RELEASE 3 // R of ADSR
|
||||||
|
#define DECREASE 4 // GAIN linear decrease mode
|
||||||
|
#define EXP 5 // GAIN exponential decrease mode
|
||||||
|
#define INCREASE 6 // GAIN linear increase mode
|
||||||
|
#define BENT 7 // GAIN bent line increase mode
|
||||||
|
#define DIRECT 8 // Directly specify ENVX
|
||||||
|
|
||||||
|
extern sndvoice SNDvoices[8];
|
||||||
|
extern s32 SNDkeys, SNDratecnt;
|
||||||
|
|
||||||
|
s32 SNDDoEnv(s32);
|
||||||
|
void SNDNoteOn(u8);
|
||||||
|
void SNDNoteOff(u8);
|
||||||
|
s32 SNDInit();
|
||||||
|
|
||||||
|
#endif
|
||||||
27
spctools/spc2it/spc2it.1
Normal file
27
spctools/spc2it/spc2it.1
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
.TH spc2it "1" "September 10 2018" "User Commands"
|
||||||
|
.SH NAME
|
||||||
|
spc2it \- converts SPC700 sound files to the Impulse Tracker format
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B spc2it
|
||||||
|
.RI [ options ] " filename"
|
||||||
|
.TP
|
||||||
|
Where <filename> is any .spc or .sp# file
|
||||||
|
.SH DESCRIPTION
|
||||||
|
This manual describes the
|
||||||
|
.B spc2it
|
||||||
|
command. It converts SPC files to IT (Impulse Tracker) files.
|
||||||
|
.TP
|
||||||
|
Options:
|
||||||
|
.TP
|
||||||
|
\fB\-t\fR x
|
||||||
|
Specify a time limit in seconds [60 default]
|
||||||
|
.TP
|
||||||
|
\fB\-d\fR xxxxxxxx Voices to disable (1\-8) [none default]
|
||||||
|
.TP
|
||||||
|
\fB\-r\fR xxx
|
||||||
|
Specify IT rows per pattern [200 default]
|
||||||
|
.IP
|
||||||
|
.SH "SEE ALSO"
|
||||||
|
.BR adplay (1),
|
||||||
|
.BR schismtracker (1).
|
||||||
|
.br
|
||||||
155
spctools/spc2it/spc2ittypes.h
Normal file
155
spctools/spc2it/spc2ittypes.h
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
/****************************************************
|
||||||
|
*Part of SPC2IT, read readme.md for more information*
|
||||||
|
****************************************************/
|
||||||
|
|
||||||
|
#ifndef SPC2ITTYPES_H
|
||||||
|
#define SPC2ITTYPES_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint16_t u16;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int8_t s8;
|
||||||
|
typedef int16_t s16;
|
||||||
|
typedef int32_t s32;
|
||||||
|
typedef int64_t s64;
|
||||||
|
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
typedef s16 pcm_t;
|
||||||
|
|
||||||
|
#define OneKB (1024)
|
||||||
|
|
||||||
|
#define Mem64k (OneKB * 64)
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char magic[4]; // IMPM
|
||||||
|
char songName[26];
|
||||||
|
u16 PHiligt; // Pattern row hilight stuff
|
||||||
|
u16 OrderNumber; // Number of orders in song
|
||||||
|
u16 InstrumentNumber; // Number of instruments
|
||||||
|
u16 SampleNumber; // Number of samples
|
||||||
|
u16 PatternNumber; // Number of patterns
|
||||||
|
u16 TrackerCreatorVersion; // The version of the tracker that created the IT file
|
||||||
|
u16 TrackerFormatVersion; // Format version
|
||||||
|
u16 Flags; // Information flags
|
||||||
|
u16 Special; // Special st0ff
|
||||||
|
u8 GlobalVolume; // Global volume
|
||||||
|
u8 MixVolume; // Mix volume
|
||||||
|
u8 InitialSpeed; // Initial speed
|
||||||
|
u8 InitialTempo; // Initial tempo
|
||||||
|
u8 PanningSeperation; // Panning separation between channels
|
||||||
|
u8 PitchWheelDepth; // Pitch wheel depth for MIDI controllers
|
||||||
|
u16 MessageLength; // Length of message if Bit 0 of Special is 1
|
||||||
|
u32 MessageOffset; // Offset of message if Bit 0 of Special is 1
|
||||||
|
u32 Reserved; // Reserved stuff
|
||||||
|
u8 ChannelPan[64]; // Channel pan
|
||||||
|
u8 ChannelVolume[64]; // Channel volume
|
||||||
|
} ITFileHeader;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char magic[4]; // IMPS
|
||||||
|
char fileName[13]; // 8.3 DOS filename (including null termating)
|
||||||
|
u8 GlobalVolume; // Global volume for sample
|
||||||
|
u8 Flags;
|
||||||
|
u8 Volume; // Default volume
|
||||||
|
char SampleName[26];
|
||||||
|
u8 Convert;
|
||||||
|
u8 DefaultPan;
|
||||||
|
u32 NumberOfSamples; // not bytes! in samples!
|
||||||
|
u32 LoopBeginning; // not bytes! in samples!
|
||||||
|
u32 LoopEnd; // not bytes! in samples!
|
||||||
|
u32 C5Speed; // Num of bytes a second for C-5
|
||||||
|
u32 SustainLoopBeginning;
|
||||||
|
u32 SustainLoopEnd;
|
||||||
|
u32 SampleOffset; // the offset of the sample in file
|
||||||
|
u8 VibratoSpeed;
|
||||||
|
u8 VibratoDepth;
|
||||||
|
u8 VibratoRate;
|
||||||
|
u8 VibratoType;
|
||||||
|
} ITFileSample;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
u16 Length;
|
||||||
|
u16 Rows;
|
||||||
|
u8 Padding[4];
|
||||||
|
} ITFilePattern;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
u8 PC[2]; // Don't change this to u16, because of the way structs work, that will misalign the struct with the file!
|
||||||
|
u8 A;
|
||||||
|
u8 X;
|
||||||
|
u8 Y;
|
||||||
|
u8 PSW;
|
||||||
|
u8 SP;
|
||||||
|
u8 Reserved[2];
|
||||||
|
} SPCFileRegisters;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char SongTitle[32];
|
||||||
|
char GameTitle[32];
|
||||||
|
char DumperName[16];
|
||||||
|
char Comment[32];
|
||||||
|
char Date[11];
|
||||||
|
char SongLength[3];
|
||||||
|
char FadeLength[5];
|
||||||
|
char Artist[32];
|
||||||
|
u8 ChannelDisabled;
|
||||||
|
u8 EmulatorDumpedWith; //0 unknown, 1 ZSNES, 2 Snes9x
|
||||||
|
u8 Reserved[45];
|
||||||
|
} SPCFileInformation;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char FileTag[33]; // SNES-SPC700 Sound File Data v0.30
|
||||||
|
u8 FileTagTerminator[2]; // 0x1A, 0x1A
|
||||||
|
u8 ContainsID666; // 0x1A for contains ID666, 0x1B for no ID666
|
||||||
|
u8 Version; //Version minor (30)
|
||||||
|
SPCFileRegisters Registers; // 9bytes
|
||||||
|
SPCFileInformation Information; // 163bytes
|
||||||
|
u8 RAM[65536];
|
||||||
|
u8 DSPBuffer[128];
|
||||||
|
} SPCFile;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
u8 Channel;
|
||||||
|
u8 Mask;
|
||||||
|
u8 Note;
|
||||||
|
u8 Sample;
|
||||||
|
u8 Volume;
|
||||||
|
u8 Command;
|
||||||
|
u8 CommandValue;
|
||||||
|
} ITPatternInfo;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
s32 mask, pitch, lvol, rvol;
|
||||||
|
u8 note;
|
||||||
|
} itdata;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
s32 length;
|
||||||
|
s32 loopto;
|
||||||
|
s16 *buf;
|
||||||
|
s32 freq;
|
||||||
|
} sndsamp;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
s32 ave;
|
||||||
|
u32 envx, envcyc;
|
||||||
|
s32 envstate;
|
||||||
|
u32 ar, dr, sl, sr, gn;
|
||||||
|
} sndvoice;
|
||||||
|
|
||||||
|
#endif
|
||||||
3526
spctools/spc2it/spc700.c
Normal file
3526
spctools/spc2it/spc700.c
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user