×

一种新的音乐交互方式开源分享

消耗积分:0 | 格式:zip | 大小:0.14 MB | 2022-11-03

分享资料个

描述

介绍

在过去的几年里,乐器作为一种教育和创意工具的创新一直是研究的主题。我们团队的两名成员是来自高中的学生,拥有强大的音乐课程,我们想出了这种新乐器。

我们提出了一种新的音乐交互方式,其中一个界面(手套)可以一次控制不同的乐器。

Matrix Voice ESP32 让我们有机会在两个不同的任务中打破这个过程:

  • 使用外部传感器(压力)收集数据和
  • 带有 SNIPS 的语音命令识别、仪器管理和播放。

ESP32 运行 Tensilica Xtensa 双核微控制器处理来自压力传感器的模拟输入,并通过 UART 通道与 Raspberry Pi 3 控制器程序通信。

poYBAGNiFNSAUM_OAACVY5_VmKA116.jpg
 

材料

以下是我们使用的材料的快照。请注意,电阻为 3.3 欧姆,并且所示手套尚未粘贴双面胶带。

 
 
 
poYBAGNiFOeAEYzZAAONTuYtmnw809.jpg
 
1 / 7
 

我们还为 RPi 使用了两张 SD 卡。第一个具有 ESP32 编程环境,第二个具有安装了 python 编程支持的 Matrix Core、HAL 和 Lite 包。

压力传感器

创建音乐的核心数据收集是连接到设备的压力传感器。我们需要对同时连接的多个传感器进行快速响应。

poYBAGNiFOuAb9ZFAAF7fXd4IfI525.jpg
 

我们使用 ESP32 从 Matrix Voice ESP32 上的扩展 GPIO 端口公开 IO 端口作为来自压力传感器的模拟输入。Arduino 脚本定义了要激活的引脚,如下所示

//Use extension port for pressure sensors
const int FSR_PIN = 12;  // Pin connected to FSR/resistor divider
const int FSR_PIN1 = 26; // Pin connected to FSR/resistor divider
const int FSR_PIN2 = 27; // Pin connected to FSR/resistor divider
const int FSR_PIN3 = 25; // Pin connected to FSR/resistor divider

上述引脚可以物理地在扩展端口中找到,如下所示。我们还使用 3.3V 和 GND 引脚为传感器供电。

poYBAGNiFO2AFit_AACkPSGBPwk592.jpg
 

初始化 UART 串​​口进行数据传输,并在 Arduino 脚本的 setup 函数中设置 ESP32 引脚作为输入。

