vapylib 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.
vapylib-0.1.0/LICENSE ADDED
@@ -0,0 +1 @@
1
+ my vim doesnt have clipboard support so i cant copy gplv3 license text to here but it's a strong copyleft license. you can edit this program however you want, but you have to publish it with the same license.
vapylib-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: vapylib
3
+ Version: 0.1.0
4
+ Summary: a python voice synthesis lib
5
+ License-Expression: GPL-3.0-only
6
+ Requires-Python: >=3.10
7
+ License-File: LICENSE
8
+ Requires-Dist: numpy
9
+ Requires-Dist: scipy
10
+ Requires-Dist: sounddevice
11
+ Requires-Dist: soundfile
12
+ Requires-Dist: PyYAML
13
+ Dynamic: license-file
@@ -0,0 +1,16 @@
1
+ # What is VAPyLib?
2
+ VAPyLib (**V**oice **A**cting **Py**thon **Lib**rary) is a library that utilizes sine waves and filters to create singing voices using formant synthesis.
3
+ # How to make songs for VAPyLib?
4
+ To make a song, you need to create a `.yaml` or `.yml` file, and parse the song using the `parse_song(filepath)` function. Note that pitch can only be between `0.0` and `1.0`
5
+ ### Example `song.yaml`:
6
+ ```yaml
7
+ version: 1
8
+ syllables:
9
+ - "ma"
10
+ - "na"
11
+ pitch_maps:
12
+ - 0.5,
13
+ - 0.7
14
+ ```
15
+ ## Note:
16
+ VAPyLib is version 0.1.0, but put **1** instead.
@@ -0,0 +1,12 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+ [project]
5
+ license = "GPL-3.0-only"
6
+ name = "vapylib"
7
+ version = "0.1.0"
8
+ description = "a python voice synthesis lib"
9
+ requires-python = ">=3.10"
10
+ dependencies = ["numpy", "scipy", "sounddevice", "soundfile", "PyYAML"]
11
+ [tool.setuptools]
12
+ packages = ["vapylib"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ from .parser import parse_song
@@ -0,0 +1,27 @@
1
+ import yaml
2
+ from . import synthesizer
3
+ class Song:
4
+ def __init__(self, syllables, pitch_maps):
5
+ self.syllables = syllables
6
+ self.pitch_maps = pitch_maps
7
+ def play(self):
8
+ synthesizer.play(self)
9
+ def save(self, filename):
10
+ synthesizer.save_wav(self, filename)
11
+ def parse_song(filepath):
12
+ with open(filepath, "r", encoding="utf8") as file:
13
+ data = yaml.safe_load(file)
14
+ if data.get("version") != 1:
15
+ raise ValueError("unsupported vapylib version")
16
+ syllables = data.get("syllables")
17
+ pitch_maps = data.get("pitch_maps")
18
+ if not isinstance(syllables, list):
19
+ raise ValueError("'syllables' must be a list")
20
+ if not isinstance(pitch_maps, list):
21
+ raise ValueError("'pitch_maps' must be a list")
22
+ if len(syllables) != len(pitch_maps):
23
+ raise ValueError("syllables and pitch_maps need to have equal amount of values")
24
+ for pitch in pitch_maps:
25
+ if not isinstance(pitch, (int, float)) or not 0 <= pitch <= 1:
26
+ raise ValueError(f"not good pitch ({pitch}), needs to be int or float and be between 0 and 1")
27
+ return Song(syllables, pitch_maps)
@@ -0,0 +1,107 @@
1
+ import numpy as np
2
+ from scipy import signal
3
+ import sounddevice as sd
4
+ import soundfile as sf
5
+ def _pitch_to_frequency(pitch, min_hz=100, max_hz=800):
6
+ return min_hz + pitch * (max_hz - min_hz)
7
+ _phonemes_list = [
8
+ "a", "e", "i", "o", "u",
9
+ "f", "s", "m", "n"
10
+ ]
11
+ def _split_syllable(syllable):
12
+ phonemes = sorted(_phonemes_list, key=len, reverse=True)
13
+ result = []
14
+ position = 0
15
+ while position < len(syllable):
16
+ for phoneme in phonemes:
17
+ if syllable.startswith(phoneme, position):
18
+ result.append(phoneme)
19
+ position += len(phoneme)
20
+ break
21
+ else:
22
+ raise ValueError(f"not known phoneme at {syllable[position:]}")
23
+ return result
24
+ #a = 800 1200 2300
25
+ #e = 500 1500 2600
26
+ #i = 250 2750 3195
27
+ #o = 515 960 2695
28
+ #u = 255 1200 1935
29
+ _vowels = {
30
+ "a": [800, 1200, 2300],
31
+ "e": [500, 1500, 2600],
32
+ "i": [250, 2750, 3195],
33
+ "o": [515, 960, 2695],
34
+ "u": [255, 1200, 1935]
35
+ }
36
+ _fricatives = {
37
+ "f": {
38
+ "low": 1000,
39
+ "high": 8000
40
+ },
41
+ "s": {
42
+ "low": 4000,
43
+ "high": 12000
44
+ }
45
+ }
46
+ _nasals = {
47
+ "m": [250, 1000, 2200],
48
+ "n": [300, 1500, 2500]
49
+ }
50
+ _nasal_antiresonances = {
51
+ "m": [750, 1800],
52
+ "n": [1200, 2500]
53
+ }
54
+ def _apply_resonances(source, frequencies, sample_rate):
55
+ result = source
56
+ for frequency in frequencies:
57
+ if frequency >= sample_rate / 2:
58
+ continue
59
+ b, a = signal.iirpeak(frequency, Q=4, fs=sample_rate)
60
+ result = signal.lfilter(b, a, result)
61
+ return result
62
+ def _apply_antiresonances(source, frequencies, sample_rate):
63
+ result = source
64
+ for frequency in frequencies:
65
+ if frequency >= sample_rate / 2:
66
+ continue
67
+ b, a = signal.iirnotch(frequency, Q=4, fs=sample_rate)
68
+ result = signal.lfilter(b, a, result)
69
+ def _generate_source(pitch, sample_rate=44100):
70
+ frequency = _pitch_to_frequency(pitch)
71
+ sample_count = int(sample_rate * 0.5)
72
+ t = np.arange(sample_count) / sample_rate
73
+ return np.sin(2 * np.pi * frequency * t)
74
+ def _generate_vowel(phoneme, pitch, sample_rate=44100):
75
+ source = _generate_source(pitch, 0.5, sample_rate)
76
+ return _apply_resonances(source, _vowels[phoneme], sample_rate)
77
+ def _generate_fricative(phoneme, pitch, sample_rate=44100):
78
+ sample_count = int(sample_rate * 0.5)
79
+ source = np.random.normal(0, 1, sample_count)
80
+ return _apply_resonances(source, _fricatives[phoneme], sample_rate)
81
+ def _generate_nasal(phoneme, pitch, sample_rate=44100):
82
+ source = _generate_source(pitch, 0.5, sample_rate)
83
+ return _apply_antiresonances(_apply_resonances(source, _nasals[phoneme], sample_rate), _nasal_antiresonances[phoneme], sample_rate)
84
+ def _generate_audio(song, sample_rate=44100):
85
+ audio = []
86
+ for syllable, pitch in zip(song.syllables, song.pitch_maps):
87
+ syllable_audio = []
88
+ for phoneme in _split_syllable(syllable):
89
+ if phoneme in _vowels:
90
+ sound = _generate_vowel(phoneme, pitch, sample_rate=sample_rate)
91
+ elif phoneme in _fricatives:
92
+ sound = _generate_fricative(phoneme, pitch, sample_rate=sample_rate)
93
+ elif phoneme in _nasals:
94
+ sound = _generate_nasal(phoneme, pitch, sample_rate=sample_rate)
95
+ else:
96
+ continue
97
+ syllable_audio.append(sound)
98
+ if syllable_audio:
99
+ audio.extend(np.concatenate(syllable_audio))
100
+ return np.array(audio, dtype=np.float32)
101
+ def play(song):
102
+ audio = _generate_audio(song)
103
+ sd.play(audio, samplerate=44100)
104
+ sd.wait()
105
+ def save_wav(song, filename):
106
+ audio = _generate_audio(song)
107
+ sf.write(filename, audio, 44100)
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: vapylib
3
+ Version: 0.1.0
4
+ Summary: a python voice synthesis lib
5
+ License-Expression: GPL-3.0-only
6
+ Requires-Python: >=3.10
7
+ License-File: LICENSE
8
+ Requires-Dist: numpy
9
+ Requires-Dist: scipy
10
+ Requires-Dist: sounddevice
11
+ Requires-Dist: soundfile
12
+ Requires-Dist: PyYAML
13
+ Dynamic: license-file
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ vapylib/__init__.py
5
+ vapylib/parser.py
6
+ vapylib/synthesizer.py
7
+ vapylib.egg-info/PKG-INFO
8
+ vapylib.egg-info/SOURCES.txt
9
+ vapylib.egg-info/dependency_links.txt
10
+ vapylib.egg-info/requires.txt
11
+ vapylib.egg-info/top_level.txt
@@ -0,0 +1,5 @@
1
+ numpy
2
+ scipy
3
+ sounddevice
4
+ soundfile
5
+ PyYAML
@@ -0,0 +1 @@
1
+ vapylib