// H to Pas Converter Ver.1.15.1.42 by K.Kumaki
//     Date : 29 Nov 2002
//   Source : SNESAPU.h (25 Aug 2001)

unit _SNESAPU_Moonlight;

{$X+}{$Z2}{$A8}

interface

uses Windows,Classes,SysUtils;

(***************************************************************************************************
* Program:    Super Nintendo Entertainment System(tm) Audio Processing Unit Emulator DLL           *
* Platform:   Intel 80386 & MMX                                                                    *
* Programmer: Anti Resonance                                                                       *
*                                                                                                  *
* "SNES" and "Super Nintendo Entertainment System" are trademarks of Nintendo Co., Limited and its *
* subsidiary companies.                                                                            *
*                                                                                                  *
* This program is free software; you can redistribute it and/or modify it under the terms of the   *
* GNU General Public License as published by the Free Software Foundation; either version 2 of     *
* the License, or (at your option) any later version.                                              *
*                                                                                                  *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;        *
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.        *
* See the GNU General Public License for more details.                                             *
*                                                                                                  *
* You should have received a copy of the GNU General Public License along with this program;       *
* if not, write to the Free Software Foundation, Inc.                                              *
* 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.                                        *
*                                                                                                  *
* ------------------------------------------------------------------------------------------------ *
* Revision History:                                                                                *
*                                                                                                  *
* 0.95 15.05.2001                                                                                  *
*                                                                                                  *
* 0.92 03.03.2001 SNESAmp v2.1                                                                     *
*                                                                                                  *
* 0.90 01.01.2001 SNESAmp v2.0                                                                     *
*      + Made some changes to the internal interface with the DSP                                  *
*                                                                                                  *
* 0.85 14.12.2000 Super Jukebox v3.0                                                               *
*                                                           Copyright (C)2001 Alpha-II Productions *
***************************************************************************************************)

// Include SNESAPU_Types

type
  p_v0  = ^v0;
  v0	= Pointer;

  p_b8  = ^b8;
  b8	= boolean;

  p_u8  = ^u8;
  u8	= byte;
  p_u16 = ^u16;
  u16	= word;
  p_u32 = ^u32;
  u32	= dword;
  p_u64 = ^u64;
  u64   = int64;

  p_s8  = ^s8;
  s8	= shortint;
  p_s16 = ^s16;
  s16	= Smallint;
  p_s32 = ^s32;
  s32	= longint;
  p_s64 = ^s64;
  s64	= int64;

  p_f32 = ^f32;
  f32	= Single;
  p_f64 = ^f64;
  f64	= double;
  p_f80 = ^f80;
  f80	= Extended;

//**************************************************************************************************
// DSP

//Build options bits 15-8 (see DSP.Inc) --------
const
  SA_VMETERM	= $100;	//Volume metering on main output (for APR)
  SA_VMETERC	= $200;	//Volume metering on voices (for visualization)

//CPU capability -------------------------------
  CPU_MMX	= 1;	//Multi-Media eXtensions
  CPU_3DNOW	= 2;	//3DNow! extensions
  CPU_SSE	= 4;	//Streaming SIMD Extensions

//Mixing routines (see DSP.ASM for details of each routine)
  MIX_NONE	= 0;	//No mixing
  MIX_80386	= 1;	//80386
  MIX_MMX	= 2;	//MMX
  MIX_3DNOW	= 3;	//AMD 3DNow!
  MIX_SSE	= 4;	//Intel SSE

//Interpolation routines -----------------------
  INT_NONE	= 0;	//None
  INT_LINEAR	= 1;	//Linear
  INT_CUBIC	= 2;	//Cubic
  INT_GAUSS	= 3;	//Gaussian

//DSP options ----------------------------------
  DSP_LOW	= 1;	//Low-pass filter
  DSP_OLDSMP	= 2;	//Old ADPCM sample decompression routine
  DSP_SURND	= 4;	//"Surround" sound
  DSP_REVERSE	= 8;	//Reverse stereo samples

//DSP Pitch ----------------------------------
  DSPPitch_Normal	= 0;
  DSPPitch_OldSB	= 1;
  DSPPitch_OldZSNES	= 2;
  DSPPitchInt_Normal	= 32000;
  DSPPitchInt_OldSB	= 32458;
  DSPPitchInt_OldZSNES	= 32768;


//**************************************************************************************************
//Public Variables

