daadsp 0.1.0__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.
- daadsp-0.1.0/.gitignore +218 -0
- daadsp-0.1.0/PKG-INFO +3 -0
- daadsp-0.1.0/pyproject.toml +10 -0
- daadsp-0.1.0/src/daadsp/__init__.py +6 -0
- daadsp-0.1.0/src/daadsp/amp.py +51 -0
- daadsp-0.1.0/src/daadsp/freq.py +239 -0
- daadsp-0.1.0/src/daadsp/misc.py +120 -0
- daadsp-0.1.0/src/daadsp/osc.py +14 -0
- daadsp-0.1.0/src/daadsp/series.py +88 -0
- daadsp-0.1.0/src/daadsp/xenharmonic.py +60 -0
daadsp-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
# Temporary file for partial code execution
|
|
204
|
+
tempCodeRunnerFile.py
|
|
205
|
+
|
|
206
|
+
# Ruff stuff:
|
|
207
|
+
.ruff_cache/
|
|
208
|
+
|
|
209
|
+
# PyPI configuration file
|
|
210
|
+
.pypirc
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
216
|
+
|
|
217
|
+
# Streamlit
|
|
218
|
+
.streamlit/secrets.toml
|
daadsp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import math as _math
|
|
2
|
+
|
|
3
|
+
def rpow_to_pow(rpow):
|
|
4
|
+
'convert a root-power quantity to power. returns rpow²'
|
|
5
|
+
return rpow * rpow
|
|
6
|
+
|
|
7
|
+
def pow_to_rpow(pow):
|
|
8
|
+
'convert a root-power quantity to power. returns √pow'
|
|
9
|
+
return _math.sqrt(pow)
|
|
10
|
+
|
|
11
|
+
def db_to_bel(db):
|
|
12
|
+
'convert db to bel. returns db / 10'
|
|
13
|
+
return db / 10
|
|
14
|
+
|
|
15
|
+
def bel_to_db(bel):
|
|
16
|
+
'convert bel to decibel. returns bel * 10'
|
|
17
|
+
return bel * 10
|
|
18
|
+
|
|
19
|
+
def db_to_np(db):
|
|
20
|
+
'convert decibels to nepers. by default uses db * 20 * log⏨(e) which assumes decibels describe power ratios and nepers describe root-power ratios'
|
|
21
|
+
# db / (20 * _math.log10(_math.e))
|
|
22
|
+
return db * 0.11512925464970228420089957273421821038036201556142658733102210701271816669945107
|
|
23
|
+
|
|
24
|
+
def np_to_db(np):
|
|
25
|
+
'convert nepers to decibels. by default uses np / (20 * log⏨(e)) which assumes decibels describe power ratios and nepers describe root-power ratios'
|
|
26
|
+
# np * 20 * _math.log10(_math.e)
|
|
27
|
+
return np * 8.6858896380650365530225783783321016458647830411094563350443397173668347208571878
|
|
28
|
+
|
|
29
|
+
def rpow_to_db(rpow, *, reference_rpow = 1, factor = 20, base = 10):
|
|
30
|
+
'convert a ratio of root-power quantities like amplitude or voltage to decibels. by default uses 20 * log⏨(rpow)'
|
|
31
|
+
return factor * _math.log(rpow / reference_rpow, base)
|
|
32
|
+
|
|
33
|
+
def db_to_rpow(db, *, reference_rpow = 1, factor = 20, base = 10):
|
|
34
|
+
'convert decibels to a ratio of root-power quantities like amplitude or voltage. by default uses 10 ^ (db / 20)'
|
|
35
|
+
return reference_rpow * base ** (db / factor)
|
|
36
|
+
|
|
37
|
+
def pow_to_db(pow, *, reference_pow = 1, factor = 10, base = 10):
|
|
38
|
+
'convert a power ratio to decibel. by default uses 10 * log⏨(pow)'
|
|
39
|
+
return factor * _math.log(pow / reference_pow, base)
|
|
40
|
+
|
|
41
|
+
def db_to_pow(db, *, reference_pow = 1, factor = 10, base = 10):
|
|
42
|
+
'convert decibels to a power ratio. by default uses 10 ^ (db / 10)'
|
|
43
|
+
return reference_pow * base ** (db / factor)
|
|
44
|
+
|
|
45
|
+
def rpow_to_np(rpow, *, reference_rpow = 1, factor = 1, base = _math.e):
|
|
46
|
+
'convert a ratio of root-power quantities into nepers'
|
|
47
|
+
return factor * _math.log(rpow / reference_rpow, base)
|
|
48
|
+
|
|
49
|
+
def np_to_rpow(np, *, reference_rpow = 1, factor = 1, base = _math.e):
|
|
50
|
+
'convert nepers to a ratio of root-power quantities'
|
|
51
|
+
return reference_rpow * base ** (np / factor)
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# utilities related to frequency
|
|
2
|
+
|
|
3
|
+
import math as _math
|
|
4
|
+
from daamath import soft_log as _soft_log, soft_exp as _soft_exp
|
|
5
|
+
from typing import Literal as _Literal
|
|
6
|
+
|
|
7
|
+
def hz_to_midi(hz, *, reference_hz = 440, offset_midi = 69) -> float:
|
|
8
|
+
'convert a midi note to Hz. by default uses 69 (nice) + 12 * log₂(hz / 440)'
|
|
9
|
+
return offset_midi + 12 * _math.log2(hz / reference_hz)
|
|
10
|
+
|
|
11
|
+
def midi_to_hz(midi, *, reference_hz = 440, offset_midi = 69) -> float:
|
|
12
|
+
'convert a midi note to Hz. by default uses 440 * 2 ^ ((midi - 69nice) / 12)'
|
|
13
|
+
return reference_hz * 2 ** ((midi - offset_midi) / 12)
|
|
14
|
+
|
|
15
|
+
def hz_to_str(
|
|
16
|
+
hz,
|
|
17
|
+
char_double_flat : str = '𝄫',
|
|
18
|
+
char_flat : str = '♭',
|
|
19
|
+
char_natural : str = '♮',
|
|
20
|
+
char_sharp : str = '♯',
|
|
21
|
+
char_double_sharp: str = '𝄪',
|
|
22
|
+
notes : str = 'CDEFGABC',
|
|
23
|
+
) -> str:
|
|
24
|
+
'convert frequency in hertz to a 12-TET '
|
|
25
|
+
return notes[(midi + offset) % 7] + str((midi + offset) // 7)
|
|
26
|
+
"""if isinstance(midi, int):
|
|
27
|
+
elif isinstance(midi, float):
|
|
28
|
+
temp = round(midi)
|
|
29
|
+
else:
|
|
30
|
+
raise ValueError("expected int or float")"""
|
|
31
|
+
|
|
32
|
+
def hz_to_oct(hz, base = 2) -> float:
|
|
33
|
+
'convert frequency in hz to log2 frequency. by default uses log₂(hz)'
|
|
34
|
+
return _math.log(base, hz)
|
|
35
|
+
|
|
36
|
+
def oct_to_hz(oct, base = 2) -> int | float:
|
|
37
|
+
'convert log2 frequency to frequency in hz. by default uses 2ʰᶻ'
|
|
38
|
+
return base ** oct
|
|
39
|
+
|
|
40
|
+
def hz_to_mel(hz, softness = 700, low_x = 0, high_x = 1000, low_y = 0, high_y = 1000):
|
|
41
|
+
"""convert frequency in hz to mel scale. it is based on O'Shaughnessy's formula from 1987:
|
|
42
|
+
|
|
43
|
+
2595 * log⏨(1 + hz / 700) where 2595 should actually be 1000 / log⏨(1 + 1000 / 700)
|
|
44
|
+
"""
|
|
45
|
+
return _soft_log(hz, softness = softness, low_x = low_x, high_x = high_x, low_y = low_y, high_y = high_y)
|
|
46
|
+
|
|
47
|
+
def mel_to_hz(mel, softness = 700, low_x = 0, high_x = 1000, low_y = 0, high_y = 1000):
|
|
48
|
+
'inverse of hz_to_mel'
|
|
49
|
+
return _soft_exp(mel, softness = softness, low_x = low_x, high_x = high_x, low_y = low_y, high_y = high_y)
|
|
50
|
+
#return break_hz * ((1 + norm_hz / break_hz) ** (mel / norm_hz) - 1)
|
|
51
|
+
|
|
52
|
+
def hz_to_mel2(hz, break_hz = 1000, norm_hz = 1000, base = _math.e):
|
|
53
|
+
"""convert frequency in hz to mel scale. uses a two-piece fit. the derivative w.r.t. hz is continuous as well, as long as you dont change the base of the logarithm (though the actual function will still be continuous)
|
|
54
|
+
|
|
55
|
+
default formula: hz if hz ≤ 1000 else (1 + ln(hz / 1000)) * 1000
|
|
56
|
+
|
|
57
|
+
this is based on a 1000Hz normalized version of slanley's formula, except its mathematically elegant so it avoids wierd constants:
|
|
58
|
+
|
|
59
|
+
3 * hz / 200 if hz < 1000 else 15 + 27 * log₆.₄(hz / 1000)
|
|
60
|
+
"""
|
|
61
|
+
return hz if hz <= break_hz else (1 + _math.log(hz / break_hz, base)) / (1 + _math.log(norm_hz / break_hz, base)) * norm_hz
|
|
62
|
+
|
|
63
|
+
def mel_to_hz2(mel, break_hz = 1000, norm_hz = 1000, base = _math.e):
|
|
64
|
+
"""inverse of hz_to_mel2. converts mel scale to frequency in hz. by default uses:
|
|
65
|
+
|
|
66
|
+
mel if mel ≤ 1000 else 1000 * e ** (mel / 1000 - 1)
|
|
67
|
+
"""
|
|
68
|
+
return mel if mel <= break_hz else break_hz * base ** (mel / norm_hz * (1 + _math.log(norm_hz / break_hz, base)) - 1)
|
|
69
|
+
|
|
70
|
+
_beranek_1949_hz = [20, 160, 394, 670, 1000, 1420, 1900, 2450, 3120, 4000, 5100, 6600, 9000, 14000]
|
|
71
|
+
_beranek_1949_mel = [0, 250, 500, 750, 1000, 1250, 1500, 1750, 2000, 2250, 2500, 2750, 3000, 3250]
|
|
72
|
+
_umesh_1999_hz = [40, 161, 200, 404, 693, 867, 1000, 2022, 3000, 3393, 4109, 5526, 6500, 7743, 12000]
|
|
73
|
+
_umesh_1999_mel = [43, 257, 300, 514, 771, 928, 1000, 1542, 2000, 2142, 2314, 2600, 2771, 2914, 3228]
|
|
74
|
+
"""
|
|
75
|
+
def hz_to_mel3(hz, = _beranek_1949_hz, = _beranek_1949_mel):
|
|
76
|
+
'convert frequency in hz to mel scale. uses a linear interpolation of the data from beranek 1949'
|
|
77
|
+
def hz_to_mel4(hz, = _umesh_1999_hz, = _umesh_1999_mel):
|
|
78
|
+
'convert frequency in hz to mel scale. uses a linear interpolation of the data from umesh 1999'
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def hz_to_bark(hz: float, norm_hz: float = 0, formula: _Literal['zt1', 'zt2', 'zt3', 'tjomov', 'schroeder', 'ht1', 'ht2', 'wsg'] = 'ht2') -> float:
|
|
82
|
+
'convert frequency in Hertz to the bark scale'
|
|
83
|
+
match formula:
|
|
84
|
+
case 'zt2':
|
|
85
|
+
result = 14.2 * _math.log10(hz / 1000) + 8.7
|
|
86
|
+
case 'zt3':
|
|
87
|
+
result = 6.578 * _math.log(hz) - 36.99
|
|
88
|
+
case 'zt1':
|
|
89
|
+
result = 13 * arctan(0.00076 * hz) + 3.5 * arctan((hz / 7500) ** 2)
|
|
90
|
+
case 'ht1':
|
|
91
|
+
result = 26.81 * hz / (1960 + hz) - 0.53
|
|
92
|
+
case 'ht2':
|
|
93
|
+
bark = 26.81 * hz / (1960 + hz) - 0.53
|
|
94
|
+
if bark < 2:
|
|
95
|
+
result = bark + 0.15 * (2 - bark)
|
|
96
|
+
elif bark > 20.1:
|
|
97
|
+
result = bark + 0.22 * (bark - 20.1)
|
|
98
|
+
else:
|
|
99
|
+
result = bark
|
|
100
|
+
case 'tjomov':
|
|
101
|
+
result = 600 * _math.sinh(hz / 6.7) + 20
|
|
102
|
+
case 'schroeder':
|
|
103
|
+
result = 650 * _math.sinh(hz / 7)
|
|
104
|
+
case 'wsg':
|
|
105
|
+
result = 6 * _math.asinh(hz / 600)
|
|
106
|
+
case _:
|
|
107
|
+
raise ValueError('unrecognized formula')
|
|
108
|
+
|
|
109
|
+
if norm_hz > 0:
|
|
110
|
+
return result / hz_to_bark(norm_hz, formula = formula) * norm_hz
|
|
111
|
+
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
def bark_to_hz(bark: float, norm_hz: float = 0, formula: str = 'ht2') -> float:
|
|
115
|
+
'inverse of hz_to_bark. converts bark scale values to frequency in Hertz'
|
|
116
|
+
if norm_hz > 0:
|
|
117
|
+
bark = bark * hz_to_bark(norm_hz, formula = formula) / norm_hz
|
|
118
|
+
|
|
119
|
+
match formula:
|
|
120
|
+
case 'zt2':
|
|
121
|
+
result = 10 ** ((bark - 33.9) / 14.2)
|
|
122
|
+
case 'zt3':
|
|
123
|
+
result = _math.exp((bark + 36.99) / 6.578)
|
|
124
|
+
case 'ht1':
|
|
125
|
+
if hasattr(_math, 'fma'):
|
|
126
|
+
result = _math.fma(bark, 1960, 0.53) / (26.28 - bark)
|
|
127
|
+
else:
|
|
128
|
+
result = 1960 * (bark + 0.53) / (26.28 - bark)
|
|
129
|
+
case 'ht2':
|
|
130
|
+
if bark < 2:
|
|
131
|
+
bark = (bark - 0.3) / 0.85
|
|
132
|
+
elif bark > 20.1:
|
|
133
|
+
bark = (bark + 4.422) / 1.22
|
|
134
|
+
result = 1960 * (bark + 0.53) / (26.28 - bark)
|
|
135
|
+
case 'tjomov':
|
|
136
|
+
result = 6.7 * _math.asinh((bark - 20) / 600)
|
|
137
|
+
case 'schroeder':
|
|
138
|
+
result = 7 * _math.asinh(bark / 650)
|
|
139
|
+
case 'wsg':
|
|
140
|
+
result = 600 * _math.sinh(bark / 6)
|
|
141
|
+
case _:
|
|
142
|
+
raise ValueError('unrecognized formula')
|
|
143
|
+
|
|
144
|
+
return result
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
|
148
|
+
60 150 250 350 450 570 700 840 1000 1170 1370 1600 1850 2150 2500 2900 3400 4000 4800 5800 7000 8500 10500 13500
|
|
149
|
+
20 100 200 300 400 510 630 770 920 1080 1270 1480 1720 2000 2320 2700 3150 3700 4400 5300 6400 7700 9500 12000 15500
|
|
150
|
+
80 100 100 100 110 120 140 150 160 190 210 240 280 320 380 450 550 700 900 1100 1300 1800 2500 3500
|
|
151
|
+
no. center cutoff bandwidth
|
|
152
|
+
20
|
|
153
|
+
1 60 100 80
|
|
154
|
+
2 150 200 100
|
|
155
|
+
3 250 300 100
|
|
156
|
+
4 350 400 100
|
|
157
|
+
5 450 510 110
|
|
158
|
+
6 570 630 120
|
|
159
|
+
7 700 770 140
|
|
160
|
+
8 840 920 150
|
|
161
|
+
9 1000 1080 160
|
|
162
|
+
10 1170 1270 190
|
|
163
|
+
11 1370 1480 210
|
|
164
|
+
12 1600 1720 240
|
|
165
|
+
13 1850 2000 280
|
|
166
|
+
14 2150 2320 320
|
|
167
|
+
15 2500 2700 380
|
|
168
|
+
16 2900 3150 450
|
|
169
|
+
17 3400 3700 550
|
|
170
|
+
18 4000 4400 700
|
|
171
|
+
19 4800 5300 900
|
|
172
|
+
20 5800 6400 1100
|
|
173
|
+
21 7000 7700 1300
|
|
174
|
+
22 8500 9500 1800
|
|
175
|
+
23 10500 12000 2500
|
|
176
|
+
24 13500 15500 3500
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def hz_to_erbs(hz, norm_hz = 0):
|
|
180
|
+
"""convert hz to erbs (also known as caws)
|
|
181
|
+
|
|
182
|
+
formula: A * log1p(B * (hz / (hz + C))) which is mathematically equivalent to A * ln(1 + (hz * B) / (hz + C))
|
|
183
|
+
where: A = 100000 / √80109737
|
|
184
|
+
≈ 11.1726796613307508659409591598553369046875791603224092625910488…
|
|
185
|
+
B = (9339 + √80109737) / (9339 - √80109737) - 1
|
|
186
|
+
≈ 46.0653791116351008187372868311781991418099625357253121238190101…
|
|
187
|
+
C = (9339 + √80109737) / 1.246
|
|
188
|
+
≈ 14678.4946168094343650325240435293097711148959365979858846912749…
|
|
189
|
+
the constants are precomputed using an arbitrary precision calculator so they are as precise as a double can be
|
|
190
|
+
this is a numerically stable version of the integral of reciprocal of 6.23khz² + 93.39khz + 28.52. with the integration constant set so that hz_to_erbs(0) = 0, the formula is ln(abs(1.246 * hz + 9339 - √80109737) / abs(1.246 * hz + 9339 + √80109737)) * 100000 / √80109737 + ln(abs(9339 - √80109737) / abs(9339 + √80109737)) * 10000/ √80109737. derive the rest yourself
|
|
191
|
+
|
|
192
|
+
an alternate formula is: log((hz + A) / (hz + B), base = C) + D
|
|
193
|
+
where: A = (9339 - √80109737) / 1.246
|
|
194
|
+
≈ 311.87456457098324…
|
|
195
|
+
B = (9339 + √80109737) / 1.246
|
|
196
|
+
≈ 14678.494616809434…
|
|
197
|
+
C = e ^ (100000 / √80109737)
|
|
198
|
+
≈ 1.0936317547750625…
|
|
199
|
+
D = log(B / A, base = C) = ln(B / A) * 10000 / √80109737
|
|
200
|
+
≈ 43.031996702539864…
|
|
201
|
+
"""
|
|
202
|
+
if norm_hz > 0:
|
|
203
|
+
return _math.log1p(46.0653791116351008187372868311781991418099625357253121238190101 * (hz / (hz + 14678.4946168094343650325240435293097711148959365979858846912749))) / _math.log1p(46.0653791116351008187372868311781991418099625357253121238190101 * (norm_hz / (norm_hz + 14678.4946168094343650325240435293097711148959365979858846912749)))
|
|
204
|
+
else:
|
|
205
|
+
return 11.1726796613307508659409591598553369046875791603224092625910488 * _math.log1p(46.0653791116351008187372868311781991418099625357253121238190101 * (hz / (hz + 14678.4946168094343650325240435293097711148959365979858846912749)))
|
|
206
|
+
|
|
207
|
+
def erbs_to_hz(erbs):
|
|
208
|
+
"""inverse of hz_to_erbs. there are many ways to perform this but they are less numerically stable than hz_to_erbs
|
|
209
|
+
|
|
210
|
+
default formula: A * (T / (B - T))
|
|
211
|
+
where A = (9339 + √80109737) / 1.246
|
|
212
|
+
≈ 14678.4946168094343650325240435293097711148959365979858846912…
|
|
213
|
+
B = (9339 + √80109737) / (9339 - √80109737) - 1
|
|
214
|
+
≈ 46.0653791116351008187372868311781991418099625357253121238190…
|
|
215
|
+
T = expm1 (erbs * C) which is mathematically equivalent to e ^ (erbs * C) - 1
|
|
216
|
+
where C = √80109737 / 100000
|
|
217
|
+
≈ 0.089504042925445552188305249582375199748091603370010904123253286141232001286708029
|
|
218
|
+
|
|
219
|
+
the constants are precomputed using an arbitrary precision calculator so they are as precise as a double can be
|
|
220
|
+
|
|
221
|
+
there may be other forms. i havent explored them yet
|
|
222
|
+
"""
|
|
223
|
+
temp = _math.expm1(erbs / 11.1726796613307508659409591598553369046875791603224092625910488)
|
|
224
|
+
|
|
225
|
+
return 14678.4946168094343650325240435293097711148959365979858846912749 * (temp / (46.0653791116351008187372868311781991418099625357253121238190101 - temp))
|
|
226
|
+
|
|
227
|
+
def hz_to_gw(hz, softness = 165.4, low_x = 20, low_y = 0, high_x = 20000, high_y = 1):
|
|
228
|
+
"""convert frequency in hertz to cochlear frequency–place map according to the greenwood function
|
|
229
|
+
|
|
230
|
+
this is the "actual" form of greenwood's original function which is hz = 165.4 * (10 ^ (2.1 * gw) - 0.88). the inverse is gw = log10(hz / 165.4 + 0.88) / 2.1. in my formula, 165.4 stays as it is, but 0.88 is supposed to be 1 - 20 / 165.4 and 2.1 is supposed to be log10(1 + (20000 - 20) / 165.4). derive the rest yourself
|
|
231
|
+
"""
|
|
232
|
+
return _soft_log(hz, softness = softness, low_x = low_x, high_x = high_x, low_y = low_y, high_y = high_y)
|
|
233
|
+
|
|
234
|
+
def gw_to_hz(gw, softness = 165.4, low_x = 0, low_y = 20, high_x = 1, high_y = 20000):
|
|
235
|
+
"""inverse of hz_to_gw. derived from greenwood's formula hz = 165.4 * (10 ^ (2.1 * gw) - 0.88) where 0.88 is supposed to be 1 - 20 / 165.4 and 2.1 is supposed to be log10(1 + (20000 - 20) / 165.4). derive the rest yourself
|
|
236
|
+
"""
|
|
237
|
+
return _soft_exp(gw, softness = softness, low_x = low_x, high_x = high_x, low_y = low_y, high_y = high_y)
|
|
238
|
+
|
|
239
|
+
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from daamath import lerp as _plerp, unlerp as _unplerp # for frequency lerping
|
|
2
|
+
from collections.abc import Sequence as _Sequence
|
|
3
|
+
|
|
4
|
+
_DEFAULT_LOW_HZ = 20
|
|
5
|
+
_DEFAULT_HIGH_HZ = 20000
|
|
6
|
+
|
|
7
|
+
def lerp_freq(
|
|
8
|
+
value: float ,
|
|
9
|
+
low : float = _DEFAULT_LOW_HZ ,
|
|
10
|
+
high : float = _DEFAULT_HIGH_HZ,
|
|
11
|
+
power: float = 0 ,
|
|
12
|
+
) -> float:
|
|
13
|
+
'lerp a fractional value to a frequency in an interval. by default, maps [0, 1] → [20, 20000] in oct space'
|
|
14
|
+
return _plerp(value, low, high, power = power)
|
|
15
|
+
|
|
16
|
+
def unlerp_freq(
|
|
17
|
+
value: float ,
|
|
18
|
+
low : float = _DEFAULT_LOW_HZ ,
|
|
19
|
+
high : float = _DEFAULT_HIGH_HZ,
|
|
20
|
+
power: float = 0 ,
|
|
21
|
+
) -> float:
|
|
22
|
+
'unlerp a frequency in an interval to a fractional value. by default, maps [20, 20000] in hz space to [0, 1]'
|
|
23
|
+
return _unplerp(value, low, high, power = power)
|
|
24
|
+
|
|
25
|
+
# finished up to this ----------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
# string helpers
|
|
28
|
+
|
|
29
|
+
# factories --------------------------------------------------------------------
|
|
30
|
+
"""
|
|
31
|
+
def multilerp(value: float, x_values: _Iterable[float], y_values: _Iterable[float], is_sorted: bool = False) -> float:
|
|
32
|
+
'a slow inefficient ass function that linearly interpolates a bunch of points and lets you sample on them'
|
|
33
|
+
|
|
34
|
+
if x_value
|
|
35
|
+
|
|
36
|
+
#x_values, y_values = (x_values, y_values) if is_sorted else zip(*sorted(zip(x_values, y_values)))
|
|
37
|
+
|
|
38
|
+
index_low: int = 0
|
|
39
|
+
index_high: int = 0
|
|
40
|
+
|
|
41
|
+
if is_sorted:
|
|
42
|
+
for index, x_value in enumerate(x_values):
|
|
43
|
+
if x_value > value:
|
|
44
|
+
break
|
|
45
|
+
index_low =
|
|
46
|
+
else:
|
|
47
|
+
for index, x_value in enumerate(x_values):
|
|
48
|
+
if x_value == value:
|
|
49
|
+
return y_values[index]
|
|
50
|
+
index_low = index if x_value < value and index_low < index else index_low
|
|
51
|
+
index_high = index if x_value > value and index_high > index else index_high
|
|
52
|
+
|
|
53
|
+
value_intermediate = (value - x_values[index_low]) / (x_values[index_high] - x_values[index_low])
|
|
54
|
+
return y_values[index_low] * (1 - value_intermediate) + y_values[index_high] * value_intermediate
|
|
55
|
+
"""
|
|
56
|
+
"""
|
|
57
|
+
def factory_ascii_converter(from_str: str, to_str: str) -> _Callable[[str], str]:
|
|
58
|
+
# create a dictionary mapping for all non-space characters
|
|
59
|
+
mapping = {a: b for a, b in zip(from_str, to_str) if b.strip()}
|
|
60
|
+
def converter(s: str) -> str:
|
|
61
|
+
return ''.join(mapping.get(ch, ch) for ch in s)
|
|
62
|
+
return converter
|
|
63
|
+
|
|
64
|
+
def factory_interpolation(x_values: _Sequence[int|float], y_values: _Sequence[int|float]) -> _Callable[[int|float], int|float]:
|
|
65
|
+
'a factory that turns a bunch of points into a sample-able ℝ ⟼ ℝ function by a polynomial interpolation'
|
|
66
|
+
raise NotImplementedError
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# specific converters
|
|
71
|
+
#to_superscript = factory_ascii_converter(ASCII, ASCII_SUPERSCRIPT)
|
|
72
|
+
#to_subscript = factory_ascii_converter(ASCII, ASCII_SUBSCRIPT)
|
|
73
|
+
|
|
74
|
+
# add slerp and unslerp
|
|
75
|
+
|
|
76
|
+
# DSP --------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
def wave_square(length: int, amplitude: float = 1, period_samples: int = 2, offset: float = 0) -> _Sequence[float]:
|
|
79
|
+
if not isinstance(period_samples, int) or period_samples % 2 != 0:
|
|
80
|
+
raise ValueError("only even integer period samples supported for now")
|
|
81
|
+
|
|
82
|
+
if length % period_samples != 0:
|
|
83
|
+
raise ValueError("length should be a multiple of period_samples")
|
|
84
|
+
|
|
85
|
+
one_shot = [offset + amplitude] * (period_samples // 2) + [offset - amplitude] * (period_samples // 2)
|
|
86
|
+
return one_shot * (length // period_samples)
|
|
87
|
+
|
|
88
|
+
'''
|
|
89
|
+
def coeffs_from_roots(roots: _Sequence[float]) -> _Sequence[float]:
|
|
90
|
+
"""
|
|
91
|
+
given an iterable of roots r1, r2, ..., rn
|
|
92
|
+
returns coefficients [c0, c1, ..., cn] such that
|
|
93
|
+
|
|
94
|
+
(x - r1)(x - r2)...(x - rn)
|
|
95
|
+
= c0*x^n + c1*x^(n-1) + ... + cn
|
|
96
|
+
"""
|
|
97
|
+
coeffs = [1]
|
|
98
|
+
|
|
99
|
+
for r in roots:
|
|
100
|
+
new = [0] * (len(coeffs) + 1)
|
|
101
|
+
for i, c in enumerate(coeffs):
|
|
102
|
+
new[i] += c # multiply by x
|
|
103
|
+
new[i + 1] -= r * c # multiply by -r
|
|
104
|
+
coeffs = new
|
|
105
|
+
|
|
106
|
+
return coeffs
|
|
107
|
+
'''
|
|
108
|
+
"""
|
|
109
|
+
Fs = 48000
|
|
110
|
+
f0 = 200
|
|
111
|
+
dBgain = 10
|
|
112
|
+
Q = 2
|
|
113
|
+
|
|
114
|
+
A = 10 ** (dBgain / 40)
|
|
115
|
+
w0 = 2 * _math.pi * f0 * Fs
|
|
116
|
+
cos_w0 = cos(w0)
|
|
117
|
+
sin_w0 = sin(w0)
|
|
118
|
+
|
|
119
|
+
alpha = sin_w0 / (2 * Q)
|
|
120
|
+
"""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# oscillators
|
|
2
|
+
|
|
3
|
+
# these should ideally be generators instead of returning the whole array as is
|
|
4
|
+
|
|
5
|
+
def wave_square(length: int, amplitude: float = 1, period_samples: int = 2, offset: float = 0) -> _Sequence[float]:
|
|
6
|
+
if not isinstance(period_samples, int) or period_samples % 2 != 0:
|
|
7
|
+
raise ValueError("only even integer period samples supported for now")
|
|
8
|
+
|
|
9
|
+
if length % period_samples != 0:
|
|
10
|
+
raise ValueError("length should be a multiple of period_samples")
|
|
11
|
+
|
|
12
|
+
one_shot = [offset + amplitude] * (period_samples // 2) + [offset - amplitude] * (period_samples // 2)
|
|
13
|
+
return one_shot * (length // period_samples)
|
|
14
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import daamath as _dm
|
|
2
|
+
|
|
3
|
+
_R80=[
|
|
4
|
+
1.00,1.03,1.06,1.09, 1.12,1.15,1.18,1.22,
|
|
5
|
+
1.25,1.28,1.32,1.36, 1.40,1.45,1.50,1.55,
|
|
6
|
+
1.60,1.65,1.70,1.75, 1.80,1.85,1.90,1.95,
|
|
7
|
+
2.00,2.06,2.12,2.18, 2.24,2.30,2.36,2.43,
|
|
8
|
+
2.50,2.58,2.65,2.72, 2.80,2.90,3.00,3.07,
|
|
9
|
+
3.15,3.25,3.35,3.45, 3.55,3.65,3.75,3.87,
|
|
10
|
+
4.00,4.12,4.25,4.37, 4.50,4.62,4.75,4.87,
|
|
11
|
+
5.00,5.15,5.30,5.45, 5.60,5.80,6.00,6.15,
|
|
12
|
+
6.30,6.50,6.70,6.90, 7.10,7.30,7.50,7.75,
|
|
13
|
+
8.00,8.25,8.50,8.75, 9.00,9.25,9.50,9.75]
|
|
14
|
+
|
|
15
|
+
_E24 = [
|
|
16
|
+
1.0,1.1,1.2,1.3, 1.5,1.6,1.8,2.0,
|
|
17
|
+
2.2,2.4,2.7,3.0, 3.3,3.6,3.9,4.3,
|
|
18
|
+
4.7,5.1,5.6,6.2, 6.8,7.5,8.2,9.1]
|
|
19
|
+
|
|
20
|
+
_E192 = [
|
|
21
|
+
1.00,1.01,1.02,1.04, 1.05,1.06,1.07,1.09,
|
|
22
|
+
1.10,1.11,1.13,1.14, 1.15,1.17,1.18,1.20,
|
|
23
|
+
1.21,1.23,1.24,1.26, 1.27,1.29,1.30,1.32,
|
|
24
|
+
1.33,1.35,1.37,1.38, 1.40,1.42,1.43,1.45,
|
|
25
|
+
|
|
26
|
+
1.47,1.49,1.50,1.52, 1.54,1.56,1.58,1.60,
|
|
27
|
+
1.62,1.64,1.65,1.67, 1.69,1.72,1.74,1.76,
|
|
28
|
+
1.78,1.80,1.82,1.84, 1.87,1.89,1.91,1.93,
|
|
29
|
+
1.96,1.98,2.00,2.03, 2.05,2.08,2.10,2.13,
|
|
30
|
+
|
|
31
|
+
2.15,2.18,2.21,2.23, 2.26,2.29,2.32,2.34,
|
|
32
|
+
2.37,2.40,2.43,2.46, 2.49,2.52,2.55,2.58,
|
|
33
|
+
2.61,2.64,2.67,2.71, 2.74,2.77,2.80,2.84,
|
|
34
|
+
2.87,2.91,2.94,2.98, 3.01,3.05,3.09,3.12,
|
|
35
|
+
|
|
36
|
+
3.16,3.20,3.24,3.28, 3.32,3.36,3.40,3.44,
|
|
37
|
+
3.48,3.52,3.57,3.61, 3.65,3.70,3.74,3.79,
|
|
38
|
+
3.83,3.88,3.92,3.97, 4.02,4.07,4.12,4.17,
|
|
39
|
+
4.22,4.27,4.32,4.37, 4.42,4.48,4.53,4.59,
|
|
40
|
+
|
|
41
|
+
4.64,4.70,4.75,4.81, 4.87,4.93,4.99,5.05,
|
|
42
|
+
5.11,5.17,5.23,5.30, 5.36,5.42,5.49,5.56,
|
|
43
|
+
5.62,5.69,5.76,5.83, 5.90,5.97,6.04,6.12,
|
|
44
|
+
6.19,6.26,6.34,6.42, 6.49,6.57,6.65,6.73,
|
|
45
|
+
|
|
46
|
+
6.81,6.90,6.98,7.06, 7.15,7.23,7.32,7.41,
|
|
47
|
+
7.50,7.59,7.68,7.77, 7.87,7.96,8.06,8.16,
|
|
48
|
+
8.25,8.35,8.45,8.56, 8.66,8.76,8.87,8.98,
|
|
49
|
+
9.09,9.20,9.31,9.42, 9.53,9.65,9.76,9.88]
|
|
50
|
+
|
|
51
|
+
R = {
|
|
52
|
+
5: _R80[::16],
|
|
53
|
+
10: _R80[::8],
|
|
54
|
+
20: _R80[::4],
|
|
55
|
+
40: _R80[::2],
|
|
56
|
+
80: _R80}
|
|
57
|
+
|
|
58
|
+
E_prec = {
|
|
59
|
+
48: _E192[::4],
|
|
60
|
+
96: _E192[::2],
|
|
61
|
+
192: _E192}
|
|
62
|
+
|
|
63
|
+
E_pref = {
|
|
64
|
+
3: _E24[::8],
|
|
65
|
+
6: _E24[::4],
|
|
66
|
+
12: _E24[::2],
|
|
67
|
+
24: _E24}
|
|
68
|
+
|
|
69
|
+
E = E_pref | E_prec
|
|
70
|
+
|
|
71
|
+
def custom_series(count, decimal_digits, base = 10, floored: bool = False):
|
|
72
|
+
for i in range(count):
|
|
73
|
+
actual = base ** ((i + count * decimal_digits) / count)
|
|
74
|
+
|
|
75
|
+
if floored:
|
|
76
|
+
return _dm.floor(actual)
|
|
77
|
+
|
|
78
|
+
low = _dm.floor(actual)
|
|
79
|
+
high = _dm.ceil(actual)
|
|
80
|
+
low_error = _dm.ln_div(low, actual)
|
|
81
|
+
high_error = _dm.ln_div(high, actual)
|
|
82
|
+
|
|
83
|
+
#print(actual, low, high, low_error, high_error)
|
|
84
|
+
|
|
85
|
+
if abs(low_error) < abs(high_error):
|
|
86
|
+
yield low
|
|
87
|
+
else:
|
|
88
|
+
yield high
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# utilities related to xenharmonic theory
|
|
2
|
+
from numbers import Rational as _Rational, Integral as _Integral
|
|
3
|
+
from collections.abc import Collection as _Collection
|
|
4
|
+
from collections import namedtuple as _namedtuple
|
|
5
|
+
from fractions import Fraction as _Fraction
|
|
6
|
+
import math as _math
|
|
7
|
+
|
|
8
|
+
def tenney_height(ratio: _Rational) -> float:
|
|
9
|
+
'log2(numerator * denominator). imposes an order as a scaled norm on the rational numbers'
|
|
10
|
+
return _math.log2(ratio.numerator * ratio.denominator)
|
|
11
|
+
|
|
12
|
+
def complexity(notes: _Collection[_Integral]) -> _Integral:
|
|
13
|
+
'how complex is a chord?'
|
|
14
|
+
return _math.prod(notes) // (_math.gcd(*notes) ** len(notes))
|
|
15
|
+
|
|
16
|
+
_idfk = _namedtuple('Fraction', ['numerator', 'denominator'])
|
|
17
|
+
|
|
18
|
+
def stern_brocot_dfs(depth: int, start: _Rational = _Fraction(0, 1), stop: _Rational = _idfk(0, 1)) -> _Sequence[_Rational]:
|
|
19
|
+
'generate brocot tree as a sequence down to a certain depth, in order of absolute value'
|
|
20
|
+
|
|
21
|
+
# initialize array
|
|
22
|
+
size: int = 2 ** depth + 1
|
|
23
|
+
result: list[_Rational] = [None] * size
|
|
24
|
+
result[0] = start
|
|
25
|
+
result[-1] = stop
|
|
26
|
+
|
|
27
|
+
for depth in range(depth + 1):
|
|
28
|
+
step = size // 2 ** depth
|
|
29
|
+
|
|
30
|
+
for index in range(step, size, step * 2):
|
|
31
|
+
numerator = result[index - step].numerator + result[index + step].numerator
|
|
32
|
+
denominator = result[index - step].denominator + result[index + step].denominator
|
|
33
|
+
result[index] = _Fraction(numerator, denominator)
|
|
34
|
+
|
|
35
|
+
return result
|
|
36
|
+
|
|
37
|
+
def stern_brocot_bfs(depth: int, start: _Rational = _Fraction(0, 1), stop: _Rational = _idfk(0, 1)) -> _Sequence[_Rational]:
|
|
38
|
+
'generate brocot tree as a sequence down to a certain depth, in order of appearance'
|
|
39
|
+
seq = stern_brocot_dfs(depth, start, stop)
|
|
40
|
+
size = len(seq)
|
|
41
|
+
|
|
42
|
+
def index_translate(index: int, size = size) -> int:
|
|
43
|
+
match index:
|
|
44
|
+
case 0: return 0
|
|
45
|
+
case 1: return size - 1
|
|
46
|
+
case _: return int(size * ((index - 0.5) * 2 ** -_math.floor(_math.log2(index - 1)) - 1))
|
|
47
|
+
|
|
48
|
+
return [seq[index_translate(index)] for index in range(size)]
|
|
49
|
+
|
|
50
|
+
def prime_limit(ratio: _Rational) -> int:
|
|
51
|
+
'return the highest prime that appears in a ratio'
|
|
52
|
+
import sympy
|
|
53
|
+
#return daamath.Monzo(ratio.numerator, ratio.denominator).keys()[-1]
|
|
54
|
+
factors = set(sympy.factorint(ratio.numerator).keys()) | set(sympy.factorint(ratio.denominator).keys())
|
|
55
|
+
return max(factors) if factors else 2
|
|
56
|
+
|
|
57
|
+
def ed_note(interval: float, ed_step: float) -> float:
|
|
58
|
+
'find the note of the given interval in the given ED tuning. returns log(interval, base=ed_step)'
|
|
59
|
+
return _math.log(interval, ed_step)
|
|
60
|
+
|