Host C Specs


Directory: src/c/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 92.9% 26 / 0 / 28
Functions: 100.0% 6 / 0 / 6
Branches: 88.9% 16 / 0 / 18

core/clock/beats.c
Line Branch Exec Source
1 /**
2 * @file beats.c
3 * @brief Pure swatch .beats math, host-testable off-device.
4 *
5 * @ingroup lib_core
6 */
7 #include "clock/beats.h"
8 #include "math/scale.h"
9
10 #include <string.h>
11
12 6 int beats_from_ms(int32_t ms_into_bmt_day)
13 {
14
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
6 if (ms_into_bmt_day < 0)
15 {
16 1 ms_into_bmt_day = 0;
17 }
18
19 6 return (int)((ms_into_bmt_day % MS_PER_DAY) / MS_PER_BEAT);
20 }
21
22 3 uint32_t ms_until_next_beat(int32_t ms_into_bmt_day)
23 {
24
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (ms_into_bmt_day < 0)
25 {
26 ✗ ms_into_bmt_day = 0;
27 }
28
29 // ms position within the current beat
30 3 int32_t into_beat = ms_into_bmt_day % MS_PER_BEAT;
31
32 // result runs 1..MS_PER_BEAT and never hits 0
33 // right on a boundary we report a full beat to the next flip
34 3 return (uint32_t)(MS_PER_BEAT - into_beat);
35 }
36
37 6 int32_t beats_ms_from_hms(int hour, int minute, int second, uint16_t ms)
38 {
39 6 int32_t secs = hour * SECS_PER_HOUR + minute * SECS_PER_MIN + second;
40
41 // caps just under a day of ms (86.4 million) so int32 holds it exactly
42 6 return (((secs + BMT_UTC_OFFSET_S) % SECS_PER_DAY) * MS_PER_SEC) + ms;
43 }
44
45 /**
46 * Where the first .beats token starts in a string, or -1 when there is none. The token is three
47 * fixed characters, so a plain walk finds it with strncmp, which the firmware already exports.
48 * strstr is not exported, so it would link about 1.4 KB of newlib into every face that references
49 * the date readout.
50 *
51 * @param text The string to search.
52 * @return The offset of the first token, or -1 when there is none.
53 */
54 22 static int token_at(const char *text)
55 {
56
2/2
✓ Branch 0 taken 71 times.
✓ Branch 1 taken 12 times.
83 for (int at = 0; text[at]; at++)
57 {
58 // only a brace can start the token, so most characters skip the call
59
4/4
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 58 times.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 3 times.
71 if (text[at] == BEATS_TOKEN[0] && strncmp(text + at, BEATS_TOKEN, BEATS_TOKEN_LEN) == 0)
60 {
61 10 return at;
62 }
63 }
64
65 12 return -1;
66 }
67
68 4 bool beats_has_token(const char *format)
69 {
70
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 2 times.
4 return format && token_at(format) >= 0;
71 }
72
73 10 void beats_expand_token(char *text, int beats)
74 {
75
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (!text)
76 {
77 ✗ return;
78 }
79
80 // clamped so the reading is always three digits, which is what lets the swap happen in place
81 10 beats = clamp_int(beats, 0, 999);
82
83 // written by hand rather than with snprintf, which would want a buffer sized for any int and
84 // then warn about truncating one down to three
85 char digits[BEATS_TOKEN_LEN];
86 10 digits[0] = (char)('0' + (beats / 100));
87 10 digits[1] = (char)('0' + (beats / 10) % 10);
88 10 digits[2] = (char)('0' + (beats % 10));
89
90
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 10 times.
19 for (int at = token_at(text); at >= 0; text += at + BEATS_TOKEN_LEN, at = token_at(text))
91 {
92 9 memcpy(text + at, digits, BEATS_TOKEN_LEN);
93 }
94 }
95