1 | unit uos_libxmp;
|
---|
2 | {This unit is part of United Openlibraries of Sound (uos)}
|
---|
3 |
|
---|
4 | {This is the Pascal Wrapper + Dynamic loading of libxmp library.
|
---|
5 | Load library with xmp_load() and release with xmp_unload().
|
---|
6 | License : modified LGPL.
|
---|
7 | by Fred vS | fiens@hotmail.com | 2024}
|
---|
8 |
|
---|
9 | {$mode objfpc}{$H+}
|
---|
10 | {$PACKRECORDS C}
|
---|
11 |
|
---|
12 | interface
|
---|
13 |
|
---|
14 | uses
|
---|
15 | dynlibs,
|
---|
16 | sysutils,
|
---|
17 | CTypes;
|
---|
18 |
|
---|
19 | const
|
---|
20 | XMP_VERSION = '4.6.0';
|
---|
21 | XMP_VERCODE = $040600;
|
---|
22 | XMP_VER_MAJOR = 4;
|
---|
23 | XMP_VER_MINOR = 6;
|
---|
24 | XMP_VER_RELEASE = 0;
|
---|
25 |
|
---|
26 | const
|
---|
27 | {$IFDEF windows}
|
---|
28 | XMP_LIB_NAME = 'libxmp.dll';
|
---|
29 | {$ENDIF}
|
---|
30 |
|
---|
31 | {$IFDEF unix}
|
---|
32 | {$IFDEF darwin}
|
---|
33 | XMP_LIB_NAME = 'libxmp.dylib';
|
---|
34 | {$ELSE}
|
---|
35 | XMP_LIB_NAME = 'libxmp.so.4.6.0';
|
---|
36 | {$ENDIF}
|
---|
37 | {$ENDIF}
|
---|
38 |
|
---|
39 | const
|
---|
40 | XMP_NAME_SIZE = 64;
|
---|
41 |
|
---|
42 | // Note event constants
|
---|
43 | XMP_KEY_OFF = $81;
|
---|
44 | XMP_KEY_CUT = $82;
|
---|
45 | XMP_KEY_FADE = $83;
|
---|
46 |
|
---|
47 | // Sample format flags
|
---|
48 | XMP_FORMAT_8BIT = 1 shl 0;
|
---|
49 | XMP_FORMAT_UNSIGNED = 1 shl 1;
|
---|
50 | XMP_FORMAT_MONO = 1 shl 2;
|
---|
51 |
|
---|
52 | // Player parameters
|
---|
53 | XMP_PLAYER_AMP = 0;
|
---|
54 | XMP_PLAYER_MIX = 1;
|
---|
55 | XMP_PLAYER_INTERP = 2;
|
---|
56 | XMP_PLAYER_DSP = 3;
|
---|
57 | XMP_PLAYER_FLAGS = 4;
|
---|
58 | XMP_PLAYER_CFLAGS = 5;
|
---|
59 | XMP_PLAYER_SMPCTL = 6;
|
---|
60 | XMP_PLAYER_VOLUME = 7;
|
---|
61 | XMP_PLAYER_STATE = 8;
|
---|
62 | XMP_PLAYER_SMIX_VOLUME = 9;
|
---|
63 | XMP_PLAYER_DEFPAN = 10;
|
---|
64 | XMP_PLAYER_MODE = 11;
|
---|
65 | XMP_PLAYER_MIXER_TYPE = 12;
|
---|
66 | XMP_PLAYER_VOICES = 13;
|
---|
67 |
|
---|
68 | // Interpolation types
|
---|
69 | XMP_INTERP_NEAREST = 0;
|
---|
70 | XMP_INTERP_LINEAR = 1;
|
---|
71 | XMP_INTERP_SPLINE = 2;
|
---|
72 |
|
---|
73 | // DSP effect types
|
---|
74 | XMP_DSP_LOWPASS = 1 shl 0;
|
---|
75 | XMP_DSP_ALL = XMP_DSP_LOWPASS;
|
---|
76 |
|
---|
77 | // Player state
|
---|
78 | XMP_STATE_UNLOADED = 0;
|
---|
79 | XMP_STATE_LOADED = 1;
|
---|
80 | XMP_STATE_PLAYING = 2;
|
---|
81 |
|
---|
82 | // Player flags
|
---|
83 | XMP_FLAGS_VBLANK = 1 shl 0;
|
---|
84 | XMP_FLAGS_FX9BUG = 1 shl 1;
|
---|
85 | XMP_FLAGS_FIXLOOP = 1 shl 2;
|
---|
86 | XMP_FLAGS_A500 = 1 shl 3;
|
---|
87 |
|
---|
88 | // Player modes
|
---|
89 | XMP_MODE_AUTO = 0;
|
---|
90 | XMP_MODE_MOD = 1;
|
---|
91 | XMP_MODE_NOISETRACKER = 2;
|
---|
92 | XMP_MODE_PROTRACKER = 3;
|
---|
93 | XMP_MODE_S3M = 4;
|
---|
94 | XMP_MODE_ST3 = 5;
|
---|
95 | XMP_MODE_ST3GUS = 6;
|
---|
96 | XMP_MODE_XM = 7;
|
---|
97 | XMP_MODE_FT2 = 8;
|
---|
98 | XMP_MODE_IT = 9;
|
---|
99 | XMP_MODE_ITSMP = 10;
|
---|
100 |
|
---|
101 | // Mixer types
|
---|
102 | XMP_MIXER_STANDARD = 0;
|
---|
103 | XMP_MIXER_A500 = 1;
|
---|
104 | XMP_MIXER_A500F = 2;
|
---|
105 |
|
---|
106 | // Sample flags
|
---|
107 | XMP_SMPCTL_SKIP = 1 shl 0;
|
---|
108 |
|
---|
109 | // Limits
|
---|
110 | XMP_MAX_KEYS = 121;
|
---|
111 | XMP_MAX_ENV_POINTS = 32;
|
---|
112 | XMP_MAX_MOD_LENGTH = 256;
|
---|
113 | XMP_MAX_CHANNELS = 64;
|
---|
114 | XMP_MAX_SRATE = 49170;
|
---|
115 | XMP_MIN_SRATE = 4000;
|
---|
116 | XMP_MIN_BPM = 20;
|
---|
117 | XMP_MAX_FRAMESIZE = 5 * XMP_MAX_SRATE * 2 div XMP_MIN_BPM;
|
---|
118 |
|
---|
119 | // Error codes
|
---|
120 | XMP_END = 1;
|
---|
121 | XMP_ERROR_INTERNAL = 2;
|
---|
122 | XMP_ERROR_FORMAT = 3;
|
---|
123 | XMP_ERROR_LOAD = 4;
|
---|
124 | XMP_ERROR_DEPACK = 5;
|
---|
125 | XMP_ERROR_SYSTEM = 6;
|
---|
126 | XMP_ERROR_INVALID = 7;
|
---|
127 | XMP_ERROR_STATE = 8;
|
---|
128 |
|
---|
129 | type
|
---|
130 | xmp_context = PChar;
|
---|
131 |
|
---|
132 | type
|
---|
133 | xmp_channel = record
|
---|
134 | pan: integer; // Pan de canal (0x80 centrum)
|
---|
135 | vol: integer; // Volume of canal
|
---|
136 | flg: integer; // Flags of canal
|
---|
137 | end;
|
---|
138 |
|
---|
139 | xmp_pattern = record
|
---|
140 | rows: integer;
|
---|
141 | index: array[1..1] of integer;
|
---|
142 | end;
|
---|
143 |
|
---|
144 | xmp_event = record
|
---|
145 | note: byte;
|
---|
146 | ins: byte;
|
---|
147 | vol: byte;
|
---|
148 | fxt: byte;
|
---|
149 | fxp: byte;
|
---|
150 | f2t: byte;
|
---|
151 | f2p: byte;
|
---|
152 | _flag: byte;
|
---|
153 | end;
|
---|
154 |
|
---|
155 | xmp_track = record
|
---|
156 | rows: integer;
|
---|
157 | event: array[1..1] of xmp_event;
|
---|
158 | end;
|
---|
159 |
|
---|
160 | xmp_envelope = record
|
---|
161 | flg: integer;
|
---|
162 | npt: integer;
|
---|
163 | scl: integer;
|
---|
164 | sus: integer;
|
---|
165 | sue: integer;
|
---|
166 | lps: integer;
|
---|
167 | lpe: integer;
|
---|
168 | Data: array[1..XMP_MAX_ENV_POINTS * 2] of smallint;
|
---|
169 | end;
|
---|
170 |
|
---|
171 | xmp_subinstrument = record
|
---|
172 | vol: integer;
|
---|
173 | gvl: integer;
|
---|
174 | pan: integer;
|
---|
175 | xpo: integer;
|
---|
176 | fin: integer;
|
---|
177 | vwf: integer;
|
---|
178 | vde: integer;
|
---|
179 | vra: integer;
|
---|
180 | vsw: integer;
|
---|
181 | rvv: integer;
|
---|
182 | sid: integer;
|
---|
183 | nna: integer;
|
---|
184 | dct: integer;
|
---|
185 | dca: integer;
|
---|
186 | ifc: integer;
|
---|
187 | ifr: integer;
|
---|
188 | end;
|
---|
189 |
|
---|
190 | xmp_instrument = record
|
---|
191 | Name: array[0..31] of char;
|
---|
192 | vol: integer;
|
---|
193 | nsm: integer;
|
---|
194 | rls: integer;
|
---|
195 | aei: xmp_envelope;
|
---|
196 | pei: xmp_envelope;
|
---|
197 | fei: xmp_envelope;
|
---|
198 | map: array[0..XMP_MAX_KEYS] of record
|
---|
199 | ins: byte;
|
---|
200 | xpo: shortint;
|
---|
201 | end;
|
---|
202 | sub: ^xmp_subinstrument;
|
---|
203 | extra: Pointer;
|
---|
204 | end;
|
---|
205 |
|
---|
206 | xmp_sample = record
|
---|
207 | Name: array[0..31] of char;
|
---|
208 | len: integer;
|
---|
209 | lps: integer;
|
---|
210 | lpe: integer;
|
---|
211 | flg: integer;
|
---|
212 | Data: PByte;
|
---|
213 | end;
|
---|
214 |
|
---|
215 | xmp_sequence = record
|
---|
216 | entry_point: integer;
|
---|
217 | duration: integer;
|
---|
218 | end;
|
---|
219 |
|
---|
220 | xmp_module = record
|
---|
221 | Name: array[0..XMP_NAME_SIZE - 1] of char;
|
---|
222 | typ: array[0..XMP_NAME_SIZE - 1] of char;
|
---|
223 | pat: integer;
|
---|
224 | trk: integer;
|
---|
225 | chn: integer;
|
---|
226 | ins: integer;
|
---|
227 | smp: integer;
|
---|
228 | spd: integer;
|
---|
229 | bpm: integer;
|
---|
230 | len: integer;
|
---|
231 | rst: integer;
|
---|
232 | gvl: integer;
|
---|
233 | xxp: ^xmp_pattern;
|
---|
234 | xxt: ^xmp_track;
|
---|
235 | xxi: ^xmp_instrument;
|
---|
236 | xxs: ^xmp_sample;
|
---|
237 | xxc: array[0..XMP_MAX_CHANNELS - 1] of xmp_channel;
|
---|
238 | xxo: array[0..XMP_MAX_MOD_LENGTH] of byte;
|
---|
239 | end;
|
---|
240 |
|
---|
241 | xmp_test_info = record
|
---|
242 | Name: array[0..XMP_NAME_SIZE - 1] of char;
|
---|
243 | type_: array[0..XMP_NAME_SIZE - 1] of char;
|
---|
244 | end;
|
---|
245 |
|
---|
246 | xmp_module_info = record
|
---|
247 | md5: array[0..15] of byte;
|
---|
248 | vol_base: integer;
|
---|
249 | module: ^xmp_module;
|
---|
250 | comment: PChar;
|
---|
251 | num_sequences: integer;
|
---|
252 | seq_data: ^xmp_sequence;
|
---|
253 | end;
|
---|
254 |
|
---|
255 | xmp_channel_info = record
|
---|
256 | period: longword;
|
---|
257 | position: longword;
|
---|
258 | pitchbend: smallint;
|
---|
259 | note: byte;
|
---|
260 | instrument: byte;
|
---|
261 | sample: byte;
|
---|
262 | volume: byte;
|
---|
263 | pan: byte;
|
---|
264 | reserved: byte;
|
---|
265 | event: xmp_event;
|
---|
266 | end;
|
---|
267 |
|
---|
268 | xmp_frame_info = record
|
---|
269 | pos: integer;
|
---|
270 | pattern: integer;
|
---|
271 | row: integer;
|
---|
272 | num_rows: integer;
|
---|
273 | frame: integer;
|
---|
274 | speed: integer;
|
---|
275 | bpm: integer;
|
---|
276 | time: integer;
|
---|
277 | total_time: integer;
|
---|
278 | frame_time: integer;
|
---|
279 | buffer: Pointer;
|
---|
280 | buffer_size: integer;
|
---|
281 | total_size: integer;
|
---|
282 | volume: integer;
|
---|
283 | loop_count: integer;
|
---|
284 | virt_channels: integer;
|
---|
285 | virt_used: integer;
|
---|
286 | sequence: integer;
|
---|
287 | channel_info: array[0..XMP_MAX_CHANNELS - 1] of xmp_channel_info;
|
---|
288 | end;
|
---|
289 |
|
---|
290 | xmp_callbacks = record
|
---|
291 | read_func: function(dest: Pointer; len, nmemb: longword; priv: Pointer): longword;
|
---|
292 | seek_func: function(priv: Pointer; offset: longint; whence: integer): integer;
|
---|
293 | tell_func: function(priv: Pointer): longint;
|
---|
294 | close_func: function(priv: Pointer): integer;
|
---|
295 | end;
|
---|
296 |
|
---|
297 | { Dynamic load : Vars that will hold our dynamically loaded functions...
|
---|
298 |
|
---|
299 | *************************** functions ******************************* }
|
---|
300 |
|
---|
301 | var
|
---|
302 | xmp_create_context: function: pointer; cdecl;
|
---|
303 | xmp_free_context: procedure(ctx: Pointer); cdecl;
|
---|
304 | xmp_load_module: function(ctx: Pointer; const filename: PChar): Integer; cdecl;
|
---|
305 | xmp_load_module_from_memory: function(ctx: xmp_context; const Data: Pointer; size: longint): integer; cdecl;
|
---|
306 | xmp_load_module_from_file: function(ctx: xmp_context; file_: Pointer; size: longint): integer; cdecl;
|
---|
307 | xmp_load_module_from_callbacks: function(ctx: xmp_context; file_: Pointer; callbacks: xmp_callbacks): integer; cdecl;
|
---|
308 | xmp_test_module: function(const filename: PChar; info: xmp_test_info): Integer; cdecl;
|
---|
309 | xmp_release_module: procedure(ctx: xmp_context); cdecl;
|
---|
310 | xmp_start_player: function(ctx: xmp_context; rate: Integer; flags: Integer): Integer; cdecl;
|
---|
311 | xmp_play_buffer: function(ctx: xmp_context; buffer: Pointer; size: Integer; loop: Integer): Integer; cdecl;
|
---|
312 | xmp_get_frame_info: procedure(ctx: xmp_context; var info: xmp_frame_info); cdecl;
|
---|
313 | xmp_end_player: procedure(ctx: xmp_context); cdecl;
|
---|
314 | xmp_get_module_info: procedure(ctx: xmp_context; var info: xmp_module_info); cdecl;
|
---|
315 | xmp_get_format_list: function(): PAnsiChar; cdecl;
|
---|
316 | xmp_stop_module: procedure(ctx: xmp_context); cdecl;
|
---|
317 | xmp_restart_module: procedure(ctx: xmp_context); cdecl;
|
---|
318 | xmp_channel_vol: function(ctx: xmp_context; channel: Integer; volume: Integer): Integer; cdecl;
|
---|
319 | xmp_set_player: function(ctx: xmp_context; param: Integer; value: Integer): Integer; cdecl;
|
---|
320 | xmp_set_position: function(ctx: xmp_context; pos: Integer): Integer; cdecl;
|
---|
321 | xmp_seek_time: function(ctx: xmp_context; time: Integer): Integer; cdecl;
|
---|
322 |
|
---|
323 | // Not used yet...
|
---|
324 | //function xmp_test_module_from_memory(const data: Pointer; size: LongInt; info: xmp_test_info): Integer; cdecl; external 'xmp';
|
---|
325 | //function xmp_test_module_from_file(file_: Pointer; info: xmp_test_info): Integer; cdecl; external 'xmp';
|
---|
326 | //function xmp_test_module_from_callbacks(file_: Pointer; callbacks: xmp_callbacks; info: xmp_test_info): Integer; cdecl; external 'xmp';
|
---|
327 | //procedure xmp_scan_module(ctx: xmp_context); cdecl; external 'xmp';
|
---|
328 | //function xmp_play_frame(ctx: xmp_context): Integer; cdecl; external 'xmp';
|
---|
329 | //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';
|
---|
330 | //function xmp_prev_position(ctx: xmp_context): Integer; cdecl; external 'xmp';
|
---|
331 | //function xmp_set_position(ctx: xmp_context; pos: Integer): Integer; cdecl; external 'xmp';
|
---|
332 | //function xmp_set_row(ctx: xmp_context; row: Integer): Integer; cdecl; external 'xmp';
|
---|
333 | //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';
|
---|
334 | //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';
|
---|
335 | //function xmp_set_instrument_path(ctx: xmp_context; const path: PChar): Integer; cdecl; external 'xmp';
|
---|
336 |
|
---|
337 |
|
---|
338 | {Special function for dynamic loading of lib ...}
|
---|
339 |
|
---|
340 | var
|
---|
341 | xmp_Handle: TLibHandle = dynlibs.NilHandle;
|
---|
342 | {$if defined(cpu32) and defined(windows)} // try load dependency if not in /windows/system32/
|
---|
343 | gc_Handle :TLibHandle=dynlibs.NilHandle;
|
---|
344 | {$endif}
|
---|
345 | var
|
---|
346 | ReferenceCounter: cardinal = 0; // Reference counter
|
---|
347 |
|
---|
348 | function xmp_IsLoaded: Boolean; inline;
|
---|
349 |
|
---|
350 | function xmp_Load(const libfilename: string): Boolean; // load the lib
|
---|
351 |
|
---|
352 | procedure xmp_Unload(); // unload and frees the lib from memory : do not forget to call it before close application.
|
---|
353 |
|
---|
354 | implementation
|
---|
355 |
|
---|
356 | function xmp_IsLoaded: boolean;
|
---|
357 | begin
|
---|
358 | Result := (xmp_Handle <> dynlibs.NilHandle);
|
---|
359 | end;
|
---|
360 |
|
---|
361 | Function xmp_Load(const libfilename:string) :boolean;
|
---|
362 | var
|
---|
363 | thelib, thelibgcc: string;
|
---|
364 | begin
|
---|
365 | Result := False;
|
---|
366 | if xmp_Handle<>0 then
|
---|
367 | begin
|
---|
368 | Inc(ReferenceCounter);
|
---|
369 | result:=true {is it already there ?}
|
---|
370 | end else
|
---|
371 | begin
|
---|
372 | {$if defined(cpu32) and defined(windows)}
|
---|
373 | if Length(libfilename) = 0 then thelibgcc := 'libgcc_s_dw2-1.dll' else
|
---|
374 | thelibgcc := IncludeTrailingBackslash(ExtractFilePath(libfilename)) + 'libgcc_s_dw2-1.dll';
|
---|
375 | gc_Handle:= DynLibs.SafeLoadLibrary(thelibgcc);
|
---|
376 | {$endif}
|
---|
377 |
|
---|
378 | {go & load the library}
|
---|
379 | if Length(libfilename) = 0 then thelib := XMP_LIB_NAME else thelib := libfilename;
|
---|
380 | xmp_Handle:=DynLibs.LoadLibrary(thelib); // obtain the handle we want
|
---|
381 | if xmp_Handle <> DynLibs.NilHandle then
|
---|
382 | begin {now we tie the functions to the VARs from above}
|
---|
383 |
|
---|
384 | Pointer(xmp_create_context):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_create_context'));
|
---|
385 | Pointer(xmp_free_context):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_free_context'));
|
---|
386 | Pointer(xmp_load_module):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_load_module'));
|
---|
387 | Pointer(xmp_load_module_from_memory):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_load_module_from_memory'));
|
---|
388 | Pointer(xmp_load_module_from_file):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_load_module_from_file'));
|
---|
389 | Pointer(xmp_load_module_from_callbacks):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_load_module_from_callbacks'));
|
---|
390 | Pointer(xmp_test_module):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_test_module'));
|
---|
391 | Pointer(xmp_release_module):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_release_module'));
|
---|
392 | Pointer(xmp_start_player):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_start_player'));
|
---|
393 | Pointer(xmp_play_buffer):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_play_buffer'));
|
---|
394 | Pointer(xmp_get_frame_info):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_get_frame_info'));
|
---|
395 | Pointer(xmp_end_player):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_end_player'));
|
---|
396 | Pointer(xmp_get_module_info):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_get_module_info'));
|
---|
397 | Pointer(xmp_get_format_list):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_get_format_list'));
|
---|
398 | Pointer(xmp_stop_module):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_stop_module'));
|
---|
399 | Pointer(xmp_restart_module):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_restart_module'));
|
---|
400 | Pointer(xmp_channel_vol):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_channel_vol'));
|
---|
401 | Pointer(xmp_set_player):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_set_player'));
|
---|
402 | Pointer(xmp_set_position):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_set_position'));
|
---|
403 | Pointer(xmp_seek_time):=DynLibs.GetProcedureAddress(xmp_Handle,PChar('xmp_seek_time'));
|
---|
404 |
|
---|
405 | end;
|
---|
406 | Result := xmp_IsLoaded;
|
---|
407 | ReferenceCounter:=1;
|
---|
408 | end;
|
---|
409 |
|
---|
410 | end;
|
---|
411 |
|
---|
412 | Procedure xmp_Unload;
|
---|
413 | begin
|
---|
414 | if ReferenceCounter > 0 then
|
---|
415 | dec(ReferenceCounter);
|
---|
416 | if ReferenceCounter > 0 then
|
---|
417 | exit;
|
---|
418 | if xmp_IsLoaded then
|
---|
419 | begin
|
---|
420 | DynLibs.UnloadLibrary(xmp_Handle);
|
---|
421 | xmp_Handle:=DynLibs.NilHandle;
|
---|
422 | {$if defined(cpu32) and defined(windows)}
|
---|
423 | if gc_Handle <> DynLibs.NilHandle then begin
|
---|
424 | DynLibs.UnloadLibrary(gc_Handle);
|
---|
425 | gc_Handle:=DynLibs.NilHandle;
|
---|
426 | end;
|
---|
427 | {$endif}
|
---|
428 | end;
|
---|
429 | end;
|
---|
430 |
|
---|
431 |
|
---|
432 | end.
|
---|
433 |
|
---|