2 Copyright (c) 2020 Adrian I. Lam
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38 #include <pulse/simple.h>
40 #include "mouse_cursor_tracker.h"
42 static double rms(float *buf
, std
::size_t count
)
45 for (std
::size_t i
= 0; i
< count
; i
++)
47 sum
+= buf
[i
] * buf
[i
];
49 return std
::sqrt(sum
/ count
);
52 MouseCursorTracker
::MouseCursorTracker(std
::string cfgPath
)
56 m_xdo
= xdo_new(nullptr);
58 const pa_sample_spec ss
=
60 .format
= PA_SAMPLE_FLOAT32NE
,
64 m_pulse
= pa_simple_new(nullptr, "MouseCursorTracker", PA_STREAM_RECORD
,
65 nullptr, "LipSync", &ss
, nullptr, nullptr, nullptr);
68 throw std
::runtime_error("Unable to create pulse");
71 m_getVolumeThread
= std
::thread(&MouseCursorTracker
::audioLoop
, this);
74 void MouseCursorTracker
::audioLoop(void)
76 float *buf
= new float[m_cfg
.audioBufSize
];
78 std
::size_t audioBufByteSize
= m_cfg
.audioBufSize
* sizeof *buf
;
82 if (pa_simple_read(m_pulse
, buf
, audioBufByteSize
, nullptr) < 0)
84 throw std
::runtime_error("Unable to get audio data");
86 m_currentVol
= rms(buf
, m_cfg
.audioBufSize
);
92 MouseCursorTracker
::~MouseCursorTracker()
95 m_getVolumeThread
.join();
96 pa_simple_free(m_pulse
);
99 void MouseCursorTracker
::stop(void)
104 MouseCursorTracker
::Params MouseCursorTracker
::getParams(void) const
106 Params params
= Params();
108 int xOffset
= m_curPos
.x
- m_cfg
.middle
.x
;
109 int leftRange
= m_cfg
.middle
.x
- m_cfg
.left
;
110 int rightRange
= m_cfg
.right
- m_cfg
.middle
.x
;
112 if (xOffset
> 0) // i.e. to the right
114 params
.faceXAngle
= 30.0 * xOffset
/ rightRange
;
118 params
.faceXAngle
= 30.0 * xOffset
/ leftRange
;
121 int yOffset
= m_curPos
.y
- m_cfg
.middle
.y
;
122 int topRange
= m_cfg
.middle
.y
- m_cfg
.top
;
123 int bottomRange
= m_cfg
.bottom
- m_cfg
.middle
.y
;
125 if (yOffset
> 0) // downwards
127 params
.faceYAngle
= -30.0 * yOffset
/ bottomRange
;
131 params
.faceYAngle
= -30.0 * yOffset
/ topRange
;
134 params
.faceZAngle
= 0;
136 params
.leftEyeOpenness
= 1;
137 params
.rightEyeOpenness
= 1;
139 params
.autoBlink
= m_cfg
.autoBlink
;
140 params
.autoBreath
= m_cfg
.autoBreath
;
141 params
.randomMotion
= m_cfg
.randomMotion
;
142 params
.useLipSync
= m_cfg
.useLipSync
;
144 params
.mouthForm
= m_cfg
.mouthForm
;
146 if (m_cfg
.useLipSync
)
148 params
.lipSyncParam
= m_currentVol
* m_cfg
.lipSyncGain
;
149 if (params
.lipSyncParam
< m_cfg
.lipSyncCutOff
)
151 params
.lipSyncParam
= 0;
153 else if (params
.lipSyncParam
> 1)
155 params
.lipSyncParam
= 1;
160 // Leave everything else as zero
166 void MouseCursorTracker
::mainLoop(void)
174 xdo_get_mouse_location(m_xdo
, &x
, &y
, &screenNum
);
176 if (screenNum
== m_cfg
.screen
)
181 // else just silently ignore for now
182 std
::this_thread
::sleep_for(std
::chrono
::milliseconds(m_cfg
.sleepMs
));
186 void MouseCursorTracker
::parseConfig(std
::string cfgPath
)
188 populateDefaultConfig();
192 std
::ifstream
file(cfgPath
);
195 throw std
::runtime_error("Failed to open config file");
198 unsigned int lineNum
= 0;
200 while (std
::getline(file
, line
))
207 std
::istringstream
ss(line
);
208 std
::string paramName
;
212 if (paramName
== "sleep_ms")
214 if (!(ss
>> m_cfg
.sleepMs
))
216 throw std
::runtime_error("Error parsing sleep_ms");
219 else if (paramName
== "autoBlink")
221 if (!(ss
>> m_cfg
.autoBlink
))
223 throw std
::runtime_error("Error parsing autoBlink");
226 else if (paramName
== "autoBreath")
228 if (!(ss
>> m_cfg
.autoBreath
))
230 throw std
::runtime_error("Error parsing autoBreath");
233 else if (paramName
== "randomMotion")
235 if (!(ss
>> m_cfg
.randomMotion
))
237 throw std
::runtime_error("Error parsing randomMotion");
240 else if (paramName
== "useLipSync")
242 if (!(ss
>> m_cfg
.useLipSync
))
244 throw std
::runtime_error("Error parsing useLipSync");
247 else if (paramName
== "lipSyncGain")
249 if (!(ss
>> m_cfg
.lipSyncGain
))
251 throw std
::runtime_error("Error parsing lipSyncGain");
254 else if (paramName
== "lipSyncCutOff")
256 if (!(ss
>> m_cfg
.lipSyncCutOff
))
258 throw std
::runtime_error("Error parsing lipSyncCutOff");
261 else if (paramName
== "audioBufSize")
263 if (!(ss
>> m_cfg
.audioBufSize
))
265 throw std
::runtime_error("Error parsing audioBufSize");
268 else if (paramName
== "mouthForm")
270 if (!(ss
>> m_cfg
.mouthForm
))
272 throw std
::runtime_error("Error parsing mouthForm");
275 else if (paramName
== "screen")
277 if (!(ss
>> m_cfg
.screen
))
279 throw std
::runtime_error("Error parsing screen");
282 else if (paramName
== "middle_x")
284 if (!(ss
>> m_cfg
.middle
.x
))
286 throw std
::runtime_error("Error parsing middle_x");
289 else if (paramName
== "middle_y")
291 if (!(ss
>> m_cfg
.middle
.y
))
293 throw std
::runtime_error("Error parsing middle_y");
296 else if (paramName
== "top")
298 if (!(ss
>> m_cfg
.top
))
300 throw std
::runtime_error("Error parsing top");
303 else if (paramName
== "bottom")
305 if (!(ss
>> m_cfg
.bottom
))
307 throw std
::runtime_error("Error parsing bottom");
310 else if (paramName
== "left")
312 if (!(ss
>> m_cfg
.left
))
314 throw std
::runtime_error("Error parsing left");
317 else if (paramName
== "right")
319 if (!(ss
>> m_cfg
.right
))
321 throw std
::runtime_error("Error parsing right");
326 throw std
::runtime_error("Unrecognized config parameter");
333 void MouseCursorTracker
::populateDefaultConfig(void)
336 m_cfg
.autoBlink
= true;
337 m_cfg
.autoBreath
= true;
338 m_cfg
.randomMotion
= false;
339 m_cfg
.useLipSync
= true;
340 m_cfg
.lipSyncGain
= 10;
341 m_cfg
.lipSyncCutOff
= 0.15;
342 m_cfg
.audioBufSize
= 4096;
347 m_cfg
.right
= 1919; // These will be the full screen for 1920x1080
350 m_cfg
.middle
= {1600, 870}; // Somewhere near the bottom right