fixed sample-based audio

This commit is contained in:
Steven Hugg 2018-02-12 14:02:55 -06:00
parent e7067ff50d
commit 8c3939ac6c
6 changed files with 616 additions and 14 deletions

View File

@ -0,0 +1,87 @@
.arch nano8
.org 128
.define PADDLE_X 0
.define PADDLE_Y 1
.define PLAYER_X 2
.define PLAYER_Y 3
.define ENEMY_X 4
.define ENEMY_Y 5
.define ENEMY_DIR 6
.define SPEED 7
.define TRACKPOS_LO 8
.define TRACKPOS_HI 9
.define IN_HPOS $40
.define IN_VPOS $41
.define IN_FLAGS $42
.define F_DISPLAY 1
.define F_HPADDLE 2
.define F_VPADDLE 4
.define F_HSYNC 8
.define F_VSYNC 16
.define F_COLLIDE 32
Start:
lda 128
sta PLAYER_X
sta ENEMY_X
sta ENEMY_Y
lda 180
sta PLAYER_Y
zero A
sta SPEED
; test hpaddle flag
DisplayLoop:
lda F_HPADDLE
ldb IN_FLAGS
andrb NOP
bz DisplayLoop
; [vpos] -> paddle_x
ldb IN_VPOS
movrb A
sta PLAYER_X
; wait for vsync=1 then vsync=0
lda F_VSYNC
ldb IN_FLAGS
WaitForVsyncOn:
andrb NOP
bz WaitForVsyncOn
WaitForVsyncOff:
andrb NOP
bnz WaitForVsyncOff
; check collision
lda F_COLLIDE
ldb IN_FLAGS
andrb NOP
bz NoCollision
; load slow speed
lda 16
sta SPEED
NoCollision:
; update speed
ldb SPEED
movrb A
inc A
; don't store if == 0
bz MaxSpeed
sta SPEED
MaxSpeed:
movrb A
lsr A
lsr A
lsr A
lsr A
; add to lo byte of track pos
ldb TRACKPOS_LO
addrb B
swapab
sta TRACKPOS_LO
swapab
; update enemy vert pos
ldb ENEMY_Y
addrb A
sta ENEMY_Y
jmp DisplayLoop
reset

163
presets/verilog/music.v Normal file
View File

