激光乐动琵琶

【摘要】

现在有很多的孩子学习乐器,我也是其中一员,但我也知道,很多时候,我对于自己的琵琶往往是痛恨的咬牙切齿。原因和其他人一样:练习的时候枯燥无味,而且每次的练习只有短短几个小结,甚至连不成完整的“一句话”。所以我设计了激光乐动琵琶,这样以来,即使是短短的几个小结,也可以让人感觉很有乐趣,在绚丽的激光下,随机展现出不同的简单图案,不过,如果有电脑之类的设备,就可以用processing把一个个音符变成一串更为动感绚丽的图案投射出来。因为我是练习琵琶的,所以我想先从琵琶改造起。

首先,这个琵琶有激光功能,人人的家里都有墙壁,而激光可以直接打到墙壁上,随着音乐的变换,给人一种变幻多端的感觉。这样便解决了练习的时候没有动力的问题。

其次,利用processing,将弹出来的音符变成一组组有趣的图案,而这些图案又是随机,随着音调的转化,也往往能够带给人一种别样的新奇与刺激,而且代码公开,可以让练习者自行改变,达到自己想要的效果和目的。

关键词:激光振镜原理;单片机控制;processing;琵琶

激光乐动琵琶

目录

【摘要】 3

1引言 4

1.1项目由来 4

1.2内容介绍 5

2研究内容 5

2.1运用运行方案设计 5

图1

图1

2.3装置的内部结构 8

3总结与讨论 15

3.1难点分析 15

3.2先进性及独特之处 15

3.3与同类项目比较 15

4成品总结 17

5展望 18

6致谢 19

7参考文献 19

8附录 20

1 引言

1.1 项目由来

现在学习乐器的人很多,可能他们一开始的学习是因为兴趣,但是我相信,很多人和我一样,渐渐地,就会失去原有的激情,甚至开始厌恶练习,但这也是人之常情,如下图所示,兴趣渐渐都会散退,往往大家都会有这样的问题,我也不例外,所以我就想制造出这款激光乐动琵琶。

此款激光乐动琵琶可以让人们重拾学习的兴趣。同时它也方便携带,因此,它也可以在人们参加志愿者服务的时候使用,这样便不再局限于场地的限制,也可以使效果更上一层楼。

1.2研究内容

本项目主要通过在琵琶上加入声音传感器,通过傅里叶变换对数据进行处理,利用这些数据和激光振镜原理以及单片机原理,对此进行编程,从而组成一个能够控制琵琶产生变化的激光,显示在二维平面上,再用其他辅助功能的系统,让音符在processing下组成一组组的变幻莫测图案,显示在电脑上。

2 研究内容

2.1运用运行方案设计

该装置设计两大部件。

第一组部件是激光模块,通过声音传感器采集的数据进行一系列转化后(后文有专题)来控制叶片的转动快慢,将激光打在叶片上,从而显示不同图案的花纹。

第二组部件是由processing模块组成的。它是负责更高级的部分,同样,它也是在声音传感器采集的数据进行一系列转化后(后文有专题),在有电子显示器的情况下,不在仅仅局限于叶片上的规定图案,而是不同的随机的变化曲线。

该装置初步所设计的功能分为两部分。

第一部分是主要的,利用激光将音符转化成一个个可以直接投影在任何平面上的图案。使用者将此放在地上或者乐器上,连接好电源便即可显示。第二部分是一个辅助部分,在一定条件下(有大型电子设备时),将这些音符从音谱变成频谱,通过processing展现在显示屏上。当使用者演奏的时候,显示屏就会将这些图案随机通过频谱的变换显示出来,体现出更加新颖,意想不到的效果。

2.2装置总体结构及逻辑图

声音传感器和我们的日常用到的麦克风原理是大致相同的。主要是用来采集声波的强弱信号。当有声音时,声波引起话筒内的驻极体薄膜震动,导致电容的变化,从而产生与声音对应变化的电信号;用来接受声音,并作为主要指令,为接下来的运行提出要求和控制。

激光振镜也叫激光扫描器,是有X-Y光学扫描头。电子驱动放大器和光学反射镜片组成。电脑控制器提供的信号通过驱动放大电路驱动光学扫描头。从而在X-Y平面控制激光束的偏转。在激光演示系统中,光学扫描的波形是一种矢量扫描,系统的扫描速度,决定了激光图形的稳定性,因近几年来人们已经开发出高速的扫描器,扫描速度达到每秒钟45000个点,因此能够演示更为复杂的激光动画,不过这也是我之后才能更深层次的研究。

逻辑图

