-
Notifications
You must be signed in to change notification settings - Fork 0
/
wtfile.py
122 lines (95 loc) · 3.42 KB
/
wtfile.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" process wavetable files """
import sys
import os
import struct
import numpy as np
import soundfile as sf
WAV_SAMPLE_RATE = 44100
H2P_HEADER = """\
#defaults=no
#cm=OSC
Wave=2
<?
float Wave[%d];
"""
H2P_FMULT = 0.999969
def print_err(msg):
print(msg, file=sys.stderr)
class Wt:
"""
save wavetables
"""
def __init__(self, waveforms, bitwidth=32):
"""
waveforms: numpy array with shape (waveforms, samples)
"""
self.wf = waveforms.flatten()
self.num_waveforms, self.num_samples = waveforms.shape
if bitwidth not in [16, 32]:
print_err(f'wrong bitwidth: {bitwidth}')
sys.exit(1)
self.bitwidth = bitwidth
self.normalize = True
# print(f'Wav waveforms: {self.num_waveforms}, '
# f'samples: {self.num_samples}')
def set_normalize(self, is_required):
self.normalize = bool(is_required)
def save_wav(self, fn):
"""
save WAV wavetable, 16 PCM / 32 float
https://pysoundfile.readthedocs.io/en/latest/#module-soundfile
"""
if os.path.exists(fn):
print_err(f'File "{fn}" exists')
return
normalized = self.wf / np.max(np.abs(self.wf)) if self.normalize else self.wf
if self.bitwidth == 32:
data = np.float32(normalized)
wav_type = 'FLOAT'
else:
data = np.int16(normalized * 32767)
wav_type = 'PCM_16'
sf.write(fn, data, WAV_SAMPLE_RATE, wav_type)
def save_wt(self, fn):
"""
save WT wavetable for Surge XT and Bitwig
https://github.com/surge-synthesizer/surge/blob/main/resources/data/wavetables/WT%20fileformat.txt
"""
if os.path.exists(fn):
print_err(f'File "{fn}" exists')
return
with open(fn, "wb") as file:
header = bytearray(12)
header[:4] = b'vawt'
header[4:8] = struct.pack('<I', self.num_samples)
header[8:10] = struct.pack('<H', self.num_waveforms)
# 0x04 int16/float32 2/4 bytes, int16 if 1
bits = 0 if self.bitwidth == 32 else 4
header[10:12] = bytes([bits, 0])
file.write(header)
normalized = self.wf / np.max(np.abs(self.wf)) if self.normalize else self.wf
if self.bitwidth == 32:
normalized.astype(np.float32).tofile(file)
else:
(normalized * 32767).astype(np.int16).tofile(file)
def save_h2p(self, fn):
"""
save h2p wavetable for u-he Zebra 2 oscillator
source array should be (16, 256)
format borrowed from
https://github.com/harveyormston/osc_gen/blob/main/osc_gen/zosc.py
"""
if os.path.exists(fn):
print_err(f'File "{fn}" exists')
return
with open(fn, "w", encoding="utf-8") as file:
print(H2P_HEADER % self.num_samples, file=file)
for tn, i in enumerate(range(0, len(self.wf), self.num_samples), start=1):
print(f'//table {tn}', file=file)
wave_values = [f*H2P_FMULT for f in self.wf[i:(i+self.num_samples)]]
for k, f in enumerate(wave_values):
print(f'Wave[{k}] = {f:.10f};', file=file)
print(f'Selected.WaveTable.set({tn}, Wave);\n', file=file)
print('?>', file=file)