#include #include #include #include #include #include #include #include // <-- needed for std::max using namespace std; /* Original Mario-Kart-inspired chiptune: - SNES-like pulse waves (duty 12.5% / 25% / 50%) - Simple drums (kick/snare/hat) using noise & envelopes - Bass + lead + chord blips - Stereo panning & slight detune - Seamless-ish 2x loop Output: kart_loop.wav (stereo, 16-bit, 44.1kHz) */ struct Note { double pitch; double length; double vel {1.0}; // default to full volume so notes aren’t silent }; static inline double clamp1(double x){ return x < -1.0 ? -1.0 : (x > 1.0 ? 1.0 : x); } // simple linear interpolation used by ADSR static inline double myLerp(double a, double b, double t){ return a + (b-a)*t; } int main() { const double half {pow(2.0, 1.0/12.0)}; const double whole {half*half}; const double C {261.63}; const double D {C*whole}; const double E {D*whole}; const double F {E*half}; const double G {F*whole}; const double A {G*whole}; const double B {A*whole}; const uint32_t sampleRate {210u * 210u}; // 44100 Hz const double q {0.30}; const int LOOPS {2}; // ===== MELODY ===== const vector leadPattern { {C, q}, {E, q}, {G, q}, {C*2, q}, {B, q}, {G, q}, {E, q}, {C, q}, {C, q}, {E, q}, {G, q}, {E, q}, {A, q}, {C*2, q}, {A, q}, {E, q}, {F, q}, {A, q}, {C*2, q}, {A, q}, {G, q}, {B, q}, {D*2, q}, {B, q}, {E, q}, {G, q}, {B, q}, {G, q}, {F, q}, {A, q}, {C*2, q}, {A, q}, {G, q}, {A, q}, {B, q}, {C*2, q}, {B, q}, {A, q}, {G, q}, {E, q}, {C, q}, {G, q}, {E, q}, {C, q*2} }; // ===== BASS ===== const vector bassPattern { {C/2, q}, {C/2, q}, {C/2, q}, {C/2, q}, {A/2, q}, {A/2, q}, {A/2, q}, {A/2, q}, {F/2, q}, {F/2, q}, {F/2, q}, {F/2, q}, {G/2, q}, {G/2, q}, {G/2, q}, {G/2, q}, {A/2, q}, {A/2, q}, {A/2, q}, {A/2, q}, {C/2, q}, {C/2, q}, {G/2, q}, {G/2, q} }; // ===== CHORD BLIPS ===== const vector chordPattern { {E, q/2}, {G, q/2}, {E, q/2}, {G, q/2}, {C, q/2}, {E, q/2}, {C, q/2}, {E, q/2}, {A, q/2}, {C, q/2}, {A, q/2}, {C, q/2}, {B, q/2}, {D, q/2}, {B, q/2}, {D, q/2}, {A, q/2}, {C, q/2}, {E, q/2}, {G, q/2} }; // ===== DRUM GRID ===== const double barLen {4*q}; const vector kickBeats {0.0, 2*q}; const vector snareBeats {1*q, 3*q}; const vector hatBeats { 0.0, 0.5*q, 1*q, 1.5*q, 2*q, 2.5*q, 3*q, 3.5*q }; // durations const double leadDur = accumulate(leadPattern.begin(), leadPattern.end(), 0.0, [](double s, const Note& n){ return s + n.length; }); const double bassDur = accumulate(bassPattern.begin(), bassPattern.end(), 0.0, [](double s, const Note& n){ return s + n.length; }); const double drumsDur = 4*barLen; // portable max-of-three (avoid initializer_list overload issues) const double oneLoopDur = std::max(leadDur, std::max(bassDur, drumsDur)); const double duration {oneLoopDur * LOOPS}; const int numSamples {static_cast(duration * sampleRate)}; vector L(numSamples, 0.0), R(numSamples, 0.0); // ===== helpers ===== auto pulse = [](double phase, double duty)->double { double norm {phase / (2.0*M_PI)}; norm -= floor(norm); return (norm < duty) ? 1.0 : -1.0; }; auto panAdd = [&](int i, double s, double pan) { // pan [-1..1] left..right double ang = (pan * M_PI/4.0); double cl = cos(ang); double cr = sin(ang) + 1.0; double norm = 1.0 / (cl + cr); L[i] += s * cl * norm; R[i] += s * cr * norm; }; auto envADSR = [](int s, int total, int A, int D, double S, int R)->double { if (s < A) return (double)s / (double)max(1, A); if (s < A + D) { double t = (double)(s - A) / (double)max(1, D); return myLerp(1.0, S, t); } if (s < total - R) return S; int pos = s - (total - R); double t = (double)pos / (double)max(1, R); return myLerp(S, 0.0, t); }; // noise rng uint32_t rng {0x1234567u}; auto frand = [&](){ rng ^= rng << 13; rng ^= rng >> 17; rng ^= rng << 5; return ((rng & 0x7FFFFFFFu) / (double)0x7FFFFFFF) * 2.0 - 1.0; }; // ===== renderers ===== // Lead auto renderNotesPulse = [&](const vector& seq, double duty, double gain, double panCenter, double panSwing){ double cursor = 0.0; const double vibHz = 5.5; const double vibDepth = 0.004; double phase2 = 0.0; // persistent detune phase for (int loop = 0; loop < LOOPS; ++loop) { for (const auto& n : seq) { const int ns = (int)(n.length * sampleRate); const int A=120, D=90, R=180; const double S=0.7; double phase = 0.0; for (int s = 0; s < ns; ++s) { int i = (int)((cursor + (double)s/sampleRate) * sampleRate); if (i >= numSamples) break; double t = (cursor + (double)s/sampleRate); double pan = panCenter + panSwing * sin(2*M_PI*0.25*t); if (n.pitch > 0.0) { double env = envADSR(s, ns, A, D, S, R) * n.vel; double f1 = n.pitch * (1.0 + vibDepth * sin(2*M_PI*vibHz*t)); double f2 = n.pitch * 1.005; double w1 = 2.0*M_PI*f1 / sampleRate; double w2 = 2.0*M_PI*f2 / sampleRate; phase += w1; if (phase > 2*M_PI) phase -= 2*M_PI; phase2 += w2; if (phase2> 2*M_PI) phase2-= 2*M_PI; double y = 0.65*pulse(phase, duty) + 0.35*pulse(phase2, 0.25); y *= env * gain; panAdd(i, y, pan); } } cursor += n.length; } } }; // Bass auto renderBass = [&](const vector& seq){ double cursor = 0.0; for (int loop=0; loop= numSamples) break; if (n.pitch > 0.0) { double env = envADSR(s, ns, A, D, S, R) * (n.vel*0.9); double w = 2.0*M_PI*n.pitch / sampleRate; phase += w; if (phase>2*M_PI) phase -= 2*M_PI; double y = pulse(phase, 0.5) * env * 0.55; panAdd(i, y, -0.25); } } cursor += n.length; } } }; // Chord blips auto renderChords = [&](const vector& seq){ double cursor = 0.0; for (int loop=0; loop= numSamples) break; if (n.pitch > 0.0) { double env = envADSR(s, ns, A, D, S, R) * n.vel; double w = 2.0*M_PI*n.pitch / sampleRate; phase += w; if (phase>2*M_PI) phase -= 2*M_PI; double y = pulse(phase, 0.125) * env * 0.35; panAdd(i, y, +0.30); } } cursor += n.length; } } }; // Drums auto renderDrums = [&](){ const double loopBars = 4.0; for (int loop=0; loop=numSamples) break; double t = (double)s / sampleRate; double f = f0 * (1.0 - 0.85 * (double)s/len); double env = exp(-7.0*t); double y = sin(2*M_PI*f*t) * env * 0.9; panAdd(i, y*0.6, 0.0); } } // Snare for (double tb : snareBeats) { double t0 = baseTime + tb; int start = (int)(t0 * sampleRate); int len = (int)(0.16 * sampleRate); for (int s=0; s=numSamples) break; double t = (double)s / sampleRate; double body = sin(2*M_PI*180.0*t) * exp(-10.0*t); double noise = frand() * exp(-20.0*t); double y = 0.25*body + 0.75*noise; panAdd(i, y*0.7, 0.0); } } // Hi-hats for (double tb : hatBeats) { double t0 = baseTime + tb; int start = (int)(t0 * sampleRate); int len = (int)(0.05 * sampleRate); double lp = 0.0; for (int s=0; s=numSamples) break; double t = (double)s / sampleRate; double n = frand(); lp = lp + 0.1*(n - lp); double hp = n - lp; double env = exp(-35.0*t); double y = hp * env * 0.5; panAdd(i, y, +0.45); } } } }; // render all parts renderNotesPulse(leadPattern, 0.25, 0.55, 0.0, 0.20); renderBass(bassPattern); renderChords(chordPattern); renderDrums(); // master & interleave const double masterGain = 0.9; vector interleaved(numSamples*2); for (int i=0; i(l * numeric_limits::max()); interleaved[2*i+1] = static_cast(r * numeric_limits::max()); } // WAV header (stereo, 16-bit) struct WAVHeader { char chunkId[4]; uint32_t chunkSize; char format[4]; char subchunk1Id[4]; uint32_t subchunk1Size; uint16_t audioFormat; uint16_t numChannels; uint32_t sampleRate; uint32_t byteRate; uint16_t blockAlign; uint16_t bitsPerSample; char subchunk2Id[4]; uint32_t subchunk2Size; } header { {'R','I','F','F'}, 0, {'W','A','V','E'}, {'f','m','t',' '}, 16, 1, 2, sampleRate, sampleRate * 2 * 16 / 8, static_cast(2 * 16 / 8), 16, {'d','a','t','a'}, 0 }; header.subchunk2Size = static_cast(interleaved.size() * sizeof(int16_t)); header.chunkSize = 36 + header.subchunk2Size; ofstream file("kart_loop.wav", ios::binary); if (!file) { cerr << "Couldn't open output file.\n"; return EXIT_FAILURE; } file.write(reinterpret_cast(&header), sizeof header); file.write(reinterpret_cast(interleaved.data()), header.subchunk2Size); file.close(); return EXIT_SUCCESS; }