@ -0,0 +1,163 @@
`include "hvsync_generator.v"
module sound_psg(clk, reset, out, reg_sel, reg_data, reg_write);
input clk, reset;
input [3:0] reg_sel;
input [7:0] reg_data;
input reg_write;
output [7:0] out;
parameter NVOICES = 4;
reg outputs[NVOICES];
reg [17:0] count[NVOICES];
reg [7:0] register[16];
always @(posedge clk) begin
out = 0;
for (int i=0; i<NVOICES; i++) begin
if (count[i][17:6] == {register[i*2+1][3:0], register[i*2]}) begin
outputs[i] <= outputs[i] ^ 1;
count[i] <= 0;
end else begin
count[i] <= count[i] + 1;
end
/* verilator lint_off BLKSEQ */
if (register[15][i] && outputs[i]) begin
out = out + 1;
end
end
if (reg_write) begin
register[reg_sel] <= reg_data;
end
end
endmodule;
module music_player(clk, reset, advance,
psg_sel, psg_data, psg_write);
parameter NVOICES = 4;
input clk, reset;
input advance;
output [3:0] psg_sel;
output [7:0] psg_data;
output psg_write;
//./mknotes.py -m 12 -f 75898
// Namespace(bias=0, freq=75898.0, length=64, maxbits=12.0, upper=49)
// 434.7 3.23120101673 49
reg [11:0] note_table[64] = '{
2960, 2794, 2637, 2489, 2349, 2217, 2093, 1975,
1864, 1760, 1661, 1568, 1480, 1397, 1318, 1244,
1175, 1109, 1046, 988, 932, 880, 831, 784,
740, 698, 659, 622, 587, 554, 523, 494,
466, 440, 415, 392, 370, 349, 330, 311,
294, 277, 262, 247, 233, 220, 208, 196,
185, 175, 165, 156, 147, 139, 131, 123,
117, 110, 104, 98, 92, 87, 82, 78
};
reg [7:0] music_table[292] = '{
8'h1e,8'h12,8'h8c,8'h23,8'h17,8'h86,8'h2f,8'h86,8'h36,8'h2a,8'h27,8'h86,8'h2f,8'h86,8'h33,8'h1e,8'h23,8'h86,8'h36,8'h2a,8'h86,8'h24,8'h18,8'h86,8'h2e,8'h86,8'h2a,8'h36,8'h25,8'h86,8'h2e,8'h86,8'h31,8'h28,8'h22,8'h86,8'h36,8'h2a,8'h86,8'h1e,8'h22,8'h28,8'h8c,8'h1e,8'h12,8'h8c,8'h23,8'h17,8'h86,8'h2f,8'h86,8'h36,8'h2a,8'h27,8'h86,8'h2f,8'h86,8'h33,8'h1e,8'h23,8'h86,8'h36,8'h2a,8'h86,8'h24,8'h18,8'h86,8'h2e,8'h86,8'h36,8'h2a,8'h25,8'h86,8'h2e,8'h86,8'h31,8'h28,8'h22,8'h86,8'h36,8'h2a,8'h86,8'h28,8'h22,8'h1e,8'h8c,8'h12,8'h1e,8'h86,8'h36,8'h2a,8'h86,8'h1f,8'h13,8'h86,8'h2f,8'h86,8'h32,8'h86,8'h37,8'h2b,8'h86,8'h1e,8'h12,8'h86,8'h36,8'h2a,8'h86,8'h1e,8'h12,8'h86,8'h2a,8'h36,8'h86,8'h1f,8'h13,8'h86,8'h2f,8'h86,8'h32,8'h86,8'h37,8'h2b,8'h86,8'h1e,8'h12,8'h86,8'h36,8'h2a,8'h92,8'h0b,8'h86,8'h17,8'h86,8'h1a,8'h86,8'h23,8'h86,8'h17,8'h86,8'h23,8'h86,8'h26,8'h86,8'h2f,8'h86,8'h23,8'h86,8'h2f,8'h86,8'h32,8'h86,8'h3b,8'h86,8'h2f,8'h86,8'h3b,8'h86,8'h3e,8'h86,8'h86,8'h3b,8'h29,8'h2c,8'h8c,8'h3b,8'h32,8'h2f,8'h8c,8'h3b,8'h32,8'h2f,8'h8c,8'h3b,8'h29,8'h2c,8'h86,8'h3b,8'h86,8'h33,8'h2f,8'h2a,8'h86,8'h86,8'h33,8'h2f,8'h2a,8'h86,8'h3f,8'h86,8'h2a,8'h2f,8'h33,8'h86,8'h3b,8'h86,8'h33,8'h2f,8'h2a,8'h86,8'h37,8'h3b,8'h86,8'h32,8'h2f,8'h2b,8'h86,8'h3d,8'h86,8'h3e,8'h37,8'h2b,8'h86,8'h3b,8'h86,8'h3d,8'h33,8'h2f,8'h86,8'h3f,8'h36,8'h86,8'h33,8'h2f,8'h2a,8'h86,8'h3b,8'h86,8'h3f,8'h36,8'h33,8'h86,8'h3b,8'h86,8'h3d,8'h36,8'h2a,8'h8c,8'h3b,8'h36,8'h33,8'h92,8'h3b,8'h2f,8'h86,8'h26,8'h23,8'h20,8'h8c,8'h3b,8'h2f,8'h1d,8'h8c,8'h3b,8'h2f,8'h26,8'h8c,8'h3b,8'h2f,8'h26,8'h86,8'h3b,8'h2f,8'h86,8'h1e,8'h23,8'h27,8'h86,8'h36,8'h86,8'h38,8'h2f,8'h27,8'h86,8'h33,8'h86,8'h36,8'h27,8'h23,8'h86,8'h38,8'h2f,8'h86,8'h1e,8'h23,8'h27,8'h86,8'h2f,8'h2b,8'h86,8'h26,8'hff
};
reg [15:0] music_ptr = 0;
reg [2:0] freech = 0;
reg [7:0] cur_duration = 0;
reg [3:0] ch_durations[NVOICES];
reg [7:0] psg_regs[16];
reg [3:0] next_reg;
wire [7:0] note = music_table[music_ptr[8:0]];
wire [11:0] period = note_table[note[5:0]];
always @(posedge advance or posedge reset) begin
if (reset) begin
cur_duration <= 0;
music_ptr <= 0;
psg_regs[15] <= 0;
end else if (cur_duration == 0) begin
// TODO: 0xff
music_ptr <= music_ptr + 1;
if (note[7]) begin
cur_duration <= note & 63;
end else begin
psg_regs[freech*2] <= period[7:0];
psg_regs[freech*2+1] <= 8'(period[11:8]);
ch_durations[freech+0] = 4;
psg_regs[15][freech] <= 1;
freech <= (freech == NVOICES-1) ? 0 : freech+1;
end
end else begin
cur_duration <= cur_duration - 1;
for (int i=0; i<NVOICES; i++) begin
if (ch_durations[i] == 0) begin
psg_regs[15][i] <= 0;
end else begin
ch_durations[i] <= ch_durations[i] - 1;
end
end
end
end
always @(posedge clk or posedge reset) begin
psg_sel <= next_reg;
psg_data <= psg_regs[next_reg];
psg_write <= 1;
next_reg <= next_reg + 1;
end
endmodule;
module top(clk, reset, hsync, vsync, rgb, spkr);
input clk, reset;
output hsync, vsync;
output [2:0] rgb;
output [7:0] spkr;
wire display_on;
wire [8:0] hpos;
wire [8:0] vpos;
hvsync_generator hvsync_gen(
.clk(clk),
.reset(reset),
.hsync(hsync),
.vsync(vsync),
.display_on(display_on),
.hpos(hpos),
.vpos(vpos)
);
wire r = display_on && spkr[2];
wire g = display_on && spkr[1];
wire b = display_on && spkr[0];
assign rgb = {b,g,r};
reg [3:0] sel;
reg [7:0] data;
reg write = 0;
sound_psg psg(
.clk(clk),
.reset(reset),
.reg_sel(sel),
.reg_data(data),
.reg_write(write),
.out(spkr));
music_player player(
.clk(hsync),
.reset(reset),
.advance(vsync),
.psg_sel(sel),
.psg_data(data),
.psg_write(write)
);
endmodule

84
presets/verilog/nano8.cfg Normal file
View File

@ -0,0 +1,84 @@
; Architecture file for the NANO8
; default output format is a memory initialization file
.outfmt mif
; mif file is this big
.mifwords 256
.mifwidth 8
; Opcodes for core instruction set
.define A 0
.define B 1
.define IP 2
.define NOP 3
.define ADD 8
; ALU A + B -> dest
mova 2 { 00 (0) 0000 }
movb 2 { 00 (0) 0001 }
inc 2 { 00 (0) 0010 }
dec 2 { 00 (0) 0011 }
or 2 { 00 (0) 0100 }
and 2 { 00 (0) 0101 }
xor 2 { 00 (0) 0110 }
zero 2 { 00 (0) 0111 }
add 2 { 00 (0) 1000 }
sub 2 { 00 (0) 1001 }
asl 2 { 00 (0) 1010 }
lsr 2 { 00 (0) 1011 }
adc 2 { 00 (0) 1100 }
sbb 2 { 00 (0) 1101 }
rol 2 { 00 (0) 1110 }
ror 2 { 00 (0) 1111 }
; ALU A + immediate -> dest
movi 2 8 { 01 (0) 0001 (1) }
ori 2 8 { 01 (0) 0100 (1) }
andi 2 8 { 01 (0) 0101 (1) }
xori 2 8 { 01 (0) 0110 (1) }
addi 2 8 { 01 (0) 1000 (1) }
subi 2 8 { 01 (0) 1001 (1) }
asli 2 8 { 01 (0) 1010 (1) }
lsri 2 8 { 01 (0) 1011 (1) }
adci 2 8 { 01 (0) 1100 (1) }
sbbi 2 8 { 01 (0) 1101 (1) }
roli 2 8 { 01 (0) 1110 (1) }
rori 2 8 { 01 (0) 1111 (1) }
; ALU A + read [B] -> dest
movrb 2 { 11 (0) 0001 }
orrb 2 { 11 (0) 0100 }
andrb 2 { 11 (0) 0101 }
xorrb 2 { 11 (0) 0110 }
addrb 2 { 11 (0) 1000 }
subrb 2 { 11 (0) 1001 }
aslrb 2 { 11 (0) 1010 }
lsrrb 2 { 11 (0) 1011 }
adcrb 2 { 11 (0) 1100 }
sbbrb 2 { 11 (0) 1101 }
rolrb 2 { 11 (0) 1110 }
rorrb 2 { 11 (0) 1111 }
; A -> write [nnnn]
sta 4 { 1001 (0) }
; other insns
clc { 10001000 }
swapab { 10000001 }
reset { 10001111 }
lda 8 { 01 00 0001 (0) }
ldb 8 { 01 01 0001 (0) }
jmp 8 { 01 10 0001 (0) }
; conditional branch
bcc 8 { 1010 0001 (0) }
bcs 8 { 1010 0011 (0) }
bz 8 { 1010 0100 (0) }
bnz 8 { 1010 1100 (0) }
; allow raw byte positioning
byte 8 { (0) } ; One byte constant

View File

@ -0,0 +1,265 @@
`include "hvsync_generator.v"
`include "sprite_renderer.v"
`include "cpu8.v"
// uncomment to see scope view
//`define DEBUG
module sprite_multiple_top(clk, reset, hsync, vsync, hpaddle, vpaddle,
address_bus, to_cpu, from_cpu, write_enable
`ifdef DEBUG
, output [7:0] A
, output [7:0] B
, output [7:0] IP
, output carry
, output zero
`else
, output [2:0] rgb
`endif
);
input clk, reset;
input hpaddle, vpaddle;
output hsync, vsync;
wire display_on;
wire [8:0] hpos;
wire [8:0] vpos;
`ifdef DEBUG
wire [2:0] rgb;
assign IP = cpu.IP;
assign A = cpu.A;
assign B = cpu.B;
assign carry = cpu.carry;
assign zero = cpu.zero;
`endif
parameter PADDLE_X = 0;
parameter PADDLE_Y = 1;
parameter PLAYER_X = 2;
parameter PLAYER_Y = 3;
parameter ENEMY_X = 4;
parameter ENEMY_Y = 5;
parameter ENEMY_DIR = 6;
parameter SPEED = 7;
parameter TRACKPOS_LO = 8;
parameter TRACKPOS_HI = 9;
parameter IN_HPOS = 8'b01000000;
parameter IN_VPOS = 8'b01000001;
parameter IN_FLAGS = 8'b01000010;
reg [7:0] ram[0:63];
reg [7:0] rom[0:127];
output wire [7:0] address_bus;
output reg [7:0] to_cpu;
output wire [7:0] from_cpu;
output wire write_enable;
CPU cpu(.clk(clk),
.reset(reset),
.address(address_bus),
.data_in(to_cpu),
.data_out(from_cpu),
.write(write_enable));
always @(posedge clk)
if (write_enable)
ram[address_bus[5:0]] <= from_cpu;
always @(*)
casez (address_bus)
// RAM
8'b00??????: to_cpu = ram[address_bus[5:0]];
// special read registers
IN_HPOS: to_cpu = hpos[7:0];
IN_VPOS: to_cpu = vpos[7:0];
IN_FLAGS: to_cpu = {2'b0, frame_collision,
vsync, hsync, vpaddle, hpaddle, display_on};
// ROM
8'b1???????: to_cpu = rom[address_bus[6:0]];
default: ;
endcase
hvsync_generator hvsync_gen(
.clk(clk),
.reset(0),
.hsync(hsync),
.vsync(vsync),
.display_on(display_on),
.hpos(hpos),
.vpos(vpos)
);
wire player_vstart = {1'0,ram[PLAYER_Y]} == vpos;
wire player_hstart = {1'0,ram[PLAYER_X]} == hpos;
wire player_gfx;
wire player_is_drawing;
wire enemy_vstart = {1'0,ram[ENEMY_Y]} == vpos;
wire enemy_hstart = {1'0,ram[ENEMY_X]} == hpos;
wire enemy_gfx;
wire enemy_is_drawing;
wire [3:0] car_sprite_yofs;
wire [7:0] car_sprite_bits;
car_bitmap car(
.yofs(car_sprite_yofs),
.bits(car_sprite_bits));
sprite_renderer player_renderer(
.clk(clk),
.vstart(player_vstart),
.hstart(player_hstart),
.load(hpos == 256), //TODO?
.rom_addr(car_sprite_yofs),
.rom_bits(car_sprite_bits),
.gfx(player_gfx),
.in_progress(player_is_drawing));
sprite_renderer enemy_renderer(
.clk(clk),
.vstart(enemy_vstart),
.hstart(enemy_hstart),
.load(hpos == 260), //TODO?
.rom_addr(car_sprite_yofs),
.rom_bits(car_sprite_bits),
.gfx(enemy_gfx),
.in_progress(player_is_drawing));
/*
always @(posedge hsync)
begin
if (!hpaddle) ram[PADDLE_X] <= vpos[7:0];
if (!vpaddle) ram[PADDLE_Y] <= vpos[7:0];
end
wire enemy_hit_left = (enemy_x == 64);
wire enemy_hit_right = (enemy_x == 192);
wire enemy_hit_edge = enemy_hit_left || enemy_hit_right;
always @(posedge vsync)
begin
player_x <= paddle_x;
player_y <= 180;
track_pos <= track_pos + {11'b0,speed[7:4]};
enemy_y <= enemy_y + {3'b0, speed[7:4]};
if (enemy_hit_edge)
enemy_dir <= !enemy_dir;
if (enemy_dir ^ enemy_hit_edge)
enemy_x <= enemy_x + 1;
else
enemy_x <= enemy_x - 1;
// collision check?
if (frame_collision)
speed <= 16;
else if (speed < ~paddle_y)
speed <= speed + 1;
else
speed <= speed - 1;
end
*/
initial begin
rom = '{
// initialize registers
`I_CONST_IMM_A,
128,
`I_STORE_A(PLAYER_X),
`I_STORE_A(ENEMY_X),
`I_STORE_A(ENEMY_Y),
`I_CONST_IMM_A,
180,
`I_STORE_A(PLAYER_Y),
`I_ZERO_A,
`I_STORE_A(SPEED),
// test hpaddle flag
`I_CONST_IMM_A,
8'b00000010,
`I_CONST_IMM_B,
IN_FLAGS,
`I_COMPUTE_READB(DEST_NOP, OP_AND),
`I_BRANCH_IF_ZERO(1),
128+10,
// [vpos] -> paddle_x
`I_CONST_IMM_B,
IN_VPOS,
`I_COMPUTE_READB(DEST_A, OP_LOAD_B),
`I_STORE_A(PLAYER_X),
// wait for vsync=1 then vsync=0
`I_CONST_IMM_A,
8'b00010000,
`I_CONST_IMM_B,
IN_FLAGS,
`I_COMPUTE_READB(DEST_NOP, OP_AND),
`I_BRANCH_IF_ZERO(1),
128+25,
`I_COMPUTE_READB(DEST_NOP, OP_AND),
`I_BRANCH_IF_ZERO(0),
128+28,
// check collision
`I_CONST_IMM_A,
8'b00100000,
`I_CONST_IMM_B,
IN_FLAGS,
`I_COMPUTE_READB(DEST_NOP, OP_AND),
`I_BRANCH_IF_ZERO(1),
128+41,
// load slow speed
`I_CONST_IMM_A,
16,
`I_STORE_A(SPEED),
// update speed
`I_CONST_IMM_B,
SPEED,
`I_COMPUTE_READB(DEST_A, OP_LOAD_B),
`I_COMPUTE(DEST_A, OP_INC),
// don't store if == 0
`I_BRANCH_IF_ZERO(1),
128+48,
`I_STORE_A(SPEED),
// branch target
`I_COMPUTE_READB(DEST_A, OP_LOAD_B),
`I_COMPUTE(DEST_A, OP_LSR),
`I_COMPUTE(DEST_A, OP_LSR),
`I_COMPUTE(DEST_A, OP_LSR),
`I_COMPUTE(DEST_A, OP_LSR),
// add to lo byte of track pos
`I_CONST_IMM_B,
TRACKPOS_LO,
`I_COMPUTE_READB(DEST_B, OP_ADD),
`I_SWAP_AB,
`I_STORE_A(TRACKPOS_LO),
`I_SWAP_AB,
// update enemy vert pos
`I_CONST_IMM_B,
ENEMY_Y,
`I_COMPUTE_READB(DEST_A, OP_ADD),
`I_STORE_A(ENEMY_Y),
// repeat main loop
`I_JUMP_IMM,
128+10,
// leftover elements
63{0}
};
end
reg frame_collision;
always @(posedge clk)
if (player_gfx && (enemy_gfx || track_gfx))
frame_collision <= 1;
else if (vpos==0)
frame_collision <= 0;
wire track_offside = (hpos[7:5]==0) || (hpos[7:5]==7);
wire track_shoulder = (hpos[7:3]==3) || (hpos[7:3]==28);
wire track_gfx = (vpos[5:1]!=ram[TRACKPOS_LO][5:1]) && track_offside;
wire r = display_on && (player_gfx || enemy_gfx || track_shoulder);
wire g = display_on && (player_gfx || track_gfx);
wire b = display_on && (enemy_gfx || track_shoulder);
assign rgb = {b,g,r};
endmodule

View File

@ -311,11 +311,11 @@ var SampleAudio = function(clockfreq) {
var self = this;
var sfrac, sinc, accum;
var buffer, bufpos, bufferlist;
var idrain, ifill;
function mix(ape) {
var buflen=ape.outputBuffer.length;
var lbuf = ape.outputBuffer.getChannelData(0);
//var rbuf = ape.outputBuffer.getChannelData(1);
var m = this.module;
if (!m) m = ape.srcElement.module;
if (!m) return;
@ -323,10 +323,12 @@ var SampleAudio = function(clockfreq) {
m.callback(lbuf);
return;
} else {
var buf = bufferlist[1];
var buf = bufferlist[idrain];
for (var i=0; i<lbuf.length; i++) {
lbuf[i] = buf[i];
//lbuf[i] = (i&128) ? 1.0 : 0.33;
}
idrain = (idrain + 1) % bufferlist.length;
}
}
@ -343,11 +345,7 @@ var SampleAudio = function(clockfreq) {
// Amiga 500 fixed filter at 6kHz. WebAudio lowpass is 12dB/oct, whereas
// older Amigas had a 6dB/oct filter at 4900Hz.
self.filterNode=self.context.createBiquadFilter();
if (self.amiga500) {
self.filterNode.frequency.value=6000;
} else {
self.filterNode.frequency.value=28867;
}
self.filterNode.frequency.value=6000;
// "LED filter" at 3275kHz - off by default
self.lowpassNode=self.context.createBiquadFilter();
@ -381,7 +379,9 @@ var SampleAudio = function(clockfreq) {
accum = 0;
bufpos = 0;
bufferlist = [];
for (var i=0; i<2; i++) {
idrain = 1;
ifill = 0;
for (var i=0; i<3; i++) {
var arrbuf = new ArrayBuffer(self.bufferlen*4);
bufferlist[i] = new Float32Array(arrbuf);
}
@ -400,8 +400,10 @@ var SampleAudio = function(clockfreq) {
buffer[bufpos++] = value;
if (bufpos >= buffer.length) {
bufpos = 0;
bufferlist[0] = bufferlist[1];
bufferlist[1] = buffer;
bufferlist[ifill] = buffer;
var inext = (ifill + 1) % bufferlist.length;
if (inext != idrain) ifill = inext;
buffer = bufferlist[ifill];
}
}
@ -411,8 +413,9 @@ var SampleAudio = function(clockfreq) {
sfrac += sinc;
if (sfrac >= 1) {
sfrac -= 1;
accum = 0;
this.addSingleSample(accum / sfrac);
value *= sfrac;
this.addSingleSample(accum - value);
accum = value;
}
}
}

View File

@ -181,7 +181,7 @@ var VerilogPlatform = function(mainElement, options) {
var idata, timer;
var gen;
var frameRate = 60;
var AUDIO_FREQ = 15750;
var AUDIO_FREQ = (256+23+7+23)*262*60; // 4857480
var current_output;
var paddle_x = 0;
var paddle_y = 0;
@ -219,7 +219,7 @@ var VerilogPlatform = function(mainElement, options) {
function vidtick() {
gen.tick2();
audio.addSingleSample(0+gen.spkr); // TODO: sync with audio freq
audio.feedSample((gen.spkr&255)*(1.0/255.0), 1);
if (debugCond && debugCond()) debugCond = null;
}