uses windows,crt; {$packrecords 1} { will need to write own kb handler to replace friggin crt and get rid of } { the mouse cursor that keeps showing in full screen mode... and hopefully } { to reduce the horrible processing weight of pressing any key! } { for memory references, a few array types! } type bytearray=array[0..1048575] of byte; wordarray=array[0..524288] of word; lonkero=array[0..262144] of dword; consoleoutput=array[0..7999] of dword; envelope=record { 400 bytes } value:integer; progress,movingto:byte; cloopend,cloopstart:byte; sustend,suststart:byte; speed,ofs:dword; point:array[0..63] of record height:word; { 0..2048 } next:dword; { one second = 1024 } end; end; i_type=record { basic 838 bytes, +417 per osku } i_name:array[0..32] of char; { entry 0 is length } i_pan:byte; { default panning for stereo mixing, 0..255 } i_faderate:word; { 0..512, speed of release of instrument } i_panenv:envelope; i_filterenv:envelope; i_oskucount:byte; { 1 to 8 oscillators in use } i_oskuaddout:byte; { which oscillators to mix for output } i_osku:array[1..8] of record i_sensitivity:byte; { 0 or 1..31 } i_ampscalea,i_ampscaleb:byte; { 0..255 } i_interpolation:byte; i_waveform:word; i_waveformbitsize:byte; i_offset:word; { 0..8191 or random, initial ofs } i_freqconstant:boolean; { note altered by various stuff ? } i_notix:integer; { 0..5373 if constant, or delta } i_fmstrength:word; { 0..32k:32k multiplier for FM } i_FMO,i_AMO:byte; { bitflag modulations } i_FMlinear:boolean; { use linear or log FM for each osku ? } i_volenv:envelope; end; end; var hanska:array[0..15] of handle; thread:handle; threadid:dword; conhanska:handle; conbuffy:pointer; i,j,l,m:dword; karma:array[0..255] of char; mixing:boolean; { set when mixer is at work, to prevent overlap } honk:boolean; quit:boolean; hr:hresult; audio1,audio2:pointer; { pointers for buffy writing } audiosize1,audiosize2:dword; { size for previous } com:char; error:byte; overcall:dword; imppi:byte; f:file; filetys:pointer; { for faster file access } fileofs:dword; strutsifile:openfilename; { for win file management } octave:byte; { octave displacement for the keyboard } freq:word; { mixing and preferred output frequency } trakfreq:byte; { bit value - freq of tracking updates: 10 = 1024 / sec } stereo:byte; { if 1 then mix two channels } maxchannels:byte; { polyphony, 1 to 128 } buffysize:dword; buffyblox:byte; buffyofs:dword; { memory offset in buffy where next word of audio goes } chnptr:byte; { channelpointer, used to figure out next free channel } chnlist:array[0..128] of byte; { linked list for tracking playing chns } killflag:boolean; playframe:dword; { how many 2048ths of a second have we been playing } playstream:boolean; { if true, process datastream } datastreamofs:dword; datastream:pointer; { midi commands stuffed in one stream } datastreamsize:dword; { stream comprises of Pulse:dword + Com:byte [+ Chn:byte + Data] } data1,data2,data3:pointer; { midi file loading buffers } mix_counter,mix_size:dword; { mixer variables } mix_ax,mix_sex,mix_mix:dword; mix_puls,mix_tix:dword; mix_tpp:dword; { moonsynth ticks per midi pulse, 16.16 } ppqn:word; { midi pulses per quarter note } tempo:dword; { microsecs per midi pulse, default 500000 } mix_box,mix_crux:longint; mix_z,mix_x:longint; mix_buffy:pointer; { 32-bit buffer, filled in Mixer } mix_c,mix_o:byte; strutsi:string; strupsi:array[0..511] of char; strupsar:pchar; { playrate = wave length * notefreq } { notefreq = (ogre shifted for octave) div freq } notefreq:array[0..5375] of dword; midichn:array[0..15] of record chnmap:array[0..128] of byte; instrument:byte; volume:byte; pan:byte; pitchrange:word; pitchdelta:integer; vibradepth:byte; vibrarate:dword; dparam:word; end; { Logical channels, maximum 128 of them :: Each channel can play any sound } chnsize,oskusize:word; ministreamsize:dword; ministream:array[1..128] of pointer; ministreamofsread,ministreamofswrite:array[1..128] of word; instru:array[0..255] of pointer; syn_voice:array[1..128] of record playing:byte; { 1=Pending NoteOn, 2=Playing, 4=Pending NoteOff, 8=Fade } midichannel:byte; instrument:word; volume:byte; { midi channel volume as 0..255 } volumeto:byte; zzzz:byte; { not used } filter:dword; filterenv:envelope; pan:byte; panenv:envelope; note:integer; { 0..5375 different notes available } enote:integer; time:dword; ticker:dword; noteoff:boolean; { not used } faderate:word; pitchdelta,pitchdeltato:integer; pitchrange:word; vibradepth:byte; vibrarate,vibraofs:dword; oskucount:byte; oskuaddout:byte; osku:array[1..8] of record value,value2:integer; offset:dword; playrate:dword; waveform:pointer; waveformsize:dword; waveformbitsize:byte; interpolation:byte; freqconstant:boolean; notix:integer; fmstrength:word; { 0..32k:32k multiplier for being oscillated FM } volume:word; volenv:envelope; ampscalea,ampscaleb:byte; fmo,amo:byte; fmlinear:boolean; end; end; wavetable:array[0..15] of record { table of waveforms } samples:pointer; { an array of 0..1 shl bitsize words } bitsize:byte; end; const keymap:array[0..28] of char= { musical keyboard keyboard } ('z','s','x','d','c','v','g','b','h','n','j','m','q','2','w','3' ,'e','r','5','t','6','y','7','u','i','9','o','0','p'); sine:array[0..63] of word=( { precalculated real sine } $8000,$8C8C,$98F9,$A528,$B0FC,$BC57,$C71D,$D134, $DA82,$E2F2,$EA6E,$F0E3,$F642,$FA7D,$FD8A,$FF62, $FFFF,$FF62,$FD8A,$FA7D,$F642,$F0E3,$EA6E,$E2F2, $DA82,$D134,$C71D,$BC57,$B0FC,$A528,$98F9,$8C8C, $8000,$7374,$6707,$5AD8,$4F04,$43A9,$38E3,$2ECC, $257E,$1D0E,$1592,$0F1D,$09BE,$0583,$0276,$009E, $0000,$009E,$0276,$0583,$09BE,$0F1D,$1592,$1D0E, $257E,$2ECC,$38E3,$43A9,$4F04,$5AD8,$6707,$7374); { This is a logarhitmic volume table. The recommended values are apparently } { derivable from 40*ln(volume/127) but I will expand this to full 8-bit } { width, while also loudening the curve slightly so the lowest notes won't } { be inaudible - the suggested formula would give volume 1 a decibel value } { of -193.8, while -160 is the lowest we need to go in a 16-bit stream. } { The formula I use is: 40 * ln ( [volume + 4] / 259 ). Results are -dB. } { The volumes are [0..32768] = 0.5 ^ (dB/10) * 32768 } logvol:array[0..255] of word=( $0000,$0001,$0002,$0003,$0004,$0005,$0006,$0007,$0008,$0009,$000A,$000C,$000F,$0011,$0014,$0017, $001B,$001F,$0023,$0028,$002D,$0032,$0038,$003E,$0045,$004C,$0053,$005B,$0063,$006C,$0076,$007F, $008A,$0095,$00A0,$00AC,$00B9,$00C6,$00D3,$00E2,$00F0,$0100,$0110,$0121,$0132,$0144,$0157,$016A, $017E,$0193,$01A8,$01BE,$01D5,$01ED,$0205,$021E,$0238,$0253,$026E,$028A,$02A7,$02C5,$02E4,$0303, $0324,$0345,$0367,$038A,$03AE,$03D3,$03F8,$041F,$0446,$046F,$0498,$04C2,$04ED,$051A,$0547,$0575, $05A4,$05D4,$0605,$0638,$066B,$069F,$06D5,$070B,$0742,$077B,$07B5,$07EF,$082B,$0868,$08A6,$08E5, $0926,$0967,$09AA,$09EE,$0A33,$0A79,$0AC0,$0B09,$0B53,$0B9E,$0BEA,$0C38,$0C86,$0CD6,$0D28,$0D72, $0DCE,$0E23,$0E79,$0ED1,$0F2A,$0F85,$0FE0,$103D,$109C,$10FB,$115D,$11BF,$1223,$1288,$12EF,$1357, $13C0,$142B,$1498,$1505,$1575,$15E5,$1658,$16CB,$1740,$17B7,$182F,$18A9,$1924,$19A1,$1A1F,$1A9F, $1B20,$1BA3,$1C27,$1CAD,$1D35,$1DBE,$1E49,$1ED5,$1F63,$1FF3,$2084,$2117,$21AB,$2242,$22DA,$2373, $240E,$24AB,$254A,$25EA,$268C,$2730,$27D5,$287C,$2925,$29D0,$2A7C,$2B2B,$2BDB,$2C8C,$2D40,$2DF5, $2EAC,$2F65,$3020,$30DD,$319B,$325B,$331D,$33E1,$34A7,$356F,$3639,$3704,$37D2,$38A1,$3972,$3A45, $3B1A,$3BF1,$3CCA,$3DA5,$3E82,$3F61,$4042,$4124,$4209,$42F0,$43D9,$44C3,$45B0,$469F,$4790,$4883, $4978,$4A6F,$4B68,$4C63,$4D60,$4E5F,$4F61,$5064,$516A,$5272,$537B,$5487,$5596,$56A6,$57B8,$58CD, $59E4,$5AFC,$5C18,$5D35,$5E54,$5F76,$609A,$61C0,$62E8,$6413,$6540,$666F,$67A0,$68D4,$6A0A,$6B42, $6C7C,$6DB9,$6EF8,$7039,$717D,$72C3,$740B,$7556,$76A3,$77F2,$7944,$7A98,$7BEE,$7D47,$7EA2,$8000); { The frequencies in the below table are the highest octave playable at 32x } { finetuning. The frequency values start from 16 minitones below C-13 at } { 8133.7 Hz and end 15 minitones above B-13 at 16238 Hz. The values are all } { 16.16 fixed point hexadecimal. They should've been 14.18 because the } { highest frequencies are happy with 14 integer bits, while the lowest go } { below one Hz and need more accuracy in the real part. No point doing this } { all again though, as the values can be scaled easily while the accuracy } { loss remains negligible. } ogre:array[0..383] of dword=( { C-13 } $1FC5AED8,$1FD460CC,$1FE3198C,$1FF1D91B,$20009F7D,$200F6CB4,$201E40C3,$202D1BAE, $203BFD79,$204AE625,$2059D5B6,$2068CC30,$2077C996,$2086CDEA,$2095D91D,$20A4EB6D, $20B404A1,$20C324D1,$20D24C00,$20E17A31,$20F0AF68,$20FFEBA7,$210F2EF2,$211E794C, $212DCAB9,$213D233C,$214C82D7,$215BE98F,$216B5766,$217ACC61,$218A4881,$2199CBCB, { C#13 } $21A95642,$21B8E7E8,$21C880C2,$21D820D3,$21E7C81E,$21F776A7,$22072C70,$2216E97D, $2226ADD2,$22367972,$22464C60,$225626A0,$22660834,$2275F121,$2285E16A,$2295D912, $22A5D81C,$22B5DE8D,$22C5EC67,$22D601AE,$22E61E65,$22F64290,$23066E32,$2316A14F, $2326DBEA,$23371E06,$234767A8,$2357B8D2,$23681188,$237871CD,$2388D9A6,$23994915, { D-13 } $23A9C01E,$23BA3EC4,$23CAC50C,$23DB52F8,$23EBE88C,$23FC85CC,$240D2ABB,$241DD75D, $242E8BB5,$243F47C7,$24500B97,$2460D727,$2471AA7C,$2482859A,$24936883,$24A4533B, $24B545C7,$24C64029,$24D74266,$24E84C80,$24F95E7C,$250A785D,$251B9A27,$252CC3DD, $253DF584,$254F2F1E,$256070B0,$2571BA3D,$25830BC9,$25946558,$25A5C6ED,$25B7308C, { D#13 } $25C8A238,$25DA1BF6,$25EB9DC9,$25FD27B6,$260EB9BE,$262053E8,$2631F635,$2643A0AA, $2655534B,$26670E1C,$2678D120,$268A9C5A,$269C6FD0,$26AE4B84,$26C02F7B,$26D21BB8, $26E41040,$26F60D15,$2708123C,$271A1FB9,$272C358F,$273E53C2,$27507A57,$2762A951, $2774E0B4,$27872084,$279968C5,$27ABB97A,$27BE12A8,$27D07452,$27E2DE7D,$27F5512C, { E-13 } $2807CC64,$281A5028,$282CDC7C,$283F7164,$28520EE4,$2864B501,$287763BD,$288A1B1E, $289CDB27,$28AFA3DC,$28C27540,$28D54F5A,$28E8322B,$28FB1DB8,$290E1206,$29210F18, $293414F2,$29472398,$295A3B0F,$296D5B5B,$2980847F,$2993B680,$29A6F161,$29BA3528, $29CD81D8,$29E0D774,$29F43602,$2A079D86,$2A1B0E03,$2A2E877D,$2A4209FA,$2A55957C, { F-13 } $2A692A09,$2A7CC7A4,$2A906E52,$2AA41E17,$2AB7D6F6,$2ACB98F5,$2ADF6417,$2AF33860, $2B0715D6,$2B1AFC7B,$2B2EEC55,$2B42E568,$2B56E7B7,$2B6AF348,$2B7F081E,$2B93263E, $2BA74DAC,$2BBB7E6C,$2BCFB882,$2BE3FBF4,$2BF848C5,$2C0C9EF9,$2C20FE96,$2C35679F, $2C49DA18,$2C5E5606,$2C72DB6E,$2C876A54,$2C9C02BC,$2CB0A4AA,$2CC55024,$2CDA052C, { F#13 } $2CEEC3C9,$2D038BFE,$2D185DCF,$2D2D3942,$2D421E5A,$2D570D1C,$2D6C058D,$2D8107B0, $2D96138C,$2DAB2923,$2DC0487A,$2DD57197,$2DEAA47D,$2DFFE131,$2E1527B8,$2E2A7816, $2E3FD24F,$2E553669,$2E6AA467,$2E801C4F,$2E959E25,$2EAB29ED,$2EC0BFAC,$2ED65F67, $2EEC0923,$2F01BCE3,$2F177AAD,$2F2D4286,$2F431471,$2F58F074,$2F6ED693,$2F84C6D3, { G-13 } $2F9AC139,$2FB0C5C9,$2FC6D488,$2FDCED7A,$2FF310A5,$30093E0D,$301F75B7,$3035B7A8, $304C03E4,$30625A70,$3078BB52,$308F268C,$30A59C26,$30BC1C22,$30D2A687,$30E93B59, $30FFDA9C,$31168456,$312D388B,$3143F741,$315AC07B,$31719440,$31887293,$319F5B7A, $31B64EFA,$31CD4D17,$31E455D7,$31FB693E,$32128751,$3229B015,$3240E390,$325821C6, { G#13 } $326F6ABB,$3286BE76,$329E1CFB,$32B5864F,$32CCFA76,$32E47977,$32FC0356,$33139818, $332B37C2,$3342E259,$335A97E2,$33725863,$338A23E0,$33A1FA5E,$33B9DBE3,$33D1C874, $33E9C015,$3401C2CC,$3419D09D,$3431E98F,$344A0DA7,$34623CE8,$347A7759,$3492BCFF, $34AB0DDF,$34C369FE,$34DBD161,$34F4440E,$350CC209,$35254B59,$353DE001,$35568008, { A-13 } $356F2B73,$3587E247,$35A0A489,$35B9723E,$35D24B6D,$35EB3019,$36042049,$361D1C02, $36362349,$364F3623,$36685496,$36817EA8,$369AB45D,$36B3F5BA,$36CD42C7,$36E69B86, $37000000,$37197037,$3732EC33,$374C73F8,$3766078C,$377FA6F4,$37995263,$37B30958, $37CCCC5E,$37E69B4E,$3800762F,$381A5D05,$38344FD5,$384E4EA6,$3868597D,$3882705F, { A#13 } $389C9353,$38B6C25D,$38D0FD84,$38EB44CC,$3905983C,$391FF7D9,$393A63A8,$3954DBB0, $396F5FF6,$3989F080,$39A48D53,$39BF3675,$39D9EBEC,$39F4ADBD,$3A0F7BEE,$3A2A5685, $3A453D88,$3A6030FC,$3A7B30E8,$3A963D50,$3AB1563B,$3ACC7BAE,$3AE7ADAF,$3B02EC45, $3B1E3774,$3B398F43,$3B54F3B8,$3B7064D8,$3B8BE2A9,$3BA76D31,$3BC30476,$3BDEA87F, { B-13 } $3BFA594F,$3C1616EF,$3C31E163,$3C4DB8B2,$3C699CE1,$3C858DF7,$3CA18BF9,$3CBD96ED, $3CD9AEDA,$3CF5D3C5,$3D1205B5,$3D2E44AF,$3D4A90B9,$3D66E9DA,$3D835018,$3D9FC378, $3DBC4400,$3DD8D1B8,$3DF56CA4,$3E1214CC,$3E2ECA34,$3E4B8CE4,$3E685CE1,$3E853A31, $3EA224DB,$3EBF1CE5,$3EDC2255,$3EF93531,$3F165580,$3F338347,$3F50BE8E,$3F6E0759); function endian(luku:dword):dword; assembler; asm mov eax,luku; ror eax,8; xchg ah,al; ror eax,16; xchg ah,al; ror eax,8 end; function strdec(luku:dword):string; { Takes a value and returns it in plain numbers in an ascii string } var tempstr:string; begin tempstr:=''; while luku>9 do begin tempstr:=chr(luku mod 10+48)+tempstr; luku:=luku div 10; end; strdec:=chr(luku+48)+tempstr; end; function strhex(luku:dword):string; { Takes a value and returns it in hex in an ascii string } var tempstr:string; begin tempstr:=''; while luku>15 do begin if luku and 15<10 then tempstr:=chr(luku mod 16+48)+tempstr else tempstr:=chr(luku mod 16+55)+tempstr; luku:=luku shr 4; end; if luku and 15<10 then strhex:=chr(luku mod 16+48)+tempstr else strhex:=chr(luku mod 16+55)+tempstr; end; procedure mwrite(xx,yy:byte;txt:string); var ko,la:coord; sr:small_rect; mi,mu:word; mjolk,lonkero:byte; begin mi:=xx+yy*80-1; mjolk:=2; lonkero:=length(txt); for mu:=1 to length(txt) do if txt[mu]=chr(255) then begin if ord(txt[mu+1])<58 then mjolk:=ord(txt[mu+1])-48 else mjolk:=ord(txt[mu+1])-55; inc(mu,1); dec(lonkero,2); end else consoleoutput(conbuffy^)[mi+mu+lonkero-length(txt)]:=ord(txt[mu]) or (mjolk shl 16); ko.x:=80; ko.y:=25; if lonkero+xx<80 then begin sr.left:=xx; sr.right:=xx+lonkero-1; sr.top:=yy; sr.bottom:=yy; la.x:=xx; la.y:=yy; end else begin sr.left:=0; sr.right:=79; sr.top:=yy; sr.bottom:=yy+(xx+lonkero) div 80; la.x:=0; la.y:=yy; end; writeconsoleoutput(conhanska,conbuffy,ko,la,sr); end; procedure mixer; { MIXER is called at every notification point that DirectSound sends from } { the tiny procedure HANDLERI. The MIXER synthesizes and mixes all sounds, } { and moves the data into a DirectSound secondary buffer for playback. } begin buffy^^.GetCurrentPosition(buffy,@mix_counter,@mix_size); if hr<>DS_OK then begin error:=1; exit; end; { getbuffyposition failed } { === Calculate how much sound can be mixed === } { buffysize + buffyofs are byte sizes! } { number of 16-bit frames to mix is then mix_size / 2 } { mix_counter is a frame counter, thus compared to mix_size / 2. } { mix_size is also the number of bytes to output, like buffysize! } { mix_buffy is 32-BIT ! ... it is 2 x buffysize! } if buffyofs<=mix_counter then mix_size:=mix_counter-buffyofs { mixing offset is behind playptr } else mix_size:=buffysize-buffyofs+mix_counter; { play pointer has wrapped } asm { clear 32bit unsigned buffy } mov edi,mix_buffy; mov ecx,mix_size shr ecx,1; mov eax,$80000000; rep stosd end; { ::: TRACKER stream splitting premix ::: } for mix_c:=1 to maxchannels do begin ministreamofswrite[mix_c]:=0; ministreamofsread[mix_c]:=0; lonkero(ministream[mix_c]^)[0]:=$7FFFFFFF; end; if playstream then begin {---} { read datastream until tick mix_ax is reached } mix_ax:=(mix_size shr 1)*2048 div freq+playframe; mix_x:=lonkero(datastream^)[datastreamofs shr 2]; mix_sex:=(((mix_x-mix_puls)*mix_tpp) shr 16)+mix_tix; mix_puls:=mix_x; mix_tix:=mix_sex; { mix_sex = how manieth 2048th frame contains next queued command } { mix_ax = which 2048th frame is last to be mixed } while (mix_sex$FF do begin lonkero(ministream[midichn[mix_c].chnmap[mix_x]]^)[ministreamofswrite[midichn[mix_c].chnmap[mix_x]] shr 2]:=(mix_sex-playframe) shr (11-trakfreq); bytearray(ministream[midichn[mix_c].chnmap[mix_x]]^)[ministreamofswrite[midichn[mix_c].chnmap[mix_x]]+4]:=0; ministreamofswrite[midichn[mix_c].chnmap[mix_x]]:=dword(ministreamofswrite[midichn[mix_c].chnmap[mix_x]]+8) mod ministreamsize; lonkero(ministream[midichn[mix_c].chnmap[mix_x]]^)[ministreamofswrite[midichn[mix_c].chnmap[mix_x]] shr 2]:=$FFFFFFFF; mix_x:=midichn[mix_c].chnmap[mix_x]; end; inc(datastreamofs,8); end; 2: if bytearray(datastream^)[datastreamofs+5]=9 then { ignore percuss } inc(datastreamofs,8) else begin { Note On } mix_c:=chnptr; mix_o:=mix_c; while (syn_voice[mix_c].playing>0) and (mix_o<$FF) do begin if mix_c=maxchannels then mix_c:=0; inc(mix_c); if mix_c=mix_o then mix_o:=$FF; end; chnptr:=mix_c; if mix_o=$FF then begin { none free, remove one from list } mix_o:=0; while chnlist[mix_o]<>mix_c do mix_o:=chnlist[mix_o]; chnlist[mix_o]:=chnlist[mix_c]; mix_o:=0; while midichn[syn_voice[mix_c].midichannel].chnmap[mix_o]<>mix_c do mix_o:=midichn[syn_voice[mix_c].midichannel].chnmap[mix_o]; midichn[syn_voice[mix_c].midichannel].chnmap[mix_o]:=midichn[syn_voice[mix_c].midichannel].chnmap[mix_c]; end; mix_x:=bytearray(datastream^)[datastreamofs+5]; { midi channel } midichn[mix_x].chnmap[mix_c]:=midichn[mix_x].chnmap[0]; midichn[mix_x].chnmap[0]:=mix_c; with syn_voice[mix_c] do begin note:=(bytearray(datastream^)[datastreamofs+6]+37)*32-16; lonkero(ministream[mix_c]^)[ministreamofswrite[mix_c] shr 2]:=(mix_sex-playframe) shr (11-trakfreq); bytearray(ministream[mix_c]^)[ministreamofswrite[mix_c]+4]:=1; ministreamofswrite[mix_c]:=dword(ministreamofswrite[mix_c]+8) mod ministreamsize; lonkero(ministream[mix_c]^)[ministreamofswrite[mix_c] shr 2]:=$FFFFFFFF; pan:=i_type(instru[instrument]^).i_pan; midichannel:=mix_x; instrument:=midichn[mix_x].instrument; time:=0; playing:=1; pitchdelta:=midichn[mix_x].pitchdelta; pitchrange:=midichn[mix_x].pitchrange; pitchdeltato:=pitchdelta; faderate:=i_type(instru[instrument]^).i_faderate; oskucount:=i_type(instru[instrument]^).i_oskucount; oskuaddout:=i_type(instru[instrument]^).i_oskuaddout; for mix_o:=1 to oskucount do begin osku[mix_o].waveform:=wavetable[i_type(instru[instrument]^).i_osku[mix_o].i_waveform].samples; osku[mix_o].waveformbitsize:=i_type(instru[instrument]^).i_osku[mix_o].i_waveformbitsize; osku[mix_o].waveformsize:=1 shl (osku[mix_o].waveformbitsize+16)-1; osku[mix_o].offset:=i_type(instru[instrument]^).i_osku[mix_o].i_offset; if osku[mix_o].offset=$FFFF then osku[mix_o].offset:=random(osku[mix_o].waveformsize); osku[mix_o].interpolation:=i_type(instru[instrument]^).i_osku[mix_o].i_interpolation; osku[mix_o].freqconstant:=i_type(instru[instrument]^).i_osku[mix_o].i_freqconstant; osku[mix_o].notix:=i_type(instru[instrument]^).i_osku[mix_o].i_notix; osku[mix_o].volume:=(8192*logvol[bytearray(datastream^)[datastreamofs+7]]) shr 15; osku[mix_o].volenv:=i_type(instru[instrument]^).i_osku[mix_o].i_volenv; osku[mix_o].ampscalea:=i_type(instru[instrument]^).i_osku[mix_o].i_ampscalea; osku[mix_o].ampscaleb:=i_type(instru[instrument]^).i_osku[mix_o].i_ampscaleb; osku[mix_o].FMO:=i_type(instru[instrument]^).i_osku[mix_o].i_FMO; osku[mix_o].AMO:=i_type(instru[instrument]^).i_osku[mix_o].i_AMO; osku[mix_o].fmstrength:=i_type(instru[instrument]^).i_osku[mix_o].i_fmstrength; osku[mix_o].fmlinear:=i_type(instru[instrument]^).i_osku[mix_o].i_fmlinear; if osku[mix_o].freqconstant then osku[mix_o].playrate:=(notefreq[osku[mix_o].notix] shl osku[mix_o].waveformbitsize) shr 2 else osku[mix_o].playrate:=(notefreq[note] shl osku[mix_o].waveformbitsize) shr 2; end; inc(datastreamofs,8); end; chnlist[mix_c]:=chnlist[0]; chnlist[0]:=mix_c; end; 3: begin { Note Off } mix_c:=bytearray(datastream^)[datastreamofs+5]; { chn } mix_x:=(bytearray(datastream^)[datastreamofs+6]+37)*32-16; { note } mix_mix:=bytearray(datastream^)[datastreamofs+7]; { fade velo } mix_o:=0; while midichn[mix_c].chnmap[mix_o]<>$FF do begin if syn_voice[midichn[mix_c].chnmap[mix_o]].note=mix_x then begin lonkero(ministream[midichn[mix_c].chnmap[mix_o]]^)[ministreamofswrite[midichn[mix_c].chnmap[mix_o]] shr 2]:=(mix_sex-playframe) shr (11-trakfreq); bytearray(ministream[midichn[mix_c].chnmap[mix_o]]^)[ministreamofswrite[midichn[mix_c].chnmap[mix_o]]+4]:=2; ministreamofswrite[midichn[mix_c].chnmap[mix_o]]:=dword(ministreamofswrite[midichn[mix_c].chnmap[mix_o]]+8) mod ministreamsize; lonkero(ministream[midichn[mix_c].chnmap[mix_o]]^)[ministreamofswrite[midichn[mix_c].chnmap[mix_o]] shr 2]:=$FFFFFFFF; syn_voice[midichn[mix_c].chnmap[mix_o]].playing:=syn_voice[midichn[mix_c].chnmap[mix_o]].playing or 4; end; mix_o:=midichn[mix_c].chnmap[mix_o]; end; inc(datastreamofs,8); end; 4: begin { tempo change } asm mov esi,datastream; add esi,datastreamofs and ecx,0; mov cl,[esi+5]; shl ecx,16; mov cx,[esi+6]; mov tempo,ecx mov ax,15625; mul ppqn; shl edx,16; mov dx,ax mov ebx,edx { EBX <-- ppqn * 15625 } mov eax,ecx; mov edx,eax shr edx,11; shl eax,21 { EDX:EAX <-- tempo shl 21 } div ebx; mov mix_tpp,eax { TPP = tempo / (ppqn*15625); 16.16 } end; inc(datastreamofs,8); end; 5: begin { data parameter number set } mix_x:=wordarray(datastream^)[(datastreamofs shr 1)+3]; mix_c:=bytearray(datastream^)[datastreamofs+5]; if mix_x and $80=$80 then midichn[mix_c].dparam:=(midichn[mix_c].dparam and $FF) or (mix_x and $FF00) else midichn[mix_c].dparam:=(midichn[mix_c].dparam and $FF00) or (mix_x and $FF); inc(datastreamofs,8); end; 6: begin { data entry } mix_x:=wordarray(datastream^)[(datastreamofs shr 1)+3]; mix_c:=bytearray(datastream^)[datastreamofs+5]; if midichn[mix_c].dparam=0 then if mix_x and $80=$80 then midichn[mix_c].pitchrange:=(midichn[mix_c].pitchrange and 31) or ((mix_x and $7F00) shr 2) else midichn[mix_c].pitchrange:=(midichn[mix_c].pitchrange and 16352)+(mix_x and $7F)*32 div 50; inc(datastreamofs,8); end; 7: begin { channel volume change } mix_o:=bytearray(datastream^)[datastreamofs+6]; mix_c:=bytearray(datastream^)[datastreamofs+5]; midichn[mix_c].volume:=mix_o; mix_x:=0; while midichn[mix_c].chnmap[mix_x]<>$FF do begin lonkero(ministream[midichn[mix_c].chnmap[mix_x]]^)[ministreamofswrite[midichn[mix_c].chnmap[mix_x]] shr 2]:=(mix_sex-playframe) shr (11-trakfreq); bytearray(ministream[midichn[mix_c].chnmap[mix_x]]^)[ministreamofswrite[midichn[mix_c].chnmap[mix_x]]+4]:=4; bytearray(ministream[midichn[mix_c].chnmap[mix_x]]^)[ministreamofswrite[midichn[mix_c].chnmap[mix_x]]+5]:=mix_o; ministreamofswrite[midichn[mix_c].chnmap[mix_x]]:=dword(ministreamofswrite[midichn[mix_c].chnmap[mix_x]]+8) mod ministreamsize; lonkero(ministream[midichn[mix_c].chnmap[mix_x]]^)[ministreamofswrite[midichn[mix_c].chnmap[mix_x]] shr 2]:=$FFFFFFFF; mix_x:=midichn[mix_c].chnmap[mix_x]; end; inc(datastreamofs,8); end; $C: begin { instrument change } midichn[bytearray(datastream^)[datastreamofs+5]].instrument:=bytearray(datastream^)[datastreamofs+6]; inc(datastreamofs,8); end; $E: begin { pitch wheel } mix_c:=bytearray(datastream^)[datastreamofs+5]; midichn[mix_c].pitchdelta:=wordarray(datastream^)[(datastreamofs shr 1)+3]; mix_o:=0; while midichn[mix_c].chnmap[mix_o]<>$FF do begin if syn_voice[midichn[mix_c].chnmap[mix_o]].playing and 4=0 then begin lonkero(ministream[midichn[mix_c].chnmap[mix_o]]^)[ministreamofswrite[midichn[mix_c].chnmap[mix_o]] shr 2]:=(mix_sex-playframe) shr (11-trakfreq); bytearray(ministream[midichn[mix_c].chnmap[mix_o]]^)[ministreamofswrite[midichn[mix_c].chnmap[mix_o]]+4]:=3; wordarray(ministream[midichn[mix_c].chnmap[mix_o]]^)[(ministreamofswrite[midichn[mix_c].chnmap[mix_o]] shr 1)+3]:=wordarray(datastream^)[(datastreamofs shr 1)+3]; ministreamofswrite[midichn[mix_c].chnmap[mix_o]]:=dword(ministreamofswrite[midichn[mix_c].chnmap[mix_o]]+8) mod ministreamsize; lonkero(ministream[midichn[mix_c].chnmap[mix_o]]^)[ministreamofswrite[midichn[mix_c].chnmap[mix_o]] shr 2]:=$FFFFFFFF; end; mix_o:=midichn[mix_c].chnmap[mix_o]; end; inc(datastreamofs,8); end; end; mix_x:=lonkero(datastream^)[datastreamofs shr 2]; mix_sex:=(((mix_x-mix_puls)*mix_tpp) shr 16)+mix_tix; mix_puls:=mix_x; mix_tix:=mix_sex; if mix_x=$7FFFFFFF then begin { asm mov edi,datastream; mov datastreamofs,0; mov eax,[edi]; mov mix_sex,eax end; dec(mix_ax,playframe); playframe:=0;} playstream:=false; end; end; playframe:=playframe+(mix_size shr 1)*2048 div freq; if playframe>=$7FFF0000 then begin playstream:=false; playframe:=0; end; end; {---} { === Fill temporary buffer with sound, one channel at a time === } mix_c:=chnlist[0]; while mix_c<>$FF do begin { loop through all playing channels } killflag:=false; mix_counter:=0; dec(mix_counter); mix_mix:=0; repeat { loop until enough frames generated or channel turned off } inc(mix_counter); { ::: TRACKER per channel ::: 4..2048 times a second } inc(syn_voice[mix_c].ticker,65536); while (syn_voice[mix_c].ticker>freq shl 16 shr trakfreq) and (not killflag) do begin dec(syn_voice[mix_c].ticker,freq shl 16 shr trakfreq); inc(mix_mix); { ---=== process midi commands } while mix_mix>=lonkero(ministream[mix_c]^)[ministreamofsread[mix_c] shr 2] do begin case bytearray(ministream[mix_c]^)[ministreamofsread[mix_c]+4] of { Controller Reset } 0: with syn_voice[mix_c] do begin volumeto:=180; pan:=128; pitchrange:=128; pitchdeltato:=8192; vibradepth:=0; vibrarate:=0; end; { Note ON } 1: with syn_voice[mix_c] do begin playing:=playing or 2; volume:=midichn[midichannel].volume; volumeto:=volume; end; { Note OFF } 2: syn_voice[mix_c].playing:=syn_voice[mix_c].playing or 8; { Pitch change } 3: syn_voice[mix_c].pitchdeltato:=wordarray(ministream[mix_c]^)[(ministreamofsread[mix_c] shr 1)+3]; { Volume change } 4: syn_voice[mix_c].volumeto:=bytearray(ministream[mix_c]^)[ministreamofsread[mix_c]+5]; end; { flow forward in stream to next entry } ministreamofsread[mix_c]:=dword(ministreamofsread[mix_c]+8) mod ministreamsize; end; { ---=== end midi command processing } with syn_voice[mix_c] do begin { update channel volume } if volume>volumeto then if volume>volumeto+12-trakfreq then dec(volume,12-trakfreq) else dec(volume); if volume enote } pitchdelta:=(3*pitchdeltato+pitchdelta+2) shr 2; enote:=note+((pitchdelta*pitchrange) shr 14)-(pitchrange shr 1); end; { update envelope for each active oscillator } if syn_voice[mix_c].playing and 2<>0 then begin inc(syn_voice[mix_c].time,2048 shr trakfreq); mix_o:=0; repeat inc(mix_o); with syn_voice[mix_c].osku[mix_o].volenv do begin ofs:=ofs+speed; while (ofs>=point[progress].next) and (not killflag) do begin dec(ofs,point[progress].next); progress:=movingto; if cloopend=progress then movingto:=cloopstart else if (syn_voice[mix_c].playing and 8=0) and (sustend=progress) then movingto:=suststart else movingto:=progress+1; if point[movingto].height=$FFFF then killflag:=true; end; if killflag then value:=0 else begin asm { linearry interpolate envelope value } lea edi,syn_voice; and eax,0; mov al,mix_c; dec al; mul chnsize shl edx,16; mov dx,ax; add edi,edx and eax,0; mov al,mix_o; dec al; mul oskusize add edi,eax; add edi,844 { EDI <-- syn_voice[mix_c].osku[mix_o]. } add edi,29 { EDI <-- .volenv } push edi mov ebx,[edi+12] { EBX <-- ofs } mov al,[edi+3] { movingto } mov cl,[edi+2] { progress } add edi,16 { EDI <-- .volenv.point[] } mov esi,edi; mov dl,6; mul dl; and eax,$FFFF; add esi,eax { ESI <-- .point[movingto] } mov al,6; mul cl; add edi,eax { EDI <-- .point[progress] } mov ax,[esi] { EAX <-- [movingto].height } and ecx,0; mov cx,[edi] { ECX <-- [progress].height } mov esi,[edi+2] { ESI <-- [progress].next } mul ebx; { EDX:EAX <-- movingto.height*ofs } xor ebx,$FFFFFFFF; inc ebx; add ebx,esi { EBX = -ofs+[progress].next } mov mix_box,edx; mov edi,eax; { mix_box:EDI <-- movingto.height*ofs } mov eax,ecx; mul ebx { EDX:EAX <-- progress.height*(prognext-ofs) } add edx,mix_box; mov ebx,eax; add ebx,edi { EDX:EBX } lahf; shr eax,8; and eax,1; add edx,eax { carry 1 if necessary } mov eax,ebx; div esi { EAX <-- value } pop edi; mov [edi],ax end; value:=(value*syn_voice[mix_c].osku[mix_o].volume) shr 11; end; end; until (mix_o=syn_voice[mix_c].oskucount) or (killflag); end; end; { ::: MIXER ::: } if syn_voice[mix_c].playing and 2<>0 then begin { Note Status is ON } mix_z:=0; for mix_o:=1 to syn_voice[mix_c].oskucount do begin { Frequency Modulation: adjust playrate } if syn_voice[mix_c].osku[mix_o].fmo>0 then begin { if oscillator has constant frequency, notix is the mininote number } { otherwise use the effective mininote adjusted up or down by notix } { Mix_Box becomes the mininote this oscillator plays at } if syn_voice[mix_c].osku[mix_o].freqconstant then mix_box:=syn_voice[mix_c].osku[mix_o].notix else mix_box:=syn_voice[mix_c].enote+syn_voice[mix_c].osku[mix_o].notix; { add together each modulator's output value } { Mix_Crux becomes the value of frequency change, in mininotes or Hz } mix_sex:=syn_voice[mix_c].osku[mix_o].fmo; mix_ax:=1; mix_crux:=0; repeat // if mix_sex and 1=1 then inc(mix_crux,syn_voice[mix_c].osku[mix_ax].value); inc(mix_crux,syn_voice[mix_c].osku[mix_ax].value and (((mix_sex and 1)-1) xor $FFFFFFFF)); mix_sex:=mix_sex shr 1; inc(mix_ax); until mix_sex=0; { if you want logarhitmic FM, set FMlinear to false and uncomment this } { then the frequency modulation happens in mininotes } // if syn_voice[mix_c].osku[mix_o].FMlinear=false then inc(mix_box,mix_crux); { retrieve Playrate for this mininote: 14.18 fixed point Hz/MixFreq value } syn_voice[mix_c].osku[mix_o].playrate:=(notefreq[mix_box] { adjust the Mix_Crux sum by modulation strength for this oscillator } { convert Mix_Crux into Hz/MixFreq and add it to Playrate } { Mix_Crux * FMstrength div 32768 ... * 262144 div MixFreq } +mix_crux*(syn_voice[mix_c].osku[mix_o].FMstrength shl 3) div freq) { adjust playrate for waveform, make it 16.16: shl waveformbitsize shr 2 } shl syn_voice[mix_c].osku[mix_o].waveformbitsize shr 2; { we use automatically linear FM so this is done above } { if syn_voice[mix_c].osku[mix_o].FMlinear=true then begin mix_crux:=(mix_crux*262144) div freq; inc(syn_voice[mix_c].osku[mix_o].playrate,mix_crux); end; syn_voice[mix_c].osku[mix_o].playrate:=syn_voice[mix_c].osku[mix_o].playrate shl syn_voice[mix_c].osku[mix_o].waveformbitsize shr 2; } end; if syn_voice[mix_c].osku[mix_o].interpolation=1 then begin asm { linearry interpolated waveform frame } lea edi,syn_voice and eax,0; mov al,mix_c; dec al; mul chnsize shl edx,16; mov dx,ax; add edi,edx and eax,0; mov al,mix_o; dec al; mul oskusize add edi,eax; add edi,844 { EDI <-- syn_voice[mix_c].osku[mix_o]. } mov eax,[edi+4] { EAX <-- offset } mov ebx,[edi+16] { EBX <-- waveformsize for ANDing } add eax,[edi+8]; and eax,ebx { offset+playrate and waveformsize } mov [edi+4],eax; mov ecx,eax mov esi,[edi+12]; shr ecx,16; shl ecx,1; add esi,ecx and ecx,0; mov cx,[esi] { CX <-- 1st waveform frame } add eax,65536; and eax,ebx { add one frame to offset, may wrap } mov bx,ax { BX <-- fraction } mov esi,[edi+12]; shr eax,16; shl eax,1; add esi,eax mov ax,[esi] { AX <-- 2nd waveform frame } mul bx; mov si,dx; shl esi,16; mov si,ax { mul 2nd frame by fraction } mov ax,$FFFF; sub ax,bx mul cx; shl edx,16; mov dx,ax { mul 1st by inverted fraction } add edx,esi; shr edx,16 mov ax,dx; sub ax,32768 mov dx,[edi+29]; imul dx add ax,4096; and cx,0; setc cl; add dx,cx { +4096 for rounding } mov cx,8192; idiv cx { *volenv.value div 8192 } mov [edi],ax end; end else with syn_voice[mix_c].osku[mix_o] do begin offset:=(offset+playrate) and waveformsize; value:=((wordarray(waveform^)[offset shr 16]-32768)*volenv.value+4096) div 8192; end; // if (syn_voice[mix_c].oskuaddout shr (mix_o-1)) and 1=1 then inc(mix_z,syn_voice[mix_c].osku[mix_o].value); inc(mix_z,syn_voice[mix_c].osku[mix_o].value and ((((syn_voice[mix_c].oskuaddout shr (mix_o-1)) and 1)-1) xor $FFFFFFFF)); end; { apply channel volume on channel output } mix_z:=(mix_z*logvol[syn_voice[mix_c].volume]) div 32768; asm mov edi,mix_buffy; mov ecx,mix_counter; shl ecx,2; add edi,ecx mov eax,mix_z; mov ebx,[edi]; add ebx,eax; mov [edi],ebx end; end; until (killflag) or (mix_counter=(mix_size shr 1)-1); if killflag then begin { channel stops playing } mix_o:=0; while chnlist[mix_o]<>mix_c do mix_o:=chnlist[mix_o]; chnlist[mix_o]:=chnlist[mix_c]; if syn_voice[mix_c].midichannel<>$FF then begin mix_o:=0; while midichn[syn_voice[mix_c].midichannel].chnmap[mix_o]<>mix_c do mix_o:=midichn[syn_voice[mix_c].midichannel].chnmap[mix_o]; midichn[syn_voice[mix_c].midichannel].chnmap[mix_o]:=midichn[syn_voice[mix_c].midichannel].chnmap[mix_c]; end; syn_voice[mix_c].playing:=0; end; mix_c:=chnlist[mix_c]; end; { === Copy sound from temporary buffer to DirectSound playback buffer === } { clip 32-bit unsigned stream while copying to 16-bit signed output } asm mov edi,mix_buffy; mov esi,edi { read from esi, write to trailing edi } mov ecx,mix_size; shr ecx,1 { number of frames to process } @loska: lodsd { EAX <-- 32-bit unsigned mixed audio frame } cmp eax,$80008000; jb @high_ok; mov eax,$80007FFF { clip if over 7FFF } @high_ok: cmp eax,$7FFF8000; jae @low_ok; mov eax,$7FFF8000 { clip if under -8000 } @low_ok: sub eax,$7FFF8000 { convert to 16 bit unsigned } add ax,$8000 { convert to signed } stosw; loop @loska { write processed value back in buffy } end; hr:=buffy^^.Lock(buffy,buffyofs,mix_size,audio1,audiosize1,audio2,audiosize2,0); if hr0 then { wrap from end to beginning if necessary } move(bytearray(mix_buffy^)[audiosize1],audio2^,audiosize2); hr:=buffy^^.Unlock(buffy,audio1,audiosize1,audio2,audiosize2); if hr=buffysize then dec(buffyofs,buffysize); end; procedure handleri; { HANDLERI is a child thread of MoonSynth, which sits in the background and } { waits for DirectSound to reach a notification point in playback. Then } { HANDLERI will call the MIXER and return to waiting, until program quits. } begin while not quit do begin msgwaitformultipleobjects(buffyblox,hanska,false,infinite,qs_allevents); if mixing then inc(overcall) else begin mixing:=true; mixer; honk:=true; mixing:=false; end; end; exitthread(0); end; function initdirectsound:boolean; begin { create primary sound device } hr:=directsoundcreate(nil,ids,nil); if hrDS_OK then writeln('Couldn''t set priority level... ',hr); { create primary sound buffer } fillchar(primarybufferdesc,sizeof(primarybufferdesc),0); with primarybufferdesc do begin dwSize:=sizeof(primarybufferdesc); dwFlags:=DSBCAPS_PRIMARYBUFFER; dwBufferBytes:=0; lpwfxFormat:=nil; end; hr:=ids^^.CreateSoundBuffer(ids,primarybufferdesc,idsbp,nil); if hrDS_OK then begin writeln('Problem playing secondary buffy... ',hr); initdirectsound:=false; exit; end; initdirectsound:=true; end; procedure shutdowndirectsound; begin if buffy^^.Stop(buffy)<>DS_ok then writeln('Error ',hr,' trying to stop secondary playback.'); if idsbp^^.Stop(idsbp)<>DS_ok then writeln('Error ',hr,' trying to stop primary playback.'); while mixing do; for i:=0 to buffyblox-1 do closehandle(hanska[i]); if assigned(buffy) then begin buffy^^.release(buffy); buffy:=nil; end; if assigned(idsbp) then begin idsbp^^.release(idsbp); idsbp:=nil; end; if assigned(ids) then begin ids^^.release(ids); ids:=nil; end; freemem(mix_buffy,buffysize shl 1); if thread<>0 then begin terminatethread(threadid,0); closehandle(thread); end; end; procedure referee(whereto:pointer;size:word); begin if (fileofs<49152) and (fileofs>=16384) then begin { 2nd or 3rd quadrant } move(bytearray(filetys^)[fileofs],whereto^,size); if (fileofs+size>=49152) then begin if filesize(f)-filepos(f)>=32768 then blockread(f,filetys^,32768) else blockread(f,filetys^,filesize(f)-filepos(f)); end; inc(fileofs,size); end else if fileofs<16384 then begin { 1st quadrant } move(bytearray(filetys^)[fileofs],whereto^,size); if (fileofs+size>=16384) then begin if filesize(f)-filepos(f)>=32768 then blockread(f,bytearray(filetys^)[32768],32768) else blockread(f,bytearray(filetys^)[32768],filesize(f)-filepos(f)); end; inc(fileofs,size); end else begin { 4th quadrant } if fileofs+size<65536 then move(bytearray(filetys^)[fileofs],whereto^,size) else begin move(bytearray(filetys^)[fileofs],whereto^,65536-fileofs); move(filetys^,bytearray(whereto^)[65536-fileofs],size+fileofs-65536); end; fileofs:=(fileofs+size) and 65535; end; end; function readvarilen:dword; var kurpitsa:byte; begin readvarilen:=0; repeat referee(@kurpitsa,1); readvarilen:=readvarilen or (kurpitsa and $7F); if kurpitsa and $80=$80 then readvarilen:=readvarilen shl 7; until kurpitsa and $80=0; end; procedure readmidifile; var chunklength:dword; midifiletype:word; trax,traknum:word; lastcom,activechn:byte; lastpulse:dword; ofs2,ofs3:dword; begin playstream:=false; for ofs2:=1 to maxchannels do if syn_voice[ofs2].playing>0 then syn_voice[ofs2].playing:=syn_voice[ofs2].playing or 8; while mixing do sleep(50); for i:=0 to 15 do with midichn[i] do begin instrument:=0; volume:=180; pan:=128; pitchrange:=128; pitchdelta:=8192; vibradepth:=0; vibrarate:=0; end; if assigned(datastream) then freemem(datastream,datastreamsize); reset(f,1); fileofs:=65536; getmem(filetys,65536); if filesize(f)>32768 then blockread(f,filetys^,32768) else blockread(f,filetys^,filesize(f)); datastreamsize:=filesize(f)*4; getmem(data1,datastreamsize); getmem(data3,datastreamsize); referee(@karma,4); referee(@chunklength,4); referee(@midifiletype,2); midifiletype:=endian(midifiletype shl 16); referee(@trax,2); trax:=endian(trax shl 16); writeln('Midifile type ',midifiletype,', has ',trax,' tracks.'); referee(@ppqn,2); ppqn:=endian(ppqn shl 16); { pulses per quarter note } if ppqn and $8000=0 then writeln('Pulses per Quarter Note: ',ppqn) else writeln('SMPTE time! ',strhex(ppqn)); ofs3:=0; lonkero(data3^)[0]:=$7FFFFFFF; for traknum:=1 to trax do begin lastpulse:=0; datastreamofs:=0; activechn:=traknum-1; referee(@karma,4); referee(@chunklength,4); chunklength:=endian(chunklength); getmem(data2,chunklength*4); if (karma[0]='M') and (karma[1]='T') and (karma[2]='r') and (karma[3]='k') then begin lastcom:=0; j:=0; repeat i:=0; i:=readvarilen; { read delta time of next command } inc(lastpulse,i); j:=bytearray(filetys^)[fileofs]; { check the next command } if j<$80 then begin { running status, assume repeat of previous command } if lastcom>0 then j:=lastcom else writeln('Running status error! Last command ',strhex(lastcom),', stream byte ',strhex(j)); end else referee(@j,1); { read it for real } m:=0; l:=0; i:=0; if (j and $F0)=$80 then referee(@m,2); if (j and $F0)=$90 then begin { command is a note on event... } referee(@m,2); if (m and $FF00=0) then { but velo 0 is really a note off! } j:=j and $F else begin { insert Note On event } asm mov edi,data2; add edi,datastreamofs mov eax,lastpulse; stosd { midi pulse when to play } mov al,2; stosb { internal play note command } mov eax,j; and al,$F; stosb { channel } mov eax,m; stosb { midi note number } shl ah,1; test ah,$FF; jz @zippy; inc ah @zippy: mov [edi],ah { velocity } add datastreamofs,8 end; end; end; { --- Translate Midi Commands --- } case j of $90..$9F: begin end; { note ons are handled above } 0..$F,$80..$8F: begin { Note Off } asm mov edi,data2; add edi,datastreamofs mov eax,lastpulse; stosd { midi pulse time } mov al,3; stosb { internal NoteOff command } mov eax,j; and al,$F; stosb { channel } mov eax,m; stosb { midi note number } shr ax,8; mov [edi],al { faderate } add datastreamofs,8 end; if j<$10 then j:=j or $90; { it was technically NoteON } end; $A0..$AF: begin referee(@m,2); end; $B0..$BF: begin { controller command } referee(@m,2); case m and $FF of 0,32: begin end; { Bank Select } 121: begin { reset controllers } asm mov edi,data2; add edi,datastreamofs mov eax,lastpulse; stosd { midi pulse time } mov al,0; stosb { reset controllers command } mov eax,j; and al,$F; stosb { channel } add datastreamofs,8 end; end; 12,13,91..95: begin end; { various effects } 11: begin end; { expression } 64: begin end; { pedal thingy } 70..79: begin end; { Sound controls and stuff } 10,42: begin end; { panning } 1,33: begin end; { mod wheel } 6,38: begin { Data Entry } lonkero(data2^)[datastreamofs shr 2]:=lastpulse; bytearray(data2^)[datastreamofs+4]:=6; { data entry } bytearray(data2^)[datastreamofs+5]:=j and $F; if m and $FF=6 then wordarray(data2^)[(datastreamofs shr 1)+3]:=(m and $7F00) or $80 else wordarray(data2^)[(datastreamofs shr 1)+3]:=((m and $7F00) shr 8) or $8000; inc(datastreamofs,8); end; 101,100: begin { set data parameter number } lonkero(data2^)[datastreamofs shr 2]:=lastpulse; bytearray(data2^)[datastreamofs+4]:=5; bytearray(data2^)[datastreamofs+5]:=j and $F; if m and $FF=101 then wordarray(data2^)[(datastreamofs shr 1)+3]:=(m and $7F00) or $80 else wordarray(data2^)[(datastreamofs shr 1)+3]:=((m and $7F00) shr 8) or $8000; inc(datastreamofs,8); end; 98,99: begin end; { non-registered param number } 39: begin end; { volume fine } 7: begin { midi channel volume adjust } asm mov edi,data2; add edi,datastreamofs mov eax,lastpulse; stosd { midi pulse time } mov al,7; stosb { volume change command } mov eax,j; and al,$F; stosb { channel } mov eax,m; shr eax,8; shl eax,1 { 0..127 -> 0..254 } test al,$FF; jz @skippy; inc al { 0, 2..255 } @skippy: stosb { channel volume } add datastreamofs,8 end; end; else begin writeln('Unrecognized controller ',strdec(m and $FF),' on chn ',j and $F); sleep(50); end; end; { writeln('Adjust controller ',m and $FF,' on channel ',j and $F,' to ',m shr 8);} end; $C0..$CF: begin { select instrument } referee(@m,1); asm mov edi,data2; add edi,datastreamofs mov eax,lastpulse; stosd { midi pulse time } mov al,$C; stosb { instrument change command } mov eax,j; and al,$F; stosb { channel } mov eax,m; stosb { which one to use? } add datastreamofs,8 end; end; $D0..$DF: begin referee(@m,1); end; { channel pressure } $E0..$EF: begin { pitch wheel } referee(@m,2); m:=((m and $7F00) shr 1) or (m and $7F); asm mov edi,data2; add edi,datastreamofs mov eax,lastpulse; stosd { midi pulse time } mov al,$E; stosb { pitch wheel command } mov eax,j; and al,$F; stosb { channel } mov eax,m; stosw { to position } add datastreamofs,8 end; end; $F0: begin { SysEx } i:=readvarilen; j:=0; while j0 do begin if i>=256 then begin referee(@karma,256); dec(i,256); for m:=0 to 255 do write(karma[m]); end else begin referee(@karma,i); for m:=0 to i-1 do write(karma[m]); i:=0; end; end; writeln; end; 3: begin { Track Name } write('Track Name: '); while i>0 do begin if i>=256 then begin referee(@karma,256); dec(i,256); for m:=0 to 255 do write(karma[m]); end else begin referee(@karma,i); for m:=0 to i-1 do write(karma[m]); i:=0; end; end; writeln; end; 2: begin { Kopyright } while i>0 do begin if i>256 then begin referee(@karma,256); dec(i,256); for m:=0 to 255 do write(karma[m]); end else begin referee(@karma,i); for m:=0 to i-1 do write(karma[m]); i:=0; end; end; writeln; end; 1: begin { subliminal text } while i>0 do begin if i>256 then begin referee(@karma,256); dec(i,256); for m:=0 to 255 do write(karma[m]); end else begin referee(@karma,i); for m:=0 to i-1 do write(karma[m]); i:=0; end; end; writeln; end; else begin writeln('Unsupported Meta-event ',strhex(l),' : length ',i); while i>128 do begin referee(@karma,128); dec(i,128); end; referee(@karma,i); sleep(30); end; end; end; else begin writeln('Unsupported Event : ',strhex(j)); sleep(70); end; end; if j<$F0 then lastcom:=j else if j<$F8 then lastcom:=0; { as per midispex rec } until (j=$FF) and (l=$2F); end else for i:=1 to chunklength do referee(@j,1); { Mix Data2 and Data3 into Data1, copy that to Data3 } ofs2:=datastreamofs; lonkero(data2^)[ofs2 shr 2]:=$7FFFFFFF; { ofs2 = data2size, current track } { ofs3 = data3size, size of all tracks before this } j:=0; i:=0; datastreamofs:=0; while (i11 then trakfreq:=11; if trakfreq<2 then trakfreq:=2; buffyblox:=5; buffysize:=((stereo+1)*freq shl 1) div 3; { 1/3 seconds, bytes, 16bitbuffy } buffysize:=(16+(buffysize div buffyblox) and $FFFFFFFC)*buffyblox; write('16 bits, '); if stereo=0 then write('mono, ') else write('stereo, '); writeln(freq,' Hz'); writeln('Buffy size is ',buffysize,' bytes, will be divided in ',buffyblox,' parts.'); writeln('Channel commands and envelope updates are handled ',1 shl trakfreq,' times/second.'); writeln('Maximum simultaneous sounds: ',maxchannels); getmem(mix_buffy,buffysize shl 1); { buffysize tells 16bit size, mix 32bit } buffyofs:=0; error:=0; overcall:=0; chnptr:=1; octave:=7; quit:=false; playframe:=0; for i:=0 to 128 do chnlist[i]:=$FF; chnsize:=sizeof(syn_voice[1]); oskusize:=sizeof(syn_voice[1].osku[1]); ministreamsize:=800; for i:=0 to 15 do with midichn[i] do begin chnmap[0]:=$FF; instrument:=0; volume:=180; pan:=128; pitchrange:=128; pitchdelta:=8192; vibradepth:=0; vibrarate:=0; end; for i:=1 to maxchannels do begin getmem(ministream[i],ministreamsize); lonkero(ministream[i]^)[0]:=$7FFFFFFF; ministreamofsread[i]:=0; ministreamofswrite[i]:=0; syn_voice[i].playing:=0; end; if not initdirectsound then begin freemem(mix_buffy,buffysize shl 1); for i:=1 to maxchannels do freemem(ministream[i],ministreamsize); halt; end; { Skvare } wavetable[0].bitsize:=1; getmem(wavetable[0].samples,2 shl wavetable[0].bitsize); wordarray(wavetable[0].samples^)[0]:=$FFFF; wordarray(wavetable[0].samples^)[1]:=0; { Sine } wavetable[1].bitsize:=6; getmem(wavetable[1].samples,2 shl wavetable[1].bitsize); move(sine[0],wavetable[1].samples^,2 shl wavetable[1].bitsize); { Sine^2 } wavetable[2].bitsize:=6; getmem(wavetable[2].samples,2 shl wavetable[2].bitsize); { === Use OGRE to calculate whole frequency notemap === } {for i:=0 to 5375 do notefreq[i]:=((ogre[i mod 384] shr (13-i div 384))+freq shr 1) div freq; } asm lea edi,notefreq mov cl,14 { 14 octaves to loop through } mov ax,freq; and eax,$FFFF; mov l,eax { l <-- freq } shr eax,1; mov i,eax { i <-- freq shr 1, to cause natural rounding } @orc: lea esi,ogre { start precalculated table over for every octave } dec cl; mov bx,384 { 384 mininotes to loop through for every octave } @troll: lodsd; { EAX <-- precalculated frequency for mininote as 16.16 } shl eax,2; shr eax,cl { shift to correct octave, and add x4 for accuracy } add eax,i { add half of frequency for natural rounding } and edx,0; div l { EAX <-- rounded mininote freq divided by mixing freq } stosd; { store calculated note in the NoteFreq table } dec bx; cmp bx,0; jnz @troll { loop through 384 mininotes } cmp cl,0; jnz @orc { and loop through 14 octaves } end; { === Load Instruments === } for i:=0 to 127 do begin getmem(instru[i],sizeof(i_type)); with i_type(instru[i]^) do begin i_name:=chr(0)+'Sound '; i_pan:=128; i_faderate:=0; { i_panenv; i_filterenv;} i_oskucount:=2; i_oskuaddout:=2; with i_osku[1] do begin i_sensitivity:=0; i_ampscalea:=255; i_ampscaleb:=255; i_waveform:=1; i_waveformbitsize:=6; i_interpolation:=1; i_offset:=$FFFF; i_freqconstant:=false; case i and 3 of 0: i_notix:=0; 1: i_notix:=694; 2: i_notix:=0; 3: i_notix:=384; end; i_FMO:=0; i_AMO:=0; i_FMstrength:=0; i_FMlinear:=false; with i_volenv do begin progress:=0; movingto:=1; cloopend:=2; cloopstart:=1; suststart:=64; sustend:=64; speed:=65536; ofs:=0; value:=0; point[0].height:=0; point[0].next:=123 shl 6 shl trakfreq; point[1].height:=2048; point[1].next:=(256+i shl 2) shl 6 shl trakfreq; point[2].height:=512+(i and 96) shl 3; point[2].next:=(256+i shl 2) shl 6 shl trakfreq; end; end; with i_osku[2] do begin i_sensitivity:=0; i_ampscalea:=255; i_ampscaleb:=255; if i and 8=8 then begin i_waveform:=0; i_waveformbitsize:=1; i_interpolation:=1; end else begin i_waveform:=1; if i and 16=16 then i_waveformbitsize:=6 else i_waveformbitsize:=5; i_interpolation:=1; end; i_offset:=$FFFF; i_freqconstant:=false; i_notix:=0; i_FMO:=1; i_AMO:=0; i_fmstrength:=4+(i and 7) shl 5; i_FMlinear:=true; with i_volenv do begin progress:=0; movingto:=1; cloopend:=64; cloopstart:=64; suststart:=2; sustend:=2; speed:=65536; ofs:=0; value:=0; point[0].height:=0; point[0].next:=90 shl 6 shl trakfreq; point[1].height:=512; point[1].next:=60 shl 6 shl trakfreq; point[2].height:=(point[1].height*3) shr 2; point[2].next:=50 shl 6 shl trakfreq; point[3].height:=point[1].height shr 1; point[3].next:=70 shl 6 shl trakfreq; point[4].height:=0; point[4].next:=10; point[5].height:=$FFFF; end; end; end; end; writeln('Use Z to P to play. + and - change octave. * plays or stops midi. ? opens file.'); writeln('. and , change instrument. \ pauses midi. > allows setting midi frame position.'); writeln('Space releases notes. ESC to exit.'); repeat com:=chr(0); if keypressed then com:=readkey; if com=chr(27) then quit:=true; if (com='+') and (octave<12) then inc(octave); if (com='-') and (octave>0) then dec(octave); if (com='>') and (assigned(datastream)) then begin write('Enter datastream offset > '); readln(i); datastreamofs:=i and $FFFFFFF8; mix_puls:=lonkero(datastream^)[datastreamofs shr 2]; playframe:=(mix_puls*mix_tpp) shr 16; mix_tix:=playframe; end; if com='.' then begin imppi:=(imppi+1) and 127; writeln('#',imppi,': Waveform ',i_type(instru[imppi]^).i_osku[2].i_waveform); end; if com=',' then begin imppi:=(imppi-1) and 127; writeln('#',imppi,': Waveform ',i_type(instru[imppi]^).i_osku[2].i_waveform); end; if com=' ' then for i:=1 to maxchannels do if syn_voice[i].playing>0 then syn_voice[i].playing:=syn_voice[i].playing or 8; if (com='\') and (assigned(datastream)) then playstream:=not playstream; if com='?' then begin with strutsifile do begin lstructsize:=sizeof(strutsifile); hwndowner:=0; karma[0]:='M'; karma[1]:='i'; karma[2]:='d'; karma[3]:='i'; karma[4]:=' '; karma[5]:='F'; karma[6]:='i'; karma[7]:='l'; karma[8]:='e'; karma[9]:='s'; karma[10]:=chr(0); karma[11]:='*'; karma[12]:='.'; karma[13]:='M'; karma[14]:='I'; karma[15]:='D'; karma[16]:=chr(0); karma[17]:=chr(0); lpstrfilter:=@karma; lpstrcustomfilter:=nil; nfilterindex:=1; strupsi[0]:=chr(0); lpstrfile:=@strupsi; nmaxfile:=512; lpstrfiletitle:=nil; lpstrinitialdir:=nil; lpstrtitle:=nil; flags:=ofn_filemustexist or ofn_hidereadonly; nfileoffset:=0; nfileextension:=0; lpstrdefext:=nil; end; if getopenfilename(@strutsifile) then begin assign(f,pchar(strupsi)); writeln;writeln('Loading ',strupsi); readmidifile; writeln('Use Z to P to play. + and - change octave. * plays or stops midi. ? opens file.'); writeln('. and , change instrument. \ pauses midi. > allows setting midi frame position.'); writeln('Space releases notes. ESC to exit.'); end else begin i:=commdlgextendederror; case i of cderr_dialogfailure: writeln('Call to DialogBox function failed.'); cderr_findresfailure: writeln('Specified resource not found.'); cderr_initialization: writeln('DialogBox init error. Not enough memory?'); cderr_memlockfailure: writeln('Unable to lock memory of a handle.'); cderr_structsize: writeln('Invalid structure size.'); fnerr_buffertoosmall: writeln('Filename buffy @lpstrfile too small.'); fnerr_invalidfilename: writeln('Invalid filename!'); frerr_bufferlengthzero: writeln('Pointer to an invalid buffer.'); end; end; end; if (com='*') and (assigned(datastream)) then begin for i:=1 to maxchannels do if syn_voice[i].playing>0 then syn_voice[i].playing:=syn_voice[i].playing or 8; if playstream=false then begin mix_puls:=0; mix_tix:=0; playframe:=0; datastreamofs:=0; tempo:=500000; for i:=0 to 15 do midichn[i].volume:=180; asm mov ax,15625; mul ppqn; shl edx,16; mov dx,ax mov ebx,edx { EBX <-- ppqn * 15625 } mov eax,tempo; mov edx,eax shr edx,11; shl eax,21 { EDX:EAX <-- tempo shl 21 } div ebx; mov mix_tpp,eax { TicksPerPulse = tempo / (ppqn*15625); 16.16 } end; end; playstream:=not playstream; end; for i:=0 to 28 do if (com=keymap[i]) then begin { play new note! :D } j:=chnptr; while (syn_voice[chnptr].playing>0) and (j<$FF) do begin if chnptr=maxchannels then chnptr:=0; inc(chnptr); if chnptr=j then j:=$FF; end; if j=$FF then begin { overwriting channel [chnptr] so remove from list } j:=0; while chnlist[j]<>chnptr do j:=chnlist[j]; chnlist[j]:=chnlist[chnptr]; j:=0; while midichn[syn_voice[chnptr].midichannel].chnmap[j]<>chnptr do j:=midichn[syn_voice[chnptr].midichannel].chnmap[j]; midichn[syn_voice[chnptr].midichannel].chnmap[j]:=midichn[syn_voice[chnptr].midichannel].chnmap[chnptr]; end; with syn_voice[chnptr] do begin playing:=2; note:=octave*384+(i+1)*32-16; if note>5375 then note:=5375; midichannel:=$FF; instrument:=imppi; pan:=i_type(instru[instrument]^).i_pan; pitchdelta:=8192; pitchrange:=128; volume:=180; volumeto:=180; time:=0; faderate:=i_type(instru[instrument]^).i_faderate; oskucount:=i_type(instru[instrument]^).i_oskucount; oskuaddout:=i_type(instru[instrument]^).i_oskuaddout; for j:=1 to oskucount do begin osku[j].waveform:=wavetable[i_type(instru[instrument]^).i_osku[j].i_waveform].samples; osku[j].waveformbitsize:=i_type(instru[instrument]^).i_osku[j].i_waveformbitsize; osku[j].waveformsize:=1 shl (osku[j].waveformbitsize+16)-1; osku[j].offset:=i_type(instru[instrument]^).i_osku[j].i_offset; if osku[j].offset=$FFFF then osku[j].offset:=random(osku[j].waveformsize); osku[j].interpolation:=i_type(instru[instrument]^).i_osku[j].i_interpolation; osku[j].freqconstant:=i_type(instru[instrument]^).i_osku[j].i_freqconstant; osku[j].notix:=i_type(instru[instrument]^).i_osku[j].i_notix; osku[j].volume:=8192; osku[j].volenv:=i_type(instru[instrument]^).i_osku[j].i_volenv; osku[j].ampscalea:=i_type(instru[instrument]^).i_osku[j].i_ampscalea; osku[j].ampscaleb:=i_type(instru[instrument]^).i_osku[j].i_ampscaleb; osku[j].FMO:=i_type(instru[instrument]^).i_osku[j].i_FMO; osku[j].AMO:=i_type(instru[instrument]^).i_osku[j].i_AMO; osku[j].fmstrength:=i_type(instru[instrument]^).i_osku[j].i_fmstrength; osku[j].fmlinear:=i_type(instru[instrument]^).i_osku[j].i_fmlinear; if osku[j].freqconstant then osku[j].playrate:=(notefreq[osku[j].notix] shl osku[j].waveformbitsize) shr 2 else osku[j].playrate:=(notefreq[note] shl osku[j].waveformbitsize) shr 2; end; end; chnlist[chnptr]:=chnlist[0]; chnlist[0]:=chnptr; end; if honk then begin honk:=false; sleep(32); end; if not keypressed then sleep(32); if error<>0 then writeln('Error ',error,' HR: ',hr); if overcall>0 then writeln('Overcalls: ',overcall); mwrite(54,2,strdec(playframe shr 11)+' '); for i:=0 to 15 do mwrite(72,i,strhex(midichn[i].pitchdelta)+' '); for i:=0 to 15 do mwrite(66,i,strdec(midichn[i].volume)+' '); strutsi[0]:=chr(32); for i:=1 to 32 do if syn_voice[i].playing=0 then strutsi[i]:='.' else if syn_voice[i].playing and 10=2 then strutsi[i]:='*' else strutsi[i]:='+'; mwrite(32,0,strutsi); until quit; shutdowndirectsound; freemem(conbuffy,8000); for i:=1 to maxchannels do freemem(ministream[i],ministreamsize); if assigned(datastream) then freemem(datastream,datastreamsize); for i:=0 to 127 do freemem(instru[i],sizeof(i_type)); for i:=0 to 15 do if assigned(wavetable[i].samples) then freemem(wavetable[i].samples,2 shl wavetable[i].bitsize); end.