FemCal Lite AdHoc Requests - iPhone Period and Ovulation Tracker 2

Posted by jonathan on February 21, 2009

App Store approval seems to be a little on the slow side right now, and I’d like to get some feedback on my latest application FemCal Lite.

FemCal Lite Calendar Display

FemCal Lite Calendar Display

About FemCal Lite

FemCal Lite is the Lite version of FemCal, but still includes all the information that can be entered in FemCal, plus the ability to show a graphical report of your cycles.

The screenshot on the left is the main calendar display from FemCal Lite.

Infertile days are highlighted in red, whilst fertile days are highlighted in green.

The icons displayed on each day include period flow icons, medication, cervical fluid, symptoms and upcoming periods.

You can enter as much data into FemCal Lite as you like, but only the last 60 days is displayed in the calendar. I’d like people to try FemCal Lite and upgrade to FemCal if they like FemCal Lite.



FemCal Lite Screenshots

The following screenshots are taken from FemCal Lite.

Enter Basal Body Temperature

Built-in Help Text

Zoomed in on the Temperature Chart



Requesting an AdHoc Copy of FemCal Lite (Pending App Store Approval)

If you’d like to request a copy of FemCal Lite, please email me the UDID identifier of your iPhone. You can obtain this in iTunes by clicking the text ‘Serial’ until it turns into the word ‘Identifier’. Or you can use Erica Sadun’s utility (free) to email me the identifier. Search the App Store for ‘ad hoc helper udid’.

I’ll update this post when I stop sending out copies. It takes quite a bit of work to generate certificates and generate a working file, so please only ask if you feel that FemCal Lite is of interest.

If you’d like an Ad Hoc copy, send an email with the subject ‘FemCal Lite Ad Hoc Request’ to ‘femcallite@jonathanwatmough.com’, including your UDID. Please follow those instructions exactly. ;-)

Once FemCal Lite is available on the App Store, I’ll update this post.

FemCal Original Version

In the meantime, the version 1.0 of FemCal is available on the App Store.

Click here to visit the iTunes App Store.

FemCal - iPhone Cycle Tracking Application Launched 3

Posted by jonathan on January 20, 2009

FemCal on the iPhone

FemCal is an easy to use application for tracking and charting your monthly cycle.

FemCal provides an easy to use entry page for entering waking temperature, period, cervical fluid, symptoms, medications and more.

FemCal displays an easy to read chart of any stored cycle, which shows the entered information.

FemCal is available on the App store. Click to visit the iTunes Store.

FEATURES

Entering Information

The following daily items can be tracked by hitting edit for the current day, or by simply double-tapping the day in the calendar:

- Waking temperature from 97.0 - 99.0 oF
- Menses flow from spotting, light, medium heavy,
- Cervical fluid consistency,
- Cervical firmness
- Vaginal sensation,
- Intercourse,
- Ovulation test kit name and result,
- Pregnancy test kit name and result,
- Other procedure, such as BSE etc.,
- Up to three symptoms,
- Up to three medications, with an editable list of medications,
- A daily note, allowing you to store a comment or reminder,

The Calendar Display

The standard display of FemCal shows a calendar display that shows entered waking temperatures, and icons that represent the entered data. By touching a day, the data entered for that day is shown in a summary area below the calendar, that can be scrolled if necessary.

If configured through settings, FemCal can shade in red — reflecting possible lower likelihood of fertility — days that fit the following rules:

- During your period, if a temperature rise occurred 12 - 16 days prior to the current cycle,
- After your period, dry days prior to an increase in cervical fluid,
- 3+ days after a rise in temperature above coverline (estimated from six or more entered temperatures, post-menses)
- 4+ days after peak cervical fluid

These indicators are only as good as the entered data, and may be of interest if you are following a published book such as Taking Charge of Your Fertility.

The Report View

FemCal also contains a report view — see example in screenshots — that displays the start date of the selected cycle, and includes a graph of entered temperatures through the cycle, and the other entered information.

With sufficient entered temperatures, FemCal can estimate peak day, and plot coverline temperature, which may aid you in understanding of your cycle.

Important note: FemCal is intended only to be an aid in tracking your cycle, and is not intended to diagnose, treat, cure or prevent any medical condition.

LANGUAGE
English

