From b7a2e58109a6ac120ae52e6d500e663215fa59e7 Mon Sep 17 00:00:00 2001 From: Andy Sloane Date: Fri, 30 Oct 2015 17:58:24 -0700 Subject: [PATCH] Ripped font gfx out of FastTracker 2 :) --- ft2font.png | Bin 0 -> 6036 bytes index.html | 20 +++++--- xm.js | 128 ++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 127 insertions(+), 21 deletions(-) create mode 100644 ft2font.png diff --git a/ft2font.png b/ft2font.png new file mode 100644 index 0000000000000000000000000000000000000000..a6bf60de031885eb538c4c2791179bfccd4e79f4 GIT binary patch literal 6036 zcmaJ_XEYmb)K7>_5_`t@ml`!&r8ZTHik;e<(h{RW%@TW$QfkDkqV}wstxfH{ zqEyv<{m(njc|W}Gm*+lr{m%VxpXdDUjnvgvqoHJ@1ONau>W?1i0{}!A006iMyfFY^ zrAz8J0%WhEr2+s{#Zg^YlHBOr){peH0095n001To064obVKxB(Z#V$3jRF7=sQ>`8 zTV|u4!i|H->ap4b0C4^-ec_Ep=B2LnkZggH6AXisE@|xn08E+c4^#~OX7;jl(6X85x^nS7$J556^UG3G2b1|T>wsU?g+bCH zPp{86pZ-4gdGfU6y=A@StjiS5UWJG=ZFv11>69kXG8K=}`}J=>d&|A?jM4rrm#YZ? z-z)Y`eC~Zh<4BU5=U9OQttzhrj{Q#g%5UMvB~qQM2K&;Ks+b@C!;an!OP|Fz<*img zTPd})2(qn2xLV+8(R0~7S1+Y$)X)h(e+fN=b=GP&2P6LpQ&jY#V&CB_GDX4bp7&Z% zWBOKoNAe5trnUlxRe@{T ziq}r&W#5URl;6g$ZT}guMEd{*thi5S{S6K#4hoPB@miBLBa@ zrHeT@|7<8Z(Y4US`qWTaslxP+-p?Rq$2r;VY{wUX>$Xk^H0la2kU>UoIpXM9WOgm~ z&h`lT``apSxf}Kk1EwymUqJ5GD|VfXsLrdjDo|%2ueahJVV9uJ(lGFj+uMT_to6qf zY|DAc*HV!?ThJjf$a~&gh)oWLa+MgGDO_3DX*ur_zP1ED8MxAourwBXe6OoRgFX0U zb~`wiz94h-EjGZxaEz;66>#b7w|;?u~c9t?ZU@Z=FFRy1GVBbY}*>f^Q)Kjf28*RFlzhkFx?qPt+<^D=P$ikG9#8uZ|=EW}5{Y1shLs zO)mp!9{)VyfF$|Wa0(stAq%cOP-ZQwstE=I%ZMLhUS?3gB7R5W*Qn>I4Q`L@?W$$q zlKIi&agMr&ENk;8E&%mik4FtM7>3>Yj6FMmx|BSx8M`uN`hfw~r1U<_t_13x`ubHV zc;S9bmI2I4)P(e|Yu@IDA#Lj85H59UO-g5Hwuy8_^3`nrFZ%NMQu1_GW69(beUBb zZbNP7_!n(8rZjq96Fn~qQ-q5JiNnuZmH&;C$j>z5+14%5)x0U}gP}l?;VGy0rhsg@ zPXITfybyQF1KOx(3AUg?kBovL*iCX$bSt0hAq3k7bBmx{Ca zeLGTcIXW=h85aVWnt!rw&yq=BuX8Uh4&V&6j9D8Z60!4#d7@Z*xXDq}KxGADm&I1o z$_&z#YdViFB*^&#tGb=+k{!+sz-n^4rZW>dJ5{qf*fza`rS_R!)2+0{=ZmCIntmqf zh9o=xV3pMZv1Pyb@gAz$yMFWep9zo1NiDeqp@O4&+Puqym%MScVy;7{3*r!+mV!t3&Tm>?x1zZrZ3DMDS4oMIgqCvT7gKO#;*BXH z!%ya^-v%FzojJgqairm+5V_BZWch3#WW;H1mhg$hd0*4cOEcfMf6$J&)S!{}@KTQCv1aV!Xv2HI`+lM#7;dhS}+l09Cm z3C{Xn{AhB-_kP3uT)5YKtr{o78jZygpM^RQa{Vek=&;`s@s%l)Nm7aLmWnln*xOq@_9c zDDj=d>%9D+#OPk`wD_&qHF4T*)4;gzNAW-A%sWuq@imWP)4fLX($4YQuM84GeTEA- zbe<`T%omq|as7p>ECQa8*p9WaTLzzmuJ$}7Fhge^TaK+GBI8$2GW#b20J3>K3?qwF z!8$FMHaz&{KQ`U6ZiY=D!WM*?$Weu1f~ZSGZyo_$XEo?oz(rIV%SBXXZVWEdvvz_4HQ?NjULCgwNE6*bTw$dEo2~;4M;Bz0?6wPPnYKL>sjFO#x_!*2 z6`s&D$`Wb1muG1xN$rBX-ttBQhf-nAc01~6WTy3qQJkHb2v`{2cdkKA{dEBoEGUxA13pCP{(2z`GH26j-aoU zq{LQVSeMtZ0qMfa8O;oKM3%B3TY85CTBvBy2Ocw_p~0=&7W7GSEg1~13IpMnd_e~5 zM30LZm8ilLK<^n)B3AD1diu1yFlsGP{5o%&`a4Hq&|*;z=}89;`?5_yD2o#hlB3W_ zR0kOOvGwVD%P~*Qt7GV=rC7J|>zc&;dPAVGNN%TTO$@!|EJy)W(`Jkn<`U>;krOA` zt)6|z)Sr}K>#m!b%c{~8F9nv_qg?@qNE=iNe@@CsAKxkM z*Du#{f!VccSq_-IsjL!c`$=?%Yu5RLB2QxoNfYo5hRJ~;fk7pg1SGao9}ENW@zZUc z6BB7fxJk~(*#Kp0Fj-)!d>A6jCLc3-AI{)CmT?rFz10Wr6 z;)qZCWgjup*}#is{yG#B%U`b;8-Tl8d+vP#fy$sHK%NKc zG(zN5Avc9$6?d>aGYALDED@n8o!Z48$+w9GCYadV8|s|-okD>RuW^y7U!)*(yGn*- zkvy>ES3aBt86Vm*H-RYBl3#(YR?4th+DuTfFXAKG7$JCuF-#m@&_~u@NTIyP^G`?* z@U94-08Vx;tj(GK_-+$T?%T^G77l@9Tu~&K5_3x8{XQA2D(b|LWKvE5+=gtPdb%)pkmZRx z6EK6?l{MhkUmbfN?|tA!*6C=az0bHkX8Iu$t1|7y_ncusOaOp&fZ!I@j^QG`lsk^& z_-62q+FJ@JG_S%{Qg)sNK5@{Q&K-`L0H~ zNc=-Bay}<&jp2hx?};cIX8RVyqa-7s7! zt2x=A~y^Q)QC9;oob$|qJ-U=Y~#8Irpv#dL&`=0nTp$9jF)lCB{Kit0V zEV~f0J*1Ak;i-nWnjFW$*Glz=_2TGESzYYuTgQe%uhgTPD)ZHd^;V>f34Z(qV$Aw>L<{V{T8KWmJ``h}`{HQ9yzXR{DKx&lxA$2k9BDcLr z(MeN*L%QO5Z@{7e$fzOg8RiQ^OzV*~DwHo(nzPW+ojTgFmbP!JJR=r=7e*o_Omxx( z>`ss#*>mp_On|B}_2xVWS|h%`4SMt+ZnJoPQR9=eEq$}2$b-C&_2hqS~@Cn_JgX5kVb z_fygZGTNDT;>MOb-WEICX(Kq&a>8_ z$l(~{CE%_@@|jf18KFY%hPKp|*Y?8e!wCEY{i{uc30}7NS%0e0tn0HPcl0C_K2C( z6k@Aw+%L8msgKEKxkDeS!aQI{HkWGuzaN}<5xht|jb$7eUsv$E2%pIzQ{AM2QpHMSPo^(Po(zSoxLZA9z{TUoGa^*hJv(mkzpF zva!$aWB+GJbU&s;eEa(oJ@f~eGGaO8muBsmq!cX2PFI9i(X)f|?3~X!G zFOjWhF}?Zt?l;tvQ-g9a7a-zZEvOBlG=tVf6RV0h*F3bQzvOXW6b*W>yU2rESQCqaKc*4+I2{O~l3yhK{BpiH zZ~wG?X8f+T^YMPoyK~~!gRA$BvMDF{&s55B796cM$-{>d@cr#FX7m zx;uNX3E)gA%O>DJcG2s;xq{1rg7;C)vrZw{`n_Om0j()}ntcI8HE^rOSCvADzI5~; z2(0XEvZ!DHG_nxK9}qXDq|N*UD;l~~Nn;Pk(5nmdkM7Csb}I|4?d3R|kEhR6hzehX zkBF%O{rp@PvyH|SM-Ll*Pm426>s-c3`+_FChJQ83di%sE`>f9~H>fGkBLeQXT`2~| zGAl9|_0rRbegL1hUt$E(0_Z63l;JyCC<}5TdP$x3B10_zHgO!AWyfkcW0$iqL`MOL zoWIxCg*-YZt`Vu`_oIr>dO783uNc7iqoTjw$7VMNF>|8;FUGmNTeXT8tqje>v2k(0 z2A@|Kt1K?NUY)ja&y^4@NVTbVAcdyesI}8ml8dO|Ny>(2YE#Q|62MI}9>VA$mD^4v zvTbFBr9ofK&t{e;kwUcPnke5)KF~-$@KAiV3n5iuK??a!D|AlTLZbKoG{}E<&Q#X! zpYv*@)3CD4*PGQfpVvcUFB?lQTZFZT?Tr9Pz{RCS#Ni^6a6@q^goHdoN=^hWi-5z4 jwlui@SAw&v%?rDi|966C?XQBH1c3TO?FSY2Q6c{U`T9V5 literal 0 HcmV?d00001 diff --git a/index.html b/index.html index 84b1da7..eed5fbe 100644 --- a/index.html +++ b/index.html @@ -3,10 +3,22 @@ +
+
+
+
+
+
 code: github.com/a1k0n/jsxm
-tune: my dirty old kamel - Zalza
 todo:
+ - file picker, upload more xms to a host we can access cross-origin
+ - make display even more faithfully FT2-like
+ - sync display & sound to 60Hz precision (right now it only updates at
+   about 10Hz)
+ - buffer the song more after that's done
+ - render instrument list
+ - oscilloscopes
  - fix bugs!
    - envelope loops - are they right or not now?
  - multi-sample instruments
@@ -15,12 +27,6 @@
    - E3x, E4x, E5x, E6x, E7x, E9x, EDx, EEx
    - 7xy - tremolo
    - Gxx, Hxy, Kxx, Lxx, Pxy, Txy
- - oscilloscopes
-
- -
-
-
 
diff --git a/xm.js b/xm.js index 4d91b4b..9c11451 100644 --- a/xm.js +++ b/xm.js @@ -1,8 +1,102 @@ +var songname = ''; +var fontimg = new Image(); +var pat_canvas = document.createElement('canvas'); +fontimg.onload = function() { + /* NO!!!!! + var canv = document.createElement('canvas'); + canv.width = fontimg.width; + canv.height = fontimg.height; + var c = canv.getContext('2d'); + c.drawImage(fontimg, 0, 0); + var image_data = c.getImageData(0, 0, fontimg.width, fontimg.height); + var idx = 0; + var data = image_data.data; + for(var i = 0; i < fontimg.height; i++) { + for(var j = 0; j < fontimg.width; j++) { + // image_data.data[((i*(img.width*4)) + (j*4) + 3)] = 127; + if (data[idx] == 0) + data[idx+3] = 0; + idx += 4; + } + } + c.putImageData(image_data, 0, 0); + fontimg = canv; + */ +}; + +fontimg.src = "ft2font.png"; + var _note_names = ["C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"]; var f_smp = 44100; // updated by play callback, default value here audioContext = window.AudioContext || window.webkitAudioContext; +var _fontmap_notes = [8*5, 8*22, 8*28]; +var _pattern_cellwidth = 16 + 4 + 8 + 4 + 8 + 16 + 4; +var _pattern_border = 20; +var pat_canvas_patnum; +function RenderPattern(canv, pattern) { + // a pattern consists of NxM cells which look like + // N-O II VV EFF + var cellwidth = _pattern_cellwidth; + canv.width = pattern[0].length * cellwidth + _pattern_border; + canv.height = pattern.length * 8; + var ctx = canv.getContext('2d'); + ctx.fillcolor='#000'; + ctx.fillRect(0, 0, canv.width, canv.height); + for (var j = 0; j < pattern.length; j++) { + var row = pattern[j]; + var dy = j * 8; + // render row number + ctx.drawImage(fontimg, 8*(j>>4), 0, 8, 8, 2, dy, 8, 8); + ctx.drawImage(fontimg, 8*(j&15), 0, 8, 8, 10, dy, 8, 8); + + for (var i = 0; i < row.length; i++) { + var dx = i*cellwidth + 2 + _pattern_border; + var data = row[i]; + + // render note + var note = data[0]; + if (note < 0) { + ctx.drawImage(fontimg, 0, 8*5, 16, 8, dx, dy, 16, 8); + } else { + var octave = (note/12)|0; + var note_fontrow = _fontmap_notes[(octave/3)|0]; + note = (note % (12*3))|0; + ctx.drawImage(fontimg, 16+16*note, note_fontrow, 16, 8, dx, dy, 16, 8); + } + dx += 20; + + // render instrument + var inst = data[1]; + if (inst != -1) { // no instrument = render nothing + ctx.drawImage(fontimg, 8*(inst>>4), 4*8, 4, 8, dx, dy, 4, 8); + ctx.drawImage(fontimg, 8*(inst&15), 4*8, 4, 8, dx+4, dy, 4, 8); + } + dx += 12; + + // render volume + var vol = data[2]; + if (vol < 0x10) { + ctx.drawImage(fontimg, 312, 0, 8, 8, dx, dy, 8, 8); + } else { + vol -= 0x10; + ctx.drawImage(fontimg, 8*(vol>>4), 4*8, 4, 8, dx, dy, 4, 8); + ctx.drawImage(fontimg, 8*(vol&15), 4*8, 4, 8, dx+4, dy, 4, 8); + } + dx += 8; + + // render effect + var eff = data[3]; + var effdata = data[4]; + ctx.drawImage(fontimg, 8*eff + 16*8, 4*8, 8, 8, dx, dy, 8, 8); + dx += 8; + ctx.drawImage(fontimg, 8*(effdata>>4), 4*8, 4, 8, dx, dy, 4, 8); + ctx.drawImage(fontimg, 8*(effdata&15), 4*8, 4, 8, dx+4, dy, 4, 8); + } + } +} + function prettify_note(note) { if (note < 0) return "---"; if (note == 96) return "^^^"; @@ -94,11 +188,9 @@ function next_row() { var p = patterns[cur_pat]; var r = p[cur_row]; cur_row++; - pretty_row = []; for (var i = 0; i < r.length; i++) { var ch = channelinfo[i]; ch.update = false; - pretty_row.push(prettify_notedata(r[i])); var triggernote = false; // instrument trigger if (r[i][1] != -1) { @@ -191,10 +283,6 @@ function next_row() { ch.period = PeriodForNote(ch, note); } } - patdisplay.push(pretty_row.join(" ")); - if (patdisplay.length > 16) { - patdisplay.shift(); - } } function Envelope(points, type, sustain, loopstart, loopend) { @@ -508,9 +596,21 @@ function audio_cb(e) { } var debug = document.getElementById("debug"); - debug.innerHTML = 'pat ' + cur_pat + ' row ' + (cur_row-1); - var pat = document.getElementById("pattern"); - pat.innerHTML = patdisplay.join("\n"); + debug.innerHTML = songname + '
pat ' + cur_pat + ' row ' + (cur_row-1); + + var gfx = document.getElementById("gfxpattern"); + if (cur_pat != pat_canvas_patnum) { + RenderPattern(pat_canvas, patterns[cur_pat]); + pat_canvas_patnum = cur_pat; + } + var ctx = gfx.getContext('2d'); + ctx.fillStyle = '#000'; + ctx.fillRect(0, 0, gfx.width, gfx.height); + ctx.fillStyle = '#2a5684'; + ctx.fillRect(0, gfx.height/2 - 4, gfx.width, 8); + ctx.globalCompositeOperation = 'lighten'; + ctx.drawImage(pat_canvas, 0, gfx.height / 2 - 4 - 8*(cur_row - 1)); + ctx.globalCompositeOperation = 'source-over'; }); } @@ -799,7 +899,7 @@ function playXM(arrayBuf) { var dv = new DataView(arrayBuf); window.dv = dv; - var name = getstring(dv, 17, 20); + songname = getstring(dv, 17, 20); var hlen = dv.getUint32(0x3c, true) + 0x3c; var songlen = dv.getUint16(0x40, true); song_looppos = dv.getUint16(0x42, true); @@ -847,7 +947,6 @@ function playXM(arrayBuf) { idx += 9; for (var j = 0; patsize > 0 && j < patrows; j++) { row = []; - pretty_row = []; for (var k = 0; k < nchan; k++) { var byte0 = dv.getUint8(idx); idx++; var note = -1, inst = -1, vol = -1, efftype = 0, effparam = 0; @@ -877,7 +976,6 @@ function playXM(arrayBuf) { effparam = dv.getUint8(idx); idx++; } var notedata = [note, inst, vol, efftype, effparam]; - pretty_row.push(prettify_notedata(notedata)); row.push(notedata); } pattern.push(row); @@ -1008,8 +1106,10 @@ function playXM(arrayBuf) { jsNode.connect(gainNode); var debug = document.getElementById("debug"); - console.log("loaded \"" + name + "\""); - debug.innerHTML = name; + console.log("loaded \"" + songname + "\""); + debug.innerHTML = songname; + var gfxpattern = document.getElementById("gfxpattern"); + gfxpattern.width = _pattern_cellwidth * nchan + _pattern_border; // start playing gainNode.connect(audioctx.destination);