Use editline instead of GNU Readline
[mouse-tracker-for-cubism.git] / src / mouse_cursor_tracker.cpp
CommitLineData
126d8fa4
AIL
1/****
2Copyright (c) 2020 Adrian I. Lam
3
4Permission is hereby granted, free of charge, to any person obtaining a copy
5of this software and associated documentation files (the "Software"), to deal
6in the Software without restriction, including without limitation the rights
7to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8copies of the Software, and to permit persons to whom the Software is
9furnished to do so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20SOFTWARE.
21****/
22
23#include <stdexcept>
24#include <string>
25#include <chrono>
26#include <thread>
27#include <fstream>
28#include <sstream>
29#include <vector>
eba2eb3a
AIL
30#include <utility>
31#include <algorithm>
126d8fa4
AIL
32#include <cstdlib>
33#include <cmath>
eba2eb3a 34#include <cassert>
126d8fa4
AIL
35
36#include <iostream>
eba2eb3a
AIL
37#include <cctype>
38#include <mutex>
126d8fa4
AIL
39
40extern "C"
41{
42#include <xdo.h>
43#include <pulse/simple.h>
cec13e56
AIL
44#include <string.h> // strdup
45#include "editline.h"
126d8fa4
AIL
46}
47#include "mouse_cursor_tracker.h"
48
49static 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
eba2eb3a
AIL
59static 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
94static 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
125std::vector<std::string> commands =
126{
127 "help", "motion", "expression", "set", "clear"
128};
129
130std::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};
145std::vector<std::pair<std::string, int> > MCT_motions;
146std::vector<std::string> MCT_expressions;
147std::map<std::string, double> *MCT_overrideMap;
148
cec13e56 149static int cliListPossible(char *token, char ***av)
eba2eb3a
AIL
150{
151 // Reference: https://github.com/eliben/code-for-blog/blob/master/2016/readline-samples/readline-complete-subcommand.cpp
eba2eb3a
AIL
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 }
cec13e56 195 break; // No need to loop through the list, all motions have the same set of priorities
eba2eb3a
AIL
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 {
cec13e56 225 return 0;
eba2eb3a
AIL
226 }
227
cec13e56 228 std::string text(token);
eba2eb3a
AIL
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
cec13e56
AIL
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++)
eba2eb3a 240 {
cec13e56 241 (*av)[i] = strdup(matches[i].c_str());
eba2eb3a 242 }
cec13e56
AIL
243 return matches.size();
244}
245
246static 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;
eba2eb3a 253
cec13e56
AIL
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]);
eba2eb3a 260 }
cec13e56
AIL
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;
eba2eb3a
AIL
279}
280
281MouseCursorTracker::MouseCursorTracker(std::string cfgPath,
282 std::vector<std::pair<std::string, int> > motions,
283 std::vector<std::string> expressions)
126d8fa4
AIL
284 : m_stop(false)
285{
eba2eb3a
AIL
286 m_motionPriority = MotionPriority::none;
287 m_motionNumber = 0;
288
289
126d8fa4
AIL
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);
eba2eb3a
AIL
307 m_parseCommandThread = std::thread(&MouseCursorTracker::cliLoop, this);
308
309 MCT_motions = motions;
310 MCT_expressions = expressions;
311 MCT_overrideMap = &m_overrideMap;
126d8fa4
AIL
312}
313
314void 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
eba2eb3a
AIL
332void MouseCursorTracker::cliLoop(void)
333{
cec13e56
AIL
334 rl_set_complete_func(&cliComplete);
335 rl_set_list_possib_func(&cliListPossible);
eba2eb3a
AIL
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
355void 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 }
eba2eb3a
AIL
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
126d8fa4
AIL
486MouseCursorTracker::~MouseCursorTracker()
487{
488 xdo_free(m_xdo);
489 m_getVolumeThread.join();
eba2eb3a 490 m_parseCommandThread.join();
126d8fa4
AIL
491 pa_simple_free(m_pulse);
492}
493
494void MouseCursorTracker::stop(void)
495{
496 m_stop = true;
497}
498
eba2eb3a 499MouseCursorTracker::Params MouseCursorTracker::getParams(void)
126d8fa4
AIL
500{
501 Params params = Params();
502
eba2eb3a
AIL
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;
126d8fa4
AIL
506
507 if (xOffset > 0) // i.e. to the right
508 {
eba2eb3a 509 params.live2d["ParamAngleX"] = 30.0 * xOffset / rightRange;
126d8fa4
AIL
510 }
511 else // to the left
512 {
eba2eb3a 513 params.live2d["ParamAngleX"] = 30.0 * xOffset / leftRange;
126d8fa4
AIL
514 }
515
eba2eb3a
AIL
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;
126d8fa4
AIL
519
520 if (yOffset > 0) // downwards
521 {
eba2eb3a 522 params.live2d["ParamAngleY"] = -30.0 * yOffset / bottomRange;
126d8fa4
AIL
523 }
524 else // upwards
525 {
eba2eb3a 526 params.live2d["ParamAngleY"] = -30.0 * yOffset / topRange;
126d8fa4
AIL
527 }
528
126d8fa4
AIL
529 params.autoBlink = m_cfg.autoBlink;
530 params.autoBreath = m_cfg.autoBreath;
eba2eb3a 531 params.randomIdleMotion = m_cfg.randomIdleMotion;
126d8fa4
AIL
532 params.useLipSync = m_cfg.useLipSync;
533
eba2eb3a 534 params.live2d["ParamMouthForm"] = m_cfg.mouthForm;
126d8fa4
AIL
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
eba2eb3a
AIL
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 }
126d8fa4
AIL
567
568 // Leave everything else as zero
569
570
eba2eb3a
AIL
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
126d8fa4
AIL
581 return params;
582}
583
584void 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
604void 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 }
eba2eb3a 651 else if (paramName == "randomIdleMotion")
126d8fa4 652 {
eba2eb3a 653 if (!(ss >> m_cfg.randomIdleMotion))
126d8fa4 654 {
eba2eb3a 655 throw std::runtime_error("Error parsing randomIdleMotion");
126d8fa4
AIL
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 }
eba2eb3a 700 else if (paramName == "origin_x")
126d8fa4 701 {
eba2eb3a 702 if (!(ss >> m_cfg.origin.x))
126d8fa4 703 {
eba2eb3a 704 throw std::runtime_error("Error parsing origin_x");
126d8fa4
AIL
705 }
706 }
eba2eb3a 707 else if (paramName == "origin_y")
126d8fa4 708 {
eba2eb3a 709 if (!(ss >> m_cfg.origin.y))
126d8fa4 710 {
eba2eb3a 711 throw std::runtime_error("Error parsing origin_y");
126d8fa4
AIL
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
751void MouseCursorTracker::populateDefaultConfig(void)
752{
753 m_cfg.sleepMs = 5;
754 m_cfg.autoBlink = true;
755 m_cfg.autoBreath = true;
eba2eb3a 756 m_cfg.randomIdleMotion = false;
126d8fa4
AIL
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;
eba2eb3a 768 m_cfg.origin = {1600, 870}; // Somewhere near the bottom right
126d8fa4
AIL
769}
770