sex my girlfriend

singles au

gils gone wild

sexofree org com

hearts singles

for sex dates

women looking for married

ebony personals

hudson singles

housewife movies

norwegian singles

web camera girls

russian woman

one night stands

sex old wives

biker dating network

singles health insurance

miami gay escort

singlesonline

space booty

somali singles

sexi

christian singles sydney

mod singles

florida swing clubs

live gay chat

singles norwich

jewish singles chicago

onenightstand

www sexe

kolkata women seeking men

singles camping

dateng

scorts com

mp3 single

sex dates

singles in adelaide

phone sex uk

video adults

seniors dating services

online dating service business

dating for kids

singles camping holidays

las vegas singles events

single moms grants

singles manchester

progressive singles

singles dance

swinger date

sex finding

how to internet dating

turkey singles

adultmatchmaker com u

naughty dating

guys get laid

free freind finder

dating personals sites

single latin ladies

friend finder maxis

worst dating sites

speed dating vancouver

girls for sex

internet dating statistics

single girls in bangalore

escort agency los angeles

sex wive

sex dating personals

kolkata women seeking man

teenage personals

sex in fresno

female sex chat

online dating austin

big booty black woman

www beast dating

singles from around

dating latin men

www adultmatchmaker co

upstate singles

deammates

hot housewives pictures

oakland singles

http www iwantu com

marriage and dating

cyber cheating wives

parent dating uk

power exchange personals

jacksonville personals

the dating code

affairs cheating

sex hookups

phone sex web cam

swinger chats

swapping wifes

dating websites london

matchmaker service

local sex contacts

women having sex on cam

online dating montreal

free and online dating

sexphone chat

Prototyping Code in Clojure – A Simple Audio Displaying App 3

Posted by jonathan on February 24, 2008

Clojure is a new Lisp, created by Rich Hickey. The primary portal to all things Clojure is here on Source Forge.

I just got through prototyping a little app that takes in sound samples, and displays a stacked series of frequency spectra, and a 2d ‘sonogram’ type display. The running app looks like:

Frequency Spectra in Clojure

Not so easy to see, but the lower part of the screen is a 2d sonogram.

With the help of the KJ DSP library (which has a homepage on SourceForge), the app runs in real-ish time, and does a creditable job of displaying frequency spectra.

I hoped to try and get it running on Windows too, but the Java sampling api under Windows has defeated me for now. The code below works on a Mac Book Pro. Nothing else is tested.

The code is split into two main parts, which deal with getting sound samples, and with displaying a simple gui. I’ve reproduced the code below.

Note that this code isn’t complete, since I have a separate jar KJ.JAR which has the FFT code, and a NetBeans front end that provides isometric.gui (controls: start, stop, test:JButton, inputs:JCombobox, isopanel, sonopanel:JPanel).

; The following file is written by Jonathan Watmough
; This file is free for any use, providing this notice is preserved.
;
; This file works with the sound system on a Mac Book Pro.
; Nothing else is tested.

; imports
(import '(java.lang Thread))
(import '(java.nio ByteBuffer ShortBuffer))
(import '(javax.sound.sampled DataLine AudioSystem LineEvent LineListener AudioFormat))
;(import '(javax.sound.sampled.DataLine Info))

; supported audio filetypes
(def filetypes (. AudioSystem (getAudioFileTypes)))
(print (str "Supported audio: " (seq filetypes)))
(newline)

; supported mixers
(def mixer-info (seq (. AudioSystem (getMixerInfo))))

; get mixer-info, name, description of each mixer
(def mixer-info-list
  (map #(let [m %] { :mixer-info m
                     :name (. m (getName))
                     :description (. m (getDescription))}) mixer-info))

;
;---- user interface ----

; create a desired pcm audio format
; -> float sampleRate, int sampleSizeInBits, int channels, boolean signed, boolean bigEndian
(def format (new AudioFormat 44100 16 1 false true))
(def buffer-size (* 22050 2))   ; 44k = 1/2 sec x 2 bytes / sample mono

; set a ref for the open line
(def input (ref nil))

; from each mixer-info, get the target data lines (inputs)
(def input-lines
  (mapcat #(let [mix-info (:mixer-info %)
                 mixer    (. AudioSystem (getMixer mix-info))
                 targets  (. mixer (getTargetLineInfo))]
              (map #(let [target-info     %
                          get-line    (fn[]
                                      (let [mixer (. AudioSystem (getMixer mix-info))
                                            line  (. mixer (getLine target-info))]
                                      (do (. line (open format buffer-size))
                                          (. line (start))
                                          (sync nil
                                            ; ### should close old input - When @input ...
                                            (set input (. mixer (getLine target-info)))
                                            (. @input (start))))))
                          mixer-name  (. mix-info (getName))]
                      {:mixer-name  mixer-name
                       :get-line    get-line}) targets)) mixer-info-list))

