Remy Sharp
October 06, 2017
110

# Recreating the ZX Spectrum loader with Web APIs

This talk is about using new technology to replicate an old, reasonably useless, technology: replicating the ZX Spectrum tape loader audio and visuals (but without the tape…), and sharing what I learned along the way.

October 06, 2017

## Transcript

1. Recreating the
Remy Sharp
@REM

2. What?
Why!?

3. 1986

4. Sound

5. 1.Pilot tone
2.data

6. 1.Pilot tone
2.data

7. 1.Pilot tone
2.data

8. What does
01110010
sound like?

9. * 3.5Hmz

10. * 3.5Hmz
* 1 t-state = 1/3,500,000

11. * 3.5Hmz
* 1 t-state = 1/3,500,000
* 1T = 1/2 pulse wave

12. * 3.5Hmz
* 1 t-state = 1/3,500,000
* 1T = 1/2 pulse wave
* 1 bit of data = 2 equal
pulses

13. * 3.5Hmz
* 1 t-state = 1/3,500,000
* 1T = 1/2 pulse wave
* 1 bit of data = 2 equal
pulses
* binary 0 = 855T

14. * 3.5Hmz
* 1 t-state = 1/3,500,000
* 1T = 1/2 pulse wave
* 1 bit of data = 2 equal
pulses
* binary 0 = 855T
* binary 1 = 1710T

15. What does
01110010
LOOK like?

16. http://teropa.info/blog/2016/08/04/sine-waves.html

17. Circles &
Timing

18. const T = 1/3500000;
const SAMPLE_RATE = 44100; // 44.1Mhz
const ONE = 1710 * 2; // 2 = HIGH + LOW
const asHz = pulse => 1 / (T * pulse);
const toRadian = hz => hz * Math.PI * 2;
function generateSample(output) {
const length = ONE * T * SAMPLE_RATE;
for (let i = 0; i < length; i++) {
const time = i / SAMPLE_RATE;
const angle = time * toRadian(asHz(ONE));
// store a square wave
output[i] = Math.sin(angle) < 0 ? -1 : 1;
}
}

