Friday, August 15, 2014

TDD: An Argument with Assumptions

TDD: An Argument with Assumptions

"Why do you have to write your tests first?  If you didn't, how would you know that those tests cover everything?  Why do we have a suite of tests?  What is the purpose of our suite of tests? So that we can refactor.  The suite of tests is not there to satisfy a QA group.  It's not there to prove to other people that our code works. It is there so that we can refactor." - Bob Martin

So that it is abundantly clear, according to Robert Martin, the purpose of testing is the ability to refactor.  Let's go over this argument.

Nutshell version:

If refactorability is paramount, then tests should be written first.
Refactorability is paramount.
Therefore, tests should be written first.

Of course, this argument alone is fairly weak, as it depends on a relationship between refactorability and when tests should be written.  So here is a longer version.

A. Refactorability is paramount.
B. Tests increase refactorability.
C. Writing tests before code is written ensures that all code written is covered by the test suite.
D. Ensuring that code is covered by the test suite increases refactorability.
E. Writing tests before code is written increases refactorability. (by C and D)
F. A greater number of tests increases refactorability more than fewer tests do.
G. A test suite which covers code is a greater number of tests than a test suite which does not cover code.
H. Writing tests before code is written increases refactorability more than writing tests afterwards does. (by F and G)
I. Methodologies of programming which increase refactorability should be chosen over those that either do not, or do so less. (implied by A)
J. Therefore, not only should tests be written, but they should be written before the code they cover.

Refactorability is Paramount

First of all, all code is able to be refactored.  Refactoring is changing code without breaking it, where "breaking it" means there there is some change in the mapping between inputs and outputs (including side effects).  Let's call this mapping between inputs and outputs "the behavior".

Refactoring is changing the code without changing the behavior.

So what is refactorability?  Refactorability is the ability to change code without breaking it.  It is the ability to change the code and have it still do what it's supposed to.

Refactorability is the ability to change code while it still does what it's supposed to do.

Refactorability is not absolute.  Refactorability is relative to the programmer.  What is refactorable to you is not refactorable to me, and as a result, refactorability is not a property of the code itself, which is counter-intuitive.  Code cannot be made to be refactorable.  Your ability as a programmer does that.

Do not be confused by the ease of saying "this code is refactorable", as this really only means "I have the ability to change this code and not break it".

Where does this ability come from?  How does one increase one's ability to change code but not break it?  There are three ways to gain such ability.

1. Use the program (hey, at least you now have some semblance of what this code is supposed to do)
2. Gain skill in programming. (generally OR specifically to the current code)
3. Write an automated test suite. (a programmatic way of describing intended behavior)

There are no other ways of gaining knowledge about side effects and boundary conditions, which is another way of saying there are no other ways of increasing/decreasing refactorability.

So is refactorability paramount? I don't know.  That's something for you to decide, but hopefully I've given you some tools to consider it wisely.  I think we can say that refactorability is beneficial.  It is something that ought to be considered and at least to some degree strived for.  But do note that if you say that refactorability is paramount, you're saying that the ability to change code without breaking it is more important than making code do what it's supposed to do.

Tests Increase Refactorability

"Given enough eyeballs, all bugs are shallow."
    - Linus's Law

Testing is a way to reduce the number of eyeballs required.  Tests provide this ability objectively, after all, tests only ever pass or they fail.

Tests are programmatic ways of checking boundary conditions (of inputs, outputs, and side effects).
Tests (should) give confidence that you don't have any unintended or unwanted behavior.
Tests (should) give confidence that you have intended and wanted behavior.

Tests give confidence that the code does what it's supposed to do.

I don't think it's any dispute that tests increase refactorability, unless they're bad tests.  A horrible test suite which anchors you to an implementation can decrease refactorability.  If my ability to make changes to a codebase is restricted by some poorly written tests, this is not a win.  But of course, tests can always be deleted, or rewritten.  So it's not as much of a loss as I'm making it out to be.