; get a set of samples for level-checking in ui
; assume we are dealing with signed 16 bit samples (short = 2 x bytes)
; may want to sync on 'input'
(defn get-inst-samples ([num-samples]
  (do
    (if (not (. @input (isOpen)))
      (. @input (open format buffer-size)))
    (if (not (. @input (isRunning)))
      (. @input (start)))
    (let [bytes   (make-array (. Byte TYPE) (* 2 num-samples))
          bcount  (. @input (read bytes 0 (* 2 num-samples)))
          bbyte   (. ByteBuffer (wrap bytes))
          bshort  (. bbyte (asShortBuffer))
          shorts  (make-array (. Short TYPE) num-samples)
          ]
      (do (print "Read: " (str bcount) " bytes.")
          (newline)
          (. *out* (flush))
          (. bshort (get shorts 0 num-samples))
          shorts)))))

(comment

; "Built-in Microphone"  <-- we'll use this

; get the mixer info for the mic
(def mic-mixer-info
  (:mixer-info (first (filter #(= "Built-in Microphone" (:name %)) mixer-info-list))))

; get the built in mic mixer
(def mic (. AudioSystem (getMixer mic-mixer-info)))

; get the supported source and target lines for the mixer
(def sources (seq (. mic (getSourceLineInfo))))   ; nil
(def targets (seq (. mic (getTargetLineInfo))))   ; (interface TargetDataLine supporting 72 audio formats)

; get a target line
(def line-info (first targets))
(def mic-line (. mic (getLine line-info)))

; add a line listener for events on the line
(. mic-line (addLineListener
              (implement [LineListener]
                (update [evt]
                  (do (print "Event: " (. evt (getType)))
                      (newline)
                      (. *out* (flush)))))))

; check if we can get this format from the built in mic
(. mic-line (open format buffer-size))

; start the input
(. mic-line (start))	

; try looping and counting available samples
(dotimes i 100
  (print "Available data: " (. mic-line (available)))
  (newline)
  (. *out* (flush))
  (let [buffer  (make-array (. Byte TYPE) 2048)
        bcount  (. mic-line (read buffer 0 2048))
        bbyte   (. ByteBuffer (wrap buffer))
        bshort  (. bbyte (asShortBuffer))
        ]
    (print "Read: " bcount " bytes. Buffer state:" (str bshort))
    (print " ... Converted to short: "  (str (. bshort (get 0))))
    (newline))
  (. Thread (sleep 20)))   ; 1 milli sleep = 1/1000 of a sec = 44 samples

; stop the input
;(. mic-line (stop))

; close mic
;(. mic-line (close))

)

GUI Code
The following is the GUI part of the code. It generates some functions that draw over a couple of passed in controls. Create a simple GUI interface, then pass a couple of labels.

; osx sampling code
(load-file "iso-source.clj")

; load in the windows sampling code
;(load-file "iso-source-win.clj")

; graphics code
(import '(java.awt Color Polygon))
(import '(java.awt.event ActionListener))
(import '(java.awt.image BufferedImage VolatileImage))
(import '(javax.swing JLabel JFrame ImageIcon))
(import '(java.lang Thread))
(import '(java.util.concurrent Executors))

; make a set of functions that are associated with a graphcs in the ui
; this function is called with a couple of labels that we will draw on
(defn get-oscilloscope ([iso sono]
  (let [c   iso
        s   sono
        of  false
        oc  100
        pa  (ref nil)       ; store previous array so we can undraw
        ox  (ref 0)
        oy  (ref 0)

        ; might be neat to 'fade out'
        turn-off
          (fn []
            (print "Turned off. Fading.")
            (newline))

        ; draw an oscilloscope grid
        turn-on
          (fn []
            (let [g   (. c (getGraphics))
                  x   (. c (getX))
                  y   (. c (getY))
                  w   (. c (getWidth))
                  h   (. c (getHeight))
                  r   7
                  xs  (/ (- w 1) (- r 1))
                  ys  (/ (- h 1) (- r 1))]
              (do
                (. g (clearRect x y w h))
                (. g (setColor (. Color gray)))
                (. g (fillRect x y w h))
                (. g (setColor (. Color lightGray)))
                (dotimes ix r
                  (. g (drawLine (+ x (* ix xs)) y (+ x (* ix xs)) (+ y h))))
                (dotimes iy r
                  (. g (drawLine x (+ y (* iy ys)) (+ x w) (+ y (* iy ys)))))
                (. g (setColor (. Color white)))
                (. g (dispose)))))

        ; draw a set of samples, then xor out next time round
        draw-samples
          (fn [samples]
            (let [g (. c (getGraphics))
                  x (. c (getX))
                  y (. c (getY))
                  w (. c (getWidth))
                  h (. c (getHeight))
                  n (count samples)
                  s (/ w n)
                  m 32768                     ; signed 16-bit samples
                  ]
              (do (print "Drawing at " x "," y " size: " w " by " h ". Sample count: " n )
                  (newline)
                  (. *out* (flush))
                  (. g (setColor (. Color gray)))
                  (. g (setXORMode (. Color white)))

                  ; undraw the old one
                  (when @pa
                    (. g (drawPolygon @pa)))

                  ; draw the new one
                  (loop [i    0
                         xa   (make-array (. Integer TYPE) n)
                         ya   (make-array (. Integer TYPE) n)]
                    (if (< i n)
                      (let [nx (int (+ x (* i s)))
                            ny (int (- (/ h 2) (/ (* (aget samples i) h) m)))]
                        (do (aset-int xa i nx)
                            (aset-int ya i ny)
                            (recur (+ i 1) xa ya)))
                      (let [p (new Polygon xa ya n)]
                        (do (sync nil (set pa p))
                            (. g (drawPolygon p))))))

                  ; clean up graphics context
                  (. g (dispose)))))

        ; draw a 2d sonogram - need a better color scheme
        si  (ref 0)
        draw-sono
          (fn [samples]
            (let [g   (. s (getGraphics))
                  w   (. s (getWidth))
                  h   (. s (getHeight))
                  x   (. s (getX))
                  y   h
                  n   (/ (count samples) 4)
                  ss  (/ h n)
                  m   32768                     ; signed 16-bit samples
                  sx  (+ x (* ss @si))          ; start pos
                  ]
              (dotimes iy n
                (let [sy  (- y (* iy ss) ss)
                      gain   3                  ; sonogram gain
                      c   (int (* gain 255 (/ (aget samples iy) m)))
                      c   (if (> c 255) 255 c)]
                  (do (. g (setColor (new Color c c c)))
                      (. g (fillRect sx sy ss ss)))))
              (. g (dispose))
              (if (> (* @si ss) w)
                  (sync nil (set si 0))
                  (sync nil (set si (+ 1 @si))))))

        ; draw a frequency spectra and let it 'walk' down screen +x+y
        ; should clamp either end of spectra to zero to fix graphic glitches
        draw-frequencies
          (fn [spectra]
            (let [g (. c (getGraphics))
                  x (. c (getX))
                  y (. c (getY))
                  w (. c (getWidth))
                  h (. c (getHeight))
                  n (/ (count spectra) 4)
                  s (/ (- w 100) n)
                  m 32768                     ; signed 16-bit samples
                  ]
              (do ; bump iso
                  (sync nil
                    (set ox (+ @ox 1))
                    (set oy (+ @oy 1))
                    (when (> @ox (- (/ h 2) 40))
                      (turn-on)
                      (set ox 0)
                      (set oy 0)))

                  ; Fill a red polygon, and outline in white
                  (loop [i    0
                         xl   (make-array (. Integer TYPE) n)
                         yl   (make-array (. Integer TYPE) n)]
                    (if (< i n)
                      (let [nx  (+ x (* i s) @ox)
                            ny  (- (+ (/ h 2) @oy) (/ (* (aget spectra i) h) m))]
                        (do (aset-int xl i nx)
                            (aset-int yl i ny)
                            (recur (+ i 1) xl yl)))
                      (do (let [p   (new Polygon xl yl n)]
                          (. g (setColor (. Color red)))
                          (. g (fillPolygon p))
                          (. g (setColor (. Color white)))
                          (. g (drawPolygon p))))))

                  (. g (dispose)))))
        ]

    ; return our 'vtable' associated with the two labels
    { :turn-on      turn-on
      :draw-samples draw-samples
      :draw-sono    draw-sono
      :draw-frequencies draw-frequencies})))

;---- lame utility functions ----

; convert an array of shorts to floats
; there *has* to be a better way. But how?
(defn get-as-floats ([shorts]
  (let [n       (count shorts)
        floats  (make-array (. Float TYPE) n)]
    (dotimes idx n (aset floats idx (aget shorts idx)))
    floats)))

;---- load up the GUI, capture and analyze samples ----

; create an analyzer
(def fft (new kj.dsp.KJFFT 512))

; running flag
(def running (ref nil))

; oscilloscope
(defn oscilloscope ([]
  (let [gui       (new isometric.gui)
        ipanel    (. gui isopanel)
        iw        (. ipanel (getWidth))
        ih        (. ipanel (getHeight))
        iimage    (new BufferedImage iw ih (. BufferedImage TYPE_3BYTE_BGR))
        iicon     (new ImageIcon iimage)
        ilabel    (new JLabel iicon)
        spanel    (. gui sonopanel)
        sw        (. spanel (getWidth))
        sh        (. spanel (getHeight))
        simage    (new BufferedImage sw sh (. BufferedImage TYPE_3BYTE_BGR))
        sicon     (new ImageIcon simage)
        slabel    (new JLabel sicon)
        startbutton   (. gui start)
        oneshotbutton (. gui test)
        stopbutton    (. gui stop)
        inputs        (. gui inputs)
        pool      (. Executors (newFixedThreadPool 1))
        ]
    (. ilabel (setSize iw ih))
    (. ipanel (add ilabel))
    (. ipanel (doLayout))
    (. slabel (setSize sw sh))
    (. spanel (add slabel))
    (. spanel (doLayout))
    (. gui (pack))
    (. gui (setVisible true))
    ; populate input sources
    (doseq inp input-lines
      (. inputs (addItem (:mixer-name inp))))
    (let [funcs     (get-oscilloscope ilabel slabel)
          turn-on   (:turn-on funcs)
          draw-freq (:draw-frequencies funcs)
          draw-samp (:draw-samples funcs)
          draw-sono (:draw-sono funcs)

          ; worker function that samples and calls the draw functions
          worker
            (fn []
              (turn-on)
              (loop []
                (let [samples (get-inst-samples 512)
                      floats  (get-as-floats samples)
                      spectra (. fft (calculate floats))
                      ]
                  (aset-float spectra 0 0)
                  (draw-freq spectra)
                  (draw-sono spectra)
;                  (draw-samp samples)
                  (. Thread (sleep 10))
                  (if @running
                      (recur)))))

          ; starts a worker, who will quit if @running == nil
          start
            (fn []
              (when (not @running)
                (do
                  (sync nil (set running true))
                  (. pool (submit worker)))))

          ; one-shot, sample and analyze a single 'frame'
          one-shot
            (fn []
              (when (not @running)
                (do
                  (. pool (submit worker)))))

          ; kil the worker by setting @running = nil
          stop
            (fn []
              (sync nil (set running nil)))]

          ; set up UI buttons
          (do
            (. inputs
              (addActionListener
                (implement [ActionListener]
                  (actionPerformed [evt]
                    (let [mixer-name  (. inputs (getSelectedItem))
                          sel-line    (:get-line (first (filter #(= mixer-name (:mixer-name %)) input-lines)))]
                      (do (print "Selecting: " mixer-name)
                          (newline)
                          (sel-line)))))))

            (. startbutton
              (addActionListener
                (implement [ActionListener]
                  (actionPerformed [evt]
                    (start)))))

            (. oneshotbutton
              (addActionListener
                (implement [ActionListener]
                  (actionPerformed [evt]
                    (one-shot)))))

            (. stopbutton
              (addActionListener
                (implement [ActionListener]
                  (actionPerformed [evt]
                    (print "=== STOP ===")
                    (newline)
                    (. *out* (flush))
                    (stop)))))

            (print "Hit start to go..."))))))

; comment out the following to have it not start automatically
(oscilloscope)
Trackbacks

Use this link to trackback from your own site.

Comments

Leave a response

  1. Gryphon Thu, 24 Apr 2008 15:07:01 UTC


    the Java sampling api under Windows has defeated me for now. The code below works on a Mac Book Pro. Nothing else is tested

    So much for the much acclaimed “Write Once Run Anywhere”

  2. Kris Fudalewski Fri, 27 Feb 2009 22:33:15 UTC

    KJ DSP actually has it’s home at: http://sirk.sytes.net/software/libs/kjdss/index.htm
    My website was down for a couple of years because I moved to Holland. ;)

  3. Coen Stevens Sun, 16 Jan 2011 13:43:10 UTC

    Looks great! I’m trying to run it on my mac but I miss the isometric.gui
    Any chance you could put it online or send it to me by email? That would be very much appreciated.

Comments