Initial commit: InTono — app KMP per l'apprendimento musicale
App Kotlin Multiplatform (Android/iOS/Desktop) con Compose Multiplatform: controllo intonazione (YIN), pratica guidata, metronomo, ear training, sfide, scansione spartiti via AI, spartiti bundled e pentagramma con glifi tipografici, travature, battute e spaziatura proporzionale. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
commit
21ad969b10
245 files changed
+28963
No files matched your search
@@ -0,0 +1,213 @@
|
||||
import { writeFileSync } from "fs";
|
||||
|
||||
const SAMPLE_RATE = 44100;
|
||||
const DURATION = 30; // seconds
|
||||
const TOTAL_SAMPLES = SAMPLE_RATE * DURATION;
|
||||
|
||||
// Note frequencies (Hz)
|
||||
const FREQ = {
|
||||
C3: 130.81, E3: 164.81, G3: 196.0,
|
||||
C4: 261.63, D4: 293.66, E4: 329.63, F4: 349.23,
|
||||
G4: 392.0, A4: 440.0, B4: 493.88, C5: 523.25,
|
||||
C2: 65.41, G2: 98.0,
|
||||
};
|
||||
|
||||
// C major scale for the staff scene
|
||||
const SCALE = [FREQ.C4, FREQ.D4, FREQ.E4, FREQ.F4, FREQ.G4, FREQ.A4, FREQ.B4, FREQ.C5];
|
||||
|
||||
function envelope(t, attack, sustain, release, totalDur) {
|
||||
if (t < attack) return t / attack;
|
||||
if (t < attack + sustain) return 1.0;
|
||||
const rel = t - attack - sustain;
|
||||
if (rel < release) return 1.0 - rel / release;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function softSine(t, freq) {
|
||||
// Sine with soft harmonics for a piano-like tone
|
||||
return (
|
||||
Math.sin(2 * Math.PI * freq * t) * 0.6 +
|
||||
Math.sin(2 * Math.PI * freq * 2 * t) * 0.2 +
|
||||
Math.sin(2 * Math.PI * freq * 3 * t) * 0.1 +
|
||||
Math.sin(2 * Math.PI * freq * 4 * t) * 0.05
|
||||
);
|
||||
}
|
||||
|
||||
function padTone(t, freq) {
|
||||
// Very soft, warm pad sound
|
||||
return (
|
||||
Math.sin(2 * Math.PI * freq * t) * 0.5 +
|
||||
Math.sin(2 * Math.PI * freq * 1.001 * t) * 0.3 + // slight detune for warmth
|
||||
Math.sin(2 * Math.PI * freq * 2 * t) * 0.1
|
||||
);
|
||||
}
|
||||
|
||||
// Simple reverb via comb filter
|
||||
function applyReverb(samples, delay, decay) {
|
||||
const delaySamples = Math.floor(delay * SAMPLE_RATE);
|
||||
const out = new Float64Array(samples.length);
|
||||
for (let i = 0; i < samples.length; i++) {
|
||||
out[i] = samples[i];
|
||||
if (i >= delaySamples) {
|
||||
out[i] += out[i - delaySamples] * decay;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Generate samples
|
||||
const left = new Float64Array(TOTAL_SAMPLES);
|
||||
const right = new Float64Array(TOTAL_SAMPLES);
|
||||
|
||||
for (let i = 0; i < TOTAL_SAMPLES; i++) {
|
||||
const t = i / SAMPLE_RATE; // time in seconds
|
||||
let sampleL = 0;
|
||||
let sampleR = 0;
|
||||
|
||||
// === 1. Ambient pad (entire duration, fades in/out) ===
|
||||
const padEnv =
|
||||
envelope(t, 3.0, 22.0, 5.0, DURATION) * 0.12;
|
||||
|
||||
// C major chord pad (C3, E3, G3)
|
||||
const pad =
|
||||
padTone(t, FREQ.C3) * 0.4 +
|
||||
padTone(t, FREQ.E3) * 0.3 +
|
||||
padTone(t, FREQ.G3) * 0.3;
|
||||
|
||||
sampleL += pad * padEnv;
|
||||
sampleR += pad * padEnv;
|
||||
|
||||
// Sub bass
|
||||
const bassEnv = envelope(t, 4.0, 20.0, 6.0, DURATION) * 0.08;
|
||||
sampleL += Math.sin(2 * Math.PI * FREQ.C2 * t) * bassEnv;
|
||||
sampleR += Math.sin(2 * Math.PI * FREQ.C2 * t) * bassEnv;
|
||||
|
||||
// === 2. Intro shimmer (0-6s) ===
|
||||
if (t < 7) {
|
||||
const shimmerEnv = envelope(t, 1.0, 3.5, 2.5, 7.0) * 0.06;
|
||||
const shimmer =
|
||||
Math.sin(2 * Math.PI * FREQ.G4 * t) * 0.5 +
|
||||
Math.sin(2 * Math.PI * FREQ.C5 * t) * 0.3 +
|
||||
Math.sin(2 * Math.PI * FREQ.E4 * t) * 0.2;
|
||||
// Slight stereo spread
|
||||
sampleL += shimmer * shimmerEnv * 1.1;
|
||||
sampleR += shimmer * shimmerEnv * 0.9;
|
||||
}
|
||||
|
||||
// === 3. Scale notes during staff scene (6-15s) ===
|
||||
const scaleStart = 6.0; // seconds
|
||||
const noteDuration = 1.0; // each note lasts 1s
|
||||
for (let n = 0; n < SCALE.length; n++) {
|
||||
const noteStart = scaleStart + n * noteDuration;
|
||||
const noteT = t - noteStart;
|
||||
if (noteT >= 0 && noteT < 2.0) {
|
||||
// Note with decay
|
||||
const noteEnv = envelope(noteT, 0.02, 0.3, 1.5, 2.0) * 0.22;
|
||||
const note = softSine(t, SCALE[n]);
|
||||
// Add fifth harmonic quietly
|
||||
const fifth = softSine(t, SCALE[n] * 1.5) * 0.08;
|
||||
sampleL += (note + fifth) * noteEnv;
|
||||
sampleR += (note + fifth) * noteEnv;
|
||||
}
|
||||
}
|
||||
|
||||
// === 4. Feature section arpeggios (14-24s) ===
|
||||
if (t >= 14 && t < 24) {
|
||||
const arpNotes = [FREQ.C4, FREQ.E4, FREQ.G4, FREQ.C5];
|
||||
const arpSpeed = 0.6; // seconds per note
|
||||
const arpT = t - 14;
|
||||
const arpIndex = Math.floor(arpT / arpSpeed) % arpNotes.length;
|
||||
const noteT = arpT % arpSpeed;
|
||||
const arpEnv = envelope(noteT, 0.01, 0.15, 0.4, arpSpeed) * 0.1;
|
||||
const fadeInOut =
|
||||
envelope(t - 14, 1.5, 6.0, 2.5, 10.0);
|
||||
const arp = softSine(t, arpNotes[arpIndex]);
|
||||
// Alternate stereo slightly
|
||||
sampleL += arp * arpEnv * fadeInOut * (arpIndex % 2 === 0 ? 1.1 : 0.9);
|
||||
sampleR += arp * arpEnv * fadeInOut * (arpIndex % 2 === 0 ? 0.9 : 1.1);
|
||||
}
|
||||
|
||||
// === 5. Outro resolution chord (23-30s) ===
|
||||
if (t >= 23) {
|
||||
const outroT = t - 23;
|
||||
const chordEnv = envelope(outroT, 2.0, 2.5, 2.5, 7.0) * 0.15;
|
||||
// Rich C major chord
|
||||
const chord =
|
||||
padTone(t, FREQ.C3) * 0.25 +
|
||||
padTone(t, FREQ.E3) * 0.2 +
|
||||
padTone(t, FREQ.G3) * 0.2 +
|
||||
padTone(t, FREQ.C4) * 0.2 +
|
||||
padTone(t, FREQ.E4) * 0.1 +
|
||||
padTone(t, FREQ.G4) * 0.05;
|
||||
sampleL += chord * chordEnv;
|
||||
sampleR += chord * chordEnv;
|
||||
|
||||
// High sparkle at the end
|
||||
if (outroT > 1.0 && outroT < 4.0) {
|
||||
const sparkleEnv = envelope(outroT - 1.0, 0.5, 1.0, 1.5, 3.0) * 0.04;
|
||||
sampleL += Math.sin(2 * Math.PI * FREQ.C5 * 2 * t) * sparkleEnv;
|
||||
sampleR += Math.sin(2 * Math.PI * FREQ.G4 * 2 * t) * sparkleEnv;
|
||||
}
|
||||
}
|
||||
|
||||
left[i] = sampleL;
|
||||
right[i] = sampleR;
|
||||
}
|
||||
|
||||
// Apply simple reverb
|
||||
const reverbL = applyReverb(left, 0.08, 0.3);
|
||||
const reverbR = applyReverb(right, 0.11, 0.28);
|
||||
|
||||
// Mix dry + wet
|
||||
for (let i = 0; i < TOTAL_SAMPLES; i++) {
|
||||
left[i] = left[i] * 0.65 + reverbL[i] * 0.35;
|
||||
right[i] = right[i] * 0.65 + reverbR[i] * 0.35;
|
||||
}
|
||||
|
||||
// Normalize
|
||||
let maxAmp = 0;
|
||||
for (let i = 0; i < TOTAL_SAMPLES; i++) {
|
||||
maxAmp = Math.max(maxAmp, Math.abs(left[i]), Math.abs(right[i]));
|
||||
}
|
||||
const normFactor = maxAmp > 0 ? 0.85 / maxAmp : 1;
|
||||
|
||||
// Convert to 16-bit PCM WAV
|
||||
const numChannels = 2;
|
||||
const bitsPerSample = 16;
|
||||
const byteRate = SAMPLE_RATE * numChannels * (bitsPerSample / 8);
|
||||
const blockAlign = numChannels * (bitsPerSample / 8);
|
||||
const dataSize = TOTAL_SAMPLES * numChannels * (bitsPerSample / 8);
|
||||
const fileSize = 44 + dataSize;
|
||||
|
||||
const buffer = Buffer.alloc(fileSize);
|
||||
let offset = 0;
|
||||
|
||||
// RIFF header
|
||||
buffer.write("RIFF", offset); offset += 4;
|
||||
buffer.writeUInt32LE(fileSize - 8, offset); offset += 4;
|
||||
buffer.write("WAVE", offset); offset += 4;
|
||||
|
||||
// fmt chunk
|
||||
buffer.write("fmt ", offset); offset += 4;
|
||||
buffer.writeUInt32LE(16, offset); offset += 4;
|
||||
buffer.writeUInt16LE(1, offset); offset += 2; // PCM
|
||||
buffer.writeUInt16LE(numChannels, offset); offset += 2;
|
||||
buffer.writeUInt32LE(SAMPLE_RATE, offset); offset += 4;
|
||||
buffer.writeUInt32LE(byteRate, offset); offset += 4;
|
||||
buffer.writeUInt16LE(blockAlign, offset); offset += 2;
|
||||
buffer.writeUInt16LE(bitsPerSample, offset); offset += 2;
|
||||
|
||||
// data chunk
|
||||
buffer.write("data", offset); offset += 4;
|
||||
buffer.writeUInt32LE(dataSize, offset); offset += 4;
|
||||
|
||||
// Interleaved stereo samples
|
||||
for (let i = 0; i < TOTAL_SAMPLES; i++) {
|
||||
const l = Math.max(-1, Math.min(1, left[i] * normFactor));
|
||||
const r = Math.max(-1, Math.min(1, right[i] * normFactor));
|
||||
buffer.writeInt16LE(Math.round(l * 32767), offset); offset += 2;
|
||||
buffer.writeInt16LE(Math.round(r * 32767), offset); offset += 2;
|
||||
}
|
||||
|
||||
writeFileSync("public/music.wav", buffer);
|
||||
console.log(`Generated music.wav (${(fileSize / 1024 / 1024).toFixed(1)} MB, ${DURATION}s stereo)`);
|
||||
Reference in new issue
Block a user