Add rate-limited parameter updates and message type system

- Rate limit parameter updates to 100ms minimum interval
- Send immediate updates if rate limit allows, otherwise queue
- Process pending updates during beat handling
- All knob changes (CC30-37) now trigger parameter updates
- Add message type field: 'b' for beats, 'u' for updates
- Optimize message type to single letters to save packet space
- Prevents ESP-NOW network flooding during rapid knob adjustments
- All packets stay under 230-byte limit with automatic splitting
This commit is contained in:
2025-09-18 22:11:17 +12:00
parent fcbe9e9094
commit 5f7db51851

View File

@@ -59,6 +59,12 @@ class MidiHandler:
self.current_bpm: float | None = None
self.current_pattern: str = ""
self.beat_index: int = 0
# Rate limiting for parameter updates
self.last_param_update: float = 0.0
self.param_update_interval: float = 0.1 # 100ms minimum between updates
self.pending_param_update: bool = False
# Sequential pulse pattern state
self.sequential_pulse_enabled: bool = False
self.sequential_pulse_step: int = 0
@@ -79,6 +85,7 @@ class MidiHandler:
# Create minimal payload - only send pattern
payload = {
"d": { # Defaults - only pattern
"t": "b", # Message type: beat
"pt": "p", # pulse
}
}
@@ -100,6 +107,7 @@ class MidiHandler:
# Create minimal payload - only send what changes
payload = {
"d": { # Defaults - only pattern and n1/n2
"t": "b", # Message type: beat
"pt": "a", # alternating
"n1": self.n1,
"n2": self.n2,
@@ -126,6 +134,7 @@ class MidiHandler:
# Calculate packet size for full parameters
full_payload = {
"d": {
"t": "u", # Message type: update
"pt": PATTERN_NAMES.get(self.current_pattern, self.current_pattern),
"dl": self.delay,
"cl": [self._current_color_rgb()],
@@ -144,6 +153,7 @@ class MidiHandler:
# Packet 1: Pattern and timing parameters
payload1 = {
"d": {
"t": "u", # Message type: update
"pt": PATTERN_NAMES.get(self.current_pattern, self.current_pattern),
"dl": self.delay,
"br": self.brightness,
@@ -155,6 +165,7 @@ class MidiHandler:
# Packet 2: Color and pattern parameters
payload2 = {
"d": {
"t": "u", # Message type: update
"cl": [self._current_color_rgb()],
"n1": self.n1,
"n2": self.n2,
@@ -176,10 +187,26 @@ class MidiHandler:
logging.debug(f"[Full Params] Sending single packet ({payload_size} bytes)")
await self.ws_client.send_data(full_payload)
async def _request_param_update(self):
"""Request a parameter update with rate limiting"""
import time
current_time = time.time()
if current_time - self.last_param_update >= self.param_update_interval:
# Can send immediately
self.last_param_update = current_time
await self._send_full_parameters()
logging.debug("[Rate Limit] Parameter update sent immediately")
else:
# Rate limited - mark as pending
self.pending_param_update = True
logging.debug("[Rate Limit] Parameter update queued (rate limited)")
async def _send_normal_pattern(self):
"""Send normal pattern to all bars - minimal payload for beats"""
payload = {
"d": { # Defaults - only pattern
"t": "b", # Message type: beat
"pt": PATTERN_NAMES.get(self.current_pattern, self.current_pattern),
}
}
@@ -233,6 +260,16 @@ class MidiHandler:
if self.beat_index % 8 == 0:
await self._send_full_parameters()
# Check for pending parameter updates (rate limited)
if self.pending_param_update:
import time
current_time = time.time()
if current_time - self.last_param_update >= self.param_update_interval:
self.last_param_update = current_time
self.pending_param_update = False
await self._send_full_parameters()
logging.debug("[Rate Limit] Pending parameter update sent")
if self.current_pattern == "sequential_pulse":
# Sequential pulse pattern: each bar pulses for 1 beat, then next bar, mirrored
await self._handle_sequential_pulse()
@@ -369,9 +406,11 @@ class MidiHandler:
case 36:
self.n3 = max(1, msg.value) # Update n3 step rate
logging.info(f"n3 set to {self.n3} by MIDI controller (CC36)")
await self._request_param_update()
case 37:
self.delay = msg.value * 4 # Update instance delay
logging.info(f"Delay set to {self.delay} ms by MIDI controller (CC37)")
await self._request_param_update()
case 27:
if msg.value == 127:
self.beat_sending_enabled = True
@@ -387,24 +426,30 @@ class MidiHandler:
# Map 0-127 to 0-100 brightness scale
self.brightness = round((msg.value / 127) * 100)
logging.info(f"Brightness set to {self.brightness} by MIDI controller (CC33)")
await self._request_param_update()
case 30:
# Red 0-127 -> 0-255
self.color_r = round((msg.value / 127) * 255)
logging.info(f"Red set to {self.color_r}")
await self._request_param_update()
case 31:
# Green 0-127 -> 0-255
self.color_g = round((msg.value / 127) * 255)
logging.info(f"Green set to {self.color_g}")
await self._request_param_update()
case 32:
# Blue 0-127 -> 0-255
self.color_b = round((msg.value / 127) * 255)
logging.info(f"Blue set to {self.color_b}")
await self._request_param_update()
case 34:
self.n1 = int(msg.value)
logging.info(f"n1 set to {self.n1} by MIDI controller (CC34)")
await self._request_param_update()
case 35:
self.n2 = int(msg.value)
logging.info(f"n2 set to {self.n2} by MIDI controller (CC35)")
await self._request_param_update()
await asyncio.sleep(0.001) # Important: Yield control to asyncio event loop