Hey sorry if I sound like a dick Saiyato. Just want to get this working well (for everyone).
First: i’m still a bit of a noob with Linux, so I have no idea how to view the log from your plugin. There is no mention of it in the readme and I can’t trace it down in your code all that quickly., so I haven’t done that yet.
I’ve coded my own python script in standard Raspbian to control ALSA audio levels, worked on it until it felt smooth, responsive and clear. It became obvious to me when writing this that you want quite a bit of customization depending on your hardware and personal prefs. I obviously did not do any SW debounce.
I mention this to point out I did gain insight in programming the encoder for responsiveness.
I am familiar with Python and C, but have never writen javascript like your plugin. I did look at your code and did see a few things to mirror with my own experience.
I was inspired by moOde audio exposing 3 values: update cycle length (100ms default), accel rate (if more than a set amount of steps are gained in a cycle) and an accelerated step/gain rate for the volume (increased over standard if accel condition is met). Result is that you can finetune in small steps when going slow, or you can whizz through in faster increments if you spin fast. It does still allow you to customize the responsiveness to a high degree by exposing these 3 parameters (ok their UI is pretty bad for it, but still i got it working flawless in a few seconds with the same hardware).
So comparing my successfull effort to emulate this, to your code:
You seem to be only tracking an single upward or a downward step, your tick then sets this in single increment steps (volume plus/minus). theoretically it should work, but as Michelangelo said, perhap the async code messes with this, Timings are all very important for the responsive feeling I’ve noticed.
What I do instead:
keep track of encoder absolute position increments: in python this is a callback with a very small bounce/repeat time, 20ms works in my case. It’s only called if the encoder changes position and adds or subtracts 1 depending on direction.
A separate loop runs at a longer interval, 100ms works well (ideally customizable), and checks the DELTA/ difference between the current encoder abolute position and the one from the previous tick. It then sets the volume in a batch: not by doing single increments, but getting the current level, and adding or subtracting the delta multiplied by the rate. By using delta only, we’ve still got the position independence. I guess it would only break if you manage to overflow the integer or something (extremely unlikely).
The default gain rate can be 1 or 2 (ideally customizable) if more than the defined acceleration steps were made in a loop (customizable parameter), the rate is higher, 5 worked well for me (customizable param again).
Here’s my code:
import RPi.GPIO as GPIO
from time import sleep
import os
try:
import alsaaudio
except:
#this probably won't work but at least here's the commands
os.system("sudo apt-get install libasound2-dev")
os.system("sudo -H pip3 install pyalsaaudio")
import alsaaudio
class MediaControl:
CLOCKWISE = 1
ANTICLOCKWISE = 0
def __init__(self, p_controlmode = "default"):
self.m_alsamixer = alsaaudio.Mixer()
self.rotary1Pin = 23
self.rotary2Pin = 24
self.switchPin = 10
self.m_controlmode = p_controlmode
self.m_currpos = 0
self.m_prevpos = 0
self.m_accel = 3
self.m_slow = 2
self.m_fast = 5
GPIO.setmode(GPIO.BCM)
GPIO.setup(self.rotary1Pin, GPIO.IN)
GPIO.setup(self.rotary2Pin, GPIO.IN)
GPIO.setup(self.switchPin, GPIO.IN, pull_up_down = GPIO.PUD_UP)
def start(self):
GPIO.add_event_detect(self.rotary1Pin,
GPIO.FALLING,
callback=self._clockCallback,
bouncetime=50)
GPIO.add_event_detect(self.switchPin,
GPIO.FALLING,
callback=self._switchCallback,
bouncetime=300)
def stop(self):
GPIO.remove_event_detect(self.rotary1Pin)
GPIO.remove_event_detect(self.switchPin)
GPIO.cleanup()
print("GPIO released and cleaned")
def ChangeVolume(self, p_value):
l_currvol = int(self.m_alsamixer.getvolume()[0])
l_vol = l_currvol + p_value
if l_vol >= 100:
#print("setting volume with " + str(p_value) + " to " + str(l_vol))
self.m_alsamixer.setvolume(100)
elif l_vol <= 0:
self.m_alsamixer.setvolume(0)
else:
self.m_alsamixer.setvolume(l_vol)
def _clockCallback(self, pin):
data = GPIO.input(self.rotary2Pin)
if data == 1:
#ANTICLOCKWISE
self.m_currpos -=1
else:
#CLOCKWISE
self.m_currpos +=1
def _switchCallback(self, pin):
if GPIO.input(self.switchPin) == 0:
print("switch pressed")
def Check(self):
l_diff = self.m_prevpos - self.m_currpos
l_step = self.m_slow
if (abs(l_diff) >= self.m_accel):
l_step = self.m_fast
if l_diff != 0:
self.ChangeVolume(l_diff*l_step)
print(self.m_currpos)
self.m_prevpos = self.m_currpos
if __name__ == "__main__":
MediaMan = MediaControl()
MediaMan.start()
try:
while True:
MediaMan.Check()
sleep(0.1)
finally:
MediaMan.stop()