用javaScript编写lrc歌词解析器

2023-11-17

如果想要了解如何编写的请继续往下看,如果只需要代码,请点击这里Github

lrc歌词文件介绍

来先看一下以下歌词 Heart To Heart.lrc

[ti:Heart To Heart]
[ar:James Blunt]
[al:Heart To Heart]
[by:]
[offset:0]
[00:00.00]Heart To Heart (诚恳的) - James Blunt (詹姆仕·布朗特)
[00:13.72]There are times when
[00:14.57]I don't know where I stand
[00:16.73][00:22.40](oh  sometimes)
[00:18.49]You make me feel like
[00:19.68]I'm a boy and not a man

其中大致包含两类标签 标识标签时间标签
标识标签:主要标识歌曲的信息(作者,作词者…) 其格式为:[标识名:值]
[ar:歌手名]
[ti:歌曲名]
[al:专辑名]
[by:编辑者(指lrc歌词的制作人)]
[offset:时间补偿值]歌词整体的偏移量
时间标签:表示啥时候出现歌词 标准格式:[分钟:秒.毫秒] 歌词
除此之外还有其他格式:[分钟:秒] 歌词,[分钟:秒:毫秒] 歌词,[分钟:秒:毫秒][分钟:秒:毫秒] 歌词.


大致了解了lrc的格式就可以开始编写代码了

首先定个大致流程

  1. 先解析标识标签
  2. 再解析时间标签
  3. 封装两个解析部件(目标已经完成)
  4. 优化添加axios异步获取歌词

1. 先解析标识标签

标识标签格式已经得到那么可以用正则匹配就行了/\[[A-Za-z]+\:[^\[\]]*\]/g,
匹配完了之后当做对象返回 格式为{标签名:标签值}

function getFlagTags (text) {
        // 解析标记标签 text lrc文本 
        // return {标签名:标签值} 例如: {by:xiaohuohu}
        let res = {};
        let find = text.match(/\[[A-Za-z]+\:[^\[\]]*\]/g);// 匹标志标签
        for (let findKey in find) {
            let textArrayItem = find[findKey];
            let tagName = textArrayItem.substring(textArrayItem.indexOf('[') + 1, textArrayItem.indexOf(':'));
            let tagText = textArrayItem.substring(textArrayItem.indexOf(':') + 1, textArrayItem.indexOf(']'));
            res[tagName] = tagText;
        }
        return res;
    }

2. 再解析时间标签

这里工作就大了起来,我们再具体分析一下解析要求:

  1. 有多种标准:[分钟:秒.毫秒] 歌词,[分钟:秒] 歌词,[分钟:秒:毫秒] 歌词.需要兼容
  2. [offset:时间补偿值] 会影响时间
  3. [01:02.03][1:2.3] 无论前面是否添加0都是符合要求的
  4. [分钟:秒:毫秒][分钟:秒:毫秒]歌词可以多个时间对应一个歌词

然后我们就可以开始编写代码了

  1. 根据lrc一行一个标签的特点我们先新建lrc进行切分
var text='歌词源文件内容';
var textArray=text.split('\n');// 切割后的数组
  1. 我们依据要求1 编写一个筛选结构过滤掉标识标签以及一些不合规矩的文字,一样的正则走起/(?:\[\d+\:\d+(?:[.:]\d+)?\])+.*/
for (let textArrayKey in textArray) {
      let textArrayItem = textArray[textArrayKey];// 子项
      if (textArrayItem.match(/(?:\[\d+\:\d+(?:[.:]\d+)?\])+.*/)) {// 判断是否符合歌词的规则
        // 符合要求的
      }
}
  1. 由于要求4多个之间的问题,那我们不如先解决歌词
    其思路是将时间标签替换为 ‘空’ 剩下的就是歌词啦.
let findWord = textArrayItem.replace(/(?:\[\d+\:\d+(?:[.:]\d+)?\])+/g, '').trim();// 歌去除时间保留歌词,并去除两端多余空格
if (findWord) {// 去除歌词为空的项
    // ......
}
  1. 匹配完歌词剩下的就是时间
    先匹配多个时间块:/(?:\[\d+\:\d+(?:[.:]\d+)?\])/g歌词无视
    再对每一个时间快转为为毫秒 [m:s.ms] m * 60000 + s * 1000 + ms,
    为了拆解时方便计算我们将每一位的进率单独提出来timeRule = [60000, 1000, 1]
