Skip to content

Instantly share code, notes, and snippets.

@plugnburn
Created January 5, 2023 20:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save plugnburn/63cc2825e02311a617af55653aecef1a to your computer and use it in GitHub Desktop.
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
// 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