Files
LEDMatrix/config/config.template.json
T
ChuckBuildsandClaude 7b6b7cd153 Sub-pixel scrolling: motion at the frame rate, not the pixel rate
With integer positioning the number of distinct frames per second equals the
scroll speed in px/s, however fast the loop renders. Measured at 50px/s and
78.7fps, 36% of frames were byte-identical: the extra frames cost work and
bought no motion, and what was left was 50 discrete 1px steps a second.

Two things were wrong with the pre-existing sub-pixel support. get_visible_portion
never consulted sub_pixel_scrolling — it always took the integer path, so the flag
and _get_visible_portion_subpixel were dead code. And that implementation needed
scipy.ndimage.shift, which is not installed on the target devices (HAS_SCIPY is
False there), so it would not have interpolated even if reached. Verified both:
positions 1000.0 and 1000.5 produced identical frames either way.

Blending is now wired up and implemented with numpy. Two details make it
affordable: slice cached_array directly instead of building two PIL images only
to convert them straight back (the naive version measured 15x the integer path),
and use fixed-point uint16 multiply-add rather than float32, which suits the Pi's
cores and gives finer weighting than the panel can resolve. Result 0.939ms
against 0.237ms — 0.70ms added per frame, a 1065fps ceiling.

Measured on hardware: 81.2 fps with blending on, against 78.7 with it off, so no
cost within noise — and every frame is now a distinct position rather than one in
three being a repeat.

The trade is a slight horizontal softening of text, since each frame blends two
positions. Set smooth_scroll false for maximum crispness.

Also benchmarked and cleared as non-issues: extending the strip costs 9.4ms on an
11,000px strip and trimming 2.5ms, both under one frame at this rate.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
2026-07-29 15:14:41 -04:00

168 lines
4.6 KiB
JSON

{
"web_display_autostart": true,
"schedule": {
"enabled": false,
"mode": "per-day",
"start_time": "07:00",
"end_time": "23:00",
"days": {
"monday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"tuesday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"wednesday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"thursday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"friday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"saturday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"sunday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
}
}
},
"dim_schedule": {
"enabled": false,
"dim_brightness": 30,
"mode": "global",
"start_time": "20:00",
"end_time": "07:00",
"days": {
"monday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"tuesday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"wednesday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"thursday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"friday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"saturday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"sunday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
}
}
},
"timezone": "America/New_York",
"location": {
"city": "Tampa",
"state": "Florida",
"country": "US"
},
"display": {
"hardware": {
"rows": 32,
"cols": 64,
"chain_length": 2,
"parallel": 1,
"brightness": 90,
"hardware_mapping": "adafruit-hat",
"scan_mode": 0,
"pwm_bits": 9,
"pwm_dither_bits": 1,
"pwm_lsb_nanoseconds": 130,
"disable_hardware_pulsing": false,
"inverse_colors": false,
"show_refresh_rate": false,
"led_rgb_sequence": "RGB",
"limit_refresh_rate_hz": 100
},
"runtime": {
"gpio_slowdown": 3,
"rp1_rio": 0
},
"double_sided": {
"enabled": false,
"copies": 2,
"axis": "horizontal"
},
"display_durations": {},
"plugin_rotation_order": [],
"use_short_date_format": true,
"vegas_scroll": {
"enabled": false,
"scroll_speed": 50,
"separator_width": 32,
"plugin_order": [],
"excluded_plugins": [],
"target_fps": 125,
"buffer_ahead": 2,
"intra_plugin_gap": 8,
"render_width_pct": 100,
"min_content_separation": 24,
"min_cut_gap": 6,
"continuous_scroll": true,
"smooth_scroll": true,
"extend_threshold_screens": 2.0,
"auto_trim": true,
"trim_threshold": 10,
"content_padding": 8,
"min_plugin_width": 8,
"lead_in_width": 0,
"plugins_per_cycle": 6,
"max_plugin_width_ratio": 3.0,
"dynamic_duration_enabled": true,
"min_cycle_duration": 60,
"max_cycle_duration": 240
}
},
"sync": {
"role": "standalone",
"port": 5765,
"follower_position": "left"
},
"plugin_system": {
"plugins_directory": "plugin-repos",
"auto_discover": true,
"auto_load_enabled": true
},
"web-ui-info": {
"enabled": true,
"display_duration": 10
}
}