let timeRule = [60000, 1000, 1];// // 对应位数的毫秒数
let res={};// 结果对象 {时间:歌词}
let findTime = textArrayItem.match(/(?:\[\d+\:\d+(?:[.:]\d+)?\])/g);// 匹配多个时间 例如 [1:2.3][4:2.4]hello world
for (let findTimeKey in findTime) {
     let findTimeItem = findTime[findTimeKey].match(/\d+/g);// 切割每一个时间的m s ms部分
     let nowTime = 0;// 初始为0;
     for (let x = 0; x < findTimeItem.length; x++) {
            nowTime += parseInt(findTimeItem[x]) * timeRule[x];// 分钟,秒,毫秒转换为转毫秒之后累加
     }
     res[nowTime] = findWord;// 添加结果 {时间:歌词}
}
  1. 解决要求2[offset:0] 前面步骤2不是在过滤符合要求的歌词?那我们就在他下面再过滤[offset:0] /^\[offset\:\-?[1-9]\d+\]$/
let addTime = 0;// 偏移量
let textArrayItem = textArray[textArrayKey];
if (textArrayItem.match(/(?:\[\d+\:\d+(?:[.:]\d+)?\])+.*/)) {// 判断是否符合歌词的规则
        // 歌词
} else if (textArrayItem.match(/^\[offset\:\-?[1-9]\d+\]$/)) {// 匹配偏移时间 解决[offset:-232]操作
      // [offset:-232]正值表示整体提前,负值相反
      addTime -= parseInt(textArrayItem.substring(textArrayItem.indexOf(':') + 1, textArrayItem.length));
}
  1. addTime应用上去
let nowTime = addTime;// 初始由0变为addTime;
for (let x = 0; x < findTimeItem.length; x++) {
    nowTime += parseInt(findTimeItem[x]) * timeRule[x];// 分钟,秒,毫秒转换为转毫秒之后累加
}
  1. 最后整理一下
function getTimeTags (text) {
    // 解析歌词标签 text lrc全部文本
    // return {time:word} 例如{12300:'hello world'}
    // [m:s.ms] or [m:s:ms] or [m:s], [1:2.3] or [01:02.03] or [01:02] ...
    let res = {};// 结果
    if (typeof (text) != 'string') { return res; }// 检查参数
    let textArray = text.split('\n');// 歌词拆分
    let timeRule = [60000, 1000, 1];// 对应位数的毫秒数
    let addTime = 0;// 时间补偿
    for (let textArrayKey in textArray) {
        let textArrayItem = textArray[textArrayKey];
        if (textArrayItem.match(/(?:\[\d+\:\d+(?:[.:]\d+)?\])+.*/)) {// 判断是否符合歌词的规则
            let findWord = textArrayItem.replace(/(?:\[\d+\:\d+(?:[.:]\d+)?\])+/g, '').trim();// 歌去除时间保留歌词,并去除两端多余空格
            if (findWord) {// 去除歌词为空的项
                let findTime = textArrayItem.match(/(?:\[\d+\:\d+(?:[.:]\d+)?\])/g);// 匹配多个时间 例如 [1:2.3][4:2.4]hello world
                for (let findTimeKey in findTime) {
                    let findTimeItem = findTime[findTimeKey].match(/\d+/g);// 切割每一个时间的m s ms部分
                    let nowTime = addTime;// 初始为偏移时间 解决[offset:-232]操作
                    for (let x = 0; x < findTimeItem.length; x++) {
                        nowTime += parseInt(findTimeItem[x]) * timeRule[x];// 分钟,秒,毫秒转换为转毫秒之后累加
                    }
                    res[nowTime > 0 ? nowTime : 0] = findWord;// 限制下线时间 0
                }
            }
        } else if (textArrayItem.match(/^\[offset\:\-?[1-9]\d+\]$/)) {// 匹配偏移时间 解决[offset:-232]操作
            // [offset:-232]正值表示整体提前,负值相反
            addTime -= parseInt(textArrayItem.substring(textArrayItem.indexOf(':') + 1, textArrayItem.length));
        }
    }
    return res;
}

3. 到此为止大部分功能已经完成

接下来就是添加axios然后再封装一下就大功告成(以下代码就可以直接使用,记得添加axios哟)

