vibeaudio 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +601 -0
- package/bin/vibeaudio.js +16 -0
- package/package.json +51 -0
- package/src/cli.js +993 -0
- package/src/hooks.js +980 -0
- package/src/hud.js +84 -0
- package/src/interactive.js +338 -0
- package/src/mcp.js +292 -0
- package/src/player.js +666 -0
- package/src/synth/chime.js +137 -0
- package/src/synth/chiptune.js +132 -0
- package/src/synth/drone.js +133 -0
- package/src/synth/electronic.js +133 -0
- package/src/synth/generator.js +179 -0
- package/src/synth/jazz.js +178 -0
- package/src/synth/lofi.js +148 -0
- package/src/synth/piano.js +158 -0
- package/src/synth/synthwave.js +170 -0
- package/src/synth/zen.js +148 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chill Synthwave / Cyberpunk Procedural Synth Engine
|
|
3
|
+
* Analog Chorused Pads (Dmin -> Bbmaj7 -> Cmaj) + Pulsing 80s Bassline + Shimmering Arp
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
SAMPLE_RATE,
|
|
8
|
+
noteToFreq,
|
|
9
|
+
analogSaw,
|
|
10
|
+
triangle,
|
|
11
|
+
makeRng,
|
|
12
|
+
pick,
|
|
13
|
+
createWavBuffer
|
|
14
|
+
} = require("./generator");
|
|
15
|
+
|
|
16
|
+
// Three progressions in D minor, each paired with the arpeggio figures that
|
|
17
|
+
// sit on top of it (index 0 for tier 2, index 1 for the busier tier 3).
|
|
18
|
+
const PROGRESSIONS = [
|
|
19
|
+
{
|
|
20
|
+
steps: [
|
|
21
|
+
{ pad: ["D3", "F3", "A3", "D4"], bass: "D2" },
|
|
22
|
+
{ pad: ["A#2", "F3", "A3", "D4"], bass: "A#1" },
|
|
23
|
+
{ pad: ["C3", "G3", "C4", "E4"], bass: "C2" }
|
|
24
|
+
],
|
|
25
|
+
arps: [
|
|
26
|
+
["D5", "F5", "A5", "C6", "D6", "A5", "F5", "E5"],
|
|
27
|
+
["D5", "A5", "F5", "C6", "D6", "E6", "A5", "F5"]
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
steps: [
|
|
32
|
+
{ pad: ["A2", "E3", "A3", "C4"], bass: "A1" },
|
|
33
|
+
{ pad: ["F3", "A3", "C4", "F4"], bass: "F2" },
|
|
34
|
+
{ pad: ["G3", "A#3", "D4", "G4"], bass: "G2" }
|
|
35
|
+
],
|
|
36
|
+
arps: [
|
|
37
|
+
["A4", "C5", "E5", "A5", "G5", "E5", "C5", "D5"],
|
|
38
|
+
["A4", "E5", "C5", "A5", "G5", "D6", "E5", "C5"]
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
steps: [
|
|
43
|
+
{ pad: ["F3", "A3", "C4", "F4"], bass: "F2" },
|
|
44
|
+
{ pad: ["C3", "G3", "C4", "E4"], bass: "C2" },
|
|
45
|
+
{ pad: ["D3", "F3", "A3", "D4"], bass: "D2" }
|
|
46
|
+
],
|
|
47
|
+
arps: [
|
|
48
|
+
["F5", "A5", "C6", "A5", "D6", "C6", "A5", "G5"],
|
|
49
|
+
["F5", "C6", "A5", "D6", "E6", "C6", "A5", "F5"]
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
function generateSynthwaveLoop(durationSec = 6.8, tier = 2, seed = 0) {
|
|
55
|
+
const variant = pick(makeRng(seed), PROGRESSIONS);
|
|
56
|
+
const progression = variant.steps;
|
|
57
|
+
const arps = variant.arps;
|
|
58
|
+
const totalSamples = Math.floor(SAMPLE_RATE * durationSec);
|
|
59
|
+
const left = new Float64Array(totalSamples);
|
|
60
|
+
const right = new Float64Array(totalSamples);
|
|
61
|
+
|
|
62
|
+
// Analog chorused pad renderer
|
|
63
|
+
function addPadChord(chordNotes, startSec, durSec, vel = 0.15) {
|
|
64
|
+
const startIdx = Math.floor(startSec * SAMPLE_RATE);
|
|
65
|
+
const numSamples = Math.floor(durSec * SAMPLE_RATE);
|
|
66
|
+
|
|
67
|
+
for (const note of chordNotes) {
|
|
68
|
+
const f = noteToFreq(note);
|
|
69
|
+
for (let i = 0; i < numSamples; i++) {
|
|
70
|
+
const idx = startIdx + i;
|
|
71
|
+
if (idx >= totalSamples) break;
|
|
72
|
+
const t = i / SAMPLE_RATE;
|
|
73
|
+
|
|
74
|
+
const attack = Math.min(1.0, t * 2.5);
|
|
75
|
+
const rel = t > durSec - 0.35 ? Math.min(1.0, (durSec - t) * 3.0) : 1.0;
|
|
76
|
+
const env = attack * rel;
|
|
77
|
+
|
|
78
|
+
// Dual detuned oscillators for lush analog chorus
|
|
79
|
+
const detune = 0.0035;
|
|
80
|
+
const oscL = analogSaw(f * (1.0 - detune) * t);
|
|
81
|
+
const oscR = analogSaw(f * (1.0 + detune) * t);
|
|
82
|
+
|
|
83
|
+
left[idx] += oscL * env * vel;
|
|
84
|
+
right[idx] += oscR * env * vel;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Pulsing 8th-note synthwave bass
|
|
90
|
+
function addBass(bassNote, startSec, durSec, bpm = 110) {
|
|
91
|
+
const startIdx = Math.floor(startSec * SAMPLE_RATE);
|
|
92
|
+
const numSamples = Math.floor(durSec * SAMPLE_RATE);
|
|
93
|
+
const f = noteToFreq(bassNote);
|
|
94
|
+
const stepSec = 60.0 / bpm / 2.0;
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < numSamples; i++) {
|
|
97
|
+
const idx = startIdx + i;
|
|
98
|
+
if (idx >= totalSamples) break;
|
|
99
|
+
const t = i / SAMPLE_RATE;
|
|
100
|
+
const tNote = t % stepSec;
|
|
101
|
+
const env = Math.exp(-tNote * 8.0);
|
|
102
|
+
const sample = (triangle(f * t) * 0.7 + analogSaw(f * t) * 0.3) * env * 0.20;
|
|
103
|
+
|
|
104
|
+
left[idx] += sample;
|
|
105
|
+
right[idx] += sample;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Shimmering 16th-note arpeggiator lead
|
|
110
|
+
function addArp(leadNotes, startSec, durSec) {
|
|
111
|
+
const stepSec = 0.136;
|
|
112
|
+
const startIdx = Math.floor(startSec * SAMPLE_RATE);
|
|
113
|
+
const numSamples = Math.floor(durSec * SAMPLE_RATE);
|
|
114
|
+
|
|
115
|
+
for (let i = 0; i < numSamples; i++) {
|
|
116
|
+
const idx = startIdx + i;
|
|
117
|
+
if (idx >= totalSamples) break;
|
|
118
|
+
const t = i / SAMPLE_RATE;
|
|
119
|
+
const noteIdx = Math.floor(t / stepSec) % leadNotes.length;
|
|
120
|
+
const f = noteToFreq(leadNotes[noteIdx]);
|
|
121
|
+
const tNote = t % stepSec;
|
|
122
|
+
const env = Math.exp(-tNote * 6.5);
|
|
123
|
+
const sample = analogSaw(f * t) * env * 0.085;
|
|
124
|
+
|
|
125
|
+
const pan = 0.5 + 0.35 * Math.sin(2 * Math.PI * 1.5 * t);
|
|
126
|
+
left[idx] += sample * (1.0 - pan);
|
|
127
|
+
right[idx] += sample * pan;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const padVel = tier === 1 ? 0.18 : 0.15;
|
|
132
|
+
const slots = [
|
|
133
|
+
{ start: 0.0, dur: 3.2 },
|
|
134
|
+
{ start: 3.2, dur: 1.8 },
|
|
135
|
+
{ start: 5.0, dur: 1.8 }
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
progression.forEach((step, i) => {
|
|
139
|
+
addPadChord(step.pad, slots[i].start, slots[i].dur, padVel);
|
|
140
|
+
if (tier >= 2) addBass(step.bass, slots[i].start, slots[i].dur);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (tier >= 2) {
|
|
144
|
+
addArp(tier === 3 ? arps[1] : arps[0], 0.6, 6.0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Stereo ping-pong delay
|
|
148
|
+
const delayL = Math.floor(0.27 * SAMPLE_RATE);
|
|
149
|
+
const delayR = Math.floor(0.36 * SAMPLE_RATE);
|
|
150
|
+
const feedback = 0.28;
|
|
151
|
+
|
|
152
|
+
for (let i = 0; i < totalSamples; i++) {
|
|
153
|
+
if (i >= delayL) left[i] += right[i - delayL] * feedback;
|
|
154
|
+
if (i >= delayR) right[i] += left[i - delayR] * feedback;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Soft boundary fade
|
|
158
|
+
const fadeLen = Math.floor(0.08 * SAMPLE_RATE);
|
|
159
|
+
for (let i = 0; i < fadeLen; i++) {
|
|
160
|
+
const s = i / fadeLen;
|
|
161
|
+
left[i] *= s;
|
|
162
|
+
right[i] *= s;
|
|
163
|
+
left[totalSamples - 1 - i] *= s;
|
|
164
|
+
right[totalSamples - 1 - i] *= s;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return createWavBuffer({ left, right, sampleRate: SAMPLE_RATE });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = { generateSynthwaveLoop };
|
package/src/synth/zen.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zen Ambient / Deep Meditation Procedural Synth Engine
|
|
3
|
+
* Singing Bowls + Celestial Morphing Pads + Zero Rhythm / Floating Harmony
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
SAMPLE_RATE,
|
|
8
|
+
noteToFreq,
|
|
9
|
+
sine,
|
|
10
|
+
makeRng,
|
|
11
|
+
pick,
|
|
12
|
+
ornamentRng,
|
|
13
|
+
createWavBuffer
|
|
14
|
+
} = require("./generator");
|
|
15
|
+
|
|
16
|
+
// Curated pad pairs, all within D major so any choice stays consonant with
|
|
17
|
+
// the bowls below.
|
|
18
|
+
const PAD_PAIRS = [
|
|
19
|
+
[["D3", "A3", "F#4", "B4"], ["G2", "D3", "B3", "E4"]],
|
|
20
|
+
[["A2", "E3", "C#4", "F#4"], ["D3", "A3", "F#4", "B4"]],
|
|
21
|
+
[["B2", "F#3", "D4", "A4"], ["G2", "D3", "B3", "E4"]]
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// D major pentatonic - the safest set to strike a bowl on over any pad above.
|
|
25
|
+
const BOWL_NOTES = ["D4", "E4", "F#4", "A4", "B4", "D5"];
|
|
26
|
+
|
|
27
|
+
function generateZenLoop(durationSec = 7.2, tier = 2, seed = 0) {
|
|
28
|
+
const rng = makeRng(seed);
|
|
29
|
+
const pads = pick(rng, PAD_PAIRS);
|
|
30
|
+
|
|
31
|
+
const totalSamples = Math.floor(SAMPLE_RATE * durationSec);
|
|
32
|
+
const left = new Float64Array(totalSamples);
|
|
33
|
+
const right = new Float64Array(totalSamples);
|
|
34
|
+
const padVel = tier === 1 ? 0.10 : tier === 3 ? 0.14 : 0.12;
|
|
35
|
+
|
|
36
|
+
// 1. Slow Celestial Morphing Ambient Pads (D Major / Pentatonic)
|
|
37
|
+
const padChords = [
|
|
38
|
+
{ notes: pads[0], start: 0.0, dur: 4.0 },
|
|
39
|
+
{ notes: pads[1], start: 3.6, dur: 4.0 }
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
for (const chord of padChords) {
|
|
43
|
+
const startIdx = Math.floor(chord.start * SAMPLE_RATE);
|
|
44
|
+
const numSamples = Math.floor(chord.dur * SAMPLE_RATE);
|
|
45
|
+
|
|
46
|
+
for (const note of chord.notes) {
|
|
47
|
+
const f = noteToFreq(note);
|
|
48
|
+
for (let i = 0; i < numSamples; i++) {
|
|
49
|
+
const idx = startIdx + i;
|
|
50
|
+
if (idx >= totalSamples) break;
|
|
51
|
+
const t = i / SAMPLE_RATE;
|
|
52
|
+
|
|
53
|
+
// Very slow ambient swell attack and release
|
|
54
|
+
const attack = Math.min(1.0, t * 0.9);
|
|
55
|
+
const rel = t > chord.dur - 1.2 ? Math.min(1.0, (chord.dur - t) / 1.2) : 1.0;
|
|
56
|
+
const env = attack * rel;
|
|
57
|
+
|
|
58
|
+
// Dual detuned pure sines for warm chorus drift
|
|
59
|
+
const sL = sine(f * 0.998 * t);
|
|
60
|
+
const sR = sine(f * 1.002 * t);
|
|
61
|
+
|
|
62
|
+
left[idx] += sL * env * padVel;
|
|
63
|
+
right[idx] += sR * env * padVel;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 2. Tibetan Singing Bowl Simulation (Resonant harmonic overtones with natural acoustic beating)
|
|
69
|
+
function addSingingBowl(baseFreq, startSec, pan = 0.5) {
|
|
70
|
+
const startIdx = Math.floor(startSec * SAMPLE_RATE);
|
|
71
|
+
const ringSamples = Math.floor(4.5 * SAMPLE_RATE);
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < ringSamples; i++) {
|
|
74
|
+
const idx = startIdx + i;
|
|
75
|
+
if (idx >= totalSamples) break;
|
|
76
|
+
const t = i / SAMPLE_RATE;
|
|
77
|
+
const env = Math.exp(-t * 0.85);
|
|
78
|
+
|
|
79
|
+
// Tibetan bowls produce fundamental + 2.76x overtone with a 2Hz beat
|
|
80
|
+
const beat = 1.0 + 0.15 * Math.sin(2 * Math.PI * 2.1 * t);
|
|
81
|
+
const f1 = baseFreq;
|
|
82
|
+
const f2 = baseFreq * 2.76;
|
|
83
|
+
const f3 = baseFreq * 5.4;
|
|
84
|
+
|
|
85
|
+
const sample = (
|
|
86
|
+
sine(f1 * t) * 0.70 * beat +
|
|
87
|
+
sine(f2 * t) * 0.22 * Math.exp(-t * 1.5) +
|
|
88
|
+
sine(f3 * t) * 0.08 * Math.exp(-t * 3.5)
|
|
89
|
+
) * env * 0.16;
|
|
90
|
+
|
|
91
|
+
left[idx] += sample * (1.0 - pan);
|
|
92
|
+
right[idx] += sample * pan;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Drawn regardless of tier so every tier ornaments the same piece.
|
|
97
|
+
const orn = ornamentRng(seed);
|
|
98
|
+
const strikes = [0.4, 4.0, 2.2, 5.6].map((time) => ({
|
|
99
|
+
note: pick(orn, BOWL_NOTES),
|
|
100
|
+
time,
|
|
101
|
+
pan: 0.35 + orn() * 0.3
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
// Tier 1 floats on sparse bowls; higher tiers fill the space
|
|
105
|
+
addSingingBowl(noteToFreq(strikes[0].note), strikes[0].time, strikes[0].pan);
|
|
106
|
+
addSingingBowl(noteToFreq(strikes[1].note), strikes[1].time, strikes[1].pan);
|
|
107
|
+
|
|
108
|
+
if (tier >= 2) {
|
|
109
|
+
addSingingBowl(noteToFreq(strikes[2].note), strikes[2].time, strikes[2].pan);
|
|
110
|
+
addSingingBowl(noteToFreq(strikes[3].note), strikes[3].time, strikes[3].pan);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Tier 3: deep sustained drone underneath for long prompts
|
|
114
|
+
if (tier >= 3) {
|
|
115
|
+
const droneF = noteToFreq("D2");
|
|
116
|
+
for (let i = 0; i < totalSamples; i++) {
|
|
117
|
+
const t = i / SAMPLE_RATE;
|
|
118
|
+
const swell = 0.6 + 0.4 * Math.sin(2 * Math.PI * 0.07 * t);
|
|
119
|
+
const sample = (sine(droneF * t) * 0.8 + sine(droneF * 2.0 * t) * 0.2) * swell * 0.07;
|
|
120
|
+
left[i] += sample;
|
|
121
|
+
right[i] += sample;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 3. Deep Stereo Spatial Reverberation
|
|
126
|
+
const delayL = Math.floor(0.42 * SAMPLE_RATE);
|
|
127
|
+
const delayR = Math.floor(0.56 * SAMPLE_RATE);
|
|
128
|
+
const feedback = 0.32;
|
|
129
|
+
|
|
130
|
+
for (let i = 0; i < totalSamples; i++) {
|
|
131
|
+
if (i >= delayL) left[i] += right[i - delayL] * feedback;
|
|
132
|
+
if (i >= delayR) right[i] += left[i - delayR] * feedback;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Seamless crossfade boundary
|
|
136
|
+
const fadeLen = Math.floor(0.12 * SAMPLE_RATE);
|
|
137
|
+
for (let i = 0; i < fadeLen; i++) {
|
|
138
|
+
const s = i / fadeLen;
|
|
139
|
+
left[i] *= s;
|
|
140
|
+
right[i] *= s;
|
|
141
|
+
left[totalSamples - 1 - i] *= s;
|
|
142
|
+
right[totalSamples - 1 - i] *= s;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return createWavBuffer({ left, right, sampleRate: SAMPLE_RATE });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = { generateZenLoop };
|