2.3装置的内部结构

2.3.1 Arduino读取声音传感器模块

arduino单片机:主要用于发出指令,控制各部件进行操作[3]。

使用arduino单片机因此单片机有这一特点:它是一款便捷灵活、方便上手的开源电子原型平台, Arduino,是一个基于开放原始码的软硬件平台。

本项目采用了arduino Mega2560这钟开发板。这种开发板的优点是串口多,这样可以让我在一块板上添加更多的模块。更重要的运算能力比arduino强,可以处理更多的信息,储存更多的代码。可以为将来的拓展留下足够的空间,而且可以兼容我的所有用到的模块。

声音传感器模块共用4个引脚,按从上到下的引脚顺序分别是模拟输出口,地线,电源和数字输出口,(如图)。其中模拟输出口可实时输出麦克风的电压信号,数字输出口相当于一个开关,在没有声音信号或声音信号较弱的时候,输出低电平。当声音强度达到一个阈值(可通过调节电位器来改变)时,才输出高电平。

在接受声音之后就使用傅里叶变换对它进行处理。使用声音传感器接收声音,不过得到的是音乐在时域的样子,而这个对于我们而言并不能很好的利用,所以接下来就利用傅里叶变换,把音乐在时域的样子转化为音乐在频域的样子,傅里叶变换告诉我们,任何周期函数,都可以看作是不同振幅,不同相位正弦波的叠加。利用对不同琴键不同力度,不同时间点的敲击,可以组合出任何一首乐曲,再将其转化为频谱,我们对于频谱是可以操作的。

在傅里叶变换之后,就可以得到在频域里面的数据,而这些数据也是最为核心的部分。接来下我们规定每一次的采样都有256个样本,采样率使用的是每秒钟20kHz,这是单片机采样的最大极限,采样率使用越高,它的还原度也越高,因此,在接下来的步中得到的一系列变换的还原度也越高,最后将这256个样本化为128阶,第一步“音乐”的接收与转换就到这里告一段落。

2.3.2激光振镜模块

本项目采用的是激光振镜的原理。扫描图案是二维效图案,所以扫描电机采用X,Y两个电机控制,一个时刻确定一个点的位置,通过扫描频率控制不同时刻点的位置到达整个扫描图案的变幻,扫描频率(即速度)越低图案闪烁越明显,可以用电影的原理方式来理解。激光振镜的设计思路完全沿袭电流表的设计方法,镜片取代了秒针,而探头的信号由计算机控制的-5V—+5V或-10V—+10V的直流信号取代,以完成预定的动作。同专镜式扫描系统相同,这种典型的控制系统采用了一对折返镜,不同的是,驱动这套镜片的步进电机被伺服电机所取代,在这套控制系统中,位置传感器的使用和负反馈回路的设计思路进一步保证了系统的精度,整个系统的扫描速度和重复定位精度达到了一个新的水平。

将激光打在衍射光栅片上,衍射光栅通常简称为“光栅”,一种由密集﹑等间距平行刻线构成的非常重要的光学器件。它利用多缝衍射和干涉作用﹐将射到光栅上的光束按波长的不同进行色散﹐再经成像镜聚焦而形成光谱。应用较多的是反射光栅﹐它的基底是低膨胀系数的玻璃或熔石英﹐上面镀铝﹐然后把平行线刻在铝膜上。

因此,只要通过控制固定衍射光栅片的马达的转速,来改变衍射光栅片的变化频率,从而是激光打出来的固定画案有一定的变换。马达的转速则是通过第一步中得到的那128阶来控制,因为马达的转动率低,不需要很精确的数据,之后通过倍频程的方法将这128阶分为8分,在这8分中,每一份我都取它们的最大值,从而使这8分都有一个确定的值,而这些值就是来控制马达的转速。

2.3.3Processing模块

此项目采用processing,在得到的128阶后,把它们导入电脑中,由于128阶的划分度已经算较为精确了,因此还原度也是有保障的。接下来就可以把它们在processing互动平台中显示出来。通过processing,将那段已经通过变为由时间划分得到的一段段频率以及声音的响度进行转化,从而得到一组组的变化的图案,在一定的控制下,也可以将这些图案变成一组组的漫画,从而能体现出每一首曲子它所想表现出内容与情感。Processing模块只要就是将那些已经收集的数据通过processing这个模块转化成动画。

由于制作这些动画的源代码公开,这样使用者也可以按照自己的想法来进行编程。动画制作也是这一项目的一大亮点,可以通过自己的改编,做出随着音乐波动的图案。

3 总结与讨论

3.1难点分析

3.2先进性及独特之处

