Created
January 5, 2023 20:24
-
-
Save plugnburn/63cc2825e02311a617af55653aecef1a to your computer and use it in GitHub Desktop.
Telememer: a JS library to encode/decode information to/from Casio module 2747/5574 Telememo 30 format
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Telememer: a JS library to encode/decode information to/from Casio module 2747/5574 Telememo 30 format | |
// Supports up to 378 bytes of raw binary data | |
// Requires BigInt support in the browser's or other JS engine | |
// Created by Luxferre in 2023, released into public domain | |
// Made in Ukraine | |
Telememer = (params => { | |
var nameAlphabet = params[0], | |
digitAlphabet = params[1], | |
indexAlphabet = params[2], | |
nameLen = params[3], | |
effectiveNameLen = nameLen - 1, // the first character is used for part indexing because of the autosort feature | |
numLen = params[4], | |
nameAlphaLen = nameAlphabet.length, // length of the name alphabet | |
digitAlphaLen = digitAlphabet.length, // length of the digit alphabet | |
telememoSize = indexAlphabet.length, // overall size of the telememo memory (determined by the length of the index alphabet) | |
namePartBits = 0 | (effectiveNameLen * Math.log2(nameAlphaLen)), // bit length we can encode in the name part | |
digitPartBits = 0 | (numLen * Math.log2(digitAlphaLen)), // bit length we can encode in the number part | |
recordBits = namePartBits + digitPartBits, // bit length we can encode in the whole record | |
maxBits = recordBits * telememoSize, // maximum storage capacity in bits | |
// universal base conversion methods | |
toBase = (number, radix, digits) => { | |
if(number === 0) return digits[0] | |
var a = [] | |
number = BigInt(number) | |
radix = BigInt(radix) | |
while(number !== 0n) { | |
a.unshift(digits[Number(number % radix)]) | |
number = number/radix | |
} | |
return a.join('') | |
}, | |
fromBase = (number, radix, digits) => { | |
number = ''+number | |
if(number === digits[0]) return 0 | |
number = number.split('') | |
var v = 0n | |
radix = BigInt(radix) | |
while(number.length) | |
v = v*radix + BigInt(digits.indexOf(number.shift())) | |
return v | |
}, | |
// record part conversion methods | |
valToName = n => toBase(n, nameAlphaLen, nameAlphabet).padStart(effectiveNameLen, nameAlphabet[0]), | |
valToDigits = n => toBase(n, digitAlphaLen, digitAlphabet).padStart(numLen, digitAlphabet[0]), | |
binBitNorm = (x, n) => { // convert a number to byte- or other bit-number aligned binary value | |
var raw = x.toString(2) | |
return raw.padStart(n * Math.ceil(raw.length / n), '0') | |
} | |
return { | |
encode: data => { // (Uint8)Array => [records], where a record is ['name', 'number'] | |
var bitstream = [], dl = data.length, i, pos, records = [] | |
for(i=0;i<dl;i++) // create a bitstream from our data | |
binBitNorm(data[i], 8).split('').forEach(x => {bitstream.push(+x)}) | |
var bitLen = bitstream.length | |
if(bitLen > maxBits) { // truncate the input stream if it exceeds the full capacity | |
bitLen = maxBits | |
bitstream = bitstream.slice(0, maxBits) | |
} | |
var recLen = Math.ceil(bitLen / recordBits) // message length in full records | |
for(i=0,pos=0;i<recLen;i++) { // iterate over bit sets | |
var rec = [] | |
dl = BigInt('0b'+bitstream.slice(pos, pos+namePartBits).join('').padEnd(namePartBits, '0')) | |
rec.push(indexAlphabet[i] + valToName(dl)) | |
pos += namePartBits | |
dl = BigInt('0b'+bitstream.slice(pos, pos+digitPartBits).join('').padEnd(digitPartBits, '0')) | |
rec.push(valToDigits(dl)) | |
pos += digitPartBits | |
records.push(rec) | |
} | |
return records | |
}, | |
decode: (recs, expectedLength) => { // [records](, expectedLengthInBytes) => Uint8Array | |
var bitstream = [], recLen = recs.length, i, nameval, numval | |
for(i=0;i<recLen;i++) { //iterate over records | |
nameval = fromBase(recs[i][0].slice(1), nameAlphaLen, nameAlphabet) // drop the index character for the name | |
numval = fromBase(recs[i][1], digitAlphaLen, digitAlphabet); | |
(binBitNorm(nameval, namePartBits) + binBitNorm(numval, digitPartBits)).split('').forEach(x => { | |
bitstream.push(+x) | |
}) | |
} | |
var bytes = bitstream.join('').match(/\d{8}/g).map(x => parseInt(x, 2)) | |
if(expectedLength) bytes = bytes.slice(0, expectedLength) | |
return Uint8Array.from(bytes) | |
} | |
} | |
})([ | |
" ABCDEFGHIJKLMNOPQRSTUVWXYZ@!?',.;:()/+-0123456789", // full 50-character range for name | |
' 0123456789()+-', // full 15-character range for number value | |
'ABCDEFGHIJKLMNOPQRSTUVWXY12345', // 30-character range for index position | |
8, // name field length | |
16 // number field length | |
]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
FYI. this library has been ported to Python 3 as a standalone CLI app: Databankr