Engee 文档
Notebook

我们以KPM节奏演奏"帝国进行曲"

在这个例子中,我们将看看如何使用Engee脚本编写旋律,然后在KPM节奏上播放它。

导言

在简化的视图中,任何旋律都是一系列不同音高和持续时间的声音,以及它们的缺席(暂停)。 声音可以用不同音高的谐波信号来描述. 声音的音高反过来由这种信号的频率决定。 总之,编写旋律的任务在技术上可以表示为形成具有不同持续时间的单个信号的频率序列的任务。

旋律准备

原始旋律由下图中的音符描述。

image.png

为了方便其翻译"成数字",我们将通过以下步骤:

  1. 确定每个音符的适当频率。

  2. 我们将通过零频率定义暂停。

  3. 让我们来定义改变的功能-尖锐和平坦,它们分别提高和降低音符的频率半音。

  4. 确定信号的最小周期。

  5. 设置音符持续时间功能。

  6. 生成所得到的旋律矢量-按照节奏的信号频率序列。

注意事项

通过[平均темперированному]计算整个比例的频率(https://ru.wikipedia.org/wiki/Равномерно_темперированный_строй )音乐结构定义如下:

哪里 -音叉频率(第一个八度的La,或科学记数法中的A4),

-所研究的声音相对于音叉的半音数 .

让我们写下一个数字的定义 对于笔记从5октав。我们用科学记数法来表示它们。

In [ ]:
(C2, C♯2, D2, D♯2, E2, F2, F♯2, G2, G♯2, A2, A♯2, B2) = collect(-33:-22);  # Большая октава
(C3, C♯3, D3, D♯3, E3, F3, F♯3, G3, G♯3, A3, A♯3, B3) = collect(-21:-10);  # Малая октава
(C4, C♯4, D4, D♯4, E4, F4, F♯4, G4, G♯4, A4, A♯4, B4) = collect(-9:2);     # Первая октава
(C5, C♯5, D5, D♯5, E5, F5, F♯5, G5, G♯5, A5, A♯5, B5) = collect(3:14);     # Вторая октава
(C6, C♯6, D6, D♯6, E6, F6, F♯6, G6, G♯6, A6, A♯6, B6) = collect(15:26);    # Третья октава

声音

根据声音频率的定义,Engee模型中生成的谐波信号将如下所示:

In [ ]:
t = collect(0:0.0001:0.01); f = 440*2^(A4/12);
gr()
plot(t, sin.(2*pi*f*t); label="A4")
Out[0]:

改建工程

准备的下一阶段是辅助功能的定义,这将简化旋律的重写。 这就是您如何确定录制音符的尖锐(频率增加半音)和平坦(频率增加半音)。:

In [ ]:
# Функции альтераций
(n) = n + 1; # Диез
(n) = n - 1; # Бемоль

暂停;暂停

如果给定键的声音由某个频率描述,那么暂停由零频率描述。 用音符在一个数字序列中形成停顿 为了确定频率,我们将最短持续时间𝄿(第十六个)的暂停设置为一个数字 为其得到的频率为 . 在第十六次之后确定多个持续时间的暂停。

In [ ]:
𝄿 = [-999];     # Шестнадцатая пауза
𝄾 = vcat(𝄿, 𝄿);  # Восьмая пауза
𝄽 = vcat(𝄾, 𝄾);  # Четвертная пауза
𝄼 = vcat(𝄽, 𝄽);  # Половинная пауза

最低期限

信号在Engee模型中变化的最小周期将取决于音符、停顿的最小持续时间,也取决于音乐的节奏。

原始乐谱中的节奏设置为𝅘 𝅥=100,即每分钟100个季度持续时间。

旋律中的最小持续时间是第十六个。 因此,信号的最小持续时间可以如下设置:

In [ ]:
tempo = 60/100/4 # Шестнадцатая длительность = 150 мс 
Out[0]:
0.15

持续时间

为了简化代码中注释及其持续时间的记录,我们定义了不同持续时间的函数。 除第十六个持续时间之外的所有持续时间将在结束时补充第十六个持续时间。 这是一个粗略的假设,但它会改善我们原型KPM节奏音乐播放器中音频信号的感知。

In [ ]:
# Функции нотных длительностей
𝅘𝅥𝅯(n) = [n];                        # Шестнадцатая
𝅘𝅥𝅮(n) = [n, 𝄿...];                  # Восьмая
𝅘𝅥𝅮⋅(n) = [n, n, 𝄿...];               # Восьмая с точкой 
𝅘𝅥(n) = [n, n, n, 𝄿...];             # Четвертная 
𝅗𝅥(n) = [n, n, n, n, n, n, 𝄾...];    # Половинная 

旋律

所有的准备工作已经完成,结果,原来的旋律可以根据音符改写如下:

In [ ]:
notes = vcat(𝅘𝅥(C4), 𝅘𝅥(C4), 𝅘𝅥(C4), 𝅘𝅥𝅮⋅((A3)), 𝅘𝅥𝅯((E4)),
             𝅘𝅥(C4), 𝅘𝅥𝅮⋅((A3)), 𝅘𝅥𝅯((E4)), 𝅗𝅥(C4),
             𝅘𝅥(G4), 𝅘𝅥(G4), 𝅘𝅥(G4), 𝅘𝅥𝅮⋅((A4)), 𝅘𝅥𝅯((E4)),
             𝅘𝅥((C4)), 𝅘𝅥𝅮⋅((A3)), 𝅘𝅥𝅯((E4)), 𝅗𝅥(C4),
             𝅘𝅥(C5), 𝅘𝅥𝅮⋅(C4), 𝅘𝅥𝅯(C4), 𝅘𝅥(C5), 𝅘𝅥𝅮⋅(B4), 𝅘𝅥𝅯((B4)),
             𝅘𝅥𝅯(A4), 𝅘𝅥𝅯((G4)), 𝅘𝅥𝅮(A4), 𝄾, 𝅘𝅥𝅮((C4)), 𝅘𝅥((F4)), 𝅘𝅥𝅮⋅(F4), 𝅘𝅥𝅯(E4),
             𝅘𝅥𝅯((E4)), 𝅘𝅥𝅯(D4), 𝅘𝅥𝅮((E4)), 𝄾, 𝅘𝅥𝅮((A3)), 𝅘𝅥((C4)), 𝅘𝅥𝅮⋅((A3)), 𝅘𝅥𝅯((C4)),
             𝅘𝅥((E4)), 𝅘𝅥𝅮⋅((C4)), 𝅘𝅥𝅯((E4)), 𝅗𝅥((G4)),
             𝅘𝅥(C5), 𝅘𝅥𝅮⋅(C4), 𝅘𝅥𝅯(C4), 𝅘𝅥(C5), 𝅘𝅥𝅮⋅(B4), 𝅘𝅥𝅯((B4)),
             𝅘𝅥𝅯(A4), 𝅘𝅥𝅯((G4)), 𝅘𝅥𝅮(A4), 𝄾, 𝅘𝅥𝅮((C4)), 𝅘𝅥((F4)), 𝅘𝅥𝅮⋅(F4), 𝅘𝅥𝅯(E4),
             𝅘𝅥𝅯((E4)), 𝅘𝅥𝅯(D4), 𝅘𝅥𝅮((E4)), 𝄾, 𝅘𝅥𝅮((A3)), 𝅘𝅥((C4)), 𝅘𝅥𝅮⋅((A3)), 𝅘𝅥𝅯((E4)),
             𝅘𝅥(C4), 𝅘𝅥𝅮⋅((A3)), 𝅘𝅥𝅯((E4)), 𝅗𝅥(C4),
             𝄼, 𝄼);

在旋律结束时,我们将暂停一个完整的节拍(𝄼,𝄼)。

In [ ]:
println("В нашей мелодии: ", sizeof(notes), " элементов шестнадцатой длительности.\n")
# @markdown ### Результаты переноса мелодии:
println("Вот как выглядит вектор с полутонами, из которого будут сгенерированы звуковые сигналы:\n")
print(notes)
В нашей мелодии: 1664 элементов шестнадцатой длительности.

Вот как выглядит вектор с полутонами, из которого будут сгенерированы звуковые сигналы:

[-9, -9, -9, -999, -9, -9, -9, -999, -9, -9, -9, -999, -13, -13, -999, -6, -9, -9, -9, -999, -13, -13, -999, -6, -9, -9, -9, -9, -9, -9, -999, -999, -2, -2, -2, -999, -2, -2, -2, -999, -2, -2, -2, -999, -1, -1, -999, -6, -10, -10, -10, -999, -13, -13, -999, -6, -9, -9, -9, -9, -9, -9, -999, -999, 3, 3, 3, -999, -9, -9, -999, -9, 3, 3, 3, -999, 2, 2, -999, 1, 0, -1, 0, -999, -999, -999, -8, -999, -3, -3, -3, -999, -4, -4, -999, -5, -6, -7, -6, -999, -999, -999, -13, -999, -10, -10, -10, -999, -13, -13, -999, -10, -6, -6, -6, -999, -10, -10, -999, -6, -1, -1, -1, -1, -1, -1, -999, -999, 3, 3, 3, -999, -9, -9, -999, -9, 3, 3, 3, -999, 2, 2, -999, 1, 0, -1, 0, -999, -999, -999, -8, -999, -3, -3, -3, -999, -4, -4, -999, -5, -6, -7, -6, -999, -999, -999, -13, -999, -10, -10, -10, -999, -13, -13, -999, -6, -9, -9, -9, -999, -13, -13, -999, -6, -9, -9, -9, -9, -9, -9, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999]

当样本模型打开时,序列检测会自动启动。 [回调]用于此(https://engee.com/helpcenter/stable/ru-en/julia/JuMP/tutorials/linear/callbacks.html )模型。

示例模型

产生的序列(旋律)将被转移到[重复序列楼梯]块(https://engee.com/helpcenter/stable/ru-en/base-lib-sources/repeating-sequence-stair.html )-它将以给定的周期一个接一个地将值从矢量传输到输出。 然后,我们将使用Engee标准库的模块将这些值转换为给定频率的正弦信号。 接收到的信号将被传送到KPM节奏模拟输出单元,[RITMeX GP-LC-4X]模块(https://engee.com/helpcenter/stable/ru-en/ritmex-gp-lc-45/blocks.html )和监视器上的图形输出块中。

[KPM节奏模拟](https://engee.com/helpcenter/stable/ru-en/ritm.html )允许您实时重现模型。 这对于例如精确地控制所产生的信号的频率和持续时间是必要的。

image.png

模型设置:

*建模步骤的大小,与 - 1e-5

*模拟模式 - Быстрый счёт

*模拟结束 - Inf

*计算步骤溢出时的操作 - Уведомить

硬件:音频输出到扬声器

对于音频输出,我们使用带有3.5mm插孔TRS模拟信号输入的计算机扬声器。 该示例使用单个音频通道(单声道音频)。 对于GP-LC模块上的立体声,实现从DAC2DAC的第二通道输出的信号就足够了。 扬声器与节奏模块的连接如下图所示。:

RITM_Star_Wars_bb.png

基于节奏的建模

在实时机器上开始模拟之前,有必要建立连接和几个准备步骤,这些步骤在[相应的工作示例]中有所描述(https://engee.com/community/ru/catalogs/projects/kpm-ritm-bystryi-start )。

在节奏上启动模型后,可以在监视器上观察到具有不同持续时间和频率的模拟信号,并且在右侧扬声器中会听到数字化的旋律。 在不连接节奏的情况下,您可以在下面的代码单元格的输出中收听它:

In [ ]:
import Pkg; Pkg.add(["WAV","Base64"])
using WAV, Base64
In [ ]:
using WAV, Base64
x, fs = wavread( "$(@__DIR__)/the imperial march.wav" );

buf = IOBuffer();
wavwrite(x, buf; Fs=fs);
data = base64encode(unsafe_string(pointer(buf.data), buf.size));
markup = """<audio controls="controls" {autoplay}>
              <source src="data:audio/wav;base64,$data" type="audio/wav" />
              Your browser does not support the audio element.
              </audio>"""
display( "text/html", markup );

您可以直接从脚本在节奏上运行模拟-为此,我们使用[外部硬件支持包](https://engee.com/helpcenter/stable/ru-en/ritm/working-with-RITM.html )和相应的【软件管理方法】(https://engee.com/helpcenter/stable/ru-en/ritm/ritm-functions.html )。

自动化实时测试过程的详细描述在上一篇примерах.

In [ ]:
engee.package.install("Engee-Device-Manager");
Ссылка для подключения: engee.com/prod/user/demo54365638-alexevs
Установите клиентскую программу:
https://dl.kpm-ritm.ru/repo/Host-Device-Manager-v0.68-Windows.zip - Для Windows
https://dl.kpm-ritm.ru/repo/Host-Device-Manager-v0.68-Linux.zip - Для Linux
Пакет поддержки 'Engee-Device-Manager' версии 'v0.68' успешно установлен.
In [ ]:
# @markdown ## Подключение КПМ РИТМ

IP = "192.168.56.3" # @param {type:"string",placeholder:"192.168.56.3"}
порт = "8000" # @param {type:"string",placeholder:"8000"}

using Main.EngeeDeviceManager.Targets
using Main.EngeeDeviceManager.Targets.RITM_API

ritm = Targets.RITM.Ritm()
Targets.RITM_API.setUrl(ritm, "http://$IP:$порт/")

println("КПМ РИТМ подключен: ", RITM_API.isConnected(ritm))
КПМ РИТМ подключен: true
In [ ]:
# @markdown ## Запуск модели в режиме Standalone
имя_модели = "the_imperial_march" # @param {type:"string",placeholder:"hil_dcm"}
path = joinpath(@__DIR__, "$имя_модели.engee")
model = engee.load(path)
resp = Targets.upload_model(ritm, model)
println("\n", "Загрузка модели: ", resp)
resp = Targets.generate_executable_code(ritm, model, false)
println("\n", "Генерация кода: ", resp)
resp = Targets.compile_model(ritm, model)
println("\n", "Компиляция: ", resp)
Targets.start_model(ritm, model)
engee.close(model; force=true)

结论

在这个例子中,我们不仅检查了音乐的数学基础和音乐符号信号的合成,而且还使用实时支架再现了一个简单的旋律。 这只是一个来自音频信号数字合成领域的小例子,可以在Engee和KPM节奏上实现。