What is this package all about?
===============================

This is about integration of C64 content in your web site using JSIDPlay2.

Quickstart:
===========
If you don't care how it works and just want to jump-start to add it into your web-page,
just use the file set and upload to your web server.
I have added example HTML code for different use-cases. Use it or add the code to your web-site's HTML code.
test-sound.html - Choose a tune and play a SID
test-video.html - Choose a tune and play a SID while watching the video screen.
test-disk.html -  Choose a disk image, reset C64 and load first entry. For disk swap just choose another disk.
test-quad-core.html - Play QuadCore Demo (Play 4 C64 simultaneously, using 4 parallel worker threads).
c64jukebox.vue, c64jukebox.scss and app folder - This is a complex example using VUE and Bootstrap dependencies for web developers,
  the actual page you are watching.

Background Story:
=================

JSIDPlay2 is a Java based C64 emulator and since Java is not popular to be installed on the client side
for security reasons, taste or because it is not user friendly enough, i have therefore presumably a small user base.

On the client side in a browser, JavaScript dominates since the beginning as a de-facto standard for web development.
Even more modern frameworks are based on JavaScript.

But web content uses more and more logic and JavaScript is quite often megabytes of code to be executed on the client side. 
Web sites tend to get slower and slower due to this fact. Browser vendors have a lot of work to speed things up and are doing a great job.
The World Wide Web consortium (W3C) knows well about the problem and has created a new standard to not replace, but to
extend JavaScript by a new language called "Web Assembly". It can be executed near native speed and is supported widely by all browser vendors.

But they have learned from the past. Do you remember where Java shipped with a browser plugin and the security risk was always high?
Web Assembly in contrast can only be used for computational tasks. There is zero I/O possibilities.
That makes Web Assembly a safe technology. Whenever I/O operations must happen, JavaScript code is required.

Many programming languages can convert source code to web assembly including C/C++, Python and so on.
Java is no exception here, even though it is not a trivial task, since a garbage collector is always required to free memory during execution.
For Java there are several tools out there like GWT, TeaVM or CherpJ. Each of them with different approaches to generate web content from Java.
One of them - TeaVM - is a great project out there, that makes it possible to get Java code executed on the client side in a different form.
This magic is done using the intermediate language of Java, the class files, and execution by ahead-of-time compilation, that emits
output as JavaScript or Web Assembly.

Using class files, not Java source code - makes it possible to use it with other programming languages than Java as well, like Scala and Kotlin,
since these compiles to class files like Java. No source code is required with that approach.

TeaVM can be used with maven or gradle.
No other technology stack is required, just what we already use: maven and a Java IDE is just fine.

If you want to learn more about this fantastic project called TeaVM, please visit their web-site at "https://teavm.org/".

I got curious about the possibility to make use of this project, even though the documentation is sparse.
Is it able to use my emulation core and push a decent Java emulator like JSIDPlay2 to the client side?
It took me some weeks, but fair to say, there was only minimum problems I got to solve for my code base to make that happen.
Some of the problems:
1. If you need resources like ROMs, you need to read it during compilation phase and put it in the resulting version.
   Same for the PAL emulation color palette. It is 2MB big and calculation would be just too huge and slow for the client side.
   Therefore it's pre-calculated.
2. Are there bugs in TeaVM? I found very few. It is actively maintained, stable and ready for production use.
   Additionally some JDK class library code may not be completely supported, yet, but it is getting better and better.
   Reflection is only partly implemented, though.
3. Debugging Web Assembly is unsupported. I added debug messages for the console (optionally) and you can generate JavaScript for debugging.

Now, as a result, you can directly compare Web Assembly and JavaScript version of JSIDPlay2 to find out what works better for you.
Web assembly (Garbage Collector Proposal) is actually the fastest version right now and a version based on SharedArrayBuffers is now default
   to save transfer time of large buffers between worker thread and main thread (zero-copying approach).
   
