Python编曲实践(三):如何模拟“弯音轮”实现滑音和颤音效果

2024-04-23 02:32

本文主要是介绍Python编曲实践(三):如何模拟“弯音轮”实现滑音和颤音效果,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

弯音轮,是在MIDI键盘或专业电子琴一旁安装的一个装置(如下图)。
弯音轮
通过前后拨动滚轮,可以实现弯音和颤音的效果。这对于追求特殊电音效果的作曲者来说是必不可少的,而这两个技巧也是吉他等乐器演奏时十分常用的技巧,故在编程中学会更加自然和协调的模拟弯音和颤音效果,是模拟吉他等乐器时必不可少的。

再谈Message

我们第一篇文章已经简单讲过Message类是MIDI编曲中最为重要的概念,地位和作用相当于人体的细胞。
再次参考 Mido官方文档中的Message Type章节 ,我们可以看到在所有的Message种类中有pitchwheel,它便是用于模拟弯音轮效果的一种消息类别,也是实现滑音和颤音的关键。
Pitchwheel类Message的基本格式如下:

Message('pitchwheel', pitch, time, channel)

其中time和channel的意义同之前相同,而pitch参数是一个区间为-8192到8192的整数,用于表示音高“弯曲”的程度,取正数时趋向于高音,取负数时趋向于低音。pitch取3000的时候效果是“弯曲”一个半音。
若要实现完整的滑音过程,我们还需要Aftertouch这个类型的Message类型,其基本格式如下:

Message('aftertouch', time, channel, ...)

这种Message是用于在音符按下且未结束的时候改变某些属性,比如音量和频道等,在此我们仅仅用它来维持我们的音高。

编程实现

我们的目标是通过Pitchwheel这一种Message类型实现两种效果——滑音和颤音,故我们对这两种效果分别编码,将相关的代码添加到改名后的 实践(一)的play_note函数——add_note函数中:

def add_note(note, length, track, base_num=0, delay=0, velocity=1.0, channel=0, pitch_type=0, tremble_setting=None, bend_setting=None):bpm = get_bpm(track)meta_time = 60 * 60 * 10 / bpmmajor_notes = [0, 2, 2, 1, 2, 2, 2, 1]base_note = 60if pitch_type == 0: # No Pitch Wheel Messagetrack.append(Message('note_on', note=base_note + base_num*12 + sum(major_notes[0:note]), velocity=round(64*velocity), time=round(delay*meta_time), channel=channel))track.append(Message('note_off', note=base_note + base_num*12 + sum(major_notes[0:note]), velocity=round(64*velocity), time=round(meta_time*length), channel=channel))elif pitch_type == 1: # Trembletry:pitch = tremble_setting['pitch']wheel_times = tremble_setting['wheel_times']track.append(Message('note_on', note=base_note + base_num * 12 + sum(major_notes[0:note]),velocity=round(64 * velocity),time=round(delay * meta_time), channel=channel))for i in range(wheel_times):track.append(Message('pitchwheel', pitch=pitch, time=round(meta_time * length / (2 * wheel_times)),channel=channel))track.append(Message('pitchwheel', pitch=0, time=0, channel=channel))track.append(Message('pitchwheel', pitch=-pitch, time=round(meta_time * length / (2 * wheel_times)),channel=channel))track.append(Message('pitchwheel', pitch=0, time=0, channel=channel))track.append(Message('note_off', note=base_note + base_num * 12 + sum(major_notes[0:note]),velocity=round(64 * velocity), time=0, channel=channel))except:print(traceback.format_exc())elif pitch_type == 2: # Bendtry:pitch = bend_setting['pitch']PASDA = bend_setting['PASDA'] # Prepare-Attack-Sustain-Decay-Aftermath (Taken the notion of ADSR)prepare_rate = PASDA[0] / sum(PASDA)attack_rate = PASDA[1] / sum(PASDA)sustain_rate = PASDA[2] / sum(PASDA)decay_rate = PASDA[3] / sum(PASDA)aftermath_rate = PASDA[4] / sum(PASDA)track.append(Message('note_on', note=base_note + base_num * 12 + sum(major_notes[0:note]),velocity=round(64 * velocity), time=round(delay * meta_time), channel=channel))track.append(Message('aftertouch', time=round(meta_time * length * prepare_rate), channel=channel))track.append(Message('pitchwheel', pitch=pitch, time=round(meta_time * length * attack_rate), channel=channel))track.append(Message('aftertouch', time=round(meta_time * length * sustain_rate), channel=channel))track.append(Message('pitchwheel', pitch=0, time=round(meta_time * length * decay_rate), channel=channel))track.append(Message('note_off', note=base_note + base_num * 12 + sum(major_notes[0:note]),velocity=round(64 * velocity), time=round(meta_time * length * aftermath_rate), channel=channel))except:print(traceback.format_exc())

根据pitch_type的值,我们将函数分为三部分:

  • pitch_type为0,代表没有附加效果,同之前的play_note效果一样。
  • pitch_type为1,代表添加颤音效果,即吉他中的揉弦。产生这一效果的两个参数pitch和wheel_time通过tremble_setting传入,分别表示颤音的幅度和颤音的次数。根据我的实践来看,一个全音符跟随3至4次颤音是比较自然的;而pitch的赋值也应适中,在1000左右比较合适,太小则看不出效果,太大则会跳动到另一个音符,很不自然。
  • pitch_type为2,代表滑音效果,即吉他中的推弦。由于这一效果的变化十分多样,故我参考电子合成音乐中的 ADSR(Attack Decay Sustain Release) 属性,自己设计了一个 PASDA(Prepare - Attack - Sustain - Decay - Aftermath) 属性,即 初始音 - 向目标音行进过程中 - 滑到目标音后保持 - 向初始音行进过程中 - 初始音,这样就可以比较好地表示滑音的属性了,可以参考下图来进行理解:
    pasda
    根据PASDA不同阶段所占比例的大小,我们就能很好地构建出心怡的滑音效果。
    之后我们就可以对原始的音乐进行改进:
