Dial-up internet

The sound of connecting to the Internet before the 2000s.

Based on: windytan.com/2012/11…dialup-pictured.html

Log in to post a comment.

// Dial-up internet. Created by Reinder Nijhoff 2023
// https://dittytoy.net/ditty/91185791e3
//
// The sound of connecting to the Internet before the 2000s.
//
// https://www.windytan.com/2012/11/the-sound-of-dialup-pictured.html
// https://oona.windytan.com/posters/dialup-final.png
//

ditty.bpm = 60; // so one tick is one second

//
// Synth that emulates the sound of sending data
//
const data = synth.def(class {
    // https://en.wikipedia.org/wiki/Modem
    // https://en.wikipedia.org/wiki/Phase-shift_keying
    constructor(options) {
        this.freq = options.carrier * Math.PI * 2;
        this.phase = 0;
        this.amp = 1;
        
        this.bitTimeOut = 0;
    }   
    
    process(note, env, tick, options) {
        this.bitTimeOut -= ditty.dt;

        if (this.bitTimeOut < 0) {
            const bits = Math.random() * 4 | 0;
            switch (options.cmd) {
                case 'ASK': // Amplitude-shift keying
                    this.amp = (bits + 1) / 5;
                    break;
                case 'FSK': // Frequency-shift keying
                    const c = options.carrier;
                    this.freq = bits < 2 || tick < 0.1 ? c[0] : c[1];     
                    this.freq *= Math.PI * 2;
                    break;
                case 'PSK': // Phase-shift keying
                    this.phase += bits * Math.PI / 2;
                    break;
            }
            this.bitTimeOut = 1 / options.baud;
        }
        
        return Math.sin( this.phase += this.freq * ditty.dt ) * this.amp;
    }
}, { env: adsr, attack: 0.01, release: 0.01, carrier: 1920, baud: 2400, amp: 0.35, cmd: 'PSK' } );

//
// Dial synth
//
const dial = synth.def(class {
    constructor(options) {
        // Multi-frequency signaling
        this.freq = [];
        this.time = 0;
        
        // https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.8bis-199608-S!!PDF-E&type=items
        const freqTable = {
            // https://en.wikipedia.org/wiki/Call-progress_tone
            'dial': [350, 440],
            'ring': [440, 480],
            
            // V.8 bis Events - https://www.rfc-editor.org/rfc/rfc4734.html
            'ESiSeg' : [980],
            'ESrSeg' : [1650],
            'CRdSeg' : [1900],
            'CReSeg' : [400],
            'MRdSeg' : [1150],
            'MReSeg' : [650],
            'V8bISeg': [1375, 2002],           
            'V8bRSeg': [1529, 2225],
            
            'ANSam':   [2100], // disable echo suppression - should have phase reversals and amp modulation
        };
        
        if (freqTable[options.cmd]) {
            this.freq = freqTable[options.cmd];
        } else {
            // https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling
            const freqCol = [ 1209,	1336, 1477,	1633 ];
            const freqRow = [  697,  770,  852,  941 ];
            const index = '123A456B789C*0#D'.indexOf(options.cmd);
            if (index >= 0) {
                this.freq = [freqCol[ index % 4], freqRow[ index / 4 | 0] ];   
            }
        }
    }
    process(note, env, tick, options) {
        this.time += ditty.dt;
        
        // amplitude modulation
        const mod = options.mod ? lerp(options.mod[1], options.mod[2], .5 + .5 * Math.sin(options.mod[0] * ditty.time)) : 1;
        
        // phase reversals
        if (options.phaserev > 0 && (ditty.time - ditty.dt) % options.phaserev > ditty.time % options.phaserev) {
            this.time += 0.5 / this.freq[0];
        }
        
        return this.freq.reduce( (a, c) => a + Math.sin(c * this.time * Math.PI * 2), 0) * .5 * mod;
    }   
}, {env: adsr, attack: 0.01, release: 0.01, duration: 0.2, cmd: 'dial', mod: false, phaserev: 0});