So do tests increase refactorability?  They can, if they describe what the code should do, and what it shouldn't.  This requires a skilled drafter of tests and a subject matter expert.  But it's a skill, and you can always get better at a skill with practice.

Is it worth it to learn how to write tests?  Well it will certainly win you brownie points in the corporate programming world, but this may be another personal choice you have to make.  But, once again, hopefully I've given you some tools to think about it and come to a reasoned and sound position.

Code Coverage

Code coverage is an interesting thing.  There are no official definitions, but there are two main notions.  One is an objective sense of coverage to mean that your test suite causes every line of code in your application to be used at least once.  An awesome tool for checking this is Coveralls.  It will even tell you how many times each line was grazed over by your tests.

But I don't think that's what Bob Martin means by coverage.  I think he means something a little more beefy.  Something like "all the possible common inputs were tested".  But this begs the question "What is a common input".  For example, if you write a square root function in a dynamically typed language, should you test what happens if someone asks for the square root of the string "hello"?  Would your test suite not have full coverage if you didn't?  It sounds silly, so to give him the benefit of the doubt, clearly full coverage requires checking all the reasonable inputs. So if you don't check if your square root function deals with negative input, we may say it doesn't have full coverage, but this can be a case-by-case basis.

But even if we should strive for coverage, it doesn't follow that tests should be written first, just that they should be written.  I'll let you decide on that though.

Tests Should Be Written Before the Code they Cover

 So I think this is one of the weaker points in the argument.  The argument for it is that if you write your tests later, how will you know every line was covered?  And I think tools like Coveralls do a wonderful job telling you what code you aren't testing.  Writing tests first might be a good habit, but it's not by any means necessary for the goal of refactorability, even if it is paramount.

So I leave you with some thoughts to ponder with your friends.  What do you think?  Have I missed any important points?  Have I misrepresented TDD so terribly I should go sit in a corner?  I love discussion, and I'd love to hear your thoughts.

Sunday, August 10, 2014

Object relationship between Device, Data, and Format

In the world of digital sound, data and format are invariably coupled.  You cannot understand a data stream without an interpretation, and the format is that interpretation.

In the Sound gem, we have Device, Format, and Data objects.  A Data has to have a Format for a Device to interpret it.

    data = Data.new
    data.format = Format.new
    device = Device.new
    device.interpret data

    class Device
      def interpret(data)
        #given data.format, read data
      end
    end

A Device has no format, but it interprets Data given one.

Thursday, June 12, 2014

 

 

Ruby Sound and Music Project


I've decided to found the Ruby Sound and Music Project, or RSMP for short.  It's aim, in short, is to provide audio tools for Ruby developers.

Here are some notes I jotted down this morning:

Mission Statement:

  To create and provide tools for Ruby developers to work with music, audio, and sound in .general on any platform for any application.

  For example: to open and play an mp3 file using only Ruby in a Linux, Windows, or iOS environment.
Or perhaps to create audio conversion wrappers.  Want to convert that ogg to wav?  Nor problem.

To provide a central hub for discussion and development of the project.

Also.. mobile development?  Raspberry Pi tools?  Rails-specific audio helpers?  Want to write music?  Want your computer to write music for you?  Love Ruby? Look no further!

______________

Anyhow, these are some rough-and-tumble ideas to get the project going, and with my Juicy gem, this can get off the ground in no time!

Saturday, March 8, 2014

Holy crap I've done it

I've done it.  I can now play two arbitrary frequencies through FFI using native library calls.  And all in 155 lines of code. Also, it takes 76 milliseconds to calculate 1 second of PCM audio from scratch on my system.


require 'ffi'

