vibeaudio 0.6.1 → 0.6.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeaudio",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Procedural focus music while your AI coding tools (Claude Code, Codex, Cursor, Grok, Gemini, Copilot) think \u2014 a different arrangement per project.",
5
5
  "bin": {
6
6
  "vibeaudio": "bin/vibeaudio.js",
@@ -64,16 +64,26 @@ function generateChiptuneLoop(durationSec = 7.5, tier = 2, seed = 0) {
64
64
  const startIdx = Math.floor(m.start * secPerBeat * SAMPLE_RATE);
65
65
  const numSamples = Math.floor(m.dur * secPerBeat * SAMPLE_RATE);
66
66
 
67
+ // Accumulated, for the same reason as the arpeggio below, plus one of its
68
+ // own: `softPulse((f + vib) * t)` is not vibrato but a phase that jumps
69
+ // when the vibrato switches on at t > 0.15 - by (vib * 0.15) cycles, once
70
+ // per note. Accumulating makes `vib` an actual frequency deviation of
71
+ // +/-3.5Hz, which is what it was meant to be, and the switch-on changes
72
+ // only the rate.
73
+ let leadPhase = 0.0;
74
+
67
75
  for (let i = 0; i < numSamples; i++) {
68
76
  const idx = startIdx + i;
69
77
  if (idx >= totalSamples) break;
70
78
  const t = i / SAMPLE_RATE;
71
79
  const env = Math.min(1.0, t * 75.0) * Math.max(0.0, 1.0 - (t / (m.dur * secPerBeat)));
72
80
  const vib = t > 0.15 ? Math.sin(2 * Math.PI * 5.5 * t) * 3.5 : 0.0;
73
- const sample = softPulse((f + vib) * t) * env * 0.22;
81
+ const sample = softPulse(leadPhase) * env * 0.22;
74
82
 
75
83
  left[idx] += sample;
76
84
  right[idx] += sample;
85
+
86
+ leadPhase += (f + vib) / SAMPLE_RATE;
77
87
  }
78
88
  }
79
89
 
@@ -81,6 +91,22 @@ function generateChiptuneLoop(durationSec = 7.5, tier = 2, seed = 0) {
81
91
  const chords = variant.chords;
82
92
  const arpSpeed = 0.075;
83
93
 
94
+ // The phase is accumulated, not computed as `f * t`.
95
+ //
96
+ // This arpeggio steps to a new note every 75ms and has no envelope at all -
97
+ // it is meant to run continuously. With `softPulse(f * t)`, changing f while
98
+ // t keeps running jumps the phase by (f2 - f1) * t: hundreds of cycles a few
99
+ // seconds in, which lands the waveform on an arbitrary value. That is a
100
+ // click, 13 times a second, with no envelope to hide it - reported as static
101
+ // on 8bit, and at tier 1 every single step over 0.05 full-scale landed
102
+ // exactly on one of these boundaries.
103
+ //
104
+ // Accumulating instead means a new note changes the *rate* the phase
105
+ // advances, never its value, so the waveform stays continuous across the
106
+ // step. It is also how a real oscillator behaves.
107
+ let arpPhase = 0.0;
108
+ let shimmerPhase = 0.0;
109
+
84
110
  for (let i = 0; i < totalSamples; i++) {
85
111
  const t = i / SAMPLE_RATE;
86
112
  const beat = Math.floor(t / secPerBeat);
@@ -88,17 +114,20 @@ function generateChiptuneLoop(durationSec = 7.5, tier = 2, seed = 0) {
88
114
  const chord = chords[chordIdx];
89
115
  const noteIdx = Math.floor(t / arpSpeed) % chord.length;
90
116
  const f = noteToFreq(chord[noteIdx]);
91
- const sample = softPulse(f * t) * (tier === 1 ? 0.09 : 0.075);
117
+ const sample = softPulse(arpPhase) * (tier === 1 ? 0.09 : 0.075);
92
118
 
93
119
  left[i] += sample * 0.85;
94
120
  right[i] += sample * 0.85;
95
121
 
96
122
  // Tier 3: octave-up shimmer arpeggio doubling
97
123
  if (tier >= 3) {
98
- const shimmer = softPulse(f * 2.0 * t) * 0.03;
124
+ const shimmer = softPulse(shimmerPhase) * 0.03;
99
125
  left[i] += shimmer * 1.1;
100
126
  right[i] += shimmer * 0.9;
101
127
  }
128
+
129
+ arpPhase += f / SAMPLE_RATE;
130
+ shimmerPhase += (f * 2.0) / SAMPLE_RATE;
102
131
  }
103
132
 
104
133
  // 3. NES Triangle Bass