unit uos_libxmp;
{This unit is part of United Openlibraries of Sound (uos)}

{This is the Pascal Wrapper + Dynamic loading of libxmp library.
 Load library with xmp_load() and release with xmp_unload().
 License : modified LGPL. 
  by Fred vS | fiens@hotmail.com | 2024}

{$mode objfpc}{$H+}
{$PACKRECORDS C}

interface

uses
  dynlibs,
  sysutils,
  CTypes;

const
  XMP_VERSION     = '4.6.0';
  XMP_VERCODE     = $040600;
  XMP_VER_MAJOR   = 4;
  XMP_VER_MINOR   = 6;
  XMP_VER_RELEASE = 0;

const
{$IFDEF windows}
  XMP_LIB_NAME = 'libxmp.dll'; 
{$ENDIF}
 
{$IFDEF unix} 
 {$IFDEF darwin}
  XMP_LIB_NAME = 'libxmp.dylib'; 
 {$ELSE}
  XMP_LIB_NAME    = 'libxmp.so.4.6.0';
 {$ENDIF}
{$ENDIF}

const
  XMP_NAME_SIZE     = 64;

  // Note event constants
  XMP_KEY_OFF       = $81;
  XMP_KEY_CUT       = $82;
  XMP_KEY_FADE      = $83;

  // Sample format flags
  XMP_FORMAT_8BIT   = 1 shl 0;
  XMP_FORMAT_UNSIGNED = 1 shl 1;
  XMP_FORMAT_MONO   = 1 shl 2;

  // Player parameters
  XMP_PLAYER_AMP    = 0;
  XMP_PLAYER_MIX    = 1;
  XMP_PLAYER_INTERP = 2;
  XMP_PLAYER_DSP    = 3;
  XMP_PLAYER_FLAGS  = 4;
  XMP_PLAYER_CFLAGS = 5;
  XMP_PLAYER_SMPCTL = 6;
  XMP_PLAYER_VOLUME = 7;
  XMP_PLAYER_STATE  = 8;
  XMP_PLAYER_SMIX_VOLUME = 9;
  XMP_PLAYER_DEFPAN = 10;
  XMP_PLAYER_MODE   = 11;
  XMP_PLAYER_MIXER_TYPE = 12;
  XMP_PLAYER_VOICES = 13;

  // Interpolation types
  XMP_INTERP_NEAREST = 0;
  XMP_INTERP_LINEAR = 1;
  XMP_INTERP_SPLINE = 2;

  // DSP effect types
  XMP_DSP_LOWPASS   = 1 shl 0;
  XMP_DSP_ALL       = XMP_DSP_LOWPASS;

  // Player state
  XMP_STATE_UNLOADED = 0;
  XMP_STATE_LOADED  = 1;
  XMP_STATE_PLAYING = 2;

  // Player flags
  XMP_FLAGS_VBLANK  = 1 shl 0;
  XMP_FLAGS_FX9BUG  = 1 shl 1;
  XMP_FLAGS_FIXLOOP = 1 shl 2;
  XMP_FLAGS_A500    = 1 shl 3;

  // Player modes
  XMP_MODE_AUTO     = 0;
  XMP_MODE_MOD      = 1;
  XMP_MODE_NOISETRACKER = 2;
  XMP_MODE_PROTRACKER = 3;
  XMP_MODE_S3M      = 4;
  XMP_MODE_ST3      = 5;
  XMP_MODE_ST3GUS   = 6;
  XMP_MODE_XM       = 7;
  XMP_MODE_FT2      = 8;
  XMP_MODE_IT       = 9;
  XMP_MODE_ITSMP    = 10;

  // Mixer types
  XMP_MIXER_STANDARD = 0;
  XMP_MIXER_A500    = 1;
  XMP_MIXER_A500F   = 2;

  // Sample flags
  XMP_SMPCTL_SKIP   = 1 shl 0;

  // Limits
  XMP_MAX_KEYS      = 121;
  XMP_MAX_ENV_POINTS = 32;
  XMP_MAX_MOD_LENGTH = 256;
  XMP_MAX_CHANNELS  = 64;
  XMP_MAX_SRATE     = 49170;
  XMP_MIN_SRATE     = 4000;
  XMP_MIN_BPM       = 20;
  XMP_MAX_FRAMESIZE = 5 * XMP_MAX_SRATE * 2 div XMP_MIN_BPM;

  // Error codes
  XMP_END           = 1;
  XMP_ERROR_INTERNAL = 2;
  XMP_ERROR_FORMAT  = 3;
  XMP_ERROR_LOAD    = 4;
  XMP_ERROR_DEPACK  = 5;
  XMP_ERROR_SYSTEM  = 6;
  XMP_ERROR_INVALID = 7;
  XMP_ERROR_STATE   = 8;

