tinykw 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tinykw/__init__.py +17 -0
- tinykw/libs/linux-arm64/libtinykw.so +0 -0
- tinykw/libs/linux-x86_64/libtinykw.so +0 -0
- tinykw/libs/macos-arm64/libtinykw.dylib +0 -0
- tinykw/libs/macos-x86_64/libtinykw.dylib +0 -0
- tinykw/libs/windows-arm64/libtinykw.dll +0 -0
- tinykw/libs/windows-x86_64/libtinykw.dll +0 -0
- tinykw/wrapper.py +160 -0
- tinykw-1.0.0.dist-info/METADATA +43 -0
- tinykw-1.0.0.dist-info/RECORD +13 -0
- tinykw-1.0.0.dist-info/WHEEL +5 -0
- tinykw-1.0.0.dist-info/licenses/LICENSE +28 -0
- tinykw-1.0.0.dist-info/top_level.txt +1 -0
tinykw/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .wrapper import (
|
|
2
|
+
tkw_init,
|
|
3
|
+
tkw_add_keyword,
|
|
4
|
+
tkw_process_frame,
|
|
5
|
+
tkw_is_keyword_detected,
|
|
6
|
+
tkw_clear_keyword_flag,
|
|
7
|
+
AUDIO_FRAME_SIZE,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"tkw_init",
|
|
12
|
+
"tkw_add_keyword",
|
|
13
|
+
"tkw_process_frame",
|
|
14
|
+
"tkw_is_keyword_detected",
|
|
15
|
+
"tkw_clear_keyword_flag",
|
|
16
|
+
"AUDIO_FRAME_SIZE"
|
|
17
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
tinykw/wrapper.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
import platform
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
def _load_library():
|
|
6
|
+
system = platform.system()
|
|
7
|
+
machine = platform.machine()
|
|
8
|
+
arch = {
|
|
9
|
+
"x86_64": "x86_64",
|
|
10
|
+
"AMD64": "x86_64",
|
|
11
|
+
"arm64": "arm64",
|
|
12
|
+
"aarch64": "arm64",
|
|
13
|
+
}.get(machine)
|
|
14
|
+
|
|
15
|
+
if arch is None:
|
|
16
|
+
raise OSError(f"Unsupported architecture: {machine}")
|
|
17
|
+
|
|
18
|
+
lib_dir = Path(__file__).parent / "libs"
|
|
19
|
+
if system == "Darwin":
|
|
20
|
+
lib_path = lib_dir / f"macos-{arch}" / "libtinykw.dylib"
|
|
21
|
+
elif system == "Linux":
|
|
22
|
+
lib_path = lib_dir / f"linux-{arch}" / "libtinykw.so"
|
|
23
|
+
elif system == "Windows":
|
|
24
|
+
lib_path = lib_dir / f"windows-{arch}" / "libtinykw.dll"
|
|
25
|
+
else:
|
|
26
|
+
raise OSError(f"Unsupported platform: {system}")
|
|
27
|
+
|
|
28
|
+
if not lib_path.exists():
|
|
29
|
+
raise OSError(f"No native library found for {system}/{arch}.")
|
|
30
|
+
|
|
31
|
+
return ctypes.CDLL(str(lib_path))
|
|
32
|
+
|
|
33
|
+
_lib = _load_library()
|
|
34
|
+
|
|
35
|
+
_lib.tkw_init.restype = ctypes.c_int32
|
|
36
|
+
_lib.tkw_init.argtypes = []
|
|
37
|
+
|
|
38
|
+
_lib.tkw_add_keyword.restype = ctypes.c_int32
|
|
39
|
+
_lib.tkw_add_keyword.argtypes = [ctypes.POINTER(ctypes.c_uint8),
|
|
40
|
+
ctypes.c_uint8,
|
|
41
|
+
ctypes.POINTER(ctypes.c_uint32)]
|
|
42
|
+
|
|
43
|
+
_lib.tkw_process_frame.restype = ctypes.c_int32
|
|
44
|
+
_lib.tkw_process_frame.argtypes = [ctypes.POINTER(ctypes.c_int16),
|
|
45
|
+
ctypes.c_size_t]
|
|
46
|
+
|
|
47
|
+
_lib.tkw_is_keyword_detected.restype = ctypes.c_int32
|
|
48
|
+
_lib.tkw_is_keyword_detected.argtypes = [ctypes.c_uint32,
|
|
49
|
+
ctypes.POINTER(ctypes.c_uint32)]
|
|
50
|
+
|
|
51
|
+
_lib.tkw_clear_keyword_flag.restype = ctypes.c_int32
|
|
52
|
+
_lib.tkw_clear_keyword_flag.argtypes = [ctypes.c_uint32]
|
|
53
|
+
|
|
54
|
+
_lib.tkw_status_string.restype = ctypes.c_char_p
|
|
55
|
+
_lib.tkw_status_string.argtypes = [ctypes.c_int32]
|
|
56
|
+
|
|
57
|
+
AUDIO_FRAME_SIZE = 480
|
|
58
|
+
|
|
59
|
+
class tkwError(RuntimeError):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
def _check_status(status: int):
|
|
63
|
+
if status != 0:
|
|
64
|
+
msg = _lib.tkw_status_string(status)
|
|
65
|
+
msg = msg.decode() if msg else f"Error code {status}"
|
|
66
|
+
raise tkwError(msg)
|
|
67
|
+
|
|
68
|
+
def tkw_init() -> None:
|
|
69
|
+
"""
|
|
70
|
+
Initialize or reset the library.
|
|
71
|
+
|
|
72
|
+
This should be called before any other API function. Calling tkw_init() multiple times is allowed
|
|
73
|
+
and resets all internal state, including registered keywords and detection flags.
|
|
74
|
+
"""
|
|
75
|
+
status = _lib.tkw_init()
|
|
76
|
+
_check_status(status)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def tkw_add_keyword(data: list[int] | bytes, detection_threshold: float = 0.5) -> int:
|
|
80
|
+
"""
|
|
81
|
+
Adds a keyword to the engine.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
data: 48 bytes keyword encoding (can be obtained from https://voyelle.io/tinykw).
|
|
85
|
+
detection_threshold: value between 0 and 1.
|
|
86
|
+
Controls how strict the engine is when deciding whether a detection is valid.
|
|
87
|
+
A high value makes the engine more conservative: only high-scoring detections are accepted (fewer false positives, but more missed detections).
|
|
88
|
+
A low value makes the engine more permissive: more detections are accepted (fewer misses, but more false positives).
|
|
89
|
+
This parameter may need tuning depending on the keyword, use case and environment. A good starting point is to set it mid-range at 0.5, and
|
|
90
|
+
then eventually adjust it based on your tolerance for false positives vs missed detections.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
id: the assigned keyword ID
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
data = [0x3f, 0x12, 0x00, ...] # 48 bytes
|
|
97
|
+
id = tkw_add_keyword(data, 0.75)
|
|
98
|
+
"""
|
|
99
|
+
if isinstance(data, bytes):
|
|
100
|
+
data = list(data)
|
|
101
|
+
if len(data) != 48:
|
|
102
|
+
raise ValueError(f"data must be exactly 48 bytes, got {len(data)}")
|
|
103
|
+
if not (0.0 <= detection_threshold <= 1.0):
|
|
104
|
+
raise ValueError("detection_threshold must be in [0.0, 1.0]")
|
|
105
|
+
detection_threshold = max(0, min(255, round(detection_threshold * 256)))
|
|
106
|
+
c_data = (ctypes.c_uint8 * 48)(*data)
|
|
107
|
+
out_id = ctypes.c_uint32()
|
|
108
|
+
status = _lib.tkw_add_keyword(c_data,
|
|
109
|
+
ctypes.c_uint8(detection_threshold),
|
|
110
|
+
ctypes.byref(out_id))
|
|
111
|
+
_check_status(status)
|
|
112
|
+
return out_id.value
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def tkw_process_frame(waveform) -> None:
|
|
116
|
+
"""
|
|
117
|
+
Processes one frame of 16-bit PCM audio sampled at 16 kHz.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
waveform: int16 numpy array or list of length tinykw.AUDIO_FRAME_SIZE.
|
|
121
|
+
"""
|
|
122
|
+
if len(waveform) != AUDIO_FRAME_SIZE:
|
|
123
|
+
raise ValueError(
|
|
124
|
+
f"waveform must have length {AUDIO_FRAME_SIZE}, got {len(waveform)}"
|
|
125
|
+
)
|
|
126
|
+
if hasattr(waveform, "ctypes"):
|
|
127
|
+
ptr = waveform.ctypes.data_as(ctypes.POINTER(ctypes.c_int16))
|
|
128
|
+
else:
|
|
129
|
+
arr = (ctypes.c_int16 * len(waveform))(*waveform)
|
|
130
|
+
ptr = arr
|
|
131
|
+
status = _lib.tkw_process_frame(ptr, len(waveform))
|
|
132
|
+
_check_status(status)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def tkw_is_keyword_detected(keyword_id: int) -> bool:
|
|
136
|
+
"""
|
|
137
|
+
Reads the detection flag for a specific keyword.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
id: keyword assigned ID.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
True if detected, False otherwise.
|
|
144
|
+
Detection remains set until cleared with tkw_clear_keyword_flag().
|
|
145
|
+
"""
|
|
146
|
+
out = ctypes.c_uint32()
|
|
147
|
+
status = _lib.tkw_is_keyword_detected(ctypes.c_uint32(keyword_id), ctypes.byref(out))
|
|
148
|
+
_check_status(status)
|
|
149
|
+
return bool(out.value)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def tkw_clear_keyword_flag(keyword_id: int) -> None:
|
|
153
|
+
"""
|
|
154
|
+
Clear detection flag.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
id: keyword id
|
|
158
|
+
"""
|
|
159
|
+
status = _lib.tkw_clear_keyword_flag(ctypes.c_uint32(keyword_id))
|
|
160
|
+
_check_status(status)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tinykw
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python binding for TinyKW
|
|
5
|
+
Project-URL: Homepage, https://voyelle.io/tinykw
|
|
6
|
+
Project-URL: Repository, https://github.com/voyelle-io/tinykw.git
|
|
7
|
+
Keywords: keyword spotting,wake word
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: numpy; extra == "dev"
|
|
13
|
+
Requires-Dist: scipy; extra == "dev"
|
|
14
|
+
Requires-Dist: pytest; extra == "dev"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
Python binding for TinyKW.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
`pip install tinykw`
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import tinykw
|
|
27
|
+
|
|
28
|
+
tinykw.tkw_init()
|
|
29
|
+
|
|
30
|
+
kw_id = tinykw.tkw_add_keyword(keyword_bytes, detection_threshold)
|
|
31
|
+
# ...
|
|
32
|
+
tinykw.tkw_process_frame(samples)
|
|
33
|
+
# ...
|
|
34
|
+
if tinykw.tkw_is_keyword_detected(kw_id):
|
|
35
|
+
tinykw.tkw_clear_keyword_flag(kw_id)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
See [examples/python](../examples/python/) for full usage.
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
TinyKW is free for non-commercial use, including research, education, prototyping, and evaluation.
|
|
43
|
+
Please see [LICENSE](LICENSE) for details. For commercial enquiries, please contact [contact@voyelle.io](mailto:contact@voyelle.io).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
tinykw/__init__.py,sha256=g0FqZFzbzrH9hxScfGhcbOxX0VdUCP-mvpYGd21Gdv0,324
|
|
2
|
+
tinykw/wrapper.py,sha256=j6nAJEXNsg3d5yWAHyleN2PiJ1szJkSQ89Geu_52NTg,5462
|
|
3
|
+
tinykw/libs/linux-arm64/libtinykw.so,sha256=LCaP360L8dQgtRaaKmvDgi6wplBUKUwnTWMPP9Fy9n8,317648
|
|
4
|
+
tinykw/libs/linux-x86_64/libtinykw.so,sha256=Nd__IvAJUGpRKksI215YOcuWFpXTZQCf4bPkQRp_QjQ,320960
|
|
5
|
+
tinykw/libs/macos-arm64/libtinykw.dylib,sha256=CHTl5oMmw_p6jMLc2r0pdfjGre9Z_1k2tQFCxIC_DiY,362192
|
|
6
|
+
tinykw/libs/macos-x86_64/libtinykw.dylib,sha256=fU7W6ZoK7jeWA9_UwHjBSb7HUaifDAPi2bWbKTRDHhw,332752
|
|
7
|
+
tinykw/libs/windows-arm64/libtinykw.dll,sha256=H6p-88X6cC3uAiL2w0fWGygzleoQopi5IpDbaIPlS1w,326144
|
|
8
|
+
tinykw/libs/windows-x86_64/libtinykw.dll,sha256=CcDElE8NXxw3YNedr6CjRirCXD8UDg3PpjqmNGk-LGA,333824
|
|
9
|
+
tinykw-1.0.0.dist-info/licenses/LICENSE,sha256=7TQoAJLKq41XnHkEUzq-U4nuBdjTxR6d-I67YwT9dcA,1499
|
|
10
|
+
tinykw-1.0.0.dist-info/METADATA,sha256=WXk9FZ9iGnUTcZ8l0AkHwAXUd9Bkv8yLe87uqBdKSdo,1105
|
|
11
|
+
tinykw-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
12
|
+
tinykw-1.0.0.dist-info/top_level.txt,sha256=5-pvJSN5r_PM_tAxf0v6m24ZAJlEufMinzHYJbjbLUQ,7
|
|
13
|
+
tinykw-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Copyright (c) 2026 TinyKW
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person or organization
|
|
4
|
+
obtaining a copy of this software and associated documentation files
|
|
5
|
+
(the "Software") to use, reproduce, and distribute the Software in binary form
|
|
6
|
+
for non-commercial purposes only, including internal evaluation,
|
|
7
|
+
research, prototyping, and educational use, provided that this license notice is
|
|
8
|
+
retained in all copies or distributions.
|
|
9
|
+
|
|
10
|
+
Commercial use of the Software is prohibited without a separate written
|
|
11
|
+
commercial license from the copyright holder. For purposes of this license,
|
|
12
|
+
"commercial use" means use of the Software in or for a product, service, or
|
|
13
|
+
activity that generates revenue or other commercial benefit, including use
|
|
14
|
+
in products or services that are sold, licensed, hosted, subscription-based,
|
|
15
|
+
or monetized in any form.
|
|
16
|
+
|
|
17
|
+
You may not:
|
|
18
|
+
- reverse engineer, decompile, or disassemble the Software except where
|
|
19
|
+
such restriction is prohibited by applicable law;
|
|
20
|
+
- remove or alter copyright or license notices.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL
|
|
25
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING
|
|
27
|
+
FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
28
|
+
DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tinykw
|