├── .gitignore ├── sphinx-models ├── respeaker │ ├── pocketsphinx-data │ │ ├── hmm │ │ │ ├── mdef │ │ │ ├── means │ │ │ ├── sendump │ │ │ ├── variances │ │ │ ├── transition_matrices │ │ │ ├── noisedict │ │ │ └── feat.params │ │ ├── keywords.txt │ │ └── dictionary.txt │ ├── tdt_sc_8k │ │ ├── mdef │ │ ├── means │ │ ├── sendump │ │ ├── variances │ │ ├── transition_matrices │ │ ├── noisedict │ │ └── feat.params │ ├── TAR0287 │ │ ├── 0287.vocab │ │ ├── 0287.dic │ │ ├── 0287.sent │ │ ├── 0287.log_pronounce │ │ └── 0287.lm │ ├── __init__.py │ ├── assistant.py │ ├── usb_hid │ │ ├── interface.py │ │ ├── __init__.py │ │ ├── hidapi_backend.py │ │ ├── pywinusb_backend.py │ │ └── pyusb_backend.py │ ├── spectrum_analyzer.py │ ├── vad.py │ ├── pixel_ring.py │ ├── fft.py │ ├── spi.py │ ├── player.py │ ├── gpio.py │ ├── bing_speech_api.py │ └── microphone.py ├── TAR0287.tgz ├── basic.txt ├── output.txt ├── genearate_keywords.py ├── new.dic ├── create_symbol.py └── chinese_dicit.py ├── requirements.txt ├── models ├── xiaobai.pmdl ├── close-light.pmdl └── open-light.pmdl ├── screenshots └── generate-sphinx-knowledge.png ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.pyc 3 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/pocketsphinx-data/hmm/mdef: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/pocketsphinx-data/hmm/means: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/pocketsphinx-data/hmm/sendump: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/pocketsphinx-data/hmm/variances: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/pocketsphinx-data/hmm/transition_matrices: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | jieba 2 | pocketsphinx 3 | pyaudio 4 | webrtcvad 5 | requests 6 | -------------------------------------------------------------------------------- /models/xiaobai.pmdl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/diy-private-smart-speaker/master/models/xiaobai.pmdl -------------------------------------------------------------------------------- /sphinx-models/respeaker/pocketsphinx-data/hmm/noisedict: -------------------------------------------------------------------------------- 1 | SIL 2 | SIL 3 | SIL 4 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/pocketsphinx-data/keywords.txt: -------------------------------------------------------------------------------- 1 | respeaker /1e-30/ 2 | alexa /1e-30/ 3 | play music /1e-40/ -------------------------------------------------------------------------------- /models/close-light.pmdl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/diy-private-smart-speaker/master/models/close-light.pmdl -------------------------------------------------------------------------------- /models/open-light.pmdl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/diy-private-smart-speaker/master/models/open-light.pmdl -------------------------------------------------------------------------------- /sphinx-models/TAR0287.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/diy-private-smart-speaker/master/sphinx-models/TAR0287.tgz -------------------------------------------------------------------------------- /sphinx-models/respeaker/pocketsphinx-data/dictionary.txt: -------------------------------------------------------------------------------- 1 | respeaker R IY S P IY K ER 2 | alexa AH L EH K S AH 3 | play P L EY 4 | music M Y UW Z IH K -------------------------------------------------------------------------------- /sphinx-models/respeaker/tdt_sc_8k/mdef: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/diy-private-smart-speaker/master/sphinx-models/respeaker/tdt_sc_8k/mdef -------------------------------------------------------------------------------- /sphinx-models/respeaker/tdt_sc_8k/means: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/diy-private-smart-speaker/master/sphinx-models/respeaker/tdt_sc_8k/means -------------------------------------------------------------------------------- /screenshots/generate-sphinx-knowledge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/diy-private-smart-speaker/master/screenshots/generate-sphinx-knowledge.png -------------------------------------------------------------------------------- /sphinx-models/respeaker/tdt_sc_8k/sendump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/diy-private-smart-speaker/master/sphinx-models/respeaker/tdt_sc_8k/sendump -------------------------------------------------------------------------------- /sphinx-models/respeaker/tdt_sc_8k/variances: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/diy-private-smart-speaker/master/sphinx-models/respeaker/tdt_sc_8k/variances -------------------------------------------------------------------------------- /sphinx-models/respeaker/tdt_sc_8k/transition_matrices: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/diy-private-smart-speaker/master/sphinx-models/respeaker/tdt_sc_8k/transition_matrices -------------------------------------------------------------------------------- /sphinx-models/respeaker/pocketsphinx-data/hmm/feat.params: -------------------------------------------------------------------------------- 1 | -nfilt 40 2 | -lowerf 133.3334 3 | -upperf 6855.4976 4 | -feat s2_4x 5 | -agc none 6 | -varnorm no 7 | -cmninit 8,0,0 8 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/tdt_sc_8k/noisedict: -------------------------------------------------------------------------------- 1 | SIL 2 | SIL 3 | SIL 4 | ++laugh++ +LAUGH+ 5 | ++lipsmack++ +LIPSMACK+ 6 | ++cough++ +COUGH+ 7 | ++breath++ +BREATHE+ 8 | -------------------------------------------------------------------------------- /sphinx-models/basic.txt: -------------------------------------------------------------------------------- 1 | 打开台灯 2 | 关闭台灯 3 | 打开电视 4 | 关闭电视 5 | 打开小米盒子 6 | 关闭小米盒子 7 | 打开客厅的空调 8 | 关闭客厅的空调 9 | 把客厅的空调设为25度 10 | 把客厅的空调调为25度 11 | 将客厅的空调设为25度 12 | 把卧室的空调设为25度 13 | 把空调设为25度 14 | 我要听音乐 15 | 今天的天气怎样? 16 | 今天需要带伞吗? 17 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/TAR0287/0287.vocab: -------------------------------------------------------------------------------- 1 | 25 2 | 今天 3 | 关闭 4 | 卧室 5 | 台灯 6 | 吗 7 | 听 8 | 天气 9 | 客厅 10 | 将 11 | 小米 12 | 带伞 13 | 度 14 | 怎样 15 | 我要 16 | 打开 17 | 把 18 | 电视 19 | 的 20 | 盒子 21 | 空调 22 | 设为 23 | 调为 24 | 需要 25 | 音乐 26 | ? 27 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/TAR0287/0287.dic: -------------------------------------------------------------------------------- 1 | 25 T UW F AY V 2 | 今天 3 | 关闭 4 | 卧室 5 | 台灯 6 | 吗 7 | 听 8 | 天气 9 | 客厅 10 | 将 11 | 小米 12 | 带伞 13 | 度 14 | 怎样 15 | 我要 16 | 打开 17 | 把 18 | 电视 19 | 的 20 | 盒子 21 | 空调 22 | 设为 23 | 调为 24 | 需要 25 | 音乐 26 | ? 27 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/tdt_sc_8k/feat.params: -------------------------------------------------------------------------------- 1 | -nfilt 20 2 | -lowerf 1 3 | -upperf 4000 4 | -wlen 0.025 5 | -transform dct 6 | -round_filters no 7 | -remove_dc yes 8 | -feat 1s_c_d_dd 9 | -svspec 0-12/13-25/26-38 10 | -agc none 11 | -cmn current 12 | -cmninit 54,-1,2 13 | -varnorm no 14 | -------------------------------------------------------------------------------- /sphinx-models/output.txt: -------------------------------------------------------------------------------- 1 | 打开 台灯 2 | 关闭 台灯 3 | 打开 电视 4 | 关闭 电视 5 | 打开 小米 盒子 6 | 关闭 小米 盒子 7 | 打开 客厅 的 空调 8 | 关闭 客厅 的 空调 9 | 把 客厅 的 空调 设为 25 度 10 | 把 客厅 的 空调 调为 25 度 11 | 将 客厅 的 空调 设为 25 度 12 | 把 卧室 的 空调 设为 25 度 13 | 把 空调 设为 25 度 14 | 我要 听 音乐 15 | 今天 的 天气 怎样 ? 16 | 今天 需要 带伞 吗 ? 17 | -------------------------------------------------------------------------------- /sphinx-models/genearate_keywords.py: -------------------------------------------------------------------------------- 1 | import jieba 2 | 3 | output = open('output.txt', 'w') 4 | with open("basic.txt") as f: 5 | for line in f: 6 | seg_list = jieba.cut(line) 7 | seg_list_with_split = " ".join(seg_list) 8 | output.write(seg_list_with_split) 9 | 10 | output.close() 11 | -------------------------------------------------------------------------------- /sphinx-models/new.dic: -------------------------------------------------------------------------------- 1 | 今天 j in t ian 2 | 关闭 g uan b i 3 | 卧室 w o sh ib 4 | 台灯 t ai d eng 5 | 吗 m a 6 | 听 t ing 7 | 天气 t ian q i 8 | 客厅 k e t ing 9 | 将 j iang 10 | 小米 x iao m i 11 | 度 d u 12 | 怎样 z en y ang 13 | 我要 w o y ao 14 | 打开 d a k ai 15 | 把 b a 16 | 电视 d ian sh ib 17 | 的 d e 18 | 盒子 h e z if 19 | 空调 k ong t iao 20 | 需要 x ux y ao 21 | 音乐 y in uxs uxe 22 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/TAR0287/0287.sent: -------------------------------------------------------------------------------- 1 | 打开 台灯 2 | 关闭 台灯 3 | 打开 电视 4 | 关闭 电视 5 | 打开 小米 盒子 6 | 关闭 小米 盒子 7 | 打开 客厅 的 空调 8 | 关闭 客厅 的 空调 9 | 把 客厅 的 空调 设为 25 度 10 | 把 客厅 的 空调 调为 25 度 11 | 将 客厅 的 空调 设为 25 度 12 | 把 卧室 的 空调 设为 25 度 13 | 把 空调 设为 25 度 14 | 我要 听 音乐 15 | 今天 的 天气 怎样 ? 16 | 今天 需要 带伞 吗 ? 17 | -------------------------------------------------------------------------------- /sphinx-models/create_symbol.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | new_dict = open('new.dic', 'w', encoding='UTF-8') 5 | origin_dicts = open('TAR0287/0287.dic', encoding='UTF-8') 6 | with origin_dicts as origin_file: 7 | print(origin_file) 8 | for origin_dict in origin_file: 9 | origin_key = origin_dict.split("\t")[0] 10 | with open("zh_broadcastnews_utf8.dic", encoding='UTF-8') as f: 11 | for line in f: 12 | split = line.split(" ") 13 | if len(split) >= 2: 14 | key = split[0] 15 | value = split[1] 16 | if key == origin_key: 17 | new_line = origin_key + "\t" + line[len(key) + len(" "):] 18 | new_dict.write(new_line) 19 | 20 | origin_dicts.close() 21 | new_dict.close() 22 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | ReSpeaker Python Library 3 | Copyright (c) 2016 Seeed Technology Limited. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | from microphone import Microphone 19 | from spi import SPI, spi 20 | from player import Player 21 | from pixel_ring import PixelRing, pixel_ring 22 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/TAR0287/0287.log_pronounce: -------------------------------------------------------------------------------- 1 | I think this is a non-word: 25 2 | pronounce: verbosity is 1 3 | TWO - Morpheme: TWO 4 | FIVE - Morpheme: FIVE 5 | I think this is a non-word: 今天 6 | I think this is a non-word: 关闭 7 | I think this is a non-word: 卧室 8 | I think this is a non-word: 台灯 9 | I think this is a non-word: 吗 10 | I think this is a non-word: 听 11 | I think this is a non-word: 天气 12 | I think this is a non-word: 客厅 13 | I think this is a non-word: 将 14 | I think this is a non-word: 小米 15 | I think this is a non-word: 带伞 16 | I think this is a non-word: 度 17 | I think this is a non-word: 怎样 18 | I think this is a non-word: 我要 19 | I think this is a non-word: 打开 20 | I think this is a non-word: 把 21 | I think this is a non-word: 电视 22 | I think this is a non-word: 的 23 | I think this is a non-word: 盒子 24 | I think this is a non-word: 空调 25 | I think this is a non-word: 设为 26 | I think this is a non-word: 调为 27 | I think this is a non-word: 需要 28 | I think this is a non-word: 音乐 29 | I think this is a non-word: ? 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Phodal Huang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # how-to-diy-smart-speaker 2 | 3 | How to DIY a Chinese Smart Speaker 4 | 5 | Sphinx 生成 6 | --- 7 | 8 | 打开 [http://www.speech.cs.cmu.edu/tools/lmtool-new.html](http://www.speech.cs.cmu.edu/tools/lmtool-new.html) 9 | 10 | 1. 点击 Choose File 上传文件 11 | 2. 点击 COMPILE KNOWLEDGE BASE 进行转换 12 | 3. 打开 [http://www.speech.cs.cmu.edu/tools/product/1503889661_19829/](http://www.speech.cs.cmu.edu/tools/product/1503889661_19829/) 下载 tgz 文件 13 | 14 | 15 | ```` 16 | JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock 17 | JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock 18 | INFO:mic:Use ReSpeaker MicArray UAC2.0: USB Audio (hw:2,0) 19 | 20 | INFO:mic:Start detecting 21 | INFO:mic:Detected 空调 22 | INFO:mic:Detected 空调 23 | INFO:mic:Detected 度 24 | INFO:mic:Detected 今天 25 | INFO:mic:Detected 今天 26 | INFO:mic:Detected 空调 27 | ``` 28 | 29 | 下载的 txt 需要生成模型 30 | 31 | 执行: 32 | 33 | ``` 34 | cd sphinx-models 35 | python create_symbol.py 36 | ``` 37 | 38 | ### Mac OS 39 | 40 | ``` 41 | brew install portaudio hidapi 42 | ``` 43 | 44 | 45 | ``` 46 | python assistant.py  22:48:35 47 | ERROR:root:cython-hidapi is required on a Mac OS X Machine 48 | ``` 49 | 50 | 51 | ``` 52 | pip install cython 53 | git clone https://github.com/gbishop/cython-hidapi.git 54 | python setup.py install 55 | ``` 56 | 57 | Text Generate ? 58 | --- 59 | 60 | 61 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/assistant.py: -------------------------------------------------------------------------------- 1 | """ 2 | ReSpeaker Python Library 3 | Copyright (c) 2016 Seeed Technology Limited. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | """ 14 | 15 | import logging 16 | import time 17 | from threading import Thread, Event 18 | 19 | from microphone import Microphone 20 | 21 | def task(quit_event): 22 | mic = Microphone(quit_event=quit_event) 23 | 24 | while not quit_event.is_set(): 25 | if mic.wakeup('respeaker'): 26 | print('Wake up') 27 | data = mic.listen() 28 | text = mic.recognize(data) 29 | if text: 30 | print('Recognized %s' % text) 31 | 32 | 33 | def main(): 34 | logging.basicConfig(level=logging.DEBUG) 35 | 36 | quit_event = Event() 37 | thread = Thread(target=task, args=(quit_event,)) 38 | thread.start() 39 | while True: 40 | try: 41 | time.sleep(1) 42 | except KeyboardInterrupt: 43 | print('Quit') 44 | quit_event.set() 45 | break 46 | thread.join() 47 | 48 | if __name__ == '__main__': 49 | main() -------------------------------------------------------------------------------- /sphinx-models/respeaker/usb_hid/interface.py: -------------------------------------------------------------------------------- 1 | """ 2 | USB HID API from pyOCD project 3 | Copyright (c) 2006-2013 ARM Limited 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | 19 | class Interface(object): 20 | 21 | def __init__(self): 22 | self.vid = 0 23 | self.pid = 0 24 | self.vendor_name = "" 25 | self.product_name = "" 26 | self.packet_count = 1 27 | return 28 | 29 | def init(self): 30 | return 31 | 32 | def write(self, data): 33 | return 34 | 35 | def read(self, size=-1, timeout=-1): 36 | return 37 | 38 | def getInfo(self): 39 | return self.vendor_name + " " + \ 40 | self.product_name + " (" + \ 41 | str(hex(self.vid)) + ", " + \ 42 | str(hex(self.pid)) + ")" 43 | 44 | def setPacketCount(self, count): 45 | # Unless overridden the packet count cannot be changed 46 | return 47 | 48 | def getPacketCount(self): 49 | return self.packet_count 50 | 51 | def close(self): 52 | return 53 | -------------------------------------------------------------------------------- /sphinx-models/chinese_dicit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # author: binux(17175297.hk@gmail.com) 4 | # https://github.com/binux/binux-tools/blob/master/python/chinese_digit.py 5 | 6 | dict ={u'零':0, u'一':1, u'二':2, u'三':3, u'四':4, u'五':5, u'六':6, u'七':7, u'八':8, u'九':9, u'十':10, u'百':100, u'千':1000, u'万':10000, 7 | u'0':0, u'1':1, u'2':2, u'3':3, u'4':4, u'5':5, u'6':6, u'7':7, u'8':8, u'9':9, 8 | u'壹':1, u'贰':2, u'叁':3, u'肆':4, u'伍':5, u'陆':6, u'柒':7, u'捌':8, u'玖':9, u'拾':10, u'佰':100, u'仟':1000, u'萬':10000, 9 | u'亿':100000000} 10 | 11 | def getResultForDigit(a, encoding="utf-8"): 12 | if isinstance(a, str): 13 | a = a.decode(encoding) 14 | 15 | count = 0 16 | result = 0 17 | tmp = 0 18 | Billion = 0 19 | while count < len(a): 20 | tmpChr = a[count] 21 | #print tmpChr 22 | tmpNum = dict.get(tmpChr, None) 23 | #如果等于1亿 24 | if tmpNum == 100000000: 25 | result = result + tmp 26 | result = result * tmpNum 27 | #获得亿以上的数量,将其保存在中间变量Billion中并清空result 28 | Billion = Billion * 100000000 + result 29 | result = 0 30 | tmp = 0 31 | #如果等于1万 32 | elif tmpNum == 10000: 33 | result = result + tmp 34 | result = result * tmpNum 35 | tmp = 0 36 | #如果等于十或者百,千 37 | elif tmpNum >= 10: 38 | if tmp == 0: 39 | tmp = 1 40 | result = result + tmpNum * tmp 41 | tmp = 0 42 | #如果是个位数 43 | elif tmpNum is not None: 44 | tmp = tmp * 10 + tmpNum 45 | count += 1 46 | result = result + tmp 47 | result = result + Billion 48 | return result -------------------------------------------------------------------------------- /sphinx-models/respeaker/usb_hid/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | USB HID API from pyOCD project 3 | Copyright (c) 2006-2013 ARM Limited 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | import os 19 | import logging 20 | from usb_hid.hidapi_backend import HidApiUSB 21 | from usb_hid.pyusb_backend import PyUSB 22 | from usb_hid.pywinusb_backend import PyWinUSB 23 | 24 | INTERFACE = { 25 | 'hidapiusb': HidApiUSB, 26 | 'pyusb': PyUSB, 27 | 'pywinusb': PyWinUSB, 28 | } 29 | 30 | # Allow user to override backend with an environment variable. 31 | usb_backend = os.getenv('PYOCD_USB_BACKEND', "") 32 | 33 | # Check validity of backend env var. 34 | if usb_backend and ((usb_backend not in INTERFACE.keys()) or (not INTERFACE[usb_backend].isAvailable)): 35 | logging.error("Invalid USB backend specified in PYOCD_USB_BACKEND: " + usb_backend) 36 | usb_backend = "" 37 | 38 | # Select backend based on OS and availability. 39 | if not usb_backend: 40 | if os.name == "nt": 41 | # Prefer hidapi over pyWinUSB for Windows, since pyWinUSB has known bug(s) 42 | if HidApiUSB.isAvailable: 43 | usb_backend = "hidapiusb" 44 | elif PyWinUSB.isAvailable: 45 | usb_backend = "pywinusb" 46 | else: 47 | raise Exception("No USB backend found") 48 | elif os.name == "posix": 49 | # Select hidapi for OS X and pyUSB for Linux. 50 | if os.uname()[0] == 'Darwin': 51 | usb_backend = "hidapiusb" 52 | else: 53 | usb_backend = "pyusb" 54 | else: 55 | raise Exception("No USB backend found") 56 | 57 | 58 | devices = None 59 | if INTERFACE[usb_backend].isAvailable: 60 | devices = INTERFACE[usb_backend].getAllConnectedInterface() 61 | 62 | 63 | def get(index=0): 64 | global devices 65 | 66 | if not devices: 67 | if INTERFACE[usb_backend].isAvailable: 68 | devices = INTERFACE[usb_backend].getAllConnectedInterface() 69 | if devices and len(devices) > index: 70 | return devices[index] 71 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/spectrum_analyzer.py: -------------------------------------------------------------------------------- 1 | """ 2 | ReSpeaker Python Library 3 | Copyright (c) 2016 Seeed Technology Limited. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | import array 19 | import math 20 | from fft import FFT 21 | 22 | 23 | class SpectrumAnalyzer: 24 | def __init__(self, size, sample_rate=16000, band_number=12, window=[50, 8000]): 25 | self.size = 1 << math.frexp(size - 1)[1] 26 | self.sample_rate = float(sample_rate) 27 | self.resolution = self.sample_rate / self.size # (sample_rate/2) / (band/2) 28 | 29 | self.set_band(band_number, window) 30 | 31 | self.fft = FFT(self.size) 32 | 33 | def set_band(self, n, window=[50, 8000]): 34 | self.band = n 35 | self.breakpoints = [0] * (n + 1) 36 | self.frequencies = [0.0] * (n + 1) 37 | self.strength = [0.0] * n 38 | 39 | delta = math.pow(float(window[1]) / window[0], 1.0 / n) 40 | for i in range(n + 1): 41 | self.frequencies[i] = math.pow(delta, i) * window[0] 42 | 43 | breakpoint = 0 44 | for i in range(1, self.size / 2): 45 | if self.resolution * i >= self.frequencies[breakpoint]: 46 | self.breakpoints[breakpoint] = i 47 | breakpoint += 1 48 | if breakpoint > n: 49 | break 50 | 51 | self.breakpoints[n] = self.size / 2 + 1 52 | self.band_size = [self.breakpoints[i + 1] - self.breakpoints[i] for i in range(n)] 53 | # print self.frequencies 54 | # print self.breakpoints 55 | 56 | def analyze(self, data): 57 | amplitude = self.fft.dft(data) 58 | for i in range(self.band): 59 | self.strength[i] = sum(amplitude[self.breakpoints[i]:self.breakpoints[i + 1]]) # / self.band_size[i] 60 | 61 | return self.strength 62 | 63 | 64 | if __name__ == '__main__': 65 | N = 2048 66 | rate = 16000 67 | 68 | data = array.array('h', [0] * N) 69 | w = 2 * math.pi * 50 / rate 70 | for t in range(N): 71 | data[t] = int(100 * math.sin(w * t)) 72 | 73 | analyzer = SpectrumAnalyzer(N, rate) 74 | strength = analyzer.analyze(data.tostring()) 75 | print([int(f) for f in analyzer.frequencies]) 76 | print([int(s) for s in strength]) 77 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/vad.py: -------------------------------------------------------------------------------- 1 | """ 2 | ReSpeaker Python Library 3 | Copyright (c) 2016 Seeed Technology Limited. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | import collections 19 | import sys 20 | 21 | import webrtcvad 22 | 23 | 24 | class WebRTCVAD: 25 | def __init__(self, sample_rate=16000, level=0): 26 | """ 27 | 28 | Args: 29 | sample_rate: audio sample rate 30 | level: between 0 and 3. 0 is the least aggressive about filtering out non-speech, 3 is the most aggressive. 31 | """ 32 | self.sample_rate = sample_rate 33 | 34 | self.frame_ms = 30 35 | self.frame_bytes = int(2 * self.frame_ms * self.sample_rate / 1000) # S16_LE, 2 bytes width 36 | 37 | self.vad = webrtcvad.Vad(level) 38 | self.active = False 39 | self.data = b'' 40 | self.history = collections.deque(maxlen=128) 41 | 42 | def is_speech(self, data): 43 | self.data += data 44 | while len(self.data) >= self.frame_bytes: 45 | frame = self.data[:self.frame_bytes] 46 | self.data = self.data[self.frame_bytes:] 47 | 48 | if self.vad.is_speech(frame, self.sample_rate): 49 | sys.stdout.write('1') 50 | self.history.append(1) 51 | else: 52 | sys.stdout.write('0') 53 | self.history.append(0) 54 | 55 | num_voiced = 0 56 | for i in range(-8, 0): 57 | try: 58 | num_voiced += self.history[i] 59 | except IndexError: 60 | continue 61 | 62 | if not self.active: 63 | if num_voiced >= 4: 64 | sys.stdout.write('+') 65 | self.active = True 66 | break 67 | elif len(self.history) == self.history.maxlen and sum(self.history) == 0: 68 | sys.stdout.write('Todo: increase capture volume') 69 | for _ in range(self.history.maxlen // 2): 70 | self.history.popleft() 71 | 72 | else: 73 | if num_voiced < 1: 74 | sys.stdout.write('-') 75 | self.active = False 76 | elif sum(self.history) > self.history.maxlen * 0.9: 77 | sys.stdout.write('Todo: decrease capture volume') 78 | for _ in range(int(self.history.maxlen / 2)): 79 | self.history.popleft() 80 | 81 | return self.active 82 | 83 | def reset(self): 84 | self.data = b'' 85 | self.active = False 86 | self.history.clear() 87 | 88 | 89 | vad = WebRTCVAD() 90 | 91 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/pixel_ring.py: -------------------------------------------------------------------------------- 1 | """ 2 | ReSpeaker Python Library 3 | Copyright (c) 2016 Seeed Technology Limited. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | import usb_hid 19 | from spi import spi 20 | 21 | 22 | class PixelRing: 23 | mono_mode = 1 24 | listening_mode = 2 25 | waiting_mode = 3 26 | speaking_mode = 4 27 | 28 | def __init__(self): 29 | self.hid = usb_hid.get() 30 | 31 | def off(self): 32 | self.set_color(rgb=0) 33 | 34 | def set_color(self, rgb=None, r=0, g=0, b=0): 35 | if rgb: 36 | self.write(0, [self.mono_mode, rgb & 0xFF, (rgb >> 8) & 0xFF, (rgb >> 16) & 0xFF]) 37 | else: 38 | self.write(0, [self.mono_mode, b, g, r]) 39 | 40 | def listen(self, direction=None): 41 | if direction is None: 42 | self.write(0, [7, 0, 0, 0]) 43 | else: 44 | self.write(0, [2, 0, direction & 0xFF, (direction >> 8) & 0xFF]) 45 | 46 | def wait(self): 47 | self.write(0, [self.waiting_mode, 0, 0, 0]) 48 | 49 | def speak(self, strength, direction): 50 | self.write(0, [self.speaking_mode, strength, direction & 0xFF, (direction >> 8) & 0xFF]) 51 | 52 | def set_volume(self, volume): 53 | self.write(0, [5, 0, 0, volume]) 54 | 55 | @staticmethod 56 | def to_bytearray(data): 57 | if type(data) is int: 58 | array = bytearray([data & 0xFF]) 59 | elif type(data) is bytearray: 60 | array = data 61 | elif type(data) is str: 62 | array = bytearray(data) 63 | elif type(data) is list: 64 | array = bytearray(data) 65 | else: 66 | raise TypeError('%s is not supported' % type(data)) 67 | 68 | return array 69 | 70 | def write(self, address, data): 71 | data = self.to_bytearray(data) 72 | length = len(data) 73 | if self.hid: 74 | packet = bytearray([address & 0xFF, (address >> 8) & 0xFF, length & 0xFF, (length >> 8) & 0xFF]) + data 75 | self.hid.write(packet) 76 | print(packet) 77 | spi.write(address=address, data=data) 78 | 79 | def close(self): 80 | if self.hid: 81 | self.hid.close() 82 | 83 | 84 | pixel_ring = PixelRing() 85 | 86 | 87 | if __name__ == '__main__': 88 | import time 89 | 90 | pixel_ring.listen() 91 | time.sleep(3) 92 | pixel_ring.wait() 93 | time.sleep(3) 94 | for level in range(2, 8): 95 | pixel_ring.speak(level, 0) 96 | time.sleep(1) 97 | pixel_ring.set_volume(4) 98 | time.sleep(3) 99 | 100 | color = 0x800000 101 | while True: 102 | try: 103 | pixel_ring.set_color(rgb=color) 104 | color += 0x10 105 | time.sleep(1) 106 | except KeyboardInterrupt: 107 | break 108 | 109 | pixel_ring.off() 110 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/fft.py: -------------------------------------------------------------------------------- 1 | """ 2 | DFT wrapper of FFTW3 3 | Copyright (c) 2016 Seeed Technology Limited. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | import array 19 | import ctypes 20 | import math 21 | import os 22 | import logging 23 | 24 | 25 | class FFT: 26 | def __init__(self, size): 27 | self.size = 1 << math.frexp(size - 1)[1] 28 | 29 | self.real_input = array.array('f', [0.0] * self.size) 30 | self.complex_output = array.array('f', [0.0] * (self.size * 2)) 31 | self.amplitude = array.array('f', [0.0] * (self.size / 2 + 1)) 32 | self.phase = array.array('f', [0.0] * (self.size / 2 + 1)) 33 | 34 | try: 35 | if os.name == "nt": 36 | self.fftw3f = ctypes.CDLL('libfftw3f-3.dll') 37 | else: 38 | self.fftw3f = ctypes.CDLL('libfftw3f.so') 39 | 40 | # fftw_plan fftw_plan_dft_r2c_1d(int band_number, double *in, fftw_complex *out, unsigned flags); 41 | self.fftwf_plan_dft_r2c_1d = self.fftw3f.fftwf_plan_dft_r2c_1d 42 | self.fftwf_plan_dft_r2c_1d.argtypes = (ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint) 43 | self.fftwf_plan_dft_r2c_1d.restype = ctypes.c_void_p 44 | 45 | # void fftwf_execute(const fftwf_plan plan) 46 | self.fftwf_execute = self.fftw3f.fftwf_execute 47 | self.fftwf_execute.argtypes = (ctypes.c_void_p,) 48 | self.fftwf_execute.restype = None 49 | 50 | input_ptr, _ = self.real_input.buffer_info() 51 | output_ptr, _ = self.complex_output.buffer_info() 52 | self.fftwf_plan = self.fftwf_plan_dft_r2c_1d(self.size, input_ptr, output_ptr, 1) 53 | except Exception as e: 54 | logging.warn('Can not find libffw3f dynamic library, return error - {}'.format(e.message)) 55 | self.fftwf_execute = lambda x: None 56 | self.fftwf_plan = None 57 | 58 | def dft(self, data, typecode='h'): 59 | if type(data) is str: 60 | a = array.array(typecode, data) 61 | for index, value in enumerate(a): 62 | self.real_input[index] = float(value) 63 | elif type(data) is array.array: 64 | for index, value in enumerate(data): 65 | self.real_input[index] = float(value) 66 | 67 | self.fftwf_execute(self.fftwf_plan) 68 | 69 | for i in range(len(self.amplitude)): 70 | self.amplitude[i] = math.hypot(self.complex_output[i * 2], self.complex_output[i * 2 + 1]) 71 | # self.phase[i] = math.atan2(self.complex_output[i * 2 + 1], self.complex_output[i * 2]) 72 | 73 | return self.amplitude # , self.phase 74 | 75 | 76 | if __name__ == '__main__': 77 | N = 128 78 | rate = 16000 79 | 80 | data = array.array('h', [1] * N) 81 | w = 2 * math.pi * 1000 / rate 82 | for t in range(N): 83 | data[t] = 10 + int(100 * math.sin(w * t)) + int(200 * math.sin(2 * w * t)) 84 | 85 | fft = FFT(N) 86 | print(fft.dft(data)) 87 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/TAR0287/0287.lm: -------------------------------------------------------------------------------- 1 | Language model created by QuickLM on Sun Aug 27 23:07:42 EDT 2017 2 | Copyright (c) 1996-2010 Carnegie Mellon University and Alexander I. Rudnicky 3 | 4 | The model is in standard ARPA format, designed by Doug Paul while he was at MITRE. 5 | 6 | The code that was used to produce this language model is available in Open Source. 7 | Please visit http://www.speech.cs.cmu.edu/tools/ for more information 8 | 9 | The (fixed) discount mass is 0.5. The backoffs are computed using the ratio method. 10 | This model based on a corpus of 16 sentences and 28 words 11 | 12 | \data\ 13 | ngram 1=28 14 | ngram 2=44 15 | ngram 3=48 16 | 17 | \1-grams: 18 | -1.6021 25 -0.2900 19 | -1.0969 -0.3010 20 | -1.0969 -0.2648 21 | -2.0000 今天 -0.2833 22 | -1.6990 关闭 -0.2765 23 | -2.3010 卧室 -0.2856 24 | -2.0000 台灯 -0.2648 25 | -2.3010 吗 -0.2967 26 | -2.3010 听 -0.2989 27 | -2.3010 天气 -0.2989 28 | -1.6021 客厅 -0.2856 29 | -2.3010 将 -0.2900 30 | -2.0000 小米 -0.2967 31 | -2.3010 带伞 -0.2989 32 | -1.6021 度 -0.2648 33 | -2.3010 怎样 -0.2967 34 | -2.3010 我要 -0.2989 35 | -1.6990 打开 -0.2765 36 | -1.6990 把 -0.2718 37 | -2.0000 电视 -0.2648 38 | -1.4559 的 -0.2833 39 | -2.0000 盒子 -0.2648 40 | -1.4559 空调 -0.2529 41 | -1.6990 设为 -0.2900 42 | -2.3010 调为 -0.2900 43 | -2.3010 需要 -0.2989 44 | -2.3010 音乐 -0.2648 45 | -2.0000 ? -0.2648 46 | 47 | \2-grams: 48 | -0.3010 25 度 0.0000 49 | -1.2041 今天 0.0000 50 | -0.9031 关闭 0.0000 51 | -1.5051 将 0.0000 52 | -1.5051 我要 0.0000 53 | -0.9031 打开 0.0000 54 | -0.9031 把 0.0000 55 | -0.6021 今天 的 -0.2688 56 | -0.6021 今天 需要 0.0000 57 | -0.9031 关闭 台灯 0.0000 58 | -0.9031 关闭 客厅 0.0000 59 | -0.9031 关闭 小米 0.0000 60 | -0.9031 关闭 电视 0.0000 61 | -0.3010 卧室 的 -0.0580 62 | -0.3010 台灯 -0.3010 63 | -0.3010 吗 ? 0.0000 64 | -0.3010 听 音乐 0.0000 65 | -0.3010 天气 怎样 0.0000 66 | -0.3010 客厅 的 -0.0580 67 | -0.3010 将 客厅 0.0000 68 | -0.3010 小米 盒子 0.0000 69 | -0.3010 带伞 吗 0.0000 70 | -0.3010 度 -0.3010 71 | -0.3010 怎样 ? 0.0000 72 | -0.3010 我要 听 0.0000 73 | -0.9031 打开 台灯 0.0000 74 | -0.9031 打开 客厅 0.0000 75 | -0.9031 打开 小米 0.0000 76 | -0.9031 打开 电视 0.0000 77 | -0.9031 把 卧室 0.0000 78 | -0.6021 把 客厅 0.0000 79 | -0.9031 把 空调 -0.1549 80 | -0.3010 电视 -0.3010 81 | -1.1461 的 天气 0.0000 82 | -0.3680 的 空调 0.0000 83 | -0.3010 盒子 -0.3010 84 | -0.8451 空调 -0.3010 85 | -0.5441 空调 设为 0.0000 86 | -1.1461 空调 调为 0.0000 87 | -0.3010 设为 25 0.0000 88 | -0.3010 调为 25 0.0000 89 | -0.3010 需要 带伞 0.0000 90 | -0.3010 音乐 -0.3010 91 | -0.3010 ? -0.3010 92 | 93 | \3-grams: 94 | -0.3010 25 度 95 | -0.6021 今天 的 96 | -0.6021 今天 需要 97 | -0.9031 关闭 台灯 98 | -0.9031 关闭 客厅 99 | -0.9031 关闭 小米 100 | -0.9031 关闭 电视 101 | -0.3010 将 客厅 102 | -0.3010 我要 听 103 | -0.9031 打开 台灯 104 | -0.9031 打开 客厅 105 | -0.9031 打开 小米 106 | -0.9031 打开 电视 107 | -0.9031 把 卧室 108 | -0.6021 把 客厅 109 | -0.9031 把 空调 110 | -0.3010 今天 的 天气 111 | -0.3010 今天 需要 带伞 112 | -0.3010 关闭 台灯 113 | -0.3010 关闭 客厅 的 114 | -0.3010 关闭 小米 盒子 115 | -0.3010 关闭 电视 116 | -0.3010 卧室 的 空调 117 | -0.3010 吗 ? 118 | -0.3010 听 音乐 119 | -0.3010 天气 怎样 ? 120 | -0.3010 客厅 的 空调 121 | -0.3010 将 客厅 的 122 | -0.3010 小米 盒子 123 | -0.3010 带伞 吗 ? 124 | -0.3010 怎样 ? 125 | -0.3010 我要 听 音乐 126 | -0.3010 打开 台灯 127 | -0.3010 打开 客厅 的 128 | -0.3010 打开 小米 盒子 129 | -0.3010 打开 电视 130 | -0.3010 把 卧室 的 131 | -0.3010 把 客厅 的 132 | -0.3010 把 空调 设为 133 | -0.3010 的 天气 怎样 134 | -0.7782 的 空调 135 | -0.6021 的 空调 设为 136 | -1.0792 的 空调 调为 137 | -0.3010 空调 设为 25 138 | -0.3010 空调 调为 25 139 | -0.3010 设为 25 度 140 | -0.3010 调为 25 度 141 | -0.3010 需要 带伞 吗 142 | 143 | \end\ 144 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/usb_hid/hidapi_backend.py: -------------------------------------------------------------------------------- 1 | """ 2 | USB HID API from pyOCD project 3 | Copyright (c) 2006-2013 ARM Limited 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | from usb_hid.interface import Interface 19 | import logging, os 20 | 21 | try: 22 | import hid 23 | except: 24 | if os.name == "posix" and os.uname()[0] == 'Darwin': 25 | logging.error("cython-hidapi is required on a Mac OS X Machine") 26 | isAvailable = False 27 | else: 28 | isAvailable = True 29 | 30 | class HidApiUSB(Interface): 31 | """ 32 | This class provides basic functions to access 33 | a USB HID device using cython-hidapi: 34 | - write/read an endpoint 35 | """ 36 | vid = 0 37 | pid = 0 38 | 39 | isAvailable = isAvailable 40 | 41 | def __init__(self): 42 | super(HidApiUSB, self).__init__() 43 | # Vendor page and usage_id = 2 44 | self.device = None 45 | 46 | def open(self): 47 | pass 48 | 49 | @staticmethod 50 | def getAllConnectedInterface(): 51 | """ 52 | returns all the connected devices which matches HidApiUSB.vid/HidApiUSB.pid. 53 | returns an array of HidApiUSB (Interface) objects 54 | """ 55 | 56 | devices = hid.enumerate() 57 | 58 | if not devices: 59 | logging.debug("No Mbed device connected") 60 | return [] 61 | 62 | boards = [] 63 | 64 | for deviceInfo in devices: 65 | product_name = deviceInfo['product_string'] 66 | if (product_name.find("MicArray") < 0): 67 | # Skip non cmsis-dap devices 68 | continue 69 | 70 | try: 71 | dev = hid.device(vendor_id=deviceInfo['vendor_id'], product_id=deviceInfo['product_id'], 72 | path=deviceInfo['path']) 73 | except IOError: 74 | logging.debug("Failed to open Mbed device") 75 | continue 76 | 77 | # Create the USB interface object for this device. 78 | new_board = HidApiUSB() 79 | new_board.vendor_name = deviceInfo['manufacturer_string'] 80 | new_board.product_name = deviceInfo['product_string'] 81 | new_board.serial_number = deviceInfo['serial_number'] 82 | new_board.vid = deviceInfo['vendor_id'] 83 | new_board.pid = deviceInfo['product_id'] 84 | new_board.device_info = deviceInfo 85 | new_board.device = dev 86 | try: 87 | dev.open_path(deviceInfo['path']) 88 | except AttributeError: 89 | pass 90 | except IOError: 91 | # Ignore failure to open a device by skipping the device. 92 | continue 93 | 94 | boards.append(new_board) 95 | 96 | return boards 97 | 98 | def write(self, data): 99 | """ 100 | write data on the OUT endpoint associated to the HID interface 101 | """ 102 | for _ in range(64 - len(data)): 103 | data.append(0) 104 | #logging.debug("send: %s", data) 105 | self.device.write(bytearray([0]) + data) 106 | return 107 | 108 | 109 | def read(self, timeout=-1): 110 | """ 111 | read data on the IN endpoint associated to the HID interface 112 | """ 113 | return self.device.read(64) 114 | 115 | def getSerialNumber(self): 116 | return self.serial_number 117 | 118 | def close(self): 119 | """ 120 | close the interface 121 | """ 122 | logging.debug("closing interface") 123 | self.device.close() 124 | 125 | def setPacketCount(self, count): 126 | # No interface level restrictions on count 127 | self.packet_count = count 128 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/usb_hid/pywinusb_backend.py: -------------------------------------------------------------------------------- 1 | """ 2 | USB HID API from pyOCD project 3 | Copyright (c) 2006-2013 ARM Limited 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | from usb_hid.interface import Interface 19 | import logging, os, collections 20 | from time import time 21 | 22 | try: 23 | import pywinusb.hid as hid 24 | except: 25 | if os.name == "nt": 26 | logging.error("PyWinUSB is required on a Windows Machine") 27 | isAvailable = False 28 | else: 29 | isAvailable = True 30 | 31 | class PyWinUSB(Interface): 32 | """ 33 | This class provides basic functions to access 34 | a USB HID device using pywinusb: 35 | - write/read an endpoint 36 | """ 37 | vid = 0 38 | pid = 0 39 | 40 | isAvailable = isAvailable 41 | 42 | def __init__(self): 43 | super(PyWinUSB, self).__init__() 44 | # Vendor page and usage_id = 2 45 | self.report = [] 46 | # deque used here instead of synchronized Queue 47 | # since read speeds are ~10-30% faster and are 48 | # comprable to a based list implmentation. 49 | self.rcv_data = collections.deque() 50 | self.device = None 51 | return 52 | 53 | # handler called when a report is received 54 | def rx_handler(self, data): 55 | #logging.debug("rcv: %s", data[1:]) 56 | self.rcv_data.append(data[1:]) 57 | 58 | def open(self): 59 | self.device.set_raw_data_handler(self.rx_handler) 60 | self.device.open(shared=False) 61 | 62 | @staticmethod 63 | def getAllConnectedInterface(): 64 | """ 65 | returns all the connected CMSIS-DAP devices 66 | """ 67 | all_devices = hid.find_all_hid_devices() 68 | 69 | # find devices with good vid/pid 70 | all_mbed_devices = [] 71 | for d in all_devices: 72 | if (d.product_name.find("MicArray") >= 0): 73 | all_mbed_devices.append(d) 74 | 75 | boards = [] 76 | for dev in all_mbed_devices: 77 | try: 78 | dev.open(shared=False) 79 | report = dev.find_output_reports() 80 | if (len(report) == 1): 81 | new_board = PyWinUSB() 82 | new_board.report = report[0] 83 | new_board.vendor_name = dev.vendor_name 84 | new_board.product_name = dev.product_name 85 | new_board.serial_number = dev.serial_number 86 | new_board.vid = dev.vendor_id 87 | new_board.pid = dev.product_id 88 | new_board.device = dev 89 | new_board.device.set_raw_data_handler(new_board.rx_handler) 90 | 91 | boards.append(new_board) 92 | except Exception as e: 93 | logging.error("Receiving Exception: %s", e) 94 | dev.close() 95 | 96 | return boards 97 | 98 | def write(self, data): 99 | """ 100 | write data on the OUT endpoint associated to the HID interface 101 | """ 102 | for _ in range(64 - len(data)): 103 | data.append(0) 104 | #logging.debug("send: %s", data) 105 | self.report.send(bytearray([0]) + data) 106 | return 107 | 108 | 109 | def read(self, timeout=1.0): 110 | """ 111 | read data on the IN endpoint associated to the HID interface 112 | """ 113 | start = time() 114 | while len(self.rcv_data) == 0: 115 | if time() - start > timeout: 116 | # Read operations should typically take ~1-2ms. 117 | # If this exception occurs, then it could indicate 118 | # a problem in one of the following areas: 119 | # 1. Bad usb driver causing either a dropped read or write 120 | # 2. CMSIS-DAP firmware problem cause a dropped read or write 121 | # 3. CMSIS-DAP is performing a long operation or is being 122 | # halted in a debugger 123 | raise Exception("Read timed out") 124 | return self.rcv_data.popleft() 125 | 126 | def setPacketCount(self, count): 127 | # No interface level restrictions on count 128 | self.packet_count = count 129 | 130 | def getSerialNumber(self): 131 | return self.serial_number 132 | 133 | def close(self): 134 | """ 135 | close the interface 136 | """ 137 | logging.debug("closing interface") 138 | self.device.close() 139 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/spi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ReSpeaker Python Library 3 | Copyright (c) 2016 Seeed Technology Limited. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | import platform 19 | 20 | 21 | CRC8_TABLE = ( 22 | 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 23 | 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, 24 | 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, 25 | 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 26 | 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5, 27 | 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, 28 | 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 29 | 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, 30 | 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, 31 | 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, 32 | 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, 33 | 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a, 34 | 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 35 | 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, 36 | 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, 37 | 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 38 | 0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c, 39 | 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, 40 | 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, 41 | 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, 42 | 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 43 | 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, 44 | 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, 45 | 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34, 46 | 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 47 | 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, 48 | 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, 49 | 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 50 | 0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb, 51 | 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, 52 | 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, 53 | 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3 54 | ) 55 | 56 | 57 | def crc8(data): 58 | result = 0 59 | for b in data: 60 | result = CRC8_TABLE[result ^ b] 61 | return result 62 | 63 | 64 | if platform.machine() == 'mips': 65 | from gpio import * 66 | from threading import RLock 67 | import time 68 | 69 | 70 | class SPI: 71 | def __init__(self, sck=15, mosi=17, miso=16, cs=14): 72 | self.sck = Gpio(sck, OUTPUT) 73 | self.mosi = Gpio(mosi, OUTPUT) 74 | self.miso = Gpio(miso, INPUT) 75 | self.cs = Gpio(cs, OUTPUT) 76 | 77 | self.cs.write(1) 78 | 79 | self.frequency(10000000) 80 | self.format(8, 0) 81 | self.lock = RLock() 82 | 83 | def frequency(self, hz=10000000): 84 | self.freq = hz 85 | 86 | def format(self, bits=8, mode=0): 87 | self.bits = bits 88 | self.mode = mode 89 | self.polarity = (mode >> 1) & 1 90 | self.phase = mode & 1 91 | self.sck.write(self.polarity) 92 | 93 | def _exchange(self, data): 94 | read = 0 95 | for bit in range(self.bits - 1, -1, -1): 96 | self.mosi.write((data >> bit) & 0x01) 97 | 98 | if 0 == self.phase: 99 | read |= self.miso.read() << bit 100 | 101 | self.sck.write(1 - self.polarity) 102 | # time.sleep(0.5 / self.freq) 103 | 104 | if 1 == self.phase: 105 | read |= self.miso.read() << bit 106 | 107 | self.sck.write(self.polarity) 108 | # time.sleep(0.5 / self.freq) 109 | 110 | return read 111 | 112 | def _write(self, data): 113 | response = bytearray() 114 | self.cs.write(0) 115 | if type(data) is int: 116 | response.append(self._exchange(data)) 117 | elif type(data) is bytearray: 118 | for b in data: 119 | response.append(self._exchange(b)) 120 | elif type(data) is str: 121 | for b in bytearray(data): 122 | response.append(self._exchange(b)) 123 | elif type(data) is list: 124 | for item in data: 125 | self.write(item) 126 | else: 127 | self.cs.write(1) 128 | raise TypeError('%s is not supported' % type(data)) 129 | 130 | self.cs.write(1) 131 | return response 132 | 133 | def write(self, data=None, address=None): 134 | with self.lock: 135 | if address is not None: 136 | data = bytearray([0xA5, address & 0xFF, len(data) & 0xFF]) + data + bytearray([crc8(data)]) 137 | response = self._write(data)[3:-1] 138 | else: 139 | response = self._write(data) 140 | 141 | return response 142 | 143 | def close(self): 144 | pass 145 | else: 146 | class SPI: 147 | def __init__(self): 148 | pass 149 | 150 | def write(self, data=None, address=None): 151 | pass 152 | 153 | def close(self): 154 | pass 155 | 156 | 157 | spi = SPI() 158 | 159 | 160 | if __name__ == '__main__': 161 | while True: 162 | spi.write('hello\n') 163 | time.sleep(1) 164 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/player.py: -------------------------------------------------------------------------------- 1 | """ 2 | ReSpeaker Python Library 3 | Copyright (c) 2016 Seeed Technology Limited. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | import audioop 19 | import threading 20 | import platform 21 | import subprocess 22 | import tempfile 23 | import types 24 | import wave 25 | 26 | try: # Python 2 27 | import Queue 28 | except: # Python 3 29 | import queue as Queue 30 | 31 | import pyaudio 32 | 33 | from pixel_ring import pixel_ring 34 | from spectrum_analyzer import SpectrumAnalyzer 35 | from spi import spi 36 | 37 | CHUNK_SIZE = 1024 38 | BAND_NUMBER = 16 39 | 40 | 41 | class Player: 42 | def __init__(self, pyaudio_instance=None): 43 | self.pyaudio_instance = pyaudio_instance if pyaudio_instance else pyaudio.PyAudio() 44 | self.stop_event = threading.Event() 45 | 46 | def ignite(queue): 47 | data = queue.get() 48 | analyzer = SpectrumAnalyzer(len(data), band_number=BAND_NUMBER) 49 | while True: 50 | while not queue.empty(): 51 | data = queue.get() 52 | 53 | amplitude = analyzer.analyze(data) 54 | level = bytearray(len(amplitude)) 55 | for i, v in enumerate(amplitude): 56 | l = int(v / 1024 / 128) 57 | if l > 255: 58 | l = 255 59 | level[i] = l 60 | 61 | spi.write(address=0xA0, data=level) 62 | 63 | data = queue.get() 64 | 65 | self.queue = Queue.Queue() 66 | self.thread = threading.Thread(target=ignite, args=(self.queue,)) 67 | self.thread.daemon = True 68 | self.thread.start() 69 | 70 | def _play(self, data, rate=16000, channels=1, width=2, spectrum=True): 71 | stream = self.pyaudio_instance.open( 72 | format=self.pyaudio_instance.get_format_from_width(width), 73 | channels=channels, 74 | rate=rate, 75 | output=True, 76 | # output_device_index=1, 77 | frames_per_buffer=CHUNK_SIZE, 78 | ) 79 | 80 | if isinstance(data, types.GeneratorType): 81 | for d in data: 82 | if self.stop_event.is_set(): 83 | break 84 | 85 | stream.write(d) 86 | 87 | if spectrum: 88 | if channels == 2: 89 | d = audioop.tomono(d, 2, 0.5, 0.5) 90 | self.queue.put(d) 91 | else: 92 | stream.write(data) 93 | 94 | stream.close() 95 | 96 | def play(self, wav=None, data=None, rate=16000, channels=1, width=2, block=True, spectrum=None): 97 | """ 98 | play wav file or raw audio (string or generator) 99 | Args: 100 | wav: wav file path 101 | data: raw audio data, str or iterator 102 | rate: sample rate, only for raw audio 103 | channels: channel number, only for raw data 104 | width: raw audio data width, 16 bit is 2, only for raw data 105 | block: if true, block until audio is played. 106 | spectrum: if true, use a spectrum analyzer thread to analyze data 107 | """ 108 | if wav: 109 | f = wave.open(wav, 'rb') 110 | rate = f.getframerate() 111 | channels = f.getnchannels() 112 | width = f.getsampwidth() 113 | 114 | def gen(w): 115 | d = w.readframes(CHUNK_SIZE) 116 | while d: 117 | yield d 118 | d = w.readframes(CHUNK_SIZE) 119 | w.close() 120 | 121 | data = gen(f) 122 | 123 | self.stop_event.clear() 124 | if block: 125 | self._play(data, rate, channels, width, spectrum) 126 | else: 127 | thread = threading.Thread(target=self._play, args=(data, rate, channels, width, spectrum)) 128 | thread.start() 129 | 130 | def play_raw(self, data, rate=16000, channels=1, width=2): 131 | self.play(data=data, rate=rate, channels=channels, width=width) 132 | 133 | def play_mp3(self, mp3=None, data=None, block=True): 134 | """ 135 | It supports GeneratorType mp3 stream or mp3 data string 136 | Args: 137 | mp3: mp3 file 138 | data: mp3 generator or data 139 | block: if true, block until audio is played. 140 | """ 141 | if platform.machine() == 'mips': 142 | command = 'madplay -o wave:- - | aplay -M' 143 | else: 144 | command = 'ffplay -autoexit -nodisp -' 145 | 146 | if mp3: 147 | def gen(m): 148 | with open(m, 'rb') as f: 149 | d = f.read(1024) 150 | while d: 151 | yield d 152 | d = f.read(1024) 153 | 154 | data = gen(mp3) 155 | 156 | if isinstance(data, types.GeneratorType): 157 | p = subprocess.Popen(command, stdin=subprocess.PIPE, shell=True) 158 | for d in data: 159 | p.stdin.write(d) 160 | 161 | p.stdin.close() 162 | else: 163 | with tempfile.NamedTemporaryFile(mode='w+b') as f: 164 | f.write(data) 165 | f.flush() 166 | f.seek(0) 167 | p = subprocess.Popen(command, stdin=f, shell=True) 168 | 169 | if block: 170 | p.wait() 171 | 172 | def stop(self): 173 | self.stop_event.set() 174 | 175 | def close(self): 176 | pass 177 | 178 | 179 | def main(): 180 | import sys 181 | 182 | if len(sys.argv) < 2: 183 | print('Usage: python {} music.wav'.format(sys.argv[0])) 184 | sys.exit(1) 185 | 186 | player = Player() 187 | player.play(sys.argv[1], spectrum=True) 188 | 189 | 190 | if __name__ == '__main__': 191 | main() 192 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/usb_hid/pyusb_backend.py: -------------------------------------------------------------------------------- 1 | """ 2 | USB HID API from pyOCD project 3 | Copyright (c) 2006-2013 ARM Limited 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | from usb_hid.interface import Interface 19 | import logging, os, threading 20 | 21 | try: 22 | import usb.core 23 | import usb.util 24 | except: 25 | if os.name == "posix" and not os.uname()[0] == 'Darwin': 26 | logging.error("PyUSB is required on a Linux Machine") 27 | isAvailable = False 28 | else: 29 | isAvailable = True 30 | 31 | class PyUSB(Interface): 32 | """ 33 | This class provides basic functions to access 34 | a USB HID device using pyusb: 35 | - write/read an endpoint 36 | """ 37 | 38 | vid = 0 39 | pid = 0 40 | intf_number = 0 41 | 42 | isAvailable = isAvailable 43 | 44 | def __init__(self): 45 | super(PyUSB, self).__init__() 46 | self.ep_out = None 47 | self.ep_in = None 48 | self.dev = None 49 | self.closed = False 50 | self.rcv_data = [] 51 | self.read_sem = threading.Semaphore(0) 52 | 53 | def start_rx(self): 54 | self.thread = threading.Thread(target=self.rx_task) 55 | self.thread.daemon = True 56 | self.thread.start() 57 | 58 | def rx_task(self): 59 | while not self.closed: 60 | self.read_sem.acquire() 61 | if not self.closed: 62 | # Timeouts appear to corrupt data occasionally. Because of this the 63 | # timeout is set to infinite. 64 | self.rcv_data.append(self.ep_in.read(self.ep_in.wMaxPacketSize, -1)) 65 | 66 | @staticmethod 67 | def getAllConnectedInterface(): 68 | """ 69 | returns all the connected devices which matches PyUSB.vid/PyUSB.pid. 70 | returns an array of PyUSB (Interface) objects 71 | """ 72 | # find all devices matching the vid/pid specified 73 | all_devices = usb.core.find(find_all=True) 74 | 75 | if not all_devices: 76 | logging.debug("No device connected") 77 | return [] 78 | 79 | boards = [] 80 | 81 | # iterate on all devices found 82 | for board in all_devices: 83 | interface_number = -1 84 | try: 85 | # The product string is read over USB when accessed. 86 | # This can cause an exception to be thrown if the device 87 | # is malfunctioning. 88 | product = board.product 89 | except usb.core.USBError as error: 90 | logging.warning("Exception getting product string: %s", error) 91 | continue 92 | if (product is None) or (product.find("MicArray") < 0): 93 | # Not a ReSpeaker MicArray device so close it 94 | usb.util.dispose_resources(board) 95 | continue 96 | 97 | # get active config 98 | config = board.get_active_configuration() 99 | 100 | # iterate on all interfaces: 101 | # - if we found a HID interface 102 | for interface in config: 103 | if interface.bInterfaceClass == 0x03: 104 | interface_number = interface.bInterfaceNumber 105 | break 106 | 107 | if interface_number == -1: 108 | continue 109 | 110 | try: 111 | if board.is_kernel_driver_active(interface_number): 112 | board.detach_kernel_driver(interface_number) 113 | except Exception as e: 114 | print(e) 115 | 116 | ep_in, ep_out = None, None 117 | for ep in interface: 118 | if ep.bEndpointAddress & 0x80: 119 | ep_in = ep 120 | else: 121 | ep_out = ep 122 | 123 | """If there is no EP for OUT then we can use CTRL EP""" 124 | if not ep_in: 125 | logging.error('Endpoints not found') 126 | return None 127 | 128 | new_board = PyUSB() 129 | new_board.ep_in = ep_in 130 | new_board.ep_out = ep_out 131 | new_board.dev = board 132 | new_board.vid = board.idVendor 133 | new_board.pid = board.idProduct 134 | new_board.intf_number = interface_number 135 | new_board.product_name = product 136 | new_board.vendor_name = board.manufacturer 137 | new_board.serial_number = board.serial_number 138 | new_board.start_rx() 139 | boards.append(new_board) 140 | 141 | return boards 142 | 143 | def write(self, data): 144 | """ 145 | write data on the OUT endpoint associated to the HID interface 146 | """ 147 | 148 | # report_size = 64 149 | # if self.ep_out: 150 | # report_size = self.ep_out.wMaxPacketSize 151 | # 152 | # for _ in range(report_size - len(data)): 153 | # data.append(0) 154 | 155 | self.read_sem.release() 156 | 157 | if not self.ep_out: 158 | bmRequestType = 0x21 #Host to device request of type Class of Recipient Interface 159 | bmRequest = 0x09 #Set_REPORT (HID class-specific request for transferring data over EP0) 160 | wValue = 0x200 #Issuing an OUT report 161 | wIndex = self.intf_number #mBed Board interface number for HID 162 | self.dev.ctrl_transfer(bmRequestType, bmRequest, wValue, wIndex, data) 163 | return 164 | #raise ValueError('EP_OUT endpoint is NULL') 165 | 166 | self.ep_out.write(data) 167 | #logging.debug('sent: %s', data) 168 | return 169 | 170 | 171 | def read(self): 172 | """ 173 | read data on the IN endpoint associated to the HID interface 174 | """ 175 | while len(self.rcv_data) == 0: 176 | pass 177 | return self.rcv_data.pop(0) 178 | 179 | def setPacketCount(self, count): 180 | # No interface level restrictions on count 181 | self.packet_count = count 182 | 183 | def getSerialNumber(self): 184 | return self.serial_number 185 | 186 | def close(self): 187 | """ 188 | close the interface 189 | """ 190 | logging.debug("closing interface") 191 | self.closed = True 192 | self.read_sem.release() 193 | self.thread.join() 194 | usb.util.dispose_resources(self.dev) 195 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/gpio.py: -------------------------------------------------------------------------------- 1 | """ 2 | Linux SysFS-based native GPIO implementation Based on https://github.com/derekstavis/python-sysfs-gpio 3 | 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2016 Yihui Xiong 7 | Copyright (c) 2014 Derek Willian Stavis 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | """ 27 | 28 | import logging 29 | import os 30 | import select 31 | from threading import Thread 32 | 33 | Logger = logging.getLogger(__file__) 34 | 35 | # Sysfs constants 36 | 37 | SYSFS_BASE_PATH = '/sys/class/gpio' 38 | 39 | SYSFS_EXPORT_PATH = SYSFS_BASE_PATH + '/export' 40 | SYSFS_UNEXPORT_PATH = SYSFS_BASE_PATH + '/unexport' 41 | 42 | SYSFS_GPIO_PATH = SYSFS_BASE_PATH + '/gpio%d' 43 | SYSFS_GPIO_DIRECTION_PATH = SYSFS_GPIO_PATH + '/direction' 44 | SYSFS_GPIO_EDGE_PATH = SYSFS_GPIO_PATH + '/edge' 45 | SYSFS_GPIO_VALUE_PATH = SYSFS_GPIO_PATH + '/value' 46 | SYSFS_GPIO_ACTIVE_LOW_PATH = SYSFS_GPIO_PATH + '/active_low' 47 | 48 | SYSFS_GPIO_VALUE_LOW = '0' 49 | SYSFS_GPIO_VALUE_HIGH = '1' 50 | 51 | EPOLL_TIMEOUT = 1 # second 52 | 53 | # Public interface 54 | 55 | INPUT = 'in' 56 | OUTPUT = 'out' 57 | 58 | # Compatible with MRAA 59 | DIR_IN = INPUT 60 | DIR_OUT = OUTPUT 61 | 62 | RISING = 'rising' 63 | FALLING = 'falling' 64 | BOTH = 'both' 65 | 66 | ACTIVE_LOW_ON = 1 67 | ACTIVE_LOW_OFF = 0 68 | 69 | DIRECTIONS = (INPUT, OUTPUT) 70 | EDGES = (RISING, FALLING, BOTH) 71 | ACTIVE_LOW_MODES = (ACTIVE_LOW_ON, ACTIVE_LOW_OFF) 72 | 73 | 74 | class Gpio(object): 75 | """ 76 | Represent a pin in SysFS 77 | """ 78 | 79 | def __init__(self, number, direction=INPUT, callback=None, edge=None, active_low=0): 80 | """ 81 | @type number: int 82 | @param number: The pin number 83 | @type direction: int 84 | @param direction: Pin direction, enumerated by C{Direction} 85 | @type callback: callable 86 | @param callback: Method be called when pin changes state 87 | @type edge: int 88 | @param edge: The edge transition that triggers callback, 89 | enumerated by C{Edge} 90 | @type active_low: int 91 | @param active_low: Indicator of whether this pin uses inverted 92 | logic for HIGH-LOW transitions. 93 | """ 94 | self._number = number 95 | self._direction = direction 96 | self._callback = callback 97 | self._active_low = active_low 98 | 99 | if not os.path.isdir(self._sysfs_gpio_value_path()): 100 | with open(SYSFS_EXPORT_PATH, 'w') as export: 101 | export.write('%d' % number) 102 | else: 103 | Logger.debug("SysfsGPIO: Pin %d already exported" % number) 104 | 105 | self._fd = open(self._sysfs_gpio_value_path(), 'r+') 106 | 107 | if callback and not edge: 108 | raise Exception('You must supply a edge to trigger callback on') 109 | 110 | with open(self._sysfs_gpio_direction_path(), 'w') as fsdir: 111 | fsdir.write(direction) 112 | 113 | if edge: 114 | with open(self._sysfs_gpio_edge_path(), 'w') as fsedge: 115 | fsedge.write(edge) 116 | self._poll = select.epoll() 117 | self._poll.register(self, (select.EPOLLPRI | select.EPOLLET)) 118 | self.thread = Thread(target=self._run) 119 | self.thread.daemon = True 120 | self._running = True 121 | self.start() 122 | 123 | if active_low: 124 | if active_low not in ACTIVE_LOW_MODES: 125 | raise Exception('You must supply a value for active_low which is either 0 or 1.') 126 | with open(self._sysfs_gpio_active_low_path(), 'w') as fsactive_low: 127 | fsactive_low.write(str(active_low)) 128 | 129 | @property 130 | def callback(self): 131 | """ 132 | Gets this pin callback 133 | """ 134 | return self._callback 135 | 136 | @callback.setter 137 | def callback(self, value): 138 | """ 139 | Sets this pin callback 140 | """ 141 | self._callback = value 142 | 143 | @property 144 | def direction(self): 145 | """ 146 | Pin direction 147 | """ 148 | return self._direction 149 | 150 | @property 151 | def number(self): 152 | """ 153 | Pin number 154 | """ 155 | return self._number 156 | 157 | @property 158 | def active_low(self): 159 | """ 160 | Pin number 161 | """ 162 | return self._active_low 163 | 164 | def dir(self, direction): 165 | self._direction = direction 166 | with open(self._sysfs_gpio_direction_path(), 'w') as fsdir: 167 | fsdir.write(direction) 168 | 169 | def set(self): 170 | """ 171 | Set pin to HIGH logic setLevel 172 | """ 173 | self._fd.write(SYSFS_GPIO_VALUE_HIGH) 174 | self._fd.seek(0) 175 | 176 | def reset(self): 177 | """ 178 | Set pin to LOW logic setLevel 179 | """ 180 | self._fd.write(SYSFS_GPIO_VALUE_LOW) 181 | self._fd.seek(0) 182 | 183 | def read(self): 184 | """ 185 | Read pin value 186 | 187 | @rtype: int 188 | @return: I{0} when LOW, I{1} when HIGH 189 | """ 190 | val = self._fd.read() 191 | self._fd.seek(0) 192 | return int(val) 193 | 194 | def write(self, value): 195 | if value: 196 | self.set() 197 | else: 198 | self.reset() 199 | 200 | def close(self): 201 | self._running = False 202 | self._fd.close() 203 | 204 | def fileno(self): 205 | """ 206 | Get the file descriptor associated with this pin. 207 | 208 | @rtype: int 209 | @return: File descriptor 210 | """ 211 | return self._fd.fileno() 212 | 213 | def changed(self, state): 214 | if callable(self._callback): 215 | self._callback(self.number, state) 216 | 217 | def _run(self): 218 | 219 | while self._running: 220 | events = self._poll.poll(EPOLL_TIMEOUT) 221 | for fd, event in events: 222 | if not (event & (select.EPOLLPRI | select.EPOLLET)): 223 | continue 224 | 225 | self.changed(self.read()) 226 | 227 | def _sysfs_gpio_value_path(self): 228 | """ 229 | Get the file that represent the value of this pin. 230 | 231 | @rtype: str 232 | @return: the path to sysfs value file 233 | """ 234 | return SYSFS_GPIO_VALUE_PATH % self.number 235 | 236 | def _sysfs_gpio_direction_path(self): 237 | """ 238 | Get the file that represent the direction of this pin. 239 | 240 | @rtype: str 241 | @return: the path to sysfs direction file 242 | """ 243 | return SYSFS_GPIO_DIRECTION_PATH % self.number 244 | 245 | def _sysfs_gpio_edge_path(self): 246 | """ 247 | Get the file that represent the edge that will trigger an interrupt. 248 | 249 | @rtype: str 250 | @return: the path to sysfs edge file 251 | """ 252 | return SYSFS_GPIO_EDGE_PATH % self.number 253 | 254 | def _sysfs_gpio_active_low_path(self): 255 | """ 256 | Get the file that represents the active_low setting for this pin. 257 | 258 | @rtype: str 259 | @return: the path to sysfs active_low file 260 | """ 261 | return SYSFS_GPIO_ACTIVE_LOW_PATH % self.number 262 | 263 | 264 | __all__ = ('DIRECTIONS', 'INPUT', 'OUTPUT', 'DIR_IN', 'DIR_OUT', 'EDGES', 'RISING', 'FALLING', 'BOTH', 'Gpio') 265 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/bing_speech_api.py: -------------------------------------------------------------------------------- 1 | """ 2 | Bing Speech To Text (STT) and Text To Speech (TTS) 3 | 4 | ReSpeaker Python Library 5 | Copyright (c) 2016 Seeed Technology Limited. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | """ 19 | 20 | import io 21 | import os 22 | import types 23 | import uuid 24 | import wave 25 | 26 | import requests 27 | 28 | try: # Python 2 and Python <= 3.2 29 | from monotonic import monotonic 30 | except: # Python >= 3.3 31 | from time import monotonic 32 | 33 | 34 | class RequestError(Exception): 35 | pass 36 | 37 | 38 | class BingSpeechAPI: 39 | def __init__(self, key=os.getenv('BING_KEY', '')): 40 | self.key = key 41 | self.access_token = None 42 | self.expire_time = None 43 | self.locales = { 44 | "ar-eg": {"Female": "Microsoft Server Speech Text to Speech Voice (ar-EG, Hoda)"}, 45 | "de-DE": {"Female": "Microsoft Server Speech Text to Speech Voice (de-DE, Hedda)", 46 | "Male": "Microsoft Server Speech Text to Speech Voice (de-DE, Stefan, Apollo)"}, 47 | "en-AU": {"Female": "Microsoft Server Speech Text to Speech Voice (en-AU, Catherine)"}, 48 | "en-CA": {"Female": "Microsoft Server Speech Text to Speech Voice (en-CA, Linda)"}, 49 | "en-GB": {"Female": "Microsoft Server Speech Text to Speech Voice (en-GB, Susan, Apollo)", 50 | "Male": "Microsoft Server Speech Text to Speech Voice (en-GB, George, Apollo)"}, 51 | "en-IN": {"Male": "Microsoft Server Speech Text to Speech Voice (en-IN, Ravi, Apollo)"}, 52 | "en-US": {"Female": "Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)", 53 | "Male": "Microsoft Server Speech Text to Speech Voice (en-US, BenjaminRUS)"}, 54 | "es-ES": {"Female": "Microsoft Server Speech Text to Speech Voice (es-ES, Laura, Apollo)", 55 | "Male": "Microsoft Server Speech Text to Speech Voice (es-ES, Pablo, Apollo)"}, 56 | "es-MX": {"Male": "Microsoft Server Speech Text to Speech Voice (es-MX, Raul, Apollo)"}, 57 | "fr-CA": {"Female": "Microsoft Server Speech Text to Speech Voice (fr-CA, Caroline)"}, 58 | "fr-FR": {"Female": "Microsoft Server Speech Text to Speech Voice (fr-FR, Julie, Apollo)", 59 | "Male": "Microsoft Server Speech Text to Speech Voice (fr-FR, Paul, Apollo)"}, 60 | "it-IT": {"Male": "Microsoft Server Speech Text to Speech Voice (it-IT, Cosimo, Apollo)"}, 61 | "ja-JP": {"Female": "Microsoft Server Speech Text to Speech Voice (ja-JP, Ayumi, Apollo)", 62 | "Male": "Microsoft Server Speech Text to Speech Voice (ja-JP, Ichiro, Apollo)"}, 63 | "pt-BR": {"Male": "Microsoft Server Speech Text to Speech Voice (pt-BR, Daniel, Apollo)"}, 64 | "ru-RU": {"Female": "Microsoft Server Speech Text to Speech Voice (pt-BR, Daniel, Apollo)", 65 | "Male": "Microsoft Server Speech Text to Speech Voice (ru-RU, Pavel, Apollo)"}, 66 | "zh-CN": {"Female": "Microsoft Server Speech Text to Speech Voice (zh-CN, HuihuiRUS)", 67 | "Female2": "Microsoft Server Speech Text to Speech Voice (zh-CN, Yaoyao, Apollo)", 68 | "Male": "Microsoft Server Speech Text to Speech Voice (zh-CN, Kangkang, Apollo)"}, 69 | "zh-HK": {"Female": "Microsoft Server Speech Text to Speech Voice (zh-HK, Tracy, Apollo)", 70 | "Male": "Microsoft Server Speech Text to Speech Voice (zh-HK, Danny, Apollo)"}, 71 | "zh-TW": {"Female": "Microsoft Server Speech Text to Speech Voice (zh-TW, Yating, Apollo)", 72 | "Male": "Microsoft Server Speech Text to Speech Voice (zh-TW, Zhiwei, Apollo)"} 73 | } 74 | 75 | self.session = requests.Session() 76 | 77 | def authenticate(self): 78 | if self.expire_time is None or monotonic() > self.expire_time: # first credential request, or the access token from the previous one expired 79 | # get an access token using OAuth 80 | credential_url = "https://api.cognitive.microsoft.com/sts/v1.0/issueToken" 81 | headers = {"Ocp-Apim-Subscription-Key": self.key} 82 | 83 | start_time = monotonic() 84 | response = self.session.post(credential_url, headers=headers) 85 | 86 | if response.status_code != 200: 87 | raise RequestError("http request error with status code {}".format(response.status_code)) 88 | 89 | self.access_token = response.content 90 | expiry_seconds = 590 # document mentions the access token is expired in 10 minutes 91 | 92 | self.expire_time = start_time + expiry_seconds 93 | 94 | def recognize(self, audio_data, language="en-US", show_all=False): 95 | self.authenticate() 96 | if isinstance(audio_data, types.GeneratorType): 97 | def generate(audio): 98 | yield self.get_wav_header() 99 | for a in audio: 100 | yield a 101 | 102 | data = generate(audio_data) 103 | else: 104 | data = self.to_wav(audio_data) 105 | 106 | params = { 107 | "version": "3.0", 108 | "requestid": uuid.uuid4(), 109 | "appID": "D4D52672-91D7-4C74-8AD8-42B1D98141A5", 110 | "format": "json", 111 | "locale": language, 112 | "device.os": "wp7", 113 | "scenarios": "ulm", 114 | "instanceid": uuid.uuid4(), 115 | "result.profanitymarkup": "0", 116 | } 117 | 118 | headers = { 119 | "Authorization": "Bearer {0}".format(self.access_token), 120 | "Content-Type": "audio/wav; samplerate=16000; sourcerate=16000; trustsourcerate=true", 121 | } 122 | 123 | url = "https://speech.platform.bing.com/recognize/query" 124 | response = self.session.post(url, params=params, headers=headers, data=data) 125 | 126 | if response.status_code != 200: 127 | raise RequestError("http request error with status code {}".format(response.status_code)) 128 | 129 | result = response.json() 130 | 131 | if show_all: 132 | return result 133 | if "header" not in result or "lexical" not in result["header"]: 134 | raise ValueError('Unexpected response: {}'.format(result)) 135 | return result["header"]["lexical"] 136 | 137 | def synthesize(self, text, language="en-US", gender="Female", stream=None, chunk_size=4096): 138 | self.authenticate() 139 | 140 | if language not in self.locales.keys(): 141 | raise ValueError("language is not supported.") 142 | 143 | lang = self.locales.get(language) 144 | 145 | if gender not in ["Female", "Male", "Female2"]: 146 | gender = "Female" 147 | 148 | if len(lang) == 1: 149 | gender = lang.keys()[0] 150 | 151 | service_name = lang[gender] 152 | 153 | body = "\ 154 | %s\ 155 | " % (language, gender, service_name, text) 156 | 157 | headers = { 158 | "Content-type": "application/ssml+xml", 159 | "X-Microsoft-OutputFormat": "raw-16khz-16bit-mono-pcm", 160 | "Authorization": "Bearer " + self.access_token, 161 | "X-Search-AppId": "07D3234E49CE426DAA29772419F436CA", 162 | "X-Search-ClientID": str(uuid.uuid1()).replace('-', ''), 163 | "User-Agent": "TTSForPython" 164 | } 165 | 166 | url = "https://speech.platform.bing.com/synthesize" 167 | response = self.session.post(url, headers=headers, data=body, stream=stream) 168 | if stream: 169 | data = response.iter_content(chunk_size=chunk_size) 170 | else: 171 | data = response.content 172 | 173 | return data 174 | 175 | @staticmethod 176 | def to_wav(raw_data): 177 | # generate the WAV file contents 178 | with io.BytesIO() as wav_file: 179 | wav_writer = wave.open(wav_file, "wb") 180 | try: # note that we can't use context manager, since that was only added in Python 3.4 181 | wav_writer.setframerate(16000) 182 | wav_writer.setsampwidth(2) 183 | wav_writer.setnchannels(1) 184 | wav_writer.writeframes(raw_data) 185 | wav_data = wav_file.getvalue() 186 | finally: # make sure resources are cleaned up 187 | wav_writer.close() 188 | return wav_data 189 | 190 | @staticmethod 191 | def get_wav_header(): 192 | # generate the WAV header 193 | with io.BytesIO() as f: 194 | w = wave.open(f, "wb") 195 | try: 196 | w.setframerate(16000) 197 | w.setsampwidth(2) 198 | w.setnchannels(1) 199 | w.writeframes('') 200 | header = f.getvalue() 201 | finally: 202 | w.close() 203 | return header 204 | 205 | 206 | def main(): 207 | import timeit 208 | import logging 209 | 210 | logging.basicConfig(level=logging.DEBUG) 211 | 212 | bing = BingSpeechAPI() 213 | 214 | def test(text, stream=None): 215 | try: 216 | print('TTS:{}'.format(text)) 217 | speech = bing.synthesize(text, stream=stream) 218 | text = bing.recognize(speech, language='en-US') 219 | print('STT:{}'.format(text.encode('utf-8'))) 220 | print('Stream mode:{}'.format('yes' if stream else 'no')) 221 | except RequestError as e: 222 | print("Could not request results from Microsoft Bing Voice Recognition service; {0}".format(e)) 223 | 224 | texts = [ 225 | 'Your beliefs become your thoughts', 226 | 'Your thoughts become your words', 227 | 'Your words become your actions', 228 | 'Your actions become your habits', 229 | 'Your habits become your values', 230 | 'Your values become your destiny', 231 | ] 232 | 233 | for n, text in enumerate(texts): 234 | print('No.{} try'.format(n)) 235 | print(timeit.timeit(lambda: test(text, n & 1), number=1)) 236 | 237 | if __name__ == '__main__': 238 | main() 239 | -------------------------------------------------------------------------------- /sphinx-models/respeaker/microphone.py: -------------------------------------------------------------------------------- 1 | """ 2 | ReSpeaker Python Library 3 | Copyright (c) 2016 Seeed Technology Limited. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | 19 | import os 20 | import wave 21 | import types 22 | import collections 23 | import random 24 | import string 25 | import logging 26 | from threading import Thread, Event 27 | 28 | try: # Python 2 29 | import Queue 30 | except: # Python 3 31 | import queue as Queue 32 | 33 | import pyaudio 34 | 35 | from pixel_ring import pixel_ring 36 | from vad import vad 37 | 38 | 39 | logger = logger = logging.getLogger('mic') 40 | collecting_audio = os.getenv('COLLECTING_AUDIO', 'no') 41 | 42 | 43 | def random_string(length): 44 | return ''.join(random.choice(string.digits) for _ in range(length)) 45 | 46 | 47 | def save_as_wav(data, prefix): 48 | prefix = prefix.replace(' ', '_') 49 | filename = prefix + random_string(8) + '.wav' 50 | while os.path.isfile(filename): 51 | filename = prefix + random_string(8) + '.wav' 52 | 53 | f = wave.open(filename, 'wb') 54 | f.setframerate(16000) 55 | f.setsampwidth(2) 56 | f.setnchannels(1) 57 | f.writeframes(data) 58 | f.close() 59 | 60 | logger.info('Save audio as %s' % filename) 61 | 62 | 63 | class Microphone: 64 | sample_rate = 16000 65 | frames_per_buffer = 512 66 | listening_mask = (1 << 0) 67 | detecting_mask = (1 << 1) 68 | recording_mask = (1 << 2) 69 | 70 | def __init__(self, pyaudio_instance=None, quit_event=None, decoder=None): 71 | pixel_ring.set_color(rgb=0x400000) 72 | 73 | self.pyaudio_instance = pyaudio_instance if pyaudio_instance else pyaudio.PyAudio() 74 | 75 | self.device_index = None 76 | for i in range(self.pyaudio_instance.get_device_count()): 77 | dev = self.pyaudio_instance.get_device_info_by_index(i) 78 | name = dev['name'].encode('utf-8') 79 | # print(i, name, dev['maxInputChannels'], dev['maxOutputChannels']) 80 | if name.lower().find(b'respeaker') >= 0 and dev['maxInputChannels'] > 0: 81 | logger.info('Use {}'.format(name)) 82 | self.device_index = i 83 | break 84 | 85 | if not self.device_index: 86 | device = self.pyaudio_instance.get_default_input_device_info() 87 | self.device_index = device['index'] 88 | self.stream = self.pyaudio_instance.open( 89 | input=True, 90 | start=False, 91 | format=pyaudio.paInt16, 92 | channels=1, 93 | rate=self.sample_rate, 94 | frames_per_buffer=self.frames_per_buffer, 95 | stream_callback=self._callback, 96 | input_device_index=self.device_index, 97 | ) 98 | 99 | self.quit_event = quit_event if quit_event else Event() 100 | 101 | self.listen_queue = Queue.Queue() 102 | self.detect_queue = Queue.Queue() 103 | 104 | self.decoder = decoder if decoder else self.create_decoder() 105 | self.decoder.start_utt() 106 | 107 | self.status = 0 108 | self.active = False 109 | 110 | self.listen_history = collections.deque(maxlen=8) 111 | self.detect_history = collections.deque(maxlen=48) 112 | 113 | self.wav = None 114 | self.record_countdown = None 115 | self.listen_countdown = [0, 0] 116 | 117 | @staticmethod 118 | def create_decoder(): 119 | from pocketsphinx.pocketsphinx import Decoder 120 | 121 | path = os.path.dirname(os.path.realpath(__file__)) 122 | pocketsphinx_data = os.getenv('POCKETSPHINX_DATA', os.path.join(path, 'pocketsphinx-data')) 123 | hmm = os.getenv('POCKETSPHINX_HMM', os.path.join(pocketsphinx_data, 'hmm')) 124 | dict = os.getenv('POCKETSPHINX_DIC', os.path.join(pocketsphinx_data, 'dictionary.txt')) 125 | kws = os.getenv('POCKETSPHINX_KWS', os.path.join(pocketsphinx_data, 'keywords.txt')) 126 | 127 | config = Decoder.default_config() 128 | config.set_string('-hmm', hmm) 129 | config.set_string('-dict', dict) 130 | config.set_string('-kws', kws) 131 | # config.set_int('-samprate', SAMPLE_RATE) # uncomment if rate is not 16000. use config.set_float() on ubuntu 132 | config.set_int('-nfft', 512) 133 | config.set_float('-vad_threshold', 2.7) 134 | config.set_string('-logfn', os.devnull) 135 | 136 | return Decoder(config) 137 | 138 | def recognize(self, data): 139 | self.decoder.end_utt() 140 | self.decoder.start_utt() 141 | 142 | if not data: 143 | return '' 144 | 145 | if isinstance(data, types.GeneratorType): 146 | for d in data: 147 | self.decoder.process_raw(d, False, False) 148 | else: 149 | self.decoder.process_raw(data, False, True) 150 | 151 | hypothesis = self.decoder.hyp() 152 | if hypothesis: 153 | logger.info('Recognized {}'.format(hypothesis.hypstr)) 154 | return hypothesis.hypstr 155 | 156 | return '' 157 | 158 | def detect(self, keyword=None): 159 | self.decoder.end_utt() 160 | self.decoder.start_utt() 161 | 162 | pixel_ring.off() 163 | 164 | self.detect_history.clear() 165 | 166 | self.detect_queue.queue.clear() 167 | self.status |= self.detecting_mask 168 | self.stream.start_stream() 169 | 170 | result = None 171 | logger.info('Start detecting') 172 | while not self.quit_event.is_set(): 173 | size = self.detect_queue.qsize() 174 | if size > 4: 175 | logger.info('Too many delays, {} in queue'.format(size)) 176 | 177 | data = self.detect_queue.get() 178 | self.detect_history.append(data) 179 | self.decoder.process_raw(data, False, False) 180 | 181 | hypothesis = self.decoder.hyp() 182 | if hypothesis: 183 | logger.info('Detected {}'.format(hypothesis.hypstr)) 184 | if collecting_audio != 'no': 185 | logger.debug(collecting_audio) 186 | save_as_wav(b''.join(self.detect_history), hypothesis.hypstr) 187 | self.detect_history.clear() 188 | if keyword: 189 | if hypothesis.hypstr.find(keyword) >= 0: 190 | result = hypothesis.hypstr 191 | break 192 | else: 193 | self.decoder.end_utt() 194 | self.decoder.start_utt() 195 | self.detect_history.clear() 196 | else: 197 | result = hypothesis.hypstr 198 | break 199 | 200 | self.status &= ~self.detecting_mask 201 | self.stop() 202 | 203 | return result 204 | 205 | wakeup = detect 206 | 207 | def listen(self, duration=9, timeout=3): 208 | vad.reset() 209 | 210 | self.listen_countdown[0] = (duration * self.sample_rate + self.frames_per_buffer - 1) / self.frames_per_buffer 211 | self.listen_countdown[1] = (timeout * self.sample_rate + self.frames_per_buffer - 1) / self.frames_per_buffer 212 | 213 | self.listen_queue.queue.clear() 214 | self.status |= self.listening_mask 215 | self.start() 216 | pixel_ring.listen() 217 | 218 | logger.info('Start listening') 219 | 220 | def _listen(): 221 | try: 222 | data = self.listen_queue.get(timeout=timeout) 223 | while data and not self.quit_event.is_set(): 224 | yield data 225 | data = self.listen_queue.get(timeout=timeout) 226 | except Queue.Empty: 227 | pass 228 | 229 | self.stop() 230 | 231 | return _listen() 232 | 233 | def record(self, file_name, seconds=1800): 234 | self.wav = wave.open(file_name, 'wb') 235 | self.wav.setsampwidth(2) 236 | self.wav.setnchannels(1) 237 | self.wav.setframerate(self.sample_rate) 238 | self.record_countdown = (seconds * self.sample_rate + self.frames_per_buffer - 1) / self.frames_per_buffer 239 | self.status |= self.recording_mask 240 | self.start() 241 | 242 | def quit(self): 243 | self.status = 0 244 | self.quit_event.set() 245 | self.listen_queue.put('') 246 | if self.wav: 247 | self.wav.close() 248 | self.wav = None 249 | 250 | def start(self): 251 | if self.stream.is_stopped(): 252 | self.stream.start_stream() 253 | 254 | def stop(self): 255 | if not self.status and self.stream.is_active(): 256 | self.stream.stop_stream() 257 | 258 | def close(self): 259 | self.quit() 260 | self.stream.close() 261 | 262 | def _callback(self, in_data, frame_count, time_info, status): 263 | if self.status & self.recording_mask: 264 | pass 265 | 266 | if self.status & self.detecting_mask: 267 | self.detect_queue.put(in_data) 268 | 269 | if self.status & self.listening_mask: 270 | active = vad.is_speech(in_data) 271 | if active: 272 | if not self.active: 273 | for d in self.listen_history: 274 | self.listen_queue.put(d) 275 | self.listen_countdown[0] -= 1 276 | 277 | self.listen_history.clear() 278 | 279 | self.listen_queue.put(in_data) 280 | self.listen_countdown[0] -= 1 281 | else: 282 | if self.active: 283 | self.listen_queue.put(in_data) 284 | else: 285 | self.listen_history.append(in_data) 286 | 287 | self.listen_countdown[1] -= 1 288 | 289 | if self.listen_countdown[0] <= 0 or self.listen_countdown[1] <= 0: 290 | self.listen_queue.put('') 291 | self.status &= ~self.listening_mask 292 | pixel_ring.wait() 293 | logger.info('Stop listening') 294 | 295 | self.active = active 296 | 297 | return None, pyaudio.paContinue 298 | 299 | 300 | def task(quit_event): 301 | import time 302 | 303 | mic = Microphone(quit_event=quit_event) 304 | 305 | while not quit_event.is_set(): 306 | if mic.wakeup('respeaker'): 307 | print('Wake up') 308 | data = mic.listen() 309 | text = mic.recognize(data) 310 | if text: 311 | time.sleep(3) 312 | print('Recognized %s' % text) 313 | 314 | 315 | def main(): 316 | import time 317 | 318 | logging.basicConfig(level=logging.DEBUG) 319 | 320 | q = Event() 321 | t = Thread(target=task, args=(q,)) 322 | t.start() 323 | while True: 324 | try: 325 | time.sleep(1) 326 | except KeyboardInterrupt: 327 | print('Quit') 328 | q.set() 329 | break 330 | t.join() 331 | 332 | if __name__ == '__main__': 333 | main() 334 | --------------------------------------------------------------------------------