Futuristic Cityscape

Experience the sounds of a distant futuristic city - a blend of technology and nature, where calmness meets bustling energy.

Log in to post a comment.

/*

===========================================================
===========================================================
================                         ==================
================  FUTURISTIC  CITYSCAPE  ==================
================                         ==================
===========================================================
===========================================================

Let's imagine an artificial environment set in the distant
future, specifically a large, bustling city on a different
planet. The city is filled with towering buildings and
advanced technology that makes everything run smoothly and
efficiently.

As you step into the city, the first thing you notice is the
hum of energy that fills the air. The buildings are powered
by advanced energy sources, and you can hear the low, steady
hum of machinery as they work to keep the city running.

Despite the technological hustle and bustle, there is a
sense of calmness in the air. A soft, ambient music can be
heard playing in the background, created by advanced AI
algorithms that know exactly what type of music will help
people feel relaxed and at ease.

As you walk down the streets, you can hear the gentle
whirring of electric cars and hoverbikes passing by. The
streets are clean and quiet, thanks to the advanced
transportation systems that are designed to minimize noise
pollution.

As you approach a park, you can hear the sound of water
trickling from a nearby fountain. The water is illuminated
by advanced bioluminescent technology, creating a beautiful
and calming display.

===========================================================

The text above was written by ChatGPT.
So were the title and description.
But the sound design is mine.

- Alexis THIBAULT, March 2023
*/

ditty.bpm = 60;
const TWOPI = 2*Math.PI;
const tri = (x) => 1 - 2 * Math.abs((x-(~~x)) - 0.5);

function rn(a) {
    // Random number between -a and +a
    return 2*a*(Math.random()-0.5);
}

function nsin(a) {
    // Not quite a sinusoid
    let x = a - (~~a);
    //return Math.sin(2*Math.PI*x);
    return x*(x-1)*(x-0.5)*20.785;
    //x -= 0.5; x *= 2 * 3.0786; return x - x**3/6 + x**5/(1*2*3*4*5) - x**7/(1*2*3*4*5*6*7);
}

function softclip(x) {
    return x<-1?-1:x>1?1:1.5*(1-x*x/3)*x;
}
function varsaw(p, formant) {
    let x = p-~~p;
    return (x - 0.5) * softclip(formant*x*(1-x));
}


class GrayNoise {
    constructor(bitDepth=16) {
        this.bitDepth = bitDepth;
        this.v = 1 << (bitDepth - 1);
        this.x = 0;
    }
    process() {
        let rnd = Math.random();
        // Compute a random 16-bit number,
        // e.g. 1000100101101100
        let m = ~~(rnd * (1 << this.bitDepth));
        // Find largest power of two divisor of m, 
        // e.g. 0000000000000100
        m &= -m;
        // Toggle that bit in v
        this.v ^= m;
        // Compute the corresponding value in the -1 to 1 range
        this.x = this.v / (1 << (this.bitDepth-1)) - 1;
        return this.x;
    }
}

let grayNoise = synth.def(class {
    constructor(o) {
        this.gn = new GrayNoise(o.bitDepth);
    }
    process(n,e,t,o) {
        return this.gn.process() * e.value;
    }
}, {bitDepth:5});

