Sculptor:实时相位声码器

作者:Nick Bailey

在某些方面,计算机音乐对操作系统提出了极高的要求,尤其是在廉价的桌面平台现在拥有足够的原始处理能力来实时执行相对复杂的信号处理任务的情况下。共享内存和 System V IPC 是实现实时控制下音频操作工具的强大盟友。Sculptor 是一套用于 Linux 的工具,它使用这些技术即使在配置普通的平台上也能产生令人印象深刻的吞吐量。它最初被设想为一种研究工具,但最终可能成为一种乐器。

计算机音乐和实时数字信号处理

这是一个关于一个程序的故事,该程序在 PDP-11 上以批处理模式运行,需要数小时才能产生几秒钟的音频输出,而现在可以在廉价的桌面 Linux 机器上实时运行。将程序从批处理模式更改为实时模式对程序员提出了巨大的挑战:用户界面成为一个问题,对软件施加了全新的结构,因为源自用户界面的事件需要与实时音频合成异步处理。

音色意味着声音的色彩,是谐波内容的感知相关性,就像音高在感知上与频率相关一样。小提琴和长笛可以以相同的音高和响度演奏,但始终具有不同的音色。计算机音乐家喜欢使用的一种过程是变形,其中声音可以从初始音色平滑地改变为最终音色。许多读者会熟悉视频变形的过程,并且会理解它与简单的交叉淡化是完全不同的过程。实现音频等效的一种方法是操作音频信号,不是作为一系列时间样本,而是作为一系列不断演变的频谱。通过改变声音频谱演变过程中的属性,可以产生这种效果和许多其他有趣的效果。

在本文中,将探讨一种实时操作频谱并提供连续音频输出的方法。已经编写了一个示例 xview 应用程序,因此任何拥有这些库和合适声卡的人都可以自行实验。

使用相位声码器进行频谱操作

相位声码器是在频域中操作声音的更强大的方法之一。它不是一项新技术;麻省理工学院的 CSound 应用程序(请参阅“资源”),该程序由 Barry Vercoe 从最初为 PDP-11 小型计算机用汇编语言编写的 MUSIC11 程序移植到 C 语言和 UNIX,其中包含相位声码器软件。然而,该算法非常复杂,而且当时的计算机处理能力不足,通常需要数小时的处理才能实现每秒的音频输出。直到最近,足够的处理能力才到达桌面,使实时相位声码成为可行的方案。

声码器是一种电子信号处理器,由分布在感兴趣频带上的滤波器组组成。最初,人们希望这种设备能够减少语音电话传输所需的带宽,但它很快在流行音乐中找到了其他应用。语音信号可以通过滤波器组实时分析,并将输出应用于压控滤波器组或振荡器组,以产生原始信号的失真再现。在一些 Electric Light Orchestra 的曲目以及电影 Educating Rita 的主题音乐中可以听到这种效果。

在 Michael Portnoff(请参阅“资源”)演示了一种以数字方式构建所需滤波器组的有效方法之后,计算机数字相位声码器的实现大门敞开,为音频的分析、操作和合成带来了大量的可能性。为了利用这项技术来提高我对声音音色及其频谱之间关系的理解,我着手编写了 Sculptor,这是一个用于 Linux 的实时交互式相位声码器。

以数字方式实现相位声码器

Sculptor 中的相位声码器分为两个部分:一个名为 analyse 的批处理模式分析器,以及一个可能更具想象力的实时合成器 prismanalyse 读取 Sun/NeXT 音频格式的输入文件。我们最常使用的采样率是每秒 22,050 个样本,因为我家的 P120 机器可以舒适地跟上这种使用浮点运算的重合成速率,并且有足够的剩余功率来处理运行 X Window 系统界面的工作。可以使用命令行录音工具以通常的方式获取样本,但我们发现这相当乏味,因此我们用 Tcl/Tk 编写了 Studio(请参阅“资源”),以使获取短样本的过程更易于访问。

