Created
November 7, 2025 12:47
-
-
Save tam17aki/1adf19685e0dbd367fab606ab8d4c6ab to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -*- coding: utf-8 -*- | |
| """Plots vocal tract area functions for Japanese vowels. | |
| This script processes vocal tract diameter data from Arai (2007) to | |
| generate and display interpolated area function graphs for the five | |
| standard Japanese vowels. | |
| Copyright (C) 2025 by Akira TAMAMORI | |
| 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. | |
| """ | |
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| import numpy.typing as npt | |
| from scipy.interpolate import PchipInterpolator | |
| def get_vowel_area_data() -> dict[str, npt.NDArray[np.float64]]: | |
| """Provides vocal tract diameter data for the five Japanese vowels. | |
| Returns: | |
| dict[str, np.ndarray]: A dictionary where keys are vowel names | |
| (e.g., '/a/') and values are numpy arrays | |
| of diameters in millimeters. | |
| """ | |
| # Diameters (in mm) for the five Japanese vowels from Arai (2007), | |
| # Table 1. Index 1 corresponds to the lips, and index 16 to the | |
| # glottis. | |
| vowel_data = { | |
| "/i/": np.array( | |
| [24, 14, 12, 10, 10, 10, 16, 24, 32, 32, 32, 32, 32, 32, 12, 12] | |
| ), | |
| "/e/": np.array( | |
| [24, 22, 22, 20, 18, 16, 16, 18, 24, 28, 30, 30, 30, 30, 12, 12] | |
| ), | |
| "/a/": np.array( | |
| [32, 28, 30, 34, 38, 38, 34, 30, 26, 20, 14, 12, 16, 26, 12, 12] | |
| ), | |
| "/o/": np.array( | |
| [14, 22, 26, 32, 38, 38, 34, 28, 22, 16, 14, 16, 22, 30, 12, 12] | |
| ), | |
| "/u/": np.array( | |
| [16, 14, 20, 22, 24, 26, 22, 14, 18, 26, 30, 30, 30, 30, 12, 12] | |
| ), | |
| } | |
| return vowel_data | |
| def process_diameter_data( | |
| diameter_mm: npt.NDArray[np.float64], | |
| num_sections: int = 16, | |
| section_length_m: float = 0.01, | |
| ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: | |
| """Processes diameter data into position and area arrays. | |
| Args: | |
| diameter_mm (np.ndarray): | |
| Array of vocal tract diameters in millimeters. | |
| num_sections (int): | |
| The number of sections in the vocal tract model. | |
| section_length_m (float): | |
| The length of each section in meters. | |
| Returns: | |
| Tuple[np.ndarray, np.ndarray]: | |
| A tuple containing: | |
| - pos_m: Position from the glottis in meters. | |
| - area_cm2: Cross-sectional area in square centimeters. | |
| Raises: | |
| ValueError: If the input diameter is not the number of sections. | |
| """ | |
| if len(diameter_mm) != num_sections: | |
| raise ValueError( | |
| f"Input diameter data must have {num_sections} elements." | |
| ) | |
| index = np.arange(1, num_sections + 1) | |
| # Position from glottis [m] The center of section `i` (from glottis) | |
| # is at (i-0.5)*section_length Index 16 is section 1, Index 1 is | |
| # section 16. | |
| pos_m = (num_sections + 0.5 - index) * section_length_m | |
| # Cross-sectional area [cm^2] for plotting | |
| radius_cm = (diameter_mm / 2) / 10 | |
| area_cm2 = np.pi * radius_cm**2 | |
| return pos_m.astype(np.float64), area_cm2 | |
| def create_interpolator( | |
| pos_m: np.ndarray, area_cm2: np.ndarray | |
| ) -> PchipInterpolator: | |
| """Creates a PchipInterpolator from position and area data. | |
| Args: | |
| pos_m (np.ndarray): Position data in meters. | |
| area_cm2 (np.ndarray): Area data in square centimeters. | |
| Returns: | |
| PchipInterpolator: An interpolator function. | |
| """ | |
| # Sort data by position, as required by the interpolator | |
| sort_indices = np.argsort(pos_m) | |
| pos_m_sorted = pos_m[sort_indices] | |
| area_cm2_sorted = area_cm2[sort_indices] | |
| return PchipInterpolator(pos_m_sorted, area_cm2_sorted) | |
| def plot_vocal_tract_area( | |
| vowel_name: str, | |
| pos_m_data: npt.NDArray[np.float64], | |
| area_cm2_data: npt.NDArray[np.float64], | |
| interpolator: PchipInterpolator, | |
| l_vt_m: float, | |
| ) -> None: | |
| """Processes diameter data into position and area arrays. | |
| Args: | |
| vowel_name (str): | |
| The name of the vowel (e.g., '/a/'). | |
| pos_m_data (np.ndarray): | |
| Original position data points in meters. | |
| area_cm2_data (np.ndarray): | |
| Original area data points in square centimeters. | |
| interpolator (PchipInterpolator): | |
| The interpolator function. | |
| l_vt_m (float): | |
| Total length of the vocal tract in meters. | |
| """ | |
| # Generate smooth data for plotting the interpolated curve | |
| pos_m_smooth = np.linspace(0, l_vt_m, 1000) | |
| area_cm2_smooth = interpolator(pos_m_smooth) | |
| # Create the plot | |
| plt.figure(figsize=(10, 6)) | |
| # Plot interpolated curve | |
| plt.plot( | |
| pos_m_smooth * 100, | |
| area_cm2_smooth, | |
| label="PCHIP Interpolation", | |
| color="royalblue", | |
| linewidth=2, | |
| ) | |
| # Plot original data points | |
| plt.plot( | |
| pos_m_data * 100, | |
| area_cm2_data, | |
| "o", | |
| label="Original Data Points (Arai, 2007)", | |
| color="crimson", | |
| markersize=8, | |
| ) | |
| # Formatting | |
| plt.title(f"Vocal Tract Area Function for Vowel {vowel_name}", fontsize=16) | |
| plt.xlabel("Position from Glottis [cm]", fontsize=12) | |
| plt.ylabel("Cross-sectional Area [cm$^2$]", fontsize=12) | |
| plt.grid(True, linestyle="--", alpha=0.6) | |
| plt.legend(fontsize=11) | |
| plt.xlim(0, l_vt_m * 100) | |
| plt.ylim(bottom=0) | |
| # Add text labels | |
| max_area = max(area_cm2_data) * 0.9 | |
| plt.text(1, max_area, "Glottis side", ha="left") | |
| plt.text(l_vt_m * 100 - 1, max_area, "Lips side", ha="right") | |
| plt.show() | |
| def main() -> None: | |
| """Perform plot.""" | |
| target_vowels = ["/i/", "/e/", "/a/", "/o/", "/u/"] # Plot all vowels | |
| num_sections = 16 | |
| section_length_m = 0.01 | |
| vocal_tract_length_m = num_sections * section_length_m | |
| # --- Main Logic --- | |
| all_vowel_data = get_vowel_area_data() | |
| for vowel in target_vowels: | |
| if vowel in all_vowel_data: | |
| # 1. Get raw diameter data | |
| diameter_data = all_vowel_data[vowel] | |
| # 2. Process data to get position and area | |
| pos_data, area_data = process_diameter_data( | |
| diameter_data, num_sections, section_length_m | |
| ) | |
| # 3. Create an interpolator | |
| interpolator_func = create_interpolator(pos_data, area_data) | |
| # 4. Plot the results | |
| plot_vocal_tract_area( | |
| vowel, | |
| pos_data, | |
| area_data, | |
| interpolator_func, | |
| vocal_tract_length_m, | |
| ) | |
| else: | |
| print(f"Warning: Data for vowel '{vowel}' not found.") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment