Monday, January 23, 2012

Important FFRend bug fix; testers needed please!

The last released version of FFRend ( had a bug that caused the entire desktop to flicker whenever a row view was updated. Since just about every command updates one or more row views, the desktop flickered like crazy. You'd think I would have noticed it during testing but I normally run FFRend full-screen. I'm pretty embarrassed about it. Anyway please take the latest version, which fixes this lame bug and some others too.

FFRend download

I would greatly appreciate it if any FFRend users who hang out here would take the new version out for a spin and report back if they see anything weird. I'm especially worried about the row view code, because that's what caused the desktop flicker bug in the first place. The Freeframe Parameter and MIDI Setup row views are now supposed to remember the scroll position separately for each plugin. Try switching back and forth between a plugin that's scrolled and one that isn't. Do you see any painting artifacts, or does the view paint smoothly?

This version also fixes a bunch of problems related to synchronization of oscillators between plugins, which crept in with V2 as side effects of parallel processing. The earlier versions of V2 would lose sync between plugins at the drop of a hat. It should be a lot better now. The problem was basically that in a pipeline each plugin has its own frame of reference in terms of time, so it's necessary to compensate for that in various places. To debug it I had to make a pair of special Freeframe plugins, one that stamps its parameter directly onto the frame as a number, and another that draws its parameter onto the frame as a waveform, like an oscilloscope. They're pretty handy actually. I can make them available if anyone wants them...

Thursday, January 12, 2012

check for updates & automatic updates

The basic steps are:

1. Obtain the version number of the latest release, by downloading the project web site's download page and parsing its HTML for the download URL.

2. If the installed version is out of date, use the download URL found in the download page to download the zip file of the current binary release.

3. Launch a helper that unzips the zip file, and then runs the installer. The helper is needed because it's impossible to reinstall an app while the app is running.

The first two steps are simple enough. CInternetSession::OpenURL is the easiest way I found to download files via HTTP. OpenURL creates a CHttpFile, and from there it's straightforward. The only gotcha was that the CHttpFile instance is only valid while the session exists. The hardest part was parsing the download page for the version number and download URL. Parsing is always a pain. And of course there were degenerate cases: exceptions to be handled, temp file leaks to be avoided, etc.

The update code adds about 20K to the .NET executable. This is mostly due to the static linking of MFC, which means we pay for bloated objects such as CInternetSession and CHttpFile. There's also the cost of the minizip library but this is much more modest since we're already paying for zlib anyway.

The main problem is that the app has to exit before the installer runs, otherwise the installer fails, understandably enough. It's also expected behavior for the app to restart after the installer finishes, and for extra credit the installer file should be deleted from the temp folder afterwards. Initially I thought we'd need an actual helper app to deal with all this, but batch files came to the rescue.

To avoid a race, the script needs to give FFRend enough time to exit before starting the installer. This seemed problematic since XP batch syntax doesn't support sleep, but then I discoved the ping hack, which works fine (a sleep command was finally added in Vista).

Fortunately it's possible to make msiexec non-interactive by specifying the /passive flag. This completely suppresses the installer dialog though a progress bar is briefly shown. The next problem was restarting the app without leaving the batch file's console window up. Here the secret was the incredibly useful start command, which I somehow managed not to discover for all these years. The start command does have a catch though: it doesn't necessarily pass a fully qualified path to the app, which causes serious problems if the app is expecting to be able to deduce its home folder from the command line. Luckily you can force start to pass the full path, by using the %cd% batch variable, which translates to the current directory. Also watch out for start's mandatory first argument, the window title, which has to be enclosed in quotes regardless of whether it contains spaces. In this case the window isn't visible long enough to read the title, so an empty string is fine.

Here's the completed reinstall.bat, which gets created by FFRend using CreateProcess "cmd /c". Note that the script assumes it's running in the same working folder as the application, which can be ensured by setting CreateProcess's lpCurrentDirectory argument to the application path. The arguments are:
%1 ping count: waits approximately N - 1 seconds
%2 installer path, e.g. "C:\whatever\temp\FFRend.msi"
%3 the name of the executable to launch, e.g. FFRend

@echo off
title Reinstalling %3
ping -n %1 >nul
msiexec /passive /i %2 REINSTALLMODE=vomus REINSTALL=ALL
if errorlevel 1 goto error
start "" "%cd%\%3"
del %2 >nul