图 1. 分析音频样本

analyse 读取样本文件并将其分解为长度约为 10 毫秒的重叠窗口。选择此窗口长度是因为有证据表明耳朵对更短时间尺度上的频谱变化不敏感。每个窗口都经过傅里叶变换,产生频谱样本数组(参见图 1),但不是简单地存储每个傅里叶结果(bin)的幅度和相位,而是记录每个窗口的幅度和相位变化。

为了理解为什么每个窗口的相位变化比绝对相位更重要,让我们考虑一个简单的例子。假设我们使用 8192Hz 的采样率并具有 128 点 FFT。每个窗口将持续约 15 毫秒,傅里叶 bin 之间的间隔将为 64Hz。现在用 1KHz 的正弦波呈现此程序。傅里叶变换无法精确表示此信号;回想一下,它的行为类似于间隔 64Hz 的滤波器组,因此最近的滤波器频率将分别是 bin 15 和 16,分别为 960 和 1024Hz。

当四分之一窗口后分析相同的信号时,它仍将表示为 1024Hz 的正弦波。由于其实际频率较低,因此相位似乎会滞后。通过存储每个窗口的相位变化,保留了足够的信息,至少可以通过在重合成时重叠逆傅里叶变换结果并将它们加在一起,来近似原始的 1000Hz 正弦波。

实时,而非真快

实时运行的声音合成程序与仅以高于声音设备选择吞吐速率生成输出样本的程序之间存在很大差异。对于一个程序来说,要成为实时合成器,它必须对输入参数的变化做出明显的瞬时响应。例如,前面提到的 CSound 应用程序不是实时的,因为它在初始化时读取乐谱和管弦乐队的规范,然后产生音频输出。在程序产生声音时无法影响程序产生的声音。(实际上,一些实时扩展已经可用,但为了这个例子,我选择忽略它们。)在功能强大的工作站上运行 CSound 通常会导致它产生的样本速度快于实际速度,但这并不能使其成为实时程序。

要设计一个实时程序,最重要的设计考虑因素之一是用户界面,而用户界面又受到所需效果的强烈影响。设计过程的下一阶段是考虑此类应用程序所需的操作类型。

作为乐器的相位声码器

当合成程序变为实时时,它就变成了乐器,当计算机程序变成乐器时,操作员就变成了演奏者。乐器的人体工程学非常复杂,但从以前在计算机音乐中使用此算法的背景来看,显然必须涵盖一些核心领域:音高移调,样本音高的变化,但持续时间不变;播放速率,样本在时间上的拉伸或压缩,但音高不变;以及音色变形,其中一种声音平滑地变成另一种声音,就像视频变形中的图片一样。

Sculptor 允许即使在非常普通的计算平台上也能实时独立控制音高和播放速率,并充当更快平台上更高级算法的试验台。它最初是在没有浮点协处理器的 386DX40 上开发的,并且可以尝试以每秒 8000 个样本(语音电话质量)进行实时合成。

选择范例

在确定应用程序本质上包含两个部分(实时合成器和 GUI)之后,将处理过程在这两者之间划分似乎是有意义的。一个进程将负责音频合成,另一个进程将负责鼠标和窗口相关的处理。

像大多数 UNIX 系统一样,Linux 提供了两种不同的进程间通信 (IPC) 方法。第一种是基于通道的:套接字、管道等。这种 IPC 有许多优点;可以轻松地将进程映射到通过网络连接的不同机器上,并且可以轻松安排同步,因为可以将通道设置为以高效、非轮询的方式阻塞,直到数据到达。

