GameLib is my library for making games
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

811 lines
34 KiB

(set-cakelisp-option executable-output "VocalGame")
(import &comptime-only "Options.cake" "ComptimeHelpers.cake")
(import "Ogre.cake" "OgreInitialize.cake" "SDL.cake" "Math.cake" "Tracy.cake" "Aubio.cake")
;; TODO: Should this happen automatically, because import automatically adds current working dir?
;; Should it add working dir?
(add-c-search-directory-module ".")
(c-import "SDL.h" "SDL_syswm.h" "SDL_timer.h"
"<math.h>" "<stdio.h>" "<string.h>")
(var g-window (* SDL_Window) null)
(var g-monkey-mesh mesh-handle)
(var g-monkey-node scene-node)
(var g-monkey-anim-handle OgreAnimationHandle)
(var g-light-node scene-node)
(var g-pitch-particles particle-system-handle)
(var g-reload-sentinel int 2)
(var g-initialized bool false)
;; These are read and written to from different threads (currently, without locking)
(var g-audio-is-initialized bool false)
(var g-audio-is-recording bool false)
(var g-audio-looper-buffer (* Uint8) null)
(var g-audio-looper-buffer-size int 0)
(var g-audio-looper-write-head int 0)
(var g-audio-looper-read-head int 0)
(var g-audio-output-device SDL_AudioDeviceID 0)
(var g-audio-input-device SDL_AudioDeviceID 0)
(var g-audio-output-device-spec SDL_AudioSpec)
(var g-audio-input-device-spec SDL_AudioSpec)
;; (var g-enable-audio bool false)
(var g-enable-audio bool true)
(defstruct-local circular-buffer
data (* Uint8)
size int
read-head int
write-head int)
(defun-local circular-buffer-initialize (buffer (* circular-buffer) desired-size int)
(set (path buffer > data) (type-cast (calloc desired-size (sizeof Uint8)) (* Uint8)))
(set (path buffer > size) desired-size)
(set (path buffer > read-head) 0)
;; To get things started, write-head needs to not equal read
(set (path buffer > write-head) 0))
(defun-local circular-buffer-increment-read (buffer (* circular-buffer))
(incr (path buffer > read-head))
(when (>= (path buffer > read-head) (path buffer > size))
(set (path buffer > read-head) 0)))
(defun-local circular-buffer-increment-write (buffer (* circular-buffer))
(incr (path buffer > write-head))
(when (>= (path buffer > write-head) (path buffer > size))
(set (path buffer > write-head) 0)))
;; TODO: Read in batches
;; Returns false if no new samples, i.e. read head caught up with write head
(defun-local circular-buffer-read (buffer (* circular-buffer) sample-out (* Uint8) &return bool)
(unless (and buffer (!= (path buffer > read-head) (path buffer > write-head)))
;; Waiting for new samples to be written
(return false))
(set (deref sample-out) (at (path buffer > read-head) (path buffer > data)))
(circular-buffer-increment-read buffer)
(return true))
;; Returns false if write-head = read head, and sample will not be written
(defun-local circular-buffer-write (buffer (* circular-buffer) sample-in Uint8 &return bool)
;; TODO This isn't quite right. Write head should stop right before read instead of on it
(circular-buffer-increment-write buffer)
(unless (and buffer (!= (path buffer > write-head) (path buffer > read-head)))
;; Write head reached read head. Samples will be dropped!
(return false))
(set (at (path buffer > write-head) (path buffer > data)) sample-in)
(return true))
(var g-audio-pitch-input-buffer circular-buffer)
(var g-audio-input-samples-lost int 0)
;; Outputs to a format that can be plotted with gnuplot:
;; gnuplot> plot 'out.dat' with lines
(defun-local audio-dump-recorded-buffer (output-filename (* (const char))
buffer (* Uint8) buffer-size int)
(ZoneScopedN "Audio dump recording")
(var dest-file (* FILE) (fopen output-filename "w"))
(unless dest-file
(printf "Could not open file to write data\n")
(var i int 0)
(while (< i buffer-size)
(fprintf dest-file "%d %d\n" i (at i buffer))
(incr i))
(fclose dest-file))
(defun-local audio-looper-buffer-initialize ()
(set g-audio-looper-buffer-size 44100)
(set g-audio-looper-buffer (type-cast (calloc g-audio-looper-buffer-size (sizeof Uint8))
(* Uint8)))
(var i int 0)
(while (< i g-audio-looper-buffer-size)
;; TODO: Use silence value from SDL audio spec
(set (at i g-audio-looper-buffer) 127)
(incr i)))
(defun-local audio-looper-buffer-destroy ()
(free g-audio-looper-buffer)
(set g-audio-looper-buffer null))
(defun-local audio-looper-output-callback (userdata (* void) stream (* Uint8) stream-length int)
(ZoneScopedN "Audio output")
;; (printf "Audio len %d\n" stream-length)
(var-static up bool false)
(set up (not up))
(var i int 0)
(var num-channels int 2)
(var samples-per-channel int (/ stream-length num-channels))
(while (< i stream-length)
(var mono-sample int 127)
;; Square
;; (set mono-sample (? up 255 0))
;; Sawtooth
;; (set mono-sample (mod (/ i 4) 255))
;; Sine
;; (set mono-sample
;; ;; Map to 0-255
;; (+ 127 (* 127
;; ;; Map to -1 to +1
;; (sin
;; ;; Map from position in buffer to 2pi range
;; (/ (* i 2 g-pi) (type-cast samples-per-channel float))))))
;; Loop playback
(if g-audio-is-recording
(set mono-sample 127) ;; Silence
(block ;; Else, play the recording
(var recording-index int (+ (/ i num-channels) g-audio-looper-read-head))
(set recording-index (mod recording-index g-audio-looper-buffer-size))
(set mono-sample (at recording-index g-audio-looper-buffer))))
;; Channels are interleaved, e.g. LRLR, not LLRR
(var channel int 0)
(while (< channel num-channels)
(set (at (+ i channel) stream) mono-sample)
;; (printf "[%d][%d] %d\n" (+ i channel) channel (at i stream))
(incr channel))
(set i (+ i num-channels)))
(set g-audio-looper-read-head
(mod (+ g-audio-looper-read-head samples-per-channel)
;; Note: If input is sampled at a different rate, playback will be at a lower pitch. Use SDL's
;; audio conversion functions to handle that properly
(defun-local audio-looper-input-callback (userdata (* void) stream (* Uint8) stream-length int)
(ZoneScopedN "Audio input")
(unless g-audio-is-recording
;; (printf "received audio %d %d %d\n"
;; (at 0 stream)
;; (at (/ stream-length 2) stream)
;; (at (- stream-length 1) stream))
(var i int 0)
(while (< i stream-length)
(set (at g-audio-looper-write-head g-audio-looper-buffer) (at i stream))
(if (= g-audio-looper-write-head (- g-audio-looper-buffer-size 1))
(set g-audio-looper-write-head 0)
(incr g-audio-looper-write-head))
(incr i)))
(defun-local audio-pitch-input-callback (userdata (* void) stream (* Uint8) stream-length int)
(ZoneScopedN "Audio input")
(var i int 0)
(while (< i stream-length)
(var sample-written bool
(circular-buffer-write (addr g-audio-pitch-input-buffer) (at i stream)))
;; Buffer is full; has the read thread gotten behind?
(unless sample-written
(incr g-audio-input-samples-lost)
(incr i)))
(defun-local initialize-audio (output-device-out (* SDL_AudioDeviceID)
input-device-out (* SDL_AudioDeviceID)
output-device-spec-out (* SDL_AudioSpec)
input-device-spec-out (* SDL_AudioSpec)
&return bool)
(ZoneScopedN "Audio intialization")
;; This will take a while. Make sure we aren't marked as ready while preparing
(set g-audio-is-initialized false)
(var audio-driver (* (const char)) (SDL_GetCurrentAudioDriver))
"Audio drivers"
(printf "Available drivers:\n")
(var num-drivers int (SDL_GetNumAudioDrivers))
(var i int 0)
(while (< i num-drivers)
(var driver-name (* (const char)) (SDL_GetAudioDriver i))
(when driver-name
(printf "\t[%d] %s\n" i driver-name))
(incr i))
(unless audio-driver
(printf "No audio driver found")
(return false))
(printf "Active audio driver: %s\n" audio-driver))
;; Use my USB webcam
(var macoy-input-device-index int 2)
;; Use my HDMI output device
(var macoy-output-device-index int 2)
;; Pulse audio has different sources
(when (= 0 (strcmp audio-driver "pulseaudio"))
(set macoy-input-device-index 0)
(set macoy-output-device-index 1))
(var is-capture bool false)
(var devices (* (* (const char))) null)
(var num-devices int (sdl-audio-get-devices (addr devices) is-capture))
(unless num-devices (return false))
(var selected-output-device (* (const char)) nullptr)
(var output-device-id SDL_AudioDeviceID 0)
(var obtained-output-spec SDL_AudioSpec (array 0))
"Audio output device"
(var desired-spec SDL_AudioSpec (array 0))
(set (field desired-spec freq) 44100)
(set (field desired-spec format) AUDIO_U8)
(set (field desired-spec channels) 2) ;; 1 = Mono 2 = Stereo
(set (field desired-spec samples) 512)
(set (field desired-spec callback) audio-looper-output-callback)
(unless (< macoy-output-device-index num-devices)
(printf "Could not choose device %d\n" macoy-output-device-index)
(sdl-audio-free-device-list devices num-devices)
(return false))
(var device-name (* (const char)) (at macoy-output-device-index devices))
(var valid-device-start-num (const int) 2)
(set output-device-id (SDL_OpenAudioDevice
;; null = reasonable default (doesn't work in my case)
false ;; iscapture
(addr desired-spec) (addr obtained-output-spec)
(if (>= output-device-id valid-device-start-num)
(block (set selected-output-device device-name)
(set (deref output-device-spec-out) obtained-output-spec))
(block (sdl-print-error)
(sdl-audio-free-device-list devices num-devices)
(return false))))
(var selected-input-device (* (const char)) nullptr)
(var obtained-input-spec SDL_AudioSpec (array 0))
(var input-device-id SDL_AudioDeviceID 0)
"Audio input device"
(var desired-spec SDL_AudioSpec (array 0))
(set (field desired-spec freq) 44100)
(set (field desired-spec format) AUDIO_U8)
(set (field desired-spec channels) 1) ;; 1 = Mono 2 = Stereo
(set (field desired-spec samples) 512)
;; (set (field desired-spec callback) audio-looper-input-callback)
(set (field desired-spec callback) audio-pitch-input-callback)
(var capture-devices (* (* (const char))) null)
(var num-capture-devices int (sdl-audio-get-devices (addr capture-devices) true))
(unless num-capture-devices (return false))
(unless (< macoy-input-device-index num-capture-devices)
(printf "Could not choose device %d\n" macoy-input-device-index)
(sdl-audio-free-device-list capture-devices num-capture-devices)
(return false))
(var device-name (* (const char)) (at macoy-input-device-index capture-devices))
(var valid-device-start-num (const int) 2)
(set input-device-id (SDL_OpenAudioDevice
;; null = reasonable default (doesn't work in my case)
true ;; iscapture
(addr desired-spec) (addr obtained-input-spec)
(when (< input-device-id valid-device-start-num)
(sdl-audio-free-device-list capture-devices num-capture-devices)
(return false))
(set (deref input-device-spec-out) obtained-input-spec)
(set selected-input-device device-name)
(when selected-input-device ;; print final settings
(printf "Final input settings:\n")
(printf "device: %s\n" selected-input-device)
(sdl-audio-list-specification (addr obtained-input-spec))
;; Start playing
;; Important note: SDL_PauseAudio works on the default device, NOT the opened one
;; This tripped me up when my audio wasn't actually getting played on my opened one
(SDL_PauseAudioDevice input-device-id 0))
(sdl-audio-free-device-list capture-devices num-capture-devices))
(when selected-output-device
(printf "Final output settings:\n")
(printf "device: %s\n" selected-output-device)
(sdl-audio-list-specification (addr obtained-output-spec))
;; Start playing
;; Important note: SDL_PauseAudio works on the default device, NOT the opened one
;; This tripped me up when my audio wasn't actually getting played on my opened one
(SDL_PauseAudioDevice output-device-id 0))
(sdl-audio-free-device-list devices num-devices)
(when output-device-out (set (deref output-device-out) output-device-id))
(when input-device-out (set (deref input-device-out) input-device-id))
(set g-audio-is-initialized true)
(return (and output-device-id input-device-id)))
(defun-local audio-close (output-device SDL_AudioDeviceID
input-device SDL_AudioDeviceID)
(when (>= output-device 2) (SDL_CloseAudioDevice output-device))
(when (>= input-device 2) (SDL_CloseAudioDevice input-device)))
(forward-declare (namespace Ogre
(class Root)
(class SceneManager)))
ogreCreatePbsSpheres (root (* (in Ogre Root))
scene-manager (* (in Ogre SceneManager))))
(defun-local audio-initialize-thread (user-data (* void) &return int)
(TracyCSetThreadName "Audio Initialization")
(unless (initialize-audio (addr g-audio-output-device) (addr g-audio-input-device)
(addr g-audio-output-device-spec) (addr g-audio-input-device-spec))
;; TODO: Communicate with main thread and close
(printf "warning: audio failed to initialize\n")
(return 1))
(return 0))
(defun-local audio-shutdown-thread (user-data (* void) &return int)
(ZoneScopedN "SDL audio shutdown")
(audio-close g-audio-output-device
(return 0))
(defun-local audio-set-recording-state (new-value bool)
(scope ;; Toggle recording state
(SDL_LockAudioDevice g-audio-output-device)
(SDL_LockAudioDevice g-audio-input-device)
(set g-audio-is-recording new-value)
(SDL_UnlockAudioDevice g-audio-input-device)
(SDL_UnlockAudioDevice g-audio-output-device)))
(defun-local app-main (&return int)
(TracyCZoneN startup-zone "Startup" 1)
(unless g-initialized ;; Note, continues after audio thread starts up
"SDL initialization"
;; (defun main (&return int)
(unless (sdl-initialize-for-3d (addr g-window) "Vocal Game" 1920 1080)
(TracyCZoneEnd startup-zone)
(return 1))))
;; Audio needs to be re-g-initialized due to reload removing callbacks
(when g-enable-audio
;; Allow one second of no reads before we stop dropping samples
(circular-buffer-initialize (addr g-audio-pitch-input-buffer) 44100)
;; Audio initialization is extremely slow (~2-3 seconds on my machine). Let's do it on a
;; separate thread to keep start up responsive
(var audio-init-thread-handle (* SDL_Thread)
(SDL_CreateThread audio-initialize-thread "AudioInitialization" null))
;; While we do want to know if this thread fails, we don't want to block to know, so we'll
;; detach it to not leave it dangling (see SDL_thread.h)
(SDL_DetachThread audio-init-thread-handle)
(ignore ;; Single threaded version
(unless (initialize-audio (addr g-audio-output-device) (addr g-audio-input-device)
(addr g-audio-output-device-spec) (addr g-audio-input-device-spec))
(sdl-shutdown g-window)
(audio-close g-audio-output-device g-audio-input-device)
(TracyCZoneEnd startup-zone)
(return 1))))
(unless g-initialized
"Ogre initialization"
;; Ogre uses exceptions for error handling, so we can't gracefully close without getting all that
;; stuff set up (which I don't really want to do; it belongs in Gamelib)
(unless (ogre-initialize-sdl)
(TracyCZoneEnd startup-zone)
(return 1)))
"Scene setup/load meshes"
(set g-monkey-mesh (ogre-load-mesh "Monkey_Mesh.mesh"))
(set g-monkey-node (ogre-node-from-item g-monkey-mesh))
;; TODO: How to change this animation's name?
(set g-monkey-anim-handle
(ogre-attach-animation g-monkey-mesh "Monkey_Mesh.skeleton" "Monkey_AngleChin"))
(set g-light-node (ogre-create-light))
(set g-pitch-particles (ogre-create-particle-system))
(when g-ogre-root
(printf "Creating PBS spheres\n")
(ogreCreatePbsSpheres g-ogre-root (ogre-get-scene-manager))
(printf "Creating PBS spheres done\n")))
(set g-initialized true))
(var exit-reason (* (const char)) null)
(var tracker-position vec3 (array -10.f 0.f 0.f))
(var move-speed float 10.f)
(var counter-num-ticks-per-second (const Uint64) (SDL_GetPerformanceFrequency))
(var last-frame-perf-count Uint64 (* 0.016f counter-num-ticks-per-second))
;; "Debounce" via only reloading on key release
(var was-reload-pressed bool false)
(TracyCZoneEnd startup-zone)
;; Main loop
(while (not exit-reason)
(FrameMarkNamed "Frame")
(ZoneScopedN "Frame")
(var event SDL_Event)
(while (SDL_PollEvent (addr event))
(when (= (field event type) SDL_QUIT)
(set exit-reason "Window closed")))
;; Note: this requires SDL_PollEvent in order to be up-to-date
(var currentKeyStates (* (const Uint8)) (SDL_GetKeyboardState null))
(when (at SDL_SCANCODE_ESCAPE currentKeyStates)
(set exit-reason "Escape pressed"))
(var reload-pressed bool (at SDL_SCANCODE_R currentKeyStates))
(when reload-pressed
(set was-reload-pressed true))
(when (and (not reload-pressed) was-reload-pressed)
(set was-reload-pressed false)
(printf "\nReloading\n\n")
;; Our audio callbacks are going away!
;; The SDL_QueueAudio() API would make this easier to deal with, but then I need to manage
;; an audio thread on my own
(audio-close g-audio-output-device
(return g-reload-sentinel))
(var delta-position vec3 (array 0.f 0.f 0.f))
(when (at SDL_SCANCODE_RIGHT currentKeyStates)
(set (vec-x delta-position) (+ (vec-x delta-position) move-speed)))
(when (at SDL_SCANCODE_LEFT currentKeyStates)
(set (vec-x delta-position) (- (vec-x delta-position) move-speed)))
(when (at SDL_SCANCODE_UP currentKeyStates)
(set (vec-y delta-position) (+ (vec-y delta-position) move-speed)))
(when (at SDL_SCANCODE_DOWN currentKeyStates)
(set (vec-y delta-position) (- (vec-y delta-position) move-speed)))
"Audio read pitch input"
(var num-samples-read int 0)
(var sample Uint8 0)
(var pitch-buffer-size int 512)
(var pitch-buffer ([] 512 Uint8))
(while (circular-buffer-read (addr g-audio-pitch-input-buffer) (addr sample))
;; TODO use last 512, not first 512
(when (< num-samples-read pitch-buffer-size)
(set (at num-samples-read pitch-buffer) sample))
(incr num-samples-read))
;; (printf "Read %d samples\n" num-samples-read)
(when g-audio-input-samples-lost
(printf "warning: lost %d input samples\n" g-audio-input-samples-lost)
(set g-audio-input-samples-lost 0))
(when (>= num-samples-read pitch-buffer-size)
"Detect pitch"
(var pitch-hz float
(audio-detect-pitch pitch-buffer pitch-buffer-size
(field g-audio-output-device-spec freq)))
(printf "Pitch (hz): %f\n" pitch-hz)
(set (vec-y delta-position)
;; On-camera range
0.f 7.f
;; Human vocal range is ~100hz to ~5000hz
;; 100.f 2000.f ;; Harmonica
100.f 700.f ;; My voice
(var prev-recording-value bool g-audio-is-recording)
(audio-set-recording-state (at SDL_SCANCODE_SPACE currentKeyStates))
(when (and g-audio-is-initialized
(!= prev-recording-value g-audio-is-recording)
(not g-audio-is-recording))
(ZoneScopedN "Audio conversion")
(scope ;; Convert audio to playback format.
;; This was necessary in my case because my microphone records at 48kHz, but my sound card
;; expects 44.1kHz output
;; Audio Streams are worth looking at in the future, so it's not all one big batch process
(var audio-conversion-settings SDL_AudioCVT)
(SDL_BuildAudioCVT (addr audio-conversion-settings)
;; Src
(field g-audio-input-device-spec format)
(field g-audio-input-device-spec channels)
(field g-audio-input-device-spec freq)
;; Dest
(field g-audio-output-device-spec format)
1 ;; (field g-audio-output-device-spec channels) ;; We convert to stereo in playback callback
(field g-audio-output-device-spec freq))
(set (field audio-conversion-settings len) g-audio-looper-buffer-size)
;; TODO: Does the buffer really nead to be 8x the src? Am I doing something wrong with my len?
;; Could be conversion to float then 2x that for resampling = 4x * 2x = 8x
(var converted-buffer-size-bytes int
(* (field audio-conversion-settings len)
(field audio-conversion-settings len_mult)))
(printf "Converted buffer size: %d bytes because %d len %d len_mult\n"
(field audio-conversion-settings len)
(field audio-conversion-settings len_mult))
(set (field audio-conversion-settings buf)
(type-cast (malloc converted-buffer-size-bytes) (* Uint8)))
(scope ;; Copy src to in-place conversion buffer
(var i int 0)
(while (< i g-audio-looper-buffer-size)
(set (at i (field audio-conversion-settings buf)) (at i g-audio-looper-buffer))
(incr i)))
(unless (= 0 (SDL_ConvertAudio (addr audio-conversion-settings)))
(set exit-reason "Failed to convert audio")
(free (field audio-conversion-settings buf))
;; TODO: This shouldn't be byte size, it should be sample count, to support other formats
(audio-dump-recorded-buffer "converted.dat"
(field audio-conversion-settings buf)
;; Remove the extra buffer used by the converter
(/ converted-buffer-size-bytes
(field audio-conversion-settings len_mult)))
(scope ;; Copy back to destination buffer. Note: this could drop samples, because it should
;; use the converted buffer size instead of the fixed array size!
(var i int 0)
(while (< i g-audio-looper-buffer-size)
(set (at i g-audio-looper-buffer)
(at i (type-cast (field audio-conversion-settings buf) (* Uint8))))
;; TODO: If my sample rate is higher when recorded, that means I need to record more
;; samples and play back fewer. Because I use the same buffer, it means I don't have
;; enough samples at the lower playback rate, and start hitting buffer workspace.
;; Zero them out for now. I should record with enough samples to make it exactly fit
;; after conversion
(when (>= i (- g-audio-looper-buffer-size 3900))
(set (at i g-audio-looper-buffer) 127))
(incr i)))
(free (field audio-conversion-settings buf)))
(scope ;; Normalize audio
;; Note: peak in both low and high, never greater than range / 2
(var highest-peak Uint8 0)
(var i int 0)
(while (< i g-audio-looper-buffer-size)
(var current-value int (- (type-cast (at i g-audio-looper-buffer) int) 127))
(when (< highest-peak (abs current-value))
(set highest-peak (abs current-value)))
(incr i))
(when highest-peak
;; Get within 90% of the max, to avoid clipping
(var desired-high-peak (const int) (* (/ 256 2) 0.9f))
(var scaling-factor float (/ desired-high-peak (type-cast highest-peak float)))
(printf "Scaling recording %.4f because desired = %d and highest was %d\n"
scaling-factor desired-high-peak highest-peak)
(var scaling-limit float 6.f)
;; If there's only silence, you don't want to go crazy with boosting it
(when (> scaling-factor scaling-limit)
(printf "scaling limited to %f\n" scaling-limit)
(set scaling-factor scaling-limit))
(when scaling-factor
(set i 0)
(while (< i g-audio-looper-buffer-size)
(var current-value int (- (type-cast (at i g-audio-looper-buffer) int) 127))
(set (at i g-audio-looper-buffer) (type-cast
;; Map back to 0-255
(+ 127.f
(* current-value scaling-factor))
(incr i)))))
"Detect pitch"
(var pitch-hz float
(audio-detect-pitch g-audio-looper-buffer g-audio-looper-buffer-size
(field g-audio-output-device-spec freq)))))
(var current-counter-ticks Uint64 (SDL_GetPerformanceCounter))
(var frame-diff-ticks Uint64 (- current-counter-ticks last-frame-perf-count))
(var delta-time float (/ frame-diff-ticks
(type-cast counter-num-ticks-per-second float)))
;; (printf "%lu %f\n" frame-diff-ticks delta-time)
;; Visualize audio playback
(var audio-read-head-to-world float (* 20.f
(/ g-audio-looper-read-head
(type-cast g-audio-looper-buffer-size float))))
(var audio-volume-to-world float (* 7.f
(/ (- (at g-audio-looper-read-head g-audio-looper-buffer) 127)
(var audio-pos vec3 (array audio-read-head-to-world 0.f audio-volume-to-world))
;; (var final-pos vec3 (vec3-scale-add-vec3 delta-position delta-time tracker-position))
;; (var final-pos vec3 (vec3-scale-add-vec3 delta-position delta-time
;; (vec3-add tracker-position audio-pos)))
(var final-pos vec3 delta-position)
(when g-audio-is-recording
(set (vec-y final-pos) (+ (vec-y final-pos) -2.f)))
(ogre-node-set-position (addr g-monkey-node) (vec-xyz final-pos))
;; This moves the entire particle system, not the emitter
;; (ogre-node-set-position (addr g-particle-node) (vec-xyz final-pos))
(ogre-particle-emitter-set-position (addr g-pitch-particles) (vec-xyz final-pos))
(ogre-animation-add-time g-monkey-anim-handle delta-time)
(set last-frame-perf-count (SDL_GetPerformanceCounter))
"Ogre render + vsync"
(unless (ogre-render-frame)
(set exit-reason "Failed to render frame")
(ZoneScopedN "Shutdown")
(audio-set-recording-state false)
;; Audio shutdown is extremely slow (~2-3 seconds on my machine). Let's do it on a
;; separate thread to let other things shutdown at the same time
(var audio-shutdown-thread-handle (* SDL_Thread)
(SDL_CreateThread audio-shutdown-thread "AudioShutdown" null))
(audio-dump-recorded-buffer "out.dat"
g-audio-looper-buffer g-audio-looper-buffer-size)
"Ogre shutdown"
;; Make sure audio is shutdown before destroying SDL
(var status int -1)
(SDL_WaitThread audio-shutdown-thread-handle (addr status))
"SDL shutdown"
(sdl-shutdown g-window))
(when exit-reason
(printf "Exit reason: %s\n" exit-reason))
(return 0))
;; TODO: Automatically make entry point
(defun reloadableEntryPoint (&return bool)
(var result int (app-main))
(cond ((= 0 result)
(return false))
((= 1 result)
(return false))
((= g-reload-sentinel result)
(return true)))
(return false))
;; Assets
;; TODO Improvements:
;; - Check blender command for changes to cause rebuild
;; - Check linked Blender files (would need to hard code because blender is too slow to start)
;; - Don't rebuild due to different build configuration (assets remain unchanged)
;; - Actually check assets instead of just cache file
(defun-comptime process-3d-assets (manager (& ModuleManager) module (* Module) &return bool)
(scope ;; Models/meshes
;; Make sure output dir exists and we have an absolute path to it
(var model-assets ([] (* (const char))) (array "Monkey" "MaterialSphere"))
(var model-relative-dir (* (const char)) "data/Models")
(makeDirectory model-relative-dir)
;; Output must be absolute or OgreMeshTool will fail (probably due to different working dir)
(var model-output-dir (* (const char)) (makeAbsolutePath_Allocated null model-relative-dir))
(unless model-output-dir
(Logf "Asset-Building: could not make directory %s absolute\n" model-relative-dir)
(return false))
(var i int 0)
(while (< i (array-size model-assets))
(var blend-asset ([] MAX_PATH_LENGTH char) (array 0))
(PrintfBuffer blend-asset "assets/%s.blend" (at i model-assets))
;; It is too slow to check Blender for all the files the blend will export, then check whether
;; the .blend file is more recently modified. Instead, create a file in the cache to represent
;; the last time the .blend was known to have been exported. Hack, especially because other
;; configurations don't need to rebuild assets. Probably better to just leave asset building to
;; another executable
(var cache-reference-filename ([] MAX_PATH_LENGTH char) (array 0))
(unless (outputFilenameFromSourceFilename (call-on c_str (field manager buildOutputDir))
"txt" ;; Add to end of file for type
cache-reference-filename (sizeof cache-reference-filename))
(free (type-cast model-output-dir (* void)))
(return false))
(unless (fileIsMoreRecentlyModified blend-asset cache-reference-filename)
(incr i)
"--background" blend-asset
"--python-exit-code" "1" ;; If there's a python exception, return 1
"--python" "../tools/"
"--" model-output-dir)
(Log "Asset-Building: failed to build 3D asset. Is Blender on your path? Is blender2ogre set
up on your Blender default preferences? See for setup\n
You may need to copy blender2ogre to your new blender version, e.g.:\n\n
cp -r Dependencies/blender2ogre/io_ogre ~/.config/blender/[version]\n\n
Or, open your new version of Blender and select 'Copy settings from [previous version]'\n
on the splash screen.\n")
(free (type-cast model-output-dir (* void)))
(return false))
(scope ;; Write reference file
(var cache-reference (* FILE) (fopen cache-reference-filename "w"))
(unless cache-reference
(Logf "Asset-Building: failed to open cache reference file %s\n" cache-reference-filename)
(free (type-cast model-output-dir (* void)))
(return false))
(fprintf cache-reference "%s exported\n" blend-asset)
(fclose cache-reference))
(incr i))
(free (type-cast model-output-dir (* void))))
(scope ;; Textures
(var texture-assets ([] (* (const char))) (array "Monkey_Texture"))
(var texture-process-results (<> std::vector int))
(call-on resize texture-process-results (array-size texture-assets))
(var max-num-processes int 4)
(var num-processes-running int 0)
(var i int 0)
(while (< i (array-size texture-assets))
(var texture-asset ([] MAX_PATH_LENGTH char) (array 0))
(PrintfBuffer texture-asset "assets/%s.png" (at i texture-assets))
(var texture-converted ([] MAX_PATH_LENGTH char) (array 0))
(PrintfBuffer texture-converted "data/Materials/Textures/" (at i texture-assets))
(unless (fileIsMoreRecentlyModified texture-asset texture-converted)
(incr i)
;; Don't spin up too many processes
(when (>= num-processes-running max-num-processes)
(Log "wait for processes\n")
(waitForAllProcessesClosed null)
(set num-processes-running 0))
(set (at i texture-process-results) -1)
(run-process-start-or (addr (at i texture-process-results))
("convert" texture-asset texture-converted)
(Log "Asset-Building: failed to convert 2D texture. Is 'convert' on your
path? You may need to install ImageMagick. See\n")
(return false))
(incr num-processes-running)
(incr i))
(waitForAllProcessesClosed null)
(for-in result int texture-process-results
(unless (= 0 result)
(Log "Asset-Building: failed to convert texture\n")
(return false))))
(return true))
;; TODO: This should be a post-build hook
(add-compile-time-hook-module pre-build process-3d-assets :priority-decrease 10)
;; Building
;; TODO: Somehow inherit this from SDL.cake?
(use-ogre-build-options) ;; Only needed for the dependency
(add-cpp-build-dependency "OgreHlms.cpp")
(import &comptime-only "Dependencies.cake")
(add-dependency-git-submodule clone-blender2ogre ""