//DSP registers --------------------------------
type
  p_DSPVoice = ^DSPVoice;
  DSPVoice = record
	volL	: s8;	// $00 Volume Left  (vol<0 ReverseWaveForm)
	volR	: s8;	// $01 Volume Right (vol<0 ReverseWaveForm)
	pitch	: u16;	// $02 Pitch (rate/32000) (3.11) 14bits
	wave	: u8;	// $04 Wave form being played back
	adsr	: array[0..1] of u8;	// $05 Envelope rates for attack, decay, and sustain
	gain	: u8;	// $07 Envelope gain (if not using ADSR)
	envx	: s8;	// $08 Current envelope height (.7)
	outx	: s8;	// $09 Current sample being output (-.7)
	__r	: array[0..5] of s8; // $0a-$0f etc
  end;

  p_DSPGlobal = ^DSPGlobal;
  DSPGlobal = record
	__r0	: array[$00..$0b] of s8; // $00-$0b voice
	mvolL	: s8;	// $0c Main Volume Left (-.7)
	efb	: s8;	// $0d Echo Feedback (-.7)
        __r01   : array[$00..$01] of s8; // $0e-$0f unuse
	__r1	: array[$00..$0b] of s8; // $10-$1b voice
	mvolR	: s8;	// $1c Main Volume Right (-.7)
	unuse	: s8;	// $1d unuse
        __r11   : array[$00..$01] of s8; // $1e-$1f unuse
	__r2	: array[$00..$0b] of s8; // $20-$2b voice
	evolL	: s8;	// $2c Echo Volume Left (-.7)
	pmon	: u8;	// $2d Pitch Modulation on/off for each voice
        __r21   : array[$00..$01] of s8; // $2e-$2f unuse
	__r3	: array[$00..$0b] of s8; // $30-$3b voice
	evolR	: s8;	// $3c Echo Volume Right (-.7)
	non	: u8;	// $3d Noise output on/off for each voice
        __r31   : array[$00..$01] of s8; // $3e-$3f unuse
	__r4	: array[$00..$0b] of s8; // $40-$4b voice
	kon	: u8;	// $4c Key On for each voice
	eon	: u8;	// $4d Echo on/off for each voice
        __r41   : array[$00..$01] of s8; // $4e-$4f unuse
	__r5	: array[$00..$0b] of s8; // $50-$5b voice
	koff	: u8;	// $5c Key Off for each voice (instantiates release mode)
	dir	: u8;	// $5d Page containing source directory (wave table offsets)
        __r51   : array[$00..$01] of s8; // $5e-$5f unuse
	__r6	: array[$00..$0b] of s8; // $60-$6b voice
	flg	: u8;	// $6c DSP flags and noise frequency
	esa	: u8;	// $6d Starting page used to store echo waveform
        __r61   : array[$00..$01] of s8; // $6e-$6f unuse
	__r7	: array[$00..$0b] of s8; // $70-$7b voice
	endx	: u8;	// $7c Waveform has ended
	edel	: u8;	// $7d Echo Delay in ms >> 4
        __r71   : array[$00..$01] of s8; // $7e-$7f unuse
  end;

  p_DSPFIR = ^DSPFIR;
  DSPFIR = record
	__r1    : array[$00..$0e] of s8; // $00-$0e etc
	c	: s8;	// $0f Filter coefficient
  end;

  p_DSPRAM = ^DSPRAM;
  DSPRAM = record
	case Integer of
        0: (
          DirectDSP: array[0..127] of s8;
          DirectUnused: array[0..63] of s8;
          DirectExtra: array[0..63] of s8;
        );
	1: (
	  voice	: array[0..7] of DSPVoice;
	);
	2: (
	  global: DSPGlobal;
	);
	3: (
	  fir	: array[0..7] of DSPFIR;
	);
  end;

  p_Voice = ^aVoice;
  Voice = record
//Waveform --------18
	sp1	: s32;	//Last sample decompressed (prev1)
	sp2	: s32;	//Second to last sample (prev2)
	bCur	: Pointer;	//-> current block
	bLoop	: Pointer;	//-> loop start block
	bHdr	: u8;	//Block Header for current block
	mFlg	: u8;	//Mixing options (see MixO)
//Envelope --------22
	eMode	: u8;	//Current mode (see EnvM)
	eRIdx	: u8;	//Index in RateTab (0-31)
	eRate	: u32;	//Rate of envelope adjustment (16.16)
	eDec	: u32;	//Rate Decimal (.16)
	eVal	: u32;	//Current envelope value
	eAdj	: s32;	//Amount to adjust envelope height
	eDest	: u32;	//Envelope Destination (16.16)
//Visualization ---08
	vMaxL	: s32;	//Maximum absolute sample output
	vMaxR	: s32;
