1
0
mirror of https://github.com/ScrelliCopter/VGM-Tools synced 2025-02-21 04:09:25 +11:00

Compare commits

26 Commits

Author SHA1 Message Date
6510096f90 malloc cast is unnecessary in C 2019-10-01 23:50:01 +10:00
07ee0b546c rename and build as C 2019-10-01 23:46:52 +10:00
454fcd3226 use c include for stdint 2019-10-01 23:40:22 +10:00
c94016e793 use a lightweight buffer class for in-memory samples 2019-10-01 23:37:55 +10:00
172174c837 import stdio instead of string 2019-10-01 23:26:19 +10:00
3fafdfd3f4 use c string for out names 2019-10-01 23:25:13 +10:00
6ccf9f6e38 snprintf for output names 2019-10-01 23:23:38 +10:00
b12258970e replace cout with printf 2019-09-30 23:17:47 +10:00
886966d7dc merge DecodeSample & DumpBytes 2019-09-30 20:43:07 +10:00
63aacb9a01 FILE io for DumpBytes 2019-09-30 20:33:58 +10:00
7c66bbab52 fix some error prone stuff, also I goofed the short circuit... oops 2019-09-30 17:14:07 +10:00
fbf887b534 c io for input 2019-09-29 11:51:14 +10:00
0a26bc8998 reformat 2019-09-28 10:18:39 +10:00
6d60ea91c0 update README.md 2019-09-27 16:16:50 +10:00
6164533c1a port autoextract to shell 2019-09-27 15:58:08 +10:00
7c5a577fba makefile for crappy vgm pcm scanner util 2019-09-26 22:54:08 +10:00
7e0740d03a adpcm decoders compiling on loonix 2019-09-26 22:06:51 +10:00
c1a5003a82 batch should be crlf I guess 2019-09-26 14:53:40 +10:00
1969f7ae9a add spc2it manpage from upstream 2019-09-26 14:43:11 +10:00
33851696b1 move relevant gitignore 2019-09-26 14:24:37 +10:00
7aff052810 fix spelling mistake 2019-09-26 14:22:28 +10:00
85a3e5089b update spc2it README 2019-09-26 14:19:39 +10:00
508f464aa7 housekeeping 2019-09-26 14:06:37 +10:00
86317f57ec create plain makefile for spc2it 2019-09-26 14:01:18 +10:00
d0945220a6 Cleaned up some loose semicolons... old habits die hard. 2018-10-10 23:14:38 +11:00
45ec9cf2cf Reorganised slightly, and added my spc sample ripper + spc2it. 2018-09-03 02:45:47 +10:00
36 changed files with 6510 additions and 241 deletions

7
.gitignore vendored
View File

@@ -1,6 +1 @@
*.exe
*.vgm
*.vgz
*.log
*.pcm
*.wav
.idea/

View File

@@ -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.

View File

@@ -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 \

View File

@@ -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
View File

@@ -0,0 +1,11 @@
*.exe
*.vgm
*.vgz
*.log
*.pcm
*.wav
*.o
adpcm
adpcmb
neoadpcmextract

46
neotools/README.md Normal file
View 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
View 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)

View File

@@ -1,8 +1,6 @@
#include <process.h>
#include <stdio.h>
#include <mem.h>
#include <stdlib.h>
#include <math.h>
#include <io.h>
#define BUFFER_SIZE 1024*256
#define ADPCMA_VOLUME_RATE 1
@@ -70,8 +68,11 @@ int main(int argc, char *argv[])
}
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[0x28])) = Filelen*4;

View File

@@ -4,7 +4,7 @@ CFLAGS = -O3
SRC = .
OBJ = .
OUT_OBJ = $(OBJ)/adpcmb.exe
OUT_OBJ = $(OBJ)/adpcmb
all: $(OUT_OBJ)

View File

@@ -1,19 +1,19 @@
if ["%~x1"]==[".vgz"] goto vgztopcm else goto vgmtopcm
:vgmtopcm
neoadpcmextract.exe %1
goto pcmtowav
:vgztopcm
copy /y %1 temp.vgm.gz
gzip.exe -d temp.vgm.gz
neoadpcmextract.exe temp.vgm
del temp.vgm
goto pcmtowav
:pcmtowav
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"
del "*.pcm"
mkdir "%~n1"
if ["%~x1"]==[".vgz"] goto vgztopcm else goto vgmtopcm
:vgmtopcm
neoadpcmextract.exe %1
goto pcmtowav
:vgztopcm
copy /y %1 temp.vgm.gz
gzip.exe -d temp.vgm.gz
neoadpcmextract.exe temp.vgm
del temp.vgm
goto pcmtowav
:pcmtowav
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"
del "*.pcm"
mkdir "%~n1"
move "*.wav" "%~n1"

20
neotools/autoextract.sh Executable file
View 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"

View 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)

View 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
View 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)

View 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
View File

@@ -0,0 +1,8 @@
obj/
build/
spc2it
*.exe
*.spc
*.it
.DS_Store
Thumbs.db

View 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
View 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
View 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

View 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
View 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

View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}

View 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
View 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
View 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
View 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

View 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

File diff suppressed because it is too large Load Diff