{"id":11614,"date":"2026-11-05T14:27:11","date_gmt":"2026-11-05T13:27:11","guid":{"rendered":"https:\/\/retrofixer.it\/?page_id=11614"},"modified":"2026-11-05T14:31:23","modified_gmt":"2026-11-05T13:31:23","slug":"sid-composer","status":"publish","type":"page","link":"https:\/\/retrofixer.it\/en\/sid-composer\/","title":{"rendered":"SID Composer"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"11614\" class=\"elementor elementor-11614\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7f3c456 e-con-full e-flex e-con e-parent\" data-id=\"7f3c456\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-662b79a elementor-widget elementor-widget-html\" data-id=\"662b79a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@400;600;700;800&family=Press+Start+2P&family=VT323&display=swap\" rel=\"stylesheet\">\n\n<style>\n.rfsid{--bg:#0f0c24;--bg2:#171338;--panel:#1d1846;--line:#342c74;--text:#eceaff;--muted:#aaa5d8;--c64l:#7c70da;--mag:#e0398a;--yel:#ffd23f;--grn:#5ad15f;--cya:#3fd8ff;--red:#f94144;--org:#ff9f43;font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;color:var(--text);background:var(--bg);line-height:1.5;padding:36px 0 28px;font-size:15px}\n.rfsid *,.rfsid *::before,.rfsid *::after{box-sizing:border-box}\n.rfsid h2,.rfsid h3,.rfsid h4,.rfsid p,.rfsid ul,.rfsid ol{margin:0;padding:0}\n.rfsid h2,.rfsid h3,.rfsid h4{font-family:'Inter',system-ui,sans-serif!important;letter-spacing:0!important;text-transform:none!important;font-weight:700;color:var(--text)}\n.rfsid a{color:var(--cya)}\n.rfsid button,.rfsid input,.rfsid select,.rfsid textarea{font:inherit;color:inherit;margin:0}\n.rfsid button{cursor:pointer}\n.rfsid [hidden]{display:none!important}\n.post-views.post-11614{background:#0f0c24;color:#aaa5d8;margin:0;padding:0 16px 8px;font-size:13px}\n.rfsid code{font-family:ui-monospace,Consolas,monospace;font-size:.9em;background:var(--bg2);border:1px solid var(--line);border-radius:4px;padding:0 4px}\n.rfsid .wrap{max-width:1500px;margin:0 auto;padding:0 16px}\n.rfsid .head .kicker{font-family:'VT323',monospace;font-size:20px;color:var(--cya);margin-bottom:6px}\n.rfsid .head h2{font-family:'Press Start 2P',monospace!important;font-size:clamp(18px,3.2vw,30px);line-height:1.4;color:var(--yel);margin-bottom:12px}\n.rfsid .head .lead{color:var(--muted);max-width:980px;font-size:16px}\n.rfsid .head .lead b{color:var(--text)}\n\n\/* barre *\/\n.rfsid .app{margin-top:22px}\n.rfsid .bar{display:flex;flex-wrap:wrap;align-items:center;gap:8px 10px;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:8px 10px;margin-bottom:8px}\n.rfsid .btn{background:var(--bg2)!important;border:1px solid var(--line)!important;border-radius:7px!important;padding:7px 12px!important;font-size:14px!important;font-weight:600;color:var(--text)!important;line-height:1.2!important;white-space:nowrap;box-shadow:none!important;text-transform:none!important;letter-spacing:0!important;min-height:0!important;height:auto!important}\n.rfsid .btn:hover{border-color:var(--c64l)!important;color:#fff!important}\n.rfsid .btn.play{background:var(--mag)!important;border-color:var(--mag)!important;color:#fff!important}\n.rfsid .btn.play:hover{filter:brightness(1.1)}\n.rfsid .btn.rec[aria-pressed=true]{background:#5a1030!important;border-color:var(--red)!important;color:#ffb3b5!important}\n.rfsid .btn.rec[aria-pressed=true] span{color:var(--red)}\n.rfsid .btn.on{border-color:var(--yel)!important;color:var(--yel)!important}\n.rfsid .btn.sm{padding:5px 10px!important;font-size:13px!important}\n.rfsid .btn.xs{padding:3px 8px!important;font-size:13px!important;min-width:28px}\n.rfsid .btn.exp{color:var(--cya)!important}\n.rfsid .fld{display:inline-flex;align-items:center;gap:6px;font-size:13px;color:var(--muted);margin:0!important}\n.rfsid .fld.grow{flex:1 1 170px}\n.rfsid #rfsidEx{max-width:min(300px,62vw)}\n.rfsid .fld.grow input{flex:1;min-width:0}\n.rfsid input[type=text],.rfsid input[type=number],.rfsid select{background:var(--bg2)!important;border:1px solid var(--line)!important;border-radius:6px!important;padding:4px 6px!important;font-size:14px!important;color:var(--text)!important;height:auto!important;min-height:0!important;box-shadow:none!important;width:auto;max-width:100%;margin:0!important;line-height:1.3!important}\n.rfsid input[type=number]{width:62px}\n.rfsid input:focus,.rfsid select:focus{outline:none;border-color:var(--c64l)!important}\n.rfsid input[type=checkbox]{accent-color:var(--yel);width:15px;height:15px;margin:0!important}\n.rfsid input[type=range]{accent-color:var(--yel);width:100%;margin:0;padding:0;background:none!important;border:0!important}\n.rfsid .lbl{font-size:13px;color:var(--muted)}\n.rfsid .sep{flex:0 0 1px;align-self:stretch;background:var(--line);margin:0 2px}\n.rfsid .pos{font-family:'VT323',monospace;font-size:22px;color:var(--yel);min-width:90px;text-align:center;background:var(--bg);border:1px solid var(--line);border-radius:6px;padding:0 8px}\n.rfsid .audio{font-size:12px;color:var(--muted);margin-left:auto}\n.rfsid .audio.on{color:var(--grn)}\n.rfsid .msg{font-size:13px;color:var(--grn);margin-left:auto}\n.rfsid .msg.err{color:var(--red)}\n\n\/* griglia principale *\/\n.rfsid .grid{display:grid;grid-template-columns:200px minmax(0,1fr) 410px;gap:8px;align-items:start}\n.rfsid .mid{display:flex;flex-direction:column;gap:8px;min-width:0}\n.rfsid .grid2{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:8px;align-items:start}\n.rfsid .box{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:10px;min-width:0}\n.rfsid .box h3{font-family:'Press Start 2P',monospace!important;font-size:11px!important;line-height:1.4;color:var(--yel)!important;margin-bottom:8px!important;font-weight:400!important}\n.rfsid .hint{font-size:12.5px;color:var(--muted);margin-top:8px}\n.rfsid .hint b{color:var(--text)}\n\n\/* ordine *\/\n.rfsid .otools{display:flex;flex-wrap:wrap;gap:4px;align-items:center;margin:4px 0}\n.rfsid .olist{position:relative;max-height:430px;overflow:auto;background:var(--bg);border:1px solid var(--line);border-radius:6px;font-family:'VT323',monospace;font-size:20px;line-height:1.15;scrollbar-width:thin;scrollbar-color:var(--line) transparent}\n.rfsid .orow{display:grid;grid-template-columns:16px 30px repeat(3,1fr);align-items:center;padding:0 4px;cursor:pointer}\n.rfsid .orow:hover{background:#221c52}\n.rfsid .orow.sel{background:#2a2366}\n.rfsid .orow.play .on{color:var(--grn)}\n.rfsid .orow .lp{color:var(--mag);font-size:16px}\n.rfsid .orow .on{color:var(--c64l)}\n.rfsid .orow .oc{text-align:center;border-radius:3px}\n.rfsid .orow .oc.cur{background:var(--yel);color:#1a1440}\n\n\/* pattern *\/\n.rfsid .phead{display:grid;grid-template-columns:30px repeat(3,minmax(0,1fr));gap:0;margin-bottom:4px}\n.rfsid .vh{display:flex;flex-wrap:wrap;gap:2px;align-items:center;padding:2px 4px;border-left:1px solid var(--line)}\n.rfsid .vh .vn{font-family:'Press Start 2P',monospace;font-size:9px;color:var(--cya);margin-right:2px}\n.rfsid .vh .pn{font-family:'VT323',monospace;font-size:19px;color:var(--yel);margin-right:auto}\n.rfsid .vh .btn.xs{padding:1px 5px!important;font-size:11.5px!important;min-width:0}\n.rfsid .vh .env{flex:1 1 100%;height:4px;background:var(--bg);border-radius:2px;overflow:hidden}\n.rfsid .vh .env i{display:block;height:100%;width:0;background:var(--grn)}\n.rfsid .vh.muted .vn{color:var(--red);text-decoration:line-through}\n.rfsid .pgrid{position:relative;background:#0b0920;border:1px solid var(--line);border-radius:6px;font-family:'VT323',monospace;font-size:21px;line-height:1.08;outline:none;max-height:600px;overflow:auto;user-select:none;scrollbar-width:thin;scrollbar-color:var(--line) transparent}\n.rfsid .pgrid:focus{border-color:var(--c64l)}\n.rfsid .pr{display:grid;grid-template-columns:30px repeat(3,minmax(0,1fr))}\n.rfsid .pr .rn{color:#5b5394;text-align:right;padding-right:6px}\n.rfsid .pr.pbeat .rn,.rfsid .pr.pbar .rn{color:var(--c64l)}\n.rfsid .pr.pbeat{background:#120f30}\n.rfsid .pr.pbar{background:#17133d}\n.rfsid .pr.cur{background:#251f5c}\n.rfsid .pr.play{background:#1d3a2c}\n.rfsid .pr.play .rn{color:var(--grn)}\n.rfsid .pc{border-left:1px solid #241e55;padding:0 6px;white-space:nowrap;overflow:hidden}\n.rfsid .pc span{padding:0 1px;border-radius:2px}\n.rfsid .pc .nt{color:#fff}\n.rfsid .pc .nt.off{color:var(--red)}\n.rfsid .pc .in{color:var(--cya)}\n.rfsid .pc .cm{color:var(--org)}\n.rfsid .pc .e{color:#3d3672}\n.rfsid .pc .k{background:var(--yel);color:#1a1440!important}\n.rfsid .pgrid.rec .pc .k{background:var(--red);color:#fff!important}\n.rfsid .pc.mut{opacity:.45}\n\n\/* strumenti *\/\n.rfsid .ilist{display:flex;gap:6px;margin-bottom:8px}\n.rfsid .ilist select{flex:1;font-family:'VT323',monospace!important;font-size:18px!important;padding:2px!important;height:auto!important}\n.rfsid .ilist .icol{display:flex;flex-direction:column;gap:4px}\n.rfsid .isec{border-top:1px solid var(--line);padding-top:7px;margin-top:7px}\n.rfsid .isec h4{font-size:12px!important;font-weight:600;color:var(--muted)!important;text-transform:uppercase!important;letter-spacing:.06em!important;margin:0 0 6px!important}\n.rfsid .irow{display:grid;grid-template-columns:86px minmax(0,1fr) 64px;gap:6px;align-items:center;font-size:13px;margin:3px 0}\n.rfsid .irow .v{font-family:'VT323',monospace;font-size:17px;color:var(--yel);text-align:right;white-space:nowrap}\n.rfsid .irow input[type=number]{width:100%}\n.rfsid .ichk{display:flex;align-items:center;gap:6px;font-size:13px;margin:4px 0;color:var(--text)}\n.rfsid .iname{width:100%}\n.rfsid canvas.envc{width:100%;height:56px;display:block;background:var(--bg);border:1px solid var(--line);border-radius:6px;margin-top:4px}\n.rfsid .wt{width:100%;border-collapse:collapse!important;font-size:12px;margin:0!important;border:0!important}\n.rfsid .wt td,.rfsid .wt th{padding:2px 2px!important;border:0!important;background:none!important;text-align:center;vertical-align:middle;color:var(--text)}\n.rfsid .wt th{color:var(--muted);font-weight:600;font-size:11px}\n.rfsid .wt tr.lp td:first-child{color:var(--mag)}\n.rfsid .wt .wb{display:inline-block;min-width:0;padding:1px 2px;margin:0 1px 0 0;border:1px solid var(--line);border-radius:4px;background:var(--bg);color:#5b5394;font-size:9.5px;font-weight:700;cursor:pointer;user-select:none;letter-spacing:-.02em}\n.rfsid .wt .wb.on{background:var(--c64l);border-color:var(--c64l);color:#fff}\n.rfsid .wt .wb.on.x{background:var(--mag);border-color:var(--mag)}\n.rfsid .wt select,.rfsid .wt input{padding:1px 2px!important;font-size:12px!important}\n.rfsid .wt input[type=text]{width:40px;text-align:center}\n.rfsid .wt td.nw{white-space:nowrap}\n.rfsid .wt input[type=number]{width:42px}\n.rfsid .wt .hx{font-family:'VT323',monospace;font-size:15px;color:var(--muted)}\n.rfsid .wt .del{background:none!important;border:0!important;color:var(--muted)!important;padding:0 4px!important;font-size:15px}\n.rfsid .wt .del:hover{color:var(--red)!important}\n.rfsid .modes{display:flex;gap:4px}\n\n\/* tastiera *\/\n.rfsid .piano{position:relative;height:110px;user-select:none;touch-action:none;display:flex}\n.rfsid .piano .w{flex:1;background:#eceaff;border:1px solid #1a1440;border-radius:0 0 5px 5px;position:relative;font-family:'VT323',monospace;font-size:14px;color:#7c70da;display:flex;align-items:flex-end;justify-content:center;padding-bottom:3px;cursor:pointer}\n.rfsid .piano .b{position:absolute;top:0;height:62%;background:#1a1440;border:1px solid #000;border-radius:0 0 4px 4px;z-index:2;cursor:pointer}\n.rfsid .piano .w.dn{background:var(--yel)}\n.rfsid .piano .b.dn{background:var(--mag)}\n\n\/* monitor *\/\n.rfsid canvas#rfsidScope{width:100%;height:96px;display:block;background:#0b0920;border:1px solid var(--line);border-radius:6px}\n.rfsid .regs{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin-top:8px;font-family:'VT323',monospace;font-size:17px}\n.rfsid .regs .rg h5{font-family:'Inter',sans-serif;font-size:11px;color:var(--cya);margin:0 0 2px;font-weight:600}\n.rfsid .regs .rg div{display:flex;justify-content:space-between;gap:4px;color:var(--muted);line-height:1.1}\n.rfsid .regs .rg div b{color:var(--text);font-weight:400}\n\n\/* guida *\/\n.rfsid .guide{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px;margin-top:28px}\n.rfsid .guide h3{font-size:17px!important;color:var(--yel)!important;margin:18px 0 8px!important}\n.rfsid .guide .gcol>h3:first-child{margin-top:0!important}\n.rfsid .prose{color:var(--muted);font-size:14.5px}\n.rfsid .prose p{margin:0 0 10px}\n.rfsid .prose b{color:var(--text)}\n.rfsid .prose ol{padding-left:20px!important}\n.rfsid .prose li{margin:0 0 6px}\n.rfsid .prose table{width:100%;border-collapse:collapse!important;margin:0!important;border:0!important;font-size:13.5px}\n.rfsid .prose td{border:0!important;border-bottom:1px solid var(--line)!important;padding:4px 6px!important;background:none!important;color:var(--muted);vertical-align:top;text-align:left}\n.rfsid .prose td:first-child{font-family:'VT323',monospace;font-size:18px;color:var(--cya);white-space:nowrap}\n\n@media (max-width:1250px){\n .rfsid .grid{grid-template-columns:190px minmax(0,1fr)}\n .rfsid .grid2{grid-template-columns:minmax(0,1fr)}\n .rfsid .grid .instr{grid-column:1\/-1}\n}\n@media (max-width:820px){\n .rfsid .grid,.rfsid .grid2,.rfsid .guide{grid-template-columns:minmax(0,1fr)}\n .rfsid .olist{max-height:220px}\n .rfsid .pgrid{font-size:17px}\n .rfsid .pc{padding:0 2px}\n}\n<\/style>\n\n<div class=\"rfsid\" data-no-translation>\n <div class=\"wrap\">\n\n  <header class=\"head\">\n   <p class=\"kicker\" data-t=\"kicker\"><\/p>\n   <h2>SID COMPOSER<\/h2>\n   <p class=\"lead\" data-th=\"lead\"><\/p>\n  <\/header>\n\n  <div class=\"app\" id=\"rfsidApp\">\n\n   <div class=\"bar transport\">\n    <button type=\"button\" class=\"btn play\" id=\"rfsidPlay\" data-tt=\"ttPlay\"><span aria-hidden=\"true\">&#9654;<\/span> <span data-t=\"play\"><\/span><\/button>\n    <button type=\"button\" class=\"btn\" id=\"rfsidPlayPos\" data-tt=\"ttPlayPos\"><span aria-hidden=\"true\">&#9654;|<\/span> <span data-t=\"playPos\"><\/span><\/button>\n    <button type=\"button\" class=\"btn\" id=\"rfsidPlayPat\" data-tt=\"ttPlayPat\"><span aria-hidden=\"true\">&#10227;<\/span> <span data-t=\"playPat\"><\/span><\/button>\n    <button type=\"button\" class=\"btn stop\" id=\"rfsidStop\" data-tt=\"ttStop\"><span aria-hidden=\"true\">&#9632;<\/span> <span data-t=\"stop\"><\/span><\/button>\n    <button type=\"button\" class=\"btn rec\" id=\"rfsidRec\" aria-pressed=\"false\" data-tt=\"ttRec\"><span aria-hidden=\"true\">&#9679;<\/span> REC<\/button>\n    <label class=\"fld\"><span data-t=\"octave\"><\/span> <select id=\"rfsidOct\"><\/select><\/label>\n    <label class=\"fld\"><span data-t=\"step\"><\/span> <input type=\"number\" id=\"rfsidStep\" min=\"0\" max=\"16\" value=\"1\"><\/label>\n    <label class=\"fld chk\"><input type=\"checkbox\" id=\"rfsidFollow\" checked> <span data-t=\"follow\"><\/span><\/label>\n    <span class=\"pos\" id=\"rfsidPos\">--:--<\/span>\n    <span class=\"audio\" id=\"rfsidAudio\" data-t=\"audioOff\"><\/span>\n   <\/div>\n\n   <div class=\"bar song\">\n    <label class=\"fld grow\"><span data-t=\"title\"><\/span> <input type=\"text\" id=\"rfsidTitle\" maxlength=\"31\"><\/label>\n    <label class=\"fld grow\"><span data-t=\"author\"><\/span> <input type=\"text\" id=\"rfsidAuthor\" maxlength=\"31\"><\/label>\n    <label class=\"fld grow\"><span data-t=\"released\"><\/span> <input type=\"text\" id=\"rfsidReleased\" maxlength=\"31\"><\/label>\n    <label class=\"fld\"><span data-t=\"speed\"><\/span> <input type=\"number\" id=\"rfsidSpeed\" min=\"1\" max=\"127\"><\/label>\n    <label class=\"fld\"><span data-t=\"rows\"><\/span> <input type=\"number\" id=\"rfsidRows\" min=\"1\" max=\"64\"><\/label>\n    <label class=\"fld\"><span data-t=\"volume\"><\/span> <input type=\"number\" id=\"rfsidVol\" min=\"0\" max=\"15\"><\/label>\n    <label class=\"fld\"><span>SID<\/span> <select id=\"rfsidModel\"><option value=\"6581\">6581<\/option><option value=\"8580\">8580<\/option><\/select><\/label>\n    <label class=\"fld\"><span data-t=\"video\"><\/span> <select id=\"rfsidVideo\"><option value=\"PAL\">PAL 50 Hz<\/option><option value=\"NTSC\">NTSC 60 Hz<\/option><\/select><\/label>\n   <\/div>\n\n   <div class=\"bar files\">\n    <label class=\"fld\"><span data-t=\"examples\"><\/span> <select id=\"rfsidEx\"><\/select><\/label>\n    <button type=\"button\" class=\"btn sm\" id=\"rfsidNew\" data-t=\"new\"><\/button>\n    <button type=\"button\" class=\"btn sm\" id=\"rfsidOpen\" data-t=\"open\"><\/button>\n    <button type=\"button\" class=\"btn sm\" id=\"rfsidSave\" data-t=\"save\"><\/button>\n    <button type=\"button\" class=\"btn sm\" id=\"rfsidUndo\" data-tt=\"ttUndo\">&#8630;<\/button>\n    <button type=\"button\" class=\"btn sm\" id=\"rfsidRedo\" data-tt=\"ttRedo\">&#8631;<\/button>\n    <span class=\"sep\"><\/span>\n    <span class=\"lbl\" data-t=\"exportC64\"><\/span>\n    <button type=\"button\" class=\"btn sm exp\" id=\"rfsidPRG\">.PRG<\/button>\n    <button type=\"button\" class=\"btn sm exp\" id=\"rfsidSID\">.SID<\/button>\n    <button type=\"button\" class=\"btn sm exp\" id=\"rfsidASM\">.ASM ACME<\/button>\n    <button type=\"button\" class=\"btn sm exp\" id=\"rfsidTASS\">.ASM 64tass<\/button>\n    <button type=\"button\" class=\"btn sm exp\" id=\"rfsidWAV\">.WAV<\/button>\n    <input type=\"file\" id=\"rfsidFile\" accept=\".json,.rfsid,application\/json\" hidden>\n    <span class=\"msg\" id=\"rfsidMsg\" role=\"status\"><\/span>\n   <\/div>\n\n   <div class=\"grid\">\n    <section class=\"box order\">\n     <h3 data-t=\"orderT\"><\/h3>\n     <div class=\"otools\">\n      <button type=\"button\" class=\"btn xs\" id=\"rfsidOAdd\" data-tt=\"ttOAdd\">+<\/button>\n      <button type=\"button\" class=\"btn xs\" id=\"rfsidODup\" data-tt=\"ttODup\">&#10697;<\/button>\n      <button type=\"button\" class=\"btn xs\" id=\"rfsidOClone\" data-tt=\"ttOClone\">&#10697;+<\/button>\n      <button type=\"button\" class=\"btn xs\" id=\"rfsidODel\" data-tt=\"ttODel\">&minus;<\/button>\n      <button type=\"button\" class=\"btn xs\" id=\"rfsidOUp\" data-tt=\"ttOUp\">&#8593;<\/button>\n      <button type=\"button\" class=\"btn xs\" id=\"rfsidODown\" data-tt=\"ttODown\">&#8595;<\/button>\n      <button type=\"button\" class=\"btn xs\" id=\"rfsidOLoop\" data-tt=\"ttOLoop\">L<\/button>\n     <\/div>\n     <div class=\"olist\" id=\"rfsidOrder\"><\/div>\n     <div class=\"otools\">\n      <span class=\"lbl\" data-t=\"cellPat\"><\/span>\n      <button type=\"button\" class=\"btn xs\" id=\"rfsidPPrev\">&lt;<\/button>\n      <button type=\"button\" class=\"btn xs\" id=\"rfsidPNext\">&gt;<\/button>\n      <button type=\"button\" class=\"btn xs\" id=\"rfsidPFree\" data-t=\"freePat\" data-tt=\"ttFree\"><\/button>\n     <\/div>\n     <p class=\"hint\" data-th=\"orderHint\"><\/p>\n    <\/section>\n\n    <div class=\"mid\">\n    <section class=\"box pattern\">\n     <div class=\"phead\" id=\"rfsidPHead\"><\/div>\n     <div class=\"pgrid\" id=\"rfsidGrid\" tabindex=\"0\" role=\"grid\" aria-label=\"Pattern\"><\/div>\n     <p class=\"hint\" data-th=\"gridHint\"><\/p>\n    <\/section>\n    <div class=\"grid2\">\n    <section class=\"box keys\">\n     <h3 data-t=\"pianoT\"><\/h3>\n     <div class=\"piano\" id=\"rfsidPiano\"><\/div>\n     <p class=\"hint\" data-th=\"pianoHint\"><\/p>\n    <\/section>\n    <section class=\"box monitor\">\n     <h3 data-t=\"monT\"><\/h3>\n     <canvas id=\"rfsidScope\" width=\"560\" height=\"96\"><\/canvas>\n     <div class=\"regs\" id=\"rfsidRegs\"><\/div>\n    <\/section>\n    <\/div>\n    <\/div>\n\n    <section class=\"box instr\">\n     <h3 data-t=\"instrT\"><\/h3>\n     <div class=\"ilist\">\n      <select id=\"rfsidInsSel\" size=\"6\"><\/select>\n      <div class=\"icol\">\n       <button type=\"button\" class=\"btn xs\" id=\"rfsidINew\" data-t=\"iNew\"><\/button>\n       <button type=\"button\" class=\"btn xs\" id=\"rfsidIDup\" data-t=\"iDup\"><\/button>\n       <button type=\"button\" class=\"btn xs\" id=\"rfsidIDel\" data-t=\"iDel\"><\/button>\n      <\/div>\n     <\/div>\n     <div id=\"rfsidInsEd\"><\/div>\n    <\/section>\n   <\/div>\n\n  <\/div>\n\n  <section class=\"guide\">\n   <div class=\"gcol\">\n    <h3 data-t=\"gStartT\"><\/h3>\n    <div class=\"prose\" data-th=\"gStart\"><\/div>\n    <h3 data-t=\"gKeysT\"><\/h3>\n    <div class=\"prose\" data-th=\"gKeys\"><\/div>\n   <\/div>\n   <div class=\"gcol\">\n    <h3 data-t=\"gCmdT\"><\/h3>\n    <div class=\"prose\" data-th=\"gCmd\"><\/div>\n    <h3 data-t=\"gInsT\"><\/h3>\n    <div class=\"prose\" data-th=\"gIns\"><\/div>\n   <\/div>\n   <div class=\"gcol\">\n    <h3 data-t=\"gC64T\"><\/h3>\n    <div class=\"prose\" data-th=\"gC64\"><\/div>\n    <h3 data-t=\"gTechT\"><\/h3>\n    <div class=\"prose\" data-th=\"gTech\"><\/div>\n   <\/div>\n  <\/section>\n <\/div>\n<\/div>\n\n<script>\n\/* RetroFixer \u00b7 SID Composer \u00b7 emulazione del chip SID MOS 6581 \/ 8580\n * Scritta da zero per questa pagina. Oscillatori, rumore, sync, ring e inviluppo girano ciclo per ciclo\n * (985 248 cicli al secondo in PAL), compreso il \"bug\" dell'ADSR del contatore a 15 bit.\n * Il filtro \u00e8 un state-variable a 2 poli con la curva del cutoff tipica del 6581 o lineare dell'8580.\n * Le forme d'onda combinate vengono da un modello a \"bit vicini\" (approssimato, non campionato da un chip).\n *\/\n(function (RF) {\n'use strict';\n\nvar RATE = [9, 32, 63, 95, 149, 220, 267, 313, 392, 977, 1954, 3126, 3907, 11720, 19532, 31251];\nvar NORM = 1 \/ (2048 * 255);\n\n\/\/ ---- forme d'onda combinate ------------------------------------------------------------------\n\/\/ Ogni bit d'uscita guarda i bit vicini con un peso che cala con la distanza: se la media supera la\n\/\/ soglia il bit resta a 1. Cos\u00ec le combinazioni suonano \"pi\u00f9 magre\" di un semplice AND, come sul chip.\nfunction combTable(kind, strength, thr) {\n  var t = new Uint16Array(4096), b = new Float32Array(12), w = new Float32Array(24), i, j, k;\n  for (i = 0; i < 24; i++) w[ i] = Math.pow(strength, -Math.abs(i - 12));\n  for (i = 0; i < 4096; i++) {\n    var tri = ((i & 0x800) ? (~i & 0x7FF) : (i & 0x7FF)) << 1;\n    for (j = 0; j < 12; j++) {\n      var s = (i >> j) & 1, tr = (tri >> j) & 1;\n      b[ j] = kind === 'ST' ? (s + tr) \/ 2 : kind === 'PT' ? (1 + tr) \/ 2 : kind === 'PS' ? (1 + s) \/ 2 : (1 + s + tr) \/ 3;\n    }\n    var out = 0;\n    for (k = 0; k < 12; k++) {\n      var sum = 0, n = 0;\n      for (j = 0; j < 12; j++) { sum += w[j - k + 12] * b[ j]; n += w[j - k + 12]; }\n      if (sum \/ n > thr) out |= 1 << k;\n    }\n    t[ i] = out;\n  }\n  return t;\n}\nvar TABLES = {};\nfunction tablesFor(model) {\n  if (TABLES[ model]) return TABLES[ model];\n  var p = model === 8580\n    ? { ST: [2.2, 0.80], PT: [2.6, 0.70], PS: [2.4, 0.72], PST: [2.2, 0.78] }\n    : { ST: [1.7, 0.86], PT: [2.0, 0.80], PS: [1.9, 0.82], PST: [1.7, 0.86] };\n  return (TABLES[ model] = { ST: combTable('ST', p.ST[0], p.ST[1]), PT: combTable('PT', p.PT[0], p.PT[1]),\n    PS: combTable('PS', p.PS[0], p.PS[1]), PST: combTable('PST', p.PST[0], p.PST[1]) });\n}\n\n\/\/ ---- curva del cutoff (Hz) ---------------------------------------------------------------------\n\/\/ 6581: curva \"media\" (ogni chip \u00e8 diverso), con la zona morta in basso e la salita ripida a met\u00e0.\nvar FC6581 = [[0, 200], [256, 230], [384, 280], [512, 400], [640, 700], [768, 1300], [896, 2400],\n  [1024, 3800], [1280, 6800], [1536, 9600], [1792, 12000], [2047, 14000]];\nfunction cutoffHz(model, fc) {\n  if (model === 8580) return 30 + fc * 5.8;\n  for (var i = 1; i < FC6581.length; i++) {\n    if (fc <= FC6581[ i][0]) {\n      var a = FC6581[i - 1], b = FC6581[ i], t = (fc - a[0]) \/ (b[0] - a[0]);\n      return Math.exp(Math.log(a[1]) + t * (Math.log(b[1]) - Math.log(a[1])));\n    }\n  }\n  return 14000;\n}\n\nfunction SID(sampleRate, model, clockHz) {\n  this.fs = sampleRate || 48000;\n  this.regs = new Uint8Array(32);\n  this.acc = new Int32Array(3); this.freq = new Int32Array(3); this.pw = new Int32Array(3);\n  this.ctrl = new Int32Array(3); this.last = new Int32Array(3); this.shift = new Int32Array(3); this.nout = new Int32Array(3);\n  this.env = new Int32Array(3); this.state = new Int32Array(3); this.rc = new Int32Array(3); this.rp = new Int32Array(3);\n  this.ec = new Int32Array(3); this.ep = new Int32Array(3); this.hold = new Int32Array(3);\n  this.att = new Int32Array(3); this.dec = new Int32Array(3); this.sus = new Int32Array(3); this.rel = new Int32Array(3);\n  this.out = new Float32Array(4096); this.outN = 0;\n  this.mute = 0;\n  this.setModel(model || 6581);\n  this.setClock(clockHz || 985248);\n  this.reset();\n}\nSID.prototype.reset = function () {\n  for (var v = 0; v < 3; v++) {\n    this.acc[ v] = 0; this.freq[ v] = 0; this.pw[ v] = 0; this.ctrl[ v] = 0; this.last[ v] = 0;\n    this.shift[ v] = 0x7FFFFF; this.nout[ v] = noiseOut(0x7FFFFF);\n    this.env[ v] = 0; this.state[ v] = 2; this.rc[ v] = 0; this.rp[ v] = RATE[0]; this.ec[ v] = 0; this.ep[ v] = 1; this.hold[ v] = 1;\n    this.att[ v] = this.dec[ v] = this.sus[ v] = this.rel[ v] = 0;\n  }\n  this.regs.fill(0);\n  this.fc = 0; this.res = 0; this.filt = 0; this.mode = 0; this.vol = 0; this.v3off = 0;\n  this.lp = 0; this.bp = 0; this.e1 = 0; this.e2 = 0;\n  this.fsum = 0; this.dsum = 0; this.n = 0; this.sc = this.cps;\n  this.updFilter();\n};\nSID.prototype.setClock = function (hz) {\n  this.clockHz = hz; this.cps = hz \/ this.fs; this.sc = this.cps;\n  this.updFilter();\n};\nSID.prototype.setModel = function (model) {\n  this.model = model === 8580 ? 8580 : 6581;\n  var t = tablesFor(this.model);\n  this.tST = t.ST; this.tPT = t.PT; this.tPS = t.PS; this.tPST = t.PST;\n  \/\/ il 6581 ha uno zero della forma d'onda spostato e un'uscita in continua: per questo il registro\n  \/\/ del volume fa \"click\" (le vecchie digi). Sull'8580 \u00e8 quasi tutto centrato.\n  this.wz = this.model === 8580 ? 0x800 : 0x380;\n  this.vdc = this.model === 8580 ? 0 : Math.round(0.18 \/ NORM);\n  this.mdc = this.model === 8580 ? 0.02 : 0.25;\n  this.updFilter();\n};\nSID.prototype.updFilter = function () {\n  if (!this.fs) return;\n  var hz = cutoffHz(this.model, this.fc || 0);\n  var f = 2 * Math.sin(Math.PI * Math.min(hz, this.fs * 0.9) \/ (this.fs * 2));\n  this.f = Math.min(f, 1.3);\n  this.q = this.model === 8580 ? Math.pow(2, (4 - (this.res || 0)) \/ 8) : 1 \/ (0.707 + (this.res || 0) * 0.1);\n  this.ea = 1 - Math.exp(-2 * Math.PI * 16000 \/ this.fs);\n  this.eb = 1 - Math.exp(-2 * Math.PI * 16 \/ this.fs);\n};\n\nfunction noiseOut(s) {\n  return ((s >> 9) & 0x800) | ((s >> 8) & 0x400) | ((s >> 5) & 0x200) | ((s >> 3) & 0x100) |\n    ((s >> 2) & 0x080) | ((s << 1) & 0x040) | ((s << 3) & 0x020) | ((s << 4) & 0x010);\n}\n\/\/ quando il rumore \u00e8 combinato con un'altra forma d'onda i bit a 0 si \"scrivono\" nel registro a scorrimento\nfunction noiseWriteBack(s, o) {\n  o = ~o;\n  return s & ~(((o & 0x800) << 9) | ((o & 0x400) << 8) | ((o & 0x200) << 5) | ((o & 0x100) << 3) |\n    ((o & 0x080) << 2) | ((o & 0x040) >> 1) | ((o & 0x020) >> 3) | ((o & 0x010) >> 4));\n}\n\nSID.prototype.write = function (r, val) {\n  r &= 0x1F; val &= 0xFF;\n  this.regs[ r] = val;\n  if (r < 21) {\n    var v = (r \/ 7) | 0, k = r - v * 7;\n    switch (k) {\n      case 0: this.freq[ v] = (this.freq[ v] & 0xFF00) | val; break;\n      case 1: this.freq[ v] = (this.freq[ v] & 0xFF) | (val << 8); break;\n      case 2: this.pw[ v] = (this.pw[ v] & 0xF00) | val; break;\n      case 3: this.pw[ v] = (this.pw[ v] & 0xFF) | ((val & 0x0F) << 8); break;\n      case 4:\n        var old = this.ctrl[ v];\n        if ((val & 1) && !(old & 1)) { this.state[ v] = 0; this.rp[ v] = RATE[this.att[ v]]; this.hold[ v] = 0; }\n        else if (!(val & 1) && (old & 1)) { this.state[ v] = 2; this.rp[ v] = RATE[this.rel[ v]]; }\n        if (val & 8) { this.acc[ v] = 0; this.shift[ v] = 0x7FFFFF; this.nout[ v] = noiseOut(0x7FFFFF); }\n        this.ctrl[ v] = val;\n        break;\n      case 5:\n        this.att[ v] = val >> 4; this.dec[ v] = val & 15;\n        if (this.state[ v] === 0) this.rp[ v] = RATE[this.att[ v]]; else if (this.state[ v] === 1) this.rp[ v] = RATE[this.dec[ v]];\n        break;\n      case 6:\n        this.sus[ v] = val >> 4; this.rel[ v] = val & 15;\n        if (this.state[ v] === 2) this.rp[ v] = RATE[this.rel[ v]];\n        break;\n    }\n    return;\n  }\n  switch (r) {\n    case 21: this.fc = (this.fc & 0x7F8) | (val & 7); this.updFilter(); break;\n    case 22: this.fc = (this.fc & 7) | (val << 3); this.updFilter(); break;\n    case 23: this.res = val >> 4; this.filt = val & 7; this.updFilter(); break;\n    case 24: this.mode = (val >> 4) & 7; this.v3off = val & 0x80; this.vol = val & 15; break;\n  }\n};\nSID.prototype.read = function (r) {\n  r &= 0x1F;\n  if (r === 27) return this.last[2] >> 4;\n  if (r === 28) return this.env[2];\n  return 0;\n};\n\n\/\/ fa avanzare il chip di n cicli; ogni campione audio finisce in this.out\nSID.prototype.clock = function (n) {\n  var acc = this.acc, freq = this.freq, pw = this.pw, ctrl = this.ctrl, last = this.last, shift = this.shift, nout = this.nout;\n  var env = this.env, state = this.state, rc = this.rc, rp = this.rp, ec = this.ec, ep = this.ep, hold = this.hold;\n  var dec = this.dec, sus = this.sus;\n  var tST = this.tST, tPT = this.tPT, tPS = this.tPS, tPST = this.tPST, wz = this.wz, vdc = this.vdc;\n  var filt = this.filt, mute = this.mute, v3off = this.v3off;\n  var fsum = this.fsum, dsum = this.dsum, cnt = this.n, sc = this.sc, cps = this.cps;\n  var rise0 = 0, rise1 = 0, rise2 = 0, v, a, p, c, o, w, e;\n  while (n-- > 0) {\n    \/\/ oscillatori\n    for (v = 0; v < 3; v++) {\n      c = ctrl[ v];\n      if (c & 8) { if (v === 0) rise0 = 0; else if (v === 1) rise1 = 0; else rise2 = 0; continue; }\n      p = acc[ v]; a = (p + freq[ v]) & 0xFFFFFF; acc[ v] = a;\n      if (v === 0) rise0 = ~p & a & 0x800000; else if (v === 1) rise1 = ~p & a & 0x800000; else rise2 = ~p & a & 0x800000;\n      if (~p & a & 0x80000) {\n        var s = shift[ v];\n        if ((c & 0x80) && (c & 0x70)) s = noiseWriteBack(s, last[ v]);\n        s = ((s << 1) & 0x7FFFFF) | (((s >> 22) ^ (s >> 17)) & 1);\n        shift[ v] = s; nout[ v] = noiseOut(s);\n      }\n    }\n    \/\/ hard sync: la sorgente della voce 1 \u00e8 la 3, della 2 \u00e8 la 1, della 3 \u00e8 la 2\n    if (rise2 && (ctrl[0] & 2)) acc[0] = 0;\n    if (rise0 && (ctrl[1] & 2)) acc[1] = 0;\n    if (rise1 && (ctrl[2] & 2)) acc[2] = 0;\n    \/\/ inviluppi e uscita delle voci\n    for (v = 0; v < 3; v++) {\n      e = rc[ v] + 1;\n      if (e & 0x8000) e = (e + 1) & 0x7FFF;\n      if (e === rp[ v]) {\n        e = 0;\n        if (state[ v] === 0 || ++ec[ v] === ep[ v]) {\n          ec[ v] = 0;\n          if (!hold[ v]) {\n            var lv = env[ v];\n            if (state[ v] === 0) { lv = (lv + 1) & 0xFF; if (lv === 0xFF) { state[ v] = 1; rp[ v] = RATE[dec[ v]]; } }\n            else if (state[ v] === 1) { if (lv !== sus[ v] * 17) lv = (lv - 1) & 0xFF; }\n            else lv = (lv - 1) & 0xFF;\n            env[ v] = lv;\n            switch (lv) {\n              case 0xFF: ep[ v] = 1; break; case 0x5D: ep[ v] = 2; break; case 0x36: ep[ v] = 4; break;\n              case 0x1A: ep[ v] = 8; break; case 0x0E: ep[ v] = 16; break; case 0x06: ep[ v] = 30; break;\n              case 0x00: ep[ v] = 1; hold[ v] = 1; break;\n            }\n          }\n        }\n      }\n      rc[ v] = e;\n      c = ctrl[ v]; a = acc[ v];\n      switch (c >> 4) {\n        case 0: w = last[ v]; break;\n        case 1:\n          if (c & 4) { var src = v === 0 ? acc[2] : acc[v - 1]; o = (a ^ src) & 0x800000; } else o = a & 0x800000;\n          w = ((o ? ~a : a) >> 11) & 0xFFF; break;\n        case 2: w = a >> 12; break;\n        case 3: w = tST[a >> 12]; break;\n        case 4: w = ((c & 8) || (a >> 12) >= pw[ v]) ? 0xFFF : 0; break;\n        case 5: w = ((c & 8) || (a >> 12) >= pw[ v]) ? tPT[a >> 12] : 0; break;\n        case 6: w = ((c & 8) || (a >> 12) >= pw[ v]) ? tPS[a >> 12] : 0; break;\n        case 7: w = ((c & 8) || (a >> 12) >= pw[ v]) ? tPST[a >> 12] : 0; break;\n        case 8: w = nout[ v]; break;\n        default:\n          o = (c >> 4) & 7;\n          w = o === 1 ? ((a & 0x800000 ? ~a : a) >> 11) & 0xFFF : o === 2 ? a >> 12 : o === 3 ? tST[a >> 12] :\n            ((a >> 12) >= pw[ v] ? (o === 4 ? 0xFFF : o === 5 ? tPT[a >> 12] : o === 6 ? tPS[a >> 12] : tPST[a >> 12]) : 0);\n          w &= nout[ v];\n      }\n      last[ v] = w;\n      if (!((mute >> v) & 1)) {\n        o = (w - wz) * env[ v] + vdc;\n        if ((filt >> v) & 1) fsum += o; else if (v !== 2 || !v3off) dsum += o;\n      }\n    }\n    cnt++;\n    if (--sc <= 0) {\n      sc += cps;\n      this.fsum = fsum; this.dsum = dsum; this.n = cnt;\n      this.emit();\n      fsum = 0; dsum = 0; cnt = 0;\n    }\n  }\n  this.fsum = fsum; this.dsum = dsum; this.n = cnt; this.sc = sc;\n};\n\nSID.prototype.emit = function () {\n  var k = NORM \/ (this.n || 1), fi = this.fsum * k, di = this.dsum * k;\n  var lp = this.lp, bp = this.bp, hp = 0, f = this.f, q = this.q;\n  for (var i = 0; i < 2; i++) {\n    hp = fi - lp - q * bp;\n    bp += f * hp;\n    lp += f * bp;\n  }\n  if (this.model === 6581) { bp = bp \/ (1 + 0.02 * bp * bp); lp = lp \/ (1 + 0.01 * lp * lp); }   \/\/ leggera saturazione\n  this.lp = lp; this.bp = bp;\n  var m = this.mode, fo = ((m & 1) ? lp : 0) + ((m & 2) ? bp : 0) + ((m & 4) ? hp : 0);\n  var x = ((fo + di) * 0.22 + this.mdc) * this.vol \/ 15;\n  this.e1 += this.ea * (x - this.e1);          \/\/ filtro esterno passa basso ~16 kHz\n  this.e2 += this.eb * (this.e1 - this.e2);    \/\/ condensatore d'uscita (passa alto ~16 Hz)\n  var y = this.e1 - this.e2;\n  if (y > 0.9) y = 0.9 + (y - 0.9) \/ (1 + (y - 0.9) * 8); else if (y < -0.9) y = -0.9 + (y + 0.9) \/ (1 - (y + 0.9) * 8);\n  if (this.outN < this.out.length) this.out[this.outN++] = y;\n};\n\nRF.SID = SID;\nRF.SID_RATE = RATE;\nRF.cutoffHz = cutoffHz;\n})(globalThis.RFSid = globalThis.RFSid || {});\n\n\/* RetroFixer \u00b7 SID Composer \u00b7 CPU 6502 (MOS 6510 del C64)\n * Tutti gli opcode documentati, con i cicli e il ciclo in pi\u00f9 quando l'indirizzo cambia pagina.\n * La stessa tabella serve all'assembler, cos\u00ec CPU e assembler non possono andare in disaccordo.\n *\/\n(function (RF) {\n'use strict';\n\nvar LIST = (\n  '69 ADC imm 2|65 ADC zp 3|75 ADC zpx 4|6D ADC abs 4|7D ADC abx 4 p|79 ADC aby 4 p|61 ADC izx 6|71 ADC izy 5 p|' +\n  '29 AND imm 2|25 AND zp 3|35 AND zpx 4|2D AND abs 4|3D AND abx 4 p|39 AND aby 4 p|21 AND izx 6|31 AND izy 5 p|' +\n  '0A ASL acc 2|06 ASL zp 5|16 ASL zpx 6|0E ASL abs 6|1E ASL abx 7|' +\n  '90 BCC rel 2|B0 BCS rel 2|F0 BEQ rel 2|30 BMI rel 2|D0 BNE rel 2|10 BPL rel 2|50 BVC rel 2|70 BVS rel 2|' +\n  '24 BIT zp 3|2C BIT abs 4|00 BRK imp 7|18 CLC imp 2|D8 CLD imp 2|58 CLI imp 2|B8 CLV imp 2|' +\n  'C9 CMP imm 2|C5 CMP zp 3|D5 CMP zpx 4|CD CMP abs 4|DD CMP abx 4 p|D9 CMP aby 4 p|C1 CMP izx 6|D1 CMP izy 5 p|' +\n  'E0 CPX imm 2|E4 CPX zp 3|EC CPX abs 4|C0 CPY imm 2|C4 CPY zp 3|CC CPY abs 4|' +\n  'C6 DEC zp 5|D6 DEC zpx 6|CE DEC abs 6|DE DEC abx 7|CA DEX imp 2|88 DEY imp 2|' +\n  '49 EOR imm 2|45 EOR zp 3|55 EOR zpx 4|4D EOR abs 4|5D EOR abx 4 p|59 EOR aby 4 p|41 EOR izx 6|51 EOR izy 5 p|' +\n  'E6 INC zp 5|F6 INC zpx 6|EE INC abs 6|FE INC abx 7|E8 INX imp 2|C8 INY imp 2|' +\n  '4C JMP abs 3|6C JMP ind 5|20 JSR abs 6|' +\n  'A9 LDA imm 2|A5 LDA zp 3|B5 LDA zpx 4|AD LDA abs 4|BD LDA abx 4 p|B9 LDA aby 4 p|A1 LDA izx 6|B1 LDA izy 5 p|' +\n  'A2 LDX imm 2|A6 LDX zp 3|B6 LDX zpy 4|AE LDX abs 4|BE LDX aby 4 p|' +\n  'A0 LDY imm 2|A4 LDY zp 3|B4 LDY zpx 4|AC LDY abs 4|BC LDY abx 4 p|' +\n  '4A LSR acc 2|46 LSR zp 5|56 LSR zpx 6|4E LSR abs 6|5E LSR abx 7|EA NOP imp 2|' +\n  '09 ORA imm 2|05 ORA zp 3|15 ORA zpx 4|0D ORA abs 4|1D ORA abx 4 p|19 ORA aby 4 p|01 ORA izx 6|11 ORA izy 5 p|' +\n  '48 PHA imp 3|08 PHP imp 3|68 PLA imp 4|28 PLP imp 4|' +\n  '2A ROL acc 2|26 ROL zp 5|36 ROL zpx 6|2E ROL abs 6|3E ROL abx 7|' +\n  '6A ROR acc 2|66 ROR zp 5|76 ROR zpx 6|6E ROR abs 6|7E ROR abx 7|40 RTI imp 6|60 RTS imp 6|' +\n  'E9 SBC imm 2|E5 SBC zp 3|F5 SBC zpx 4|ED SBC abs 4|FD SBC abx 4 p|F9 SBC aby 4 p|E1 SBC izx 6|F1 SBC izy 5 p|' +\n  '38 SEC imp 2|F8 SED imp 2|78 SEI imp 2|' +\n  '85 STA zp 3|95 STA zpx 4|8D STA abs 4|9D STA abx 5|99 STA aby 5|81 STA izx 6|91 STA izy 6|' +\n  '86 STX zp 3|96 STX zpy 4|8E STX abs 4|84 STY zp 3|94 STY zpx 4|8C STY abs 4|' +\n  'AA TAX imp 2|A8 TAY imp 2|BA TSX imp 2|8A TXA imp 2|9A TXS imp 2|98 TYA imp 2'\n).split('|');\n\nvar OPS = new Array(256), BYNAME = {};\nLIST.forEach(function (s) {\n  var f = s.split(' '), op = parseInt(f[0], 16);\n  OPS[ op] = { op: op, n: f[1], m: f[2], c: +f[3], p: f[4] === 'p' };\n  (BYNAME[f[1]] = BYNAME[f[1]] || {})[f[2]] = op;\n});\nRF.OPS = OPS;\nRF.OPS_BYNAME = BYNAME;\nRF.OPLEN = { imp: 1, acc: 1, imm: 2, zp: 2, zpx: 2, zpy: 2, rel: 2, izx: 2, izy: 2, abs: 3, abx: 3, aby: 3, ind: 3 };\n\nfunction CPU(mem) {\n  this.mem = mem || new Uint8Array(65536);\n  this.a = 0; this.x = 0; this.y = 0; this.sp = 0xFF; this.pc = 0;\n  this.c = 0; this.z = 0; this.i = 1; this.d = 0; this.v = 0; this.n = 0;\n  this.cycles = 0; this.halted = false;\n  this.ioWrite = null;  \/\/ function (addr, value) per $D400-$D7FF (SID)\n  this.ioRead = null;\n}\nCPU.prototype.rd = function (a) {\n  if ((a & 0xFC00) === 0xD400 && this.ioRead) return this.ioRead(a) & 0xFF;\n  return this.mem[ a];\n};\nCPU.prototype.wr = function (a, v) {\n  if ((a & 0xFC00) === 0xD400 && this.ioWrite) this.ioWrite(a, v);\n  this.mem[ a] = v;\n};\nCPU.prototype.getP = function (b) {\n  return (this.n << 7) | (this.v << 6) | 0x20 | (b ? 0x10 : 0) | (this.d << 3) | (this.i << 2) | (this.z ? 2 : 0) | this.c;\n};\nCPU.prototype.setP = function (p) {\n  this.n = (p >> 7) & 1; this.v = (p >> 6) & 1; this.d = (p >> 3) & 1; this.i = (p >> 2) & 1; this.z = (p >> 1) & 1; this.c = p & 1;\n};\nCPU.prototype.push = function (v) { this.mem[0x100 | this.sp] = v & 0xFF; this.sp = (this.sp - 1) & 0xFF; };\nCPU.prototype.pull = function () { this.sp = (this.sp + 1) & 0xFF; return this.mem[0x100 | this.sp]; };\nCPU.prototype.nz = function (v) { this.z = v === 0 ? 1 : 0; this.n = (v >> 7) & 1; return v; };\n\nCPU.prototype.adc = function (v) {\n  var a = this.a, c = this.c, t;\n  if (!this.d) {\n    t = a + v + c;\n    this.v = ((~(a ^ v) & (a ^ t)) & 0x80) ? 1 : 0;\n    this.c = t > 0xFF ? 1 : 0;\n    this.a = this.nz(t & 0xFF);\n    return;\n  }\n  var lo = (a & 0x0F) + (v & 0x0F) + c;\n  if (lo > 9) lo += 6;\n  var hi = (a >> 4) + (v >> 4) + (lo > 0x0F ? 1 : 0);\n  this.z = ((a + v + c) & 0xFF) === 0 ? 1 : 0;\n  this.n = (hi & 8) ? 1 : 0;\n  this.v = ((~(a ^ v) & (a ^ (hi << 4))) & 0x80) ? 1 : 0;\n  if (hi > 9) hi += 6;\n  this.c = hi > 0x0F ? 1 : 0;\n  this.a = ((hi << 4) | (lo & 0x0F)) & 0xFF;\n};\nCPU.prototype.sbc = function (v) {\n  if (!this.d) { this.adc(v ^ 0xFF); return; }\n  var a = this.a, b = 1 - this.c, t = a - v - b;\n  this.v = (((a ^ v) & (a ^ t)) & 0x80) ? 1 : 0;\n  this.nz(t & 0xFF);\n  var lo = (a & 0x0F) - (v & 0x0F) - b, hi = (a >> 4) - (v >> 4);\n  if (lo < 0) { lo -= 6; hi--; }\n  if (hi < 0) hi -= 6;\n  this.c = t >= 0 ? 1 : 0;\n  this.a = ((hi << 4) | (lo & 0x0F)) & 0xFF;\n};\nCPU.prototype.cmp = function (r, v) { var t = r - v; this.c = t >= 0 ? 1 : 0; this.nz(t & 0xFF); };\n\n\/\/ esegue un'istruzione, restituisce i cicli\nCPU.prototype.step = function () {\n  var m = this.mem, pc = this.pc, e = OPS[m[ pc]];\n  if (!e) { this.halted = true; return 0; }\n  pc = (pc + 1) & 0xFFFF;\n  var ad = 0, b, z, cyc = e.c;\n  switch (e.m) {\n    case 'imm': ad = pc; pc++; break;\n    case 'zp': ad = m[ pc]; pc++; break;\n    case 'zpx': ad = (m[ pc] + this.x) & 0xFF; pc++; break;\n    case 'zpy': ad = (m[ pc] + this.y) & 0xFF; pc++; break;\n    case 'abs': ad = m[ pc] | (m[(pc + 1) & 0xFFFF] << 8); pc += 2; break;\n    case 'abx': b = m[ pc] | (m[(pc + 1) & 0xFFFF] << 8); ad = (b + this.x) & 0xFFFF; if (e.p && ((b ^ ad) & 0xFF00)) cyc++; pc += 2; break;\n    case 'aby': b = m[ pc] | (m[(pc + 1) & 0xFFFF] << 8); ad = (b + this.y) & 0xFFFF; if (e.p && ((b ^ ad) & 0xFF00)) cyc++; pc += 2; break;\n    case 'ind': b = m[ pc] | (m[(pc + 1) & 0xFFFF] << 8); ad = m[ b] | (m[(b & 0xFF00) | ((b + 1) & 0xFF)] << 8); pc += 2; break;\n    case 'izx': z = (m[ pc] + this.x) & 0xFF; ad = m[ z] | (m[(z + 1) & 0xFF] << 8); pc++; break;\n    case 'izy': z = m[ pc]; b = m[ z] | (m[(z + 1) & 0xFF] << 8); ad = (b + this.y) & 0xFFFF; if (e.p && ((b ^ ad) & 0xFF00)) cyc++; pc++; break;\n    case 'rel': b = m[ pc]; pc++; ad = (pc + (b < 128 ? b : b - 256)) & 0xFFFF; break;\n  }\n  pc &= 0xFFFF;\n  this.pc = pc;\n  var v, t;\n  switch (e.n) {\n    case 'LDA': this.a = this.nz(this.rd(ad)); break;\n    case 'LDX': this.x = this.nz(this.rd(ad)); break;\n    case 'LDY': this.y = this.nz(this.rd(ad)); break;\n    case 'STA': this.wr(ad, this.a); break;\n    case 'STX': this.wr(ad, this.x); break;\n    case 'STY': this.wr(ad, this.y); break;\n    case 'ADC': this.adc(this.rd(ad)); break;\n    case 'SBC': this.sbc(this.rd(ad)); break;\n    case 'AND': this.a = this.nz(this.a & this.rd(ad)); break;\n    case 'ORA': this.a = this.nz(this.a | this.rd(ad)); break;\n    case 'EOR': this.a = this.nz(this.a ^ this.rd(ad)); break;\n    case 'CMP': this.cmp(this.a, this.rd(ad)); break;\n    case 'CPX': this.cmp(this.x, this.rd(ad)); break;\n    case 'CPY': this.cmp(this.y, this.rd(ad)); break;\n    case 'BIT': v = this.rd(ad); this.z = (this.a & v) ? 0 : 1; this.n = (v >> 7) & 1; this.v = (v >> 6) & 1; break;\n    case 'ASL': case 'LSR': case 'ROL': case 'ROR':\n      v = e.m === 'acc' ? this.a : this.rd(ad);\n      if (e.n === 'ASL') { this.c = (v >> 7) & 1; v = (v << 1) & 0xFF; }\n      else if (e.n === 'LSR') { this.c = v & 1; v >>= 1; }\n      else if (e.n === 'ROL') { t = this.c; this.c = (v >> 7) & 1; v = ((v << 1) | t) & 0xFF; }\n      else { t = this.c; this.c = v & 1; v = (v >> 1) | (t << 7); }\n      this.nz(v);\n      if (e.m === 'acc') this.a = v; else this.wr(ad, v);\n      break;\n    case 'INC': this.wr(ad, this.nz((this.rd(ad) + 1) & 0xFF)); break;\n    case 'DEC': this.wr(ad, this.nz((this.rd(ad) - 1) & 0xFF)); break;\n    case 'INX': this.x = this.nz((this.x + 1) & 0xFF); break;\n    case 'INY': this.y = this.nz((this.y + 1) & 0xFF); break;\n    case 'DEX': this.x = this.nz((this.x - 1) & 0xFF); break;\n    case 'DEY': this.y = this.nz((this.y - 1) & 0xFF); break;\n    case 'TAX': this.x = this.nz(this.a); break;\n    case 'TAY': this.y = this.nz(this.a); break;\n    case 'TXA': this.a = this.nz(this.x); break;\n    case 'TYA': this.a = this.nz(this.y); break;\n    case 'TSX': this.x = this.nz(this.sp); break;\n    case 'TXS': this.sp = this.x; break;\n    case 'PHA': this.push(this.a); break;\n    case 'PHP': this.push(this.getP(true)); break;\n    case 'PLA': this.a = this.nz(this.pull()); break;\n    case 'PLP': this.setP(this.pull()); break;\n    case 'CLC': this.c = 0; break;\n    case 'SEC': this.c = 1; break;\n    case 'CLI': this.i = 0; break;\n    case 'SEI': this.i = 1; break;\n    case 'CLD': this.d = 0; break;\n    case 'SED': this.d = 1; break;\n    case 'CLV': this.v = 0; break;\n    case 'NOP': break;\n    case 'JMP': this.pc = ad; break;\n    case 'JSR': t = (pc - 1) & 0xFFFF; this.push(t >> 8); this.push(t); this.pc = ad; break;\n    case 'RTS': t = this.pull(); t |= this.pull() << 8; this.pc = (t + 1) & 0xFFFF; break;\n    case 'RTI': this.setP(this.pull()); t = this.pull(); t |= this.pull() << 8; this.pc = t; break;\n    case 'BRK':\n      t = (pc + 1) & 0xFFFF; this.push(t >> 8); this.push(t); this.push(this.getP(true)); this.i = 1;\n      this.pc = m[0xFFFE] | (m[0xFFFF] << 8); break;\n    default: \/\/ salti condizionati\n      var take;\n      switch (e.n) {\n        case 'BCC': take = !this.c; break; case 'BCS': take = this.c; break;\n        case 'BEQ': take = this.z; break; case 'BNE': take = !this.z; break;\n        case 'BMI': take = this.n; break; case 'BPL': take = !this.n; break;\n        case 'BVC': take = !this.v; break; case 'BVS': take = this.v; break;\n      }\n      if (take) { cyc++; if ((pc ^ ad) & 0xFF00) cyc++; this.pc = ad; }\n  }\n  this.cycles += cyc;\n  return cyc;\n};\n\nRF.CPU6502 = CPU;\n})(globalThis.RFSid = globalThis.RFSid || {});\n\n\/* RetroFixer \u00b7 SID Composer \u00b7 la \"macchina\": 64 KB di RAM, CPU 6502 e SID\n * Ogni frame (50 Hz PAL, 60 Hz NTSC) chiama la routine play del player 6502, proprio come l'interrupt\n * raster sul C64. Le scritture nei registri del SID arrivano al chip nel ciclo esatto in cui la CPU le fa.\n * Lo stesso codice gira nell'AudioWorklet (anteprima) e nella pagina (esportazione WAV, prove).\n *\/\n(function (RF) {\n'use strict';\n\nvar CLOCKS = { PAL: { hz: 985248, frame: 19656 }, NTSC: { hz: 1022727, frame: 17095 } };\nvar SENTINEL = 0xFFF0;\nRF.CLOCKS = CLOCKS;\n\nfunction Engine(sampleRate) {\n  var self = this;\n  this.fs = sampleRate;\n  this.mem = new Uint8Array(65536);\n  this.cpu = new RF.CPU6502(this.mem);\n  this.sid = new RF.SID(sampleRate, 6581, CLOCKS.PAL.hz);\n  this.video = 'PAL';\n  this.frameCycles = CLOCKS.PAL.frame;\n  this.sidPos = 0;\n  this.syms = null;\n  this.queue = [];\n  this.fifo = new Float32Array(16384); this.fr = 0; this.fw = 0;\n  this.frames = 0;\n  this.writes = null;     \/\/ se \u00e8 un array, ci finiscono tutte le scritture [frame, ciclo, registro, valore]\n  this.error = '';\n  this.cpu.ioWrite = function (a, v) {\n    var c = self.cpu.cycles;\n    if (c > self.sidPos) { self.sid.clock(c - self.sidPos); self.sidPos = c; }\n    self.sid.write(a & 0x1F, v);\n    if (self.writes) self.writes.push(self.frames, c, a & 0x1F, v);\n  };\n  this.cpu.ioRead = function (a) { return self.sid.read(a & 0x1F); };\n}\n\nEngine.prototype.config = function (model, video) {\n  if (model) this.sid.setModel(+model);\n  if (video && CLOCKS[ video]) {\n    this.video = video; this.frameCycles = CLOCKS[ video].frame; this.sid.setClock(CLOCKS[ video].hz);\n  }\n};\n\n\/\/ carica il binario del player+canzone. Con preserve tiene le variabili del player (modifiche durante la riproduzione)\nEngine.prototype.load = function (bin, addr, syms, preserve) {\n  var keep = null;\n  if (preserve && this.syms && syms && this.syms.vars === syms.vars && this.syms.varsend === syms.varsend)\n    keep = this.mem.slice(syms.vars, syms.varsend);\n  this.mem.set(bin, addr);\n  if (keep) this.mem.set(keep, syms.vars);\n  this.syms = syms;\n};\n\n\/\/ chiama una subroutine 6502 come farebbe JSR; si ferma all'RTS finale\nEngine.prototype.call = function (addr, a, x, y) {\n  var cpu = this.cpu, m = this.mem, limit = cpu.cycles + 40000;\n  cpu.a = a & 0xFF; cpu.x = x & 0xFF; cpu.y = y & 0xFF; cpu.sp = 0xFF; cpu.d = 0;\n  var r = SENTINEL - 1;\n  m[0x1FF] = r >> 8; m[0x1FE] = r & 0xFF; cpu.sp = 0xFD;\n  cpu.pc = addr; cpu.halted = false;\n  while (cpu.pc !== SENTINEL) {\n    cpu.step();\n    if (cpu.halted || cpu.cycles > limit) { this.error = 'CPU ' + (cpu.halted ? 'opcode' : 'loop') + ' @' + cpu.pc.toString(16); break; }\n  }\n};\n\nEngine.prototype.command = function (c) { this.queue.push(c); };\n\nEngine.prototype.runFrame = function () {\n  var s = this.syms, cpu = this.cpu;\n  cpu.cycles = 0; this.sidPos = 0;\n  while (this.queue.length) {\n    var c = this.queue.shift();\n    if (!s) continue;\n    if (c.t === 'init') this.call(s.init, c.a, 0, 0);\n    else if (c.t === 'jam') this.call(s.jam, c.note, c.voice, c.ins);\n  }\n  if (s) this.call(s.play, 0, 0, 0);\n  var rest = this.frameCycles - this.sidPos;\n  if (rest > 0) this.sid.clock(rest);\n  this.sidPos = 0;\n  this.frames++;\n  var o = this.sid.out, n = this.sid.outN, f = this.fifo, L = f.length;\n  for (var i = 0; i < n; i++) { f[this.fw] = o[ i]; this.fw = (this.fw + 1) % L; }\n  this.sid.outN = 0;\n};\n\nEngine.prototype.available = function () { return (this.fw - this.fr + this.fifo.length) % this.fifo.length; };\n\nEngine.prototype.render = function (out, n) {\n  var f = this.fifo, L = f.length, done = 0;\n  while (done < n) {\n    var k = Math.min(n - done, 4096);\n    while (this.available() < k) this.runFrame();\n    for (var i = 0; i < k; i++) { out[done + i] = f[this.fr]; this.fr = (this.fr + 1) % L; }\n    done += k;\n  }\n};\n\nEngine.prototype.status = function () {\n  var s = this.syms, m = this.mem, sid = this.sid;\n  return {\n    t: 'st', frame: this.frames,\n    playing: s ? m[s.playing] : 0, pos: s ? m[s.curpos] : 0, row: s ? m[s.currow] : 0, speed: s ? m[s.speed] : 0,\n    regs: Array.prototype.slice.call(sid.regs, 0, 25),\n    env: [sid.env[0], sid.env[1], sid.env[2]], err: this.error\n  };\n};\n\n\/\/ gestione dei messaggi (worklet o fallback ScriptProcessor)\nEngine.prototype.message = function (d) {\n  switch (d.t) {\n    case 'load': this.load(d.bin, d.addr, d.syms, d.preserve); break;\n    case 'init': case 'jam': this.command(d); break;\n    case 'cfg': this.config(d.model, d.video); break;\n    case 'mute': this.sid.mute = d.mask; break;\n    case 'reset': this.queue = []; this.sid.reset(); break;\n  }\n};\n\nRF.Engine = Engine;\n})(globalThis.RFSid = globalThis.RFSid || {});\n\n\/* RetroFixer \u00b7 SID Composer \u00b7 assembler 6502 a due passate (sintassi ACME)\n * Capisce: etichette in colonna 0, costanti (nome = valore), * = indirizzo, !byte, !word, !fill, !text, !to\n * ed espressioni con + - * \/ & | ^ << >> e i prefissi < (byte basso) e > (byte alto).\n * Con lo stesso sorgente ACME o 64tass producono byte identici (il player non usa forme ambigue).\n *\/\n(function (RF) {\n'use strict';\n\nfunction Expr(src, syms, pc) { this.s = src; this.i = 0; this.syms = syms; this.pc = pc; this.unknown = false; }\nExpr.prototype.ws = function () { while (this.i < this.s.length && \/\\s\/.test(this.s[this.i])) this.i++; };\nExpr.prototype.peek = function (t) { this.ws(); return this.s.substr(this.i, t.length) === t; };\nExpr.prototype.eat = function (t) { if (this.peek(t)) { this.i += t.length; return true; } return false; };\nExpr.prototype.parse = function () {\n  this.ws();\n  if (this.eat('<')) return this.parse() & 0xFF;\n  if (this.eat('>')) return (this.parse() >> 8) & 0xFF;\n  return this.or();\n};\nExpr.prototype.or = function () { var v = this.xor(); while (this.eat('|')) v = v | this.xor(); return v; };\nExpr.prototype.xor = function () { var v = this.and(); while (this.eat('^')) v = v ^ this.and(); return v; };\nExpr.prototype.and = function () { var v = this.shift(); while (this.eat('&')) v = v & this.shift(); return v; };\nExpr.prototype.shift = function () {\n  var v = this.add();\n  for (;;) { if (this.eat('<<')) v = v << this.add(); else if (this.eat('>>')) v = v >> this.add(); else return v; }\n};\nExpr.prototype.add = function () {\n  var v = this.mul();\n  for (;;) { if (this.eat('+')) v += this.mul(); else if (this.eat('-')) v -= this.mul(); else return v; }\n};\nExpr.prototype.mul = function () {\n  var v = this.unary();\n  for (;;) {\n    if (this.eat('*')) v *= this.unary();\n    else if (this.eat('\/')) { var d = this.unary(); v = d ? Math.trunc(v \/ d) : 0; }\n    else return v;\n  }\n};\nExpr.prototype.unary = function () {\n  if (this.eat('-')) return -this.unary();\n  if (this.eat('(')) { var v = this.parse(); if (!this.eat(')')) throw new Error('manca )'); return v; }\n  return this.atom();\n};\nExpr.prototype.atom = function () {\n  this.ws();\n  var s = this.s, m, rest = s.slice(this.i);\n  if ((m = \/^\\$([0-9a-fA-F]+)\/.exec(rest))) { this.i += m[0].length; return parseInt(m[1], 16); }\n  if ((m = \/^%([01]+)\/.exec(rest))) { this.i += m[0].length; return parseInt(m[1], 2); }\n  if ((m = \/^[0-9]+\/.exec(rest))) { this.i += m[0].length; return parseInt(m[0], 10); }\n  if ((m = \/^'(.)'\/.exec(rest))) { this.i += m[0].length; return m[1].charCodeAt(0); }\n  if ((m = \/^[A-Za-z_][\\w]*\/.exec(rest))) {\n    this.i += m[0].length;\n    var k = m[0].toLowerCase();\n    if (Object.prototype.hasOwnProperty.call(this.syms, k)) return this.syms[ k];\n    this.unknown = true; this.undef = m[0]; return 0;\n  }\n  if (rest[0] === '*') { this.i++; return this.pc; }\n  throw new Error('espressione non valida: ' + rest);\n};\n\nfunction evalExpr(src, syms, pc) {\n  var e = new Expr(src, syms, pc), v = e.parse();\n  e.ws();\n  if (e.i < e.s.length) throw new Error('testo in pi\u00f9: ' + e.s.slice(e.i));\n  return { v: v, unknown: e.unknown, undef: e.undef };\n}\n\nfunction stripComment(line) {\n  var q = false;\n  for (var i = 0; i < line.length; i++) {\n    var ch = line[ i];\n    if (ch === '\"') q = !q;\n    else if (ch === \"'\" && !q && line[i + 2] === \"'\") i += 2;\n    else if (ch === ';' && !q) return line.slice(0, i);\n  }\n  return line;\n}\nfunction splitArgs(s) {\n  var out = [], cur = '', q = false;\n  for (var i = 0; i < s.length; i++) {\n    var ch = s[ i];\n    if (ch === '\"') q = !q;\n    if (ch === ',' && !q) { out.push(cur.trim()); cur = ''; } else cur += ch;\n  }\n  if (cur.trim() !== '') out.push(cur.trim());\n  return out;\n}\n\nRF.assemble = function (src) {\n  var lines = src.split(\/\\r?\\n\/), syms = {}, errors = [], zpDecision = {};\n  var mem = new Uint8Array(65536), used = new Uint8Array(65536), min = 0x10000, max = -1;\n  var BY = RF.OPS_BYNAME, LEN = RF.OPLEN;\n\n  function pass(n) {\n    var pc = 0;\n    function emit(b) {\n      if (n === 2) { mem[pc & 0xFFFF] = b & 0xFF; used[pc & 0xFFFF] = 1; if (pc < min) min = pc; if (pc > max) max = pc; }\n      pc++;\n    }\n    function val(expr, li, allowUnknown) {\n      var r = evalExpr(expr, syms, pc);\n      if (r.unknown && n === 2) throw new Error('simbolo non definito: ' + r.undef);\n      if (r.unknown && !allowUnknown) throw new Error('simbolo non ancora definito: ' + r.undef);\n      return r;\n    }\n    for (var li = 0; li < lines.length; li++) {\n      var raw = stripComment(lines[ li]), line = raw.replace(\/\\s+$\/, '');\n      if (!line.trim()) continue;\n      try {\n        var m = \/^\\s*([A-Za-z_][\\w]*)\\s*=\\s*(.+)$\/.exec(line);\n        if (m) { var cv = val(m[2], li, false); syms[m[1].toLowerCase()] = cv.v; continue; }\n        var stmt = line;\n        if (\/^[A-Za-z_]\/.test(line)) {\n          m = \/^([A-Za-z_][\\w]*)(.*)$\/.exec(line);\n          var lab = m[1].toLowerCase();\n          if (n === 1 && Object.prototype.hasOwnProperty.call(syms, lab)) throw new Error('etichetta doppia: ' + m[1]);\n          syms[ lab] = pc;\n          stmt = m[2];\n        }\n        stmt = stmt.trim();\n        if (!stmt) continue;\n        if ((m = \/^\\*\\s*=\\s*(.+)$\/.exec(stmt))) { pc = val(m[1], li, false).v; continue; }\n        if (stmt[0] === '!') {\n          m = \/^!(\\w+)\\s*(.*)$\/.exec(stmt);\n          var d = m[1].toLowerCase(), args = splitArgs(m[2]);\n          if (d === 'to' || d === 'cpu' || d === 'sl') continue;\n          if (d === 'byte' || d === 'by' || d === '8') { args.forEach(function (a) { emit(val(a, li, true).v); }); continue; }\n          if (d === 'word' || d === 'wo' || d === '16') { args.forEach(function (a) { var w = val(a, li, true).v; emit(w); emit(w >> 8); }); continue; }\n          if (d === 'fill' || d === 'fi') { var cnt = val(args[0], li, false).v, fv = args[1] ? val(args[1], li, true).v : 0; for (var k = 0; k < cnt; k++) emit(fv); continue; }\n          if (d === 'text' || d === 'tx' || d === 'raw') {\n            args.forEach(function (a) {\n              if (a[0] === '\"') { for (var q = 1; q < a.length - 1; q++) emit(a.charCodeAt(q)); } else emit(val(a, li, true).v);\n            });\n            continue;\n          }\n          throw new Error('direttiva sconosciuta: !' + d);\n        }\n        m = \/^([A-Za-z]{3})\\b\\s*(.*)$\/.exec(stmt);\n        if (!m) throw new Error('istruzione non valida: ' + stmt);\n        var mn = m[1].toUpperCase(), opd = m[2].trim(), modes = BY[ mn];\n        if (!modes) throw new Error('istruzione sconosciuta: ' + m[1]);\n        var mode, expr = null, r;\n        if (opd === '' || \/^a$\/i.test(opd)) { mode = modes.imp !== undefined ? 'imp' : 'acc'; }\n        else if (opd[0] === '#') { mode = 'imm'; expr = opd.slice(1); }\n        else if ((r = \/^\\((.+),\\s*x\\s*\\)$\/i.exec(opd))) { mode = 'izx'; expr = r[1]; }\n        else if ((r = \/^\\((.+)\\)\\s*,\\s*y$\/i.exec(opd))) { mode = 'izy'; expr = r[1]; }\n        else if (mn === 'JMP' && (r = \/^\\((.+)\\)$\/.exec(opd))) { mode = 'ind'; expr = r[1]; }\n        else if ((r = \/^(.+),\\s*([xy])$\/i.exec(opd))) { mode = 'ab' + r[2].toLowerCase(); expr = r[1]; }\n        else if (modes.rel !== undefined) { mode = 'rel'; expr = opd; }\n        else { mode = 'abs'; expr = opd; }\n        var value = 0;\n        if (expr !== null) {\n          var ev = val(expr, li, true);\n          value = ev.v;\n          if (mode === 'abs' || mode === 'abx' || mode === 'aby') {\n            var zm = mode === 'abs' ? 'zp' : mode === 'abx' ? 'zpx' : 'zpy';\n            if (n === 1) zpDecision[ li] = !ev.unknown && value >= 0 && value < 256 && modes[ zm] !== undefined;\n            if (zpDecision[ li]) mode = zm;\n          }\n        }\n        if (modes[ mode] === undefined) throw new Error('modo di indirizzamento non valido per ' + mn);\n        var op = modes[ mode], len = LEN[ mode];\n        if (n === 2) {\n          if (mode === 'rel') {\n            var off = value - (pc + 2);\n            if (off < -128 || off > 127) throw new Error('salto troppo lontano (' + off + ')');\n            value = off & 0xFF;\n          } else if (len === 2 && (value < -128 || value > 255)) throw new Error('valore fuori da 8 bit: ' + value);\n        }\n        emit(op);\n        if (len >= 2) emit(value);\n        if (len === 3) emit(value >> 8);\n      } catch (err) {\n        if (n === 2 || !\/non ancora definito\/.test(err.message)) errors.push({ line: li + 1, msg: err.message, text: lines[ li] });\n        else errors.push({ line: li + 1, msg: err.message, text: lines[ li] });\n      }\n    }\n  }\n  pass(1);\n  if (!errors.length) pass(2);\n  return {\n    mem: mem, start: min, end: max + 1, syms: syms, errors: errors,\n    bytes: function (from, to) { return mem.slice(from === undefined ? min : from, to === undefined ? max + 1 : to); }\n  };\n};\n})(globalThis.RFSid = globalThis.RFSid || {});\n\n\/* RetroFixer \u00b7 SID Composer \u00b7 il player 6502 e la trasformazione della canzone in dati\n * Il sorgente qui sotto \u00e8 quello che finisce nel .PRG, nel .SID e nel file .asm esportato. L'anteprima del\n * browser esegue esattamente questi byte su una CPU 6502 emulata: i registri del SID sono gli stessi del C64.\n *\n *   $1000  init  A = posizione di partenza nell'ordine (A >= $80: fermo, solo strumenti)\n *   $1003  play  da chiamare una volta per frame (50 Hz PAL \/ 60 Hz NTSC)\n *   $1006  jam   A = nota 0-94 ($FF = rilascia), X = voce 0-2, Y = strumento\n *\/\n(function (RF) {\n'use strict';\n\nRF.PLAYER_VERSION = 1;\n\nRF.PLAYER_SRC = [\n'; =====================================================================',\n';  RetroFixer SID Composer - player v1 (www.retrofixer.it)',\n'; =====================================================================',\n'ZP       = $fb                 ; puntatore in pagina zero ($fb\/$fc)',\n'SID      = $d400',\n'',\n'         * = $1000',\n'         jmp init',\n'         jmp play',\n'         jmp jamnote',\n'',\n'; ---- variabili (indirizzo fisso) ----',\n'vars',\n'playing  !byte 0',\n'speed    !byte 0',\n'tick     !byte 0',\n'orderpos !byte 0',\n'row      !byte 0',\n'curpos   !byte 0',\n'currow   !byte 0',\n'rowofs   !byte 0',\n'cutlo    !byte 0',\n'cuthi    !byte 0',\n'cutspd   !byte 0',\n'resrt    !byte 0',\n'modevol  !byte 0',\n'tmp      !byte 0',\n'tmp2     !byte 0',\n't_note   !byte 0',\n't_ins    !byte 0',\n't_cmd    !byte 0',\n't_par    !byte 0',\n'v_note   !fill 3,0',\n'v_inst   !fill 3,0',\n'v_gate   !fill 3,0',\n'v_wave   !fill 3,0',\n'v_wtpos  !fill 3,0',\n'v_wtdur  !fill 3,0',\n'v_wnote  !fill 3,0',\n'v_sldlo  !fill 3,0',\n'v_sldhi  !fill 3,0',\n'v_porta  !fill 3,0',\n'v_cmd    !fill 3,0',\n'v_par    !fill 3,0',\n'v_pwlo   !fill 3,0',\n'v_pwhi   !fill 3,0',\n'v_pwspd  !fill 3,0',\n'v_viblo  !fill 3,0',\n'v_vibhi  !fill 3,0',\n'v_vofslo !fill 3,0',\n'v_vofshi !fill 3,0',\n'v_vibdel !fill 3,0',\n'v_vibcnt !fill 3,0',\n'v_vibq   !fill 3,0',\n'v_vibspd !fill 3,0',\n'v_ad     !fill 3,0',\n'v_sr     !fill 3,0',\n'v_frqlo  !fill 3,0',\n'v_frqhi  !fill 3,0',\n'varsend',\n'',\n'; ---- init: A = posizione di partenza ($80-$ff = fermo) ----',\n'init     pha',\n'         ldx #0',\n'         lda #0',\n'in_clr   sta vars,x',\n'         inx',\n'         cpx #varsend-vars',\n'         bne in_clr',\n'         ldx #$18',\n'in_sid   sta SID,x',\n'         dex',\n'         bpl in_sid',\n'         lda song_speed',\n'         sta speed',\n'         lda song_vol',\n'         sta modevol',\n'         pla',\n'         bmi in_stop',\n'         sta orderpos',\n'         lda #1',\n'         sta playing',\n'in_stop  rts',\n'',\n'; ---- play: una volta per frame ----',\n'play     lda playing',\n'         beq fx',\n'         lda tick',\n'         bne pl_nonew',\n'         jsr newrow',\n'pl_nonew lda speed',\n'         cmp #3',\n'         bcc pl_nohr',\n'         sec',\n'         sbc #2',\n'         cmp tick',\n'         bne pl_nohr',\n'         jsr hardrst',\n'pl_nohr  inc tick',\n'         lda tick',\n'         cmp speed',\n'         bcc fx',\n'         lda #0',\n'         sta tick',\n'fx       ldx #2',\n'fx_loop  jsr voice',\n'         dex',\n'         bpl fx_loop',\n'         lda cutspd',\n'         beq fx_wf',\n'         jsr cutsweep',\n'fx_wf    lda cutlo',\n'         sta tmp',\n'         and #7',\n'         sta SID+$15',\n'         lda cuthi',\n'         sta tmp2',\n'         lsr tmp2',\n'         ror tmp',\n'         lsr tmp2',\n'         ror tmp',\n'         lsr tmp2',\n'         ror tmp',\n'         lda tmp',\n'         sta SID+$16',\n'         lda resrt',\n'         sta SID+$17',\n'         lda modevol',\n'         sta SID+$18',\n'         rts',\n'',\n'; ---- cutoff + velocita\\' (con segno), limitato a 0-2047 ----',\n'cutsweep ldy #0',\n'         lda cutspd',\n'         bpl cs_pos',\n'         dey',\n'cs_pos   clc',\n'         adc cutlo',\n'         sta cutlo',\n'         tya',\n'         adc cuthi',\n'         sta cuthi',\n'         bmi cs_zero',\n'         cmp #8',\n'         bcc cs_ok',\n'         lda #7',\n'         sta cuthi',\n'         lda #$ff',\n'         sta cutlo',\n'         rts',\n'cs_zero  lda #0',\n'         sta cuthi',\n'         sta cutlo',\n'cs_ok    rts',\n'',\n'; ---- legge una riga per le 3 voci e avanza ----',\n'newrow   lda orderpos',\n'         cmp song_len',\n'         bcc nr_pos',\n'         lda song_loop',\n'         sta orderpos',\n'nr_pos   lda row',\n'         cmp song_rows',\n'         bcc nr_row',\n'         lda #0',\n'         sta row',\n'nr_row   lda orderpos',\n'         sta curpos',\n'         lda row',\n'         sta currow',\n'         asl',\n'         asl',\n'         sta rowofs',\n'         ldx #0',\n'nr_loop  jsr readvoc',\n'         inx',\n'         cpx #3',\n'         bne nr_loop',\n'         inc row',\n'         lda row',\n'         cmp song_rows',\n'         bcc nr_done',\n'         lda #0',\n'         sta row',\n'         inc orderpos',\n'         lda orderpos',\n'         cmp song_len',\n'         bcc nr_done',\n'         lda song_loop',\n'         sta orderpos',\n'nr_done  rts',\n'',\n'; ---- ZP = indirizzo del pattern della voce X alla posizione orderpos ----',\n'setptr   lda ordlo,x',\n'         sta ZP',\n'         lda ordhi,x',\n'         sta ZP+1',\n'         ldy orderpos',\n'         lda (ZP),y',\n'         tay',\n'         lda patlo,y',\n'         sta ZP',\n'         lda pathi,y',\n'         sta ZP+1',\n'         rts',\n'',\n'readvoc  jsr setptr',\n'         ldy rowofs',\n'         lda (ZP),y',\n'         sta t_note',\n'         iny',\n'         lda (ZP),y',\n'         sta t_ins',\n'         iny',\n'         lda (ZP),y',\n'         sta t_cmd',\n'         sta v_cmd,x',\n'         iny',\n'         lda (ZP),y',\n'         sta t_par',\n'         sta v_par,x',\n'         lda t_ins',\n'         beq rv_noins',\n'         sta v_inst,x',\n'rv_noins lda t_note',\n'         beq docmd',\n'         cmp #$fe',\n'         bne rv_note',\n'         lda #0',\n'         sta v_gate,x',\n'         beq docmd',\n'rv_note  sec',\n'         sbc #1',\n'         ldy t_cmd',\n'         cpy #3',\n'         bne rv_trig',\n'         jsr portaset',\n'         jmp docmd',\n'rv_trig  jsr trigger',\n'',\n'; ---- comandi immediati della riga ----',\n'docmd    lda t_cmd',\n'         bne dc_go',\n'         rts',\n'dc_go    cmp #3',\n'         bne dc_n3',\n'         lda t_par',\n'         sta v_porta,x',\n'         rts',\n'dc_n3    cmp #4',\n'         bne dc_n4',\n'         lda t_par',\n'         lsr',\n'         lsr',\n'         lsr',\n'         lsr',\n'         bne dc_vs',\n'         lda #1',\n'dc_vs    sta v_vibspd,x',\n'         sta v_vibcnt,x',\n'         lda #0',\n'         sta v_vibdel,x',\n'         lda t_par',\n'         and #$0f',\n'         jmp setvib',\n'dc_n4    cmp #5',\n'         bne dc_n5',\n'         lda t_par',\n'         sta v_ad,x',\n'         rts',\n'dc_n5    cmp #6',\n'         bne dc_n6',\n'         lda t_par',\n'         sta v_sr,x',\n'         rts',\n'dc_n6    cmp #8',\n'         bne dc_n8',\n'         lda t_par',\n'         asl',\n'         asl',\n'         asl',\n'         asl',\n'         sta v_pwlo,x',\n'         lda t_par',\n'         lsr',\n'         lsr',\n'         lsr',\n'         lsr',\n'         sta v_pwhi,x',\n'         rts',\n'dc_n8    cmp #9',\n'         bne dc_n9',\n'         lda t_par',\n'         sta v_pwspd,x',\n'         rts',\n'dc_n9    cmp #$0a',\n'         bne dc_na',\n'         lda t_par',\n'         asl',\n'         asl',\n'         asl',\n'         sta cutlo',\n'         lda t_par',\n'         lsr',\n'         lsr',\n'         lsr',\n'         lsr',\n'         lsr',\n'         sta cuthi',\n'         lda #0',\n'         sta cutspd',\n'         rts',\n'dc_na    cmp #$0b',\n'         bne dc_nb',\n'         lda t_par',\n'         and #$f0',\n'         sta tmp',\n'         lda resrt',\n'         and #$0f',\n'         ora tmp',\n'         sta resrt',\n'         lda t_par',\n'         and #$07',\n'         asl',\n'         asl',\n'         asl',\n'         asl',\n'         sta tmp',\n'         lda modevol',\n'         and #$8f',\n'         ora tmp',\n'         sta modevol',\n'         rts',\n'dc_nb    cmp #$0c',\n'         bne dc_nc',\n'         lda t_par',\n'         and #$0f',\n'         sta tmp',\n'         lda modevol',\n'         and #$f0',\n'         ora tmp',\n'         sta modevol',\n'         rts',\n'dc_nc    cmp #$0e',\n'         bne dc_ne',\n'         lda t_par',\n'         sta cutspd',\n'         rts',\n'dc_ne    cmp #$0f',\n'         bne dc_done',\n'         lda t_par',\n'         beq dc_done',\n'         sta speed',\n'dc_done  rts',\n'',\n'; ---- nuova nota: A = nota 0-94, X = voce ----',\n'trigger  sta v_note,x',\n'         sta v_wnote,x',\n'         ldy v_inst,x',\n'         lda ins_ad,y',\n'         sta v_ad,x',\n'         lda ins_sr,y',\n'         sta v_sr,x',\n'         lda ins_wt,y',\n'         sta v_wtpos,x',\n'         lda #0',\n'         sta v_wtdur,x',\n'         sta v_sldlo,x',\n'         sta v_sldhi,x',\n'         sta v_porta,x',\n'         sta v_vofslo,x',\n'         sta v_vofshi,x',\n'         sta v_vibq,x',\n'         lda #1',\n'         sta v_gate,x',\n'         lda ins_flags,y',\n'         and #2',\n'         bne tr_nopw',\n'         lda ins_pwlo,y',\n'         sta v_pwlo,x',\n'         lda ins_pwhi,y',\n'         sta v_pwhi,x',\n'         lda ins_pwspd,y',\n'         sta v_pwspd,x',\n'tr_nopw  lda ins_vibdel,y',\n'         sta v_vibdel,x',\n'         lda ins_vibspd,y',\n'         sta v_vibspd,x',\n'         sta v_vibcnt,x',\n'         lda ins_vibdep,y',\n'         jsr setvib',\n'         ldy v_inst,x',\n'         lda ins_fflag,y',\n'         bpl tr_nofil',\n'         lda resrt',\n'         ora vbit,x',\n'         sta resrt',\n'         lda ins_fflag,y',\n'         and #$40',\n'         beq tr_fdone',\n'         lda ins_fres,y',\n'         and #$f0',\n'         sta tmp',\n'         lda resrt',\n'         and #$0f',\n'         ora tmp',\n'         sta resrt',\n'         lda ins_fres,y',\n'         asl',\n'         asl',\n'         asl',\n'         asl',\n'         and #$70',\n'         sta tmp',\n'         lda modevol',\n'         and #$8f',\n'         ora tmp',\n'         sta modevol',\n'         lda ins_cutlo,y',\n'         sta cutlo',\n'         lda ins_cuthi,y',\n'         sta cuthi',\n'         lda ins_cutspd,y',\n'         sta cutspd',\n'tr_fdone rts',\n'tr_nofil lda vbit,x',\n'         eor #$ff',\n'         and resrt',\n'         sta resrt',\n'         rts',\n'',\n'; ---- vibrato: delta = (freq(nota+1) - freq(nota)) >> (8 - profondita\\') ----',\n'setvib   sta tmp',\n'         lda #0',\n'         sta v_viblo,x',\n'         sta v_vibhi,x',\n'         lda tmp',\n'         beq sv_done',\n'         ldy v_note,x',\n'         cpy #94',\n'         bcc sv_ok',\n'         ldy #93',\n'sv_ok    lda freqlo+1,y',\n'         sec',\n'         sbc freqlo,y',\n'         sta v_viblo,x',\n'         lda freqhi+1,y',\n'         sbc freqhi,y',\n'         sta v_vibhi,x',\n'         lda #8',\n'         sec',\n'         sbc tmp',\n'         tay',\n'         beq sv_done',\n'sv_sh    lsr v_vibhi,x',\n'         ror v_viblo,x',\n'         dey',\n'         bne sv_sh',\n'sv_done  rts',\n'',\n'; ---- portamento verso la nota A senza riattaccare ----',\n'portaset sta v_note,x',\n'         sta v_wnote,x',\n'         tay',\n'         lda v_frqlo,x',\n'         sec',\n'         sbc freqlo,y',\n'         sta v_sldlo,x',\n'         lda v_frqhi,x',\n'         sbc freqhi,y',\n'         sta v_sldhi,x',\n'         rts',\n'',\n'; ---- hard restart: 2 frame prima di una nuova nota gate off e ADSR a 0 ----',\n'hardrst  ldx #0',\n'hr_loop  jsr setptr',\n'         lda row',\n'         asl',\n'         asl',\n'         tay',\n'         lda (ZP),y',\n'         beq hr_next',\n'         cmp #$fe',\n'         bcs hr_next',\n'         iny',\n'         iny',\n'         lda (ZP),y',\n'         cmp #3',\n'         beq hr_next',\n'         dey',\n'         lda (ZP),y',\n'         bne hr_ins',\n'         lda v_inst,x',\n'hr_ins   tay',\n'         lda ins_flags,y',\n'         and #1',\n'         beq hr_next',\n'         lda #0',\n'         sta v_gate,x',\n'         sta v_ad,x',\n'         sta v_sr,x',\n'hr_next  inx',\n'         cpx #3',\n'         bne hr_loop',\n'         rts',\n'',\n'; ---- jam: suona una nota da tastiera ----',\n'jamnote  cmp #$ff',\n'         bne jm_on',\n'         lda #0',\n'         sta v_gate,x',\n'         rts',\n'jm_on    pha',\n'         tya',\n'         sta v_inst,x',\n'         pla',\n'         jmp trigger',\n'',\n'; ---- aggiornamento di una voce (X) a ogni frame ----',\n'voice    lda v_wtdur,x',\n'         beq vw_read',\n'         dec v_wtdur,x',\n'         jmp vw_done',\n'vw_read  ldy v_wtpos,x',\n'         lda wt_wave,y',\n'         cmp #$ff',\n'         bne vw_nojmp',\n'         lda wt_note,y',\n'         sta v_wtpos,x',\n'         tay',\n'         lda wt_wave,y',\n'         cmp #$fe',\n'         bcs vw_done',\n'         bcc vw_step',\n'vw_nojmp cmp #$fe',\n'         beq vw_done',\n'vw_step  cmp #0',\n'         beq vw_keep',\n'         sta v_wave,x',\n'vw_keep  lda wt_note,y',\n'         bmi vw_abs',\n'         cmp #$40',\n'         bcs vw_neg',\n'         clc',\n'         adc v_note,x',\n'         cmp #95',\n'         bcc vw_setn',\n'         lda #94',\n'         bne vw_setn',\n'vw_neg   ora #$80',\n'         clc',\n'         adc v_note,x',\n'         bcs vw_setn',\n'         lda #0',\n'         beq vw_setn',\n'vw_abs   and #$7f',\n'         cmp #95',\n'         bcc vw_setn',\n'         lda #94',\n'vw_setn  sta v_wnote,x',\n'         lda wt_dur,y',\n'         sec',\n'         sbc #1',\n'         sta v_wtdur,x',\n'         iny',\n'         tya',\n'         sta v_wtpos,x',\n'vw_done  lda v_cmd,x',\n'         cmp #1',\n'         bne vf_n1',\n'         lda v_par,x',\n'         clc',\n'         adc v_sldlo,x',\n'         sta v_sldlo,x',\n'         lda #0',\n'         adc v_sldhi,x',\n'         sta v_sldhi,x',\n'         jmp vf_porta',\n'vf_n1    cmp #2',\n'         bne vf_porta',\n'         lda v_sldlo,x',\n'         sec',\n'         sbc v_par,x',\n'         sta v_sldlo,x',\n'         lda v_sldhi,x',\n'         sbc #0',\n'         sta v_sldhi,x',\n'vf_porta lda v_porta,x',\n'         beq vf_vib',\n'         lda v_sldhi,x',\n'         bmi vf_pneg',\n'         ora v_sldlo,x',\n'         beq vf_pstop',\n'         lda v_sldlo,x',\n'         sec',\n'         sbc v_porta,x',\n'         sta v_sldlo,x',\n'         lda v_sldhi,x',\n'         sbc #0',\n'         sta v_sldhi,x',\n'         bpl vf_vib',\n'         bmi vf_pstop',\n'vf_pneg  lda v_sldlo,x',\n'         clc',\n'         adc v_porta,x',\n'         sta v_sldlo,x',\n'         lda v_sldhi,x',\n'         adc #0',\n'         sta v_sldhi,x',\n'         bmi vf_vib',\n'vf_pstop lda #0',\n'         sta v_sldlo,x',\n'         sta v_sldhi,x',\n'         sta v_porta,x',\n'vf_vib   lda v_viblo,x',\n'         ora v_vibhi,x',\n'         beq vf_freq',\n'         lda v_vibdel,x',\n'         beq vv_run',\n'         dec v_vibdel,x',\n'         jmp vf_freq',\n'vv_run   lda v_vibq,x',\n'         beq vv_add',\n'         cmp #3',\n'         beq vv_add',\n'         lda v_vofslo,x',\n'         sec',\n'         sbc v_viblo,x',\n'         sta v_vofslo,x',\n'         lda v_vofshi,x',\n'         sbc v_vibhi,x',\n'         sta v_vofshi,x',\n'         jmp vv_cnt',\n'vv_add   lda v_vofslo,x',\n'         clc',\n'         adc v_viblo,x',\n'         sta v_vofslo,x',\n'         lda v_vofshi,x',\n'         adc v_vibhi,x',\n'         sta v_vofshi,x',\n'vv_cnt   dec v_vibcnt,x',\n'         bne vf_freq',\n'         lda v_vibspd,x',\n'         sta v_vibcnt,x',\n'         inc v_vibq,x',\n'         lda v_vibq,x',\n'         and #3',\n'         sta v_vibq,x',\n'vf_freq  ldy v_wnote,x',\n'         lda freqlo,y',\n'         clc',\n'         adc v_sldlo,x',\n'         sta tmp',\n'         lda freqhi,y',\n'         adc v_sldhi,x',\n'         sta tmp2',\n'         lda tmp',\n'         clc',\n'         adc v_vofslo,x',\n'         sta v_frqlo,x',\n'         lda tmp2',\n'         adc v_vofshi,x',\n'         sta v_frqhi,x',\n'         lda v_pwspd,x',\n'         beq vp_done',\n'         bmi vp_neg',\n'         clc',\n'         adc v_pwlo,x',\n'         sta v_pwlo,x',\n'         lda v_pwhi,x',\n'         adc #0',\n'         sta v_pwhi,x',\n'         ldy v_inst,x',\n'         lda v_pwlo,x',\n'         cmp ins_pwmaxl,y',\n'         lda v_pwhi,x',\n'         sbc ins_pwmaxh,y',\n'         bcc vp_done',\n'         lda ins_pwmaxl,y',\n'         sta v_pwlo,x',\n'         lda ins_pwmaxh,y',\n'         sta v_pwhi,x',\n'         jmp vp_flip',\n'vp_neg   clc',\n'         adc v_pwlo,x',\n'         sta v_pwlo,x',\n'         lda v_pwhi,x',\n'         adc #$ff',\n'         sta v_pwhi,x',\n'         bmi vp_min',\n'         ldy v_inst,x',\n'         lda v_pwlo,x',\n'         cmp ins_pwminl,y',\n'         lda v_pwhi,x',\n'         sbc ins_pwminh,y',\n'         bcs vp_done',\n'vp_min   ldy v_inst,x',\n'         lda ins_pwminl,y',\n'         sta v_pwlo,x',\n'         lda ins_pwminh,y',\n'         sta v_pwhi,x',\n'vp_flip  lda v_pwspd,x',\n'         eor #$ff',\n'         clc',\n'         adc #1',\n'         sta v_pwspd,x',\n'vp_done  ldy voffs,x',\n'         lda v_frqlo,x',\n'         sta SID,y',\n'         lda v_frqhi,x',\n'         sta SID+1,y',\n'         lda v_pwlo,x',\n'         sta SID+2,y',\n'         lda v_pwhi,x',\n'         sta SID+3,y',\n'         lda v_ad,x',\n'         sta SID+5,y',\n'         lda v_sr,x',\n'         sta SID+6,y',\n'         lda v_wave,x',\n'         and #$fe',\n'         ora v_gate,x',\n'         sta SID+4,y',\n'         rts',\n'',\n'voffs    !byte 0,7,14',\n'vbit     !byte 1,2,4',\n''\n].join('\\n');\n\n\/\/ programma di avvio per il .PRG: riga BASIC \"10 SYS2061\", testo a schermo, interrupt raster che chiama play\nRF.STUB_SRC = function (lines) {\n  var bytes = [0x93];\n  lines.forEach(function (l) { bytes = bytes.concat(l); bytes.push(13); });\n  bytes.push(0);\n  var rows = [];\n  for (var i = 0; i < bytes.length; i += 16) rows.push('         !byte ' + bytes.slice(i, i + 16).map(hex2).join(','));\n  return [\n    '; ---- avvio: 10 SYS2061 ----',\n    '         * = $0801',\n    '         !word bas_end, 10',\n    '         !byte $9e, $32, $30, $36, $31, 0',\n    'bas_end  !word 0',\n    'start    lda #0',\n    '         sta $d020',\n    '         sta $d021',\n    '         ldx #0',\n    'st_print lda st_msg,x',\n    '         beq st_irq',\n    '         jsr $ffd2',\n    '         inx',\n    '         bne st_print',\n    'st_irq   sei',\n    '         lda #<st_isr',\n    '         sta $0314',\n    '         lda #>st_isr',\n    '         sta $0315',\n    '         lda #$7f',\n    '         sta $dc0d',\n    '         lda $dc0d',\n    '         lda #$1b',\n    '         sta $d011',\n    '         lda #$80',\n    '         sta $d012',\n    '         lda #1',\n    '         sta $d01a',\n    '         sta $d019',\n    '         lda #0',\n    '         jsr init',\n    '         cli',\n    'st_loop  jmp st_loop',\n    'st_isr   lda #1',\n    '         sta $d019',\n    '         inc $d020                ; il bordo mostra il tempo raster del player',\n    '         jsr play',\n    '         dec $d020',\n    '         jmp $ea31',\n    'st_msg',\n    rows.join('\\n'),\n    ''\n  ].join('\\n');\n};\n\nfunction hex2(v) { return '$' + ('0' + (v & 0xFF).toString(16)).slice(-2); }\nRF.hex2 = hex2;\n\n\/\/ testo ASCII -> PETSCII maiuscolo (modo grafico del C64 all'accensione)\nRF.petscii = function (s, color) {\n  var out = color ? [ color] : [];\n  s = String(s || '').toUpperCase();\n  for (var i = 0; i < s.length; i++) {\n    var c = s.charCodeAt(i);\n    if (c >= 32 && c <= 93) out.push(c);\n    else if (\/[\u00c0\u00c1\u00c2\u00c4]\/.test(s[ i])) out.push(65); else if (\/[\u00c8\u00c9\u00ca\u00cb]\/.test(s[ i])) out.push(69);\n    else if (\/[\u00cc\u00cd\u00ce\u00cf]\/.test(s[ i])) out.push(73); else if (\/[\u00d2\u00d3\u00d4\u00d6]\/.test(s[ i])) out.push(79);\n    else if (\/[\u00d9\u00da\u00db\u00dc]\/.test(s[ i])) out.push(85); else out.push(63);\n  }\n  return out.slice(0, 40);\n};\n\n\/\/ ---- tabella delle frequenze: La4 = 440 Hz, Do0 = nota 0, 95 note (fino a La#7) ----\nRF.NOTES = 95;\nRF.freqTable = function (video) {\n  var clk = RF.CLOCKS[ video] ? RF.CLOCKS[ video].hz : 985248, t = [];\n  for (var n = 0; n < 95; n++) t.push(Math.min(65535, Math.round(440 * Math.pow(2, (n - 57) \/ 12) * 16777216 \/ clk)));\n  return t;\n};\n\n\/\/ ---- canzone (JSON dell'editor) -> sorgente dei dati per il player ----\nfunction clamp(v, a, b) { v = Math.round(+v || 0); return v < a ? a : v > b ? b : v; }\nfunction sbyte(v) { return clamp(v, -127, 127) & 0xFF; }\nfunction bytesLines(label, arr) {\n  if (!arr.length) return label + '\\n';\n  var out = [];\n  for (var i = 0; i < arr.length; i += 16) out.push((i === 0 ? pad(label + ' ', 11) : '           ') + '!byte ' + arr.slice(i, i + 16).map(hex2).join(','));\n  return out.join('\\n') + '\\n';\n}\nfunction pad(s, n) { while (s.length < n) s += ' '; return s; }\n\nRF.songData = function (song, opts) {\n  opts = opts || {};\n  var rows = clamp(song.rows, 1, 64), order = song.order.length ? song.order : [[0, 0, 0]];\n  if (opts.onlyRow !== undefined) order = [order[opts.onlyRow] || order[0]];\n  var songLen = Math.min(order.length, 255), loop = opts.onlyRow !== undefined ? 0 : clamp(song.loop, 0, songLen - 1);\n  \/\/ pattern usati, rinumerati in ordine di apparizione\n  var map = {}, used = [];\n  order.slice(0, songLen).forEach(function (r) { r.forEach(function (p) { if (map[ p] === undefined) { map[ p] = used.length; used.push(p); } }); });\n  var src = '; ---- dati della canzone ----\\n';\n  src += 'song_speed !byte ' + hex2(clamp(song.speed, 1, 127)) + '\\n';\n  src += 'song_vol   !byte ' + hex2(clamp(song.volume === undefined ? 15 : song.volume, 0, 15)) + '\\n';\n  src += 'song_len   !byte ' + hex2(songLen) + '\\n';\n  src += 'song_loop  !byte ' + hex2(loop) + '\\n';\n  src += 'song_rows  !byte ' + hex2(rows) + '\\n';\n  src += 'ordlo    !byte <order1, <order2, <order3\\n';\n  src += 'ordhi    !byte >order1, >order2, >order3\\n';\n  for (var v = 0; v < 3; v++) src += bytesLines('order' + (v + 1), order.slice(0, songLen).map(function (r) { return map[r[ v]]; }));\n  src += 'patlo    !byte ' + used.map(function (p, i) { return '<pat' + i; }).join(',') + '\\n';\n  src += 'pathi    !byte ' + used.map(function (p, i) { return '>pat' + i; }).join(',') + '\\n';\n  used.forEach(function (p, i) {\n    var pat = song.patterns[ p], b = [];\n    for (var r = 0; r < rows; r++) {\n      var c = pat && pat[ r] ? pat[ r] : [0, 0, 0, 0];\n      var note = c[0] === 254 ? 0xFE : clamp(c[0], 0, 95);\n      b.push(note, clamp(c[1], 0, 255), clamp(c[2], 0, 15), clamp(c[3], 0, 255));\n    }\n    src += '; pattern ' + hex2(p).slice(1) + '\\n' + bytesLines('pat' + i, b);\n  });\n  \/\/ strumenti (l'indice 0 \u00e8 vuoto) e tabella delle onde\n  var ins = song.instruments, n = Math.max(1, ins.length);\n  var T = { ad: [], sr: [], wt: [], flags: [], pwlo: [], pwhi: [], pwspd: [], pwminl: [], pwminh: [], pwmaxl: [], pwmaxh: [],\n    vibdel: [], vibspd: [], vibdep: [], fflag: [], fres: [], cutlo: [], cuthi: [], cutspd: [] };\n  var wtw = [0xFE], wtn = [0], wtd = [1];\n  for (var i = 0; i < n; i++) {\n    var I = i > 0 ? ins[ i] : null;\n    if (!I) { Object.keys(T).forEach(function (k) { T[ k].push(0); }); continue; }\n    T.ad.push(((clamp(I.a, 0, 15) << 4) | clamp(I.d, 0, 15)));\n    T.sr.push(((clamp(I.s, 0, 15) << 4) | clamp(I.r, 0, 15)));\n    T.wt.push(wtw.length);\n    var steps = (I.wave || []).slice(0, 32);\n    if (!steps.length) steps = [{ w: 0x41, n: 0, abs: false, d: 1 }];\n    var start = wtw.length;\n    steps.forEach(function (s) {\n      wtw.push(clamp(s.w, 0, 0xFD) & 0xFE);\n      wtn.push(s.abs ? (0x80 | clamp(s.n, 0, 94)) : (clamp(s.n, -64, 63) & 0x7F));\n      wtd.push(clamp(s.d, 1, 255));\n    });\n    var lp = I.loop === undefined || I.loop === null ? -1 : I.loop;\n    if (lp >= 0 && lp < steps.length) { wtw.push(0xFF); wtn.push(start + lp); wtd.push(1); }\n    else { wtw.push(0xFE); wtn.push(0); wtd.push(1); }\n    T.flags.push((I.hr === false ? 0 : 1) | (I.pwKeep ? 2 : 0));\n    var pw = clamp(I.pw, 0, 4095), pmin = clamp(I.pwMin === undefined ? 0x100 : I.pwMin, 0, 4095), pmax = clamp(I.pwMax === undefined ? 0xF00 : I.pwMax, 0, 4095);\n    if (pmax < pmin) { var tt = pmax; pmax = pmin; pmin = tt; }\n    T.pwlo.push(pw & 0xFF); T.pwhi.push(pw >> 8); T.pwspd.push(sbyte(I.pwSpd));\n    T.pwminl.push(pmin & 0xFF); T.pwminh.push(pmin >> 8); T.pwmaxl.push(pmax & 0xFF); T.pwmaxh.push(pmax >> 8);\n    var dep = clamp(I.vibDepth, 0, 8);\n    T.vibdel.push(clamp(I.vibDelay, 0, 255)); T.vibspd.push(dep ? clamp(I.vibSpeed, 1, 15) : 1); T.vibdep.push(dep);\n    var F = I.filter || {};\n    T.fflag.push((F.on ? 0x80 : 0) | (F.on && F.set ? 0x40 : 0));\n    T.fres.push((clamp(F.res, 0, 15) << 4) | (clamp(F.mode === undefined ? 1 : F.mode, 0, 7)));\n    var cut = clamp(F.cut, 0, 2047);\n    T.cutlo.push(cut & 0xFF); T.cuthi.push(cut >> 8); T.cutspd.push(sbyte(F.spd));\n  }\n  if (wtw.length > 255) throw new Error(opts.lang === 'en' ? 'Wave table too long (max 255 steps in total)' : 'Tabella delle onde troppo lunga (massimo 255 passi in totale)');\n  Object.keys(T).forEach(function (k) { src += bytesLines('ins_' + k, T[ k]); });\n  src += bytesLines('wt_wave', wtw) + bytesLines('wt_note', wtn) + bytesLines('wt_dur', wtd);\n  var ft = RF.freqTable(song.video);\n  src += bytesLines('freqlo', ft.map(function (f) { return f & 0xFF; })) + bytesLines('freqhi', ft.map(function (f) { return f >> 8; }));\n  src += 'song_end\\n';\n  return src;\n};\n\n\/\/ sorgente completo (avvio + player + dati)\nRF.songSource = function (song, opts) {\n  opts = opts || {};\n  var head = [\n    '; ' + (song.title || 'untitled') + (song.author ? ' - ' + song.author : ''),\n    '; Creato con RetroFixer SID Composer - https:\/\/www.retrofixer.it',\n    '; Chip ' + (song.model || 6581) + ', ' + (song.video || 'PAL'),\n    ';',\n    '; ACME:   acme song.asm   (crea song.prg)',\n    '; Il .PRG si avvia con LOAD\"SONG\",8 e RUN (10 SYS2061).',\n    '; Senza il blocco di avvio il player e\\' a $1000: init $1000 (A=0), play $1003 una volta per frame.',\n    ''\n  ].join('\\n');\n  var lines = [RF.petscii('RETROFIXER SID COMPOSER', 0x9E), []];\n  if (song.title) lines.push(RF.petscii(song.title, 0x05));\n  if (song.author) lines.push(RF.petscii(song.author, 0x9F));\n  if (song.released) lines.push(RF.petscii(song.released, 0x9B));\n  lines.push([], RF.petscii('SID ' + (song.model || 6581) + ' ' + (song.video || 'PAL'), 0x9A), RF.petscii('WWW.RETROFIXER.IT', 0x9A));\n  var stub = opts.noStub ? '' : RF.STUB_SRC(lines);\n  return head + (opts.acme === false ? '' : '!to \"song.prg\", cbm\\n') + '\\n' + stub + '\\n' + RF.PLAYER_SRC + '\\n' + RF.songData(song, opts);\n};\n\n\/\/ assembla tutto e restituisce il binario da caricare + i simboli usati dall'anteprima\nRF.buildSong = function (song, opts) {\n  var src = RF.songSource(song, opts), r = RF.assemble(src);\n  if (r.errors.length) {\n    var e = r.errors[0];\n    throw new Error('ASM riga ' + e.line + ': ' + e.msg + ' [' + e.text.trim() + ']');\n  }\n  var S = r.syms;\n  if (S.song_end > 0xA000) throw new Error(opts && opts.lang === 'en' ? 'Song too big: it goes past $A000' : 'Canzone troppo grande: supera $A000');\n  return {\n    src: src, asm: r, start: r.start, end: r.end, bin: r.bytes(),\n    player: r.bytes(0x1000, r.end),\n    syms: { init: 0x1000, play: 0x1003, jam: 0x1006, vars: S.vars, varsend: S.varsend, playing: S.playing, curpos: S.curpos,\n      currow: S.currow, speed: S.speed, tick: S.tick, song_end: S.song_end }\n  };\n};\n\n\/\/ 64tass: stesse istruzioni, direttive con il punto\nRF.toTass = function (src) {\n  return src.split('\\n').filter(function (l) { return !\/^!to\\b\/.test(l); }).map(function (l) {\n    return l.replace(\/!byte\\b\/g, '.byte').replace(\/!word\\b\/g, '.word').replace(\/!fill\\b\/g, '.fill')\n      .replace('; ACME:   acme song.asm   (crea song.prg)', '; 64tass: 64tass -C -a -o song.prg song.asm');\n  }).join('\\n');\n};\n})(globalThis.RFSid = globalThis.RFSid || {});\n\n\/* RetroFixer \u00b7 SID Composer \u00b7 canzoni d'esempio (composizioni originali per questa pagina)\n * Le tracce si scrivono una riga per token:  C-4:1  nota e strumento,  .  vuoto,  ===  note off,\n * !A20  comando senza nota,  E-5!310  nota con comando.\n *\/\n(function (RF) {\n'use strict';\n\nvar NAMES = ['C-', 'C#', 'D-', 'D#', 'E-', 'F-', 'F#', 'G-', 'G#', 'A-', 'A#', 'B-'];\nRF.NOTE_NAMES = NAMES;\nRF.noteName = function (n) { \/\/ 1..95\n  if (n === 254) return '===';\n  if (!n) return '...';\n  n--; return NAMES[n % 12] + Math.floor(n \/ 12);\n};\nRF.parseNote = function (s) { \/\/ \"C#4\" -> 1..95\n  var i = NAMES.indexOf(s.slice(0, 2).toUpperCase()), o = +s[2];\n  if (i < 0 || isNaN(o)) return 0;\n  var v = o * 12 + i + 1; return v >= 1 && v <= 95 ? v : 0;\n};\nfunction track(str, rows) {\n  var t = str.trim().split(\/\\s+\/), out = [];\n  for (var r = 0; r < rows; r++) {\n    var tok = t[ r] || '.', cell = [0, 0, 0, 0], m;\n    var cmd = \/!([0-9A-F])([0-9A-F]{2})$\/i.exec(tok);\n    if (cmd) { cell[2] = parseInt(cmd[1], 16); cell[3] = parseInt(cmd[2], 16); tok = tok.slice(0, cmd.index); }\n    if (tok === '===') cell[0] = 254;\n    else if ((m = \/^([A-G][-#]\\d)(?::(\\d+))?$\/i.exec(tok))) { cell[0] = RF.parseNote(m[1]); cell[1] = m[2] ? +m[2] : 0; }\n    out.push(cell);\n  }\n  return out;\n}\nRF.track = track;\n\nfunction W(w, n, d, abs) { return { w: w, n: n, d: d || 1, abs: !!abs }; }\nfunction N(s) { return RF.parseNote(s) - 1; } \/\/ nota assoluta 0..94 per la tabella onde\n\n\/\/ strumento con valori predefiniti\nRF.newInstrument = function (o) {\n  var I = { name: 'nuovo', a: 0, d: 9, s: 0, r: 9, wave: [W(0x41, 0)], loop: -1, pw: 0x800, pwSpd: 0, pwMin: 0x100, pwMax: 0xF00, pwKeep: false,\n    vibDelay: 0, vibSpeed: 3, vibDepth: 0, hr: true, filter: { on: false, set: false, mode: 1, res: 0, cut: 1024, spd: 0 } };\n  if (o) Object.keys(o).forEach(function (k) { I[ k] = k === 'filter' ? Object.assign(I.filter, o.filter) : o[ k]; });\n  return I;\n};\n\nRF.newSong = function () {\n  return {\n    format: 'rfsid', version: 1, title: '', author: '', released: '', model: 6581, video: 'PAL',\n    speed: 6, rows: 32, volume: 15, loop: 0, order: [[0, 1, 2]], patterns: { 0: track('', 32), 1: track('', 32), 2: track('', 32) },\n    instruments: [null,\n      RF.newInstrument({ name: 'Lead impulso', a: 0, d: 9, s: 10, r: 9, pw: 0x400, pwSpd: 20, pwMin: 0x200, pwMax: 0xC00, vibDelay: 10, vibSpeed: 3, vibDepth: 5 }),\n      RF.newInstrument({ name: 'Basso dente di sega', a: 0, d: 9, s: 4, r: 6, wave: [W(0x81, N('C-7'), 1, true), W(0x21, 0)] }),\n      RF.newInstrument({ name: 'Triangolo', a: 0, d: 0, s: 15, r: 8, wave: [W(0x11, 0)] })]\n  };\n};\n\n\/\/ ---- 1. Pixel Sunrise (La minore, 125 BPM) --------------------------------------------------------\nfunction pixelSunrise() {\n  var R = 32, P = {};\n  P[0] = track('', R);\n  \/\/ melodia (strumento 1)\n  P[1] = track('A-4:1 . . . C-5 . . . E-5 . . . D-5 . C-5 . ' +\n               'A-4 . . . . . . . F-4 . . . A-4 . C-5 .', R);\n  P[2] = track('G-4:1 . . . . . E-4 . G-4 . . . C-5 . . . ' +\n               'B-4 . . . . . . . A-4 . G-4 . D-5 . . .', R);\n  P[3] = track('E-5:1 . . . D-5 . C-5 . D-5 . E-5 . A-5 . . . ' +\n               'G-5 . F-5 . E-5 . . . C-5 . . . A-4!410 . . .', R);\n  P[4] = track('G-4:1 . . . C-5 . . . E-5 . . . G-5 . . . ' +\n               'D-5!308 . . . B-4 . . . G-4 . . . . . === .', R);\n  \/\/ accordi arpeggiati (2 = minore, 3 = maggiore)\n  var stab = function (n, i) { return [n + ':' + i, '.', '.', n, '.', '.', n, '.', n, '.', '.', n, '.', '.', n, '.'].join(' '); };\n  P[5] = track(stab('A-3', 2) + ' ' + stab('F-3', 3), R);\n  P[6] = track(stab('C-4', 3) + ' ' + stab('G-3', 3), R);\n  \/\/ basso (4) e batteria (5 cassa, 6 rullante) sulla voce 3\n  var bass = function (root, oct) {\n    return ['C-4:5', '.', root + ':4', '.', 'C-4:6', '.', oct + ':4', '.', 'C-4:5', '.', root + ':4', root, 'C-4:6', '.', oct + ':4', root].join(' ');\n  };\n  P[7] = track(bass('A-2', 'A-3') + ' ' + bass('F-2', 'F-3'), R);\n  P[8] = track(bass('C-3', 'C-4') + ' ' + bass('G-2', 'G-3'), R);\n  return {\n    format: 'rfsid', version: 1, title: 'Pixel Sunrise', author: 'RetroFixer', released: '2026 RetroFixer', model: 6581, video: 'PAL',\n    speed: 6, rows: R, volume: 15, loop: 2,\n    order: [[0, 5, 7], [0, 6, 8], [1, 5, 7], [2, 6, 8], [3, 5, 7], [4, 6, 8]],\n    patterns: P,\n    instruments: [null,\n      RF.newInstrument({ name: 'Lead', a: 0, d: 9, s: 10, r: 9, pw: 0x380, pwSpd: 24, pwMin: 0x200, pwMax: 0xD00, vibDelay: 14, vibSpeed: 3, vibDepth: 5 }),\n      RF.newInstrument({ name: 'Arpeggio minore', a: 0, d: 7, s: 7, r: 6, wave: [W(0x41, 0), W(0x41, 3), W(0x41, 7)], loop: 0, pw: 0x600, pwSpd: 8, pwMin: 0x400, pwMax: 0x900 }),\n      RF.newInstrument({ name: 'Arpeggio maggiore', a: 0, d: 7, s: 7, r: 6, wave: [W(0x41, 0), W(0x41, 4), W(0x41, 7)], loop: 0, pw: 0x600, pwSpd: 8, pwMin: 0x400, pwMax: 0x900 }),\n      RF.newInstrument({ name: 'Basso', a: 0, d: 9, s: 5, r: 8, wave: [W(0x81, N('A-6'), 1, true), W(0x41, 0)], pw: 0x280, pwSpd: 40, pwMin: 0x200, pwMax: 0xA00,\n        filter: { on: true, set: true, mode: 1, res: 10, cut: 620, spd: 0 } }),\n      RF.newInstrument({ name: 'Cassa', a: 0, d: 8, s: 0, r: 0, wave: [W(0x81, N('C-6'), 1, true), W(0x41, N('A-2'), 1, true), W(0x41, N('E-2'), 1, true), W(0x41, N('B-1'), 1, true), W(0x41, N('G-1'), 1, true), W(0x40, N('E-1'), 1, true)], pw: 0x800 }),\n      RF.newInstrument({ name: 'Rullante', a: 0, d: 8, s: 0, r: 8, wave: [W(0x81, N('A-6'), 1, true), W(0x41, N('D-3'), 1, true), W(0x81, N('E-6'), 2, true), W(0x81, N('C-6'), 1, true)], pw: 0x800 })]\n  };\n}\n\n\/\/ ---- 2. Neon Filter (filtro risonante che si apre, campana in ring modulation) ---------------------\nfunction neonFilter() {\n  var R = 32, P = {};\n  P[0] = track('', R);\n  var acid = function (a, b) {\n    return [a + ':1', '.', a, '.', a + '!E0C', a, '.', b, a, '.', a, b, '.', a, b + '!EF8', '.'].join(' ');\n  };\n  P[1] = track(acid('D-2', 'D-3') + ' ' + acid('D-2', 'D-3'), R);\n  P[2] = track(acid('A#1', 'A#2') + ' ' + acid('C-2', 'C-3'), R);\n  P[3] = track('D-5:2 . . . . . F-5 . . . A-5 . . . . . G-5 . . . F-5 . . . E-5 . . . . . C-5 .', R);\n  P[4] = track('D-5:2 . . . . . . . A-4 . . . . . . . A#4 . . . . . . . C-5 . . . E-5 . . .', R);\n  var drums = 'C-4:3 . C-4:5 . C-4:4 . C-4:5 . C-4:3 . C-4:5 C-4:3 C-4:4 . C-4:5 C-4:5';\n  P[5] = track(drums + ' ' + drums, R);\n  return {\n    format: 'rfsid', version: 1, title: 'Neon Filter', author: 'RetroFixer', released: '2026 RetroFixer', model: 8580, video: 'PAL',\n    speed: 5, rows: R, volume: 15, loop: 0,\n    order: [[1, 0, 5], [2, 0, 5], [1, 3, 5], [2, 4, 5]],\n    patterns: P,\n    instruments: [null,\n      RF.newInstrument({ name: 'Basso acido', a: 0, d: 8, s: 4, r: 4, wave: [W(0x21, 0)], filter: { on: true, set: true, mode: 1, res: 14, cut: 150, spd: 10 } }),\n      RF.newInstrument({ name: 'Campana ring', a: 0, d: 10, s: 0, r: 10, wave: [W(0x15, 0)], vibDelay: 20, vibSpeed: 4, vibDepth: 3 }),\n      RF.newInstrument({ name: 'Cassa', a: 0, d: 7, s: 0, r: 0, wave: [W(0x81, N('D-6'), 1, true), W(0x11, N('G-2'), 1, true), W(0x11, N('D-2'), 1, true), W(0x11, N('A-1'), 1, true), W(0x11, N('F-1'), 2, true)] }),\n      RF.newInstrument({ name: 'Rullante', a: 0, d: 7, s: 0, r: 7, wave: [W(0x81, N('G-6'), 1, true), W(0x41, N('C-3'), 1, true), W(0x81, N('D-6'), 1, true)], pw: 0x800 }),\n      RF.newInstrument({ name: 'Charleston', a: 0, d: 3, s: 0, r: 2, wave: [W(0x81, N('A#7'), 1, true)] })]\n  };\n}\n\n\/\/ ---- 3. Effetti: un giro per comando ------------------------------------------------------------\nfunction effects() {\n  var R = 16, P = {};\n  P[0] = track('', R);\n  P[1] = track('C-4:1 . . . . . . . E-4:2 . . . . . . .', R);                 \/\/ strumenti: impulso e onda tabella\n  P[2] = track('C-4:1!110 !110 !110 !110 !210 !210 !210 !210 G-4:1 . . . . . . ===', R);   \/\/ 1xx 2xx\n  P[3] = track('C-4:1 . . . G-4!304 . . . C-5!302 . . . C-4!310 . . ===', R);    \/\/ 3xx portamento\n  P[4] = track('E-4:3 . . . !446 . . . !4A8 . . . . . . ===', R);               \/\/ 4xy vibrato\n  P[5] = track('C-3:4!B71 . . . !A40 . . . !A80 . . . !E06 . . ===', R);        \/\/ Axx Bxy Exx filtro\n  P[6] = track('C-4:5 . . . . . . . G-4:6 . . . . . . ===', R);                 \/\/ sync e ring\n  P[7] = track('C-3:7 . . . C-3!840 . . . C-3!8C0 . . . C-3!920 . . ===', R);   \/\/ 8xx 9xx impulso\n  return {\n    format: 'rfsid', version: 1, title: 'Effetti SID', author: 'RetroFixer', released: '2026 RetroFixer', model: 6581, video: 'PAL',\n    speed: 6, rows: R, volume: 15, loop: 0,\n    order: [[1, 0, 0], [2, 0, 0], [3, 0, 0], [4, 0, 0], [5, 0, 0], [0, 0, 6], [7, 0, 0]],\n    patterns: P,\n    instruments: [null,\n      RF.newInstrument({ name: 'Impulso semplice', a: 0, d: 9, s: 10, r: 9, pw: 0x800 }),\n      RF.newInstrument({ name: 'Accordo in tabella', a: 0, d: 9, s: 8, r: 9, wave: [W(0x21, 0), W(0x21, 4), W(0x21, 7), W(0x21, 12)], loop: 0 }),\n      RF.newInstrument({ name: 'Flauto con vibrato', a: 2, d: 4, s: 12, r: 8, wave: [W(0x11, 0)] }),\n      RF.newInstrument({ name: 'Basso filtrato', a: 0, d: 0, s: 15, r: 6, wave: [W(0x21, 0)], filter: { on: true, set: true, mode: 1, res: 8, cut: 300, spd: 0 } }),\n      RF.newInstrument({ name: 'Sync (voce 3)', a: 0, d: 0, s: 15, r: 8, wave: [W(0x23, 0)] }),\n      RF.newInstrument({ name: 'Ring (voce 3)', a: 0, d: 0, s: 15, r: 8, wave: [W(0x15, 0)] }),\n      RF.newInstrument({ name: 'Impulso stretto', a: 0, d: 0, s: 15, r: 6, pw: 0x100 })]\n  };\n}\n\nRF.EXAMPLES = [\n  { id: 'pixel', name: { it: 'Pixel Sunrise (arpeggi, basso e batteria)', en: 'Pixel Sunrise (arpeggios, bass and drums)' }, make: pixelSunrise },\n  { id: 'neon', name: { it: 'Neon Filter (filtro risonante 8580, ring mod)', en: 'Neon Filter (8580 resonant filter, ring mod)' }, make: neonFilter },\n  { id: 'fx', name: { it: 'Effetti SID (un comando per pattern)', en: 'SID effects (one command per pattern)' }, make: effects }\n];\n})(globalThis.RFSid = globalThis.RFSid || {});\n\n\/* RetroFixer \u00b7 SID Composer \u00b7 esportazione: .PRG, .SID (PSID v2), sorgente .asm, WAV *\/\n(function (RF) {\n'use strict';\n\n\/\/ .PRG: 2 byte di indirizzo di caricamento ($0801) + avvio BASIC + player + dati\nRF.makePRG = function (build) {\n  var out = new Uint8Array(build.bin.length + 2);\n  out[0] = build.start & 0xFF; out[1] = build.start >> 8;\n  out.set(build.bin, 2);\n  return out;\n};\n\nfunction latin1(s, n) {\n  var b = new Uint8Array(n);\n  s = String(s || '').normalize('NFC');\n  for (var i = 0, j = 0; i < s.length && j < n; i++) {\n    var c = s.charCodeAt(i);\n    b[j++] = c < 256 ? c : 63;\n  }\n  return b;\n}\n\n\/\/ .SID: intestazione PSID v2 (0x7C byte), poi indirizzo di caricamento e player a $1000\nRF.makePSID = function (build, song) {\n  var data = build.player, h = new Uint8Array(0x7C), dv = new DataView(h.buffer);\n  h.set([0x50, 0x53, 0x49, 0x44], 0);       \/\/ \"PSID\"\n  dv.setUint16(0x04, 2);                     \/\/ versione\n  dv.setUint16(0x06, 0x7C);                  \/\/ inizio dati\n  dv.setUint16(0x08, 0);                     \/\/ indirizzo nei primi 2 byte dei dati\n  dv.setUint16(0x0A, 0x1000);                \/\/ init\n  dv.setUint16(0x0C, 0x1003);                \/\/ play\n  dv.setUint16(0x0E, 1);                     \/\/ canzoni\n  dv.setUint16(0x10, 1);                     \/\/ canzone iniziale\n  dv.setUint32(0x12, 0);                     \/\/ velocit\u00e0: interrupt video (50\/60 Hz)\n  h.set(latin1(song.title || 'Untitled', 31), 0x16);\n  h.set(latin1(song.author || '<?>', 31), 0x36);\n  h.set(latin1(song.released || '', 31), 0x56);\n  var clock = song.video === 'NTSC' ? 2 : 1, model = +song.model === 8580 ? 2 : 1;\n  dv.setUint16(0x76, (clock << 2) | (model << 4));\n  var out = new Uint8Array(0x7C + 2 + data.length);\n  out.set(h, 0); out[0x7C] = 0x00; out[0x7D] = 0x10; out.set(data, 0x7E);\n  return out;\n};\n\n\/\/ WAV mono 16 bit: suona la canzone una volta (fino al ritorno al punto di loop)\nRF.renderWAV = function (song, build, fs, maxSec) {\n  fs = fs || 44100; maxSec = maxSec || 600;\n  var e = new RF.Engine(fs), S = build.syms, m = e.mem;\n  e.config(song.model, song.video);\n  e.load(build.bin, build.start, S);\n  e.command({ t: 'init', a: 0 });\n  var rowsTotal = Math.max(1, Math.min(song.order.length, 255)) * Math.max(1, Math.min(song.rows, 64));\n  var chunks = [], total = 0, reads = 0, fps = RF.CLOCKS[song.video] ? RF.CLOCKS[song.video].hz \/ e.frameCycles : 50;\n  for (var f = 0; f < maxSec * fps; f++) {\n    var willRead = e.frames === 0 || (m[S.playing] && m[S.tick] === 0);\n    if (willRead) { reads++; if (reads > rowsTotal) break; }\n    e.runFrame();\n    var n = e.available(), b = new Float32Array(n);\n    for (var i = 0; i < n; i++) { b[ i] = e.fifo[e.fr]; e.fr = (e.fr + 1) % e.fifo.length; }\n    chunks.push(b); total += n;\n  }\n  var tail = Math.round(fs * 0.04), pcm = new Int16Array(total), p = 0;\n  chunks.forEach(function (b) { for (var i = 0; i < b.length; i++) { var g = p > total - tail ? (total - p) \/ tail : 1; pcm[p++] = Math.max(-32767, Math.min(32767, Math.round(b[ i] * g * 32767))); } });\n  var buf = new ArrayBuffer(44 + pcm.length * 2), dv = new DataView(buf);\n  function str(o, s) { for (var i = 0; i < s.length; i++) dv.setUint8(o + i, s.charCodeAt(i)); }\n  str(0, 'RIFF'); dv.setUint32(4, 36 + pcm.length * 2, true); str(8, 'WAVE'); str(12, 'fmt ');\n  dv.setUint32(16, 16, true); dv.setUint16(20, 1, true); dv.setUint16(22, 1, true); dv.setUint32(24, fs, true);\n  dv.setUint32(28, fs * 2, true); dv.setUint16(32, 2, true); dv.setUint16(34, 16, true); str(36, 'data');\n  dv.setUint32(40, pcm.length * 2, true);\n  new Int16Array(buf, 44).set(pcm);\n  return { data: new Uint8Array(buf), seconds: total \/ fs };\n};\n\n\/\/ registri del SID frame per frame (per confrontare con un emulatore o un C64 vero)\nRF.registerDump = function (song, build, frames) {\n  var e = new RF.Engine(44100);\n  e.config(song.model, song.video); e.load(build.bin, build.start, build.syms); e.command({ t: 'init', a: 0 });\n  var lines = ['; frame: $D400-$D418'];\n  for (var f = 0; f < frames; f++) {\n    e.runFrame();\n    lines.push(('0000' + f).slice(-5) + ': ' + Array.prototype.map.call(e.sid.regs.slice(0, 25), function (v) { return ('0' + v.toString(16)).slice(-2); }).join(' '));\n  }\n  return lines.join('\\n') + '\\n';\n};\n})(globalThis.RFSid = globalThis.RFSid || {});\n\n\/* RetroFixer \u00b7 SID Composer \u00b7 testi in italiano e inglese *\/\n(function (RF) {\n'use strict';\nRF.TEXTS = {\nit: {\n  kicker: 'Musica \u00b7 Commodore 64 \u00b7 tracker nel browser',\n  lead: 'Un compositore per il chip <b>SID<\/b> del Commodore 64, nello stile di GoatTracker, SID-Wizard e CheeseCutter: tre voci, pattern, strumenti con tabella delle onde, ADSR, filtro, ring modulation e sync. Suona con un\\'emulazione del SID 6581 o 8580 e si esporta in un <b>.PRG<\/b> che parte su un C64 vero, in <b>.SID<\/b> e in sorgente assembler.',\n  play: 'Canzone', playPos: 'Da qui', playPat: 'Pattern', stop: 'Stop',\n  ttPlay: 'Suona la canzone dall\\'inizio', ttPlayPos: 'Suona dalla riga dell\\'ordine selezionata', ttPlayPat: 'Ripete la riga dell\\'ordine selezionata',\n  ttStop: 'Ferma (barra spaziatrice: suona\/ferma)', ttRec: 'Modalit\u00e0 scrittura (Esc)', ttUndo: 'Annulla (Ctrl+Z)', ttRedo: 'Ripeti (Ctrl+Y)',\n  octave: 'Ottava', step: 'Passo', follow: 'Segui', audioOff: 'audio spento: premi \u25b6 o un tasto',\n  audioOn: 'audio attivo', audioNo: 'audio non disponibile in questo browser',\n  title: 'Titolo', author: 'Autore', released: 'Anno\/crediti', speed: 'Velocit\u00e0', rows: 'Righe', volume: 'Volume', video: 'Video',\n  examples: 'Esempi', exChoose: '\u2014 scegli una canzone \u2014', new: 'Nuova', open: 'Apri\u2026', save: 'Salva progetto',\n  exportC64: 'Esporta:',\n  orderT: 'Ordine', cellPat: 'Pattern della casella', freePat: 'nuovo',\n  ttOAdd: 'Aggiungi una riga dopo quella selezionata', ttODup: 'Duplica la riga (stessi pattern)', ttOClone: 'Duplica la riga con copie nuove dei pattern',\n  ttODel: 'Elimina la riga', ttOUp: 'Sposta su', ttODown: 'Sposta gi\u00f9', ttOLoop: 'Punto di ritorno (loop) della canzone', ttFree: 'Mette nella casella un pattern vuoto mai usato',\n  orderHint: 'Ogni riga dice quale pattern suona ciascuna voce. Clicca un numero per sceglierlo, scrivi in esadecimale o usa <b>&lt; &gt;<\/b>. <b>L<\/b> segna dove riparte la canzone.',\n  voice: 'Voce', mute: 'Muto', solo: 'Solo', pat: 'Pat',\n  tpUp: '+1', tpDn: '\u22121', tpOctUp: '+12', tpOctDn: '\u221212', copy: 'Copia', paste: 'Incolla', clear: 'Svuota',\n  ttTp: 'Trasponi il pattern di questa voce', ttCopy: 'Copia il pattern', ttPaste: 'Incolla sul pattern', ttClear: 'Svuota il pattern',\n  gridHint: 'Clicca il pattern e suona con la tastiera del PC. Con <b>REC<\/b> acceso le note si scrivono: nota, strumento (2 cifre) e comando (3 cifre esadecimali). <b>1<\/b> = note off, <b>Canc<\/b> cancella, <b>Ins<\/b>\/<b>Backspace<\/b> inseriscono o tolgono una riga, <b>+\/\u2212<\/b> del tastierino cambiano ottava.',\n  instrT: 'Strumenti', iNew: 'Nuovo', iDup: 'Duplica', iDel: 'Elimina',\n  iName: 'Nome', iEnv: 'Inviluppo ADSR', iA: 'Attacco', iD: 'Decadimento', iS: 'Sostegno', iR: 'Rilascio',\n  iWave: 'Tabella delle onde', iWaveHint: 'Un passo per frame (o per \"durata\" frame). La nota \u00e8 relativa (+7 = quinta sopra) o assoluta (per tamburi e effetti). Passo senza forma d\\'onda = la tiene.',\n  iStep: 'Passo', iNoteRel: 'rel', iNoteAbs: 'ass', iDur: 'frame', iLoop: 'Poi', iLoopStop: 'si ferma', iLoopTo: 'torna al passo',\n  iAddStep: '+ passo', iPulse: 'Impulso', iPw: 'Larghezza', iPwSpd: 'Velocit\u00e0', iPwMin: 'Min', iPwMax: 'Max', iPwKeep: 'non azzerare a ogni nota',\n  iFilter: 'Filtro', iFOn: 'Voce filtrata', iFSet: 'Imposta il filtro a ogni nota', iFMode: 'Tipo', iFRes: 'Risonanza', iFCut: 'Cutoff', iFSpd: 'Sweep',\n  iVib: 'Vibrato', iVDel: 'Ritardo', iVSpd: 'Velocit\u00e0', iVDep: 'Profondit\u00e0', iHR: 'Hard restart (2 frame prima di ogni nota)',\n  iNone: 'nessuno strumento', iTest: 'Prova',\n  pianoT: 'Tastiera', pianoHint: 'Suona con il mouse o con la tastiera del PC: <b>Z S X D C V\u2026<\/b> ottava bassa, <b>Q 2 W 3 E R\u2026<\/b> ottava alta. Usa la voce del cursore e lo strumento selezionato.',\n  monT: 'Registri del SID ($D400-$D418)',\n  msgBuilt: 'Player + canzone: {n} byte ($1000-${end})', msgSaved: 'Progetto salvato', msgLoaded: 'Progetto caricato', msgBadFile: 'File non valido',\n  msgPRG: '.PRG creato: caricalo con LOAD\"{f}\",8 e RUN', msgSID: '.SID creato (PSID v2, init $1000, play $1003)', msgWAV: 'WAV: {s} secondi', msgWAVwait: 'Preparo il WAV\u2026',\n  msgDelLast: 'Serve almeno una riga', msgConfirmNew: 'Iniziare una canzone nuova? Quella attuale va persa se non l\\'hai salvata.',\n  msgConfirmEx: 'Caricare l\\'esempio? La canzone attuale va persa se non l\\'hai salvata.', msgRestored: 'Ripresa la canzone dell\\'ultima volta',\n  regNames: ['FREQ LO', 'FREQ HI', 'PW LO', 'PW HI', 'CTRL', 'AD', 'SR'], regGlob: ['FC LO', 'FC HI', 'RES\/FILT', 'MODE\/VOL'],\n  gStartT: 'Come si usa',\n  gStart: '<ol><li>Scegli un <b>esempio<\/b> e premi <b>\u25b6 Canzone<\/b> per sentire cosa sa fare il SID.<\/li><li>Clicca sul pattern, accendi <b>REC<\/b> (o premi Esc) e scrivi le note con la tastiera del PC. Lo strumento \u00e8 quello selezionato a destra.<\/li><li>Costruisci la canzone nella colonna <b>Ordine<\/b>: ogni riga assegna un pattern a ciascuna delle 3 voci.<\/li><li>Prova i suoni nel pannello <b>Strumenti<\/b> mentre la musica gira: le modifiche si sentono subito.<\/li><li><b>Salva progetto<\/b> scarica un file .json da riaprire con <b>Apri\u2026<\/b>. La canzone viene anche tenuta in memoria nel browser.<\/li><li>Quando ti piace, esporta il <b>.PRG<\/b> per il C64 o il <b>.SID<\/b> per gli altri player.<\/li><\/ol>',\n  gKeysT: 'Tasti',\n  gKeys: '<table><tr><td>Z S X D C V G B H N J M<\/td><td>note dell\\'ottava scelta<\/td><\/tr><tr><td>Q 2 W 3 E R 5 T 6 Y 7 U I 9 O 0 P<\/td><td>ottava sopra<\/td><\/tr><tr><td>1<\/td><td>note off (gate spento)<\/td><\/tr><tr><td>0-9 A-F<\/td><td>strumento e comando (colonne esadecimali)<\/td><\/tr><tr><td>frecce \u00b7 Tab \u00b7 Pag\u2191 Pag\u2193<\/td><td>muovi il cursore<\/td><\/tr><tr><td>Canc \u00b7 Ins \u00b7 Backspace<\/td><td>cancella \u00b7 inserisci riga \u00b7 togli riga<\/td><\/tr><tr><td>Spazio \u00b7 Esc<\/td><td>suona\/ferma \u00b7 REC<\/td><\/tr><tr><td>+ \u2212 (tastierino)<\/td><td>ottava<\/td><\/tr><tr><td>Ctrl+Z \u00b7 Ctrl+Y<\/td><td>annulla \u00b7 ripeti<\/td><\/tr><\/table>',\n  gCmdT: 'Comandi dei pattern',\n  gCmd: '<p>Il comando \u00e8 una cifra esadecimale pi\u00f9 un parametro di due cifre, attivo nella sua riga.<\/p><table><tr><td>1xx<\/td><td>glissando in su di xx a frame<\/td><\/tr><tr><td>2xx<\/td><td>glissando in gi\u00f9<\/td><\/tr><tr><td>3xx<\/td><td>portamento verso la nota della riga (velocit\u00e0 xx), senza riattaccare<\/td><\/tr><tr><td>4xy<\/td><td>vibrato: x velocit\u00e0, y profondit\u00e0 0-8<\/td><\/tr><tr><td>5xx \u00b7 6xx<\/td><td>scrive AD \u00b7 SR<\/td><\/tr><tr><td>8xx<\/td><td>larghezza impulso = xx\u00b716<\/td><\/tr><tr><td>9xx<\/td><td>velocit\u00e0 dello sweep dell\\'impulso (con segno, 80-FF negativi)<\/td><\/tr><tr><td>Axx<\/td><td>cutoff del filtro = xx\u00b78<\/td><\/tr><tr><td>Bxy<\/td><td>risonanza x, tipo y (1 passa basso, 2 banda, 4 alto, sommabili)<\/td><\/tr><tr><td>Cxx<\/td><td>volume generale (0-F)<\/td><\/tr><tr><td>Exx<\/td><td>sweep del cutoff (con segno)<\/td><\/tr><tr><td>Fxx<\/td><td>velocit\u00e0: frame per riga<\/td><\/tr><\/table>',\n  gInsT: 'Strumenti e tabella delle onde',\n  gIns: '<p>Ogni frame (1\/50 di secondo) il player legge un passo della <b>tabella delle onde<\/b>: forma d\\'onda (triangolo, dente di sega, impulso, rumore e le loro combinazioni, pi\u00f9 sync, ring e test) e nota. Con passi di 1 frame si fanno gli <b>arpeggi<\/b> classici del C64 (0, +4, +7 e \"Poi torna al passo 1\") e i <b>tamburi<\/b> (rumore e poi impulso con note assolute che scendono).<\/p><p>Il <b>ring<\/b> della voce 1 usa la voce 3, quello della voce 2 la voce 1, quello della voce 3 la voce 2; lo stesso vale per il <b>sync<\/b>. Il filtro \u00e8 uno solo per tutto il chip: lo strumento pu\u00f2 accenderlo per la sua voce e reimpostare tipo, risonanza e cutoff. L\\'<b>hard restart<\/b> spegne il gate e azzera l\\'ADSR due frame prima di ogni nota, per evitare il famoso \"bug\" dell\\'ADSR del SID.<\/p>',\n  gC64T: 'Sul Commodore 64 vero',\n  gC64: '<p>Il <b>.PRG<\/b> contiene una riga BASIC (10 SYS2061), il player e la canzone. Copialo su SD2IEC, Kung Fu Flash, 1541 Ultimate, Pi1541 o su un disco .D64 e scrivi <code>LOAD\"NOME\",8<\/code> e poi <code>RUN<\/code>. Il bordo che cambia colore mostra quanto tempo raster usa il player a ogni frame. In VICE basta trascinarlo nella finestra.<\/p><p>Il <b>.SID<\/b> (PSID v2) si ascolta con SIDPlay, JSidPlay2, VICE (VSID) o deepSID. Il <b>.ASM<\/b> \u00e8 il sorgente completo, commentato: si assembla con <code>acme song.asm<\/code> o <code>64tass -C -a -o song.prg song.asm<\/code> e d\u00e0 lo stesso file. Per usare la musica in un tuo programma: <code>LDA #0 : JSR $1000<\/code> una volta, poi <code>JSR $1003<\/code> a ogni frame.<\/p>',\n  gTechT: 'Come funziona',\n  gTech: '<p>L\\'anteprima non imita il player: lo <b>esegue<\/b>. Il sorgente 6502 viene assemblato nel browser e gira su una CPU 6502 emulata, che chiama la routine play 50 volte al secondo (60 in NTSC) e scrive i registri del SID nel ciclo esatto. Sono gli stessi byte del .PRG, quindi i registri sono gli stessi del C64.<\/p><p>Il SID \u00e8 emulato da zero in un AudioWorklet: oscillatori a 24 bit, rumore LFSR, sync, ring, inviluppo con contatore a 15 bit (con il bug dell\\'ADSR) e filtro a 2 poli con la curva del 6581 o dell\\'8580. Le forme d\\'onda combinate e la curva del filtro del 6581 sono approssimate: ogni chip vero suona un po\\' diverso.<\/p>'\n},\nen: {\n  kicker: 'Music \u00b7 Commodore 64 \u00b7 tracker in your browser',\n  lead: 'A composer for the Commodore 64 <b>SID<\/b> chip, in the style of GoatTracker, SID-Wizard and CheeseCutter: three voices, patterns, instruments with a wave table, ADSR, filter, ring modulation and sync. It plays through an emulated 6581 or 8580 SID and exports a <b>.PRG<\/b> that runs on a real C64, a <b>.SID<\/b> file and assembler source.',\n  play: 'Song', playPos: 'From here', playPat: 'Pattern', stop: 'Stop',\n  ttPlay: 'Play the song from the start', ttPlayPos: 'Play from the selected order row', ttPlayPat: 'Loop the selected order row',\n  ttStop: 'Stop (space bar: play\/stop)', ttRec: 'Edit mode (Esc)', ttUndo: 'Undo (Ctrl+Z)', ttRedo: 'Redo (Ctrl+Y)',\n  octave: 'Octave', step: 'Step', follow: 'Follow', audioOff: 'audio off: press \u25b6 or a key',\n  audioOn: 'audio on', audioNo: 'audio not available in this browser',\n  title: 'Title', author: 'Author', released: 'Year\/credits', speed: 'Speed', rows: 'Rows', volume: 'Volume', video: 'Video',\n  examples: 'Examples', exChoose: '\u2014 pick a song \u2014', new: 'New', open: 'Open\u2026', save: 'Save project',\n  exportC64: 'Export:',\n  orderT: 'Order', cellPat: 'Pattern in cell', freePat: 'new',\n  ttOAdd: 'Add a row after the selected one', ttODup: 'Duplicate the row (same patterns)', ttOClone: 'Duplicate the row with new copies of its patterns',\n  ttODel: 'Delete the row', ttOUp: 'Move up', ttODown: 'Move down', ttOLoop: 'Song loop point', ttFree: 'Put an unused empty pattern in the cell',\n  orderHint: 'Each row tells which pattern every voice plays. Click a number to select it, type in hex or use <b>&lt; &gt;<\/b>. <b>L<\/b> marks where the song loops back.',\n  voice: 'Voice', mute: 'Mute', solo: 'Solo', pat: 'Pat',\n  tpUp: '+1', tpDn: '\u22121', tpOctUp: '+12', tpOctDn: '\u221212', copy: 'Copy', paste: 'Paste', clear: 'Clear',\n  ttTp: 'Transpose this voice\\'s pattern', ttCopy: 'Copy the pattern', ttPaste: 'Paste over the pattern', ttClear: 'Clear the pattern',\n  gridHint: 'Click the pattern and play with your PC keyboard. With <b>REC<\/b> on, notes are written: note, instrument (2 digits) and command (3 hex digits). <b>1<\/b> = note off, <b>Del<\/b> clears, <b>Ins<\/b>\/<b>Backspace<\/b> insert or remove a row, keypad <b>+\/\u2212<\/b> change octave.',\n  instrT: 'Instruments', iNew: 'New', iDup: 'Duplicate', iDel: 'Delete',\n  iName: 'Name', iEnv: 'ADSR envelope', iA: 'Attack', iD: 'Decay', iS: 'Sustain', iR: 'Release',\n  iWave: 'Wave table', iWaveHint: 'One step per frame (or per \"duration\" frames). The note is relative (+7 = a fifth up) or absolute (for drums and effects). A step with no waveform keeps the previous one.',\n  iStep: 'Step', iNoteRel: 'rel', iNoteAbs: 'abs', iDur: 'frames', iLoop: 'Then', iLoopStop: 'stop', iLoopTo: 'go to step',\n  iAddStep: '+ step', iPulse: 'Pulse', iPw: 'Width', iPwSpd: 'Speed', iPwMin: 'Min', iPwMax: 'Max', iPwKeep: 'don\\'t reset on each note',\n  iFilter: 'Filter', iFOn: 'Voice filtered', iFSet: 'Set the filter on each note', iFMode: 'Type', iFRes: 'Resonance', iFCut: 'Cutoff', iFSpd: 'Sweep',\n  iVib: 'Vibrato', iVDel: 'Delay', iVSpd: 'Speed', iVDep: 'Depth', iHR: 'Hard restart (2 frames before each note)',\n  iNone: 'no instrument', iTest: 'Test',\n  pianoT: 'Keyboard', pianoHint: 'Play with the mouse or your PC keyboard: <b>Z S X D C V\u2026<\/b> lower octave, <b>Q 2 W 3 E R\u2026<\/b> upper octave. It uses the cursor\\'s voice and the selected instrument.',\n  monT: 'SID registers ($D400-$D418)',\n  msgBuilt: 'Player + song: {n} bytes ($1000-${end})', msgSaved: 'Project saved', msgLoaded: 'Project loaded', msgBadFile: 'Invalid file',\n  msgPRG: '.PRG ready: load it with LOAD\"{f}\",8 and RUN', msgSID: '.SID ready (PSID v2, init $1000, play $1003)', msgWAV: 'WAV: {s} seconds', msgWAVwait: 'Rendering the WAV\u2026',\n  msgDelLast: 'At least one row is needed', msgConfirmNew: 'Start a new song? The current one is lost if you have not saved it.',\n  msgConfirmEx: 'Load the example? The current song is lost if you have not saved it.', msgRestored: 'Your last song was restored',\n  regNames: ['FREQ LO', 'FREQ HI', 'PW LO', 'PW HI', 'CTRL', 'AD', 'SR'], regGlob: ['FC LO', 'FC HI', 'RES\/FILT', 'MODE\/VOL'],\n  gStartT: 'How to use it',\n  gStart: '<ol><li>Pick an <b>example<\/b> and press <b>\u25b6 Song<\/b> to hear what the SID can do.<\/li><li>Click the pattern, turn on <b>REC<\/b> (or press Esc) and type notes on your PC keyboard. The instrument is the one selected on the right.<\/li><li>Build the song in the <b>Order<\/b> column: each row assigns a pattern to each of the 3 voices.<\/li><li>Shape the sounds in the <b>Instruments<\/b> panel while the music plays: changes are heard immediately.<\/li><li><b>Save project<\/b> downloads a .json file you can reopen with <b>Open\u2026<\/b>. The song is also kept in your browser.<\/li><li>When you like it, export the <b>.PRG<\/b> for the C64 or the <b>.SID<\/b> for other players.<\/li><\/ol>',\n  gKeysT: 'Keys',\n  gKeys: '<table><tr><td>Z S X D C V G B H N J M<\/td><td>notes of the chosen octave<\/td><\/tr><tr><td>Q 2 W 3 E R 5 T 6 Y 7 U I 9 O 0 P<\/td><td>octave above<\/td><\/tr><tr><td>1<\/td><td>note off (gate off)<\/td><\/tr><tr><td>0-9 A-F<\/td><td>instrument and command (hex columns)<\/td><\/tr><tr><td>arrows \u00b7 Tab \u00b7 PgUp PgDn<\/td><td>move the cursor<\/td><\/tr><tr><td>Del \u00b7 Ins \u00b7 Backspace<\/td><td>clear \u00b7 insert row \u00b7 remove row<\/td><\/tr><tr><td>Space \u00b7 Esc<\/td><td>play\/stop \u00b7 REC<\/td><\/tr><tr><td>keypad + \u2212<\/td><td>octave<\/td><\/tr><tr><td>Ctrl+Z \u00b7 Ctrl+Y<\/td><td>undo \u00b7 redo<\/td><\/tr><\/table>',\n  gCmdT: 'Pattern commands',\n  gCmd: '<p>A command is one hex digit plus a two-digit parameter, active on its row.<\/p><table><tr><td>1xx<\/td><td>slide up by xx per frame<\/td><\/tr><tr><td>2xx<\/td><td>slide down<\/td><\/tr><tr><td>3xx<\/td><td>portamento to the row\\'s note (speed xx), no retrigger<\/td><\/tr><tr><td>4xy<\/td><td>vibrato: x speed, y depth 0-8<\/td><\/tr><tr><td>5xx \u00b7 6xx<\/td><td>write AD \u00b7 SR<\/td><\/tr><tr><td>8xx<\/td><td>pulse width = xx\u00b716<\/td><\/tr><tr><td>9xx<\/td><td>pulse sweep speed (signed, 80-FF negative)<\/td><\/tr><tr><td>Axx<\/td><td>filter cutoff = xx\u00b78<\/td><\/tr><tr><td>Bxy<\/td><td>resonance x, type y (1 low pass, 2 band, 4 high, can be added)<\/td><\/tr><tr><td>Cxx<\/td><td>master volume (0-F)<\/td><\/tr><tr><td>Exx<\/td><td>cutoff sweep (signed)<\/td><\/tr><tr><td>Fxx<\/td><td>speed: frames per row<\/td><\/tr><\/table>',\n  gInsT: 'Instruments and the wave table',\n  gIns: '<p>Every frame (1\/50 of a second) the player reads one step of the <b>wave table<\/b>: waveform (triangle, sawtooth, pulse, noise and their combinations, plus sync, ring and test) and note. One-frame steps give the classic C64 <b>arpeggios<\/b> (0, +4, +7 and \"Then go to step 1\") and <b>drums<\/b> (noise, then pulse with falling absolute notes).<\/p><p><b>Ring<\/b> on voice 1 uses voice 3, on voice 2 voice 1, on voice 3 voice 2; the same goes for <b>sync<\/b>. There is only one filter for the whole chip: an instrument can switch it on for its voice and reset type, resonance and cutoff. <b>Hard restart<\/b> turns the gate off and zeroes the ADSR two frames before each note, to avoid the famous SID \"ADSR bug\".<\/p>',\n  gC64T: 'On a real Commodore 64',\n  gC64: '<p>The <b>.PRG<\/b> holds a BASIC line (10 SYS2061), the player and the song. Copy it to an SD2IEC, Kung Fu Flash, 1541 Ultimate, Pi1541 or a .D64 disk, type <code>LOAD\"NAME\",8<\/code> and then <code>RUN<\/code>. The border colour change shows how much raster time the player uses each frame. In VICE just drop it on the window.<\/p><p>The <b>.SID<\/b> (PSID v2) plays in SIDPlay, JSidPlay2, VICE (VSID) or deepSID. The <b>.ASM<\/b> is the full commented source: build it with <code>acme song.asm<\/code> or <code>64tass -C -a -o song.prg song.asm<\/code> to get the same file. To use the music in your own program: <code>LDA #0 : JSR $1000<\/code> once, then <code>JSR $1003<\/code> every frame.<\/p>',\n  gTechT: 'How it works',\n  gTech: '<p>The preview does not imitate the player: it <b>runs<\/b> it. The 6502 source is assembled in the browser and runs on an emulated 6502 CPU, which calls the play routine 50 times a second (60 on NTSC) and writes the SID registers on the exact cycle. These are the same bytes as the .PRG, so the registers are the same as on the C64.<\/p><p>The SID is emulated from scratch in an AudioWorklet: 24-bit oscillators, LFSR noise, sync, ring, envelope with the 15-bit counter (including the ADSR bug) and a 2-pole filter with the 6581 or 8580 curve. Combined waveforms and the 6581 filter curve are approximations: every real chip sounds a little different.<\/p>'\n}\n};\n})(globalThis.RFSid = globalThis.RFSid || {});\nglobalThis.RFSid.WORKLET_SRC = \"\/* RetroFixer \\u00b7 SID Composer \\u00b7 emulazione del chip SID MOS 6581 \/ 8580\\n * Scritta da zero per questa pagina. Oscillatori, rumore, sync, ring e inviluppo girano ciclo per ciclo\\n * (985 248 cicli al secondo in PAL), compreso il \\\"bug\\\" dell'ADSR del contatore a 15 bit.\\n * Il filtro \\u00e8 un state-variable a 2 poli con la curva del cutoff tipica del 6581 o lineare dell'8580.\\n * Le forme d'onda combinate vengono da un modello a \\\"bit vicini\\\" (approssimato, non campionato da un chip).\\n *\/\\n(function (RF) {\\n'use strict';\\n\\nvar RATE = [9, 32, 63, 95, 149, 220, 267, 313, 392, 977, 1954, 3126, 3907, 11720, 19532, 31251];\\nvar NORM = 1 \/ (2048 * 255);\\n\\n\/\/ ---- forme d'onda combinate ------------------------------------------------------------------\\n\/\/ Ogni bit d'uscita guarda i bit vicini con un peso che cala con la distanza: se la media supera la\\n\/\/ soglia il bit resta a 1. Cos\\u00ec le combinazioni suonano \\\"pi\\u00f9 magre\\\" di un semplice AND, come sul chip.\\nfunction combTable(kind, strength, thr) {\\n  var t = new Uint16Array(4096), b = new Float32Array(12), w = new Float32Array(24), i, j, k;\\n  for (i = 0; i < 24; i++) w[ i] = Math.pow(strength, -Math.abs(i - 12));\\n  for (i = 0; i < 4096; i++) {\\n    var tri = ((i & 0x800) ? (~i & 0x7FF) : (i & 0x7FF)) << 1;\\n    for (j = 0; j < 12; j++) {\\n      var s = (i >> j) & 1, tr = (tri >> j) & 1;\\n      b[ j] = kind === 'ST' ? (s + tr) \/ 2 : kind === 'PT' ? (1 + tr) \/ 2 : kind === 'PS' ? (1 + s) \/ 2 : (1 + s + tr) \/ 3;\\n    }\\n    var out = 0;\\n    for (k = 0; k < 12; k++) {\\n      var sum = 0, n = 0;\\n      for (j = 0; j < 12; j++) { sum += w[j - k + 12] * b[ j]; n += w[j - k + 12]; }\\n      if (sum \/ n > thr) out |= 1 << k;\\n    }\\n    t[ i] = out;\\n  }\\n  return t;\\n}\\nvar TABLES = {};\\nfunction tablesFor(model) {\\n  if (TABLES[ model]) return TABLES[ model];\\n  var p = model === 8580\\n    ? { ST: [2.2, 0.80], PT: [2.6, 0.70], PS: [2.4, 0.72], PST: [2.2, 0.78] }\\n    : { ST: [1.7, 0.86], PT: [2.0, 0.80], PS: [1.9, 0.82], PST: [1.7, 0.86] };\\n  return (TABLES[ model] = { ST: combTable('ST', p.ST[0], p.ST[1]), PT: combTable('PT', p.PT[0], p.PT[1]),\\n    PS: combTable('PS', p.PS[0], p.PS[1]), PST: combTable('PST', p.PST[0], p.PST[1]) });\\n}\\n\\n\/\/ ---- curva del cutoff (Hz) ---------------------------------------------------------------------\\n\/\/ 6581: curva \\\"media\\\" (ogni chip \\u00e8 diverso), con la zona morta in basso e la salita ripida a met\\u00e0.\\nvar FC6581 = [[0, 200], [256, 230], [384, 280], [512, 400], [640, 700], [768, 1300], [896, 2400],\\n  [1024, 3800], [1280, 6800], [1536, 9600], [1792, 12000], [2047, 14000]];\\nfunction cutoffHz(model, fc) {\\n  if (model === 8580) return 30 + fc * 5.8;\\n  for (var i = 1; i < FC6581.length; i++) {\\n    if (fc <= FC6581[ i][0]) {\\n      var a = FC6581[i - 1], b = FC6581[ i], t = (fc - a[0]) \/ (b[0] - a[0]);\\n      return Math.exp(Math.log(a[1]) + t * (Math.log(b[1]) - Math.log(a[1])));\\n    }\\n  }\\n  return 14000;\\n}\\n\\nfunction SID(sampleRate, model, clockHz) {\\n  this.fs = sampleRate || 48000;\\n  this.regs = new Uint8Array(32);\\n  this.acc = new Int32Array(3); this.freq = new Int32Array(3); this.pw = new Int32Array(3);\\n  this.ctrl = new Int32Array(3); this.last = new Int32Array(3); this.shift = new Int32Array(3); this.nout = new Int32Array(3);\\n  this.env = new Int32Array(3); this.state = new Int32Array(3); this.rc = new Int32Array(3); this.rp = new Int32Array(3);\\n  this.ec = new Int32Array(3); this.ep = new Int32Array(3); this.hold = new Int32Array(3);\\n  this.att = new Int32Array(3); this.dec = new Int32Array(3); this.sus = new Int32Array(3); this.rel = new Int32Array(3);\\n  this.out = new Float32Array(4096); this.outN = 0;\\n  this.mute = 0;\\n  this.setModel(model || 6581);\\n  this.setClock(clockHz || 985248);\\n  this.reset();\\n}\\nSID.prototype.reset = function () {\\n  for (var v = 0; v < 3; v++) {\\n    this.acc[ v] = 0; this.freq[ v] = 0; this.pw[ v] = 0; this.ctrl[ v] = 0; this.last[ v] = 0;\\n    this.shift[ v] = 0x7FFFFF; this.nout[ v] = noiseOut(0x7FFFFF);\\n    this.env[ v] = 0; this.state[ v] = 2; this.rc[ v] = 0; this.rp[ v] = RATE[0]; this.ec[ v] = 0; this.ep[ v] = 1; this.hold[ v] = 1;\\n    this.att[ v] = this.dec[ v] = this.sus[ v] = this.rel[ v] = 0;\\n  }\\n  this.regs.fill(0);\\n  this.fc = 0; this.res = 0; this.filt = 0; this.mode = 0; this.vol = 0; this.v3off = 0;\\n  this.lp = 0; this.bp = 0; this.e1 = 0; this.e2 = 0;\\n  this.fsum = 0; this.dsum = 0; this.n = 0; this.sc = this.cps;\\n  this.updFilter();\\n};\\nSID.prototype.setClock = function (hz) {\\n  this.clockHz = hz; this.cps = hz \/ this.fs; this.sc = this.cps;\\n  this.updFilter();\\n};\\nSID.prototype.setModel = function (model) {\\n  this.model = model === 8580 ? 8580 : 6581;\\n  var t = tablesFor(this.model);\\n  this.tST = t.ST; this.tPT = t.PT; this.tPS = t.PS; this.tPST = t.PST;\\n  \/\/ il 6581 ha uno zero della forma d'onda spostato e un'uscita in continua: per questo il registro\\n  \/\/ del volume fa \\\"click\\\" (le vecchie digi). Sull'8580 \\u00e8 quasi tutto centrato.\\n  this.wz = this.model === 8580 ? 0x800 : 0x380;\\n  this.vdc = this.model === 8580 ? 0 : Math.round(0.18 \/ NORM);\\n  this.mdc = this.model === 8580 ? 0.02 : 0.25;\\n  this.updFilter();\\n};\\nSID.prototype.updFilter = function () {\\n  if (!this.fs) return;\\n  var hz = cutoffHz(this.model, this.fc || 0);\\n  var f = 2 * Math.sin(Math.PI * Math.min(hz, this.fs * 0.9) \/ (this.fs * 2));\\n  this.f = Math.min(f, 1.3);\\n  this.q = this.model === 8580 ? Math.pow(2, (4 - (this.res || 0)) \/ 8) : 1 \/ (0.707 + (this.res || 0) * 0.1);\\n  this.ea = 1 - Math.exp(-2 * Math.PI * 16000 \/ this.fs);\\n  this.eb = 1 - Math.exp(-2 * Math.PI * 16 \/ this.fs);\\n};\\n\\nfunction noiseOut(s) {\\n  return ((s >> 9) & 0x800) | ((s >> 8) & 0x400) | ((s >> 5) & 0x200) | ((s >> 3) & 0x100) |\\n    ((s >> 2) & 0x080) | ((s << 1) & 0x040) | ((s << 3) & 0x020) | ((s << 4) & 0x010);\\n}\\n\/\/ quando il rumore \\u00e8 combinato con un'altra forma d'onda i bit a 0 si \\\"scrivono\\\" nel registro a scorrimento\\nfunction noiseWriteBack(s, o) {\\n  o = ~o;\\n  return s & ~(((o & 0x800) << 9) | ((o & 0x400) << 8) | ((o & 0x200) << 5) | ((o & 0x100) << 3) |\\n    ((o & 0x080) << 2) | ((o & 0x040) >> 1) | ((o & 0x020) >> 3) | ((o & 0x010) >> 4));\\n}\\n\\nSID.prototype.write = function (r, val) {\\n  r &= 0x1F; val &= 0xFF;\\n  this.regs[ r] = val;\\n  if (r < 21) {\\n    var v = (r \/ 7) | 0, k = r - v * 7;\\n    switch (k) {\\n      case 0: this.freq[ v] = (this.freq[ v] & 0xFF00) | val; break;\\n      case 1: this.freq[ v] = (this.freq[ v] & 0xFF) | (val << 8); break;\\n      case 2: this.pw[ v] = (this.pw[ v] & 0xF00) | val; break;\\n      case 3: this.pw[ v] = (this.pw[ v] & 0xFF) | ((val & 0x0F) << 8); break;\\n      case 4:\\n        var old = this.ctrl[ v];\\n        if ((val & 1) && !(old & 1)) { this.state[ v] = 0; this.rp[ v] = RATE[this.att[ v]]; this.hold[ v] = 0; }\\n        else if (!(val & 1) && (old & 1)) { this.state[ v] = 2; this.rp[ v] = RATE[this.rel[ v]]; }\\n        if (val & 8) { this.acc[ v] = 0; this.shift[ v] = 0x7FFFFF; this.nout[ v] = noiseOut(0x7FFFFF); }\\n        this.ctrl[ v] = val;\\n        break;\\n      case 5:\\n        this.att[ v] = val >> 4; this.dec[ v] = val & 15;\\n        if (this.state[ v] === 0) this.rp[ v] = RATE[this.att[ v]]; else if (this.state[ v] === 1) this.rp[ v] = RATE[this.dec[ v]];\\n        break;\\n      case 6:\\n        this.sus[ v] = val >> 4; this.rel[ v] = val & 15;\\n        if (this.state[ v] === 2) this.rp[ v] = RATE[this.rel[ v]];\\n        break;\\n    }\\n    return;\\n  }\\n  switch (r) {\\n    case 21: this.fc = (this.fc & 0x7F8) | (val & 7); this.updFilter(); break;\\n    case 22: this.fc = (this.fc & 7) | (val << 3); this.updFilter(); break;\\n    case 23: this.res = val >> 4; this.filt = val & 7; this.updFilter(); break;\\n    case 24: this.mode = (val >> 4) & 7; this.v3off = val & 0x80; this.vol = val & 15; break;\\n  }\\n};\\nSID.prototype.read = function (r) {\\n  r &= 0x1F;\\n  if (r === 27) return this.last[2] >> 4;\\n  if (r === 28) return this.env[2];\\n  return 0;\\n};\\n\\n\/\/ fa avanzare il chip di n cicli; ogni campione audio finisce in this.out\\nSID.prototype.clock = function (n) {\\n  var acc = this.acc, freq = this.freq, pw = this.pw, ctrl = this.ctrl, last = this.last, shift = this.shift, nout = this.nout;\\n  var env = this.env, state = this.state, rc = this.rc, rp = this.rp, ec = this.ec, ep = this.ep, hold = this.hold;\\n  var dec = this.dec, sus = this.sus;\\n  var tST = this.tST, tPT = this.tPT, tPS = this.tPS, tPST = this.tPST, wz = this.wz, vdc = this.vdc;\\n  var filt = this.filt, mute = this.mute, v3off = this.v3off;\\n  var fsum = this.fsum, dsum = this.dsum, cnt = this.n, sc = this.sc, cps = this.cps;\\n  var rise0 = 0, rise1 = 0, rise2 = 0, v, a, p, c, o, w, e;\\n  while (n-- > 0) {\\n    \/\/ oscillatori\\n    for (v = 0; v < 3; v++) {\\n      c = ctrl[ v];\\n      if (c & 8) { if (v === 0) rise0 = 0; else if (v === 1) rise1 = 0; else rise2 = 0; continue; }\\n      p = acc[ v]; a = (p + freq[ v]) & 0xFFFFFF; acc[ v] = a;\\n      if (v === 0) rise0 = ~p & a & 0x800000; else if (v === 1) rise1 = ~p & a & 0x800000; else rise2 = ~p & a & 0x800000;\\n      if (~p & a & 0x80000) {\\n        var s = shift[ v];\\n        if ((c & 0x80) && (c & 0x70)) s = noiseWriteBack(s, last[ v]);\\n        s = ((s << 1) & 0x7FFFFF) | (((s >> 22) ^ (s >> 17)) & 1);\\n        shift[ v] = s; nout[ v] = noiseOut(s);\\n      }\\n    }\\n    \/\/ hard sync: la sorgente della voce 1 \\u00e8 la 3, della 2 \\u00e8 la 1, della 3 \\u00e8 la 2\\n    if (rise2 && (ctrl[0] & 2)) acc[0] = 0;\\n    if (rise0 && (ctrl[1] & 2)) acc[1] = 0;\\n    if (rise1 && (ctrl[2] & 2)) acc[2] = 0;\\n    \/\/ inviluppi e uscita delle voci\\n    for (v = 0; v < 3; v++) {\\n      e = rc[ v] + 1;\\n      if (e & 0x8000) e = (e + 1) & 0x7FFF;\\n      if (e === rp[ v]) {\\n        e = 0;\\n        if (state[ v] === 0 || ++ec[ v] === ep[ v]) {\\n          ec[ v] = 0;\\n          if (!hold[ v]) {\\n            var lv = env[ v];\\n            if (state[ v] === 0) { lv = (lv + 1) & 0xFF; if (lv === 0xFF) { state[ v] = 1; rp[ v] = RATE[dec[ v]]; } }\\n            else if (state[ v] === 1) { if (lv !== sus[ v] * 17) lv = (lv - 1) & 0xFF; }\\n            else lv = (lv - 1) & 0xFF;\\n            env[ v] = lv;\\n            switch (lv) {\\n              case 0xFF: ep[ v] = 1; break; case 0x5D: ep[ v] = 2; break; case 0x36: ep[ v] = 4; break;\\n              case 0x1A: ep[ v] = 8; break; case 0x0E: ep[ v] = 16; break; case 0x06: ep[ v] = 30; break;\\n              case 0x00: ep[ v] = 1; hold[ v] = 1; break;\\n            }\\n          }\\n        }\\n      }\\n      rc[ v] = e;\\n      c = ctrl[ v]; a = acc[ v];\\n      switch (c >> 4) {\\n        case 0: w = last[ v]; break;\\n        case 1:\\n          if (c & 4) { var src = v === 0 ? acc[2] : acc[v - 1]; o = (a ^ src) & 0x800000; } else o = a & 0x800000;\\n          w = ((o ? ~a : a) >> 11) & 0xFFF; break;\\n        case 2: w = a >> 12; break;\\n        case 3: w = tST[a >> 12]; break;\\n        case 4: w = ((c & 8) || (a >> 12) >= pw[ v]) ? 0xFFF : 0; break;\\n        case 5: w = ((c & 8) || (a >> 12) >= pw[ v]) ? tPT[a >> 12] : 0; break;\\n        case 6: w = ((c & 8) || (a >> 12) >= pw[ v]) ? tPS[a >> 12] : 0; break;\\n        case 7: w = ((c & 8) || (a >> 12) >= pw[ v]) ? tPST[a >> 12] : 0; break;\\n        case 8: w = nout[ v]; break;\\n        default:\\n          o = (c >> 4) & 7;\\n          w = o === 1 ? ((a & 0x800000 ? ~a : a) >> 11) & 0xFFF : o === 2 ? a >> 12 : o === 3 ? tST[a >> 12] :\\n            ((a >> 12) >= pw[ v] ? (o === 4 ? 0xFFF : o === 5 ? tPT[a >> 12] : o === 6 ? tPS[a >> 12] : tPST[a >> 12]) : 0);\\n          w &= nout[ v];\\n      }\\n      last[ v] = w;\\n      if (!((mute >> v) & 1)) {\\n        o = (w - wz) * env[ v] + vdc;\\n        if ((filt >> v) & 1) fsum += o; else if (v !== 2 || !v3off) dsum += o;\\n      }\\n    }\\n    cnt++;\\n    if (--sc <= 0) {\\n      sc += cps;\\n      this.fsum = fsum; this.dsum = dsum; this.n = cnt;\\n      this.emit();\\n      fsum = 0; dsum = 0; cnt = 0;\\n    }\\n  }\\n  this.fsum = fsum; this.dsum = dsum; this.n = cnt; this.sc = sc;\\n};\\n\\nSID.prototype.emit = function () {\\n  var k = NORM \/ (this.n || 1), fi = this.fsum * k, di = this.dsum * k;\\n  var lp = this.lp, bp = this.bp, hp = 0, f = this.f, q = this.q;\\n  for (var i = 0; i < 2; i++) {\\n    hp = fi - lp - q * bp;\\n    bp += f * hp;\\n    lp += f * bp;\\n  }\\n  if (this.model === 6581) { bp = bp \/ (1 + 0.02 * bp * bp); lp = lp \/ (1 + 0.01 * lp * lp); }   \/\/ leggera saturazione\\n  this.lp = lp; this.bp = bp;\\n  var m = this.mode, fo = ((m & 1) ? lp : 0) + ((m & 2) ? bp : 0) + ((m & 4) ? hp : 0);\\n  var x = ((fo + di) * 0.22 + this.mdc) * this.vol \/ 15;\\n  this.e1 += this.ea * (x - this.e1);          \/\/ filtro esterno passa basso ~16 kHz\\n  this.e2 += this.eb * (this.e1 - this.e2);    \/\/ condensatore d'uscita (passa alto ~16 Hz)\\n  var y = this.e1 - this.e2;\\n  if (y > 0.9) y = 0.9 + (y - 0.9) \/ (1 + (y - 0.9) * 8); else if (y < -0.9) y = -0.9 + (y + 0.9) \/ (1 - (y + 0.9) * 8);\\n  if (this.outN < this.out.length) this.out[this.outN++] = y;\\n};\\n\\nRF.SID = SID;\\nRF.SID_RATE = RATE;\\nRF.cutoffHz = cutoffHz;\\n})(globalThis.RFSid = globalThis.RFSid || {});\\n\\n\/* RetroFixer \\u00b7 SID Composer \\u00b7 CPU 6502 (MOS 6510 del C64)\\n * Tutti gli opcode documentati, con i cicli e il ciclo in pi\\u00f9 quando l'indirizzo cambia pagina.\\n * La stessa tabella serve all'assembler, cos\\u00ec CPU e assembler non possono andare in disaccordo.\\n *\/\\n(function (RF) {\\n'use strict';\\n\\nvar LIST = (\\n  '69 ADC imm 2|65 ADC zp 3|75 ADC zpx 4|6D ADC abs 4|7D ADC abx 4 p|79 ADC aby 4 p|61 ADC izx 6|71 ADC izy 5 p|' +\\n  '29 AND imm 2|25 AND zp 3|35 AND zpx 4|2D AND abs 4|3D AND abx 4 p|39 AND aby 4 p|21 AND izx 6|31 AND izy 5 p|' +\\n  '0A ASL acc 2|06 ASL zp 5|16 ASL zpx 6|0E ASL abs 6|1E ASL abx 7|' +\\n  '90 BCC rel 2|B0 BCS rel 2|F0 BEQ rel 2|30 BMI rel 2|D0 BNE rel 2|10 BPL rel 2|50 BVC rel 2|70 BVS rel 2|' +\\n  '24 BIT zp 3|2C BIT abs 4|00 BRK imp 7|18 CLC imp 2|D8 CLD imp 2|58 CLI imp 2|B8 CLV imp 2|' +\\n  'C9 CMP imm 2|C5 CMP zp 3|D5 CMP zpx 4|CD CMP abs 4|DD CMP abx 4 p|D9 CMP aby 4 p|C1 CMP izx 6|D1 CMP izy 5 p|' +\\n  'E0 CPX imm 2|E4 CPX zp 3|EC CPX abs 4|C0 CPY imm 2|C4 CPY zp 3|CC CPY abs 4|' +\\n  'C6 DEC zp 5|D6 DEC zpx 6|CE DEC abs 6|DE DEC abx 7|CA DEX imp 2|88 DEY imp 2|' +\\n  '49 EOR imm 2|45 EOR zp 3|55 EOR zpx 4|4D EOR abs 4|5D EOR abx 4 p|59 EOR aby 4 p|41 EOR izx 6|51 EOR izy 5 p|' +\\n  'E6 INC zp 5|F6 INC zpx 6|EE INC abs 6|FE INC abx 7|E8 INX imp 2|C8 INY imp 2|' +\\n  '4C JMP abs 3|6C JMP ind 5|20 JSR abs 6|' +\\n  'A9 LDA imm 2|A5 LDA zp 3|B5 LDA zpx 4|AD LDA abs 4|BD LDA abx 4 p|B9 LDA aby 4 p|A1 LDA izx 6|B1 LDA izy 5 p|' +\\n  'A2 LDX imm 2|A6 LDX zp 3|B6 LDX zpy 4|AE LDX abs 4|BE LDX aby 4 p|' +\\n  'A0 LDY imm 2|A4 LDY zp 3|B4 LDY zpx 4|AC LDY abs 4|BC LDY abx 4 p|' +\\n  '4A LSR acc 2|46 LSR zp 5|56 LSR zpx 6|4E LSR abs 6|5E LSR abx 7|EA NOP imp 2|' +\\n  '09 ORA imm 2|05 ORA zp 3|15 ORA zpx 4|0D ORA abs 4|1D ORA abx 4 p|19 ORA aby 4 p|01 ORA izx 6|11 ORA izy 5 p|' +\\n  '48 PHA imp 3|08 PHP imp 3|68 PLA imp 4|28 PLP imp 4|' +\\n  '2A ROL acc 2|26 ROL zp 5|36 ROL zpx 6|2E ROL abs 6|3E ROL abx 7|' +\\n  '6A ROR acc 2|66 ROR zp 5|76 ROR zpx 6|6E ROR abs 6|7E ROR abx 7|40 RTI imp 6|60 RTS imp 6|' +\\n  'E9 SBC imm 2|E5 SBC zp 3|F5 SBC zpx 4|ED SBC abs 4|FD SBC abx 4 p|F9 SBC aby 4 p|E1 SBC izx 6|F1 SBC izy 5 p|' +\\n  '38 SEC imp 2|F8 SED imp 2|78 SEI imp 2|' +\\n  '85 STA zp 3|95 STA zpx 4|8D STA abs 4|9D STA abx 5|99 STA aby 5|81 STA izx 6|91 STA izy 6|' +\\n  '86 STX zp 3|96 STX zpy 4|8E STX abs 4|84 STY zp 3|94 STY zpx 4|8C STY abs 4|' +\\n  'AA TAX imp 2|A8 TAY imp 2|BA TSX imp 2|8A TXA imp 2|9A TXS imp 2|98 TYA imp 2'\\n).split('|');\\n\\nvar OPS = new Array(256), BYNAME = {};\\nLIST.forEach(function (s) {\\n  var f = s.split(' '), op = parseInt(f[0], 16);\\n  OPS[ op] = { op: op, n: f[1], m: f[2], c: +f[3], p: f[4] === 'p' };\\n  (BYNAME[f[1]] = BYNAME[f[1]] || {})[f[2]] = op;\\n});\\nRF.OPS = OPS;\\nRF.OPS_BYNAME = BYNAME;\\nRF.OPLEN = { imp: 1, acc: 1, imm: 2, zp: 2, zpx: 2, zpy: 2, rel: 2, izx: 2, izy: 2, abs: 3, abx: 3, aby: 3, ind: 3 };\\n\\nfunction CPU(mem) {\\n  this.mem = mem || new Uint8Array(65536);\\n  this.a = 0; this.x = 0; this.y = 0; this.sp = 0xFF; this.pc = 0;\\n  this.c = 0; this.z = 0; this.i = 1; this.d = 0; this.v = 0; this.n = 0;\\n  this.cycles = 0; this.halted = false;\\n  this.ioWrite = null;  \/\/ function (addr, value) per $D400-$D7FF (SID)\\n  this.ioRead = null;\\n}\\nCPU.prototype.rd = function (a) {\\n  if ((a & 0xFC00) === 0xD400 && this.ioRead) return this.ioRead(a) & 0xFF;\\n  return this.mem[ a];\\n};\\nCPU.prototype.wr = function (a, v) {\\n  if ((a & 0xFC00) === 0xD400 && this.ioWrite) this.ioWrite(a, v);\\n  this.mem[ a] = v;\\n};\\nCPU.prototype.getP = function (b) {\\n  return (this.n << 7) | (this.v << 6) | 0x20 | (b ? 0x10 : 0) | (this.d << 3) | (this.i << 2) | (this.z ? 2 : 0) | this.c;\\n};\\nCPU.prototype.setP = function (p) {\\n  this.n = (p >> 7) & 1; this.v = (p >> 6) & 1; this.d = (p >> 3) & 1; this.i = (p >> 2) & 1; this.z = (p >> 1) & 1; this.c = p & 1;\\n};\\nCPU.prototype.push = function (v) { this.mem[0x100 | this.sp] = v & 0xFF; this.sp = (this.sp - 1) & 0xFF; };\\nCPU.prototype.pull = function () { this.sp = (this.sp + 1) & 0xFF; return this.mem[0x100 | this.sp]; };\\nCPU.prototype.nz = function (v) { this.z = v === 0 ? 1 : 0; this.n = (v >> 7) & 1; return v; };\\n\\nCPU.prototype.adc = function (v) {\\n  var a = this.a, c = this.c, t;\\n  if (!this.d) {\\n    t = a + v + c;\\n    this.v = ((~(a ^ v) & (a ^ t)) & 0x80) ? 1 : 0;\\n    this.c = t > 0xFF ? 1 : 0;\\n    this.a = this.nz(t & 0xFF);\\n    return;\\n  }\\n  var lo = (a & 0x0F) + (v & 0x0F) + c;\\n  if (lo > 9) lo += 6;\\n  var hi = (a >> 4) + (v >> 4) + (lo > 0x0F ? 1 : 0);\\n  this.z = ((a + v + c) & 0xFF) === 0 ? 1 : 0;\\n  this.n = (hi & 8) ? 1 : 0;\\n  this.v = ((~(a ^ v) & (a ^ (hi << 4))) & 0x80) ? 1 : 0;\\n  if (hi > 9) hi += 6;\\n  this.c = hi > 0x0F ? 1 : 0;\\n  this.a = ((hi << 4) | (lo & 0x0F)) & 0xFF;\\n};\\nCPU.prototype.sbc = function (v) {\\n  if (!this.d) { this.adc(v ^ 0xFF); return; }\\n  var a = this.a, b = 1 - this.c, t = a - v - b;\\n  this.v = (((a ^ v) & (a ^ t)) & 0x80) ? 1 : 0;\\n  this.nz(t & 0xFF);\\n  var lo = (a & 0x0F) - (v & 0x0F) - b, hi = (a >> 4) - (v >> 4);\\n  if (lo < 0) { lo -= 6; hi--; }\\n  if (hi < 0) hi -= 6;\\n  this.c = t >= 0 ? 1 : 0;\\n  this.a = ((hi << 4) | (lo & 0x0F)) & 0xFF;\\n};\\nCPU.prototype.cmp = function (r, v) { var t = r - v; this.c = t >= 0 ? 1 : 0; this.nz(t & 0xFF); };\\n\\n\/\/ esegue un'istruzione, restituisce i cicli\\nCPU.prototype.step = function () {\\n  var m = this.mem, pc = this.pc, e = OPS[m[ pc]];\\n  if (!e) { this.halted = true; return 0; }\\n  pc = (pc + 1) & 0xFFFF;\\n  var ad = 0, b, z, cyc = e.c;\\n  switch (e.m) {\\n    case 'imm': ad = pc; pc++; break;\\n    case 'zp': ad = m[ pc]; pc++; break;\\n    case 'zpx': ad = (m[ pc] + this.x) & 0xFF; pc++; break;\\n    case 'zpy': ad = (m[ pc] + this.y) & 0xFF; pc++; break;\\n    case 'abs': ad = m[ pc] | (m[(pc + 1) & 0xFFFF] << 8); pc += 2; break;\\n    case 'abx': b = m[ pc] | (m[(pc + 1) & 0xFFFF] << 8); ad = (b + this.x) & 0xFFFF; if (e.p && ((b ^ ad) & 0xFF00)) cyc++; pc += 2; break;\\n    case 'aby': b = m[ pc] | (m[(pc + 1) & 0xFFFF] << 8); ad = (b + this.y) & 0xFFFF; if (e.p && ((b ^ ad) & 0xFF00)) cyc++; pc += 2; break;\\n    case 'ind': b = m[ pc] | (m[(pc + 1) & 0xFFFF] << 8); ad = m[ b] | (m[(b & 0xFF00) | ((b + 1) & 0xFF)] << 8); pc += 2; break;\\n    case 'izx': z = (m[ pc] + this.x) & 0xFF; ad = m[ z] | (m[(z + 1) & 0xFF] << 8); pc++; break;\\n    case 'izy': z = m[ pc]; b = m[ z] | (m[(z + 1) & 0xFF] << 8); ad = (b + this.y) & 0xFFFF; if (e.p && ((b ^ ad) & 0xFF00)) cyc++; pc++; break;\\n    case 'rel': b = m[ pc]; pc++; ad = (pc + (b < 128 ? b : b - 256)) & 0xFFFF; break;\\n  }\\n  pc &= 0xFFFF;\\n  this.pc = pc;\\n  var v, t;\\n  switch (e.n) {\\n    case 'LDA': this.a = this.nz(this.rd(ad)); break;\\n    case 'LDX': this.x = this.nz(this.rd(ad)); break;\\n    case 'LDY': this.y = this.nz(this.rd(ad)); break;\\n    case 'STA': this.wr(ad, this.a); break;\\n    case 'STX': this.wr(ad, this.x); break;\\n    case 'STY': this.wr(ad, this.y); break;\\n    case 'ADC': this.adc(this.rd(ad)); break;\\n    case 'SBC': this.sbc(this.rd(ad)); break;\\n    case 'AND': this.a = this.nz(this.a & this.rd(ad)); break;\\n    case 'ORA': this.a = this.nz(this.a | this.rd(ad)); break;\\n    case 'EOR': this.a = this.nz(this.a ^ this.rd(ad)); break;\\n    case 'CMP': this.cmp(this.a, this.rd(ad)); break;\\n    case 'CPX': this.cmp(this.x, this.rd(ad)); break;\\n    case 'CPY': this.cmp(this.y, this.rd(ad)); break;\\n    case 'BIT': v = this.rd(ad); this.z = (this.a & v) ? 0 : 1; this.n = (v >> 7) & 1; this.v = (v >> 6) & 1; break;\\n    case 'ASL': case 'LSR': case 'ROL': case 'ROR':\\n      v = e.m === 'acc' ? this.a : this.rd(ad);\\n      if (e.n === 'ASL') { this.c = (v >> 7) & 1; v = (v << 1) & 0xFF; }\\n      else if (e.n === 'LSR') { this.c = v & 1; v >>= 1; }\\n      else if (e.n === 'ROL') { t = this.c; this.c = (v >> 7) & 1; v = ((v << 1) | t) & 0xFF; }\\n      else { t = this.c; this.c = v & 1; v = (v >> 1) | (t << 7); }\\n      this.nz(v);\\n      if (e.m === 'acc') this.a = v; else this.wr(ad, v);\\n      break;\\n    case 'INC': this.wr(ad, this.nz((this.rd(ad) + 1) & 0xFF)); break;\\n    case 'DEC': this.wr(ad, this.nz((this.rd(ad) - 1) & 0xFF)); break;\\n    case 'INX': this.x = this.nz((this.x + 1) & 0xFF); break;\\n    case 'INY': this.y = this.nz((this.y + 1) & 0xFF); break;\\n    case 'DEX': this.x = this.nz((this.x - 1) & 0xFF); break;\\n    case 'DEY': this.y = this.nz((this.y - 1) & 0xFF); break;\\n    case 'TAX': this.x = this.nz(this.a); break;\\n    case 'TAY': this.y = this.nz(this.a); break;\\n    case 'TXA': this.a = this.nz(this.x); break;\\n    case 'TYA': this.a = this.nz(this.y); break;\\n    case 'TSX': this.x = this.nz(this.sp); break;\\n    case 'TXS': this.sp = this.x; break;\\n    case 'PHA': this.push(this.a); break;\\n    case 'PHP': this.push(this.getP(true)); break;\\n    case 'PLA': this.a = this.nz(this.pull()); break;\\n    case 'PLP': this.setP(this.pull()); break;\\n    case 'CLC': this.c = 0; break;\\n    case 'SEC': this.c = 1; break;\\n    case 'CLI': this.i = 0; break;\\n    case 'SEI': this.i = 1; break;\\n    case 'CLD': this.d = 0; break;\\n    case 'SED': this.d = 1; break;\\n    case 'CLV': this.v = 0; break;\\n    case 'NOP': break;\\n    case 'JMP': this.pc = ad; break;\\n    case 'JSR': t = (pc - 1) & 0xFFFF; this.push(t >> 8); this.push(t); this.pc = ad; break;\\n    case 'RTS': t = this.pull(); t |= this.pull() << 8; this.pc = (t + 1) & 0xFFFF; break;\\n    case 'RTI': this.setP(this.pull()); t = this.pull(); t |= this.pull() << 8; this.pc = t; break;\\n    case 'BRK':\\n      t = (pc + 1) & 0xFFFF; this.push(t >> 8); this.push(t); this.push(this.getP(true)); this.i = 1;\\n      this.pc = m[0xFFFE] | (m[0xFFFF] << 8); break;\\n    default: \/\/ salti condizionati\\n      var take;\\n      switch (e.n) {\\n        case 'BCC': take = !this.c; break; case 'BCS': take = this.c; break;\\n        case 'BEQ': take = this.z; break; case 'BNE': take = !this.z; break;\\n        case 'BMI': take = this.n; break; case 'BPL': take = !this.n; break;\\n        case 'BVC': take = !this.v; break; case 'BVS': take = this.v; break;\\n      }\\n      if (take) { cyc++; if ((pc ^ ad) & 0xFF00) cyc++; this.pc = ad; }\\n  }\\n  this.cycles += cyc;\\n  return cyc;\\n};\\n\\nRF.CPU6502 = CPU;\\n})(globalThis.RFSid = globalThis.RFSid || {});\\n\\n\/* RetroFixer \\u00b7 SID Composer \\u00b7 la \\\"macchina\\\": 64 KB di RAM, CPU 6502 e SID\\n * Ogni frame (50 Hz PAL, 60 Hz NTSC) chiama la routine play del player 6502, proprio come l'interrupt\\n * raster sul C64. Le scritture nei registri del SID arrivano al chip nel ciclo esatto in cui la CPU le fa.\\n * Lo stesso codice gira nell'AudioWorklet (anteprima) e nella pagina (esportazione WAV, prove).\\n *\/\\n(function (RF) {\\n'use strict';\\n\\nvar CLOCKS = { PAL: { hz: 985248, frame: 19656 }, NTSC: { hz: 1022727, frame: 17095 } };\\nvar SENTINEL = 0xFFF0;\\nRF.CLOCKS = CLOCKS;\\n\\nfunction Engine(sampleRate) {\\n  var self = this;\\n  this.fs = sampleRate;\\n  this.mem = new Uint8Array(65536);\\n  this.cpu = new RF.CPU6502(this.mem);\\n  this.sid = new RF.SID(sampleRate, 6581, CLOCKS.PAL.hz);\\n  this.video = 'PAL';\\n  this.frameCycles = CLOCKS.PAL.frame;\\n  this.sidPos = 0;\\n  this.syms = null;\\n  this.queue = [];\\n  this.fifo = new Float32Array(16384); this.fr = 0; this.fw = 0;\\n  this.frames = 0;\\n  this.writes = null;     \/\/ se \\u00e8 un array, ci finiscono tutte le scritture [frame, ciclo, registro, valore]\\n  this.error = '';\\n  this.cpu.ioWrite = function (a, v) {\\n    var c = self.cpu.cycles;\\n    if (c > self.sidPos) { self.sid.clock(c - self.sidPos); self.sidPos = c; }\\n    self.sid.write(a & 0x1F, v);\\n    if (self.writes) self.writes.push(self.frames, c, a & 0x1F, v);\\n  };\\n  this.cpu.ioRead = function (a) { return self.sid.read(a & 0x1F); };\\n}\\n\\nEngine.prototype.config = function (model, video) {\\n  if (model) this.sid.setModel(+model);\\n  if (video && CLOCKS[ video]) {\\n    this.video = video; this.frameCycles = CLOCKS[ video].frame; this.sid.setClock(CLOCKS[ video].hz);\\n  }\\n};\\n\\n\/\/ carica il binario del player+canzone. Con preserve tiene le variabili del player (modifiche durante la riproduzione)\\nEngine.prototype.load = function (bin, addr, syms, preserve) {\\n  var keep = null;\\n  if (preserve && this.syms && syms && this.syms.vars === syms.vars && this.syms.varsend === syms.varsend)\\n    keep = this.mem.slice(syms.vars, syms.varsend);\\n  this.mem.set(bin, addr);\\n  if (keep) this.mem.set(keep, syms.vars);\\n  this.syms = syms;\\n};\\n\\n\/\/ chiama una subroutine 6502 come farebbe JSR; si ferma all'RTS finale\\nEngine.prototype.call = function (addr, a, x, y) {\\n  var cpu = this.cpu, m = this.mem, limit = cpu.cycles + 40000;\\n  cpu.a = a & 0xFF; cpu.x = x & 0xFF; cpu.y = y & 0xFF; cpu.sp = 0xFF; cpu.d = 0;\\n  var r = SENTINEL - 1;\\n  m[0x1FF] = r >> 8; m[0x1FE] = r & 0xFF; cpu.sp = 0xFD;\\n  cpu.pc = addr; cpu.halted = false;\\n  while (cpu.pc !== SENTINEL) {\\n    cpu.step();\\n    if (cpu.halted || cpu.cycles > limit) { this.error = 'CPU ' + (cpu.halted ? 'opcode' : 'loop') + ' @' + cpu.pc.toString(16); break; }\\n  }\\n};\\n\\nEngine.prototype.command = function (c) { this.queue.push(c); };\\n\\nEngine.prototype.runFrame = function () {\\n  var s = this.syms, cpu = this.cpu;\\n  cpu.cycles = 0; this.sidPos = 0;\\n  while (this.queue.length) {\\n    var c = this.queue.shift();\\n    if (!s) continue;\\n    if (c.t === 'init') this.call(s.init, c.a, 0, 0);\\n    else if (c.t === 'jam') this.call(s.jam, c.note, c.voice, c.ins);\\n  }\\n  if (s) this.call(s.play, 0, 0, 0);\\n  var rest = this.frameCycles - this.sidPos;\\n  if (rest > 0) this.sid.clock(rest);\\n  this.sidPos = 0;\\n  this.frames++;\\n  var o = this.sid.out, n = this.sid.outN, f = this.fifo, L = f.length;\\n  for (var i = 0; i < n; i++) { f[this.fw] = o[ i]; this.fw = (this.fw + 1) % L; }\\n  this.sid.outN = 0;\\n};\\n\\nEngine.prototype.available = function () { return (this.fw - this.fr + this.fifo.length) % this.fifo.length; };\\n\\nEngine.prototype.render = function (out, n) {\\n  var f = this.fifo, L = f.length, done = 0;\\n  while (done < n) {\\n    var k = Math.min(n - done, 4096);\\n    while (this.available() < k) this.runFrame();\\n    for (var i = 0; i < k; i++) { out[done + i] = f[this.fr]; this.fr = (this.fr + 1) % L; }\\n    done += k;\\n  }\\n};\\n\\nEngine.prototype.status = function () {\\n  var s = this.syms, m = this.mem, sid = this.sid;\\n  return {\\n    t: 'st', frame: this.frames,\\n    playing: s ? m[s.playing] : 0, pos: s ? m[s.curpos] : 0, row: s ? m[s.currow] : 0, speed: s ? m[s.speed] : 0,\\n    regs: Array.prototype.slice.call(sid.regs, 0, 25),\\n    env: [sid.env[0], sid.env[1], sid.env[2]], err: this.error\\n  };\\n};\\n\\n\/\/ gestione dei messaggi (worklet o fallback ScriptProcessor)\\nEngine.prototype.message = function (d) {\\n  switch (d.t) {\\n    case 'load': this.load(d.bin, d.addr, d.syms, d.preserve); break;\\n    case 'init': case 'jam': this.command(d); break;\\n    case 'cfg': this.config(d.model, d.video); break;\\n    case 'mute': this.sid.mute = d.mask; break;\\n    case 'reset': this.queue = []; this.sid.reset(); break;\\n  }\\n};\\n\\nRF.Engine = Engine;\\n})(globalThis.RFSid = globalThis.RFSid || {});\\n\\n\/* RetroFixer \\u00b7 SID Composer \\u00b7 processore AudioWorklet (gira nel thread audio del browser) *\/\\n(function (RF) {\\n'use strict';\\nif (typeof AudioWorkletProcessor === 'undefined') return;\\nclass RFSidProcessor extends AudioWorkletProcessor {\\n  constructor() {\\n    super();\\n    this.e = new RF.Engine(sampleRate);\\n    this.last = -1;\\n    this.port.onmessage = (ev) => this.e.message(ev.data);\\n  }\\n  process(inputs, outputs) {\\n    var out = outputs[0], ch = out && out[0];\\n    if (!ch) return true;\\n    this.e.render(ch, ch.length);\\n    for (var i = 1; i < out.length; i++) out[ i].set(ch);\\n    if (this.e.frames !== this.last) { this.last = this.e.frames; this.port.postMessage(this.e.status()); }\\n    return true;\\n  }\\n}\\nregisterProcessor('rfsid-engine', RFSidProcessor);\\n})(globalThis.RFSid);\\n\";\n\/* RetroFixer \u00b7 SID Composer \u00b7 interfaccia della pagina *\/\n(function (RF) {\n'use strict';\nvar root = document.querySelector('.rfsid');\nif (!root) return;\nvar EN = \/^en\/i.test(document.documentElement.lang || '');\nvar L = EN ? 'en' : 'it', T = RF.TEXTS[ L];\nfunction tr(k, v) {\n  var s = T[ k] !== undefined ? T[ k] : (RF.TEXTS.it[ k] !== undefined ? RF.TEXTS.it[ k] : k);\n  if (v && typeof s === 'string') s = s.replace(\/\\{(\\w+)\\}\/g, function (m0, n) { return v[ n] !== undefined ? v[ n] : m0; });\n  return s;\n}\nfunction $(id) { return document.getElementById(id); }\nroot.querySelectorAll('[data-t]').forEach(function (e) { e.textContent = tr(e.dataset.t); });\nroot.querySelectorAll('[data-th]').forEach(function (e) { e.innerHTML = tr(e.dataset.th); });\nroot.querySelectorAll('[data-tt]').forEach(function (e) { e.title = tr(e.dataset.tt); });\nfunction hex(v, n) { return ('0000' + ((v >>> 0) & 0xFFFF).toString(16).toUpperCase()).slice(-(n || 2)); }\nfunction esc(s) { return String(s).replace(\/[&<>\"]\/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' }[ c]; }); }\nfunction clamp(v, a, b) { v = Math.round(+v || 0); return v < a ? a : v > b ? b : v; }\n\n\/\/ ---- stato ---------------------------------------------------------------------------------------\nvar song = null, curIns = 1, cur = { o: 0, r: 0, v: 0, f: 0 }, rec = false, octave = 3, step = 1;\nvar playMode = null, patRow = 0, status = null, clip = null, muteMask = 0, undoS = [], redoS = [], lastSnap = 0;\nvar grid = $('rfsidGrid'), olist = $('rfsidOrder');\n\nfunction emptyCell() { return [0, 0, 0, 0]; }\nfunction pat(p) {\n  var P = song.patterns[ p];\n  if (!P) P = song.patterns[ p] = [];\n  while (P.length < song.rows) P.push(emptyCell());\n  return P;\n}\nfunction normalize(s) {\n  var d = RF.newSong();\n  ['title', 'author', 'released'].forEach(function (k) { s[ k] = String(s[ k] || '').slice(0, 31); });\n  s.model = +s.model === 8580 ? 8580 : 6581; s.video = s.video === 'NTSC' ? 'NTSC' : 'PAL';\n  s.speed = clamp(s.speed || 6, 1, 127); s.rows = clamp(s.rows || 32, 1, 64); s.volume = clamp(s.volume === undefined ? 15 : s.volume, 0, 15);\n  if (!Array.isArray(s.order) || !s.order.length) s.order = [[0, 1, 2]];\n  s.order = s.order.slice(0, 255).map(function (r) { return [0, 1, 2].map(function (v) { return clamp(r && r[ v], 0, 255); }); });\n  s.loop = clamp(s.loop, 0, s.order.length - 1);\n  if (!s.patterns || typeof s.patterns !== 'object') s.patterns = {};\n  Object.keys(s.patterns).forEach(function (k) {\n    var P = s.patterns[ k];\n    if (!Array.isArray(P)) { delete s.patterns[ k]; return; }\n    s.patterns[ k] = P.map(function (c) { c = Array.isArray(c) ? c : []; return [c[0] === 254 ? 254 : clamp(c[0], 0, 95), clamp(c[1], 0, 255), clamp(c[2], 0, 15), clamp(c[3], 0, 255)]; });\n  });\n  if (!Array.isArray(s.instruments) || s.instruments.length < 2) s.instruments = d.instruments;\n  s.instruments = s.instruments.slice(0, 33).map(function (I, i) { return i === 0 || !I ? (i === 0 ? null : RF.newInstrument()) : RF.newInstrument(I); });\n  s.format = 'rfsid'; s.version = 1;\n  return s;\n}\n\n\/\/ ---- messaggi ------------------------------------------------------------------------------------\nvar msgT = 0;\nfunction msg(text, err) {\n  var m = $('rfsidMsg'); m.textContent = text; m.className = 'msg' + (err ? ' err' : '');\n  clearTimeout(msgT); if (!err) msgT = setTimeout(function () { m.textContent = ''; }, 6000);\n}\n\n\/\/ ---- audio ---------------------------------------------------------------------------------------\nvar ac = null, node = null, analyser = null, mainEngine = null, audioP = null;\nfunction post(m) {\n  if (node) node.port.postMessage(m);\n  else if (mainEngine) mainEngine.message(m);\n}\nfunction ensureAudio() {\n  if (audioP) { if (ac && ac.state === 'suspended') ac.resume(); return audioP; }\n  var AC = window.AudioContext || window.webkitAudioContext;\n  if (!AC) { $('rfsidAudio').textContent = tr('audioNo'); return (audioP = Promise.reject(new Error('no audio'))); }\n  ac = new AC({ latencyHint: 'interactive' });\n  analyser = ac.createAnalyser(); analyser.fftSize = 2048;\n  analyser.connect(ac.destination);\n  function ready() {\n    $('rfsidAudio').textContent = tr('audioOn'); $('rfsidAudio').className = 'audio on';\n    post({ t: 'cfg', model: song.model, video: song.video });\n    post({ t: 'mute', mask: muteMask });\n    rebuild(false);\n    post({ t: 'init', a: 0x80 });\n  }\n  function fallback() {\n    mainEngine = new RF.Engine(ac.sampleRate);\n    var sp = ac.createScriptProcessor(1024, 0, 1);\n    sp.onaudioprocess = function (e) {\n      var out = e.outputBuffer.getChannelData(0);\n      mainEngine.render(out, out.length);\n      status = mainEngine.status();\n    };\n    sp.connect(analyser);\n    ready();\n  }\n  audioP = new Promise(function (res) {\n    if (!ac.audioWorklet || !window.AudioWorkletNode) { fallback(); res(); return; }\n    var url = URL.createObjectURL(new Blob([RF.WORKLET_SRC], { type: 'application\/javascript' }));\n    ac.audioWorklet.addModule(url).then(function () {\n      node = new AudioWorkletNode(ac, 'rfsid-engine', { numberOfInputs: 0, numberOfOutputs: 1, outputChannelCount: [1] });\n      node.port.onmessage = function (e) { status = e.data; };\n      node.connect(analyser);\n      ready(); res();\n    }).catch(function () { fallback(); res(); });\n  });\n  if (ac.state === 'suspended') ac.resume();\n  return audioP;\n}\n\n\/\/ ---- costruzione del player -----------------------------------------------------------------------\nvar build = null, rebuildT = 0, saveT = 0;\nfunction rebuild(preserve) {\n  try {\n    build = RF.buildSong(song, { onlyRow: playMode === 'pattern' ? patRow : undefined, lang: L });\n    post({ t: 'load', bin: build.bin, addr: build.start, syms: build.syms, preserve: preserve !== false });\n    $('rfsidMsg').classList.contains('err') && msg('');\n    return true;\n  } catch (e) { msg(e.message, true); return false; }\n}\nfunction changed() {\n  clearTimeout(rebuildT); rebuildT = setTimeout(function () { if (audioP) rebuild(true); }, 60);\n  clearTimeout(saveT); saveT = setTimeout(function () { try { localStorage.setItem('rfsidSong', JSON.stringify(song)); } catch (e) {} }, 800);\n}\nfunction snap(key) {\n  var now = Date.now();\n  if (key && key === snap.key && now - lastSnap < 800) { lastSnap = now; return; }\n  snap.key = key; lastSnap = now;\n  undoS.push(JSON.stringify(song)); if (undoS.length > 200) undoS.shift();\n  redoS = [];\n}\nfunction undo(redo) {\n  var from = redo ? redoS : undoS, to = redo ? undoS : redoS;\n  if (!from.length) return;\n  to.push(JSON.stringify(song));\n  song = normalize(JSON.parse(from.pop())); snap.key = null;\n  afterSongChange(true);\n}\n\n\/\/ ---- riproduzione --------------------------------------------------------------------------------\nfunction play(mode, from) {\n  ensureAudio().then(function () {\n    playMode = mode; patRow = cur.o;\n    if (!rebuild(false)) { playMode = null; return; }\n    post({ t: 'init', a: mode === 'pattern' ? 0 : from });\n    status = null;\n  }).catch(function () {});\n}\nfunction stop() {\n  if (!audioP) return;\n  playMode = null;\n  rebuild(false);\n  post({ t: 'init', a: 0x80 });\n  clearPlayRow();\n}\n$('rfsidPlay').onclick = function () { play('song', 0); };\n$('rfsidPlayPos').onclick = function () { play('song', cur.o); };\n$('rfsidPlayPat').onclick = function () { play('pattern', 0); };\n$('rfsidStop').onclick = stop;\nfunction setRec(v) { rec = v; $('rfsidRec').setAttribute('aria-pressed', rec ? 'true' : 'false'); grid.classList.toggle('rec', rec); }\n$('rfsidRec').onclick = function () { setRec(!rec); grid.focus(); };\n\n\/\/ ---- campi della canzone --------------------------------------------------------------------------\nvar octSel = $('rfsidOct');\nfor (var o = 0; o <= 6; o++) octSel.add(new Option(o, o));\noctSel.onchange = function () { octave = +octSel.value; renderPiano(); };\n$('rfsidStep').onchange = function () { step = clamp(this.value, 0, 16); this.value = step; };\nfunction bindSong(id, key, num, lo, hi, cb) {\n  var el = $(id);\n  el.addEventListener(num ? 'change' : 'input', function () {\n    snap('song.' + key);\n    song[ key] = num ? clamp(el.value, lo, hi) : el.value;\n    if (num) el.value = song[ key];\n    if (cb) cb();\n    changed();\n  });\n}\nbindSong('rfsidTitle', 'title'); bindSong('rfsidAuthor', 'author'); bindSong('rfsidReleased', 'released');\nbindSong('rfsidSpeed', 'speed', true, 1, 127); bindSong('rfsidVol', 'volume', true, 0, 15);\nbindSong('rfsidRows', 'rows', true, 1, 64, function () { Object.keys(song.patterns).forEach(pat); cur.r = Math.min(cur.r, song.rows - 1); renderGrid(); });\n$('rfsidModel').onchange = function () { snap(); song.model = +this.value; post({ t: 'cfg', model: song.model }); changed(); };\n$('rfsidVideo').onchange = function () { snap(); song.video = this.value; post({ t: 'cfg', video: song.video }); changed(); };\nfunction fillSongFields() {\n  $('rfsidTitle').value = song.title; $('rfsidAuthor').value = song.author; $('rfsidReleased').value = song.released;\n  $('rfsidSpeed').value = song.speed; $('rfsidRows').value = song.rows; $('rfsidVol').value = song.volume;\n  $('rfsidModel').value = String(song.model); $('rfsidVideo').value = song.video;\n}\n\n\/\/ ---- ordine --------------------------------------------------------------------------------------\nvar oDigits = '';\nfunction renderOrder() {\n  var h = '', pp = status && status.playing && playMode === 'song' ? status.pos : -1;\n  song.order.forEach(function (r, i) {\n    h += '<div class=\"orow' + (i === cur.o ? ' sel' : '') + (i === pp ? ' play' : '') + '\" data-o=\"' + i + '\"><span class=\"lp\">' + (i === song.loop ? 'L' : '') +\n      '<\/span><span class=\"on\">' + hex(i) + '<\/span>' + r.map(function (p, v) {\n        return '<span class=\"oc' + (i === cur.o && v === cur.v ? ' cur' : '') + '\" data-v=\"' + v + '\">' + hex(p) + '<\/span>';\n      }).join('') + '<\/div>';\n  });\n  olist.innerHTML = h;\n  var sel = olist.querySelector('.sel');\n  if (sel) { var t = sel.offsetTop, b = t + sel.offsetHeight; if (t < olist.scrollTop) olist.scrollTop = t; else if (b > olist.scrollTop + olist.clientHeight) olist.scrollTop = b - olist.clientHeight; }\n}\nolist.tabIndex = 0;\nolist.addEventListener('mousedown', function (e) {\n  var r = e.target.closest('.orow'); if (!r) return;\n  cur.o = +r.dataset.o; var c = e.target.closest('.oc'); if (c) cur.v = +c.dataset.v;\n  oDigits = ''; renderAll();\n});\nolist.addEventListener('keydown', function (e) {\n  var k = e.key, d = \/^[0-9a-f]$\/i.test(k) ? parseInt(k, 16) : -1;\n  if (k === 'ArrowUp') { cur.o = Math.max(0, cur.o - 1); }\n  else if (k === 'ArrowDown') { cur.o = Math.min(song.order.length - 1, cur.o + 1); }\n  else if (k === 'ArrowLeft') { cur.v = (cur.v + 2) % 3; }\n  else if (k === 'ArrowRight') { cur.v = (cur.v + 1) % 3; }\n  else if (k === '+' || k === '-') { setCellPat(song.order[cur.o][cur.v] + (k === '+' ? 1 : -1)); }\n  else if (d >= 0 && !e.ctrlKey && !e.metaKey) { oDigits = (oDigits + k).slice(-2); setCellPat(parseInt(oDigits, 16)); if (oDigits.length === 2) oDigits = ''; }\n  else if (k === 'Enter') { grid.focus(); }\n  else return;\n  e.preventDefault(); if (d < 0) oDigits = ''; renderAll();\n});\nfunction setCellPat(p) { snap('cell' + cur.o + cur.v); song.order[cur.o][cur.v] = clamp(p, 0, 255); pat(song.order[cur.o][cur.v]); changed(); }\nfunction freePattern() { for (var p = 0; p < 256; p++) { var used = song.order.some(function (r) { return r.indexOf(p) >= 0; }); if (!used && !(song.patterns[ p] && song.patterns[ p].some(function (c) { return c[0] || c[1] || c[2] || c[3]; }))) return p; } return 255; }\nfunction orderOp(fn) { snap(); fn(); song.loop = clamp(song.loop, 0, song.order.length - 1); cur.o = clamp(cur.o, 0, song.order.length - 1); changed(); renderAll(); }\n$('rfsidOAdd').onclick = function () { orderOp(function () { song.order.splice(cur.o + 1, 0, [freePattern(), 0, 0].map(function (p, i) { if (i) p = song.order[cur.o][ i]; return p; })); cur.o++; pat(song.order[cur.o][0]); }); };\n$('rfsidODup').onclick = function () { orderOp(function () { song.order.splice(cur.o + 1, 0, song.order[cur.o].slice()); cur.o++; }); };\n$('rfsidOClone').onclick = function () {\n  orderOp(function () {\n    var r = song.order[cur.o].map(function (p) { var n = freePattern(); song.patterns[ n] = pat(p).map(function (c) { return c.slice(); }); return n; });\n    song.order.splice(cur.o + 1, 0, r); cur.o++;\n  });\n};\n$('rfsidODel').onclick = function () { if (song.order.length < 2) { msg(tr('msgDelLast'), true); return; } orderOp(function () { song.order.splice(cur.o, 1); if (song.loop > cur.o) song.loop--; }); };\n$('rfsidOUp').onclick = function () { if (cur.o > 0) orderOp(function () { var t = song.order[cur.o]; song.order[cur.o] = song.order[cur.o - 1]; song.order[cur.o - 1] = t; cur.o--; }); };\n$('rfsidODown').onclick = function () { if (cur.o < song.order.length - 1) orderOp(function () { var t = song.order[cur.o]; song.order[cur.o] = song.order[cur.o + 1]; song.order[cur.o + 1] = t; cur.o++; }); };\n$('rfsidOLoop').onclick = function () { orderOp(function () { song.loop = cur.o; }); };\n$('rfsidPPrev').onclick = function () { setCellPat(song.order[cur.o][cur.v] - 1); renderAll(); };\n$('rfsidPNext').onclick = function () { setCellPat(song.order[cur.o][cur.v] + 1); renderAll(); };\n$('rfsidPFree').onclick = function () { setCellPat(freePattern()); renderAll(); };\n\n\/\/ ---- intestazione delle voci ----------------------------------------------------------------------\nfunction renderHead() {\n  var h = '<div><\/div>';\n  for (var v = 0; v < 3; v++) {\n    h += '<div class=\"vh' + ((muteMask >> v) & 1 ? ' muted' : '') + '\" data-v=\"' + v + '\">' +\n      '<button type=\"button\" class=\"btn xs vn\" data-a=\"mute\" title=\"' + tr('mute') + '\">V' + (v + 1) + '<\/button>' +\n      '<span class=\"pn\">' + tr('pat') + ' ' + hex(song.order[cur.o][ v]) + '<\/span>' +\n      '<button type=\"button\" class=\"btn xs\" data-a=\"tp\" data-n=\"-12\" title=\"' + tr('ttTp') + '\">' + tr('tpOctDn') + '<\/button>' +\n      '<button type=\"button\" class=\"btn xs\" data-a=\"tp\" data-n=\"-1\" title=\"' + tr('ttTp') + '\">' + tr('tpDn') + '<\/button>' +\n      '<button type=\"button\" class=\"btn xs\" data-a=\"tp\" data-n=\"1\" title=\"' + tr('ttTp') + '\">' + tr('tpUp') + '<\/button>' +\n      '<button type=\"button\" class=\"btn xs\" data-a=\"tp\" data-n=\"12\" title=\"' + tr('ttTp') + '\">' + tr('tpOctUp') + '<\/button>' +\n      '<button type=\"button\" class=\"btn xs\" data-a=\"copy\" title=\"' + tr('ttCopy') + '\">&#10697;<\/button>' +\n      '<button type=\"button\" class=\"btn xs\" data-a=\"paste\" title=\"' + tr('ttPaste') + '\">&#128203;<\/button>' +\n      '<button type=\"button\" class=\"btn xs\" data-a=\"clear\" title=\"' + tr('ttClear') + '\">&#10005;<\/button>' +\n      '<span class=\"env\"><i id=\"rfsidEnv' + v + '\"><\/i><\/span><\/div>';\n  }\n  $('rfsidPHead').innerHTML = h;\n}\n$('rfsidPHead').addEventListener('click', function (e) {\n  var b = e.target.closest('button'), vh = e.target.closest('.vh'); if (!b || !vh) return;\n  var v = +vh.dataset.v, P = pat(song.order[cur.o][ v]), a = b.dataset.a;\n  if (a === 'mute') { muteMask ^= 1 << v; post({ t: 'mute', mask: muteMask }); renderHead(); renderGrid(); return; }\n  if (a === 'copy') { clip = P.map(function (c) { return c.slice(); }); return; }\n  snap();\n  if (a === 'tp') { var n = +b.dataset.n; P.forEach(function (c) { if (c[0] >= 1 && c[0] <= 95) c[0] = clamp(c[0] + n, 1, 95); }); }\n  if (a === 'paste' && clip) { for (var r = 0; r < song.rows; r++) P[ r] = (clip[ r] || emptyCell()).slice(); }\n  if (a === 'clear') { for (r = 0; r < P.length; r++) P[ r] = emptyCell(); }\n  changed(); renderGrid();\n});\n\n\/\/ ---- griglia del pattern ----------------------------------------------------------------------------\nvar playRowEl = null;\nfunction cellHTML(c, v, r) {\n  var k = function (f) { return cur.v === v && cur.r === r && cur.f === f ? ' k' : ''; };\n  var nt = c[0] === 254 ? '<span class=\"nt off' + k(0) + '\" data-f=\"0\">===<\/span>' : c[0] ? '<span class=\"nt' + k(0) + '\" data-f=\"0\">' + RF.noteName(c[0]) + '<\/span>' : '<span class=\"e' + k(0) + '\" data-f=\"0\">...<\/span>';\n  var ins = c[1] ? hex(c[1]) : '..', cls = c[1] ? 'in' : 'e';\n  var hasCmd = c[2] || c[3], cm = hasCmd ? hex(c[2], 1) + hex(c[3]) : '...', cc = hasCmd ? 'cm' : 'e';\n  return nt + ' <span class=\"' + cls + k(1) + '\" data-f=\"1\">' + ins[0] + '<\/span><span class=\"' + cls + k(2) + '\" data-f=\"2\">' + ins[1] + '<\/span> ' +\n    '<span class=\"' + cc + k(3) + '\" data-f=\"3\">' + cm[0] + '<\/span><span class=\"' + cc + k(4) + '\" data-f=\"4\">' + cm[1] + '<\/span><span class=\"' + cc + k(5) + '\" data-f=\"5\">' + cm[2] + '<\/span>';\n}\nfunction renderGrid() {\n  var pats = song.order[cur.o].map(pat), h = '';\n  for (var r = 0; r < song.rows; r++) {\n    h += '<div class=\"pr' + (r % 16 === 0 ? ' pbar' : r % 4 === 0 ? ' pbeat' : '') + (r === cur.r ? ' cur' : '') + '\" data-r=\"' + r + '\"><span class=\"rn\">' + hex(r) + '<\/span>';\n    for (var v = 0; v < 3; v++) h += '<div class=\"pc' + ((muteMask >> v) & 1 ? ' mut' : '') + '\" data-v=\"' + v + '\">' + cellHTML(pats[ v][ r], v, r) + '<\/div>';\n    h += '<\/div>';\n  }\n  grid.innerHTML = h; playRowEl = null;\n  scrollToRow(cur.r);\n}\nfunction scrollToRow(r) {\n  var el = grid.children[ r]; if (!el) return;\n  var t = el.offsetTop, b = t + el.offsetHeight, pad = el.offsetHeight * 3;\n  if (t - pad < grid.scrollTop) grid.scrollTop = Math.max(0, t - pad);\n  else if (b + pad > grid.scrollTop + grid.clientHeight) grid.scrollTop = b + pad - grid.clientHeight;\n}\nfunction clearPlayRow() { if (playRowEl) playRowEl.classList.remove('play'); playRowEl = null; }\ngrid.addEventListener('mousedown', function (e) {\n  var pr = e.target.closest('.pr'); if (!pr) return;\n  cur.r = +pr.dataset.r;\n  var pc = e.target.closest('.pc'); if (pc) cur.v = +pc.dataset.v;\n  var sp = e.target.closest('[data-f]'); cur.f = sp ? +sp.dataset.f : 0;\n  renderGrid(); grid.focus(); e.preventDefault();\n});\n\nvar NOTEKEYS = { KeyZ: 0, KeyS: 1, KeyX: 2, KeyD: 3, KeyC: 4, KeyV: 5, KeyG: 6, KeyB: 7, KeyH: 8, KeyN: 9, KeyJ: 10, KeyM: 11, Comma: 12, KeyL: 13, Period: 14,\n  KeyQ: 12, Digit2: 13, KeyW: 14, Digit3: 15, KeyE: 16, KeyR: 17, Digit5: 18, KeyT: 19, Digit6: 20, KeyY: 21, Digit7: 22, KeyU: 23, KeyI: 24, Digit9: 25, KeyO: 26, Digit0: 27, KeyP: 28 };\nvar jamKey = null;\nfunction noteFromKey(code) { var o = NOTEKEYS[ code]; if (o === undefined) return 0; var n = octave * 12 + o + 1; return n >= 1 && n <= 95 ? n : 0; }\nfunction jam(n) { var v = cur.v, i = curIns; ensureAudio().then(function () { post({ t: 'jam', note: n - 1, voice: v, ins: i }); }).catch(function () {}); }\nfunction jamOff() { var v = cur.v; if (audioP) audioP.then(function () { post({ t: 'jam', note: 255, voice: v, ins: curIns }); }).catch(function () {}); }\nfunction moveRow(d) { cur.r = ((cur.r + d) % song.rows + song.rows) % song.rows; }\nfunction curCell() { return pat(song.order[cur.o][cur.v])[cur.r]; }\n\ngrid.addEventListener('keydown', function (e) {\n  var code = e.code, k = e.key;\n  if (e.ctrlKey || e.metaKey) {\n    if (k === 'z' || k === 'Z') { undo(e.shiftKey); e.preventDefault(); }\n    else if (k === 'y' || k === 'Y') { undo(true); e.preventDefault(); }\n    return;\n  }\n  if (e.altKey) return;\n  var handled = true, redraw = true;\n  if (code === 'Space') { if (playMode) stop(); else play('song', cur.o); redraw = false; }\n  else if (code === 'Escape') { setRec(!rec); redraw = false; }\n  else if (code === 'ArrowUp') moveRow(-1);\n  else if (code === 'ArrowDown') moveRow(1);\n  else if (code === 'PageUp') cur.r = Math.max(0, cur.r - 8);\n  else if (code === 'PageDown') cur.r = Math.min(song.rows - 1, cur.r + 8);\n  else if (code === 'Home') cur.r = 0;\n  else if (code === 'End') cur.r = song.rows - 1;\n  else if (code === 'ArrowLeft') { if (cur.f > 0) cur.f--; else { cur.v = (cur.v + 2) % 3; cur.f = 5; } }\n  else if (code === 'ArrowRight') { if (cur.f < 5) cur.f++; else { cur.v = (cur.v + 1) % 3; cur.f = 0; } }\n  else if (code === 'Tab') { cur.v = (cur.v + (e.shiftKey ? 2 : 1)) % 3; cur.f = 0; }\n  else if (code === 'NumpadAdd' || code === 'NumpadSubtract') { octave = clamp(octave + (code === 'NumpadAdd' ? 1 : -1), 0, 6); octSel.value = octave; renderPiano(); redraw = false; }\n  else if (rec && code === 'Delete') {\n    snap(); var c = curCell();\n    if (cur.f === 0) { c[0] = 0; c[1] = 0; } else if (cur.f <= 2) c[1] = 0; else { c[2] = 0; c[3] = 0; }\n    moveRow(step); changed();\n  }\n  else if (rec && code === 'Insert') { snap(); var P = pat(song.order[cur.o][cur.v]); P.splice(cur.r, 0, emptyCell()); P.length = song.rows; changed(); }\n  else if (rec && code === 'Backspace') {\n    if (cur.r > 0) { snap(); P = pat(song.order[cur.o][cur.v]); P.splice(cur.r - 1, 1); P.push(emptyCell()); cur.r--; changed(); }\n  }\n  else if (cur.f === 0 && rec && code === 'Digit1') { snap(); c = curCell(); c[0] = 254; c[1] = 0; moveRow(step); changed(); }\n  else if (cur.f > 0 && rec && \/^[0-9a-f]$\/i.test(k)) {\n    snap(); c = curCell(); var d = parseInt(k, 16);\n    if (cur.f === 1) { c[1] = (d << 4) | (c[1] & 15); cur.f = 2; }\n    else if (cur.f === 2) { c[1] = (c[1] & 0xF0) | d; cur.f = 1; moveRow(step); }\n    else if (cur.f === 3) { c[2] = d; cur.f = 4; }\n    else if (cur.f === 4) { c[3] = (d << 4) | (c[3] & 15); cur.f = 5; }\n    else { c[3] = (c[3] & 0xF0) | d; cur.f = 3; moveRow(step); }\n    if (c[1] > 32) c[1] = 32;\n    changed();\n  }\n  else if (NOTEKEYS[ code] !== undefined && (cur.f === 0 || !rec)) {\n    var n = noteFromKey(code);\n    if (!n) { e.preventDefault(); return; }\n    if (!e.repeat) { jam(n); jamKey = code; }\n    if (rec && !e.repeat) { snap(); c = curCell(); c[0] = n; c[1] = curIns; moveRow(step); changed(); }\n    else redraw = false;\n  }\n  else handled = false;\n  if (handled) { e.preventDefault(); if (redraw) renderGrid(); }\n});\ndocument.addEventListener('keyup', function (e) { if (jamKey && e.code === jamKey) { jamKey = null; jamOff(); } });\n\n\/\/ ---- strumenti -----------------------------------------------------------------------------------\nvar A_MS = [2, 8, 16, 24, 38, 56, 68, 80, 100, 250, 500, 800, 1000, 3000, 5000, 8000];\nvar D_MS = [6, 24, 48, 72, 114, 168, 204, 240, 300, 750, 1500, 2400, 3000, 9000, 15000, 24000];\nfunction ms(v) { return v >= 1000 ? (v \/ 1000).toFixed(v % 1000 ? 1 : 0) + ' s' : v + ' ms'; }\nvar insSel = $('rfsidInsSel');\nfunction ins() { return song.instruments[ curIns]; }\nfunction renderInsList() {\n  insSel.innerHTML = '';\n  for (var i = 1; i < song.instruments.length; i++) insSel.add(new Option(hex(i) + ' ' + (song.instruments[ i].name || ''), i));\n  insSel.value = curIns;\n}\ninsSel.onchange = function () { curIns = +insSel.value; renderInsEd(); };\n$('rfsidINew').onclick = function () {\n  if (song.instruments.length > 32) return;\n  snap(); song.instruments.push(RF.newInstrument({ name: (EN ? 'new ' : 'nuovo ') + hex(song.instruments.length) })); curIns = song.instruments.length - 1; changed(); renderIns();\n};\n$('rfsidIDup').onclick = function () {\n  if (song.instruments.length > 32) return;\n  snap(); var c = JSON.parse(JSON.stringify(ins())); c.name = (c.name + ' 2').slice(0, 24); song.instruments.push(RF.newInstrument(c)); curIns = song.instruments.length - 1; changed(); renderIns();\n};\n$('rfsidIDel').onclick = function () {\n  if (song.instruments.length < 3) return;\n  snap();\n  var del = curIns;\n  song.instruments.splice(del, 1);\n  Object.keys(song.patterns).forEach(function (k) { song.patterns[ k].forEach(function (c) { if (c[1] === del) c[1] = 0; else if (c[1] > del) c[1]--; }); });\n  curIns = Math.min(curIns, song.instruments.length - 1); changed(); renderIns(); renderGrid();\n};\n\nvar WBITS = [[0x10, 'TRI'], [0x20, 'SAW'], [0x40, 'PUL'], [0x80, 'NOI'], [0x02, 'SYN', 'x'], [0x04, 'RNG', 'x'], [0x08, 'TST', 'x']];\nfunction rng(key, label, val, min, max, show) {\n  return '<div class=\"irow\"><span>' + label + '<\/span><input type=\"range\" data-k=\"' + key + '\" min=\"' + min + '\" max=\"' + max + '\" value=\"' + val + '\"><span class=\"v\" data-v=\"' + key + '\">' + show + '<\/span><\/div>';\n}\nfunction num(key, label, val, min, max, show) {\n  return '<div class=\"irow\"><span>' + label + '<\/span><input type=\"number\" data-k=\"' + key + '\" min=\"' + min + '\" max=\"' + max + '\" value=\"' + val + '\"><span class=\"v\" data-v=\"' + key + '\">' + (show || '') + '<\/span><\/div>';\n}\nfunction chk(key, label, val) { return '<label class=\"ichk\"><input type=\"checkbox\" data-k=\"' + key + '\"' + (val ? ' checked' : '') + '> ' + label + '<\/label>'; }\nfunction noteTxt(s) { return s.abs ? RF.noteName(s.n + 1) : (s.n > 0 ? '+' : '') + s.n; }\nfunction show(key, I) {\n  var F = I.filter;\n  switch (key) {\n    case 'a': return ms(A_MS[I.a]); case 'd': return ms(D_MS[I.d]); case 's': return hex(I.s, 1) + ' (' + Math.round(I.s \/ 15 * 100) + '%)'; case 'r': return ms(D_MS[I.r]);\n    case 'pw': return '$' + hex(I.pw, 3) + ' ' + Math.round(I.pw \/ 40.96) + '%'; case 'pwMin': return '$' + hex(I.pwMin, 3); case 'pwMax': return '$' + hex(I.pwMax, 3);\n    case 'filter.cut': return Math.round(RF.cutoffHz(song.model, F.cut)) + ' Hz'; case 'filter.res': return hex(F.res, 1);\n    case 'vibDepth': return I.vibDepth ? I.vibDepth : 'off';\n  }\n  return '';\n}\nfunction renderInsEd() {\n  var I = ins(), box = $('rfsidInsEd');\n  if (!I) { box.innerHTML = '<p class=\"hint\">' + tr('iNone') + '<\/p>'; return; }\n  var F = I.filter, h = '';\n  h += '<input type=\"text\" class=\"iname\" data-k=\"name\" maxlength=\"24\" value=\"' + esc(I.name || '') + '\" aria-label=\"' + tr('iName') + '\">';\n  h += '<div class=\"isec\"><h4>' + tr('iEnv') + '<\/h4>' + rng('a', tr('iA'), I.a, 0, 15, show('a', I)) + rng('d', tr('iD'), I.d, 0, 15, show('d', I)) +\n    rng('s', tr('iS'), I.s, 0, 15, show('s', I)) + rng('r', tr('iR'), I.r, 0, 15, show('r', I)) + '<canvas class=\"envc\" width=\"340\" height=\"56\" id=\"rfsidEnvC\"><\/canvas>' +\n    chk('hr', tr('iHR'), I.hr !== false) + '<\/div>';\n  h += '<div class=\"isec\"><h4>' + tr('iWave') + '<\/h4><table class=\"wt\"><thead><tr><th>#<\/th><th><\/th><th>$<\/th><th>' + tr('iStep') + '<\/th><th>' + tr('iDur') + '<\/th><th><\/th><\/tr><\/thead><tbody>';\n  I.wave.forEach(function (s, i) {\n    h += '<tr data-i=\"' + i + '\"' + (I.loop === i ? ' class=\"lp\"' : '') + '><td>' + (i + 1) + '<\/td><td>' +\n      WBITS.map(function (b) { return '<span class=\"wb' + (b[2] ? ' x' : '') + (s.w & b[0] ? ' on' : '') + '\" data-bit=\"' + b[0] + '\">' + b[1] + '<\/span>'; }).join('') +\n      '<\/td><td class=\"hx\">' + (s.w ? hex(s.w) : '==') + '<\/td><td class=\"nw\"><select data-w=\"abs\"><option value=\"0\"' + (!s.abs ? ' selected' : '') + '>' + tr('iNoteRel') + '<\/option><option value=\"1\"' + (s.abs ? ' selected' : '') + '>' + tr('iNoteAbs') + '<\/option><\/select> ' +\n      '<input type=\"text\" data-w=\"n\" value=\"' + noteTxt(s) + '\"><\/td><td><input type=\"number\" data-w=\"d\" min=\"1\" max=\"255\" value=\"' + s.d + '\"><\/td><td><button type=\"button\" class=\"del\" data-w=\"del\">&#10005;<\/button><\/td><\/tr>';\n  });\n  h += '<\/tbody><\/table><div class=\"otools\"><button type=\"button\" class=\"btn xs\" id=\"rfsidWAdd\">' + tr('iAddStep') + '<\/button> <span class=\"lbl\">' + tr('iLoop') + '<\/span> <select id=\"rfsidWLoop\"><option value=\"-1\">' + tr('iLoopStop') + '<\/option>' +\n    I.wave.map(function (s, i) { return '<option value=\"' + i + '\"' + (I.loop === i ? ' selected' : '') + '>' + tr('iLoopTo') + ' ' + (i + 1) + '<\/option>'; }).join('') + '<\/select><\/div>' +\n    '<p class=\"hint\">' + tr('iWaveHint') + '<\/p><\/div>';\n  h += '<div class=\"isec\"><h4>' + tr('iPulse') + '<\/h4>' + rng('pw', tr('iPw'), I.pw, 0, 4095, show('pw', I)) + num('pwSpd', tr('iPwSpd'), I.pwSpd, -127, 127) +\n    rng('pwMin', tr('iPwMin'), I.pwMin, 0, 4095, show('pwMin', I)) + rng('pwMax', tr('iPwMax'), I.pwMax, 0, 4095, show('pwMax', I)) + chk('pwKeep', tr('iPwKeep'), I.pwKeep) + '<\/div>';\n  h += '<div class=\"isec\"><h4>' + tr('iFilter') + '<\/h4>' + chk('filter.on', tr('iFOn'), F.on) + chk('filter.set', tr('iFSet'), F.set) +\n    '<div class=\"irow\"><span>' + tr('iFMode') + '<\/span><span class=\"modes\">' + [[1, 'LP'], [2, 'BP'], [4, 'HP']].map(function (m) {\n      return '<label class=\"ichk\"><input type=\"checkbox\" data-mode=\"' + m[0] + '\"' + (F.mode & m[0] ? ' checked' : '') + '>' + m[1] + '<\/label>'; }).join('') + '<\/span><span><\/span><\/div>' +\n    rng('filter.res', tr('iFRes'), F.res, 0, 15, show('filter.res', I)) + rng('filter.cut', tr('iFCut'), F.cut, 0, 2047, show('filter.cut', I)) + num('filter.spd', tr('iFSpd'), F.spd, -127, 127) + '<\/div>';\n  h += '<div class=\"isec\"><h4>' + tr('iVib') + '<\/h4>' + num('vibDelay', tr('iVDel'), I.vibDelay, 0, 255) + rng('vibSpeed', tr('iVSpd'), I.vibSpeed, 1, 15, I.vibSpeed) +\n    rng('vibDepth', tr('iVDep'), I.vibDepth, 0, 8, show('vibDepth', I)) + '<\/div>';\n  box.innerHTML = h;\n  drawEnv();\n}\nfunction setKey(I, key, val) { var p = key.split('.'); if (p.length === 2) I[p[0]][p[1]] = val; else I[ key] = val; }\nfunction getKey(I, key) { var p = key.split('.'); return p.length === 2 ? I[p[0]][p[1]] : I[ key]; }\nvar LIM = { a: [0, 15], d: [0, 15], s: [0, 15], r: [0, 15], pw: [0, 4095], pwSpd: [-127, 127], pwMin: [0, 4095], pwMax: [0, 4095], 'filter.res': [0, 15], 'filter.cut': [0, 2047],\n  'filter.spd': [-127, 127], vibDelay: [0, 255], vibSpeed: [1, 15], vibDepth: [0, 8] };\n$('rfsidInsEd').addEventListener('input', function (e) {\n  var t = e.target, I = ins(), key = t.dataset.k; if (!I) return;\n  if (key) {\n    snap('ins' + curIns + key);\n    if (t.type === 'checkbox') setKey(I, key, t.checked);\n    else if (key === 'name') { I.name = t.value; renderInsList(); }\n    else { if (t.type === 'number' && (t.value === '' || t.value === '-')) return; setKey(I, key, clamp(t.value, LIM[ key][0], LIM[ key][1])); }\n    var lab = $('rfsidInsEd').querySelector('[data-v=\"' + key + '\"]'); if (lab) lab.textContent = key === 'vibSpeed' ? I.vibSpeed : show(key, I);\n    if ('adsr'.indexOf(key) >= 0) drawEnv();\n    changed(); return;\n  }\n  if (t.dataset.mode) { snap(); var m = +t.dataset.mode; I.filter.mode = t.checked ? I.filter.mode | m : I.filter.mode & ~m; changed(); }\n});\n$('rfsidInsEd').addEventListener('change', function (e) {\n  var t = e.target, I = ins(), tr_ = t.closest('tr'); if (!I) return;\n  if (t.id === 'rfsidWLoop') { snap(); I.loop = +t.value; changed(); renderInsEd(); return; }\n  if (t.type === 'number' && t.dataset.k) { t.value = getKey(I, t.dataset.k); }\n  if (!tr_ || !t.dataset.w) return;\n  var s = I.wave[+tr_.dataset.i]; snap();\n  if (t.dataset.w === 'abs') { s.abs = t.value === '1'; s.n = s.abs ? 48 : 0; }\n  else if (t.dataset.w === 'n') {\n    var v = t.value.trim();\n    if (s.abs) { var nn = RF.parseNote(v.replace(\/^([a-g])(\\d)$\/i, '$1-$2')); if (nn) s.n = nn - 1; }\n    else { var x = parseInt(v, 10); if (!isNaN(x)) s.n = clamp(x, -64, 63); }\n  }\n  else if (t.dataset.w === 'd') s.d = clamp(t.value, 1, 255);\n  changed(); renderInsEd();\n});\n$('rfsidInsEd').addEventListener('click', function (e) {\n  var t = e.target, I = ins(); if (!I) return;\n  if (t.id === 'rfsidWAdd') { if (I.wave.length >= 32) return; snap(); var last = I.wave[I.wave.length - 1]; I.wave.push(last ? { w: 0, n: last.abs ? last.n : 0, d: 1, abs: last.abs } : { w: 0x41, n: 0, d: 1, abs: false }); changed(); renderInsEd(); return; }\n  var tr_ = t.closest('tr'); if (!tr_) return;\n  var i = +tr_.dataset.i, s = I.wave[ i];\n  if (t.dataset.bit) { snap(); s.w ^= +t.dataset.bit; changed(); renderInsEd(); }\n  else if (t.dataset.w === 'del') {\n    if (I.wave.length < 2) return;\n    snap(); I.wave.splice(i, 1); if (I.loop >= I.wave.length) I.loop = I.wave.length - 1; else if (I.loop > i) I.loop--; changed(); renderInsEd();\n  }\n});\nfunction drawEnv() {\n  var c = $('rfsidEnvC'), I = ins(); if (!c || !I) return;\n  var g = c.getContext('2d'), W = c.width, H = c.height, a = Math.log(A_MS[I.a] + 2), d = Math.log(D_MS[I.d] + 2), r = Math.log(D_MS[I.r] + 2), hold = 2.2;\n  var tot = a + d + hold + r, x = function (t) { return 4 + t \/ tot * (W - 8); }, y = function (l) { return H - 4 - l * (H - 8); }, sl = I.s \/ 15;\n  g.clearRect(0, 0, W, H);\n  g.strokeStyle = '#342c74'; g.beginPath(); g.moveTo(x(a + d + hold), 0); g.lineTo(x(a + d + hold), H); g.stroke();\n  g.strokeStyle = '#ffd23f'; g.lineWidth = 2; g.beginPath(); g.moveTo(x(0), y(0)); g.lineTo(x(a), y(1));\n  for (var i = 0; i <= 20; i++) g.lineTo(x(a + d * i \/ 20), y(sl + (1 - sl) * Math.pow(1 - i \/ 20, 3)));\n  g.lineTo(x(a + d + hold), y(sl));\n  for (i = 0; i <= 20; i++) g.lineTo(x(a + d + hold + r * i \/ 20), y(sl * Math.pow(1 - i \/ 20, 3)));\n  g.stroke();\n  g.fillStyle = '#aaa5d8'; g.font = '11px Inter, sans-serif'; g.fillText('gate off', x(a + d + hold) + 4, 12);\n}\nfunction renderIns() { renderInsList(); renderInsEd(); }\n\n\/\/ ---- tastiera a schermo --------------------------------------------------------------------------\nvar piano = $('rfsidPiano'), pianoDown = null;\nfunction renderPiano() {\n  var h = '', whites = 0, blacks = [], base = octave * 12;\n  for (var n = 0; n <= 24; n++) {\n    var pc = n % 12, isB = [1, 3, 6, 8, 10].indexOf(pc) >= 0;\n    if (isB) blacks.push([n, whites]);\n    else { h += '<div class=\"w\" data-n=\"' + (base + n + 1) + '\">' + (pc === 0 ? 'C' + (octave + n \/ 12) : '') + '<\/div>'; whites++; }\n  }\n  blacks.forEach(function (b) { h += '<div class=\"b\" data-n=\"' + (base + b[0] + 1) + '\" style=\"left:calc(' + (b[1] \/ whites * 100) + '% - ' + (100 \/ whites * 0.3) + '%);width:' + (100 \/ whites * 0.6) + '%\"><\/div>'; });\n  piano.innerHTML = h;\n}\npiano.addEventListener('pointerdown', function (e) {\n  var k = e.target.closest('[data-n]'); if (!k) return;\n  var n = +k.dataset.n; if (n < 1 || n > 95) return;\n  pianoDown = k; k.classList.add('dn'); jam(n); e.preventDefault();\n  if (rec) { snap(); var c = curCell(); c[0] = n; c[1] = curIns; moveRow(step); changed(); renderGrid(); }\n});\nfunction pianoUp() { if (pianoDown) { pianoDown.classList.remove('dn'); pianoDown = null; jamOff(); } }\ndocument.addEventListener('pointerup', pianoUp); piano.addEventListener('pointerleave', pianoUp);\n\n\/\/ ---- monitor dei registri -------------------------------------------------------------------------\nvar regEls = [];\n(function () {\n  var h = '', names = tr('regNames'), gl = tr('regGlob');\n  for (var v = 0; v < 3; v++) {\n    h += '<div class=\"rg\"><h5>' + tr('voice') + ' ' + (v + 1) + ' $D4' + hex(v * 7) + '<\/h5>';\n    for (var i = 0; i < 7; i++) h += '<div>' + names[ i] + ' <b data-r=\"' + (v * 7 + i) + '\">00<\/b><\/div>';\n    h += '<\/div>';\n  }\n  h += '<div class=\"rg\"><h5>Filtro\/Vol $D415<\/h5>';\n  for (i = 0; i < 4; i++) h += '<div>' + gl[ i] + ' <b data-r=\"' + (21 + i) + '\">00<\/b><\/div>';\n  h += '<div>WAVE <b id=\"rfsidWName\">-<\/b><\/div><\/div>';\n  $('rfsidRegs').innerHTML = h;\n  $('rfsidRegs').querySelectorAll('b[data-r]').forEach(function (b) { regEls[+b.dataset.r] = b; });\n  if (EN) $('rfsidRegs').querySelectorAll('h5')[3].textContent = 'Filter\/Vol $D415';\n})();\nvar scope = $('rfsidScope'), sg = scope.getContext('2d'), sbuf = null;\nfunction drawScope() {\n  var W = scope.width, H = scope.height;\n  sg.fillStyle = '#0b0920'; sg.fillRect(0, 0, W, H);\n  sg.strokeStyle = '#241e55'; sg.beginPath(); sg.moveTo(0, H \/ 2); sg.lineTo(W, H \/ 2); sg.stroke();\n  if (!analyser) return;\n  if (!sbuf) sbuf = new Float32Array(analyser.fftSize);\n  analyser.getFloatTimeDomainData(sbuf);\n  var start = 0; for (var i = 1; i < sbuf.length \/ 2; i++) if (sbuf[i - 1] < 0 && sbuf[ i] >= 0) { start = i; break; }\n  sg.strokeStyle = '#3fd8ff'; sg.lineWidth = 1.5; sg.beginPath();\n  var n = Math.min(1024, sbuf.length - start);\n  for (i = 0; i < n; i++) { var x = i \/ n * W, y = H \/ 2 - sbuf[start + i] * H * 0.9; if (i) sg.lineTo(x, y); else sg.moveTo(x, y); }\n  sg.stroke();\n}\n\n\/\/ ---- file ----------------------------------------------------------------------------------------\nfunction download(name, data, type) {\n  var a = document.createElement('a'), url = URL.createObjectURL(new Blob([ data], { type: type || 'application\/octet-stream' }));\n  a.href = url; a.download = name; document.body.appendChild(a); a.click();\n  setTimeout(function () { URL.revokeObjectURL(url); a.remove(); }, 1000);\n}\nfunction baseName() { return (song.title || 'song').toLowerCase().normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').replace(\/[^a-z0-9]+\/g, '-').replace(\/^-|-$\/g, '').slice(0, 16) || 'song'; }\nfunction fullBuild() { return RF.buildSong(song, { lang: L }); }\n$('rfsidSave').onclick = function () { download(baseName() + '.rfsid.json', JSON.stringify(song, null, 1), 'application\/json'); msg(tr('msgSaved')); };\n$('rfsidOpen').onclick = function () { $('rfsidFile').click(); };\n$('rfsidFile').onchange = function () {\n  var f = this.files[0]; if (!f) return;\n  f.text().then(function (t) {\n    var s = JSON.parse(t);\n    if (!s || s.format !== 'rfsid') throw new Error('format');\n    snap(); song = normalize(s); afterSongChange(); msg(tr('msgLoaded'));\n  }).catch(function () { msg(tr('msgBadFile'), true); });\n  this.value = '';\n};\nfunction tryExport(fn) { try { fn(fullBuild()); } catch (e) { msg(e.message, true); } }\n$('rfsidPRG').onclick = function () { tryExport(function (b) { download(baseName() + '.prg', RF.makePRG(b)); msg(tr('msgPRG', { f: baseName().toUpperCase() })); }); };\n$('rfsidSID').onclick = function () { tryExport(function (b) { download(baseName() + '.sid', RF.makePSID(b, song)); msg(tr('msgSID')); }); };\n$('rfsidASM').onclick = function () { tryExport(function (b) { download(baseName() + '.asm', b.src, 'text\/plain'); msg(tr('msgBuilt', { n: b.end - 0x1000, end: hex(b.end - 1, 4) })); }); };\n$('rfsidTASS').onclick = function () { tryExport(function (b) { download(baseName() + '-64tass.asm', RF.toTass(b.src), 'text\/plain'); msg(tr('msgBuilt', { n: b.end - 0x1000, end: hex(b.end - 1, 4) })); }); };\n$('rfsidWAV').onclick = function () {\n  msg(tr('msgWAVwait'));\n  setTimeout(function () { tryExport(function (b) { var w = RF.renderWAV(song, b, 44100, 600); download(baseName() + '.wav', w.data, 'audio\/wav'); msg(tr('msgWAV', { s: w.seconds.toFixed(1) })); }); }, 30);\n};\n$('rfsidNew').onclick = function () { if (!confirm(tr('msgConfirmNew'))) return; snap(); song = normalize(RF.newSong()); afterSongChange(); };\nvar exSel = $('rfsidEx');\nexSel.add(new Option(tr('exChoose'), ''));\nRF.EXAMPLES.forEach(function (x) { exSel.add(new Option(x.name[ L], x.id)); });\nexSel.onchange = function () {\n  var x = RF.EXAMPLES.filter(function (q) { return q.id === exSel.value; })[0];\n  exSel.value = ''; if (!x) return;\n  if (!confirm(tr('msgConfirmEx'))) return;\n  snap(); loadExample(x);\n};\nfunction loadExample(x) {\n  song = normalize(x.make());\n  if (EN) song.instruments.forEach(function (I) { if (I && EN_NAMES[I.name]) I.name = EN_NAMES[I.name]; });\n  afterSongChange();\n}\nvar EN_NAMES = { 'Arpeggio minore': 'Minor arpeggio', 'Arpeggio maggiore': 'Major arpeggio', 'Basso': 'Bass', 'Cassa': 'Kick', 'Rullante': 'Snare', 'Basso acido': 'Acid bass',\n  'Campana ring': 'Ring bell', 'Charleston': 'Hi-hat', 'Impulso semplice': 'Plain pulse', 'Accordo in tabella': 'Wave table chord', 'Flauto con vibrato': 'Flute with vibrato',\n  'Basso filtrato': 'Filtered bass', 'Sync (voce 3)': 'Sync (voice 3)', 'Ring (voce 3)': 'Ring (voice 3)', 'Impulso stretto': 'Narrow pulse', 'Lead impulso': 'Pulse lead',\n  'Basso dente di sega': 'Sawtooth bass', 'Triangolo': 'Triangle' };\n$('rfsidUndo').onclick = function () { undo(false); };\n$('rfsidRedo').onclick = function () { undo(true); };\n\nfunction afterSongChange(keepCursor) {\n  if (!keepCursor) { cur = { o: 0, r: 0, v: 0, f: 0 }; curIns = 1; }\n  cur.o = Math.min(cur.o, song.order.length - 1); cur.r = Math.min(cur.r, song.rows - 1);\n  curIns = Math.min(Math.max(1, curIns), song.instruments.length - 1);\n  if (audioP) { post({ t: 'cfg', model: song.model, video: song.video }); if (!keepCursor) stop(); }\n  fillSongFields(); renderAll(); renderIns(); changed();\n}\nfunction renderAll() { renderOrder(); renderHead(); renderGrid(); }\n\n\/\/ ---- ciclo di aggiornamento -----------------------------------------------------------------------\nvar lastPos = -1, lastRow = -1, WN = { 1: 'TRI', 2: 'SAW', 4: 'PUL', 8: 'NOI' };\nfunction frame() {\n  var st = status;\n  if (st) {\n    var playing = st.playing && playMode;\n    $('rfsidPos').textContent = playing ? hex(playMode === 'pattern' ? patRow : st.pos) + ':' + hex(st.row) : '--:--';\n    for (var i = 0; i < 25; i++) if (regEls[ i]) regEls[ i].textContent = hex(st.regs[ i]);\n    var wn = []; for (var v = 0; v < 3; v++) { var c = st.regs[v * 7 + 4] >> 4; wn.push(c ? Object.keys(WN).filter(function (b) { return c & b; }).map(function (b) { return WN[ b]; }).join('+') : '-'); }\n    $('rfsidWName').textContent = wn.join(' ');\n    for (v = 0; v < 3; v++) { var eb = $('rfsidEnv' + v); if (eb) eb.style.width = (st.env[ v] \/ 2.55) + '%'; }\n    if (playing) {\n      var pos = playMode === 'pattern' ? patRow : st.pos;\n      if (playMode === 'song' && $('rfsidFollow').checked && pos !== cur.o && pos < song.order.length) { cur.o = pos; renderAll(); lastRow = -1; }\n      if (pos !== lastPos) { lastPos = pos; renderOrder(); }\n      if (pos === cur.o && st.row !== lastRow) {\n        lastRow = st.row; clearPlayRow();\n        playRowEl = grid.children[st.row] || null;\n        if (playRowEl) { playRowEl.classList.add('play'); if ($('rfsidFollow').checked && document.activeElement !== grid) scrollToRow(st.row); }\n      }\n    } else if (lastPos !== -1 || playRowEl) { lastPos = -1; lastRow = -1; clearPlayRow(); renderOrder(); }\n  }\n  drawScope();\n  requestAnimationFrame(frame);\n}\n\n\/\/ ---- avvio ---------------------------------------------------------------------------------------\nvar saved = null; try { saved = JSON.parse(localStorage.getItem('rfsidSong') || 'null'); } catch (e) {}\nif (saved && saved.format === 'rfsid') { song = normalize(saved); afterSongChange(); msg(tr('msgRestored')); }\nelse loadExample(RF.EXAMPLES[0]);\noctSel.value = octave; renderPiano();\nrequestAnimationFrame(frame);\nRF.ui = { get song() { return song; }, build: function () { return build; }, status: function () { return status; } };\n})(globalThis.RFSid = globalThis.RFSid || {});\n<\/script>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Componi musica per il chip SID del Commodore 64 nel browser: tracker a 3 voci con strumenti, filtro ed emulazione 6581\/8580. Esporta .PRG per il C64 vero, .SID e sorgente assembler.<\/p>","protected":false},"author":1,"featured_media":11615,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"footnotes":""},"categories":[],"tags":[601,145,215,515,526,600],"class_list":["post-11614","page","type-page","status-publish","has-post-thumbnail","hentry","tag-chiptune","tag-commodore-64","tag-emulatore","tag-musica","tag-sid","tag-tracker"],"_links":{"self":[{"href":"https:\/\/retrofixer.it\/en\/wp-json\/wp\/v2\/pages\/11614","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/retrofixer.it\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/retrofixer.it\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/retrofixer.it\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/retrofixer.it\/en\/wp-json\/wp\/v2\/comments?post=11614"}],"version-history":[{"count":7,"href":"https:\/\/retrofixer.it\/en\/wp-json\/wp\/v2\/pages\/11614\/revisions"}],"predecessor-version":[{"id":11624,"href":"https:\/\/retrofixer.it\/en\/wp-json\/wp\/v2\/pages\/11614\/revisions\/11624"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/retrofixer.it\/en\/wp-json\/wp\/v2\/media\/11615"}],"wp:attachment":[{"href":"https:\/\/retrofixer.it\/en\/wp-json\/wp\/v2\/media?parent=11614"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/retrofixer.it\/en\/wp-json\/wp\/v2\/categories?post=11614"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/retrofixer.it\/en\/wp-json\/wp\/v2\/tags?post=11614"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}