module Win32

  WAVE_FORMAT_PCM = 1
  WAVE_MAPPER = -1
  
  class HWAVEOUT < FFI::Struct
  
    layout :i, :int
    
  end

  class WAVEHDR < FFI::Struct
  
    def initialize(dwBufferLength, dwBytesRecorded, dwLoops, dwFlags, lpData)
      self[:dwBufferLength] = dwBufferLength
      self[:dwBytesRecorded] = dwBytesRecorded
      self[:dwLoops] = dwLoops
      self[:dwFlags] = dwFlags
      self[:lpData] = lpData
    end
  
    layout :lpData,          :pointer,
           :dwBufferLength,  :ulong,
           :dwBytesRecorded, :ulong,
           :dwUser,          :ulong,
           :dwFlags,         :ulong,
           :dwLoops,         :ulong,
           :lpNext,          :pointer,
           :reserved,        :ulong
      
  end

  class WAVEFORMATEX < FFI::Struct
  
    def initialize(nSamplesPerSec, wBitsPerSample, nChannels, cbSize)
      self[:nSamplesPerSec] = nSamplesPerSec
      self[:wBitsPerSample] = wBitsPerSample
      self[:nChannels] = nChannels
      self[:cbSize] = cbSize
      self[:wFormatTag] = WAVE_FORMAT_PCM
      self[:nBlockAlign] = (self[:wBitsPerSample] >> 3) * self[:nChannels]
      self[:nAvgBytesPerSec] = self[:nBlockAlign] * self[:nSamplesPerSec]
    end
  
    layout :wFormatTag,      :ushort,
           :nChannels,       :ushort,
           :nSamplesPerSec,  :ulong, 
           :nAvgBytesPerSec, :ulong, 
           :nBlockAlign,     :ushort,
           :wBitsPerSample,  :ushort,
           :cbSize,          :ushort
    
  end
  
  class Sound
    extend FFI::Library
    
    private
    
    typedef :uintptr_t, :hwaveout
    typedef :uint, :mmresult
    typedef :ulong, :dword
    
    ffi_lib :winmm
    
    attach_function :waveOutOpen, [:pointer, :uint, :pointer, :dword, :dword, :dword], :mmresult
    attach_function :waveOutPrepareHeader, [:hwaveout, :pointer, :uint], :mmresult
    attach_function :waveOutWrite, [:hwaveout, :pointer, :uint], :mmresult
    attach_function :waveOutUnprepareHeader, [:hwaveout, :pointer, :uint], :mmresult
    attach_function :waveOutClose, [:hwaveout], :mmresult
    
    ffi_lib FFI::Library::LIBC
    
    attach_function :malloc, [:size_t], :pointer
    attach_function :calloc, [:size_t], :pointer
    attach_function :realloc, [:pointer, :size_t], :pointer
    attach_function :free, [:pointer], :void
    attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer
    
    def self.fill_data
    
      data = []
      period = 1.0/440.0
      
      ramp = 1500.0
      
      time = Time.now
      
      44100.times do |sample|
        t = sample/44100.0
        angle = (2.0*Math::PI/period) * t
        angle2 = 1.5*angle
        factor = 0.5*Math.sin(angle) + 0.5
        factor2 = 0.5*Math.sin(angle2) + 0.5
        x = 0.5*32768.0*(factor + factor2)
        if sample < ramp
          x *= sample/ramp
        end
        if 44100 - sample < ramp
          x *= (44100 - sample)/ramp
        end
        data << x.floor
      end
      
      puts "This took #{Time.now - time} milliseconds to compute"
      
      data
      
    end
    
    public
    
    def self.play
    
      hWaveOut = HWAVEOUT.new
      wfx = WAVEFORMATEX.new(44100, 16, 2, 0)
      
      if ((error_code = waveOutOpen(hWaveOut.pointer, WAVE_MAPPER, wfx.pointer, 0, 0, 0)) != 0)
        raise SystemCallError, FFI.errno, "waveOutOpen: #{error_code}"
      end
      
      data = fill_data
      
      data_buffer = malloc(data.first.size * data.size)
      data_buffer.write_array_of_int data
      
      header = WAVEHDR.new(4*44100, 0, 1, (4 | 8), data_buffer)
      
      if ((error_code = waveOutPrepareHeader(hWaveOut[:i], header.pointer, header.size)) != 0)
        raise SystemCallError, FFI.errno, "waveOutPrepareHeader: #{error_code}"
      end
      
      if ((error_code = waveOutWrite(hWaveOut[:i], header.pointer, header.size)) != 0)
        raise SystemCallError, FFI.errno, "waveOutWrite: #{error_code}"
      end
        
      while (waveOutUnprepareHeader(hWaveOut[:i], header.pointer, header.size) == 33)
        sleep 0.1
      end
      
      if ((error_code = waveOutClose(hWaveOut[:i])) != 0)
        raise SystemCallError, FFI.errno, "waveOutClose: #{error_code}"
      end
      
      self
    end
  
  end
  