//Samples ---------60
	__r1	: array[0..1] of u32;	//reserved
	sBufP	: array[0..3] of s16;	//Last 4 samples from previous block (needed for inter.)
	sBuf	: array[0..15] of s16;	//32 bytes for decompressed sample blocks
	sIdx	: p_s16;	//-> current sample in sBuf
	__r2	: u32;	//reserved
//Mixing ----------20
	mChnL	: s32;	//Channel Volume (-24.7)
	mChnR	: s32;	// "  "
	mRate	: u32;	//Pitch Rate after modulation (16.16)
	mDec	: u32;	//Pitch Decimal (.16) (used as delta for interpolation)
	mOrgP	: u32;	//Pitch rate converted from the DSP (16.16)
	mOut	: s32;	//Last sample output before chn vol (used for pitch mod)
  end;
  aVoice= array[0..7] of Voice;

//**************************************************************************************************
//External Functions - See other header files for exported function descriptions

type
  p_SPCFlags = ^SPCFlags;
  SPCFlags = byte;

type
  DebugSPC =  function (var pc: u8; ya: u16; x: u8; psw: SPCFlags; var sp: u8): u32; cdecl;

type
  TSNESReg=record
    pc: u16;
    a: u8;
    y: u8;
    x: u8;
    psw: u8;
    sp: u8;
    reserve: array[0..2] of byte;
  end;
  TpSNESData=record
    pAPURAM: Pointer;
    pExtraRAM: p_u8;
    pSPCOut: p_u8;
    pT64Cnt: p_u32;
    pDSP: p_DSPRAM;
    pMix: p_Voice;
    pVMMaxL: p_u32;
    pVMMaxR: p_u32;
  end;
  TID666Text=record
    SongLenSec:dword; // $00A9
    FadeoutLenMSec:dword; // $00AC
    ArtistName:string; // $00B1
    MuteChBits:byte; // $00D1
    UseEmulator:byte; // $00D2
  end;
  TID666Binary=record
    SongLenSec:dword; // $00A9
    FadeoutLenMSec:dword; // $00AC
    ArtistName:string; // $00B0
    MuteChBits:byte; // $00D0
    UseEmulator:byte; // $00D1
  end;
  TID666=record
    SongTitle:string; // $002E
    GameTitle:string; // $004E
    DumperName:string; // $006E
    Comments:string; // $007E
    SongLenSec:dword;
    FadeoutLenMSec:dword;
    ArtistName:string;
    MuteChBits:byte;
    UseEmulator:byte;
  end;
  TExtID666=record
    Enabled:boolean;
    ID01_SongName:string;
    ID02_GameName:string;
    ID03_ArtistName:string;
    ID04_DumperName:string;
    ID05_DateSongDumped:dword;
    ID06_EmulatorUsed:byte;
    ID07_Comments:string;
    ID10_OfficialSoundtrackTitle:string;
    ID11_OSTdisc:string;
    ID12_OSTtrack:word;
    ID13_PublisherName:string;
    ID14_CopyrightYear:word;
    ID30_IntroductionLength:dword;
    ID31_LoopLength:dword;
    ID32_EndLength:dword;
    ID33_FadeLength:dword;
    ID34_MutedChBits:byte;
  end;

const SPCInfoOK=0;
const SPCInfoInvalidHeader=1;
const SPCInfoInvalidMinorVersion=3; // v0.10/v0.20/v0.30のみ対応

type
  TSPCInfo=record
    HeaderID:string;
    VersionMinor:byte;
    TotalLenMSec:dword;
    ID666Exists:boolean;
    ID666TextMode:boolean; // $D2が$00以外ならTrue(ID666Text)
    ID666:TID666;
    ExtID666:TExtID666;
  end;

var
  HSNESAPU:HMODULE;
  SNESAPUInfo:procedure(var pVer,pMin,pOpt:u32); stdcall;
 	LoadSPCFile:procedure(pSPC:Pointer); stdcall;
  GetAPUData:procedure(var pAPURAM: Pointer; var pExtraRAM: p_u8; var pSPCOut: p_u8; var pT64Cnt: p_u32; var pDSP: p_DSPRAM; var pMix:p_Voice; var pVMMaxL: p_u32; var pVMMaxR: p_u32); stdcall;
  ResetAPU:procedure(amp: u32); stdcall;
  FixAPU:procedure(pc: u16; a: u8; y: u8; x: u8; psw: u8; sp: u8); stdcall;
  SetAPUOpt:procedure(mix: u32; chn: u32; smp: u32; rate: u32; inter: u32; opts: u32); stdcall;
  SetAPUSmpClk:procedure(speed: u32); stdcall;
  SetAPULength:function(song: u32; fade: u32): u32; stdcall;
  EmuAPU:function(pBuf: Pointer; len: u32; t: b8): Pointer; stdcall;
  SeekAPU:procedure(time: u32; fast: b8); stdcall;
  SetSPCDbg:procedure(pTrace: DebugSPC; opts: u32); stdcall;