If we talk about speed - the speed is so incredible fast, you won't believe it.
   Play 10 SIDs in parallel, use a sound expander or watch oscilloscope output live.
   We can play C64 content like SID music, executable programs (PRG) or even content from tape, disk or cartridge for your enjoyment on a mobile phone.

What does the package of JSIDPlay2 contain:
===========================================
So, the package contains the basic functionality of JSIDPlay2 (namely the package libsidplay).
A complete hardware setup with C64, Floppy, Datasette, Cartridge support and more.
I added some glue code to import/export I/O from or to JavaScript.

What files do I need?
=====================
This ZIP file contains several demo HTML pages on the root level just for you to learn how it works altogether with this README.
Use the related sub-directory depending on the version you need:
js/*							(JavaScript) or
js_em2015/*						(JavaScript EcmaScript 2015) or
wasm/*							(WebAssembly - deprecated version) or
wasm_gc/*						(WebAssembly Garbage Collector Proposal) or
wasm_gc_sab/*					(WebAssembly Garbage Collector Proposal using SharedArrayBuffer)

Each sub-directory contains the generated emulator file jsidplay2.wasm (Web Assembly) or jsidplay2.js (JavaScript).
All releases have an additional web worker (*-worker*.js) and Web Assembly requires a runtime in JavaScript (*-runtime*.js).
That's it. 2 files for JavaScript or 3 for Web Assembly and thats all you need.

Upload this file-set to your web server and then you need a bit JavaScript in your main page to start controlling the emulator.

What code do I need to control the emulator?
============================================
Since the emulator runs as a web worker (to not block the main thread), you will setup this worker, first, calling jsidplay2Worker(...).

Parameters of jsidplay2Worker:
What tune to play is answered by the first two parameters. "contents" and "tune name" are specifying a tune to play.
1. contents						- Uint8Array with the PSID, PRG, P00 or T64
2. tuneName						- the tune name must be present and contain a file extension one of ".sid", ".prg", ".p00", ".t64"
								With contents and tuneName left undefined the emulator just performs a RESET.
								If a cartridge is required, please specify the next similar two parameters.
3. cartContents					- Uint8Array with the cartridge
4. cartName						- the cartridge name
								The cartridge can be a multi purpose cartridge (CRT) like EasyFlash, Action Replay and more.
								REU can be attached at runtime using the appropriate business methods, only GeoRAM is currently not supported
								Some content needs a start command to load from disk or tape. Therefore you can set it with the next parameter.
5. command						- Start command to be used (e.g. 'LOAD"*",8,1\rRUN\r' or 'LOAD\rRUN\r').
6. Start song					- Many tunes contains more than one song to play. You can specify the song number here (choose 0 for the start song)

Code Snippet:
==================================================
      // image queue, required for video output, only (example implementation)
      function Queue() {
        var head, tail;
        return Object.freeze({
          enqueue(value) {
            const link = { value, next: undefined };
            tail = head ? (tail.next = link) : (head = link);
          },
          enqueueAll(queue) {
            if (queue.head) {
              if (head) {
                tail.next = queue.head;
              } else {
                head = queue.head;
              }
              tail = queue.tail;
            }
          },
          dequeue() {
            if (head) {
              var value = head.value;
              head = head.next;
              return value;
            }
            return undefined;
          },
          dequeueAll() {
            var dequeued = {
              head: head,
              tail: tail,
            };
            tail = head = undefined;
            return dequeued;
          },
          peek() {
            return head?.value;
          },
          clear() {
            tail = head = undefined;
          },
          isNotEmpty() {
            return head;
          },
        });
      }
      var canvas, canvasContext;
      var imageQueue = new Queue();
      var internalImageQueue = new Queue();
      var animateActive;
      let msPrev;
      let frames, actualFrames;
      var defaultClockSpeed = 50.124562474562474;
      var nthFrame = 0;

      function jsidplay2Worker(contents, tuneName, cartContents, cartName, command, startSong) {
        // terminate last emulation instance
        if (worker) {
          worker.terminate();
          worker = undefined;
        }
        audioContext = new AudioContext({
          latencyHint: "interactive",
          sampleRate: 48000,
        });
        // create a new emulator
        worker = new Worker("${teaVMFormat}/jsidplay2-${teaVMFormat}-worker.js", { type: '${teaVMWorkerAttrs}' });

        return new Promise((resolve, reject) => {
          // Everything starts with INITIALISE
          // You can configure browser console output here, which is useful for debug purposes
          worker.postMessage({
            eventType: "INITIALISE",
              eventData: {
                verbose: 0,		// Verbosity (level=0,1,2)
                quiet: false,	// Quiet (no output)
                debug: false,	// Enable/Disable additional debug messages
              },
          });

          const eventMap = {
            INITIALISED: function(eventData) {
              // If the emulation instance is initialized, the tune can be opened to play, but first...

              // ... do your settings here (these are dependent to the browsers audio context, therefore)
              worker.postMessage({
                eventType: "SET_SAMPLING_RATE",
                eventData: {
                  samplingRate: toSamplingRate(audioContext.sampleRate),
                },
              });
              worker.postMessage({
                eventType: "SET_BUFFER_SIZE",
                eventData: {
                  bufferSize: 3 * audioContext.sampleRate,
                },
              });
              worker.postMessage({
                eventType: "SET_AUDIO_BUFFER_SIZE",
                eventData: {
                  audioBufferSize: audioContext.sampleRate,
                },
              });
              worker.postMessage({
                eventType: "SET_WHATSSID",
                eventData: {
                  enable: false,
                  captureTime: 15,
                  matchStartTime: 15,
                  matchRetryTime: 15,
                  minimumRelativeConfidence: 4.5,
                },
              });
              
              // if you need additional configuration (set volume level, stereo mode, etc), this is the right place before the tune is opened.

              // When we are done, we open the tune to start playing              
              worker.postMessage({
                eventType: "OPEN",
                eventData: {
                  contents: contents,						// tune as Uint8Array
                  tuneName: tuneName,						// tune name with extension for type detection
                  startSong: startSong,						// a tune can contain several songs, you can specify the song to play here
                  nthFrame: nthFrame,						// we capture every Nth frame for performance reasons. 0 means no frame output at all (that means only audio)
                  sidWrites: false,							// If SID writes should be captured, set to true here
                  cartContents: cartContents,				// cartridge data as Uint8Array
                  cartName: cartName,						// cartridge name
                  command: command,							// Command to enter after C64 reset to play the tune a.g. Load commmand
                  songLength: 0,							// play length until tune gets stopped or 0 (use default play length)
                  sfxSoundExpander: false,					// Use SFX Sound Expander [FM-YAM alike]
                  sfxSoundExpanderType: 0					// SFX Sound Expander Type (OPL1: YM3526 = 0, OPL2: YM3812 = 1)
                },
              });

              nextTime = 0;
              playing = true;
              paused = false;
              
              // Only for video output:
              if (nthFrame > 0) {
                imageQueue.clear();
                internalImageQueue.clear();
                framesCounter = defaultClockSpeed / nthFrame;
                canvasContext.clearRect(0, 0, canvas.width, canvas.height);
      	  	    frames = actualFrames = 0;
                msPrev = window.performance.now()
                animate();
              }
            },
            OPENED: function(eventData) {
              // Every time a tune gets opened, we start the clocking here. After clocking, we clock again and so on.
              // Maybe we must insert media, because the tune requires it, but it's optional.
              insertDisk();
              insertTape();

              worker.postMessage({ eventType: "CLOCK" });
            },
            CLOCKED: function(eventData) {
              // The idea of sending IDLE states, where NO clocking is done at all is simple:
              // the emulator must not run and produce frames and samples faster than a person can watch, therefore the braking here.
              let clock = !paused;
              if (screen) {
                clock = clock &&
                  animateActive && (nextTime - audioContext.currentTime <= 1 || framesCounter < 50);
              } else {
                clock = clock && nextTime - audioContext.currentTime <= 1;
              }
              animateActive = false;
              if (clock) {
                worker.postMessage({ eventType: "CLOCK" });
              } else {
                // get on the brakes, do nothing (sleep time helps to reduce CPU usage and to reduce CPU fan noise)
                worker.postMessage({ eventType: "IDLE", eventData: { sleepTime: ${sleepTime} } });
              }
            },
            SAMPLES: function(eventData) {
              // the worker has produced a chunk of sound data. We create a stereo buffer and send it to the sound card
              var buffer = audioContext.createBuffer(2, eventData.length, audioContext.sampleRate);
              buffer.getChannelData(0).set(new Float32Array(eventData.left));
              buffer.getChannelData(1).set(new Float32Array(eventData.right));

              var sourceNode = audioContext.createBufferSource();
              sourceNode.buffer = buffer;
              sourceNode.connect(audioContext.destination);

              // some magic to stay in sync, please experiment for yourself
              if (nextTime == 0) {
                nextTime = audioContext.currentTime + 0.05; // add 50ms latency to work well across systems
              } else if (nextTime < audioContext.currentTime) {
                nextTime = audioContext.currentTime + 0.005; // if samples are not produced fast enough, add small hick-up and hope for better times
              }
              sourceNode.start(nextTime);
              imageQueue.enqueueAll(internalImageQueue.dequeueAll());
              nextTime += buffer.duration;
            },
            FRAME: function(eventData) {
              // the worker has produced a video frame to display. Since it does it 50 times per second,
              // we need a queue here and display one frame every 50 (PAL) or 60 (NTSC) per second
              internalImageQueue.enqueue({
                image: eventData.image,
              });
            },
            SID_WRITE: function(eventData) {
              // The worker notifies about a SID write to the SID chip. We can ignore/report or send it to another SID chip implementation.
              console.log("relTime=" + eventData.relTime + ", addr=" + eventData.addr + ", value=" + eventData.value);
            },
          };
          worker.addEventListener("message", function (event) {
            var thisFun = eventMap[event.data.eventType];

            if (thisFun) thisFun(event.data.eventData);
          });

          worker.addEventListener("error", function (error) {
            reject(error);
          });
        });
      }

      // periodically show frames during playback
      function animate() {
        animateActive = true;
        var msPerFrame = 1000 * nthFrame / defaultClockSpeed;
        if (playing) {
            window.requestAnimationFrame(animate)
        }
        const msNow = window.performance.now();
        if (msNow - msPrev > 1000) {
          // not called far too long? We give up synchronicity
          msPrev = window.performance.now();
        }
        const msPassed = msNow - msPrev;

        if (msPassed < msPerFrame) return

        const excessTime = msPassed - msPerFrame
        msPrev = msNow - excessTime

        if (!paused) {
          var elem = imageQueue.dequeue();
          if (elem) {
            canvasContext.drawImage(elem.image, 0, 0);
            actualFrames++;
          }
        }
        frames++
        if (frames * nthFrame >= defaultClockSpeed) {
          framesCounter = Math.min(50 / nthFrame, actualFrames);
          frames = 0;
          actualFrames = 0;
        }
      }

      function toSamplingRate(sampleRate) {
        switch (sampleRate) {
          case 44100:
            return "LOW";
          case 48000:
            return "MEDIUM";
          case 96000:
            return "HIGH";
        }
      }

=====================================
To communicate with the web worker, events are required as always.
Events consists of EventType and EventData.

The events can be imagined as a request and response ping pong protocol between your web site and the emulation web worker.
Nearly every command event has an answer event to report fulfillment.

For detailed information of the web worker events take a look inside README_API and jsidplay2-teavm.html

I hope this little explanation helps you to understand the basics.
The test-*.html files are pretty small, so everybody can jump-start and get a working result very quick.
If you need a more complex example look inside c64jukebox.vue

If you are missing features in the API or you are missing documentation or have any further questions,
please contact me by email: kschwiersch@yahoo.de
Any feedback is welcome!