prism 应用程序有两个进程,它们基本上异步运行。本质上,无论用户使用鼠标做什么,重合成器都必须保持运行并产生音频样本。出于这个原因,使用了第二种 IPC 方法,即共享内存或 System-V IPC。System-V IPC 还提供了进程同步的方法:信号量。可以提升或等待信号量。可以将其视为一种特殊的变量,其行为方式如下。如果一个或多个进程正在等待信号量,则提升信号量将使这些进程中的一个能够继续进行。如果没有进程在等待,则变量的值会递增。等待非零信号量会递减其值,但允许进程立即继续。等待零值信号量会将当前进程添加到(可能为空的)等待进程列表中,等待另一个代理提升信号量。

信号量用于共享内存情况,以实现互斥锁并防止更新异常,即多个进程同时尝试修改共享数据结构。然而,prism 仅使用两个进程访问共享内存块:GUI 是生产者,因为它提供控制参数,而合成器是消费者,因为它使用这些参数来生成音频样本。由于只有一个生产者和一个消费者,因此无需使用信号量作为访问仲裁器。实际上,利用共享内存 IPC 的优势,允许生产者提供一组根据用户手势变化的“魔法”参数。

分配和使用共享内存块

启动时,prism 必须分配和设置一个共享内存块,然后派生进程以生成音频输出。它使用的例程记录在 shmop 手册页中。分配足够的内存来保存控制结构和分析程序生成的所有频谱数据(请参阅 列表 1)。

prism 调用 shmget 来分配所需的内存量;它返回内存块的句柄以供后续使用。其他参数以正常的 chmod 格式指定访问权限,如果该块尚不存在,则将创建该块。然后,该进程派生子进程负责合成,父进程负责控制功能。

fork 调用之后,父进程和子进程都必须附加共享内存块,并使其出现在各自的内存空间中。适当的系统调用是 shmat。参数指示共享内存块的句柄和所需的目标地址。将 NULL 作为后者传递会告诉系统选择地址。在 i386 架构上运行的 Linux 中,块在内存中向下分配,从 1.5GB 的地址开始。可以在 fork 系统调用之前进行此调用一次,因为共享块随后将出现在子进程和父进程的内存空间中。

一个陷阱在等待抓住使用共享内存块的不谨慎的程序员:它们是持久的。如果您的应用程序崩溃而没有正确清理共享内存块,则内存会像筛子一样泄漏。用户可以使用 ipcs 命令检查任何未删除的内存块,并使用 ipcrm 命令删除它们。prism 会尽力应对任何意外事件,方法是在退出之前捕获 SEGV 信号并关闭任何共享内存活动。但是,防止内存泄漏的最佳保障是在创建共享内存块后立即将其标记为删除。与直觉相反,执行此操作的方法是使用 shmctl 调用将块标记为瞬态,然后将进程与共享内存块分离。共享内存块将持续存在,直到所有使用它的进程都使用 shmdt 调用分离,因此当父进程和子进程退出时,该块将自动消失。

选择 GUI

编写 Sculptor 的目的应该是练习高效的信号处理,而不是复杂的 GUI 设计。最终选择的 GUI 库是 xview,这是一个相当老旧的库,尽管基于 glibc2 的 Red Hat 5.0 版本仍然提供动态和静态版本,因此它还没有完全过时。选择它的主要原因是因为我熟悉它。应用程序的主要要求,即一些简单的菜单、滑块以及在性能画布上检测鼠标事件,几乎可以被从最古老的 Athena 到最新的 GTK+ 的任何小部件集所满足,因此以开放形式广泛提供该库比其技术复杂性更令人关注。Linux Journal 已经发表了一篇文章(请参阅“资源”),演示了 xview 的使用以及使用可变参数列表调用构建简单应用程序的简易性。

当 prism 应用程序以名称 xprism 调用时,将启用 xview GUI。xprism 中的大多数数据流是单向的:GUI 进程生成控制数据,合成器消耗它以产生音频样本。但是,当构思 xprism 的规范时,很明显需要相反方向的异步流。