type
  xmp_context = PChar;

type
  xmp_channel = record
    pan: integer;   // Pan de canal (0x80 centrum)
    vol: integer;   // Volume of canal
    flg: integer;   // Flags of canal
  end;

  xmp_pattern = record
    rows: integer;
    index: array[1..1] of integer;
  end;

  xmp_event = record
    note: byte;
    ins: byte;
    vol: byte;
    fxt: byte;
    fxp: byte;
    f2t: byte;
    f2p: byte;
    _flag: byte;
  end;

  xmp_track = record
    rows: integer;
    event: array[1..1] of xmp_event;
  end;

  xmp_envelope = record
    flg: integer;
    npt: integer;
    scl: integer;
    sus: integer;
    sue: integer;
    lps: integer;
    lpe: integer;
    Data: array[1..XMP_MAX_ENV_POINTS * 2] of smallint;
  end;

  xmp_subinstrument = record
    vol: integer;
    gvl: integer;
    pan: integer;
    xpo: integer;
    fin: integer;
    vwf: integer;
    vde: integer;
    vra: integer;
    vsw: integer;
    rvv: integer;
    sid: integer;
    nna: integer;
    dct: integer;
    dca: integer;
    ifc: integer;
    ifr: integer;
  end;

  xmp_instrument = record
    Name: array[0..31] of char;
    vol: integer;
    nsm: integer;
    rls: integer;
    aei: xmp_envelope;
    pei: xmp_envelope;
    fei: xmp_envelope;
    map: array[0..XMP_MAX_KEYS] of record
      ins: byte;
      xpo: shortint;
    end;
    sub: ^xmp_subinstrument;
    extra: Pointer;
  end;

  xmp_sample = record
    Name: array[0..31] of char;
    len: integer;
    lps: integer;
    lpe: integer;
    flg: integer;
    Data: PByte;
  end;

  xmp_sequence = record
    entry_point: integer;
    duration: integer;
  end;

  xmp_module = record
    Name: array[0..XMP_NAME_SIZE - 1] of char;
    typ: array[0..XMP_NAME_SIZE - 1] of char;
    pat: integer;
    trk: integer;
    chn: integer;
    ins: integer;
    smp: integer;
    spd: integer;
    bpm: integer;
    len: integer;
    rst: integer;
    gvl: integer;
    xxp: ^xmp_pattern;
    xxt: ^xmp_track;
    xxi: ^xmp_instrument;
    xxs: ^xmp_sample;
    xxc: array[0..XMP_MAX_CHANNELS - 1] of xmp_channel;
    xxo: array[0..XMP_MAX_MOD_LENGTH] of byte;
  end;

  xmp_test_info = record
    Name: array[0..XMP_NAME_SIZE - 1] of char;
    type_: array[0..XMP_NAME_SIZE - 1] of char;
  end;

  xmp_module_info = record
    md5: array[0..15] of byte;
    vol_base: integer;
    module: ^xmp_module;
    comment: PChar;
    num_sequences: integer;
    seq_data: ^xmp_sequence;
  end;

  xmp_channel_info = record
    period: longword;
    position: longword;
    pitchbend: smallint;
    note: byte;
    instrument: byte;
    sample: byte;
    volume: byte;
    pan: byte;
    reserved: byte;
    event: xmp_event;
  end;

  xmp_frame_info = record
    pos: integer;
    pattern: integer;
    row: integer;
    num_rows: integer;
    frame: integer;
    speed: integer;
    bpm: integer;
    time: integer;
    total_time: integer;
    frame_time: integer;
    buffer: Pointer;
    buffer_size: integer;
    total_size: integer;
    volume: integer;
    loop_count: integer;
    virt_channels: integer;
    virt_used: integer;
    sequence: integer;
    channel_info: array[0..XMP_MAX_CHANNELS - 1] of xmp_channel_info;
  end;

  xmp_callbacks = record
    read_func: function(dest: Pointer; len, nmemb: longword; priv: Pointer): longword;
    seek_func: function(priv: Pointer; offset: longint; whence: integer): integer;
    tell_func: function(priv: Pointer): longint;
    close_func: function(priv: Pointer): integer;
  end;

  { Dynamic load : Vars that will hold our dynamically loaded functions...

   *************************** functions ******************************* }

