From d194bf7d765aa5eb39a57892f6e5c4e66eeae5e5 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sat, 31 Aug 2019 21:06:53 +0300 Subject: [PATCH] Sound: OpenAL: libxmp support --- src/engine/e_sound_al.inc | 287 +++++++++++++++++-------- src/engine/e_soundfile.pas | 57 +++-- src/engine/e_soundfile_wav.pas | 102 ++++++--- src/engine/e_soundfile_xmp.pas | 197 +++++++++++++++++ src/game/Doom2DF.lpr | 2 + src/lib/openal/al.pas | 4 +- src/lib/xmp/xmp.pas | 375 +++++++++++++++++++++++++++++++++ 7 files changed, 883 insertions(+), 141 deletions(-) create mode 100644 src/engine/e_soundfile_xmp.pas create mode 100644 src/lib/xmp/xmp.pas diff --git a/src/engine/e_sound_al.inc b/src/engine/e_sound_al.inc index d570129..dfe5919 100644 --- a/src/engine/e_sound_al.inc +++ b/src/engine/e_sound_al.inc @@ -24,9 +24,7 @@ uses type TSoundRec = record - Fmt: TSoundFormat; - Data: Pointer; - DataLen: Integer; + Loader: TSoundLoader; alBuffer: ALuint; isMusic: Boolean; Loops: Boolean; @@ -91,6 +89,7 @@ procedure e_ReleaseSoundSystem(); procedure e_SoundUpdate(); var + e_SoundFormat: TSoundFormat; // desired sound format e_SoundsArray: array of TSoundRec = nil; e_ZeroPosition: array [0..2] of ALfloat; e_ALError: ALenum = 0; @@ -101,17 +100,24 @@ uses g_window, g_options, utils; const - NUM_SOURCES = 250; + NUM_SOURCES = 255; // + 1 stereo + NUM_STREAM_BUFFERS = 8; + STREAM_BUFSIZE = 8192; MUSIC_SOURCE = 0; var + SoundMuted: Boolean = False; + CurStream: DWORD = NO_SOUND_ID; alDevice: PALCdevice = nil; alContext: PALCcontext = nil; - // sources for one-shot sounds - // TBasicSounds have a built in source that lives and dies with them - alSources: array of ALuint; - alOwners: array of TBasicSound; - SoundMuted: Boolean = False; + // sources for everything + alSources: array [0..NUM_SOURCES] of ALuint; + // last TBasicSound that has played on each source + alOwners: array [0..NUM_SOURCES] of TBasicSound; + // buffers for the music stream + alStreamBufs: array [0..NUM_STREAM_BUFFERS-1] of ALuint; + alStreamData: array [0..STREAM_BUFSIZE-1] of Byte; + alStreamAvail: Integer = NUM_STREAM_BUFFERS; function CheckALError(): Boolean; begin @@ -136,8 +142,8 @@ end; function e_InitSoundSystem(NoOutput: Boolean = False): Boolean; var alExt, alRend, alVendor, alVer: string; - DevName: string = ''; - alAttrs: array [0..4] of ALCint = ( + WantDev: string = ''; + WantAttrs: array [0..4] of ALCint = ( ALC_STEREO_SOURCES, 1, ALC_MONO_SOURCES, NUM_SOURCES, 0 @@ -145,21 +151,21 @@ var begin Result := False; - DevName := alcGetString(nil, ALC_DEVICE_SPECIFIER); - e_LogWritefln('AL: available devices: %s', [DevName]); + WantDev := alcGetString(nil, ALC_DEVICE_SPECIFIER); + e_LogWritefln('AL: available devices: %s', [WantDev]); // TODO: open a dummy device when NoOutput is true or something - DevName := alcGetString(nil, ALC_DEFAULT_DEVICE_SPECIFIER); - e_LogWritefln('AL: trying to open device %s', [DevName]); + WantDev := alcGetString(nil, ALC_DEFAULT_DEVICE_SPECIFIER); + e_LogWritefln('AL: trying to open device %s', [WantDev]); - alDevice := alcOpenDevice(PChar(DevName)); + alDevice := alcOpenDevice(PChar(WantDev)); if alDevice = nil then begin - e_LogWritefln('AL: ERROR: could not open device %s: %s', [DevName, GetALError()]); + e_LogWritefln('AL: ERROR: could not open device %s: %s', [WantDev, GetALError()]); exit; end; - alContext := alcCreateContext(alDevice, alAttrs); + alContext := alcCreateContext(alDevice, WantAttrs); if alContext = nil then begin e_LogWritefln('AL: ERROR: could not create context: %s', [GetALError()]); @@ -170,29 +176,43 @@ begin alcMakeContextCurrent(alContext); + // TODO: actually parse these from alc attributes or something + e_SoundFormat.SampleRate := 48000; + e_SoundFormat.SampleBits := 16; + e_SoundFormat.Channels := 2; + alVendor := alGetString(AL_VENDOR); alRend := alGetString(AL_RENDERER); alVer := alGetString(AL_VERSION); alExt := alGetString(AL_EXTENSIONS); e_LogWriteln('AL INFO:'); - e_LogWriteln(' Version: ' + alVer); - e_LogWriteln(' Vendor: ' + alVendor); - e_LogWriteln(' Renderer: ' + alRend); - e_LogWriteln(' Device: ' + DevName); + e_LogWriteln(' Version: ' + alVer); + e_LogWriteln(' Vendor: ' + alVendor); + e_LogWriteln(' Renderer: ' + alRend); + e_LogWriteln(' Device: ' + WantDev); + e_LogWriteln(' Sample rate: ' + IntToStr(e_SoundFormat.SampleRate)); e_LogWriteln(' Extensions:'); e_LogWriteln(' ' + alExt); - SetLength(alSources, NUM_SOURCES + 1); // 0 is the music source - SetLength(alOwners, NUM_SOURCES + 1); // to avoid destructive operations on sources - ZeroMemory(@alSources[0], sizeof(alSources[0]) * Length(alSources)); - ZeroMemory(@alOwners[0], sizeof(alOwners[0]) * Length(alOwners)); + ZeroMemory(@alSources[0], sizeof(alSources)); + ZeroMemory(@alOwners[0], sizeof(alOwners)); + ZeroMemory(@alStreamBufs[0], sizeof(alStreamBufs)); + ZeroMemory(@alStreamData[0], sizeof(alStreamData)); + CurStream := NO_SOUND_ID; alGetError(); // reset the goddamn error state alGenSources(1, @alSources[0]); // generate the music source if CheckALError() then e_LogWriteln('AL: ERROR: alGenSources() for music failed: ' + GetALError()); + alStreamAvail := 0; + alGenBuffers(NUM_STREAM_BUFFERS, @alStreamBufs[0]); // generate buffers for the music stream + if CheckALError() then + e_LogWriteln('AL: ERROR: alGenSources() for music failed: ' + GetALError()) + else + alStreamAvail := NUM_STREAM_BUFFERS; + Result := True; end; @@ -203,7 +223,7 @@ var begin if e_SoundsArray <> nil then for i := 0 to High(e_SoundsArray) do - if e_SoundsArray[i].alBuffer = 0 then + if (e_SoundsArray[i].alBuffer = 0) and (e_SoundsArray[i].Loader = nil) then begin Result := i; Exit; @@ -248,14 +268,15 @@ function e_LoadSound(FileName: String; var ID: DWORD; isMusic: Boolean; ForceNoL var find_id: DWORD; Loader: TSoundLoader; + OutData: Pointer; + OutLen: LongWord; begin ID := NO_SOUND_ID; Result := False; find_id := FindESound(); - e_SoundsArray[find_id].Data := nil; - e_SoundsArray[find_id].DataLen := 0; + e_SoundsArray[find_id].Loader := nil; e_SoundsArray[find_id].isMusic := isMusic; e_SoundsArray[find_id].Loops := isMusic and not ForceNoLoop; e_SoundsArray[find_id].nRefs := 0; @@ -267,8 +288,7 @@ begin exit; end; - e_SoundsArray[find_id].Data := Loader.Load(FileName, e_SoundsArray[find_id].DataLen, e_SoundsArray[find_id].Fmt); - if e_SoundsArray[find_id].Data = nil then + if not Loader.Load(FileName, e_SoundsArray[find_id].isMusic) then begin e_LogWritefln('Could not load sound `%s`', [FileName]); exit; @@ -276,32 +296,41 @@ begin alGetError(); // reset error state, god damn it - alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer)); - if CheckALError() then + if not Loader.Streaming then begin - e_LogWritefln('Could not create AL buffer for `%s`: %s', [FileName, GetALError()]); - Loader.Free(e_SoundsArray[find_id].Data); - exit; - end; + alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer)); + if CheckALError() then + begin + e_LogWritefln('Could not create AL buffer for `%s`: %s', [FileName, GetALError()]); + Loader.Free(); + exit; + end; - alBufferData( - e_SoundsArray[find_id].alBuffer, - GetALSoundFormat(e_SoundsArray[find_id].Fmt), - e_SoundsArray[find_id].Data, - e_SoundsArray[find_id].DataLen, - e_SoundsArray[find_id].Fmt.SampleRate - ); + OutLen := Loader.GetAll(OutData); + alBufferData( + e_SoundsArray[find_id].alBuffer, + GetALSoundFormat(Loader.Format), + OutData, + OutLen, + Loader.Format.SampleRate + ); - // don't need this anymore - Loader.Free(e_SoundsArray[find_id].Data); - e_SoundsArray[find_id].Data := nil; - e_SoundsArray[find_id].DataLen := 0; + // don't need this anymore + Loader.Free(); + Loader := nil; - if CheckALError() then + if CheckALError() then + begin + e_LogWriteln('AL: what the fuck: ' + GetALError()); + alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer)); + e_SoundsArray[find_id].alBuffer := 0; + exit; + end; + end + else begin - e_LogWriteln('AL: what the fuck: ' + GetALError()); - alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer)); - exit; + e_SoundsArray[find_id].alBuffer := 0; + e_SoundsArray[find_id].Loader := Loader; end; ID := find_id; @@ -312,27 +341,27 @@ function e_LoadSoundMem(pData: Pointer; Length: Integer; var ID: DWORD; isMusic: var find_id: DWORD; Loader: TSoundLoader; + OutData: Pointer; + OutLen: LongWord; begin ID := NO_SOUND_ID; Result := False; find_id := FindESound(); - e_SoundsArray[find_id].Data := nil; - e_SoundsArray[find_id].DataLen := 0; + e_SoundsArray[find_id].Loader := nil; e_SoundsArray[find_id].isMusic := isMusic; e_SoundsArray[find_id].Loops := isMusic and not ForceNoLoop; e_SoundsArray[find_id].nRefs := 0; - Loader := e_GetSoundLoader(pData, Length); + Loader := e_GetSoundLoader(pData, LongWord(Length)); if Loader = nil then begin e_LogWritefln('Could not find loader for sound `%p`', [pData]); exit; end; - e_SoundsArray[find_id].Data := Loader.Load(pData, Length, e_SoundsArray[find_id].DataLen, e_SoundsArray[find_id].Fmt); - if e_SoundsArray[find_id].Data = nil then + if not Loader.Load(pData, LongWord(Length), e_SoundsArray[find_id].isMusic) then begin e_LogWritefln('Could not load sound `%p`', [pData]); exit; @@ -340,32 +369,41 @@ begin alGetError(); // reset error state, god damn it - alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer)); - if CheckALError() then + if not Loader.Streaming then begin - e_LogWritefln('Could not create AL buffer for `%p`: %s', [pData, GetALError()]); - Loader.Free(e_SoundsArray[find_id].Data); - exit; - end; + alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer)); + if CheckALError() then + begin + e_LogWritefln('Could not create AL buffer for `%p`: %s', [pData, GetALError()]); + Loader.Free(); + exit; + end; - alBufferData( - e_SoundsArray[find_id].alBuffer, - GetALSoundFormat(e_SoundsArray[find_id].Fmt), - e_SoundsArray[find_id].Data, - e_SoundsArray[find_id].DataLen, - e_SoundsArray[find_id].Fmt.SampleRate - ); + OutLen := Loader.GetAll(OutData); + alBufferData( + e_SoundsArray[find_id].alBuffer, + GetALSoundFormat(Loader.Format), + OutData, + OutLen, + Loader.Format.SampleRate + ); - // don't need this anymore - Loader.Free(e_SoundsArray[find_id].Data); - e_SoundsArray[find_id].Data := nil; - e_SoundsArray[find_id].DataLen := 0; + // don't need this anymore + Loader.Free(); + Loader := nil; - if CheckALError() then + if CheckALError() then + begin + e_LogWriteln('AL: what the fuck: ' + GetALError()); + alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer)); + e_SoundsArray[find_id].alBuffer := 0; + exit; + end; + end + else begin - e_LogWriteln('AL: what the fuck: ' + GetALError()); - alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer)); - exit; + e_SoundsArray[find_id].alBuffer := 0; + e_SoundsArray[find_id].Loader := Loader; end; ID := find_id; @@ -380,9 +418,11 @@ begin if ID > High(e_SoundsArray) then exit; - if e_SoundsArray[ID].isMusic then + if e_SoundsArray[ID].Loader <> nil then begin - // last source is for music + // first source is for streaming sounds + // it always exists + alOwners[MUSIC_SOURCE] := nil; Result := MUSIC_SOURCE; exit; end; @@ -409,12 +449,29 @@ end; procedure AssignSound(ID: DWORD; Src: ALuint); inline; begin alGetError(); // reset error state - alSourcei(Src, AL_BUFFER, e_SoundsArray[ID].alBuffer); - alSourcei(Src, AL_SOURCE_RELATIVE, AL_TRUE); - if (e_SoundsArray[ID].Loops) then - alSourcei(Src, AL_LOOPING, AL_TRUE) + + if e_SoundsArray[ID].Loader <> nil then + begin + // this is a stream + // reset position + e_SoundsArray[ID].Loader.SetPosition(0); + if CurStream <> ID then // changing streams, stop the thing just in case + alSourceStop(Src); + // this shit is playing now + CurStream := ID; + end else - alSourcei(Src, AL_LOOPING, AL_FALSE); + begin + // this is a full chunk, assign local buffer + alSourcei(Src, AL_BUFFER, e_SoundsArray[ID].alBuffer); + // these can loop + if (e_SoundsArray[ID].Loops) then + alSourcei(Src, AL_LOOPING, AL_TRUE) + else + alSourcei(Src, AL_LOOPING, AL_FALSE); + end; + + alSourcei(Src, AL_SOURCE_RELATIVE, AL_TRUE); end; function e_PlaySound(ID: DWORD): Integer; @@ -480,11 +537,12 @@ begin alDeleteBuffers(1, Addr(e_SoundsArray[ID].alBuffer)); e_SoundsArray[ID].alBuffer := 0; end; - if (e_SoundsArray[ID].Data <> nil) then + if (e_SoundsArray[ID].Loader <> nil) then begin - e_SoundsArray[ID].Fmt.Loader.Free(e_SoundsArray[ID].Data); - e_SoundsArray[ID].Data := nil; - e_SoundsArray[ID].DataLen := 0; + e_SoundsArray[ID].Loader.Free(); + e_SoundsArray[ID].Loader := nil; + if ID = CurStream then + CurStream := NO_SOUND_ID; end; end; @@ -543,6 +601,7 @@ begin e_DeleteSound(i); SetLength(e_SoundsArray, 0); e_SoundsArray := nil; + CurStream := NO_SOUND_ID; end; procedure e_ReleaseSoundSystem(); @@ -557,11 +616,56 @@ begin alContext := nil; end; +procedure UpdateStreamSource(Src: Integer); +var + OutLen: LongWord; + Buf: ALuint; + S: Integer; +begin + if alSources[Src] = 0 then Exit; + + alGetError(); // reset error state + + alGetSourcei(alSources[Src], AL_BUFFERS_PROCESSED, S); + // unqueue processed buffers + if S > 0 then + begin + alSourceUnqueueBuffers(alSources[Src], S, @alStreamBufs[alStreamAvail]); + alStreamAvail := alStreamAvail + S; + end; + + alGetError(); // reset error state + + if (alStreamAvail > 0) and (CurStream <> NO_SOUND_ID) then + begin + // some buffers have freed up, advance stream playback + OutLen := e_SoundsArray[CurStream].Loader.FillBuffer(@alStreamData[0], STREAM_BUFSIZE); + if OutLen = 0 then Exit; // ran out of stream + Buf := alStreamBufs[alStreamAvail-1]; + Dec(alStreamAvail); + // upload + alBufferData( + Buf, + GetALSoundFormat(e_SoundsArray[CurStream].Loader.Format), + @alStreamData[0], + OutLen, + e_SoundsArray[CurStream].Loader.Format.SampleRate + ); + // attach + alSourceQueueBuffers(alSources[Src], 1, @Buf); + // restart if needed + S := GetALSourceState(alSources[Src]); + if (S = AL_STOPPED) or (S = AL_INITIAL) then + alSourcePlay(alSources[Src]); + end; +end; + procedure e_SoundUpdate(); var S: Integer; begin alGetError(); // reset error state + // clear out all stopped sources for S := 1 to High(alSources) do if (alSources[S] <> 0) and (GetALSourceState(alSources[S]) = AL_STOPPED) then @@ -570,6 +674,9 @@ begin alSources[S] := 0; alOwners[S] := nil; end; + + // update the stream sources + UpdateStreamSource(MUSIC_SOURCE); end; { TBasicSound: } @@ -646,6 +753,8 @@ end; procedure TBasicSound.Stop(); begin + if FID = CurStream then + CurStream := NO_SOUND_ID; if InvalidSource() then Exit; GetPosition(); diff --git a/src/engine/e_soundfile.pas b/src/engine/e_soundfile.pas index df4aa45..919715c 100644 --- a/src/engine/e_soundfile.pas +++ b/src/engine/e_soundfile.pas @@ -22,38 +22,49 @@ type TSoundLoader = class; TSoundFormat = record - Loader: TSoundLoader; SampleBits: Integer; SampleRate: Integer; Channels: Integer; end; - // each sound file format has its own loader - // TODO: maybe make TBasicSound contain an instance of its loader - // and add a FetchSamples method or something, for streaming shit + // each sound file format has its own loader factory and loader class, + // each sound has its own loader instance for streaming purposes + TSoundLoader = class + protected + FFormat: TSoundFormat; + FStreaming: Boolean; + public - // can this loader load the sound file in Data? - function CanLoad(Data: Pointer; Len: Integer): Boolean; virtual; abstract; overload; - // can this loader load the sound file at FName? - function CanLoad(FName: string): Boolean; virtual; abstract; overload; - // load from memory - function Load(Data: Pointer; Len: Integer; var OutLen: Integer; var OutFmt: TSoundFormat): Pointer; virtual; abstract; overload; - // load from file - function Load(FName: string; var OutLen: Integer; var OutFmt: TSoundFormat): Pointer; virtual; abstract; overload; - // needed in case memory is allocated in a lib or something - procedure Free(Data: Pointer); virtual; abstract; + function Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean; virtual; abstract; overload; + function Load(FName: string; SStreaming: Boolean): Boolean; virtual; abstract; overload; + + function SetPosition(Pos: LongWord): Boolean; virtual; abstract; + function FillBuffer(Buf: Pointer; Len: LongWord): LongWord; virtual; abstract; + + function GetAll(var OutPtr: Pointer): LongWord; virtual; abstract; + + procedure Free(); virtual; abstract; + + property Format: TSoundFormat read FFormat; + property Streaming: Boolean read FStreaming; + end; + + TSoundLoaderFactory = class + function MatchHeader(Data: Pointer; Len: LongWord): Boolean; virtual; abstract; + function MatchExtension(FName: string): Boolean; virtual; abstract; + function GetLoader(): TSoundLoader; virtual; abstract; end; -function e_GetSoundLoader(Data: Pointer; Len: Integer): TSoundLoader; overload; +function e_GetSoundLoader(Data: Pointer; Len: LongWord): TSoundLoader; overload; function e_GetSoundLoader(FName: string): TSoundLoader; overload; -procedure e_AddSoundLoader(Loader: TSoundLoader); +procedure e_AddSoundLoader(Loader: TSoundLoaderFactory); implementation var - e_SoundLoaders: array of TSoundLoader; + e_SoundLoaders: array of TSoundLoaderFactory; function e_GetSoundLoader(FName: string): TSoundLoader; overload; var @@ -61,27 +72,27 @@ var begin Result := nil; for I := Low(e_SoundLoaders) to High(e_SoundLoaders) do - if e_SoundLoaders[I].CanLoad(FName) then + if e_SoundLoaders[I].MatchExtension(FName) then begin - Result := e_SoundLoaders[I]; + Result := e_SoundLoaders[I].GetLoader(); break; end; end; -function e_GetSoundLoader(Data: Pointer; Len: Integer): TSoundLoader; overload; +function e_GetSoundLoader(Data: Pointer; Len: LongWord): TSoundLoader; overload; var I: Integer; begin Result := nil; for I := Low(e_SoundLoaders) to High(e_SoundLoaders) do - if e_SoundLoaders[I].CanLoad(Data, Len) then + if e_SoundLoaders[I].MatchHeader(Data, Len) then begin - Result := e_SoundLoaders[I]; + Result := e_SoundLoaders[I].GetLoader(); break; end; end; -procedure e_AddSoundLoader(Loader: TSoundLoader); +procedure e_AddSoundLoader(Loader: TSoundLoaderFactory); begin SetLength(e_SoundLoaders, Length(e_SoundLoaders) + 1); e_SoundLoaders[High(e_SoundLoaders)] := Loader; diff --git a/src/engine/e_soundfile_wav.pas b/src/engine/e_soundfile_wav.pas index b88dd5a..b5b838d 100644 --- a/src/engine/e_soundfile_wav.pas +++ b/src/engine/e_soundfile_wav.pas @@ -22,20 +22,33 @@ uses e_soundfile; type // a WAV loader that just uses SDL_LoadWAV + TWAVLoader = class (TSoundLoader) public - function CanLoad(Data: Pointer; Len: Integer): Boolean; override; overload; - function CanLoad(FName: string): Boolean; override; overload; - function Load(Data: Pointer; Len: Integer; var OutLen: Integer; var OutFmt: TSoundFormat): Pointer; override; overload; - function Load(FName: string; var OutLen: Integer; var OutFmt: TSoundFormat): Pointer; override; overload; - procedure Free(Data: Pointer); override; + function Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean; override; overload; + function Load(FName: string; SStreaming: Boolean): Boolean; override; overload; + function SetPosition(Pos: LongWord): Boolean; override; + function FillBuffer(Buf: Pointer; Len: LongWord): LongWord; override; + function GetAll(var OutPtr: Pointer): LongWord; override; + procedure Free(); override; + private + FData: Pointer; + FDataLen: LongWord; + end; + + TWAVLoaderFactory = class (TSoundLoaderFactory) + function MatchHeader(Data: Pointer; Len: LongWord): Boolean; override; + function MatchExtension(FName: string): Boolean; override; + function GetLoader(): TSoundLoader; override; end; implementation uses sdl2, utils, e_log; -function TWAVLoader.CanLoad(Data: Pointer; Len: Integer): Boolean; +(* TWAVLoaderFactory *) + +function TWAVLoaderFactory.MatchHeader(Data: Pointer; Len: LongWord): Boolean; var P: PByte; begin @@ -48,20 +61,27 @@ begin Result := ((P+0)^ = Ord('R')) and ((P+1)^ = Ord('I')) and ((P+2)^ = Ord('F')) and ((P+3)^ = Ord('F')); end; -function TWAVLoader.CanLoad(FName: string): Boolean; +function TWAVLoaderFactory.MatchExtension(FName: string): Boolean; begin - // TODO: actually check for RIFF header + // TODO: ehhh Result := GetFilenameExt(FName) = '.wav'; end; -function TWAVLoader.Load(Data: Pointer; Len: Integer; var OutLen: Integer; var OutFmt: TSoundFormat): Pointer; +function TWAVLoaderFactory.GetLoader(): TSoundLoader; +begin + Result := TWAVLoader.Create(); +end; + +(* TWAVLoader *) + +function TWAVLoader.Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean; var Spec: TSDL_AudioSpec; RW: PSDL_RWops; TmpLen: UInt32; TmpBuf: PUInt8; begin - Result := nil; + Result := False; RW := SDL_RWFromConstMem(Data, Len); @@ -71,25 +91,26 @@ begin end else begin - OutFmt.Loader := self; - OutFmt.SampleRate := Spec.freq; - OutFmt.SampleBits := SDL_AUDIO_BITSIZE(Spec.format); - OutFmt.Channels := Spec.channels; - OutLen := TmpLen; - Result := TmpBuf; + FFormat.SampleRate := Spec.freq; + FFormat.SampleBits := SDL_AUDIO_BITSIZE(Spec.format); + FFormat.Channels := Spec.channels; + FStreaming := False; // never stream wavs + FDataLen := TmpLen; + FData := TmpBuf; + Result := True; end; SDL_RWclose(RW); end; -function TWAVLoader.Load(FName: string; var OutLen: Integer; var OutFmt: TSoundFormat): Pointer; +function TWAVLoader.Load(FName: string; SStreaming: Boolean): Boolean; var Spec: TSDL_AudioSpec; RW: PSDL_RWops; TmpLen: UInt32; TmpBuf: PUInt8; begin - Result := nil; + Result := False; RW := SDL_RWFromFile(PChar(FName), 'rb'); @@ -102,26 +123,51 @@ begin if SDL_LoadWAV_RW(RW, 0, @Spec, @TmpBuf, @TmpLen) = nil then begin e_LogWritefln('Could not load WAV file `%s`: %s', [FName, SDL_GetError()]); - Result := nil; end else begin - OutFmt.Loader := self; - OutFmt.SampleRate := Spec.freq; - OutFmt.SampleBits := SDL_AUDIO_BITSIZE(Spec.format); - OutFmt.Channels := Spec.channels; - OutLen := TmpLen; - Result := TmpBuf; + FFormat.SampleRate := Spec.freq; + FFormat.SampleBits := SDL_AUDIO_BITSIZE(Spec.format); + FFormat.Channels := Spec.channels; + FStreaming := False; // never stream wavs + FDataLen := TmpLen; + FData := TmpBuf; + Result := True; end; SDL_RWclose(RW); end; -procedure TWAVLoader.Free(Data: Pointer); +function TWAVLoader.SetPosition(Pos: LongWord): Boolean; +begin + Result := False; // makes no sense when not streaming +end; + +function TWAVLoader.FillBuffer(Buf: Pointer; Len: LongWord): LongWord; +begin + if FDataLen < Len then + Len := FDataLen; + if FData <> nil then + begin + Move(FData^, Buf^, Len); + Result := Len; + end + else + Result := 0; +end; + +function TWAVLoader.GetAll(var OutPtr: Pointer): LongWord; +begin + OutPtr := FData; + Result := FDataLen; +end; + +procedure TWAVLoader.Free(); begin - SDL_FreeWAV(Data); // SDL allocates inside the DLL, so we need this + if FData <> nil then + SDL_FreeWAV(FData); // SDL allocates inside the DLL, so we need this end; initialization - e_AddSoundLoader(TWAVLoader.Create()); + e_AddSoundLoader(TWAVLoaderFactory.Create()); end. diff --git a/src/engine/e_soundfile_xmp.pas b/src/engine/e_soundfile_xmp.pas new file mode 100644 index 0000000..aaf1df1 --- /dev/null +++ b/src/engine/e_soundfile_xmp.pas @@ -0,0 +1,197 @@ +(* Copyright (C) Doom 2D: Forever Developers + * + * 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 3 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, see . + *) +{$INCLUDE ../shared/a_modes.inc} +unit e_soundfile_xmp; + +interface + +uses e_soundfile, XMP; + +type + // a module loader that uses libxmp-lite + + TXMPLoader = class (TSoundLoader) + public + function Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean; override; overload; + function Load(FName: string; SStreaming: Boolean): Boolean; override; overload; + function SetPosition(Pos: LongWord): Boolean; override; + function FillBuffer(Buf: Pointer; Len: LongWord): LongWord; override; + function GetAll(var OutPtr: Pointer): LongWord; override; + procedure Free(); override; + private + FXMP: xmp_context; + FLoaded: Boolean; + end; + + TXMPLoaderFactory = class (TSoundLoaderFactory) + function MatchHeader(Data: Pointer; Len: LongWord): Boolean; override; + function MatchExtension(FName: string): Boolean; override; + function GetLoader(): TSoundLoader; override; + end; + +implementation + +uses sysutils, utils, e_sound, e_log; + +(* TXMPLoaderFactory *) + +function TXMPLoaderFactory.MatchHeader(Data: Pointer; Len: LongWord): Boolean; +var + Ctx: xmp_context; + Err: LongInt; +begin + // HACK: these fine gentlemen didn't provide us with a xmp_test_module_from_memory() + // so just load the module and unload it + + Result := False; + + Ctx := xmp_create_context(); + Err := xmp_load_module_from_memory(Ctx, Data, Len); + + if Err = 0 then + begin + xmp_release_module(Ctx); + Result := True; + end; + + xmp_free_context(Ctx); +end; + +function TXMPLoaderFactory.MatchExtension(FName: string): Boolean; +var + Ext: string; +begin + Ext := GetFilenameExt(FName); + Result := (Ext = '.it') or (Ext = '.xm') or (Ext = '.mod') or (Ext = '.s3m'); +end; + +function TXMPLoaderFactory.GetLoader(): TSoundLoader; +begin + Result := TXMPLoader.Create(); +end; + +(* TXMPLoader *) + +function TXMPLoader.Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean; +var + Err: LongInt; +begin + Result := False; + + FLoaded := False; + FXMP := xmp_create_context(); + if FXMP = nil then Exit; + + try + Err := xmp_load_module_from_memory(FXMP, Data, Len); + if Err <> 0 then + raise Exception.Create('xmp_load_module_from_memory failed'); + + if xmp_start_player(FXMP, e_SoundFormat.SampleRate, 0) <> 0 then + raise Exception.Create('xmp_start_player failed'); + + FFormat.SampleRate := e_SoundFormat.SampleRate; + FFormat.SampleBits := 16; + FFormat.Channels := 2; + + FStreaming := True; // modules are always streaming + FLoaded := True; + Result := True; + except + on E: Exception do + begin + e_LogWriteln('TXMPLoader.Load() error: ' + E.Message); + if Err = 0 then xmp_release_module(FXMP); + xmp_free_context(FXMP); + FXMP := nil; + end; + end; +end; + +function TXMPLoader.Load(FName: string; SStreaming: Boolean): Boolean; +var + Err: LongInt; +begin + Result := False; + + FLoaded := False; + FXMP := xmp_create_context(); + if FXMP = nil then Exit; + + try + Err := xmp_load_module(FXMP, PChar(FName)); + if Err <> 0 then + raise Exception.Create('xmp_load_module failed'); + + if xmp_start_player(FXMP, e_SoundFormat.SampleRate, 0) <> 0 then + raise Exception.Create('xmp_start_player failed'); + + FFormat.SampleRate := e_SoundFormat.SampleRate; + FFormat.SampleBits := 16; + FFormat.Channels := 2; + + FStreaming := True; // modules are always streaming + FLoaded := True; + Result := True; + except + on E: Exception do + begin + e_LogWritefln('TXMPLoader.Load(%s) error: %s', [FName, E.Message]); + if Err = 0 then xmp_release_module(FXMP); + xmp_free_context(FXMP); + FXMP := nil; + end; + end; +end; + +function TXMPLoader.SetPosition(Pos: LongWord): Boolean; +begin + Result := False; + if FXMP = nil then Exit; + Result := xmp_set_position(FXMP, Pos) = 0; +end; + +function TXMPLoader.FillBuffer(Buf: Pointer; Len: LongWord): LongWord; +begin + Result := 0; + if FXMP = nil then Exit; + if xmp_play_buffer(FXMP, Buf, Len, 0) = 0 then + Result := Len; +end; + +function TXMPLoader.GetAll(var OutPtr: Pointer): LongWord; +begin + Result := 0; // modules are always streaming, so this don't make sense +end; + +procedure TXMPLoader.Free(); +begin + if FXMP <> nil then + begin + if FLoaded then + begin + xmp_end_player(FXMP); + xmp_release_module(FXMP); + end; + xmp_free_context(FXMP); + FXMP := nil; + end; + FLoaded := False; +end; + +initialization + e_AddSoundLoader(TXMPLoaderFactory.Create()); +end. diff --git a/src/game/Doom2DF.lpr b/src/game/Doom2DF.lpr index f972e59..c3c7c3e 100644 --- a/src/game/Doom2DF.lpr +++ b/src/game/Doom2DF.lpr @@ -61,8 +61,10 @@ uses {$ENDIF} {$IFDEF USE_OPENAL} AL in '../lib/openal/al.pas', + XMP in '../lib/xmp/xmp.pas', e_soundfile in '../engine/e_soundfile.pas', e_soundfile_wav in '../engine/e_soundfile_wav.pas', + e_soundfile_xmp in '../engine/e_soundfile_xmp.pas', {$ENDIF} ENet in '../lib/enet/enet.pp', e_graphics in '../engine/e_graphics.pas', diff --git a/src/lib/openal/al.pas b/src/lib/openal/al.pas index 0d6afba..2ca6fc1 100644 --- a/src/lib/openal/al.pas +++ b/src/lib/openal/al.pas @@ -14,7 +14,9 @@ uses ctypes; {$IFDEF WINDOWS} - {$DEFINE AL_DYNAMIC} + {$IFNDEF AL_WINDOZE_STATIC} + {$DEFINE AL_DYNAMIC} + {$ENDIF} {$ENDIF} {$IF DEFINED(AL_DYNAMIC)} diff --git a/src/lib/xmp/xmp.pas b/src/lib/xmp/xmp.pas new file mode 100644 index 0000000..2f48d07 --- /dev/null +++ b/src/lib/xmp/xmp.pas @@ -0,0 +1,375 @@ +unit XMP; + +interface + +{$IFDEF FPC} +{$PACKRECORDS C} +{$MODE OBJFPC} +{$ENDIF} + +{$IFDEF WINDOWS} + {$IFNDEF LIBXMP_WINDOZE_STATIC} + {$DEFINE XMP_DYNAMIC} + {$ENDIF} +{$ENDIF} + +{$IF DEFINED(XMP_DYNAMIC)} +const +{$IF DEFINED(WINDOWS)} + xmplib = 'libxmp-lite.dll'; +{$ELSEIF DEFINED(UNIX)} + xmplib = 'libxmp-lite.so'; +{$ELSE} + {$MESSAGE ERROR 'XMP_DYNAMIC not supported'} +{$IFEND} +{$ELSE} + {$LINKLIB xmp-lite} +{$ENDIF} + +const + XMP_VER_STRING = '4.5.0'; + XMP_VER_CODE = $040500; + XMP_VER_MAJOR = 4; + XMP_VER_MINOR = 5; + XMP_VER_RELEASE = 0; + +const + XMP_NAME_SIZE = 64; (* Size of module name and type *) + XMP_KEY_OFF = $81; (* Note number for key off event *) + XMP_KEY_CUT = $82; (* Note number for key cut event *) + XMP_KEY_FADE = $83; (* Note number for fade event *) + (* mixer parameter macros *) + (* sample format flags *) + XMP_FORMAT_8BIT = (1 shl 0); (* Mix to 8-bit instead of 16 *) + XMP_FORMAT_UNSIGNED = (1 shl 1); (* Mix to unsigned samples *) + XMP_FORMAT_MONO = (1 shl 2); (* Mix to mono instead of stereo *) + (* player parameters *) + XMP_PLAYER_AMP = 0; (* Amplification factor *) + XMP_PLAYER_MIX = 1; (* Stereo mixing *) + XMP_PLAYER_INTERP = 2; (* Interpolation type *) + XMP_PLAYER_DSP = 3; (* DSP effect flags *) + XMP_PLAYER_FLAGS = 4; (* Player flags *) + XMP_PLAYER_CFLAGS = 5; (* Player flags for current module *) + XMP_PLAYER_SMPCTL = 6; (* Sample control flags *) + XMP_PLAYER_VOLUME = 7; (* Player module volume *) + XMP_PLAYER_STATE = 8; (* Internal player state (read only) *) + XMP_PLAYER_SMIX_VOLUME = 9; (* SMIX volume *) + XMP_PLAYER_DEFPAN = 10; (* Default pan setting *) + XMP_PLAYER_MODE = 11; (* Player personality *) + XMP_PLAYER_MIXER_TYPE = 12; (* Current mixer (read only) *) + XMP_PLAYER_VOICES = 13; (* Maximum number of mixer voices *) + (* interpolation types *) + XMP_INTERP_NEAREST = 0; (* Nearest neighbor *) + XMP_INTERP_LINEAR = 1; (* Linear (default) *) + XMP_INTERP_SPLINE = 2; (* Cubic spline *) + (* dsp effect types *) + XMP_DSP_LOWPASS = (1 shl 0); (* Lowpass filter effect *) + XMP_DSP_ALL = (XMP_DSP_LOWPASS); + (* player state *) + XMP_STATE_UNLOADED = 0; (* Context created *) + XMP_STATE_LOADED = 1; (* Module loaded *) + XMP_STATE_PLAYING = 2; (* Module playing *) + (* player flags *) + XMP_FLAGS_VBLANK = (1 shl 0); (* Use vblank timing *) + XMP_FLAGS_FX9BUG = (1 shl 1); (* Emulate FX9 bug *) + XMP_FLAGS_FIXLOOP = (1 shl 2); (* Emulate sample loop bug *) + XMP_FLAGS_A500 = (1 shl 3); (* Use Paula mixer in Amiga modules *) + (* player modes *) + XMP_MODE_AUTO = 0; (* Autodetect mode (default) *) + XMP_MODE_MOD = 1; (* Play as a generic MOD player *) + XMP_MODE_NOISETRACKER = 2; (* Play using Noisetracker quirks *) + XMP_MODE_PROTRACKER = 3; (* Play using Protracker quirks *) + XMP_MODE_S3M = 4; (* Play as a generic S3M player *) + XMP_MODE_ST3 = 5; (* Play using ST3 bug emulation *) + XMP_MODE_ST3GUS = 6; (* Play using ST3+GUS quirks *) + XMP_MODE_XM = 7; (* Play as a generic XM player *) + XMP_MODE_FT2 = 8; (* Play using FT2 bug emulation *) + XMP_MODE_IT = 9; (* Play using IT quirks *) + XMP_MODE_ITSMP = 10; (* Play using IT sample mode quirks *) + (* mixer types *) + XMP_MIXER_STANDARD = 0; (* Standard mixer *) + XMP_MIXER_A500 = 1; (* Amiga 500 *) + XMP_MIXER_A500F = 2; (* Amiga 500 with led filter *) + (* sample flags *) + XMP_SMPCTL_SKIP = (1 shl 0); (* Don't load samples *) + (* limits *) + XMP_MAX_KEYS = 121; (* Number of valid keys *) + XMP_MAX_ENV_POINTS = 32; (* Max number of envelope points *) + XMP_MAX_MOD_LENGTH = 256; (* Max number of patterns in module *) + XMP_MAX_CHANNELS = 64; (* Max number of channels in module *) + XMP_MAX_SRATE = 49170; (* max sampling rate (Hz) *) + XMP_MIN_SRATE = 4000; (* min sampling rate (Hz) *) + XMP_MIN_BPM = 20; (* min BPM *) + (* frame rate = (50 * bpm / 125) Hz *) + (* frame size = (sampling rate * channels * size) / frame rate *) + XMP_MAX_FRAMESIZE = (5*XMP_MAX_SRATE*2 div XMP_MIN_BPM); + (* error codes *) + XMP_END = 1; + XMP_ERROR_INTERNAL = 2; (* Internal error *) + XMP_ERROR_FORMAT = 3; (* Unsupported module format *) + XMP_ERROR_LOAD = 4; (* Error loading file *) + XMP_ERROR_DEPACK = 5; (* Error depacking file *) + XMP_ERROR_SYSTEM = 6; (* System error *) + XMP_ERROR_INVALID = 7; (* Invalid parameter *) + XMP_ERROR_STATE = 8; (* Invalid player state *) + +const + XMP_CHANNEL_SYNTH = (1 shl 0); (* Channel is synthesized *) + XMP_CHANNEL_MUTED = (1 shl 1); (* Channel is muted *) + XMP_CHANNEL_SPLIT = (1 shl 2); (* Split Amiga channel in bits 5-4 *) + XMP_CHANNEL_SURROUND = (1 shl 4); (* Surround channel *) + +const + XMP_ENVELOPE_ON = (1 shl 0); (* Envelope is enabled *) + XMP_ENVELOPE_SUS = (1 shl 1); (* Envelope has sustain point *) + XMP_ENVELOPE_LOOP = (1 shl 2); (* Envelope has loop *) + XMP_ENVELOPE_FLT = (1 shl 3); (* Envelope is used for filter *) + XMP_ENVELOPE_SLOOP = (1 shl 4); (* Envelope has sustain loop *) + XMP_ENVELOPE_CARRY = (1 shl 5); (* Don't reset envelope position *) + +const + XMP_INST_NNA_CUT = $00; + XMP_INST_NNA_CONT = $01; + XMP_INST_NNA_OFF = $02; + XMP_INST_NNA_FADE = $03; + XMP_INST_DCT_OFF = $00; + XMP_INST_DCT_NOTE = $01; + XMP_INST_DCT_SMP = $02; + XMP_INST_DCT_INST = $03; + XMP_INST_DCA_CUT = XMP_INST_NNA_CUT; + XMP_INST_DCA_OFF = XMP_INST_NNA_OFF; + XMP_INST_DCA_FADE = XMP_INST_NNA_FADE; + +const + XMP_SAMPLE_16BIT = (1 shl 0); (* 16bit sample *) + XMP_SAMPLE_LOOP = (1 shl 1); (* Sample is looped *) + XMP_SAMPLE_LOOP_BIDIR = (1 shl 2); (* Bidirectional sample loop *) + XMP_SAMPLE_LOOP_REVERSE = (1 shl 3); (* Backwards sample loop *) + XMP_SAMPLE_LOOP_FULL = (1 shl 4); (* Play full sample before looping *) + XMP_SAMPLE_SLOOP = (1 shl 5); (* Sample has sustain loop *) + XMP_SAMPLE_SLOOP_BIDIR = (1 shl 6); (* Bidirectional sustain loop *) + XMP_SAMPLE_SYNTH = (1 shl 15); (* Data contains synth patch *) + +type + xmp_channel = record + pan: longint; (* Channel pan (0x80 is center) *) + vol: longint; (* Channel volume *) + flg: longint; (* Channel flags *) + end; + + xmp_pattern = record + rows: longint; (* Number of rows *) + index: array [0..0] of longint; (* Track index *) + end; + + xmp_event = record + note: byte; (* Note number (0 means no note) *) + ins: byte; (* Patch number *) + vol: byte; (* Volume (0 to basevol) *) + fxt: byte; (* Effect type *) + fxp: byte; (* Effect parameter *) + f2t: byte; (* Secondary effect type *) + f2p: byte; (* Secondary effect parameter *) + _flag: byte; (* Internal (reserved) flags *) + end; + + pxmp_event = ^xmp_event; + + xmp_track = record + rows: longint; (* Number of rows *) + event: array [0..0] of xmp_event; (* Event data *) + end; + + xmp_envelope = record + flg: longint; (* Flags *) + npt: longint; (* Number of envelope points *) + scl: longint; (* Envelope scaling *) + sus: longint; (* Sustain start point *) + sue: longint; (* Sustain end point *) + lps: longint; (* Loop start point *) + lpe: longint; (* Loop end point *) + data: array [0..(XMP_MAX_ENV_POINTS*2)-1] of smallint; + end; + + xmp_subinstrument = record + vol: longint; (* Default volume *) + gvl: longint; (* Global volume *) + pan: longint; (* Pan *) + xpo: longint; (* Transpose *) + fin: longint; (* Finetune *) + vwf: longint; (* Vibrato waveform *) + vde: longint; (* Vibrato depth *) + vra: longint; (* Vibrato rate *) + vsw: longint; (* Vibrato sweep *) + rvv: longint; (* Random volume/pan variation (IT) *) + sid: longint; (* Sample number *) + nna: longint; (* New note action *) + dct: longint; (* Duplicate check type *) + dca: longint; (* Duplicate check action *) + ifc: longint; (* Initial filter cutoff *) + ifr: longint; (* Initial filter resonance *) + end; + + pxmp_subinstrument = ^xmp_subinstrument; + + xmp_instrument = record + name: array [0..31] of char; (* Instrument name *) + vol: longint; (* Instrument volume *) + nsm: longint; (* Number of samples *) + rls: longint; (* Release (fadeout) *) + aei: xmp_envelope; (* Amplitude envelope info *) + pei: xmp_envelope; (* Pan envelope info *) + fei: xmp_envelope; (* Frequency envelope info *) + map : array[0..(XMP_MAX_KEYS)-1] of record + ins : byte; + xpo : char; + end; + sub: pxmp_subinstrument; + extra: pointer; + end; + + xmp_sample = record + name: array [0..31] of char; (* Sample name *) + len: longint; (* Sample length *) + lps: longint; (* Loop start *) + lpe: longint; (* Loop end *) + flg: longint; (* Flags *) + data: pbyte; (* Sample data *) + end; + + xmp_sequence = record + entry_point: longint; + duration: longint; + end; + + pxmp_sequence = ^xmp_sequence; + + xmp_module = record + name: array [0..XMP_NAME_SIZE-1] of char; (* Module title *) + format: array [0..XMP_NAME_SIZE-1] of char; (* Module format *) + + pat: longint; (* Number of patterns *) + trk: longint; (* Number of tracks *) + chn: longint; (* Tracks per pattern *) + ins: longint; (* Number of instruments *) + smp: longint; (* Number of samples *) + spd: longint; (* Initial speed *) + bpm: longint; (* Initial BPM *) + len: longint; (* Module length in patterns *) + rst: longint; (* Restart position *) + gvl: longint; (* Global volume *) + + xxp: pointer; // xmp_pattern** + xxt: pointer; // xmp_track** + xxi: pointer; // xmp_instrument* + xxs: pointer; // xmp_sample* + xxc: array [0..XMP_MAX_CHANNELS-1] of xmp_channel; + xxo: array [0..XMP_MAX_MOD_LENGTH-1] of byte; + end; + + pxmp_module = ^xmp_module; + + xmp_test_info = record + name: array [0..XMP_NAME_SIZE-1] of char; (* Module title *) + format: array [0..XMP_NAME_SIZE-1] of char; (* Module format *) + end; + + xmp_module_info = record + md5: array [0..15] of byte; (* MD5 message digest *) + vol_base: longint; (* Volume scale *) + module: pxmp_module; (* Pointer to module data *) + comment: pchar; (* Comment text, if any *) + num_sequences: longint; (* Number of valid sequences *) + seq_data: pxmp_sequence; (* Pointer to sequence data *) + end; + + xmp_channel_info = record + period: longword; (* Sample period (times 4096) *) + position: longword; (* Sample position *) + pitchbend: word; (* Linear bend from base note*) + note: byte; (* Current base note number *) + instrument: byte; (* Current instrument number *) + usample: byte; (* Current sample number *) + volume: byte; (* Current volume *) + pan: byte; (* Current stereo pan *) + reserved: byte; (* Reserved *) + event: xmp_event; (* Current track event *) + end; + + (* Current frame information *) + xmp_frame_info = record + pos: longint; (* Current position *) + pattern: longint; (* Current pattern *) + row: longint; (* Current row in pattern *) + num_rows: longint; (* Number of rows in current pattern *) + frame: longint; (* Current frame *) + speed: longint; (* Current replay speed *) + bpm: longint; (* Current bpm *) + time: longint; (* Current module time in ms *) + total_time: longint; (* Estimated replay time in ms*) + frame_time: longint; (* Frame replay time in us *) + buffer: plongint; (* Pointer to sound buffer *) + buffer_size: longint; (* Used buffer size *) + total_size: longint; (* Total buffer size *) + volume: longint; (* Current master volume *) + loop_count: longint; (* Loop counter *) + virt_channels: longint; (* Number of virtual channels *) + virt_used: longint; (* Used virtual channels *) + sequence: longint; (* Current sequence *) + channel_info: array [0..XMP_MAX_CHANNELS-1] of xmp_channel_info; + end; + + xmp_context = pointer; + +var + xmp_version: pchar; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; + xmp_vercode: longword; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; + +function xmp_create_context(): xmp_context; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +procedure xmp_free_context(ctx: xmp_context); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +function xmp_get_format_list(): ppchar; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; + +function xmp_test_module(fname: pchar; var info: xmp_test_info): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +function xmp_load_module(ctx: xmp_context; fname: pchar): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +function xmp_load_module_from_memory(ctx: xmp_context; buf: pointer; bufsiz: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +function xmp_load_module_from_file(ctx: xmp_context; fstream: pointer; msize: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +procedure xmp_scan_module(ctx: xmp_context); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +procedure xmp_get_module_info(ctx: xmp_context; var info: xmp_module_info); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +procedure xmp_release_module(ctx: xmp_context); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; + +function xmp_start_player(ctx: xmp_context; rate, format: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +function xmp_play_frame(ctx: xmp_context): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +function xmp_play_buffer(ctx: xmp_context; buf: pointer; bufsiz: longint; loop: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +procedure xmp_get_frame_info(ctx: xmp_context; var info: xmp_frame_info); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +procedure xmp_end_player(ctx: xmp_context); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; + +procedure xmp_inject_event(ctx: xmp_context; ch: longint; ev: pxmp_event); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; + +function xmp_next_position(ctx: xmp_context): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +function xmp_prev_position(ctx: xmp_context): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +function xmp_set_position(ctx: xmp_context; pos: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +function xmp_seek_time(ctx: xmp_context; ms: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +procedure xmp_stop_module(ctx: xmp_context); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +procedure xmp_restart_module(ctx: xmp_context); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; + +function xmp_channel_mute(ctx: xmp_context; ch, mute: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +function xmp_channel_vol(ctx: xmp_context; ch, vol: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; + +function xmp_set_player(ctx: xmp_context; param, value: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +function xmp_get_player(ctx: xmp_context; param: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; +function xmp_set_instrument_path(ctx: xmp_context; path: pchar): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF}; + +(* +function xmp_start_smix; +procedure xmp_end_smix; +function xmp_smix_play_instrument; +function xmp_smix_play_sample; +function xmp_smix_channel_pan; +function xmp_smix_load_sample; +function xmp_smix_release_sample; +*) + +implementation + + +end. -- 2.29.2