{$M 16000,0,0} {$I-,X+,V-,G+,D+} unit s3mplay; INTERFACE CONST version = 1.70; { Variable ranges } MAX_samples = 100; { 0..99 samples } MAX_patterns = 100; { 1..100 patterns } MAX_orders = 255; { 0..255 orders } MAX_channels = 32; { 0..31 channels } { error constants } noerror = 0; notenoughmem = -1; wrongformat = -2; filecorrupt = -3; filenotexist = -4; packedsamples = -5; { sorry I don't know about DigiPlay 3.0 ADPCM packing was anyway not used yet in S3Ms ... } Allreadyallocbuffers = -6; { don't try to allocate memory for buffers twice } nota386orhigher = -7; { for playing any sound we need a 386 or higher - sorry but I optimized it for a 486 (pipeline etc.) and it runs fine on a 386 ;) (Hey guys a 486DX is not that expensive - for the same price I got an slow 386SX in 1991) } nosounddevice = -8; { before 'start playing' - set a sounddevice ! } noS3Minmemory = -9; { before 'start playing' - load a S3M ! } ordercorrupt = -10; { if there's no playable entry in order -> that would cause an endless loop in readnotes if you try to play it } internal_failure = -11; { I'm sorry if this happend :( } sample2large = -12; { I can't handle samples >64511 } {$I TYPDEF.INC} { variables for public } VAR load_Error:integer; player_Error:integer; { Tables : } Instruments:^TInstrArray; { pointer to data for all instruments } PATTERN :TPatternSarray; { segment for every pattern } { $Fxyy -> at EMS page YY on Offset X*5120 } ORDER :TOrderArray; { song arrangement } Channel :TchannelArray; { all public/private data for every channel } songname:string[28]; { name given by the musician } { numbers of ? } ordnum:word; insnum:word; Patnum:word; usedchannels:byte; { possible values : 1..32 (kill all Adlib) } patlength :word; { length of one pattern } savedunder:real; { ST version file was created with } { songposition : (you can change them while playing to jump arround) } curorder :word; { position in song arrangement } curpattern :byte; { current pattern - is specified also by [curorder] - so it's only for the user ... } curline :byte; { current line in pattern } curtick :byte; { current tick - we only calc one tick per call (look at MIXING.ASM) } lastorder :byte; { -> last order to play ... } Ploop_on :boolean;{ in a Pattern loop? } Ploop_no :byte; { number of loops left } Ploop_to :byte; { position to loop to } patterndelay:byte; gVolume :byte; { global volume -> usedvol = instrvol*gvolume/255 } loopS3M :boolean; { flag if restart if we reach the end of the S3M module } EndOfSong :boolean; toslow :boolean; justinfill :boolean; rastertime :boolean; useEMS :boolean; FPS :byte; { frames per second ... default is about 70Hz } LQmode :boolean; { flag if lowquality mode } DMArealbufsize:array[0..63] of word; { e.g. 0,128,256,384 <- positions of dmabuffer parts (changes with samplerate) } TickBytesLeft:word; { Bytes left to next Tick } {$IFDEF BETATEST} startorder :word; {$ENDIF} playbuffer :pointer; { pointer to DMAbuffer - for public use, but don't write into it !!! - it's never used for any action while mixing ! - while playing you can read the DMA base counter and find out in that way what sample value the SB currently plays ... refer to DMA Controller } DMAhalf :byte; { last DMAbuffer part to calculate } numBuffers :byte; { number of parts in DMAbuffer } { EMS things : } patEMShandle :WORD; { handle to access EMS for patterns } smpEMShandle :WORD; { hanlde to access EMS for samples <- I seperated them, but that does not matter, well ? } savHandle :WORD; { EMS handle for saving mapping while playing } EMSpat :boolean; { patterns in EMS ? } EMSsmp :boolean; { samples in EMS ? } PatperPage :byte; { count of patterns per page (<64!!!) } FUNCTION load_s3m(name:string):BOOLEAN; { load S3M module into memory } PROCEDURE done_module; { free memory used by S3M } FUNCTION Init_device(input:byte):boolean; { = false if set device failed } FUNCTION Init_S3Mplayer:boolean; { init DMAbuffer,tickbuffer,volumetable and some variables } PROCEDURE Done_S3Mplayer; { free buffers used by player } PROCEDURE setSampleRate(var SR:word;stereo:boolean); { set SampleRate for playing mono/stereo - higher frequency means more processor time for calc sound stereo question is because possible stereo/mono rates may differ } FUNCTION startplaying(var A_stereo,A_16Bit:boolean;LQ:Boolean):Boolean; (* play totaly in background - you have nothin else to do for continue playing ! It'll interrupt your program itself and calculate the next data is required *) procedure set_mastervolume(vol:byte); procedure set_ST3order(new:boolean); (* look at ST3order *) { To get some infos : } function getspeed:byte; function gettempo:byte; function get_mvolume:byte; function get_delay:byte; function getSamplerate:word; function getusedEMSsmp:longint; { get size of samples in EMS } function getusedEMSpat:longint; { get size of patterns in EMS } { not supported functions: } FUNCTION getuseddevice(var typ:byte;var base:word;var dma8,dma16:byte;var irq:byte):byte; FUNCTION load_specialdata(var p):boolean; { allocate memory and load special data from file } IMPLEMENTATION uses EMStool,blaster,crt,dos; CONST DMAbuffersize=8*1024; { <- maximum size of DMAbuffer } { Internal variables : } VAR S3M_inMemory:BOOLEAN; PROC386:boolean; { A 386 processor ? } filename:string; { name of file currently in memory } buffersreserved:boolean; sounddevice :boolean; Samplerate :word; Userate :word; { mixing variables : } tickbuffer :pointer; { the well known buffer for one tick - size depends on _currennt_tempo_ } DMAbuffer :pointer; { DMA and SB loop inside ... and we copy data into that buffer } AllocBuffer :pointer; { position where we allocate DMA buffer - remember that we may use second half ... } lastready :byte; { last ready calculated DMAbuffer part } volumetablePTR : pointer; { pointer to volumetable (see CALCVolumetable) } { S3M flags : } st2vibrato :boolean; { not supported } st2tempo :boolean; { not supported } amigaslides :boolean; { not supported } SBfilter :boolean; { not supported } costumeflag :boolean; { not supported - set if costumedata } vol0opti :boolean; { PSIs volume 0 optimization } amigalimits :boolean; { check for amiga limits } stereoflag :boolean; { not supported - we do what's possible on detected SB } signeddata :boolean; { signed/unsigned data (only volumetable differs in those modes) } { options : } mvolume :byte; { master volume -> calc posttables } initspeed :byte; { initial speed } inittempo :byte; { initial tempo } curspeed :byte; { current speed - length of one tick } curtempo :byte; { current tempo - count of ticks per note } { own Flags : } ST3order :boolean; { if true then handle order like ST3 - if a "--"=255 is found - stop or loop to the song start (look loopS3M) } { if false - play the whole order and simply skip the "--" if curorder=ordnum then stop or loop to the beginning } BPT :word; { bytes per tick - depends on samplerate + tempo } { some saved values for correct restoring former status : } oldexitproc :pointer; { tables for mixing : } post8bit :array[0..4095] of byte; post16bit :array[0..4095] of word; sinuswave, rampwave :array[0..63] of shortint; squarewave :array[0..63] of byte; {$L DOSPROC.OBJ} function getdosmem(var p;anz:longint):boolean; external; procedure freedosmem(var p); external; function getfreesize:word; external; function setsize(var p;anz:longint):boolean; external; {$L EMS4FCT.OBJ} procedure setEMSnames; near; external; {$L READNOTE.OBJ} procedure readnewnotes; near; external; procedure SetupNewInst; near; external; { don't call it from pascal - has its internal use } procedure SetnewNote; near; external; { don't call it from pascal - has its internal use } {$L MIXING.OBJ} procedure calc_mono_tick; near; external; procedure calc_stereo_tick; near; external; {$L VOLUME.OBJ} procedure calcVolumeTable; near; external; {$L PROCESSO.OBJ} function check386:boolean; near; external; {$L FILLDMA.OBJ} procedure fill_DMAbuffer; near; external; procedure mixroutines; near; external; { getuseddevice is not implemented yet } FUNCTION getuseddevice(var typ:byte;var base:word;var dma8,dma16:byte; var irq:byte):byte; { = 0 ... no device set / = 1 ... use SB mixing / > 1 ... other devices not supported yet } { typ ... up2now only SB typ - look at BLASTER.PAS } begin end; PROCEDURE done_module; var i:word; p:pointer; psmp:PsmpHeader; BEGIN if not S3M_inMemory then exit; { Free samples & instruments : } for i:=1 to MAX_Samples do begin psmp:=addr(Instruments^[i]); if (psmp^.typ=1) then begin if psmp^.mempos<$f000 then { no EMS instrument } begin p:=ptr(psmp^.mempos,0); psmp^.mempos:=0; if p<>Nil then freedosmem(p); end; end; Instruments^[i,0]:=0; end; { Free patterns : } for i:=0 to MAX_patterns do begin if pattern[i]<$C000 then begin { pattern in normal memory - it's a shame :) } p:=ptr(PATTERN[i],0); if p<>Nil then freedosmem(p); Pattern[i]:=0; end; end; if EMSpat then { patterns in EMS } begin EMSfree(savHandle); EMSfree(patEMShandle);EMSpat:=false; end; if EMSsmp then { samples in EMS } begin EMSfree(smpEMShandle);EMSsmp:=false; end; S3M_inMemory:=false; END; PROCEDURE Done_S3Mplayer; begin restore_irq; if volumetablePtr<>Nil then freeDOSmem(volumetableptr); if AllocBuffer<>Nil then freeDOSmem(AllocBuffer); if Tickbuffer<>Nil then freeDOSmem(TickBuffer); buffersreserved:=false; playbuffer:=Nil; DMABuffer:=Nil; end; PROCEDURE NewExitRoutine; Far; begin stop_play; { halt SB :) } speaker_off; { switch it off ... } if S3M_inMemory then done_module; if buffersreserved then done_S3Mplayer else restore_irq; exitproc:=oldexitproc; end; {$I LOADPROC.INC} FUNCTION Init_device(input:byte):boolean; { input= 0 ... use settings in BLASTER unit = 1 ... hardware autodetect SB = 2 ... read blaster enviroment = 3 ... input by hand } begin Init_device:=false; if Input = 0 then { 'checkthem' not yet implemented } sounddevice:=true else if Input = 1 then Sounddevice:=DetectSoundblaster(true) else if Input = 2 then Sounddevice:=UseBlasterEnv else if Input = 3 then Sounddevice:=InputSoundblasterValues; Init_device:=Sounddevice; end; function checkoverride(var p;l:word):boolean; assembler; asm mov bx,1 mov ax,word ptr [p+2] rol ax,4 and al,00fh add ax,l jc @@anoverride xor bx,bx @@anoverride: mov ax,bx end; FUNCTION Init_S3Mplayer:boolean; var p:pArray; begin Init_S3Mplayer:=false; if not proc386 then begin player_error:=nota386orhigher;exit end; if buffersreserved then begin player_error:=Allreadyallocbuffers;Init_S3Mplayer:=true;exit end; { buffersreserved = false ! } if not getdosmem(volumetablePTR,65*256*2) then begin player_error:=notenoughmem;exit end; if not getdosmem(Allocbuffer,(DMABuffersize+15)*2) then begin player_error:=notenoughmem;exit end; { ok and now check for DMA page overrides } if checkoverride(Allocbuffer^,DMAbuffersize) then { it's a page override in first DMAbuffer - use second } begin { Can't free the first part - sorry it's not possible with a DOS function } { I know I can creat my own PSP etc., maybe later, ok ? - it's a problem } { for final activities. } p:=allocBuffer; DMAbuffer:=ptr(seg(p^)+Dmabuffersize div 16,0); {$IFDEF BETATEST} write(' Use second part of DMAbuffer ... at ',seg(Dmabuffer^)); {$ENDIF} end else begin { use first buffer and free the rest } {setsize(Allocbuffer,DMABuffersize);} DMAbuffer:=AllocBuffer; {$IFDEF BETATEST} write(' Use first part of DMAbuffer ... at ',seg(DMAbuffer^)); {$ENDIF} end; { in tick buffer we calc one DMA buffer half - that are dmabuffersize/2 words } if not getdosmem(Tickbuffer,DMAbuffersize) then begin freedosmem(Allocbuffer); freedosmem(VolumetablePTR); player_error:=notenoughmem; exit end; playBuffer:=DMABuffer; buffersreserved:=true; { clear those buffers : } fillchar(dmabuffer^,dmabuffersize,0); fillchar(tickbuffer^,dmabuffersize,0); fillchar(volumetablePtr^,65*256*2,0); Init_S3Mplayer:=true; end; PROCEDURE setSampleRate(var SR:word;stereo:boolean); var w:word; i,j:byte; begin check_Samplerate(SR,stereo);Samplerate :=SR; if LQmode then Userate:=SR div 2 else Userate:=SR; w:=(1+ord(stereo))*(trunc(1000000/(trunc(1000000/Userate))/FPS)+1); i:=DMAbuffersize div w; j:=1;while j=32) then begin curtempo:=tempo; end else tempo:=curtempo; if curtempo<>0 then BPT:=trunc(Userate/50*125/curtempo); end; function getspeed:byte; begin getspeed:=curspeed; end; function gettempo:byte; begin gettempo:=curtempo end; var inside:boolean; PROCEDURE PLAY_IRQ; interrupt; var x,y:integer; begin asm cli @wait: cmp [inside],1 je @wait mov [inside],1 { change DMAhalf: } mov ah,[numbuffers] dec ah inc [DMAhalf] and [DMAhalf],ah mov [inside],0 end; if rastertime then asm { set screen border, if user wants to know testing ... } mov dx,03dah in al,dx mov dx,03c0h mov al,31h out dx,al mov al,1 out dx,al end; asm { ackknowledge the interrupt on SB : } mov dx,dsp_addr add dx,0eh add dl,[_16Bit] { in 16Bit mode we have to ackknowledge 22f ;) } in al,dx { ackknowledge the hardwareinterrupt : } mov al,20h out 0A0h,al out 020h,al { now new hardware interrupts are allowed ! } end; fill_dmabuffer; if rastertime then asm { screen border back to black ... } mov dx,03dah in al,dx mov dx,03c0h mov al,31h out dx,al mov al,0 out dx,al sti end; end; procedure calcposttable(use16bit:boolean); var z,i:integer; a,b,c:real; p:pointer; begin if use16bit then begin { not implemented yet } end else begin z:=mvolume and 127; c:=256*127/z; a:=2048-c/2; b:=2048+c/2; for i:=0 to 4095 do begin if (ib) then post8bit[i]:=255 else post8bit[i]:=trunc((i-a)*z/128); end; end; end; procedure Initchannels; var i:byte; begin for i:=0 to usedchannels-1 do begin channel[i].VibTabOfs:=ofs(sinuswave); channel[i].TrmTabOfs:=ofs(sinuswave); end; end; procedure set_mastervolume(vol:byte); begin if vol>127 then vol:=127; mvolume:=vol; calcposttable(_16bit); end; function get_mvolume:byte; begin get_mvolume:=mvolume; end; function get_delay:byte; begin get_delay:=patterndelay; end; function getSamplerate:word; begin getSamplerate:=Samplerate; end; function handlesize(h:word):word; assembler; asm mov ah,4ch mov dx,h int 67h cmp ah,0 jz @@ok xor bx,bx @@ok: mov ax,bx end; function getusedEMSsmp:longint; { get size of samples in EMS } begin if EMSsmp then getusedEMSsmp:=16*handlesize(smpEMShandle) else getusedEMSsmp:=0; end; function getusedEMSpat:longint; { get size of patterns in EMS } begin if EMSpat then getusedEMSpat:=16*handlesize(patEMShandle) else getusedEMSpat:=0; end; procedure set_ST3order(new:boolean); var i:byte; begin ST3order:=new; if new then begin { search for first '--' } i:=0; while (i0) and (order[i]>=254) do dec(i); lastorder:=i; end; end; FUNCTION startplaying(var A_stereo,A_16Bit:boolean;LQ:Boolean):boolean; var key:boolean; p:parray; begin startplaying:=false; player_error:=0; lqmode:=LQ; A_stereo:=A_Stereo and Stereo_possible; A_16Bit:=A_16Bit and _16Bit_possible; if not sounddevice then begin player_error:=nosounddevice;exit; end; { sorry no device was set } if not S3M_inMemory then begin player_error:=noS3Minmemory;exit end; { hmm load it first ;) } set_ready_irq(@play_irq); Initblaster(Samplerate,a_stereo,a_16Bit); setSamplerate(Samplerate,a_stereo); calcVolumetable; { <- now after loading we know if signed data or not } calcposttable(A_16bit); curtick:=1; { last tick -> goto next note ! } curLine:=0; { <- next line to read from } {$IFDEF BETATEST} curorder:=startorder; {$ELSE} curOrder:=0; { <- next order to read from } {$ENDIF} curpattern:=order[0]; { next pattern to read from } patterndelay:=0; { no patterndelay at start of course ! } Ploop_on:=false; Ploop_to:=0; curspeed:=initspeed;set_tempo(inittempo); set_ST3order(ST3order); { <- don't remove this ! it's important ! (setup lastorder) } EndOfSong:=false;toslow:=false; TickBytesLeft:=0; { emmidiately next tick } Initchannels; if lqmode then begin set_DMAvalues(DMABuffer,2*(numBuffers*DMArealbufsize[1]),true); { loop through whole DMAbuffer } DMAhalf:=numbuffers-1; lastready:=numbuffers; fill_dmabuffer; { calc all buffer parts ... } play_firstblock(2*dmarealbufsize[1]); { double buffering } end else begin set_DMAvalues(DMABuffer,NumBuffers*DMArealbufsize[1],true); { loop through whole DMAbuffer } DMAhalf:=numbuffers-1; lastready:=numbuffers; fill_dmabuffer; { calc all buffer parts ... } play_firstblock(dmarealbufsize[1]); { double buffering } end; { ok, now everything works in background ... } startplaying:=true; end; VAR i:byte; procedure calcwaves; begin for i:=0 to 63 do begin squarewave[i]:=255*ord(i<64); sinuswave[i] :=round(sin(pi/32*i)*(127)); rampwave[i] :=i*2-127; end; end; BEGIN inside:=false; PROC386:=check386; calcwaves; buffersreserved:=false; sounddevice:=false; oldexitproc:=exitproc; exitproc:=@newExitRoutine; volumetablePTR:=Nil; DMAbuffer:=Nil; AllocBuffer:=Nil; playBuffer:=Nil; Tickbuffer:=Nil; Samplerate:=22000; { not the highest but nice sounding samplerate :) } Userate:=22000; loopS3M:=false; ST3order:=false; { Ok let's hear all patterns are saved ... } rastertime:=false; useEMS:=EMSinstalled; { more space for Modules ! } if not getdosmem(instruments,5*16*max_samples) then begin asm mov ax,3 int 10h end; writeln(' Hey S3M-Player needs some DOSmem (programmers info: lower PAS-heap !) '); halt(1); end; FOR i:=1 TO MAX_Samples DO BEGIN Instruments^[i,0]:=0; END; FOR i:=0 TO MAX_patterns-1 DO BEGIN PATTERN[i]:=0; END; FPS:=70; LQmode:=false; END.