Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Recreating the ZX Spectrum loader with Web APIs

Remy Sharp
October 06, 2017

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.

Remy Sharp

October 06, 2017
Tweet

More Decks by Remy Sharp

Other Decks in Technology

Transcript

  1. Recreating the
    ZX Spectrum loader
    Remy Sharp
    @REM

    View Slide

  2. What?
    Why!?

    View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. 1986

    View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. View Slide

  14. View Slide

  15. View Slide

  16. Sound

    View Slide

  17. 1.Pilot tone
    2.data

    View Slide

  18. 1.Pilot tone
    2.data

    View Slide

  19. 1.Pilot tone
    2.data

    View Slide

  20. View Slide

  21. View Slide

  22. View Slide

  23. View Slide

  24. What does
    01110010
    sound like?

    View Slide

  25. * 3.5Hmz

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  30. * 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

    View Slide

  31. What does
    01110010
    LOOK like?

    View Slide

  32. View Slide

  33. View Slide

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

    View Slide

  35. Circles &
    Timing

    View Slide

  36. 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;
    }
    }

    View Slide

  37. 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

    View Slide

  38. View Slide

  39. Problem:
    Buffers Bad

    View Slide

  40. 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
    this.read(input, performance.now());
    // copy the input directly across to the output
    const outputBuffer = audioProcessingEvent.outputBuffer;
    const output = outputBuffer.getChannelData(channel);
    inputBuffer.copyFromChannel(output, channel, channel);
    };
    // constructor continues...

    View Slide

  41. Otherwise:

    View Slide

  42. Visuals

    View Slide

  43. IMAGE >
    ...> binary
    ...> audio

    View Slide

  44. IMAGE >
    ...> binary
    ...> audio
    ...> Canvas

    View Slide

  45. IMAGE >
    ...> binary
    ...> audio

    View Slide

  46. * Draw image into canvas

    View Slide

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

    View Slide

  48. * Draw image into canvas
    * export canvas as blob
    * File Reader API to read
    as binary string

    View Slide

  49. * Draw image into canvas
    * export canvas as blob
    * File Reader API to read
    as binary string
    * convert char to binary

    View Slide

  50. * Draw image into canvas
    * export canvas as blob
    * File Reader API to read
    as binary string
    * convert char to binary
    * TURN binary INTO audio

    View Slide

  51. 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));
    })
    }

    View Slide

  52. 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));
    })
    }

    View Slide

  53. function fileToBinary(blob) {
    return new Promise(resolve => {
    const reader = new window.FileReader();
    reader.onloadend = () => {
    const binary = [];
    const result = reader.result;
    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(''));
    }, [])
    );
    };
    reader.readAsBinaryString(blob);
    });
    }

    View Slide

  54. function fileToBinary(blob) {
    return new Promise(resolve => {
    const reader = new window.FileReader();
    reader.onloadend = () => {
    const binary = [];
    const result = reader.result;
    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(''));
    }, [])
    );
    };
    reader.readAsBinaryString(blob);
    });
    }

    View Slide

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

    View Slide

  56. View Slide

  57. Reception

    View Slide

  58. View Slide

  59. Accessibility

    View Slide

  60. Seizures #SZR

    View Slide

  61. Vestibular
    disorders
    #dzy

    View Slide

  62. View Slide

  63. IMAGE >
    ...> binary
    ...> audio
    ...> Canvas?

    View Slide

  64. * SAMple audio

    View Slide

  65. * SAMple audio
    * detect edge

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  71. * 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

    View Slide

  72. export function loadEdge1(buffer) {
    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
    }

    View Slide

  73. pulse => bit?

    View Slide

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

    View Slide

  75. BIT => Bytes

    View Slide

  76. View Slide

  77. View Slide

  78. View Slide

  79. View Slide

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

    View Slide

  81. 45 seconds
    later...

    View Slide

  82. View Slide

  83. View Slide

  84. View Slide

  85. View Slide

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

    View Slide

  87. View Slide

  88. What did I
    learn?

    View Slide

  89. Why?

    View Slide

  90. View Slide

  91. demos: zx.isthe.link
    checkout
    Thanks,
    @rem

    View Slide