19. 000100110000000000000000000000110110001101101111011011100110111001100101011000110111010000110100001000000010000001110010000000000000000000000000000000001000000010111011
011101000000000011111111001000011011101011000011001000100111101101011100001111100000001011001101000000010001011000010001011011101100001100000001000011000000000011001101
001111000010000000010001011110101100001100000001010000000000000011001101001111000010000011001001000101100000011000001100010000110100111101001110010011100100010101000011
010101000010000000110100000100000000011100010001000000010001011000001010000011001001000010010000100100001001000010010000100100001001000000010110000010110000110010010000
100100001001000010010000100100001001000010010000000101100000110000001100100100001001000010010000100100001001000010010000100100000001011000001101000011001001000010010000
100100001001000010010000100100001001000000010110000011100000110010010000100100001001000010010000100100001001000010010000000101100000111100001100100100001001000010010000
100100001001000010010000100100000000000000011000001111000111111001111110001111000001100000000000100001010001001100000000000000000000001101100011011011110110111001101110
011001010110001101110100001101000010000000100000011100100000000000000000000000000000000010000000101110110111010000000000111111110010000110111010110000110010001001111011
010111000011111000000010110011010000000100010110000100010110111011000011000000010000110000000000110011010011110000100000000100010111101011000011000000010100000000000000
110011010011110000100000110010010001011000000110000011000100001101001111010011100100111001000101010000110101010000100000001101000001000000000111000100010000000100010110
000010100000110010010000100100001001000010010000100100001001000010010000000101100000101100001100100100001001000010010000100100001001000010010000100100000001011000001100
000011001001000010010000100100001001000010010000100100001001000000010110000011010000110010010000100100001001000010010000100100001001000010010000000101100000111000001100
100100001001000010010000100100001001000010010000100100000001011000001111000011001001000010010000100100001001000010010000100100001001000000000000000110000011110001111110
011111100011110000011000000000001000010100010011000000000000000000000011011000110110111101101110011011100110010101100011011101000011010000100000001000000111001000000000
000000000000000000000000100000001011101101110100000000001111111100100001101110101100001100100010011110110101110000111110000000101100110100000001000101100001000101101110
110000110000000100001100000000001100110100111100001000000001000101111010110000110000000101000000000000001100110100111100001000001100100100010110000001100000110001000011
010011110100111001001110010001010100001101010100001000000011010000010000000001110001000100000001000101100000101000001100100100001001000010010000100100001001000010010000
100100000001011000001011000011001001000010010000100100001001000010010000100100001001000000010110000011000000110010010000100100001001000010010000100100001001000010010000
000101100000110100001100100100001001000010010000100100001001000010010000100100000001011000001110000011001001000010010000100100001001000010010000100100001001000000010110
000011110000110010010000100100001001000010010000100100001001000010010000000000000001100000111100011111100111111000111100000110000000000010000101000100110000000000000000
000000110110001101101111011011100110111001100101011000110111010000110100001000000010000001110010000000000000000000000000000000001000000010111011011101000000000011111111
001000011011101011000011001000100111101101011100001111100000001011001101000000010001011000010001011011101100001100000001000011000000000011001101001111000010000000010001
011110101100001100000001010000000000000011001101001111000010000011001001000101100000011000001100010000110100111101001110010011100100010101000011010101000010000000110100
000100000000011100010001000000010001011000001010000011001001000010010000100100001001000010010000100100001001000000010110000010110000110010010000100100001001000010010000
100100001001000010010000000101100000110000001100100100001001000010010000100100001001000010010000100100000001011000001101000011001001000010010000100100001001000010010000
100100001001000000010110000011100000110010010000100100001001000010010000100100001001000010010000000101100000111100001100100100001001000010010000100100001001000010010000
100100000000000000011000001111000111111001111110001111000001100000000000100001010001001100000000000000000000001101100011011011110110111001101110011001010110001101110100
001101000010000000100000011100100000000000000000000000000000000010000000101110110111010000000000111111110010000110111010110000110010001001111011010111000011111000000010
110011010000000100010110000100010110111011000011000000010000110000000000110011010011110000100000000100010111101011000011000000010100000000000000110011010011110000100000
110010010001011000000110000011000100001101001111010011100100111001000101010000110101010000100000001101000001000000000111000100010000000100010110000010100000110010010000
100100001001000010010000100100001001000010010000000101100000101100001100100100001001000010010000100100001001000010010000100100000001011000001100000011001001000010010000
100100001001000010010000100100001001000000010110000011010000110010010000100100001001000010010000100100001001000010010000000101100000111000001100100100001001000010010000
100100001001000010010000100100000001011000001111000011001001000010010000100100001001000010010000100100001001000000000000000110000011110001111110011111100011110000011000
000000001000010100010011000000000000000000000011011000110110111101101110011011100110010101100011011101000011010000100000001000000111001000000000000000000000000000000000
100000001011101101110100000000001111111100100001101110101100001100100010011110110101110000111110000000101100110100000001000101100001000101101110110000110000000100001100
000000001100110100111100001000000001000101111010110000110000000101000000000000001100110100111100001000001100100100010110000001100000110001000011010011110100111001001110
010001010100001101010100001000000011010000010000000001110001000100000001000101100000101000001100100100001001000010010000100100001001000010010000100100000001011000001011
000011001001000010010000100100001001000010010000100100001001000000010110000011000000110010010000100100001001000010010000100100001001000010010000000101100000110100001100
100100001001000010010000100100001001000010010000100100000001011000001110000011001001000010010000100100001001000010010000100100001001000000010110000011110000110010010000
100100001001000010010000100100001001000010010000000000000001100000111100011111100111111000111100000110000000000010000101000100110000000000000000000000110110001101101111
011011100110111001100101011000110111010000110100001000000010000001110010000000000000000000000000000000001000000010111011011101000000000011111111001000011011101011000011
001000100111101101011100001111100000001011001101000000010001011000010001011011101100001100000001000011000000000011001101001111000010000000010001011110101100001100000001
010000000000000011001101001111000010000011001001000101100000011000001100010000110100111101001110010011100100010101000011010101000010000000110100000100000000011100010001
000000010001011000001010000011001001000010010000100100001001000010010000100100001001000000010110000010110000110010010000100100001001000010010000100100001001000010010000
000101100000110000001100100100001001000010010000100100001001000010010000100100000001011000001101000011001001000010010000100100001001000010010000100100001001000000010110
000011100000110010010000100100001001000010010000100100001001000010010000000101100000111100001100100100001001000010010000100100001001000010010000100100000000000000011000
001111000111111001111110001111000001100000000000100001010001001100000000000000000000001101100011011011110110111001101110011001010110001101110100001101000010000000100000
011100100000000000000000000000000000000010000000101110110111010000000000111111110010000110111010110000110010001001111011010111000011111000000010110011010000000100010110
000100010110111011000011000000010000110000000000110011010011110000100000000100010111101011000011000000010100000000000000110011010011110000100000110010010001011000000110
000011000100001101001111010011100100111001000101010000110101010000100000001101000001000000000111000100010000000100010110000010100000110010010000100100001001000010010000
100100001001000010010000000101100000101100001100100100001001000010010000100100001001000010010000100100000001011000001100000011001001000010010000100100001001000010010000
100100001001000000010110000011010000110010010000100100001001000010010000100100001001000010010000000101100000111000001100100100001001000010010000100100001001000010010000
100100000001011000001111000011001001000010010000100100001001000010010000100100001001000000000000000110000011110001111110011111100011110000011000000000001000010100010011
000000000000000000000011011000110110111101101110011011100110010101100011011101000011010000100000001000000111001000000000000000000000000000000000100000001011101101110100
000000001111111100100001101110101100001100100010011110110101110000111110000000101100110100000001000101100001000101101110110000110000000100001100000000001100110100111100
001000000001000101111010110000110000000101000000000000001100110100111100001000001100100100010110000001100000110001000011010011110100111001001110010001010100001101010100
001000000011010000010000000001110001000100000001000101100000101000001100100100001001000010010000100100001001000010010000100100000001011000001011000011001001000010010000
100100001001000010010000100100001001000000010110000011000000110010010000100100001001000010010000100100001001000010010000000101100000110100001100100100001001000010010000
100100001001000010010000100100000001011000001110000011001001000010010000100100001001000010010000100100001001000000010110000011110000110010010000100100001001000010010000
100100001001000010010000000000000001100000111100011111100111111000111100000110000000000010000101000100110000000000000000000000110110001101101111011011100110111001100101
011000110111010000110100001000000010000001110010000000000000000000000000000000001000000010111011011101000000000011111111001000011011101011000011001000100111101101011100
001111100000001011001101000000010001011000010001011011101100001100000001000011000000000011001101001111000010000000010001011110101100001100000001010000000000000011001101
001111000010000011001001000101100000011000001100010000110100111101001110010011100100010101000011010101000010000000110100000100000000011100010001000000010001011000001010
000011001001000010010000100100001001000010010000100100001001000000010110000010110000110010010000100100001001000010010000100100001001000010010000000101100000110000001100
100100001001000010010000100100001001000010010000100100000001011000001101000011001001000010010000100100001001000010010000100100001001000000010110000011100000110010010000
100100001001000010010000100100001001000010010000000101100000111100001100100100001001000010010000100100001001000010010000100100000000000000011000001111000111111001111110