REQUIREMENTS
Compatible with iPhone and iPhone Touch.

Implementing Tap to Zoom in UIScrollView on an iPhone 16

Posted by jonathan on December 11, 2008

I just spent the past two days wrestling with trying to get a working implementation of tap to zoom working correctly in a UIScrollView.

I’m writing an iPhone application that generates a report view, and I really need to allow users to tap to zoom in, tap to zoom out, and still be able to use pinch zooming and panning.

A long search of the Apple demo code, and internet articles did not turn anything definitive up, besides the fact that almost everyone else seems to be having similar problems, including but not limited to: fuzzy renderings when expanding, confused and incorrectly zooming UIScrollViews after manual tap zooming, and much else besides.

I ran into all these problems, and eventually I concluded that the layers underneath a UIView are used to cache zoom renderings, and also carry other state, that it just is not possible to get at through the published api. There *is* a private api, but it is just that, private, and I’d like to get this app on the App Store, so I can’t use it.

Ok, so the approach I created is simply to recreate the scroll view and content view whenever I need to. It turns out that this works well enough for me, both in the simulator, and on the device.

The following is my TapZoomDemo I created to show this solution:

TapZoomAppDelegate.h

//
//  TapZoomDemoAppDelegate.h
//  TapZoomDemo
//
//  Created by Jonathan Watmough on 12/10/08.
//  Copyright __MyCompanyName__ 2008. All rights reserved.
//
 
#import <UIKit/UIKit.h>
 
@interface TapZoomDemoAppDelegate : NSObject <UIApplicationDelegate,UIScrollViewDelegate> 
{
    UIWindow *window;
}
 
// properties
@property (nonatomic, retain) IBOutlet UIWindow *window;
 
// methods
- (void)createScrollView:(CGRect)scrollFrame contentOffset:(CGPoint)contentOffset contentFrame:(CGRect)contentFrame
           scalingFactor:(float)scalingFactor;
 
@end

TapZoomAppDelegate.m

//
//  TapZoomDemoAppDelegate.m
//  TapZoomDemo
//
//  Created by Jonathan Watmough on 12/10/08.
//  Copyright __MyCompanyName__ 2008. All rights reserved.
//
 
#import "TapZoomDemoAppDelegate.h"
#import "MyContentView.h"
 
@implementation TapZoomDemoAppDelegate
 
@synthesize window;
 
// content min and max widths (used to calculate min/max scale factors)
// hardcoded to same as and 2x width of scroll view
static const float kMinWidth = 320;
static const float kMaxWidth = 640;
 
- (void)dealloc 
{
    [window release];
    [super dealloc];
}
 
- (void)applicationDidFinishLaunching:(UIApplication *)application {
 
    // Override point for customization after application launch
    
    // put up a backing checked view
    UIView * checks = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
    checks.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"4x4check.png"]];
    [window addSubview:checks];
    
    // use app frame for scroll view
    CGRect scrollFrame = [[UIScreen mainScreen] applicationFrame];
    
    // display our view initially at max size (2x), offset to be centered
    CGRect contentFrame = CGRectMake(0, 0, scrollFrame.size.width*2, scrollFrame.size.height*2);
    CGPoint contentOffset = CGPointMake((contentFrame.size.width-scrollFrame.size.width)/2,
                                        (contentFrame.size.height-scrollFrame.size.height)/2);
 
    // create scroll view and content view
    [self createScrollView:scrollFrame contentOffset:contentOffset contentFrame:contentFrame scalingFactor:0.0];
 
    // register for double-tap on content view
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doubleTapZoom:) 
                                                 name:@"DoubleTapZoom" object:nil];
    // bring up app window
    [window makeKeyAndVisible];
}
 