// =================== echo filter by srtuss =================== 
// https://dittytoy.net/ditty/cef878f9e1
class Delayline {
    constructor(n) {
        this.n = ~~n;
        this.p = 0;
        this.lastOut = 0;
        this.data = new Float32Array(n);
    }
    process(input) {
        this.lastOut = this.data[this.p];
        this.data[this.p] = input;
        if(++this.p >= this.n)
            this.p = 0;
        return this.lastOut;
    }
    tap(offset) {
        var x = this.p - offset - 1;
        x %= this.n;
        if(x < 0)
            x += this.n;
        return this.data[x];
    }
}
const echo = filter.def(class {
    constructor(opt) {
        this.lastOut = [0, 0];
        var division = opt.division || 3/4;
        var pan = clamp01((opt.pan || 0)*.5+.5);
        var sidetime = (opt.sidetime || 0) / ditty.dt;
        var time = 60 * division / ditty.bpm;
        this.fb = clamp(opt.feedback || 0, -1, 1);
        this.kl = 1-pan;
        this.kr = pan;
        this.wet = opt.wet || .5;
        this.stereo = isFinite(opt.stereo) ? opt.stereo : 1;
        var n = ~~(time / ditty.dt);
        this.delay = [new Delayline(n), new Delayline(n)];
        this.dside = new Delayline(~~sidetime);
        // --- Filtering added by athibaul
        this.a0 = clamp01(TWOPI*opt.cutoff*ditty.dt);
        debug.log("a0", this.a0);
        this.sfilt = [0, 0];
        // ---
    }
    process(inv, opt) {
        this.dside.process(inv[0]);
        var l = this.dside.lastOut * this.kl;
        var r = inv[1] * this.kr;
        var nextl = l + this.delay[1].lastOut * this.fb;
        var nextr = r + this.delay[0].lastOut * this.fb;
        // --- Filtering added by athibaul
        this.sfilt[0] += this.a0 * (this.delay[0].lastOut - this.sfilt[0]);
        this.sfilt[1] += this.a0 * (this.delay[1].lastOut - this.sfilt[1]);
        this.lastOut[0] = inv[0] + this.sfilt[0] * this.wet;
        this.lastOut[1] = inv[1] + this.sfilt[0] * this.wet;
        // ---
        this.delay[0].process(nextl);
        this.delay[1].process(nextr);
        if(this.stereo != 1) {
            var m = (this.lastOut[0] + this.lastOut[1])*.5;
            var s = (this.lastOut[0] - this.lastOut[1])*.5;
            s *= this.stereo;
            this.lastOut[0] = m+s;
            this.lastOut[1] = m-s;
        }
        return this.lastOut;
    }
}, {sidetime: .01, division: 1/2, pan: .5, wet: .5, feedback: .6, stereo: 2, cutoff:3000});
// =======================================================================


const hum = synth.def(class {
    constructor(o) {
        this.f = o.f;
        this.df = 0;
        this.p = 0;
        this.a0 = ditty.dt * o.dfHz; // Frequency variation speed
        this.s1 = 0;
        this.a1 = ditty.dt * o.nco; // Noise cutoff
    }
    process(n,e,t,o) {
        this.df += this.a0 * (-this.df + rn(o.dfAmt));
        this.f = o.f * (1 + this.df);
        this.p += this.f * ditty.dt;
        let sig =  nsin(this.p);
        let noise = rn(1);
        this.s1 += this.a1 * (noise - this.s1);
        sig += this.s1 * Math.exp(o.nSpiky * sig) * o.nAmt;
        return sig * e.value;
    }
}, {f:55, dfHz:10, dfAmt:2, 
nco:1500, nSpiky:1, nAmt:0.2,
env:adsr, attack:1, duration:1000, release:10, amp:0.1});

// City hum
loop((i) => {
    hum.play(c4, {nco:1300, nAmt:0.2, attack:3,release:5,duration:10});
    sleep(10*Math.random());
}, {name:"Hum", amp:0.5})
.connect(echo.create({wet:0.9,feedback:0.6}));

// Car traffic
loop((i) => {
    let freq = 110 + rn(10);
    let df = 10 + rn(6);
    let direction = Math.random() < 0.5 ? 1 : -1;
    hum.play(0, {
        f: (t) => freq - df * Math.atan(-1 + 2*t/6),
        dfAmt:8, dfHz:1, nco:800, nAmt:0.5, nSpiky:0.3,
        attack:3, release:3, duration:3, curve:[3,0,-3],
        pan: (t) => (-1 + 2*t/6) * direction,
        amp: Math.random() * 0.5,
    });
    //hum.play(0, {f:110, dfAmt:8, dfHz:3, nco:800, nAmt:0.5, nSpiky:0.3, pan:1});
    sleep(6 * Math.random());
}, {name: "Car traffic", amp:0.5})
.connect(echo.create({wet:0.6, feedback:0.5}));

// Hoverbike traffic
loop((i) => {
    sleep(20 * Math.random());
    let freq = 60 + rn(20);
    let df = 2;
    let direction = Math.random() < 0.5 ? 1 : -1;
    hum.play(0, {
        f: (t) => freq - df * Math.atan(-1 + 2*t/10),
        dfAmt:20, dfHz:10, nco:2000, nAmt:0.03, nSpiky:4,
        attack:5, release:5, duration:5, curve:[3,0,-3],
        pan: (t) => (-1 + 2*t/10) * direction,
        amp: Math.random() * 0.3,
    });
    sleep(0.1);
    //hum.play(0, {f:110, dfAmt:8, dfHz:3, nco:800, nAmt:0.5, nSpiky:0.3, pan:1});
}, {name: "Hoverbike traffic", amp:0.3})
.connect(echo.create({wet:0.6, feedback:0.5}));


// ======================================
// Ambient music forked from my ditty "Blade Runner Pad"
// https://dittytoy.net/ditty/7d36343a29

const lushPad = synth.def(class {
    constructor(options) {
        this.freq = midi_to_hz(options.note);
        this.notes = options.notes;
        this.relfreqs = this.notes.map( (nn) => 2**((nn-options.note)/12) * options.detune);
        this.phase = 0;
    }
    
    process(note, env, tick, options) {
        this.phase += this.freq * ditty.dt;
        let relfreqs = this.relfreqs;
        let phases = relfreqs.map((rf) => this.phase * rf);
        let vols = relfreqs.map( (rf) => 1/Math.sqrt(rf));
        let pans = relfreqs.map( (rf,i) => nsin(i + 0.2*tick*Math.sqrt(6*rf)) );
        let vib = relfreqs.map( (rf,i) => 1 + 0.3*nsin(i + 2*tick*Math.sqrt(rf)) );
        let osc;
        if(options.type == "saw") {
            osc = phases.map( (p,i) => varsaw(p,30*env.value * vols[i] * vib[i]) * vols[i] * 0.1 * env.value);
        } else {
            osc = phases.map( (p,i) => Math.sin(TWOPI*p + vib[i]*nsin(p)*env.value) * vols[i] * 0.1 * env.value);
        }
        let osc2 = osc.map( (sig,i) => [sig * (1 - pans[i]), sig * (1 + pans[i])] );
        let sig = osc2.reduce( (a,b) => [a[0]+b[0],a[1]+b[1]], [0,0]);
        return sig;
    }
}, {detune:1, notes:[], type:"saw"});

const chimes = synth.def((p,e,t,o) => {
    let iom = e.value**4 * clamp01(0.8**(o.note-69)) * o.amp;
    let vib = 1+0.5*Math.cos(TWOPI*t*o.vibHz);
    return Math.sin(TWOPI*o.fc*p + iom * nsin(o.fm*p)) * e.value * vib;
}, {fc:1, fm:7.05});

ditty.bpm = 60;

/*
const mychords = [[c2,c3,g3,b3,d4,e4,a4],
                  [f2,c3,f3,g3,a3,d4,e4,b4], 
                  [e2,e3,a3,b3,c4,d4,g4],
                  [f2,c3,f3,b3,c4,d4,e4,a4],
                  [bb1,bb2,f3,a3,bb3,d4,f4,e5],
                  [ab1,ab2,eb3,ab3,c4,eb4,g4,d5],
                  [g2,f3,g3,a3,b3,c3,d4,e4]];
                  */

// Chord sequence inspired by a ChatGPT answer
const mychords = [
    [d2,a2,d3,fs3,a3,d4], // D
    [c2,c3,eb3,g3,c4,eb4,a4], // Cm6
    [d2,a2,d3,e4,fs3,a3,c4,fs4,a4,d5], // D9
    [c2,eb3,g3,bb3,d4,eb4,c4,a4], // Cm69
    //[c2,eb4,g2,bb3,d4,eb3,bb4], // Cm7 
    //[bb1,e4,ab3,bb2,db4,f3,ab4], // Bbm7b5 
    [g2,bb3,d3,f3,g3,d4,g4], // Gm7
    [f1,f2,ab3,c3,f3,eb4,f4,ab4,c5,f5], // Fm7 
    [eb1,eb2,bb2,db3,g3,bb3,db4,e4], // Eb7b9
];

loop((i) => {
    lushPad.play(c4, {notes:mychords.ring(i), attack:5, decay:3, sustain:0.7, duration:10, release:2, detune:1, type:"fm"});
    sleep(10);
}, {name:"FM synth", amp:0.2}).connect(echo.create({wet:0.9}));

loop((i) => {
    sleep(1);
    if(Math.floor(i / mychords.length) % 2 == 1) {
        lushPad.play(c4, {notes:mychords.ring(i), attack:5, decay:3, sustain:0.7, duration:10, release:2, amp:0.8, detune:1.01, type:"saw"});
    }
    sleep(9);
}, {name:"Saw synth", amp:0.3}).connect(echo.create());

loop((i) => {
    if(i % mychords.length<= 1) {
        return;
    }
    for(let j=0; j<100; j++) {
        let octave = 12 * Math.floor(Math.random() * 3);
        chimes.play(
            mychords.ring(i).choose() + octave, 
            {
                amp:1.5*Math.random(),attack:0.01, decay:0.5,sustain:0.5,duration:0.5,release:1, pan:Math.random()*2-1,
                vibHz:6**Math.random()
            }
        );
        sleep(Math.random()*0.5 + 0.05); // 0.3s on average
    }
}, {name:"Chimes", amp:0.2*0.1, sync:10})
.connect(echo.create({wet:0.9}));



loop((i) => {
    if(Math.floor(i / mychords.length) % 2 == 0) {
        return;
    }
    for(let j=0; j<100; j++) {
        let octave = 12 + 12 * Math.floor(Math.random() * 2);
        chimes.play(
            mychords.ring(i).choose() + octave, 
            {
                amp:1.5*Math.random(), attack:0.1, decay:0.2,sustain:0.5,duration:0.3,release:0.2, pan:rn(1),
                vibHz:6**Math.random(), fc:2, fm:1
            }
        );
        sleep(0.25); // 0.3s on average
    }
}, {name:"Chimes 2", amp:0.2*0.1, sync:10})
.connect(echo.create({wet:0.9}));


let waterFX = synth.def(class {
    constructor(o) {
        this.gn = new GrayNoise(o.bitDepth);
        this.p = 0;
    }
    process(n,e,t,o) {
        // Fast, random frequency variation simulates the noise of bubbles
        this.gn.process();
        let freq = o.fmin * (o.fmax/o.fmin)**this.gn.x;
        this.p += freq * ditty.dt;
        return Math.sin(2*Math.PI*this.p) * e.value;
    }
}, {bitDepth:5, fmin:100, fmax:10000, amp:0.1});

const bioLumTech = synth.def((p,e,t,o) => tri(p + 0.5 * tri(10*t)*tri(6.1803*t)) * nsin(t) * e.value);

loop((i) => {
    if(i % mychords.length == 4) {
        let width = adsr.create( { attack: 10, decay: 20, release: 0.2, duration: 1 });
        
        grayNoise.play(c4, {env:adsr, attack:8, release:25, curve:[0,0,0],bitDepth:8});
        grayNoise.play(c4, {env:adsr, attack:9, release:23, curve:[0,0,0], bitDepth:3, pan: () => 0.2 * width.value});
        grayNoise.play(c4, {env:adsr, attack:10, release:20, curve:[0,0,0], bitDepth:5, pan: () => -0.2 * width.value});
        
        
        bioLumTech.play(f5, {env:adsr, attack:10, release:20, curve:[0,0,0], amp:3});
        
        for(let j=0; j<3; j++) {
            waterFX.play(c4, {env:adsr, attack:10, release:22,curve:[0,0,0], fmin:1000, fmax:10000, bitDepth:9+j, pan:0.2*(j-1), amp:0.3});
        }
    }
}, {name:"Fountain", sync:10, amp:0.01})
.connect(echo.create());