FemCal Lite AdHoc Requests - iPhone Period and Ovulation Tracker 3

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.

Implementing Tap to Zoom in UIScrollView on an iPhone 15

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

iPhone Application Design: A Time Tracking App 5

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.

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 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)

Helpful Guide to Setting Up an SVN Repository 3

Posted by jonathan on February 21, 2008

Regular Backups!

If you value your peace of mind, you will have already bought a few external hard drives, and backed up your computer on a regular(ish) basis. If you haven’t, and you own a mac, go to Shirt Pocket Software and download Super Duper, I can wait.

Setting Up an SVN Repository

Ok, now that’s out of the way, here are the indicators that you need to set up your own version control system…

  • You work on any information that is important to you.
  • You work on multiple projects on multiple computers, and it can be hard to keep everything straight.
  • You work on software, or web pages, or documents where it would be beneficial to be able to recover to older versions.
  • You’d like to track (exactly) what you did (with diffs), and when you did it.

If any of these sound like you, then you probably already realize that you could benefit from a version control system. Here’s why it can be much easier than you think:

  • Mac OS X Leopard comes with a version control system called Subversion already installed! (Even if you didn’t install developer tools.)
  • Subversion, or svn for short, is super easy to set up.
  • Lots of common programs, such as NetBeans and TextMate, already support Subversion directly.
  • Subversion can be used easily from the command line.
  • As with most Unix tools, subversion works great over the network, including ssh, and has Windows clients too, like TortoiseSVN.
  • Subversion is Open Source software and can be built from sources. Barring hardware and software failures, you will never lose access to code, data and information that you entrust to Subversion.

With all those reasons to install version control, you should be persuaded! If you aren’t, please post in the comments and let me know why. Seriously.

And without further, prevarication, prognostication or procrastination, here are the steps you need to set up a basic Subversion repository, and check in your first few folders:

Decide where you want your repository.
The repository stores everything that you check-in, and tracks it by version. You could put the repository in your home folder, or on an external drive, but for simplicity, I’ll assume you have admin rights and we’ll put in in ‘/’.

The command to create your repository is:

svnadmin create /svnrepo

where ’svnrepo’ is whatever name you want. I wanted something easy to remember, and quick to type.

Edit the configuration file and password file.
The default setup is to have read access for guest or anonymous, and write access for the logged-in user.

Edit the following lines in /svnrepo/conf/svnserve.conf to fix the access permissions.

anon-access = none
auth-access = write

Now you have to manually add passwords to the file /svnrepo/conf/passwd. Add lines like the examples,

yourname = yourpassword

and save the file. ESC :wq if you are in vi.

Setup an editor for Subversion.
Before you can interact with Subversion, you must setup an editor for Subversion to use to allow you to input import and check-in comments. Add the following command to your ~/.profile:

export SVN_EDITOR=vi

Of course, you could pick any editor that you’re comfortable with!

Import a Project.
Now you are ready to import your first folder into Subversion! As an example, you might want to import your Documents folder, or your Sites folder. In this example, I’ll import my Sites folder, on the same machine as the svn repository, assuming my Subversion repository is at /svnrepo.

svn import Sites file:///svnrepo/Sites

Subversion will bring up your editor. Type a line that describes the import. Since we are local, the Sites folder and contents will be imported to your repository. Note that the URL in the above command starts with file://.

Start the Subversion server.
We’ll start a server to allow you to access the repository across your network. Type the following command:

svnserve -d

This starts the server. Now you will be able to do a ‘check-out’ of the files in your server to any other machine on your network! Let’s do another import first, but across the network. Type the following (but replace my folder with your folder, my username with your svn username, my ip with your ip, my repository with your repository…):

svn import PDFs svn://jonathan@192.168.0.3/svnrepo/PDFs

You should be able to understand the command now. svn import is the main command. PDFs is the folder we want to import to the repository. svn://jonathan@192.168.0.3/svnrepo/PDFs is the (take a deep breath) file protocol, user name, ip address, repository base, repository folder (phew!) that we want the files to be placed under.

After giving a check-in comment, and your password, the PDFs folder, or whatever you specified will be imported to your server.

Getting a folder listing from Subversion.
It would be cruel to leave you without a means of at least getting a file listing. Here’s the ‘ls’ of Subversion:

svn ls file:///svnrepo
svn ls file:///svnrepo/Path

Checking out files from Subversion
To get files out of Subversion, and copy them under the folder ‘WorkArea’, it’s easy:

cd ~/WorkArea
svn co file:///svnrepo/PDFs PDFs

Specifying the folder as the last part of the command makes Subversion create that folder if it doesn’t already exist. If you only want the files copied into the current folder, replace PDFs, in this example, with ‘.’.

Even if you just imported a folder to Subversion, and you want to track some changes that you are about to make, you must check-out the folder so that Subversion can create some files that allow the files to be checked back in correctly when you are done.