// helper method for creating a scroll view and content view
- (void)createScrollView:(CGRect)scrollFrame contentOffset:(CGPoint)contentOffset contentFrame:(CGRect)contentFrame
           scalingFactor:(float)scalingFactor
{
    // create content frame at required size, with optional scaling
    MyContentView * content = [[MyContentView alloc] initWithFrame:contentFrame];
    if( scalingFactor!=0 )
        [content setTransform:CGAffineTransformMakeScale(scalingFactor, scalingFactor)];
    
    // create a new scroll view and add to window
    UIScrollView * scrollView = [[UIScrollView alloc] initWithFrame:scrollFrame];
    [window addSubview:scrollView];
    
    // reset min and max scaling
    [scrollView setMinimumZoomScale:kMinWidth/contentFrame.size.width];
    [scrollView setMaximumZoomScale:kMaxWidth/contentFrame.size.width];
    
    // constrain the content offset
    float offsetX = contentOffset.x;
    if( offsetX<0 )
        offsetX = 0;
    if( offsetX>contentFrame.size.width-scrollFrame.size.width )
        offsetX = contentFrame.size.width-scrollFrame.size.width;
    float offsetY = contentOffset.y;
    if( offsetY<0 )
        offsetY = 0;
    if( offsetY>contentFrame.size.height-scrollFrame.size.height )
        offsetY = contentFrame.size.height-scrollFrame.size.height;
    
    // setup the scroll view, size is passed, (possible bad) offset is passed
    // we'll go ahead and use the passed offset, but animate back to constrained
    [scrollView addSubview:content];
    [scrollView setContentSize:contentFrame.size];
    [scrollView setContentOffset:contentOffset];
    [scrollView setDelegate:self];
    
    // be nice and animate back to constrained offset, with optional scaling back to 1.0 (tap zoom)
    [UIView beginAnimations:@"" context:nil];
    [scrollView setContentOffset:CGPointMake(offsetX,offsetY)];
    if( scalingFactor!=0.0 )
        [content setTransform:CGAffineTransformIdentity];
    [UIView commitAnimations];
    
    // rather than cluttering up autorelease pool
    [content release];
    [scrollView release];
}
 
// scroll delegate methods
 
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    // return the first subview of the scroll view
    return [scrollView.subviews objectAtIndex:0];
}
 
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
    // get the view
    UIView * content = [scrollView.subviews objectAtIndex:0];
    
    // get size and offset of (dynamic scaled) content view
    CGPoint contentOffset = [scrollView contentOffset];
    CGSize contentSize = [scrollView contentSize];
    
    // dump the old views
    [content removeFromSuperview];
    [scrollView removeFromSuperview];
    
    // scale the size of the frame to scaled size
    CGRect contentFrame = CGRectMake(0, 0, contentSize.width, contentSize.height);
    
    // create a new scroll view
    CGRect scrollFrame = [[UIScreen mainScreen] applicationFrame];
    
    // create our new content view and containing scroll view
    [self createScrollView:scrollFrame contentOffset:contentOffset contentFrame:contentFrame scalingFactor:0.0];
}
 
// Double Tap Zoom
- (void)doubleTapZoom:(NSNotification *)notification
{
    // scroll view passed as object, get content subview
    UIScrollView * scrollView = [notification object];
    UIView * content = [[scrollView subviews] objectAtIndex:0];
 
    // get dictionary holding event, and event
    NSDictionary * dict = [notification userInfo];
    UIEvent * event = [dict objectForKey:@"event"];
    
    // get touch from set of touches for view
    UITouch * touch = [[event touchesForView:content] anyObject];
    
    // need scroll frame, content frame and content offset
    CGRect scrollFrame = scrollView.frame;
    CGRect contentFrame;
    CGPoint contentOffset;
 
    // zoom in if not at max size, other zoom out to fit to page
    float scalingFactor = 0.0;
    float currentWidth = [content frame].size.width;
    if( currentWidth < kMaxWidth )
    {
        contentFrame = CGRectMake(0, 0, scrollFrame.size.width*2, scrollFrame.size.height*2);
        scalingFactor = kMaxWidth / currentWidth;
        CGPoint touchPoint = [touch locationInView:content];
        contentOffset = CGPointMake(touchPoint.x*scalingFactor/2, touchPoint.y*scalingFactor/2);
    }
    else
    {
        scalingFactor = kMinWidth / currentWidth;
        contentFrame = CGRectMake(0, 0, scrollFrame.size.width, scrollFrame.size.height);
        contentOffset = CGPointMake(0,0);
    }
    
    // dump the old views
    [content removeFromSuperview];
    [scrollView removeFromSuperview];
    
    // create scroll view and content view
    [self createScrollView:scrollFrame contentOffset:contentOffset contentFrame:contentFrame scalingFactor:1.0/scalingFactor];
}
 
@end

MyContentView.m

