audio partly working
This commit is contained in:
parent
115d2639de
commit
6776951aea
7 changed files with 191 additions and 696 deletions
15
Makefile
15
Makefile
|
@ -8,18 +8,23 @@ all : sample0.xex sample1.xex sample2.xex sample3.xex sample4.xex sample5.xex sa
|
||||||
%.s : %.jpg dither-image.js
|
%.s : %.jpg dither-image.js
|
||||||
node dither-image.js $< $@ $@.png
|
node dither-image.js $< $@ $@.png
|
||||||
|
|
||||||
|
chickens.s : chickens.wav pack-wav.js
|
||||||
|
node pack-wav.js $< $@
|
||||||
|
|
||||||
%.o : %.s
|
%.o : %.s
|
||||||
ca65 -v -t atari -o $@ $<
|
ca65 -v -t atari -o $@ $<
|
||||||
|
|
||||||
%.xex : %.o dither4.o
|
%.xex : %.o dither4.o chickens.o
|
||||||
ld65 -v -C atari-asm-xex.cfg -o $@ $< dither4.o
|
ld65 -v -C atari-asm-xex.cfg -o $@ $< dither4.o chickens.o
|
||||||
|
|
||||||
clean :
|
clean :
|
||||||
rm -f dither4.o
|
rm -f dither4.o
|
||||||
rm -f dither4.xex
|
rm -f dither4.xex
|
||||||
rm -f sample[0-6].o
|
|
||||||
rm -f sample[0-6].s
|
rm -f sample[0-6].s
|
||||||
|
rm -f sample[0-6].o
|
||||||
rm -f sample[0-6].xex
|
rm -f sample[0-6].xex
|
||||||
rm -f sample[0-6].png
|
rm -f sample[0-6].s.png
|
||||||
|
rm -f chickens.s
|
||||||
|
rm -f chickens.o
|
||||||
|
|
||||||
.dummy: sample0.s sample1.s sample2.s sample3.s sample4.s sample5.s sample6.s
|
.dummy: sample0.s sample1.s sample2.s sample3.s sample4.s sample5.s sample6.s chickens.s
|
BIN
chickens.wav
Normal file
BIN
chickens.wav
Normal file
Binary file not shown.
660
dither4.js
660
dither4.js
|
@ -1,660 +0,0 @@
|
||||||
function toLinear(val) {
|
|
||||||
// use a 2.4 gamma approximation
|
|
||||||
// this is BT.1886 compatible
|
|
||||||
// and simpler than sRGB
|
|
||||||
let unit = val / 255;
|
|
||||||
unit **= 2.4;
|
|
||||||
return unit * 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fromLinear(val) {
|
|
||||||
let unit = val / 255;
|
|
||||||
unit **= (1 / 2.4);
|
|
||||||
return unit * 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
class RGB {
|
|
||||||
constructor(r, g, b) {
|
|
||||||
this.r = r;
|
|
||||||
this.g = g;
|
|
||||||
this.b = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromHex(val) {
|
|
||||||
let r = val & 0xff;
|
|
||||||
let g = (val >> 8) & 0xff;
|
|
||||||
let b = (val >> 16) & 0xff;
|
|
||||||
return new RGB(r,g,b);
|
|
||||||
}
|
|
||||||
|
|
||||||
toLinear() {
|
|
||||||
return new RGB(
|
|
||||||
toLinear(this.r),
|
|
||||||
toLinear(this.g),
|
|
||||||
toLinear(this.b)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fromLinear() {
|
|
||||||
return new RGB(
|
|
||||||
fromLinear(this.r),
|
|
||||||
fromLinear(this.g),
|
|
||||||
fromLinear(this.b)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
cap() {
|
|
||||||
if (this.r < 0) {
|
|
||||||
this.r = 0;
|
|
||||||
}
|
|
||||||
if (this.g < 0) {
|
|
||||||
this.g = 0;
|
|
||||||
}
|
|
||||||
if (this.b < 0) {
|
|
||||||
this.b = 0;
|
|
||||||
}
|
|
||||||
if (this.r > 255) {
|
|
||||||
this.r = 255;
|
|
||||||
}
|
|
||||||
if (this.g > 255) {
|
|
||||||
this.g = 255;
|
|
||||||
}
|
|
||||||
if (this.b > 255) {
|
|
||||||
this.b = 255;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
add(other) {
|
|
||||||
return new RGB(
|
|
||||||
this.r + other.r,
|
|
||||||
this.g + other.g,
|
|
||||||
this.b + other.b
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
difference(other) {
|
|
||||||
return new RGB(
|
|
||||||
this.r - other.r,
|
|
||||||
this.g - other.g,
|
|
||||||
this.b - other.b
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
magnitude() {
|
|
||||||
return Math.sqrt(
|
|
||||||
this.r * this.r +
|
|
||||||
this.g * this.g +
|
|
||||||
this.b * this.b
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
distance(other) {
|
|
||||||
return this.difference(other).magnitude();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// snarfed from https://lospec.com/palette-list/atari-8-bit-family-gtia
|
|
||||||
// which was calculated with Retrospecs App's Atari 800 emulator
|
|
||||||
let palette256 = [
|
|
||||||
0x000000,
|
|
||||||
0x111111,
|
|
||||||
0x222222,
|
|
||||||
0x333333,
|
|
||||||
0x444444,
|
|
||||||
0x555555,
|
|
||||||
0x666666,
|
|
||||||
0x777777,
|
|
||||||
0x888888,
|
|
||||||
0x999999,
|
|
||||||
0xaaaaaa,
|
|
||||||
0xbbbbbb,
|
|
||||||
0xcccccc,
|
|
||||||
0xdddddd,
|
|
||||||
0xeeeeee,
|
|
||||||
0xffffff,
|
|
||||||
0x190700,
|
|
||||||
0x2a1800,
|
|
||||||
0x3b2900,
|
|
||||||
0x4c3a00,
|
|
||||||
0x5d4b00,
|
|
||||||
0x6e5c00,
|
|
||||||
0x7f6d00,
|
|
||||||
0x907e09,
|
|
||||||
0xa18f1a,
|
|
||||||
0xb3a02b,
|
|
||||||
0xc3b13c,
|
|
||||||
0xd4c24d,
|
|
||||||
0xe5d35e,
|
|
||||||
0xf7e46f,
|
|
||||||
0xfff582,
|
|
||||||
0xffff96,
|
|
||||||
0x310000,
|
|
||||||
0x3f0000,
|
|
||||||
0x531700,
|
|
||||||
0x642800,
|
|
||||||
0x753900,
|
|
||||||
0x864a00,
|
|
||||||
0x975b0a,
|
|
||||||
0xa86c1b,
|
|
||||||
0xb97d2c,
|
|
||||||
0xca8e3d,
|
|
||||||
0xdb9f4e,
|
|
||||||
0xecb05f,
|
|
||||||
0xfdc170,
|
|
||||||
0xffd285,
|
|
||||||
0xffe39c,
|
|
||||||
0xfff4b2,
|
|
||||||
0x420404,
|
|
||||||
0x4f0000,
|
|
||||||
0x600800,
|
|
||||||
0x711900,
|
|
||||||
0x822a0d,
|
|
||||||
0x933b1e,
|
|
||||||
0xa44c2f,
|
|
||||||
0xb55d40,
|
|
||||||
0xc66e51,
|
|
||||||
0xd77f62,
|
|
||||||
0xe89073,
|
|
||||||
0xf9a183,
|
|
||||||
0xffb298,
|
|
||||||
0xffc3ae,
|
|
||||||
0xffd4c4,
|
|
||||||
0xffe5da,
|
|
||||||
0x410103,
|
|
||||||
0x50000f,
|
|
||||||
0x61001b,
|
|
||||||
0x720f2b,
|
|
||||||
0x83203c,
|
|
||||||
0x94314d,
|
|
||||||
0xa5425e,
|
|
||||||
0xb6536f,
|
|
||||||
0xc76480,
|
|
||||||
0xd87591,
|
|
||||||
0xe986a2,
|
|
||||||
0xfa97b3,
|
|
||||||
0xffa8c8,
|
|
||||||
0xffb9de,
|
|
||||||
0xffcaef,
|
|
||||||
0xfbdcf6,
|
|
||||||
0x330035,
|
|
||||||
0x440041,
|
|
||||||
0x55004c,
|
|
||||||
0x660c5c,
|
|
||||||
0x771d6d,
|
|
||||||
0x882e7e,
|
|
||||||
0x993f8f,
|
|
||||||
0xaa50a0,
|
|
||||||
0xbb61b1,
|
|
||||||
0xcc72c2,
|
|
||||||
0xdd83d3,
|
|
||||||
0xee94e4,
|
|
||||||
0xffa5e4,
|
|
||||||
0xffb6e9,
|
|
||||||
0xffc7ee,
|
|
||||||
0xffd8f3,
|
|
||||||
0x1d005c,
|
|
||||||
0x2e0068,
|
|
||||||
0x400074,
|
|
||||||
0x511084,
|
|
||||||
0x622195,
|
|
||||||
0x7332a6,
|
|
||||||
0x8443b7,
|
|
||||||
0x9554c8,
|
|
||||||
0xa665d9,
|
|
||||||
0xb776ea,
|
|
||||||
0xc887eb,
|
|
||||||
0xd998eb,
|
|
||||||
0xe9a9ec,
|
|
||||||
0xfbbaeb,
|
|
||||||
0xffcbef,
|
|
||||||
0xffdff9,
|
|
||||||
0x020071,
|
|
||||||
0x13007d,
|
|
||||||
0x240b8c,
|
|
||||||
0x351c9d,
|
|
||||||
0x462dae,
|
|
||||||
0x573ebf,
|
|
||||||
0x684fd0,
|
|
||||||
0x7960e1,
|
|
||||||
0x8a71f2,
|
|
||||||
0x9b82f7,
|
|
||||||
0xac93f7,
|
|
||||||
0xbda4f7,
|
|
||||||
0xceb5f7,
|
|
||||||
0xdfc6f7,
|
|
||||||
0xf0d7f7,
|
|
||||||
0xffe8f8,
|
|
||||||
0x000068,
|
|
||||||
0x000a7c,
|
|
||||||
0x081b90,
|
|
||||||
0x192ca1,
|
|
||||||
0x2a3db2,
|
|
||||||
0x3b4ec3,
|
|
||||||
0x4c5fd4,
|
|
||||||
0x5d70e5,
|
|
||||||
0x6e81f6,
|
|
||||||
0x7f92ff,
|
|
||||||
0x90a3ff,
|
|
||||||
0xa1b4ff,
|
|
||||||
0xb2c5ff,
|
|
||||||
0xc3d6ff,
|
|
||||||
0xd4e7ff,
|
|
||||||
0xe5f8ff,
|
|
||||||
0x000a4d,
|
|
||||||
0x001b63,
|
|
||||||
0x002c79,
|
|
||||||
0x023d8f,
|
|
||||||
0x134ea0,
|
|
||||||
0x245fb1,
|
|
||||||
0x3570c2,
|
|
||||||
0x4681d3,
|
|
||||||
0x5792e4,
|
|
||||||
0x68a3f5,
|
|
||||||
0x79b4ff,
|
|
||||||
0x8ac5ff,
|
|
||||||
0x9bd6ff,
|
|
||||||
0xace7ff,
|
|
||||||
0xbdf8ff,
|
|
||||||
0xceffff,
|
|
||||||
0x001a26,
|
|
||||||
0x002b3c,
|
|
||||||
0x003c52,
|
|
||||||
0x004d68,
|
|
||||||
0x065e7c,
|
|
||||||
0x176f8d,
|
|
||||||
0x28809e,
|
|
||||||
0x3991af,
|
|
||||||
0x4aa2c0,
|
|
||||||
0x5bb3d1,
|
|
||||||
0x6cc4e2,
|
|
||||||
0x7dd5f3,
|
|
||||||
0x8ee6ff,
|
|
||||||
0x9ff7ff,
|
|
||||||
0xb0ffff,
|
|
||||||
0xc1ffff,
|
|
||||||
0x01250a,
|
|
||||||
0x023610,
|
|
||||||
0x004622,
|
|
||||||
0x005738,
|
|
||||||
0x05684d,
|
|
||||||
0x16795e,
|
|
||||||
0x278a6f,
|
|
||||||
0x389b80,
|
|
||||||
0x49ac91,
|
|
||||||
0x5abda2,
|
|
||||||
0x6bceb3,
|
|
||||||
0x7cdfc4,
|
|
||||||
0x8df0d5,
|
|
||||||
0x9effe5,
|
|
||||||
0xaffff1,
|
|
||||||
0xc0fffd,
|
|
||||||
0x04260d,
|
|
||||||
0x043811,
|
|
||||||
0x054713,
|
|
||||||
0x005a1b,
|
|
||||||
0x106b1b,
|
|
||||||
0x217c2c,
|
|
||||||
0x328d3d,
|
|
||||||
0x439e4e,
|
|
||||||
0x54af5f,
|
|
||||||
0x65c070,
|
|
||||||
0x76d181,
|
|
||||||
0x87e292,
|
|
||||||
0x98f3a3,
|
|
||||||
0xa9ffb3,
|
|
||||||
0xbaffbf,
|
|
||||||
0xcbffcb,
|
|
||||||
0x00230a,
|
|
||||||
0x003510,
|
|
||||||
0x044613,
|
|
||||||
0x155613,
|
|
||||||
0x266713,
|
|
||||||
0x377813,
|
|
||||||
0x488914,
|
|
||||||
0x599a25,
|
|
||||||
0x6aab36,
|
|
||||||
0x7bbc47,
|
|
||||||
0x8ccd58,
|
|
||||||
0x9dde69,
|
|
||||||
0xaeef7a,
|
|
||||||
0xbfff8b,
|
|
||||||
0xd0ff97,
|
|
||||||
0xe1ffa3,
|
|
||||||
0x001707,
|
|
||||||
0x0e2808,
|
|
||||||
0x1f3908,
|
|
||||||
0x304a08,
|
|
||||||
0x415b08,
|
|
||||||
0x526c08,
|
|
||||||
0x637d08,
|
|
||||||
0x748e0d,
|
|
||||||
0x859f1e,
|
|
||||||
0x96b02f,
|
|
||||||
0xa7c140,
|
|
||||||
0xb8d251,
|
|
||||||
0xc9e362,
|
|
||||||
0xdaf473,
|
|
||||||
0xebff82,
|
|
||||||
0xfcff8e,
|
|
||||||
0x1b0701,
|
|
||||||
0x2c1801,
|
|
||||||
0x3c2900,
|
|
||||||
0x4d3b00,
|
|
||||||
0x5f4c00,
|
|
||||||
0x705e00,
|
|
||||||
0x816f00,
|
|
||||||
0x938009,
|
|
||||||
0xa4921a,
|
|
||||||
0xb2a02b,
|
|
||||||
0xc7b43d,
|
|
||||||
0xd8c64e,
|
|
||||||
0xead760,
|
|
||||||
0xf6e46f,
|
|
||||||
0xfffa84,
|
|
||||||
0xffff99,
|
|
||||||
].map((hex) => RGB.fromHex(hex).toLinear());
|
|
||||||
|
|
||||||
function decimate(input, palette, n) {
|
|
||||||
// to brute-force, the possible palettes are:
|
|
||||||
// 255 * 254 * 253 = 16,386,810
|
|
||||||
//
|
|
||||||
// we could brute force it but that's a lot :D
|
|
||||||
// but can do some bisection :D
|
|
||||||
//
|
|
||||||
// need a fitness metric.
|
|
||||||
// each pixel in the dithered line gives a distance
|
|
||||||
// sum/average them? median? maximum?
|
|
||||||
// summing evens out the ups/downs from dithering
|
|
||||||
// but doesn't distinguish between two close and two distant options
|
|
||||||
// consider median, 90th-percentile, and max of abs(distance)
|
|
||||||
// consider doing the distance for each channel?
|
|
||||||
|
|
||||||
let line = input.slice();
|
|
||||||
|
|
||||||
// Apply dithering with given palette
|
|
||||||
let dither = (palette) => {
|
|
||||||
let fitness = new Float64Array(line.length);
|
|
||||||
let error = {
|
|
||||||
right: new RGB(0, 0, 0),
|
|
||||||
red: new Float64Array(line.length),
|
|
||||||
green: new Float64Array(line.length),
|
|
||||||
blue: new Float64Array(line.length),
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = new Int32Array(line.length);
|
|
||||||
let popularity = new Int32Array(palette.length);
|
|
||||||
|
|
||||||
// Try dithering with this palette.
|
|
||||||
for (let x = 0; x < line.length; x++) {
|
|
||||||
let rgb = line[x];
|
|
||||||
rgb = rgb.add(error.right);
|
|
||||||
//rgb.cap();
|
|
||||||
|
|
||||||
// find the closest possible color
|
|
||||||
let shortest = Infinity;
|
|
||||||
let pick = 1;
|
|
||||||
|
|
||||||
for (let i = 0; i < palette.length; i++) {
|
|
||||||
let diff = rgb.difference(palette[i]);
|
|
||||||
let dist = diff.magnitude();
|
|
||||||
if (dist < shortest) {
|
|
||||||
nextError = diff;
|
|
||||||
shortest = dist;
|
|
||||||
pick = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output[x] = pick;
|
|
||||||
popularity[pick]++;
|
|
||||||
|
|
||||||
/*
|
|
||||||
// horiz only
|
|
||||||
error.right.r = nextError.r;
|
|
||||||
error.right.g = nextError.g;
|
|
||||||
error.right.b = nextError.b;
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
error.red[x] += nextError.r;
|
|
||||||
error.green[x] += nextError.g;
|
|
||||||
error.blue[x] += nextError.b;
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
error.right.r = nextError.r / 2;
|
|
||||||
error.right.g = nextError.g / 2;
|
|
||||||
error.right.b = nextError.b / 2;
|
|
||||||
|
|
||||||
error.red[x] += nextError.r / 2;
|
|
||||||
error.green[x] += nextError.g / 2;
|
|
||||||
error.blue[x] += nextError.b / 2;
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (x == 159) {
|
|
||||||
error.red[x] += error.right.r;
|
|
||||||
error.green[x] += error.right.g;
|
|
||||||
error.blue[x] += error.right.b;
|
|
||||||
} else {
|
|
||||||
error.right.r = nextError.r / 4;
|
|
||||||
error.right.g = nextError.g / 4;
|
|
||||||
error.right.b = nextError.b / 4;
|
|
||||||
|
|
||||||
error.red[x - 1] += nextError.r / 4;
|
|
||||||
error.green[x - 1] += nextError.g / 4;
|
|
||||||
error.blue[x - 1] += nextError.b / 4;
|
|
||||||
|
|
||||||
error.red[x] += nextError.r / 4;
|
|
||||||
error.green[x] += nextError.g / 4;
|
|
||||||
error.blue[x] += nextError.b / 4;
|
|
||||||
|
|
||||||
error.red[x + 1] += nextError.r / 4;
|
|
||||||
error.green[x + 1] += nextError.g / 4;
|
|
||||||
error.blue[x + 1] += nextError.b / 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
error.right.r = nextError.r / 4;
|
|
||||||
error.right.g = nextError.g / 4;
|
|
||||||
error.right.b = nextError.b / 4;
|
|
||||||
|
|
||||||
error.red[x - 1] += nextError.r / 4;
|
|
||||||
error.green[x - 1] += nextError.g / 4;
|
|
||||||
error.blue[x - 1] += nextError.b / 4;
|
|
||||||
|
|
||||||
error.red[x] += nextError.r / 4;
|
|
||||||
error.green[x] += nextError.g / 4;
|
|
||||||
error.blue[x] += nextError.b / 4;
|
|
||||||
|
|
||||||
error.red[x + 1] += nextError.r / 4;
|
|
||||||
error.green[x + 1] += nextError.g / 4;
|
|
||||||
error.blue[x + 1] += nextError.b / 4;
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 442 is the 3d distance across the rgb cube
|
|
||||||
//fitness[x] = 442 - (nextError.magnitude());
|
|
||||||
//fitness[x] = 442 / (442 - nextError.magnitude());
|
|
||||||
fitness[x] = 255 / (256 - Math.max(0, nextError.r, nextError.g, nextError.b));
|
|
||||||
|
|
||||||
/*
|
|
||||||
fitness[x] = Math.max(
|
|
||||||
255 - Math.abs(nextError.r),
|
|
||||||
255 - Math.abs(nextError.g),
|
|
||||||
255 - Math.abs(nextError.b),
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
output,
|
|
||||||
palette,
|
|
||||||
fitness,
|
|
||||||
popularity,
|
|
||||||
error
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// black, red, blue, white
|
|
||||||
let rbw = [
|
|
||||||
palette256[0x00],
|
|
||||||
palette256[0x87],
|
|
||||||
palette256[0xf7],
|
|
||||||
palette256[0x0f],
|
|
||||||
];
|
|
||||||
|
|
||||||
let rgb = [
|
|
||||||
palette256[0x00],
|
|
||||||
palette256[0x87],
|
|
||||||
palette256[0xc7],
|
|
||||||
palette256[0xf7],
|
|
||||||
];
|
|
||||||
|
|
||||||
// grayscale
|
|
||||||
let gray = [
|
|
||||||
palette256[0x00],
|
|
||||||
palette256[0x05],
|
|
||||||
palette256[0x0a],
|
|
||||||
palette256[0x0f],
|
|
||||||
];
|
|
||||||
|
|
||||||
//palette = rgb;
|
|
||||||
//palette = rbw;
|
|
||||||
//palette = gray;
|
|
||||||
|
|
||||||
let start = Date.now();
|
|
||||||
let decimated = palette.slice();
|
|
||||||
|
|
||||||
while (decimated.length > n) {
|
|
||||||
let {popularity, fitness, output} = dither(decimated);
|
|
||||||
|
|
||||||
// Try dropping least used color on each iteration
|
|
||||||
let least = Infinity;
|
|
||||||
let pick = -1;
|
|
||||||
for (let i = 1; i < decimated.length; i++) {
|
|
||||||
if (decimated[i] === palette256[0]) {
|
|
||||||
continue; // keep black always
|
|
||||||
}
|
|
||||||
if (decimated[i] === palette256[0x0f]) {
|
|
||||||
continue; // keep white always
|
|
||||||
}
|
|
||||||
|
|
||||||
//let coolFactor = popularity[i];
|
|
||||||
|
|
||||||
let coolFactor = 0;
|
|
||||||
if (popularity[i]) {
|
|
||||||
for (let x = 0; x < line.length; x++) {
|
|
||||||
if (output[x] == i) {
|
|
||||||
//coolFactor += (fitness[x] ** 2);
|
|
||||||
coolFactor += (fitness[x] ** 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (coolFactor < least) {
|
|
||||||
pick = i;
|
|
||||||
least = coolFactor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let old = decimated.length;
|
|
||||||
//decimated.splice(pick, 1);
|
|
||||||
decimated = decimated.filter((rgb, i) => {
|
|
||||||
if (i == pick) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (rgb !== palette256[0] && popularity[i] == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (rgb !== palette256[0x0f] && popularity[i] == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
if (decimated.length >= old) {
|
|
||||||
console.log(decimated);
|
|
||||||
debugger;
|
|
||||||
throw new Error('logic error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let delta = Date.now() - start;
|
|
||||||
console.log(`${delta}ms for line`);
|
|
||||||
|
|
||||||
// Palette fits
|
|
||||||
if (decimated.length > 4) {
|
|
||||||
debugger;
|
|
||||||
}
|
|
||||||
console.log(decimated);
|
|
||||||
return dither(decimated);
|
|
||||||
}
|
|
||||||
|
|
||||||
function convert(source, sink) {
|
|
||||||
|
|
||||||
let width = 320;
|
|
||||||
let height = 192;
|
|
||||||
|
|
||||||
let canvas = sink;
|
|
||||||
let ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
// Draw the source image down, then grab it
|
|
||||||
// and re-draw it with custom palette & dither.
|
|
||||||
|
|
||||||
ctx.drawImage(source, 0, 0);
|
|
||||||
|
|
||||||
let imageData = ctx.getImageData(0, 0, width, height);
|
|
||||||
let {data} = imageData;
|
|
||||||
let nextError;
|
|
||||||
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
let line = new Uint8Array(data.buffer, y * width * 4, width * 4);
|
|
||||||
let input = [];
|
|
||||||
|
|
||||||
// Note we take two pixels because we're using the 160-wide 4-color mode
|
|
||||||
for (let x = 0; x < width; x += 2) {
|
|
||||||
let i = x >> 1;
|
|
||||||
let rgb = new RGB(
|
|
||||||
(line[x * 4 + 0] + line[x * 4 + 4]) / 2,
|
|
||||||
(line[x * 4 + 1] + line[x * 4 + 5]) / 2,
|
|
||||||
(line[x * 4 + 2] + line[x * 4 + 6]) / 2
|
|
||||||
).toLinear();
|
|
||||||
if (nextError) {
|
|
||||||
rgb.r += nextError.red[i];
|
|
||||||
rgb.g += nextError.green[i];
|
|
||||||
rgb.b += nextError.blue[i];
|
|
||||||
//rgb.cap();
|
|
||||||
}
|
|
||||||
input.push(rgb);
|
|
||||||
}
|
|
||||||
|
|
||||||
let {output, palette, error} = decimate(input, palette256, 4);
|
|
||||||
nextError = error;
|
|
||||||
|
|
||||||
for (let x = 0; x < width; x++) {
|
|
||||||
let rgb = palette[output[x >> 1]].fromLinear();
|
|
||||||
line[x * 4 + 0] = rgb.r;
|
|
||||||
line[x * 4 + 1] = rgb.g;
|
|
||||||
line[x * 4 + 2] = rgb.b;
|
|
||||||
line[x * 4 + 3] = 0xff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.putImageData(imageData, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function run() {
|
|
||||||
for (let i = 0; i < 7; i++) {
|
|
||||||
let source = document.querySelector('#source' + i);
|
|
||||||
let sink = document.querySelector('#sink' + i);
|
|
||||||
|
|
||||||
let doit = () => convert(source, sink);
|
|
||||||
|
|
||||||
if (source.complete) {
|
|
||||||
doit();
|
|
||||||
} else {
|
|
||||||
source.addEventListener('load', doit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
|
||||||
addEventListener('DOMContentLoaded', run);
|
|
||||||
} else {
|
|
||||||
run();
|
|
||||||
}
|
|
104
dither4.s
104
dither4.s
|
@ -1,13 +1,11 @@
|
||||||
SAVMSC = $58
|
SAVMSC = $58
|
||||||
;SDMCTL = $22F
|
|
||||||
;SDLSTL = $230
|
|
||||||
;SDLSTH = $231
|
|
||||||
COLPF0 = $D016
|
COLPF0 = $D016
|
||||||
COLPF1 = $D017
|
COLPF1 = $D017
|
||||||
COLPF2 = $D018
|
COLPF2 = $D018
|
||||||
COLPF3 = $D019
|
COLPF3 = $D019
|
||||||
COLBK = $D01A
|
COLBK = $D01A
|
||||||
|
|
||||||
|
AUDC1 = $D201
|
||||||
DMACTL = $D400
|
DMACTL = $D400
|
||||||
DLISTL = $D402
|
DLISTL = $D402
|
||||||
DLISTH = $D403
|
DLISTH = $D403
|
||||||
|
@ -24,10 +22,14 @@ temp1 = temp1l
|
||||||
temp2l = $82
|
temp2l = $82
|
||||||
temp2h = $83
|
temp2h = $83
|
||||||
temp2 = temp2l
|
temp2 = temp2l
|
||||||
|
sample_ptrl = $84
|
||||||
|
sample_ptrh = $85
|
||||||
|
sample_ptr = sample_ptrl
|
||||||
|
|
||||||
height = 192
|
height = 192
|
||||||
bytes_per_line = 40
|
bytes_per_line = 40
|
||||||
pages_per_frame = 32
|
pages_per_frame = 32
|
||||||
|
lines_per_frame = 262
|
||||||
|
|
||||||
.data
|
.data
|
||||||
|
|
||||||
|
@ -35,6 +37,8 @@ pages_per_frame = 32
|
||||||
.import palette2
|
.import palette2
|
||||||
.import palette3
|
.import palette3
|
||||||
.import bitmap
|
.import bitmap
|
||||||
|
.import audio_samples
|
||||||
|
.import audio_samples_end
|
||||||
|
|
||||||
displaylist:
|
displaylist:
|
||||||
; 24 lines overscan
|
; 24 lines overscan
|
||||||
|
@ -70,18 +74,24 @@ displaylist:
|
||||||
lda #.hibyte(bitmap)
|
lda #.hibyte(bitmap)
|
||||||
sta temp1h
|
sta temp1h
|
||||||
lda #.lobyte(framebuffer)
|
lda #.lobyte(framebuffer)
|
||||||
sta temp2h
|
sta temp2l
|
||||||
lda #.hibyte(framebuffer)
|
lda #.hibyte(framebuffer)
|
||||||
sta temp2h
|
sta temp2h
|
||||||
jsr copy_half_frame
|
jsr copy_half_frame
|
||||||
|
|
||||||
; Second half of bitmap has to be separately aligned
|
; Second half of bitmap has to be separately aligned
|
||||||
lda #.lobyte(framebuffer2)
|
lda #.lobyte(framebuffer2)
|
||||||
sta temp2h
|
sta temp2l
|
||||||
lda #.hibyte(framebuffer2)
|
lda #.hibyte(framebuffer2)
|
||||||
sta temp2h
|
sta temp2h
|
||||||
jsr copy_half_frame
|
jsr copy_half_frame
|
||||||
|
|
||||||
|
; Set up the audio sample buffer
|
||||||
|
lda #.lobyte(audio_samples)
|
||||||
|
sta sample_ptrl
|
||||||
|
lda #.hibyte(audio_samples)
|
||||||
|
sta sample_ptrh
|
||||||
|
|
||||||
; Disable display DMA
|
; Disable display DMA
|
||||||
lda #$00
|
lda #$00
|
||||||
sta DMACTL
|
sta DMACTL
|
||||||
|
@ -106,18 +116,73 @@ wait_vblank:
|
||||||
lda #$22
|
lda #$22
|
||||||
sta DMACTL
|
sta DMACTL
|
||||||
|
|
||||||
wait_loop:
|
; Wait for the next even scanline (VCOUNT is scan line / 2, so look for a change)
|
||||||
sta WSYNC
|
|
||||||
wait_start:
|
wait_start:
|
||||||
lda VCOUNT
|
lda VCOUNT
|
||||||
cmp #15
|
sta temp1
|
||||||
bne wait_loop
|
wait_loop:
|
||||||
ldy #0
|
lda VCOUNT
|
||||||
|
cmp temp1
|
||||||
|
beq wait_loop
|
||||||
|
|
||||||
each_scanline:
|
each_scanline:
|
||||||
tya
|
lda VCOUNT
|
||||||
|
; Resynchronize the scanline counter
|
||||||
|
; it'll fire on unused lines, but harmlessly
|
||||||
|
clc
|
||||||
|
sbc #15
|
||||||
|
asl
|
||||||
|
tay
|
||||||
|
|
||||||
|
.macro audio_prep
|
||||||
|
; audio sample; low nybble
|
||||||
|
ldx #0
|
||||||
|
lda (sample_ptr,x)
|
||||||
|
sta temp2
|
||||||
|
|
||||||
|
; high nybble
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
; set the volume-only bit
|
||||||
|
ora #$10
|
||||||
pha
|
pha
|
||||||
|
|
||||||
|
; low nybble
|
||||||
|
lda temp2
|
||||||
|
and #$0f
|
||||||
|
; set the volume-only bit
|
||||||
|
ora #$10
|
||||||
|
pha
|
||||||
|
.endmacro
|
||||||
|
|
||||||
|
.macro audio_inc
|
||||||
|
; Increment sample ptr
|
||||||
|
clc
|
||||||
|
lda sample_ptrl
|
||||||
|
adc #1
|
||||||
|
sta sample_ptrl
|
||||||
|
lda sample_ptrh
|
||||||
|
adc #0
|
||||||
|
sta sample_ptrh
|
||||||
|
|
||||||
|
lda sample_ptrh
|
||||||
|
cmp #.hibyte(audio_samples_end)
|
||||||
|
bne audio_cont
|
||||||
|
lda sample_ptrl
|
||||||
|
cmp #.lobyte(audio_samples_end)
|
||||||
|
bne audio_cont
|
||||||
|
lda #.lobyte(audio_samples)
|
||||||
|
sta sample_ptrl
|
||||||
|
lda #.hibyte(audio_samples)
|
||||||
|
sta sample_ptrh
|
||||||
|
|
||||||
|
audio_cont:
|
||||||
|
.endmacro
|
||||||
|
|
||||||
|
.macro inner_scanline
|
||||||
|
; Leisurely memory fetches
|
||||||
lda palette1,y
|
lda palette1,y
|
||||||
pha
|
pha
|
||||||
ldx palette2,y
|
ldx palette2,y
|
||||||
|
@ -133,13 +198,20 @@ each_scanline:
|
||||||
stx COLPF1
|
stx COLPF1
|
||||||
sty COLPF2
|
sty COLPF2
|
||||||
|
|
||||||
|
; Audio sample
|
||||||
pla
|
pla
|
||||||
tay
|
sta AUDC1
|
||||||
iny
|
.endmacro
|
||||||
cpy #height
|
|
||||||
bne each_scanline
|
|
||||||
|
|
||||||
jmp wait_start
|
audio_prep
|
||||||
|
inner_scanline
|
||||||
|
|
||||||
|
audio_inc
|
||||||
|
iny
|
||||||
|
inner_scanline
|
||||||
|
|
||||||
|
;jmp wait_start
|
||||||
|
jmp each_scanline
|
||||||
|
|
||||||
.endproc
|
.endproc
|
||||||
|
|
||||||
|
|
60
pack-wav.js
Normal file
60
pack-wav.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import wavefile from 'wavefile';
|
||||||
|
let WaveFile = wavefile.WaveFile;
|
||||||
|
|
||||||
|
import {
|
||||||
|
readFileSync,
|
||||||
|
writeFileSync
|
||||||
|
} from 'fs';
|
||||||
|
|
||||||
|
function byte2byte(arr) {
|
||||||
|
let lines = [];
|
||||||
|
for (let i=0; i < arr.length; i++) {
|
||||||
|
lines.push(`.byte ${arr[i]}`);
|
||||||
|
}
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function to4bit(val8) {
|
||||||
|
let val = val8 + 7 >> 4;
|
||||||
|
if (val > 15) {
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pack(audio) {
|
||||||
|
let packed = [];
|
||||||
|
for (let i = 0; i < audio.length; i += 2) {
|
||||||
|
let low = to4bit(audio[i]);
|
||||||
|
let high = to4bit(audio[i + 1]);
|
||||||
|
let byte = low | (high << 4);
|
||||||
|
packed.push(byte);
|
||||||
|
}
|
||||||
|
return packed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function audio2assembly(audio) {
|
||||||
|
return `.data
|
||||||
|
.export audio_samples
|
||||||
|
.export audio_samples_end
|
||||||
|
|
||||||
|
audio_samples:
|
||||||
|
${byte2byte(pack(audio))}
|
||||||
|
audio_samples_end:
|
||||||
|
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function wav2assembly(buffer) {
|
||||||
|
let wav = new WaveFile(buffer);
|
||||||
|
let samples = wav.getSamples();
|
||||||
|
return audio2assembly(samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
let infile = process.argv[2];
|
||||||
|
let outfile = process.argv[3];
|
||||||
|
|
||||||
|
let buffer = readFileSync(infile);
|
||||||
|
let asm = wav2assembly(buffer);
|
||||||
|
writeFileSync(outfile, asm, 'utf-8');
|
||||||
|
|
19
package-lock.json
generated
19
package-lock.json
generated
|
@ -5,7 +5,8 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jimp": "^0.16.2"
|
"jimp": "^0.16.2",
|
||||||
|
"wavefile": "^11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
|
@ -740,6 +741,17 @@
|
||||||
"pako": "^1.0.5"
|
"pako": "^1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wavefile": {
|
||||||
|
"version": "11.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wavefile/-/wavefile-11.0.0.tgz",
|
||||||
|
"integrity": "sha512-/OBiAALgWU24IG7sC84cDO/KfFuvajWc5Uec0oV2zrpOOZZDgGdOwHwgEzOrwh8jkubBk7PtZfQBIcI1OaE5Ng==",
|
||||||
|
"bin": {
|
||||||
|
"wavefile": "bin/wavefile.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xhr": {
|
"node_modules/xhr": {
|
||||||
"version": "2.6.0",
|
"version": "2.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz",
|
||||||
|
@ -1343,6 +1355,11 @@
|
||||||
"pako": "^1.0.5"
|
"pako": "^1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"wavefile": {
|
||||||
|
"version": "11.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wavefile/-/wavefile-11.0.0.tgz",
|
||||||
|
"integrity": "sha512-/OBiAALgWU24IG7sC84cDO/KfFuvajWc5Uec0oV2zrpOOZZDgGdOwHwgEzOrwh8jkubBk7PtZfQBIcI1OaE5Ng=="
|
||||||
|
},
|
||||||
"xhr": {
|
"xhr": {
|
||||||
"version": "2.6.0",
|
"version": "2.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jimp": "^0.16.2"
|
"jimp": "^0.16.2",
|
||||||
|
"wavefile": "^11.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue