Oasis 0.8.7 mobile (nodejs-project baseline)

This commit is contained in:
s1to 2026-07-09 09:56:30 +02:00
commit e563b86ede
226 changed files with 141968 additions and 0 deletions

181
src/models/melody_model.js Normal file
View file

@ -0,0 +1,181 @@
"use strict";
const pull = require('../server/node_modules/pull-stream');
const NOTE_NAMES = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'];
const OCTAVES = [3, 4, 5];
const TYPE_TO_DEGREE = {
post: 0,
vote: 2,
about: 4,
contact: 5,
forum: 7,
tribe: 9,
transfer: 11,
shop: 1,
shopProduct: 1,
job: 3,
project: 6,
event: 8,
task: 10,
bookmark: 4,
feed: 5,
pad: 6,
chat: 7,
audio: 8,
video: 9,
image: 10,
document: 11,
torrent: 0,
map: 2,
pixelia: 3,
gameScore: 5,
votes: 7,
calendar: 8,
curriculum: 9,
report: 10,
parliament: 11,
courts: 0,
market: 1,
aiExchange: 6,
tombstone: 4
};
const NOTE_FREQS = (() => {
const a4 = 440;
const map = {};
for (const oct of [2, 3, 4, 5, 6]) {
for (let i = 0; i < NOTE_NAMES.length; i++) {
const name = NOTE_NAMES[i] + oct;
const semis = (oct - 4) * 12 + (i - 9);
map[name] = a4 * Math.pow(2, semis / 12);
}
}
return map;
})();
function blockToNote(msg) {
const c = msg && msg.value && msg.value.content;
if (!c || typeof c !== 'object') return null;
const type = String(c.type || '').trim() || 'unknown';
const degree = TYPE_TO_DEGREE[type] != null ? TYPE_TO_DEGREE[type] : (Math.abs(hashStr(type)) % 12);
const size = Buffer.byteLength(JSON.stringify(msg.value), 'utf8');
const octIdx = size < 256 ? 0 : (size < 1024 ? 1 : 2);
const octave = OCTAVES[octIdx];
const name = NOTE_NAMES[degree] + octave;
const freq = NOTE_FREQS[name] || 440;
const durMs = Math.min(600, 200 + Math.round(size / 64));
return {
id: msg.key,
ts: msg.value.timestamp,
type,
size,
name,
freq,
durMs
};
}
function hashStr(s) {
let h = 0;
for (let i = 0; i < s.length; i++) h = ((h << 5) - h + s.charCodeAt(i)) | 0;
return h;
}
module.exports = ({ cooler }) => {
let ssb = null;
const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; };
async function getUserMelodyInternal(userId, limit = 200) {
const client = await openSsb();
const me = userId || client.id;
const msgs = await new Promise((resolve, reject) => {
pull(
client.createUserStream({ id: me, reverse: true, limit }),
pull.collect((err, rows) => err ? reject(err) : resolve(rows))
);
});
const seq = msgs
.filter(m => m && m.value && m.value.content && typeof m.value.content === 'object')
.map(blockToNote)
.filter(Boolean)
.reverse();
const stats = {};
for (const n of seq) {
stats[n.type] = (stats[n.type] || 0) + 1;
}
return { feedId: me, total: seq.length, sequence: seq, stats };
}
return {
NOTE_NAMES,
OCTAVES,
TYPE_TO_DEGREE,
getUserMelody: getUserMelodyInternal,
embedTextInWav,
extractTextFromWav
};
};
const STEG_MAGIC = Buffer.from([0xBC, 0x53]);
const STEG_HEADER_BITS = 32;
function embedTextInWav(wavBuffer, text) {
if (!Buffer.isBuffer(wavBuffer) || wavBuffer.length < 44) return wavBuffer;
const payload = Buffer.from(String(text || ''), 'utf8');
if (payload.length === 0) return wavBuffer;
if (payload.length > 4096) return wavBuffer;
const lenBuf = Buffer.alloc(2);
lenBuf.writeUInt16BE(payload.length, 0);
const fullData = Buffer.concat([STEG_MAGIC, lenBuf, payload]);
const totalBits = fullData.length * 8;
const dataLen = wavBuffer.readUInt32LE(40);
const numSamples = Math.floor(dataLen / 2);
if (totalBits > numSamples) return wavBuffer;
const out = Buffer.from(wavBuffer);
for (let i = 0; i < totalBits; i++) {
const byteIdx = i >> 3;
const bitIdx = i & 7;
const bit = (fullData[byteIdx] >> (7 - bitIdx)) & 1;
const sampleOffset = 44 + (i * 2);
let sample = out.readInt16LE(sampleOffset);
sample = (sample & ~1) | bit;
out.writeInt16LE(sample, sampleOffset);
}
return out;
}
function readBitsAsBuffer(wavBuffer, startBit, numBits) {
const bytes = Math.ceil(numBits / 8);
const out = Buffer.alloc(bytes, 0);
for (let i = 0; i < numBits; i++) {
const sampleOffset = 44 + ((startBit + i) * 2);
if (sampleOffset + 2 > wavBuffer.length) return null;
const sample = wavBuffer.readInt16LE(sampleOffset);
const bit = sample & 1;
const byteIdx = i >> 3;
const bitIdx = i & 7;
out[byteIdx] |= bit << (7 - bitIdx);
}
return out;
}
function extractTextFromWav(wavBuffer) {
if (!Buffer.isBuffer(wavBuffer) || wavBuffer.length < 44 + STEG_HEADER_BITS * 2) return null;
if (wavBuffer.slice(0, 4).toString('ascii') !== 'RIFF') return null;
if (wavBuffer.slice(8, 12).toString('ascii') !== 'WAVE') return null;
const magic = readBitsAsBuffer(wavBuffer, 0, 16);
if (!magic || magic[0] !== STEG_MAGIC[0] || magic[1] !== STEG_MAGIC[1]) return null;
const lenBuf = readBitsAsBuffer(wavBuffer, 16, 16);
if (!lenBuf) return null;
const len = lenBuf.readUInt16BE(0);
if (len === 0 || len > 4096) return null;
const payload = readBitsAsBuffer(wavBuffer, 32, len * 8);
if (!payload) return null;
try {
const text = payload.toString('utf8');
if (!text) return null;
return text;
} catch (_) { return null; }
}