//
//  MyContentView.m
//  TapZoomDemo
//
//  Created by Jonathan Watmough on 12/10/08.
//  Copyright 2008 __MyCompanyName__. All rights reserved.
//
 
#import "MyContentView.h"
 
@implementation MyContentView
 
- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        // Initialization code
        [self setBackgroundColor:[UIColor whiteColor]];
    }
    return self;
}
 
- (void)drawRect:(CGRect)rect
{
    // Drawing code - This will be called with different bounds at the end of each
    // tap/pinch zoom operation.
    // For illustration, I'm scaling so that a '1' glyph fills the area.
 
    // context
    CGContextRef context = UIGraphicsGetCurrentContext();
 
    // Fill view with a 20-point '1'
    UIFont * f = [UIFont systemFontOfSize:20];
    [[UIColor darkGrayColor] set];
    
    // get bounds
    CGRect b = [self bounds];
    
    // draw a string
    NSString * text = @"1";
    CGSize sz = [text sizeWithFont:f];
 
    // scale to bounds / text size
    CGContextScaleCTM(context, b.size.width/sz.width, b.size.height/sz.height);
    
    // draw
    [text drawAtPoint:CGPointMake(0,0) withFont:f];
}
 
// look for double taps
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    // get any touch
    UITouch * t = [touches anyObject];
    if( [t tapCount]>1 )
    {
        NSDictionary *dict = [NSDictionary dictionaryWithObject:event forKey:@"event"];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"DoubleTapZoom" 
                                                            object:[self superview]
                                                          userInfo:dict];
    }
}
 
- (void)dealloc {
    [super dealloc];
}
 
@end

Rome Vacation

Posted by jonathan on July 25, 2008

I finally got round to posting up some of my photography from our vacation in Rome in 2007. We had a fantastic time in Rome, and saw lots of wonderful sights. I hope you enjoy some of the pictures!

Included in Day 1 is pictures of our rented apartment, and the Colliseum.

Meredith at the Colliseum
Meredith at the Colliseum

Included in Day 2 is pictures of The Holy Father, and art at the Vatican Museum.

St Peter\'s Basilica
St Peter’s Basilica

Included in Day 3 is pictures of the interior of Basilica of St Peter.

Interior of St Peter\'s Basilica
Interior of St Peter’s Basilica

A link to all the pictures on Shutterfly.

San Antonio

Posted by jonathan on July 24, 2008

For our wedding anniversary this year, we visited San Antonio.

Some pictures from the trip are online at Shutterfly.

The Alamo San Antonio

I also made a nice picture of The Alamo at night, which can be seen here.

Buy a Print of The Alamo.

Butterfly Scans on Shutterfly

Posted by jonathan on July 23, 2008

I ran across some old scans of some butterfly pictures I’d done as a test of my Nikon F3, PK-13 macro attachment and Nikon SB-13 flash unit. Here is my very favorite image from this batch.

You can buy me on Shutterfly!

I’ve posted the full resolution scans up on Shutterfly who can fulfil print orders up to 20×30″ at very reasonable cost.

I have some great photography sitting around in various boxes of slides and it’s about time some of it found a wider audience. I’m looking forward to catching up with some of my old pictures.

iPhone Application Design: A Time Tracking App 6

Posted by jonathan on July 13, 2008

Outline

It’s pretty hackneyed, but I could actually use a decent application for tracking my times and expenses on my iPhone. I work 9 - 10 hours a day, on various projects, and sometimes have to resort to looking at my sent and received emails to find out what I was actually doing.

Requirements

I have a fairly simple list of requirements for tracking time:

  • Track time starting ‘now’
  • Track time against a project
  • Track time against a project task
  • Allow editing of billable time
  • Allow projects and tasks to be pre-populated
  • Associate a comment with billable time
  • Generate a simple report and email it

Given these requirements, it looks like the most important aspect of the application is the ability to add new periods of billable time, and to view what you’ve recently worked on.

Thoughts on Implementation

Bearing in mind that this is an iPhone application, performance must be fast, and the user interface must be clear, clean and smooth.

Other features should include being able to view work by project, and setting up projects and tasks.

Non-features in a version 0.5, but which are probably work thinking about include the ability to generate and track an actual invoice.

Doing a Mock-Up

