CGArt-中国CGer中的绿色家园
首页 信息动态 原创排行 互动教程 资源千寻 CG人才 CGArt杂志 艺术设计 CG画廊 CG论坛 酷站欣赏 CG搜索 会员中心
Flash AS制作LRC歌词同步的详细教程
来源:ActionScript 3天地会门户 作者:alan98 编辑:浪漫的季节 发布时间:2008年06月04日 08:39:32

一、准备工作
既然要制作歌词同步程序,首先要准备一首歌,我们就以“周杰伦-青花瓷”为例。首先要下载这首“青花瓷.mp3”,保存为“C:\My Player\Music\青花瓷.mp3”。还要下载青花瓷的 LRC 文件,大家可以到网上下载(地址见附录),将文本内容保存为“C:\My Player\LRC\青花瓷.lrc”。我们的程序(类和FLA)则保存在“C:\My Player\”文件夹下。
青花瓷.lrc 文件:

--------------------------------------------------------------------------------
[ti:青花瓷]
[ar:周杰伦]
[al:我很忙]
[by:张琪]
[00:00.00]发送短信18到291199下载该歌曲到手机
[00:01.11]青花瓷
[03:36.49]
[00:21.39]素眉勾勒秋千话北风龙转丹 
[00:26.08]屏层鸟绘的牡丹一如你梳妆
[00:30.46]黯然腾香透过窗心事我了然 
[00:34.93]宣纸上皱边直尺各一半
[00:39.49]油色渲染侍女图因为被失藏 
[00:43.83]而你嫣然的一笑如含苞待放
[00:48.30]你的美一缕飘散 
[00:50.77]去到我去不了的地方
[02:23.97][00:55.77]
[03:01.92][02:25.63][00:56.90]天正在等烟雨 
[03:03.57][02:27.91][00:58.99]而我在等你 
[03:05.92][02:30.44][01:00.93]炊烟袅袅升起 
[03:07.76][02:32.25][01:03.49]隔江千万里
[03:10.36][02:34.85][01:05.84]在平地书刻你房间上的飘影 
[03:14.67][02:38.73][01:09.87]就当我为遇见你伏笔
[03:18.83][02:43.35][01:14.34]天正在等烟雨 
[03:21.20][02:45.60][01:16.68]而我在等你 
[03:23.71][02:48.01][01:18.99]月色被打捞起 
[03:25.74][02:50.10][01:21.18]掩盖了结局
[03:28.33][02:52.54][01:23.72]如传世的青花瓷在独自美丽 
[03:32.30][02:56.67][01:27.65]你眼的笑意
[01:50.25]色白花青的景已跃然于碗底 
[01:54.69]临摹宋体落款时却惦记着你
[01:59.22]你隐藏在药效里一千年的秘密 
[02:03.75]急溪里犹如羞花沾落地
[02:08.32]林外芭蕉 惹咒语 
[02:10.57]梦幻的铜绿
[02:12.84]而我路过那江南小镇的等你
[02:17.19]在泼墨山水画里 
[02:19.75]你从墨色深处被隐去

--------------------------------------------------------------------------------
大家也可以把这个文本内容复制下来,然后在“C:\My Player\LRC\”下创建一个文本文档,将内容粘贴上去,再将文档保存为“青花瓷.lrc”,注意扩展名是“.lrc”。

二、LRC 内容分析
准备工作完成了,下面分析一下这个 LRC 文件。之所以叫 LRC ,是因为它是 Lyric (歌词) 的缩写。这种格式真是一目了然,前面“[ ]”中的数字表示其后歌词的开始时间。例如,“[01:50.25]色白花青的景已跃然于碗底”表示在1分50.25秒时,歌词内容是“色白花青的景已跃然于碗底”。
还有一种形式是“[03:01.92][02:25.63][00:56.90]天正在等烟雨”这种形式常用于赋格部分(俗称:歌曲的高潮部分),它表示在 03:01.92, 02:25.63, 00:56.90 时的歌词都是“天正在等烟雨”。由于这种形式的存在,使后面的编程稍显复杂,不过没关系,凭借各位的聪明智**

