mirror of
https://github.com/FlourishingWorld/hk4e.git
synced 2026-02-04 14:22:26 +08:00
348 lines
8.2 KiB
Go
348 lines
8.2 KiB
Go
package game
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"image/jpeg"
|
|
"math"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"time"
|
|
|
|
"hk4e/pkg/logger"
|
|
|
|
"hk4e/gs/model"
|
|
"hk4e/protocol/proto"
|
|
|
|
"github.com/pkg/errors"
|
|
"gitlab.com/gomidi/midi/v2"
|
|
"gitlab.com/gomidi/midi/v2/smf"
|
|
)
|
|
|
|
const (
|
|
KeyOffset = -12 * 1 // 八度修正偏移
|
|
)
|
|
|
|
var AUDIO_CHAN chan uint32
|
|
|
|
func init() {
|
|
AUDIO_CHAN = make(chan uint32, 1000)
|
|
}
|
|
|
|
func PlayAudio() {
|
|
audio, err := smf.ReadFile("./audio.mid")
|
|
if err != nil {
|
|
logger.Error("read midi file error: %v", err)
|
|
return
|
|
}
|
|
tempoChangeList := audio.TempoChanges()
|
|
if len(tempoChangeList) != 1 {
|
|
logger.Error("midi file format not support")
|
|
return
|
|
}
|
|
tempoChange := tempoChangeList[0]
|
|
metricTicks := audio.TimeFormat.(smf.MetricTicks)
|
|
tickTime := ((60000000.0 / tempoChange.BPM) / float64(metricTicks.Resolution())) / 1000.0
|
|
logger.Debug("start play audio")
|
|
for _, track := range audio.Tracks {
|
|
// 全部轨道
|
|
totalTick := uint64(0)
|
|
for _, event := range track {
|
|
// 单个轨道
|
|
delay := uint32(float64(event.Delta) * tickTime)
|
|
// busyPollWaitMilliSecond(delay)
|
|
interruptWaitMilliSecond(delay)
|
|
totalTick += uint64(delay)
|
|
|
|
msg := event.Message
|
|
if msg.Type() != midi.NoteOnMsg {
|
|
continue
|
|
}
|
|
midiMsg := midi.Message(msg)
|
|
var channel, key, velocity uint8
|
|
midiMsg.GetNoteOn(&channel, &key, &velocity)
|
|
// TODO 测试一下客户端是否支持更宽的音域
|
|
// 60 -> 中央C C4
|
|
// if key < 36 || key > 71 {
|
|
// continue
|
|
// }
|
|
note := int32(key) + int32(KeyOffset)
|
|
if note < 21 || note > 108 {
|
|
// 非88键钢琴音域
|
|
continue
|
|
}
|
|
if velocity == 0 {
|
|
// 可能是NoteOffMsg
|
|
continue
|
|
}
|
|
|
|
AUDIO_CHAN <- uint32(note)
|
|
// logger.Debug("send midi note: %v, delay: %v, totalTick: %v", note, delay, totalTick)
|
|
}
|
|
}
|
|
}
|
|
|
|
func interruptWaitMilliSecond(delay uint32) {
|
|
time.Sleep(time.Millisecond * time.Duration(delay))
|
|
}
|
|
|
|
func busyPollWaitMilliSecond(delay uint32) {
|
|
start := time.Now()
|
|
end := start.Add(time.Millisecond * time.Duration(delay))
|
|
for {
|
|
now := time.Now()
|
|
if now.After(end) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
const (
|
|
SCREEN_WIDTH = 80
|
|
SCREEN_HEIGHT = 80
|
|
SCREEN_DPI = 0.5
|
|
)
|
|
const GADGET_ID = 70590015
|
|
|
|
var BASE_POS = &model.Vector{
|
|
X: 2700,
|
|
Y: 200,
|
|
Z: -1800,
|
|
}
|
|
var SCREEN_ENTITY_ID_LIST []uint32
|
|
var FRAME_COLOR [][]int
|
|
var FRAME [][]bool
|
|
|
|
const (
|
|
GADGET_RED = 70590016
|
|
GADGET_GREEN = 70590019
|
|
GADGET_BLUE = 70590017
|
|
GADGET_CYAN = 70590014
|
|
GADGET_YELLOW = 70590015
|
|
GADGET_CYAN_BLUE = 70590018
|
|
GADGET_PURPLE = 70590020
|
|
)
|
|
const (
|
|
RED_RGB = "C3764F"
|
|
GREEN_RGB = "559F30"
|
|
BLUE_RGB = "6293EA"
|
|
CYAN_RGB = "479094"
|
|
YELLOW_RGB = "DBB643"
|
|
CYAN_BLUE_RGB = "2B89C9"
|
|
PURPLE_RGB = "6E5BC5"
|
|
)
|
|
|
|
var COLOR_GADGET_MAP = map[string]int{
|
|
RED_RGB: GADGET_RED,
|
|
GREEN_RGB: GADGET_GREEN,
|
|
BLUE_RGB: GADGET_BLUE,
|
|
CYAN_RGB: GADGET_CYAN,
|
|
YELLOW_RGB: GADGET_YELLOW,
|
|
CYAN_BLUE_RGB: GADGET_CYAN_BLUE,
|
|
PURPLE_RGB: GADGET_PURPLE,
|
|
}
|
|
var ALL_COLOR = []string{RED_RGB, GREEN_RGB, BLUE_RGB, CYAN_RGB, YELLOW_RGB, CYAN_BLUE_RGB, PURPLE_RGB}
|
|
|
|
type ColorLight struct {
|
|
Color string
|
|
Light uint8
|
|
}
|
|
|
|
type COLOR_LIGHT_LIST_SORT []*ColorLight
|
|
|
|
var COLOR_LIGHT_LIST COLOR_LIGHT_LIST_SORT
|
|
|
|
func (s COLOR_LIGHT_LIST_SORT) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
func (s COLOR_LIGHT_LIST_SORT) Less(i, j int) bool {
|
|
return s[i].Light < s[j].Light
|
|
}
|
|
|
|
func (s COLOR_LIGHT_LIST_SORT) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
|
|
func init() {
|
|
CalcColorLight()
|
|
}
|
|
|
|
func CalcColorLight() {
|
|
COLOR_LIGHT_LIST = make(COLOR_LIGHT_LIST_SORT, 0)
|
|
for _, c := range ALL_COLOR {
|
|
r, g, b := GetColorRGB(c)
|
|
gray := float32(r)*0.299 + float32(g)*0.587 + float32(b)*0.114
|
|
COLOR_LIGHT_LIST = append(COLOR_LIGHT_LIST, &ColorLight{
|
|
Color: c,
|
|
Light: uint8(gray),
|
|
})
|
|
}
|
|
sort.Stable(COLOR_LIGHT_LIST)
|
|
total := len(COLOR_LIGHT_LIST)
|
|
div := 255.0 / float32(total)
|
|
for index, colorLight := range COLOR_LIGHT_LIST {
|
|
colorLight.Light = uint8(div * float32(index+1))
|
|
}
|
|
}
|
|
|
|
func GetColorRGB(c string) (r, g, b uint8) {
|
|
if len(c) != 6 {
|
|
return 0, 0, 0
|
|
}
|
|
rr, err := strconv.ParseUint(c[0:2], 16, 8)
|
|
if err != nil {
|
|
return 0, 0, 0
|
|
}
|
|
r = uint8(rr)
|
|
gg, err := strconv.ParseUint(c[2:4], 16, 8)
|
|
if err != nil {
|
|
return 0, 0, 0
|
|
}
|
|
g = uint8(gg)
|
|
bb, err := strconv.ParseUint(c[4:6], 16, 8)
|
|
if err != nil {
|
|
return 0, 0, 0
|
|
}
|
|
b = uint8(bb)
|
|
return r, g, b
|
|
}
|
|
|
|
func ReadJpgFile(fileName string) image.Image {
|
|
file, err := os.Open(fileName)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
defer func() {
|
|
_ = file.Close()
|
|
}()
|
|
img, err := jpeg.Decode(file)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return img
|
|
}
|
|
|
|
func WriteJpgFile(fileName string, jpg image.Image) {
|
|
file, err := os.Create(fileName)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
_ = file.Close()
|
|
}()
|
|
err = jpeg.Encode(file, jpg, &jpeg.Options{
|
|
Quality: 100,
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
func LoadFrameFile() error {
|
|
frameImg := ReadJpgFile("./frame.jpg")
|
|
if frameImg == nil {
|
|
return errors.New("file not exist")
|
|
}
|
|
FRAME = make([][]bool, SCREEN_WIDTH)
|
|
for w := 0; w < SCREEN_WIDTH; w++ {
|
|
FRAME[w] = make([]bool, SCREEN_HEIGHT)
|
|
}
|
|
FRAME_COLOR = make([][]int, SCREEN_WIDTH)
|
|
for w := 0; w < SCREEN_WIDTH; w++ {
|
|
FRAME_COLOR[w] = make([]int, SCREEN_HEIGHT)
|
|
}
|
|
grayAvg := uint64(0)
|
|
grayImg := image.NewRGBA(image.Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT))
|
|
for w := 0; w < SCREEN_WIDTH; w++ {
|
|
for h := 0; h < SCREEN_HEIGHT; h++ {
|
|
pix := frameImg.At(w, h)
|
|
r, g, b, _ := pix.RGBA()
|
|
gray := float32(r>>8)*0.299 + float32(g>>8)*0.587 + float32(b>>8)*0.114
|
|
grayImg.SetRGBA(w, h, color.RGBA{R: uint8(gray), G: uint8(gray), B: uint8(gray), A: 255})
|
|
grayAvg += uint64(gray)
|
|
}
|
|
}
|
|
WriteJpgFile("./frame_gray.jpg", grayImg)
|
|
grayAvg /= SCREEN_WIDTH * SCREEN_HEIGHT
|
|
rgbImg := image.NewRGBA(image.Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT))
|
|
binImg := image.NewRGBA(image.Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT))
|
|
for w := 0; w < SCREEN_WIDTH; w++ {
|
|
for h := 0; h < SCREEN_HEIGHT; h++ {
|
|
pix := frameImg.At(w, h)
|
|
r, g, b, _ := pix.RGBA()
|
|
gray := float32(r>>8)*0.299 + float32(g>>8)*0.587 + float32(b>>8)*0.114
|
|
c := ""
|
|
for _, colorLight := range COLOR_LIGHT_LIST {
|
|
if float32(colorLight.Light) > gray {
|
|
c = colorLight.Color
|
|
break
|
|
}
|
|
}
|
|
if c == "" {
|
|
c = COLOR_LIGHT_LIST[len(COLOR_LIGHT_LIST)-1].Color
|
|
}
|
|
rr, gg, bb := GetColorRGB(c)
|
|
rgbImg.SetRGBA(w, h, color.RGBA{R: rr, G: gg, B: bb, A: 255})
|
|
FRAME_COLOR[w][h] = COLOR_GADGET_MAP[c]
|
|
if gray > float32(grayAvg) {
|
|
FRAME[w][h] = true
|
|
binImg.SetRGBA(w, h, color.RGBA{R: 255, G: 255, B: 255, A: 255})
|
|
}
|
|
}
|
|
}
|
|
WriteJpgFile("./frame_rgb.jpg", rgbImg)
|
|
WriteJpgFile("./frame_bin.jpg", binImg)
|
|
return nil
|
|
}
|
|
|
|
var OBJECT_ID_COUNTER uint64 = math.MaxUint64
|
|
|
|
func UpdateFrame(rgb bool) {
|
|
err := LoadFrameFile()
|
|
if err != nil {
|
|
return
|
|
}
|
|
world := WORLD_MANAGER.GetAiWorld()
|
|
scene := world.GetSceneById(3)
|
|
if scene == nil {
|
|
logger.Error("scene is nil, sceneId: %v", 3)
|
|
return
|
|
}
|
|
for _, v := range SCREEN_ENTITY_ID_LIST {
|
|
scene.DestroyEntity(v)
|
|
}
|
|
GAME_MANAGER.RemoveSceneEntityNotifyBroadcast(scene, proto.VisionType_VISION_REMOVE, SCREEN_ENTITY_ID_LIST)
|
|
SCREEN_ENTITY_ID_LIST = make([]uint32, 0)
|
|
leftTopPos := &model.Vector{
|
|
X: BASE_POS.X + float64(SCREEN_WIDTH)*SCREEN_DPI/2,
|
|
Y: BASE_POS.Y + float64(SCREEN_HEIGHT)*SCREEN_DPI,
|
|
Z: BASE_POS.Z,
|
|
}
|
|
for w := 0; w < SCREEN_WIDTH; w++ {
|
|
for h := 0; h < SCREEN_HEIGHT; h++ {
|
|
// 创建像素点
|
|
OBJECT_ID_COUNTER++
|
|
if rgb {
|
|
entityId := scene.CreateEntityGadgetNormal(&model.Vector{
|
|
X: leftTopPos.X - float64(w)*SCREEN_DPI,
|
|
Y: leftTopPos.Y - float64(h)*SCREEN_DPI,
|
|
Z: leftTopPos.Z,
|
|
}, new(model.Vector), uint32(FRAME_COLOR[w][h]), 271003, OBJECT_ID_COUNTER)
|
|
SCREEN_ENTITY_ID_LIST = append(SCREEN_ENTITY_ID_LIST, entityId)
|
|
} else {
|
|
if !FRAME[w][h] {
|
|
entityId := scene.CreateEntityGadgetNormal(&model.Vector{
|
|
X: leftTopPos.X - float64(w)*SCREEN_DPI,
|
|
Y: leftTopPos.Y - float64(h)*SCREEN_DPI,
|
|
Z: leftTopPos.Z,
|
|
}, new(model.Vector), uint32(GADGET_ID), 271003, OBJECT_ID_COUNTER)
|
|
SCREEN_ENTITY_ID_LIST = append(SCREEN_ENTITY_ID_LIST, entityId)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
GAME_MANAGER.AddSceneEntityNotify(world.GetOwner(), proto.VisionType_VISION_BORN, SCREEN_ENTITY_ID_LIST, true, false)
|
|
}
|