def verse(track):add_note(1, 0.5, track)       # 小add_note(1, 0.5, track, pitch_type=2, bend_setting={'pitch': 6000, 'PASDA': [0.1, 0.3, 2, 0.3, 0]})       # 时add_note(1, 1.5, track, pitch_type=1, tremble_setting={'pitch': 800, 'wheel_times': 10})       # 候add_note(7, 0.25, track, -1)  # 妈add_note(6, 0.25, track, -1)  # 妈add_note(5, 0.5, track, -1, channel=1)  # 对add_note(2, 0.5, track, channel=1, pitch_type=2, bend_setting={'pitch': 6000, 'PASDA': [0.1, 0.8, 2, 0, 0]})      # 我add_note(3, 2, track, channel=1, pitch_type=1, tremble_setting={'pitch': 640, 'wheel_times': 8})        # 讲add_note(3, 0.5, track)           # 大add_note(3, 0.5, track, pitch_type=2, bend_setting={'pitch': 3000, 'PASDA': [0.1, 0.8, 2, 0.3, 0]})add_note(3, 1.5, track, pitch_type=1, tremble_setting={'pitch': 400, 'wheel_times': 6})           # 海add_note(2, 0.25, track)          # 就add_note(1, 0.25, track)          # 是add_note(6, 0.5, track, -1, channel=1)  # 我add_note(1, 0.5, track, channel=1, pitch_type=2, bend_setting={'pitch': 6000, 'PASDA': [0.2, 0.8, 2, 0, 0]})      # 故add_note(2, 2, track, channel=1, pitch_type=1, tremble_setting={'pitch': 600, 'wheel_times': 8})        # 乡add_note(7, 0.5, track, -1)  # 海add_note(1, 0.5, track)add_note(7, 1.5, track, -1, tremble_setting={'pitch': 500, 'wheel_times': 6})  # 边add_note(6, 0.25, track, -1)add_note(5, 0.25, track, -1)add_note(5, 0.5, track, -1, channel=1)  # 出add_note(1, 0.5, track, channel=1, pitch_type=2, bend_setting={'pitch': 6000, 'PASDA': [0.2, 1.5, 3, 0, 0]})add_note(2, 2, track, channel=1, pitch_type=1, tremble_setting={'pitch': 400, 'wheel_times': 8})        # 生add_note(3, 1.5, track, pitch_type=2, bend_setting={'pitch': 3000, 'PASDA': [0, 0.3, 3, 0, 0]})       # 海add_note(3, 0.5, track)       # 里add_note(1, 0.5, track, channel=1)       # 成add_note(6, 0.5, track, -1, channel=1)add_note(1, 3, track, channel=1, pitch_type=1, tremble_setting={'pitch': 800, 'wheel_times': 10})         # 长

完整代码见 Github

参考资料

  • MIDI Tutorial
  • ADSR - Wikipedia

这篇关于Python编曲实践(三):如何模拟“弯音轮”实现滑音和颤音效果的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/927546

相关文章

Spring Boot整合Redis注解实现增删改查功能(Redis注解使用)

《SpringBoot整合Redis注解实现增删改查功能(Redis注解使用)》文章介绍了如何使用SpringBoot整合Redis注解实现增删改查功能,包括配置、实体类、Repository、Se... 目录配置Redis连接定义实体类创建Repository接口增删改查操作示例插入数据查询数据删除数据更

Java Lettuce 客户端入门到生产的实现步骤

《JavaLettuce客户端入门到生产的实现步骤》本文主要介绍了JavaLettuce客户端入门到生产的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 目录1 安装依赖MavenGradle2 最小化连接示例3 核心特性速览4 生产环境配置建议5 常见问题

使用python生成固定格式序号的方法详解

《使用python生成固定格式序号的方法详解》这篇文章主要为大家详细介绍了如何使用python生成固定格式序号,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下... 目录生成结果验证完整生成代码扩展说明1. 保存到文本文件2. 转换为jsON格式3. 处理特殊序号格式(如带圈数字)4

linux ssh如何实现增加访问端口

《linuxssh如何实现增加访问端口》Linux中SSH默认使用22端口,为了增强安全性或满足特定需求,可以通过修改SSH配置来增加或更改SSH访问端口,具体步骤包括修改SSH配置文件、增加或修改... 目录1. 修改 SSH 配置文件2. 增加或修改端口3. 保存并退出编辑器4. 更新防火墙规则使用uf

Java 的ArrayList集合底层实现与最佳实践

《Java的ArrayList集合底层实现与最佳实践》本文主要介绍了Java的ArrayList集合类的核心概念、底层实现、关键成员变量、初始化机制、容量演变、扩容机制、性能分析、核心方法源码解析、... 目录1. 核心概念与底层实现1.1 ArrayList 的本质1.1.1 底层数据结构JDK 1.7

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

JDK21对虚拟线程的几种用法实践指南

《JDK21对虚拟线程的几种用法实践指南》虚拟线程是Java中的一种轻量级线程,由JVM管理,特别适合于I/O密集型任务,:本文主要介绍JDK21对虚拟线程的几种用法,文中通过代码介绍的非常详细,... 目录一、参考官方文档二、什么是虚拟线程三、几种用法1、Thread.ofVirtual().start(

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node