//  SPCIn:procedure(port: u32; val: u32); stdcall;
//  SPCInW:procedure(port: u32; val: u32); stdcall;
  EmuSPC:function(cyc: u32): s32; stdcall;
  GetProcInfo:function : u32; stdcall;
//  DSPIn:procedure ; stdcall;
  SetDSPPitch:procedure(base: u32); stdcall;
//  SetDSPAmp:procedure(amp: u32); stdcall;
  SetDSPStereo:procedure(sep: u32); stdcall;
  SetDSPEFBCT:procedure(leak: s32); stdcall;
  EmuDSP:function(pBuf: Pointer; size: s32): Pointer; stdcall;
  VMax2dB:procedure(pList: Pointer; fp: b8); stdcall;

function  SNESAPU_LoadDLL:boolean;
procedure SNESAPU_FreeDLL;
procedure SNESAPU_Init;
function  SNESAPU_GetVersion:string;
function  SNESAPU_GetSPCIDBuf(buf:PByteArray;bufcount:integer):string;
function  SNESAPU_LoadSPC(Filename:string;var SPCInfo:TSPCInfo;UseID666Len:boolean;DefSongLen,DefFadeoutLen:dword):boolean;
function  SNESAPU_LoadSPCBuf(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo;UseID666Len:boolean;DefSongLen,DefFadeoutLen:dword):boolean;
function  SNESAPU_LoadSPC010(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):boolean;
function  SNESAPU_LoadSPCv010(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):boolean;
function  SNESAPU_LoadSPCv020(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):boolean;
function  SNESAPU_LoadSPCv030(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):boolean;
function  SNESAPU_LoadSPCInfo(Filename:string;var SPCInfo:TSPCInfo):integer;
function  SNESAPU_LoadSPCInfoBuf(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):integer;
function  SNESAPU_LoadSPCInfo010(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):integer;
function  SNESAPU_LoadSPCInfov010(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):integer;
function  SNESAPU_LoadSPCInfov020(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):integer;
function  SNESAPU_LoadSPCInfov030(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):integer;
function  SNESAPU_MSecToAPUClock(msec:dword):u32;
function  SNESAPU_SecToAPUClock(sec:dword):u32;

var
  SNESReg:TSNESReg;
  pSNESData:TpSNESData;

implementation

function SNESAPU_LoadDLL:boolean;
begin
  Result:=False;

  HSNESAPU:=LoadLibrary('SNESAPU.DLL');
  if HSNESAPU=0 then exit;

  SNESAPUInfo:=GetProcAddress(HSNESAPU,'SNESAPUInfo');
  GetAPUData:=GetProcAddress(HSNESAPU,'GetAPUData');
  LoadSPCFile:=GetProcAddress(HSNESAPU,'LoadSPCFile');
  ResetAPU:=GetProcAddress(HSNESAPU,'ResetAPU');
  FixAPU:=GetProcAddress(HSNESAPU,'FixAPU');
  SetAPUOpt:=GetProcAddress(HSNESAPU,'SetAPUOpt');
  SetAPUSmpClk:=GetProcAddress(HSNESAPU,'SetAPUSmpClk');
  SetAPULength:=GetProcAddress(HSNESAPU,'SetAPULength');
  EmuAPU:=GetProcAddress(HSNESAPU,'EmuAPU');
  SeekAPU:=GetProcAddress(HSNESAPU,'SeekAPU');
  SetSPCDbg:=GetProcAddress(HSNESAPU,'SetSPCDbg');
//  SPCIn:=GetProcAddress(HSNESAPU,'SPCIn');
//  SPCInW:=GetProcAddress(HSNESAPU,'SPCInW');
  EmuSPC:=GetProcAddress(HSNESAPU,'EmuSPC');
  GetProcInfo:=GetProcAddress(HSNESAPU,'GetProcInfo');
//  DSPIn:=GetProcAddress(HSNESAPU,'DSPIn');
  SetDSPPitch:=GetProcAddress(HSNESAPU,'SetDSPPitch');
