Use editline instead of GNU Readline
[mouse-tracker-for-cubism.git] / src / mouse_cursor_tracker.cpp
1 /****
2 Copyright (c) 2020 Adrian I. Lam
3
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:
10
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13
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
20 SOFTWARE.
21 ****/
22
23 #include <stdexcept>
24 #include <string>
25 #include <chrono>
26 #include <thread>
27 #include <fstream>
28 #include <sstream>
29 #include <vector>
30 #include <utility>
31 #include <algorithm>
32 #include <cstdlib>
33 #include <cmath>
34 #include <cassert>
35
36 #include <iostream>
37 #include <cctype>
38 #include <mutex>
39
40 extern "C"
41 {
42 #include <xdo.h>
43 #include <pulse/simple.h>
44 #include <string.h> // strdup
45 #include "editline.h"
46 }
47 #include "mouse_cursor_tracker.h"
48
49 static double rms(float *buf, std::size_t count)
50 {
51 double sum = 0;
52 for (std::size_t i = 0; i < count; i++)
53 {
54 sum += buf[i] * buf[i];
55 }
56 return std::sqrt(sum / count);
57 }
58
59 static std::vector<std::string> split(std::string s)
60 {
61 std::vector<std::string> v;
62 std::string tmp;
63
64 for (std::size_t i = 0; i < s.length(); i++)
65 {
66 char c = s[i];
67 if (std::isspace(c))
68 {
69 if (tmp != "")
70 {
71 v.push_back(tmp);
72 tmp = "";
73 }
74 }
75 else
76 {
77 tmp += c;
78 }
79 }
80 if (tmp != "")
81 {
82 v.push_back(tmp);
83 }
84 return v;
85 }
86
87
88 /* Using readline callback functions means that we need to pass
89 * information from our class to the callback, and the only way
90 * to do so is using globals.
91 */
92
93 // Taken from https://github.com/eliben/code-for-blog/blob/master/2016/readline-samples/utils.cpp
94 static std::string longest_common_prefix(std::string s,
95 const std::vector<std::string>& candidates) {
96 assert(candidates.size() > 0);
97 if (candidates.size() == 1) {
98 return candidates[0];
99 }
100
101 std::string prefix(s);
102 while (true) {
103 // Each iteration of this loop advances to the next location in all the
104 // candidates and sees if they match up to it.
105 size_t nextloc = prefix.size();
106 auto i = candidates.begin();
107 if (i->size() <= nextloc) {
108 return prefix;
109 }
110 char nextchar = (*(i++))[nextloc];
111 for (; i != candidates.end(); ++i) {
112 if (i->size() <= nextloc || (*i)[nextloc] != nextchar) {
113 // Bail out if there's a mismatch for this candidate.
114 return prefix;
115 }
116 }
117 // All candidates have contents[nextloc] == nextchar, so we can safely
118 // extend the prefix.
119 prefix.append(1, nextchar);
120 }
121
122 assert(0 && "unreachable");
123 }
124
125 std::vector<std::string> commands =
126 {
127 "help", "motion", "expression", "set", "clear"
128 };
129
130 std::vector<std::string> live2dParams =
131 { // https://docs.live2d.com/cubism-editor-manual/standard-parametor-list/?locale=ja
132 "ParamAngleX", "ParamAngleY", "ParamAngleZ",
133 "ParamEyeLOpen", "ParamEyeLSmile", "ParamEyeROpen", "ParamEyeRSmile",
134 "ParamEyeBallX", "ParamEyeBallY", "ParamEyeBallForm",
135 "ParamBrowLY", "ParamBrowRY", "ParamBrowLX", "ParamBrowRX",
136 "ParamBrowLAngle", "ParamBrowRAngle", "ParamBrowLForm", "ParamBrowRForm",
137 "ParamMouthForm", "ParamMouthOpenY", "ParamTere",
138 "ParamBodyAngleX", "ParamBodyAngleY", "ParamBodyAngleZ", "ParamBreath",
139 "ParamArmLA", "ParamArmRA", "ParamArmLB", "ParamArmRB",
140 "ParamHandL", "ParamHandR",
141 "ParamHairFront", "ParamHairSide", "ParamHairBack", "ParamHairFluffy",
142 "ParamShoulderY", "ParamBustX", "ParamBustY",
143 "ParamBaseX", "ParamBaseY"
144 };
145 std::vector<std::pair<std::string, int> > MCT_motions;
146 std::vector<std::string> MCT_expressions;
147 std::map<std::string, double> *MCT_overrideMap;
148
149 static int cliListPossible(char *token, char ***av)
150 {
151 // Reference: https://github.com/eliben/code-for-blog/blob/master/2016/readline-samples/readline-complete-subcommand.cpp
152
153 std::string line(rl_line_buffer);
154
155 std::vector<std::string> cmdline = split(line);
156
157 std::vector<std::string> constructed;
158 std::vector<std::string> *vocab = nullptr;
159
160 if (cmdline.size() == 0 ||
161 (cmdline.size() == 1 && line.back() != ' ') ||
162 cmdline[0] == "help")
163 {
164 vocab = &commands;
165 }
166 else if (cmdline[0] == "motion")
167 {
168 for (auto it = MCT_motions.begin(); it != MCT_motions.end(); ++it)
169 {
170 if ((cmdline.size() == 1 && line.back() == ' ') ||
171 (cmdline.size() == 2 && line.back() != ' '))
172 { // motionGroup
173 {
174 constructed.push_back(it->first);
175 }
176 }
177 else if ((cmdline.size() == 2 && line.back() == ' ') ||
178 (cmdline.size() == 3 && line.back() != ' '))
179 { // motionNumber
180 if (it->first == cmdline[1])
181 {
182 for (int i = 0; i < it->second; i++)
183 {
184 constructed.push_back(std::to_string(i));
185 }
186 break;
187 }
188 }
189 else if (cmdline.size() <= 4)
190 { // priority
191 for (int i = 0; i < 4; i++)
192 {
193 constructed.push_back(std::to_string(i));
194 }
195 break; // No need to loop through the list, all motions have the same set of priorities
196 }
197 }
198
199 vocab = &constructed;
200 }
201 else if (cmdline[0] == "expression")
202 {
203 vocab = &MCT_expressions;
204 }
205 else if (cmdline[0] == "set")
206 {
207 if ((cmdline.size() % 2 == 0 && line.back() != ' ') ||
208 (cmdline.size() % 2 == 1 && line.back() == ' '))
209 {
210 vocab = &live2dParams;
211 }
212 }
213 else if (cmdline[0] == "clear")
214 {
215 constructed.push_back("all");
216 for (auto const &entry : *MCT_overrideMap)
217 {
218 constructed.push_back(entry.first);
219 }
220 vocab = &constructed;
221 }
222
223 if (!vocab)
224 {
225 return 0;
226 }
227
228 std::string text(token);
229 std::vector<std::string> matches;
230 std::copy_if(vocab->begin(), vocab->end(), std::back_inserter(matches),
231 [&text](const std::string &s)
232 {
233 return (s.size() >= text.size() &&
234 s.compare(0, text.size(), text) == 0);
235 });
236
237 *av = static_cast<char **>(malloc(matches.size() * sizeof (**av)));
238 if (!*av) return 0;
239 for (std::size_t i = 0; i < matches.size(); i++)
240 {
241 (*av)[i] = strdup(matches[i].c_str());
242 }
243 return matches.size();
244 }
245
246 static char *cliComplete(char *token, int *match)
247 {
248 char **av;
249 int numMatches = cliListPossible(token, &av);
250
251 *match = 0;
252 if (numMatches == 0) return nullptr;
253
254 std::vector<std::string> list;
255
256 for (std::size_t i = 0; i < numMatches; i++)
257 {
258 list.push_back(std::string(av[i]));
259 free(av[i]);
260 }
261 free(av);
262
263 if (numMatches == 1)
264 {
265 *match = 1;
266 std::string result = list[0] + " ";
267 return strdup(result.c_str() + strlen(token));
268 }
269
270 std::string lcp = longest_common_prefix(token, list);
271
272 if (lcp.size() > strlen(token))
273 {
274 *match = 1;
275 return strdup(lcp.c_str() + strlen(token));
276 }
277
278 return nullptr;
279 }
280
281 MouseCursorTracker::MouseCursorTracker(std::string cfgPath,
282 std::vector<std::pair<std::string, int> > motions,
283 std::vector<std::string> expressions)
284 : m_stop(false)
285 {
286 m_motionPriority = MotionPriority::none;
287 m_motionNumber = 0;
288
289
290 parseConfig(cfgPath);
291 m_xdo = xdo_new(nullptr);
292
293 const pa_sample_spec ss =
294 {
295 .format = PA_SAMPLE_FLOAT32NE,
296 .rate = 44100,
297 .channels = 2
298 };
299 m_pulse = pa_simple_new(nullptr, "MouseCursorTracker", PA_STREAM_RECORD,
300 nullptr, "LipSync", &ss, nullptr, nullptr, nullptr);
301 if (!m_pulse)
302 {
303 throw std::runtime_error("Unable to create pulse");
304 }
305
306 m_getVolumeThread = std::thread(&MouseCursorTracker::audioLoop, this);
307 m_parseCommandThread = std::thread(&MouseCursorTracker::cliLoop, this);
308
309 MCT_motions = motions;
310 MCT_expressions = expressions;
311 MCT_overrideMap = &m_overrideMap;
312 }
313
314 void MouseCursorTracker::audioLoop(void)
315 {
316 float *buf = new float[m_cfg.audioBufSize];
317
318 std::size_t audioBufByteSize = m_cfg.audioBufSize * sizeof *buf;
319
320 while (!m_stop)
321 {
322 if (pa_simple_read(m_pulse, buf, audioBufByteSize, nullptr) < 0)
323 {
324 throw std::runtime_error("Unable to get audio data");
325 }
326 m_currentVol = rms(buf, m_cfg.audioBufSize);
327 }
328
329 delete[] buf;
330 }
331
332 void MouseCursorTracker::cliLoop(void)
333 {
334 rl_set_complete_func(&cliComplete);
335 rl_set_list_possib_func(&cliListPossible);
336 while (!m_stop)
337 {
338 char *buf = readline(">> ");
339
340 if (buf)
341 {
342 std::string cmdline(buf);
343 free(buf);
344 processCommand(cmdline);
345 }
346 else
347 {
348 std::cout << "Exiting CLI loop. Use Ctrl+C to exit the whole process." << std::endl;
349 stop();
350 }
351 }
352 }
353
354
355 void MouseCursorTracker::processCommand(std::string cmdline)
356 {
357 auto cmdSplit = split(cmdline);
358
359 if (cmdSplit.size() > 0)
360 {
361 add_history(cmdline.c_str());
362
363 if (cmdSplit[0] == "help")
364 {
365 if (cmdSplit.size() == 1)
366 {
367 std::cout << "Available commands: motion set clear\n"
368 << "Type \"help <command>\" for more help" << std::endl;
369 }
370 else if (cmdSplit[1] == "motion")
371 {
372 std::cout << "motion <motionGroup> <motionNumber> [<priority>]\n"
373 << "motionGroup: The motion name in the .model3.json file\n"
374 << "motionNumber: The index of this motion in the .model3.json file, 0-indexed\n"
375 << "priority: 0 = none, 1 = idle, 2 = normal, 3 = force (default normal)" << std::endl;
376 }
377 else if (cmdSplit[1] == "expression")
378 {
379 std::cout << "expression <expressionName>\n"
380 << "expressionName: Name of expression in the .model3.json file" << std::endl;
381 }
382 else if (cmdSplit[1] == "set")
383 {
384 std::cout << "set <param1> <value1> [<param2> <value2> ...]\n"
385 << "Set parameter value. Overrides any tracking."
386 << "See live2D documentation for full list of params" << std::endl;
387 }
388 else if (cmdSplit[1] == "clear")
389 {
390 std::cout << "clear <param1> [<param2> ...]\n"
391 << "Clear parameter value. Re-enables tracking if it was overridden by \"set\"\n"
392 << "You can also use \"clear all\" to clear everything" << std::endl;
393 }
394 else
395 {
396 std::cout << "Unrecognized command" << std::endl;
397 }
398 }
399 else if (cmdSplit[0] == "motion")
400 {
401 if (cmdSplit.size() == 3 || cmdSplit.size() == 4)
402 {
403 std::unique_lock<std::mutex> lock(m_motionMutex, std::defer_lock);
404 lock.lock();
405 m_motionGroup = cmdSplit[1];
406 try
407 {
408 m_motionNumber = std::stoi(cmdSplit[2]);
409 if (cmdSplit.size() == 4)
410 {
411 m_motionPriority = static_cast<MotionPriority>(std::stoi(cmdSplit[3]));
412 }
413 else
414 {
415 m_motionPriority = MotionPriority::normal;
416 }
417 }
418 catch (const std::exception &e)
419 {
420 std::cerr << "std::stoi failed" << std::endl;
421 }
422 lock.unlock();
423 }
424 else
425 {
426 std::cerr << "Incorrect command, expecting 2 or 3 arguments" << std::endl;
427 std::cerr << "motion motionGroup motionNumber [motionPriority]" << std::endl;
428 }
429 }
430 else if (cmdSplit[0] == "expression")
431 {
432 if (cmdSplit.size() == 2)
433 {
434 std::unique_lock<std::mutex> lock(m_motionMutex, std::defer_lock);
435 lock.lock();
436 m_expression = cmdSplit[1];
437 lock.unlock();
438 }
439 else
440 {
441 std::cerr << "Incorrect command, expecting 1 argument: expressionName" << std::endl;
442 }
443 }
444 else if (cmdSplit[0] == "set")
445 {
446 if (cmdSplit.size() % 2 != 1)
447 {
448 // "set param1 value1 param2 value2 ..."
449 std::cerr << "Incorrect number of arguments for command 'set'" << std::endl;
450 }
451 for (std::size_t i = 1; i < cmdSplit.size(); i += 2)
452 {
453 try
454 {
455 m_overrideMap[cmdSplit[i]] = std::stod(cmdSplit[i + 1]);
456 }
457 catch (const std::exception &e)
458 {
459 std::cerr << "std::stod failed" << std::endl;
460 }
461 }
462 }
463 else if (cmdSplit[0] == "clear")
464 {
465 for (std::size_t i = 1; i < cmdSplit.size(); i++)
466 {
467 if (cmdSplit[i] == "all")
468 {
469 m_overrideMap.clear();
470 break;
471 }
472 std::size_t removed = m_overrideMap.erase(cmdSplit[i]);
473 if (removed == 0)
474 {
475 std::cerr << "Warning: key " << cmdSplit[i] << " not found" << std::endl;
476 }
477 }
478 }
479 else
480 {
481 std::cerr << "Unknown command" << std::endl;
482 }
483 }
484 }
485
486 MouseCursorTracker::~MouseCursorTracker()
487 {
488 xdo_free(m_xdo);
489 m_getVolumeThread.join();
490 m_parseCommandThread.join();
491 pa_simple_free(m_pulse);
492 }
493
494 void MouseCursorTracker::stop(void)
495 {
496 m_stop = true;
497 }
498
499 MouseCursorTracker::Params MouseCursorTracker::getParams(void)
500 {
501 Params params = Params();
502
503 int xOffset = m_curPos.x - m_cfg.origin.x;
504 int leftRange = m_cfg.origin.x - m_cfg.left;
505 int rightRange = m_cfg.right - m_cfg.origin.x;
506
507 if (xOffset > 0) // i.e. to the right
508 {
509 params.live2d["ParamAngleX"] = 30.0 * xOffset / rightRange;
510 }
511 else // to the left
512 {
513 params.live2d["ParamAngleX"] = 30.0 * xOffset / leftRange;
514 }
515
516 int yOffset = m_curPos.y - m_cfg.origin.y;
517 int topRange = m_cfg.origin.y - m_cfg.top;
518 int bottomRange = m_cfg.bottom - m_cfg.origin.y;
519
520 if (yOffset > 0) // downwards
521 {
522 params.live2d["ParamAngleY"] = -30.0 * yOffset / bottomRange;
523 }
524 else // upwards
525 {
526 params.live2d["ParamAngleY"] = -30.0 * yOffset / topRange;
527 }
528
529 params.autoBlink = m_cfg.autoBlink;
530 params.autoBreath = m_cfg.autoBreath;
531 params.randomIdleMotion = m_cfg.randomIdleMotion;
532 params.useLipSync = m_cfg.useLipSync;
533
534 params.live2d["ParamMouthForm"] = m_cfg.mouthForm;
535
536 if (m_cfg.useLipSync)
537 {
538 params.lipSyncParam = m_currentVol * m_cfg.lipSyncGain;
539 if (params.lipSyncParam < m_cfg.lipSyncCutOff)
540 {
541 params.lipSyncParam = 0;
542 }
543 else if (params.lipSyncParam > 1)
544 {
545 params.lipSyncParam = 1;
546 }
547 }
548
549 // Don't block in getParams()
550 std::unique_lock<std::mutex> lock(m_motionMutex, std::try_to_lock);
551 if (lock.owns_lock())
552 {
553 if (m_motionPriority != MotionPriority::none)
554 {
555 params.motionPriority = m_motionPriority;
556 params.motionGroup = m_motionGroup;
557 params.motionNumber = m_motionNumber;
558
559 m_motionPriority = MotionPriority::none;
560 m_motionGroup = "";
561 m_motionNumber = 0;
562 }
563 params.expression = m_expression;
564 m_expression = "";
565 lock.unlock();
566 }
567
568 // Leave everything else as zero
569
570
571 // Process overrides
572 for (auto const &x : m_overrideMap)
573 {
574 std::string key = x.first;
575 double val = x.second;
576
577 params.live2d[key] = val;
578 }
579
580
581 return params;
582 }
583
584 void MouseCursorTracker::mainLoop(void)
585 {
586 while (!m_stop)
587 {
588 int x;
589 int y;
590 int screenNum;
591
592 xdo_get_mouse_location(m_xdo, &x, &y, &screenNum);
593
594 if (screenNum == m_cfg.screen)
595 {
596 m_curPos.x = x;
597 m_curPos.y = y;
598 }
599 // else just silently ignore for now
600 std::this_thread::sleep_for(std::chrono::milliseconds(m_cfg.sleepMs));
601 }
602 }
603
604 void MouseCursorTracker::parseConfig(std::string cfgPath)
605 {
606 populateDefaultConfig();
607
608 if (cfgPath != "")
609 {
610 std::ifstream file(cfgPath);
611 if (!file)
612 {
613 throw std::runtime_error("Failed to open config file");
614 }
615 std::string line;
616 unsigned int lineNum = 0;
617
618 while (std::getline(file, line))
619 {
620 if (line[0] == '#')
621 {
622 continue;
623 }
624
625 std::istringstream ss(line);
626 std::string paramName;
627
628 if (ss >> paramName)
629 {
630 if (paramName == "sleep_ms")
631 {
632 if (!(ss >> m_cfg.sleepMs))
633 {
634 throw std::runtime_error("Error parsing sleep_ms");
635 }
636 }
637 else if (paramName == "autoBlink")
638 {
639 if (!(ss >> m_cfg.autoBlink))
640 {
641 throw std::runtime_error("Error parsing autoBlink");
642 }
643 }
644 else if (paramName == "autoBreath")
645 {
646 if (!(ss >> m_cfg.autoBreath))
647 {
648 throw std::runtime_error("Error parsing autoBreath");
649 }
650 }
651 else if (paramName == "randomIdleMotion")
652 {
653 if (!(ss >> m_cfg.randomIdleMotion))
654 {
655 throw std::runtime_error("Error parsing randomIdleMotion");
656 }
657 }
658 else if (paramName == "useLipSync")
659 {
660 if (!(ss >> m_cfg.useLipSync))
661 {
662 throw std::runtime_error("Error parsing useLipSync");
663 }
664 }
665 else if (paramName == "lipSyncGain")
666 {
667 if (!(ss >> m_cfg.lipSyncGain))
668 {
669 throw std::runtime_error("Error parsing lipSyncGain");
670 }
671 }
672 else if (paramName == "lipSyncCutOff")
673 {
674 if (!(ss >> m_cfg.lipSyncCutOff))
675 {
676 throw std::runtime_error("Error parsing lipSyncCutOff");
677 }
678 }
679 else if (paramName == "audioBufSize")
680 {
681 if (!(ss >> m_cfg.audioBufSize))
682 {
683 throw std::runtime_error("Error parsing audioBufSize");
684 }
685 }
686 else if (paramName == "mouthForm")
687 {
688 if (!(ss >> m_cfg.mouthForm))
689 {
690 throw std::runtime_error("Error parsing mouthForm");
691 }
692 }
693 else if (paramName == "screen")
694 {
695 if (!(ss >> m_cfg.screen))
696 {
697 throw std::runtime_error("Error parsing screen");
698 }
699 }
700 else if (paramName == "origin_x")
701 {
702 if (!(ss >> m_cfg.origin.x))
703 {
704 throw std::runtime_error("Error parsing origin_x");
705 }
706 }
707 else if (paramName == "origin_y")
708 {
709 if (!(ss >> m_cfg.origin.y))
710 {
711 throw std::runtime_error("Error parsing origin_y");
712 }
713 }
714 else if (paramName == "top")
715 {
716 if (!(ss >> m_cfg.top))
717 {
718 throw std::runtime_error("Error parsing top");
719 }
720 }
721 else if (paramName == "bottom")
722 {
723 if (!(ss >> m_cfg.bottom))
724 {
725 throw std::runtime_error("Error parsing bottom");
726 }
727 }
728 else if (paramName == "left")
729 {
730 if (!(ss >> m_cfg.left))
731 {
732 throw std::runtime_error("Error parsing left");
733 }
734 }
735 else if (paramName == "right")
736 {
737 if (!(ss >> m_cfg.right))
738 {
739 throw std::runtime_error("Error parsing right");
740 }
741 }
742 else
743 {
744 throw std::runtime_error("Unrecognized config parameter");
745 }
746 }
747 }
748 }
749 }
750
751 void MouseCursorTracker::populateDefaultConfig(void)
752 {
753 m_cfg.sleepMs = 5;
754 m_cfg.autoBlink = true;
755 m_cfg.autoBreath = true;
756 m_cfg.randomIdleMotion = false;
757 m_cfg.useLipSync = true;
758 m_cfg.lipSyncGain = 10;
759 m_cfg.lipSyncCutOff = 0.15;
760 m_cfg.audioBufSize = 4096;
761 m_cfg.mouthForm = 0;
762 m_cfg.top = 0;
763 m_cfg.bottom = 1079;
764 m_cfg.left = 0;
765 m_cfg.right = 1919; // These will be the full screen for 1920x1080
766
767 m_cfg.screen = 0;
768 m_cfg.origin = {1600, 870}; // Somewhere near the bottom right
769 }
770