Skip to content

Commit

Permalink
Update to RC 1
Browse files Browse the repository at this point in the history
  • Loading branch information
Sg4Dylan committed Feb 25, 2019
1 parent 5b2e146 commit 173864b
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 18 deletions.
33 changes: 29 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Emiya Engine 是一个用来丰富音频频谱的脚本。可以将频谱变得

### 当前版本:

`RC Version 0`
`RC Version 1`

### 编年史:

Expand Down Expand Up @@ -38,14 +38,19 @@ Emiya Engine 是一个用来丰富音频频谱的脚本。可以将频谱变得
> 因此不存在爆音和咔哒声,也不再需要额外多倍重采样,速度极大提升
> 这一版本参数调节极其重要,需要参照结果反复调整参数
> 正确调整参数的处理样本完全不增加爆音及咔哒声,加上 EQ 能完全抹平处理痕迹
- `RC 1`
> 加入了参数辅助调整选项
> 尝试在 CopyBand 模式下保护动态范围
### 当前版本使用说明
### RC 版本使用说明

为了方便使用,特地做了个 GUI 界面,
但实际上还是挺难用的,所以还是说一下。

工具: Spek(仅频谱观察用),Audition(频谱观察/频率分析/后期处理用)

---

首先需要分析音乐类型,对于以下类型不建议使用 AkkoMode:
- 电子合成纯音乐,背景乐器只有一两样的
- 人声清唱带一个伴奏乐器
Expand All @@ -57,9 +62,29 @@ Emiya Engine 是一个用来丰富音频频谱的脚本。可以将频谱变得
AkkoMode 适用于大部分时候音量都很大的流行乐(比如 JPOP),
处理时应选用 Apple iTunes 购买的 AAC 格式音频,常见的频谱长这样:
![some-jpop](https://i.imgur.com/swdtDz6.jpg)
因为参数只有俩,调整并不麻烦,此处就不展开说了。
因为参数只有俩,多试几次就知道,此处就不展开说了。

---

CopyBand 辅助调整:

CopyBand 模式需要设置六个参数,上手困难,
因此在 `RC 1` 版本中加入了辅助调节手段。

首先勾选 `优化建议``样本输出模式` 并将谐波增益倍率设定为 0。然后执行一次。
执行完成后,将提示窗中的 `建议增益` 填写到冲击增益倍率,再执行一次。
以此反复,当提示 `建议维持冲击增益` 时,即辅助调节冲击增益完成。
若需要更好品质,可尝试将谐波增益倍率改为 1,然后再执行一次
加入谐波后,提示窗中的 `建议增益` 失去参考价值,
此时可以参考提示的 `当前增益` 进行调节,
同样的,当提示 `建议维持冲击增益` 时,即调节完成。
若需要更好的频谱「品相」,可以参考下步骤手动调整。

---

CopyBand 完全手动调整步骤:

CopyBand 模式需要设置六个参数,配置之前观察频谱。
配置之前观察频谱。
以某网站下载的音乐为例,以下是其频谱图及频率分析图:
![sample-spec-0](https://i.imgur.com/RzEzmtl.jpg)
![sample-spec-1](https://i.imgur.com/t0ps5iS.png)
Expand Down
10 changes: 8 additions & 2 deletions core/akkomode.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def core(
output_sr=48000,inter_sr=1,
test_mode=False,
sv_l=0.02,sv_h=0.55,
update=None
update=None,msgbox=None
):

# 加载音频
Expand Down Expand Up @@ -58,4 +58,10 @@ def core(
output_sr * inter_sr,
output_sr,
filter='kaiser_fast')
librosa.output.write_wav(output_path, final_data, output_sr)
try:
librosa.output.write_wav(output_path, final_data, output_sr)
except PermissionError:
msgbox.emit("警告",
"无法写入文件,请检查目标路径写入权限" \
"以及文件是否已被其他程序开启。",
0)
103 changes: 96 additions & 7 deletions core/copyband.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,22 @@
import scipy.signal as signal
import librosa
import resampy
#from tqdm import tqdm


def core(
input_path,output_path,
output_sr=48000,inter_sr=1,
test_mode=False,
test_mode=False,opti_mode=True,dyn_protect=True,
harmonic_hpfc=6000,harmonic_sft=16000,harmonic_gain=1.2,
percussive_hpfc=6000,percussive_stf=16000,percussive_gain=2.5,
update=None
update=None,msgbox=None
):

def hpd_n_shift(data, lpf, sft, gain):
# 高通滤波
b,a = signal.butter(3,lpf/(sr/2),'high')
data = librosa.stft(signal.filtfilt(b,a,librosa.istft(data)))
# 拷贝频谱
#for i in tqdm(range(data.shape[1]),unit='Segment',ascii=True):
for i in range(data.shape[1]):
update.emit(i/data.shape[1])
shift = sft
Expand All @@ -32,6 +30,12 @@ def hpd_n_shift(data, lpf, sft, gain):
data *= gain
return data

# Dyn Protect Tips
if dyn_protect:
msgbox.emit("提示",
"动态范围保护特性已启用\n",
1)

# 加载音频
y, sr = librosa.load(input_path,mono=False,sr=None)
if test_mode:
Expand All @@ -45,13 +49,98 @@ def hpd_n_shift(data, lpf, sft, gain):
D_harmonic,D_percussive = librosa.decompose.hpss(chan, margin=4)
D_harmonic = hpd_n_shift(D_harmonic,harmonic_hpfc,harmonic_sft,harmonic_gain)
D_percussive = hpd_n_shift(D_percussive,percussive_hpfc,percussive_stf,percussive_gain)
chan += D_harmonic
chan += D_percussive

if not dyn_protect:
chan += D_harmonic + D_percussive
else:
# 动态范围保护
adp = D_harmonic + D_percussive
adp_power = np.mean(np.abs(adp))
src_power = np.mean(np.abs(chan))
src_f = 1-(adp_power/src_power)
d_ls.append(src_f)
adp += src_f*chan
chan *= 0
chan += adp


# 合并输出
istft_list = [librosa.istft(chan) for chan in stft_list]
final_data = resampy.resample(np.array(istft_list),
output_sr * inter_sr,
output_sr,
filter='kaiser_fast')
librosa.output.write_wav(output_path, final_data, output_sr)
try:
librosa.output.write_wav(output_path, final_data, output_sr)
except PermissionError:
msgbox.emit("警告",
"无法写入文件,请检查目标路径写入权限" \
"以及文件是否已被其他程序开启。",
0)
# 参数优化
if not opti_mode:
return
optimizer(output_path,
percussive_hpfc,percussive_stf,
percussive_gain,msgbox)


def optimizer(
output_path,
hpf_cut_freq,
hpf_mod_freq,
hpf_gain,
msgbox
):

# 加载音频
y, sr = librosa.load(output_path,mono=False,sr=None)
# 产生 STFT 谱
stft_list = [librosa.stft(chan) for chan in y]

# 加载
l_power = 0
h_power = 0
for chan in stft_list:
# 截止频率为 hpf_cut_freq 的 HPF
b,a = signal.butter(11,hpf_cut_freq/(sr/2),'high')
l_data = librosa.stft(signal.filtfilt(b,a,librosa.istft(chan)))
l_power_sum = np.mean(np.abs(l_data.real))

# 截止频率为 hpf_mod_freq 的 HPF
b,a = signal.butter(11,hpf_mod_freq/(sr/2),'high')
h_data = librosa.stft(signal.filtfilt(b,a,librosa.istft(chan)))
h_power_sum = np.mean(np.abs(h_data.real))

# 移相差分
l_power_sum -= h_power_sum

# 合并音轨数据
l_power += l_power_sum
h_power += h_power_sum

# 测试 HPF 输出
chan *= 0
chan += h_data

# 频带比例矫正能量比例
pf = ((sr/2)-hpf_mod_freq)/(hpf_mod_freq-hpf_cut_freq)
l_power *= pf
# 计算当前增益比率
lnh_power = np.log2(l_power/h_power) * 6
# 如果是正数,就是增益偏小
target_r = np.exp2((lnh_power-14.8)/6)
recomm_r = target_r*hpf_gain
# Tips
tips = ''
tips += f'来源加和:{l_power}\n'
tips += f'目标加和:{h_power}\n'
tips += f'当前增益:{lnh_power} dB\n'
if lnh_power < 14.5 or lnh_power > 16:
tips += f'建议增益:{recomm_r}'
else:
tips += '建议维持冲击增益,微调谐波增益!'

msgbox.emit('优化建议',tips,1)
#istft_list = [librosa.istft(chan) for chan in stft_list]
#librosa.output.write_wav('kk.wav', np.array(istft_list), sr)
22 changes: 17 additions & 5 deletions main.pyw
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Core(QtCore.QThread):

Update = QtCore.pyqtSignal([float])
Finish = QtCore.pyqtSignal()
MsgBox = QtCore.pyqtSignal([str,str,int])

def __init__(self, parent, mode, **kwargs):
super(Core, self).__init__(parent)
Expand All @@ -21,18 +22,19 @@ class Core(QtCore.QThread):
copyband.core(
self.kwargs['input_path'],self.kwargs['output_path'],
self.kwargs['output_sr'],self.kwargs['inter_sr'],
self.kwargs['test_mode'],
self.kwargs['test_mode'],self.kwargs['opti_mode'],
self.kwargs['dyn_protect'],
self.kwargs['harmonic_hpfc'],self.kwargs['harmonic_sft'],
self.kwargs['harmonic_gain'],self.kwargs['percussive_hpfc'],
self.kwargs['percussive_stf'],self.kwargs['percussive_gain'],
self.Update)
self.Update,self.MsgBox)
else:
akkomode.core(
self.kwargs['input_path'],self.kwargs['output_path'],
self.kwargs['output_sr'],self.kwargs['inter_sr'],
self.kwargs['test_mode'],
self.kwargs['sv_l'],self.kwargs['sv_h'],
self.Update)
self.Update,self.MsgBox)
self.Finish.emit()

class MainUI(QtWidgets.QMainWindow, Ui_MainWindow):
Expand All @@ -45,7 +47,7 @@ class MainUI(QtWidgets.QMainWindow, Ui_MainWindow):
self.lang = json.loads(open('res/lang.json','rb').read())[Config['lang']]
self.input_path,self.output_path = None, None
self.is_started = False

def _bind_ui_(self):
self.selectInputFile.clicked.connect(lambda:self.openfile(False))
self.selectOutputFile.clicked.connect(lambda:self.openfile(True))
Expand Down Expand Up @@ -77,17 +79,21 @@ class MainUI(QtWidgets.QMainWindow, Ui_MainWindow):
output_sr=int(self.commOutputSr.currentText()[:-2]),
inter_sr=int(self.commInsertSr.currentText()[:-1]),
test_mode=self.useSampleOutput.isChecked(),
opti_mode=self.useOptimizer.isChecked(),
dyn_protect=self.dynProtect.isChecked(),
harmonic_hpfc=int(self.cbHarmonicHpfCutFreq.value()),
harmonic_sft=int(self.cbHarmonicShiftFreq.value()),
harmonic_gain=float(self.cbHarmonicGain.value()),
percussive_hpfc=int(self.cbPercussiveHpfCutFreq.value()),
percussive_stf=int(self.cbPercussiveShiftFreq.value()),
percussive_gain=float(self.cbPercussiveGain.value()),
sv_l=float(self.akkoJitterDownFactor.value()),
sv_h=float(self.akkoJitterUpFactor.value()))
sv_h=float(self.akkoJitterUpFactor.value()),
qwidget=self)
self.CoreObject.start()
self.CoreObject.Update.connect(self.proc_bar_bind)
self.CoreObject.Finish.connect(self.proc_end_bind)
self.CoreObject.MsgBox.connect(self.msgbox)
self.globalExec.setText(self.lang['ExecBtnTextStop'])
self.is_started = True
else:
Expand All @@ -103,6 +109,12 @@ class MainUI(QtWidgets.QMainWindow, Ui_MainWindow):
self.is_started = False
self.progressBar.setValue(0)
self.globalExec.setText(self.lang['ExecBtnTextStart'])

def msgbox(self,title,text,type=0):
if type == 0:
QtWidgets.QMessageBox.critical(self, title, text)
else:
QtWidgets.QMessageBox.information(self, title, text)


if __name__ == "__main__":
Expand Down
32 changes: 32 additions & 0 deletions res/window.ui
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
<height>31</height>
</rect>
</property>
<property name="dragEnabled">
<bool>false</bool>
</property>
</widget>
<widget class="QPushButton" name="selectInputFile">
<property name="geometry">
Expand Down Expand Up @@ -101,6 +104,9 @@
<height>31</height>
</rect>
</property>
<property name="dragEnabled">
<bool>false</bool>
</property>
</widget>
<widget class="QPushButton" name="selectOutputFile">
<property name="geometry">
Expand Down Expand Up @@ -595,6 +601,32 @@
<string>样本输出模式</string>
</property>
</widget>
<widget class="QCheckBox" name="useOptimizer">
<property name="geometry">
<rect>
<x>10</x>
<y>40</y>
<width>101</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>优化建议</string>
</property>
</widget>
<widget class="QCheckBox" name="dynProtect">
<property name="geometry">
<rect>
<x>10</x>
<y>60</y>
<width>101</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>动态范围保护</string>
</property>
</widget>
</widget>
<widget class="QPushButton" name="globalExec">
<property name="geometry">
Expand Down

0 comments on commit 173864b

Please sign in to comment.