var
  xmp_create_context: function: pointer; cdecl;
  xmp_free_context: procedure(ctx: Pointer); cdecl;
  xmp_load_module: function(ctx: Pointer; const filename: PChar): Integer; cdecl;
  xmp_load_module_from_memory: function(ctx: xmp_context; const Data: Pointer; size: longint): integer; cdecl;
  xmp_load_module_from_file: function(ctx: xmp_context; file_: Pointer; size: longint): integer; cdecl;
  xmp_load_module_from_callbacks: function(ctx: xmp_context; file_: Pointer; callbacks: xmp_callbacks): integer; cdecl;
  xmp_test_module: function(const filename: PChar; info: xmp_test_info): Integer; cdecl; 
  xmp_release_module: procedure(ctx: xmp_context); cdecl;
  xmp_start_player: function(ctx: xmp_context; rate: Integer; flags: Integer): Integer; cdecl; 
  xmp_play_buffer: function(ctx: xmp_context; buffer: Pointer; size: Integer; loop: Integer): Integer; cdecl;
  xmp_get_frame_info: procedure(ctx: xmp_context; var info: xmp_frame_info); cdecl;
  xmp_end_player: procedure(ctx: xmp_context); cdecl; 
  xmp_get_module_info: procedure(ctx: xmp_context; var info: xmp_module_info); cdecl; 
  xmp_get_format_list: function(): PAnsiChar; cdecl; 
  xmp_stop_module: procedure(ctx: xmp_context); cdecl; 
  xmp_restart_module: procedure(ctx: xmp_context); cdecl; 
  xmp_channel_vol: function(ctx: xmp_context; channel: Integer; volume: Integer): Integer; cdecl;
  xmp_set_player: function(ctx: xmp_context; param: Integer; value: Integer): Integer; cdecl;

// Not used yet...
//function xmp_test_module_from_memory(const data: Pointer; size: LongInt; info: xmp_test_info): Integer; cdecl; external 'xmp';
//function xmp_test_module_from_file(file_: Pointer; info: xmp_test_info): Integer; cdecl; external 'xmp';
//function xmp_test_module_from_callbacks(file_: Pointer; callbacks: xmp_callbacks; info: xmp_test_info): Integer; cdecl; external 'xmp';
//procedure xmp_scan_module(ctx: xmp_context); cdecl; external 'xmp';
//function xmp_play_frame(ctx: xmp_context): Integer; cdecl; external 'xmp';
//procedure xmp_inject_event(ctx: xmp_context; channel: Integer; var event: xmp_event); cdecl; external 'xmp';//function xmp_next_position(ctx: xmp_context): Integer; cdecl; external 'xmp';
//function xmp_prev_position(ctx: xmp_context): Integer; cdecl; external 'xmp';
//function xmp_set_position(ctx: xmp_context; pos: Integer): Integer; cdecl; external 'xmp';
//function xmp_set_row(ctx: xmp_context; row: Integer): Integer; cdecl; external 'xmp';
//function xmp_set_tempo_factor(ctx: xmp_context; factor: Double): Integer; cdecl; external 'xmp';//function xmp_seek_time(ctx: xmp_context; time: Integer): Integer; cdecl; external 'xmp';
//function xmp_channel_mute(ctx: xmp_context; channel: Integer; mute: Integer): Integer; cdecl; external 'xmp';//function xmp_get_player(ctx: xmp_context; param: Integer): Integer; cdecl; external 'xmp';
//function xmp_set_instrument_path(ctx: xmp_context; const path: PChar): Integer; cdecl; external 'xmp';


