comparison org/hearing.org @ 220:c5f6d880558b

making hearing.org up-to-date
author Robert McIntyre <rlm@mit.edu>
date Sat, 11 Feb 2012 07:08:38 -0700
parents facc2ef3fe5c
children 7c374c6cfe17
comparison
equal deleted inserted replaced
219:5f14fd7b1288 220:c5f6d880558b
7 #+INCLUDE: ../../aurellem/org/level-0.org 7 #+INCLUDE: ../../aurellem/org/level-0.org
8 #+BABEL: :exports both :noweb yes :cache no :mkdirp yes 8 #+BABEL: :exports both :noweb yes :cache no :mkdirp yes
9 9
10 * Hearing 10 * Hearing
11 11
12 I want to be able to place ears in a similar manner to how I place 12 At the end of this post I will have simulated ears that work the same
13 the eyes. I want to be able to place ears in a unique spatial 13 way as the simulated eyes in the last post. I will be able to place
14 position, and receive as output at every tick the F.F.T. of whatever 14 any number of ear-nodes in a blender file, and they will bind to the
15 signals are happening at that point. 15 closest physical object and follow it as it moves around. Each ear
16 will provide access to the sound data it picks up between every frame.
16 17
17 Hearing is one of the more difficult senses to simulate, because there 18 Hearing is one of the more difficult senses to simulate, because there
18 is less support for obtaining the actual sound data that is processed 19 is less support for obtaining the actual sound data that is processed
19 by jMonkeyEngine3. 20 by jMonkeyEngine3. There is no "split-screen" support for rendering
21 sound from different points of view, and there is no way to directly
22 access the rendered sound data.
23
24 ** Brief Description of jMonkeyEngine's Sound System
20 25
21 jMonkeyEngine's sound system works as follows: 26 jMonkeyEngine's sound system works as follows:
22 27
23 - jMonkeyEngine uses the =AppSettings= for the particular application 28 - jMonkeyEngine uses the =AppSettings= for the particular application
24 to determine what sort of =AudioRenderer= should be used. 29 to determine what sort of =AudioRenderer= should be used.
25 - although some support is provided for multiple AudioRendering 30 - Although some support is provided for multiple AudioRendering
26 backends, jMonkeyEngine at the time of this writing will either 31 backends, jMonkeyEngine at the time of this writing will either
27 pick no AudioRenderer at all, or the =LwjglAudioRenderer= 32 pick no =AudioRenderer= at all, or the =LwjglAudioRenderer=.
28 - jMonkeyEngine tries to figure out what sort of system you're 33 - jMonkeyEngine tries to figure out what sort of system you're
29 running and extracts the appropriate native libraries. 34 running and extracts the appropriate native libraries.
30 - the =LwjglAudioRenderer= uses the [[http://lwjgl.org/][=LWJGL=]] (LightWeight Java Game 35 - The =LwjglAudioRenderer= uses the [[http://lwjgl.org/][=LWJGL=]] (LightWeight Java Game
31 Library) bindings to interface with a C library called [[http://kcat.strangesoft.net/openal.html][=OpenAL=]] 36 Library) bindings to interface with a C library called [[http://kcat.strangesoft.net/openal.html][=OpenAL=]]
32 - =OpenAL= calculates the 3D sound localization and feeds a stream of 37 - =OpenAL= renders the 3D sound and feeds the rendered sound directly
33 sound to any of various sound output devices with which it knows 38 to any of various sound output devices with which it knows how to
34 how to communicate. 39 communicate.
35 40
36 A consequence of this is that there's no way to access the actual 41 A consequence of this is that there's no way to access the actual
37 sound data produced by =OpenAL=. Even worse, =OpenAL= only supports 42 sound data produced by =OpenAL=. Even worse, =OpenAL= only supports
38 one /listener/, which normally isn't a problem for games, but becomes 43 one /listener/ (it renders sound data from only one perspective),
39 a problem when trying to make multiple AI creatures that can each hear 44 which normally isn't a problem for games, but becomes a problem when
40 the world from a different perspective. 45 trying to make multiple AI creatures that can each hear the world from
46 a different perspective.
41 47
42 To make many AI creatures in jMonkeyEngine that can each hear the 48 To make many AI creatures in jMonkeyEngine that can each hear the
43 world from their own perspective, it is necessary to go all the way 49 world from their own perspective, or to make a single creature with
44 back to =OpenAL= and implement support for simulated hearing there. 50 many ears, it is necessary to go all the way back to =OpenAL= and
51 implement support for simulated hearing there.
45 52
46 * Extending =OpenAL= 53 * Extending =OpenAL=
47 ** =OpenAL= Devices 54 ** =OpenAL= Devices
48 55
49 =OpenAL= goes to great lengths to support many different systems, all 56 =OpenAL= goes to great lengths to support many different systems, all
69 the only point in the sound rendering process where this data is 76 the only point in the sound rendering process where this data is
70 available. 77 available.
71 78
72 Therefore, in order to support multiple listeners, and get the sound 79 Therefore, in order to support multiple listeners, and get the sound
73 data in a form that the AIs can use, it is necessary to create a new 80 data in a form that the AIs can use, it is necessary to create a new
74 Device, which supports this features. 81 Device which supports this features.
75 82
76 ** The Send Device 83 ** The Send Device
77 Adding a device to OpenAL is rather tricky -- there are five separate 84 Adding a device to OpenAL is rather tricky -- there are five separate
78 files in the =OpenAL= source tree that must be modified to do so. I've 85 files in the =OpenAL= source tree that must be modified to do so. I've
79 documented this process [[./add-new-device.org][here]] for anyone who is interested. 86 documented this process [[../../audio-send/html/add-new-device.html][here]] for anyone who is interested.
80 87
81 88 Again, my objectives are:
82 Onward to that actual Device!
83
84 again, my objectives are:
85 89
86 - Support Multiple Listeners from jMonkeyEngine3 90 - Support Multiple Listeners from jMonkeyEngine3
87 - Get access to the rendered sound data for further processing from 91 - Get access to the rendered sound data for further processing from
88 clojure. 92 clojure.
93
94 I named it the "Multiple Audio Send" Deives, or =Send= Device for
95 short, since it sends audio data back to the callig application like
96 an Aux-Send cable on a mixing board.
97
98 Onward to the actual Device!
89 99
90 ** =send.c= 100 ** =send.c=
91 101
92 ** Header 102 ** Header
93 #+name: send-header 103 #+name: send-header
170 #+end_src 180 #+end_src
171 181
172 Switching between contexts is not the normal operation of a Device, 182 Switching between contexts is not the normal operation of a Device,
173 and one of the problems with doing so is that a Device normally keeps 183 and one of the problems with doing so is that a Device normally keeps
174 around a few pieces of state such as the =ClickRemoval= array above 184 around a few pieces of state such as the =ClickRemoval= array above
175 which will become corrupted if the contexts are not done in 185 which will become corrupted if the contexts are not rendered in
176 parallel. The solution is to create a copy of this normally global 186 parallel. The solution is to create a copy of this normally global
177 device state for each context, and copy it back and forth into and out 187 device state for each context, and copy it back and forth into and out
178 of the actual device state whenever a context is rendered. 188 of the actual device state whenever a context is rendered.
179 189
180 ** Synchronization Macros 190 ** Synchronization Macros
396 Device->Contexts = currentContext; 406 Device->Contexts = currentContext;
397 Device->NumContexts = currentNumContext; 407 Device->NumContexts = currentNumContext;
398 } 408 }
399 #+end_src 409 #+end_src
400 410
401 =OpenAL= normally renders all Contexts in parallel, outputting the 411 =OpenAL= normally renders all contexts in parallel, outputting the
402 whole result to the buffer. It does this by iterating over the 412 whole result to the buffer. It does this by iterating over the
403 Device->Contexts array and rendering each context to the buffer in 413 Device->Contexts array and rendering each context to the buffer in
404 turn. By temporally setting Device->NumContexts to 1 and adjusting 414 turn. By temporally setting Device->NumContexts to 1 and adjusting
405 the Device's context list to put the desired context-to-be-rendered 415 the Device's context list to put the desired context-to-be-rendered
406 into position 0, we can get trick =OpenAL= into rendering each slave 416 into position 0, we can get trick =OpenAL= into rendering each context
407 context separate from all the others. 417 separate from all the others.
408 418
409 ** Main Device Loop 419 ** Main Device Loop
410 #+name: main-loop 420 #+name: main-loop
411 #+begin_src C 421 #+begin_src C
412 //////////////////// Main Device Loop 422 //////////////////// Main Device Loop
416 */ 426 */
417 static void init(ALCdevice *Device){ 427 static void init(ALCdevice *Device){
418 ALCcontext *masterContext = alcGetCurrentContext(); 428 ALCcontext *masterContext = alcGetCurrentContext();
419 addContext(Device, masterContext); 429 addContext(Device, masterContext);
420 } 430 }
421
422 431
423 static void renderData(ALCdevice *Device, int samples){ 432 static void renderData(ALCdevice *Device, int samples){
424 if(!Device->Connected){return;} 433 if(!Device->Connected){return;}
425 send_data *data = (send_data*)Device->ExtraData; 434 send_data *data = (send_data*)Device->ExtraData;
426 ALCcontext *current = alcGetCurrentContext(); 435 ALCcontext *current = alcGetCurrentContext();
449 alcMakeContextCurrent(current); 458 alcMakeContextCurrent(current);
450 } 459 }
451 #+end_src 460 #+end_src
452 461
453 The main loop synchronizes the master LWJGL context with all the slave 462 The main loop synchronizes the master LWJGL context with all the slave
454 contexts, then walks each context, rendering just that context to it's 463 contexts, then iterates through each context, rendering just that
455 audio-sample storage buffer. 464 context to it's audio-sample storage buffer.
456 465
457 ** JNI Methods 466 ** JNI Methods
458 467
459 At this point, we have the ability to create multiple listeners by 468 At this point, we have the ability to create multiple listeners by
460 using the master/slave context trick, and the rendered audio data is 469 using the master/slave context trick, and the rendered audio data is
461 waiting patiently in internal buffers, one for each listener. We need 470 waiting patiently in internal buffers, one for each listener. We need
462 a way to transport this information to Java, and also a way to drive 471 a way to transport this information to Java, and also a way to drive
463 this device from Java. The following JNI interface code is inspired 472 this device from Java. The following JNI interface code is inspired
464 by the way LWJGL interfaces with =OpenAL=. 473 by the LWJGL JNI interface to =OpenAL=.
465 474
466 *** step 475 *** Stepping the Device
467 #+name: jni-step 476 #+name: jni-step
468 #+begin_src C 477 #+begin_src C
469 //////////////////// JNI Methods 478 //////////////////// JNI Methods
470 479
471 #include "com_aurellem_send_AudioSend.h" 480 #include "com_aurellem_send_AudioSend.h"
488 complicated AI whose mind takes 100 seconds of computer time to 497 complicated AI whose mind takes 100 seconds of computer time to
489 simulate 1 second of AI-time would miss almost all of the sound in 498 simulate 1 second of AI-time would miss almost all of the sound in
490 its environment. 499 its environment.
491 500
492 501
493 *** getSamples 502 *** Device->Java Data Transport
494 #+name: jni-get-samples 503 #+name: jni-get-samples
495 #+begin_src C 504 #+begin_src C
496 /* 505 /*
497 * Class: com_aurellem_send_AudioSend 506 * Class: com_aurellem_send_AudioSend
498 * Method: ngetSamples 507 * Method: ngetSamples
637 0); 646 0);
638 return format; 647 return format;
639 } 648 }
640 #+end_src 649 #+end_src
641 650
642 ** Boring Device management stuff 651 ** Boring Device Management Stuff / Memory Cleanup
643 This code is more-or-less copied verbatim from the other =OpenAL= 652 This code is more-or-less copied verbatim from the other =OpenAL=
644 backends. It's the basis for =OpenAL='s primitive object system. 653 Devices. It's the basis for =OpenAL='s primitive object system.
645 #+name: device-init 654 #+name: device-init
646 #+begin_src C 655 #+begin_src C
647 //////////////////// Device Initialization / Management 656 //////////////////// Device Initialization / Management
648 657
649 static const ALCchar sendDevice[] = "Multiple Audio Send"; 658 static const ALCchar sendDevice[] = "Multiple Audio Send";
730 #+end_src 739 #+end_src
731 740
732 * The Java interface, =AudioSend= 741 * The Java interface, =AudioSend=
733 742
734 The Java interface to the Send Device follows naturally from the JNI 743 The Java interface to the Send Device follows naturally from the JNI
735 definitions. It is included here for completeness. The only thing here 744 definitions. The only thing here of note is the =deviceID=. This is
736 of note is the =deviceID=. This is available from LWJGL, but to only 745 available from LWJGL, but to only way to get it is with reflection.
737 way to get it is reflection. Unfortunately, there is no other way to 746 Unfortunately, there is no other way to control the Send device than
738 control the Send device than to obtain a pointer to it. 747 to obtain a pointer to it.
739 748
740 #+include: "../java/src/com/aurellem/send/AudioSend.java" src java :exports code 749 #+include: "../../audio-send/java/src/com/aurellem/send/AudioSend.java" src java
750
751 * The Java Audio Renderer, =AudioSendRenderer=
752
753 #+include: "../../jmeCapture/src/com/aurellem/capture/audio/AudioSendRenderer.java" src java
754
755 The =AudioSendRenderer= is a modified version of the
756 =LwjglAudioRenderer= which implements the =MultiListener= interface to
757 provide access and creation of more than one =Listener= object.
758
759 ** MultiListener.java
760
761 #+include: "../../jmeCapture/src/com/aurellem/capture/audio/MultiListener.java" src java
762
763 ** SoundProcessors are like SceneProcessors
764
765 A =SoundProcessor= is analgous to a =SceneProcessor=. Every frame, the
766 =SoundProcessor= registered with a given =Listener= recieves the
767 rendered sound data and can do whatever processing it wants with it.
768
769 #+include "../../jmeCapture/src/com/aurellem/capture/audio/SoundProcessor.java" src java
741 770
742 * Finally, Ears in clojure! 771 * Finally, Ears in clojure!
743 772
744 Now that the infrastructure is complete the clojure ear abstraction is 773 Now that the =C= and =Java= infrastructure is complete, the clojure
745 simple. Just as there were =SceneProcessors= for vision, there are 774 hearing abstraction is simple and closely parallels the [[./vision.org][vision]]
746 now =SoundProcessors= for hearing. 775 abstraction.
747 776
748 #+include "../../jmeCapture/src/com/aurellem/capture/audio/SoundProcessor.java" src java 777 ** Hearing Pipeline
749 778
750 779 All sound rendering is done in the CPU, so =(hearing-pipeline)= is
780 much less complicated than =(vision-pipelie)= The bytes available in
781 the ByteBuffer obtained from the =send= Device have different meanings
782 dependant upon the particular hardware or your system. That is why
783 the =AudioFormat= object is necessary to provide the meaning that the
784 raw bytes lack. =(byteBuffer->pulse-vector)= uses the excellent
785 conversion facilities from [[http://www.tritonus.org/ ][tritonus]] ([[http://tritonus.sourceforge.net/apidoc/org/tritonus/share/sampled/FloatSampleTools.html#byte2floatInterleaved%2528byte%5B%5D,%2520int,%2520float%5B%5D,%2520int,%2520int,%2520javax.sound.sampled.AudioFormat%2529][javadoc]]) to generate a clojure vector of
786 floats which represent the linear PCM encoded waveform of the
787 sound. With linear PCM (pulse code modulation) -1.0 represents maximum
788 rarefaction of the air while 1.0 represents maximum compression of the
789 air at a given instant.
751 790
752 #+name: ears 791 #+name: ears
792 #+begin_src clojure
793 (in-ns 'cortex.hearing)
794
795 (defn hearing-pipeline
796 "Creates a SoundProcessor which wraps a sound processing
797 continuation function. The continuation is a function that takes
798 [#^ByteBuffer b #^Integer int numSamples #^AudioFormat af ], each of which
799 has already been apprpiately sized."
800 [continuation]
801 (proxy [SoundProcessor] []
802 (cleanup [])
803 (process
804 [#^ByteBuffer audioSamples numSamples #^AudioFormat audioFormat]
805 (continuation audioSamples numSamples audioFormat))))
806
807 (defn byteBuffer->pulse-vector
808 "Extract the sound samples from the byteBuffer as a PCM encoded
809 waveform with values ranging from -1.0 to 1.0 into a vector of
810 floats."
811 [#^ByteBuffer audioSamples numSamples #^AudioFormat audioFormat]
812 (let [num-floats (/ numSamples (.getFrameSize audioFormat))
813 bytes (byte-array numSamples)
814 floats (float-array num-floats)]
815 (.get audioSamples bytes 0 numSamples)
816 (FloatSampleTools/byte2floatInterleaved
817 bytes 0 floats 0 num-floats audioFormat)
818 (vec floats)))
819 #+end_src
820
821 ** Physical Ears
822
823 Together, these three functions define how ears found in a specially
824 prepared blender file will be translated to =Listener= objects in a
825 simulation. =(ears)= extracts all the children of to top level node
826 named "ears". =(add-ear!)= and =(update-listener-velocity!)= use
827 =(bind-sense)= to bind a =Listener= object located at the initial
828 position of an "ear" node to the closest physical object in the
829 creature. That =Listener= will stay in the same orientation to the
830 object with which it is bound, just as the camera in the [[http://aurellem.localhost/cortex/html/sense.html#sec-4-1][sense binding
831 demonstration]]. =OpenAL= simulates the doppler effect for moving
832 listeners, =(update-listener-velocity!)= ensures that this velocity
833 information is always up-to-date.
834
835 #+begin_src clojure
836 (defvar
837 ^{:arglists '([creature])}
838 ears
839 (sense-nodes "ears")
840 "Return the children of the creature's \"ears\" node.")
841
842 (defn update-listener-velocity!
843 "Update the listener's velocity every update loop."
844 [#^Spatial obj #^Listener lis]
845 (let [old-position (atom (.getLocation lis))]
846 (.addControl
847 obj
848 (proxy [AbstractControl] []
849 (controlUpdate [tpf]
850 (let [new-position (.getLocation lis)]
851 (.setVelocity
852 lis
853 (.mult (.subtract new-position @old-position)
854 (float (/ tpf))))
855 (reset! old-position new-position)))
856 (controlRender [_ _])))))
857
858 (defn add-ear!
859 "Create a Listener centered on the current position of 'ear
860 which follows the closest physical node in 'creature and
861 sends sound data to 'continuation."
862 [#^Application world #^Node creature #^Spatial ear continuation]
863 (let [target (closest-node creature ear)
864 lis (Listener.)
865 audio-renderer (.getAudioRenderer world)
866 sp (hearing-pipeline continuation)]
867 (.setLocation lis (.getWorldTranslation ear))
868 (.setRotation lis (.getWorldRotation ear))
869 (bind-sense target lis)
870 (update-listener-velocity! target lis)
871 (.addListener audio-renderer lis)
872 (.registerSoundProcessor audio-renderer lis sp)))
873 #+end_src
874
875 ** Ear Creation
876
877 #+begin_src clojure
878 (defn hearing-kernel
879 "Returns a functon which returns auditory sensory data when called
880 inside a running simulation."
881 [#^Node creature #^Spatial ear]
882 (let [hearing-data (atom [])
883 register-listener!
884 (runonce
885 (fn [#^Application world]
886 (add-ear!
887 world creature ear
888 (comp #(reset! hearing-data %)
889 byteBuffer->pulse-vector))))]
890 (fn [#^Application world]
891 (register-listener! world)
892 (let [data @hearing-data
893 topology
894 (vec (map #(vector % 0) (range 0 (count data))))]
895 [topology data]))))
896
897 (defn hearing!
898 "Endow the creature in a particular world with the sense of
899 hearing. Will return a sequence of functions, one for each ear,
900 which when called will return the auditory data from that ear."
901 [#^Node creature]
902 (for [ear (ears creature)]
903 (hearing-kernel creature ear)))
904 #+end_src
905
906 Each function returned by =(hearing-kernel!)= will register a new
907 =Listener= with the simulation the first time it is called. Each time
908 it is called, the hearing-function will return a vector of linear PCM
909 encoded sound data that was heard since the last frame. The size of
910 this vector is of course determined by the overall framerate of the
911 game. With a constant framerate of 60 frames per second and a sampling
912 frequency of 44,100 samples per second, the vector will have exactly
913 735 elements.
914
915 ** Visualizing Hearing
916
917 This is a simple visualization function which displaye the waveform
918 reported by the simulated sense of hearing. It converts the values
919 reported in the vector returned by the hearing function from the range
920 [-1.0, 1.0] to the range [0 255], converts to integer, and displays
921 the number as a greyscale pixel.
922
923 #+begin_src clojure
924 (defn view-hearing
925 "Creates a function which accepts a list of auditory data and
926 display each element of the list to the screen as an image."
927 []
928 (view-sense
929 (fn [[coords sensor-data]]
930 (let [pixel-data
931 (vec
932 (map
933 #(rem (int (* 255 (/ (+ 1 %) 2))) 256)
934 sensor-data))
935 height 50
936 image (BufferedImage. (count coords) height
937 BufferedImage/TYPE_INT_RGB)]
938 (dorun
939 (for [x (range (count coords))]
940 (dorun
941 (for [y (range height)]
942 (let [raw-sensor (pixel-data x)]
943 (.setRGB image x y (gray raw-sensor)))))))
944 image))))
945 #+end_src
946
947 * Testing Hearing
948
949 ** Advanced Java Example
950
951 I wrote a test case in Java that demonstrates the use of the Java
952 components of this hearing system. It is part of a larger java library
953 to capture perfect Audio from jMonkeyEngine. Some of the clojure
954 constructs above are partially reiterated in the java source file. But
955 first, the video! As far as I know this is the first instance of
956 multiple simulated listeners in a virtual environment using OpenAL.
957
958 #+begin_html
959 <div class="figure">
960 <center>
961 <video controls="controls" width="500">
962 <source src="../video/java-hearing-test.ogg" type="video/ogg"
963 preload="none" poster="../images/aurellem-1280x480.png" />
964 </video>
965 </center>
966 <p>The blue ball is emitting a constant sound. Each blue box is
967 listening for sound, and will change color from blue to green if it
968 detects sound which is louder than a certain threshold. As the blue
969 sphere travels along the path, it excites each of the cubes in turn.</p>
970 </div>
971
972 #+end_html
973
974 #+include "../../jmeCapture/src/com/aurellem/capture/examples/Advanced.java" src java
975
976 Here is a small clojure program to drive the java program and make it
977 available as part of my test suite.
978
979 #+name: test-hearing
980 #+begin_src clojure
981 (in-ns 'cortex.test.hearing)
982
983 (defn test-java-hearing
984 "Testing hearing:
985 You should see a blue sphere flying around several
986 cubes. As the sphere approaches each cube, it turns
987 green."
988 []
989 (doto (com.aurellem.capture.examples.Advanced.)
990 (.setSettings
991 (doto (AppSettings. true)
992 (.setAudioRenderer "Send")))
993 (.setShowSettings false)
994 (.setPauseOnLostFocus false)))
995 #+end_src
996
997 ** Adding Hearing to the Worm
998
999
1000
1001 * Headers
1002
1003 #+name: hearing-header
753 #+begin_src clojure 1004 #+begin_src clojure
754 (ns cortex.hearing 1005 (ns cortex.hearing
755 "Simulate the sense of hearing in jMonkeyEngine3. Enables multiple 1006 "Simulate the sense of hearing in jMonkeyEngine3. Enables multiple
756 listeners at different positions in the same world. Automatically 1007 listeners at different positions in the same world. Automatically
757 reads ear-nodes from specially prepared blender files and 1008 reads ear-nodes from specially prepared blender files and
767 (:import javax.sound.sampled.AudioFormat) 1018 (:import javax.sound.sampled.AudioFormat)
768 (:import (com.jme3.scene Spatial Node)) 1019 (:import (com.jme3.scene Spatial Node))
769 (:import com.jme3.audio.Listener) 1020 (:import com.jme3.audio.Listener)
770 (:import com.jme3.app.Application) 1021 (:import com.jme3.app.Application)
771 (:import com.jme3.scene.control.AbstractControl)) 1022 (:import com.jme3.scene.control.AbstractControl))
772 1023 #+end_src
773 (defn sound-processor 1024
774 "Deals with converting ByteBuffers into Vectors of floats so that 1025 #+begin_src clojure
775 the continuation functions can be defined in terms of immutable
776 stuff."
777 [continuation]
778 (proxy [SoundProcessor] []
779 (cleanup [])
780 (process
781 [#^ByteBuffer audioSamples numSamples #^AudioFormat audioFormat]
782 (let [bytes (byte-array numSamples)
783 num-floats (/ numSamples (.getFrameSize audioFormat))
784 floats (float-array num-floats)]
785 (.get audioSamples bytes 0 numSamples)
786 (FloatSampleTools/byte2floatInterleaved
787 bytes 0 floats 0 num-floats audioFormat)
788 (continuation
789 (vec floats))))))
790
791 (defvar
792 ^{:arglists '([creature])}
793 ears
794 (sense-nodes "ears")
795 "Return the children of the creature's \"ears\" node.")
796
797 (defn update-listener-velocity!
798 "Update the listener's velocity every update loop."
799 [#^Spatial obj #^Listener lis]
800 (let [old-position (atom (.getLocation lis))]
801 (.addControl
802 obj
803 (proxy [AbstractControl] []
804 (controlUpdate [tpf]
805 (let [new-position (.getLocation lis)]
806 (.setVelocity
807 lis
808 (.mult (.subtract new-position @old-position)
809 (float (/ tpf))))
810 (reset! old-position new-position)))
811 (controlRender [_ _])))))
812
813 (defn add-ear!
814 "Create a Listener centered on the current position of 'ear
815 which follows the closest physical node in 'creature and
816 sends sound data to 'continuation."
817 [#^Application world #^Node creature #^Spatial ear continuation]
818 (let [target (closest-node creature ear)
819 lis (Listener.)
820 audio-renderer (.getAudioRenderer world)
821 sp (sound-processor continuation)]
822 (.setLocation lis (.getWorldTranslation ear))
823 (.setRotation lis (.getWorldRotation ear))
824 (bind-sense target lis)
825 (update-listener-velocity! target lis)
826 (.addListener audio-renderer lis)
827 (.registerSoundProcessor audio-renderer lis sp)))
828
829 (defn hearing-fn
830 "Returns a functon which returns auditory sensory data when called
831 inside a running simulation."
832 [#^Node creature #^Spatial ear]
833 (let [hearing-data (atom [])
834 register-listener!
835 (runonce
836 (fn [#^Application world]
837 (add-ear!
838 world creature ear
839 (fn [data]
840 (reset! hearing-data (vec data))))))]
841 (fn [#^Application world]
842 (register-listener! world)
843 (let [data @hearing-data
844 topology
845 (vec (map #(vector % 0) (range 0 (count data))))
846 scaled-data
847 (vec
848 (map
849 #(rem (int (* 255 (/ (+ 1 %) 2))) 256)
850 data))]
851 [topology scaled-data]))))
852
853 (defn hearing!
854 "Endow the creature in a particular world with the sense of
855 hearing. Will return a sequence of functions, one for each ear,
856 which when called will return the auditory data from that ear."
857 [#^Node creature]
858 (for [ear (ears creature)]
859 (hearing-fn creature ear)))
860
861 (defn view-hearing
862 "Creates a function which accepts a list of auditory data and
863 display each element of the list to the screen as an image."
864 []
865 (view-sense
866 (fn [[coords sensor-data]]
867 (let [height 50
868 image (BufferedImage. (count coords) height
869 BufferedImage/TYPE_INT_RGB)]
870 (dorun
871 (for [x (range (count coords))]
872 (dorun
873 (for [y (range height)]
874 (let [raw-sensor (sensor-data x)]
875 (.setRGB image x y (gray raw-sensor)))))))
876 image))))
877
878 #+end_src
879
880 #+results: ears
881 : #'cortex.hearing/hearing!
882
883 * Example
884
885 #+name: test-hearing
886 #+begin_src clojure :results silent
887 (ns cortex.test.hearing 1026 (ns cortex.test.hearing
888 (:use (cortex world util hearing)) 1027 (:use (cortex world util hearing))
889 (:import (com.jme3.audio AudioNode Listener)) 1028 (:import (com.jme3.audio AudioNode Listener))
890 (:import com.jme3.scene.Node 1029 (:import com.jme3.scene.Node
891 com.jme3.system.AppSettings)) 1030 com.jme3.system.AppSettings))
892 1031 #+end_src
893 (defn setup-fn [world] 1032
894 (let [listener (Listener.)] 1033
895 (add-ear world listener #(println-repl (nth % 0))))) 1034 * Next
896
897 (defn play-sound [node world value]
898 (if (not value)
899 (do
900 (.playSource (.getAudioRenderer world) node))))
901
902 (defn test-basic-hearing []
903 (let [node1 (AudioNode. (asset-manager) "Sounds/pure.wav" false false)]
904 (world
905 (Node.)
906 {"key-space" (partial play-sound node1)}
907 setup-fn
908 no-op)))
909
910 (defn test-advanced-hearing
911 "Testing hearing:
912 You should see a blue sphere flying around several
913 cubes. As the sphere approaches each cube, it turns
914 green."
915 []
916 (doto (com.aurellem.capture.examples.Advanced.)
917 (.setSettings
918 (doto (AppSettings. true)
919 (.setAudioRenderer "Send")))
920 (.setShowSettings false)
921 (.setPauseOnLostFocus false)))
922
923 #+end_src
924
925 This extremely basic program prints out the first sample it encounters
926 at every time stamp. You can see the rendered sound being printed at
927 the REPL.
928
929 - As a bonus, this method of capturing audio for AI can also be used 1035 - As a bonus, this method of capturing audio for AI can also be used
930 to capture perfect audio from a jMonkeyEngine application, for use 1036 to capture perfect audio from a jMonkeyEngine application, for use
931 in demos and the like. 1037 in demos and the like.
932 1038
933 1039
1040
934 * COMMENT Code Generation 1041 * COMMENT Code Generation
935 1042
936 #+begin_src clojure :tangle ../src/cortex/hearing.clj 1043 #+begin_src clojure :tangle ../src/cortex/hearing.clj
1044 <<hearing-header>>
937 <<ears>> 1045 <<ears>>
938 #+end_src 1046 #+end_src
939 1047
940 #+begin_src clojure :tangle ../src/cortex/test/hearing.clj 1048 #+begin_src clojure :tangle ../src/cortex/test/hearing.clj
941 <<test-hearing>> 1049 <<test-hearing>>