From 3ef50464aad752fd914a7c2801711ff666b16081 Mon Sep 17 00:00:00 2001 From: Izan Gil <66965250+SrIzan10@users.noreply.github.com> Date: Thu, 29 May 2025 16:55:01 +0200 Subject: [PATCH] chore: use sfmt and hardcoded id in sleep --- src/lib/mersenne.ts | 281 ++++++++++++++++++++++++-------------- src/lib/stations/sleep.ts | 6 +- 2 files changed, 182 insertions(+), 105 deletions(-) diff --git a/src/lib/mersenne.ts b/src/lib/mersenne.ts index 245ba98..5b816a1 100644 --- a/src/lib/mersenne.ts +++ b/src/lib/mersenne.ts @@ -1,7 +1,8 @@ /* - * TypeScript port by Thilo Planz - * + * Original API from TypeScript port by Thilo Planz. * https://gist.github.com/thiloplanz/6abf04f957197e9e3912 + * + * SFMT implementation by Claude Sonnet 4. */ /* @@ -12,7 +13,7 @@ If you want to use this as a substitute for Math.random(), use the random() method like so: - var m = new MersenneTwister(); + var m = new SIMDMersenneTwister(); var randomNumber = m.random(); You can also call the other genrand_{foo}() methods on the instance. @@ -20,13 +21,21 @@ If you want to use a specific seed in order to get a repeatable random sequence, pass an integer into the constructor: - var m = new MersenneTwister(123); + var m = new SIMDMersenneTwister(123); and that will always produce the same random sequence. Sean McCullough (banksean@gmail.com) */ +/* + * SIMD-oriented Fast Mersenne Twister (SFMT) TypeScript implementation + * Based on the SFMT algorithm by Mutsuo Saito and Makoto Matsumoto + * + * This implementation provides better performance through SIMD-like operations + * while maintaining the same API as the standard Mersenne Twister. + */ + /* A C-program for MT19937, with initialization improved 2002/1/26. Coded by Takuji Nishimura and Makoto Matsumoto. @@ -70,151 +79,219 @@ email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) */ -class MersenneTwister { - /* Period parameters */ - private N = 624; - private M = 397; - private MATRIX_A = 0x9908b0df; /* constant vector a */ - private UPPER_MASK = 0x80000000; /* most significant w-r bits */ - private LOWER_MASK = 0x7fffffff; /* least significant r bits */ +class SIMDMersenneTwister { + /* SFMT period parameters for SFMT19937 */ + private readonly MEXP = 19937; + private readonly N = Math.floor(this.MEXP / 128) + 1; // 156 + private readonly N32 = this.N * 4; // 624 + private readonly POS1 = 122; + private readonly SL1 = 18; + private readonly SL2 = 1; + private readonly SR1 = 11; + private readonly SR2 = 1; + private readonly MSK1 = 0xdfffffef; + private readonly MSK2 = 0xddfecb7f; + private readonly MSK3 = 0xbffaffff; + private readonly MSK4 = 0xbffffff6; + private readonly PARITY1 = 0x00000001; + private readonly PARITY2 = 0x00000000; + private readonly PARITY3 = 0x00000000; + private readonly PARITY4 = 0x13c9e684; - private mt = new Array(this.N); /* the array for the state vector */ - private mti = this.N + 1; /* mti==N+1 means mt[N] is not initialized */ + /* 128-bit state array represented as 32-bit chunks */ + private state = new Uint32Array(this.N32); + private idx = this.N32; /* index counter */ constructor(seed?: number) { - if (seed == undefined) { + if (seed === undefined) { seed = new Date().getTime(); } this.init_genrand(seed); } - /* initializes mt[N] with a seed */ - private init_genrand(s: number) { - this.mt[0] = s >>> 0; - for (this.mti = 1; this.mti < this.N; this.mti++) { - s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30); - this.mt[this.mti] = - ((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253 + this.mti; - /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ - /* In the previous versions, MSBs of the seed affect */ - /* only MSBs of the array mt[]. */ - /* 2002/01/09 modified by Makoto Matsumoto */ - this.mt[this.mti] >>>= 0; - /* for >32 bit machines */ + /* Initialize generator state with seed */ + private init_genrand(seed: number): void { + this.state[0] = seed >>> 0; + for (let i = 1; i < this.N32; i++) { + this.state[i] = (1812433253 * (this.state[i - 1] ^ (this.state[i - 1] >>> 30)) + i) >>> 0; + } + this.idx = this.N32; + this.period_certification(); + } + + /* Period certification */ + private period_certification(): void { + const PARITY = [this.PARITY1, this.PARITY2, this.PARITY3, this.PARITY4]; + let inner = 0; + + for (let i = 0; i < 4; i++) { + inner ^= this.state[i] & PARITY[i]; + } + + for (let i = 16; i > 0; i >>= 1) { + inner ^= inner >> i; + } + inner &= 1; + + if (inner === 1) return; + + /* Period certification failed, fix it */ + for (let i = 0; i < 4; i++) { + let work = 1; + for (let j = 0; j < 32; j++) { + if ((work & PARITY[i]) !== 0) { + this.state[i] ^= work; + return; + } + work <<= 1; + } } } - /* initialize by an array with array-length */ - /* init_key is the array for initializing keys */ - /* key_length is its length */ - /* slight change for C++, 2004/2/26 */ + /* Initialize by an array with array-length */ init_by_array(init_key: number[], key_length: number): void { - var i: number, j: number, k: number; + let i = 1, j = 0; + let count = this.N32 > key_length ? this.N32 : key_length; + this.init_genrand(19650218); - i = 1; - j = 0; - k = this.N > key_length ? this.N : key_length; - for (; k; k--) { - var s: number = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30); - this.mt[i] = - (this.mt[i] ^ - (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + (s & 0x0000ffff) * 1664525)) + - init_key[j] + - j; /* non linear */ - this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */ + + for (; count > 0; count--) { + this.state[i] = ((this.state[i] ^ ((this.state[i - 1] ^ (this.state[i - 1] >>> 30)) * 1664525)) + init_key[j] + j) >>> 0; i++; j++; - if (i >= this.N) { - this.mt[0] = this.mt[this.N - 1]; + if (i >= this.N32) { + this.state[0] = this.state[this.N32 - 1]; i = 1; } if (j >= key_length) j = 0; } - for (k = this.N - 1; k; k--) { - var s: number = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30); - this.mt[i] = - (this.mt[i] ^ - (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941)) - - i; /* non linear */ - this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */ + + for (count = this.N32 - 1; count > 0; count--) { + this.state[i] = ((this.state[i] ^ ((this.state[i - 1] ^ (this.state[i - 1] >>> 30)) * 1566083941)) - i) >>> 0; i++; - if (i >= this.N) { - this.mt[0] = this.mt[this.N - 1]; + if (i >= this.N32) { + this.state[0] = this.state[this.N32 - 1]; i = 1; } } + + this.state[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */ + this.idx = this.N32; + this.period_certification(); + } - this.mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */ + /* SIMD 128-bit recursion */ + private do_recursion(a: Uint32Array, b: Uint32Array, c: Uint32Array, d: Uint32Array): void { + const lshift128 = (a: Uint32Array, out: Uint32Array, shift: number): void => { + const th = shift >>> 5; + const tl = shift & 31; + const tr = 32 - tl; + + if (th >= 4) { + out[0] = out[1] = out[2] = out[3] = 0; + return; + } + + for (let i = 0; i < 4 - th; i++) { + out[i] = (a[i + th] << tl) | (i + th + 1 < 4 ? a[i + th + 1] >>> tr : 0); + } + for (let i = 4 - th; i < 4; i++) { + out[i] = 0; + } + }; + + const rshift128 = (a: Uint32Array, out: Uint32Array, shift: number): void => { + const th = shift >>> 5; + const tl = shift & 31; + const tr = 32 - tl; + + if (th >= 4) { + out[0] = out[1] = out[2] = out[3] = 0; + return; + } + + for (let i = 3; i >= th; i--) { + out[i] = (a[i - th] >>> tl) | (i - th - 1 >= 0 ? a[i - th - 1] << tr : 0); + } + for (let i = th - 1; i >= 0; i--) { + out[i] = 0; + } + }; + + const x = new Uint32Array(4); + const y = new Uint32Array(4); + + lshift128(a, x, this.SL2); + rshift128(c, y, this.SR2); + + a[0] = a[0] ^ x[0] ^ ((b[0] >>> this.SR1) & this.MSK1) ^ y[0] ^ (d[0] << this.SL1); + a[1] = a[1] ^ x[1] ^ ((b[1] >>> this.SR1) & this.MSK2) ^ y[1] ^ (d[1] << this.SL1); + a[2] = a[2] ^ x[2] ^ ((b[2] >>> this.SR1) & this.MSK3) ^ y[2] ^ (d[2] << this.SL1); + a[3] = a[3] ^ x[3] ^ ((b[3] >>> this.SR1) & this.MSK4) ^ y[3] ^ (d[3] << this.SL1); + } + + /* Generate the next N 128-bit blocks */ + private gen_rand_all(): void { + let r1 = this.N - 2; + let r2 = this.N - 1; + + for (let i = 0; i < this.N - this.POS1; i++) { + this.do_recursion( + this.state.subarray(i * 4, (i + 1) * 4), + this.state.subarray((i + this.POS1) * 4, (i + this.POS1 + 1) * 4), + this.state.subarray(r1 * 4, (r1 + 1) * 4), + this.state.subarray(r2 * 4, (r2 + 1) * 4) + ); + r1 = r2; + r2 = i; + } + + for (let i = this.N - this.POS1; i < this.N; i++) { + this.do_recursion( + this.state.subarray(i * 4, (i + 1) * 4), + this.state.subarray((i + this.POS1 - this.N) * 4, (i + this.POS1 - this.N + 1) * 4), + this.state.subarray(r1 * 4, (r1 + 1) * 4), + this.state.subarray(r2 * 4, (r2 + 1) * 4) + ); + r1 = r2; + r2 = i; + } + + this.idx = 0; } /* generates a random number on [0,0xffffffff]-interval */ - genrand_int32() { - var y; - var mag01 = new Array(0x0, this.MATRIX_A); - /* mag01[x] = x * MATRIX_A for x=0,1 */ - - if (this.mti >= this.N) { - /* generate N words at one time */ - var kk; - - if (this.mti == this.N + 1) - /* if init_genrand() has not been called, */ - this.init_genrand(5489); /* a default initial seed is used */ - - for (kk = 0; kk < this.N - this.M; kk++) { - y = (this.mt[kk] & this.UPPER_MASK) | (this.mt[kk + 1] & this.LOWER_MASK); - this.mt[kk] = this.mt[kk + this.M] ^ (y >>> 1) ^ mag01[y & 0x1]; - } - for (; kk < this.N - 1; kk++) { - y = (this.mt[kk] & this.UPPER_MASK) | (this.mt[kk + 1] & this.LOWER_MASK); - this.mt[kk] = this.mt[kk + (this.M - this.N)] ^ (y >>> 1) ^ mag01[y & 0x1]; - } - y = (this.mt[this.N - 1] & this.UPPER_MASK) | (this.mt[0] & this.LOWER_MASK); - this.mt[this.N - 1] = this.mt[this.M - 1] ^ (y >>> 1) ^ mag01[y & 0x1]; - - this.mti = 0; + genrand_int32(): number { + if (this.idx >= this.N32) { + this.gen_rand_all(); } - - y = this.mt[this.mti++]; - - /* Tempering */ - y ^= y >>> 11; - y ^= (y << 7) & 0x9d2c5680; - y ^= (y << 15) & 0xefc60000; - y ^= y >>> 18; - - return y >>> 0; + return this.state[this.idx++]; } /* generates a random number on [0,0x7fffffff]-interval */ - genrand_int31() { + genrand_int31(): number { return this.genrand_int32() >>> 1; } /* generates a random number on [0,1]-real-interval */ - genrand_real1() { + genrand_real1(): number { return this.genrand_int32() * (1.0 / 4294967295.0); - /* divided by 2^32-1 */ } /* generates a random number on [0,1)-real-interval */ - random() { + random(): number { return this.genrand_int32() * (1.0 / 4294967296.0); - /* divided by 2^32 */ } /* generates a random number on (0,1)-real-interval */ - genrand_real3() { + genrand_real3(): number { return (this.genrand_int32() + 0.5) * (1.0 / 4294967296.0); - /* divided by 2^32 */ } /* generates a random number on [0,1) with 53-bit resolution*/ - genrand_res53() { - var a = this.genrand_int32() >>> 5, - b = this.genrand_int32() >>> 6; + genrand_res53(): number { + const a = this.genrand_int32() >>> 5; + const b = this.genrand_int32() >>> 6; return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0); } - - /* These real versions are due to Isaku Wada, 2002/01/09 added */ } diff --git a/src/lib/stations/sleep.ts b/src/lib/stations/sleep.ts index 5b33ce6..3f22b8f 100644 --- a/src/lib/stations/sleep.ts +++ b/src/lib/stations/sleep.ts @@ -1,7 +1,7 @@ import type { Song } from "@/types"; export async function getSleepStationSongs(): Promise { - const m = new MersenneTwister(); + const m = new SIMDMersenneTwister(); const res = await fetch('https://lofi-cdn.srizan.dev/sleep/list.json'); const files = await res.json() as string[]; @@ -9,7 +9,7 @@ export async function getSleepStationSongs(): Promise { const mapped = files.map(file => { const [artist, title] = file.replace('.opus', '').split(' - '); return { - id: parseInt((m.random() * 1000000).toFixed(0)), + id: 727, fileId: file.replace('.opus', ''), endpoint: `https://lofi-cdn.srizan.dev/sleep/${file}`, artists: artist, @@ -17,7 +17,7 @@ export async function getSleepStationSongs(): Promise { image: `https://lofi-cdn.srizan.dev/sleep/thumbs/${file.replace('.opus', '')}.webp`, }; }) as Song[]; - + const shuffled = mapped.sort(() => 0.5 - m.random()).slice(0, 5); return shuffled; }