//  SetDSPAmp:=GetProcAddress(HSNESAPU,'SetDSPAmp');
  SetDSPStereo:=GetProcAddress(HSNESAPU,'SetDSPStereo');
  SetDSPEFBCT:=GetProcAddress(HSNESAPU,'SetDSPEFBCT');
  EmuDSP:=GetProcAddress(HSNESAPU,'EmuDSP');
  VMax2dB:=GetProcAddress(HSNESAPU,'VMax2dB');

  if addr(SNESAPUInfo)=nil then exit;
  if addr(GetAPUData)=nil then exit;
  if addr(LoadSPCFile)=nil then exit;
  if addr(ResetAPU)=nil then exit;
  if addr(FixAPU)=nil then exit;
  if addr(SetAPUOpt)=nil then exit;
  if addr(SetAPUSmpClk)=nil then exit;
  if addr(SetAPULength)=nil then exit;
  if addr(EmuAPU)=nil then exit;
  if addr(SeekAPU)=nil then exit;
  if addr(SetSPCDbg)=nil then exit;
//  if addr(SPCIn)=nil then exit;
//  if addr(SPCInW)=nil then exit;
  if addr(EmuSPC)=nil then exit;
  if addr(GetProcInfo)=nil then exit;
//  if addr(DSPIn)=nil then exit;
  if addr(SetDSPPitch)=nil then exit;
//  if addr(SetDSPAmp)=nil then exit;
  if addr(SetDSPStereo)=nil then exit;
  if addr(SetDSPEFBCT)=nil then exit;
  if addr(EmuDSP)=nil then exit;
  if addr(VMax2dB)=nil then exit;

  Result:=True;
end;

procedure SNESAPU_FreeDLL;
begin
  if HSNESAPU=0 then exit;

  FreeLibrary(HSNESAPU);
end;

procedure SNESAPU_Init;
begin
  ResetAPU(30);

  with pSNESData do begin
    GetAPUData(pAPURAM,pExtraRAM,pSPCOut,pT64Cnt,pDSP,pMix,pVMMaxL,pVMMaxR);
  end;
end;

function SNESAPU_GetVersion:string;
var
  Ver,Min,Opt:u32;
begin
  SNESAPUInfo(Ver,Min,Opt);
  Result:='SNESAPU.DLL Ver'+format('%1.2f',[((Ver shr 16) and $ff)+(((Ver shr 8) and $ff)/100)]);
  if (Ver and $ff)<>$00 then Result:=Result+char(Ver and $ff);
  Result:=Result+' MinVer.$'+inttohex(Min,4);
end;

function SNESAPU_GetSPCIDBuf(buf:PByteArray;bufcount:integer):string;
var
  bufpos:integer;
begin
  if bufcount<=32 then begin
    Result:='';
  end;

  Result:=StringOfChar(' ',33);
  for bufpos:=0 to 32 do begin
    Result[bufpos+1]:=char(buf[bufpos]);
  end;
  if Result[33]=char($00) then Result[33]:=' ';
end;

function SNESAPU_LoadSPC(Filename:string;var SPCInfo:TSPCInfo;UseID666Len:boolean;DefSongLen,DefFadeoutLen:dword):boolean;
var
  fs:TFileStream;
  bufcount:integer;
  buf:array of byte;
begin
  if FileExists(Filename)=False then begin
    Result:=False;
    exit;
  end;

  fs:=TFileStream.Create(Filename,fmOpenRead or fmShareDenyWrite);
  bufcount:=fs.Size;
  SetLength(buf,bufcount);
  fs.ReadBuffer(buf[0],bufcount);
  fs.Free;

  Result:=SNESAPU_LoadSPCBuf(PByteArray(buf),bufcount,SPCInfo,UseID666Len,DefSongLen,DefFadeoutLen);
end;

function SNESAPU_LoadSPCBuf(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo;UseID666Len:boolean;DefSongLen,DefFadeoutLen:dword):boolean;
var
  res:integer;
begin
  ResetAPU(30);

  res:=SNESAPU_LoadSPCInfoBuf(buf,bufcount,SPCInfo);
  if res<>SPCInfoOK then begin
    Result:=False;
    exit;
  end;

  Result:=False;
  SPCInfo.HeaderID:=SNESAPU_GetSPCIDBuf(buf,bufcount);
  if SPCInfo.HeaderID='SNES-SPC700 Sound File Data 0.10 ' then Result:=SNESAPU_LoadSPC010(buf,bufcount,SPCInfo);
  if SPCInfo.HeaderID='SNES-SPC700 Sound File Data v0.10' then Result:=SNESAPU_LoadSPCv010(buf,bufcount,SPCInfo);
  if SPCInfo.HeaderID='SNES-SPC700 Sound File Data v0.20' then Result:=SNESAPU_LoadSPCv020(buf,bufcount,SPCInfo);
  if SPCInfo.HeaderID='SNES-SPC700 Sound File Data v0.30' then Result:=SNESAPU_LoadSPCv030(buf,bufcount,SPCInfo);

  with SNESReg do begin
    FixAPU(pc,a,y,x,psw,sp);
  end;

  with SPCInfo.ID666 do begin
    if (UseID666Len=False) or (SPCInfo.ID666Exists=False) or (SPCInfo.ID666.SongLenSec=0) then begin
      SongLenSec:=DefSongLen;
      FadeoutLenMSec:=DefFadeoutLen*1000;
    end;
    SetAPULength(SNESAPU_SecToAPUClock(SongLenSec),SNESAPU_MSecToAPUClock(FadeoutLenMSec));
    SPCInfo.TotalLenMSec:=(SongLenSec*1000)+FadeoutLenMSec;
  end;
end;

function SNESAPU_LoadSPC010(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):boolean;
begin
  // データ部はVer0.30と同じと仮定する。
  Result:=SNESAPU_LoadSPCv030(buf,bufcount,SPCInfo);
end;

function SNESAPU_LoadSPCv010(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):boolean;
begin
  // データ部はVer0.30と同じと仮定する。
  Result:=SNESAPU_LoadSPCv030(buf,bufcount,SPCInfo);
end;

function SNESAPU_LoadSPCv020(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):boolean;
begin
  // データ部はVer0.30と同じと仮定する。
  Result:=SNESAPU_LoadSPCv030(buf,bufcount,SPCInfo);
end;

function SNESAPU_LoadSPCv030(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):boolean;
var
  bufpos:integer;
  t0,t1:byte;
  function GetBufByte:byte;
  begin
    Result:=buf[bufpos];
    inc(bufpos);
  end;
  procedure GetBufMem(p:Pointer;bufpos:integer;len:integer);
  var
    cnt:integer;
  begin
    if (bufpos+len)<=bufcount then begin
//      MoveMemory(p,@buf[bufpos],len);
      MoveMemory(p,ptr(dword(buf)+dword(bufpos)),len);
      end else begin
      if bufpos<bufcount then begin
        for cnt:=0 to len-1 do begin
          if (bufpos+cnt)<bufcount then begin
            PByteArray(p)[cnt]:=buf[bufpos+cnt];
            end else begin
            PByteArray(p)[cnt]:=$00;
          end;
        end;
        end else begin
        ZeroMemory(p,len);
      end;
    end;
  end;
begin
  with SNESReg do begin
    bufpos:=$000025;
    t0:=GetBufByte;
    t1:=GetBufByte;
    pc:=(Cardinal(t1) shl 8)+Cardinal(t0);
    a:=GetBufByte;
    x:=GetBufByte;
    y:=GetBufByte;
    psw:=GetBufByte;
    sp:=GetBufByte;
    reserve[0]:=GetBufByte;
    reserve[1]:=GetBufByte;
    reserve[2]:=GetBufByte;
  end;

  with pSNESData do begin
    GetBufMem(addr(pAPURAM^),$000100,$ffff);
    GetBufMem(addr(pDSP.DirectDSP),$010100,$0080);
    GetBufMem(addr(pExtraRAM^),$0101C0,$0040);
    GetBufMem(addr(pDSP.DirectUnused),$010180,$0040);
    GetBufMem(addr(pDSP.DirectExtra),$0101C0,$0040);
  end;

  Result:=True;
end;

function SNESAPU_LoadSPCInfo(Filename:string;var SPCInfo:TSPCInfo):integer;
var
  fs:TFileStream;
  bufcount:integer;
  buf:array of byte;
begin
  if FileExists(Filename)=False then begin
    Result:=SPCInfoInvalidHeader;
    exit;
  end;

  fs:=TFileStream.Create(Filename,fmOpenRead or fmShareDenyWrite);
  bufcount:=fs.Size;
  SetLength(buf,bufcount);
  fs.ReadBuffer(buf[0],bufcount);
  fs.Free;

  Result:=SNESAPU_LoadSPCInfoBuf(PByteArray(buf),bufcount,SPCInfo);
end;

function SNESAPU_LoadSPCInfoBuf(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):integer;
begin
  with SPCInfo.ID666 do begin
    SongTitle:='';
    GameTitle:='';
    DumperName:='';
    Comments:='';
    SongLenSec:=0;
    FadeoutLenMSec:=0;
    ArtistName:='';
    MuteChBits:=$00;
    UseEmulator:=$00;
  end;

  Result:=SPCInfoInvalidMinorVersion;
  SPCInfo.HeaderID:=SNESAPU_GetSPCIDBuf(buf,bufcount);
  if SPCInfo.HeaderID='SNES-SPC700 Sound File Data 0.10 ' then Result:=SNESAPU_LoadSPCInfo010(buf,bufcount,SPCInfo);
  if SPCInfo.HeaderID='SNES-SPC700 Sound File Data v0.10' then Result:=SNESAPU_LoadSPCInfov010(buf,bufcount,SPCInfo);
  if SPCInfo.HeaderID='SNES-SPC700 Sound File Data v0.20' then Result:=SNESAPU_LoadSPCInfov020(buf,bufcount,SPCInfo);
  if SPCInfo.HeaderID='SNES-SPC700 Sound File Data v0.30' then Result:=SNESAPU_LoadSPCInfov030(buf,bufcount,SPCInfo);
