Hi!
I'm developing a motorised controller and have some questions on how to best control the platter.
I have a working prototype HID implementation. I can get values to and from VDJ. I can control the track movement with the jogwheel. The definition and mapping for that looks like this:
definition:
mapping:
The jogwheel is motorised and with this configuration the sound plays almost correctly but not fully when I run the platter. It's a bit glitchy, small tempo jerks here and there.
So this is not ideal and I see there's some better candidates in the API:
motorwheel and speedwheel
For motorwheel I have to run
It's not clear how I can get the the jogwheel data from the definition into those functions.
Similarly for the speedwheel. I have to provide 2 parameters. It's not clear how I make the link from the out page in the definition to variables in the mapping/scripts.
Can anyone provide an example on how to do that?
Other questions:
How often does VDJ read the HID reports? i.e. how often should I ideally create new reports with the platter position.
How does the <action> element in the controller definition work? Can you give an example? I have a feeling that might solve my question above.
Thanks!
Beau
I'm developing a motorised controller and have some questions on how to best control the platter.
I have a working prototype HID implementation. I can get values to and from VDJ. I can control the track movement with the jogwheel. The definition and mapping for that looks like this:
definition:
<page type="in">
<jog byte="1" size="word" full="65535" name="JOGWHEEL" deck="1" />
...
mapping:
<map value="JOGWHEEL" action="jogwheel" />
The jogwheel is motorised and with this configuration the sound plays almost correctly but not fully when I run the platter. It's a bit glitchy, small tempo jerks here and there.
So this is not ideal and I see there's some better candidates in the API:
motorwheel and speedwheel
For motorwheel I have to run
motorwheel "move" +1followed by
motorwheel "timestamp" 1000.0
It's not clear how I can get the the jogwheel data from the definition into those functions.
Similarly for the speedwheel. I have to provide 2 parameters. It's not clear how I make the link from the out page in the definition to variables in the mapping/scripts.
Can anyone provide an example on how to do that?
Other questions:
How often does VDJ read the HID reports? i.e. how often should I ideally create new reports with the platter position.
How does the <action> element in the controller definition work? Can you give an example? I have a feeling that might solve my question above.
Thanks!
Beau
Posté Sun 29 Jan 23 @ 9:34 pm
Add motor="true" to <device ... motor="true" />
For the mapping, you could use
motorwheel3 posdelta
and
motorwheel3 timedelta 1000
To get the data to these 2 from hid, you could use <jog> as you've done 2 times, one for the position and one for the time.
Your controller should have in the hid reports a number counting up when moving forward and counting down when moving backward, wrapping around at the boundaries defined in the <jog /> definition
The definition defines full= as one rotation.
Your controller should also have a counter that counts based on time, always counting up at a fixed speed regardless of what the jog is doing. For most microcontrollers I think there will probably be such a timer based on the clock speed that could be used and scaled.
The '1000' in the mapping is the number of counts per millisecond for the time counter. (currently it assume full=16384 so you may need to use a different value depending on your counter.
For the mapping, you could use
motorwheel3 posdelta
and
motorwheel3 timedelta 1000
To get the data to these 2 from hid, you could use <jog> as you've done 2 times, one for the position and one for the time.
Your controller should have in the hid reports a number counting up when moving forward and counting down when moving backward, wrapping around at the boundaries defined in the <jog /> definition
The definition defines full= as one rotation.
Your controller should also have a counter that counts based on time, always counting up at a fixed speed regardless of what the jog is doing. For most microcontrollers I think there will probably be such a timer based on the clock speed that could be used and scaled.
The '1000' in the mapping is the number of counts per millisecond for the time counter. (currently it assume full=16384 so you may need to use a different value depending on your counter.
Posté Sun 29 Jan 23 @ 10:18 pm
Thanks for the quick reply.
I added the time counter and I am able to move the track, but not correctly yet.
I don't fully get the scaling. A value of 16384 in the report page should correspond to a time of 1000 microseconds / 1millisecond? and it's the time delta since the last report?
I added the time counter and I am able to move the track, but not correctly yet.
I don't fully get the scaling. A value of 16384 in the report page should correspond to a time of 1000 microseconds / 1millisecond? and it's the time delta since the last report?
Posté Mon 30 Jan 23 @ 9:24 am
Your hid report should just count, calculating the delta between reports is done by vdj
Posté Mon 30 Jan 23 @ 10:21 am
Still missing something.. it runs now but way to fast.
I'm sending this value in the counter:
I use the same timer to send the position of a dummy platter for testing:
definition has:
mapping:
Using just
moves the track at almost correct speed, so I assume the position value is correct but the time counter isn't.
Bedankt voor de hulp!
I'm sending this value in the counter:
uint64_t time = esp_timer_get_time(); //microseconds
uint16_t time_vdj = (uint16_t)((time * 16384 / 1000)) ; //'full' 16384 = 1000us
I use the same timer to send the position of a dummy platter for testing:
uint16_t position = int(0.049152 * float(time)); //45pm=0.75rev/s 1s='full' 2^16 -> seems to give correct speed for 'jogwheel'
definition has:
<jog byte="1" size="word" full="65535" name="POSDELTA" deck="1" />
<jog byte="3" size="word" full="16384" name="TIMEDELTA" deck="1"/>
mapping:
<map value="POSDELTA" action="motorwheel3 posdelta" />
<map value="TIMEDELTA" action="motorwheel3 timedelta" />
Using just
<map value="POSDELTA" action="jogwheel" />
moves the track at almost correct speed, so I assume the position value is correct but the time counter isn't.
Bedankt voor de hulp!
Posté Mon 30 Jan 23 @ 12:25 pm
Did you try to add '1000' as mentioned for the mapping?
The default value when not adding '1000' is not 1000.
motorwheel3 timedelta 1000
The default value when not adding '1000' is not 1000.
Posté Mon 30 Jan 23 @ 1:07 pm
Yes, but it didn't change much.
I also tried this:
report counter with just microseconds, no other scaling.
definition with jog full="65535" i.e. the full 16bit range is used
and a mapping with
no luck... :/
I also tried this:
report counter with just microseconds, no other scaling.
definition with jog full="65535" i.e. the full 16bit range is used
and a mapping with
motorwheel3 timedelta 65536
no luck... :/
Posté Mon 30 Jan 23 @ 2:35 pm
Could it be that motorwheel3 posdelta expects 33RPM while jogwheel requires 45RPM?
When I run the position at 33RPM, with the motorwheel3 posdelta mapping, it runs almost correctly but only as long as the track is not in play mode (still the occasional nudge/glitch though..) As soon as I press play, it runs very very slow.
With the jogwheel mapping there's no difference between being paused but having the jogwheel move the track or in play mode.
When I run the position at 33RPM, with the motorwheel3 posdelta mapping, it runs almost correctly but only as long as the track is not in play mode (still the occasional nudge/glitch though..) As soon as I press play, it runs very very slow.
With the jogwheel mapping there's no difference between being paused but having the jogwheel move the track or in play mode.
Posté Mon 30 Jan 23 @ 4:15 pm
The rpm should follow the RPM setting.
Posté Mon 30 Jan 23 @ 4:24 pm
So I got it running.. but during play I have to scale position by 16.. as if I'm running 16x33rpm. Guessing It would give higher resolution needed for accurate playback.
Is that expected? I should scale my position output depending on the play/pause state?
i.e. when jogging during pause I send 33rpm, during play I send 16x33rpm?
(or 45rpm depending on the config)
Is that expected? I should scale my position output depending on the play/pause state?
i.e. when jogging during pause I send 33rpm, during play I send 16x33rpm?
(or 45rpm depending on the config)
Posté Mon 30 Jan 23 @ 4:41 pm
The resulting "speed" is always governed by the following three settings:
jogSensitivityScratch (default value = 1)
jogSensitivityCue (default value 0.5)
jogSensitivityBend (default value 0.05)
These values are multipliers.
Your controller should be defined in a way that produces the correct playback speed when the deck is playing (and therefore jogSensitivityScratch multiplier is used)
The easiest way to test this is to clone the motorized controlled deck while playing to a non motorized deck and watch if it drifts ahead or behind.
If you just set your jog to rotate while the deck is not in "play" status then the jogSensitivityCue multiplier is used. Which with the default values means that the deck should play at around half the normal speed.
Obviously in your case jogSensitivityBend is not used since you are using a motorized jog. On a non motorized jog it's used when the user spins a "touchwheel" without actually touching the top area (and thus pitch bending the track instead of scratching it)
jogSensitivityScratch (default value = 1)
jogSensitivityCue (default value 0.5)
jogSensitivityBend (default value 0.05)
These values are multipliers.
Your controller should be defined in a way that produces the correct playback speed when the deck is playing (and therefore jogSensitivityScratch multiplier is used)
The easiest way to test this is to clone the motorized controlled deck while playing to a non motorized deck and watch if it drifts ahead or behind.
If you just set your jog to rotate while the deck is not in "play" status then the jogSensitivityCue multiplier is used. Which with the default values means that the deck should play at around half the normal speed.
Obviously in your case jogSensitivityBend is not used since you are using a motorized jog. On a non motorized jog it's used when the user spins a "touchwheel" without actually touching the top area (and thus pitch bending the track instead of scratching it)
Posté Mon 30 Jan 23 @ 9:00 pm
I managed to get it running at the correct speed. It was the time delta value that was not correct.
I assumed with the definition having full=16384 and the mapping motorwheel3 timedelta 1000
That a value of 16384 should correspond to 1000 microseconds / 1ms:
but that's not the case. It seems it just wants microseconds, no scaling. Not sure what the 16384 value in the mapping means.. but it works now.
I do have a problem when I stop the wheel. The track doesn't stop but slowly drifts. Seemingly in the last direction the platter was moving before the stop. I looks like vdj thinks the controller is not sending updates anymore so it keeps moving at the last speed it detected? Is this expected behaviour? Can I configure that somehow?
If not, should I start/stop the track explicitly? How would I map a toggle element (1 bit, on = play...) to play/pause?
I assumed with the definition having full=16384 and the mapping motorwheel3 timedelta 1000
That a value of 16384 should correspond to 1000 microseconds / 1ms:
uint64_t time = esp_timer_get_time(); //microseconds
uint16_t time_vdj = (uint16_t)((time * 16384 / 1000)) ; //'full' 16384 = 1000us... this is wrong
but that's not the case. It seems it just wants microseconds, no scaling. Not sure what the 16384 value in the mapping means.. but it works now.
I do have a problem when I stop the wheel. The track doesn't stop but slowly drifts. Seemingly in the last direction the platter was moving before the stop. I looks like vdj thinks the controller is not sending updates anymore so it keeps moving at the last speed it detected? Is this expected behaviour? Can I configure that somehow?
If not, should I start/stop the track explicitly? How would I map a toggle element (1 bit, on = play...) to play/pause?
Posté Tue 14 Feb 23 @ 10:40 am
Ideally you continue to send updates but without movement. (you could reduce sending rate or stop after a while to lower processing power)
You can also combine it with sending pause/cue to vdj so that the motorwheel options in vdj will work properly, ignoring motor updates a few seconds after stop to prevent wobble
You can also combine it with sending pause/cue to vdj so that the motorwheel options in vdj will work properly, ignoring motor updates a few seconds after stop to prevent wobble
Posté Tue 14 Feb 23 @ 12:35 pm
I do keep sending updates. motorwheel3 timedelta keeps counting, just posdelta has the same value.. but when that happens vdj starts that slow drift.
Posté Tue 14 Feb 23 @ 1:59 pm
I added play/pause messages. This puts the track correctly in pause mode whenever the platter is not moving and vice-versa automatically in play when moving.
It only solves the drifting for a second while it ignores the platter wrt motorWheelLockTime, after it starts drifting again.
It only solves the drifting for a second while it ignores the platter wrt motorWheelLockTime, after it starts drifting again.
Posté Tue 14 Feb 23 @ 4:01 pm
I noticed the rane twelve mapping uses ns7_platter and ns7_platter 'timestamp'
Maybe I have better luck with those. What's the expected data for the timestamp?
Maybe I have better luck with those. What's the expected data for the timestamp?
Posté Tue 14 Feb 23 @ 4:28 pm
I managed to get it working.. but it's a bit hacky. I just assure I don't have any repeating position values.
When the platter is stopped I add some noise (position counter +5 if it would be the same value, I scaled the jog values to have a bigger range, 10x the actual encoder count so +5 is half a step out of 5760 and not noticeable, track looks completely stable)
This can't be the proper solution so I'd love to get a real fix for this. I could provide you with the code to program an esp32-s3 to replicate the problem, or even mail you one that's ready for testing.
Once I have the bugs ironed out I'll release the code with definition and mapping files to have full HID (platter) controller example code available.
When the platter is stopped I add some noise (position counter +5 if it would be the same value, I scaled the jog values to have a bigger range, 10x the actual encoder count so +5 is half a step out of 5760 and not noticeable, track looks completely stable)
This can't be the proper solution so I'd love to get a real fix for this. I could provide you with the code to program an esp32-s3 to replicate the problem, or even mail you one that's ready for testing.
Once I have the bugs ironed out I'll release the code with definition and mapping files to have full HID (platter) controller example code available.
Posté Tue 14 Feb 23 @ 8:42 pm
Hello Beau, have you success ?
I am trying to make a motorized controller too.
i get too much jitter .
I am using midi messages.
regards
I am trying to make a motorized controller too.
i get too much jitter .
I am using midi messages.
regards
Posté Thu 11 May 23 @ 9:12 pm
Hi, sorry for the late reply. I didn't see you post.
Yes, I got it working fine now. Still got some issues when trying to match the platter speed to the pitch setting though.
I think midi might be too slow for this though. Did you get it working?
Yes, I got it working fine now. Still got some issues when trying to match the platter speed to the pitch setting though.
I think midi might be too slow for this though. Did you get it working?
Posté Sat 05 Aug 23 @ 11:26 am
I am having the same problem and am having trouble understanding the full relationship between what my controller sends and what the definition and mapping files expect- particularly for the time values. All of my combinations of tweaking controller messages, definition file, and mapping file result in very fast or glitchy playback.
My controller has 8210 ticks per rotation with values wrapped from 0 to 8209, counting up for forward and down for backwards. My controller is sending time in microseconds wrapped from 0 to 16383. I have confirmed the data with HID Trace and the device is recognized and sends data to VDJ. Right now, I am sending an HID packet every platter tick change and running the platter at 33 1/3 RPM, but will implement a regular update as suggested by Adion.
My definition file:
and my mapping file:
I am confused about this text from the documentation: "The '1000' in the mapping is the number of counts per millisecond for the time counter. (currently it assume full=16384 so you may need to use a different value depending on your counter." Does full mean the amount of counts per rotation, or something else?
All jogSensitivity settings are default.
Clarification along the lines of "send microsecond timestamps wrapped to X, use full=Y in definition with max=Z, use mapping with timedelta 1000" would be greatly appreciated.
Thank you!
My controller has 8210 ticks per rotation with values wrapped from 0 to 8209, counting up for forward and down for backwards. My controller is sending time in microseconds wrapped from 0 to 16383. I have confirmed the data with HID Trace and the device is recognized and sends data to VDJ. Right now, I am sending an HID packet every platter tick change and running the platter at 33 1/3 RPM, but will implement a regular update as suggested by Adion.
My definition file:
<jog byte="1" size="word" full="8210" min="0" max="8209" name="POSDELTA" deck="1" />
<jog byte="3" size="word" full="16384" min="0" max="16383" name="TIMEDELTA" deck="1"/>
and my mapping file:
<map value="POSDELTA" action="motorwheel3 posdelta" />
<map value="TIMEDELTA" action="motorwheel3 timedelta 1000" />
I am confused about this text from the documentation: "The '1000' in the mapping is the number of counts per millisecond for the time counter. (currently it assume full=16384 so you may need to use a different value depending on your counter." Does full mean the amount of counts per rotation, or something else?
All jogSensitivity settings are default.
Clarification along the lines of "send microsecond timestamps wrapped to X, use full=Y in definition with max=Z, use mapping with timedelta 1000" would be greatly appreciated.
Thank you!
Posté Tue 21 May 24 @ 2:51 pm