20. Problem:

21. this.node = ctx.createScriptProcessor(bufferSize, 1, 1);
this.node.onaudioprocess = audioProcessingEvent => {
const channel = 0;
const inputBuffer = audioProcessingEvent.inputBuffer;
const input = inputBuffer.getChannelData(channel);
// then we'll read the values for own processing
// copy the input directly across to the output
const outputBuffer = audioProcessingEvent.outputBuffer;
const output = outputBuffer.getChannelData(channel);
inputBuffer.copyFromChannel(output, channel, channel);
};
// constructor continues...

22. Otherwise:

23. Visuals

24. IMAGE >
...> binary
...> audio

25. IMAGE >
...> binary
...> audio
...> Canvas

26. IMAGE >
...> binary
...> audio

27. * Draw image into canvas

28. * Draw image into canvas
* export canvas as blob

29. * Draw image into canvas
* export canvas as blob
as binary string

30. * Draw image into canvas
* export canvas as blob
as binary string
* convert char to binary

31. * Draw image into canvas
* export canvas as blob
as binary string
* convert char to binary
* TURN binary INTO audio

32. function imageToBlob(img) {
const c = document.createElement('canvas');
const ctx = c.getContext('2d');
c.width = img.width;
c.height = img.height;
ctx.drawImage(img, 0, 0);
return new Promise(resolve => {
c.toBlob(file => resolve(file));
})
}

