Share lossless voice prefixes across companion announcements

This commit is contained in:
mikecarper committed 2026-10-03 19:11:15 -07:00
1 parent d4f88680e7
commit f0828e025a
18 files changed
+2284 -2018

No files matched your search

+3
View File
@@ -346,7 +346,10 @@ jobs:
python3 -B test/test_button_voice_generator.py -v
python3 -B test/test_button_voice_preview.py -v
python3 -B test/test_g726_voice.py -v
MESH_G726_TEST_SANITIZERS=1 python3 -B test/test_g726_voice.py -v
python3 -B test/test_g726_voice_generator.py -v
python3 -B test/test_voice_prefixes.py -v
MESH_VOICE_TEST_SANITIZERS=1 python3 -B test/test_voice_prefixes.py -v
python3 -B test/test_nimble_companion.py
python3 -B test/test_bluetooth_pairing_ui.py
python3 -B test/test_companion_pairing_display.py
+18
View File
@@ -77,6 +77,24 @@ Power-on and wake wiring remain board-specific. Speech is nonblocking and uses
the existing nRF52 PWM player; each buzzer still needs a physical listening
test for intelligibility. All spoken button, alert-mode and GPS recordings use
Microsoft Zira Desktop with the selected compressed Companion voice profile.
GPS on/off use three stored audio spans: one shared encoded GPS prefix and two
phrase tails. Advert queued/failed also share an identical encoded prefix;
Sound off/Sound only share one only on `HAS_DRV2605` boards. The tails are not
independently decodable words. Playback treats each prefix and tail as one
continuous compressed stream, without resetting its codec, bit position,
interpolation or DMA at the seam. The approved full-phrase PCM and PWM output
remain bit-for-bit identical, including pronunciation and pauses.
The IMA audio payload is 3,881 bytes smaller on ordinary voice boards, or
4,971 bytes smaller on vibration-equipped boards, before the small segmented
descriptor/decoder overhead. Tiny shared silence prefixes are not split.
A representative Cortex-M4 GCC 14.2 size-optimized/LTO player-and-catalog
build saves 3,732 or 4,792 bytes respectively after that overhead; final
firmware savings can vary with toolchain and surrounding code.
The existing generators still synthesize the two complete GPS phrases and
button confirmations, then share byte-identical encoded prefixes; they do not
try to identify word boundaries or splice independently spoken words. Windows
Zira synthesis still requires Windows' local `System.Speech` tooling.
R1 Neo's buzzer pin aliases its optional QSPI clock, so voice is
not default-enabled when `QSPIFLASH` is selected. ThinkNode M4 is deliberately
excluded: its former
+65 -13
View File
@@ -3,7 +3,7 @@
import argparse
from pathlib import Path
from generate_gps_voice import encode, read
from generate_gps_voice import encode, read, shared_prefix, emit_array
PHRASES = (
('ready', 'Ready'),
@@ -22,6 +22,64 @@ PHRASES = (
('alertsSilent', 'Silent'),
)
# Only meaningful byte-identical prefixes are shared. These retain the exact
# approved phrase prosody; adaptive compressed tails are not standalone words.
PREFIX_GROUPS = (
('advertPrefix', 'advertQueued', 'advertFailed'),
('soundPrefix', 'soundOff', 'alertsSoundOnly'),
)
def split_catalog(encoded):
tails = dict(encoded)
prefixes = {}
references = {}
for prefix_name, first, second in PREFIX_GROUPS:
prefix, one, two = shared_prefix(encoded[first], encoded[second])
# Tiny shared silence does not justify additional metadata/storage.
if len(prefix) < 64:
continue
prefixes[prefix_name] = prefix
tails[first], tails[second] = one, two
references[first] = references[second] = prefix_name
return prefixes, tails, references
def emit_clip(lines, name, data, count, rate=8000, *, prefix=None, codec=None):
emit_array(lines, name, data)
fields = f' {name}Data, sizeof({name}Data), {count}, {rate}'
if codec:
fields += f', VoiceCodec::{codec}'
if prefix:
fields += f', {prefix}Data, sizeof({prefix}Data)'
lines.extend([f'static constexpr VoiceClip {name}Clip = {{', fields, '};'])
def emit_catalog(lines, samples, encoded, *, codec=None):
prefixes, tails, references = split_catalog(encoded)
for prefix_name, data in prefixes.items():
if prefix_name == 'soundPrefix':
lines.append('#ifdef HAS_DRV2605')
lines.append('// Shared encoded prefix; keep adaptive state at the seam.')
emit_array(lines, prefix_name, data)
if prefix_name == 'soundPrefix':
lines.append('#endif // HAS_DRV2605')
for name, phrase in PHRASES:
if name == 'alertsSoundVibration':
lines.append('#ifdef HAS_DRV2605')
lines.append(f'// "{phrase}"')
if name == 'soundOff' and name in references:
# Non-vibration boards have no second consumer for this prefix.
# Keep their original contiguous phrase, without extra span data.
lines.append('#ifdef HAS_DRV2605')
emit_clip(lines, name, tails[name], len(samples[name]),
prefix=references[name], codec=codec)
lines.append('#else')
emit_clip(lines, name, encoded[name], len(samples[name]), codec=codec)
lines.append('#endif // HAS_DRV2605')
else:
emit_clip(lines, name, tails[name], len(samples[name]),
prefix=references.get(name), codec=codec)
lines.append('#endif // HAS_DRV2605')
return sum(map(len, prefixes.values()))
def main():
parser = argparse.ArgumentParser(description=__doc__)
@@ -39,27 +97,21 @@ def main():
]
if args.voice is not None:
lines.insert(2, f'// Voice: {args.voice}')
total = 0
samples_by_name = {}
encoded_by_name = {}
for name, phrase in PHRASES:
samples = read(args.directory / (name + '.wav'))
# Shutdown drains audio nonblockingly for at most five seconds.
if not samples or len(samples) > 32000:
raise ValueError(f'{phrase}: expected a nonempty clip under four seconds')
encoded = encode(samples)
total += len(encoded)
if name == 'alertsSoundVibration':
# Only buzzer+vibration devices need the additional mode catalog.
lines.append('#ifdef HAS_DRV2605')
lines.extend([f'// "{phrase}"', f'static const uint8_t {name}Data[] = {{'])
for offset in range(0, len(encoded), 20):
lines.append(' ' + ','.join(f'0x{b:02x}' for b in encoded[offset:offset + 20]) + ',')
lines.extend(['};', f'static constexpr VoiceClip {name}Clip = {{',
f' {name}Data, sizeof({name}Data), {len(samples)}, 8000', '};'])
samples_by_name[name] = samples
encoded_by_name[name] = encoded
print(f'{phrase}: {len(samples) / 8000:.3f}s, {len(encoded)} bytes')
lines.append('#endif // HAS_DRV2605')
saved = emit_catalog(lines, samples_by_name, encoded_by_name)
lines.append('}} // namespace mesh::audio')
args.output.write_text('\n'.join(lines) + '\n', encoding='utf-8')
print(f'Button confirmations: {total} audio bytes')
print(f'Button confirmations: {sum(map(len, encoded_by_name.values())) - saved} audio bytes')
if __name__ == '__main__':
+16 -17
View File
@@ -10,8 +10,8 @@ import struct
import subprocess
from pathlib import Path
from generate_button_voice import PHRASES
from generate_gps_voice import read
from generate_button_voice import PHRASES, emit_catalog, emit_clip
from generate_gps_voice import read, shared_prefix, emit_array
def encode(samples, bitrate, ffmpeg="ffmpeg"):
@@ -59,22 +59,21 @@ def main():
]
for bitrate in (16, 24):
lines.append(f"#if MESH_GPS_VOICE_G726_BITRATE == {bitrate}")
total = 0
for name, phrase, pcm in samples:
mode = name.startswith("alerts")
if mode:
lines.append("#ifdef HAS_DRV2605")
data = encode(pcm, bitrate, args.ffmpeg)
total += len(data)
lines += [f'// "{phrase}"', f"static const uint8_t {name}Data[] = {{"]
for offset in range(0, len(data), 20):
lines.append(" " + ",".join(f"0x{b:02x}" for b in data[offset:offset + 20]) + ",")
lines += ["};", f"static constexpr VoiceClip {name}Clip = {{",
f" {name}Data, sizeof({name}Data), {len(pcm)}, 8000, VoiceCodec::G726{bitrate}", "};"]
if mode:
lines.append("#endif // HAS_DRV2605")
pcm_by_name = {name: pcm for name, _, pcm in samples}
encoded = {name: encode(pcm, bitrate, args.ffmpeg) for name, _, pcm in samples}
saved = emit_catalog(lines, pcm_by_name, encoded, codec=f'G726{bitrate}')
prefix, on, off = shared_prefix(encoded['gpsOn'], encoded['gpsOff'])
if len(prefix) < 64:
prefix, on, off = b'', encoded['gpsOn'], encoded['gpsOff']
if prefix:
lines.append('// GPS spans form one continuous stream, including cross-byte codes.')
emit_array(lines, 'gps', prefix)
for name, data in (('gpsOn', on), ('gpsOff', off)):
emit_clip(lines, name, data, len(pcm_by_name[name]),
prefix='gps' if prefix else None, codec=f'G726{bitrate}')
lines.append(f"#endif // {bitrate} kbps")
print(f"G.726-{bitrate}: {total} bytes across all 16 clips")
total = sum(map(len, encoded.values())) - saved - len(prefix)
print(f"G.726-{bitrate}: {total} bytes across all 16 phrases")
lines.append("}} // namespace mesh::audio")
args.output.write_text("\n".join(lines) + "\n", encoding="utf-8")
+39 -10
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Encode 8/16 kHz mono 16-bit WAV speech as independent IMA ADPCM clips."""
"""Encode GPS phrases, sharing their identical encoded prefix without loss."""
import argparse
from pathlib import Path
import struct
@@ -41,6 +41,25 @@ def read(path, rate=8000):
data = wav.readframes(wav.getnframes())
return struct.unpack('<' + 'h' * (len(data)//2),data)
def shared_prefix(on, off):
"""Return three spans whose concatenations exactly restore both streams.
A split is byte-based, not a word/codec boundary. Playback must preserve
adaptive codec state and bit position at the seam. Leave a nonempty tail
for both phrases, including identical or very short fixture recordings.
"""
limit = min(len(on), len(off)) - 1
split = 0
while split < limit and on[split] == off[split]:
split += 1
return on[:split], on[split:], off[split:]
def emit_array(lines, name, data):
lines.append(f'static const uint8_t {name}Data[] = {{')
for i in range(0,len(data),20):
lines.append(' '+','.join(f'0x{b:02x}' for b in data[i:i+20])+',')
lines.append('};')
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--on',type=Path,required=True)
@@ -57,15 +76,25 @@ def main():
'namespace mesh { namespace audio {']
if args.voice is not None:
lines.insert(2, f'// Voice: {args.voice}')
for name,path in (('On',args.on),('Off',args.off)):
samples = read(path,args.rate)
encoded = encode(samples)
lines.append(f'static const uint8_t gps{name}Data[] = {{')
for i in range(0,len(encoded),20):
lines.append(' '+','.join(f'0x{b:02x}' for b in encoded[i:i+20])+',')
lines.extend(['};',f'static constexpr VoiceClip gps{name}Clip = {{',
f' gps{name}Data, sizeof(gps{name}Data), {len(samples)}, {args.rate}', '};'])
print(f'GPS {name.lower()}: {len(samples)/args.rate:.3f}s, {len(encoded)} bytes')
phrases = [read(path,args.rate) for path in (args.on,args.off)]
if any(not samples or len(samples) > args.rate * 4 for samples in phrases):
raise ValueError('GPS phrases must be nonempty clips under four seconds')
encoded = [encode(samples) for samples in phrases]
prefix, on, off = shared_prefix(*encoded)
if len(prefix) < 64:
prefix, on, off = b'', *encoded
if prefix:
lines.append('// Three stored spans; preserve decoder state across the shared prefix.')
emit_array(lines, 'gps', prefix)
for name,samples,data in zip(('On','Off'),phrases,(on,off)):
emit_array(lines, 'gps'+name, data)
fields = f' gps{name}Data, sizeof(gps{name}Data), {len(samples)}, {args.rate}'
if prefix:
fields += ', gpsData, sizeof(gpsData)'
lines.extend([f'static constexpr VoiceClip gps{name}Clip = {{',
fields, '};'])
print(f'GPS {name.lower()}: {len(samples)/args.rate:.3f}s, {len(data)} tail bytes')
print(f'GPS shared prefix: {len(prefix)} bytes; audio saved: {len(prefix)} bytes')
lines.append('}} // namespace mesh::audio')
args.output.write_text('\n'.join(lines)+'\n',encoding='utf-8')
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+34 -12
View File
@@ -22,18 +22,24 @@ enum class VoiceCodec : uint8_t { Ima, G72616, G72624 };
#endif
// Low nibble first, independent IMA ADPCM stream starting at predictor/index 0.
// An optional shared prefix and the data span form ONE encoded stream. Never
// reset adaptive state between them: the spans can even divide a G.726 code.
// The samples live in flash; playback only needs two small DMA buffers in RAM.
struct VoiceClip {
const uint8_t* data;
size_t bytes;
uint32_t samples;
uint32_t sample_rate;
const uint8_t* prefix;
size_t prefix_bytes;
#if defined(MESH_GPS_VOICE_G726_BITRATE)
VoiceCodec codec;
constexpr VoiceClip(const uint8_t* data = nullptr, size_t bytes = 0,
uint32_t samples = 0, uint32_t sample_rate = 8000,
VoiceCodec codec = VoiceCodec::Ima)
: data(data), bytes(bytes), samples(samples), sample_rate(sample_rate), codec(codec) {}
VoiceCodec codec = VoiceCodec::Ima,
const uint8_t* prefix = nullptr, size_t prefix_bytes = 0)
: data(data), bytes(bytes), samples(samples), sample_rate(sample_rate),
prefix(prefix), prefix_bytes(prefix_bytes), codec(codec) {}
unsigned bits() const {
if (codec == VoiceCodec::Ima) return 4;
#if MESH_GPS_VOICE_G726_BITRATE == 16
@@ -50,15 +56,30 @@ struct VoiceClip {
return (count / 8) * b + ((count % 8) * b + 7) / 8;
}
bool valid() const {
return data && samples && bits() && bytes >= requiredBytes(samples)
return data && (!prefix_bytes || prefix) && samples && bits()
&& hasBytes(requiredBytes(samples))
&& (codec == VoiceCodec::Ima ? (sample_rate == 8000 || sample_rate == 16000)
: sample_rate == 8000);
}
#else
constexpr VoiceClip(const uint8_t* data = nullptr, size_t bytes = 0,
uint32_t samples = 0, uint32_t sample_rate = 8000)
: data(data), bytes(bytes), samples(samples), sample_rate(sample_rate) {}
uint32_t samples = 0, uint32_t sample_rate = 8000,
const uint8_t* prefix = nullptr, size_t prefix_bytes = 0)
: data(data), bytes(bytes), samples(samples), sample_rate(sample_rate),
prefix(prefix), prefix_bytes(prefix_bytes) {}
bool valid() const {
return data && (!prefix_bytes || prefix) && samples
&& hasBytes(samples / 2 + (samples & 1))
&& (sample_rate == 8000 || sample_rate == 16000);
}
#endif
// Subtract instead of adding the spans, so SIZE_MAX metadata cannot wrap.
bool hasBytes(size_t count) const {
return count <= prefix_bytes || count - prefix_bytes <= bytes;
}
uint8_t byteAt(size_t position) const {
return position < prefix_bytes ? prefix[position] : data[position - prefix_bytes];
}
};
class VoiceDecoder {
@@ -82,11 +103,12 @@ public:
uint32_t position() const { return position_; }
bool done() const {
#if defined(MESH_GPS_VOICE_G726_BITRATE)
return !clip_.data || !clip_.bits() || position_ >= clip_.samples
|| clip_.requiredBytes(position_ + 1) > clip_.bytes;
return !clip_.data || (clip_.prefix_bytes && !clip_.prefix) || !clip_.bits()
|| position_ >= clip_.samples
|| !clip_.hasBytes(clip_.requiredBytes(position_ + 1));
#else
return !clip_.data || position_ >= clip_.samples
|| position_ / 2 >= clip_.bytes;
return !clip_.data || (clip_.prefix_bytes && !clip_.prefix)
|| position_ >= clip_.samples || !clip_.hasBytes(position_ / 2 + 1);
#endif
}
int16_t next() {
@@ -105,17 +127,17 @@ public:
const unsigned bits = clip_.bits();
const unsigned bit = ((position_ % 8) * bits) % 8;
const size_t byte = (position_ / 8) * bits + ((position_ % 8) * bits) / 8;
unsigned word = clip_.data[byte];
unsigned word = clip_.byteAt(byte);
unsigned shift;
if (bit + bits > 8) {
word = (word << 8) | clip_.data[byte + 1];
word = (word << 8) | clip_.byteAt(byte + 1);
shift = 16 - bit - bits;
} else shift = 8 - bit - bits;
++position_;
return g726_decode(&g726_, (word >> shift) & ((1u << bits) - 1), bits);
}
#endif
const uint8_t code = (clip_.data[position_ / 2]
const uint8_t code = (clip_.byteAt(position_ / 2)
>> ((position_ & 1) * 4)) & 15;
++position_;
const int32_t step = steps[index_];
+153 -306
View File
@@ -4,7 +4,8 @@
#pragma once
#include <helpers/ui/GpsVoice.h>
namespace mesh { namespace audio {
static const uint8_t gpsOnData[] = {
// Three stored spans; preserve decoder state across the shared prefix.
static const uint8_t gpsData[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,
0x7f,0xf7,0xf7,0x61,0x5c,0x8b,0xd1,0x04,0x4a,0x8c,0xb3,0x83,0x90,0x98,0x91,0x22,0x2d,0x89,0x28,0xd1,
0xa3,0x20,0x0a,0x80,0xf1,0xb7,0x01,0x5d,0x1a,0x88,0x91,0x18,0x2a,0xa0,0xb5,0x81,0x38,0x2c,0x88,0x2b,
@@ -161,84 +162,86 @@ static const uint8_t gpsOnData[] = {
0xc9,0x10,0x08,0x16,0x9e,0x22,0x1c,0xb3,0x0b,0x10,0x78,0x98,0x88,0x88,0x00,0x87,0x0c,0x81,0x19,0xc4,
0x08,0x20,0x3c,0xa8,0xe2,0x93,0x11,0x3b,0x0c,0x82,0xa1,0xb2,0x2b,0x79,0x89,0xf2,0x82,0x18,0x88,0x11,
0x1b,0x20,0x9e,0x94,0x09,0x01,0x2c,0x90,0x13,0xf0,0x48,0x80,0x29,0x0b,0x4b,0xd1,0x10,0xb0,0x7e,0xa0,
0x80,0xd3,0x10,0x82,0x4c,0x89,0xa0,0xb3,0x92,0x08,0x38,0x8b,0xf5,0x01,0x92,0x19,0x8b,0x86,0x91,0x1b,
0x38,0x6a,0x8a,0x99,0xa4,0x20,0x08,0x0f,0xa5,0x88,0xa3,0x1c,0x34,0x1f,0x08,0x09,0x92,0xc0,0x02,0x1a,
0x80,0xb3,0x28,0x39,0x0f,0xa2,0x08,0x82,0x2c,0xa1,0x13,0xf0,0x40,0x9a,0x95,0x98,0x18,0x80,0x3b,0x3f,
0x4b,0xb1,0x91,0x11,0x4f,0x19,0x99,0x92,0x98,0x13,0x2b,0x7c,0x1c,0x28,0x9a,0xb4,0x28,0x41,0x0f,0x81,
0xb0,0x84,0x9a,0x52,0x0d,0x10,0x88,0x11,0xd8,0x20,0xb0,0x10,0xe3,0x28,0x92,0x4c,0x2b,0xa9,0x94,0x49,
0x90,0x88,0xe4,0x28,0x88,0x18,0xb0,0x02,0x48,0x80,0x0f,0x12,0x19,0xa8,0x0e,0x80,0x15,0xca,0x41,0x08,
0x3a,0xb9,0x2e,0xa5,0x09,0x80,0x08,0x24,0xcb,0x85,0x8c,0x22,0xb0,0x1a,0x84,0x2f,0x12,0x9d,0x92,0x18,
0x86,0x0a,0xa8,0x4c,0x83,0x8a,0xc1,0x38,0x22,0x9b,0xca,0xa2,0x07,0x09,0x3b,0x88,0x13,0xf8,0x1b,0x95,
0x01,0x80,0x99,0x88,0x7b,0x39,0xb8,0xb2,0xb5,0x5d,0x28,0x1b,0xc1,0xc4,0x02,0x2b,0x59,0xc9,0x82,0xc3,
0x49,0x89,0x4b,0xe3,0x91,0x83,0x19,0x10,0x0e,0x81,0xa9,0x24,0xc8,0x28,0x98,0x5c,0x00,0xa8,0xa3,0x18,
0x30,0x8b,0xba,0x3b,0x96,0x94,0x81,0x99,0xb9,0x05,0x68,0x8a,0x99,0xa8,0x63,0x01,0x2a,0xf9,0x92,0xa4,
0x4b,0x1a,0x0b,0x97,0x90,0x88,0x3b,0x60,0xc1,0x90,0xa4,0x29,0x20,0x8e,0x03,0xc0,0x99,0x28,0x38,0x89,
0x02,0x05,0x20,0x00,0xb8,0xab,0x00,0xef,0x11,0xb8,0x49,0x89,0x1c,0x15,0x00,0x12,0xa8,0x70,0xa1,0xbb,
0xa1,0xac,0x59,0x00,0x18,0x84,0x89,0x30,0x98,0x11,0x84,0x1a,0x09,0xbc,0xa1,0x77,0x90,0x9d,0xa8,0xa8,
0x42,0x20,0x10,0x92,0xa8,0x30,0x18,0x30,0x94,0xca,0x98,0x9c,0x2b,0x77,0xb0,0x8d,0x99,0x89,0x24,0x12,
0x28,0x90,0xba,0x14,0x02,0x50,0x88,0xcb,0xa0,0xa8,0x7a,0x17,0xda,0x89,0x98,0x39,0x33,0xa2,0x01,0xa9,
0x0c,0x33,0x83,0x14,0xa8,0x9f,0x88,0xa0,0x74,0x84,0xbc,0x8a,0x9a,0x48,0x24,0x80,0x80,0xb8,0x19,0x34,
0x11,0x11,0xf9,0x9a,0x00,0x88,0x73,0x87,0xdb,0x98,0x88,0x30,0x32,0x88,0x98,0xb8,0x38,0x45,0x00,0x88,
0xc9,0x9a,0x30,0x18,0x71,0x87,0xcc,0x89,0x08,0x20,0x24,0xa8,0x88,0x09,0x39,0x16,0xa2,0x88,0x9b,0x0c,
0x21,0x92,0x70,0x87,0xcd,0x88,0x01,0x28,0x22,0xa8,0x99,0x81,0x20,0x44,0x90,0xa9,0xa9,0x89,0x32,0x82,
0x7a,0x07,0xce,0x09,0x01,0x28,0x22,0xc0,0x89,0x20,0x28,0x33,0xc2,0xca,0x08,0x19,0x30,0xa2,0x3c,0x67,
0xeb,0x8b,0x12,0x80,0x22,0xa2,0xbb,0x21,0x22,0x32,0x94,0xbc,0x8b,0x01,0x13,0x92,0xbc,0x77,0xd1,0x9e,
0x11,0x81,0x28,0x01,0xaa,0x19,0x23,0x11,0x01,0xca,0x9b,0x38,0x22,0x80,0xeb,0x7a,0x17,0xec,0x09,0x12,
0x88,0x21,0x90,0xab,0x31,0x23,0x80,0x89,0xab,0x8b,0x15,0x84,0x8a,0x8c,0x71,0x97,0xcd,0x10,0x02,0x88,
0x11,0xa0,0xaa,0x52,0x21,0x0a,0x8a,0xb0,0x90,0x43,0x00,0xad,0xa9,0x77,0xb1,0x9e,0x10,0x82,0x08,0x11,
0xa8,0x0c,0x41,0x82,0xa8,0x08,0x09,0x8a,0x14,0xa3,0xac,0x2b,0x77,0xb0,0x9e,0x20,0x01,0x09,0x11,0xa0,
0x8c,0x41,0x03,0xa9,0x89,0x80,0x99,0x42,0x93,0xcc,0x0a,0x75,0x92,0xbf,0x28,0x12,0x89,0x20,0x91,0xcb,
0x40,0x23,0xa8,0x9b,0x82,0xb0,0x20,0x25,0xca,0x9c,0x21,0x67,0xc0,0xac,0x20,0x02,0x90,0x11,0x98,0x9c,
0x41,0x04,0xb0,0x99,0x01,0x09,0x39,0x13,0xfa,0x9a,0x41,0x46,0xb8,0xbf,0x20,0x12,0x88,0x10,0x88,0x9c,
0x40,0x04,0xa0,0x9a,0x10,0x08,0x08,0x13,0xd9,0x9c,0x21,0x76,0x91,0xbe,0x19,0x13,0x81,0x09,0x00,0xb9,
0x29,0x36,0x91,0xbb,0x08,0x02,0x88,0x30,0xa0,0xce,0x18,0x53,0x27,0xf9,0x9b,0x30,0x12,0x98,0x08,0x90,
0xa9,0x61,0x23,0xb9,0x9c,0x11,0x11,0x89,0x10,0xc8,0xbb,0x50,0x52,0x17,0xf8,0xab,0x30,0x23,0xa8,0x09,
0x80,0x98,0x40,0x34,0xb8,0xad,0x20,0x83,0xa0,0x08,0x90,0xac,0x08,0x24,0x77,0xa1,0xbf,0x18,0x23,0x91,
0x8a,0x18,0x88,0x09,0x35,0x92,0xbc,0x1a,0x33,0xa1,0xa9,0x10,0xca,0x8c,0x73,0x21,0x16,0xf8,0xad,0x20,
0x23,0xa0,0x8a,0x00,0x80,0x28,0x25,0xb1,0xbc,0x28,0x24,0x98,0x8a,0x08,0xaa,0x1a,0x34,0xb2,0x70,0x27,
0xee,0x89,0x32,0x82,0x9a,0x19,0x01,0x89,0x41,0x13,0xca,0x8c,0x31,0x83,0xb9,0x09,0x80,0xbb,0x38,0x16,
0xb8,0x77,0xa1,0xbf,0x29,0x33,0x90,0x9a,0x20,0x90,0x09,0x44,0x82,0xbc,0x2a,0x43,0xa0,0x9a,0x10,0xa9,
0x8c,0x41,0x82,0xdb,0x71,0x07,0xcc,0x0a,0x33,0x81,0x8b,0x28,0x91,0xaa,0x73,0x12,0xba,0x8b,0x32,0x92,
0x9a,0x38,0xa9,0xaf,0x11,0x04,0xb8,0x3b,0x77,0xd1,0xac,0x30,0x13,0xa8,0x09,0x01,0xa9,0x38,0x27,0xa0,
0xbb,0x20,0x23,0x99,0x1a,0x80,0xbd,0x1a,0x53,0x90,0xbb,0x69,0x77,0xc8,0x9c,0x30,0x02,0x98,0x18,0x81,
0xaa,0x48,0x34,0xb0,0xbb,0x30,0x11,0xa9,0x01,0xc3,0xcc,0x2a,0x32,0xa8,0xb9,0x83,0x77,0x87,0xcc,0x1a,
0x22,0x81,0x89,0x01,0xa8,0x0a,0x35,0x84,0xba,0x0a,0x12,0xa1,0x00,0x21,0xbe,0x8c,0x22,0x83,0xba,0x09,
0x00,0x78,0x77,0xd9,0x8b,0x31,0x02,0x99,0x01,0xa0,0x8b,0x71,0x22,0xc8,0xa9,0x11,0x01,0x09,0x20,0xc9,
0xcb,0x11,0x04,0x98,0x8a,0x00,0x98,0x77,0x97,0xad,0x18,0x12,0x90,0x08,0x01,0xb9,0x19,0x36,0x91,0xab,
0x18,0x82,0x99,0x21,0x92,0xce,0x1a,0x32,0x88,0x9b,0x00,0xb1,0x39,0x77,0x97,0xaf,0x10,0x12,0x88,0x18,
0x80,0xab,0x48,0x34,0xa0,0xab,0x20,0x80,0x09,0x42,0xb0,0xaf,0x08,0x11,0x01,0x80,0x99,0xab,0x38,0x74,
0x67,0xd9,0x9b,0x31,0x01,0x88,0x11,0xc8,0x9a,0x62,0x12,0xa9,0x89,0x81,0x98,0x31,0x13,0xfb,0x9a,0x11,
0x81,0x19,0x29,0xcb,0xaa,0x34,0x73,0x47,0xf9,0x8b,0x21,0x81,0x18,0x10,0xb9,0x8c,0x62,0x02,0x99,0x88,
0x90,0x98,0x32,0x04,0xbc,0x9a,0x11,0x88,0x28,0x20,0xbe,0x99,0x05,0x92,0x76,0x94,0xbe,0x08,0x03,0x08,
0x30,0x98,0xdb,0x20,0x24,0x80,0x1a,0x98,0xd8,0x11,0x23,0xa9,0x9c,0x88,0xa8,0x30,0x12,0xcb,0xaa,0x02,
0x13,0x12,0x77,0x27,0xcf,0x18,0x81,0x00,0x21,0x98,0x9c,0x20,0x23,0x81,0x08,0xaa,0x9b,0x42,0x03,0xa9,
0xaa,0xac,0x0a,0x23,0x92,0xab,0x0f,0x19,0x20,0x05,0x72,0x77,0xdb,0x09,0x81,0x18,0x31,0xa0,0x9c,0x10,
0x12,0x31,0x02,0xbb,0x9b,0x22,0x31,0x39,0xcc,0xfa,0x90,0x01,0x38,0x09,0xbc,0xa0,0x83,0x42,0x11,0x77,
0x87,0xad,0x08,0x90,0x20,0x14,0xaa,0x89,0x01,0x11,0x34,0x88,0x9c,0x88,0x92,0x12,0x10,0x8f,0x8a,0x88,
0x80,0x92,0xa0,0x8a,0x09,0x82,0x87,0xa3,0x77,0x87,0xad,0x80,0xa0,0x31,0x03,0x9b,0x09,0xb1,0x42,0x25,
0x0a,0x0a,0xa8,0x80,0x05,0x98,0x98,0xaa,0x8a,0x11,0x80,0x0a,0xbc,0x1c,0x43,0x01,0x85,0x73,0x67,0xdb,
0x88,0x99,0x38,0x23,0xa9,0x81,0xc8,0x40,0x33,0x09,0x80,0xd1,0x8a,0x13,0x3a,0x3a,0xfb,0xb0,0x92,0x08,
0x38,0x8b,0x8b,0xc3,0xa3,0x25,0x29,0x77,0xa7,0x0c,0xa9,0x8a,0x24,0x91,0x10,0x88,0x1d,0x22,0x91,0x12,
0xb1,0x0c,0x21,0x9b,0x12,0xf1,0x8a,0x81,0x8a,0x48,0xb8,0xa0,0x96,0x80,0x30,0x99,0x78,0x77,0x99,0x98,
0xac,0x19,0x80,0x29,0x11,0x89,0x43,0x03,0x42,0x12,0x8a,0x20,0xd8,0x88,0xb0,0x9d,0x89,0xbb,0x09,0xc0,
0x80,0x14,0x49,0x78,0x18,0x20,0x77,0x92,0x09,0xdb,0xab,0x90,0x9a,0x10,0x80,0x40,0x24,0x31,0x25,0x02,
0x22,0x91,0xa9,0xb8,0xaf,0x9b,0xdb,0xa9,0xa0,0x09,0x30,0x31,0x42,0x24,0x33,0x75,0x17,0x08,0xa0,0xcb,
0x9a,0xbb,0x9b,0xa8,0x98,0x43,0x31,0x73,0x23,0x32,0x15,0x01,0x21,0x99,0x9b,0xcb,0xcd,0xb8,0xba,0x8b,
0x8b,0x1b,0x11,0x03,0x47,0x77,0x00,0x10,0x99,0xaa,0xc0,0xaa,0x89,0x9a,0x29,0x81,0x22,0x17,0x22,0x52,
0x20,0x20,0x82,0xa1,0x81,0xac,0x9b,0xbd,0xcb,0xb9,0xbb,0x89,0x8b,0x81,0x77,0x17,0x18,0x01,0xbb,0x89,
0xcc,0x8a,0xa8,0x1a,0x21,0x20,0x36,0x13,0x43,0x14,0x20,0x21,0x98,0x90,0xe9,0x9a,0xcb,0x9c,0xaa,0xbb,
0x99,0xb8,0x21,0x79,0x77,0x11,0x01,0xba,0x8a,0xcc,0xaa,0x98,0x0a,0x32,0x20,0x27,0x13,0x52,0x21,0x10,
0x01,0x98,0x98,0xcb,0x9c,0xca,0xba,0xb9,0x9b,0x0a,0x0b,0x10,0x45,0x77,0x13,0x21,0xca,0xab,0xea,0x9b,
0x99,0x9a,0x22,0x11,0x45,0x23,0x52,0x22,0x20,0x02,0xa0,0x90,0xdb,0xab,0xdb,0xab,0xb9,0xba,0x80,0x98,
0x58,0x20,0x77,0x05,0x11,0xb9,0xaa,0xea,0x9b,0x99,0x8a,0x21,0x11,0x45,0x23,0x52,0x12,0x11,0x02,0x98,
0x88,0xbb,0xad,0xda,0xaa,0xb9,0x8c,0x89,0x9a,0x01,0x70,0x77,0x18,0x01,0xaa,0x99,0xdb,0x9a,0x99,0x0a,
0x02,0x21,0x37,0x21,0x43,0x02,0x22,0x82,0x09,0x98,0xad,0xaa,0x9c,0xba,0xba,0xbb,0xae,0xa9,0xb9,0x77,
0x03,0x21,0xb1,0xab,0xe9,0x9d,0x98,0x9a,0x11,0x02,0x72,0x12,0x32,0x23,0x11,0x13,0x90,0x18,0xbc,0xac,
0xda,0xbb,0xa9,0x9d,0x99,0xa9,0x10,0x79,0x57,0x10,0x01,0xba,0x9a,0xec,0x99,0xa9,0x09,0x01,0x20,0x35,
0x22,0x35,0x12,0x32,0x82,0x00,0xa0,0xac,0xb9,0xae,0x9a,0xcb,0x09,0xa9,0x09,0x00,0x77,0x13,0x21,0xca,
0xaa,0xfb,0x9b,0x9a,0x9b,0x00,0x11,0x54,0x22,0x53,0x13,0x32,0x13,0x00,0x02,0x9b,0xa9,0xfb,0x9a,0xbb,
0x9d,0x1a,0x80,0x80,0x08,0x80,0x08,0x80,0x08,0x80,0x08,0x80,0x08,0x80,0x08,0x08,0x08,0x08,0x80,0x80,
0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0xd3,0x10,0x82,0x4c,0x89,0xa0,0xb3,0x92,0x08,0x38,0x8b,0xf5,0x01,0x92,
};
static const uint8_t gpsOnData[] = {
0x19,0x8b,0x86,0x91,0x1b,0x38,0x6a,0x8a,0x99,0xa4,0x20,0x08,0x0f,0xa5,0x88,0xa3,0x1c,0x34,0x1f,0x08,
0x09,0x92,0xc0,0x02,0x1a,0x80,0xb3,0x28,0x39,0x0f,0xa2,0x08,0x82,0x2c,0xa1,0x13,0xf0,0x40,0x9a,0x95,
0x98,0x18,0x80,0x3b,0x3f,0x4b,0xb1,0x91,0x11,0x4f,0x19,0x99,0x92,0x98,0x13,0x2b,0x7c,0x1c,0x28,0x9a,
0xb4,0x28,0x41,0x0f,0x81,0xb0,0x84,0x9a,0x52,0x0d,0x10,0x88,0x11,0xd8,0x20,0xb0,0x10,0xe3,0x28,0x92,
0x4c,0x2b,0xa9,0x94,0x49,0x90,0x88,0xe4,0x28,0x88,0x18,0xb0,0x02,0x48,0x80,0x0f,0x12,0x19,0xa8,0x0e,
0x80,0x15,0xca,0x41,0x08,0x3a,0xb9,0x2e,0xa5,0x09,0x80,0x08,0x24,0xcb,0x85,0x8c,0x22,0xb0,0x1a,0x84,
0x2f,0x12,0x9d,0x92,0x18,0x86,0x0a,0xa8,0x4c,0x83,0x8a,0xc1,0x38,0x22,0x9b,0xca,0xa2,0x07,0x09,0x3b,
0x88,0x13,0xf8,0x1b,0x95,0x01,0x80,0x99,0x88,0x7b,0x39,0xb8,0xb2,0xb5,0x5d,0x28,0x1b,0xc1,0xc4,0x02,
0x2b,0x59,0xc9,0x82,0xc3,0x49,0x89,0x4b,0xe3,0x91,0x83,0x19,0x10,0x0e,0x81,0xa9,0x24,0xc8,0x28,0x98,
0x5c,0x00,0xa8,0xa3,0x18,0x30,0x8b,0xba,0x3b,0x96,0x94,0x81,0x99,0xb9,0x05,0x68,0x8a,0x99,0xa8,0x63,
0x01,0x2a,0xf9,0x92,0xa4,0x4b,0x1a,0x0b,0x97,0x90,0x88,0x3b,0x60,0xc1,0x90,0xa4,0x29,0x20,0x8e,0x03,
0xc0,0x99,0x28,0x38,0x89,0x02,0x05,0x20,0x00,0xb8,0xab,0x00,0xef,0x11,0xb8,0x49,0x89,0x1c,0x15,0x00,
0x12,0xa8,0x70,0xa1,0xbb,0xa1,0xac,0x59,0x00,0x18,0x84,0x89,0x30,0x98,0x11,0x84,0x1a,0x09,0xbc,0xa1,
0x77,0x90,0x9d,0xa8,0xa8,0x42,0x20,0x10,0x92,0xa8,0x30,0x18,0x30,0x94,0xca,0x98,0x9c,0x2b,0x77,0xb0,
0x8d,0x99,0x89,0x24,0x12,0x28,0x90,0xba,0x14,0x02,0x50,0x88,0xcb,0xa0,0xa8,0x7a,0x17,0xda,0x89,0x98,
0x39,0x33,0xa2,0x01,0xa9,0x0c,0x33,0x83,0x14,0xa8,0x9f,0x88,0xa0,0x74,0x84,0xbc,0x8a,0x9a,0x48,0x24,
0x80,0x80,0xb8,0x19,0x34,0x11,0x11,0xf9,0x9a,0x00,0x88,0x73,0x87,0xdb,0x98,0x88,0x30,0x32,0x88,0x98,
0xb8,0x38,0x45,0x00,0x88,0xc9,0x9a,0x30,0x18,0x71,0x87,0xcc,0x89,0x08,0x20,0x24,0xa8,0x88,0x09,0x39,
0x16,0xa2,0x88,0x9b,0x0c,0x21,0x92,0x70,0x87,0xcd,0x88,0x01,0x28,0x22,0xa8,0x99,0x81,0x20,0x44,0x90,
0xa9,0xa9,0x89,0x32,0x82,0x7a,0x07,0xce,0x09,0x01,0x28,0x22,0xc0,0x89,0x20,0x28,0x33,0xc2,0xca,0x08,
0x19,0x30,0xa2,0x3c,0x67,0xeb,0x8b,0x12,0x80,0x22,0xa2,0xbb,0x21,0x22,0x32,0x94,0xbc,0x8b,0x01,0x13,
0x92,0xbc,0x77,0xd1,0x9e,0x11,0x81,0x28,0x01,0xaa,0x19,0x23,0x11,0x01,0xca,0x9b,0x38,0x22,0x80,0xeb,
0x7a,0x17,0xec,0x09,0x12,0x88,0x21,0x90,0xab,0x31,0x23,0x80,0x89,0xab,0x8b,0x15,0x84,0x8a,0x8c,0x71,
0x97,0xcd,0x10,0x02,0x88,0x11,0xa0,0xaa,0x52,0x21,0x0a,0x8a,0xb0,0x90,0x43,0x00,0xad,0xa9,0x77,0xb1,
0x9e,0x10,0x82,0x08,0x11,0xa8,0x0c,0x41,0x82,0xa8,0x08,0x09,0x8a,0x14,0xa3,0xac,0x2b,0x77,0xb0,0x9e,
0x20,0x01,0x09,0x11,0xa0,0x8c,0x41,0x03,0xa9,0x89,0x80,0x99,0x42,0x93,0xcc,0x0a,0x75,0x92,0xbf,0x28,
0x12,0x89,0x20,0x91,0xcb,0x40,0x23,0xa8,0x9b,0x82,0xb0,0x20,0x25,0xca,0x9c,0x21,0x67,0xc0,0xac,0x20,
0x02,0x90,0x11,0x98,0x9c,0x41,0x04,0xb0,0x99,0x01,0x09,0x39,0x13,0xfa,0x9a,0x41,0x46,0xb8,0xbf,0x20,
0x12,0x88,0x10,0x88,0x9c,0x40,0x04,0xa0,0x9a,0x10,0x08,0x08,0x13,0xd9,0x9c,0x21,0x76,0x91,0xbe,0x19,
0x13,0x81,0x09,0x00,0xb9,0x29,0x36,0x91,0xbb,0x08,0x02,0x88,0x30,0xa0,0xce,0x18,0x53,0x27,0xf9,0x9b,
0x30,0x12,0x98,0x08,0x90,0xa9,0x61,0x23,0xb9,0x9c,0x11,0x11,0x89,0x10,0xc8,0xbb,0x50,0x52,0x17,0xf8,
0xab,0x30,0x23,0xa8,0x09,0x80,0x98,0x40,0x34,0xb8,0xad,0x20,0x83,0xa0,0x08,0x90,0xac,0x08,0x24,0x77,
0xa1,0xbf,0x18,0x23,0x91,0x8a,0x18,0x88,0x09,0x35,0x92,0xbc,0x1a,0x33,0xa1,0xa9,0x10,0xca,0x8c,0x73,
0x21,0x16,0xf8,0xad,0x20,0x23,0xa0,0x8a,0x00,0x80,0x28,0x25,0xb1,0xbc,0x28,0x24,0x98,0x8a,0x08,0xaa,
0x1a,0x34,0xb2,0x70,0x27,0xee,0x89,0x32,0x82,0x9a,0x19,0x01,0x89,0x41,0x13,0xca,0x8c,0x31,0x83,0xb9,
0x09,0x80,0xbb,0x38,0x16,0xb8,0x77,0xa1,0xbf,0x29,0x33,0x90,0x9a,0x20,0x90,0x09,0x44,0x82,0xbc,0x2a,
0x43,0xa0,0x9a,0x10,0xa9,0x8c,0x41,0x82,0xdb,0x71,0x07,0xcc,0x0a,0x33,0x81,0x8b,0x28,0x91,0xaa,0x73,
0x12,0xba,0x8b,0x32,0x92,0x9a,0x38,0xa9,0xaf,0x11,0x04,0xb8,0x3b,0x77,0xd1,0xac,0x30,0x13,0xa8,0x09,
0x01,0xa9,0x38,0x27,0xa0,0xbb,0x20,0x23,0x99,0x1a,0x80,0xbd,0x1a,0x53,0x90,0xbb,0x69,0x77,0xc8,0x9c,
0x30,0x02,0x98,0x18,0x81,0xaa,0x48,0x34,0xb0,0xbb,0x30,0x11,0xa9,0x01,0xc3,0xcc,0x2a,0x32,0xa8,0xb9,
0x83,0x77,0x87,0xcc,0x1a,0x22,0x81,0x89,0x01,0xa8,0x0a,0x35,0x84,0xba,0x0a,0x12,0xa1,0x00,0x21,0xbe,
0x8c,0x22,0x83,0xba,0x09,0x00,0x78,0x77,0xd9,0x8b,0x31,0x02,0x99,0x01,0xa0,0x8b,0x71,0x22,0xc8,0xa9,
0x11,0x01,0x09,0x20,0xc9,0xcb,0x11,0x04,0x98,0x8a,0x00,0x98,0x77,0x97,0xad,0x18,0x12,0x90,0x08,0x01,
0xb9,0x19,0x36,0x91,0xab,0x18,0x82,0x99,0x21,0x92,0xce,0x1a,0x32,0x88,0x9b,0x00,0xb1,0x39,0x77,0x97,
0xaf,0x10,0x12,0x88,0x18,0x80,0xab,0x48,0x34,0xa0,0xab,0x20,0x80,0x09,0x42,0xb0,0xaf,0x08,0x11,0x01,
0x80,0x99,0xab,0x38,0x74,0x67,0xd9,0x9b,0x31,0x01,0x88,0x11,0xc8,0x9a,0x62,0x12,0xa9,0x89,0x81,0x98,
0x31,0x13,0xfb,0x9a,0x11,0x81,0x19,0x29,0xcb,0xaa,0x34,0x73,0x47,0xf9,0x8b,0x21,0x81,0x18,0x10,0xb9,
0x8c,0x62,0x02,0x99,0x88,0x90,0x98,0x32,0x04,0xbc,0x9a,0x11,0x88,0x28,0x20,0xbe,0x99,0x05,0x92,0x76,
0x94,0xbe,0x08,0x03,0x08,0x30,0x98,0xdb,0x20,0x24,0x80,0x1a,0x98,0xd8,0x11,0x23,0xa9,0x9c,0x88,0xa8,
0x30,0x12,0xcb,0xaa,0x02,0x13,0x12,0x77,0x27,0xcf,0x18,0x81,0x00,0x21,0x98,0x9c,0x20,0x23,0x81,0x08,
0xaa,0x9b,0x42,0x03,0xa9,0xaa,0xac,0x0a,0x23,0x92,0xab,0x0f,0x19,0x20,0x05,0x72,0x77,0xdb,0x09,0x81,
0x18,0x31,0xa0,0x9c,0x10,0x12,0x31,0x02,0xbb,0x9b,0x22,0x31,0x39,0xcc,0xfa,0x90,0x01,0x38,0x09,0xbc,
0xa0,0x83,0x42,0x11,0x77,0x87,0xad,0x08,0x90,0x20,0x14,0xaa,0x89,0x01,0x11,0x34,0x88,0x9c,0x88,0x92,
0x12,0x10,0x8f,0x8a,0x88,0x80,0x92,0xa0,0x8a,0x09,0x82,0x87,0xa3,0x77,0x87,0xad,0x80,0xa0,0x31,0x03,
0x9b,0x09,0xb1,0x42,0x25,0x0a,0x0a,0xa8,0x80,0x05,0x98,0x98,0xaa,0x8a,0x11,0x80,0x0a,0xbc,0x1c,0x43,
0x01,0x85,0x73,0x67,0xdb,0x88,0x99,0x38,0x23,0xa9,0x81,0xc8,0x40,0x33,0x09,0x80,0xd1,0x8a,0x13,0x3a,
0x3a,0xfb,0xb0,0x92,0x08,0x38,0x8b,0x8b,0xc3,0xa3,0x25,0x29,0x77,0xa7,0x0c,0xa9,0x8a,0x24,0x91,0x10,
0x88,0x1d,0x22,0x91,0x12,0xb1,0x0c,0x21,0x9b,0x12,0xf1,0x8a,0x81,0x8a,0x48,0xb8,0xa0,0x96,0x80,0x30,
0x99,0x78,0x77,0x99,0x98,0xac,0x19,0x80,0x29,0x11,0x89,0x43,0x03,0x42,0x12,0x8a,0x20,0xd8,0x88,0xb0,
0x9d,0x89,0xbb,0x09,0xc0,0x80,0x14,0x49,0x78,0x18,0x20,0x77,0x92,0x09,0xdb,0xab,0x90,0x9a,0x10,0x80,
0x40,0x24,0x31,0x25,0x02,0x22,0x91,0xa9,0xb8,0xaf,0x9b,0xdb,0xa9,0xa0,0x09,0x30,0x31,0x42,0x24,0x33,
0x75,0x17,0x08,0xa0,0xcb,0x9a,0xbb,0x9b,0xa8,0x98,0x43,0x31,0x73,0x23,0x32,0x15,0x01,0x21,0x99,0x9b,
0xcb,0xcd,0xb8,0xba,0x8b,0x8b,0x1b,0x11,0x03,0x47,0x77,0x00,0x10,0x99,0xaa,0xc0,0xaa,0x89,0x9a,0x29,
0x81,0x22,0x17,0x22,0x52,0x20,0x20,0x82,0xa1,0x81,0xac,0x9b,0xbd,0xcb,0xb9,0xbb,0x89,0x8b,0x81,0x77,
0x17,0x18,0x01,0xbb,0x89,0xcc,0x8a,0xa8,0x1a,0x21,0x20,0x36,0x13,0x43,0x14,0x20,0x21,0x98,0x90,0xe9,
0x9a,0xcb,0x9c,0xaa,0xbb,0x99,0xb8,0x21,0x79,0x77,0x11,0x01,0xba,0x8a,0xcc,0xaa,0x98,0x0a,0x32,0x20,
0x27,0x13,0x52,0x21,0x10,0x01,0x98,0x98,0xcb,0x9c,0xca,0xba,0xb9,0x9b,0x0a,0x0b,0x10,0x45,0x77,0x13,
0x21,0xca,0xab,0xea,0x9b,0x99,0x9a,0x22,0x11,0x45,0x23,0x52,0x22,0x20,0x02,0xa0,0x90,0xdb,0xab,0xdb,
0xab,0xb9,0xba,0x80,0x98,0x58,0x20,0x77,0x05,0x11,0xb9,0xaa,0xea,0x9b,0x99,0x8a,0x21,0x11,0x45,0x23,
0x52,0x12,0x11,0x02,0x98,0x88,0xbb,0xad,0xda,0xaa,0xb9,0x8c,0x89,0x9a,0x01,0x70,0x77,0x18,0x01,0xaa,
0x99,0xdb,0x9a,0x99,0x0a,0x02,0x21,0x37,0x21,0x43,0x02,0x22,0x82,0x09,0x98,0xad,0xaa,0x9c,0xba,0xba,
0xbb,0xae,0xa9,0xb9,0x77,0x03,0x21,0xb1,0xab,0xe9,0x9d,0x98,0x9a,0x11,0x02,0x72,0x12,0x32,0x23,0x11,
0x13,0x90,0x18,0xbc,0xac,0xda,0xbb,0xa9,0x9d,0x99,0xa9,0x10,0x79,0x57,0x10,0x01,0xba,0x9a,0xec,0x99,
0xa9,0x09,0x01,0x20,0x35,0x22,0x35,0x12,0x32,0x82,0x00,0xa0,0xac,0xb9,0xae,0x9a,0xcb,0x09,0xa9,0x09,
0x00,0x77,0x13,0x21,0xca,0xaa,0xfb,0x9b,0x9a,0x9b,0x00,0x11,0x54,0x22,0x53,0x13,0x32,0x13,0x00,0x02,
0x9b,0xa9,0xfb,0x9a,0xbb,0x9d,0x1a,0x80,0x80,0x08,0x80,0x08,0x80,0x08,0x80,0x08,0x80,0x08,0x80,0x08,
0x08,0x08,0x08,0x80,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
@@ -247,235 +250,78 @@ static const uint8_t gpsOnData[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
static constexpr VoiceClip gpsOnClip = {
gpsOnData, sizeof(gpsOnData), 9683, 8000
gpsOnData, sizeof(gpsOnData), 9683, 8000, gpsData, sizeof(gpsData)
};
static const uint8_t gpsOffData[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,
0x7f,0xf7,0xf7,0x61,0x5c,0x8b,0xd1,0x04,0x4a,0x8c,0xb3,0x83,0x90,0x98,0x91,0x22,0x2d,0x89,0x28,0xd1,
0xa3,0x20,0x0a,0x80,0xf1,0xb7,0x01,0x5d,0x1a,0x88,0x91,0x18,0x2a,0xa0,0xb5,0x81,0x38,0x2c,0x88,0x2b,
0x5a,0xb9,0xa7,0xa1,0xb4,0xc3,0x51,0x1d,0x08,0x90,0x20,0x3c,0x1b,0xf3,0xa3,0x81,0x28,0x1b,0x98,0xb4,
0x82,0x6c,0x8a,0xc3,0xc4,0x11,0x09,0x00,0x0a,0xb1,0xa5,0x20,0x2c,0x39,0x4f,0x8a,0xa2,0x01,0x5b,0x0a,
0xc1,0xb6,0x82,0x4a,0x8a,0xb2,0x02,0x5c,0x1b,0xa1,0xa2,0x01,0x3a,0x2a,0x3b,0x99,0xf7,0xa3,0x30,0x2d,
0x19,0x29,0x2c,0xc0,0xb5,0x21,0x3c,0x2b,0xc0,0xb4,0xb4,0x11,0x2a,0x1a,0x98,0x82,0x3a,0x89,0x10,0xb9,
0xb7,0x82,0x5a,0x2b,0xe0,0xc6,0x82,0x49,0x2c,0x89,0xc3,0x82,0x29,0x4b,0x8a,0xd4,0xa3,0x20,0x4d,0x0a,
0xc2,0x82,0x29,0x2b,0xb0,0xb5,0x02,0x4b,0x3b,0x8a,0xd4,0xb4,0x01,0x29,0x4b,0x2b,0xd0,0xb6,0x82,0x39,
0x2c,0x89,0xb2,0xa3,0x10,0x1a,0x90,0xa2,0x50,0x2e,0xe1,0xb4,0x02,0x5c,0x2b,0x89,0xa2,0x01,0x19,0x09,
0xc2,0x11,0x3b,0x4c,0xa9,0xb5,0x11,0x5b,0x1b,0xc1,0x83,0x5b,0x0a,0xc2,0x82,0x39,0x2c,0x88,0x29,0x89,
0x93,0x4d,0x99,0xe6,0xb5,0x82,0x5b,0x2b,0x98,0xc3,0x82,0x49,0x3d,0x99,0xc4,0x82,0x39,0x3d,0x99,0xc3,
0x93,0x39,0x2c,0x09,0xb1,0xa4,0x10,0x3b,0x89,0xb2,0x12,0x3d,0xc8,0xb5,0x21,0x4f,0x2b,0xa0,0xb3,0x92,
0x18,0x19,0x4a,0x3c,0x0a,0x00,0x3a,0x0a,0xf5,0xc5,0x21,0x3d,0x0a,0xa1,0xa3,0x80,0xb1,0x03,0x6e,0x2c,
0x98,0xb3,0x92,0x00,0x5b,0x1a,0x88,0x00,0x19,0x98,0xc6,0x21,0x4f,0x0a,0xa0,0xb4,0x82,0x08,0x19,0x08,
0x4a,0x1a,0x29,0xb8,0xb4,0xc5,0x21,0x3c,0x4b,0x9a,0xc5,0x82,0x39,0x3d,0x0a,0xb1,0xa4,0x81,0x18,0x2a,
0xa8,0xa4,0x28,0xb0,0x31,0x2e,0xa0,0xc3,0xb3,0x70,0x4f,0x0a,0xc1,0xa3,0x20,0x4d,0x1a,0xb0,0xa5,0x10,
0x4b,0x0a,0xa1,0xc3,0x82,0x19,0x19,0xa0,0x11,0x3b,0x1b,0xf5,0xa3,0x10,0x4c,0x0a,0x80,0x30,0x9a,0xb7,
0x11,0x2c,0x8a,0xb0,0xb2,0x21,0x6c,0x19,0x91,0x93,0x02,0x29,0x2b,0x0b,0x8b,0xf8,0x24,0x3f,0xc0,0xa3,
0x88,0x8a,0x1a,0x9a,0xa4,0x13,0x32,0x34,0x50,0x18,0x98,0xd1,0xb0,0x99,0x1f,0xb3,0x78,0x2b,0xd1,0x00,
0x0c,0xa8,0x00,0x18,0x12,0x52,0x12,0x32,0x39,0xb9,0xd0,0xaa,0xcb,0x2a,0xf3,0x04,0x5c,0x9a,0xb2,0x19,
0x9b,0x91,0x30,0x41,0x62,0x21,0x83,0x11,0x1c,0xbb,0xe8,0x99,0x59,0x2d,0xc1,0x83,0x2c,0xaa,0xb1,0x08,
0x00,0x42,0x51,0x03,0x15,0x10,0x89,0xa9,0xca,0xba,0x0c,0xf2,0x12,0x4d,0xb8,0xb4,0x39,0x0c,0x90,0x11,
0x20,0x14,0x42,0x20,0x91,0xb1,0xc9,0xca,0xab,0x6b,0x1d,0xe2,0x83,0x3b,0x9b,0xb2,0x18,0x90,0x06,0x60,
0x10,0x83,0x11,0x0a,0xb8,0x9b,0x9e,0x1a,0xd9,0xb7,0x30,0x1d,0xc0,0x92,0x29,0x09,0x11,0x31,0x13,0x15,
0x30,0x1a,0xb9,0xf9,0xb8,0x8a,0xe2,0x12,0x3e,0xc9,0xb4,0x39,0x1c,0x90,0x02,0x12,0x14,0x42,0x28,0x98,
0xc1,0xa9,0x9c,0x0c,0xf2,0x83,0x4c,0xa9,0xc3,0x18,0x8a,0x81,0x40,0x38,0x13,0x24,0x01,0xa2,0x98,0x9e,
0xca,0x99,0xf3,0x83,0x4d,0xa9,0xd3,0x10,0x1b,0xa0,0x84,0x31,0x21,0x42,0x28,0xb0,0xd3,0x1a,0x0f,0x0a,
0xd1,0xb5,0x20,0x1d,0xa8,0x92,0x19,0x08,0x12,0x60,0x01,0x94,0x12,0x3c,0xab,0xf2,0x80,0x4a,0x1c,0xc1,
0xa4,0x39,0x0c,0xa0,0x81,0x18,0x01,0x33,0x70,0x08,0xb3,0x82,0x1c,0x8c,0xc8,0xb3,0x58,0x1c,0xb0,0x81,
0x1d,0xb8,0x93,0x69,0x18,0x85,0x22,0x28,0xa0,0xc2,0x89,0x9c,0xa9,0xc5,0x12,0x3d,0x9a,0xd2,0x08,0x8a,
0xa2,0x32,0x40,0x14,0x33,0x38,0xb0,0xe2,0x0a,0x8f,0x00,0x3b,0xf0,0xb4,0x38,0x1e,0xb0,0xa3,0x20,0x29,
0x12,0x41,0x18,0xb3,0x92,0x2d,0x8d,0x99,0xd3,0x83,0x4c,0x8b,0xb0,0x19,0x8c,0xb2,0x24,0x79,0x10,0x03,
0x82,0x81,0x09,0x0e,0xab,0x01,0x3c,0xf0,0xa4,0x4a,0x0d,0xb0,0x82,0x18,0x81,0x14,0x50,0x18,0x91,0xa2,
0x09,0x0d,0x8b,0xf4,0x93,0x6b,0x0b,0xc1,0x81,0x1a,0x98,0x12,0x48,0x01,0x85,0x12,0x08,0x98,0x0a,0x0f,
0x88,0x09,0xe1,0xb3,0x4a,0x0d,0xa8,0x81,0x28,0x01,0x15,0x60,0x18,0xa2,0x82,0x0b,0xca,0x88,0xb0,0x94,
0x5d,0x0c,0xc0,0xa1,0x88,0x08,0x30,0x30,0x86,0x86,0x21,0x19,0x89,0xa9,0xd9,0x82,0x4e,0x9a,0xc3,0x00,
0x1c,0x99,0x00,0x4a,0x00,0x85,0x32,0x38,0x81,0x92,0x8a,0xad,0xc9,0xa3,0x7b,0x1c,0xe2,0x81,0x2b,0x9b,
0xc3,0x31,0x5a,0x01,0x04,0x21,0x18,0x98,0x99,0xbb,0x9b,0xf3,0x02,0x5f,0x8b,0xe2,0x81,0x2a,0x0a,0x81,
0x12,0x12,0x24,0x40,0x29,0x99,0xf1,0xa0,0x1a,0xa8,0x93,0x5d,0x8b,0xe2,0x00,0x2b,0x8a,0x93,0x04,0x32,
0x61,0x28,0x91,0xb2,0xa0,0x0d,0x9c,0xc3,0x58,0x2d,0xc0,0x92,0x2b,0x9b,0xc3,0x31,0x5a,0x01,0x14,0x31,
0x08,0xa1,0x99,0x9d,0xba,0x3b,0xbb,0xc7,0x33,0x2f,0xaa,0xd3,0x00,0x2a,0x08,0x03,0x43,0x31,0x12,0x10,
0x9a,0xf9,0xb8,0x8b,0x4c,0x1b,0xe5,0x83,0x4c,0x8b,0xc1,0x01,0x2a,0x00,0x31,0x30,0x85,0x13,0x4b,0xaa,
0xd1,0x99,0x8b,0x3a,0x2c,0xe5,0x83,0x5b,0x9b,0xd3,0x00,0x1a,0x81,0x13,0x31,0x04,0x32,0x1a,0xe0,0xa0,
0x0d,0x9b,0x08,0xd2,0x85,0x6a,0x8a,0xd2,0x00,0x1b,0x98,0x82,0x22,0x41,0x41,0x11,0x92,0x88,0x8c,0xbc,
0xb9,0xaa,0x42,0x5d,0xb1,0x84,0x2b,0xbb,0xe1,0x08,0x2a,0x10,0x15,0x43,0x31,0x11,0xa1,0xa8,0xad,0xac,
0xba,0x9a,0x87,0x58,0x88,0xa2,0x09,0x8d,0xba,0xa0,0x48,0x48,0x23,0x17,0x31,0x00,0xa1,0xa8,0x9d,0xbb,
0xca,0x09,0x42,0x38,0x85,0x91,0x0a,0xaf,0xc8,0x89,0x19,0x11,0x25,0x34,0x42,0x11,0x81,0x98,0xac,0xdb,
0xba,0x9a,0x39,0x12,0x25,0x21,0x80,0xd8,0xca,0xbb,0x8c,0xa8,0xa8,0x72,0x43,0x24,0x32,0x21,0x00,0x99,
0xcb,0xbc,0xab,0xa9,0x00,0x08,0x80,0xa1,0xaa,0xbd,0xdb,0xa9,0x0a,0x30,0x44,0x35,0x34,0x24,0x23,0x12,
0x81,0xa8,0xbb,0x09,0x8b,0xb9,0xfb,0xbd,0xbd,0xdb,0xaa,0x9b,0x89,0x11,0x44,0x53,0x33,0x24,0x23,0x12,
0x00,0xa8,0xba,0x9d,0x91,0x18,0x80,0xb8,0xdc,0xbc,0xcd,0xba,0xba,0x9a,0x18,0x41,0x44,0x43,0x24,0x33,
0x32,0x21,0x80,0xa8,0x1b,0xc9,0xa8,0xdb,0xcb,0xad,0xdb,0xaa,0xab,0x9a,0x88,0x30,0x52,0x34,0x34,0x34,
0x32,0x23,0x22,0x01,0xa1,0x35,0x1b,0xa8,0xcc,0xbd,0xbd,0xdb,0xaa,0xab,0x9a,0x89,0x11,0x35,0x63,0x42,
0x32,0x32,0x32,0x12,0x01,0xb0,0x54,0x8d,0xa8,0xdb,0xbb,0xbc,0xac,0xaa,0x99,0x00,0x33,0x45,0x53,0x32,
0x23,0x14,0x02,0x81,0xa8,0xba,0xcd,0x21,0x9f,0xa8,0xc9,0xa9,0xaa,0xa9,0x10,0x41,0x51,0x43,0x23,0x24,
0x23,0x12,0x00,0xa9,0xcc,0xda,0xba,0xda,0x32,0x0e,0x08,0x99,0xa9,0x80,0x88,0x41,0x32,0x34,0x43,0x33,
0x32,0x93,0xb1,0xda,0xdb,0xca,0xaa,0xdb,0xb8,0x67,0x0a,0x00,0x08,0xba,0x29,0xba,0xb5,0x87,0x08,0x01,
0x08,0x11,0xa5,0x21,0x1e,0xa8,0xa0,0xa2,0x1b,0x31,0x88,0xf1,0x38,0xc0,0x12,0x91,0x70,0x80,0x98,0x19,
0x8b,0xa2,0x5c,0xc0,0x80,0x21,0x18,0x79,0xa8,0x97,0xa2,0x19,0x9b,0xa9,0x0a,0x8e,0x7b,0xa1,0x81,0x20,
0x19,0x88,0x07,0x88,0x1a,0x14,0x3a,0x59,0xc8,0x90,0x82,0x9d,0x00,0x89,0xe1,0x13,0x28,0x3d,0x89,0x80,
0x11,0x09,0xf9,0x89,0x29,0xe4,0x08,0x83,0x49,0x5a,0x90,0x18,0xb1,0x29,0x4b,0x90,0x11,0xb3,0x1f,0x1a,
0x99,0x18,0xb9,0xb2,0x87,0x41,0x69,0x00,0x19,0x0b,0xcb,0xaa,0xb7,0x11,0x00,0x90,0x9e,0x80,0x42,0x8d,
0x30,0xa0,0xa6,0x04,0x0a,0x00,0x99,0x01,0xd3,0x21,0x1f,0x89,0xa0,0x08,0x18,0x02,0x02,0x51,0x95,0x22,
0x89,0x8f,0xc1,0x90,0x19,0x91,0xa8,0x5b,0x3d,0x98,0x02,0xa2,0x1f,0x9a,0x96,0x02,0x2a,0x11,0x42,0x5a,
0x08,0xb1,0x00,0xa8,0x9f,0xc2,0xa9,0xa0,0x8e,0x21,0x6b,0x10,0x83,0x1a,0x11,0xb0,0x82,0xd1,0x90,0x30,
0x4c,0x09,0xa3,0x9a,0xc8,0xf1,0xb8,0x07,0x1b,0x89,0xb3,0x91,0x70,0x09,0x81,0x6a,0xb2,0x2d,0x14,0xb9,
0x33,0xf9,0x38,0xa9,0x9a,0x10,0x1c,0x93,0x7b,0xb1,0x18,0x04,0xf0,0x7f,0xb1,0x3a,0x08,0x9a,0x07,0x0b,
0xa6,0x0a,0x12,0x0a,0x80,0xf3,0x21,0x1a,0x2f,0xb0,0x01,0x93,0x1a,0x0a,0x8a,0x17,0x5d,0xa0,0xaa,0x07,
0x3d,0x88,0x00,0x88,0x2a,0xc0,0x95,0x29,0x18,0x0f,0xa4,0x90,0x02,0x48,0x9d,0x05,0x0d,0xa4,0x3b,0xb1,
0x38,0xc1,0x01,0x92,0x5b,0xa9,0x95,0x3a,0x08,0x1e,0xc4,0x28,0x88,0x08,0xb4,0x80,0x58,0x8d,0x94,0x0a,
0x93,0x5d,0xc0,0x21,0xb8,0x22,0x0b,0xb1,0x41,0xac,0x14,0x1d,0x92,0x98,0x83,0x4e,0xa8,0x11,0xb8,0x22,
0x8c,0x01,0x09,0x28,0xc2,0x7a,0xe8,0x84,0x88,0x39,0xb8,0x11,0xf1,0x22,0x2e,0x90,0x08,0xb2,0x30,0xaa,
0x12,0x1d,0xc3,0x11,0xa9,0xa7,0x49,0xaa,0xa6,0x38,0x8a,0x10,0xa9,0xa5,0x49,0x0a,0x81,0x2a,0xe1,0x94,
0x39,0x0a,0x29,0xd8,0xa6,0x10,0x1a,0x90,0x28,0xa8,0x93,0x88,0x90,0x20,0x9c,0xc7,0x21,0x89,0x30,0x1d,
0x98,0xc5,0x11,0x0a,0x38,0x2d,0xd1,0xa4,0x28,0x1a,0x19,0xd2,0x02,0x4c,0x0a,0xa2,0x18,0xc0,0xb5,0x02,
0x5b,0x2b,0x99,0xd6,0x93,0x28,0x2b,0x19,0x1a,0xe2,0xb4,0x82,0x4a,0x1b,0xd1,0xb4,0x12,0x4c,0x0a,0xa0,
0xb4,0x02,0x5b,0x2b,0x09,0xb0,0xb7,0xa3,0x38,0x3d,0x0a,0x90,0x91,0xb3,0x40,0x2c,0x89,0xa1,0x02,0xa8,
0x31,0x3c,0xc8,0xb7,0x81,0x00,0x4c,0x8a,0xa2,0x20,0xb8,0x44,0x3f,0xb9,0xb6,0x01,0x2a,0x88,0x6a,0x1b,
0xb1,0xc6,0x93,0x38,0x3e,0x0a,0xb1,0xa3,0x18,0x18,0x19,0x4a,0x8a,0x21,0x1e,0xe2,0xb5,0x20,0x3b,0x0a,
0x00,0xc0,0xa5,0x01,0x6b,0x2c,0x19,0x88,0xc2,0xb3,0x31,0x1f,0x80,0x08,0x80,0xb1,0xb4,0x42,0x3e,0x89,
0x90,0x88,0xc1,0xa3,0x48,0x3c,0x09,0x91,0x01,0x90,0x92,0x38,0x3c,0x9a,0x17,0x7a,0x0a,0xc0,0x80,0x0c,
0xa9,0xa2,0x11,0x20,0x22,0x51,0x10,0xa2,0xb3,0x80,0x3c,0xb7,0x51,0x2f,0xa9,0x91,0x8a,0xc8,0xb3,0x40,
0x4b,0x00,0x03,0x11,0xa2,0xa4,0x18,0x4f,0xa8,0xa5,0x18,0x8b,0xd8,0x90,0x0a,0x89,0x02,0x51,0x30,0x03,
0x14,0x38,0x1b,0xab,0x7a,0xb8,0xc7,0x82,0x2d,0x9b,0xb0,0x08,0x09,0x02,0x53,0x58,0x01,0x83,0x10,0x8a,
0xd8,0x22,0x3f,0xb9,0xc5,0x08,0x8b,0xb8,0x00,0x4a,0x00,0x05,0x41,0x28,0x81,0x81,0x1a,0x0c,0xe5,0x93,
0x5b,0x8c,0xa0,0x88,0x8a,0xc2,0x13,0x7a,0x18,0x82,0x12,0x19,0x98,0x98,0x48,0x3e,0xc9,0xc3,0x09,0x8c,
0xb8,0x10,0x4a,0x81,0x87,0x22,0x38,0x80,0x91,0x8a,0x0c,0xf3,0x83,0x3d,0x9b,0xf2,0x80,0x2b,0x99,0x83,
0x50,0x10,0x04,0x21,0x29,0xb8,0xd1,0x20,0x2e,0xc8,0xc3,0x19,0x0d,0xa8,0x00,0x39,0x00,0x06,0x31,0x38,
0x91,0x92,0x0a,0x1e,0xf3,0xa3,0x39,0x0e,0xb8,0x09,0x0b,0xb1,0x85,0x41,0x30,0x22,0x40,0x09,0xe1,0xb1,
0x58,0x2e,0xa8,0xc3,0x18,0x0c,0xa8,0x28,0x4c,0x88,0x95,0x22,0x39,0x80,0x81,0x1a,0x8c,0xf5,0xa3,0x39,
0x0d,0xa8,0x80,0x1a,0xb0,0x96,0x31,0x39,0x02,0x03,0x38,0x0c,0xb9,0x21,0x3f,0xcb,0xd6,0x00,0x1b,0xa9,
0x92,0x59,0x3a,0x92,0x86,0x02,0x00,0x00,0x1b,0x1c,0xf1,0xb3,0x38,0x1e,0xa9,0x90,0x1a,0x89,0x93,0x14,
0x32,0x24,0x32,0x39,0xb8,0xf2,0xd4,0x20,0x2f,0x9a,0xb1,0x08,0x8b,0xb2,0x13,0x50,0x11,0x05,0x21,0x08,
0xb1,0x98,0x7a,0x3e,0xaa,0xd4,0x91,0x1a,0x8a,0x88,0x39,0x10,0x96,0x85,0x21,0x18,0x80,0x09,0x5c,0x0b,
0xf3,0x91,0x1a,0x8c,0x98,0x19,0x19,0xa3,0x07,0x31,0x48,0x18,0x90,0xb1,0x88,0xf2,0xa3,0x5c,0x0d,0xc0,
0xa2,0x08,0x09,0x92,0x32,0x58,0x01,0x83,0x20,0x1c,0xd8,0xc5,0x11,0x3d,0x9b,0xb1,0x08,0x0b,0xc1,0x84,
0x40,0x38,0x11,0x02,0x92,0xc2,0xa1,0x91,0x6c,0x1e,0xa9,0xc1,0x88,0x9a,0xb2,0x32,0x7a,0x01,0x95,0x13,
0x29,0x89,0x98,0xb3,0x6a,0x1e,0xc9,0xc2,0x08,0x0b,0x88,0x21,0x79,0x00,0x84,0x02,0x10,0x89,0x98,0x91,
0x7b,0x1e,0xc8,0xc2,0x80,0x1a,0x09,0x01,0x31,0x31,0x04,0x04,0x48,0x1b,0xe8,0xb5,0x20,0x2f,0x9a,0xb1,
0x08,0x0a,0xa1,0x84,0x60,0x29,0x82,0x03,0x28,0x8a,0xb8,0xd6,0x92,0x3b,0x0f,0xa8,0x90,0x09,0xa0,0x95,
0x42,0x59,0x00,0x92,0x01,0x89,0x39,0x0c,0xf1,0xc2,0x88,0x8b,0x8a,0x1a,0x18,0x04,0x15,0x23,0x13,0x22,
0x3a,0x8e,0x81,0x4c,0x9c,0xf1,0xa0,0x0a,0x1b,0x09,0x11,0x32,0x35,0x33,0x13,0x02,0x89,0x1f,0xf1,0xc3,
0x29,0x0f,0xa8,0xa1,0x28,0x2a,0x92,0x15,0x40,0x18,0x92,0x92,0x89,0x28,0x2f,0xca,0xf4,0x91,0x2a,0x0b,
0x90,0x83,0x22,0x31,0x22,0x32,0x29,0x9a,0x8b,0xf6,0xa2,0x3c,0x8e,0xc0,0x80,0x2a,0xa0,0x86,0x31,0x59,
0x80,0x92,0x81,0x8a,0x80,0x4c,0x8c,0xf1,0xa1,0x09,0x0a,0x90,0x12,0x51,0x40,0x10,0x93,0x93,0x09,0x1d,
0xf2,0xb3,0x3a,0x8f,0xb0,0xa1,0x29,0x08,0x04,0x42,0x48,0x81,0x93,0x08,0xab,0x82,0x4f,0x9c,0xf3,0x80,
0x1a,0x99,0x82,0x30,0x48,0x02,0x85,0x01,0x19,0x8a,0x9a,0xe6,0x93,0x4b,0x8c,0xc0,0x80,0x09,0x91,0x23,
0x70,0x10,0x82,0x01,0x0a,0xba,0x82,0x3f,0xab,0xf3,0x08,0x0b,0xa8,0x11,0x30,0x04,0x25,0x48,0x08,0xb1,
0x88,0x2e,0xe1,0x93,0x5b,0x8c,0xc1,0x00,0x2b,0x90,0x04,0x50,0x18,0x92,0x01,0x8a,0xe0,0x21,0x3e,0xa9,
0xd4,0x00,0x0b,0xa8,0x10,0x4a,0x01,0x15,0x30,0x00,0xa2,0x89,0x9d,0xd1,0x18,0x1c,0xc0,0x92,0x09,0x98,
0x02,0x58,0x00,0x85,0x22,0x29,0x98,0xb0,0x19,0xf2,0x82,0x2e,0xac,0xd1,0x08,0x1a,0x11,0x33,0x14,0x04,
0x10,0x99,0xb9,0x9a,0x3c,0xd2,0x96,0x29,0x8d,0xb9,0x99,0x88,0x42,0x70,0x11,0x82,0x82,0x89,0x8b,0xaa,
0xb0,0x87,0x40,0x0d,0xc8,0x98,0x8a,0x18,0x40,0x32,0x14,0x02,0x00,0x9a,0xba,0xa9,0x98,0x37,0x3c,0xe9,
0xc0,0x99,0x8a,0x38,0x41,0x14,0x13,0x11,0x09,0x0c,0x9a,0x88,0x10,0x7a,0xd4,0xa2,0x0d,0xab,0x88,0x00,
0x24,0x51,0x10,0x01,0x09,0x98,0x08,0x19,0x90,0xa0,0x79,0xa9,0xf4,0xaa,0xaa,0x29,0x38,0x25,0x52,0x18,
0x81,0xa1,0x81,0x39,0x1c,0xa8,0xf0,0x38,0xf2,0x00,0x8e,0xb1,0x98,0x11,0x61,0x28,0x92,0x11,0x19,0x29,
0x0b,0xd2,0xa0,0xb8,0xb9,0x77,0x2d,0xc8,0x90,0x98,0x11,0x38,0x85,0x12,0x08,0x2b,0xaa,0xb4,0x01,0x1b,
0xca,0xf2,0xa1,0x77,0x1b,0xd0,0x88,0x99,0x31,0x48,0x02,0x80,0xe1,0x28,0x1b,0x92,0x91,0x92,0x2f,0x8c,
0x01,0x79,0xb7,0x10,0x8d,0x88,0x88,0x85,0x30,0x88,0x09,0xb9,0x83,0x3a,0x92,0x2a,0xf9,0x00,0x98,0x23,
0x7d,0xe3,0xa2,0xaa,0x10,0x4a,0x83,0x12,0xa8,0x1a,0x9c,0x84,0x20,0xa0,0x98,0xaa,0x39,0x29,0x06,0x58,
0xb7,0x4b,0x9e,0x00,0x80,0x15,0x19,0xa0,0x99,0xa1,0x50,0x19,0x00,0xaa,0x88,0x0a,0x04,0x31,0x3a,0x7f,
0xf3,0x80,0x0b,0x38,0x88,0x87,0x08,0x89,0x1b,0xb2,0x23,0x08,0x2a,0xad,0x91,0x21,0x41,0x89,0xe1,0x79,
0xe2,0x00,0xaa,0x21,0x19,0x15,0x89,0xa1,0x0c,0x80,0x48,0xa2,0x10,0xbb,0x88,0x88,0x17,0x29,0x98,0xca,
0x57,0x0d,0xb0,0x09,0x82,0x60,0x80,0x80,0xc8,0x10,0x1a,0x12,0x98,0x91,0x0d,0x08,0x10,0x94,0x18,0x99,
0x3c,0x07,0x3d,0xdb,0x08,0xa2,0x44,0x08,0x90,0xc9,0x82,0x5b,0x10,0x98,0xc1,0x09,0x19,0x38,0xa3,0x83,
0xab,0x5c,0x07,0x2c,0xea,0x08,0x80,0x43,0x80,0x81,0xbb,0x81,0x19,0x05,0x19,0xc0,0x8a,0x80,0x69,0x91,
0x01,0xa9,0x39,0x37,0x2f,0xdb,0x08,0x91,0x63,0x08,0x80,0xb9,0x01,0x3b,0x12,0xa8,0xd3,0x1b,0x89,0x11,
0xa7,0x11,0x8b,0xb0,0x77,0x0a,0xc8,0x8a,0x81,0x50,0x01,0x00,0xb8,0x29,0x0a,0x12,0xa1,0x91,0x8f,0x90,
0x10,0x23,0x2b,0xb8,0x9a,0x77,0xe2,0xa2,0x8d,0x18,0x28,0x05,0x18,0x99,0x8a,0xa1,0x31,0x81,0x89,0xe9,
0x18,0x00,0x31,0xa0,0x08,0xac,0x70,0xa7,0x00,0xae,0x18,0x00,0x25,0x90,0x08,0xac,0x11,0x00,0x13,0x9b,
0x99,0x0d,0x03,0x01,0x94,0x0c,0x89,0x39,0x77,0x1b,0xcb,0x8a,0x84,0x51,0x00,0x89,0xb8,0x28,0x01,0x20,
0xe1,0x09,0x99,0x30,0xa3,0x02,0xba,0x4d,0x1a,0x57,0x8b,0xbb,0x8d,0x05,0x21,0x11,0x8c,0xa8,0x18,0x13,
0x29,0xb8,0xac,0x01,0x69,0x01,0x89,0xb8,0x19,0x41,0x77,0x9b,0xaa,0x8b,0x15,0x31,0x82,0xac,0x90,0x28,
0x33,0x99,0xe1,0x9a,0x30,0x39,0x94,0xc8,0x08,0x2b,0x61,0x27,0x8d,0xab,0x8b,0x07,0x11,0x01,0x9c,0x80,
0x00,0x23,0x8a,0x99,0xbb,0x24,0x01,0x21,0xae,0x18,0x88,0x34,0x47,0x9e,0x99,0x8b,0x16,0x01,0x00,0xbb,
0x01,0x82,0x52,0x9a,0x9a,0xc0,0x42,0x10,0x0a,0xf0,0x00,0x20,0x4a,0x07,0xbb,0x8a,0x8c,0x34,0x82,0x10,
0xbc,0x28,0x93,0x33,0xc8,0x0d,0x98,0x21,0x03,0x8b,0xb8,0x1c,0x53,0x10,0x57,0xbc,0x9a,0xaa,0x36,0x01,
0x18,0xca,0x18,0x02,0x40,0xb0,0x9b,0x88,0x49,0x04,0xa9,0x90,0x9c,0x42,0x11,0x77,0xba,0x8a,0xab,0x53,
0x02,0x28,0xc9,0x09,0x03,0x40,0x81,0x9d,0xa0,0x08,0x43,0x99,0x90,0xda,0x31,0x28,0x77,0xb9,0x9a,0xba,
0x42,0x13,0x20,0xc8,0x0b,0x12,0x31,0x86,0xab,0xa8,0x8a,0x34,0x90,0x18,0xbd,0x29,0x92,0x77,0xa2,0xac,
0xd9,0x28,0x22,0x20,0xb2,0xab,0x30,0x59,0x33,0xc8,0xa8,0xbb,0x40,0x10,0x11,0xf0,0x89,0x88,0x71,0x87,
0xa9,0xb9,0x0d,0x21,0x11,0x04,0xb9,0x08,0x08,0x63,0x81,0x88,0xc9,0x0b,0x11,0x30,0x83,0xbd,0x99,0x1b,
0x77,0x91,0x9a,0xdc,0x08,0x02,0x42,0x81,0x9a,0xa0,0x38,0x44,0x28,0xa1,0xfa,0x88,0x19,0x30,0xa1,0xa0,
0xe9,0x78,0x82,0x98,0xf8,0x8a,0x18,0x38,0x04,0xa1,0x92,0x0c,0x40,0x00,0x04,0xb1,0x1a,0x8c,0x29,0xb2,
0x83,0x98,0x2f,0x07,0x98,0xb8,0x9f,0x09,0x88,0x15,0x00,0x18,0x9a,0x10,0x94,0x43,0x00,0x3a,0xc9,0x80,
0xa2,0x38,0x1a,0x8e,0x61,0xa5,0xa0,0xbc,0x8e,0x88,0x81,0x23,0x89,0x82,0x2a,0x11,0x12,0x07,0x10,0x10,
0x1a,0x81,0x80,0x02,0x0a,0xb9,0x77,0x92,0x8c,0xbf,0x98,0x98,0x22,0x19,0x29,0x9b,0x13,0x12,0x15,0x00,
0x31,0x5a,0x11,0x94,0xa5,0x82,0x49,0x2c,0x57,0xab,0xa0,0x9d,0x02,0xa1,0x5a,0x1d,0x0a,0xb2,0xa3,0x84,
0x2a,0x0a,0x2f,0x82,0x96,0x99,0x68,0x4b,0x08,0xc1,0xa1,0x02,0x79,0x99,0x38,0xd9,0x12,0xc8,0x10,0x1c,
0x18,0xc0,0xb3,0x92,0x7a,0x3a,0x1a,0xb1,0x84,0x93,0x10,0x8c,0x7a,0xa0,0x08,0xa8,0x4a,0xa8,0x1a,0xf4,
0x00,0x01,0x38,0x29,0x1c,0x82,0xb0,0x95,0xb3,0x20,0x1f,0x8a,0x92,0xd1,0x98,0x33,0x48,0x0e,0x18,0x21,
0xa0,0xc8,0x92,0x58,0x08,0x9f,0x94,0x08,0x82,0x1f,0x30,0x0a,0xc4,0xa0,0x12,0x29,0x99,0xaa,0x95,0x12,
0xc9,0x10,0x08,0x16,0x9e,0x22,0x1c,0xb3,0x0b,0x10,0x78,0x98,0x88,0x88,0x00,0x87,0x0c,0x81,0x19,0xc4,
0x08,0x20,0x3c,0xa8,0xe2,0x93,0x11,0x3b,0x0c,0x82,0xa1,0xb2,0x2b,0x79,0x89,0xf2,0x82,0x18,0x88,0x11,
0x1b,0x20,0x9e,0x94,0x09,0x01,0x2c,0x90,0x13,0xf0,0x48,0x80,0x29,0x0b,0x4b,0xd1,0x10,0xb0,0x7e,0xa0,
0x80,0xd3,0x10,0x82,0x4c,0x89,0xa0,0xb3,0x92,0x08,0x38,0x8b,0xf5,0x01,0x92,0x29,0x8c,0x84,0xa2,0x1b,
0x38,0x7a,0x8a,0xa8,0xa4,0x20,0x08,0x0f,0xa4,0x90,0x94,0x1c,0x33,0x0f,0x00,0x09,0x92,0xd0,0x02,0x1a,
0x80,0xb3,0x28,0x28,0x0f,0x92,0x88,0x82,0x2c,0xa1,0x13,0xf0,0x40,0x9a,0x95,0x98,0x18,0x80,0x3b,0x3f,
0x4b,0xb1,0x91,0x11,0x3f,0x18,0x8a,0xa2,0xa0,0x04,0x2a,0x6c,0x1c,0x28,0x9a,0xb4,0x38,0x40,0x0f,0x81,
0xb0,0x84,0x8b,0x52,0x0d,0x10,0x90,0x11,0xd8,0x20,0xc0,0x20,0xe2,0x10,0x81,0x4b,0x2c,0xa8,0x93,0x6a,
0x88,0x88,0xe3,0x28,0x88,0x18,0xb0,0x02,0x59,0x80,0x0e,0x03,0x19,0xb0,0x0e,0x80,0x15,0xca,0x41,0x08,
0x3a,0xb9,0x2e,0xa5,0x09,0x80,0x08,0x24,0xcb,0x85,0x8c,0x13,0xb0,0x1a,0x84,0x2f,0x12,0x9d,0xa2,0x10,
0x86,0x0a,0xa8,0x4c,0x83,0x8a,0xc1,0x38,0x22,0x9b,0xca,0xa2,0x07,0x09,0x3b,0x88,0x04,0xd8,0x1b,0x95,
0x01,0x91,0x99,0x98,0x7b,0x49,0xa8,0xa1,0xc4,0x7b,0x18,0x0a,0xb1,0xc4,0x12,0x1c,0x59,0xc8,0x82,0xb2,
0x49,0x0a,0x4c,0xd2,0x91,0x83,0x19,0x20,0x0f,0x91,0x98,0x23,0xc8,0x18,0x90,0x4c,0x01,0xa9,0xa5,0x08,
0x21,0x9a,0xa9,0x3b,0x85,0xb3,0x82,0xb8,0xb8,0x06,0x7a,0x0a,0x99,0xb0,0x34,0x01,0x4b,0xea,0xa3,0xa5,
0x4b,0x1a,0x1b,0xa6,0x90,0x08,0x2c,0x41,0xd2,0x90,0xa4,0x29,0x20,0x0e,0x93,0xc0,0x99,0x28,0x38,0x89,
0x14,0x12,0x48,0x08,0xc0,0x9a,0x18,0xbf,0x11,0xc8,0x48,0x9a,0x4b,0x14,0x10,0x13,0xa9,0x70,0xa2,0xad,
0xa0,0xbb,0x48,0x02,0x21,0x04,0x1a,0x39,0x99,0x85,0x82,0x19,0xbb,0xeb,0x91,0x18,0x4a,0x98,0xb0,0x84,
0x29,0x58,0x81,0xb2,0x77,0xa4,0x9f,0x90,0x99,0x51,0x10,0x08,0x92,0xa9,0x58,0x00,0x88,0xa3,0xba,0x1b,
0x99,0x90,0x07,0x98,0x29,0x19,0x1a,0x05,0x92,0x28,0xa9,0x77,0xb5,0xaf,0x10,0x88,0x30,0x14,0xa9,0x19,
0xa8,0x18,0x16,0x98,0x89,0x99,0x8b,0x22,0x82,0x29,0x89,0x9d,0x81,0x13,0x62,0x00,0xba,0xc0,0x77,0xa3,
0xbf,0x08,0x00,0x40,0x22,0xc8,0x98,0x00,0x29,0x14,0xc2,0x9a,0x1a,0x0c,0x12,0x94,0x99,0x19,0x8a,0x12,
0x07,0x88,0x2b,0x9a,0xb2,0x77,0x94,0xdc,0x09,0x10,0x20,0x22,0xc0,0x8b,0x30,0x00,0x13,0xb1,0xaf,0x19,
0x82,0x11,0x01,0xbc,0x98,0x14,0x20,0x20,0xba,0x9c,0x31,0x30,0x7a,0x57,0xfb,0x0b,0x21,0x82,0x11,0x90,
0x9e,0x11,0x03,0x08,0x09,0xbc,0x08,0x15,0x08,0x09,0xb9,0x88,0x63,0x00,0x9a,0x90,0x98,0x51,0x10,0xba,
0x77,0xa1,0xbf,0x28,0x13,0x00,0x00,0xd9,0x1a,0x43,0xa1,0x98,0x99,0x8b,0x53,0x93,0xab,0x1a,0x80,0x22,
0x14,0xcb,0x0b,0x22,0x82,0x10,0x9a,0x9f,0x77,0xd2,0x9e,0x11,0x03,0x08,0x08,0xba,0x28,0x35,0xa9,0xab,
0x01,0x18,0x60,0x90,0xbb,0x20,0x32,0x0a,0x88,0xe8,0x18,0x42,0x08,0xaa,0xc8,0x28,0x77,0xd2,0x9f,0x21,
0x12,0x89,0x89,0x98,0x38,0x43,0xc8,0xab,0x21,0x11,0x18,0xb0,0xdb,0x48,0x33,0xc0,0xaa,0x28,0x28,0x12,
0xa0,0x9e,0x39,0x84,0x71,0x97,0xec,0x19,0x33,0x91,0xaa,0x08,0x18,0x41,0xa3,0xcc,0x2a,0x23,0xa1,0x98,
0x8a,0x8a,0x16,0x82,0x9c,0x09,0x83,0x02,0x18,0xac,0x98,0x15,0xa1,0x77,0xc0,0x9f,0x20,0x14,0x99,0x8a,
0x81,0x01,0x22,0xa8,0xae,0x11,0x23,0xa9,0x99,0x98,0x20,0x24,0xb8,0xac,0x30,0x23,0x89,0xa9,0xb9,0x50,
0x51,0x49,0x67,0xfa,0x0d,0x31,0x02,0xaa,0x1a,0x10,0x11,0x12,0xe9,0x9b,0x42,0x83,0xb9,0x8a,0x18,0x21,
0x83,0xd8,0x8b,0x31,0x04,0xa8,0x9a,0x08,0x12,0x86,0x59,0x57,0xfa,0x8d,0x32,0x02,0xba,0x09,0x11,0x02,
0x11,0xba,0xad,0x33,0x14,0xab,0x8c,0x81,0x13,0x10,0xab,0xba,0x34,0x33,0xcb,0xab,0x22,0x42,0x09,0x6a,
0x77,0xe9,0x8d,0x31,0x84,0xa9,0x1a,0x11,0x81,0x02,0x9a,0x8d,0x21,0x04,0x9a,0x8c,0x00,0x03,0x10,0x8c,
0xa9,0x03,0x14,0x0a,0xab,0x92,0x14,0x10,0xab,0x77,0xc5,0xaf,0x30,0x14,0x99,0x9b,0x11,0x03,0x01,0x99,
0xbb,0x11,0x26,0x98,0xac,0x08,0x13,0x82,0xaa,0x9b,0x31,0x43,0xb8,0xcb,0x41,0x32,0xb9,0xd8,0x77,0x93,
0xef,0x18,0x32,0x90,0x9c,0x10,0x12,0x18,0x99,0xa9,0x10,0x52,0x88,0xcb,0x08,0x41,0x00,0xb9,0x99,0x30,
0x22,0xb0,0xc9,0x29,0x51,0x81,0xb9,0x77,0x94,0xcf,0x29,0x43,0xa0,0xab,0x20,0x22,0x08,0xa0,0xba,0x49,
0x53,0xb0,0xd9,0x18,0x21,0x80,0xb8,0x99,0x48,0x21,0xc1,0xb8,0x19,0x68,0x02,0xd1,0x72,0x84,0xdf,0x08,
0x24,0x98,0xaa,0x20,0x12,0x08,0x88,0xba,0x20,0x44,0x98,0xac,0x18,0x31,0x18,0xaa,0xba,0x30,0x25,0xb0,
0xbb,0x38,0x61,0x80,0xc8,0x77,0x92,0xcf,0x18,0x33,0xa0,0xac,0x11,0x12,0x00,0x98,0xaa,0x28,0x35,0xb0,
0xbc,0x10,0x23,0x90,0xaa,0x9b,0x30,0x16,0xa0,0x9c,0x18,0x05,0x81,0x1b,0x77,0xc2,0xbf,0x30,0x14,0xb8,
0x8b,0x21,0x12,0x10,0x99,0x9d,0x30,0x15,0xa9,0xab,0x21,0x21,0x98,0xca,0x80,0x32,0x82,0xca,0x99,0x41,
0x21,0xb8,0x78,0x77,0xfb,0x0a,0x42,0x81,0xba,0x18,0x21,0x01,0x81,0xbb,0x0c,0x35,0x81,0xac,0x1a,0x12,
0x01,0x89,0xbb,0x08,0x35,0x81,0xad,0x88,0x04,0x11,0x1b,0x77,0xd2,0xae,0x20,0x14,0xa8,0x8b,0x11,0x12,
0x10,0xa8,0xbc,0x51,0x23,0xca,0x9a,0x11,0x12,0x88,0xb9,0x8b,0x30,0x33,0xa1,0xe9,0x09,0x51,0x01,0x70,
0x17,0xfc,0x0b,0x43,0xa2,0xaa,0x19,0x22,0x01,0x81,0xcb,0x2b,0x45,0x91,0xac,0x19,0x31,0x80,0xb8,0xb9,
0x28,0x44,0xa1,0xcb,0x18,0x42,0x80,0x49,0x57,0xf9,0x8c,0x31,0x83,0xba,0x1a,0x03,0x02,0x21,0xca,0x9c,
0x53,0x02,0xcb,0x09,0x11,0x00,0x80,0xc9,0x0a,0x62,0x00,0xba,0x19,0x22,0x00,0x19,0x77,0xd3,0xaf,0x20,
0x14,0x99,0x8b,0x01,0x12,0x30,0xa9,0xad,0x41,0x23,0xca,0x9a,0x11,0x20,0x80,0xc9,0x0b,0x68,0x02,0xb9,
0x8a,0x21,0x12,0x91,0x77,0xa4,0xde,0x08,0x43,0x98,0x9a,0x00,0x11,0x21,0x90,0xdb,0x28,0x34,0xa8,0xab,
0x08,0x21,0x01,0xb1,0x9f,0x3a,0x33,0xb0,0xbb,0x4a,0x21,0x93,0x74,0x17,0xed,0x0a,0x42,0x91,0xa9,0x08,
0x10,0x12,0x93,0xca,0x1b,0x34,0x93,0xca,0x0a,0x28,0x21,0xa1,0xdb,0x0a,0x22,0x83,0xc9,0x8b,0x10,0x06,
0x70,0x27,0xfa,0x8c,0x31,0x02,0xaa,0x0a,0x10,0x22,0x13,0xca,0x9c,0x42,0x13,0x9a,0x9c,0x00,0x31,0x00,
0xe9,0xa9,0x40,0x11,0xb8,0xa9,0x39,0x68,0x78,0x07,0xe9,0x0c,0x30,0x83,0xb9,0x1a,0x18,0x12,0x04,0xa9,
0x8d,0x31,0x14,0x9a,0x9b,0x00,0x22,0x02,0xda,0xab,0x31,0x33,0xbb,0xbc,0x01,0x61,0x79,0x27,0xfa,0x0c,
0x30,0x82,0x99,0x0a,0x08,0x03,0x15,0x9a,0x8d,0x22,0x12,0x8a,0xaa,0x80,0x41,0x30,0xea,0x99,0x20,0x21,
0xa9,0xa9,0x1b,0x58,0x71,0x37,0xfb,0x0d,0x21,0x92,0xa8,0x09,0x88,0x22,0x24,0xba,0x9c,0x23,0x23,0x0a,
0xc9,0xc0,0x41,0x30,0xc9,0xc8,0x20,0x28,0x88,0xb0,0x1c,0x0a,0x73,0x17,0xeb,0x1b,0x21,0x92,0x08,0x8a,
0xba,0x34,0x24,0xab,0xa9,0x04,0x30,0x20,0xd0,0x99,0x69,0x10,0xa0,0x98,0x0a,0x28,0x11,0xaa,0x9c,0x08,
0x79,0x17,0xdd,0x28,0x00,0x90,0x82,0xba,0x39,0x24,0x90,0x09,0x80,0x01,0x16,0xa8,0x08,0x30,0x4a,0x19,
0xa9,0xb8,0x96,0x00,0x1c,0x9a,0x98,0x01,0x7f,0xc7,0x99,0x11,0x1a,0x80,0xb0,0x29,0x23,0xa8,0x11,0x2a,
0x93,0xa7,0x92,0x69,0x2a,0x88,0x91,0x2a,0x88,0xb2,0x89,0xe0,0x25,0x8f,0x32,0x8f,0x2a,0xc2,0xa5,0x18,
0x39,0x8f,0x84,0xa0,0x10,0xa0,0x01,0x41,0x2d,0x09,0xc2,0x90,0x82,0xf3,0x88,0x31,0x9c,0x32,0xba,0xa1,
0x83,0x88,0xa7,0x3c,0x22,0x1f,0xc5,0x91,0x21,0x1e,0x90,0xb2,0x11,0x2b,0xc0,0x81,0x97,0x5a,0x1a,0xb8,
0xa3,0x94,0x1b,0x83,0x0b,0x18,0x98,0x30,0xf5,0x28,0x91,0x7b,0xa8,0xd3,0x93,0x10,0x2e,0x88,0xb1,0x13,
0x1e,0x01,0xe1,0x21,0x89,0x3a,0xc0,0x48,0xc0,0x80,0x94,0x90,0x21,0x0d,0x10,0x91,0x0a,0xb2,0x39,0x08,
0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x88,0x00,0x88,0x00,0x08,0x08,0x08,0x08,0x88,0x80,0x80,
0x80,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x29,0x8c,0x84,0xa2,0x1b,0x38,0x7a,0x8a,0xa8,0xa4,0x20,0x08,0x0f,0xa4,0x90,0x94,0x1c,0x33,0x0f,0x00,
0x09,0x92,0xd0,0x02,0x1a,0x80,0xb3,0x28,0x28,0x0f,0x92,0x88,0x82,0x2c,0xa1,0x13,0xf0,0x40,0x9a,0x95,
0x98,0x18,0x80,0x3b,0x3f,0x4b,0xb1,0x91,0x11,0x3f,0x18,0x8a,0xa2,0xa0,0x04,0x2a,0x6c,0x1c,0x28,0x9a,
0xb4,0x38,0x40,0x0f,0x81,0xb0,0x84,0x8b,0x52,0x0d,0x10,0x90,0x11,0xd8,0x20,0xc0,0x20,0xe2,0x10,0x81,
0x4b,0x2c,0xa8,0x93,0x6a,0x88,0x88,0xe3,0x28,0x88,0x18,0xb0,0x02,0x59,0x80,0x0e,0x03,0x19,0xb0,0x0e,
0x80,0x15,0xca,0x41,0x08,0x3a,0xb9,0x2e,0xa5,0x09,0x80,0x08,0x24,0xcb,0x85,0x8c,0x13,0xb0,0x1a,0x84,
0x2f,0x12,0x9d,0xa2,0x10,0x86,0x0a,0xa8,0x4c,0x83,0x8a,0xc1,0x38,0x22,0x9b,0xca,0xa2,0x07,0x09,0x3b,
0x88,0x04,0xd8,0x1b,0x95,0x01,0x91,0x99,0x98,0x7b,0x49,0xa8,0xa1,0xc4,0x7b,0x18,0x0a,0xb1,0xc4,0x12,
0x1c,0x59,0xc8,0x82,0xb2,0x49,0x0a,0x4c,0xd2,0x91,0x83,0x19,0x20,0x0f,0x91,0x98,0x23,0xc8,0x18,0x90,
0x4c,0x01,0xa9,0xa5,0x08,0x21,0x9a,0xa9,0x3b,0x85,0xb3,0x82,0xb8,0xb8,0x06,0x7a,0x0a,0x99,0xb0,0x34,
0x01,0x4b,0xea,0xa3,0xa5,0x4b,0x1a,0x1b,0xa6,0x90,0x08,0x2c,0x41,0xd2,0x90,0xa4,0x29,0x20,0x0e,0x93,
0xc0,0x99,0x28,0x38,0x89,0x14,0x12,0x48,0x08,0xc0,0x9a,0x18,0xbf,0x11,0xc8,0x48,0x9a,0x4b,0x14,0x10,
0x13,0xa9,0x70,0xa2,0xad,0xa0,0xbb,0x48,0x02,0x21,0x04,0x1a,0x39,0x99,0x85,0x82,0x19,0xbb,0xeb,0x91,
0x18,0x4a,0x98,0xb0,0x84,0x29,0x58,0x81,0xb2,0x77,0xa4,0x9f,0x90,0x99,0x51,0x10,0x08,0x92,0xa9,0x58,
0x00,0x88,0xa3,0xba,0x1b,0x99,0x90,0x07,0x98,0x29,0x19,0x1a,0x05,0x92,0x28,0xa9,0x77,0xb5,0xaf,0x10,
0x88,0x30,0x14,0xa9,0x19,0xa8,0x18,0x16,0x98,0x89,0x99,0x8b,0x22,0x82,0x29,0x89,0x9d,0x81,0x13,0x62,
0x00,0xba,0xc0,0x77,0xa3,0xbf,0x08,0x00,0x40,0x22,0xc8,0x98,0x00,0x29,0x14,0xc2,0x9a,0x1a,0x0c,0x12,
0x94,0x99,0x19,0x8a,0x12,0x07,0x88,0x2b,0x9a,0xb2,0x77,0x94,0xdc,0x09,0x10,0x20,0x22,0xc0,0x8b,0x30,
0x00,0x13,0xb1,0xaf,0x19,0x82,0x11,0x01,0xbc,0x98,0x14,0x20,0x20,0xba,0x9c,0x31,0x30,0x7a,0x57,0xfb,
0x0b,0x21,0x82,0x11,0x90,0x9e,0x11,0x03,0x08,0x09,0xbc,0x08,0x15,0x08,0x09,0xb9,0x88,0x63,0x00,0x9a,
0x90,0x98,0x51,0x10,0xba,0x77,0xa1,0xbf,0x28,0x13,0x00,0x00,0xd9,0x1a,0x43,0xa1,0x98,0x99,0x8b,0x53,
0x93,0xab,0x1a,0x80,0x22,0x14,0xcb,0x0b,0x22,0x82,0x10,0x9a,0x9f,0x77,0xd2,0x9e,0x11,0x03,0x08,0x08,
0xba,0x28,0x35,0xa9,0xab,0x01,0x18,0x60,0x90,0xbb,0x20,0x32,0x0a,0x88,0xe8,0x18,0x42,0x08,0xaa,0xc8,
0x28,0x77,0xd2,0x9f,0x21,0x12,0x89,0x89,0x98,0x38,0x43,0xc8,0xab,0x21,0x11,0x18,0xb0,0xdb,0x48,0x33,
0xc0,0xaa,0x28,0x28,0x12,0xa0,0x9e,0x39,0x84,0x71,0x97,0xec,0x19,0x33,0x91,0xaa,0x08,0x18,0x41,0xa3,
0xcc,0x2a,0x23,0xa1,0x98,0x8a,0x8a,0x16,0x82,0x9c,0x09,0x83,0x02,0x18,0xac,0x98,0x15,0xa1,0x77,0xc0,
0x9f,0x20,0x14,0x99,0x8a,0x81,0x01,0x22,0xa8,0xae,0x11,0x23,0xa9,0x99,0x98,0x20,0x24,0xb8,0xac,0x30,
0x23,0x89,0xa9,0xb9,0x50,0x51,0x49,0x67,0xfa,0x0d,0x31,0x02,0xaa,0x1a,0x10,0x11,0x12,0xe9,0x9b,0x42,
0x83,0xb9,0x8a,0x18,0x21,0x83,0xd8,0x8b,0x31,0x04,0xa8,0x9a,0x08,0x12,0x86,0x59,0x57,0xfa,0x8d,0x32,
0x02,0xba,0x09,0x11,0x02,0x11,0xba,0xad,0x33,0x14,0xab,0x8c,0x81,0x13,0x10,0xab,0xba,0x34,0x33,0xcb,
0xab,0x22,0x42,0x09,0x6a,0x77,0xe9,0x8d,0x31,0x84,0xa9,0x1a,0x11,0x81,0x02,0x9a,0x8d,0x21,0x04,0x9a,
0x8c,0x00,0x03,0x10,0x8c,0xa9,0x03,0x14,0x0a,0xab,0x92,0x14,0x10,0xab,0x77,0xc5,0xaf,0x30,0x14,0x99,
0x9b,0x11,0x03,0x01,0x99,0xbb,0x11,0x26,0x98,0xac,0x08,0x13,0x82,0xaa,0x9b,0x31,0x43,0xb8,0xcb,0x41,
0x32,0xb9,0xd8,0x77,0x93,0xef,0x18,0x32,0x90,0x9c,0x10,0x12,0x18,0x99,0xa9,0x10,0x52,0x88,0xcb,0x08,
0x41,0x00,0xb9,0x99,0x30,0x22,0xb0,0xc9,0x29,0x51,0x81,0xb9,0x77,0x94,0xcf,0x29,0x43,0xa0,0xab,0x20,
0x22,0x08,0xa0,0xba,0x49,0x53,0xb0,0xd9,0x18,0x21,0x80,0xb8,0x99,0x48,0x21,0xc1,0xb8,0x19,0x68,0x02,
0xd1,0x72,0x84,0xdf,0x08,0x24,0x98,0xaa,0x20,0x12,0x08,0x88,0xba,0x20,0x44,0x98,0xac,0x18,0x31,0x18,
0xaa,0xba,0x30,0x25,0xb0,0xbb,0x38,0x61,0x80,0xc8,0x77,0x92,0xcf,0x18,0x33,0xa0,0xac,0x11,0x12,0x00,
0x98,0xaa,0x28,0x35,0xb0,0xbc,0x10,0x23,0x90,0xaa,0x9b,0x30,0x16,0xa0,0x9c,0x18,0x05,0x81,0x1b,0x77,
0xc2,0xbf,0x30,0x14,0xb8,0x8b,0x21,0x12,0x10,0x99,0x9d,0x30,0x15,0xa9,0xab,0x21,0x21,0x98,0xca,0x80,
0x32,0x82,0xca,0x99,0x41,0x21,0xb8,0x78,0x77,0xfb,0x0a,0x42,0x81,0xba,0x18,0x21,0x01,0x81,0xbb,0x0c,
0x35,0x81,0xac,0x1a,0x12,0x01,0x89,0xbb,0x08,0x35,0x81,0xad,0x88,0x04,0x11,0x1b,0x77,0xd2,0xae,0x20,
0x14,0xa8,0x8b,0x11,0x12,0x10,0xa8,0xbc,0x51,0x23,0xca,0x9a,0x11,0x12,0x88,0xb9,0x8b,0x30,0x33,0xa1,
0xe9,0x09,0x51,0x01,0x70,0x17,0xfc,0x0b,0x43,0xa2,0xaa,0x19,0x22,0x01,0x81,0xcb,0x2b,0x45,0x91,0xac,
0x19,0x31,0x80,0xb8,0xb9,0x28,0x44,0xa1,0xcb,0x18,0x42,0x80,0x49,0x57,0xf9,0x8c,0x31,0x83,0xba,0x1a,
0x03,0x02,0x21,0xca,0x9c,0x53,0x02,0xcb,0x09,0x11,0x00,0x80,0xc9,0x0a,0x62,0x00,0xba,0x19,0x22,0x00,
0x19,0x77,0xd3,0xaf,0x20,0x14,0x99,0x8b,0x01,0x12,0x30,0xa9,0xad,0x41,0x23,0xca,0x9a,0x11,0x20,0x80,
0xc9,0x0b,0x68,0x02,0xb9,0x8a,0x21,0x12,0x91,0x77,0xa4,0xde,0x08,0x43,0x98,0x9a,0x00,0x11,0x21,0x90,
0xdb,0x28,0x34,0xa8,0xab,0x08,0x21,0x01,0xb1,0x9f,0x3a,0x33,0xb0,0xbb,0x4a,0x21,0x93,0x74,0x17,0xed,
0x0a,0x42,0x91,0xa9,0x08,0x10,0x12,0x93,0xca,0x1b,0x34,0x93,0xca,0x0a,0x28,0x21,0xa1,0xdb,0x0a,0x22,
0x83,0xc9,0x8b,0x10,0x06,0x70,0x27,0xfa,0x8c,0x31,0x02,0xaa,0x0a,0x10,0x22,0x13,0xca,0x9c,0x42,0x13,
0x9a,0x9c,0x00,0x31,0x00,0xe9,0xa9,0x40,0x11,0xb8,0xa9,0x39,0x68,0x78,0x07,0xe9,0x0c,0x30,0x83,0xb9,
0x1a,0x18,0x12,0x04,0xa9,0x8d,0x31,0x14,0x9a,0x9b,0x00,0x22,0x02,0xda,0xab,0x31,0x33,0xbb,0xbc,0x01,
0x61,0x79,0x27,0xfa,0x0c,0x30,0x82,0x99,0x0a,0x08,0x03,0x15,0x9a,0x8d,0x22,0x12,0x8a,0xaa,0x80,0x41,
0x30,0xea,0x99,0x20,0x21,0xa9,0xa9,0x1b,0x58,0x71,0x37,0xfb,0x0d,0x21,0x92,0xa8,0x09,0x88,0x22,0x24,
0xba,0x9c,0x23,0x23,0x0a,0xc9,0xc0,0x41,0x30,0xc9,0xc8,0x20,0x28,0x88,0xb0,0x1c,0x0a,0x73,0x17,0xeb,
0x1b,0x21,0x92,0x08,0x8a,0xba,0x34,0x24,0xab,0xa9,0x04,0x30,0x20,0xd0,0x99,0x69,0x10,0xa0,0x98,0x0a,
0x28,0x11,0xaa,0x9c,0x08,0x79,0x17,0xdd,0x28,0x00,0x90,0x82,0xba,0x39,0x24,0x90,0x09,0x80,0x01,0x16,
0xa8,0x08,0x30,0x4a,0x19,0xa9,0xb8,0x96,0x00,0x1c,0x9a,0x98,0x01,0x7f,0xc7,0x99,0x11,0x1a,0x80,0xb0,
0x29,0x23,0xa8,0x11,0x2a,0x93,0xa7,0x92,0x69,0x2a,0x88,0x91,0x2a,0x88,0xb2,0x89,0xe0,0x25,0x8f,0x32,
0x8f,0x2a,0xc2,0xa5,0x18,0x39,0x8f,0x84,0xa0,0x10,0xa0,0x01,0x41,0x2d,0x09,0xc2,0x90,0x82,0xf3,0x88,
0x31,0x9c,0x32,0xba,0xa1,0x83,0x88,0xa7,0x3c,0x22,0x1f,0xc5,0x91,0x21,0x1e,0x90,0xb2,0x11,0x2b,0xc0,
0x81,0x97,0x5a,0x1a,0xb8,0xa3,0x94,0x1b,0x83,0x0b,0x18,0x98,0x30,0xf5,0x28,0x91,0x7b,0xa8,0xd3,0x93,
0x10,0x2e,0x88,0xb1,0x13,0x1e,0x01,0xe1,0x21,0x89,0x3a,0xc0,0x48,0xc0,0x80,0x94,0x90,0x21,0x0d,0x10,
0x91,0x0a,0xb2,0x39,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x88,0x00,0x88,0x00,0x08,0x08,
0x08,0x08,0x88,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
@@ -483,9 +329,10 @@ static const uint8_t gpsOffData[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
};
static constexpr VoiceClip gpsOffClip = {
gpsOffData, sizeof(gpsOffData), 9237, 8000
gpsOffData, sizeof(gpsOffData), 9237, 8000, gpsData, sizeof(gpsData)
};
}} // namespace mesh::audio
-6
View File
@@ -78,13 +78,7 @@ class GpsVoicePlayer {
public:
bool start(VoiceClip clip) {
stop();
#if defined(MESH_GPS_VOICE_G726_BITRATE)
if (!clip.valid() || !HwPWM3.takeOwnership(OWNER)) return false;
#else
if (!clip.data || !clip.samples || clip.samples > clip.bytes * 2
|| (clip.sample_rate != 8000 && clip.sample_rate != 16000)
|| !HwPWM3.takeOwnership(OWNER)) return false;
#endif
owned_ = true;
if (!HwPWM3.addPin(PIN_BUZZER)) { stop(); return false; }
decoder_.begin(clip);
+66
View File
@@ -0,0 +1,66 @@
#include <cassert>
#include <cstdio>
#include <cstring>
#include <initializer_list>
#define PIN_BUZZER 25
#define MESH_BUTTON_AUDIO_HIL 1
#if defined(MESH_GPS_VOICE_G726_BITRATE)
#include <helpers/ui/G726VoiceData.h>
#else
#include <helpers/ui/ButtonVoiceData.h>
#include <helpers/ui/GpsVoiceData.h>
#endif
#include <helpers/ui/nrf52/GpsVoicePlayer.h>
uint32_t now_ms=0;
int main() {
using namespace mesh::audio;
const VoiceClip clips[]={readyClip,notificationClearedClip,advertQueuedClip,
advertFailedClip,soundOnClip,soundOffClip,actionFailedClip,usbSetupClip,
shuttingDownClip,restartingClip,gpsOnClip,gpsOffClip,
#ifdef HAS_DRV2605
alertsSoundVibrationClip,alertsSoundOnlyClip,alertsVibrationOnlyClip,alertsSilentClip,
#endif
};
for (auto clip:clips) {
#ifdef PWM_OUTPUT
GpsVoicePlayer player;
assert(player.setGain(2));
assert(player.start(clip));
uint16_t first[2][128];
for(unsigned i=0;i<2;++i)
std::memcpy(first[i],reinterpret_cast<const void*>(registers.SEQ[i].PTR),sizeof(first[i]));
unsigned sequence=0,events=0;
while (player.playing()) {
assert(++events<1000);
const auto data=reinterpret_cast<const uint16_t*>(registers.SEQ[sequence].PTR);
assert(std::fwrite(data,sizeof(*data),registers.SEQ[sequence].CNT,stdout)==registers.SEQ[sequence].CNT);
registers.EVENTS_SEQEND[sequence]=1;player.interrupt();sequence^=1;
}
assert(player.played()==clip.samples);
assert(player.completed()==1&&player.cancelled()==0);
player.service();assert(!player.active()&&!HwPWM3.owned&&!irq_enabled);
// Cancel before and after the GPS prefix seam, then restart. A tail must
// never inherit another announcement's adaptive/interpolation state.
for(unsigned fraction:{1u,3u}) {
assert(player.start(clip));unsigned seq=0;
while(player.playing()&&player.played()<clip.samples*fraction/4) {
registers.EVENTS_SEQEND[seq]=1;player.interrupt();seq^=1;
}
assert(player.playing());player.stop();
assert(!player.active()&&!HwPWM3.owned&&!irq_enabled);
assert(player.start(clip));
for(unsigned i=0;i<2;++i)
assert(std::memcmp(first[i],reinterpret_cast<const void*>(registers.SEQ[i].PTR),sizeof(first[i]))==0);
player.stop();
}
assert(player.started()==5&&player.completed()==1&&player.cancelled()==4);
#else
VoiceDecoder decoder;decoder.begin(clip);
for(uint32_t sample=0;sample<clip.samples;++sample) {
assert(!decoder.done());const int16_t value=decoder.next();
assert(std::fwrite(&value,sizeof(value),1,stdout)==1);
}
assert(decoder.done()&&decoder.position()==clip.samples&&decoder.next()==0);
#endif
}
}
+2 -1
View File
@@ -59,8 +59,9 @@ int main(){
for(const auto& entry:mappings){
auto actual=buttonVoiceClip(entry.prompt);
assert(actual.data==entry.clip.data&&actual.bytes==entry.clip.bytes);
assert(actual.prefix==entry.clip.prefix&&actual.prefix_bytes==entry.clip.prefix_bytes);
assert(actual.samples==entry.clip.samples&&actual.sample_rate==entry.clip.sample_rate);
assert(actual.data&&actual.bytes==(actual.samples+1)/2);
assert(actual.valid()&&actual.bytes+actual.prefix_bytes==(actual.samples+1)/2);
assert(actual.sample_rate==8000||actual.sample_rate==16000);
VoiceDecoder decoder;decoder.begin(actual);
unsigned count=0,nonzero=0;
+61 -13
View File
@@ -1,6 +1,7 @@
"""Validate the fixed prerecorded catalog without speech synthesis or hardware."""
from pathlib import Path
import hashlib
import importlib.util
import re
import shutil
import struct
@@ -64,22 +65,69 @@ def write_wav(path, samples, *, rate=8000, channels=1, width=2):
wav.writeframes(payload)
def parse_header(source):
arrays = {
def select_vibration(source, enabled=True):
"""Select the generated catalog's HAS_DRV2605 branches for asset tests."""
lines = []
stack = [True]
for line in source.splitlines():
if line == '#ifdef HAS_DRV2605':
stack.append(stack[-1] and enabled)
elif line == '#else' and len(stack) > 1:
stack[-1] = stack[-2] and not stack[-1]
elif line == '#endif // HAS_DRV2605':
stack.pop()
elif stack[-1]:
lines.append(line)
assert len(stack) == 1
return '\n'.join(lines)
def parse_header(source, has_drv2605=True):
source = select_vibration(source, has_drv2605)
spans = {
name: bytes(int(value, 16) for value in re.findall(r"0x([0-9a-fA-F]{2})", body))
for name, body in re.findall(
r"static const uint8_t (\w+)Data\[\] = \{(.*?)\};", source, re.S)
}
clips = {
name: (data, size, int(samples), int(rate))
for name, data, size, samples, rate in re.findall(
arrays, clips = {}, {}
for name, data, size, samples, rate, prefix, prefix_size in re.findall(
r"static constexpr VoiceClip (\w+)Clip = \{\s*"
r"(\w+)Data, sizeof\((\w+)Data\), (\d+), (\d+)\s*\};", source)
}
r"(\w+)Data, sizeof\((\w+)Data\), (\d+), (\d+)"
r"(?:, (\w+)Data, sizeof\((\w+)Data\))?\s*\};", source):
assert data == size and prefix == prefix_size
clips[name] = (data, size, int(samples), int(rate))
arrays[name] = (spans[prefix] if prefix else b'') + spans[data]
return arrays, clips
class EncoderTests(unittest.TestCase):
def test_profitable_prefix_groups_regenerate_exact_phrases_for_both_board_policies(self):
with tempfile.TemporaryDirectory() as temporary:
directory = Path(temporary)
samples = self.fixtures(directory)
common = [((i*997)%18001)-9000 for i in range(257)]
for name, tail in (('advertQueued',[1000,-1000]), ('advertFailed',[-3000,3000]),
('soundOff',[2000,-2000]), ('alertsSoundOnly',[-4000,4000])):
samples[name] = common+tail*80
write_wav(directory/(name+'.wav'), samples[name])
source = directory/'shared.h'
self.assertEqual(self.generate(directory,source).returncode,0)
header = source.read_text()
self.assertIn('advertPrefixData',header)
self.assertIn('soundPrefixData',header)
# Compare to the original independently encoded fixture bytes.
spec = importlib.util.spec_from_file_location(
'fixture_gps_encoder',ROOT/'scripts/generate_gps_voice.py')
encoder = importlib.util.module_from_spec(spec)
spec.loader.exec_module(encoder)
for vibration in (False,True):
arrays, clips = parse_header(header,vibration)
names = CATALOG if vibration else CATALOG[:10]
for name,_ in names:
self.assertEqual(arrays[name],encoder.encode(samples[name]))
self.assertEqual(clips[name],(name,name,len(samples[name]),8000))
self.assertEqual('soundPrefixData' in select_vibration(header,vibration),vibration)
def fixtures(self, directory):
samples = {}
for index, (name, _) in enumerate(CATALOG):
@@ -175,12 +223,12 @@ class EncoderTests(unittest.TestCase):
self.assertEqual(clips[name], (name, name, samples, 8000))
def assert_x1_only_recordings_are_guarded(self, source):
start = source.index('#ifdef HAS_DRV2605')
end = source.index('#endif', start)
self.assertLess(source.index('restartingClip'), start)
for name, _ in CATALOG[10:]:
self.assertLess(start, source.index(name+'Data'))
self.assertLess(source.index(name+'Clip'), end)
arrays, clips = parse_header(source, has_drv2605=False)
self.assertEqual(tuple(arrays), tuple(name for name, _ in CATALOG[:10]))
self.assertEqual(tuple(clips), tuple(arrays))
# The optional shared Sound prefix must not add storage on boards
# which have no sound-only alert-mode announcement.
self.assertNotIn('soundPrefixData', select_vibration(source, False))
def test_changed_source_wav_replaces_only_its_matching_clip(self):
with tempfile.TemporaryDirectory() as temporary:
+34 -1
View File
@@ -17,7 +17,8 @@ ROOT = Path(__file__).resolve().parents[1]
CC = shutil.which('gcc')
CXX = shutil.which('g++')
DECODER = ROOT / 'src/helpers/ui/g726/g726_decoder.c'
SANITIZERS = (['-fsanitize=address,undefined', '-fno-omit-frame-pointer']
SANITIZERS = (['-fsanitize=address,undefined', '-fno-sanitize-recover=all',
'-fno-omit-frame-pointer', '-fno-pie', '-no-pie']
if os.environ.get('MESH_G726_TEST_SANITIZERS') == '1' else [])
# FFmpeg 7.1 g726 encoder/decoder, 8 kHz mono, continuous MSB-first packing.
@@ -230,6 +231,38 @@ class Tests(unittest.TestCase):
for bitrate in VECTORS:
with self.subTest(bitrate=bitrate):self.build(self.fixture(PLAYER_MAIN,bitrate),bitrate)
def test_segmented_stream_preserves_cross_byte_codes_adaptive_state_and_bounds(self):
source = DECODE_MAIN.split('int main(){', 1)[1].split(' assert(full.valid()', 1)[0]
source = 'int main(){' + source + r'''
for(size_t split=0;split<sizeof(data);++split){
VoiceClip clip{data+split,sizeof(data)-split,count,8000,codec,data,split};
assert(clip.valid());VoiceDecoder decoder;decoder.begin(clip);
for(unsigned i=0;i<count;++i){assert(!decoder.done());assert(decoder.next()==expected[i]);}
assert(decoder.done()&&decoder.position()==count);
for(size_t tail=0;tail<sizeof(data)-split;++tail){
std::vector<uint8_t> prefix(data,data+split),suffix(data+split,data+split+tail);
// A non-null placeholder is legal when the suffix's declared length is
// zero. ASan bounds the actual prefix/tail allocation independently.
VoiceClip short_clip{tail?suffix.data():data,tail,count,8000,codec,
split?prefix.data():nullptr,split};
assert(!short_clip.valid());decoder.begin(short_clip);
const unsigned available=static_cast<unsigned>((split+tail)*8/bits);
for(unsigned i=0;i<available;++i){assert(!decoder.done());assert(decoder.next()==expected[i]);}
assert(decoder.done()&&decoder.position()==available&&decoder.next()==0);
}
}
VoiceClip missing_prefix{data,sizeof(data),count,8000,codec,nullptr,1};
assert(!missing_prefix.valid());VoiceDecoder decoder;decoder.begin(missing_prefix);
assert(decoder.done()&&decoder.next()==0);
VoiceClip huge{data,std::numeric_limits<size_t>::max(),
std::numeric_limits<uint32_t>::max(),8000,codec,data,1};
assert(huge.valid()&&huge.hasBytes(std::numeric_limits<size_t>::max()));
GpsVoicePlayer player;assert(!player.start(missing_prefix)&&!player.active());
}
'''
for bitrate in VECTORS:
with self.subTest(bitrate=bitrate):self.build(self.fixture(source,bitrate),bitrate)
def test_invalid_bitrates_or_missing_laboratory_gate_cannot_compile(self):
for flags,error in (
('#define MESH_GPS_VOICE_G726_BITRATE 16\n','opt-in MESH_BUTTON_AUDIO_HIL'),
+19 -20
View File
@@ -12,7 +12,7 @@ from types import SimpleNamespace
import unittest
from unittest import mock
from test_button_voice_generator import CATALOG, ZIRA_RECORDINGS
from test_button_voice_generator import CATALOG, ZIRA_RECORDINGS, select_vibration
from test_gps_voice_generator import ZIRA_GPS_RECORDINGS
ROOT = Path(__file__).resolve().parents[1]
@@ -114,8 +114,9 @@ class CatalogTests(unittest.TestCase):
for name in names:
count=len(source_samples[name])
self.assertIn(f'{name}Data, sizeof({name}Data), {count}, 8000, VoiceCodec::G726{bitrate}',body)
self.assertEqual(body.count('#ifdef HAS_DRV2605'),4)
self.assertEqual(body.count('#endif // HAS_DRV2605'),4)
plain = select_vibration(body, False)
for name in names:
self.assertEqual(f'{name}Clip' in plain, not name.startswith('alerts'))
def test_invalid_source_lengths_preserve_existing_experimental_header(self):
for count in (0,32001):
@@ -143,30 +144,28 @@ class CatalogTests(unittest.TestCase):
with self.subTest(bitrate=bitrate):
body=source.split(f'#if MESH_GPS_VOICE_G726_BITRATE == {bitrate}\n',1)[1]
body=body.split(f'#endif // {bitrate} kbps',1)[0]
arrays={name:bytes(int(value,16) for value in re.findall(r'0x([0-9a-fA-F]{2})',data))
for name,data in re.findall(r'static const uint8_t (\w+)Data\[\] = \{(.*?)\};',body,re.S)}
clips={name:(data,size,int(count),int(rate),int(codec))
for name,data,size,count,rate,codec in re.findall(
selected = select_vibration(body)
spans={name:bytes(int(value,16) for value in re.findall(r'0x([0-9a-fA-F]{2})',data))
for name,data in re.findall(r'static const uint8_t (\w+)Data\[\] = \{(.*?)\};',selected,re.S)}
arrays, clips = {}, {}
for name,data,size,count,rate,codec,prefix,prefix_size in re.findall(
r'static constexpr VoiceClip (\w+)Clip = \{\s*'
r'(\w+)Data, sizeof\((\w+)Data\), (\d+), (\d+), VoiceCodec::G726(\d+)\s*\};',body)}
r'(\w+)Data, sizeof\((\w+)Data\), (\d+), (\d+), VoiceCodec::G726(\d+)'
r'(?:, (\w+)Data, sizeof\((\w+)Data\))?\s*\};',selected):
self.assertEqual(data,size)
self.assertEqual(prefix,prefix_size)
arrays[name]=(spans[prefix] if prefix else b'')+spans[data]
clips[name]=(data,size,int(count),int(rate),int(codec))
self.assertEqual(tuple(arrays),names)
self.assertEqual(tuple(clips),names)
for name in names:
self.assertEqual(clips[name],(name,name,samples[name],8000,bitrate))
self.assertEqual(len(arrays[name]),(samples[name]*(bitrate//8)+7)//8)
self.assertEqual(hashlib.sha256(arrays[name]).hexdigest(),GOLDENS[name][index],name)
# Each optional mode must be inside a HAS_DRV2605 guard; the
# common twelve clips must remain outside those guards.
guarded=False
for line in body.splitlines():
if line=='#ifdef HAS_DRV2605':
self.assertFalse(guarded);guarded=True
elif line=='#endif // HAS_DRV2605':
self.assertTrue(guarded);guarded=False
else:
match=re.match(r'static const uint8_t (\w+)Data',line)
if match:self.assertEqual(guarded,match[1].startswith('alerts'),match[1])
self.assertFalse(guarded)
plain=select_vibration(body,False)
self.assertNotIn('soundPrefixData',plain)
for name in names:
self.assertEqual(f'{name}Clip' in plain,not name.startswith('alerts'))
def test_cli_refuses_production_filenames_and_invalid_voice_without_touching_existing_files(self):
with tempfile.TemporaryDirectory() as temporary:
+1 -1
View File
@@ -451,7 +451,7 @@ class Tests(unittest.TestCase):
self.build(PLAYER_PDM)
source=PLAYER_PDM.replace('gpsOnClip', 'clip')
source=source.replace('GpsVoicePlayer player;',
'GpsVoicePlayer player; VoiceClip clip{gpsOnClip.data,gpsOnClip.bytes,gpsOnClip.samples,16000};')
'GpsVoicePlayer player; auto clip=gpsOnClip; clip.sample_rate=16000;')
source=source.replace('interpolator.begin();','interpolator.begin(clip.sample_rate);')
source=source.replace('clip.samples*8','clip.samples*4')
self.build(source)
+32
View File
@@ -7,6 +7,7 @@ import sys
import tempfile
import unittest
from test_button_voice_generator import parse_header, write_wav
import importlib.util
ROOT = Path(__file__).resolve().parents[1]
POWERSHELL = shutil.which("pwsh") or shutil.which("powershell")
@@ -33,6 +34,37 @@ ZIRA_GPS_RECORDINGS = {
class AssetTests(unittest.TestCase):
def test_generator_shares_only_profitable_exact_encoded_prefixes(self):
spec = importlib.util.spec_from_file_location('gps_encoder', ROOT/'scripts/generate_gps_voice.py')
encoder = importlib.util.module_from_spec(spec)
spec.loader.exec_module(encoder)
for rate in (8000, 16000):
with self.subTest(rate=rate), tempfile.TemporaryDirectory() as temporary:
directory = Path(temporary)
common = [((i*997)%18001)-9000 for i in range(257)]
phrases = {'gpsOn': common+[1000,-1000]*80,
'gpsOff': common+[-3000,3000]*77}
for name, samples in phrases.items():
write_wav(directory/(name+'.wav'), samples, rate=rate)
output = directory/'shared.h'
command = [sys.executable, str(ROOT/'scripts/generate_gps_voice.py'),
'--on', str(directory/'gpsOn.wav'), '--off', str(directory/'gpsOff.wav'),
'--rate', str(rate), '--output', str(output)]
subprocess.run(command, check=True, capture_output=True, timeout=30)
source = output.read_text()
self.assertEqual(source.count('static const uint8_t '), 3)
arrays, clips = parse_header(source)
for name, samples in phrases.items():
self.assertEqual(arrays[name], encoder.encode(samples))
self.assertEqual(clips[name], (name,name,len(samples),rate))
# A completely different second phrase must not add a useless
# shared/empty blob merely to keep the nominal count at three.
write_wav(directory/'gpsOff.wav', [3000,-3000]*80, rate=rate)
subprocess.run(command, check=True, capture_output=True, timeout=30)
source = output.read_text()
self.assertEqual(source.count('static const uint8_t '), 2)
self.assertNotIn('gpsData', source)
def test_production_gps_recordings_are_the_approved_zira_audio_and_metadata(self):
source = (ROOT / 'src/helpers/ui/GpsVoiceData.h').read_text(encoding='utf-8')
self.assertEqual([line for line in source.splitlines() if line.startswith('// Voice:')],
+145
View File
@@ -0,0 +1,145 @@
"""Preserve original speech PCM/DMA output while sharing encoded flash spans."""
from pathlib import Path
import hashlib
import os
import re
import shutil
import subprocess
import tempfile
import unittest
import test_gps_voice_buttons as shared
from test_button_voice_generator import select_vibration
ROOT = Path(__file__).resolve().parents[1]
CC, CXX = shutil.which('gcc'), shutil.which('g++')
SANITIZERS = (['-fsanitize=address,undefined', '-fno-sanitize-recover=all',
'-fno-omit-frame-pointer', '-fno-pie', '-no-pie']
if os.environ.get('MESH_VOICE_TEST_SANITIZERS') == '1' else [])
# Pinned from the original contiguous approved catalogs and the original
# decoder/player before any sharing changes. PCM is s16le; DMA words are u16le
# at gain 2. These checks include every clip, final padding, IRQ refill and
# interpolation tail, not hashes derived from the implementation under test.
ORIGINAL_OUTPUT = {
(0, False): ('481630ba68b1a29d5148fb1b552ca9bb78477ddc576bf9bad6d124d193dc994c',
'7438e1cc35538956c8959adf9056aeffbd77c20e4fbd23b1204c3c17ab15a9c0'),
(0, True): ('4e7ba541d3909bf8bbc4ae94c220f3a3d6a2304efaf53e3335a0844574475e87',
'6b0419e83310946342b136661460eed5795fa92b23eb9da6f43056d840d0c025'),
(16, False): ('f61a12157647ba21320ec65fb7a4bdcee260703ca593b57e5770af45076c7972',
'f8bdd21166fe8606622abfb8cff27136612e65e61e9f18d02e95c41e62897196'),
(16, True): ('86e13b896a20c5c77ca9dae9d71c52a1da78f7603a8c8e260a6c6c74d51567d6',
'10fb34b5487048a738a75c2bd4388c4ff370cbaebb54ed3a52006738dd86f425'),
(24, False): ('e37d6bbd0c7f9ddd0018c7f50d3929b1deef27b405a7432c492b2fb67c46e11b',
'2a7815a6ea01302b0cfe2d2bb3d6a57adeeec155ba70ac8aa9e1e3965d8d54be'),
(24, True): ('59167abf199c939cc62b3b03d9dbfcffd973c495104f89de29a6dd852100cdf2',
'2915281db72ebe103e7599a797f0413eb7245346ba55879c4a0a9ca1f61be293'),
}
@unittest.skipUnless(CC and CXX, 'Native C/C++ compilers are required')
class Tests(unittest.TestCase):
def test_every_original_phrase_and_full_dma_stream_remains_bit_identical(self):
with tempfile.TemporaryDirectory() as temporary:
directory = Path(temporary)
(directory/'Arduino.h').write_text(shared.ARDUINO)
(directory/'HardwarePWM.h').write_text(shared.PWM)
for (rate, vibration), fingerprints in ORIGINAL_OUTPUT.items():
flags = ['-DMESH_BUTTON_AUDIO_HIL=1']
if rate:
flags.append(f'-DMESH_GPS_VOICE_G726_BITRATE={rate}')
if vibration:
flags.append('-DHAS_DRV2605=1')
objects = []
if rate:
obj = directory/'g726.o'
subprocess.run([CC, '-std=c99', '-Wall', '-Wextra', '-Werror',
*SANITIZERS, *flags, '-I'+str(ROOT/'src'), '-c',
str(ROOT/'src/helpers/ui/g726/g726_decoder.c'),
'-o', str(obj)], check=True, timeout=30)
objects.append(str(obj))
for pwm, fingerprint in enumerate(fingerprints):
with self.subTest(rate=rate, vibration=vibration, pwm=bool(pwm)):
binary = directory/'catalog'
subprocess.run([CXX, '-std=c++17', '-Wall', '-Wextra', '-Werror',
*SANITIZERS, *flags,
*(['-DPWM_OUTPUT=1'] if pwm else []),
'-I'+str(directory), '-I'+str(ROOT/'src'),
str(ROOT/'test/fixtures/voice_prefixes/catalog.cpp'),
*objects, '-o', str(binary)], check=True, timeout=30)
result = subprocess.run([str(binary)], check=True,
capture_output=True, timeout=30)
self.assertEqual(hashlib.sha256(result.stdout).hexdigest(), fingerprint)
def test_ima_seams_odd_lengths_truncation_and_invalid_metadata(self):
source = r'''
#include <cassert>
#include <initializer_list>
#include <limits>
#include <vector>
#include <helpers/ui/GpsVoice.h>
int main(){
using namespace mesh::audio;
const uint8_t encoded[]={0x77,0x1f,0xca,0x26,0x84,0x92,0xbb};
for(unsigned samples:{1u,2u,3u,7u,13u,14u}){
VoiceDecoder original;original.begin({encoded,sizeof(encoded),samples});
std::vector<int16_t> expected;
while(!original.done())expected.push_back(original.next());
for(size_t split=0;split<sizeof(encoded);++split){
VoiceClip clip{encoded+split,sizeof(encoded)-split,samples,8000,encoded,split};
assert(clip.valid());VoiceDecoder decoder;decoder.begin(clip);
for(auto value:expected){assert(!decoder.done());assert(decoder.next()==value);}
assert(decoder.done()&&decoder.position()==samples&&decoder.next()==0);
for(size_t tail=0;tail<sizeof(encoded)-split;++tail){
std::vector<uint8_t> prefix(encoded,encoded+split),suffix(encoded+split,encoded+split+tail);
VoiceClip short_clip{tail?suffix.data():encoded,tail,samples,8000,
split?prefix.data():nullptr,split};
decoder.begin(short_clip);
unsigned count=0;
while(!decoder.done()){assert(decoder.next()==expected[count++]);}
assert(count==((split+tail)*2<samples?(split+tail)*2:samples));
assert(short_clip.valid()==(count==samples));
}
}
}
VoiceClip bad{encoded,sizeof(encoded),14,8000,nullptr,1};
assert(!bad.valid());VoiceDecoder decoder;decoder.begin(bad);
assert(decoder.done()&&decoder.next()==0);
VoiceClip huge{encoded,std::numeric_limits<size_t>::max(),
std::numeric_limits<uint32_t>::max(),8000,encoded,1};
assert(huge.valid()&&huge.hasBytes(std::numeric_limits<size_t>::max()));
}
'''
with tempfile.TemporaryDirectory() as temporary:
directory = Path(temporary)
path, binary = directory/'seams.cpp', directory/'seams'
path.write_text(source)
subprocess.run([CXX, '-std=c++17', '-Wall', '-Wextra', '-Werror',
*SANITIZERS, '-I'+str(ROOT/'src'), str(path), '-o', str(binary)],
check=True, timeout=30)
subprocess.run([str(binary)], check=True, timeout=30)
def test_only_profitable_shared_spans_are_stored_and_optional_sound_is_gated(self):
expected = {0: (3135, 746, 1090), 16: (1535, 268, 545), 24: (1967, 427, 817)}
for rate, sizes in expected.items():
if rate:
source = (ROOT/'src/helpers/ui/G726VoiceData.h').read_text()
source = source.split(f'#if MESH_GPS_VOICE_G726_BITRATE == {rate}\n')[1]
source = source.split(f'#endif // {rate} kbps')[0]
else:
source = ((ROOT/'src/helpers/ui/GpsVoiceData.h').read_text() + '\n'
+ (ROOT/'src/helpers/ui/ButtonVoiceData.h').read_text())
for vibration in (False, True):
body = select_vibration(source, vibration)
arrays = {name:bytes.fromhex(' '.join(re.findall(r'0x([0-9a-f]{2})',data)))
for name,data in re.findall(r'static const uint8_t (\w+)Data\[\] = \{(.*?)\};',body,re.S)}
self.assertEqual(len(arrays['gps']), sizes[0])
self.assertEqual(len(arrays['advertPrefix']), sizes[1])
self.assertEqual('soundPrefix' in arrays, vibration)
if vibration:
self.assertEqual(len(arrays['soundPrefix']), sizes[2])
self.assertEqual(sum(name.startswith('gps') for name in arrays), 3)
if __name__ == '__main__':
unittest.main()