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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,2 @@
1
+ numpy>=2.1.3
2
+ scipy>=1.17.1