{Special function for dynamic loading of lib ...}

var
  xmp_Handle: TLibHandle = dynlibs.NilHandle;
  {$if defined(cpu32) and defined(windows)} // try load dependency if not in /windows/system32/
  gc_Handle :TLibHandle=dynlibs.NilHandle;
  {$endif}
var
  ReferenceCounter: cardinal = 0;  // Reference counter

function xmp_IsLoaded: Boolean; inline;

function xmp_Load(const libfilename: string): Boolean; // load the lib

procedure xmp_Unload(); // unload and frees the lib from memory : do not forget to call it before close application.

implementation

function xmp_IsLoaded: boolean;
begin
 Result := (xmp_Handle <> dynlibs.NilHandle);
end;

Function xmp_Load(const libfilename:string) :boolean;
var
thelib, thelibgcc: string; 
begin
  Result := False;
  if xmp_Handle<>0 then 
begin
 Inc(ReferenceCounter);
 result:=true {is it already there ?}
end  else 
begin 
 {$if defined(cpu32) and defined(windows)}
  if Length(libfilename) = 0 then thelibgcc := 'libgcc_s_dw2-1.dll' else
   thelibgcc := IncludeTrailingBackslash(ExtractFilePath(libfilename)) + 'libgcc_s_dw2-1.dll';
  gc_Handle:= DynLibs.SafeLoadLibrary(thelibgcc);
 {$endif}

{go & load the library}
   if Length(libfilename) = 0 then thelib := XMP_LIB_NAME else thelib := libfilename;
    xmp_Handle:=DynLibs.LoadLibrary(thelib); // obtain the handle we want
  	if xmp_Handle <> DynLibs.NilHandle then
begin {now we tie the functions to the VARs from above}

Pointer(xmp_create_context):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_create_context'));
Pointer(xmp_free_context):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_free_context'));
Pointer(xmp_load_module):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_load_module'));
Pointer(xmp_load_module_from_memory):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_load_module_from_memory'));
Pointer(xmp_load_module_from_file):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_load_module_from_file'));
Pointer(xmp_load_module_from_callbacks):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_load_module_from_callbacks'));
Pointer(xmp_test_module):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_test_module'));
Pointer(xmp_release_module):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_release_module'));
Pointer(xmp_start_player):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_start_player'));
Pointer(xmp_play_buffer):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_play_buffer'));
Pointer(xmp_get_frame_info):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_get_frame_info'));
Pointer(xmp_end_player):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_end_player'));
Pointer(xmp_get_module_info):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_get_module_info'));
Pointer(xmp_get_format_list):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_get_format_list'));
Pointer(xmp_stop_module):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_stop_module'));
Pointer(xmp_restart_module):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_restart_module'));
Pointer(xmp_channel_vol):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_channel_vol'));
Pointer(xmp_set_player):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_set_player'));
end;
   Result := xmp_IsLoaded;
   ReferenceCounter:=1;   
end;

end;

Procedure xmp_Unload;
begin
  if ReferenceCounter > 0 then
    dec(ReferenceCounter);
  if ReferenceCounter > 0 then
    exit;
  if xmp_IsLoaded then
  begin
    DynLibs.UnloadLibrary(xmp_Handle);
    xmp_Handle:=DynLibs.NilHandle;
    {$if defined(cpu32) and defined(windows)}
    if gc_Handle <> DynLibs.NilHandle then begin
    DynLibs.UnloadLibrary(gc_Handle);
    gc_Handle:=DynLibs.NilHandle;
    end;
    {$endif}
  end;
end;


end.