当 xprism 运行时,会发生以下事件序列

  • 打开分析文件,并计算包含频谱所需的内存大小。为频谱工作空间额外留出空间。与交互模式一样,鼠标操作可能负责设置合成器参数,而不是依赖于简单地自动递增频谱图。

  • 共享控制块(请参阅 列表 2)使用适当的初始值进行设置。

  • 整个频谱文件被读取到共享内存块中。

  • 该进程派生一个负责音频重合成的子进程。

  • 父 xview 小部件层次结构已初始化,并绘制了频谱图。

  • 父进程通过设置共享控制块中的 xv_ready 字段来通知子进程它可以生成音频样本。

  • 父进程和子进程并发运行,使用共享内存区域进行数据传输(参见图 2)。

图 2. 共享内存块中的数据流。

此过程对于简单的手势控制来说很好,但就目前而言,合成器进程没有关于重合成器从频谱图中何处获取其数据的反馈。参考图 3 中运行的应用程序的屏幕截图,很明显存在两种从程序中获取声音的根本不同的方式。

  1. 用户可以从速度滑块中选择播放速度,在这种情况下,通过频谱图的推进速率由重合成过程控制。当重合成源由重合成器自动移动时,需要更新绿线,当然,这与 GUI 事件异步发生。解决方案是安排重合成器在需要更新时向父进程发出信号。

  2. 用户可以单击或拖动频谱图,在这种情况下,要重合成的频谱由父控制进程控制。在频谱图画布上定位绿线没有问题:它只是由所服务的鼠标移动事件的位置参数决定的。

图 3. 应用程序屏幕截图

在 xview 客户端中使用信号

从子进程发送信号很容易实现,但可能会产生不幸的后果。应用程序不断向 X 服务器发送命令,原则上,信号可能会在客户端-服务器通信过程中发生。这很危险,仅仅是因为信号启动了 X 服务器命令(为了绘制绿线),因此必须采用某种使 X 协议请求原子化的方法。

幸运的是,xview 包提供了必要的方法。xview 客户端不应执行某些操作。其中之一是处理直接通过信号方法接收到的中断,另一个是使用 sleep 来暂停自身。两者都可能干扰客户端-服务器通信的正常运行。如果客户端想要使用中断,则必须按如下方式将其自身注册为信号接收器

notify_set_signal_func(frame, update_frame_posn,
        SIGUSR1, NOTIFY_SYNC);

此调用出现在 xview 数据结构的初始化程序的末尾,并将信号 SIGUSR1 的服务例程与服务函数 update_frame_posn 关联。frame 参数是应用程序的父框架,NOTIFY_SYNC 指示中断的服务应延迟到挂起的 X 协议交换完成为止。

结论

Sculptor 是一个可以以潜在令人兴奋的方式操作声音样本的软件包。它有一个前端,允许用户实时执行这些操作。System-V IPC 已用于将进程拆分为两个部分,这两个部分可以在多处理器机器上有效地进行负载均衡。源代码是免费提供的(请参阅“资源”)。

Sculptor 并非旨在作为良好编程实践的指南;事实上,某些代码简直是丑陋的。在早期开发中,整个应用程序需要在没有协处理器的 386 机器上实时工作,因此必须为了速度而牺牲一些清晰度。尽管如此,希望该代码可能会激发其他实时音频操作项目,现在已经证明,基于处理器密集型算法的项目可以完全交互地实时运行。该应用程序还被用于支持利兹大学的计算机音乐研究,因此根据定义,它永远不会“完成”。

资源

Sculptor: A Real Time Phase Vocoder
Nick Bailey (N.J.Bailey@leeds.ac.uk) 毕业于英国杜伦大学,获得计算机与电子学理学学士学位。曾在伦敦西区的英国电信应用技术部门工作,之后返回杜伦大学攻读博士学位,研究并行计算在音频信号合成中的应用。他目前是利兹大学电子电气工程系应用计算机系统专业的讲师,并额外负责海外和欧洲联络工作。他喜欢老旧、不可靠的快车,拥有一把大提琴,但在任何一个方向上都没有表现出明显的才华。
加载 Disqus 评论