I haven’t seen much information on the web about how to design an iPhone application, so at least for me, I’m looking for a comfortable and lightweight way to pull the ‘feel’ of an application together.

I started with Pencil software for Firefox, but it seemed clunky and difficult to use. Especially, the process and changing fonts, and laying out pieces of text seemed to be harder than necessary.

Next up and much more comfortable was the Apple presentation software Keynote. I was able to fairly quickly over the course of a couple of hours, distill the needed views and data down into a few screens.

The following shows what I ended up with.

iPhone Time Tracking App Mock-Up

This mock-up consists of just the regular Keynote text blocks in bold and regular 18 point text. It couldn’t really be much simpler, but I think it’s starting to look like a real application.

My New Dental Implants

Posted by jonathan on May 16, 2008

I’ve been catching up on some dental treatment recently.

Today I got the first part of an implant procedure and now have two large titanium posts embedded in my lower jaw.

Once these get sufficiently attached to my jaw bone, essentially grafted in, they will get two crowns built up on top of them. This should stop worse things happening in the rest of my jaw.

Next week is the first appointment for a bunch more general work that will catch up on some the other problems.

You can google for dental implants to see a lot of scary pictures. I glad my teeth aren’t that bad.

Simple SparkLine Generator in Clojure 6

Posted by jonathan on February 28, 2008

The following is a little SparkLine generator written in the Clojure language.

There’s an example in the code that covers the usage.

Please post a comment if you find it useful, or have suggestions.

I have since updated this program. It should be pixel-perfect, and much more resistant to bad data.

Thanks Rich, for the comment below. I have updated the main function to use destructuring bind on the function parameters.

SparkLine

; Sparkline Generator
; Copyright (c) Jonathan A Watmough. All Rights Reserved.
;
; The use and distribution terms for this software are covered by the
; Common Public License 1.0 (http://opensource.org/licenses/cpl1.0.txt).
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license. Do not remove this notice.
 
(defn 
#^{ :doc "Scale a list of numeric 'values' to a max height of 'height'."
    :private true}
      scale-values ([values height]
  (let [highest (apply max (filter identity values))]
    (map #(if % (dec (- height (/ (* % (- height 3)) highest))) nil) values))))
 
(defn 
#^{:doc "Make a Win-style java.awt.image.BufferedImage of 'width' x 'height'."}
      make-buffered-image ([width height]
  (let [image-type (. java.awt.image.BufferedImage TYPE_3BYTE_BGR)]
    (new java.awt.image.BufferedImage width height image-type))))
 
(defn 
#^{:doc "Create pixel-accurate sparkline bitmap graphic from {:width :height :values :marker}. :marker=-1 for last entry."}
      make-sparkline ([{ w :width h :height v :values m :marker }]
  (let [v       (scale-values v h)
        bitmap  (make-buffered-image w h)
        g       (. bitmap (getGraphics))
        m       (if (and m (< m 0)) (+ m (count v)) m)
        step    (/ (- w 3) (- (count v) 1))]
    (do (doto g (setColor (. java.awt.Color white))
                (fillRect 0 0 w h)
                (setColor (. java.awt.Color gray)))
        (dotimes idx (count v)
          (let [x-pos    (inc (* idx step))
                prev-val (nth v idx)
                this-val (nth v (inc idx))]
            (when (and prev-val this-val)
                  (. g (drawLine x-pos (dec prev-val) (+ x-pos step) (dec this-val))))))
        ; Draw marker if present and return the sparkline bitmap
        (when (and m (nth v m))
            (let [bx  (* m step)
                  by  (- (nth v m) 2)]
              (doto g (setColor (. java.awt.Color red))
                      (fillOval bx by 2 2))))
        (. g (dispose))
        bitmap))))
 
(defn 
#^{:doc "Test code for sparklines graphic generator."}
      test-sparklines []
  (let [spark (make-sparkline {:width 100 :height 30 
                              :values [1 2 10 8 2 5 8 12 14 3 4 15]
                              :marker -1
                              })
        icon  (new javax.swing.ImageIcon spark)]
    (doto (new javax.swing.JFrame "Sparkline Test")
      (add (new javax.swing.JLabel icon))
      (pack)
      (setVisible true))))
 
(test-sparklines)

Prototyping Code in Clojure - A Simple Audio Displaying App 2

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)