整体结构设计:为了乐器本身的基本功能,我将激光乐动器安放在琵琶声音所能传达到的地方,这样就没有以往那些必须将这些设备放在规定地方的条条框框。

该装置最大的贡献为那些外出演出的志愿者们节约经历,只要有一堵墙,他们所演奏的内容就可以通过激光显示出来,如果场地还有电脑,那只要再带一个U盘即可。

从时域到频域的傅里叶变换是工程和科研的重要的基础,我们通过这一小装置实现了傅里叶变换的彩色音乐

柱状图是比较传统的频谱显示,后来改成同心圆击鼓式效果也不错

3.3与同类项目比较

基于arduino单片机,以半导体激光发射管和接收管为传感器,利用半导体激光发射管发出的光束模拟电子琴的琴弦,控制芯片对接收管电路输出的数字信号进行采集和处理,实现中音区八音阶稳定发音。在软件设计上,我们采用C语言编写程序源代码。此外,在系统的外观结构上我们采用强度较低的PVC工业塑料和强度较高的轻质合金片搭配设计制作,既保证了外观上的美观性又确保了结构上的稳定性。

该系统的设计方法更大程度上体现了灵活性、美观性和创新性。在功能上拥有较高的可移植性和扩展性,方便用户根据自己的需求和爱好扩展新的功能。——基于MCU的简易激光电子琴设计与实现

作者使用的是半导体激光发射管和接收管为传感器,而我则使用模块化的声音传感器,并配合这processing模块进行音频转换,使得声音可视化。而且对于这一点,我不仅仅有processing这样的计算机模拟数据,还有激光这样的实实在在的装备,做了两手准备,更加具有可靠性和可行性。

项目的创新点

减工减力:用单片机控制整个装置,免去搬运大型的物品所浪费的人力物力。

成本低廉:使用单片机来完成数据的接受和处理。

使用程度高:越来越多的人参加此类的志愿者活动,这样的项目可以更好地帮助他们。

4 成品总结

我的项目主要的创新点是使用现在单片机技术,运用单片机开发板和一些相关的模块,将他们组合在一起,从而开发出一些新型的组合技术。这样可以将一些性能较低的单片机利用起来。

这个项目以节约人力物力,方便携带为主题。具有较大的意义,且此产品对整体的制作上技术不需过于深奥,成本也不会归于昂贵,用到了单片机技术,此产品运用的范围也较广。在这个“时间就是金钱”的时代里,每个人都在快节奏的生活中生活,而在携带时我们由于技术的问题可以省去大把的时间。我的项目可以有效的减少在运输的时间,提高活动的运营效率。

整个装置实用性强,精准度高。未来是一个信息化,自动化的时代,激光乐动器接壤了信息化与非信息化,因此我设计的智能购物车目的并非让其变成完全自动化,这样只会让国人更加依赖电子技术,只知道一味地追求自动化。我设计半自动化,只是想节约掉准备设备的时间,让大家有更多的时间去做些别的事情。

项目的创新点

减工减力:用单片机控制整个装置,免去搬运大型的物品所浪费的人力物力。

成本低廉:使用单片机来完成数据的接受和处理。

使用程度高:越来越多的人参加此类的志愿者活动,这样的项目可以更好地帮助他们。

5 展望

我的这个项目如果真正运用到生活中的话,那么将会有效地提高活动准备的效率。但我这项目仍有缺点:若是某个环节或者程序突然出错,那么相当于这个这个设备一时半会儿没法投入使用。我心里所想采用的方法的是利用不同的程序,在一个程序崩溃之后还有别的程序来顶替。

在我现有的项目之上,我还有一个想法:加入喷雾的功能,伯努利原理说在同一流质里,流速大,压强小;流速小,压强大。流体会自动从高压流向低压。在通过三叉管时,低速流动的水流向高速的流动的空气。水被高速空气撕成一小滴一小滴(设想水龙头里流出的水,刚开始速度慢,是水柱;但后来速度逐渐增大后就变成一滴一滴了)。这些小水滴喷出来后就成了雾。而这种速度的要求,就可以用到磁铁执行机构原理。在同等输出功率的情况下,其具有重量轻,快速性好,成本低,无泄漏而导致的污染环境,速度快,操作方便等优点,可以更加优化的展现出这个功能,所以在一定条件下,还可以制造出喷雾的效果。

7 参考文献

《爱上Arduino》——By Massimo Banzi

《Arduino从基础到实践》——By 【美】麦克罗伯茨

《Processing与Arduino互动编程》——黄文恺 吴羽

8 附录

