From 9bc2f6da5fde4dfddbfa66c8bcb984fbadfe1c9f Mon Sep 17 00:00:00 2001 From: fgsfds Date: Tue, 3 Sep 2019 04:28:35 +0300 Subject: [PATCH] Sound: OpenAL: OGG/Vorbis support --- src/engine/e_soundfile_ogg.pas | 300 ++++++++++++++++++++++ src/game/Doom2DF.lpr | 5 + src/lib/vorbis/ogg.pas | 215 ++++++++++++++++ src/lib/vorbis/vorbis.pas | 449 +++++++++++++++++++++++++++++++++ 4 files changed, 969 insertions(+) create mode 100644 src/engine/e_soundfile_ogg.pas create mode 100644 src/lib/vorbis/ogg.pas create mode 100644 src/lib/vorbis/vorbis.pas diff --git a/src/engine/e_soundfile_ogg.pas b/src/engine/e_soundfile_ogg.pas new file mode 100644 index 0000000..220fbd2 --- /dev/null +++ b/src/engine/e_soundfile_ogg.pas @@ -0,0 +1,300 @@ +(* 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_ogg; + +interface + +uses e_soundfile, vorbis, classes; + +type + // OGG Vorbis loader + + TOGGLoader = 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 + FOgg: OggVorbis_File; + FData: TStream; + FBuf: Pointer; + FTotal: LongWord; + FOpen: Boolean; + + function LoadStream(Stream: TStream; SStreaming: Boolean): Boolean; + function LoadEntireStream(): Pointer; + end; + + TOGGLoaderFactory = class (TSoundLoaderFactory) + public + function MatchHeader(Data: Pointer; Len: LongWord): Boolean; override; + function MatchExtension(FName: string): Boolean; override; + function GetLoader(): TSoundLoader; override; + end; + +implementation + +uses sysutils, utils, e_log, xstreams, ogg, ctypes; + +(* Reader functions for ov_callbacks *) + +function streamSeek(h: Pointer; off: ogg_int64_t; whence: cint): cint; cdecl; +var + S: TStream; +begin + Result := -1; + if h = nil then Exit; + S:= TStream(h); + try + case whence of + 0: s.Seek(off, soBeginning); // SEEK_SET + 1: s.Seek(off, soCurrent); // SEEK_CUR + 2: s.Seek(off, soEnd); // SEEK_END + end; + Result := 0; + except + Result := -1; + end; +end; + +function streamRead(buf: Pointer; sz, nmemb: csize_t; h: Pointer): csize_t; cdecl; +var + S: TStream; +begin + Result := 0; + if h = nil then Exit; + S:= TStream(h); + try + Result := S.Read(buf^, sz*nmemb) div sz; + except + Result := 0; + end; +end; + +function streamTell(h: Pointer): clong; cdecl; +var + S: TStream; +begin + Result := -1; + if h = nil then Exit; + S := TStream(h); + Result := S.Position; +end; + +var + oggIO: ov_callbacks = ( + read: streamRead; + seek: streamSeek; + close: nil; // the loader's gonna handle that + tell: streamTell; + ); + +(* TOGGLoaderFactory *) + +function TOGGLoaderFactory.MatchHeader(Data: Pointer; Len: LongWord): Boolean; +const + OGG_HEADER = $5367674F; // 'OggS' +begin + if Len < 27 then // header is at least 27 bytes + begin + Result := False; + exit; + end; + Result := (PLongWord(Data)^ = OGG_HEADER); +end; + +function TOGGLoaderFactory.MatchExtension(FName: string): Boolean; +begin + Result := GetFilenameExt(FName) = '.ogg'; +end; + +function TOGGLoaderFactory.GetLoader(): TSoundLoader; +begin + Result := TOGGLoader.Create(); +end; + +(* TOGGLoader *) + +function TOGGLoader.LoadEntireStream(): Pointer; +var + Samples: ogg_int64_t; + Ret: clong; +begin + Result := nil; + + Samples := ov_pcm_total(FOgg, -1); + if Samples < 0 then Exit; + + FTotal := Samples * 2 * FFormat.Channels; + Result := GetMem(FTotal); + if Result = nil then Exit; + + Ret := ov_read_ext(FOgg, Result, FTotal, False, 2, True); + if Ret < 0 then + begin + FreeMem(Result); + Result := nil; + end + else + FTotal := Ret; +end; + +function TOGGLoader.LoadStream(Stream: TStream; SStreaming: Boolean): Boolean; +var + Ret, Bit: clong; + Info: pvorbis_info; + FullBuf: Pointer; +begin + Result := False; + + Ret := ov_open_callbacks(Stream, FOgg, nil, 0, oggIO); + if Ret < 0 then + begin + e_LogWriteln('OGG: Load(Data) failed: ov_open_callbacks failed'); + Exit; + end; + + Info := ov_info(FOgg, -1); + if Info = nil then + begin + e_LogWriteln('OGG: Load(Data) failed: ov_info returned NULL'); + ov_clear(FOgg); + Exit; + end; + + FFormat.SampleRate := Info^.rate; + FFormat.Channels := Info^.channels; + FFormat.SampleBits := 16; + + if not SStreaming then + begin + FullBuf := LoadEntireStream(); + + if FullBuf = nil then + begin + e_LogWriteln('OGG: Load(Data) failed: couldn''t allocate for non-streaming chunk'); + ov_clear(FOgg); + FTotal := 0; + Exit; + end; + + ov_clear(FOgg); + Stream.Destroy(); + + FreeMem(FBuf); + FBuf := FullBuf; + end + else + begin + FTotal := 0; + FOpen := True; + FData := Stream; + end; + + FStreaming := SStreaming; + Result := True; +end; + +function TOGGLoader.Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean; +var + S: TStream; +begin + Result := False; + + // TODO: have to make a dupe here because Data gets deallocated after loading + // this is obviously very shit + FBuf := GetMem(Len); + if FBuf = nil then Exit; + Move(Data^, FBuf^, Len); + + S := TSFSMemoryStreamRO.Create(FBuf, Len{, True}); + Result := LoadStream(S, SStreaming); + + if not Result and (S <> nil) then + begin + S.Destroy(); + FreeMem(FBuf); + FBuf := nil; + end; +end; + +function TOGGLoader.Load(FName: string; SStreaming: Boolean): Boolean; +var + S: TStream = nil; +begin + Result := False; + + try + S := openDiskFileRO(FName); + Result := LoadStream(S, SStreaming); + except + on E: Exception do + e_LogWritefln('OGG: ERROR: could not read file `%s`: %s', [FName, E.Message]); + end; + + if not Result and (S <> nil) then + S.Destroy(); +end; + +function TOGGLoader.SetPosition(Pos: LongWord): Boolean; +begin + Result := False; + if not FOpen or (ov_seekable(FOgg) = 0) then Exit; + Result := ov_pcm_seek(FOgg, Pos) = 0; +end; + +function TOGGLoader.FillBuffer(Buf: Pointer; Len: LongWord): LongWord; +var + Ret: clong; +begin + Result := 0; + if not FOpen or not FStreaming then Exit; + Ret := ov_read_ext(FOgg, Buf, Len, False, 2, True); + if Ret < 0 then Exit; + Result := Ret; +end; + +function TOGGLoader.GetAll(var OutPtr: Pointer): LongWord; +begin + Result := 0; + if FStreaming or (FTotal = 0) then Exit; + Result := FTotal; + OutPtr := FBuf; +end; + +procedure TOGGLoader.Free(); +begin + if FOpen then + ov_clear(FOgg); + if FData <> nil then + FData.Destroy(); + if FBuf <> nil then + FreeMem(FBuf); + FData := nil; + FBuf := nil; + FOpen := False; + FTotal := 0; + FStreaming := False; +end; + +initialization + e_AddSoundLoader(TOGGLoaderFactory.Create()); +end. diff --git a/src/game/Doom2DF.lpr b/src/game/Doom2DF.lpr index 9277239..cd61dfe 100644 --- a/src/game/Doom2DF.lpr +++ b/src/game/Doom2DF.lpr @@ -63,6 +63,11 @@ uses AL in '../lib/openal/al.pas', e_soundfile in '../engine/e_soundfile.pas', e_soundfile_wav in '../engine/e_soundfile_wav.pas', + {$IFDEF USE_VORBIS} + ogg in '../lib/vorbis/ogg.pas', + vorbis in '../lib/vorbis/vorbis.pas', + e_soundfile_ogg in '../engine/e_soundfile_ogg.pas', + {$ENDIF} {$IFDEF USE_FLUIDSYNTH} fluidsynth in '../lib/fluidsynth/fluidsynth.pas', e_soundfile_fluid in '../engine/e_soundfile_fluid.pas', diff --git a/src/lib/vorbis/ogg.pas b/src/lib/vorbis/ogg.pas new file mode 100644 index 0000000..118fbad --- /dev/null +++ b/src/lib/vorbis/ogg.pas @@ -0,0 +1,215 @@ +{ + Translation of the ogg headers for FreePascal + Copyright (C) 2006 by Ivo Steinmann +} + +(******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ********************************************************************) + +unit ogg; + +{$MODE OBJFPC} +{$MINENUMSIZE 4} +{$PACKRECORDS C} + +interface + +uses + ctypes; + +{$IF DEFINED(WINDOWS)} + {$IFDEF VORBIS_WINDOZE_STATIC} + {$LINKLIB libogg.a} + {$ELSE} + {$DEFINE OGG_DYNAMIC} + const ogglib = 'libogg-0.dll'; + {$ENDIF} +{$ELSEIF DEFINED(UNIX)} + {$DEFINE OGG_DYNAMIC} + const ogglib = 'libogg.so'; +{$ELSE} + {$ERROR libogg not supported on this platform. Fix it!} +{$ENDIF} + +(***********************************************************************) +(* Header : os_types.h *) +(***********************************************************************) +type + ogg_int64_t = cint64; pogg_int64_t = ^ogg_int64_t; + ogg_int32_t = cint32; pogg_int32_t = ^ogg_int32_t; + ogg_uint32_t = cuint32; pogg_uint32_t = ^ogg_uint32_t; + ogg_int16_t = cint16; pogg_int16_t = ^ogg_int16_t; + ogg_uint16_t = cuint16; pogg_uint16_t = ^ogg_uint16_t; + + +(***********************************************************************) +(* Header : ogg.h *) +(***********************************************************************) +type + poggpack_buffer = ^oggpack_buffer; + oggpack_buffer = record + endbyte : clong; + endbit : cint; + buffer : pcuchar; + ptr : pcuchar; + storage : clong; + end; + +{ ogg_page is used to encapsulate the data in one Ogg bitstream page } + + pogg_page = ^ogg_page; + ogg_page = record + header : pcuchar; + header_len : clong; + body : pcuchar; + body_len : clong; + end; + +{ ogg_stream_state contains the current encode/decode state of a logical Ogg bitstream } + + pogg_stream_state = ^ogg_stream_state; + ogg_stream_state = record + body_data : pcuchar; { bytes from packet bodies } + body_storage : clong; { storage elements allocated } + body_fill : clong; { elements stored; fill mark } + body_returned : clong; { elements of fill returned } + + lacing_vals : pcint; { The values that will go to the segment table } + granule_vals : pogg_int64_t; { granulepos values for headers. Not compact this way, but it is simple coupled to the lacing fifo } + + lacing_storage : clong; + lacing_fill : clong; + lacing_packet : clong; + lacing_returned : clong; + + header : array[0..281] of cuchar; { working space for header encode } + header_fill : cint; + + e_o_s : cint; { set when we have buffered the last packet in the logical bitstream } + b_o_s : cint; { set after we've written the initial page of a logical bitstream } + + serialno : clong; + pageno : clong; + packetno : ogg_int64_t; { sequence number for decode; the framing knows where there's a hole in the data, + but we need coupling so that the codec (which is in a seperate abstraction layer) also knows about the gap } + granulepos : ogg_int64_t; + end; + +{ ogg_packet is used to encapsulate the data and metadata belonging to a single raw Ogg/Vorbis packet } + + pogg_packet = ^ogg_packet; + ogg_packet = record + packet : pcuchar; + bytes : clong; + b_o_s : clong; + e_o_s : clong; + + granulepos : ogg_int64_t; + packetno : ogg_int64_t; { sequence number for decode; the framing knows where there's a hole in the data, + but we need coupling so that the codec (which is in a seperate abstraction layer) also knows about the gap } + end; + + ogg_sync_state = record + data : pcuchar; + storage : cint; + fill : cint; + returned : cint; + + unsynced : cint; + headerbytes : cint; + bodybytes : cint; + end; + +{ Ogg BITSTREAM PRIMITIVES: bitstream } + +procedure oggpack_writeinit(var b: oggpack_buffer); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpack_writetrunc(var b: oggpack_buffer; bits: clong); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpack_writealign(var b: oggpack_buffer); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpack_writecopy(var b: oggpack_buffer; source: pointer; bits: clong); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpack_reset(var b: oggpack_buffer); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpack_writeclear(var b: oggpack_buffer); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpack_readinit(var b: oggpack_buffer; buf: pointer; bytes: cint); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpack_write(var b: oggpack_buffer; value: culong; bits: cint); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function oggpack_look(var b: oggpack_buffer; bits: cint): clong; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function oggpack_look1(var b: oggpack_buffer): clong; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpack_adv(var b: oggpack_buffer; bits: cint); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpack_adv1(var b: oggpack_buffer); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function oggpack_read(var b: oggpack_buffer; bits: cint): clong; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function oggpack_read1(var b: oggpack_buffer): clong; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function oggpack_bytes(var b: oggpack_buffer): clong; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function oggpack_bits(var b: oggpack_buffer): clong; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function oggpack_get_buffer(var b: oggpack_buffer): pointer; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; + +procedure oggpackB_writeinit(var b: oggpack_buffer); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpackB_writetrunc(var b: oggpack_buffer; bits: clong); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpackB_writealign(var b: oggpack_buffer); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpackB_writecopy(var b: oggpack_buffer; source: pointer; bits: clong); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpackB_reset(var b: oggpack_buffer); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpackB_writeclear(var b: oggpack_buffer); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpackB_readinit(var b: oggpack_buffer; buf: pointer; bytes: cint); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpackB_write(var b: oggpack_buffer; value: culong; bits: cint); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function oggpackB_look(var b: oggpack_buffer; bits: cint): clong; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function oggpackB_look1(var b: oggpack_buffer): clong; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpackB_adv(var b: oggpack_buffer; bits: cint); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +procedure oggpackB_adv1(var b: oggpack_buffer); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function oggpackB_read(var b: oggpack_buffer; bits: cint): clong; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function oggpackB_read1(var b: oggpack_buffer): clong; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function oggpackB_bytes(var b: oggpack_buffer): clong; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function oggpackB_bits(var b: oggpack_buffer): clong; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function oggpackB_get_buffer(var b: oggpack_buffer): pointer; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; + +{ ogglib BITSTREAM PRIMITIVES: encoding } + +function ogg_stream_packetin(var os: ogg_stream_state; var op: ogg_packet): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_stream_pageout(var os: ogg_stream_state; var op: ogg_page): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_stream_flush(var os: ogg_stream_state; var op: ogg_page): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; + +{ ogglib BITSTREAM PRIMITIVES: decoding } + +function ogg_sync_init(var oy: ogg_sync_state): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_sync_clear(var oy: ogg_sync_state): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_sync_reset(var oy: ogg_sync_state): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_sync_destroy(var oy: ogg_sync_state): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; + +function ogg_sync_buffer(var oy: ogg_sync_state; size: clong): pointer; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_sync_wrote(var oy: ogg_sync_state; bytes: clong): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_sync_pageseek(var oy: ogg_sync_state; var og: ogg_page): pointer; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_sync_pageout(var oy: ogg_sync_state; var og: ogg_page): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_stream_pagein(var os: ogg_stream_state; var og: ogg_page): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_stream_packetout(var os: ogg_stream_state; var op: ogg_packet): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_stream_packetpeek(var os: ogg_stream_state; var op: ogg_packet): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; + +{ ogglib BITSTREAM PRIMITIVES: general } + +function ogg_stream_init(var os: ogg_stream_state; serialno: cint): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_stream_clear(var os: ogg_stream_state): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_stream_reset(var os: ogg_stream_state): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_stream_reset_serialno(var os: ogg_stream_state; serialno: cint): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_stream_destroy(var os: ogg_stream_state): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_stream_eos(var os: ogg_stream_state): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; + +procedure ogg_page_checksum_set(var og: ogg_page); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; + +function ogg_page_version(var og: ogg_page): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_page_continued(var og: ogg_page): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_page_bos(var og: ogg_page): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_page_eos(var og: ogg_page): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_page_granulepos(var og: ogg_page): ogg_int64_t; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_page_serialno(var og: ogg_page): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_page_pageno(var og: ogg_page): clong; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; +function ogg_page_packets(var og: ogg_page): cint; cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; + +procedure ogg_packet_clear(var op: ogg_packet); cdecl; external {$IFDEF OGG_DYNAMIC}ogglib{$ENDIF}; + +implementation + +end. diff --git a/src/lib/vorbis/vorbis.pas b/src/lib/vorbis/vorbis.pas new file mode 100644 index 0000000..8962f50 --- /dev/null +++ b/src/lib/vorbis/vorbis.pas @@ -0,0 +1,449 @@ +{ + Translation of the vorbis headers for FreePascal + Copyright (C) 2006 by Ivo Steinmann +} + +(******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ********************************************************************) + +unit vorbis; + +{$MODE OBJFPC} +{$MINENUMSIZE 4} +{$PACKRECORDS C} + +interface + +uses + ctypes, ogg; + +{$IF DEFINED(WINDOWS)} + {$IFDEF VORBIS_WINDOZE_STATIC} + {$LINKLIB libvorbis.a} + {$LINKLIB libvorbisfile.a} + {$ELSE} + {$DEFINE OGG_DYNAMIC} + const vorbislib = 'libvorbis-0.dll'; + const vorbisfilelib = 'libvorbisfile-3.dll'; + {$ENDIF} +{$ELSEIF DEFINED(UNIX)} + {$DEFINE OGG_DYNAMIC} + const vorbislib = 'libvorbis.so'; + const vorbisfilelib = 'libvorbisfile.so'; +{$ELSE} + {$ERROR libvorbis not supported on this platform. Fix it!} +{$ENDIF} + +(***********************************************************************) +(* Header : codec.h *) +(***********************************************************************) + +type + ppcfloat = ^pcfloat; + + pvorbis_info = ^vorbis_info; + vorbis_info = record + version : cint; + channels : cint; + rate : clong; + + { The below bitrate declarations are *hints*. + Combinations of the three values carry the following implications: + + all three set to the same value: + implies a fixed rate bitstream + only nominal set: + implies a VBR stream that averages the nominal bitrate. No hard + upper/lower limit + upper and or lower set: + implies a VBR bitstream that obeys the bitrate limits. nominal + may also be set to give a nominal rate. + none set: + the coder does not care to speculate. + } + + bitrate_upper : clong; + bitrate_nominal : clong; + bitrate_lower : clong; + bitrate_window : clong; + codec_setup : pointer; + end; + +{ vorbis_dsp_state buffers the current vorbis audio analysis/synthesis state. The DSP state belongs to a specific logical bitstream } + + pvorbis_dsp_state = ^vorbis_dsp_state; + vorbis_dsp_state = record + analysisp : cint; + vi : pvorbis_info; + + pcm : ppcfloat; + pcmret : ppcfloat; + pcm_storage : cint; + pcm_current : cint; + pcm_returned : cint; + + preextrapolate : cint; + eofflag : cint; + + lW : clong; + W : clong; + nW : clong; + centerW : clong; + + granulepos : ogg_int64_t; + sequence : ogg_int64_t; + + glue_bits : ogg_int64_t; + time_bits : ogg_int64_t; + floor_bits : ogg_int64_t; + res_bits : ogg_int64_t; + + backend_state : pointer; + end; + +{ vorbis_block is a single block of data to be processed as part of + the analysis/synthesis stream; it belongs to a specific logical + bitstream, but is independant from other vorbis_blocks belonging to + that logical bitstream. } + + palloc_chain = ^alloc_chain; + alloc_chain = record + ptr : pointer; + next : palloc_chain; + end; + + pvorbis_block = ^vorbis_block; + vorbis_block = record + { necessary stream state for linking to the framing abstraction } + pcm : ppcfloat; { this is a pointer into local storage } + opb : oggpack_buffer; + + lW : clong; + W : clong; + nW : clong; + pcmend : cint; + mode : cint; + + eofflag : cint; + granulepos : ogg_int64_t; + sequence : ogg_int64_t; + vd : pvorbis_dsp_state; { For read-only access of configuration } + + { local storage to avoid remallocing; it's up to the mapping to structure it } + localstore : pointer; + localtop : clong; + localalloc : clong; + totaluse : clong; + reap : palloc_chain; + + { bitmetrics for the frame } + glue_bits : clong; + time_bits : clong; + floor_bits : clong; + res_bits : clong; + + internal : pointer; + end; + +{ vorbis_info contains all the setup information specific to the + specific compression/decompression mode in progress (eg, + psychoacoustic settings, channel setup, options, codebook + etc). vorbis_info and substructures are in backends.h. } + +{ the comments are not part of vorbis_info so that vorbis_info can be static storage } + + pvorbis_comment = ^vorbis_comment; + vorbis_comment = record + { unlimited user comment fields. libvorbis writes 'libvorbis' whatever vendor is set to in encode } + user_comments : ^pcchar; + comment_lengths : pcint; + comments : cint; + vendor : pcchar; + end; + + +{ libvorbis encodes in two abstraction layers; first we perform DSP + and produce a packet (see docs/analysis.txt). The packet is then + coded into a framed OggSquish bitstream by the second layer (see + docs/framing.txt). Decode is the reverse process; we sync/frame + the bitstream and extract individual packets, then decode the + packet back into PCM audio. + + The extra framing/packetizing is used in streaming formats, such as + files. Over the net (such as with UDP), the framing and + packetization aren't necessary as they're provided by the transport + and the streaming layer is not used } + +{ Vorbis PRIMITIVES: general } + +procedure vorbis_info_init(var vi: vorbis_info); cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +procedure vorbis_info_clear(var vi: vorbis_info); cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_info_blocksize(var vi: vorbis_info; zo: cint): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +procedure vorbis_comment_init(var vc: vorbis_comment); cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +procedure vorbis_comment_add(var vc: vorbis_comment; comment: pchar); cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +procedure vorbis_comment_add_tag(var vc: vorbis_comment; tag: pchar; contents: pchar); cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_comment_query(var vc: vorbis_comment; tag: pchar; count: cint): pchar; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_comment_query_count(var vc: vorbis_comment; tag: pchar): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +procedure vorbis_comment_clear(var vc: vorbis_comment); cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; + +function vorbis_block_init(var v: vorbis_dsp_state; var vb: vorbis_block): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_block_clear(var vb: vorbis_block): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +procedure vorbis_dsp_clear(var v: vorbis_dsp_state); cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_granule_time(var v: vorbis_dsp_state; granulepos: ogg_int64_t): cdouble; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; + +{ vorbislib PRIMITIVES: analysis/DSP layer } + +function vorbis_analysis_init(var v: vorbis_dsp_state; var vi: vorbis_info): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_commentheader_out(var vc: vorbis_comment; var op: ogg_packet): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_analysis_headerout(var v:vorbis_dsp_state; var vc: vorbis_comment; var op: ogg_packet; var op_comm: ogg_packet; var op_code: ogg_packet): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_analysis_buffer(var v: vorbis_dsp_state; vals: cint): ppcfloat; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_analysis_wrote(var v: vorbis_dsp_state; vals: cint): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_analysis_blockout(var v: vorbis_dsp_state; var vb: vorbis_block): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_analysis(var vb: vorbis_block; var op: ogg_packet): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; + +function vorbis_bitrate_addblock(var vb: vorbis_block): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_bitrate_flushpacket(var vd: vorbis_dsp_state; var op: ogg_packet): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; + +{ vorbislib PRIMITIVES: synthesis layer } + +function vorbis_synthesis_headerin(var vi: vorbis_info; var vc: vorbis_comment; var op: ogg_packet): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; + +function vorbis_synthesis_init(var v: vorbis_dsp_state; var vi: vorbis_info): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_synthesis_restart(var v: vorbis_dsp_state): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_synthesis(var vb: vorbis_block; var op: ogg_packet): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_synthesis_trackonly(var vb: vorbis_block; var op: ogg_packet): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_synthesis_blockin(var v: vorbis_dsp_state; var vb: vorbis_block): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_synthesis_pcmout(var v: vorbis_dsp_state; var pcm: ppcfloat): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_synthesis_lapout(var v: vorbis_dsp_state; var pcm: ppcfloat): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_synthesis_read(var v: vorbis_dsp_state; samples: cint): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_packet_blocksize(var vi: vorbis_info; var op: ogg_packet): clong; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; + +function vorbis_synthesis_halfrate(var v: vorbis_info; flag: cint): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; +function vorbis_synthesis_halfrate_p(var v: vorbis_info): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbislib{$ENDIF}; + +{ vorbislib ERRORS and return codes } +Const + OV_FALSE = -1; + OV_EOF = -2; + OV_HOLE = -3; + + OV_EREAD = -128; + OV_EFAULT = -129; + OV_EIMPL = -130; + OV_EINVAL = -131; + OV_ENOTVORBIS = -132; + OV_EBADHEADER = -133; + OV_EVERSION = -134; + OV_ENOTAUDIO = -135; + OV_EBADPACKET = -136; + OV_EBADLINK = -137; + OV_ENOSEEK = -138; + + +(***********************************************************************) +(* Header : vorbisfile.h *) +(***********************************************************************) + +type + +{* The function prototypes for the callbacks are basically the same as for + + * the stdio functions fread, fseek, fclose, ftell. + * The one difference is that the FILE * arguments have been replaced with + * a void * - this is to be used as a pointer to whatever internal data these + * functions might need. In the stdio case, it's just a FILE * cast to a void * + * + * If you use other functions, check the docs for these functions and return + * the right values. For seek_func(), you *MUST* return -1 if the stream is + * unseekable + *} + + read_func = function(ptr: pointer; size, nmemb: csize_t; datasource: pointer): csize_t; cdecl; + seek_func = function(datasource: pointer; offset: ogg_int64_t; whence: cint): cint; cdecl; + close_func = function(datasource: pointer): cint; cdecl; + tell_func = function(datasource: pointer): clong; cdecl; + + pov_callbacks = ^ov_callbacks; + ov_callbacks = record + read : read_func; + seek : seek_func; + close : close_func; + tell : tell_func; + end; + +const + NOTOPEN = 0; + PARTOPEN = 1; + OPENED = 2; + STREAMSET = 3; + INITSET = 4; + +type + POggVorbis_File = ^OggVorbis_File; + OggVorbis_File = record + datasource : pointer; { pointer to a FILE *, etc. } + seekable : cint; + offset : ogg_int64_t; + end_ : ogg_int64_t; + oy : ogg_sync_state; + + { If the FILE handle isn't seekable (eg, a pipe), only the current stream appears } + links : cint; + offsets : pogg_int64_t; + dataoffsets : pogg_int64_t; + serialnos : pclong; + pcmlengths : pogg_int64_t; { overloaded to maintain binary compatability; x2 size, stores both beginning and end values } + vi : pvorbis_info; + vc : pvorbis_comment; + + { Decoding working state local storage } + pcm_offset : ogg_int64_t; + ready_state : cint; + current_serialno: clong; + current_link : cint; + + bittrack : cdouble; + samptrack : cdouble; + + os : ogg_stream_state; { take physical pages, weld into a logical stream of packets } + vd : vorbis_dsp_state; { central working state for the packet->PCM decoder } + vb : vorbis_block; { local working space for packet->PCM decode } + + callbacks : ov_callbacks; + end; + + +function ov_clear(var vf: OggVorbis_File): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_open(f: pointer; var vf: OggVorbis_File; initial: pointer; ibytes: clong): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_open_callbacks(datasource: pointer; var vf: OggVorbis_File; initial: pointer; ibytes: clong; callbacks: ov_callbacks): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; + +function ov_test(f: pointer; var vf: OggVorbis_File; initial: pointer; ibytes: clong): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_test_callbacks(datasource: pointer; var vf: OggVorbis_File; initial: pointer; ibytes: clong; callbacks: ov_callbacks): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_test_open(var vf: OggVorbis_File): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; + +function ov_bitrate(var vf: OggVorbis_File; i: cint): clong; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_bitrate_instant(var vf: OggVorbis_File): clong; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_streams(var vf: OggVorbis_File): clong; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_seekable(var vf: OggVorbis_File): clong; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_serialnumber(var vf: OggVorbis_File; i: cint): clong; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; + +function ov_raw_total(var vf: OggVorbis_File; i: cint): ogg_int64_t; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_pcm_total(var vf: OggVorbis_File; i: cint): ogg_int64_t; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_time_total(var vf: OggVorbis_File; i: cint): cdouble; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; + +function ov_raw_seek(var vf: OggVorbis_File; pos: ogg_int64_t): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_pcm_seek(var vf: OggVorbis_File; pos: ogg_int64_t): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_pcm_seek_page(var vf: OggVorbis_File; pos: ogg_int64_t): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_time_seek(var vf: OggVorbis_File; pos: cdouble): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_time_seek_page(var vf: OggVorbis_File; pos: cdouble): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; + +function ov_raw_seek_lap(var vf: OggVorbis_File; pos: ogg_int64_t): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_pcm_seek_lap(var vf: OggVorbis_File; pos: ogg_int64_t): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_pcm_seek_page_lap(var vf: OggVorbis_File; pos: ogg_int64_t): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_time_seek_lap(var vf: OggVorbis_File; pos: cdouble): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_time_seek_page_lap(var vf: OggVorbis_File; pos: cdouble): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; + +function ov_raw_tell(var vf: OggVorbis_File): ogg_int64_t; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_pcm_tell(var vf: OggVorbis_File): ogg_int64_t; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_time_tell(var vf: OggVorbis_File): cdouble; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; + +function ov_info(var vf: OggVorbis_File; link: cint): pvorbis_info; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_comment(var vf: OggVorbis_File; link: cint): pvorbis_comment; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; + +function ov_read_float(var vf: OggVorbis_File; var pcm_channels: ppcfloat; samples: cint; bitstream: pcint): clong; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_read(var vf: OggVorbis_File; buffer: pointer; length: cint; bigendianp: cbool; word: cint; sgned: cbool; bitstream: pcint): clong; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_crosslap(var vf1: OggVorbis_File; var vf2: OggVorbis_File): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; + +function ov_halfrate(var vf: OggVorbis_File; flag: cint): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; +function ov_halfrate_p(var vf: OggVorbis_File): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisfilelib{$ENDIF}; + + +{ + Developer of the A52 helpers for FreePascal + Copyright (C) 2006 by Ivo Steinmann +} + +function ov_read_ext(var vf: OggVorbis_File; buffer: pointer; length: cint; bigendianp: cbool; word: cint; sgned: cbool): clong; + +(***********************************************************************) +(* Header : vorbisenc.h *) +(***********************************************************************) + +const + OV_ECTL_RATEMANAGE_GET = $10; + + OV_ECTL_RATEMANAGE_SET = $11; + OV_ECTL_RATEMANAGE_AVG = $12; + OV_ECTL_RATEMANAGE_HARD = $13; + + OV_ECTL_LOWPASS_GET = $20; + OV_ECTL_LOWPASS_SET = $21; + + OV_ECTL_IBLOCK_GET = $30; + OV_ECTL_IBLOCK_SET = $31; + +type + povectl_ratemanage_arg = ^ovectl_ratemanage_arg; + ovectl_ratemanage_arg = record + management_active : cint; + + bitrate_hard_min : clong; + bitrate_hard_max : clong; + bitrate_hard_window : cdouble; + + bitrate_av_lo : clong; + bitrate_av_hi : clong; + bitrate_av_window : cdouble; + bitrate_av_window_center : cdouble; + end; + +// function vorbis_encode_init(var vi: vorbis_info; channels, rate, max_bitrate, nominal_bitrate, min_bitrate: clong): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisenclib{$ENDIF}; +// function vorbis_encode_setup_managed(var vi: vorbis_info; channels, rate, max_bitrate, nominal_bitrate, min_bitrate: clong): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisenclib{$ENDIF}; +// function vorbis_encode_setup_vbr(var vi: vorbis_info; channels, rate: clong; quality: cfloat): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisenclib{$ENDIF}; +// (* quality level from 0. (lo) to 1. (hi) *) +// function vorbis_encode_init_vbr(var vi: vorbis_info; channels, rate: clong; base_quality: cfloat): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisenclib{$ENDIF}; +// function vorbis_encode_setup_init(var vi: vorbis_info): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisenclib{$ENDIF}; +// function vorbis_encode_ctl(var vi: vorbis_info; number: cint; arg: pointer): cint; cdecl; external {$IFDEF OGG_DYNAMIC}vorbisenclib{$ENDIF}; + +implementation + +function ov_read_ext(var vf: OggVorbis_File; buffer: pointer; length: cint; bigendianp: cbool; word: cint; sgned: cbool): clong; +var + ofs: cint; + Num: cint; + Res: cint; +begin + // check blocksize here! + {if length mod 4 <> 0 then + Exit(0);} + + ofs := 0; + num := length; + + while num > 0 do + begin + res := ov_read(vf, buffer + ofs, num, bigendianp, word, sgned, nil); + if res < 0 then + Exit(res); + + if res = 0 then + Break; + + ofs := ofs + res; + num := num - res; + end; + + Result := ofs; +end; + +end. + -- 2.29.2