Skip to content

Started making a modularized version with easily swapable backends, e… #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/codetest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Lint, test and notify

on: [push]

# env:
# PYTHONPATH: ${{ github.workspace }}:${{ github.workspace }}/nlp4all:$PYTHONPATH

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
id: cpmat
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libasound-dev portaudio19-dev libportaudio2 libportaudiocpp0
echo "PYTHONPATH=${{ github.workspace }}" >> $GITHUB_ENV
python -m pip install --progress-bar off --upgrade pip
pip install --progress-bar off wheel
pip install --progress-bar off -r requirements-dev.txt
- name: Print python os env for debugging, and pip freeze
run: |
printenv
pip freeze
- name: Analysing the code with Flake8
run: |
flake8
# figure out why mypy doesn't work on github later
- name: Type checking with mypy
run: |
mypy
- name: Run tests with pytest
run: |
pytest
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
__pycache__/
.pytest_cache/
.vscode/
*.pyc
*.pyo
*.pyd
9 changes: 9 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
MIT License

Copyright (c) 2023 zeyus

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
82 changes: 0 additions & 82 deletions PythonBlueBox.py

This file was deleted.

82 changes: 26 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,75 +1,45 @@
Python 3 Blue Box DTMF Tone Generator
===================

Really simple, functional tone generator
Modernized python blue box

**How it works**

Just type on a sequence of numbers/commands and press enter, your computer will play it back for you!
You can run this in interactive mode, or from a file, pipe, stdin, etc.

**Requirements**

- PyAudio https://people.csail.mit.edu/hubert/pyaudio/
- Python 3 (tested on 3.9 - 3.11)

```pip3 install pyaudio```
**Installation**

For OSX you need port audio, which you can install from homebrew:

```brew install portaudio```
```
git clone https://github.com/zeyus/Python3BlueBox.git
cd PythonBlueBox
pip install -r requirements.txt
```

For linux or windows, see your package manager, google it or visit here:
**Usage**

http://www.portaudio.com/download.html
```
./bluebox.py -h
```

**Usage**
**Examples**

```
./PythonBlueBox.py
./bluebox.py -i
```

You'll see the '>>>' prompt.

```>>> U12345O12345```

You can use the 'U' to switch to user tones or 'O' to switch to operator tones

The current tone mapping works as follows:

```python
user_tones = {
'1',
'2',
'3',
'A',
'4',
'5',
'6',
'B',
'7',
'8',
'9',
'C',
'*',
'0',
'#',
'D',
}
op_tones = {
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'0', # 0 or "10"
'A', # 0 or "10"
'B', # 11 or ST3
'C', # 12 or ST2
'D', # KP
'E', # KP2
'F', # ST
}
```
./bluebox.py 123456789
```



**Development**

Development of different MF implementations and audio backends is extremely easy now. Just create a new class that inherits from the MF class, and register it.
Same thing for audio backends.

Currently there are two MF implementations (DTMF and MF), and two audio backends (PyAudio and Dummy).
5 changes: 5 additions & 0 deletions bluebox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env python
from bluebox import cli

if __name__ == '__main__':
cli.bluebox()
25 changes: 25 additions & 0 deletions bluebox/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import typing as t
from .freqs import BaseMF, DTMF, MF

__version__ = '0.1.0'

_MF: t.Dict[str, t.Type[BaseMF]] = {}


def register_mf(name: str, backend: t.Type[BaseMF]) -> None:
"""Register a MF set."""
_MF[name] = backend


def get_mf(name: str) -> t.Type[BaseMF]:
"""Get a MF set."""
return _MF[name]


def list_mf() -> t.List[str]:
"""List the available MF sets."""
return list(_MF.keys())


register_mf('dtmf', DTMF)
register_mf('mf', MF)
25 changes: 25 additions & 0 deletions bluebox/backends/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import typing as t
from .base import BlueboxBackend as BlueboxBackend # noqa: F401
from .backend_pyaudio import PyAudioBackend as PyAudioBackend # noqa: F401
from .backend_dummy import DummyBackend as DummyBackend # noqa: F401

_BACKENDS: t.Dict[str, t.Type[BlueboxBackend]] = {}


def register_backend(name: str, backend: t.Type[BlueboxBackend]) -> None:
"""Register a backend."""
_BACKENDS[name] = backend


def get_backend(name: str) -> t.Type[BlueboxBackend]:
"""Get a backend."""
return _BACKENDS[name]


def list_backends() -> t.List[str]:
"""List the available backends."""
return list(_BACKENDS.keys())


register_backend('pyaudio', PyAudioBackend)
register_backend('dummy', DummyBackend)
76 changes: 76 additions & 0 deletions bluebox/backends/backend_dummy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""backend_dummy.py

This file contains the dummy backend for bluebox.
This is used for testing and instead of generating
sound, it can print the data to the console, or return
it as a list.
"""

import typing as t
import logging
from .base import BlueboxBackend


class DummyBackend(BlueboxBackend):
"""DummyBackend class for the dummy backend."""

_data: t.List[float]

def __init__(
self,
sample_rate: float = 44100.0,
channels: int = 1,
amplitude: float = 1.0,
logger: t.Optional[logging.Logger] = None,
mode: str = 'print') -> None:
"""Initialize the dummy backend."""
super().__init__(sample_rate, channels, amplitude, logger)
self._mode = mode
self._data = []

def _to_bytes(self, data: t.Iterator[float]) -> t.List[float]:
"""Wrap the data in a buffer."""
_data = []
while True:
try:
d = next(data)
_data.append(d)
except StopIteration:
break

return _data

def play(self, data: t.Iterator[float], close=True) -> None:
"""Play the given data."""
d = self._to_bytes(data)
if self._mode == 'print':
print(d)
elif self._mode == 'list':
self._data += d
else:
raise ValueError(f'Invalid mode: {self._mode}')

def play_all(self, queue: t.Iterator[t.Iterator[float]]) -> None:
"""Play the given data and then stop."""
for data in queue:
self.play(data, close=False)

def stop(self) -> None:
"""Stop playing the data."""
pass

def close(self) -> None:
"""Close the backend."""
pass

def __del__(self) -> None:
"""Delete the backend."""
self.close()

def get_data(self) -> t.List[float]:
"""Get the data."""
return self._data

def clear_data(self) -> None:
"""Clear the data."""
self._data = []
Loading