end;

function SNESAPU_LoadSPCInfo010(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):integer;
var
  bufpos:integer;
  t1,t2,t3,t4:byte;
  function GetBufByte:byte;
  begin
    Result:=buf[bufpos];
    inc(bufpos);
  end;
  procedure GetBufString(var msg:string);
  var
    cnt:integer;
    b:byte;
  begin
    msg:=StringOfChar(' ',33);
    cnt:=0;
    b:=GetBufByte;
    while (cnt<>32) and (b<>$00) do begin
      msg[cnt+1]:=char(b);
      inc(cnt);
      b:=GetBufByte;
    end;
    SetLength(msg,cnt);
  end;
begin
  bufpos:=$0021;
  t1:=GetBufByte;
  t2:=GetBufByte;
  t3:=GetBufByte;
  t4:=GetBufByte;
  if (t1+t2+t3+t4)<>$00 then begin
    Result:=SPCInfoInvalidHeader;
    exit;
  end;

  SPCInfo.ID666Exists:=True;
  
  SPCInfo.VersionMinor:=$00;

  with SPCInfo.ID666 do begin
    bufpos:=$002E;
    GetBufString(SongTitle);
    bufpos:=$004E;
    GetBufString(GameTitle);
    bufpos:=$006E;
    GetBufString(DumperName);
    bufpos:=$007E;
    GetBufString(Comments);
  end;

  Result:=SPCInfoOK;
end;

function SNESAPU_LoadSPCInfov010(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):integer;
var
  bufpos:integer;
  t1,t2:byte;
//  t,t1,t2,t3,t4:byte;
//  sec:string;
  function GetBufByte:byte;
  begin
    Result:=buf[bufpos];
    inc(bufpos);
  end;
  procedure GetBufString(var msg:string);
  var
    cnt:integer;
    b:byte;
  begin
    msg:=StringOfChar(' ',33);
    cnt:=0;
    b:=GetBufByte;
    while (cnt<>32) and (b<>$00) do begin
      msg[cnt+1]:=char(b);
      inc(cnt);
      b:=GetBufByte;
    end;
    SetLength(msg,cnt);
  end;
begin
  bufpos:=$0021;
  t1:=GetBufByte;
  t2:=GetBufByte;
  if (t1<>$1a) or (t2<>$1a) then begin
    Result:=SPCInfoInvalidHeader;
    exit;
  end;

{
  bufpos:=$0023;
  if GetBufByte=$1b then begin // no ID666tag
    SPCInfo.ID666Exists:=False;
    end else begin
    SPCInfo.ID666Exists:=True;
  end;
}
  SPCInfo.ID666Exists:=False;

  bufpos:=$0024;
  SPCInfo.VersionMinor:=GetBufByte;
  if SPCInfo.VersionMinor<>$0a then begin
    Result:=SPCInfoInvalidHeader; // 最初は厳しく
    exit;
  end;

  Result:=SPCInfoOK;
end;

function SNESAPU_LoadSPCInfov020(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):integer;
var
  bufpos:integer;
  t1,t2:byte;
//  t,t1,t2,t3,t4:byte;
//  sec:string;
  function GetBufByte:byte;
  begin
    Result:=buf[bufpos];
    inc(bufpos);
  end;
  procedure GetBufString(var msg:string);
  var
    cnt:integer;
    b:byte;
  begin
    msg:=StringOfChar(' ',33);
    cnt:=0;
    b:=GetBufByte;
    while (cnt<>32) and (b<>$00) do begin
      msg[cnt+1]:=char(b);
      inc(cnt);
      b:=GetBufByte;
    end;
    SetLength(msg,cnt);
  end;
