123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
- <meta name="description" content="cassette, mp3, wav, audio">
- <meta name="author" content="bzt">
- <title>Cassette</title>
- <link href="logo.png" rel="shortcut icon">
- <link href="bootstrap.min.css" rel="stylesheet">
- <style>
- body { margin:10px; }
- input, textarea { font-family: monospace,fixed; }
- table, button, input[type="text"], input[type="file"] { width:100%; }
- input[type="number"] { text-align:right; width:100px;}
- small { opacity:50%; }
- small[id^="help"] { display:none; }
- a[download] { display:none; }
- #loading { position:absolute;top:0px;left:0px;width:100%;height:100%;z-index:1024;background:rgba(0,0,0,0.5);cursor:wait; }
- #jserr { color:white; background:red; font-weight:bold; font-size: 150%; text-align:center; padding: 32px; }
- .nav-tabs li {position: relative;bottom: -1px;cursor:pointer;}
- .nav-tabs li label { margin-bottom: 0px !important; cursor: inherit; }
- input[name="tab"], div[rel^="tab"] { display: none; }
- #tab0:checked ~ ul li[rel="tab0"], #tab1:checked ~ ul li[rel="tab1"] { border-color: #dee2e6 #dee2e6 #fff; cursor: default; }
- #tab0:checked ~ div[rel="tab0"], #tab1:checked ~ div[rel="tab1"] { display: block; }
- </style>
- </head>
- <body>
- <h1>Cassette</h1>
- <div id="jserr">Please enable JavaScript</div>
- <script>document.getElementById("jserr").style.display = "none";</script>
- <p>This is a quick'n'dirty tool that can convert digitalized retro computer cassettes (C64, Spectrum, Homelab etc.) into binary files and vice versa. Also available as a CLI tool, <a href="https://gitlab.com/bztsrc/cassette">Open Source</a>.</p>
- <input type="radio" name="tab" id="tab0" checked onchange="help()" value="0">
- <input type="radio" name="tab" id="tab1" onchange="help()" value="1">
- <ul class="nav nav-tabs">
- <li class="nav-link" rel="tab0" style="margin-left:20px">
- <label for="tab0">Audio to binary</label>
- </li>
- <li class="nav-link" rel="tab1">
- <label for="tab1">Binary to audio</label>
- </li>
- </ul>
- <div>
- <br><table>
- <tr><td width="10%">Input</td><td width="90%"><input type="file" id="input"></td></tr>
- <tr><td width="10%">Output</td><td width="90%"><input type="text" id="output" placeholder="filename"></td></tr>
- <tr><td width="10%">Presets</td><td width="90%"><select id="preset" onchange="loadpreset(this.value)"></select></td></tr>
- <tr><td width="10%">Data</td><td width="90%"><input type="checkbox" id="bit7"> <label for="bit7">7 bits per byte</label> <input type="checkbox" id="end"> <label for="end">little endian (least significant bit first)</label> <select id="fm" onchange="help()"><option value="0" selected>AM</option><option value="1">FM, down-edge</option><option value="2">FM, up-edge</option><option value="3">FM, both edges</option></select> <select id="sy" onchange="help()"><option value="0">no sync bit</option><option value="1">leading sync bit</option><option value="2">trailing sync bit</option><option value="3">two sync bits</option></select></td></tr>
- </table>
- </div>
- <div rel="tab0">
- <table>
- <tr><td width="10%">Sample</td><td width="90%"><input type="number" id="len1" min="0" max="4096" value="0" title="in μsec (millionth of a sec), 0 = autodetect"> bit's length <input type="number" id="gap1" min="0" max="4096" value="0" title="in μsec"> gap <input type="checkbox" id="signed"> <label for="signed">input is signed 8-bit PCM</label></td></tr>
- <tr><td width="10%"></td><td width="90%"><input type="number" id="skip" min="0" max="1073741824" value="0" title="in samples"> skip samples at beginning <input type="checkbox" id="hdr1"> <label for="hdr">don't detect Homelab header</label></td></tr>
- <tr><td colspan="2"><button class="btn btn-primary" onclick="dothething(0)">Convert</button></td></tr>
- </table>
- </div>
- <div rel="tab1">
- <table>
- <tr><td width="10%">Sample</td><td width="90%"><input type="number" id="len2" min="1" max="4096" value="200" title="in μsec"> bit's length <input type="number" id="gap2" min="0" max="4096" value="0" title="in μsec"> gap <input type="number" id="ol" min="-127" max="127" value="127" title="127 = highest, -127 = smallest"> one (bit set) level <input type="number" id="zl" min="-127" max="127" value="-64" title="127 = highest, -127 = smallest"> zero (bit clear) level</td></tr>
- <tr><td width="10%"></td><td width="90%"><select id="rate" title="in Hz"><option value="22050">22050</option><option value="32000">32000</option><option value="44100" selected>44100</option><option value="48000">48000</option></select> sample rate <input type="checkbox" id="hdr2"> <label for="hdr">generate Homelab header</label></td></tr>
- <tr><td width="10%">Format</td><td width="90%"><input type="checkbox" id="mp3"> <label for="mp3">MP3 (WAV if unset)</label> <select id="bitrate" title="MP3 compression"><option value="96">96</option><option value="128" selected>128</option><option value="192">192</option><option value="256">256</option></select> bit rate <input type="number" id="quality" min="0" max="9" value="0" title="MP3 compression"> quality (0 = best, 9 = worst)</td></tr>
- <tr><td colspan="2"><button class="btn btn-primary" onclick="dothething(1)">Convert</button></td></tr>
- </table>
- </div>
- <p><small><br><b>Help</b><br><img id="helpimg"><br></small>
- <small id="help00">Bit length is detected from the up-edges. If you set gap, that many samples will be skipped after each byte, aka after every 7 or 8 bits.</small>
- <small id="help01">Only samples considered where wave crosses the base line from above. If bit length isn't set, the average of distances is used as treshold.</small>
- <small id="help02">Only samples considered where wave crosses the base line from below. If bit length isn't set, the average of distances is used as treshold.</small>
- <small id="help03">Only samples considered where wave crosses the base line. If bit length isn't set, the average of distances is used as treshold.</small>
- <small id="help10">Bit's length samples will be generated with either one level or zero level. If you set gap, that many zero level samples will be added after each byte, aka after every 7 or 8 bits.</small>
- <small id="help11">Shifted phase sine wave will be generated for each bit, either bit's length (zero) or bit's length + gap (one) long. Sign of one level and zero level must be different.</small>
- <small id="help12">Sine wave will be generated for each bit, either bit's length (zero) or bit's length + gap (one) long. Sign of one level and zero level must be different.</small>
- <small id="help13">Altering half sine wave will be generated for each bit, either bit's length (zero) or bit's length + gap (one) long. Sign of one level and zero level must be different.</small>
- <small id="helps0">No sync bit, 0 = 0, 1 = 1</small>
- <small id="helps1">A sync bit prefixes all data bits, 10 = 0, 11 = 1</small>
- <small id="helps2">A sync bit follows all data bits, 01 = 0, 11 = 1</small>
- <small id="helps3">Data bit between two sync bits, 101 = 0, 111 = 1</small>
- <small>Verbose output of conversion details can be found on the JavaScript console.</small>
- </p>
- <div id="loading" style="display:none"> </div>
- <script src="cassette.js"></script>
- <script>
- var presets = {
- "": {},
- "Commodore": { fm: 3, end: 0, bit7: 0, len1: 0, len2: 245, gap1: 0, gap2: 122, sy: 0, rate: 44100, ol: 127, zl: -127 },
- "Homelab": { fm: 0, end: 0, bit7: 0, len1: 0, len2: 771, gap1: 0, gap2: 0, sy: 2, rate: 44100, ol: 127, zl: -64, mp3: 1, bitrate: 96 }
- };
- var preset = document.getElementById("preset");
- function dothething(dir) {
- var loading = document.getElementById("loading");
- var input=document.getElementById("input");
- var output=document.getElementById("output");
- loading.style.display="block";
- /* stupid firefox, force redraw */
- setTimeout(()=>{
- if(output.value.length<1) {
- alert("No output filename.");
- loading.style.display="none";
- } else
- if(input.files.length<1) {
- alert("No input file given.");
- loading.style.display="none";
- } else {
- var reader = new FileReader();
- reader.onloadend = function() {
- var len = 0, btns = document.querySelectorAll("button");
- var casdata = new Uint8Array(reader.result);
- if(casdata.length < 1) alert("No input data.");
- else {
- const inbuf = Module._malloc(casdata.length);
- const outbuf = Module._malloc(128*1024*1024);
- Module.HEAPU8.set(casdata, inbuf);
- if(dir) {
- var encode = Module.cwrap("doencode", "number", [ "number", "number", "number", "number", "number", "number", "number", "number", "number", "number", "number", "number", "number", "number", "number", "number" ]);
- len = encode(inbuf, casdata.length, document.getElementById("mp3").checked, document.getElementById("end").checked, document.getElementById("bit7").checked ? 7 : 8, parseInt(document.getElementById("sy").value), parseInt(document.getElementById("fm").value), parseInt(document.getElementById("gap2").value), parseInt(document.getElementById("len2").value), document.getElementById("hdr2").checked, parseInt(document.getElementById("rate").value), parseInt(document.getElementById("ol").value), parseInt(document.getElementById("zl").value), parseInt(document.getElementById("bitrate").value), parseInt(document.getElementById("quality").value), outbuf,128*1024*1024);
- } else {
- var decode = Module.cwrap("dodecode", "number", [ "number", "number", "number", "number", "number", "number", "number", "number", "number", "number", "number", "number", "number" ]);
- len = decode(inbuf, casdata.length, document.getElementById("signed").checked, document.getElementById("end").checked, document.getElementById("bit7").checked ? 7 : 8, parseInt(document.getElementById("sy").value), parseInt(document.getElementById("fm").value), parseInt(document.getElementById("gap1").value), parseInt(document.getElementById("len1").value), document.getElementById("hdr1").checked, parseInt(document.getElementById("skip").value), outbuf,128*1024*1024);
- }
- if(len > 0) {
- const view = new Uint8Array(Module.HEAPU8.buffer,outbuf,len);
- var blob = new Blob([view], { type: "application/octet-stream" });
- var url = window.URL.createObjectURL(blob);
- var a = document.createElement("A");
- a.href = url;
- a.download = output.value;
- document.body.appendChild(a);
- a.click();
- a.remove();
- window.URL.revokeObjectURL(url);
- } else
- alert("Unable to generate output file.");
- Module._free(inbuf);
- Module._free(outbuf);
- }
- loading.style.display="none";
- };
- reader.readAsArrayBuffer(input.files[0]);
- }
- }, 100);
- }
- function help() {
- var imgs = [ "am.png", "fm_d.png", "fm_u.png", "fm_b.png" ], txts = document.querySelectorAll('small[id^="help"]');
- document.getElementById("helpimg").src = imgs[document.getElementById("fm").value];
- for(var i = 0; i < txts.length; i++) { txts[i].style.display="none"; }
- document.getElementById("help"+document.querySelector("input[name=tab]:checked").value+document.getElementById("fm").value).style.display="block";
- document.getElementById("helps"+document.getElementById("sy").value).style.display="block";
- }
- function loadpreset(p) {
- for(var id in presets[p]) {
- var fld = document.getElementById(id);
- var val = parseInt(presets[p][id]);
- if(fld.type == "checkbox") fld.checked = val;
- else fld.value = val;
- }
- help();
- }
- for(var p in presets) { var opt = document.createElement("option"); opt.value = p; opt.innerText = p; preset.add(opt); }
- help();
- </script>
- </body>
- </html>
|