四、LRC 的读取与存储转换(使用文档类设计)
1.读取 LRC 文件,这一步非常简单与读取普通的文本文件是一样的;

CODE:

public function LRCPlayer() {
   var loader:URLLoader=new URLLoader();
   loader.load(new URLRequest("LRC/青花瓷.lrc"));
   loader.addEventListener(Event.COMPLETE,LoadFinish);
  }
  private function LoadFinish(evt:Event):void {
   trace(evt.target.data);
  }
--------------------------------------------------------------------------------
2.将读取的 LRC 数据按行分割( "\n" 为换行符),数组的每一个元素代表 LRC 的一行内容;

CODE:

  function LoadFinish(evt:Event):void {
   var list:String=evt.target.data;
   var listarray:Array=list.split("\n");
   trace(listarray);
  }
--------------------------------------------------------------------------------
3.在数组中提取每一行的时间及歌词,解决单时间序列的问题;(注意!此段代码只作讲解,不以应用)
LRC 内容如下:

QUOTE:

[00:43.83]而你嫣然的一笑如含苞待放
[00:48.30]你的美一缕飘散 
[00:50.77]去到我去不了的地方
[03:01.92]天正在等烟雨 
[03:03.57]而我在等你 
[03:05.92]炊烟袅袅升起 
[03:07.76]隔江千万里

代码如下:

CODE:

function LoadFinish(evt:Event):void {
   var list:String=evt.target.data;
   var listarray:Array=list.split("\n");
   for (var i=0; i    var info:String=listarray[i];
    //提取每行内容,用变量 info 保存
    var lyric:String=info.substr(10);
    //将歌词内容提取到 lyric 变量中
    var ctime:String =info.substr(0,10);
    //提取时间序列字串
    var ntime:Number=Number(ctime.substr(1,2))*60+Number(ctime.substr(4,5));
    //将时间字串转换为计算机可读取的时间
    var obj:Object=new Object();
    obj.timer=ntime*1000;
    obj.lyric=lyric;
    LRCarray.push(obj);
    //将时间与歌词保存到一个 Object 中,并压入LRCarray 数组
    trace(obj.timer,obj.lyric);
   }
  }
输出结果:

QUOTE:

43830 而你嫣然的一笑如含苞待放
48300 你的美一缕飘散
50770 去到我去不了的地方
181920 天正在等烟雨
183570 而我在等你
185920 炊烟袅袅升起
187760 隔江千万里

--------------------------------------------------------------------------------
4.在LRC文件,还有多时间序列的存在,所以单时间序列算法不能满足实际需要,下面就来解决多时间序列问题;
LRC 内容如下:

QUOTE:

[00:43.83]而你嫣然的一笑如含苞待放
[00:48.30]你的美一缕飘散 
[00:50.77]去到我去不了的地方
[03:01.92][02:25.63][00:56.90]天正在等烟雨 
[03:03.57][02:27.91][00:58.99]而我在等你 
[03:05.92][02:30.44][01:00.93]炊烟袅袅升起 
[03:07.76][02:32.25][01:03.49]隔江千万里

代码如下:

CODE:

  function LoadFinish(evt:Event):void {
   var list:String=evt.target.data;
   var listarray:Array=list.split("\n");
   var reg:RegExp=/\[[0-5][0-9]:[0-5][0-9].[0-9][0-9]\]/g;
   //建立正则表达式,范围:[00:00.00]~[59:59.99]
   for (var i=0; i    var info:String=listarray[i];
    //提取每行内容,用变量 info 保存
    var len:int=info.match(reg).length;
    //该行拥有时间序列的个数
    var timeAry:Array=info.match(reg);
    //将匹配的时间序列保存到 timeAry 数组中
    var lyric:String=info.substr(len*10);
    //根据每个时间序列占10个字符,找出歌词内容的起点
    //将歌词提取到 lyric 变量中
    for (var k:int=0; k     var obj:Object=new Object();
     var ctime:String=timeAry[k];
     var ntime:Number=Number(ctime.substr(1,2))*60+Number(ctime.substr(4,5));
     obj.timer=ntime*1000;
     obj.lyric=lyric;
     LRCarray.push(obj);
     trace(obj.timer,obj.lyric);
    }
    //将时间序列转换为毫秒并与歌词一起保存为一个数组元素
   }
  }