Updating the files in the repository after changes
Once you have made some changes, it’s easy to push the changes back to Subversion. Don’t worry! Any changes you made can be rolled back, and you can recover older versions at any time. Save your work! Here’s how:

Go to the parent of the folder that you checked-out, and type:

svn commit PDFs

Subversion will display an editor and wait for a check-in comment. Describe your changes. When you quit the editor, your changes will copied into the Subversion repository.

You should practice all these commands until you are comfortable with them.

Well, that’s it for now! Take it easy and post if you’d like me to do more on this topic, or if you have other feedback. Good luck with Subversion.

Google Charts from Clojure 2

Posted by jonathan on February 16, 2008

I wrote up a very simple example of calling Google Charts api from Clojure.

Example Google Charts Graph

Note: Rich Hickey actually emailed with some suggestions, and these have been added. I added a comment on the one I found most interesting, which is a function literal. Thanks Rich!

(in-ns 'google-chart)
(clojure/refer 'clojure)

(def *amp* "&")
 
; ---- connect to google charts and retrieve a chart ----
(defn get-google-chart [request]
  (new javax.swing.ImageIcon (new java.net.URL request) "Google Chart"))
 
; ---- pop-up a png in a dialog frame ----
(import '(javax.swing JLabel JFrame))
 
(defn show-chart [request]
  (println "Showing chart: " request)
  (doto (new JFrame "Google Chart")
    (add (new JLabel (get-google-chart request)))
    (pack)
    (setVisible true)))
 
; display the hello-world chart
;(show-chart (str "http://chart.apis.google.com/chart?"
;                 "cht=p3&chd=s:hW&chs=250×100&chl=Hello|World"))
 
; ---- simple encoding to 0 - 61 in chars ----
 
(defn encode [val scale]
  (if (nil? val)
      "_"
      (let [val (. Math (floor (* 61 (/ val scale))))]
        (nth "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" val))))
 
; ---- build a chart ----
 
(defn base-url []
  "http://chart.apis.google.com/chart?")
 
(defn chart-size [x y]
  (str "chs=" x "x" y))
 
(defn chart-type [type]
  (str "cht=" type))
 
(defn chart-data [data]
  (let [mx (apply max data)]
    (apply str "chd=s:" (map #(encode % mx) data))))   ; function literal

;### fix spaces -&gt; +
(defn chart-title [title]
  (str "chtt=" title))

(defn chart-axis [x y]
  ; really basic, just an x and y axis
  (apply str "chxt=x,y" *amp*
         "chxl=0:" (concat (interleave (repeat "|") x)
                           ["|1:"] (interleave (repeat "|") y))))

;; Test it out ...
(comment
(show-chart
  (str
    (base-url)
    (chart-size 480 480) *amp*
    (chart-type "lc") *amp*
    (chart-data (map (fn [x] (* x x)) (range -50 51))) *amp*
    (chart-title "x+squared") *amp*
    (chart-axis ["-5" "0" "5"] ["" "" "y-axis"]))))
 
; Test it out ...
(show-chart
  (str
    (base-url)
    (chart-size 480 240) *amp*
    (chart-type "lc") *amp*
    (chart-data [188.5 186 181.5 183 179.5 181]) *amp*
    (chart-title "Jonathan+Weight") *amp*
    (chart-axis ["Week+1" "Week+2" "Week+3"] ["" "" "190+lbs"])))

Regular Expression Helper

Posted by jonathan on February 11, 2008

Click here to play with a simple reg exp helper for JavaScript.

Regular Expression Helper in JavaScript

Paste some text in, then experiment with typing in regular expressions, and watching the matches listed as you type.

10 Worst PC Keyboards of All Time 1

Posted by jonathan on January 10, 2008

This article on PC World lists the 10 Worst PC Keyboards of all time.

I’ll try not to spoil it but, the ZX-81 and Mattel Aquarius are represented. For those of us with long memories, where is the Sord M5?

Response to a Slashdot Post about Assembly Language

Posted by jonathan on January 10, 2008

There’s an interesting discussion going on Slashdot about programming languages.

Check here for the post about assembly language programming that made me stop and think.

My somewhat flip response was “If it generates x86 code, I refuse to look!!”

More seriously, I started assembler programming at the tender age of about 14 with 6502 code on my BBC Micro, then 68000 on the Amiga, then did a bit of (paid) ARM3 programming, but I just have never, since I owned a Speccy, been able to look at Z80, 8080 or 8086 up. I suspect the oddly named registers and vast panoply of addressing modes are the scariest things…

But tell me, is x86 code as scary as I’m treating it? Because I feel bad that I’ve ignored it for so long, but maybe I’m at the point where I’m just being dumb.

What do you think? What do people do with assembly language on these chips anyway?