-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsim_pwm.py
140 lines (114 loc) · 4.11 KB
/
sim_pwm.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import argparse
import math
import numpy
import librosa
import soundfile as sf
def params(freq, damping, dt):
w = freq * 2 * math.pi * dt
d = damping * dt
e = math.exp(d)
c1 = 2 * e * math.cos(w)
c2 = e * e
t0 = (1 - 2 * e * math.cos(w) + e * e) / (d * d + w * w)
t = d * d + w * w - math.pi * math.pi
t1 = (1 + 2 * e * math.cos(w) + e * e) / math.sqrt(t * t + 4 * d * d *
math.pi * math.pi)
b2 = (t1 - t0) / (t1 + t0)
b1 = b2 * dt * dt * (t0 + t1) / 2
return c1, c2, b1, b2
def filter_audio(
audio: numpy.ndarray, sample_rate: float,
sim_rate: float) -> numpy.ndarray:
freq = 3875
dt = 1 / sim_rate
damping = -1210
c1, c2, b1, b2 = params(freq, damping, dt)
y1 = y2 = 0
x1 = 0
x2 = 0
x1 = 1.0
scale = 1150 # TODO: analytic expression
maxy = 0
cycles_per_sample = int(sim_rate / sample_rate)
audio_idx = 0
sample = audio[0]
duty_cycle = int((sample + 1) * cycles_per_sample / 2)
next_toggle = duty_cycle
cycles = 0
y = 0
bias = 1.0
while audio_idx < len(audio) / 1:
if audio_idx % 10000 == 0:
print(audio_idx, len(audio), y / scale, x1, sample, duty_cycle,
next_toggle)
cycles += 1
# x1 += bias / 4
# if x1 > 1.0:
# x1 = 1.0
# if x1 < -1.0:
# x1 = -1.0
y = (c1 * y1 - c2 * y2 + b1 * x1 + b2 * x2)
x2 = x1
if cycles >= next_toggle:
bias *= -1.0
x1 *= -1.0
# x1 += bias / 4
# if x1 > 1.0:
# x1 = 1.0
# if x1 < -1.0:
# x1 = -1.0
if bias == 1.0:
audio_idx += 1
if audio_idx < len(audio):
sample = audio[audio_idx]
duty_cycle = int((sample + 1) * cycles_per_sample / 2)
else:
duty_cycle = cycles_per_sample - duty_cycle
next_toggle += duty_cycle
y2 = y1
y1 = y
if math.fabs(y) > maxy:
maxy = math.fabs(y)
yield y / scale
print("scale = %f" % maxy)
def preprocess(
filename: str, target_sample_rate: int, normalize: float,
normalization_percentile: int) -> numpy.ndarray:
"""Upscale input audio to target sample rate and normalize signal."""
data, _ = librosa.load(filename, sr=target_sample_rate, mono=True)
max_value = numpy.percentile(data, normalization_percentile)
data /= max_value
data *= normalize
return data
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--clock", choices=['pal', 'ntsc'],
help="Whether target machine clock speed is PAL ("
"1015657Hz) or NTSC (1020484)",
required=True)
parser.add_argument("--normalization", default=1.0, type=float,
help="Overall multiplier to rescale input audio "
"values.")
parser.add_argument("--norm_percentile", default=100,
help="Normalize to specified percentile value of input "
"audio")
parser.add_argument("input", type=str, help="input audio file to convert")
parser.add_argument("output", type=str, help="output audio file")
args = parser.parse_args()
# Effective clock rate, including every-65 cycle "long cycle" that takes
# 16/14 as long.
sim_rate = 1015657 if args.clock == 'pal' else 1020484 # NTSC
input_audio = preprocess(args.input, sim_rate / 46, args.normalization,
args.norm_percentile)
output_audio = numpy.array(
list(filter_audio(input_audio, sim_rate / 46, sim_rate)),
dtype=numpy.float32)
output_rate = 96000
output = librosa.resample(output_audio, orig_sr=sim_rate,
target_sr=output_rate)
with sf.SoundFile(
args.output, "w", output_rate, channels=1, format='WAV') \
as f:
f.write(output)
if __name__ == "__main__":
main()