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());