void setup() {
    Serial.begin(115200);
    pinMode(FSR_PIN, INPUT);
    pinMode(FSR_PIN1, INPUT);
    pinMode(FSR_PIN2, INPUT);
    pinMode(FSR_PIN3, INPUT);

主循环读取传感器输入。在某些情况下,我们注意到引脚在连续指令读取时会返回影子值。我们尝试添加一个计时器来缓解这个问题,但没有成功,而效果更好的一个是检查下一个引脚的值,以及再次读取它是否相同。这在某种程度上为我们解决了这个问题。

void loop()
{
    byte inCmd;
    int fsrADC = analogRead(FSR_PIN);
    int fsrADC1 = analogRead(FSR_PIN1);
    if (fsrADC == fsrADC1)
        fsrADC1 = analogRead(FSR_PIN1);
    int fsrADC2 = analogRead(FSR_PIN2);
    if (fsrADC1 == fsrADC2)
        fsrADC2 = analogRead(FSR_PIN2);
    int fsrADC3 = analogRead(FSR_PIN3);

现在是时候对 ESP32 芯片进行编程了。我们确实花了一些时间来弄清楚这个过程,在按照ESP32 MATRIX Voice w/Arduino IDE 上的 Program Over the Air的说明进行操作后,我们了解了手动刷新程序的过程。我们不得不说无线(OTA)对我们不起作用,我们的矩阵声音在启动时一直崩溃。但是,手动工作,所以每次我们更改程序时,我们只调用 deploy_ota, sh 脚本。

我们还使用安装了新的 Raspbian Stretch Desktop 映像的干净 SD 卡。我们按照安装ESP32 u p 的说明进行到第 1 步。

总之,你需要

  • 在 Arduino IDE 中选择 ESP32 开发模块板
pYYBAGNiFPCAUQi_AABov7tIt_E421.jpg
 
  • 单击 Sketch 菜单下的 Export Compiled Binary。这将创建一个 bin 文件
pYYBAGNiFPWADAhXAAAyYSBwIM0564.jpg
 
  • 使用您的凭据和 bin 文件的名称更新 deploy_OTA.sh 文件。
tar cf - *.bin | ssh pi@YOURIPADDRESSHERE 'tar xf - -C /tmp;sudo voice_esp32_reset;voice_esptool --chip esp32 --port /dev/ttyS0 --baud 115200 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect 0x1000 /tmp/bootloader.bin 0x10000 /tmp/YourBinaryProgramName.bin 0x8000 /tmp/partitions_two_ota.bin'
  • 启动 Git Bash 会话并为 deploy_OTA.sh 文件运行 shell 脚本
pYYBAGNiFPiAY1kPAAALjkD0gPQ268.jpg
 
  • 您的目录应包含以下文件以正确刷新 ESP32
pYYBAGNiFPqAWbHQAAAwWN62VCc003.jpg
 

顺便说一句,当您按照ESP32 MATRIX Voice w/Arduino IDE 上的 Program Over the Air 中的说明进行操作时,您可以从 esp32-arduino-ota 项目下的 starter 目录中找到 deploy_OTA、sh、bootloader.bin和 partitions_two_ota.bin 文件。

git clone https://github.com/matrix-io/esp32-arduino-ota

语音助手

为了创建一个动态的多乐器设备,我们需要一种改变乐器当前演奏的方法。口头命令创造了最通用的方式来实现这一点。

SNIPS是一个用于连接设备的 AI 语音平台,它通过可定制的语音体验来动画产品交互。

首先检查以下项目MATRIX Voice 和 MATRIX Creator Running Snips.ait o 设置您的 SNIPS 环境。

注意:请注意 /etc/asound.conf 文件可能需要一些调整才能使 SNIPS 进程正常运行。我们最终使用 RPi 嵌入式声音芯片进行声音播放和 Matrix Voice 进行录音。还要确保将采样率值检查为 16000,因为这是 Matrix 工作的默认值(如果我们对此有误,请纠正我们)。

一旦您的系统在 SNIPS 上启动并运行,我们使用 MATRIX 设备和 Snips.ai 复制了以下项目 Iron Man Arc Reactor。他为我们提供了在 SNIPS 上创建帐户的步骤,并初步了解如何创建一个应用程序。

好的,然后我们创建我们的应用程序调用 MusicGloves,我们希望暂时将其保密并将其设置为未发布。

pYYBAGNiFP2ACJpaAAC43lCWHm0775.jpg
 

我们创建了八个意图,六个用于乐器,一个用于同时演奏三种乐器的混合版本 (MixOne),还有一个用于退出程序的命令。

poYBAGNiFQGAXC4gAAC_JaJa0vw942.jpg
 

每个意图都有一个 Slot,我们创建三个可能的激活语句:Play Instrument Name、Select Instrument Name 和 Instrument Name,除了 Exit。

创建插槽后,请确保按“保存”,以便系统开始训练并创建语音助手。

pYYBAGNiFQSAPAPsAABpg_97nfU851.jpg
 

不直观的一件事是训练示例与插槽的链接。您必须选择要与插槽关联的关键字文本,然后将鼠标悬停在其上。然后它将显示一个弹出菜单来选择插槽。我们尝试了其他版本的具有多个插槽的助手,但是在接收回调函数并在 python 上解析 JSON 消息时遇到了一些问题。所以我们决定为每个乐器创建单独的意图。

pYYBAGNiFQeAZr33AAAsL6zKOYI591.jpg
 

现在你可以部署你的助手了。按照带有 MATRIX 设备和 Snips.ai的Iron Man Arc Reactor 的步骤 7 。

通过运行测试助手

sam watch

并说出热门词“Hey Snips”,您将看到 RPi 识别该词后的处理结果。

笔记

本节介绍第二个任务,使用 SNIPS 进行语音命令识别、仪器管理和播放。

Python 和 SNIPS 的配置有一些颠簸。首先,我们按照使用 Python 通过 MQTT 监听意图中的说明进行操作,但是在尝试通过 Stretch 安装 paho-mqtt 时出现问题。我们找到了解决方案Cannot install paho-mqtt in Python 3.xby运行以下命令:

sudo apt-get install mosquitto 
sudo apt-get install mosquitto-clients 
sudo python3 -m pip install paho-mqtt

设置好系统后,我们首先导入我们的库

import paho.mqtt.client as mqtt
from time import sleep
from math import pi, sin
import pygame
import time
import serial
import re
import json

请注意,我们将使用 pygame 播放乐器声音,使用串行库通过 UART 端口与 ESP32 通信,并使用 mqtt 客户端与 SNIPS 服务器链接。

我们初始化我们的意图以订阅 mqtt 客户端。请注意,我们错误地离开了 IronMan 项目的 ArcReactor 意图,哎呀!

#Snips credentials, make sure you set your Username
snipsUserName = "YourUserName"
Reactor = 'hermes/intent/'+snipsUserName+':ArcReactor'
ViolinMus = 'hermes/intent/'+snipsUserName+':Violin'
CelloMus = 'hermes/intent/'+snipsUserName+':Cello'
PianoMus = 'hermes/intent/'+snipsUserName+':Piano'
FluteMus = 'hermes/intent/'+snipsUserName+':Flute'
TimpaniMus = 'hermes/intent/'+snipsUserName+':Timpani'
ExitMus = 'hermes/intent/'+snipsUserName+':Exit'
MixOneMus = 'hermes/intent/'+snipsUserName+':MixOne'
ChorusMus = 'hermes/intent/'+snipsUserName+':Chorus'

我们初始化 mqtt 客户端,设置连接和消息接收的回调函数,尝试连接到主机并启动一个运行 mqtt 消息的新线程。请注意,我们注释了 client.loop_forever() 函数。我们从这个函数开始,但是它控制了程序并且不放手,因此函数调用之后的代码永远不会执行。我们需要一些可以在后台运行的东西,同时我们可以收集来自 ESP32 脚本的数据。

#Initialize the mqqt engine
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
#Connect to mqtt service
client.connect(HOST, PORT, 60)
#client.loop_forever()
client.loop_start()

连接建立后,来自 mqtt 服务的 on_connect 回调函数订阅意图。

# Subscribe to the programmed intents
def on_connect(client, userdata, flags, rc):
   print("Connected to {0} with result code {1}".format(HOST, rc))
   # Subscribe to the hotword detected topic
   client.subscribe("hermes/hotword/default/detected")
   # Subscribe to intent topic
   client.subscribe(ViolinMus)
   client.subscribe(CelloMus)
   client.subscribe(PianoMus)
   client.subscribe(FluteMus)
   client.subscribe(ExitMus)
   client.subscribe(TimpaniMus)
   client.subscribe(MixOneMus)
   client.subscribe(ChorusMus)

以及 SNIPS 发送的 mqtt 消息的回调函数

# Snips callback function with the detected intent
def on_message(client, userdata, msg):
   global activeInstrument
   if msg.topic == 'hermes/hotword/default/detected':
      print("Hotword detected!")
   elif msg.topic == ViolinMus:
      activeInstrument = ViolinIns
      print("Violin detected!")
   elif msg.topic == CelloMus:
      activeInstrument = CelloIns
      print("Cello detected!")
   elif msg.topic == PianoMus:
      activeInstrument = PianoIns
      print("Piano detected!")
   elif msg.topic == FluteMus:
      activeInstrument = FluteIns
      print("Flute detected!")
   elif msg.topic == TimpaniMus:
      activeInstrument = TimpaniIns
      print("Timpani detected!")
   elif msg.topic == ExitMus:
      activeInstrument = ExitIns
      print("Exit detected!")
   elif msg.topic == MixOneMus:
      activeInstrument = MixOneIns
      print("Mix One detected!")
   elif msg.topic == ChorusMus:
      activeInstrument = ChorusIns
      print("Chorus detected!")

我们使用以下命令初始化我们的 pygame 声音引擎:

# Initialize the pygame sound engine
pygame.mixer.init()
# Set the number of channels to allow multiple simultaneous sounds
pygame.mixer.set_num_channels(15)

并请求 15 个通道同时播放我们的乐器声音。

我们采取了一种逻辑方法来尽可能简单地处理资源,方法是创建一个文件路径数组和一个创建声音对象的数组,这样我们就不需要每次播放时都加载它们。合唱对象的片段如下所示。请注意,chsndObj 数组包含加载相应文件后合唱的所有声音对象。

chCEGmfPath = ['/home/pi/MusicOrch/Chorus/chorus-female-c5-PB-loop.wav',
            '/home/pi/MusicOrch/Chorus/chorus-female-e5-PB-loop.wav',
            '/home/pi/MusicOrch/Chorus/chorus-female-g5-PB-loop.wav']
# Create an array of Sound objects for each instrumment. This is the array for chorus.
# Chorus
chsndObj = [pygame.mixer.Sound(chADFmfPath[0]), pygame.mixer.Sound(chADFmfPath[1]),
         pygame.mixer.Sound(chADFmfPath[2]),
         pygame.mixer.Sound(chCEGMmfPath[0]), pygame.mixer.Sound(chCEGMmfPath[1]),
         pygame.mixer.Sound(chCEGMmfPath[2]),
         pygame.mixer.Sound(chCEGmfPath[0]), pygame.mixer.Sound(chCEGmfPath[1]),
         pygame.mixer.Sound(chCEGmfPath[2])]

selectSoundObj 和 playNote 函数负责根据活动乐器选择正确的声音并在独立通道上播放。

最后用下面的参数初始化串口

#Initialize the serial port for communication with the Matrix Voice ESP32 

ser = serial.Serial( port='/dev/ttyS0', baudrate = 115200, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=5 )

我们使用串行对象请求一组新的压力值,读取这些值并在通过 SNIPS 意图命令更改时传输活动的选定对象。

if (PauseExec == False):
   # Set read command to ESP32
   ser.write(b'1')
   time.sleep(0.1)
   # Read a line of data
   x = ser.readline()
   # print(x)
   # Convert the binary data into string
   strdecode = x.decode('utf-8')
   # split the data according to the divider
   values = strdecode.split("|")
   # print(values)
   # Play the data
   playNote(activeInstrument, int(values[0]), int(values[1]), int(values[2]))
   time.sleep(0.05)
# If instrument change let know the ESP32 task
if (currIns != activeInstrument):
   print(activeInstrument)
   currIns = activeInstrument
   if (activeInstrument == ViolinIns):
      ser.write(b'A')

请注意,我们有一个 PauseExec 变量作为信号量来同步程序中的不同事件。当我们试图从 ESP32 脚本中获取压力传感器的免费数据流时,我们曾在某个时候使用过这个变量,但它并没有那么好用。

该程序控制数据流,在播放前一个读取包的值后,在准备好处理它时请求一个新包。接收到的数据是二进制格式,我们将其转换为字符串并拆分每个压力传感器上的值。

如果 ActiveInstrument 的值通过接收来自 SNIPS 客户端的口头命令而更新,则程序将相应的代码发送到 ESP32 以更改 LED 的颜色。

时间就是一切 开发 (TIED)

在处理实时数据流时,考虑事件的同步是很重要的。在我们找到正确的流程顺序(上图)之前,我们尝试了不同的方案。

将 ESP32 Arduino 脚本设置为在每个周期传输一个读取的压力值包,实际上阻塞了 UART 缓冲区,python 程序很难仅赶上正在接收的数据。SNIPS 命令缺乏响应,并且没有及时播放声音。

然后我们尝试向 ESP32 发送一个读取命令,ESP32 反过来会读取当时的值,但是 readline 函数会附带一半创建的包,其中缺少传感器值。

我们在串口读取部分仍然有一些崩溃,这就是我们实现 try/except 情况的原因,所以我们可以重置串口并重新启动通信。

找到正确的同步过程很复杂,但在我们的 IOT 世界中是必需的。

享受音乐乐趣

最后,在将所有部件放在一起之后,我们就有了新的乐器(设备)。我们使用带有双面胶带的手套,将双面胶带粘在压力传感器和塑料球上,塑料球是传感器按压的刚性表面。

 
 
 
poYBAGNiFRaAPsAVAASrZ87JIRg384.jpg
 
1 / 2
 

我们注意到的事情

该项目最初是使用 Matrix Voice ESP32 作为独立设备开始的,不需要 RPi 板。

我们想使用 ESPNOW 协议将传感器数据传输到负责演奏所选乐器的远程服务器。Matrix Voice ESP32 将运行 SNIPS 客户端并选择活动仪器,该仪器将作为流包的一部分传递到远程服务器。

尝试 ESP32 开发模块中的 ESPNOW 示例程序很有魅力。

在单独的脚本上设置压力传感器并读取其值效果很好。

当我们将两者放在一起时,问题就开始了。由于某种我们还不知道的原因,一旦调用了 WiFi.mode(WIFI_STA) 函数,引脚的模拟读数就会停止工作。该脚本开始读取扩展端口中每个 ESP32 定义的引脚上的随机值。

这使我们放弃了 ESPNOW 并创建了一个设备。然后,我们遇到了 RPi 上的 UART 支持问题。因为,我们第一次使 SNIPS 工作是使用node.js示例,所以我们在 node.js 上搜索支持 UART 的库。不用说它不起作用,哦,那里有用于 UART 和node.js 的库,但是我们的时间不多了。

最后,我们决定使用 python,幸运的是我们找到了一种与 SNIPS 接口的方法。

这是一次漫长的旅行。哦,我们是否提到过我们还考虑重新编程 FPGA 以访问 I2C 或 UART 或扩展 GPIO。绝对是一个挑战,但这次不是。:)

未来的工作

添加更多压力传感器,找出 ESPNOW 问题并允许另一个设备控制播放任务,并使设备独立于 RPi 板运行。

增加意图的数量。我们将添加诸如“选择小提琴和长笛”或“选择所有弦乐”或“增加一个八度”或“改变音阶”或“传感器一个小提琴和传感器两个合唱”等的意图。这么多的可能性。

我们在这个项目上工作得非常愉快。希望这能激发更多关于乐器的项目。谢谢阅读。


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

评论(0)
发评论

下载排行榜

全部0条评论

快来发表一下你的评论吧 !