mirror of
https://github.com/FloatTech/ZeroBot-Plugin.git
synced 2025-12-20 06:20:08 +08:00
✨ 添加简易midi制作 (#257)
* ✨ 添加简易midi制作 * 🐛 修lint Co-authored-by: haibaraguo <haibaraguo@yeahka.com>
This commit is contained in:
parent
7f4c6eb4ac
commit
a92c584997
16
README.md
16
README.md
@ -1000,6 +1000,22 @@ print("run[CQ:image,file="+j["img"]+"]")
|
|||||||
|
|
||||||
- [x] 设置回复模式[青云客 | 小爱]
|
- [x] 设置回复模式[青云客 | 小爱]
|
||||||
|
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>简易midi音乐制作</summary>
|
||||||
|
|
||||||
|
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/midicreate"`
|
||||||
|
|
||||||
|
- [x] midi制作 CCGGAAGR FFEEDDCR GGFFEEDR GGFFEEDR CCGGAAGR FFEEDDCR
|
||||||
|
|
||||||
|
- [x] 个人听音练习
|
||||||
|
|
||||||
|
- [x] 团队听音练习
|
||||||
|
|
||||||
|
- [x] 注: 该插件需要安装timidity,安装脚本可参考https://gitcode.net/anto_july/midi/-/raw/master/timidity.sh
|
||||||
|
|
||||||
|
- [x] 符号说明: C5是中央C,后面不写数字,默认接5,Cb6<1,b代表降调,#代表升调,6比5高八度,<1代表音长×2,<2代表音长×4,<-1代表音长×0.5,<-2代表音长×0.25
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>TODO...</summary>
|
<summary>TODO...</summary>
|
||||||
|
|||||||
3
go.mod
3
go.mod
@ -21,12 +21,15 @@ require (
|
|||||||
github.com/jozsefsallai/gophersauce v1.0.1
|
github.com/jozsefsallai/gophersauce v1.0.1
|
||||||
github.com/lucas-clemente/quic-go v0.27.2
|
github.com/lucas-clemente/quic-go v0.27.2
|
||||||
github.com/mroth/weightedrand v0.4.1
|
github.com/mroth/weightedrand v0.4.1
|
||||||
|
github.com/pkg/errors v0.8.1
|
||||||
github.com/pkumza/numcn v1.0.0
|
github.com/pkumza/numcn v1.0.0
|
||||||
github.com/shirou/gopsutil/v3 v3.22.3
|
github.com/shirou/gopsutil/v3 v3.22.3
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/tidwall/gjson v1.14.1
|
github.com/tidwall/gjson v1.14.1
|
||||||
github.com/wcharczuk/go-chart/v2 v2.1.0
|
github.com/wcharczuk/go-chart/v2 v2.1.0
|
||||||
github.com/wdvxdr1123/ZeroBot v1.5.2-0.20220610070647-9eeffcb277ee
|
github.com/wdvxdr1123/ZeroBot v1.5.2-0.20220610070647-9eeffcb277ee
|
||||||
|
gitlab.com/gomidi/midi v1.23.7
|
||||||
|
gitlab.com/gomidi/midi/v2 v2.0.17
|
||||||
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd
|
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
5
go.sum
5
go.sum
@ -189,6 +189,7 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
|||||||
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
||||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkumza/numcn v1.0.0 h1:ZT5cf9IJkUZgRgEtCiNNykk0RwsrKXSTsvDHOwUTzgE=
|
github.com/pkumza/numcn v1.0.0 h1:ZT5cf9IJkUZgRgEtCiNNykk0RwsrKXSTsvDHOwUTzgE=
|
||||||
github.com/pkumza/numcn v1.0.0/go.mod h1:QSeH+al9dWCd8di5HZM/ZqHqhZmUKfph572e9Ev/ETc=
|
github.com/pkumza/numcn v1.0.0/go.mod h1:QSeH+al9dWCd8di5HZM/ZqHqhZmUKfph572e9Ev/ETc=
|
||||||
@ -258,6 +259,10 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
|||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
|
gitlab.com/gomidi/midi v1.23.7 h1:I6qKoIk9s9dcX+pNf0jC+tziCzJFn82bMpuntRkLeik=
|
||||||
|
gitlab.com/gomidi/midi v1.23.7/go.mod h1:3ohtNOhqoSakkuLG/Li1OI6I3J1c2LErnJF5o/VBq1c=
|
||||||
|
gitlab.com/gomidi/midi/v2 v2.0.17 h1:kf16wNwFFOskl0trvarOwMuZUQICdIGn37LP9QqIRuo=
|
||||||
|
gitlab.com/gomidi/midi/v2 v2.0.17/go.mod h1:quTyMKSQ4Klevxu6gY4gy2USbeZra0fV5SalndmPfsY=
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||||
|
|||||||
1
main.go
1
main.go
@ -87,6 +87,7 @@ import (
|
|||||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/jandan" // 煎蛋网无聊图
|
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/jandan" // 煎蛋网无聊图
|
||||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/juejuezi" // 绝绝子生成器
|
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/juejuezi" // 绝绝子生成器
|
||||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolicon" // lolicon 随机图片
|
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolicon" // lolicon 随机图片
|
||||||
|
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/midicreate" // 简易midi音乐制作
|
||||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyu" // 摸鱼
|
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyu" // 摸鱼
|
||||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyu_calendar" // 摸鱼人日历
|
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyu_calendar" // 摸鱼人日历
|
||||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/music" // 点歌
|
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/music" // 点歌
|
||||||
|
|||||||
393
plugin/midicreate/midicreate.go
Normal file
393
plugin/midicreate/midicreate.go
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
// Package midicreate 简易midi音乐制作
|
||||||
|
package midicreate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ctrl "github.com/FloatTech/zbpctrl"
|
||||||
|
"github.com/FloatTech/zbputils/control"
|
||||||
|
"github.com/FloatTech/zbputils/ctxext"
|
||||||
|
"github.com/FloatTech/zbputils/file"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
zero "github.com/wdvxdr1123/ZeroBot"
|
||||||
|
"github.com/wdvxdr1123/ZeroBot/message"
|
||||||
|
"gitlab.com/gomidi/midi/gm"
|
||||||
|
"gitlab.com/gomidi/midi/v2"
|
||||||
|
"gitlab.com/gomidi/midi/v2/smf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
engine := control.Register("midicreate", &ctrl.Options[*zero.Ctx]{
|
||||||
|
DisableOnDefault: false,
|
||||||
|
Help: "midi音乐制作,该插件需要安装timidity,安装脚本可参考https://gitcode.net/anto_july/midi/-/raw/master/timidity.sh\n" +
|
||||||
|
"- midi制作 CCGGAAGR FFEEDDCR GGFFEEDR GGFFEEDR CCGGAAGR FFEEDDCR\n" +
|
||||||
|
"- 个人听音练习\n" +
|
||||||
|
"- 团队听音练习",
|
||||||
|
PrivateDataFolder: "midicreate",
|
||||||
|
})
|
||||||
|
cachePath := engine.DataFolder() + "cache/"
|
||||||
|
_ = os.RemoveAll(cachePath)
|
||||||
|
err := os.MkdirAll(cachePath, 0755)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
engine.OnRegex(`^midi制作\s?(.{1,1000})$`).SetBlock(true).Limit(ctxext.LimitByUser).
|
||||||
|
Handle(func(ctx *zero.Ctx) {
|
||||||
|
uid := ctx.Event.UserID
|
||||||
|
input := ctx.State["regex_matched"].([]string)[1]
|
||||||
|
midiFile := cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
|
||||||
|
cmidiFile, err := str2music(input, midiFile)
|
||||||
|
if err != nil {
|
||||||
|
if file.IsExist(midiFile) {
|
||||||
|
ctx.UploadThisGroupFile(file.BOTPATH+"/"+midiFile, filepath.Base(midiFile), "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.SendChain(message.Text("ERROR:无法转换midi文件,", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + cmidiFile))
|
||||||
|
})
|
||||||
|
engine.OnRegex("^(个人|团队)听音练习$", zero.OnlyGroup).SetBlock(true).Limit(ctxext.LimitByUser).
|
||||||
|
Handle(func(ctx *zero.Ctx) {
|
||||||
|
uid := ctx.Event.UserID
|
||||||
|
ctx.SendChain(message.Text("欢迎来到听音练习, 一共有5个问题, 每个问题1分"))
|
||||||
|
var mode int
|
||||||
|
var next *zero.FutureEvent
|
||||||
|
var maxErrorCount int
|
||||||
|
if ctx.State["regex_matched"].([]string)[1] == "个人" {
|
||||||
|
mode = 0
|
||||||
|
next = zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^[A-G][b|#]?\d{0,2}$`),
|
||||||
|
zero.OnlyGroup, ctx.CheckSession())
|
||||||
|
maxErrorCount = 3
|
||||||
|
} else {
|
||||||
|
mode = 1
|
||||||
|
next = zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^[A-G][b|#]?\d{0,2}$`),
|
||||||
|
zero.OnlyGroup, zero.CheckGroup(ctx.Event.GroupID))
|
||||||
|
maxErrorCount = 10
|
||||||
|
}
|
||||||
|
recv, cancel := next.Repeat()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
score := make(map[int64]float64)
|
||||||
|
round := 1
|
||||||
|
maxRound := 6
|
||||||
|
errorCount := 0
|
||||||
|
target := uint8(55 + rand.Intn(34))
|
||||||
|
answer := name(target) + strconv.Itoa(int(target/12))
|
||||||
|
midiFile := cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
|
||||||
|
cmidiFile, err := str2music(answer, midiFile)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text("ERROR:听音练习结束, 无法转换midi文件, ", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + cmidiFile))
|
||||||
|
ctx.Send(
|
||||||
|
message.ReplyWithMessage(ctx.Event.MessageID,
|
||||||
|
message.Text("判断上面的音频, 输入音符, 例如C#6"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
tick := time.NewTimer(45 * time.Second)
|
||||||
|
after := time.NewTimer(60 * time.Second)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tick.C:
|
||||||
|
ctx.SendChain(message.Text("听音练习, 你还有15s作答时间"))
|
||||||
|
case <-after.C:
|
||||||
|
var text string
|
||||||
|
for k, v := range score {
|
||||||
|
text += fmt.Sprintf("%s: %.1f\n", ctx.CardOrNickName(k), v)
|
||||||
|
}
|
||||||
|
ctx.Send(
|
||||||
|
message.ReplyWithMessage(ctx.Event.MessageID,
|
||||||
|
message.Text("听音练习超时, 练习结束...答案是: ", answer, "\n所得分数如下:\n", text),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
case c := <-recv:
|
||||||
|
tick.Reset(45 * time.Second)
|
||||||
|
after.Reset(60 * time.Second)
|
||||||
|
n := processOne(c.Event.Message.String())
|
||||||
|
if n != target {
|
||||||
|
errorCount++
|
||||||
|
}
|
||||||
|
if errorCount == maxErrorCount || n == target {
|
||||||
|
if n == target {
|
||||||
|
ctx.Send(
|
||||||
|
message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("恭喜你回答正确, 答案是: ", answer),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else if errorCount == maxErrorCount {
|
||||||
|
ctx.Send(
|
||||||
|
message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("你的回答是: "),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
midiFile = cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
|
||||||
|
cmidiFile, err = str2music(c.Event.Message.String(), midiFile)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text("ERROR: can't convert midi file,", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + cmidiFile))
|
||||||
|
ctx.Send(
|
||||||
|
message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("回答错误, 答案是: ", answer, ", 错误次数已达3次, 进入下一关"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 统计分数
|
||||||
|
if mode == 0 {
|
||||||
|
switch errorCount {
|
||||||
|
case 0:
|
||||||
|
score[c.Event.UserID] += 1.0
|
||||||
|
case 1:
|
||||||
|
score[c.Event.UserID] += 0.5
|
||||||
|
case 2:
|
||||||
|
score[c.Event.UserID] += 0.2
|
||||||
|
}
|
||||||
|
} else if mode == 1 {
|
||||||
|
if errorCount != maxErrorCount {
|
||||||
|
score[c.Event.UserID] += 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 下一关
|
||||||
|
round++
|
||||||
|
if round != maxRound {
|
||||||
|
errorCount = 0
|
||||||
|
target = uint8(55 + rand.Intn(34))
|
||||||
|
answer = name(target) + strconv.Itoa(int(target/12))
|
||||||
|
midiFile = cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
|
||||||
|
cmidiFile, err = str2music(answer, midiFile)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text("ERROR:听音练习结束, 无法转换midi文件, ", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + cmidiFile))
|
||||||
|
ctx.Send(
|
||||||
|
message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("判断上面的音频, 输入音符, 例如C#6"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if n != target {
|
||||||
|
ctx.Send(
|
||||||
|
message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("你的回答是: "),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
midiFile = cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
|
||||||
|
cmidiFile, err = str2music(c.Event.Message.String(), midiFile)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text("ERROR: can't convert midi file,", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + cmidiFile))
|
||||||
|
ctx.Send(
|
||||||
|
message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("回答错误, 错误次数为", errorCount, ", 请继续回答"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if round == maxRound {
|
||||||
|
var text string
|
||||||
|
for k, v := range score {
|
||||||
|
text += fmt.Sprintf("%s: %.1f\n", ctx.CardOrNickName(k), v)
|
||||||
|
}
|
||||||
|
ctx.Send(
|
||||||
|
message.ReplyWithMessage(c.Event.MessageID,
|
||||||
|
message.Text("回答完毕, 所得分数如下:\n", text),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
noteMap = map[string]uint8{
|
||||||
|
"C": 60,
|
||||||
|
"Db": 61,
|
||||||
|
"D": 62,
|
||||||
|
"Eb": 63,
|
||||||
|
"E": 64,
|
||||||
|
"F": 65,
|
||||||
|
"Gb": 66,
|
||||||
|
"G": 67,
|
||||||
|
"Ab": 68,
|
||||||
|
"A": 69,
|
||||||
|
"Bb": 70,
|
||||||
|
"B": 71,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func str2music(input, midiFile string) (cmidiFile string, err error) {
|
||||||
|
err = mkMidi(midiFile, input)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmidiFile = strings.ReplaceAll(midiFile, ".mid", ".wav")
|
||||||
|
cmd := exec.Command("timidity", file.BOTPATH+"/"+midiFile, "-Ow", "-o", file.BOTPATH+"/"+cmidiFile)
|
||||||
|
err = cmd.Run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkMidi(filePath, input string) error {
|
||||||
|
if file.IsExist(filePath) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
bf bytes.Buffer
|
||||||
|
clock = smf.MetricTicks(96)
|
||||||
|
tr smf.Track
|
||||||
|
)
|
||||||
|
|
||||||
|
tr.Add(0, smf.MetaMeter(4, 4))
|
||||||
|
tr.Add(0, smf.MetaTempo(60))
|
||||||
|
tr.Add(0, smf.MetaInstrument("Violin"))
|
||||||
|
tr.Add(0, midi.ProgramChange(0, gm.Instr_Violin.Value()))
|
||||||
|
|
||||||
|
k := strings.ReplaceAll(input, " ", "")
|
||||||
|
|
||||||
|
var (
|
||||||
|
base uint8
|
||||||
|
level uint8
|
||||||
|
delay uint32
|
||||||
|
sleepFlag bool
|
||||||
|
lengthBytes = make([]byte, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < len(k); {
|
||||||
|
base = 0
|
||||||
|
level = 0
|
||||||
|
sleepFlag = false
|
||||||
|
lengthBytes = lengthBytes[:0]
|
||||||
|
for {
|
||||||
|
switch {
|
||||||
|
case k[i] == 'R':
|
||||||
|
sleepFlag = true
|
||||||
|
i++
|
||||||
|
case k[i] >= 'A' && k[i] <= 'G':
|
||||||
|
base = noteMap[k[i:i+1]] % 12
|
||||||
|
i++
|
||||||
|
case k[i] == 'b':
|
||||||
|
base--
|
||||||
|
i++
|
||||||
|
case k[i] == '#':
|
||||||
|
base++
|
||||||
|
i++
|
||||||
|
case k[i] >= '0' && k[i] <= '9':
|
||||||
|
level = level*10 + k[i] - '0'
|
||||||
|
i++
|
||||||
|
case k[i] == '<':
|
||||||
|
i++
|
||||||
|
for i < len(k) && (k[i] == '-' || (k[i] >= '0' && k[i] <= '9')) {
|
||||||
|
lengthBytes = append(lengthBytes, k[i])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.Errorf("无法解析第%d个位置的%c字符", i, k[i])
|
||||||
|
}
|
||||||
|
if i >= len(k) || (k[i] >= 'A' && k[i] <= 'G') || k[i] == 'R' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
length, _ := strconv.Atoi(string(lengthBytes))
|
||||||
|
if sleepFlag {
|
||||||
|
if length >= 0 {
|
||||||
|
delay = clock.Ticks4th() * (1 << length)
|
||||||
|
} else {
|
||||||
|
delay = clock.Ticks4th() / (1 << -length)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if level == 0 {
|
||||||
|
level = 5
|
||||||
|
}
|
||||||
|
tr.Add(delay, midi.NoteOn(0, o(base, level), 120))
|
||||||
|
if length >= 0 {
|
||||||
|
tr.Add(clock.Ticks4th()*(1<<length), midi.NoteOff(0, o(base, level)))
|
||||||
|
} else {
|
||||||
|
tr.Add(clock.Ticks4th()/(1<<-length), midi.NoteOff(0, o(base, level)))
|
||||||
|
}
|
||||||
|
delay = 0
|
||||||
|
}
|
||||||
|
tr.Close(0)
|
||||||
|
|
||||||
|
s := smf.New()
|
||||||
|
s.TimeFormat = clock
|
||||||
|
err := s.Add(tr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = s.WriteTo(&bf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(filePath, bf.Bytes(), 0666)
|
||||||
|
}
|
||||||
|
|
||||||
|
func o(base uint8, oct uint8) uint8 {
|
||||||
|
if oct > 10 {
|
||||||
|
oct = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
if oct == 0 {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
res := base + 12*oct
|
||||||
|
if res > 127 {
|
||||||
|
res -= 12
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func name(n uint8) string {
|
||||||
|
for k, v := range noteMap {
|
||||||
|
if v%12 == n%12 {
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func processOne(note string) uint8 {
|
||||||
|
k := strings.ReplaceAll(note, " ", "")
|
||||||
|
var (
|
||||||
|
base uint8
|
||||||
|
level uint8
|
||||||
|
)
|
||||||
|
for i := 0; i < len(k); i++ {
|
||||||
|
switch {
|
||||||
|
case k[i] >= 'A' && k[i] <= 'G':
|
||||||
|
base = noteMap[k[i:i+1]] % 12
|
||||||
|
case k[i] == 'b':
|
||||||
|
base--
|
||||||
|
case k[i] == '#':
|
||||||
|
base++
|
||||||
|
case k[i] >= '0' && k[i] <= '9':
|
||||||
|
level = level*10 + k[i] - '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if level == 0 {
|
||||||
|
level = 5
|
||||||
|
}
|
||||||
|
return o(base, level)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user