#IFNDEF __lib_mouse #DEFINE __lib_mouse 1 GOTO __skip_lib_mouse // ============================================================================ // COMMODORE 1351 MOUSE DRIVER FOR C64 // ============================================================================ // This library provides mouse input reading for the Commodore 1351 mouse. // // The 1351 uses the SID chip's POT lines to report relative movement. // The SID hardware automatically samples at ~2kHz, but software should // read once per frame (50-60Hz) to minimize CPU usage. // // Mouse is read from CONTROL PORT 1 (POT lines + buttons) // This allows simultaneous use with joystick on CONTROL PORT 2 // // Based on implementations from C64 OS and cc65. // // FAST MOVEMENT SMOOTHING: // By default, directional momentum smoothing is enabled to fix the 1351's // known issue where fast movements can register in the opposite direction. // To disable smoothing (saves CPU cycles), define MOUSE_NO_SMOOTHING: // #DEFINE MOUSE_NO_SMOOTHING 1 // #INCLUDE "mouse.c65" // // Usage: // #INCLUDE "mouse.c65" // mouse_init() // WHILE 1 // mouse_read() // Call once per frame // pointer_update_mouse(mouse_delta_x, mouse_delta_y) // WEND // ============================================================================ // SID POT line addresses WORD CONST SID_BASE = $D400 BYTE pot_x @ SID_BASE+$19 // SID+$19: POT X (analog input) BYTE pot_y @ SID_BASE+$1A // SID+$1A: POT Y (analog input) // CIA port for button reading (same as joystick port 1) WORD CONST CIA1_BASE = $DC00 BYTE cia_port1 @ CIA1_BASE+1 // $DC01: Port 1 (mouse buttons) // Mouse state variables BYTE mouse_pot_x_old // Previous POT X reading BYTE mouse_pot_y_old // Previous POT Y reading BYTE mouse_delta_x // Signed X movement delta BYTE mouse_delta_y // Signed Y movement delta BYTE mouse_buttons // Button state (bit 4=right, bit 0=left) #IFNDEF MOUSE_NO_SMOOTHING // Directional momentum tracking (for fast movement disambiguation) // 0=no momentum, 1-127=rightward, 128-255=leftward (unsigned byte as signed) // Define MOUSE_NO_SMOOTHING before including this library to disable smoothing BYTE mouse_momentum_x BYTE mouse_momentum_y // ============================================================================ // FUNC mouse_apply_momentum // Apply directional momentum to disambiguate fast movements // When delta magnitude is large (ambiguous), use momentum to determine // if we should flip the interpretation (±64 in 6-bit space) // ============================================================================ FUNC mouse_apply_momentum({BYTE delta} {BYTE momentum} out:{BYTE corrected_delta} out:{BYTE new_momentum}) BYTE abs_delta BYTE is_negative BYTE is_ambiguous BYTE was_corrected // Get absolute value and sign of delta is_negative = delta & $80 IF is_negative abs_delta = 0 - delta ELSE abs_delta = delta ENDIF // Check if delta is ambiguous (magnitude >= 12 after division by 2 in convert function) // In raw 6-bit space, deltas near ±32 become ±16 after /2, which is ambiguous is_ambiguous = 0 was_corrected = 0 #PRAGMA _P_USE_LONG_JUMP 1 IF abs_delta >= 12 #PRAGMA _P_USE_LONG_JUMP 0 is_ambiguous = 1 // Ambiguous - check if flipping (negating) would match momentum better BYTE flipped_delta BYTE flipped_negative // To flip interpretation, just negate the delta flipped_delta = abs_delta IF is_negative // Delta is negative, flipped would be positive flipped_negative = 0 ELSE // Delta is positive, flipped would be negative flipped_negative = 1 ENDIF // Use momentum to decide: if momentum sign matches flipped, use flipped IF momentum != 0 BYTE momentum_is_left BYTE signs_match momentum_is_left = 0 IF momentum >= 128 momentum_is_left = 1 ENDIF signs_match = 0 IF momentum_is_left == flipped_negative signs_match = 1 ENDIF IF signs_match == 1 // Momentum suggests flipped interpretation IF flipped_negative corrected_delta = 0 - flipped_delta ELSE corrected_delta = flipped_delta ENDIF was_corrected = 1 ELSE // Keep original corrected_delta = delta was_corrected = 1 ENDIF ELSE // No momentum yet - ignore this ambiguous delta, return 0 corrected_delta = 0 ENDIF ELSE // Unambiguous, use as-is corrected_delta = delta ENDIF // Update momentum based on corrected delta // Only update from unambiguous deltas to avoid bootstrapping in wrong direction #PRAGMA _P_USE_LONG_JUMP 1 IF is_ambiguous == 0 #PRAGMA _P_USE_LONG_JUMP 0 IF corrected_delta == 0 // No movement - decay momentum toward zero IF momentum > 0 IF momentum < 128 IF momentum > 2 momentum = momentum - 2 ELSE momentum = 0 ENDIF ELSE IF momentum < 253 momentum = momentum + 2 ELSE momentum = 0 ENDIF ENDIF ENDIF ELSE // Add corrected delta to momentum (clamped) BYTE corrected_negative corrected_negative = corrected_delta & $80 IF corrected_negative // Moving left - push momentum toward 128-255 IF momentum < 128 momentum = 128 ELSE IF momentum < 240 momentum = momentum + 8 ENDIF ENDIF ELSE // Moving right - push momentum toward 1-127 IF momentum >= 128 momentum = 127 ELSE IF momentum < 120 momentum = momentum + 8 ENDIF ENDIF ENDIF ENDIF ENDIF new_momentum = momentum FEND #IFEND // ============================================================================ // FUNC mouse_convert_pot_delta // Convert 6-bit POT delta to 8-bit signed delta // The 1351 outputs 6-bit signed values in bits 1-6 (bits 0,7 are noise) // This converts them to standard 8-bit signed values (-128 to +127) // ============================================================================ FUNC mouse_convert_pot_delta({BYTE new_val} {BYTE old_val} out:{BYTE delta}) BYTE diff BYTE sign_bit // Calculate raw difference (wraps at 64 due to 6-bit counter) diff = new_val - old_val // Mask to 7 bits (ignore bit 7 noise) diff = diff & %01111111 // Check if value is negative (bit 6 set means >= 64 in 6-bit space) sign_bit = diff & %01000000 IF sign_bit // Negative value: set high bits and divide by 2 diff = diff | %11000000 // Sign extend ASM lda |diff| cmp #$FF beq @zero sec ror // Arithmetic shift right (preserves sign) sta |diff| jmp @done @zero: lda #0 sta |diff| @done: ENDASM delta = diff ELSE // Positive value: just divide by 2 ASM lda |diff| beq @zero2 lsr // Logical shift right sta |diff| jmp @done2 @zero2: lda #0 sta |diff| @done2: ENDASM delta = diff ENDIF FEND // ============================================================================ // FUNC mouse_init // Initialize mouse driver - call once at program start // ============================================================================ FUNC mouse_init // Read initial pot values mouse_pot_x_old = pot_x mouse_pot_y_old = pot_y mouse_delta_x = 0 mouse_delta_y = 0 mouse_buttons = 0 #IFNDEF MOUSE_NO_SMOOTHING mouse_momentum_x = 0 mouse_momentum_y = 0 #IFEND FEND // ============================================================================ // FUNC mouse_read // Read mouse movement and button state // Call this every frame to update mouse state // Updates mouse_delta_x, mouse_delta_y (signed 8-bit deltas) // Applies directional momentum to fix fast movement bugs // ============================================================================ FUNC mouse_read BYTE pot_x_new BYTE pot_y_new // Read current POT values pot_x_new = pot_x pot_y_new = pot_y // Calculate X delta mouse_convert_pot_delta(pot_x_new, mouse_pot_x_old, mouse_delta_x) mouse_pot_x_old = pot_x_new #IFNDEF MOUSE_NO_SMOOTHING // Apply momentum correction to X mouse_apply_momentum(mouse_delta_x, mouse_momentum_x, mouse_delta_x, mouse_momentum_x) #IFEND // Calculate Y delta (invert for correct screen movement) mouse_convert_pot_delta(pot_y_new, mouse_pot_y_old, mouse_delta_y) mouse_delta_y = 0 - mouse_delta_y // Negate Y mouse_pot_y_old = pot_y_new #IFNDEF MOUSE_NO_SMOOTHING // Apply momentum correction to Y mouse_apply_momentum(mouse_delta_y, mouse_momentum_y, mouse_delta_y, mouse_momentum_y) #IFEND // Read buttons from CIA port mouse_buttons = cia_port1 FEND LABEL __skip_lib_mouse #IFEND