/*

Laserium Light Concert

*/

/*******************Port Interface**************

Arduino Mega ULN2003

D8 IN1

D9 IN2

D10 IN3

D11 IN4

+5V VCC

GND GND

/*******************Port Interface**************

Arduino Mega Arduino Mega

TX2 RX2

RX2 TX2

GND GND

*/

int motor_pin_1 = 8;

int motor_pin_2 = 9;

int motor_pin_3 = 10;

int motor_pin_4 = 11;

byte recValue[9] = {'0','0','0','0','0','0','0','0'};

int pos = 0;

byte mode = 0;

byte last_mode = 0;

byte delayValue = 0;

byte stepValue = 0;

byte orientation = 0;

void setup() {

pinMode(motor_pin_1,OUTPUT);

pinMode(motor_pin_2,OUTPUT);

pinMode(motor_pin_3,OUTPUT);

pinMode(motor_pin_4,OUTPUT);

Serial.begin(115200); // use the serial port

Serial2.begin(115200); // use the serial port

}

void loop() {

delayValue = 5 - mode/2;

orientation = orientation^1; // XOR

if(last_mode = mode)

stepValue = 35*mode;

else

stepValue = 15*mode;

if(orientation)

forward(delayValue,stepValue); // 512 steps for 1 circle

else

backward(delayValue,stepValue);

}

void serialEvent2(){

while (Serial2.available()) {

//Serial.println(pos);

recValue[pos] = Serial2.read();

if(recValue[0] = 'A') pos = pos + 1;

else pos = 0;

if (pos > 8){

pos = 0;

last_mode = mode;

mode = maxValue(recValue);

// Serial.print("I received: ");

// for(int i = 0;i<9;i++) Serial.println(recValue[i]);

// Serial.print("MasPos: ");

// Serial.println(mode);

}

}

}

void setStep(int w1,int w2,int w3,int w4){

digitalWrite(motor_pin_1, w1);

digitalWrite(motor_pin_2, w2);

digitalWrite(motor_pin_3, w3);

digitalWrite(motor_pin_4, w4);

}

void forward(int delays, int steps){

for(int i=0;i

setStep(1, 0, 0, 0);

delay(delays);

setStep(0, 1, 0, 0);

delay(delays);

setStep(0, 0, 1, 0);

delay(delays);

setStep(0, 0, 0, 1);

delay(delays);

}

}

void backward(int delays, int steps){

for(int i=0;i

setStep(0, 0, 0, 1);

delay(delays);

setStep(0, 0, 1, 0);

delay(delays);

setStep(0, 1, 0, 0);

delay(delays);

setStep(1, 0, 0, 0);

delay(delays);

}

}

byte maxValue(byte Array[9]){

byte maxPos = 0;

byte maxElement = 0;

for(byte i=3;i<9;i++){

// Serial.print("Array: ");

// Serial.println(Array[i]);

// Serial.print("maxElement: ");

// Serial.println(maxElement);

if(Array[i] > maxElement){

maxElement = Array[i];

maxPos = i;

}

}

return maxPos;

}

/*

Music Sampling and Processing

Time: 01/01/2017

*/

/*******************Port Interface**************

Arduino Mega Microphone

A0 DATA

+5V VCC

GND GND

/*******************Port Interface**************

Arduino Mega Arduino Mega

TX2 RX2

RX2 TX2

GND GND

*/

#define LOG_OUT 1 // use the log output function

#define OCTAVE 1

#define FHT_N 256 // set to 256 point fht

#include // include the library

#include

byte Tx_buf[9] = {'A','0','0','0','0','0','0','0','0'};

//int timeCounter;

void setup() {

Serial.begin(115200); // use the serial port

Serial2.begin(115200); // use the serial port

Timer1.initialize(500000); //2s

Timer1.attachInterrupt( timerIsr );

//TIMSK0 = 0; // turn off timer0 for lower jitter

ADCSRA = 0xe6; // set the adc to free running mode:250kHz.19.2kSPS

ADMUX = 0x40; // use adc0

DIDR0 = 0x01; // turn off the digital input for adc0

}

void loop() {

cli(); // UDRE interrupt slows this way down on arduino1.0

for (int i = 0 ; i < FHT_N ; i++) { // save 256 samples

while(!(ADCSRA & 0x10)); // wait for adc to be ready

ADCSRA = 0xf6; // restart adc

byte m = ADCL; // fetch adc data

byte j = ADCH;

int k = (j << 8) | m; // form into an int

k -= 0x0200; // form into a signed int

k <<= 6; // form into a 16b signed int

fht_input[i] = k; // put real data into bins

}

fht_window(); // window the data for better frequency response

fht_reorder(); // reorder the data before doing the fht

fht_run(); // process the data in the fht

fht_mag_log(); // take the output of the fht

fht_mag_octave();

sei();

Serial.write(255); // send a start byte

Serial.write(fht_log_out, FHT_N/2); // send out the data:delta = 961 Hz

for(int i = 1;i < 9;i++){

Tx_buf[i] = int(fht_oct_out[i-1]);

}

}

