comparison org/ear.org @ 19:22ac5a0367cd

finally, a first pass at ear.org
author Robert McIntyre <rlm@mit.edu>
date Thu, 03 Nov 2011 14:54:45 -0700
parents 1e201037f666
children e8ae40c9848c
comparison
equal deleted inserted replaced
18:1e201037f666 19:22ac5a0367cd
44 44
45 To make many AI creatures in jMonkeyEngine that can each hear the 45 To make many AI creatures in jMonkeyEngine that can each hear the
46 world from their own perspective, it is necessary to go all the way 46 world from their own perspective, it is necessary to go all the way
47 back to =OpenAL= and implement support for simulated hearing there. 47 back to =OpenAL= and implement support for simulated hearing there.
48 48
49 * Extending =OpenAL=
49 ** =OpenAL= Devices 50 ** =OpenAL= Devices
50 51
51 =OpenAL= goes to great lengths to support many different systems, all 52 =OpenAL= goes to great lengths to support many different systems, all
52 with different sound capabilities and interfaces. It acomplishes this 53 with different sound capabilities and interfaces. It acomplishes this
53 difficult task by providing code for many different sound backends in 54 difficult task by providing code for many different sound backends in
72 available. 73 available.
73 74
74 Therefore, in order to support multiple listeners, and get the sound 75 Therefore, in order to support multiple listeners, and get the sound
75 data in a form that the AIs can use, it is necessary to create a new 76 data in a form that the AIs can use, it is necessary to create a new
76 Device, which supports this features. 77 Device, which supports this features.
77
78 78
79 ** The Send Device 79 ** The Send Device
80 Adding a device to OpenAL is rather tricky -- there are five separate 80 Adding a device to OpenAL is rather tricky -- there are five separate
81 files in the =OpenAL= source tree that must be modified to do so. I've 81 files in the =OpenAL= source tree that must be modified to do so. I've
82 documented this process [[./add-new-device.org][here]] for anyone who is interested. 82 documented this process [[./add-new-device.org][here]] for anyone who is interested.
329 Most of the hard work in Context Synchronization is done in 329 Most of the hard work in Context Synchronization is done in
330 =syncSources()=. The only thing that =syncContexts()= has to worry 330 =syncSources()=. The only thing that =syncContexts()= has to worry
331 about is automoatically creating new sources whenever a slave context 331 about is automoatically creating new sources whenever a slave context
332 does not have the same number of sources as the master context. 332 does not have the same number of sources as the master context.
333 333
334 * Context Creation 334 ** Context Creation
335 #+begin_src C 335 #+begin_src C
336 static void addContext(ALCdevice *Device, ALCcontext *context){ 336 static void addContext(ALCdevice *Device, ALCcontext *context){
337 send_data *data = (send_data*)Device->ExtraData; 337 send_data *data = (send_data*)Device->ExtraData;
338 // expand array if necessary 338 // expand array if necessary
339 if (data->numContexts >= data->maxContexts){ 339 if (data->numContexts >= data->maxContexts){
357 Here, the slave context is created, and it's data is stored in the 357 Here, the slave context is created, and it's data is stored in the
358 device-wide =ExtraData= structure. The =renderBuffer= that is created 358 device-wide =ExtraData= structure. The =renderBuffer= that is created
359 here is where the rendered sound samples for this slave context will 359 here is where the rendered sound samples for this slave context will
360 eventually go. 360 eventually go.
361 361
362 * Context Switching 362 ** Context Switching
363 #+begin_src C 363 #+begin_src C
364 //////////////////// Context Switching 364 //////////////////// Context Switching
365 365
366 /* A device brings along with it two pieces of state 366 /* A device brings along with it two pieces of state
367 * which have to be swapped in and out with each context. 367 * which have to be swapped in and out with each context.
449 449
450 The main loop synchronizes the master LWJGL context with all the slave 450 The main loop synchronizes the master LWJGL context with all the slave
451 contexts, then walks each context, rendering just that context to it's 451 contexts, then walks each context, rendering just that context to it's
452 audio-sample storage buffer. 452 audio-sample storage buffer.
453 453
454 * JNI Methods 454 ** JNI Methods
455
456 At this point, we have the ability to create multiple listeners by
457 using the master/slave context trick, and the rendered audio data is
458 waiting patiently in internal buffers, one for each listener. We need
459 a way to transport this information to Java, and also a way to drive
460 this device from Java. The following JNI interface code is inspired
461 by the way LWJGL interfaces with =OpenAL=.
462
463 *** step
464
455 #+begin_src C 465 #+begin_src C
456 //////////////////// JNI Methods 466 //////////////////// JNI Methods
457 467
458 #include "com_aurellem_send_AudioSend.h" 468 #include "com_aurellem_send_AudioSend.h"
459 469
465 JNIEXPORT void JNICALL Java_com_aurellem_send_AudioSend_nstep 475 JNIEXPORT void JNICALL Java_com_aurellem_send_AudioSend_nstep
466 (JNIEnv *env, jclass clazz, jlong device, jint samples){ 476 (JNIEnv *env, jclass clazz, jlong device, jint samples){
467 UNUSED(env);UNUSED(clazz);UNUSED(device); 477 UNUSED(env);UNUSED(clazz);UNUSED(device);
468 renderData((ALCdevice*)((intptr_t)device), samples); 478 renderData((ALCdevice*)((intptr_t)device), samples);
469 } 479 }
470 480 #+end_src
481 This device, unlike most of the other devices in =OpenAL=, does not
482 render sound unless asked. This enables the system to slow down or
483 speed up depending on the needs of the AIs who are using it to
484 listen. If the device tried to render samples in real-time, a
485 complicated AI whose mind takes 100 seconds of computer time to
486 simulate 1 second of AI-time would miss almost all of the sound in
487 its environment.
488
489
490 *** getSamples
491 #+begin_src C
471 /* 492 /*
472 * Class: com_aurellem_send_AudioSend 493 * Class: com_aurellem_send_AudioSend
473 * Method: ngetSamples 494 * Method: ngetSamples
474 * Signature: (JLjava/nio/ByteBuffer;III)V 495 * Signature: (JLjava/nio/ByteBuffer;III)V
475 */ 496 */
484 send_data *data = (send_data*)recorder->ExtraData; 505 send_data *data = (send_data*)recorder->ExtraData;
485 if ((ALuint)n > data->numContexts){return;} 506 if ((ALuint)n > data->numContexts){return;}
486 memcpy(buffer_address, data->contexts[n]->renderBuffer, 507 memcpy(buffer_address, data->contexts[n]->renderBuffer,
487 BytesFromDevFmt(recorder->FmtType) * recorder->NumChan * samples); 508 BytesFromDevFmt(recorder->FmtType) * recorder->NumChan * samples);
488 } 509 }
489 510 #+end_src
511
512 This is the transport layer between C and Java that will eventually
513 allow us to access rendered sound data from clojure.
514
515 *** Listener Management
516
517 =addListener=, =setNthListenerf=, and =setNthListener3f= are
518 necessary to change the properties of any listener other than the
519 master one, since only the listener of the current active context is
520 affected by the normal =OpenAL= listener calls.
521
522 #+begin_src C
490 /* 523 /*
491 * Class: com_aurellem_send_AudioSend 524 * Class: com_aurellem_send_AudioSend
492 * Method: naddListener 525 * Method: naddListener
493 * Signature: (J)V 526 * Signature: (J)V
494 */ 527 */
539 if ((ALuint)contextNum > data->numContexts){return;} 572 if ((ALuint)contextNum > data->numContexts){return;}
540 alcMakeContextCurrent(data->contexts[contextNum]->ctx); 573 alcMakeContextCurrent(data->contexts[contextNum]->ctx);
541 alListenerf(param, v1); 574 alListenerf(param, v1);
542 alcMakeContextCurrent(current); 575 alcMakeContextCurrent(current);
543 } 576 }
544 577 #+end_src
578
579 *** Initilazation
580 =initDevice= is called from the Java side after LWJGL has created its
581 context, and before any calls to =addListener=. It establishes the
582 LWJGL context as the master context.
583
584 =getAudioFormat= is a convienence function that uses JNI to build up a
585 =javax.sound.sampled.AudioFormat= object from data in the Device. This
586 way, there is no ambiguity about what the bits created by =step= and
587 returned by =getSamples= mean.
588
589 #+begin_src C
545 /* 590 /*
546 * Class: com_aurellem_send_AudioSend 591 * Class: com_aurellem_send_AudioSend
547 * Method: ninitDevice 592 * Method: ninitDevice
548 * Signature: (J)V 593 * Signature: (J)V
549 */ 594 */
550 JNIEXPORT void JNICALL Java_com_aurellem_send_AudioSend_ninitDevice 595 JNIEXPORT void JNICALL Java_com_aurellem_send_AudioSend_ninitDevice
551 (JNIEnv *env, jclass clazz, jlong device){ 596 (JNIEnv *env, jclass clazz, jlong device){
552 UNUSED(env);UNUSED(clazz); 597 UNUSED(env);UNUSED(clazz);
553
554 ALCdevice *Device = (ALCdevice*) ((intptr_t)device); 598 ALCdevice *Device = (ALCdevice*) ((intptr_t)device);
555 init(Device); 599 init(Device);
556 600 }
557 }
558
559 601
560 /* 602 /*
561 * Class: com_aurellem_send_AudioSend 603 * Class: com_aurellem_send_AudioSend
562 * Method: ngetAudioFormat 604 * Method: ngetAudioFormat
563 * Signature: (J)Ljavax/sound/sampled/AudioFormat; 605 * Signature: (J)Ljavax/sound/sampled/AudioFormat;
569 (*env)->FindClass(env, "javax/sound/sampled/AudioFormat"); 611 (*env)->FindClass(env, "javax/sound/sampled/AudioFormat");
570 jmethodID AudioFormatConstructor = 612 jmethodID AudioFormatConstructor =
571 (*env)->GetMethodID(env, AudioFormatClass, "<init>", "(FIIZZ)V"); 613 (*env)->GetMethodID(env, AudioFormatClass, "<init>", "(FIIZZ)V");
572 614
573 ALCdevice *Device = (ALCdevice*) ((intptr_t)device); 615 ALCdevice *Device = (ALCdevice*) ((intptr_t)device);
574
575 //float frequency
576
577 int isSigned; 616 int isSigned;
578 switch (Device->FmtType) 617 switch (Device->FmtType)
579 { 618 {
580 case DevFmtUByte: 619 case DevFmtUByte:
581 case DevFmtUShort: isSigned = 0; break; 620 case DevFmtUShort: isSigned = 0; break;
582 default : isSigned = 1; 621 default : isSigned = 1;
583 } 622 }
584 float frequency = Device->Frequency; 623 float frequency = Device->Frequency;
585 int bitsPerFrame = (8 * BytesFromDevFmt(Device->FmtType)); 624 int bitsPerFrame = (8 * BytesFromDevFmt(Device->FmtType));
586 int channels = Device->NumChan; 625 int channels = Device->NumChan;
587
588
589 //printf("freq = %f, bpf = %d, channels = %d, signed? = %d\n",
590 // frequency, bitsPerFrame, channels, isSigned);
591
592 jobject format = (*env)-> 626 jobject format = (*env)->
593 NewObject( 627 NewObject(
594 env,AudioFormatClass,AudioFormatConstructor, 628 env,AudioFormatClass,AudioFormatConstructor,
595 frequency, 629 frequency,
596 bitsPerFrame, 630 bitsPerFrame,
597 channels, 631 channels,
598 isSigned, 632 isSigned,
599 0); 633 0);
600 return format; 634 return format;
601 } 635 }
602 636 #+end_src
637
638 *** Boring Device management stuff
639 This code is more-or-less copied verbatim from the other =OpenAL=
640 backends. It's the basis for =OpenAL='s primitive object system.
641
642 #+begin_src C
603 //////////////////// Device Initilization / Management 643 //////////////////// Device Initilization / Management
604 644
605 static const ALCchar sendDevice[] = "Multiple Audio Send"; 645 static const ALCchar sendDevice[] = "Multiple Audio Send";
606 646
607 static ALCboolean send_open_playback(ALCdevice *device, 647 static ALCboolean send_open_playback(ALCdevice *device,
683 break; 723 break;
684 } 724 }
685 } 725 }
686 #+end_src 726 #+end_src
687 727
688 728 * The Java interface, =AudioSend=
689 729
690 730 The Java interface to the Send Device follows naturally from the JNI
691 731 definitions. It is included here for completeness. The only thing here
692 732 of note is the =deviceID=. This is available from LWJGL, but to only
693 733 way to get it is reflection. Unfornatuently, there is no other way to
734 control the Send device than to obtain a pointer to it.
735
736 #+include: "../java/src/com/aurellem/send/AudioSend.java" src java :exports code
737
738 * Finally, Ears in clojure!
739
740 Now that the infastructure is complete (modulo a few patches to
741 jMonkeyEngine3 to support accessing this modified version of =OpenAL=
742 that are not worth discussing), the clojure ear abstraction is rather
743 simple. Just as there were =SceneProcessors= for vision, there are
744 now =SoundProcessors= for hearing.
745
746 #+include "../../jmeCapture/src/com/aurellem/capture/audio/SoundProcessor.java" src java
694 747
695 #+srcname: ears 748 #+srcname: ears
696 #+begin_src clojure 749 #+begin_src clojure
697 (ns cortex.hearing) 750 (ns cortex.hearing
698 (use 'cortex.world) 751 "Simulate the sense of hearing in jMonkeyEngine3. Enables multiple
699 (use 'cortex.import) 752 listeners at different positions in the same world. Passes vectors
700 (use 'clojure.contrib.def) 753 of floats in the range [-1.0 -- 1.0] in PCM format to any arbitray
701 (cortex.import/mega-import-jme3) 754 function."
702 (rlm.rlm-commands/help) 755 {:author "Robert McIntyre"}
703 (import java.nio.ByteBuffer) 756 (:use (cortex world util))
704 (import java.awt.image.BufferedImage) 757 (:import java.nio.ByteBuffer)
705 (import java.awt.Color) 758 (:import org.tritonus.share.sampled.FloatSampleTools)
706 (import java.awt.Dimension) 759 (:import com.aurellem.capture.audio.SoundProcessor)
707 (import java.awt.Graphics) 760 (:import javax.sound.sampled.AudioFormat))
708 (import java.awt.Graphics2D) 761
709 (import java.awt.event.WindowAdapter)
710 (import java.awt.event.WindowEvent)
711 (import java.awt.image.BufferedImage)
712 (import java.nio.ByteBuffer)
713 (import javax.swing.JFrame)
714 (import javax.swing.JPanel)
715 (import javax.swing.SwingUtilities)
716 (import javax.swing.ImageIcon)
717 (import javax.swing.JOptionPane)
718 (import java.awt.image.ImageObserver)
719
720 (import 'com.jme3.capture.SoundProcessor)
721
722
723 (defn sound-processor 762 (defn sound-processor
724 "deals with converting ByteBuffers into Arrays of bytes so that the 763 "Deals with converting ByteBuffers into Vectors of floats so that
725 continuation functions can be defined in terms of immutable stuff." 764 the continuation functions can be defined in terms of immutable
765 stuff."
726 [continuation] 766 [continuation]
727 (proxy [SoundProcessor] [] 767 (proxy [SoundProcessor] []
728 (cleanup []) 768 (cleanup [])
729 (process 769 (process
730 [#^ByteBuffer audioSamples numSamples] 770 [#^ByteBuffer audioSamples numSamples #^AudioFormat audioFormat]
731 (no-exceptions 771 (let [bytes (byte-array numSamples)
732 (let [byte-array (byte-array numSamples)] 772 floats (float-array numSamples)]
733 (.get audioSamples byte-array 0 numSamples) 773 (.get audioSamples bytes 0 numSamples)
734 (continuation 774 (FloatSampleTools/byte2floatInterleaved
735 (vec byte-array))))))) 775 bytes 0 floats 0
776 (/ numSamples (.getFrameSize audioFormat)) audioFormat)
777 (continuation
778 (vec floats))))))
736 779
737 (defn add-ear 780 (defn add-ear
738 "add an ear to the world. The continuation function will be called 781 "Add an ear to the world. The continuation function will be called
739 on the FFT or the sounds which the ear hears in the given 782 on the FFT or the sounds which the ear hears in the given
740 timeframe. Sound is 3D." 783 timeframe. Sound is 3D."
741 [world listener continuation] 784 [world listener continuation]
742 (let [renderer (.getAudioRenderer world)] 785 (let [renderer (.getAudioRenderer world)]
743 (.addListener renderer listener) 786 (.addListener renderer listener)
744 (.registerSoundProcessor renderer listener 787 (.registerSoundProcessor renderer listener
745 (sound-processor continuation)) 788 (sound-processor continuation))
746 listener)) 789 listener))
747 790 #+end_src
748 #+end_src 791
749 792 * Example
750
751 793
752 #+srcname: test-hearing 794 #+srcname: test-hearing
753 #+begin_src clojure :results silent 795 #+begin_src clojure :results silent
754 (ns test.hearing) 796 (ns test.hearing
755 (use 'cortex.world) 797 (:use (cortex world util hearing))
756 (use 'cortex.import) 798 (:import (com.jme3.audio AudioNode Listener))
757 (use 'clojure.contrib.def) 799 (:import com.jme3.scene.Node))
758 (use 'body.ear)
759 (cortex.import/mega-import-jme3)
760 (rlm.rlm-commands/help)
761 800
762 (defn setup-fn [world] 801 (defn setup-fn [world]
763 (let [listener (Listener.)] 802 (let [listener (Listener.)]
764 (add-ear world listener #(println (nth % 0))))) 803 (add-ear world listener #(println-repl (nth % 0)))))
765 804
766 (defn play-sound [node world value] 805 (defn play-sound [node world value]
767 (if (not value) 806 (if (not value)
768 (do 807 (do
769 (.playSource (.getAudioRenderer world) node)))) 808 (.playSource (.getAudioRenderer world) node))))
770 809
771 (defn test-world [] 810 (defn test-basic-hearing []
772 (let [node1 (AudioNode. (asset-manager) "Sounds/pure.wav" false false)] 811 (.start
773 (world 812 (let [node1 (AudioNode. (asset-manager) "Sounds/pure.wav" false false)]
774 (Node.) 813 (world
775 {"key-space" (partial play-sound node1)} 814 (Node.)
776 setup-fn 815 {"key-space" (partial play-sound node1)}
777 no-op 816 setup-fn
778 ))) 817 no-op))))
779 818 #+end_src
780 819
781 #+end_src 820 This extremely basic program prints out the first sample it encounters
782 821 at every time stamp. You can see the rendered sound begin printed at
783 822 the REPL.
784
785 * Example
786 823
787 * COMMENT Code Generation 824 * COMMENT Code Generation
788 825
789 #+begin_src clojure :tangle ../../cortex/src/cortex/hearing.clj 826 #+begin_src clojure :tangle ../../cortex/src/cortex/hearing.clj
790 <<ears>> 827 <<ears>>
796 833
797 834
798 #+begin_src C :tangle ../Alc/backends/send.c 835 #+begin_src C :tangle ../Alc/backends/send.c
799 <<send>> 836 <<send>>
800 #+end_src 837 #+end_src
838
839