.version 9 ; FANCO-16 v9 device test: status, buttons, twin sticks, MIDI and a ; two-octave FM piano. Text cells and font are bundled at D000 and E000. .org 0x1000 .equ cells, 0xd000 .equ font, 0xe000 .equ upper_row, 0xc000 .equ lower_row, 0xc100 start: mov sp, 0xfffe mov.w [0x10], 0x0ffb mov.w [0x7a], 256 call init_voices call init_tables mov x, 0 mov y, 0 mov a, 256 mov b, 256 mov r, 0 call rect mov x, cells+3 mov p, title call puts mov x, cells+256 mov p, help_keys call puts mov x, cells+288 mov p, help_buttons call puts mov x, cells+676 mov p, primary_label call puts mov x, cells+692 mov p, secondary_label call puts mov x, cells+686 mov p, power_label call puts mov.w [0x20], tick mov.w [0x22], 120 mov.w [0x24], midi_tick mov.w [0x26], 600 mov.b [0x30], 3 idle: hlt bra idle ; 600 Hz: MIDI notes start within 1.7 ms. midi_tick: call midi_events reti ; 120 Hz: input and sound every tick, the screen every fourth tick. tick: call keys call letter_timers call buttons call power_button call touch_piano call stick_sound mov a, [frame] inc a mov.w [frame], a and a, 3 bnz tick_done call draw tick_done: reti ; Voice v uses carrier operator 2v and modulator 2v+1; mixer slot v plays the carrier. init_voices: mov x, 0x100 mov y, 0x300 mov r, 0 init_voice: mov.b [x+4], 0 mov.b [x+5], 0x0a mov.b [x+6], 0x8a mov.b [x+7], 1 mov a, r shl a, 1 inc a or a, 0x10 mov.b [x+8], a.lo mov.b [x+9], 128 mov.b [x+14], 0x0c mov.b [x+36], 0 mov.b [x+37], 0x08 mov.b [x+38], 0x5a mov.b [x+39], 1 mov.b [x+40], 0 mov.b [x+46], 0x0c mov a, r shl a, 1 xor a, r or a, 0x8ff0 mov.w [y], a mov.b [y+2], 0 mov.b [y+3], 0 mov.b [y+4], 0 add x, 64 add y, 5 inc r cmp r, 8 bnz init_voice mov.b [0x343], 15 ret ; Piano notes for each touch X: upper_row includes black keys, lower_row whites only. init_tables: mov x, 0 table_loop: mov a, x cmp a, 2 bhs table_inside mov a, 2 table_inside: sub a, 2 mov y, a div y, 18 mov r, a mod r, 18 cmp y, 13 bls table_key mov y, 13 mov r, 17 table_key: mov p, white_notes add p, y movzx a, [p] mov p, lower_row add p, x mov.b [p], a.lo cmp r, 13 blo table_left mov p, black_after add p, y movzx b, [p] cmp b, 0 bz table_store inc a bra table_store table_left: cmp r, 5 bhs table_store cmp y, 0 bz table_store mov p, black_after-1 add p, y movzx b, [p] cmp b, 0 bz table_store dec a table_store: mov p, upper_row add p, x mov.b [p], a.lo inc x cmp x, 256 bnz table_loop ret ; a = note. Takes the next voice round-robin; the modulator plays an octave up. note_on: movzx x, [next_voice] mov y, x inc y and y, 7 mov.b [next_voice], y.lo mov p, voice_note add p, x mov.b [p], a.lo shl x, 6 add x, 0x100 inc a mov.b [x+15], a.lo add a, 12 cmp a, 128 bls note_on_modulator mov a, 128 note_on_modulator: mov.b [x+47], a.lo ret ; a = note. Releases every voice playing it. note_off: mov x, 0 mov y, 0x100 note_off_loop: mov p, voice_note add p, x movzx b, [p] cmp b, a bnz note_off_next mov.b [p], 255 mov.b [y+15], 0 mov.b [y+47], 0 note_off_next: add y, 64 inc x cmp x, 8 bnz note_off_loop ret midi_events: movzx a, [0x620] and a, 1 bz midi_done mov.b [0x620], 0 movzx a, [0x621] mov.b [midi_last], a.lo movzx x, [0x622] mov.b [midi_last+1], x.lo movzx y, [0x623] mov.b [midi_last+2], y.lo and a, 0xf0 cmp a, 0x90 bnz midi_note_off cmp y, 0 bz midi_release mov a, x call note_on bra midi_events midi_note_off: cmp a, 0x80 bnz midi_events midi_release: mov a, x call note_off bra midi_events midi_done: ret ; Acknowledges pressed keys, runs commands and plays letters; releases letters ; whose key is no longer down. keys: mov x, 8 key_scan: mov p, x shr p, 3 add p, 0x420 mov a, x and a, 7 mov b, 1 shl b, a movzx y, [p] and y, b bz key_next mov.b [p], b.lo mov.b [last_key], x.lo push x mov a, x call key_command pop x key_next: inc x cmp x, 128 bnz key_scan ret ; A letter note plays until its key-down bit (0400 bitmap) clears. letter_timers: mov r, 0 letter_tick: mov p, letter_held add p, r movzx a, [p] cmp a, 0 bz letter_next mov p, letters add p, r movzx a, [p] mov b, a and b, 7 shr a, 3 mov p, a add p, 0x400 movzx y, [p] mov a, 1 shl a, b and y, a bnz letter_next mov p, letter_held add p, r mov.b [p], 0 mov a, r add a, 60 push r call note_off pop r letter_next: inc r cmp r, 16 bnz letter_tick ret key_command: cmp a, 49 bz cmd_primary cmp a, 50 bz cmd_secondary cmp a, 51 bz cmd_touch cmp a, 52 bz cmd_wave mov p, letters mov r, 0 letter_find: movzx b, [p++] cmp b, a bz letter_found inc r cmp r, 16 bnz letter_find ret letter_found: mov p, letter_held add p, r mov.b [p], 1 mov a, r add a, 60 bra note_on buttons: movzx a, [0x14] movzx b, [previous_buttons] mov.b [previous_buttons], a.lo xor b, 255 and a, b mov.w [pressed], a and a, 1 bz button_b call cmd_wave button_b: mov a, [pressed] and a, 2 bz button_x call cmd_touch button_x: mov a, [pressed] and a, 4 bz button_y call cmd_primary button_y: mov a, [pressed] and a, 8 bz buttons_done call cmd_secondary buttons_done: ret cmd_primary: mov a, [0x10] mov b, a add b, 0x100 and b, 0x300 and a, 0xfcff or a, b mov.w [0x10], a ret cmd_secondary: mov a, [0x10] mov b, a add b, 0x400 and b, 0xc00 and a, 0xf3ff or a, b mov.w [0x10], a ret ; Toggles touch slots (piano) against floating sticks; releases touch notes. cmd_touch: mov a, [0x10] xor a, 2 mov.w [0x10], a mov r, 0 release_slot: mov p, slot_note add p, r movzx a, [p] mov.b [p], 255 cmp a, 255 bz release_next push r call note_off pop r release_next: inc r cmp r, 8 bnz release_slot ret ; Cycles the carrier waveform: sine, triangle, saw, pulse. Keeps each gate bit. cmd_wave: movzx a, [wave] inc a and a, 3 mov.b [wave], a.lo mov p, wave_bits add p, a movzx b, [p] mov y, 0 cmp a, 0 bnz wave_set mov y, 1 wave_set: mov x, 0x100 mov r, 8 wave_voice: movzx a, [x+4] and a, 1 or a, b mov.b [x+4], a.lo mov.b [x+7], y.lo add x, 64 dec r bnz wave_voice ret ; Each touch slot below y=176 plays the key under it; sliding changes notes. touch_piano: movzx a, [0x10] and a, 2 bz touch_done mov r, 0 touch_slot: mov p, r shl p, 2 add p, 0x680 mov a, 255 movzx b, [p+2] cmp b, 0 bz touch_compare movzx y, [p+1] cmp y, 176 blo touch_compare movzx x, [p] mov p, lower_row cmp y, 224 bhs touch_lookup mov p, upper_row touch_lookup: add p, x movzx a, [p] touch_compare: mov p, slot_note add p, r movzx b, [p] cmp a, b bz touch_next mov.b [p], a.lo push r push a cmp b, 255 bz touch_on mov a, b call note_off touch_on: pop a cmp a, 255 bz touch_restore call note_on touch_restore: pop r touch_next: inc r cmp r, 8 bnz touch_slot touch_done: ret ; Power button between the pads: a new touch on it sets sound request = !enabled. power_button: movzx a, [power_wait] cmp a, 0 bz power_scan dec a mov.b [power_wait], a.lo power_scan: mov b, 0 mov r, 0 power_slot: mov p, r shl p, 2 add p, 0x680 movzx a, [p+2] cmp a, 0 bz power_next movzx x, [p] sub x, 112 cmp x, 24 bhs power_next movzx y, [p+1] sub y, 113 cmp y, 24 bhs power_next mov b, 1 power_next: inc r cmp r, 8 bnz power_slot movzx a, [power_held] mov.b [power_held], b.lo cmp a, 0 bnz power_done cmp b, 0 bz power_done mov a, [0x10] or a, 4 mov.b [power_wait], 120 movzx b, [0x12] and b, 1 bz power_store and a, 0xfffb mov.b [power_wait], 0 power_store: mov.w [0x10], a power_done: ret ; Primary stick: X sets FM depth, up adds modulator feedback. ; Secondary stick: X pans, up adds carrier feedback (grit). stick_sound: movsx a, [0x16] add a, 128 mov.w [depth], a movsx a, [0x17] call up_amount mov.w [feedback], a movsx a, [0x19] call up_amount mov.w [grit], a mov x, 0x100 mov y, 0x300 mov r, 8 sound_voice: mov a, [depth] mov.b [x+9], a.lo mov a, [grit] mov.b [x+10], a.lo mov a, [feedback] mov.b [x+42], a.lo movzx a, [0x18] mov.b [y+2], a.lo add x, 64 add y, 5 dec r bnz sound_voice ret ; a = max(0, -a) * 2 up_amount: mov b, 0 sub b, a mov a, 0 smax b, a shl b, 1 mov a, b ret draw: call build_text mov a, 0 draw_text_rows: push a call draw_row pop a inc a cmp a, 10 bnz draw_text_rows mov a, 21 call draw_row call draw_piano mov x, 64 mov p, 0x16 mov r, 1 call draw_stick mov x, 192 mov p, 0x18 mov r, 2 call draw_stick call draw_power sys present ret build_text: mov x, cells+32 mov r, 224 clear_rows: mov.b [x++], 32 dec r bnz clear_rows mov x, cells+32 mov p, sound_text call puts movzx a, [0x12] and a, 1 mov p, off_on call put_choice mov x, cells+43 mov p, midi_text call puts call midi_state mov p, midi_states call put_choice mov x, cells+64 mov p, buttons_text call puts movzx r, [0x14] mov p, abxy mov b, 4 button_letter: movzx a, [p++] mov y, r and y, 1 bz button_plain or a, 128 button_plain: mov.b [x++], a.lo inc x shr r, 1 dec b bnz button_letter mov x, cells+96 mov p, primary_text call puts movzx a, [0x11] and a, 3 mov p, modes call put_choice mov x, cells+115 movsx a, [0x16] call put_signed inc x movsx a, [0x17] call put_signed movzx a, [0x15] and a, 1 bz secondary_row mov.b [x+1], 42 secondary_row: mov x, cells+128 mov p, secondary_text call puts movzx a, [0x11] shr a, 2 and a, 3 mov p, modes call put_choice mov x, cells+147 movsx a, [0x18] call put_signed inc x movsx a, [0x19] call put_signed movzx a, [0x15] and a, 2 bz midi_row mov.b [x+1], 42 midi_row: mov x, cells+160 mov p, midi_last_text call puts mov p, midi_last mov r, 3 midi_bytes: movzx a, [p++] push p call put_hex pop p inc x dec r bnz midi_bytes mov x, cells+178 mov p, voices_text call puts mov p, voice_note mov r, 8 mov a, 48 count_voices: movzx b, [p++] cmp b, 255 bz count_next inc a count_next: dec r bnz count_voices mov.b [x], a.lo mov x, cells+192 mov p, key_text call puts movzx a, [last_key] call put_hex mov x, cells+204 mov p, touch_text call puts movzx a, [0x10] shr a, 1 and a, 1 mov p, off_on call put_choice mov x, cells+224 mov p, wave_text call puts movzx a, [wave] mov p, wave_names call put_choice mov x, cells+240 mov p, button_keys call puts ret ; a = MIDI part of the status byte as a name index. midi_state: movzx b, [0x12] mov a, 5 mov y, b and y, 32 bnz midi_state_done dec a mov y, b and y, 8 bnz midi_state_done dec a mov y, b and y, 16 bnz midi_state_done dec a mov y, b and y, 4 bnz midi_state_done dec a mov y, b and y, 2 bnz midi_state_done dec a midi_state_done: ret ; a = index into the word table at p; prints that string at x. put_choice: shl a, 1 add p, a mov p, [p] puts: movzx a, [p++] cmp a, 0 bz puts_end mov.b [x++], a.lo bra puts puts_end: ret ; a = signed value -999..999, printed as sign and three digits. put_signed: mov b, 43 cmp a, 0 bge put_digits mov b, 45 mov y, 0 sub y, a mov a, y put_digits: mov.b [x++], b.lo mov y, a div y, 100 add y, 48 mov.b [x++], y.lo mov y, a div y, 10 mod y, 10 add y, 48 mov.b [x++], y.lo mod a, 10 add a, 48 mov.b [x++], a.lo ret ; a = byte, printed as two hex digits. put_hex: mov y, a shr y, 4 call hex_digit mov y, a and y, 15 hex_digit: add y, 48 cmp y, 58 blo hex_store add y, 7 hex_store: mov.b [x++], y.lo ret ; a = text row 0-31; draws its 32 cells, bit 7 inverts a cell. draw_row: mov p, a shl p, 5 add p, cells mov x, a shl x, 11 mov.w [column], 32 draw_cell: movzx y, [p++] mov b, 0 cmp y, 128 blo draw_plain and y, 127 mov b, 255 draw_plain: mov.w [invert], b shl y, 3 add y, font mov r, 8 draw_line: movzx a, [y++] xor a, [invert] push r mov r, 8 draw_pixel: mov b, a and b, 1 mul b, 255 out.b [x++], b.lo shr a, 1 dec r bnz draw_pixel pop r add x, 248 dec r bnz draw_line sub x, 2040 mov a, [column] dec a mov.w [column], a bnz draw_cell ret ; x, y = top left, a = width, b = height, r = color rect: mov.w [0x62], x mov.w [0x64], y mov.w [0x66], a mov.w [0x68], b mov.b [0x6c], 0 mov.b [0x6d], r.lo blit ret ; a = note; b = 1 when a voice plays it. is_held: mov p, voice_note mov b, 8 held_loop: movzx y, [p++] cmp y, a bz held_yes dec b bnz held_loop ret held_yes: mov b, 1 ret draw_piano: mov x, 0 mov y, 176 mov a, 256 mov b, 80 mov r, 0x49 call rect mov.w [key], 0 white_key: mov p, white_notes add p, [key] movzx a, [p] call is_held mov r, 0xff cmp b, 0 bz white_color mov r, 0xfc white_color: mov x, [key] mul x, 18 add x, 3 mov y, 176 mov a, 16 mov b, 80 call rect mov a, [key] inc a mov.w [key], a cmp a, 14 bnz white_key mov.w [key], 0 black_key: mov p, black_after add p, [key] movzx a, [p] cmp a, 0 bz black_next mov p, white_notes add p, [key] movzx a, [p] inc a call is_held mov r, 0 cmp b, 0 bz black_color mov r, 0xe0 black_color: mov x, [key] mul x, 18 add x, 15 mov y, 176 mov a, 10 mov b, 48 call rect black_next: mov a, [key] inc a mov.w [key], a cmp a, 13 bnz black_key ret ; x = center X, p = address of signed X/Y, r = active bit at 0015. draw_stick: mov.w [center], x mov.w [stick], p mov.w [stick_bit], r sub x, 40 mov y, 84 mov a, 81 mov b, 81 mov r, 0x92 call rect mov x, [center] sub x, 39 mov y, 85 mov a, 79 mov b, 79 mov r, 0 call rect mov p, [stick] movsx x, [p] mov b, 5 smulshift x, b, 4 add x, [center] sub x, 3 movsx y, [p+1] mov b, 5 smulshift y, b, 4 add y, 121 mov r, 0x92 movzx a, [0x15] and a, [stick_bit] bz stick_dot mov r, 0xfc stick_dot: mov a, 7 mov b, 7 call rect ret ; LED: green when sound is live; while it starts (1 s after the tap), green ; blinking every 200 ms; then red if requested; otherwise dark. draw_power: mov x, 112 mov y, 113 mov a, 24 mov b, 24 mov r, 0x92 call rect mov r, 0x1c movzx a, [0x12] and a, 1 bnz power_fill mov r, 0x20 movzx a, [0x10] and a, 4 bz power_fill mov r, 0xe0 movzx a, [power_wait] cmp a, 0 bz power_fill mov b, 120 sub b, a div b, 24 and b, 1 mov r, 0x1c bz power_fill mov r, 0x20 power_fill: mov x, 114 mov y, 115 mov a, 20 mov b, 20 call rect ret title: .byte "FANCO-16 V9 DEVICE TEST", 0 help_keys: .byte "KEYS AWSEDFTGYHUJKOLP: C4-D#5", 0 help_buttons: .byte "SCREEN PADS FOLLOW MODES 1 AND 2", 0 button_keys: .byte "A=4 B=3 X=1 Y=2", 0 primary_label: .byte "FM/FDBK", 0 secondary_label: .byte "PAN/GRIT", 0 power_label: .byte "SND", 0 sound_text: .byte "SOUND ", 0 midi_text: .byte "MIDI ", 0 buttons_text: .byte "BUTTONS ", 0 abxy: .byte "ABXY" primary_text: .byte "1 PRIMARY ", 0 secondary_text: .byte "2 SECOND ", 0 midi_last_text: .byte "MIDI IN ", 0 voices_text: .byte "VOICES ", 0 key_text: .byte "LAST KEY ", 0 touch_text: .byte "3 TOUCH ", 0 wave_text: .byte "4 WAVE ", 0 off_text: .byte "OFF", 0 on_text: .byte "ON", 0 cursor_text: .byte "CURSOR", 0 digital_text: .byte "DIGITAL", 0 analog_text: .byte "ANALOG", 0 no_input_text: .byte "NO INPUT", 0 input_text: .byte "INPUT OK", 0 asking_text: .byte "ASKING", 0 no_api_text: .byte "NO API", 0 denied_text: .byte "DENIED", 0 sine_text: .byte "SINE", 0 triangle_text: .byte "TRIANGLE", 0 saw_text: .byte "SAW", 0 pulse_text: .byte "PULSE", 0 off_on: .word off_text, on_text modes: .word off_text, cursor_text, digital_text, analog_text midi_states: .word off_text, no_input_text, input_text, asking_text, no_api_text, denied_text wave_names: .word sine_text, triangle_text, saw_text, pulse_text wave_bits: .byte 0, 0x10, 0x20, 0x40 white_notes: .byte 60, 62, 64, 65, 67, 69, 71, 72, 74, 76, 77, 79, 81, 83 black_after: .byte 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0 letters: .byte "awsedftgyhujkolp" voice_note: .byte 255, 255, 255, 255, 255, 255, 255, 255 slot_note: .byte 255, 255, 255, 255, 255, 255, 255, 255 letter_held: .byte 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 midi_last: .byte 0, 0, 0 next_voice: .byte 0 wave: .byte 0 previous_buttons: .byte 0 power_held: .byte 0 power_wait: .byte 0 last_key: .byte 0 frame: .word 0 pressed: .word 0 depth: .word 0 feedback: .word 0 grit: .word 0 column: .word 0 invert: .word 0 key: .word 0 center: .word 0 stick: .word 0 stick_bit: .word 0