(ns cortex.hearing "Simulate the sense of hearing in jMonkeyEngine3. Enables multiple listeners at different positions in the same world. Automatically reads ear-nodes from specially prepared blender files and instantiates them in the world as simulated ears." {:author "Robert McIntyre"} (:use (cortex world util sense)) (:import java.nio.ByteBuffer) (:import java.awt.image.BufferedImage) (:import org.tritonus.share.sampled.FloatSampleTools) (:import (com.aurellem.capture.audio SoundProcessor AudioSendRenderer)) (:import javax.sound.sampled.AudioFormat) (:import (com.jme3.scene Spatial Node)) (:import com.jme3.audio.Listener) (:import com.jme3.app.Application) (:import com.jme3.scene.control.AbstractControl)) (in-ns 'cortex.hearing) (defn hearing-pipeline "Creates a SoundProcessor which wraps a sound processing continuation function. The continuation is a function that takes [#^ByteBuffer b #^Integer int numSamples #^AudioFormat af ], each of which has already been appropriately sized." [continuation] (proxy [SoundProcessor] [] (cleanup []) (process [#^ByteBuffer audioSamples numSamples #^AudioFormat audioFormat] (continuation audioSamples numSamples audioFormat)))) (defn byteBuffer->pulse-vector "Extract the sound samples from the byteBuffer as a PCM encoded waveform with values ranging from -1.0 to 1.0 into a vector of floats." [#^ByteBuffer audioSamples numSamples #^AudioFormat audioFormat] (let [num-floats (/ numSamples (.getFrameSize audioFormat)) bytes (byte-array numSamples) floats (float-array num-floats)] (.get audioSamples bytes 0 numSamples) (FloatSampleTools/byte2floatInterleaved bytes 0 floats 0 num-floats audioFormat) (vec floats))) (def ^{:doc "Return the children of the creature's \"ears\" node." :arglists '([creature])} ears (sense-nodes "ears")) (defn update-listener-velocity! "Update the listener's velocity every update loop." [#^Spatial obj #^Listener lis] (let [old-position (atom (.getLocation lis))] (.addControl obj (proxy [AbstractControl] [] (controlUpdate [tpf] (let [new-position (.getLocation lis)] (.setVelocity lis (.mult (.subtract new-position @old-position) (float (/ tpf)))) (reset! old-position new-position))) (controlRender [_ _]))))) (defn add-ear! "Create a Listener centered on the current position of 'ear which follows the closest physical node in 'creature and sends sound data to 'continuation." [#^Application world #^Node creature #^Spatial ear continuation] (let [target (closest-node creature ear) lis (Listener.) audio-renderer (.getAudioRenderer world) sp (hearing-pipeline continuation)] (.setLocation lis (.getWorldTranslation ear)) (.setRotation lis (.getWorldRotation ear)) (bind-sense target lis) (update-listener-velocity! target lis) (.addListener audio-renderer lis) (.registerSoundProcessor audio-renderer lis sp))) (defn hearing-kernel "Returns a function which returns auditory sensory data when called inside a running simulation." [#^Node creature #^Spatial ear] (let [hearing-data (atom []) register-listener! (runonce (fn [#^Application world] (add-ear! world creature ear (comp #(reset! hearing-data %) byteBuffer->pulse-vector))))] (fn [#^Application world] (register-listener! world) (let [data @hearing-data topology (vec (map #(vector % 0) (range 0 (count data))))] [topology data])))) (defn hearing! "Endow the creature in a particular world with the sense of hearing. Will return a sequence of functions, one for each ear, which when called will return the auditory data from that ear." [#^Node creature] (for [ear (ears creature)] (hearing-kernel creature ear))) (in-ns 'cortex.hearing) (defn view-hearing "Creates a function which accepts a list of auditory data and display each element of the list to the screen as an image." [] (view-sense (fn [[coords sensor-data]] (let [pixel-data (vec (map #(rem (int (* 255 (/ (+ 1 %) 2))) 256) sensor-data)) height 50 image (BufferedImage. (max 1 (count coords)) height BufferedImage/TYPE_INT_RGB)] (dorun (for [x (range (count coords))] (dorun (for [y (range height)] (let [raw-sensor (pixel-data x)] (.setRGB image x y (gray raw-sensor))))))) image))))