Oasis 0.8.7 mobile (nodejs-project baseline)
This commit is contained in:
commit
e563b86ede
226 changed files with 141968 additions and 0 deletions
181
src/models/melody_model.js
Normal file
181
src/models/melody_model.js
Normal 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; }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue