Skip to content

Commit

Permalink
Merge pull request #2 from Sg4Dylan/dev
Browse files Browse the repository at this point in the history
Stage version
  • Loading branch information
Sg4Dylan authored Dec 26, 2018
2 parents ecea254 + 5b2e146 commit c1aba31
Show file tree
Hide file tree
Showing 12 changed files with 1,192 additions and 959 deletions.
341 changes: 0 additions & 341 deletions EmiyaEngine.py

This file was deleted.

557 changes: 0 additions & 557 deletions GUI_EmiyaEngine.py

This file was deleted.

181 changes: 120 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,124 @@
> "只要蘊藏著想成為真物的意志,偽物就比真物還要來得真實。"
Emiya Engine 是一个用来丰富音频频谱的脚本。可以将频谱变得好看那么一点。
原理是使用 FFT (快速傅立叶变换) 将音频信号采样转到频域,在频域上为空白的频谱加上与时域幅值相称的微小抖动。

### 使用须知:

- 由于 FFT 的栅栏效应,程序的处理过程不可避免地会损失部分采样信号。**故不建议将本程序用于玄学领域。**
- 由于程序缺乏细致调教,当前算法会导致部分采样块未被有效处理。
- 由于玄学原因,最后生成的文件有一定几率出现时长一百余个采样点(0.0016s)的爆音区域。
- 由于处理需要消耗大约是音源时长的 2 - 10 倍的时间,故不建议输入较长的音频文件。
- 鉴于脚本输出为 96KHz 采样 32bit 单精度浮点型 的 WAV 文件,请不要输入比输出精度更高的文件。

### 程序依赖:

- Python 3
- numpy
- scipy
- librosa
- resampy
- colorama
- PyQt5 (GUI版依赖)

#### 依赖安装建议
Windows平台:

> 实际上 librosa 的依赖非常多,如果使用 `pip`安装,可能会导致出错,
> 建议直接在[这里](http://www.lfd.uci.edu/~gohlke/pythonlibs/)下载二进制包使用`pip`离线安装。
> 除了以上列表的依赖,还有`Cython``scikit-learn`,建议一块装了。
> `librosa` 需要配置 `ffmpeg` 的目录,
> 找到 `Python` 安装目录下 `Lib\site-packages\audioread` 文件夹的 `ffdec.py` 文件。
> 修改第 32 行,修改为你的 `ffmpeg` 程序路径,比如我的放在 F 盘根目录,设置成这样:
> `COMMANDS = ('F:\\ffmpeg.exe', 'avconv')`
Linux平台:

> ArchLinux 上建议用 pacman 一路搞定 numpy scipy scikit-learn Cython,当然使用pip也是OK的。
> Debian/Ubuntu 上默认源似乎必须用 apt-get 安装,有一些包用 pip 安装有些问题。
> 与 Windows 平台一样,也需要为 librosa安装后端解码音频文件,
> 直接使用发行版自带的包管理器安装 ffmpeg 就可以。
> 如果出错了,建议从源编译最新的放在原来的路径下。
### 命令行帮助:

-h, --help 显示帮助信息.
-i INPUT, --input INPUT
待处理文件的绝对路径, 同一路径可直接输入文件名. 例如:
Music_ready_test.mp3
-d DEBUG, --debug DEBUG
调试等级设定. 默认 1 级.
设置为 0 时, 只显示任务起始日志;
设置为 1 时, 额外显示进度日志;
设置为 2 时, 额外显示处理细节日志
-s SIZE, --size SIZE
倒腾区大小. 默认 500.
使用倒腾区是因为 numpy 做大数组 append 速度远低于小数组,
故加入小数组多倒腾一手, 这个参数就是小数组的尺寸.
-w WINDOW, --window WINDOW
分析用汉宁Hann双余弦窗启用开关. 默认不使用.
输入 0 代表不使用, 1 代表使用.

### 效果预览:
音源:44.1KHz@16bit WAV
![enter image description here](https://i.imgur.com/VU9Obqw.jpg)

---

### 当前版本:

`RC Version 0`

### 编年史:

- `Alpha.0 Rev.3`
> 这算是 Emiya Engine 的第一个阶段成果,目标的最小实现
> 简单说原理就是矩形窗暴力 FFT,移频,乘以乱数,叠加,IFFT
> 大部分的代码是为了处理超大数组拼接速度缓慢的问题
> 处理后的音频有大量爆音及咔哒声,低电平音频容易看出处理痕迹
- `Alpha.1 Rev.0`
> 该版本为 Alpha.0 的重构改进,主要工作是改写为多进程执行
> 为消除频谱图上可见的断层,加入了整数倍时域重采样机制
> 事实上重采样带来的运算增加远超多进程带来的提升,所以...
> 以及因为多进程,处理需要占用更大的内存,性能消耗巨大
> 爆音和咔哒声依旧存在,但已大幅减少,处理痕迹依旧能看出来
- `Alpha.1 Rev.1`
> 这一版中加入了 AkkoMode
> 这一模式原理极其简单,就是给原始信号采样点分别乘以极小的随机数
> 可以视作信号在有微小热噪声的线路走了一趟
> 处理后无爆音及咔哒声,但在低电平音频上能听出背景噪声
> 消除背景噪声就必须暴露处理痕迹
- `Alpha.3`
> 推翻了之前的所有代码的完全重构,处理结果类似 DSEE HX
> 这一版本质是高通滤波器 + 混频器
> 将高通滤波后的信号分离为打击乐及弦乐,然后增益后叠加在原始信号上
> 丢掉了自造的 FFT 轮子,改用库实现的 SFFT
> 因此不存在爆音和咔哒声,也不再需要额外多倍重采样,速度极大提升
> 这一版本参数调节极其重要,需要参照结果反复调整参数
> 正确调整参数的处理样本完全不增加爆音及咔哒声,加上 EQ 能完全抹平处理痕迹
### 当前版本使用说明:

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

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

首先需要分析音乐类型,对于以下类型不建议使用 AkkoMode:
- 电子合成纯音乐,背景乐器只有一两样的
- 人声清唱带一个伴奏乐器
- 其他频谱图中最高频率不到 18kHz 的音乐

例如这样的:
![sample-not-for-akkomode](https://i.imgur.com/Fd4EoGN.jpg?1)

AkkoMode 适用于大部分时候音量都很大的流行乐(比如 JPOP),
处理时应选用 Apple iTunes 购买的 AAC 格式音频,常见的频谱长这样:
![some-jpop](https://i.imgur.com/swdtDz6.jpg)
因为参数只有俩,调整并不麻烦,此处就不展开说了。

CopyBand 模式需要设置六个参数,配置之前观察频谱。
以某网站下载的音乐为例,以下是其频谱图及频率分析图:
![sample-spec-0](https://i.imgur.com/RzEzmtl.jpg)
![sample-spec-1](https://i.imgur.com/t0ps5iS.png)

从两张图中可以明显看出频率在 17kHz 不到的地方戛然而止,
如果目标是生成 48kHz 文件,则需要补齐 24-17=7kHz 的部分。
而 17-7=10kHz,故 HPF 截止频率应设定在 10kHz 以下,
而调制频率则在 HPF 截止频率上加上 7k。
本例中设定为 9k 及 16k。
这首歌背景音乐以打击乐为主,因此能量集中在冲击部分,
调参数时,首先将谐波增益设置为 0,可以避免参数过多干扰测试。
冲击增益可以从 5 开始测试,勾上测试模式,启动输出,
检查输出文件频率分析结果:
![sample-result-0](https://i.imgur.com/gqBmSFy.png)

很明显,在 17-21kHz 的地方本应该是比 17kHz 以下的部分“矮”一些的。
(高频衰减更大,所以高频部分通常增益应低于低频)
因此,根据观察结果,将冲击增益调为 2.5(折半试错),再重新跑一次。
(此时要在其他软件中关闭文件,否则会发生错误)
调整后的频率分析结果变成了这样:
![sample-result-1](https://i.imgur.com/aDempeR.png)

此时已经很接近理想的样子了,因为还要加入谐波的部分(前边设定成了 0)
故再将冲击增益降低 0.5,同时给谐波增益改为 1.0 并再次执行。
结果变成了这样:
![sample-result-2](https://i.imgur.com/cAqZdbQ.png)
看起来不错,直接取消测试模式生成最终结果。
生成最终结果时可能会很卡,请不要担心并耐心等待,进度条将滚动四次(两声道音频)。

接着检查频谱,输出如下:
![sample-result-3](https://i.imgur.com/E5I1fMf.jpg)
![sample-result-4](https://i.imgur.com/pFZDbHB.png)
此时是不是有点失望了,很明显的衔接痕迹对不对。

没关系,这时可以打开 Audition 效果中的 FFT 滤波器,
接着拿起刚才的频率分析结果图,照着图调整 FFT 滤波器,比如这样:
![au-fft-filter](https://i.imgur.com/dbHxIKH.png)
应用后,频率分析结果变成了这样:
![final-0](https://i.imgur.com/9eYJs8V.png)
而频谱中的衔接痕迹已经不明显了:
![final-1](https://i.imgur.com/X1cDgcX.jpg)

放大频谱细节,可以看出雾蒙蒙的部分依然有欠缺,
![final-2](https://i.imgur.com/9AbW9j2.jpg)
这是谐波增益不够的原因,可以继续调整改善。最终得到以下结果:
![final-3](https://i.imgur.com/2UO9OnW.jpg)

### 其他提示:

由于 CopyBand 本质是复制粘贴已有的部分,
因此对于超过 48kHz 以上的拉升,需要多次处理达成,
例如以下原始文件不到 16kHz:
![ex-0](https://i.imgur.com/eAui0i7.jpg)
拉升到 48kHz 采样需要的频率片段至少为 24-16=8kHz,
而拉升到 96kHz 采样则需要 48-16=32kHz。
而原始音频中都没有 32kHz 的容量,
因此在最终拉升到 96kHz 之前需要重复至少三次操作。
在这一过程中,最大频率由 16 最终变为 48Hz。
由于事实上 20kHz 以上的听不见,所以你做得再多也无妨(笑)。
例如上边的例子被拉升到 192kHz 采样率,宛如天籁之声:
![ex-1](https://i.imgur.com/QWKpaHA.jpg)

### 特别提醒
~~请不要使用这个脚本制造 `'HiRes'` 逗玄学家玩~~

6 changes: 6 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"ui": "res/window.ui",
"lang": "chs",
"eng": "res/eng.qm",
"chs": "res/chs.ts"
}
61 changes: 61 additions & 0 deletions core/akkomode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import random
import librosa
import resampy
#from tqdm import tqdm


def core(
input_path,output_path,
output_sr=48000,inter_sr=1,
test_mode=False,
sv_l=0.02,sv_h=0.55,
update=None
):

# 加载音频
y, sr = librosa.load(input_path,mono=False,sr=None)
if test_mode:
y, sr = librosa.load(input_path,mono=False,sr=None,offset=round(len(y[0])/sr/2),duration=5)
y = resampy.resample(y, sr, output_sr * inter_sr, filter='kaiser_fast')

# AkkoMode
for chan in y:
# 是否第一次执行
is_loop_once = True
# 前一次的数值
pre_value = 0
# 前一次操作的数值
pre_opt = 0
# 实际操作
#for i in tqdm(range(len(chan)),unit='Segment',ascii=True):
for i in range(len(chan)):
update.emit(i/len(chan))
this_value = chan[i]
# 构造抖动值
linear_jitter = 0
if pre_value < this_value:
linear_jitter = random.uniform(this_value*-sv_l, this_value*sv_h)
else:
linear_jitter = random.uniform(this_value*sv_h, this_value*-sv_l)
# 应用抖动
if pre_opt*linear_jitter > 0:
chan[i] = this_value + linear_jitter
elif pre_opt*linear_jitter < 0:
chan[i] = this_value - linear_jitter
else:
pass
# 第一次操作特殊化处理
if is_loop_once:
linear_jitter = random.uniform(this_value*-sv_h, this_value*sv_h)
chan[i] += linear_jitter
is_loop_once = False
# 保存到上一次记录
pre_value = this_value
pre_opt = linear_jitter

# 合并输出
final_data = resampy.resample(y,
output_sr * inter_sr,
output_sr,
filter='kaiser_fast')
librosa.output.write_wav(output_path, final_data, output_sr)
57 changes: 57 additions & 0 deletions core/copyband.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import numpy as np
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,
harmonic_hpfc=6000,harmonic_sft=16000,harmonic_gain=1.2,
percussive_hpfc=6000,percussive_stf=16000,percussive_gain=2.5,
update=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
shift_point = round(shift/(sr/data.shape[0]))
# 调制
for p in reversed(range(len(chan[:,i]))):
data[:,i][p] = data[:,i][p-shift_point]
# 高通滤波
data = librosa.stft(signal.filtfilt(b,a,librosa.istft(data)))
data *= gain
return data

# 加载音频
y, sr = librosa.load(input_path,mono=False,sr=None)
if test_mode:
y, sr = librosa.load(input_path,mono=False,sr=None,offset=round(len(y[0])/sr/2),duration=5)
y = resampy.resample(y, sr, output_sr * inter_sr, filter='kaiser_fast')
# 产生 STFT 谱
stft_list = [librosa.stft(chan) for chan in y]

# 谐波增强模式
for chan in stft_list:
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

# 合并输出
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)
Loading

0 comments on commit c1aba31

Please sign in to comment.