输出结果:

QUOTE:

43830 而你嫣然的一笑如含苞待放
48300 你的美一缕飘散 
50770 去到我去不了的地方
181920 天正在等烟雨 
145630 天正在等烟雨 
56900 天正在等烟雨 
183570 而我在等你 
147910 而我在等你 
58990 而我在等你 
185920 炊烟袅袅升起 
150440 炊烟袅袅升起 
60930 炊烟袅袅升起 
187760 隔江千万里
152250 隔江千万里
63490 隔江千万里

--------------------------------------------------------------------------------
5.将获得的 LRCarray 数组按起始时间排序,这对于按序读取歌词有重要意义;

CODE:

  LRCarray.sort(compare);
  private function compare(paraA:Object,paraB:Object):int {
   if (paraA.timer>paraB.timer) {
    return 1;
   }
   if (paraA.timer    return -1;
   }
   return 0;
  }
结果如下:

QUOTE:

43830 而你嫣然的一笑如含苞待放
48300 你的美一缕飘散
50770 去到我去不了的地方
56900 天正在等烟雨
58990 而我在等你
60930 炊烟袅袅升起
63490 隔江千万里
145630 天正在等烟雨
147910 而我在等你
150440 炊烟袅袅升起
152250 隔江千万里
181920 天正在等烟雨
183570 而我在等你
185920 炊烟袅袅升起
187760 隔江千万里

--------------------------------------------------------------------------------
6.最后,随着音乐的播放,读取播放时间段内的歌词。用当前播放时间与 LRCarray 中的时间相比较,如果当前时间小于 LRCarray.timer 的时间,那么就显示 LRCarray[i-1].lyric 的歌词。为什么要显示 [i-1] 的歌词呢?比如说当前播放到第 500 秒,读取的 LRCarray[20].timer 时间是 400 秒,那么 i++ 。下一次读取的 LRCarray[21].timer 时间是 700 秒,这时当前播放时间小于读取的这个时间,就说明当前的第 500 秒仍处于 LRCarray[20].timer 的时间范围内。

CODE:

  var lrc_txt:TextField=new TextField();
  var LRCarray:Array=new Array();
  var sc:SoundChannel;
  public function LRCPlayer() {
   lrc_txt.width=500;
   lrc_txt.selectable=false;
   addChild(lrc_txt);
   //歌词在文本 lrc_txt 中显示
   var loader:URLLoader=new URLLoader();
   loader.load(new URLRequest("LRC/青花瓷.lrc"));
   loader.addEventListener(Event.COMPLETE,LoadFinish);
   var sound:Sound=new Sound();
   sound.load(new URLRequest("Music/青花瓷.mp3"));
   sc=sound.play();
   //播放声音,并生成 sc 变量,SoundChannel 类的实例
   stage.addEventListener(Event.ENTER_FRAME,SoundPlaying);
   //实时刷新歌词
  }
  function SoundPlaying(evt:Event):void {
   for (var i=1; i    if (sc.position     lrc_txt.text=LRCarray[i-1].lyric;
     break;
     //找到歌词,跳出循环体
    }
    lrc_txt.text=LRCarray[LRCarray.length-1].lyric;
    //找不到歌词,说明已超出了最后一句的时间,因此显示最后一句歌词
   }
  }
五、全部代码(文档类 LRCPlayer.as):

CODE:

package {
import flash.display.Sprite;
import flash.net.URLRequest;
import flash.net.URLLoader;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.events.Event;
import flash.text.TextField;
import flash.system.System;
public class LRCPlayer extends Sprite {
  var lrc_txt:TextField=new TextField();
  var LRCarray:Array=new Array();
  var sc:SoundChannel;
  public function LRCPlayer() {
   System.useCodePage=true;
   lrc_txt.width=500;
   lrc_txt.selectable=false;
   addChild(lrc_txt);
   var loader:URLLoader=new URLLoader();
   loader.load(new URLRequest("LRC/青花瓷.lrc"));
   loader.addEventListener(Event.COMPLETE,LoadFinish);
   var sound:Sound=new Sound();
   sound.load(new URLRequest("Music/青花瓷.mp3"));
   sc=sound.play();
   stage.addEventListener(Event.ENTER_FRAME,SoundPlaying);
  }
  function SoundPlaying(evt:Event):void {
   for (var i=1; i    if (sc.position     lrc_txt.text=LRCarray[i-1].lyric;
     break;
    }
    lrc_txt.text=LRCarray[LRCarray.length-1].lyric;
   }
  }
  function LoadFinish(evt:Event):void {
   var list:String=evt.target.data;
   var listarray:Array=list.split("\n");
   var reg:RegExp=/\[[0-5][0-9]:[0-5][0-9].[0-9][0-9]\]/g;
   for (var i=0; i    var info:String=listarray[i];
    var len:int=info.match(reg).length;
    var timeAry:Array=info.match(reg);
    var lyric:String=info.substr(len*10);
    for (var k:int=0; k     var obj:Object=new Object();
     var ctime:String=timeAry[k];
     var ntime:Number=Number(ctime.substr(1,2))*60+Number(ctime.substr(4,5));
     obj.timer=ntime*1000;
     obj.lyric=lyric;
     LRCarray.push(obj);
    }
   }
   LRCarray.sort(compare);
  }
  private function compare(paraA:Object,paraB:Object):int {
   if (paraA.timer>paraB.timer) {
    return 1;
   }
   if (paraA.timer    return -1;
   }
   return 0;
  }
}
}

六、*无处不在的优化
至此,该程序已经可以顺利执行了,此处只讨论一下优化问题,看不懂可以跳过。
以这段代码为例:

CODE:

function SoundPlaying(evt:Event):void {
  for (var i=1; i   if (sc.position    lrc_txt.text=LRCarray[i-1].lyric;
    break;
   }
   lrc_txt.text=LRCarray[LRCarray.length-1].lyric;
  }
}
如果要进行优化,那么这个 for 循环,应该写成:

CODE:

  for (var i=1,j=LRCarray.length; i这样在执行判断时,不必每次都进行 LRCarray.length 操作,该操用于读取数组长度,执行 Array 类的 length 方法,属于高级操作,花费的时间要比低级操作多。其实,只要读取一次长度,然后将结果保存在变量 j 中,每次判断时读取 j 的值即可。取值与赋值都属于低级别的操作,速度较快。同样的道理,在

CODE:

if (sc.position中的 sc.position 在每次判断时都要读取一遍,这时就应将它在循环之前保存到一个变量里,这段代码优化后应是这样:

CODE:

function SoundPlaying(evt:Event):void {
  var now:Number=sc.position;
  for (var i=1,j=LRCarray.length; i   if (now    lrc_txt.text=LRCarray[i-1].lyric;
    break;
   }
   lrc_txt.text=LRCarray[j-1].lyric;
  }
}
在我们的文档类中还有几个地方用到了 for 循环,请大家按照上述方法自行优化。
其实,代码优化无处不在,其中的学问不胜枚举,有兴趣的朋友可以到我的博客中看一下关于代码优化的总结贴,见附录。

七、附录
1.LRC 文件下载地址:
http://lrc.bzmtv.com/
http://www.5ilrc.com/

2.至于 MP3 的下载,我想大家比我在行,用百度或酷狗都可以。

3.代码优化总结贴地址:
http://www.webjx.com/flash/actionscript-5898.html

4.整个文件包括(歌曲、歌词、LRCPlayer.as 、FLA 文件)打包下载:
http://www.fs2you.com/zh-cn/files/cf760b0f-01a0-11dd-9174-0014221f3995/

八、结束语
    恭喜您坚持到了现在,确实内容比较长,同时也涉及了一些知识点。其实做法肯定不只这一种,所以希望大家多多发挥主观能动性,结合上述内容继续将这个播放程序做大做强。好了,就到这里,再次感谢。

共有评论0条
更多评论..
作者信息 详细信息
评论人:
验证码:
内容:
 
about us advertisement publish conformity service cooperate associate link site map contact us help jump to the top of page