const noise = synth.def( _ => Math.random()*2-1, {env: one});

//
// Second order lowpass filter without resonance (mono) by @athibaul
// https://dittytoy.net/ditty/49c3b85cc7
//
const lpf = filter.def(class {
    constructor(options) {
        this.s0 = 0;
        this.s1 = 0;
    }
    process(input, options) {
        this.a0 = clamp01(2*Math.PI * ditty.dt * options.cutoff);
        this.s0 += this.a0 * (input[0] - this.s0);
        this.s1 += this.a0 * (this.s0 - this.s1);
        //return this.s1; // Uncomment this line to get a cryptic error message
        return [this.s1, this.s1];
    }
}, { cutoff: 2500 });

//
// Helper function
//
function modem(cmd, dur, op = {}, slp = 0) {
    const options = Object.assign(op, { cmd, duration: dur + Math.random()*0.01 });
    
    if (cmd === 'ASK' || cmd === 'FSK' || cmd === 'PSK') {
        data.play(0, options);        
    } else {
        dial.play(0, options);
    }
    sleep(slp + dur + Math.random()*0.01);
}

//
// Dial-up sequence
//
loop( () => {
    noise.play(0, {duration: 16, amp: 0.015});
    
    sleep(1);
    
    modem('dial', 1.1, {}, -0.1 );
    
    const number = '0205350535';
    for (let i=0; i<number.length; i++) {
        modem( number[i], 0.1, {}, 0.1 );
    }
    
    sleep(1);
    
    // phase 1 - netwerk interaction
    
    modem('V8bISeg', 0.4);
    modem('CReSeg',  0.1);
    modem('V8bRSeg', 0.4);
    modem('CRdSeg',  0.1);
    modem('ESrSeg',  0.1);
    
    modem('FSK', 0.7, { baud: 300, carrier: [980, 1180] }, 0.1);
    modem('FSK', 0.8, { baud: 300, carrier: [1650, 1850] }, -0.2);
    modem('FSK', 0.3, { baud: 300, carrier: [980, 1180] }, 0.35);
    
    modem('ANSam',  2, { mod: [15 * Math.PI * 2, 0.8, 1.2], phaserev: 0.450 }, -1);
    modem('FSK', 2, { baud: 300, carrier: [980, 1180] }, -1);
    modem('FSK', 1, { baud: 300, carrier: [1650, 1850] });
    
    // phase 2 - probing / ranging
    
    modem('PSK', 0.1, { baud: 600, carrier: 1180 }, -0.1);
    modem('PSK', 0.1, { baud: 600, carrier: 1850 });
    
    // frequencies by srtuss
    
    for (let i=0; i<25; i++) {
        if(i != 5 && i != 7 && i != 11 && i != 15)
            sine.play(hz_to_midi(150+150*i), { duration: 0.355, attack: 0.01, release: 0.01, amp: (0.4+Math.random()*.2)/25});
    }
    sleep(.340);
    modem('PSK', 0.1, { baud: 600, carrier: 1850 });
    modem('PSK', 0.1, { baud: 600, carrier: 1180 });
    for (let i=0; i<25; i++) {
        if(i != 5 && i != 7 && i != 11 && i != 15)
            sine.play(hz_to_midi(150+150*i), { duration: 0.355, attack: 0.01, release: 0.01, amp: (0.4+Math.random()*.2)/25});
    }
    sleep(.340);
    
    modem('PSK', 0.1, { baud: 600, carrier: 1850 });
    modem('PSK', 0.1, { baud: 600, carrier: 1180 });
    sleep(.1);
    
    
    // phase 3 - equalizer and echo canceller training
    
    modem('PSK', 4, { baud: 600, carrier: 1920, amp: 0.3 }, -2);
    modem('PSK', 2, { baud: 1200, carrier: 1829, amp: 0.3 });
    
}, { sync: 16, amp: (tick) => clamp01(tick % 16) - clamp01((tick-1) % 16 - 15), name: 'modem'}).connect(lpf.create());