33. function imageToBlob(img) {
const c = document.createElement('canvas');
const ctx = c.getContext('2d');
c.width = img.width;
c.height = img.height;
ctx.drawImage(img, 0, 0);
return new Promise(resolve => {
c.toBlob(file => resolve(file));
})
}

34. function fileToBinary(blob) {
return new Promise(resolve => {
const binary = [];
for (let i = 0; i < result.length; i++) {
const char = result[i];
binary.push(charToBinary(char));
}
resolve(
binary.reduce((acc, byte) => {
return acc.concat(byte.split(''));
}, [])
);
};
});
}

35. function fileToBinary(blob) {
return new Promise(resolve => {
const binary = [];
for (let i = 0; i < result.length; i++) {
const char = result[i];
binary.push(charToBinary(char));
}
resolve(
binary.reduce((acc, byte) => {
return acc.concat(byte.split(''));
}, [])
);
};
});
}

36. function charToBinary(chr) {
return char
.charCodeAt(0) // R = 82
.toString(2) // 82 = 1010010
.padStart(8, '0'); // 1010010 = 01010010
}

37. Reception

38. Accessibility

39. Seizures #SZR

40. Vestibular
disorders
#dzy

41. IMAGE >
...> binary
...> audio
...> Canvas?

42. * SAMple audio

43. * SAMple audio
* detect edge

44. * SAMple audio
* detect edge
* length = 1/2 pulse

45. * SAMple audio
* detect edge
* length = 1/2 pulse
* read 2nd pulse, ?=== len

46. * SAMple audio
* detect edge
* length = 1/2 pulse
* read 2nd pulse, ?=== len
* Bit = len == 855 ? 0 : 1

47. * SAMple audio
* detect edge
* length = 1/2 pulse
* read 2nd pulse, ?=== len
* Bit = len == 855 ? 0 : 1
* Make bytes from 8 bits

48. * SAMple audio
* detect edge
* length = 1/2 pulse
* read 2nd pulse, ?=== len
* Bit = len == 855 ? 0 : 1
* Make bytes from 8 bits
*

49. * SAMple audio
* detect edge
* length = 1/2 pulse
* read 2nd pulse, ?=== len
* Bit = len == 855 ? 0 : 1
* Make bytes from 8 bits
*
* make image from bytes

if (!buffer.length) {
return null;
}
let last = null;
let point = buffer.shift();
pulseBuffer.push(point);
do {
// search for when the buffer point crosses the zero threshold
if (last !== null) {
// important: when we hit an edge, the data doesn't include the edge
if (edge(point, last)) {
// create a new array and return that instead
const res = Array.from(pulseBuffer);
// reset the buffer
pulseBuffer = [];
return res;
}
}
pulseBuffer.push(point);
last = point;
} while ((point = buffer.shift()));
return null; // no edge found
}

51. pulse => bit?

52. PULSE = 1 / sample rate * (HI count)
BIT = PULSE === 855 ? 0 : 1;

53. BIT => Bytes

54. 4b FF 80 32
00 a1 42 02
BB 80 c3 20
d1 d9 ff 02

55. 45 seconds
later...

56. if (data.filter(Boolean).length === 0) {
return;
}

57. What did I
learn?

58. Why?