void timerIsr()

{

Serial2.write(Tx_buf,9);

}

/* Music Visualization

Author: HuangSenhong

Email: [email protected]

Time: 01/03/2017

PS: This code communication with arduino in COM4, modify serial port if need be

*/

final int X_OFFSET = 40; // x-distance to left upper corner of window

//final int Y_OFFSET = 60; // y-distance to left upper corner of window

final int Y_OFFSET = 100; // y-distance to left upper corner of window

final int BOT_DIST = 80; // distance to bottom line of window

final int COL_WIDTH = 4; // column widt

final int Y_DIST = 64; // distance horizontal lines

final int X_DIST = 5; // distance vertical lines

final int X_MAX = (128+1)*X_DIST+1; // x-axis lenght

final int Y_MAX = 256; // y-axis lenght

final int X_WINDOW = X_MAX + 2*X_OFFSET; // window width

final int Y_WINDOW = Y_MAX+BOT_DIST+Y_OFFSET; // window height

final int X_ENUM = 10;

PFont fontA;

color graphColor = color(25, 25, 250);

PFont fontGraph;

import processing.serial.*;

Serial port;

int[] inBuffer = new int[128];

void draw_grid() // draw grid an title

{

int count=0;

background(200); // gray color

//background(255, 204, 0);

stroke(0);

for (int x=0+X_DIST; x<=X_MAX; x+=X_DIST) // vertical lines

{

if ( X_ENUM == count || 0 == count)

{

line (x+X_OFFSET, Y_OFFSET, x+X_OFFSET, Y_MAX+Y_OFFSET+10);

count=0;

}

else

{

line (x+X_OFFSET, Y_OFFSET, x+X_OFFSET, Y_MAX+Y_OFFSET);

}

count++;

}

for (int y=0; y<=Y_MAX; y+=Y_DIST) // horizontal lines

{

line (X_OFFSET, y+Y_OFFSET, X_MAX+X_OFFSET, y+Y_OFFSET);

textFont(fontA, 16);

text( (Y_MAX-y), 7, y+Y_OFFSET+6);

}

textFont(fontA, 32);

fill(graphColor);

text("128-Channel Spectrum Analyser",120, 40);

textFont(fontA, 22);

text("Minhang High School",350, 65);

text("Author: Guaner Hu",360, 90);

textFont(fontA, 16);

//text("magnitude", 7, 20);

//text("(8bit-value)", 7, 40);

text("magnitude", 7, 60);

text("(8bit-value)", 7, 80);

text("--> channel (number i)", X_OFFSET, Y_WINDOW-30);

text(" frequency f ( i ) = i * SAMPLE_RATE / FHT_N ", 350, Y_WINDOW-30 );

}

void serialEvent(Serial p) // ISR triggerd by "port.buffer(129);"

{

if ( 255 == port.read() ) // 1 start-byte

{

inBuffer = int( port.readBytes() ); // 128 data-byte

}

}

void setup()

{

//size(X_WINDOW, Y_WINDOW); // size of window

//size(726, 396); // size of window

size(726, 436); // size of window

noStroke();

fontGraph = loadFont("ArialUnicodeMS-48.vlw");

textFont(fontGraph, 12);

println(Serial.list()); // show available COM-ports

port = new Serial(this, "COM12", 115200);

port.buffer(129); // 1 start-byte + 128 data-bytes

fontA = loadFont("ArialUnicodeMS-48.vlw");

textFont(fontA, 16);

}

void draw()

{

int count=0;

draw_grid();

for (int i=0; i<128; i++)

{

fill(graphColor);

rect(i*X_DIST+X_OFFSET+X_DIST-COL_WIDTH/2, height-BOT_DIST, COL_WIDTH, -inBuffer[i]);

if ( X_ENUM == count || 0 == count)

{

text(i, (i+1)*X_DIST+X_OFFSET-COL_WIDTH/2, height-BOT_DIST+25);

count=0;

}

count++;

}

}

图2

图2

图3

图3

图4

图4

图5

图5

图6

图6

图7

图7

图8

图8

图9

图9

图10

图10

图11

图11