morse2sound 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- morse2sound-1.0.1/LICENSE +19 -0
- morse2sound-1.0.1/PKG-INFO +182 -0
- morse2sound-1.0.1/README.md +160 -0
- morse2sound-1.0.1/pyproject.toml +38 -0
- morse2sound-1.0.1/setup.cfg +4 -0
- morse2sound-1.0.1/src/cw_enc/__init__.py +131 -0
- morse2sound-1.0.1/src/cw_enc/cw_coder.py +103 -0
- morse2sound-1.0.1/src/cw_enc/driver.py +110 -0
- morse2sound-1.0.1/src/cw_enc/paddle.py +44 -0
- morse2sound-1.0.1/src/cw_enc/paddle_cache.py +55 -0
- morse2sound-1.0.1/src/cw_enc/paddle_mksnd.py +28 -0
- morse2sound-1.0.1/src/cw_enc/paddle_samplecount.py +33 -0
- morse2sound-1.0.1/src/cw_enc/parameters.py +130 -0
- morse2sound-1.0.1/src/cw_enc/wave_blocks.py +218 -0
- morse2sound-1.0.1/src/morse2sound.egg-info/PKG-INFO +182 -0
- morse2sound-1.0.1/src/morse2sound.egg-info/SOURCES.txt +26 -0
- morse2sound-1.0.1/src/morse2sound.egg-info/dependency_links.txt +1 -0
- morse2sound-1.0.1/src/morse2sound.egg-info/entry_points.txt +2 -0
- morse2sound-1.0.1/src/morse2sound.egg-info/top_level.txt +1 -0
- morse2sound-1.0.1/tests/test_cw_coder.py +58 -0
- morse2sound-1.0.1/tests/test_driver.py +157 -0
- morse2sound-1.0.1/tests/test_paddle_cache.py +205 -0
- morse2sound-1.0.1/tests/test_paddle_mksnd.py +34 -0
- morse2sound-1.0.1/tests/test_paddle_samplecount.py +22 -0
- morse2sound-1.0.1/tests/test_parameters.py +95 -0
- morse2sound-1.0.1/tests/test_sample_skaling.py +37 -0
- morse2sound-1.0.1/tests/test_skirt.py +44 -0
- morse2sound-1.0.1/tests/test_wave_generation.py +189 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright 2024 Dr. Andreas Krüger, DJ3EI, dj3ei@famsik.de
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
|
+
this software and associated documentation files (the “Software”), to deal in
|
|
5
|
+
the Software without restriction, including without limitation the rights to
|
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
8
|
+
so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: morse2sound
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Software to convert text into Morse code sound files.
|
|
5
|
+
Author-email: "Dr. Andreas Krüger, DJ3EI" <dj3ei@famsik.de>
|
|
6
|
+
Project-URL: Homepage, https://gitlab.com/4ham/morse2sound
|
|
7
|
+
Project-URL: Repository, https://gitlab.com/4ham/morse2sound
|
|
8
|
+
Project-URL: Issues, https://gitlab.com/4ham/morse2sound/-/issues
|
|
9
|
+
Keywords: Morse,Morsecode,ham,amateur radio,cw
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: Intended Audience :: Education
|
|
17
|
+
Classifier: Topic :: Communications :: Ham Radio
|
|
18
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Sound Synthesis
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
|
|
23
|
+
# Morse2sound
|
|
24
|
+
|
|
25
|
+
## What can I do with this?
|
|
26
|
+
|
|
27
|
+
From some text, produce a sound file that contains that text, encoded
|
|
28
|
+
in Morse code.
|
|
29
|
+
|
|
30
|
+
## Background
|
|
31
|
+
|
|
32
|
+
### What's this Morse code thing?
|
|
33
|
+
|
|
34
|
+
Fast message transport via telegraphy was one of the first practical
|
|
35
|
+
uses of electricity, dating back to the middle of the 18th century.
|
|
36
|
+
|
|
37
|
+
[Samuel F. B. Morse](https://en.wikipedia.org/wiki/Samuel_Morse)
|
|
38
|
+
originally devised a scheme to transmit numbers via electric pulses,
|
|
39
|
+
with a code book that allowed translation of words to those numbers
|
|
40
|
+
and back. Later, his assistant [Alfred
|
|
41
|
+
L. Vail](https://en.wikipedia.org/wiki/Alfred_Vail) improved on this
|
|
42
|
+
by coding single letters, so the code book became obsolete. He used
|
|
43
|
+
short, long, longer, and extra long impulses. His original method is
|
|
44
|
+
still remembered as [American Morse
|
|
45
|
+
Code](https://en.wikipedia.org/wiki/American_Morse_code), although it
|
|
46
|
+
sees little use. This was again improved by [Friedrich
|
|
47
|
+
C. Gerke](https://en.wikipedia.org/wiki/Friedrich_Clemens_Gerke), who
|
|
48
|
+
simplified from four different impulse lengths to just two. Gerke's
|
|
49
|
+
code is virtually identical to what is nowadays known as International
|
|
50
|
+
Morse Code. In 1865, an international treaty established the
|
|
51
|
+
[ITU](https://www.itu.int/en/Pages/default.aspx) as the first
|
|
52
|
+
multinational standards organization, with the purpose of facilitating
|
|
53
|
+
cross-border transport of messages. That same [1865
|
|
54
|
+
treaty](http://handle.itu.int/11.1004/020.1000/5.1.61.fr.200) also
|
|
55
|
+
formalized Gerke's version of the Morse code. The coding of the
|
|
56
|
+
letters a-z and the digits 0-9 have remained unchanged since then, but
|
|
57
|
+
other things like punctuation indeed have seen some changes. The ITU
|
|
58
|
+
still exists and to this day standardizes the [international Morse
|
|
59
|
+
code](https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.1677-1-200910-I!!PDF-E.pdf),
|
|
60
|
+
among many other things.
|
|
61
|
+
|
|
62
|
+
### Is anybody still using Morse telegraphy?
|
|
63
|
+
|
|
64
|
+
Morse telegraphy is still extensively used by certain sections of the
|
|
65
|
+
HAM [amateur radio](https://en.wikipedia.org/wiki/Amateur_radio)
|
|
66
|
+
community. For obscure historical/technical reasons, Morse telegraphy
|
|
67
|
+
is generally designated as "CW".
|
|
68
|
+
|
|
69
|
+
CW is ubiquitous. On an everyday basis, any CW-capable short wave
|
|
70
|
+
receiver (such as [Twente Web-SDR](http://websdr.ewi.utwente.nl:8901/)
|
|
71
|
+
available on the internet) is likely to pick up CW signals if tuned
|
|
72
|
+
through the designated telegraphy ranges, e.g., 7000-7040 kHz. CW
|
|
73
|
+
sees extensive use among DXers (radio amateurs trying to reach rare
|
|
74
|
+
far-away places), in contests (radio amateur competitions), and by the
|
|
75
|
+
"home-brew" community (radio amateurs constructing their own
|
|
76
|
+
equipment). By its supporters, CW is considered an enjoyable mode of
|
|
77
|
+
operation.
|
|
78
|
+
|
|
79
|
+
## Usage
|
|
80
|
+
|
|
81
|
+
### What does this software do?
|
|
82
|
+
|
|
83
|
+
It takes text and produces, as a sound file or stream, a Morse encoded
|
|
84
|
+
version of that text.
|
|
85
|
+
|
|
86
|
+
The output format is that of
|
|
87
|
+
[`.wav`](https://en.wikipedia.org/wiki/WAV) files.
|
|
88
|
+
|
|
89
|
+
### Basic usage
|
|
90
|
+
|
|
91
|
+
```shell
|
|
92
|
+
morse2sound --in message.txt --wpm 12 --out message.wav
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Character set
|
|
96
|
+
|
|
97
|
+
The international Morse code.
|
|
98
|
+
|
|
99
|
+
## Remarks
|
|
100
|
+
|
|
101
|
+
### Compression
|
|
102
|
+
|
|
103
|
+
The `.wav` files generated compress quite well, with the compression
|
|
104
|
+
algorithm of your choice.
|
|
105
|
+
|
|
106
|
+
## Installation
|
|
107
|
+
|
|
108
|
+
Make sure when you say `python`, then a version 3 Python comes up.
|
|
109
|
+
E.g., `python --version` should give a 3.8 or better version.
|
|
110
|
+
|
|
111
|
+
I suggest you create and activate a `venv`. On Linux:
|
|
112
|
+
|
|
113
|
+
python3 -m venv venv
|
|
114
|
+
. venv/bin/activate
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
### Install
|
|
118
|
+
|
|
119
|
+
pip install morse2sound
|
|
120
|
+
|
|
121
|
+
## Contributing
|
|
122
|
+
|
|
123
|
+
Have `git` installed.
|
|
124
|
+
|
|
125
|
+
```shell
|
|
126
|
+
git clone https://gitlab.com/4ham/morse2sound.git
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Change to the directory `morse2sound` that was created by the above.
|
|
130
|
+
|
|
131
|
+
Create and activate a virtual environment as per the
|
|
132
|
+
[documentation](https://docs.python.org/3/library/venv.html#creating-virtual-environments)
|
|
133
|
+
pertaining to your OS.
|
|
134
|
+
|
|
135
|
+
Then:
|
|
136
|
+
|
|
137
|
+
```shell
|
|
138
|
+
pip install -r requirements_test.frozen
|
|
139
|
+
./lint.py
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Do your changes and repeat that `python ./lint.py` part in your branch
|
|
143
|
+
before starting a pull request as usual towards the
|
|
144
|
+
[repository](https://gitlab.com/4ham/morse2sound).
|
|
145
|
+
|
|
146
|
+
## To run locally
|
|
147
|
+
|
|
148
|
+
```shell
|
|
149
|
+
PYTHONPATH=$(pwd)/src python src/cw_enc/driver.py -h
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## To build locally
|
|
153
|
+
|
|
154
|
+
In an activated
|
|
155
|
+
[virtual environment](https://docs.python.org/3/library/venv.html#creating-virtual-environments):
|
|
156
|
+
|
|
157
|
+
```shell
|
|
158
|
+
pip install --upgrade build
|
|
159
|
+
rm -rf dist build morse2sound.egg-info
|
|
160
|
+
python -m build
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
To install what you built:
|
|
164
|
+
|
|
165
|
+
```shell
|
|
166
|
+
pip install dist/morse2sound-*-py3-none-any.whl
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## To upload
|
|
170
|
+
|
|
171
|
+
This is useful only for the maintainer:
|
|
172
|
+
|
|
173
|
+
* Edit the version in `pyproject.toml`
|
|
174
|
+
* `git tag -s vx.y.z`
|
|
175
|
+
* `git push origin --tags`
|
|
176
|
+
|
|
177
|
+
```shell
|
|
178
|
+
pip install --upgrade build twine
|
|
179
|
+
rm -rf dist build src/morse2sound.egg-info
|
|
180
|
+
python -m build
|
|
181
|
+
python -m twine upload dist/*
|
|
182
|
+
```
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Morse2sound
|
|
2
|
+
|
|
3
|
+
## What can I do with this?
|
|
4
|
+
|
|
5
|
+
From some text, produce a sound file that contains that text, encoded
|
|
6
|
+
in Morse code.
|
|
7
|
+
|
|
8
|
+
## Background
|
|
9
|
+
|
|
10
|
+
### What's this Morse code thing?
|
|
11
|
+
|
|
12
|
+
Fast message transport via telegraphy was one of the first practical
|
|
13
|
+
uses of electricity, dating back to the middle of the 18th century.
|
|
14
|
+
|
|
15
|
+
[Samuel F. B. Morse](https://en.wikipedia.org/wiki/Samuel_Morse)
|
|
16
|
+
originally devised a scheme to transmit numbers via electric pulses,
|
|
17
|
+
with a code book that allowed translation of words to those numbers
|
|
18
|
+
and back. Later, his assistant [Alfred
|
|
19
|
+
L. Vail](https://en.wikipedia.org/wiki/Alfred_Vail) improved on this
|
|
20
|
+
by coding single letters, so the code book became obsolete. He used
|
|
21
|
+
short, long, longer, and extra long impulses. His original method is
|
|
22
|
+
still remembered as [American Morse
|
|
23
|
+
Code](https://en.wikipedia.org/wiki/American_Morse_code), although it
|
|
24
|
+
sees little use. This was again improved by [Friedrich
|
|
25
|
+
C. Gerke](https://en.wikipedia.org/wiki/Friedrich_Clemens_Gerke), who
|
|
26
|
+
simplified from four different impulse lengths to just two. Gerke's
|
|
27
|
+
code is virtually identical to what is nowadays known as International
|
|
28
|
+
Morse Code. In 1865, an international treaty established the
|
|
29
|
+
[ITU](https://www.itu.int/en/Pages/default.aspx) as the first
|
|
30
|
+
multinational standards organization, with the purpose of facilitating
|
|
31
|
+
cross-border transport of messages. That same [1865
|
|
32
|
+
treaty](http://handle.itu.int/11.1004/020.1000/5.1.61.fr.200) also
|
|
33
|
+
formalized Gerke's version of the Morse code. The coding of the
|
|
34
|
+
letters a-z and the digits 0-9 have remained unchanged since then, but
|
|
35
|
+
other things like punctuation indeed have seen some changes. The ITU
|
|
36
|
+
still exists and to this day standardizes the [international Morse
|
|
37
|
+
code](https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.1677-1-200910-I!!PDF-E.pdf),
|
|
38
|
+
among many other things.
|
|
39
|
+
|
|
40
|
+
### Is anybody still using Morse telegraphy?
|
|
41
|
+
|
|
42
|
+
Morse telegraphy is still extensively used by certain sections of the
|
|
43
|
+
HAM [amateur radio](https://en.wikipedia.org/wiki/Amateur_radio)
|
|
44
|
+
community. For obscure historical/technical reasons, Morse telegraphy
|
|
45
|
+
is generally designated as "CW".
|
|
46
|
+
|
|
47
|
+
CW is ubiquitous. On an everyday basis, any CW-capable short wave
|
|
48
|
+
receiver (such as [Twente Web-SDR](http://websdr.ewi.utwente.nl:8901/)
|
|
49
|
+
available on the internet) is likely to pick up CW signals if tuned
|
|
50
|
+
through the designated telegraphy ranges, e.g., 7000-7040 kHz. CW
|
|
51
|
+
sees extensive use among DXers (radio amateurs trying to reach rare
|
|
52
|
+
far-away places), in contests (radio amateur competitions), and by the
|
|
53
|
+
"home-brew" community (radio amateurs constructing their own
|
|
54
|
+
equipment). By its supporters, CW is considered an enjoyable mode of
|
|
55
|
+
operation.
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
### What does this software do?
|
|
60
|
+
|
|
61
|
+
It takes text and produces, as a sound file or stream, a Morse encoded
|
|
62
|
+
version of that text.
|
|
63
|
+
|
|
64
|
+
The output format is that of
|
|
65
|
+
[`.wav`](https://en.wikipedia.org/wiki/WAV) files.
|
|
66
|
+
|
|
67
|
+
### Basic usage
|
|
68
|
+
|
|
69
|
+
```shell
|
|
70
|
+
morse2sound --in message.txt --wpm 12 --out message.wav
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Character set
|
|
74
|
+
|
|
75
|
+
The international Morse code.
|
|
76
|
+
|
|
77
|
+
## Remarks
|
|
78
|
+
|
|
79
|
+
### Compression
|
|
80
|
+
|
|
81
|
+
The `.wav` files generated compress quite well, with the compression
|
|
82
|
+
algorithm of your choice.
|
|
83
|
+
|
|
84
|
+
## Installation
|
|
85
|
+
|
|
86
|
+
Make sure when you say `python`, then a version 3 Python comes up.
|
|
87
|
+
E.g., `python --version` should give a 3.8 or better version.
|
|
88
|
+
|
|
89
|
+
I suggest you create and activate a `venv`. On Linux:
|
|
90
|
+
|
|
91
|
+
python3 -m venv venv
|
|
92
|
+
. venv/bin/activate
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
### Install
|
|
96
|
+
|
|
97
|
+
pip install morse2sound
|
|
98
|
+
|
|
99
|
+
## Contributing
|
|
100
|
+
|
|
101
|
+
Have `git` installed.
|
|
102
|
+
|
|
103
|
+
```shell
|
|
104
|
+
git clone https://gitlab.com/4ham/morse2sound.git
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Change to the directory `morse2sound` that was created by the above.
|
|
108
|
+
|
|
109
|
+
Create and activate a virtual environment as per the
|
|
110
|
+
[documentation](https://docs.python.org/3/library/venv.html#creating-virtual-environments)
|
|
111
|
+
pertaining to your OS.
|
|
112
|
+
|
|
113
|
+
Then:
|
|
114
|
+
|
|
115
|
+
```shell
|
|
116
|
+
pip install -r requirements_test.frozen
|
|
117
|
+
./lint.py
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Do your changes and repeat that `python ./lint.py` part in your branch
|
|
121
|
+
before starting a pull request as usual towards the
|
|
122
|
+
[repository](https://gitlab.com/4ham/morse2sound).
|
|
123
|
+
|
|
124
|
+
## To run locally
|
|
125
|
+
|
|
126
|
+
```shell
|
|
127
|
+
PYTHONPATH=$(pwd)/src python src/cw_enc/driver.py -h
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## To build locally
|
|
131
|
+
|
|
132
|
+
In an activated
|
|
133
|
+
[virtual environment](https://docs.python.org/3/library/venv.html#creating-virtual-environments):
|
|
134
|
+
|
|
135
|
+
```shell
|
|
136
|
+
pip install --upgrade build
|
|
137
|
+
rm -rf dist build morse2sound.egg-info
|
|
138
|
+
python -m build
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
To install what you built:
|
|
142
|
+
|
|
143
|
+
```shell
|
|
144
|
+
pip install dist/morse2sound-*-py3-none-any.whl
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## To upload
|
|
148
|
+
|
|
149
|
+
This is useful only for the maintainer:
|
|
150
|
+
|
|
151
|
+
* Edit the version in `pyproject.toml`
|
|
152
|
+
* `git tag -s vx.y.z`
|
|
153
|
+
* `git push origin --tags`
|
|
154
|
+
|
|
155
|
+
```shell
|
|
156
|
+
pip install --upgrade build twine
|
|
157
|
+
rm -rf dist build src/morse2sound.egg-info
|
|
158
|
+
python -m build
|
|
159
|
+
python -m twine upload dist/*
|
|
160
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
authors = [
|
|
7
|
+
{name = "Dr. Andreas Krüger, DJ3EI", email = "dj3ei@famsik.de"},
|
|
8
|
+
]
|
|
9
|
+
name = "morse2sound"
|
|
10
|
+
description = "Software to convert text into Morse code sound files."
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
|
+
keywords = ["Morse", "Morsecode", "ham", "amateur radio", "cw"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Environment :: Console",
|
|
20
|
+
"Intended Audience :: End Users/Desktop",
|
|
21
|
+
"Intended Audience :: Education",
|
|
22
|
+
"Topic :: Communications :: Ham Radio",
|
|
23
|
+
"Topic :: Multimedia :: Sound/Audio :: Sound Synthesis"
|
|
24
|
+
]
|
|
25
|
+
version = "1.0.1"
|
|
26
|
+
dependencies = []
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://gitlab.com/4ham/morse2sound"
|
|
30
|
+
Repository = "https://gitlab.com/4ham/morse2sound"
|
|
31
|
+
Issues = "https://gitlab.com/4ham/morse2sound/-/issues"
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
morse2sound = "cw_enc.driver:drive"
|
|
35
|
+
|
|
36
|
+
[tool.isort]
|
|
37
|
+
profile = "black"
|
|
38
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import wave
|
|
3
|
+
from types import TracebackType
|
|
4
|
+
from typing import IO, Literal, Optional, Type
|
|
5
|
+
|
|
6
|
+
from cw_enc.cw_coder import (
|
|
7
|
+
CWCoder,
|
|
8
|
+
ignore_char,
|
|
9
|
+
raise_on_notmorseable_char,
|
|
10
|
+
replace_char_with_questionmark,
|
|
11
|
+
)
|
|
12
|
+
from cw_enc.paddle_cache import PaddleCache
|
|
13
|
+
from cw_enc.paddle_mksnd import PaddleMksnd
|
|
14
|
+
from cw_enc.paddle_samplecount import PaddleSampleCount
|
|
15
|
+
from cw_enc.parameters import ParameterError, Parameters
|
|
16
|
+
from cw_enc.wave_blocks import make_wave_blocks
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def cw_enc(
|
|
20
|
+
intext: str = "Hello, world!",
|
|
21
|
+
infile: Optional[str] = None,
|
|
22
|
+
on_unmorseable: str = "?",
|
|
23
|
+
out: str = "cwenc_out.wav",
|
|
24
|
+
samples_per_second: int = 8000,
|
|
25
|
+
amplitude: float | int = 2**15 - 2,
|
|
26
|
+
channels: Literal[1] | Literal[2] = 1,
|
|
27
|
+
wpm: int | float = 12.0,
|
|
28
|
+
farnsworth_wpm: None | int | float = None,
|
|
29
|
+
f: int | float = 700,
|
|
30
|
+
f_delta: int | float = 1,
|
|
31
|
+
skirt_time: float = 5e-3,
|
|
32
|
+
skirt_shape: Literal["RC", "L"] = "RC",
|
|
33
|
+
) -> None:
|
|
34
|
+
"""The main driver program to produce Morse code .wav - files from characters."""
|
|
35
|
+
|
|
36
|
+
if infile is not None and intext is not None:
|
|
37
|
+
raise ParameterError("intext (when infile is also set)", intext)
|
|
38
|
+
elif intext is not None:
|
|
39
|
+
text = intext
|
|
40
|
+
elif infile is not None:
|
|
41
|
+
with open(infile) as inf:
|
|
42
|
+
text = inf.read()
|
|
43
|
+
else:
|
|
44
|
+
raise FileNotFoundError(
|
|
45
|
+
f"No text specified to convert to sound. Try {sys.argv[0]} -h"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
pms = Parameters(
|
|
49
|
+
samples_per_second=samples_per_second,
|
|
50
|
+
amplitude=amplitude,
|
|
51
|
+
channels=channels,
|
|
52
|
+
wpm=wpm,
|
|
53
|
+
farnsworth_wpm=farnsworth_wpm,
|
|
54
|
+
f=f,
|
|
55
|
+
f_delta=f_delta,
|
|
56
|
+
skirt_time=skirt_time,
|
|
57
|
+
skirt_shape=skirt_shape,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
binout: str | IO[bytes]
|
|
61
|
+
if out == "-":
|
|
62
|
+
binout = sys.stdout.buffer
|
|
63
|
+
else:
|
|
64
|
+
binout = out
|
|
65
|
+
|
|
66
|
+
with wave.open(binout, mode="wb") as wavout:
|
|
67
|
+
wavout.setnchannels(pms.channels)
|
|
68
|
+
wavout.setsampwidth(2)
|
|
69
|
+
wavout.setframerate(pms.samples_per_second)
|
|
70
|
+
|
|
71
|
+
# We will have a "paddle" that is given dits, dahs, between-character spaces, and between-words spaces.
|
|
72
|
+
|
|
73
|
+
# Waveblock, containing pre-canned byte sequence for those four.
|
|
74
|
+
wb = make_wave_blocks(pms)
|
|
75
|
+
|
|
76
|
+
class WriteWavData:
|
|
77
|
+
"""Glue code: A simple object that has a write() method expecting bytes."""
|
|
78
|
+
|
|
79
|
+
def write(self, b: bytes) -> None:
|
|
80
|
+
wavout.writeframesraw(b)
|
|
81
|
+
|
|
82
|
+
# The paddle that actually does the "heavy lifting", writing the data to the file.
|
|
83
|
+
paddle_write_data = PaddleMksnd(what_to_write=wb, consumer=WriteWavData())
|
|
84
|
+
|
|
85
|
+
# But first, deal with a complication: .wav files want to know how long they're going to be.
|
|
86
|
+
class WriteWavHeader(PaddleSampleCount):
|
|
87
|
+
"""A subclass of Paddle that can count how many samples will be written."""
|
|
88
|
+
|
|
89
|
+
def __exit__(
|
|
90
|
+
self,
|
|
91
|
+
exc_type: Optional[Type[BaseException]],
|
|
92
|
+
exc_val: Optional[BaseException],
|
|
93
|
+
exc_tb: Optional[TracebackType],
|
|
94
|
+
) -> Literal[False]:
|
|
95
|
+
# On close, write the number of samples to the wav file:
|
|
96
|
+
wavout.setnframes(self._count)
|
|
97
|
+
return super().__exit__(exc_type, exc_val, exc_tb)
|
|
98
|
+
|
|
99
|
+
paddle_write_header = WriteWavHeader(
|
|
100
|
+
dit_samples=int(len(wb.dit) / pms.channels / 2),
|
|
101
|
+
dah_samples=int(len(wb.dah) / pms.channels / 2),
|
|
102
|
+
ci_silence_samples=int(len(wb.csilence) / pms.channels / 2),
|
|
103
|
+
iw_silence_samples=int(len(wb.iwsilence) / pms.channels / 2),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# We go through the entire message twice:
|
|
107
|
+
# First time for sample counting, secondly for real writing of the data.
|
|
108
|
+
#
|
|
109
|
+
# This is what a PaddleCache can do for us:
|
|
110
|
+
# It patiently stores all the information given to it, until it is closed.
|
|
111
|
+
# When it is closed (as a context manager), it will replay that information
|
|
112
|
+
# on each of the paddles handed in, in order, one after the other.
|
|
113
|
+
with PaddleCache([paddle_write_header, paddle_write_data]) as paddle:
|
|
114
|
+
# Someone needs to convert the morse code to strings of dits and dahs
|
|
115
|
+
# and hand those over to the paddle.
|
|
116
|
+
# That is what a CWCoder does.
|
|
117
|
+
if on_unmorseable == "False":
|
|
118
|
+
unmorseable_character_strategy = raise_on_notmorseable_char
|
|
119
|
+
elif on_unmorseable == "?":
|
|
120
|
+
unmorseable_character_strategy = replace_char_with_questionmark
|
|
121
|
+
elif on_unmorseable == "x":
|
|
122
|
+
unmorseable_character_strategy = ignore_char
|
|
123
|
+
else:
|
|
124
|
+
raise ParameterError("on_unmorseable", on_unmorseable)
|
|
125
|
+
coder = CWCoder(paddle=paddle, on_missing=unmorseable_character_strategy)
|
|
126
|
+
for c in text:
|
|
127
|
+
coder.code(c)
|
|
128
|
+
|
|
129
|
+
# For good luck and stability:
|
|
130
|
+
if out == "-":
|
|
131
|
+
sys.stdout.buffer.flush()
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
from cw_enc.paddle import Paddle
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def raise_on_notmorseable_char(char: str) -> str:
|
|
7
|
+
raise KeyError('Character "{}" not representable in Morse code.'.format(char))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def ignore_char(char: str) -> str:
|
|
11
|
+
return "x"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def replace_char_with_questionmark(char: str) -> str:
|
|
15
|
+
return "..--.."
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CWCoder:
|
|
19
|
+
"""Internal class that knows how to call dit and dah and so on, given a message character."""
|
|
20
|
+
|
|
21
|
+
codelist = {
|
|
22
|
+
"a": ".-",
|
|
23
|
+
"b": "-...",
|
|
24
|
+
"c": "-.-.",
|
|
25
|
+
"d": "-..",
|
|
26
|
+
"e": ".",
|
|
27
|
+
"f": "..-.",
|
|
28
|
+
"g": "--.",
|
|
29
|
+
"h": "....",
|
|
30
|
+
"i": "..",
|
|
31
|
+
"j": ".---",
|
|
32
|
+
"k": "-.-",
|
|
33
|
+
"l": ".-..",
|
|
34
|
+
"m": "--",
|
|
35
|
+
"n": "-.",
|
|
36
|
+
"o": "---",
|
|
37
|
+
"p": ".--.",
|
|
38
|
+
"q": "--.-",
|
|
39
|
+
"r": ".-.",
|
|
40
|
+
"s": "...",
|
|
41
|
+
"t": "-",
|
|
42
|
+
"u": "..-",
|
|
43
|
+
"v": "...-",
|
|
44
|
+
"w": ".--",
|
|
45
|
+
"x": "-..-",
|
|
46
|
+
"y": "-.--",
|
|
47
|
+
"z": "--..",
|
|
48
|
+
"0": "-----",
|
|
49
|
+
"1": ".----",
|
|
50
|
+
"2": "..---",
|
|
51
|
+
"3": "...--",
|
|
52
|
+
"4": "....-",
|
|
53
|
+
"5": ".....",
|
|
54
|
+
"6": "-....",
|
|
55
|
+
"7": "--...",
|
|
56
|
+
"8": "---..",
|
|
57
|
+
"9": "----.",
|
|
58
|
+
":": "---...",
|
|
59
|
+
"=": "-...-",
|
|
60
|
+
"-": "-....-",
|
|
61
|
+
"?": "..--..",
|
|
62
|
+
".": ".-.-.-",
|
|
63
|
+
",": "--..--",
|
|
64
|
+
"(": "-.--.",
|
|
65
|
+
")": "-.--.-",
|
|
66
|
+
"+": ".-.-.",
|
|
67
|
+
"/": "-..-.",
|
|
68
|
+
"@": ".--.-.",
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
paddle: Paddle,
|
|
74
|
+
on_missing: Callable[[str], str] = raise_on_notmorseable_char,
|
|
75
|
+
codelist: dict[str, str] = codelist,
|
|
76
|
+
) -> None:
|
|
77
|
+
self.paddle = paddle
|
|
78
|
+
|
|
79
|
+
class Codelist(dict[str, str]):
|
|
80
|
+
def __missing__(self, char: str) -> str:
|
|
81
|
+
return on_missing(char)
|
|
82
|
+
|
|
83
|
+
self.codelist = Codelist(codelist)
|
|
84
|
+
|
|
85
|
+
def code(self, char: str, csilence: bool = True) -> None:
|
|
86
|
+
if char == " " or char == "\n" or char == "\t":
|
|
87
|
+
self.paddle.iwsilence()
|
|
88
|
+
else:
|
|
89
|
+
code = self.codelist[char.lower()]
|
|
90
|
+
if code == "x":
|
|
91
|
+
pass
|
|
92
|
+
else:
|
|
93
|
+
for dida in self.codelist[char.lower()]:
|
|
94
|
+
if dida == ".":
|
|
95
|
+
self.paddle.dit()
|
|
96
|
+
elif dida == "-":
|
|
97
|
+
self.paddle.dah()
|
|
98
|
+
else:
|
|
99
|
+
raise RuntimeError(
|
|
100
|
+
'Morse symbol "{}" is neither "." nor "-".'.format(dida)
|
|
101
|
+
)
|
|
102
|
+
if csilence:
|
|
103
|
+
self.paddle.csilence()
|