organ-pipe-waveguide 0.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.
- organ_pipe_waveguide-0.0.1/LICENSE +7 -0
- organ_pipe_waveguide-0.0.1/PKG-INFO +39 -0
- organ_pipe_waveguide-0.0.1/README.md +25 -0
- organ_pipe_waveguide-0.0.1/pyproject.toml +18 -0
- organ_pipe_waveguide-0.0.1/setup.cfg +4 -0
- organ_pipe_waveguide-0.0.1/src/organ_pipe_waveguide/FluePipe.py +130 -0
- organ_pipe_waveguide-0.0.1/src/organ_pipe_waveguide/__init__.py +1 -0
- organ_pipe_waveguide-0.0.1/src/organ_pipe_waveguide.egg-info/PKG-INFO +39 -0
- organ_pipe_waveguide-0.0.1/src/organ_pipe_waveguide.egg-info/SOURCES.txt +10 -0
- organ_pipe_waveguide-0.0.1/src/organ_pipe_waveguide.egg-info/dependency_links.txt +1 -0
- organ_pipe_waveguide-0.0.1/src/organ_pipe_waveguide.egg-info/requires.txt +2 -0
- organ_pipe_waveguide-0.0.1/src/organ_pipe_waveguide.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright (c) 2026 William Brady Call
|
|
2
|
+
|
|
3
|
+
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:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
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.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: organ_pipe_waveguide
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A package for physically modelling the sounds of a pipe organ.
|
|
5
|
+
Author-email: William Brady Call <wbradycall@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/wbradycall/organ_pipe_waveguide
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: numpy>=2.1.3
|
|
12
|
+
Requires-Dist: scipy>=1.17.1
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# Organ Pipe Waveguide
|
|
16
|
+
|
|
17
|
+
This project is to be used for physically modeling organ pipes. However, it's not 100% realistic just yet and it needs some tweaking. It also only includes flue pipes for now but in later versions, it will include reed pipes.
|
|
18
|
+
|
|
19
|
+
To import the flue pipe model, you need to import the 'flue_pipe' class which one can import via "from organ_pipe_waveguide.FluePipe import flue_pipe". Here are the default parameters for the 'flue_pipe' object-oriented class and the methods:
|
|
20
|
+
|
|
21
|
+
flue_pipe(self,note=0,default_diam=0.1555,rho=1.225,c=343,gain1=0.05,gain2=0.8,k=1.4)
|
|
22
|
+
|
|
23
|
+
<!-- This method is for generating the turbulence. -->
|
|
24
|
+
jet_noise(self,time,fs=44100,seed=41)
|
|
25
|
+
|
|
26
|
+
<!-- This method is for creating the IIR filter function and returns a tuple of the list of beta values and the list of alpha values. -->
|
|
27
|
+
create_IIR(self,fs=44100)
|
|
28
|
+
|
|
29
|
+
<!-- This method is for applying an IIR filter. -->
|
|
30
|
+
def IIR(self,input_array,filter_array,index,beta,alpha)
|
|
31
|
+
|
|
32
|
+
<!-- This method is for adding an envelope to generated noise so that one can use it to resonate. -->
|
|
33
|
+
create_input(self,time,noise_data,amp=0.5,attack=0.1)
|
|
34
|
+
|
|
35
|
+
<!-- This method is for applying a non-linear jet-flue interaction simulator. -->
|
|
36
|
+
nonlinearity(self,input_array)
|
|
37
|
+
|
|
38
|
+
<!-- This method is for simulating the acoustics of the organ pipe and writing it to a .wav file (make to sure add '.wav' or ".wav" to the end of your filename). -->
|
|
39
|
+
simulate(self,filename,duration,init_amp=0.5,fs=44100,seed=41)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Organ Pipe Waveguide
|
|
2
|
+
|
|
3
|
+
This project is to be used for physically modeling organ pipes. However, it's not 100% realistic just yet and it needs some tweaking. It also only includes flue pipes for now but in later versions, it will include reed pipes.
|
|
4
|
+
|
|
5
|
+
To import the flue pipe model, you need to import the 'flue_pipe' class which one can import via "from organ_pipe_waveguide.FluePipe import flue_pipe". Here are the default parameters for the 'flue_pipe' object-oriented class and the methods:
|
|
6
|
+
|
|
7
|
+
flue_pipe(self,note=0,default_diam=0.1555,rho=1.225,c=343,gain1=0.05,gain2=0.8,k=1.4)
|
|
8
|
+
|
|
9
|
+
<!-- This method is for generating the turbulence. -->
|
|
10
|
+
jet_noise(self,time,fs=44100,seed=41)
|
|
11
|
+
|
|
12
|
+
<!-- This method is for creating the IIR filter function and returns a tuple of the list of beta values and the list of alpha values. -->
|
|
13
|
+
create_IIR(self,fs=44100)
|
|
14
|
+
|
|
15
|
+
<!-- This method is for applying an IIR filter. -->
|
|
16
|
+
def IIR(self,input_array,filter_array,index,beta,alpha)
|
|
17
|
+
|
|
18
|
+
<!-- This method is for adding an envelope to generated noise so that one can use it to resonate. -->
|
|
19
|
+
create_input(self,time,noise_data,amp=0.5,attack=0.1)
|
|
20
|
+
|
|
21
|
+
<!-- This method is for applying a non-linear jet-flue interaction simulator. -->
|
|
22
|
+
nonlinearity(self,input_array)
|
|
23
|
+
|
|
24
|
+
<!-- This method is for simulating the acoustics of the organ pipe and writing it to a .wav file (make to sure add '.wav' or ".wav" to the end of your filename). -->
|
|
25
|
+
simulate(self,filename,duration,init_amp=0.5,fs=44100,seed=41)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools >= 77.0.3"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "organ_pipe_waveguide"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
dependencies = ["numpy>=2.1.3",
|
|
9
|
+
"scipy>=1.17.1",]
|
|
10
|
+
authors = [{ name="William Brady Call", email="wbradycall@gmail.com"},]
|
|
11
|
+
description = "A package for physically modelling the sounds of a pipe organ."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
license = "MIT"
|
|
15
|
+
license-files = ["LICENSE*"]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/wbradycall/organ_pipe_waveguide"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy.fft import fft,ifft,fftfreq
|
|
3
|
+
from scipy import signal
|
|
4
|
+
import scipy.io.wavfile as wavfile
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class flue_pipe: # Organ flue pipe simulator
|
|
8
|
+
def __init__(self,note=0,default_diam=0.1555,rho=1.225,c=343,gain1=0.05,gain2=0.8,k=1.4):
|
|
9
|
+
self.note = note
|
|
10
|
+
self.f0 = 55 * pow(2,1/4) * pow(2,note/12)
|
|
11
|
+
self.r = (default_diam / 2) * pow(2,-note/16)
|
|
12
|
+
self.rho = rho
|
|
13
|
+
self.c = c
|
|
14
|
+
self.gain1 = gain1
|
|
15
|
+
self.gain2 = gain2
|
|
16
|
+
self.k = k
|
|
17
|
+
|
|
18
|
+
def __repr__(self):
|
|
19
|
+
return f"FluePipe(freq:{self.f0},radius:{self.r},rho:{self.rho},wavespeed:{self.c},gain1:{self.gain1},gain2:{self.gain2},nonlin_coeff:{self.k})"
|
|
20
|
+
|
|
21
|
+
def __str__(self):
|
|
22
|
+
return f"FluePipe(freq:{self.f0},radius:{self.r},wavespeed:{self.c})"
|
|
23
|
+
|
|
24
|
+
def jet_noise(self,time,fs=44100,seed=41):
|
|
25
|
+
N = len(time)
|
|
26
|
+
np.random.seed(seed)
|
|
27
|
+
noise_data_derivative3 = np.random.normal(0, 1, N)
|
|
28
|
+
|
|
29
|
+
cutoff = 50 * pow(2, self.note / 12)
|
|
30
|
+
frequencies = fftfreq(N, 1 / fs)
|
|
31
|
+
omegas = 2 * np.pi * frequencies
|
|
32
|
+
|
|
33
|
+
FFT = fft(noise_data_derivative3)
|
|
34
|
+
newFFT = np.zeros(N, dtype=complex)
|
|
35
|
+
|
|
36
|
+
for i in range(N):
|
|
37
|
+
if np.abs(frequencies[i]) <= cutoff:
|
|
38
|
+
pass
|
|
39
|
+
else:
|
|
40
|
+
newFFT[i] = FFT[i] / (1j * np.abs(omegas[i])) ** 3
|
|
41
|
+
|
|
42
|
+
y = ifft(newFFT).real
|
|
43
|
+
y /= np.max(np.abs(y))
|
|
44
|
+
return y
|
|
45
|
+
|
|
46
|
+
def create_IIR(self,fs=44100):
|
|
47
|
+
cutoff_freq = 1.84 * self.c / (2 * np.pi * self.r)
|
|
48
|
+
cutoff_om = 2 * np.pi * cutoff_freq
|
|
49
|
+
num = [cutoff_om]
|
|
50
|
+
den = [1, cutoff_om]
|
|
51
|
+
|
|
52
|
+
beta, alpha = signal.bilinear(num, den, fs=fs)
|
|
53
|
+
return beta, alpha
|
|
54
|
+
|
|
55
|
+
def IIR(self,input_array,filter_array,index,beta,alpha):
|
|
56
|
+
b0 = beta[0]
|
|
57
|
+
b1 = beta[1]
|
|
58
|
+
|
|
59
|
+
a0 = alpha[0]
|
|
60
|
+
a1 = alpha[1]
|
|
61
|
+
|
|
62
|
+
return (b0 * input_array[index] + b1 * input_array[index - 1] - a1 * filter_array[index - 1]) / a0
|
|
63
|
+
|
|
64
|
+
def create_input(self,time,noise_data,amp=0.5,attack=0.1):
|
|
65
|
+
N = len(time)
|
|
66
|
+
duration = np.max(time)
|
|
67
|
+
|
|
68
|
+
envelope = np.zeros(N)
|
|
69
|
+
release1 = (4 / 5) * duration
|
|
70
|
+
release2 = (22 / 25) * duration
|
|
71
|
+
|
|
72
|
+
for i in range(N):
|
|
73
|
+
if time[i] < attack:
|
|
74
|
+
envelope[i] = time[i] / attack
|
|
75
|
+
elif attack <= time[i] and time[i] < release1:
|
|
76
|
+
envelope[i] = 1
|
|
77
|
+
elif release1 <= time[i] and time[i] < release2:
|
|
78
|
+
envelope[i] = np.exp(-5 * (time[i] - release1))
|
|
79
|
+
|
|
80
|
+
return amp * noise_data * envelope
|
|
81
|
+
|
|
82
|
+
def nonlinearity(self,input_array):
|
|
83
|
+
return (self.k * input_array) + 0.005 * (self.k * input_array)**2 - (self.k * input_array)**3
|
|
84
|
+
|
|
85
|
+
def simulate(self,filename,duration,init_amp=0.5,fs=44100,seed=41):
|
|
86
|
+
N = int(duration * fs)
|
|
87
|
+
T = 1 / self.f0
|
|
88
|
+
delay = T * fs
|
|
89
|
+
start_resonating = int(delay)
|
|
90
|
+
beta, alpha = self.create_IIR(fs=fs)
|
|
91
|
+
|
|
92
|
+
time = np.linspace(0,duration,N,endpoint=False)
|
|
93
|
+
noise_data = self.jet_noise(time,fs=fs,seed=seed)
|
|
94
|
+
input_array = self.create_input(time,noise_data,amp=init_amp)
|
|
95
|
+
jet_input = np.zeros(N)
|
|
96
|
+
filter_array = np.zeros(N)
|
|
97
|
+
output_array = np.zeros(N)
|
|
98
|
+
|
|
99
|
+
for i in range(start_resonating):
|
|
100
|
+
jet_input[i] = self.nonlinearity(self.gain1 * input_array[i])
|
|
101
|
+
filter_array[i] = self.IIR(jet_input,filter_array,i,beta,alpha)
|
|
102
|
+
output_array[i] = filter_array[i]
|
|
103
|
+
print(i)
|
|
104
|
+
|
|
105
|
+
for i in range(start_resonating,N):
|
|
106
|
+
k = i-delay # Fractional delay
|
|
107
|
+
k_floor = int(np.floor(k))
|
|
108
|
+
k_ceil = int(np.ceil(k))
|
|
109
|
+
fraction = k - k_floor
|
|
110
|
+
|
|
111
|
+
output_state = (1 - fraction) * output_array[k_floor] + fraction * output_array[k_ceil]
|
|
112
|
+
|
|
113
|
+
jet_input[i] = self.nonlinearity(self.gain1 * input_array[i] + self.gain2 * output_state) # Open end
|
|
114
|
+
filter_array[i] = self.IIR(jet_input,filter_array,i,beta,alpha)
|
|
115
|
+
output_array[i] = filter_array[i]
|
|
116
|
+
print(i)
|
|
117
|
+
|
|
118
|
+
print("Finished resonating!\n")
|
|
119
|
+
print("Converting to stereo and writing to .wav file...\n")
|
|
120
|
+
|
|
121
|
+
output_array /= np.max(np.abs(output_array))
|
|
122
|
+
output_array2 = np.zeros(N)
|
|
123
|
+
|
|
124
|
+
for i in range(N - 441):
|
|
125
|
+
output_array2[i + 441] = 0.8 * output_array[i]
|
|
126
|
+
|
|
127
|
+
stereo_output = np.vstack((output_array, output_array2)).T
|
|
128
|
+
wavfile.write(filename, fs, stereo_output)
|
|
129
|
+
|
|
130
|
+
print("Finished!")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: organ_pipe_waveguide
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A package for physically modelling the sounds of a pipe organ.
|
|
5
|
+
Author-email: William Brady Call <wbradycall@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/wbradycall/organ_pipe_waveguide
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: numpy>=2.1.3
|
|
12
|
+
Requires-Dist: scipy>=1.17.1
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# Organ Pipe Waveguide
|
|
16
|
+
|
|
17
|
+
This project is to be used for physically modeling organ pipes. However, it's not 100% realistic just yet and it needs some tweaking. It also only includes flue pipes for now but in later versions, it will include reed pipes.
|
|
18
|
+
|
|
19
|
+
To import the flue pipe model, you need to import the 'flue_pipe' class which one can import via "from organ_pipe_waveguide.FluePipe import flue_pipe". Here are the default parameters for the 'flue_pipe' object-oriented class and the methods:
|
|
20
|
+
|
|
21
|
+
flue_pipe(self,note=0,default_diam=0.1555,rho=1.225,c=343,gain1=0.05,gain2=0.8,k=1.4)
|
|
22
|
+
|
|
23
|
+
<!-- This method is for generating the turbulence. -->
|
|
24
|
+
jet_noise(self,time,fs=44100,seed=41)
|
|
25
|
+
|
|
26
|
+
<!-- This method is for creating the IIR filter function and returns a tuple of the list of beta values and the list of alpha values. -->
|
|
27
|
+
create_IIR(self,fs=44100)
|
|
28
|
+
|
|
29
|
+
<!-- This method is for applying an IIR filter. -->
|
|
30
|
+
def IIR(self,input_array,filter_array,index,beta,alpha)
|
|
31
|
+
|
|
32
|
+
<!-- This method is for adding an envelope to generated noise so that one can use it to resonate. -->
|
|
33
|
+
create_input(self,time,noise_data,amp=0.5,attack=0.1)
|
|
34
|
+
|
|
35
|
+
<!-- This method is for applying a non-linear jet-flue interaction simulator. -->
|
|
36
|
+
nonlinearity(self,input_array)
|
|
37
|
+
|
|
38
|
+
<!-- This method is for simulating the acoustics of the organ pipe and writing it to a .wav file (make to sure add '.wav' or ".wav" to the end of your filename). -->
|
|
39
|
+
simulate(self,filename,duration,init_amp=0.5,fs=44100,seed=41)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/organ_pipe_waveguide/FluePipe.py
|
|
5
|
+
src/organ_pipe_waveguide/__init__.py
|
|
6
|
+
src/organ_pipe_waveguide.egg-info/PKG-INFO
|
|
7
|
+
src/organ_pipe_waveguide.egg-info/SOURCES.txt
|
|
8
|
+
src/organ_pipe_waveguide.egg-info/dependency_links.txt
|
|
9
|
+
src/organ_pipe_waveguide.egg-info/requires.txt
|
|
10
|
+
src/organ_pipe_waveguide.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
organ_pipe_waveguide
|