Thursday, June 25, 2020

building a bad paint program in react with parcel

I've had some success making the Atari Sound Forger, a webapp that lets you play with sound files representing noises an Atari 2600 can make by treating the computer keyboard like a specialized piano keyboard. It can even record the loops as you bang them out and play them back in browser or generate a small batari Basic program to listen on a real Atari or emulator!

One user ("Random Terrain", gatekeeper of the batari Basic command list suggested it would be more fun/useful if you could paint on the "piano roll" display ala Mario Paint or what not) - and damn it he's right.

The Sound Forger uses some semi-clever techniques with setInterval and setTimeout to let the browser do a fair job of pretending to be an Atari, but I kind of painted myself in a corner... fundamentally it's recording sound start and stop times in milliseconds (i.e. rather native to Javascript) and then extrapolating to the "frames" (60 per second) the Atari uses. (The sound engine is a pair of single voice/single timers that switches between notes and periods of silence) The algorithm screws up when you overlap sounds though... also while it worked pretty well for "sound on, sound off" approach, more complicated ADSR/sound envelope technique (i.e. hitting the note then reducing volume, like a piano striking a note) was going to be clunky at best.

So I decided to start from scratch, but grabbing tricks and techniques from my old code. Also I thought it would be good practice to switch into React, rather than doing yet another Vanilla JS project.

(Actually, it might make more sense to switch to P5.js, like I did for my playfield graphic editor playfieldpal-bB-DPC  - or maybe at least use a canvas-in-react approach which I would have to study up on, vs the "lets just throw DOM elements at it" approach I'm starting with)

I'm also a little burnt out on the "ALL THE THINGS!" approach of create-react-app, so I decided to fire up parcel. During that process, I (think I) had to update node and even download xcode on my new machine because of some js dependencies.) Also, the one bell-and-or-whistle I wanted was using CSS modules, so I had to follow a few more steps, running
npm add postcss-modules autoprefixer
and then making a .postcssrc file with
{
  "modules": true,
  "plugins": {
    "autoprefixer": {
      "grid": true
    }
  }
}
after that importing a "Widget.module.css" worked as expected and I had nice bundling of components with the CSS that applies to them only.

I decided to start with a simple Proof of Concept paint program... cutting to the chase I ended up the stuff I put in this codesandbox.io. (Out of laziness I punted and went back to normal CSS inclusion for the sandbox)

I used the useState hook, and to keep it simple I collapsed what might have been a 2D array into a map-like object by having the key be x + (y * width) (old programmer trick).

I'm plucking things out of the nativeEvent property of the event object my handleMouseMove function gets:
const { offsetX: x, offsetY: y, buttons } = e.nativeEvent;
I'm not quite sure what e.nativeEvent is... I could dump it out to console.log() ok, and pull out the stuff I need (offset values so the mouse position relative to the top left corner)  but if you go over the keys or call JSON.stringify on it it claims the only key is "isTrusted" (set to true).

So, it's a pretty lousy paint program, missing a lot of dots! (when I made a simpler editor, I used a trick of treating the previous mouse position and current as line endpoints to get all the dots inbetween). But for the app I have in mind, the grid is much less "fine" (it may even use clicks instead of drawing for the most part) and I think this technique (of putting small pixel-y <div>s  over the parent space for the dots) should work ok.

No comments:

Post a Comment