function Lrc(requestUrl) {
    // lrc歌词解析器 依赖axios库
    this.flagTags = {};// 解析的标志标签 [by:xiaohuohu]
    this.timeTags = {};// 解析的歌词 [3:20.3]hello world
    this.requestText = '';// 请求的lrc内容
    this.canPlay = 0;// 当前状态 0 未加载 1 获取失败 2 解析失败 3 解析成功
    this.canPlayInf = ['外星人正在搜寻歌词,请稍后', '外星人未找到歌词', '歌词外星人无法解析'];// 与canPlay 对应提示 除3之外
    this.lastWord = '';// 上一次歌词
    this.getLrc = function () {
        // 异步获取歌词
        let _this = this;
        _this.canPlay = 0;
        axios.get(requestUrl).then(function (response) {
            let data = response.data;// 获取数据
            _this.requestText = data;
            _this.flagTags = _this.getFlagTags(data);// 解析标志
            _this.timeTags = _this.getTimeTags(data);// 解析歌词
            // 判断是否成功解析,不为空.
            _this.canPlay = (Object.keys(_this.timeTags).length == 0) ? 2 : 3;// 设置当前状态
        }).catch(function (error) {
            _this.canPlay = 1;// 设置错误状态
            console.log(error);
        });
    }
    this.getRequestText = function () {
        // 获取lrc源文件
        return this.requestText;
    }
    this.getFlagTags = function (text) {
        // 解析标记标签 text lrc文本 
        // return {标签名:标签值} 例如: {by:xiaohuohu}
        let res = {};
        if (typeof (text) !== 'string') { return res; }// 检查参数
        let find = text.match(/\[[A-Za-z]+\:[^\[\]]*\]/g);// 匹标志标签
        for (let findKey in find) {
            let textArrayItem = find[findKey];
            let tagName = textArrayItem.substring(textArrayItem.indexOf('[') + 1, textArrayItem.indexOf(':'));
            let tagText = textArrayItem.substring(textArrayItem.indexOf(':') + 1, textArrayItem.indexOf(']'));
            res[tagName] = tagText;
        }
        return res;
    }
    this.getWord = function (time, flag = true, timeDeviation = 50) {
        // 获取解析歌词 time: 毫秒 flag:是否模糊匹配 若为 false 且找不到时返回 '' 
        // timeDeviation:模糊时间 (time - timeDeviation< time < time + timeDeviation),flag 为true 时生效
        if (this.canPlay == 3) {// 解析成功
            if (flag) {// 模糊匹配
                for (let key in this.timeTags) {
                    // 获取大概区间的第一个歌词
                    if ((key >= time - timeDeviation) && (key <= time + timeDeviation)) {
                        this.lastWord = this.timeTags[key];
                        return this.timeTags[key];
                    } else if (key > time + timeDeviation) {// 未找到返回上一次的歌词
                        return this.lastWord;
                    }
                }
            } else {// 精确匹配
                let res = this.timeTags[time];
                return res ? res : '';
            }
        } else {// 其它状态
            return this.canPlayInf[this.canPlay];
        }
    }
    this.getTag = function () {
        // 获取歌曲信息
        return this.flagTags;
    }
    this.getTimeTags = function (text) {
        // 解析歌词标签 text lrc全部文本
        // return {time:word} 例如{12300:'hello world'}
        // [m:s.ms] or [m:s:ms] or [m:s], [1:2.3] or [01:02.03] or [01:02] ...
        let res = {};// 结果
        if (typeof (text) != 'string') { return res; }// 检查参数
        let textArray = text.split('\n');// 歌词拆分
        let timeRule = [60000, 1000, 1];// 对应位数的毫秒数
        let addTime = 0;// 时间补偿
        for (let textArrayKey in textArray) {
            let textArrayItem = textArray[textArrayKey];
            if (textArrayItem.match(/(?:\[\d+\:\d+(?:[.:]\d+)?\])+.*/)) {// 判断是否符合歌词的规则
                let findWord = textArrayItem.replace(/(?:\[\d+\:\d+(?:[.:]\d+)?\])+/g, '').trim();// 歌去除时间保留歌词,并去除两端多余空格
                if (findWord) {// 去除歌词为空的项
                    let findTime = textArrayItem.match(/(?:\[\d+\:\d+(?:[.:]\d+)?\])/g);// 匹配多个时间 例如 [1:2.3][4:2.4]hello world
                    for (let findTimeKey in findTime) {
                        let findTimeItem = findTime[findTimeKey].match(/\d+/g);// 切割每一个时间的m s ms部分
                        let nowTime = addTime;// 初始为偏移时间 解决[offset:-232]操作
                        for (let x = 0; x < findTimeItem.length; x++) {
                            nowTime += parseInt(findTimeItem[x]) * timeRule[x];// 分钟,秒,毫秒转换为转毫秒之后累加
                        }
                        res[nowTime > 0 ? nowTime : 0] = findWord;// 限制下线时间 0
                    }
                }
            } else if (textArrayItem.match(/^\[offset\:\-?[1-9]\d+\]$/)) {// 匹配偏移时间 解决[offset:-232]操作
                // [offset:-232]正值表示整体提前,负值相反
                addTime -= parseInt(textArrayItem.substring(textArrayItem.indexOf(':') + 1, textArrayItem.length));
            }
        }
        return res;
    }
}

