Pro Yearly is on sale from $80 to $50! »

Recreating the ZX Spectrum loader with Web APIs

C8b387c489181844b3ffc704fadc0f14?s=47 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.

C8b387c489181844b3ffc704fadc0f14?s=128

Remy Sharp

October 06, 2017
Tweet

Transcript

  1. Recreating the ZX Spectrum loader Remy Sharp @REM

  2. What? Why!?

  3. None
  4. None
  5. None
  6. 1986

  7. None
  8. None
  9. None
  10. None
  11. None
  12. None
  13. None
  14. None
  15. None
  16. Sound

  17. 1.Pilot tone 2.data

  18. 1.Pilot tone 2.data

  19. 1.Pilot tone 2.data

  20. None
  21. None
  22. None
  23. None
  24. What does 01110010 sound like?

  25. * 3.5Hmz

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

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

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

    1/2 pulse wave * 1 bit of data = 2 equal pulses
  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
  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
  31. What does 01110010 LOOK like?

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

  35. Circles & Timing

  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; } }
  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
  38. None
  39. Problem: Buffers Bad

  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...
  41. Otherwise:

  42. Visuals

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

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

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

  46. * Draw image into canvas

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

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

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

    * File Reader API to read as binary string * convert char to binary
  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
  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)); }) }
  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)); }) }
  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); }); }
  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); }); }
  55. function charToBinary(chr) { return char .charCodeAt(0) // R = 82

    .toString(2) // 82 = 1010010 .padStart(8, '0'); // 1010010 = 01010010 }
  56. None
  57. Reception

  58. None
  59. Accessibility

  60. Seizures #SZR

  61. Vestibular disorders #dzy

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

  64. * SAMple audio

  65. * SAMple audio * detect edge

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

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

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

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

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

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

    pulse * read 2nd pulse, ?=== len * Bit = len == 855 ? 0 : 1 * Make bytes from 8 bits * <Repeat> * make image from bytes
  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 }
  73. pulse => bit?

  74. PULSE = 1 / sample rate * (HI count) BIT

    = PULSE === 855 ? 0 : 1;
  75. BIT => Bytes

  76. None
  77. None
  78. None
  79. None
  80. 4b FF 80 32 00 a1 42 02 BB 80

    c3 20 d1 d9 ff 02
  81. 45 seconds later...

  82. None
  83. None
  84. None
  85. None
  86. if (data.filter(Boolean).length === 0) { return; }

  87. None
  88. What did I learn?

  89. Why?

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