由于瘟疫肆虐,我在家中宅了好几天,简直不能太无聊。一个偶然的机会,我在抖音上面发现了一个类似钢琴的APP,然后就试着用App inventor做一个可以实现音乐功能的软件,暂且可以把它叫做木琴。经过3次的修改,最终成了下面的样子。让我们来一起看看吧!
一、简单的作品描述
基本界面如图1所示
功能介绍:
- 通过触摸屏幕上的彩色按钮播放八个不同的音符;
- 按“播放”按钮,回放之前弹奏的音符;
- 按“重置”按钮清除之前弹过的音符,以便输入新曲。
二、实现原理
- 使用单一的声音组件来播放不同的音频文件;
- 使用Clock组件来计算并实现两个音符之间的延时;
- 在创建一个过程时做判断;
- 创建能够自我调用的过程;
三、具体实现步骤
记得上次做的是蓝牙相关的实践,用的是汉语的界面,这次尝试用英文试一下!
首先创建前两个键,用按钮来实现:
1. 从面板(palette)的user interface组中拖出一个按钮。
-
BackgroundColor属性:为红色;
-
Text属性:为“C”;
-
Width属性:为“Fill parent”,使其占满屏幕;
- Height属性:为40像素。
2. 重复上述步骤创建第二个按钮,名为Button2,放在Button1下面。Width及Height属性值同Button1,但BackgroundColor属性设为红色,Text属性设置为“D”。如下图:
3.其他键位的设计完全按照上面的步骤来实现,在此就不一一说明了。
添加其他组件
木琴不能没有声音,所以我创建一个Sound组件,名字为Sound1。MinimumInterval(最小间隔)属性设置为0(默认值为500毫秒)。这可以让我们的演奏要多快有多快,而不必等半秒钟(500毫秒)。
下载1.wav和2.wav,并加载到项目中,注意这里的声音文件必须保持原有文件名。
当某个按钮被点击时,用程序来实现播放声音的行为,即:如果Button1被点击,则播放1.wav,如果Button2被点击,播放2.wav。
若要切换到块编辑器,如下图所示,进行以下设置:
- 从Screen1项下的Button1抽屉里拖出Button1.Click块;
- 从Sound1抽屉里拖set Sound1.Source块,放置在Button1.Click块中;
- 输入“text”来创建一个文本块(而不是从Built-in项下的Text抽屉里拖出,这样更便捷。)设置文本值为“1.wav”,并与Sound1.Source块连接;
- 添加Sound1.Play块。
点击按钮时播放声音
对Button2进行同样设置,如图(只改了文件名),代码几乎完全重复。
重复的代码提示我们最好是创建一个过程。用join块将数字(如1)与文本“.wav”连接起来,创造出正规的文件名(如“1.wav”)。下面是创建这个过程的步骤:
1. 在块编辑器中打开Procedures抽屉,拖出“to procedure”块;
2. 单击procedure将过程名改为playNote;
3. 点击procedure块左上角的蓝色方块呼出内部组件,将一个input x块插入“inputs”块;
4. 将input x块中的x改为number;
5. 将set Sound1.Source to块从Button1.Click事件处理程序中拖出,放在PlayNote过程内“do”的右边,Sound1.Play块也将随之移动;
6. 将1.wav块拖入垃圾桶;
7. 从Text抽屉中拖出join块放到set Sound1.Source to的插槽内;
8. 将鼠标悬停在playNote的number参数上,呼出并拖动get number块,并将其放入join块的第一个插槽中;
9. 从Text抽屉中拖出空文本块,放在join块的第二个插槽中;
注意:将文本值设置为“.wav”。(切记不要输入引号);
从Procedures抽屉中拖出call PlayNote块,放到空的Button1.Click内;
在number插槽中插入文本“1”。
现在,当Button1被点击时,过程PlayNote将以数字1为参数被调用。该过程将Sound1.Source属性设为“1.wav”,并播放该声音。
创建一个Button2.Click块,调用参数为2的PlayNote过程。(可以复制现有的PlayNote块,将其移动到Button2.Click块内,并将参数更改为2;也可以复制整个Button1.Click块,然后将Button1改为Button2,再将参数1改为2。)程序如图所示。
图 创建一个过程来演奏音符
四、Android加载声音
此时在手机上测试程序并不能成功:第一次按键时,弹出错误提示:“Error 703:Unable to play 1.wav”(不能播放1.wav);第二次再按同一个键时,才听到声音。
原因:Android系统是在程序运行时才加载声音文件(只需加载一次),加载过程需要一点时间。第一次按键,当call Sound1 play块开始执行时,set Sound1.Source to块的加载任务尚未完成,因此系统给出错误提示;等到第二次按键时,声音文件已经加载完成,因此可以正常播放。
解决方法:直到程序启动之后,我们也没有对Sound1.Source进行设置,因此没有对声音做初始化。我们必须在程序启动时直接加载声音文件,如图所示。
图 在应用启动时加载声音文件
五、实现其余的音符
两个按钮已经实现了演奏音符的功能,现在需要回到组件设计器,加载其余六个声音文件3.wav、4.wav、5.wav、6.wav、7.wav和8.wav,并添加其余六个音符。首先创建六个新Button组件,重复此前的步骤,
图 在组件设计器中放置其余的声音按钮
回到块编辑器中,为每个新按钮创建Click块并以相应的参数调用PlayNote过程。同样,在Screen.Initialize中加载新的声音文件,如图所示。
对按钮单击事件编程,使得键盘与音调相对应
六、记录并回放音符
为了实现回放功能,需要记录弹奏的音符并加以保存。除了要记录弹奏的音高(声音文件),还要记录两个音符之间的时间长度,否则将无法表现两个连续快弹音符与两个间隔10秒的音符之间的差别。
实现原理:维护两个列表,每弹奏一个音符,两个列表中都会各自添加一条记录:
- notes:包含与演奏的音符相对应的声音文件名,按照演奏顺序排列;
- times:记录音符演奏时的时间点。
添加必要组件
在设计器中添加一个Clock组件及“播放”和“重置”按钮,按钮放在HorizontalArrangement中:
1. 拖入一个Clock组件,它将出现在“不可见组件”区域,取消勾选TimerEnabled属性。
2. 从layout组中拖出一个HorizontalArrangement组件放在按钮下面,Width属性设为“Fill parent”;
3. 从User Interface组中拖动一个按钮,改名为PlayButton,Text属性设为“播放”;
4. 拖出另一个按钮并放在PlayButton右侧,改名为ResetButton,Text属性设为“重置”。
图 记录并回放声音的组件被添加到设计器中
七、记录音符及时间
实现原理:维护两个列表:notes与times,每次用户按下一个按钮,就向列表中添加一项:
1. 从Variables抽屉中拖出一个initialize global name to块来定义一个新的变量;
2. 单击“name”将变量命名为“notes”;
3. 打开Lists抽屉,拖动一个make a list块,将其放置在变量notes的插槽中;
这样就定义了一个名为“notes”的空列表。重复上述步骤定义另一个变量,命名为“times”。块的样子如图所示。
图 设置变量来记录音符
八、块的功能
每演奏一个音符,需要保存两项数据:声音文件名(保存到notes列表),以及演奏瞬间的时刻(保存到times列表)。用Clock1.Now块来记录时刻,它返回当前时刻的时间值,精确到毫秒。这些数据可以通过Sound1.Source和Clock1.Now块获得,将分别被添加到notes及times列表中。
添加一个Sound1.Vibrate块,通过振动来告知用户按键生效了。实现逻辑如下:
为用户的“重置”操作提供反馈
九、音符的回放
实现原理:
-
变量count用来跟踪notes列表中当前正在播放的音符的索引(位置);
-
新过程 PlayBackNote,用来播放当前音符,并移动到下一个音符;
- 编写PlayButton.Click事件处理程序,设置count为1,只要列表中有保存的音符,就调用PlayBackNote。
图 回放被记录下来的音符
自我调用-----数学递归
1. 在第一次调用PlayBackNote时,count= 1:
-
Sound1.Source被设置为在notes中的第1项,即1.wav;
-
调用Sound1.Play,播放1.wav;
- 由于count值(1)小于notes的长度(3),因此count递增为2,并再次调用PlayBackNote;
2. 第二次调用PlayBackNote时,count=2:
-
Sound1.Source被设置为notes中的第2项,即3.wav;
-
调用Sound1.Play,播放3.wav;
- 由于count(2)小于notes的长度(3),因此count递增为3,并再次调用PlayBackNote;
3. 第三次调用PlayBackNote时,count=3:
-
Sound1.Source被设置为notes中的第3项,即6.wav;
-
调用Sound1.Play,播放6.wav;
- 由于count(3)不小于notes的长度(3),因此跳出if块,回放结束。
- 递归是正确的,但需要在两次调用PlayBackNote之间添加延迟功能。
十、播放适当延迟的音符
实现原理:
延迟的设定与两个音符之间的时间差有关,用clock来为这个时间差计时。创建Clock1.Timer事件并编写事件处理程序,来说明计时结束时将发生的事情。
图 在音符之间加入延迟
块的功能
现在假设两个列表中记录了以下内容:
-
notes:1.wav,3.wav,6.wav
- times:12:00:00,12:00:01,12:00:04
如图所示,在PlayButton.Click中设置count为1,并调用PlayBackNote。
1. 第一次调用PlayBackNote时,count= 1:
-
Sound1.Source被设置为notes中的第1项,即“1.wav”;
-
调用Sound1.Play播放1.wav;
- 因为count(1)小于notes的长度(3),于是Clock1.TimerInterval被设置为times列表中的第1项(12:00:00)与第2项(12:00:01)之间的时间差:1秒。Count递增到2,启用Clock1.Timer并开始计时;
Clock1.Timer开始计时,间隔1秒之后,计时结束,定时器暂时禁用,并调用PlayBackNote。
2. 第二次调用PlayBackNote时,count= 2 :
-
Sound1.Source被设置为notes中的第2项,即“3.wav”;
-
调用Sound1.Play播放3.wav;
- 因为count(2)小于notes的长度(3),于是Clock1.TimerInterval被设置为times列表中的第2项(12:00:01)与第3项(12:00:04)之间的时间差:3秒。Count递增到3,启用Clock1.Timer并开始计时;
Clock1.Timer计时开始,间隔3秒之后,定时器暂时禁用,并调用PlayBackNote。
3. 第三次调用PlayBackNote时,count= 3 :
-
Sound1.Source被设置为notes中的第3项,即“6.wav”;
-
调用Sound1.Play来播放6.wav;
- 由于count(3)不小于notes的长度(3),跳出if块,回放完成。
最后通过扫描二维码下载制作好的软件,来打发无聊的时光吧!
本次实践的最大收获是:编写一个能自我调用的过程不仅是可能的,有时也是必要的。递归就可以实现。在编写递归过程时,一定要确保为程序的退出设定一个基本条件,否则程序将陷入无限循环。好了今天就分享的这里吧,有问题可以通过下方留言。