Skip to content

Commit 0dd447e

Browse files
guohuiyuanhaibaraguo
andauthored
✨ 添加txt转midi,优化midi转txt (#267)
* 🐛 长字符串发文件 * ✨ 添加txt转midi * ✨ 音色自定义 * ✨ 添加windows脚本说明 * 🐛 修复某些midi读取不了的问题 * ✨ 多音轨转换 Co-authored-by: haibaraguo <haibaraguo@yeahka.com>
1 parent 92c913e commit 0dd447e

File tree

4 files changed

+126
-51
lines changed

4 files changed

+126
-51
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -650,9 +650,13 @@ print("run[CQ:image,file="+j["img"]+"]")
650650

651651
- [x] 团队听音练习
652652

653-
- [x] *.mid (解析上传的mid文件)
653+
- [x] *.mid (midi 转 txt)
654654

655-
- [x] 注: 该插件需要安装timidity,安装脚本可参考https://gitcode.net/anto_july/midi/-/raw/master/timidity.sh
655+
- [x] midi制作*.txt (txt 转 midi)
656+
657+
- [x] 设置音色40 (0~127)
658+
659+
- [x] 注: 该插件需要安装timidity,linux安装脚本可参考https://gitcode.net/anto_july/midi/-/raw/master/timidity.sh,,windows安装脚本可参考https://gitcode.net/anto_july/midi/-/raw/master/timidity.bat,windows需要管理员模式运行
656660

657661
- [x] 符号说明: C5是中央C,后面不写数字,默认接5,Cb6<1,b代表降调,#代表升调,6比5高八度,<1代表音长×2,<3代表音长×8,<-1代表音长×0.5,<-3代表音长×0.125,R是休止符
658662

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ require (
2828
github.com/tidwall/gjson v1.14.1
2929
github.com/wcharczuk/go-chart/v2 v2.1.0
3030
github.com/wdvxdr1123/ZeroBot v1.5.2-0.20220610070647-9eeffcb277ee
31-
gitlab.com/gomidi/midi v1.23.7
3231
gitlab.com/gomidi/midi/v2 v2.0.17
3332
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd
3433
)

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,6 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
259259
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
260260
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
261261
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
262-
gitlab.com/gomidi/midi v1.23.7 h1:I6qKoIk9s9dcX+pNf0jC+tziCzJFn82bMpuntRkLeik=
263-
gitlab.com/gomidi/midi v1.23.7/go.mod h1:3ohtNOhqoSakkuLG/Li1OI6I3J1c2LErnJF5o/VBq1c=
264262
gitlab.com/gomidi/midi/v2 v2.0.17 h1:kf16wNwFFOskl0trvarOwMuZUQICdIGn37LP9QqIRuo=
265263
gitlab.com/gomidi/midi/v2 v2.0.17/go.mod h1:quTyMKSQ4Klevxu6gY4gy2USbeZra0fV5SalndmPfsY=
266264
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=

plugin/midicreate/midicreate.go

Lines changed: 120 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,28 @@ import (
1515
"time"
1616

1717
ctrl "github.com/FloatTech/zbpctrl"
18+
"github.com/FloatTech/zbputils/binary"
1819
"github.com/FloatTech/zbputils/control"
1920
"github.com/FloatTech/zbputils/ctxext"
2021
"github.com/FloatTech/zbputils/file"
2122
"github.com/FloatTech/zbputils/web"
2223
"github.com/pkg/errors"
2324
zero "github.com/wdvxdr1123/ZeroBot"
2425
"github.com/wdvxdr1123/ZeroBot/message"
25-
"gitlab.com/gomidi/midi/gm"
2626
"gitlab.com/gomidi/midi/v2"
2727
"gitlab.com/gomidi/midi/v2/smf"
2828
)
2929

3030
func init() {
3131
engine := control.Register("midicreate", &ctrl.Options[*zero.Ctx]{
3232
DisableOnDefault: false,
33-
Help: "midi音乐制作,该插件需要安装timidity,安装脚本可参考https://gitcode.net/anto_july/midi/-/raw/master/timidity.sh\n" +
33+
Help: "midi音乐制作,该插件需要安装timidity,linux安装脚本可参考https://gitcode.net/anto_july/midi/-/raw/master/timidity.sh,windows安装脚本可参考https://gitcode.net/anto_july/midi/-/raw/master/timidity.bat,windows需要管理员模式运行\n" +
3434
"- midi制作 CCGGAAGR FFEEDDCR GGFFEEDR GGFFEEDR CCGGAAGR FFEEDDCR\n" +
3535
"- 个人听音练习\n" +
3636
"- 团队听音练习\n" +
37-
"- *.mid (解析上传的mid文件)",
37+
"- *.mid (midi 转 txt)\n" +
38+
"- midi制作*.txt (txt 转 midi)\n" +
39+
"- 设置音色40 (0~127)",
3840
PrivateDataFolder: "midicreate",
3941
})
4042
cachePath := engine.DataFolder() + "cache/"
@@ -48,7 +50,7 @@ func init() {
4850
uid := ctx.Event.UserID
4951
input := ctx.State["args"].(string)
5052
midiFile := cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
51-
cmidiFile, err := str2music(input, midiFile)
53+
cmidiFile, err := str2music(ctx, input, midiFile)
5254
if err != nil {
5355
if file.IsExist(midiFile) {
5456
ctx.UploadThisGroupFile(file.BOTPATH+"/"+midiFile, filepath.Base(midiFile), "")
@@ -87,7 +89,7 @@ func init() {
8789
target := uint8(55 + rand.Intn(34))
8890
answer := name(target) + strconv.Itoa(int(target/12))
8991
midiFile := cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
90-
cmidiFile, err := str2music(answer, midiFile)
92+
cmidiFile, err := str2music(ctx, answer, midiFile)
9193
if err != nil {
9294
ctx.SendChain(message.Text("ERROR:听音练习结束, 无法转换midi文件, ", err))
9395
return
@@ -137,7 +139,7 @@ func init() {
137139
),
138140
)
139141
midiFile = cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
140-
cmidiFile, err = str2music(c.Event.Message.String(), midiFile)
142+
cmidiFile, err = str2music(ctx, c.Event.Message.String(), midiFile)
141143
if err != nil {
142144
ctx.SendChain(message.Text("ERROR: can't convert midi file,", err))
143145
return
@@ -172,7 +174,7 @@ func init() {
172174
target = uint8(55 + rand.Intn(34))
173175
answer = name(target) + strconv.Itoa(int(target/12))
174176
midiFile = cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
175-
cmidiFile, err = str2music(answer, midiFile)
177+
cmidiFile, err = str2music(ctx, answer, midiFile)
176178
if err != nil {
177179
ctx.SendChain(message.Text("ERROR:听音练习结束, 无法转换midi文件, ", err))
178180
return
@@ -193,7 +195,7 @@ func init() {
193195
)
194196
time.Sleep(time.Millisecond * 500)
195197
midiFile = cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
196-
cmidiFile, err = str2music(c.Event.Message.String(), midiFile)
198+
cmidiFile, err = str2music(ctx, c.Event.Message.String(), midiFile)
197199
if err != nil {
198200
ctx.SendChain(message.Text("ERROR: can't convert midi file,", err))
199201
return
@@ -231,8 +233,54 @@ func init() {
231233
ctx.SendChain(message.Text("ERROR:", err))
232234
return
233235
}
234-
midStr := mid2txt(data)
235-
ctx.SendChain(message.Text("文件名:", ctx.Event.File.Name, "\n转化的midi字符:", midStr))
236+
s, err := smf.ReadFrom(bytes.NewReader(data))
237+
if err != nil {
238+
ctx.SendChain(message.Text("ERROR:", err))
239+
return
240+
}
241+
for i := 0; i < int(s.NumTracks()); i++ {
242+
midStr := mid2txt(data, i)
243+
if err != nil {
244+
ctx.SendChain(message.Text("ERROR:", err))
245+
return
246+
}
247+
fileName := strings.ReplaceAll(cachePath+"/"+ctx.Event.File.Name, ".mid", fmt.Sprintf("-%d.txt", i))
248+
_ = os.WriteFile(fileName, binary.StringToBytes(midStr), 0666)
249+
ctx.UploadThisGroupFile(file.BOTPATH+"/"+fileName, filepath.Base(fileName), "")
250+
}
251+
})
252+
engine.On("notice/group_upload", func(ctx *zero.Ctx) bool {
253+
return path.Ext(ctx.Event.File.Name) == ".txt" && strings.Contains(ctx.Event.File.Name, "midi制作")
254+
}).SetBlock(false).Limit(ctxext.LimitByGroup).
255+
Handle(func(ctx *zero.Ctx) {
256+
fileURL := ctx.GetThisGroupFileUrl(ctx.Event.File.BusID, ctx.Event.File.ID)
257+
data, err := web.GetData(fileURL)
258+
if err != nil {
259+
ctx.SendChain(message.Text("ERROR:", err))
260+
return
261+
}
262+
uid := ctx.Event.UserID
263+
midiFile := cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
264+
cmidiFile, err := str2music(ctx, binary.BytesToString(data), midiFile)
265+
if err != nil {
266+
ctx.SendChain(message.Text("ERROR:无法转换midi文件,", err))
267+
return
268+
}
269+
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + cmidiFile))
270+
})
271+
engine.OnPrefix("设置音色").SetBlock(true).
272+
Handle(func(ctx *zero.Ctx) {
273+
param := ctx.State["args"].(string)
274+
timbre, err := strconv.Atoi(param)
275+
if err != nil {
276+
ctx.SendChain(message.Text("ERROR:", err))
277+
}
278+
err = setTimbreMode(ctx, int64(timbre))
279+
if err != nil {
280+
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(err))
281+
return
282+
}
283+
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("成功"))
236284
})
237285
}
238286

@@ -253,8 +301,8 @@ var (
253301
}
254302
)
255303

256-
func str2music(input, midiFile string) (cmidiFile string, err error) {
257-
err = mkMidi(midiFile, input)
304+
func str2music(ctx *zero.Ctx, input, midiFile string) (cmidiFile string, err error) {
305+
err = mkMidi(ctx, midiFile, input)
258306
if err != nil {
259307
return
260308
}
@@ -264,7 +312,7 @@ func str2music(input, midiFile string) (cmidiFile string, err error) {
264312
return
265313
}
266314

267-
func mkMidi(filePath, input string) error {
315+
func mkMidi(ctx *zero.Ctx, filePath, input string) error {
268316
if file.IsExist(filePath) {
269317
return nil
270318
}
@@ -276,7 +324,8 @@ func mkMidi(filePath, input string) error {
276324
tr.Add(0, smf.MetaMeter(4, 4))
277325
tr.Add(0, smf.MetaTempo(72))
278326
tr.Add(0, smf.MetaInstrument("Violin"))
279-
tr.Add(0, midi.ProgramChange(0, gm.Instr_Violin.Value()))
327+
timbre := getTimbreMode(ctx)
328+
tr.Add(0, midi.ProgramChange(0, uint8(timbre)))
280329

281330
k := strings.ReplaceAll(input, " ", "")
282331

@@ -410,46 +459,43 @@ func processOne(note string) uint8 {
410459
return o(base, level)
411460
}
412461

413-
func mid2txt(midBytes []byte) (midStr string) {
462+
func mid2txt(midBytes []byte, trackNo int) (midStr string) {
414463
var (
415-
absTicksStart float64
416-
absTicksEnd float64
417-
startNote byte
418-
endNote byte
419-
defaultMetric = 960.0
420-
defaultTrackNo = 0
464+
absTicksStart float64
465+
absTicksEnd float64
466+
startNote byte
467+
endNote byte
468+
defaultMetric = 960.0
421469
)
422-
_ = smf.ReadTracksFrom(bytes.NewReader(midBytes)).
470+
_ = smf.ReadTracksFrom(bytes.NewReader(midBytes), trackNo).
423471
Do(
424472
func(te smf.TrackEvent) {
425-
if !te.Message.IsMeta() && te.TrackNo == defaultTrackNo {
473+
if !te.Message.IsMeta() {
426474
b := te.Message.Bytes()
427-
if len(b) == 3 {
428-
if b[0] == 0x90 && b[2] > 0 {
429-
absTicksStart = float64(te.AbsTicks)
430-
startNote = b[1]
431-
}
432-
if b[0] == 0x80 || (b[0] == 0x90 && b[2] == 0x00) {
433-
absTicksEnd = float64(te.AbsTicks)
434-
endNote = b[1]
435-
}
475+
if te.Message.Is(midi.NoteOnMsg) && b[2] > 0 {
476+
absTicksStart = float64(te.AbsTicks)
477+
startNote = b[1]
436478
}
437-
if (b[0] == 0x80 || (b[0] == 0x90 && b[2] == 0x00)) && startNote == endNote {
438-
sign := name(b[1])
439-
level := b[1] / 12
440-
length := (absTicksEnd - absTicksStart) / defaultMetric
441-
midStr += sign
442-
if level != 5 {
443-
midStr += strconv.Itoa(int(level))
444-
}
445-
pow := int(math.Round(math.Log2(length)))
446-
if pow >= -4 && pow != 0 {
447-
midStr += "<" + strconv.Itoa(pow)
479+
if te.Message.Is(midi.NoteOffMsg) || (te.Message.Is(midi.NoteOnMsg) && b[2] == 0x00) {
480+
absTicksEnd = float64(te.AbsTicks)
481+
endNote = b[1]
482+
if startNote == endNote {
483+
sign := name(b[1])
484+
level := b[1] / 12
485+
length := (absTicksEnd - absTicksStart) / defaultMetric
486+
midStr += sign
487+
if level != 5 {
488+
midStr += strconv.Itoa(int(level))
489+
}
490+
pow := int(math.Round(math.Log2(length)))
491+
if pow >= -4 && pow != 0 {
492+
midStr += "<" + strconv.Itoa(pow)
493+
}
494+
startNote = 0
495+
endNote = 0
448496
}
449-
startNote = 0
450-
endNote = 0
451497
}
452-
if (b[0] == 0x90 && b[2] > 0) && absTicksStart > absTicksEnd {
498+
if (te.Message.Is(midi.NoteOnMsg) && b[2] > 0) && absTicksStart > absTicksEnd {
453499
length := (absTicksStart - absTicksEnd) / defaultMetric
454500
pow := int(math.Round(math.Log2(length)))
455501
if pow == 0 {
@@ -463,3 +509,31 @@ func mid2txt(midBytes []byte) (midStr string) {
463509
)
464510
return
465511
}
512+
513+
func setTimbreMode(ctx *zero.Ctx, timbre int64) error {
514+
gid := ctx.Event.GroupID
515+
if gid == 0 {
516+
gid = -ctx.Event.UserID
517+
}
518+
if timbre < 0 || timbre > 127 {
519+
return errors.New("音色应该在0~127之间")
520+
}
521+
m, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
522+
if !ok {
523+
return errors.New("no such plugin")
524+
}
525+
return m.SetData(gid, timbre)
526+
}
527+
528+
func getTimbreMode(ctx *zero.Ctx) (index int64) {
529+
gid := ctx.Event.GroupID
530+
if gid == 0 {
531+
gid = -ctx.Event.UserID
532+
}
533+
m, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
534+
if ok {
535+
index := m.GetData(gid)
536+
return index
537+
}
538+
return 40
539+
}

0 commit comments

Comments
 (0)