4.其它

为了节省内存,可以将Lrc的方法改进Lrc.prototype.functionName=function(){...}

5. 使用

let lrcob = new Lrc('./Counting Stars.lrc');
lrcob.getLrc();// 加载lrc歌词文件
let time = 0;
setInterval(() => {// 模仿audio,video时间进度条
    lrcob.getWord(time,true,50);// 模糊获取歌词 50为模糊值
    // lrcob.getWord(time,false); // 精确获取歌词
    time += 100;
}, 100)

其中的 getWord,getRequestText,getTag方法请自行了解

6.最后

资源奉上Github

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

用javaScript编写lrc歌词解析器 的相关文章

  • HashMap在Java里是怎么工作的

    本文翻译自 Coding Geek 原文地址 绝大多数Java开发者都在使用Map类 尤其是HashMap HashMap是一种简单易用且强大的存取数据的方法 但是 有多少人知道HashMap内部是如何工作的 几天前 为了对这个基本的数据结
  • Kubernetes 功能简述

    1 功能 1 1 主要功能 Kubernetes 是一个开源的容器编排平台 它提供了一系列功能来管理和部署容器化应用程序 以下是 Kubernetes 的一些主要功能 容器编排 Kubernetes 可以自动管理容器的部署 扩展和收缩 以满
  • 私有云不是真正的云计算!

    大数据产业创新服务媒体 聚焦数据 改变商业 中国云计算遇到困境 IaaS层面 阿里云 腾讯云等增长乏力 SaaS没有发展起来 反观美国 整个云计算蓬勃发展 AWS 微软云 谷歌云体量更大 增速却不低 SaaS已经高度发达 有不少市值几百亿美
  • 外包三年半,人废了一半

    如果不是女朋友和我提分手 我估计现在还没醒悟 大专生 18年通过校招进入湖南某软件公司 干了3年多的CRUD 今年年初 感觉自己不能够在这样下去了 长时间呆在一个舒适的环境会让一个人堕落 而我已经在一个企业干了3年的CRUD 已经让我变得不
  • C/C++ 课题解答(1)

    随机产生100个字符 a z 数组arrayOfChar 输入字符c 计算字符c在数组中出现的次数和位置 include
  • n的阶乘的两种方式

    n的阶乘的两种方式 递归与非递归 n 1 2 3 n 在n的阶乘中加入运行的时间 可以判断递归与非递归的运行效率 include
  • [vue-router] uncaught error during route navigation

    vue路由在加载组件之前会执行一些逻辑 尤其是生命周期的钩子函数 如果你在以上的钩子函数中 写了自己的逻辑 并报错了 就会触发 vue router uncaught error during route navigation这个错误 原因
  • 基于upload-labs的文件上传漏洞总结

    普通的前端绕过 1 抓包 2 上传jpg等格式的木马文件 3 bp上改回php后缀即可 普通绕过 1 抓包 2 上传jpg等格式的木马文件 3 bp上改后缀名为将后缀改为 php3 php4 php5 phtml等等 大小写绕过 即后缀名改
  • minikube命令

    Basic Commands 0minikube version查看版本 1minikube start启动一个集群 minikube start vm driver none image repository registry cn ha
  • ei计算机投稿 知乎,知乎热议:科研有很水的idea应该发表出来吗?

    原标题 知乎热议 科研有很水的idea应该发表出来吗 科研有很水的idea应该发表出来吗 来源 https www zhihu com question 372648294 小伙伴们 对于只能发EI 水会 OA SCI期刊那种 自己看到都觉
  • k8s基本命令

    k8s命令 https kubernetes io zh docs tutorials kubernetes basics 官网地址 基本命令 查看节点服务器 kubectl get nodes 查看命名空间 kubectl get ns
  • kettle(一)kettle介绍

    kettle介绍及组成 一 kettle 是什么 kettle 是一个ETL工具 ETL Extract Transform Load 数据抽取 转换 装载 kettle 是java编写 绿色无需安装 抽取高效稳定 kettle 主要用来对
  • 【零知ESP8266教程】快速入门5-使用按键来控制你的灯

    上节课 我们已经学习了如何制作一个简易交通灯 那么如何去控制一个LED的亮或者灭呢 此次试验采用按键来控制我们的LED 实现LED的简单控制 一 工具原料 电脑 windows系统 ESP8266开发板 micro usb线 LED灯一个
  • (模板)多项式乘法对任意数取模

    多项式乘法 系数对MOD 1000000007取模 常数巨大 慎用 只要选的K个素数乘积大于MOD MOD N 理论上MOD可以任取 define MOD 1000000007 define K 3 const int m K 100453
  • ajax怎么渲染数据,ajax数据请求渲染

    JQuery引入 html JS function var url https api douban com v2 movie search q E4 B8 89 E4 BD 93 count 1 ajax method get url d
  • 数据结构——第五章树(详细知识点总结)

    知识框架 5 1 树的基本概念 5 1 1 树的定义 树是n n gt 0 个节点的有限集 当n 0时 称为空树 在任意一棵非空树中应满足 有且仅有一个特定的称为根 Root 的结点 当n gt 1时 其余结点可分为m m gt 0 个互不
  • ArcGIS Maritime Server 开发教程(八)ArcGIS Bathymetry 扩展模块

    ArcGIS Maritime Server 开发教程 八 ArcGIS Bathymetry 扩展模块 本章导读 ArcGIS Bathymetry 扩展是 ArcGIS Maritime 解决方案中用于管理水深的重要模块 与传统的离散点
  • 内网穿透FRP详细教程

    简介 frp 是一个专注于内网穿透的高性能的反向代理应用 支持 TCP UDP HTTP HTTPS 等多种协议 可以将内网服务以安全 便捷的方式通过具有公网 IP 节点的中转暴露到公网 通过在具有公网 IP 的节点上部署 frp 服务端
  • 【笔记】构造函数的私有、公有、特权、静态成员

    根据书 javascript DOM高级程序设计 一书整理的笔记 var Myconstuctor function name 特权成员 与私有方法不同 特权方法能够被公开访问 而且还能够访问私有成员 特权方法是指在构造函数的作用域中使用t
  • 玩转wsl2之环境搭建

    Windows是市场占有率最高的桌面操作系统 很多开发人员还是习惯于在Windows系统中进行开发工作 但服务器领域多采用Linux操作系统 因此开发人员在开发过程中 经常会遇到windows系统开发的软件难以部署到Linux系统的问题 W

