From bcaf3d9b582625873d622f5c88856e94ea77b313 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Sat, 21 Mar 2020 23:45:51 +0000 Subject: [PATCH] Get view-menu to play the interface sound --- README.md | 49 +++++++++++++------ cmd/view-menu/main.go | 18 ++++++- go.mod | 4 +- go.sum | 9 ++++ internal/assetstore/assetstore.go | 8 ++-- internal/assetstore/sound.go | 78 +++++++++++++++++++++++++++++++ scripts/convert-wav | 6 +++ 7 files changed, 151 insertions(+), 21 deletions(-) create mode 100644 internal/assetstore/sound.go create mode 100755 scripts/convert-wav diff --git a/README.md b/README.md index ae102ae..dfbf13c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ installed on your system: ``` $ go version -go version go1.13 linux/amd64 +go version go1.14 linux/amd64 ``` In addition, you'll also need the following packages installed, at least in @@ -27,13 +27,11 @@ Debian: ``` # apt install libx11-dev libxcursor-dev mesa-common-dev libxrandr-dev \ - libxinerama-dev libgl1-mesa-dev libxi-dev mpv + libxinerama-dev libgl1-mesa-dev libxi-dev libasound2-dev mpv ffmpeg ``` You can then run `make all` in the source tree to get the binaries that are -present at hte moment. - -They're not very interesting :D. +present at the moment. Place your WH40K: Chaos Gate installation in `./orig` to benefit from automatic path defaults. Otherwise, point to it with `-game-path` @@ -42,14 +40,13 @@ The `view-map` binary attempts to render a map, and is the current focus of effort. Once I can render a whole map, including pre-placed characters (cultist scum), things can start to get more interesting. -Current status: map tiles are rendered at correct offsets. Static objects (four -per map coordinate: floor, centre, left, and right) are rendered mostly fine, -although objects at each Z level don't *quite* stack correctly on top of each -other yet. +Current status: almost pixel-perfect map rendering. Static objects (four per map +coordinate: floor, centre, left, and right) are rendered fine, and each Z level +looks good. There are a few minor artifacts here and there. Characters and animations aren't touched at all yet. Rendering performance is -atrocious. No gameplay, no sound, no campaign logic. Interaction with the play -area is minimal and limited to pan, zoom, and click for basic console output. +poor. No gameplay, no campaign logic. Interaction with the play area is minimal +and limited to pan, zoom, and click for basic console output. Still, I'm proud of myself. @@ -57,7 +54,7 @@ To run: ``` $ make view-map -$ ./view-map -map orig/Maps/Chapter01.MAP -txt orig/Maps/Chapter01.TXT +$ ./view-map -map Chapter01 ``` Looks like this: @@ -72,12 +69,36 @@ Dependency management uses `go mod`, so ensure you have at least Go 1.11. There is the **start** of the menu / campaign flow in a `wh40k` binary: ``` +$ cp config.toml.example config.toml $ make wh40k $ ./wh40k ``` -This plays the introductory video so far, and nothing else. I'm hopeful I can -render the main menu next. +This plays the introductory videos so far, and nothing else. + +Menus are in the process of being rendered; you can use the `view-menu` binary +to inspect them: + +``` +make view-menu +./view-menu -menu ./orig/Menu/Main.mnu +``` + +This menu *displays* OK, including + +## Sound + +Sound is in the very early stages. Chaos Gate uses ADPCM WAV files, which are a +pain to play in Go, so for now, a preprocessing step that converts them to .ogg +is used instead. To create ./orig/Wav/*.wav.ogg, run: + +``` +# apt install ffmpeg +$ ./scripts/convert-wav ./orig/Wav +``` + +As with video playback, the ambition is to *eventually* remove this dependency +and operate on the unmodified files instead. ## Miscellany diff --git a/cmd/view-menu/main.go b/cmd/view-menu/main.go index 67e8521..33db93c 100644 --- a/cmd/view-menu/main.go +++ b/cmd/view-menu/main.go @@ -7,12 +7,12 @@ import ( "os" "path/filepath" - "github.com/hajimehoshi/ebiten" - "code.ur.gs/lupine/ordoor/internal/assetstore" "code.ur.gs/lupine/ordoor/internal/data" "code.ur.gs/lupine/ordoor/internal/menus" "code.ur.gs/lupine/ordoor/internal/ui" + "github.com/hajimehoshi/ebiten" + "github.com/hajimehoshi/ebiten/audio" ) var ( @@ -76,6 +76,20 @@ func main() { menuObjs = append(menuObjs, obj) } + // Yay sound + if _, err := audio.NewContext(48000); err != nil { + log.Fatalf("Failed to audio: %v", err) + } + music, err := assets.Sound("music_interface") // FIXME: should be a reference to Sounds.dat + if err != nil { + log.Fatalf("Failed to find interface music: %v", err) + } + player, err := music.InfinitePlayer() + if err != nil { + log.Fatalf("Failed to generate music player for interface: %v", err) + } + player.Play() + state := state{} env := &env{ menu: menu, diff --git a/go.mod b/go.mod index 1270f66..1b57643 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.12 require ( github.com/BurntSushi/toml v0.3.1 github.com/hajimehoshi/ebiten v1.11.0-alpha.2.0.20200101150127-38815ba801a5 - golang.org/x/exp v0.0.0-20200319221330-857350248e3d // indirect + golang.org/x/exp v0.0.0-20200320212757-167ffe94c325 // indirect golang.org/x/image v0.0.0-20200119044424-58c23975cae1 // indirect golang.org/x/mobile v0.0.0-20200222142934-3c8601c510d0 // indirect - golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d // indirect + golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae // indirect ) diff --git a/go.sum b/go.sum index 839f17f..e83ca24 100644 --- a/go.sum +++ b/go.sum @@ -14,13 +14,18 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/hajimehoshi/bitmapfont v1.2.0/go.mod h1:h9QrPk6Ktb2neObTlAbma6Ini1xgMjbJ3w7ysmD7IOU= github.com/hajimehoshi/ebiten v1.10.2 h1:PiJBY4Q4udip675T+Zqvb3NKMp1eyLWBelp660ZMrkQ= github.com/hajimehoshi/ebiten v1.10.2/go.mod h1:i9dIEUf5/MuPtbK1/wHR0PB7ZtqhjOxxg+U1xfxapcY= +github.com/hajimehoshi/ebiten v1.10.5 h1:hVb3GJP4IDqOETifRmPg4xmURRgbIJoB9gQk+Jqe8Uk= github.com/hajimehoshi/ebiten v1.11.0-alpha.2.0.20200101150127-38815ba801a5 h1:hke9UdXY1YPfqjXG1bCSZnoVnfVBw9SzvmlrRn3dL3w= github.com/hajimehoshi/ebiten v1.11.0-alpha.2.0.20200101150127-38815ba801a5/go.mod h1:0SLvfr8iI2NxzpNB/olBM+dLN9Ur5a9szG13wOgQ0nQ= +github.com/hajimehoshi/go-mp3 v0.2.1 h1:DH4ns3cPv39n3cs8MPcAlWqPeAwLCK8iNgqvg0QBWI8= github.com/hajimehoshi/go-mp3 v0.2.1/go.mod h1:Rr+2P46iH6PwTPVgSsEwBkon0CK5DxCAeX/Rp65DCTE= github.com/hajimehoshi/oto v0.3.4/go.mod h1:PgjqsBJff0efqL2nlMJidJgVJywLn6M4y8PI4TfeWfA= +github.com/hajimehoshi/oto v0.5.4 h1:Dn+WcYeF310xqStKm0tnvoruYUV5Sce8+sfUaIvWGkE= github.com/hajimehoshi/oto v0.5.4/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/jakecoffman/cp v0.1.0/go.mod h1:a3xPx9N8RyFAACD644t2dj/nK4SuLg1v+jL61m2yVo4= +github.com/jfreymuth/oggvorbis v1.0.0 h1:aOpiihGrFLXpsh2osOlEvTcg5/aluzGQeC7m3uYWOZ0= github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM= +github.com/jfreymuth/vorbis v1.0.0 h1:SmDf783s82lIjGZi8EGUUaS7YxPHgRj4ZXW/h7rUi7U= github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -38,6 +43,8 @@ golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85 h1:jqhIzSw5SQNkbu5hOGpgMHhkf golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20200319221330-857350248e3d h1:1kJNg12kVM6Xid7xoFkhq/YJVU4NMTv5b3hJCfQnwjc= golang.org/x/exp v0.0.0-20200319221330-857350248e3d/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20200320212757-167ffe94c325 h1:iPGJw87eUJvke9YLYKX0jIwLHiIrY/kXcFSgOpjav28= +golang.org/x/exp v0.0.0-20200320212757-167ffe94c325/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -69,6 +76,8 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d h1:62ap6LNOjDU6uGmKXHJbSfciMoV+FeI1sRXx/pLDL44= golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae h1:3tcmuaB7wwSZtelmiv479UjUB+vviwABz7a133ZwOKQ= +golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190909214602-067311248421/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= diff --git a/internal/assetstore/assetstore.go b/internal/assetstore/assetstore.go index 2d96f31..0c099b9 100644 --- a/internal/assetstore/assetstore.go +++ b/internal/assetstore/assetstore.go @@ -32,9 +32,10 @@ type AssetStore struct { entries entryMap // These members are used to store things we've already loaded - maps map[string]*Map - objs map[string]*Object - sets map[string]*Set + maps map[string]*Map + objs map[string]*Object + sets map[string]*Set + sounds map[string]*Sound } // New returns a new AssetStore @@ -82,6 +83,7 @@ func (a *AssetStore) Refresh() error { a.maps = make(map[string]*Map) a.objs = make(map[string]*Object) a.sets = make(map[string]*Set) + a.sounds = make(map[string]*Sound) return nil } diff --git a/internal/assetstore/sound.go b/internal/assetstore/sound.go new file mode 100644 index 0000000..4b75f82 --- /dev/null +++ b/internal/assetstore/sound.go @@ -0,0 +1,78 @@ +package assetstore + +import ( + "log" + "os" + + "github.com/hajimehoshi/ebiten/audio" + "github.com/hajimehoshi/ebiten/audio/vorbis" +) + +type Sound struct { + Name string + + filename string +} + +func (a *AssetStore) Sound(name string) (*Sound, error) { + name = canonical(name) + + if sound, ok := a.sounds[name]; ok { + return sound, nil + } + + // TODO: Data/Sounds.dat + Sounds/wh40k.ds seem to operate together to allow + // attributes and a .wav file to be attached to event names, which could be + // what we use here instead. For now, we're just using the .wav files! + log.Printf("Loading sound %v", name) + + // FIXME: a preprocessing script is used to create these files from the + // original ADPCM .wav files. Instead, use the original .wav files. + filename, err := a.lookup(name, "wav.ogg", "Wav") + if err != nil { + return nil, err + } + + sound := &Sound{ + Name: name, + filename: filename, + } + + a.sounds[name] = sound + return sound, nil +} + +func (s *Sound) Player() (*audio.Player, error) { + decoder, err := s.decoder() + if err != nil { + return nil, err + } + + return audio.NewPlayer(audio.CurrentContext(), decoder) +} + +func (s *Sound) InfinitePlayer() (*audio.Player, error) { + decoder, err := s.decoder() + if err != nil { + return nil, err + } + + infinite := audio.NewInfiniteLoop(decoder, decoder.Size()) + + return audio.NewPlayer(audio.CurrentContext(), infinite) +} + +func (s *Sound) decoder() (*vorbis.Stream, error) { + f, err := os.Open(s.filename) + if err != nil { + return nil, err + } + + decoder, err := vorbis.Decode(audio.CurrentContext(), f) + if err != nil { + _ = f.Close() + return nil, err + } + + return decoder, nil +} diff --git a/scripts/convert-wav b/scripts/convert-wav new file mode 100755 index 0000000..7b3aa18 --- /dev/null +++ b/scripts/convert-wav @@ -0,0 +1,6 @@ +#!/bin/sh + +for file in $(ls $1/*.wav); do + ffmpeg -i $file -codec:a libvorbis $file.ogg +done +