end

Win32::Sound.play

waveOutOpen

I have successfully made a library call to waveOutOpen!


require 'ffi'

module WaveOutWrapper

  WAVE_FORMAT_PCM = 1
  WAVE_MAPPER = -1

  class WAVEFORMATEX < FFI::Struct

    def initialize(nSamplesPerSec, wBitsPerSample, nChannels, cbSize)
      self[:nSamplesPerSec] = nSamplesPerSec
      self[:wBitsPerSample] = wBitsPerSample
      self[:nChannels] = nChannels
      self[:cbSize] = cbSize
      self[:wFormatTag] = WAVE_FORMAT_PCM
      self[:nBlockAlign] = (self[:wBitsPerSample] >> 3) * self[:nChannels]
      self[:nAvgBytesPerSec] = self[:nBlockAlign] * self[:nSamplesPerSec]
    end

    layout :wFormatTag,      :ushort,
           :nChannels,       :ushort,
           :nSamplesPerSec,  :ulong, 
           :nAvgBytesPerSec, :ulong, 
           :nBlockAlign,     :ushort,
           :wBitsPerSample,  :ushort,
           :cbSize,          :ushort
    
  end

  class Sound
    extend FFI::Library
    
    private
    
    typedef :uint, :mmresult
    typedef :ulong, :dword
    
    ffi_lib :winmm
    
    attach_function :waveOutOpen, [:pointer, :uint, :pointer, :dword, :dword, :dword], :mmresult
    attach_function :waveOutClose, [:pointer], :mmresult
    
    public
    
    def self.play
    
      hWaveOut = FFI::MemoryPointer.new(:int)
      wfx = WAVEFORMATEX.new(44100, 16, 2, 0)
      waveOutOpenResult = waveOutOpen(hWaveOut, WAVE_MAPPER, wfx.pointer, 0, 0, 0)
      raise SystemCallError, FFI.errno, "waveOutOpen didn't work: #{waveOutOpenResult}" if waveOutOpenResult != 0
      waveOutClose(hWaveOut)
      
      self
    end

  end

end

WaveOutWrapper::Sound.play # this won't actually play anything, but it doesn't return an error!

Audio and Ruby

I'm working on a gem which will make songs for me.  This may be a pie-in-the-sky dream, but I'm still working on it.  One thing I need is to be able to play arbitrary digital signals to a sound device.  A cursory google search will show that there is a gem to do just this!

gem install win32-audio

And then all you have to do is load up the gem, and make a native system call to make a beep.

require 'win32/sound'
include Win32
Sound.beep(440, 500)

This code will play a 440Hz tone for 500 millisecond.  But there's a problem.  It's synchronous. I need to be able to play multiple tones at once, or even be able to mix some signals and stream the result to a sound device.

The win32-audio uses the ffi gem to do its dirty work, so I've decided to jump in and do the same.

My plan is to code up a gem (or an extension to win32-audio) which makes native calls to the waveOut multimedia library in windows.  Hopefully then I'll be able to shove in arbitrary PCM signal to a sound device of my choosing, and even with as many channels as I'd like!

It's basically this:

1. waveOutOpen to open up a device for streaming
2. waveOutPrepareHeader to prepare a block of audio for playback
3. waveOutWrite to submit the prepared audio to the device for playing
4. waveOutClose to close up the stream when I'm done

As much as I wish it was simple to just define these four functions in ffi, it gets a little more hairy.  Of course, I have to go in and define and set up memory management for the underlying code which relies on various structs like WAVEFORMATEX and WAVEHDR.

Wish me luck!