begin
  bufpos:=$0021;
  t1:=GetBufByte;
  t2:=GetBufByte;
  if (t1<>$1a) or (t2<>$1a) then begin
    Result:=SPCInfoInvalidHeader;
    exit;
  end;

  bufpos:=$0023;
  if GetBufByte=$1b then begin // no ID666tag
    SPCInfo.ID666Exists:=False;
    end else begin
    SPCInfo.ID666Exists:=True;
    Result:=SPCInfoInvalidHeader; // タイトル解析しないとね。
    exit;
  end;

  bufpos:=$0024;
  SPCInfo.VersionMinor:=GetBufByte;
  if SPCInfo.VersionMinor<>$0a then begin
    Result:=SPCInfoInvalidHeader; // 最初は厳しく
    exit;
  end;

  Result:=SPCInfoOK;
end;

function SNESAPU_LoadSPCInfov030(buf:PByteArray;bufcount:integer;var SPCInfo:TSPCInfo):integer;
var
  bufpos:integer;
  t,t1,t2,t3,t4:byte;
  sec:string;
  function GetBufByte:byte;
  begin
    Result:=buf[bufpos];
    inc(bufpos);
  end;
  procedure GetBufString(var msg:string);
  var
    cnt:integer;
    b:byte;
  begin
    msg:=StringOfChar(' ',33);
    cnt:=0;
    b:=GetBufByte;
    while (cnt<>32) and (b<>$00) do begin
      msg[cnt+1]:=char(b);
      inc(cnt);
      b:=GetBufByte;
    end;
    SetLength(msg,cnt);
  end;
begin
  bufpos:=$0021;
  t1:=GetBufByte;
  t2:=GetBufByte;
  if (t1<>$1a) or (t2<>$1a) then begin
    Result:=SPCInfoInvalidHeader;
    exit;
  end;

  bufpos:=$0023;
  if GetBufByte<>$1a then begin // no ID666tag
    SPCInfo.ID666Exists:=False;
    end else begin
    SPCInfo.ID666Exists:=True;
  end;

  bufpos:=$0024;
  SPCInfo.VersionMinor:=GetBufByte;
{
  if (SPCInfo.VersionMinor<>$30) and (SPCInfo.VersionMinor<>30) then begin
    Result:=SPCInfoInvalidHeader;
    exit;
  end;
}

  if SPCInfo.ID666Exists=True then begin
    bufpos:=$00D2;
    if GetBufByte<>$00 then
      SPCInfo.ID666TextMode:=True
      else
      SPCInfo.ID666TextMode:=False;

    with SPCInfo.ID666 do begin
      bufpos:=$002E;
      GetBufString(SongTitle);
      bufpos:=$004E;
      GetBufString(GameTitle);
      bufpos:=$006E;
      GetBufString(DumperName);
      bufpos:=$007E;
      GetBufString(Comments);

      if SPCInfo.ID666TextMode=True then begin
        bufpos:=$00A9;
        t:=GetBufByte;
        t1:=GetBufByte;
        t2:=GetBufByte;
        sec:=char(t)+char(t1)+char(t2);
        SongLenSec:=dword(StrToInt64Def(sec,0));

        bufpos:=$00AC;
        t:=GetBufByte;
        t1:=GetBufByte;
        t2:=GetBufByte;
        t3:=GetBufByte;
        t4:=GetBufByte;
        sec:=char(t)+char(t1)+char(t2)+char(t3)+char(t4);
        FadeoutLenMSec:=dword(StrToInt64Def(sec,0));

        bufpos:=$00B1;
        GetBufString(ArtistName);

        bufpos:=$00D1;
        MuteChBits:=GetBufByte;

        bufpos:=$00D2;
        UseEmulator:=StrToIntDef(char(GetBufByte),0);

        end else begin // BinaryMode

        bufpos:=$00A9;
        t:=GetBufByte;
        t1:=GetBufByte;
        t2:=GetBufByte;
        t3:=0;
        SongLenSec:=(Cardinal(t3) shl 24)+(Cardinal(t2) shl 16)+(Cardinal(t1) shl 8)+Cardinal(t);

        bufpos:=$00AC;
        t:=GetBufByte;
        t1:=GetBufByte;
        t2:=GetBufByte;
        t3:=GetBufByte;
        SongLenSec:=(Cardinal(t3) shl 24)+(Cardinal(t2) shl 16)+(Cardinal(t1) shl 8)+Cardinal(t);

        bufpos:=$00B0;
        GetBufString(ArtistName);

        bufpos:=$00D0;
        MuteChBits:=GetBufByte;

        bufpos:=$00D1;
        UseEmulator:=StrToIntDef(char(GetBufByte),0);
      end;
    end;
  end;

  Result:=SPCInfoOK;
end;

function SNESAPU_MSecToAPUClock(msec:dword):u32;
begin
  Result:=msec*(64000 div 1000);
end;

function SNESAPU_SecToAPUClock(sec:dword):u32;
begin
  Result:=sec*64000;
end;

end.