随机推荐

  • HTML表格嵌套、合并表格

    一 表格元素 lt table gt table常用属性 border 边框像素 width height 表格宽度 高度 bordercolor 表格边框颜色 bgcolor 表格背景颜色 二 tr th td元素 th和td元素是在tr
  • linux学习笔记-安装配置使用clamav杀毒软件

    1 安装clamav 2 更新病毒库 freshclam 如果更新不了 或者更新特别慢 可以手动下载病毒库文件 放到 var lib clamav 文件下 在更新病毒库 病毒库文件链接 三个文件 bytecode cvd http data
  • Python面试题,通过代码获取nginx.log中状态码的出现次数

    先用pyton分析nginx的一行日志 通过split函数把日志变成一个用空格分开的列表 得到了状态码是在索引为8的列表元素 开始操作 打开nginx日志文件 定义一个空字典 用来存放状态码出现的次数 结合while循环遍历每行日志
  • Python面试,这16个问题你一定要熟知

    一 Python 是如何进行内存管理的 答 从三个方面来说 一对象的引用计数机制 二垃圾回收机制 三内存池机制 对象的引用计数机制 Python 内部使用引用计数 来保持追踪内存中的对象 所有对象都有引用计数 引用计数增加的情况 一个对象分
  • C++查看变量类型办法(typeinfo)

    一 类型含意 bool b char c signed char a unsigned char h signed short int s unsigned short int t signed int i unsigned int j s
  • 一文讲懂C#、ASP.NET、ASP.NET MVC、ASP.NET web form、asp.net core mvc的区别

    微软的命名很糟糕 技术上有两个框架 ASP NET和ASP NET Core 它们分别基于 NET Framework和 NET Core构建 当Microsoft首次尝试创建一个遵循MVC模式的 现代 Web应用程序平台时 它将这个新平台
  • 主合取/析取范式

    前置知识 简单合取 析取式 合取 析取范式 极小项 当存在n个命题变项做合取时 如果这个简单合取式出现了全部的命题变项或它的否定形式 且恰好只出现一次 则这个式子属于极小项 以n 3 命题变项为p q r为例 他们的极小项如表 主析取范式
  • Ubuntu16升级为18

    https blog csdn net sean 8180 article details 81075659
  • 从mimikatz抓取密码学习攻防

    前不久在使用mimikatz抓取hash的时候遇到了报错 本着追根溯源的原则去查看了mimikatz抓取密码的原理 在学习的过程中发现了mimikatz的每种报错都有不同的原因 本文就从mimikatz的防御角度出发来分析如何防御mimik
  • 【ubuntu】报错su:认证失败

    问题 ubuntu使用su命令时提示认证失败 解决方法 报这个错误的原因是root用户默认锁定的 只要使用passwd命令重新设置下root密码即可 详细步骤 1 命令行输入sudo passwd 2 根据提示修改密码 3 输入su 4 输
  • Altium Designer编辑PCB时,器件跑出可视界面外的解决方法

    很多人在使用AD等进行PCB设计的时候 由于制作封装问题或者是其他操作问题 会遇到在PCB界面下某一个或者几个封装超出软件显示范围 不论如何移动和放大缩小 都无法显示出来 也就没法选中和编辑 下面就讲讲如何解决这个问题 一个封装超出界外 不
  • Feign 使用 @SpringQueryMap 来解决多参数传递问题

    本文目录 1 Feign传递一个bean对象参数 2 Feign传递一个bean对象参数 多个基本类型参数 3 Feign传递多个基本类型参数 4 Feign传递多个bean对象参数 在实际项目开发过程中 我们使用 Feign 实现了服务与
  • css_流光按钮(转载)

    CSS部分 初始化 取消页面的内外边距 padding 0 margin 0 body 弹性布局 让页面元素垂直 水平居中 display flex justify content center align items center 让页面
  • Mock框架应用(三)-Mock Post请求

    不带参数的post请求 description 不含参数的post请求 request uri post method post response text 不含参数的post请求 带参数的post请求 配置Json 并启动moco服务 d
  • python列表index找不到索引_Python list.index在找不到索引时抛出异常

    Why does list index throw an exception instead of using an arbitrary value for example 1 What s the idea behind this To
  • R语言数据清洗与规整-回归模型为例

    数据清洗和规整是进行数据分析的前提条件 数据的清洗和规整通常会花费比进行数据分析更多的时间 正所谓 清洗一小时 分析五秒钟 数据清洗和规整要依据实际数据的特征进行 其包括缺失值和冗余值的处理 数据重归类 字符类型转换等 这里将使用 狗熊会
  • Opencv C++ 基本数据结构 Mat

    Opencv C 基本数据结构 Mat Mat 构造单通道Mat对象 获取单通道Mat的基本信息 以三行两列的矩阵为例 1 获取行数和列数 2 使用成员函数size 获取矩阵的尺寸 3 使用成员函数channels 获取矩阵的通道数 4 使
  • Unity中的GameObjectRecorder类录制动画

    Unity中的GameObjectRecorder类录制动画 记录 GameObjectRecorder 示例代码 解析 记录 首先是 参考及示例视频 Unity制作战神等级的表情动画 游戏 CG Vtuber适用 相关代码在8分16秒之后
  • 进程间通信(IPC)

    概述 进程间通信就是在不同进程之间传播或交换信息 那么不同进程之间存在着什么双方都可以访问的介质呢 进程的用户空间是互相独立的 一般而言是不能互相访问的 唯一的例外是共享内存区 另外 系统空间是 公共场所 各进程均可以访问 所以内核也可以提
  • 用javaScript编写lrc歌词解析器

    如果想要了解如何编写的请继续往下看 如果只需要代码 请点击这里Github lrc歌词文件介绍 来先看一下以下歌词 Heart To Heart lrc ti Heart To Heart ar James Blunt al Heart T