Development Log

A Brief History of Speak Freely

When I moved to Europe in May of 1991 to help organise Autodesk's European Software Centre, I realised that one thing I'd miss is being able to listen in on design meetings and talk with individual developers without running up huge phone bills. Autodesk had a dedicated 56 Kb leased line between headquarters in California and the European Software Centre which was used primarily for transmitting software updates but which was nearly idle in the overlap hours between Europe and California. Since all of our software developers had Sun workstations which came with audio hardware, I decided to see if I could put the pieces together so we could talk and/or broadcast meetings over the leased line. Since raw Sun mu-law audio requires 64 Kb and I only had 56 Kb to work with, I hammered in a decimation/expansion scheme to reduce the bandwidth to 32 Kb. (It remains today, in a more refined form, as "Simple compression".) I knew very little about audio encoding at the time--obviously ADPCM would have been a far better choice, but I was ignorant of it and I'm not sure a public domain implementation of it existed in 1991. I first experimented with an RPC implementation which worked fine over a LAN but was hopeless over the leased line, which was routed over a satellite link and had high latency; I finally settled on UDP as the only viable protocol, a decision independently reached by the designers of RTP years later.

Anyway, the first release of what was then called NetFone was posted on July 11, 1991. Release 2 was posted on September 12, 1991 and consisted of cleanups and bug fixes.

That's where things stood until Release 3 on December 13, 1994, which corrected some compiler warnings on the Sun ANSI compiler which replaced Sun's original K&R cc.

I didn't really get back into development mode until the summer of 1995, when I discovered the public domain implementation of GSM which is still used in Speak Freely. This, along with Phil Karn's DES (which I had used in a number of other programs over the years), and the Silicon Graphics audio drivers supplied by Paul Schurman made up NetFone release 4, posted on August 2, 1995. This was the first version able to run on a typical Internet connection as opposed to a leased line, albeit still limited to Sun and Silicon Graphics workstations.

NetFone release 5 followed on August 28, 1995 and added IDEA encryption as well as fixes to features in release 4.

Netfone 5.1 was released on September 2, 1995, and was the first to include a development log file. The program was renamed Speak Freely as of release 5.2 on September 21, 1995 and all subsequent development is documented in the Unix development log.

The Windows version began as a port of NetFone 5 (aka 5.0) and all development following its initial release on August 23, 1995 is described in the following Windows development log. "Features" which were added and later removed after having been deemed ill-considered are described in grey type.

Speak Freely for Windows Development Log

23 August 1995

Initial announcement of Speak Freely Release 5.0.

24 August 1995

Peter Claus Gutman, developer of a very nice encryption library, wrote to suggest his library might prove useful. In the source code for the library, I found a clever 80x86 assembly-language implementation of IDEA, made freely available by its author, who is identified in the source code only as "Bryan". Whoever you are, Bryan, great piece of work! If you, or somebody who knows who you are, happens to read this, let me know so I can give complete attribution.

I integrated the assembly language loop into IDEA\IDEA.C, modifying it slightly to work with Microsoft Visual C's inline assembler, so you don't need a separate assembler to take advantage of the optimised code. Whether the assembler or original C code is used depends upon whether USE_ASM is defined, so you can use the original loop for reference or if, for example, your compiler doesn't support inline assembly code or is incompatible with the way Microsoft do it.

Enabling the assembly language code increased the speed of IDEA encryption and decryption on my 486/50 machine from 152,000 bytes per second to 242,000 bytes per second--well worth the trouble of integrating the code.

30 August 1995

Completed a massive revision to avoid all packet fragmentation and thus work with WINSOCK drivers such as Trumpet WINSOCK. The changes were so great and ubiquitous there's no point in trying to describe them. In debugging the changes, one of the mysteries that has been dogging me was finally solved--the random hangs, loss of synchronism, failure to release resources, etc. etc. etc. were the result of Windows discarding messages to the main window as a result of overflows of the default 8 message queue. Speak Freely juggles a lot of balls in the air at once, and it's very easy to hit this limit. At initialisation time, we now try to expand the queue to its maximum size of 120 messages or whatever lower maximum the system we're running on supports.

Added the "Extended Status" (Propeller Head) dialogue.

Made compression modes global rather than per-connection. This means compression only has to be done once, which speeds up party line transmissions. The change is necessary in any case so that packet size can be optimised.

1 September 1995

Update release 5.1.

8 September 1995

CreateSocket() in UTILITY.C contained a "defensive bind()" to address zero as a work-around for some defective WINSOCK implementations. Unfortunately, this work-around causes other TCP/IP stacks, including that built into Windows NT to fail. I made the nugatory bind conditional on a new Options/Workarounds/Always Bind Socket menu item which is, of course, saved in the .INI file.

Update release 5.1a.

9 September 1995

Discovered that the reason the socket write was failing on Windows NT and Windows 95 is that Microsoft's built-in WINSOCK, entirely incompatible with Unix and every other WINSOCK I have encountered, refuses sendto() once a datagram socket has been connect()ed. The sole function of connect() on a datagram socket is to specify a default address so subsequent writes can be done with send() (or, on Unix, write()), and there is no prohibition of overriding this default address with a subsequent sendto(). The WINSOCK specification nowhere mentions such a restriction as a Windows-specific change. I modified the socket write code in CONNECT.C and the loop-back socket write in FRAME.C to first try sendto(). If it fails, send() is then tried and if that works all subsequent socket writes for the rest of the session are done using send(). This code has been verified to work on both Windows NT and Windows 95 (first customer shipment edition). Special thanks to John Deters who both identified the source of this problem on Windows NT and tested innumerable versions slowly converging toward the actual fix.

Added an item to the Propeller Head dialogue to indicate whether sendto() or send() is being used to write to outbound sockets; it's "Sending with" in the "Network" box.

Tested with the WINSOCK implementation included with Sun PC-NFS 5.1. Works fine.

Update release 5.1b.

10 September 1995

After last week's experience I decided to indulge in some preemptive workarounds for crummy network and sound card drivers which fail in obvious ways which haven't bitten me yet. I expanded the Options/Workarounds menu to include:

Audio
Assume Half-duplex
Assumes the sound card is half-duplex without requiring it to fail an output open while input is open. Accommodates cards which are actually half-duplex but don't indicate this by failing a simultaneous input and output open. Also handles cards which crash the system or application when you try to open them in full-duplex mode.
Assume 11025 Samples/sec
Assumes the card is capable only of 11025 samples per second mode, not our preferred 8000 samples per second. Permits correct operation on cards which don't fail when opened with a sample rate of 8000 samples per second but which can't actually run at that rate.

Network
Always Bind Socket
As before; bind outbound sockets, even though there's no need to do so.
Never Connect Outbound Socket
Don't connect() the output sockets. This implies we'll always use sendto() to write to those sockets. Clears "Use send(), Not sendto()" mode if set.
Use send(), Not sendto()
Always use send() to write to outbound sockets; don't wait for a sendto() to fail first. Accommodates drivers where a sendto() on a connected socket crashes the application or system. Clears "Never Connect Outbound Socket" mode if set.
Multicast TTL Argument Is char
Certain Winsock implementations by a soi-disant "setter of standards" headquartered east of Seattle, Washington in the United States flagrantly ignore the long-established convention that Boolean arguments to multicast setsockopt() calls are of type int. Their code errors such requests with a "bad address" fault, and accept them only if the argument is passed as a character (incompatible with Unix). This workaround is set if we empirically discover this to be the case on the system on which we're running, and can be set by the user to preempt dastardly behaviour by systems that don't have the courtesy to inform us of their incompatibilities with contemporary community standards.

All the workaround modes are saved in the SPEAKFRE.INI file and apply to subsequent executions. The menu items are disabled when a connection is active.

As suggested by John Deters, I added the ability to automatically open an iconised version of Speak Freely whenever a new inbound connection is established. This lets you see the site that's just started talking to you. Since some people might find such an unsolicited pop-up irritating, this only happens if you check the new Options menu item "Look Who's Talking".

Tested under Windows 95 final build. Works fine when using the standard built-in WINSOCK, but doesn't resolve host names when Sun PC-NFS is overloaded on top of Windows networking. This appears to be a general problem of this configuration; other programs fail on gethostbyname() in precisely the same way. When you configure Windows 95, be sure to install the TCP/IP driver; if you don't you'll get nowhere fast.

12 September 1995

Sending a stereo .WAV file in ADPCM compression mode crashed the Unix speaker program. The code in READWAVE.C which calculates the number of bytes of .WAV file needed to fill a packet was incorrectly assuming the nBlockAlign field was the size of an individual sample, not the frame of samples for all channels. Fixed.

Closing a connection while a .WAV file was being sent orphaned the MMIO handle used to read the file. Fixed in CONNECT.C.

13 September 1995

Added the ability to drop saved connection (.SFX) files in the MDI frame window and thereby open (or activate, if already open) connections to the hosts given in the files. You can drop multiple connection files in a multiple selection and each will be opened.

CONNECT.C had its own implementation of DragAcceptFiles() which directly twiddled WS_EX_ACCEPTFILES. It doesn't any more.

If a connection file is named on the command line when the program is launched, it is opened once the application is initialised. This permits making an association between the .SFX extension and Speak Freely in the File Manager and launching the program for a given connection by double clicking the connection file. You can specify multiple connection files on the command line, space separated. This allows making a program item icon which opens a collection of connections, a handy thing to put in your StartUp folder. (Suggested by John Gilmore).

John also pointed out that the program wasn't usable without a mouse since the left mouse button was the only way to push to talk. I added logic in CONNECT.C that permits the space bar to be used to toggle push to talk, just as in the Unix mike program. You can cycle between open connections with Ctrl+Tab and use the space bar to select any set to which you wish to transmit.

Mouseless users who push to talk with the space bar don't have the benefit of the cursor change to indicate which connections are transmitting. I added a "Transmitting" status indicator in the connection window which appears whenever live audio is being sent to the window.

If you make a .WAV sound file with the (nonstandard) sampling rate of 8000 samples per second, it is now played correctly by READWAVE.C, not forced to the closest standard sampling rate of 11025 samples per second. Conversion of stereo .WAV files into mono is still performed for 8000 sample per second files. If the user has the ability to make 8000 sample/sec .WAVs, this reduces file size, improves sound quality, and eliminates CPU overhead when sending such files. .AU files remain the fastest, since they're already mu-law encoded.

14 September 1995

Update release 5.1c.

20 September 1995

Began work on answering machine. Defined structure for data in file, added a new ANSWER.C module with a function to save a sound buffer in an answer file in that format.

25 September 1995

Modified the new connection dialogue handler to allow numeric IP addresses which can't be resolved into host names. If the host name lookup fails, the dotted IP number from inet_ntoa is used as the host name.

Good ole' Trumpet Winsock returns an error status if gethostname() is called with a buffer too small to hold the entire name, as opposed to truncating it as Unix does. I changed the two calls in CONNECT.C to get the host name in a temporary buffer, then copy as much as will fit into the sendinghost field of the sound buffer.

Added the ability to set the multicast scope with a new item in the Options/Connection dialogue. This item is enabled only if the IP address is a valid multicast group number.

Bad ole' Windows 95 WINSOCK returns a WSAEFAULT error if you pass a single byte argument for the IP_MULTICAST_TTL setsockopt() call. This is incompatible with all Unix documentation I have seen. Trumpet works correctly with the single byte argument, and accepts the 2 byte short required by Windows 95. Given the likelihood there's some other WINSOCK that requires a one byte argument, in goes another Options/Workaround/Network item: "Multicast TTL Argument Is char" which does it the Unix way, not as required by Windows 95.

26 September 1995

Added a new Connection/Multicast Groups dialogue which allows adding and dropping membership in multicast groups. Groups can be specified by DNS-resolvable name or by IP address. A check box controls multicast loop-back of locally sent packets to groups in which this host has added membership. The loopback box is disabled on systems (such as Windows 95) which do not implement the IP_MULTICAST_LOOP setsockopt() option.

1 October 1995

Discovered the multicast tear-down code in the WM_DESTROY message handler of FRAME.C wasn't testing for a NULL multiName[], resulting in bad GlobalFree() calls when we failed to initialise a multicast port. Fixed.

FRAME.C wasn't killing the main timeout timer at WM_DESTROY. Fixed.

If the attempt to drop a multicast membership at WM_DESTROY time failed, a message box was displayed as a child window of the one frame being destroyed. This is apparently (yet another of the billions and billgatesillions) undocumented no-no--in any case, if you do it, you get an "err USER: Attempt to activate destroyed window" at the time the WM_DESTROY returns. I changed the parent of the message box in this case to be NULL and it seems to be happy now. (In FRAME.C).

Finished implementation of the answering machine, ANSWER.C. I'll probably be back before long to make it more message-oriented (select message from a list box of sites and times, individually delete messages, etc.) but at least it now has basic functionality.

2 October 1995

Added keyboard accelerator (CTRL+T) for answering machine, and a new connection menu item that lets you toggle whether incoming messages are recorded without having to pop up the answering machine dialogue. Fixed a bug in which checking or unchecking the record incoming messages box in the answering machine dialogue didn't take effect until you closed the dialogue; now it takes effect immediately.

Added code to overwrite the 16 byte session key exchanged via PGP before closing the file on disc. Unfortunately, since we can't transmit and receive the with a pipe, as we do on Unix, there's still a window while PGP is running during which the session key is visible, but at least this keeps it from lying around in unallocated disc space for an indeterminate time.

If no answering machine message file was configured, the answering machine dialogue in ANSWER.C called scanMessageFile anyway. Unfortunately, that routine didn't test for answerFile being NULL and proceeded to stomp all over memory. Fixed in ANSWER.C scanMessageFile().

Moved all translatable strings and formats from the .C modules into the string table of the resource file, using the rstring(), rfilter() functions and the Format() macro as intermediaries. Strings that aren't to be translated,such as fopen() mode strings, formats that contain only a field editing code, etc. continue to appear as strings in the source code. Banishing these strings to the resource file reclaimed almost 4K of data space, enough to give us some breathing room should it prove necessary to introduce another static full-size sound buffer for some reason.

3 October 1995

The enabling and disabling of buttons in the answering machine was befuddling Windows' dialogue box keyboard accelerator logic. I added code at the end of a message replay to restore the input focus to the button last pressed or its logical successor if that button has become disabled as a result of the message we just completed.

Keyboard accelerators in the answering machine were less than optimally chosen due to renaming of buttons during its development. I rationalised them so the most commonly used buttons have the most obvious keyboard shortcuts.

Pressing the Close button in the answering machine gave a debug kernel "err: window destroyed in window callback". Why, I know not. It uses the standard code for modeless dialogues right out of Petzold, which identical code works perfectly in the propeller-head modeless dialogue. Changing the DestroyWindow() to a PostMessage of WM_CLOSE to ourself made the message go away. I changed the propeller-head dialogue in DIALOGS.C to use the same logic.

Several modal dialogues needlessly included the system menu in their title bar. Eliminated. (The modal dialogues such as the answering machine and propeller-head continue to display the system menu.)

Installed help buttons in all the dialogues, linked to the topic in the help file which describes the dialogue.

Moved the names of our help file and the base Windows help file into the resource string table.

I removed the "How to use help" menu item, which has fallen out of fashion.

Changed "Help/Search..." to "Help/Search for Help on..." as used in current Microsoft applications.

4 October 1995

Completed moving all section and item titles for the main .INI file and saved connection files to the string table in the resource file. Whether these should be translated isn't clear: a normal user won't ever examine these files and translating renders them incompatible between different language editions. But the saving in data segment size by elminiating duplication of the section titles alone justifies the work.

Added two new string constants kS0[1] = "0" and kS1 = "1" to FRAME.C and changed all references to the explicit constants in profile file I/O to use them. This eliminates redundant string constants in the data space.

Found a few string constants I'd missed somehow in READWAVE.C. Banished.

Fixed the answering machine to update the host name when a definitive name (one not displayed in parentheses) is seen, replacing any previously displayed name.

Added help butttons to all the file open dialogues, linked to the appropriate topics in the help file.

Added a pleasant default ring file. I haven't found a suitable (well-recorded and public domain) telephone bell, so I decided to pioneer non-irritating notification of an incoming call with this wind chime derived sound. The original appeared on the CD-ROM (N° 5) accompanying "News Windows" N° 26 (octobre 1995) as the file WINDBELL.WAV. I used Silicon Graphics' soundfiler to convert this from an 11025 kHz PCM stereo file to an 8 kHz monaural .AU file for optimal transmission.

Substantial data space was being wasted by repeated constant references to the profile (.INI) file name. I moved this string to the string table in the resource file and changed the code that loads and saves the global configuration to load it once into a string on the stack and reference that temporary copy in all the [Read|Write]PrivateProfile... calls that follow.

Added a new MAKEBIN.BAT file in the home directory which builds the binary release archive. Now that the release includes more than the .EXE and .HLP file, something more archival than my fallible memory is needed to make sure

5 October 1995

Remade all screen shots for help file, the addition of the help buttons required updating all the dialogue bitmaps.

Added logic to the invocations of PGP in FRAME.C and DIALOGS.C to first try to use the SFPGP.PIF file from the Speak Freely release directory (obtained with GetModuleFileName) and then, if that fails, fall back to call on PGP counting on path search to find it. Going through the PIF allows the user to override the default modes for a WinExec call to a DOS program such as PGP, in particular, to run it in a window, which is much less disruptive of the user's equanimity than blasting out to a DOS prompt.

6 October 1995

Feature release 5.3.

30 October 1995

All of the Look Who's Listening functionality is working, at least if you don't push it into reentrancy into Winsock by trying one of the LWL dialogues while sending or receiving sound. I'll have to go back and review the appropriate locks to keep from befuddling Winsock with actual multitasking. Essentially all the code is in the new module lwl.c.

7 November 1995

Added support for RTP-compatible LPC compression (the Xerox PARC algorithm developed by Ron Frederick). This algorithm does a lot of floating point computation (forget it if you don't have a math coprocessor), and it sometimes mangles sound, especially if you drive the audio input into clipping or have a high-pitched voice. But when it works, it achieves better than 12 to 1 compression, and allows running over 9600 baud lines. The LPC code is in a new lpc subdirectory.

13 November 1995

Added a first cut "broadcast" facility to permit transmission of material to multiple hosts (over a suitably fast, probably local network) without the need to install multicast. The facility is relatively crude but should be adequate for uses applications such as broadcasting meetings across a local network.

The site performing the broadcast simply checks Connection/Broadcast. Any audio which arrives while Broadcast is checked is sent to every connected host. All input events are ignored in connection windows while a broadcast is in progress, and remotely initiated connections will not time out during a broadcast. A user can subscribe to a broadcast from a given host by initiating a connection to it and sending a short burst of sound (a second's worth, say). This opens a connection on the broadcasting host to which the broadcast will be sent. A remote host can unsubscribe from the broadcast by sending a similar short burst of sound any time after 10 seconds into the broadcast; the site's connection on the broadcasting host will be closed 10 seconds later. The 10 second delay is to prevent toggling of the broadcast state due to multiple packets being received from the remote site. Whilst broadcasting, the application title indicates "- Broadcasting" and the cursor is always the ear when over a connection window. When broadcasting is toggled off, all connection windows are marked as not being transmitted to and remotely-opened connections resume the timeout process.

Using short bursts of sound to subscribe and unsubscribe is ugly but it gets the job done. Once we have a proper RTP packet exchange delimiting the connection, it can be replaced.

14 November 1995

If somebody is already blasting sound at us when Speak Freely is launched, it got all befuddled due to packets arriving before initialisation was fully complete. I changed the WM_CREATE logic in FRAME.C to not enable input on the socket until initialisation is entirely done.

16 November 1995

Feature release 5.5

22 November 1995

People seem to get floating divide by zero errors if they try to use LPC compression on Windows 95. I added a call to _control87() in the initialisation code to disable all floating point error interrupts. This should allow the LPC code to just bumble along with infinities and NANs like it does on Unix, which doesn't seem to do any harm (my suspicion is that this happens only when the LPC code is fed dead silence). This will have no effect anywhere else, since floating point is used only in the LPC code.

I made GSM compression the default out of the box. I'm deeply weary of explaining how to enable GSM compression to hundreds of people a day who can't be bothered to read the help file.

The Phonebook/Search host didn't default to lwl.fourmilab.ch unless you'd previously made a directory listing. Fixed.

The Phonebook/Search box wasn't quite wide enough to hold the longest line of a typical server message and didn't have a horizontal scroll bar. This caused perplexed people to send hundreds of E-mails when they couldn't access the truncated URL the LWL server published. I made the box a few characters wider and enable horizontal scrolling.

There was also some ragged logic in the default server for publishing directory entries. Fixed to correctly default to lwl.fourmilab.ch.

Update release 5.5a.

28 November 1995

Integrated server-side support for the "show your face" feature. The new file FACE.C contains a dialogue that allows the user to designate a 256 colour .BMP file (the format is verified) as his or her face image and a function invoked from FRAME.C that delivers blocks of the image as requested by a remote host. Processing of face image requests occurs before audio output is acquired (but after creating a connection), so half-duplex systems can still transfer face images while sending audio.

30 November 1995

Integrated client-side handler for "show your face". Face data packets are assembled in FACE.C into an in-memory bitmap in the connection structure. If a complete bitmap is available, the WM_PAINT handler in CONNECT.C for the connection window displays the bitmap instead of the usual status information. When a bitmap is displayed in the connection window, transmit state is indicated by preceding the host name with a small ASCII-art arrow.

After adding hundreds of lines of bullshit Windows code trying to swap the palette intelligently when two face images are simultaneously displayed on a colour-mapped display, I decided to exercise that time-proven prerogative of the Windows developer and just give up. The vast majority of users won't connect to more than one person at a time. I fixed it (with even more bullshit code) so that the active window is always shown with the correct palette and inactive windows with the default palette. If you this this is easy to fix, baby, just go and try it before you write me some smart-ass E-mail. Hint: everything you read in the Windows API documentation about palettes is a lie or worse when you start to talk about MDI child windows. There's a sample application on the Developer CD which claims to do this, but a glance at it leads me to estimate at least a week to integrate and test all the crap they went through trying to do what, on any vaguely competently designed windowing system, should be essentially transparent to the application. Users of high-colour and true-colour display boards will be blithely unaware of any problem with multiple simultaneous face images.

1 December 1995

Mycal reported that messages using simple (2X) compression were played back at twice normal speed by the answering machine. Fixed.

Feature release 5.6.

20 December 1995

Added logic in CONNECT.C to set outputSocketBusy if the send() or sendto() returns less than the number of bytes we attempted to write. The WSAEWOULDBLOCK error status is treated as a truncated buffer and sets outputSocketBusy. All of this is disabled if the new workaround "Disable Output Overflow Recovery" is set just, as always, in case.

When outputSocketBusy is set, we're guaranteed by the Winsock spec that we'll receive a the FD_WRITE notification we requested in the call on WSAAsyncSelect for the socket. Right. So in the timer, should we discover the socket has become unblocked for output and the fink didn't tell us, clear outputSocketBusy so things don't hang up.

To avoid output overruns, I changed the logic that responds to face image requests to ignore requests received while audio output is active. This should keep face data from pushing an 14.4 modem connection that is barely keeping up with GSM over the edge. The transmission of the face will be resumed by the timeout on the receiving end when the audio transmission is done with no harm done. I also tweaked the timeouts so they're less likely to collide with one another.

23 December 1995

Face bitmap exchange seems to hold the potential for bad medicine when stirred in the pot with multicasting. When sending to a multicast port, CONNECT.C now never offers a face to the subscribers, and face requests received from multicast ports (shouldn't happen, but who knows: it's Windows!) are ignored and the face retrieval status set to Abandoned.

Strengthened the .BMP file format verification in FACE.C. We now verify that the bitmap has one plane and <= 256 colours, as must be the case for any compliant bitmap.

Added more stringent verification of the format of received bitmaps in FACE.C before passing them on to CONNECT.C to display. This gives us some protection against rogues who let bogus bitmaps through, and weird errors in transmission of the the bitmap that corrupt it.

31 December 1996

Added an explicit +armor=off to all invocations of PGP to guarantee the session key is encrypted in binary mode even if the user has modified the PGP configuration file to make ASCII armour the default.

16 January 1996

Integrated the VOX, Break-In, VOX GSM compression, and anti-hangup code from Dave Hawkes. In the process of testing the integrated version, I made the following (perhaps temporary) changes:

Some people would like to be able to launch Speak Freely from another application, pointing it a given host with various preset options on the connection. Writing an .SFX file naming the host and specifying the options, then invoking SPEAKFRE.EXE with the .SFX file on the command line permits this, but the .SFX file had to specify both the host name and IP address, forcing the calling application to look up the host name. I modified the newConnection() code in FRAME.C to automatically look up the IP address if an .SFX file specifies only a host name.

A typo in the Connection/Save code could have led to nugatory void entries in the .SFX file. Fixed.

17 January 1996

Added a visual indication when VOX is squelching transmission. The ear cursor now changes to an ear with a big X through it (that's the best I can think of right now, but it's better than no indication at all). This makes it a lot easier to evaluate the effect of VOX speed, especially if you don't have a local machine to run tests.

Figured what was wrong with the ULAW.C data in the code segment trick. Apparently CONNECT.C and the libraries didn't get recompiled after the definition was changed to CONST_DATA, and continued to reference the Ulaw tables as FAR (as they must). I changed ULAW.C to explicitly place the tables in the code segment, but continue to reference them with a FAR declaration in ULAW.H.

19 January 1996

As suggested by Enoch Wexler, I added the ability to send and receive Show Your Face images in GIF as well as BMP format. GIFs offer substantial compression compared to even the compressed variants of BMP, which reduces the time it takes to transfer a face image and the likelihood of disrupting audio transmission in the process. Received GIF files are converted in-memory to BMP format by the new module GIFTOBMP which is based on the NETPBM utility GIFTOPNM. GIFTOPNM is copyright 1990, 1991, 1993, by David Koblas, who notes:

Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. This software is provided "as is" without express or implied warranty.

GIF file decompression requires substantial storage for the LZW decompression buffers and colour map tables. I modified the decompression code to move all large buffers to a dynamically allocated global storage block to avoid overlow of DGROUP and/or the inclusion of static storage which would block execution of multiple instances.

The face image selection dialogue in FACE.C was modified to allow selection of GIF images as well as BMP files.

Added a new VOX menu item which calls a new function in VOX.C, vox_reset_parameters() to restore all the VOX level adjustment parameters to their original defaults. I also added a Reset button to the VOX Monitor dialogue which does the same thing. In the process, I rearranged the contents of the Monitor dialogue to create enough room to spell out its title.

Added a new workaround that totally disables the DefaultMessageLoop lockup-prevention mechanism. While I think my 10 millisecond trigger based on GetTickCount() should be enough, this provides an escape hatch in case it isn't.

23 January 1996

Based on reports that receiving a Ring message can screw up the sound card (for example, muting the microphone), I demoted the previous default call on waveOutSetVolume() in SPEAKER.C which attempts to set maximum output volume when a ring is received to a Workaround which is off by default, "Set Maximum Volume on Ring". Only in the world of Windows would you suppose that something as innocent as a volume control would conceal sharp edges and booby traps awaiting the unsuspecting developer.

Based on input from Dave Hawkes, I revised the DefaultMessageLoop code once again. This time it keeps track of the last time the program potentially yielded control to another application (by doing a PeekMessage with the PM_NOYIELD and PM_NOREMOVE flags in the main message loop in NETFONE.C, which seems to return fast enough to avoid pauses, and saving the GetTickCount() value if there is no message in the queue). Then, if DefaultMessageLoop() discovers 350 milliseconds or more have elapsed since the last yield, flushes the message queue using PeekMessage(), which will allow other applications to gain control of the CPU.

Dave also pointed out that my pointy-headed code that changes the cursor when VOX muting occurs changed the cursor even if it was outside the window. Fixed.

25 January 1996

The workaround that disables the DefaultMessageLoop() insurance did not actually turn off all traces of the code--the PeekMessage in the main message loop in NETFONE.C still remained. Since I'm sure this will cause unspeakable horrors when it triggers some booby trap Billy-boy has hidden in one of his existing products or is in store for us in the future, I made sure it's disabled when DefaultMessageLoop() is turned off.

6 February 1996

Made the inclusion of encryption conditional on the tag CRYPTO being defined in NETFONE.H. If CRYPTO is not defined, the version number in the About dialogue will have a suffix of " (no crypto)" and the IDEA patent notice will be replaced by an explanation of where to obtain a version including full encryption. The "Encryption" box in the Options/Connection dialogue will contain a more detailed explanation of the no-crypto edition. The Options/Create Key menu item is disabled in no-crypto builds. What's the rationale for this? Simple: a number of CD-ROM publishers and sound card manufacturers are interested in distributing Speak Freely. But since many will be shipping from the U.S. and other countries which attempt to restrict the export of "munitions" like Speak Freely they're afraid, and rightly so, that putting Speak Freely in the box might result in an all-expenses paid extended vacation at Club Fed. The non-crypto version allows them to include Speak Freely without such worries. Once a user has installed Speak Freely and is ready to start using encryption, they can simply follow the instructions in the dialogue boxes (and, soon, the help file) and download a full-encryption version from a site in a country which does not restrict cryptographic software. The non-crypto version can also be posted on bulletin board and commercial online services without risking government-initiated unpleasantness.

Note that undefining CRYPTO does not just block access to encryption and decryption; it totally removes the code from the program--the encryption libraries are never referenced and therefore not included by the linker in the executable. Thus there is no risk of a non-crypto build being deemed a munition. The only "cryppish" code that remains is MD5, and it is widely used (for example, in the export edition of Netscape) in non-encryption roles. In non-crypto Speak Freely, the RTP SSRC, timestamp, and packet sequence numbers are generated directly with MD5 rather than the somewhat more random idearand() used in crypto builds. Since we're not going to encrypt the RTP packets anyway, this doesn't compromise anything.

As the first step in integrating the RTP support code from the Unix version, replaced RTPACKET.C with the fully-functional one and verified the new rtcp_make_sdes() and rtcp_make_bye() didn't break LWL support.

Here's where we stand at the end of first day of the campaign to integrate RTP and VAT support into Speak Freely for Windows. The two procotol translation modules, RTPACKET.C and VATPKT.C and all their support files have been included in the program and fixed to compile without errors or warnings. As noted above, the new RTPACKET.C continues to generate valid packets for the LWL server. Sending both VAT and RTP protocol works for all compression modes, testing with VAT on another machine. VAT correctly recognises the VAT ID and RTCP SDES message we send on the control channel.

7 February 1996

Trying to integrate the LIBDES encryption package need for VAT and RTP encryption blew the data segment, so it's time to run another sweep for excess baryon particles. Using the same CONSTANT_DATA trick as in ULAW.H, I moved the large constant tables in LIBDES\FCRYPT.C and DES\DES.C into the code segment. Result: still over the brink.

The biggest memory hog, LPC\LPC.C, was unfortunately not so easily fixed, since its four large floating point analysis vectors are read/write and cannot be hidden in the code segment. I integrated a modified version of the LPC.C from NeVoT, in which the state of the decoder is in a dynamically allocated buffer. I obtain this buffer with GlobalAlloc, getting it out of the static data segment. This of course required FARs all over the place in LPC\LPC.C, but it did the trick. This will also allow, if we decide it's worth doing, maintaining a separate LPC state for each inbound connection.

It was intensely irritating to have to constantly answer E-mail from people who tried to build Speak Freely from source code but whose Winsocks not only didn't support multicast, their WINSOCK.H didn't even include the definitions for multicast. I fixed FRAME.C, DIALOGS.C, and NETFONE.H to, if IP_MAX_MEMBERSHIPS isn't defined, silently delete multicast from the build. If the user's Winsock doesn't define the variables we need to generate the code, there's no way he's going to be able to use the feature anyway. The Unix version uses the same trick to adapt to pre-multicast sockets implementations.

Swept through the program and added the fProtocol flag to all places Speak Freely protocol packets are generated. This flag helps receivers distinguish Speak Freely packets from VAT and RTP messages.

Went a long way toward implementing DES encryption of outbound RTP and VAT packets. It pretty much works--I'll make the final round of tests when DES works in both directions and I can verify correct operation in both directions.

8 February 1996

I modified code in CONNECT.C and FRAME.C to zero the SDES resend timer when the transmit protocol or encryption key is changed. This causes an immediate resend of the SDES/VAT ID in the new mode, which will help the receiver to "sync up" with the change.

I discovered that the clever way I integrated VAT and RTP encryption into sendpkt() in CONNECT.C had completely screwed up encryption for Speak Freely protocol. In the process of fixing this, I cleaned up some of the rather tangled logic in that function.

Added code to the Options/Connection dialogue to disable the fields and captions for IDEA, PGP, and Key file compression if the protocol is RTP or VAT. These protocols currently specify only DES as a standard mode.

Set the "talk spurt" flag for the first packet of a sound file.

Added VAT packet translation to sound file output in CONNECT.C.

Under certain circumstances, sending a sound file to a connection after sending a ring would just re-send the ring. Fixed in FRAME.C.

Disabled direct modem connections. This feature, which fell into the trapdoor called Windows serial port (8250) support, compounded the incoming E-mail pain due to idiots confusing direct dial-up modem connections with SLIP/PPP Internet access. Besides, since Serial I/O is near the top of the Redmond Kiddies' "API of the Year" list, the time it takes to debug it on all the ratty drivers out there exceeds the product life cycle. Users who wish to use Speak Freely as a phone scrambler on direct calls should establish a peer-to-peer TCP/IP connection and use Speak Freely in network mode. Since it will probably take Billy's bozos 20 or 30 years to debug Windows to Windows TCP/IP links, the fact that they'll blame their screwups on other vulnerable applications as well as Speak Freely will deflect a significant percentage of flames, albeit not to the flamers responsible for the mess in the first place.

9 February 1996

Voice activation didn't work with RTP and VAT protocols because load_vox_type_params() in VOX.C didn't know about the new packet sizes used by those protocols. Now it does. Independently, LPC and VOX don't seem to be getting along very well together, regardless of protocol. I'll have to look into this later on.

Coming to terms with the fact that I'll be chasing "bugs" in this program as long as there are Kode Kiddies in Redmond, I integrated the hex dump module from the Unix version. It lets me dump packets on the debug stream to see where Winsock wants to go today.

Transmission of non-encrypted VAT and RTP packets now seems to be working. That's not to say that DES encryption doesn't work, just that I haven't tested it yet. The initial tests of bouncing VAT messages off the echo server failed due to byte order dependencies in VATPKT.C. These are now fixed; the changes must now be integrated into the Unix stream to handle little-endian boxes.

10 February 1996

Added code to the WM_DESTROY handler in CONNECT.C to transmit an RTP or VAT BYE message to indicate the user has closed the connection. In the process, I added a new sendSessionControl() function which is used by both this logic and the periodic RTCP/VAT ID transmission code in the timer.

Discovered that the sockets were getting closed in WM_CLOSE rather than WM_DESTROY, which kept the BYE transmission from working. I moved the socket close to after the BYE is sent in WM_CLOSE.

11 February 1996

Well, I think I finally found out why weird things occasionally happened when you quit Speak Freely with one or more connection windows open. This is really getting too depressing to document, but here we go. Windows, with its unerring instinct for doing things in the most idiotic way possible, sends a WM_DESTROY to the application's outer window procedure, and then later sends WM_DESTROY to each of the child windows. Suppose one or more of those child windows need to do something--send a BYE message, for example--using one of the resources that get freed by the application's outermost WM_DESTROY? Blooie. But don't think you can get away with just sending WM_DESTROY to each of the child windows: nopey, nopey, no. If you try that you fall into the toilet because calling WM_DESTROY doesn't actually make them go away, and as a result they get destroyed twice and all kinds of other horrors ensue. So, once again we are forced into subterfuge by the quintessential inelegance of Windows. We dig out of this particular hole by sending a custom WM_CLEAN_UP_YOUR_ACT message to the child window to tell it we're terminating. The child will then do its regular WM_DESTROY cleanup, including releasing the client data pointer and zeroing the window word to it. When the actual WM_DESTROY arrives, it will discover the client data pointer is zero and avoid executing the cleanup twice.

12 February 1996

Integrated the new LWL\LWL.C library from the Unix version, which allows a separate decoder state for each receiver and contains numerous fixes for subtle coder gotchas such as dividing by zero if total silence is received. This, of course, ran squarely into one of the innumerable floating point code generation bugs in Visual C++ 1.5 (they call it "Visual" because only if you're seeing things would you confuse it for a production compiler for floating-point intensive code). After a fine afternoon of trying various compiler options and workarounds, I found a combination of restructuring of the loop which caused the "Stack overfl" (a very Redmond kind of error message, don't you think?) and optimisation options which got around the error. Until the next "improvement" of the compiler, I'm sure.

Found another pair of missing htons() in the LPC packet handlers in VATPKT.C and RTPACKET.C, when stuffing the decoded length into the first two bytes of the sound buffer.

Memo to file. Windows' real-time response is so pitiful that machines which are perfectly adequate to run Speak Freely protocol in all compression and encryption modes (a 486/50, for example) can't cope with the smaller packet sizes used by RTP and VAT, particularly on reception. If you want to talk to somebody who can only send RTP or VAT, you'd better make sure you have enough Intel inside to cope with Billy Boy's idea of process switch latency.

13 February 1996

Integrated a fix to rtpout() in RTPACKET.C from the Unix version. The packet length for outbound RTP ADPCM packets was 2 bytes short, which caused "gravelly" speech and horrible ticking when encryption was enabled.

It's possible for the predicted value in the ADPCM coder (ADPCM/ADPCM_U.C) to exceed the range of a signed 16 bit linear sample. Clamping code limits the range when this happens, but needed to declare the unclamped sample as a long rather than int to work on a 16 bit architecture. Fixed.

After many, many hours of painful, unremunerated toil I finally figured out what was causing the Debug kernel warnings and fatal errors due to bad pointers at the time we call WSACleanup at application termination time. The essential clue was that it only happens if one or more connection windows have been created by the receipt of a packet from a remote site not already connected. If all connections were created locally it never happened. And, of course, this only happened under the Winsock supplied with Sun PC-NFS 5.1--the problem never occurred under any circumstances on other Winsocks I've tried.

I'm sure by now you will be shocked and stunned to learn that Sun NFS 5.1 doesn't correctly implement the WSAAsyncGetHostByAddr function. Oh, you can make the call, and you even get back a valid host name. But doing so plants a time bomb which will kill you (at least under the debug kernel) much, much later when you call WSACleanup() right before exiting the program. At that time, depending on where the random pointer inside their so-called WSHELPER points, you get either two invalid global pointer errors or a fatal error due to an object usage count underflow in the (bogus) global block. If the waNetSynchronousGetHostnameAction is set, we eschew the asynchronous request and make the user wait for a blocking gethostbyaddr() which has the merit, at least, of not blowing us away at program termination time.

waNetSynchronousGetHostnameAction is set, in turn, based on the workaround waNetSynchronousGetHostname, which can take on the values 0, 1, and 2. If 2, the default, asynchronous host name retrieval is disabled if the Winsock identifies itself in the szDescription field of the WSAData structure returned by WSAStartup() as "Sun Select PC-NFS Windows Sockets Implementation". This automatic selection can be overridden by the user explicitly checking or unchecking the Options / Workarounds / Network / Get &Host Name Synchronously menu item. Thereafter, the user's selection will be used regardless of the identity the Winsock reports.

In the process of adding profile variable support for the above workaround, I observed the number of workarounds was about to exceed the rstring() cache in the name of the workarounds section remained. Since, unlike the developers of Microsoft tools, I do not feed off human suffering and take joy in setting booby traps, I modified all the profile read and write code to copy the section name to a stack string variable rather than rely on the pointer within rstring()'s retrieval area to remain valid while all variables in the section are accessed.

14 February 1996

Integrated the new RTPACKET.C, RTPACKET.H, and DESKEY.C from the Unix version. These include the facilities we'll need for parsing SDES packets, recognising BYEs, and creating RTP keys from key strings compliant with RFC 1890.f

Adjusted packet sizes returned by inputSampleCount() (FRAME.C) for VAT to the maximum permitted within both the experience base of VAT and the 512 byte guaranteed MTU of Winsock.

Integrated a fix from the Unix version to guarantee (in our context) that pad bytes added to VAT and RTP packets are zeroed.

Modified makeVATid() in VATPKT.C to, as VAT does, prefer the user's full name to the E-mail address if both are available.

Added recognition of RTP and VAT SDES/ID packets in FRAME.C. The title of the connection window will now show the user's name, if supplied by the sender. The changeAudioState() function in CONNECT.C also uses the user name, if available, in preference to the host name when it updates the window title to indicate transmit state.

RTP and VAT BYE/DONE packets now cause the receive protocol to be reset to PROTOCOL_UNKNOWN. This expedites recognition of a new protocol if the sender switches on the fly. Changed in FRAME.C.

Integrated generation of RFC 1890 RTP key and separate old-protocol VAT key. I've still to integrate automatic protocol and key sensing.

Added code to encrypt outbound RTP and VAT packets with the appropriate key. The inbound side remains to be done.

Nailed another encryption packet size rounding error in CONNECT.C, this time affecting ADPCM encoded outbound packets in VAT protocol.

15 February 1995

I remembered that there was one more place the PC-NFS 5.1 WSAAsyncGetHostByAddr() bug could stab us in the back--in the case where the user enters a numeric IP address in the Connection/New dialogue. I added a gethostbyaddr() alternative to this call if waNetSynchronousGetHostnameAction is set.

Finished integrating automatic protocol sensing and key selection for encrypted inbound RTP and VAT packets. The logic was a little less tangled than in the Unix version since we process control and data packets in different callback functions.

Did a non-CRYPTO build to make sure all the RTP and VAT changes didn't break something or suck in verboten bits. Sure enough, all of DESKEY.C needed #ifdef CRYPTO, as well as the encryption code in CONNECT.C's sendSessionControl(). Fixed.

Implemented the guts of the local loopback facility--it works, but tuning and a nice user interface remain to be done. Why local loopback? So users can debug their audio hardware problems before venturing onto the net, which will be one of the steps in the "beginner's guide to Speak Freely" I'll get around to writing one of these days. Eventually there will be a Help menu item which creates a local echo connection, but for the moment you activate such a connection by making a new connection to "localhost" (or, if your Winsock doesn't know localhost from Casper the Friendly Ghost, 127.0.0.1). Any packets you send are saved in memory until the end of your transmission and then returned, after a short delay, as if echoed by a remote site. This is, then, an echo server that doesn't use the network. Not only does it let users experiment with audio hardware locally, it allows isolating network-induced problems from those which inhere in the CPU or audio hardware.

16 February 1996

Modified changeAudioState to invalidate the connection window without erasing the background. This makes it quicker to repaint the "Transmitting" or blank status when the audio state changes.

Added support for Speak Freely SDES messages on the control port. When a Speak Freely connection is open, RTCP SDES messages with a protocol ID of 1 (the old RTP, used by no application I know of) are sent on the control port. These messages allow unambiguous recognition of Speak Freely protocol, transfer of user information, and disconnect notification.

The connection window paint code in CONNECT.C now displays the current sending protocol, user name, and E-mail address of the connected user. The connection window is resized depending on the number of lines currently displayed.

17 February 1996

Added the ability to specify a port number for a connection. This required changes all over the place:

Note that auxiliary sockets are not bound to a specific host; once a connection is established with a given port, connections from any host can be remotely initiated on that port. This means that if you want to accept connections on a given port as a matter of course, you can do so simply by opening a dummy connection (to a nonexistent address on your subnet, for example) with that port. I'll probably eventually add a separate dialogue that lets you specify ports to monitor automatically but for the moment this gets the job done. Most users will be specifying ports to connect to remote RTP and VAT conferences anyway, not accepting calls on nonstandard ports.

Guess what? Sun PC-NFS 5.1 Winsock will not only blow you away if you call WSAAsyncGetHostByAddr(), the blocking version, gethostbyaddr() has a bug in it as well--it forgets to null-terminate the host name in the h_name field, so if you retrieve a host with a name shorter than the last one part of the last host name still sticks out. Working around this by zeroing the host name after you retrieve it is a blatant violation of the Winsock spec which states (section 4.2.1) "The application must never attempt to modify this structure or to free any of its components.". I for one, am not going to add my name to the list of millions who ignore the Winsock spec, so there isn't a damned thing I can do this other than tell people to get a better Winsock. Fortunately, it's purely an ugliness that doesn't do any damage since we're just retrieving the host name to display in the connection window.

Oops! In Speak Freely protocol, control channel messages aren't supposed to be encrypted but they were. Fixed.

What the world needs now, is lots more workarounds, they're the only thing that drive the bugs to ground.... So, some more anticipatory retaliation: the following are available on the new Options/Workarounds/Protocol submenu. All are, of course, saved in the .INI file and otherwise treated as respectable citizens.

No Speak Freely Heartbeat
Disable the periodic Speak Freely protocol heartbeat on the control channel. This is primarily intended as a last resort if the (less than 1%) added bandwidth saturates a close to the edge connection, and also in case the control channel packets awake something horrid lurking on the next higher channel.
Large RTP Protocol Packets
Uses Speak Freely's preferred packet sizes for GSM and LPC compression rather than those typically sent by RTP programs. Most RTP programs were developed on fast workstations with high bandwidth network connectivity. Speak Freely users generally have slower machines and network links which benefit from larger packets. Try this if the person you're talking to reports halting audio in RTP protocol.
Disable VAT Protocol Detection
VAT protocol will never be automatically selected as a result of receiving a message on the control channel which resembles a VAT control message. Enable this if you never receive VAT protocol messages and are annoyed at how long it takes to identify the protocol of encrypted RTP messages.
Disable RTP Protocol Detection
RTP protocol will never be automatically selected as a result of receiving a message on the control channel which resembles a RTP control message. Enable this if you never receive RTP protocol messages and are annoyed at how long it takes to identify the protocol of encrypted VAT messages.
No Encryption of RTP Control Packets
RTP control packets can, according to the standard, be sent either encrypted or in the clear. Most RTP programs I've encountered encrypt their control packets, so this is the default Speak Freely sends (it accepts both encrypted and clear packets). If you set this workaround, control packets are sent in the clear.

19 February 1996

After further deliberations, I decided not to implement automatic protocol switching to the protocol received from the active window, although much of the infrastructure to do so is in place. The reason is that simply adding the new dimension of multiple protocols has the potential for inducing further confusion among users who don't understand the distinction between the compression mode received and that used in transmission. Trying to explain all the possible conditions one could get into with automatic protocol switching is probably futile. I may eventually put in a warning that pops up if the user tries to transmit to a connection which has sent us packets in a different protocol than the current transmit protocol. Protocol mismatch is never a problem when communicating with other copies of Speak Freely, since it auto-senses the protocol. Since initially relatively few users will be talking to other programs, those cutting-edge users are probably best encouraged to operate in "manual transmission" mode to avoid confusion.

The gimmick that forces immediate transmission of the identity message on the control channel (rather than waiting for the next timer interval) wasn't doing so when the protocol is set to Speak Freely. Fixed in FRAME.C.

Drat! When I added the port number criterion to decide whether a connection was already open, I forgot to handle local loopback. Fixed.

Added direct pointers from the Help menu to the FAQ and Mailing List sections of the Help file, and to create local loopback connection directly.

Direct access to local loopback required a tweak in CONNECT.C to not attempt to turn the loopback IP address into a host name.

20 February 1996

sendSessionControl() in CONNECT.C wasn't incrementing packetsSent for the extended status dialogue. Fixed.

loop_sendto() in LOOPBACK.C wasn't returning SOCKET_ERROR and setting the last error code as it should if it can't allocate the loopback buffer. Fixed.

To eliminate the choppiness that afflicted local loopback, particularly with the small packets sent by VAT protocol, I modified loopback replay to adopt a strategy of keeping the output queue stuffed with packets up to a limit of 10, and refilling the queue to that length every 10 milliseconds (Hah!! More like when Windows gets around to us.) rather than attempting to time each packet to a time resolution Windows just can't handle. (If I used the multimedia timer, it probably could, but that requires interrupt code call-backs into a DLL and imposes restrictions on what we can do from the call-back that Speak Freely couldn't live with.)

Naturally, this straightforward approach walked squarely into the jaws of disaster. The message loop insurance code was causing the timer code to be re-entered while it was playing back loopback packets, setting off a spectacular riot of recursion. I disabled the message loop insurance for loopback packet playback. Since we're strictly controlling the rate of packet arrival from loopback and the length of the stream is limited anyway, we don't really need the message loop insurance, which is intended to keep packets arriving from the network from hanging us.

21 February 1996

I added some code to the MM_WIM_DATA message handler in FRAME.C to soften the impact of the anti-lockup code on outbound audio quality. If a machine is right on the ragged edge of being able to compress in real time (a 486/50 sending GSM, for example), occasional Windows-induced delays will trigger the anti-lockup code and cause a sound packet to be dropped. I added code that allows recovery from one re-entry to the message handler by saving the packet and processing it immediately after the already-underway packet. Re-entries while an already saved packet awaits processing continue to discard packets.

The anti-lockup code in MM_WIM_DATA and socketInput did not increment the appropriate PacketLost counters. Fixed.

Added an item to the Help menu that points people directly to the echo server topic in the help file.

Building on Dave Hawkes' insight that the Windows wave audio output pause and restart could be used to implement a de-jittering replay delay with no buffering logic within Speak Freely, I implemented a first cut at de-jittering. Any input packet from the network which causes us to acquire audio output is considered the start of a "talk spurt". (Once we've transitioned to RTP, we can use the packet header bit for this, but we have to get there somehow.) When such a packet is received, if the jitterBuf has a nonzero replay delay in milliseconds, a timer with that expiration is launched to trigger the replay and wave audio output is paused. Wave audio output is restarted when the timer expires, or if the number of packets queued for replay exceeeds half the number of messages in the input queue (detected in SPEAKER.C). A new Options/Jitter Compensation menu item allows specifying the initial delay for a talk spurt. The longer the delay, the greater the suppression of jitter, but at the cost of a greater time parallax between the reception of the packet and its being played on the speaker.

When shutting down audio input, the final partial packet of sound before the shutdown could be lost. Fixing this little buglet naturally required massive changes to how audio input is torn down, since Windows likes to return packets from the queue in any old order at shutdown time, but imposes on the application a rigid order in which the API must be called. The terminateWaveInput() function in FRAME.C now actually does no such thing. In fact, it just resets audio input, causing any partial packet and the rest of the input queue to be returned to the message loop. Code for the MM_WIM_DATA message now processes any partial packets, padding them if necessary to the length prescribed by the protocol (with the correct pad depending on whether audio is 8 or 16 bits--gosh this is fun!) and sends any non-zero-length packets. If termination is underway, packets are unprepared and released, and an allocated packet counter decremented. When that counter goes to zero, wave audio is finally actually closed.

The above fix of course broke how Options/Break Input manages the transition between input and output mode for half-duplex audio hardware. Fixed (I think, pending reports to the contrary from the field).

22 February 1995

One more tweak to Break Input--if inputPaused is set, the WM_MIM_DATA handler in FRAME.C now immediately discards any partial packets that are returned during input termination. This speeds up the transition to playing the packets arriving from the socket.

26 February 1996

Port numbers greater than 32767 were not accepted in Connection/New due to being scanned as a signed short rather than unsigned. Fixed.

The supposedly private bits used by the answering machine to mark the start of a transmission conflicted with the fProtocol flag bit, resulting in each Speak Freely protocol packet being considered the start of a separate message by the answering machine. Fixed.

29 February 1996

Dodging another intracardial dagger from our south-of-the-equator purveyors of what purport to be WINSOCK drivers introduces another layer of unnecessary and unwarranted complexity. The WINSOCK spec allows mutant windsuckers to abort any call that "re-enters" WINSOCK with a WSAEINPROGRESS call. Fine: what would you expect from the "vision of the future is a reboot in the face forever" people? But could you imagine, even in your wildest fantasy, that the most innocent of all socket calls, the one which places a socket in non-blocking mode, could itself blow off if a so-called blocking call (and many calls so-deemed have no non-blocking variants) is in progress? So, I turned the code that sends the heartbeat to the Look Who's Listening server inside out to cope with this crap, and thereby avoid the dreaded "Operation already in progress" puke-o-rama which, on some WINSOCKs poisons all future network accesses. I am sure this will have ugly consequences on other buggy platforms which will become apparent in the weeks and months to come.

Failure to look up the host name corresponding to an IP address after a connection was made displayed an error dialogue. Little did I know that 95% of all Windows 95 users do not have a valid domain name server configured, and each and every one of them E-mail me when this message appears. Warning message deleted; don't keep those cards and letters coming.

1 March 1996

Integrated the changes to VATPKT.C from the Unix version to recognise IDLIST packets as valid to to provide, in the future, the ability to include a conference ID in the packets we send.

Integrated the fixes from the Unix version into FRAME.C to recognise VAT IDLIST (3) packets and correctly parse the user names therein. This allows us to connect in VAT protocol to CU-SeeMe reflectors. As part of this change, the user name field (uname) in the connection structure was made a dynamically allocated buffer to permit long lists of participants in a conference.

I modified the connection creation logic in controlInput() in FRAME.C to never create a new VAT protocol connection unless an ID (1) message is received. This keeps IDLISTS (3) which rain in from conferences you've just left from re-opening the conference connection. Also, it was inelegant to open a connection based on a VAT BYE from the blue. A remote VAT connection will be opened only upon the receipt of an unencrypted ID packet. Note that we still have a problem with VAT conference Lazarus connections which result from VAT packets which arrive on the data port and are mistaken for pre-6.0 Speak Freely sound packets--they won't be played, but they'll still open the connection. This will go away once we've gotten everybody on 6.0 and require control port session control unless a special workaround is set to communicate with older versions.

Added logic in the connection window WM_PAINT handler in CONNECT.C to distinguish a VAT IDLIST (and one of these days, a multi-party RTCP SDES) from a simple ID and list the users in the conference one one per line.

After an afternoon of flailing around that sacrified the requisite number of neurons, I finally figured out a way to handle a user quitting the program while audio output is active which does not lead to sudden death. This involves the usual flags, mushy timers, countdowns and activity tests which Windows requires to do even the simplest things, and is much too depressing to discuss here. If you must see it, start at the WM_CLOSE handler in FRAME.C and follow the trail of slime through the rest of that file.

Echo, voice on demand, and reflector servers all have a tendency to create "Lazarus connections" which, seconds after you close them, pop back into existence when a packet comes back from the other end. To prevent this, I added a special anti-Lazarus mechanism in FRAME.C and CONNECT.C. When the user closes a connection window, the WM_CLEAN_UP_YOUR_ACT message handler in CONNECT.C saves the IP address of the host in a new global Lazarus and sets the timeout counter LazarusLong to LazarusLength (15 seconds as presently configured), which is decremented by the main one-second timer in FRAME.C. If a packet arrives from the last connection window to be closed while LazarusLong has not yet counted down to zero, is it discarded by code in socketInput and controlInput in FRAME.C before it causes the connection to be reestablished. This provides a "decent interval" to allow postmortem packets arriving from the remote host to be discarded without re-opening the connection window.

To avoid the ignominy of shipping a release containing OutputDebugString diagnostic output, I added an updated version of the check for debug output in a production build that's used in Home Planet. The new version provides a primate-readable description of the error that points to the offending line and doesn't interfere with compilation of the rest of the file.

2 March 1996

Fixed a place in isHalfDuplex() in FRAME.C where in the case of an error in the process of determining whether audio is half duplex, the wave format buffer could be freed twice.

For some reason, trying to compile the program on a Pentium with twice as much free RAM as the 486, the resource compiler dies in the middle of windowsx.h with "Out of far heap". I excluded this file from resource compiler builds, and the little pointy head now deigns to work.

The VAT IDLIST packet parsing in FRAME.C's controlInput() had an off-by-one error when the name string length was a multiple of 4 and the terminating '\0' fell into the next 4 byte segment. Fixed.

One final gratuitous Gatesian gutshot this fine Saturday night--Virtual (if you confuse it for a real compiler, you're seeing things) C 1.52c generates bad code for the VAT IDLIST packet parser in controlInput() (FRAME.C) when full optimisation is selected. (I'm sure, gentle reader, this will surprise you, having come this far with me down the rathole.) So, I #pragma'ed off all optimisation in that function, wishing there were were some way I could #fragma the "making it all too much" crowd spewing their ghastly gigabytes into an industry I was once proud to be a part of.

6.0-Alpha 4 prerelease.

3 March 1996

Integrated lots of fixes all over the place for 32 bit compile problems. I made these changes in a very conservative manner--what the compiler sees when compiling the 16 bit version should be identical to the code before the fixes were installed.

4 March 1996

People were having so much trouble getting the automatic adaptive VOX to work that I disabled it and replaced it with a manual set-the-level yourself mechanism. The VOX Monitor dialogue now allows you move the red threshold indicator with the scroll bar with complete freedom, while (if audio input is live) showing the VU meter as before. Slow, Medium, and Fast continue to regulate the number of samples of silence which must be seen before transmission mutes. There is no need with a manual VOX adjustment for a Reset facility, so the menu item and button in the monitor dialogue for that function were removed. I also disabled VOX GSM compression mode, since I'm afraid explaining its interaction with manual VOX would only confuse people. All the code is still there for adaptive VOX and VOX GSM compression--if you compile with VOX_GSM defined, it will all come back.

Just as I suspected, the workaround for Trumpet Winsock's WSAEINPROGRESS bugs in contacting the Look Who's Listening server walked right into the jaws of another flaky Winsock--this time the one that comes with Windows 95. You apparently can't count on it to always notify you when a socket is closed, even if you've requested such notification via WSAAsyncSelect(). This led to timeout warning messages, attempts to make socket calls on already-closed sockets, and other horrors. Throwing up my virtual hands in disgust, I ripped out all message-based event sequencing of the LWL socket in FRAME.C and replaced it with timer logic. This is a crazy way of doing what should be trivial, but it's the only way to guarantee (to the extent one can ever use that word in conjunction with something associated with Windows) we won't be nailed by timing windows, lost notifications, or other flaky behaviour on the part of Winsock.

5 March 1996

Well, that didn't work on a production build under PC-NFS, because a blocking connect (to a slow-to-respond LWL server) could interfere with data transmission. So (and I have a very, very bad feeling about this), I made LWL transmission entirely non-blocking. When we receive the FD_CONNECT notification, that triggers the send() and sets the timer to close the socket 8 seconds later (since we don't dare count on linger mode to work properly). Wanna bet we need a timer to back up the FD_CONNECT notification in case Winsock forgets to send us one? We'll see.

Added some additional paranoia in LWL.C to make sure no traffic is sent to the LWL server while a non-blocking transmission is in progress.

6.0-Alpha 5 prerelease.

7 March 1996

It's reported that connecting to a VAT conference with 35 people active causes a "runtime error 202 at 0001:1E". I don't have the vaguest idea what is causing this, what the error number means, or even where the error message is coming from, and I can't reproduce it since I don't have access to multicast to get on such a conference. I am not going to let nonsense like this deny the 6.0 update to tens of thousands of users who will never go near anything like this. I just hammered a test in CONNECT.C that limits the number of participants displayed to 8 and puts an ellipsis at the end of the list if there are more than that.

Added code to multicastJoin() in FRAME.C to join or drop any auxiliary sockets to the current multicast list, as well as the default port sockets.

Modified monitorPort in CONNECT.C to call multicastJoin to drop and then reacquire the multicast memberships whenever a new auxiliary socket is created.

8 March 1996

Once again we see how the poor design of Windows turns what is conceptually an easy task into a snowdrift, nay polar caps, of tangled and tricky code that one is never really sure will work everywhere. This time it's double clicking in the connection window to begin continuous transmission (which a lot more people will be doing now that we have VOX and Break Input). Remember back on the 21st of February when I redid the logic of how audio input is switched off so as dodge various bullets in the multimedia complex? Well guess what...ever since then double clicking hasn't worked (I didn't notice this since I generally use the space bar). Now, the double click logic in CONNECT.C couldn't have been simpler or more compliant with the Windows interface guidelines--the second click merely extends the scope of what the first one does, and does so simply by not switching off input on the button up event following a double click. Ahhh, but recall that you can't just turn audio input on and off like a light switch. It takes a while for the input buffers to rattle through the message queue and all the assorted dust to settle. So when we received the double click and subsequent button up event, audio input was still in the process of being terminated (as a result of the first button up event), and that's no time to go and re-open it.

What to do? Well, here comes another brutal hack necessitated by irrational Microsoft design. When we process an WM_LBUTTONUP in CONNECT.C, we no longer close audio input to the connection. Instead, we set a timer (does this sound familiar?) running to expire GetDoubleClickTime() after the button was released. If we see a WM_LBUTTONDBLCLK an its subsequent WM_LBUTTONUP before the timer expires, we revoke the time with KillTimer() and leave audio input active. If the timer goes off without our having seen further mouse action, audio input to the connection is shut off at that time through the expedient of faking a space bar input to the message loop. This is spectacularly ugly but it gets the job done.

12 March 1996

I fired up the Large Software Collider for another day of flailing away with Visual C++ 4.0 under Windows 95. I discovered that the last single remaining piece of non-paranoid code in RTPACKET.C, the part that innocently stores the 16 bit length into RTCP SDES items, ran afoul of Visual C++'s talent for invariably making the wrong choice when given the slightest latitude for doing so. In this case we had a structure with several bit fields which added up to 16 bits followed by an unsigned short. Visual C++ padded the bit fields (which we are not use anyway, since it would store them in backwards order) to a 32 bit boundary before generating the short. I changed the code to use hard-coded byte numbers and casts everywhere, essentially treating Visual C++ as an assembler, which is approximately its intellectual level.

Integrated recent fixes from the 16 bit development stream to bring the source streams into sync. From now on the 32 bit version on the LSC will be the primary stream, with the 16 bit version automatically recompiled from the common source.

In the process of being led down the garden path by a bug in BoundsChecker "Compile-Time Instrumentation" (GlobalFreePtr() in windowsx.h in WIN32 generates bogus bad handle messages), I cleaned up some code belch in FRAME.C by eliminating the dynamic allocation and free of the PCMWAVEFORMAT structure in various functions. The structure is small; there's no reason not to just put it on the stack and get rid of all the error tests, frees on various paths, etc. ugly etc.

Simple file I/O is so ubquitous in programs that it was an irresistable target for the Giant Rat of Redmond. Now you're supposed to open files using CreateFile function (elegant choice of name, don't you think), which takes 6 pages to document in the API book. The old offically sanctioned way of dealing with file, _lopen(), _lread(), etc., which worked perfectly well, are still there, but they've gratuitously deleted the definition of READ_WRITE, which was how you were supposed to specify that mode. I examined WINDOWS.H for Windows 3.1 and discovered that OF_READWRITE, the new officially sanctioned name, has the same numerical value and is defined in 3.1's WINDOWS.H, so I just changed the references in CONNECT.C and FRAME.C to use that symbol in order to compile on both 16 and 32 bit without an ugly #ifdef. This, and removing an incorrect earlier fix which called _sopen (yet another entirely incompatible way of doing I/O) instead of _lopen, got sound files and ring working on 32 bit.

14 March 1996

The Look Who's Listening dialogues weren't working because they were testing for edit control notifications using hard-coded Win 3.1 notification codes. I changed them to use WM_COMMAND_NOTIFY, which fetches the notification code from the correct place for both 16 and 32 bit builds. Fixes were needed in both LWL.C and DIALOGS.C.

The handling of the WM_MDIACTIVATE message in COMMAND.C had to be revised due to the "packing" change in its arguments between Win 16 and 32. After threading through the lies and disinformation provided by what purports to be a guide to porting code to 32 bit Windows (found on the current MSDN CD-ROM), I fixed the code to work correctly on both 16 and 32 bit.

Found and "fixed" (in other words uglified) two more places in RTPACKET.C where the straightforward code from the RTP standard ran afoul of Visual C++ 4.0's eccentric ideas about structure alignment (which, as far as I know, no other 32 bit compiler seems to share).

When I disabled modem support, I forgot that CRC.C was used only by the modem and didn't disable it as well, which wasted a little data segment space. Fixed.

To avoid future disasters stemming from unwarranted trust in Microsoft compilers, I #ifdef-ed out the definitions of the RTP and RTCP packet structures in RTP.H, leaving them for documentation only. Guess what? Another bunch of field references popped out in LWL.C and RTPACKET.C. Most were harmless (at least on VC 4.0), but the ones that weren't explained why LWL searches weren't working. So, as far as I can tell, LWL publish and search are now both working again.

BoundsChecker was nattering about how we terminate the main MDI frame window at exit time. I added a handler for WM_NCDESTROY to zero the hwndMDIClient handle so a (harmless) "bad window handle" is not passed to DefFrameProc() in FRAME.C.

Who you gonna call? BoundsChecker!!! Staked a memory leak in the processing of VAT IDLIST packets in FRAME.C. If an IDLIST packet was identical to the last displayed, it was never released. Fixed.

Memo to file: BoundsChecker will report several memory leaks of buffers allocated by malloc() by the various libraries. These are nothing to worry about, as they are allocated on the local heap and do not leave resources in use after Speak Freely exits. There is no reason to complicate the termination code purely to appease BoundsChecker.

15 March 1996

When I made the WAVEFORMAT structures automatic rather than dynamically allocated, I forgot to delete the error message for allocation failure. It's gone.

If something blew up in processing at WM_CREATE time, it's possible the WM_DESTROY handler could try to walk through the MDI windows using a NULL MDI client window handle. Fixed.

The WM_CREATE handler, onCreate() in FRAME.C, returned 0 in case of error, not -1 as specified by the Windows API book. Heaven knows what you are really supposed to do since returning 0, the documented value for success, actually terminates the application. So, I return 1 just like I used to, which has the merit of working.

Under WIN32, our WinMain() function in NETFONE.C is not informed if a copy of Speak Freely is already executing. In order to implement the trick of double clicking an .SFX file to open a connection in an existing copy of Speak Freely, I had to add a FindWindow() call which searches for a preexisting window with our class name to detect if Speak Freely is already running. Because socket ports and audio hardware are non-shared resources, it is not possible to run more than one copy of Speak Freely at a time.

Connections established on the command line with non-standard ports were not working because the auxiliary socket must be created after the hwndMDIFrame window creation is complete. I moved the command line processing from the main window onCreate() in FRAME.C back to INIT.C so it's done after the frame window is created and its handle therefore available.

Added an indication to the title in the About dialogue as to whether this is a 16 or 32 bit version.

If something screwed up in the process of creating a connection which displayed an alert, it was possible for the not-yet-initialised connection to receive a timer message which caused it to try to send a heartbeat with the protocol not yet specified, which referenced a NULL pointer. Fixed.

16 March 1996

Transmission of face images to remote hosts was broken in 32 bit because I accidentally used _lseek instead of _llseek in FACE.C to position the read pointer in the face file. You can away with this on Windows 3.1, but not in 32 bit. Fixed.

Modified the code that paints connection windows in CONNECT.C and the MDI child window class definition to adopt the "new look" of Windows 95 dialogue boxes. The old connection window looked too garish alongside "grey flannel" Windows 95. This only happens for 32 bit builds.

Turned off some no-longer-necessary debug mode code from SPEAKER.C which could conflict with the display of faces and participants in a multi-party conference.

No call to desdone() was made at application termination time, which orphaned several buffers allocated by desinit(). Since these were local storage allocated by malloc(), no harm was done, but I added a call to desdone() to get rid of the natters from BoundsChecker.

Discovered that several of the libraries were being explicitly loaded from their home subdirectories rather than being dynamically selected based on the configuration. This explained why DES encryption wasn't working for Speak Freely protocol--the current library was never loaded! I fixed the search paths so that the current configuration libraries will always be used.

Deleted the "Bounds Checker" configurations which led to gnarly ~n ZIP names. If I decide to try Bounds Checker compile-time instrumentation again (like, when it really works), I'll use a portable directory name like BOUNDER.

The code which protects against OutputDebugString()s left in release version ran afoul of WIN32's defining this as a macro for its own evil purposes. I added a #undef to get rid of of any preexisting macro so our version will prevail without warning messages.

17 March 1996

Can you believe it? If you make an association between a file type and your application and double click on a long file name like "D:\Connections\Jean-Pascal Bauer.sfx" your application gets launched with the file name on the command line not quoted! Blown away is twenty years of Unix and MS-DOS convention that command line arguments are separated by spaces. Thanks, Billy boy. Okay, we have to handle this somehow, so I changed the command line parser in INIT.C and FRAME.C to, on 32 bit builds only, use a comma as the delimiter between multiple connection files on the command line. I am quite certain that Murkysoft will add quotes around long file names in a future "upgrade", at which time every application which programmed around the current idiocy will stop working. I suppose one could anticipate this dagger from the future and parse quoted file names in the present, but I am not going to stoop to speculative defensive programming.

I discovered the reason we were getting (harmless) "Can't open include file messages" when attempting to re-scan dependencies was the zany way Build/Settings/Resources parses the list of include directories. I twiddled the list delimiters until it generated a correct command line to eliminate the natter.

All the help buttons in the dialogues were broken due to the negative ID_HELP (used to avoid having a different help ID for every dialogue, thank you very much resource compiler) being widened to an int without sign extension in the WM_COMMAND handler. I added a cast to (short) to restore proper sign extension in both 16 and 32 bit builds.

Made a new SF32.HPJ file in the help directory that allows recompilation with the Visual C 4.0 HCW help compiler.

Remade the help file screen shot bitmaps for Windows 95 appearance. These are kept in a new HELP\BMP32 directory which is searched first by SF32.HPJ.

18 March 1996

Added a new Help/Performance Benchmark dialogue which displays performance of the various compression and encryption modes as a percentage of the real-time sample rate. The goal is to help people decide which modes their computer can handle and to evaluate different optimisations, compiler code generation options, etc.

Integrated the NSA LPC-10 codec into a new LPC-10 subdirectory and added code to SPEAKER.C to support decompressing packets received in LPC-10. Transmission isn't implemented yet.

19 March 1996

Two references to multicastJoin in CONNECT.C weren't disabled when IP_MAX_MEMBERSHIPS isn't defined, indicating a brain-dead Winsock that doesn't support multicast.

LPC-10 compression on output is now integrated into CONNECT.C.

Added LPC-10 compression to the performance benchmark.

21 March 1996

Integrated a fix from the Unix version for bad decryption when both a key file and another encryption mode that requires padding to an 8 byte frame. Since the padding has been done before the key file encryption is performed, key file decryption in SPEAKER.C needs to pad to guarantee the entire frame for subsequent decryption is properly decoded.

Integrated the new (slightly) improved Simple compression module, RATE.C, from the Unix version.

25 March 1996

Hitting Esc or clicking the "X" button didn't close the Performance Benchmark dialogue because there wasn't an IDCANCEL case in the dialogue procedure. Fixed.

Added a Help button to the Benchmark dialogue with appropriate link to the topic in the help file.

6.1-Alpha 1 prerelease.

3 April 1996

The new simple compression code (RATE.C) broke the answering machine's replay of simple compressed packets. For various historical reasons, all poor, simple compression was handled differently than all other types in SPEAKER.C: simple compressed packets were not expanded in place as for GSM, LPC, etc., but left compressed when written to the answering machine and decompressed again when played. This was fine prior to RATE.C, since no state information was needed to perform the decompression, but with the new algorithm the connection data structure must be available to decompress and it is not, of course, around when one plays back a message from the answering machine. I modified the simple decompression code in SPEAKER.C to work like all other forms of compression and expand the sound buffer in place, then removed the hack from ANSWER.C which allowed the fComp2X bit to pass through to the answering machine file in answerSave(). (Reported by Marc de Groot).

Deleted some long-obsolete debugging code in SPEAKER.C, already turned off with #ifdef OBSOLETE.

A race condition existed in ANSWER.C which could lead to a General Protection Fault from an access through a null pointer if the answering machine was either popped up or closed while audio was being received from the network. There was a possibility that answerSave() would decide the answering machine dialogue was up because hDlgAnswer was non-NULL, then try to add the new message to msgTable before the dialogue procedure got a chance to allocate the table. I added a test in answerSave() to only consider the answering machine dialogue up after it has finished its WM_INITDIALOG processing and the message table is allocated. (Reported by Marc de Groot.)

Sending with PGP encryption failed when creating the session key temporary file due to an incompatible change in the GetTempFileName() function in Win32. Now you have to call GetTempPath() to find out where to stick the temp file. The obvious upward-compatible way to handle the 16 bit Windows API call clearly eluded the "Monkey-C, Monkey-doo" back at the slug ranch. (Reported by John Deters).

The same temporary directory diddling had to be done in pgpSetSessionKey() in FRAME.C handle the New Official Temporary Directory Nomenclature Mechanism.

4 April 1996

Okay, I get it now...the reason the MS-DOS window hangs with the annoying alert box is that PGP was being run directly rather then through SFPGP.PIF, which is marked "Close on exit". The PIF was not being used, in turn, thanks to the fact that under Visual C 4, the program you're working on is executed from the WinDebug or WinRel (or whatever) directory, not the project directory where SFPGP.PIF lives. This shouldn't be a problem in the production version since the .EXE and .PIF will be in the same archive. To facilitate testing of the development version, I copied SFPGP.PIF into both debug and release directories. Automatic PGP encryption and decryption of session keys now seems to work OK.

After a day chasing the mysterious floating point error on a 486DX-33 when running the LPC decompression phase of the benchmark (reported by Bill Oatman), and correlating what was seen in Speak Freely with the sporadic reports of floating point errors in Home Planet which appear on 486DX machines as soon as a user installs Windows 95 (even if Home Planet has been running perfectly on the same machine for years under Windows 3.x), I am now persuaded there is something wrong in the management of the coprocessor/FPU control word related to Windows 95, which perhaps manifests itself only on slower (and maybe therefore earlier step revision) 486DX machines. Please allow me to explain.... According to Mushysoft's documentation for all C libraries dating back to Visual C 1, when a program receives control, all floating point exceptions are masked; in other words the FPU generates a NaN, Infinity, etc. instead of causing an interrupt. Here's the statement from the documentation for the _control87() function in the Visual C 4 online help:

    	Note The run-time libraries mask all floating-point
    	         exceptions by default.

Pretty unambiguous, huh? But if you believe that, then there is no way you can get a floating point exception interrupt at all, unless you use _control87() to deliberately unmask one or more exceptions which, I can assure you, I'm not doing. I did build a version of Speak Freely which unmasked exceptions and discovered that the exception which popped out of LPC.C's uncompressing phase was a completely harmless denormalised and/or underflow where the default (exception masked) behaviour of the FPU is precisely what is intended.

But this particular folder of the Hex Files now contains a number of unambiguous reports from a variety of people, affecting two different applications, which contradict the expectation of no interrupts. Naturally, this problem does not manifest itself on any machine I have tested on, including a 486DX. A search of Mickeysoft's product support database for all the obvious keywords, examination of the known bug list for Visual C++ 4.0, and errata for the 486 on Intel's Web site came up blank on anything seemingly related to a problem of this nature.

What to do? Well, like every other time something completely impossible happens on a Mangysoft platform, there's little one can do other than pile on defensive workarounds and add instrumentation to pin down the bug. In this case, I added a call on _control87() in the application initialisation code in WinMain() (NETFONE.C) which explicitly masks all floating point exceptions, even though they're supposed to already be masked when we receive control. In addition, I added temporary code, executed when you start the performance benchmark, which verifies that the FPU is in the correct modes according to the value returned by _control87(0, 0). If it isn't, a warning is issued and an attempt made to reset the modes to all masked. If this fails to do so, a second warning is issued. The output from these debug message boxes should, on a machine which exhibits the failure, point much more directly at the source of the problem.

August 10, 1997 (Brian Wiles)

Brian C. Wiles took existing Speak Freely source code, and began his modifications.

Added outgoing message to answering machine, a feature that should have been there a long time ago when the answering machine was added.

Eventually, if I can't find source code out there for a 32-bit video phone, I will be adding that to Speak Freely. Maybe we should call it "See Clearly". I'd love that! Of course, it's probably already taken.

November 30, 1997 (Brian Wiles)

I added a conference feature which was sorely lacking in John Walker's version of Speak Freely. It allows you to talk to multiple people at the same time without having to use multicasting.

I took the broadcast idea, and added another mode similar to it. The main difference is that it does not close a connection or open a new one when someone sends it a sound packet like it does for broadcast mode. This allows people to talk to you while you're sending data out to others at the same time.

Here are the differences between broadcast and normal mode as compared to the new conference mode, this giving a clearer picture of the shortcomings of each mode and what conference mode offers:

Differences from normal mode
  • Sends to multiple clients at the same time
Differences from Broadcast mode
  • Plays incoming sound
  • Won't disconnect clients or subscribe new clients

I still have to test this with 2 other people, but it works well with 2 echo servers. In my test conferencing rpcp.mit.edu and echo.fourmilab.ch, I found GSM compression will work for about 10 seconds on a 28.8 modem until it overflows the modem, causing a long silence. LPC compression works good, except when 2 people are talking at the same time, it has a lot of pops in the sound playback. LPC 10 compression works the best, but has lower quality than GSM compression.

Next, I want to adapt Speak Freely to blend incoming sounds together, much like combining wave files, so that the sound plays back like a real conference call.

April 25, 1998 (Brian Wiles)

Contacted John Walker, and asked for OK to take over Speak Freely project since it had been 2 years since he last updated it. He agreed.

April 26, 1998 (Brian Wiles)

Changed About box to Release 7.0 beta 1.

October 3, 1998 (Brian Wiles)

Added Echo Mode to act like echo server. Also added defines for toggling certain features such as broadcasting and the new (barely tested) conference and echo modes.

October 23, 1998 (Brian Wiles)

Release 7.0 beta 1.

Cleaned up source for beta distribution on web site. I still need to add file browse dialog for answering machine outgoing message.

24 October 1998 (John Walker)

In order to reduce confusion among the ICQ crowd, I added the ability to specify one or more IP addresses and optional ports on the SPEAKFRE command line. You can mix connection files and IP addresses, for example:

    speakfre echo.sfx,192.168.111.14,192.168.201.133/3074

Connections specified as IP addresses will use the last-saved connection parameters from the INI file. In the process of adding this gimmick, I discovered that I'd never gotten around to replacing the in-line command line processing in INIT.C with a call to the more general ParseCommandLine in FRAME.C (it never made any difference until the IP number code was added). I disabled the obsolete code in INIT.C and replaced it with a call on ParseCommandLine.

This was the first build with Monkey C 5.0, which naturally generated wrong code for the LPC10 compression code, with the symptom that it transmitted OK but received only silence. I changed the optimisation options for release builds to "Default" from "Maximise Speed" (well, it doesn't say "Maximise Speed while Working Properly", does it?) and that seems to have worked around this Microsoft compiler bug.

Then it was time to update the log file, whereupon we discover that the version of Weird for Windows included with Microsoft Orifice 97 generates RTF which the Help Compiler (HCW) shipped with Monkey C 5.0 cannot read and deems "corrupt". Well, I'll agree with that as a general assessment of Microsoft's commitment to compatibility. So, the log file had to be updated using Word 95 before the help compiler would digest it.

December 4, 1998 (Brian Wiles)

Fixed a bug where if the answering machine's outgoing message couldn't be opened, multiple errors would pop up at the rate of once per second without stopping.

Added a check to make sure the outgoing message was only sent once per connection. This also had the added effect of fixing the above bug.

Added a browse button and better descriptions for the answering machine outgoing message. There is no need to have an outgoing message, but it will be sent if it exists.

December 5, 1998 (Brian Wiles)

Fixed bug in GIF reading code in DoExtension() that would read forever instead of properly stopping at the end of a GIF89a extension block, causing an illegal operation. This would happen for any GIF image that contained things like transparancey information or comments. Thus, this problem would occur on a lot (if not most) GIF images used as face files. I'm guessing that this will fix a big chunk of the crashes that SF users have reported.

Added better implementation of the SpeakICQ patch for ICQ to use Speak Freely. The new function also checks the current path of the Speak Freely executable and prompts the user if they want to update it. As long as Mirabilis doesn't change the format they store the registry keys in, this should eliminate the need for any separate patch or any other user intervention.

December 6, 1998 (Brian Wiles)

Release 7.0 beta 2

Updated the help file (with Notepad so it wouldn't "corrupt" it) to change the web site address and fix a couple missing words in the introduction section.

January 25, 1999 (Brian Wiles)

Release 7.0 beta 3

Added user's full name to connection profile in case nickname is blank from ICQ. This was suggested by Birger Fricke to fix the "connection profile is invalid" error some ICQ users were experiencing.

2 March 1999 (John Walker)

Based on sporadic reports that Windows 9x refused to load programs flagged with a native language other than that of the user's own locale, I modified all the resources to be flagged "Neutral" so they should work on any locale whatsoever. Previously, they were flagged as "French (Switzerland)", not because I requested such a constraint, but because always-helpful Monkey C took it upon itself to declare that anything produced in that locale must be restricted to use therein. Now if Microsoft products only worked within a 100 km radius of Redmond...but I jest...they don't even work acceptably within their own corporate campus.

Added a new Blowfish subdirectory containing the eponymous encryption toolkit, based on the SSLeay package. Blowfish is now a sub-project of the Speakfre workspace, and is included in the C and Resource include paths and the link library search paths.

Implemented Blowfish encryption mode. A 16 byte key is generated from the given key string as for IDEA, and the separate Blowfish key can be saved in the connection file. As before, Blowfish can be used in conjunction with any other set of encryption modes (but not with VAT or RTP protocols, as they do not presently support that form of encryption). Due to horrid overlaps in the Options/Connection dialogue, I'll need to hand-edit the .rc file to restore a rational tab order to the items in it--you can always tell a GUI: it's sticky when you touch it, and it makes you regret ever going near that tar-baby.

6 March 1999 (John Walker)

Added Blowfish to the benchmark dialogue in bench.c. In the process I discovered that the encryption benchmarks were inconsistent in how they accounted for key generation time--some set the key in the inner loop while other set it only once per run. This can make a significant difference for an algorithm like Blowfish where key generation is deliberately slow and complicated in order to make actual encryption or decryption faster. Given that in normal operation Speak Freely only generates a key for a connection one time, when the key is specified by the Options/Connection dialogue, I decided to standardise the encryption benchmarks to only generate one key per run.

Cleaned up some gnarly Win16 _fmemset, etc. stuff in bench.c.

Changed some unnecessarily named items in the benchmark dialogue definition in the resource file to IDC_STATIC.

Deleted unused symbols in resource includes.

Deleted unused handlers for two obsolete menu items in Frame.c.

7 March 1999 (John Walker)

Finished adding text chat support. Here's how it works. Text chat occurs in a new modeless dialogue whose parent is the main MDI frame window, hwndMDIFrame. The dialogue procedure and support code is in the new file Chat.c. The dialogue contains a multiline read-only text edit box in which the conversation occurs and a single-line input box where the user can compose lines to be sent. When the user presses Return in this input line or presses the Send button to its right, a new WM_CHAT_TEXT_SEND (in the WM_USER range) is sent to the hwndMDIFrame window (Frame.c), which then iterates over all its child connection windows and forwards the WM_CHAT_TEXT_SEND message to them. The connection windows (Connect.c), in turn, on receiving the WM_CHAT_TEXT_SEND message, assemble an RTCP APP message of type RTCP_APP_TEXT_CHAT containing the text and call sendSessionCtrl() to transmit it on the control port.

On the receiving end, controlInput() in Frame.c detects APP messages with a content name of RTCP_APP_TEXT_CHAT, copies the payload to a newly allocated string buffer, and chatLog() in Chat.c to display it in the scrolling area. chatLog() is passed the best available user identity for the connection from which the chat text arrived, in descending order: user name, E-mail address, host name, or IP address. If the text chat dialogue isn't already displayed, chatLog() launches it, adds the received line to the scrolling area, and releases the string buffer.

Text chat is supported only when sending in Speak Freely protocol; a warning is displayed if you try to send chat text when the protocol is set to RTP or VAT.

Moved strings in the ICQ setup in Init.c which may need to be localised to the string table in the resource file. Strings which do not require localisation (registry keys, etc.) remain in the source file.

8 March 1999 (John Walker)

The code in Chat.c which copied text chat received from another site into the transcript area wasn't properly obtaining the length of the text already there, which could result in text appearing at other than the end of the transcript. Fixed.

Commented out the definition of CRYPTO in Netfone.h and added it to the project-specified includes for Debug and Release builds. Created a new "No Crypto Release" configuration which is identical to Release except it does not define Crypto. The permits maintaining Spook Freely in its own build directory without the need to modify the header file when it needs to be updated. Naturally, this required an hour of manual labour creating new configurations for each of the libraries included in the non-crypto build and entering rational settings for each, since Monkey C can always be counted on to default any value to something idiotic. Intermediate and output files for non-crypto builds will be found in the "Nocrypto" directory of each source directory. Note that none of the subdirectories actually contains code which depends on the setting of CRYPTO, but I know of now way to re-use the libraries from a Release build without hammering in explicit path names.

When I tried a non-crypto build I discovered I'd been a bit too aggressive in getting rid of "unused" resource symbols--I deleted the tags used to disable the crypto-related title strings in the performance benchmark dialogue (Bench.c). I put the symbols back, and added Blowfish to the fields disabled for a non-crypto build.

Release 7.0 beta 4.

10 March 1999 (John Walker)

The code in Frame.c's MM_WIM_DATA handler which attempts to recover from overruns processing wave input data contained two subtle errors which could be encountered only when queueing failed and a buffer actually had to be discarded. The discarded buffer was not placed back onto the queue for wave input, and after being discarded the code failed to break from the processing sequence. If anybody actually encountered this error, they probably wouldn't notice because its symptom would have been identical to that of the situation it's intended to handle--break-ups in transmitted audio due to a CPU not fast enough for the selected compression and encryption modes. Fixed.

19 March 1999 (John Walker)

Modified Idea/Idea.c to define IDEA32 on WIN32 builds, which got rid of 12 (harmless) compiler warning messages. In theory this should speed up IDEA on 32 bit processors, but according to the benchmark it made little if any difference.

Added prototypes and casts to eliminate all the warning messages when building the Lpc10 directory. Almost all of the warnings were harmless implicit narrowing in assignments, all of which are now explicitly cast.

Cleaned up harmless compiler warnings in the LPC directory and removed some Win16 gnarl which can now be dispensed with.

Cleaned up harmless compiler warnings in the GSM directory. Speak Freely now builds with any warnings on Monkey C 5.0 with Warning Level 3. Most of the libraries build with no warnings at level 4, but since Microsoft's own Windows header files generate hundreds of warnings at this level, it is impractical to adopt that level for the project as a whole.

Modified InitApplication() in Init.c to use the gimmick whereby you can specify a background brush colour as a COLOR_xxx + 1 and have RegisterClass create the brush for you. This gets rid of a harmless (and incorrect, as a matter of fact) natter from Bounds Checker. Besides, it's simpler to do it this way.

Added code to the WM_DESTROY handler in Frame.c to release the LWL and RTP SDES packets as well as the VAT ID packet. This isn't really necessary (and wasn't even on Win16, since the packets in question were allocated with malloc(), which meant they were in local memory), but it gets rid of three more natters from Bounds Checker.

The WM_DESTROY handler in Frame.c wasn't performing a closesocket() on sCommand and sControl, the sockets it uses to listen for packets from the network on the data and control ports. This didn't do any harm, but it did create a Bounds Checker natter since the first socket() call allocates a small buffer which isn't released until every open socket has been closed (the so-called WASCleanup() can't be bothered to clean up this buffer, evidently). I added ResetSocket calls for these two sockets, guaranteeing that they're closed and the buffer released, solely to get rid of the squawk.

At this point, the only remaining Bounds Checker nit is a buffer allocated within the C library function tzset() which is never released by design. The only way to get rid of this is to replace all references to the <time.h> functions with their ugly Win32 equivalents. I'll pass.

When a local loopback connection was closed, loop_flush() in Loopback.c was called before the control port Bye message was sent, creating an orphaned buffer (which did no harm, but irritated Bounds Checker). I moved the loop_flush() in the WM_CLEAN_UP_YOUR_ACT message handler in Connect.c so that it has a chance to release the Bye message.

20 March 1999 (John Walker)

Added "Speak Freely: " prefix to the titles of all dialogue boxes in which it will fit.

Made the Modem Settings dialogue and the two "Modem Rant" dialogues conditional on "MODEM" being defined. This will keep them from appearing as resources in normal builds. I'll rip them out entirely when I get around to liquidating the ill-starred MODEM connection code.

The code which edited the length of the output queue in the Extended Status (Propeller-head) dialogue played a perfectly legal but slightly underhanded trick of swapping in a format with no edit phrase for the queue length when the queue was empty, still including the queue length argument in the wsprintf() call. Unfortunately, this apparent discrepancy was caught by Bounds Checker, with the unfortunate consequence that it popped up an error box every time the Extended Status dialogue was updated and the output queue was empty. All these interruptions got in the way of testing other aspects of the program under Bounds Checker, so I re-phrased the code in question to appease Bounds Checker. The same fix was also required in the MM_WOM_DONE message handler in Frame.c, which decrements the output queue length and updates the Extended Status dialogue if it is displayed, and also in Speaker.c which increments the queue length when adding a sound buffer to it.

21 March 1999 (John Walker)

Added Audio Monitor parameters to the speakfre.ini settings file so they're remembered from session to session.

Cleaned up some more Win16 legacy gnarl in Frame.c.

22 March 1999 (John Walker)

Ripped out obsolete Win16 MakeProcInstance from Bench.c, Chat.c, Dialogs.c, Face.c, Frame.c, and Lwl.c. Changes to references to MakeProcInstance objects required modifications to Answer.c and Spectral.c as well.

Phonebook/Search in Lwl.c wouldn't submit a blank query when the Return key was pressed with the query string in focus, but would if the Search button was pressed. One may now submit a blank query either way.

Ripped out VOX_GSM code in Dialogs.c. We're never going to use it, so why go on carrying it around?

29 March 1999 (John Walker)

Added backing bitmaps to the spectrum and energy envelope displays in Spectral.c. This cleans up the paint code and guarantees a fast, complete repaint if the window is exposed after having been obscured.

Deleted the now-obsolete NETFONE.DEF file.

Removed obsolete VOX_GSM code from Vox.c, Vox.h, Frame.c, and Speaker.c.

Ripped out some more Win16 gnarl in Ulaw.c and Ulaw.h.

Removed Win16 code from Netfone.c, Netfone.h, Speaker.c, Dialogs.c, Connect, and Init.c.

Deleted some disabled and long obsolete code in Lwl.c.

31 March 1999 (John Walker)

Made propeller-head average energy and power spectrum scale display in the Audio Monitor dialogue conditional on _DEBUG being defined.

Release 7.0 beta 5.

2 April 1999 (John Walker)

As reported by Brian C. Wiles, one of the string table messages for ICQ setup was truncated because it was longer (108 characters) than the buffer in rstring() in Utility.c to retrieve it. I increased the buffer in rstring() to 132 characters. Also, rstring() incorrectly tested the result from LoadString as negative when a string is not found (this should never happen, of course). I corrected it to use the proper status of zero for a missing string.

3 April 1999 (John Walker)

When displaying audio received from the network, playSound() in Speaker.c was passing the data to spectrumUpdate() at the most convenient point--when all decryption and decompression is done, at which time the sound buffer is known to contain 8 bit mu-law samples. Unfortunately, due to jitter compensation, delays in computing the FFT, etc. this can be some time before the sound buffer is actually played, creating a "time parallax" between the audio one hears and the spectrum and envelope displays. I modified playSound() to, when the audio monitor is open and audio from the network is to be displayed, create a copy of the original sound buffer with a WORD at the beginning giving its length, and hide a pointer to it at the end of the WAVEHDR for the audio output. When the buffer has completed playing, the MM_WOM_DONE message handler in Frame.c passes this buffer to spectrumUpdate() and then releases. This does use more memory, but eliminates the time parallax. Note that the auxiliary sample buffer is allocated only if actually needed; a NULL pointer indicates no auxiliary buffer is attached to the WAVEHDR.

The envelope display in Spectral.c didn't account for the one-pixel frame included in its client rectangle, resulting in the curious flash of the leftmost line in the spectrum as it was painted and then overwritten by the refresh of the frame. Fixed. The same problem was present in the spectrum display, with less obvious consequences; fixed there also.

I added computation of the maximum energy in a packet to energy() in Spectral.c, and modified the envelope display so draw a red flag at the edges of the envelope window for any packet containing a sample which clipped.

Implemented fixes to Lpc/Lpc.c forwarded by Enzo Michelangeli. These should eliminate distortion primarily due to overflows in computing parameters. LPC still doesn't sound great, but it shouldn't break up so badly. These fixes haven't yet been integrated in the Unix version, but interoperate with un-fixed LPC CODECs so there's no problem with Unix or earlier Windows versions.

Cleaned up some Win16 legacy _fmemxxx calls in Speaker.c, as well as some obsolete _huge and FAR declarations.

Sending .au format audio files did not update the audio monitor. This was due to the .au transmission code foolishly avoiding createSoundBuffer() in the interest of "efficiency" (as if anything that ends up calling the Windows API could be "efficient"). I ripped out the "roll-your-own sound buffer" code and added logic to createSoundBuffer() in Connect.c and spectrumUpdate() in Spectral.c to interpret an align argument of 0 to mean that the sound buffer already contains mu-law samples at a rate of 8000 per second.

Added radio buttons to the Audio Monitor dialogue to select whether the maximum energy per packet (handy for setting input gain to avoid clipping) or the RMS average is shown in the envelope panel. The clipping indicator is shown regardless of the setting of these buttons, whose setting is saved in the application .INI file.

4 April 1999 (John Walker)

Cleaned up some obsolete FAR declarations in Netfone.h, Utility.c, Dialogs.c, Loopback.c, Netfone.c, Rate.c, Xdsub.c. Netfone.h is now FAR-free.

Converted _fmemxxx calls in Loopback.c to memxxx.

Made declarations of CRC and modem open/close functions in Netfone.h conditional on MODEM, marking them more clearly for elimination in the future.

Fixed a couple of TRACE_FACE #ifdef/#endif pairs in Face.c which had accidentally been indented. This works OK in Monkey C but looks ugly.

Release 7.0 beta 6.

5 April 1999 (John Walker)

RZG reported that the "t" at the end of the "Input" legend in the Text Chat dialogue box was truncated. This doesn't happen on my machine, but it's due to Monkey C's moronic default of sizing static text boxes to the precise size of their content on the machine on which they are created. I expanded the boxes containing both the "Input" and "Transcript" legends to include adequate space for any reasonable font selection.

RZG also observed that since the common OPENFILE dialogue doesn't allow you to enter a blank file name, there was no way to disable an outgoing answering machine message other than editing the SPEAKFRE.INI file. I added "Clear" buttons for both the outgoing and incoming file names in the Answer.c dialogue handler.

The answering machine displayed only the last two digits of the year when showing the time and date of a message. I modified it to use ISO 8601 "yyyy-mm-dd" format. The format used is Y10K, etc. compatible.

The "destest" project, not a part of Speak Freely but used to certify the correct operation of the DES encryption used in Speak Freely (but not RTP and VAT) protocol generated a number of warning messages at compile time and failed to run because its test data file was not supplied on the command line. In addition, if you did manually run the program, it reported errors because PERMUTATION was not defined when compiling Des.c, which the test data require. All fixed. This has no impact on Speak Freely whatsoever; it's just sweeping out an odd closet.

Release 7.0 beta 7.

April 18, 1999 (Brian C. Wiles)

Added splash screen before main window is displayed. It contains a status line at the bottom in case the program hangs while starting.

April 29, 1999 (Brian C. Wiles)

Started adding toolbar to main window. The buttons only have text for right now until I have some decent pictures for them.

May 1, 1999 (Brian C. Wiles)

Changed toolbar to only be enabled by defining TOOLBAR. The reason for this is because Monkeysoft's operating system isn't properly passing mouse messages such as WM_MOUSEMOVE and WM_LBUTTONDOWN to the main MDI parent window. The messages are, in fact, posted to the MessageLoop() function, but disappear into Windows after that. The only thing I can figure out on that so far is that Windows doesn't, by default, pass mouse messages to an MDI frame. After all, why on Earth (or whatever planet the Win32 developers are from) would anyone ever want to do such a thing. They're thinking is, "If we don't use that functionality, no one else will either." This is what makes Windows programming so much fun!

May 2, 1999 (Brian C. Wiles)

Release 7.0 Beta 8.

I finished testing Beta 8 with John Walker to make sure it still works the way it should.

4 May 1999 (John Walker)

The "No Crypto" build failed because "comctl32.lib" was not included in the link, causing the InitCommonControls() call in Init.c, added to support the toolbar, to be undefined in the link. I added comctl32.lib to all configurations of the Speakfre project.

Since the toolbar is currently disabled, to avoid the risk somebody may have a DLL conflict with the common controls DLL, which is only used when TOOLBAR is defined, I disabled the call on InitCommonControls() in Init.c when TOOLBAR is not defined.

Fixed FILEVERSION and PRODUCTVERSION in the Version resource to be 7,0,8,0.

May 7, 1999 (Brian C. Wiles)

Designed a fancy logo for the Splash Screen with a telephone about to plug into the Earth. To do this, I gathered the following images:

I then used the GNU Image Manipulation Program (The GIMP) to piece together the images and create the glowing text. I don't think it's half bad for someone who's not a graphics artist.

May 8, 1999 (Brian C. Wiles)

Finished new logo and added it to the splash screen. The image is 600x400x256 resolution, designed to be large enough to see on high resolutions while still viewable on a 640x480 (yuck) desktop.

May 9, 1999 (Brian C. Wiles)

Release 7.0

Fixed ICQ detection bug where SetupICQ() would not update the settings if Speak Freely was set as any other type of ICQ plugin besides "File", such as "ClientServer" where the registry value would be "Client Path" instead of "Path". Thanks to "Hauke D." for exporting his ICQ registry settings for me.

10 May 1999 (John Walker)

Splash_DlgProc in Dialogs.c generated a Bounds Checker squawk when it tried to set the IDC_ABOUT_TITLE field in the splash dialogue box, which existed only in the about dialogue box. Since the information in the IDC_ABOUT_TITLE is relevant, I added that field to the splash dialogue box.

June 8, 1999 (Brian C. Wiles)

Release 7.1 beta 1

Added IP address display to about dialog. It will display "Unknown" if the user is not connected to the Internet at the time.

Fixed the splash dialog problems caused by the splash dialog having the DS_SYSMODAL attribute. This would hide initialization errors, thus appearing to lock up.

Added "Save Message" option to answering machine. Just hit the button after the desired message has been played, and choose a .WAV file to write to.

June 9, 1999 (John Walker)

The Sflogo.bmp bitmap supplied with the 7.1 release was in 24 bit per pixel mode which not only ballooned the size of the Speak Freely executable by more than a factor of two, it would also fail to display properly on machines with 256 colour displays. I converted it to a 256 colour bitmap like the one supplied with 7.0 which only inflates Speakfre.exe by a factor of 50%.

The Connection Options dialogue failed to mention Blowfish among the varieties of encryption available. Fixed.

June 11, 1999 (Brian C. Wiles)

Added "Save All" button to answering machine for saving the entire message file to one .WAV file.

June 14, 1999 (Brian C. Wiles)

Changed the behavior of answering machine to only play the current message when the Play button is pressed. This allows skipping to the desired message number without having to play the messages in between.

June 15, 1999 (Brian C. Wiles)

Changed IP address in About box to read-only edit field for copying and pasting into connection window or another program.

June 29, 1999 -- Brian C. Wiles

Added encryption mode to connection protocol line, and a software line to display the other party's software name.

Integrated Quick Start Guide to help file for first time users.

January 27, 2003 -- John Walker

Well, that's over. Back to work. All subsequent log entries unless otherwise noted are by John Walker.

Installed the CELP codec as a new dependent project in the Celp directory. Fixed the 336 warning messages Monkey C generated because it didn't like me "truncating" a double result when storing into a float in a sequence of code like:

	float zot = 1.0;
	zot = zot * 4;
which will generate warnings on both lines: the first for "truncation" storing the double 1.0 (one must write "1.0f"), and the second due to the multiplication by 4 (in a more complex expression or one involving a double valued function, one must cast).

Integrated the new Rate.c from the 7.6 Unix version, modifying it to support multiple contexts as the previous Windows version did.

Added Speech.au to the directory for inclusion with the distribution. This is the speech test file currently distributed with the Unix version.

Added support for CELP compression and decompression in Frame.c and Connect.c. At the moment, we're still using the old style Robust code in conjunction with CELP and LPC10. The accelerator for CELP compression is Ctrl+4 (mnemonic, 4800 baud).

Ripped out the dumb "splash screen", which only delays the user's getting to work with the program.

January 28, 2003

Added CELP to the Bench.c dialogue. In the process, I noted that the Bench.c dialogue measured the performance of the old simple compression algorithm, not the Rate.c mechanism that's been used since 6.1. I replaced the obsolete code with rate_init() and rate_flow() calls representative of the current audio processing code.

Surprise. surprise...the CELP code which worked so nicely the first time in a debug build generates radical electric guitar effects on compression when compiled with full optimisation for the release build. Well, out comes the bucket of #pragma optimize() statements again. Now it's just a matter of finding out where to stick them in the celp/Celp.c.

Disabled the PROTECT code in celp/Celp.c. We never enable it, so there's no reason to carry it around with us.

Removed the never-implemented toolbar and all the litter associated with it.

For the nonce I just hammered in #pragma optimize("", on/off) directives around the include of all the files in celp/Celpfiles.h, which "fixes" CELP in an optimised release build. When I'm more awake I'll start tracking this down to the source(s) of the optimisation problem.

January 29, 2003

After the better part of a day psychoanalysing Monkey C and its Simian Optimiser, I figured out a way to thread the CELP code through the blind eye of the needle. This amounts to compiling the files Cgain.c, Pctolsp2.c, and Pgain.c, all in the celp directory, with "#define float double" within the function definitions. Apparently, whatever Monkey C is screwing up has something to do with float arithmetic which this works around. It is worth it--the patch optimised code runs almost three times faster for compression as the unoptimised code. This should be revisited if and when a better compiler comes down the pike. (Actually, in Pctolsp.c, rather than use the #define, I changed the local variables in question to doubles explicitly. This was required because otherwise casts of (float) needed to avoid warnings on stores into the result arrays wouldn't work.)

I've left individual #pragmas to turn optimisation off and back on around each of the files included in celp/Celpfiles.h so the next time we go to chase optimisation problems that particular task won't have to be re-done.

Added const attributes to static constant tables in the celp directory, and corresponding const attributes on functions to which they are passed. This not only protects the tables from fat-fingered modifications in the future, it makes clearer what belongs in the eventual CELP context structure, which will subsume all the existing compiled in static storage.

Replaced obsolete references to LPWAVEFORMATxx with LPWAVEFORMATEX and removed the work-around definition in Netfone.h.

Consumed by a clean-freak frenzy, I ripped through the source and extirpated all the obsolete FAR declarations left over from bad old 16-bit Windows days.

Aha! The reason we were getting those "celp.h" not found messages at link time was because I forgot to add "./celp" to the include search path for resource compilation. Not only do you have to add it to the C compiler include path, you also need to go to Project/Settings/ Resources/Additional Include Files and add it there as well. I'm sure I'll forget this the next time I add a directory.

Created a new "No Crypto" configuration of the Celp sub-project, placing its files in the "celp/Nocrypto" directory. I copied the settings from the Release build; as a CODEC, Celp doesn't vary in a no-crypto build. This fixed the undefined CELP symbols in a No Crypto build. Keep this in mind--when you add a new sub-project in its subdirectory, the Debug and Releases configurations are created automatically, but you have to manually create the "No Crypto" configuration or else that build will fail.

Cleaned up all the gnarly _fmemX() and _fstrX() references left over from the 16 bit epoch.

The build of the Destest project failed because I didn't install all the ODBC garbage which Monkey C includes by default in the link. I removed all of those DLLs, as well as OLE and other horrors not required by reasonable programs.

January 30, 2003

Improved keyboard navigation in the LWL query (Phonebook/Search) dialogue. Each field was set up with a keyboard shortcut, and the tab order was redefined to be more rational. When you enter, the focus is on the "User" (query) field. When you press Search or leave this field, the lookup is performed and, if one or more sites match, the results are placed in the results box. Otherwise, focus returns to the query field which is entirely selected, and a new "No match" message is placed in the results box. If the lookup of the LWL host fails, focus is set to the server name field which is all selected. This merits some more testing and feedback from users, but it's sure an improvement on what went before.

February 3, 2003

Implemented a new "Diagnostic Console" dialogue in Console.c. This dialogue is activated from the Help/Diagnostic Console menu and, once active, displays messages written to it with the:

    cPrintf(<format>, args...)
function, which has a varargs stackable variant of:
    vcPrintf(<format>, va_list arglist)
If the console isn't displayed, output generated by these calls are ignored. If formatting time is a consideration, diagnostic console output should be wrapped with:
    if (hDlgConsole != NULL) {
    }
Note that cPrintf formatting is done with sprintf, not wsprintf, so you can use floating point format phrases.

The diagnostic console includes an input line where commands can be typed. The console parses arguments much like a Unix shell into an args[] array of (char *) pointers. Arguments with embedded spaces may be quoted with double quotes ("), which may be forced with a backslash (/). At the moment, the only diagnostic console commands are:

    args    	-- Echo command arguments
    help    	-- Print trivial help message

As the first step toward implementing adaptive output rate control to avoid the "long delay" problem when receiving audio from sites sending in full-duplex mode or broadcasting which are generating samples more rapidly than the local audio device is playing them, I added a new bufferMicroseconds variable which keeps track of the number of microseconds of audio data in the output queue. This is incremented in Speaker.c when a buffer is queued for output and decremented in Frame.c when the buffer finishes playing. Since there is no direct correlation between the number of bytes in the audio buffer (which is all we know about it when it's done playing) and the nominal time represented by its contents, the code in Speaker.c stuffs the nominal play time in microseconds at the end of the buffer, after the audio data, and Frame.c uses that value to decrement the bufferMicroseconds remaining.

Added a display of the audio queue length in seconds, based on bufferMicroseconds, to the Extended Status display.

Memo to file: the rate conversion prior to audio output in Speaker.c when audio is constrained to 11025 samples per second uses gnarly sample replication rather than smooth Rate.c resampling. It should be rewritten to resample with Rate.c, then convert to the PCM output format of the hardware.

February 4, 2003

Installed the new Robust mode reception logic in Frame.c. In earlier versions, Robust mode was restricted to LPC10 compression and used a simple-minded equality test to determine packet validity. The new logic, imported from speaker.c in the Unix version, uses a distance test modulo 255, and supports Robust mode reception in all Speak Freely compression modes (but not RTP and VAT, as they do not define such a mode).

Integrated new-style Robust transmission mode in Connect.c and Frame.c; this is compatible with Robust mode as supported by the Unix 7.5 release, and is upward compatible with older versions sending LPC-10 in Robust mode. The new Robust mode can be used with any Speak Freely compression mode (but not RTP or VAT protocols), and may specify up to eight replications of each packet sent.

Changed working version in the About dialogue to 7.6-A1 for eventual Alpha release.

February 7, 2003

Now that we're compiling with Monkey C 7.0 (a.k.a. Visual Studio .NET), I disabled the optimisation work-arounds in the Celp directory with:

    #ifdef MSVC5OPT_BUG
The new compiler appears to generate correct code for Celp in Release mode without the work-arounds.

Code optimisation in Frame.c function controlInput has been disabled since 2 March 1996 due to a code generation problem parsing VAT IDLIST packets. This problem appears to have been fixed in Monkey C 7.0, so I commented out the #pragmas which disable optimisation with:

    #ifdef MSVC5OPT_BUG

Optimisation of the function vfeatures() in Lpc10/Voice_nn.c had been disabled due to code generation problems in Monkey C 5.0. I restored optimisation for 7.0 by disabling the optimize pragmas with:

    #ifdef MSVC5OPT_BUG

Audited all of the static storage in the Celp modules and added the "const" attribute to all constant data tables.

Implemented dynamic output rate resampling in Speaker.c. The default network exchange sample rate of 8000 per second is now defined as EXCHANGE_SAMPLE_RATE in Netfone.h (and hard coded references to 8000 should be changed as they are encountered). If the setting of currentOutputRate is not equal to this value, the audio will be resampled to the specified rate, speeding it up if less than EXCHANGE_SAMPLE_RATE, slowing it down otherwise.

Added a "rate" command to Console.c which, if invoked with no argument, prints the current output rate and, with an argument, sets it to the specified integer value.

February 8, 2003

Cleaned up the hideous inconsistent tabbing in Answer.c. Still to be tackled is the absurd duplication of code in the Save and Save All button handlers, and the ridiculous duplication of the decodeSound() function vs. the main-line packet processing code in Speaker.c.

Implemented "adaptive output rate adjustment" in Speaker.c and Frame.c to deal with the "long delay" problem when connected to a site which is transmitting continuously (either a broadcast or full-duplex transmission). This mode is enabled by default, but can be disabled by a new Options/Workarounds/Audio/Disable Adaptive Output Rate Adjustment menu item. When enabled, the adjustment is performed according to the settings of the following parameters which are defined in Netfone.h:

AORA_MINIMUM_DELAY 500
The minimum time in milliseconds, regardless of jitter delay, at which to begin evaluating output rate. If the jitter delay is greater than this value, the jitter delay is used.
AORA_ACTION_DELAY 500
Begin output rate adjustment when the length of the output queue exceeds this time, in milliseconds, plus the greater of the jitter delay and AORA_MINIMUM_DELAY. At this point, the output rate will begin to be increased for subsequently arriving packets.
AORA_MAXIMUM_RATE_PERCENT 10
Maximum increase of output rate acceptable, as a percentage of EXCHANGE_SAMPLE_RATE. The output rate will never be increased beyond this percentage, regardless of queue length. Setting this too high will result in a "chipmunk" effect if output arrives too quickly to flush the queue. ALVIN!!!! Don't talk so fast!
AORA_MAXIMUM_RATE_DELAY 3000
Length of output queue (milliseconds), in excess of the smaller of the jitter setting and AORA_MINIMUM_DELAY, at which the maximum rate increase of AORA_MAXIMUM_RATE_PERCENT is applied. This should be thought of as the longest tolerable delay, at which the speed-up of the maximum rate increase is preferable to further growth in the output queue length and perceived delay.

Code in Speaker.c and Frame.c uses these parameters to adjust currentOutputRate accordingly whenever a sound buffer is added or removed to the output queue, unless disabled by waAudioNoOutputRateAdjustment being set TRUE. Like other workarounds, this mode is saved in the profile. The setting of AORA_MAXIMUM_RATE_PERCENT to 10 is probably more aggressive than circumstances require; I'll leave it there pending further testing as I'd rather make sure we've pounded a stake through the heart of the "long delay" problem before fine tuning the maximum compensatory output rate adjustment.

February 9, 2003

Added logic to Console.c to automatically purge the first 200 characters from the console output edit control when it's within 200 characters of reaching the maximum of "32K" (Microsoft-style: 32,000 bytes, not (215-1) as you'd expect). This purge occurs on both output and in echoing of commands typed into the Command window. Naturally, this is a computational task of such staggering magnitude that on a mere 400 MHz Pentium II locks up the application sufficiently long to cause loss of incoming audio packets.

February 10, 2003

Implemented the first cut of CELP context support, as opposed to embedded static variables which don't work well with multiple connections. If you build the CELP library with CELP_USE_CONTEXT defined in celp/Celp.h, contexts are enabled. You must then supply a celp_context structure pointer, initialised by a call to celp_context_init(celp_context *) to celp_init(), celp_encode(), and celp_decode() as the first argument. Massive code changes were required in the celp directory to support this indirection through a context pointer, when the original code was translated from FORTRAN and a mix of function arguments, local variables, and common block (C static variable) references.

Fixed ill-defined conditionals on CELP_PROTECT which caused the CELP context to be smaller than the code which accessed it, stepping on the current user's LWL identity.

Cleaned up some more hideous tabbing associated with the broadcast, conference, and answering machine code in Frame.c.

Modified Bench.c to allocate its own CELP context and use it for the CELP part of the benchmark.

Added logic to Speaker.c to dynamically allocate a per-connection CELP decoding context the first time a CELP-compressed packet arrives for the connection. The context is left allocated until the connection is closed, whereupon it is released by the connection teardown code in Connect.c.

February 11, 2003

Installed Microsoft "HTML 'Help'" Workshop and converted the old-boss RTF help to new-boss HTML, generating a multitude of incomprehensible file names and links in the process. The resulting gibberish compiled the first time. Now it's just a simple matter of integrating the thousands of single-neuron-clapping "help" items to figure out how to link the resulting Speakfre.CHM file to the application's help calls.

February 12, 2003

After many experiments and the requisite search for "lore", I managed to get the first cut of HTML help working. The first thing I did was write a Perl program, htmlhelp\tools\fixjunk.pl (note that although this program is included in the Windows source distribution, it is intended to be run under Unix), which more of less undoes the damage wrought by the "conversion" from a WinHelp project. It renames all of the gibberish file names to the name of the topic they contain, then rips through the files and changes all the embedded links to the new file names.

After hours of wasted time, I figured out that when you specify the "topic" in the HH_DISPLAY_TOPIC command to HtmlHelp(), you don't specify it through the dwData argument, which is nicely documented as:

    Specifies NULL or a pointer to a topic within a compiled help file.
but rather as the name of the HTML file embedded in the .CHM compiled help file, after the weird delimiter "::/" following the CHM file name in the pszFile argument. Further, if your HTML topics are in a subdirectory of the Help build directory (the default for a converted project) and you haven't checked, "Don't include folders" under Project options/Files (you try to find it!), then you must specify the full path name. Hence your call ends up looking something like this:
    HtmlHelp(windowHandle, "myhelp.chm::/html/topic.htm", HH_DISPLAY_TOPIC, 0);
Oh yes, the recommendation to pass NULL is another lie--that gets you a warning with Monkey C. And don't forget to use HH_INITIALIZE before the first call on HtmlHelp and HH_UNINITIALIZE at the end of your program, if you've called HtmlHelp at all.

Added new displayHelpTopic() and displayHelpTopicString() functions in Utility.c which display help topics specified by string table resource IDs or C strings respectively. (The latter is required for the screwball call-back from open file dialogue boxes.) These invoke HtmlHelp and worry about initialising it the first time. Code in Frame.c uninitialises HTML help if it was ever used.

Swept through the entire program and replaced every WinHelp reference with an equivalent call on displayHelpTopic*. Then, of course, since the topic tags used to select the help page to display are based on the topic file name rather than keywords, I had to go through the string table and change all of the topic ID strings to correspond to the ones in the HTML help file.

Automatically generated a table of contents file using the gimmick which purports to do so in the HTML Help Workshop. Naturally, it was almost complete gibberish. After two hours of manual editing, during which process the Workshop crashed three times, I managed to produce a plausible first cut of a table of contents.

Enabled full text indexing in the help file compilation.

February 13, 2003

Added code in Frame.c to pop up the help window in search mode when the user selects the "Help/Search for Help on" menu item. I could find no documentation whatsoever as to how one should initialise the HH_FTS_QUERY structure required in this call, so I whipped one up with plausible contents which seems to work OK.

February 15, 2003

After about 15 hours of flat-out editing, the HTML Help text has been bashed into something almost resembling a first cut. The vast majority of corruption introduced in the process of translating the original WinHelp document to HTML has been cleaned up, the images which were lost in the process re-integrated from the HTML help text on the site, and all updates made to the site but not to the program help text have been applied. Documentation of CELP has been added, the IDEA patent information updated, and the description of Robust mode re-written to reflect its applicability to all compression modes and the ability to replicate packets up to eight times. The HTML document is nowhere near finished, but at least it's begun.

Changes to the text-based development log since its last update were integrated into the HTML version, and log descriptions of subsequently deprecated code were marked in grey type. (I haven't yet done an exhaustive review of this, and it may make sense to also colour tag the massive blocks of verbiage describing 16 bit travails and battles with eccentric third-party WINSOCKs, all of which are no longer relevant.) From now on, the master log is the one in htmlhelp\html\log.htm, and entries should no longer be made to Log.txt, which will soon be deleted.

Imported all the HTML help files into a new HTML Help folder within the Speakfre project.

February 22, 2003

Performed a complete end to end audit of transmission in all protocols, compression, and encryption modes between Speak Freely for Windows 7.6 and the released Speak Freely for Unix 7.6. All went well, although some buffer length crashes on the Unix side when changing protocols on the fly need to be investigated and fixed. The Windows version was robust against all perturbations.

Deleted the contents of the obsolete text development log in \log.txt and replaced it with a comment pointing to this file.

Converted answer.wav to .au format, halving its size. Replacing it with something less inane remains to be done.

February 23, 2003

Increased the maximum length of a resource string retrieved with rstring() in Utility.c to 256 bytes.

Removed ECHO_SUPPORT, BROADCAST_SUPPORT, and CONFERENCE_SUPPORT conditions. Like we're going to disable them....

Deleted obsolete and unreferenced string table item: IDS_T_RINGDING.

The code enabling conflicting echo, conference, and broadcast modes in Frame.c was a sloppy mess. First of all, warning messages were hard-coded English verbiage rather than resource strings in the localisable section. Second, the application name was hard-coded rather than a reference to the global pszAppName used for that purpose. Third, the parent of the message box was set inconsistently from message to message. Fourth, broadcast and conference mode failed to check for the conflict where echo is enabled, while echo mode did check for broadcast and conference. Fifth, the warning message issued when echo mode did detect a conflict mentioned conference mode but not broadcast as a source of the conflict. Sixth, the comments describing the conflict were incorrect. Seventh, the tabbing in this whole section was screwed up. Eighth, if we did manage to set conference or echo mode, the string which confirmed the mode in the application window title was a hard-coded English string rather than a localisable resource string and, when the mode was cleared, the title was set back to the internal application name rather than the localisable resource string IDS_T_APP_TITLE_NORM.

Evidently, the "Echo mode" kludged in on October 3, 1998 by whatizname had never been tested. It "worked" by using the global bEchoPackets mode flag to fall into existing the echo-back code which reflects packets bearing the fLoopBack flag. Unfortunately, that code used an XOR to clear the flag to avoid an infinite loopback situation. But of course the XOR set fLoopback in a regular packet reflected by echo mode, leading to an infinite loop where the machine running in echo mode and any victim who connected to it were subjected to something which sounded like an auditorium full of Max Headroom clones all ba-ba-babbling at once. I changed the code to clear the fLoopback flag with an AND to avoid the infinite loop.

The "Conference mode" hacked in on November 30, 1997 was a complete crock. It placed audio output in continuous transmission mode, guaranteeing overruns to all subscribers, and provided no mechanism for subscribers to hear one another. I ripped out the whole mess and replaced it with logic which, on the transmit side, works precisely like sending to individual connections (in half- or full-duplex mode, with or without voice activation), except that when conference mode is set, audio is automatically sent to all open connections regardless of the wantsInput flag. Incoming audio packets from any active connection are played, as usual, but also immediately reflected to all other active connections (but, obviously, not back to the source). Only audio packets (those for which isSoundPacket() is true) are reflected; face data, PGP keys, etc. are processed locally but never reflected. For some reason the old conference mode blocked opening of new connections and timing out of existing ones; now participants can join and leave the conference at will.

Set the mode in the htmlhelp\speakfre.hhp project file which causes the help keyword file (htmlhelp\speakfre.hhk) to be automatically re-generated from the keywords embedded in the individual HTML files.

Remade the htmlhelp\images\about.png image to reflect the current appearance of the "About" dialogue box.

Release 7.6-A1.

February 24, 2003

Deleted the obsolete HELP directory (for the old RTF/WinHelp-based help) and all its subdirectories.

Added new files to the CVS files list, removed the HELP directory and its contents, and committed the changes to the SourceForge speak-freely-w CVS repository with comment "7.6-A1". Checked out a fresh copy and verified that a complete re-build of all configurations of the program and the HTML help file worked.

This is a Fourmilab-specific comment about how the local source tree is being kept these days. The current development version, checked out from CVS, is kept in c:\projects\SpeakFreely\Current\speak_freely, the latter directory the top branch of the CVS directory. When a release is made, it is checked in with CVS and then the "Current" directory is renamed to its version number, for example "7.6-A1". A new Current directory is then made, and development tree freshly checked out from CVS for futher work. This guarantees that local development of each version starts from the same source others can check out from the CVS repository.

Fixed two operator precedence warnings in lpc10\decode.c. These were fixed in the push to eliminate compiler warnings but somehow didn't make it into the Windows source.

Remade the htmlhelp\images\bench.png screen shot to show the current version of the performance benchmark dialogue (including CELP), and fixed htmlhelp\html\benchmark.htm to reflect the size of the new image.

February 25, 2003

If the compression mode was set to CELP, the Extended Status dialogue box showed the compression mode as "None". I added code to propUpdateAudio() in Dialogs.c to handle the CELP case and the text for the mode displays to the IDS_COMPRESSION_TYPES list in the string table. I added a comment in Dialogs.c which explains that with the exception of Simple compression, the other modes are mutually exclusive and hence the mode table may be filled with pairs for each mode, not bit-coded as if they were independent. (Reported by Jordan Cooper.)

If compression mode was set to Simple+CELP, the raw sample size required to fill one transmission packet exceeded the setting of BUFL in Netfone.h. I increased BUFL to handle the worst case of Simple+CELP at 16 bits per sample and, in addition, added an Assert() to inputSampleCount() in Frame.c to verify that the calculated buffer size is less than BUFL. Note that BUFL is the size in bytes of the buffer in a sound packet, not the number of samples, and that it must account for the possibility that samples may be 16 bits, even though we never send or receive network audio in any format other than 8 bit mu-law. Sound buffers are used as a lingua franca within the program, and may, at times, contain samples in 16 bit PCM format as well as single byte per sample formats.

No, you're not dreaming...we now have an Assert() macro in Netfone.h, borrowed from Home Planet. As usual, it generates no code in a Release build where NDEBUG is defined.

February 26, 2003

Another, totally independent, buffer overflow occurred when Simple+CELP mode was selected, this one affecting the wave input buffers allocated by startWaveInput() in Frame.c. Because everything which affects audio buffer size with the exception of input sample rate can change after these buffers have been allocated, they must be sized for the "worst case"--maximum samples per input buffer at 16 bits per sample. The worst case was previously hard coded as 7200 bytes (for 8000 samples per second, correspondingly larger for 11050), which was the case for Simple+LPC10. This, of course, was insufficient for Simple+CELP, which requires 7680 bytes for 3840 16-bit samples. I removed the hard coded numbers and replaced them with computations based on BUFL, which must be set to the worst case for sound buffer processing elsewhere in the program (and, as noted above, is now checked for potential overflow in debug builds), and I added an explicit pad of the buffer size for the int placed at the end which stores the number of microseconds required to play the buffer at nominal output rate. (Before, we just relied on padding to avoid overflow--now the space is allocated explicitly.) Further, throughout Frame.c, I added assertions which verify that the wave input requested for the buffer never exceeds the space allocated for the worst case when the buffer was initially allocated. All of these checks will make it much easier to track down overflow problems if a new CODEC is integrated with a worst case buffer size greater than the existing ones.

As suggested by Mark Winthrop, I added a note in the Quick Start guide, Htmlhelp\Html\quickstart.htm, to indicate that Robust Transmission should be set to Normal as the default. While I was at it, I rewrote that rather rocky paragraph to flow more smoothly.

Added a "Quick Start Guide" item to the Help menu which displays the eponymous chapter of the help file. I suppose I could pop up a dialogue the first time a user runs Speak Freely after installation with a button to show the quick start guide and a "Don't show this again" check box, but that seems a tad too Microsoft for my taste.

February 27, 2003

The "Speak Freely:" prefix was missing from the Diagnostic Console dialogue box. I added it.

Setting the voice activation level by dragging the scroll bar "thumb" in the Voice Activation Monitor dialogue didn't work due to missing LOWORD and HIWORD extractions of wParam fields required for Win32 for the SB_THUMBPOSITION and SB_THUMBTRACK messages. The code was kind of half-converted to Win32, and worked for all other scroll bar operations, but ignored dragging the thumb. Fixed.

Added an image of the Voice Activation Monitor dialogue to the Htmlhelp\Html\vox.htm help text.

Re-made the help file image of the Diagnostic Console to reflect the addition of "Speak Freely" to the title bar.

March 1, 2003

Began the plumbing to implement AES (FIPS-197 Advanced Encryption Standard). I created an "Aes" subdirectory and imported the source code for the encryption package from the Unix version of Speak Freely. Then I created an "aes" project within the solution with the usual Release, Debug, and No Crypto Release configurations. I added aes to the dependencies of the Speakfre project.

Compilation of AES disintegrated into dust until I figured out that precompiled headers don't work well with the Elmanesque macros used to generate the AES tables. I turned off precompiled headers and everything compiled OK.

I integrated AES_cbc_encrypt() into Connect.c and AES_cbc_decrypt() into Speaker.c. Both of these are external so they can be called from the benchmark as well as used for connections.

Added the fEncAES protocol flag bit to Netfone.h, and new aesKeyString, aes_spec, and aeskey items to the connection client data structure.

Just to shake things down, I added AES to Bench.c, using the default 128 bit key for the benchmark. Results indicate it runs almost precisely the same speed as Blowfish.

Made the first cut at adding the fields required for AES to the Options/Connection dialogue. The No Crypto and non-Speak Freely protocol disables are there, but the rest still needs to be added.

Cleaned up file naming conventions and got rid of void header and resource folders for subprojects which don't have any of the corresponding files. Made sure each of the CODECs has a "No Crypto Release" target which places its files in a ".\Nocrypto" directory, and that none of the encryption modules builds in a "No Crypto" configuration. Some of this was done by editing the XML .vcproj and .sln files because life is too short to acquire the ephemeral lore on how to accomplish such simple tasks with the Visual Studio IDE.

Renamed all of the C files included in Celp/celp.c by celpfiles.h to have .h extensions, permitting them to be added to the Celp project in a new "Source Includes" folder. I'd rather name them something else, but if I call them .c, Visual Studio tries to compile them individually, leading to disaster, and if I call them something like .ch or .ci, the C syntax highlighting is disabled whilst editing them.

The code added on June 29, 1999 to display the encryption mode in the connection window was all messed up. First of all, it failed to force a redraw of the connection window when the user changed modes with the Options/Connection dialogue--the displayed mode would only change when some subsequent operation caused the window to be repainted. Second, the code was written as if encryption modes were mutually exclusive; if you had more than one mode set, the one you'd see was whichever happened to be last in the IF statement that tested the modes. Third, the code failed entirely to test for key file encryption. Fourth, the format used to tack the encryption mode onto the end of the protocol string was hard-coded in the program, not retrieved from the string table in the resource file. Fifth, the format used the sloppy trick of trailing spaces to erase a previous longer entry, but specified too few trailing spaces for the worst case, even before multiple modes were supported. I rewrote the whole ugly thing to build a string listing the active encryption modes, using a "+" between modes if more than one is active, add "Keyfile" for key file encryption, show the whole thing on a dedicated "Security:" line in the connection window, and handle the repaint properly with an invalidate of the connection window in Frame.c after return from the options dialogue. All strings are formats which come from the string table.

Finished the integration of AES encryption. AES now has its own key line in the Options/Connection dialogue, along with a check box which allows you to specify that the key is hexadecimal. Key syntax is identical to the Unix version, with an embedded "+" in an ASCII key acting as the separator between two 128 bit components of a 256 bit key. Length of a hexadecimal key is the smallest of 128, 192, or 256 which the given key will fit into. The AES key and the hexadecimal mode flag are saved in the connection file if "Save keys" is set.

Modified sessionKeyGenerate() in Dialogs.c to eliminate the use of idearand() in preparing binary session keys. The MD5-based algorithm already used for non-CRYPTO builds is perfectly adequate entropy-wise. Getting IDEA out of the loop permits the key generator to be included in non-CRYPTO builds and allows dispensing with the ugly patent disclaimer in the key generation dialogue box and the discussion in the help text.

March 2, 2003

Integrated the new logic from the Unix mike.c program which spreads the letters in the session key (which fundamentally have 4 bits of entropy each) over the alphabet rather than just using the first 16 letters. I widened the text field in which the generated key is displayed because with nonstandard font selections it might not fit in the "just big enough" field used before.

Added AES at all the appropriate places in the help text and remade the affected illustrations.

Ran a bidirectional test with all encryption modes simultaneously enabled against the Unix version--no problems.

As suggested by Dennis Antonisin, I added logic to Frame.c to save the position and display mode (normal, maximised, etc.) of the main window at time of exit in a new Geometry/WindowPlacement profile item, and restore the prior position and modes when the program is run the next time. Microsoft provide GetWindowPlacement() and SetWindowPlacement() API calls to make this "simple". What they don't tell you is that if you call the latter function anywhere other than in the handler for the WM_SHOWWINDOW message (and there once and only once, the very first time the main window is displayed), you'll be in a world of hurt. In particular, if you try to restore the window position at the most logical place, right before you call ShowWindow in your WM_CREATE handler and the window was maximised previously, you'll end up with a window which can't be returned to normal mode and can't be resized since its border is offscreen. I added a test which guarantees that the window will never be restored in minimised mode. Having the window pop up solely on the bozo bar may be what Microsoft had in mind, but I'm not going to inflict it on my users or myself.

Removed some ancient, commmented out code for twiddling floating point error interrupt modes in Netfone.c. The enabled code has been working fine for years, so we can dispense with the failed earlier attempts.

Cleaned up ICQ registry code in Init.c. The existing code had ugly tabs, inconsistent spacing, hard-coded buffer lengths (specified thrice, independently!) where _MAX_PATH was intended, and unnecessary goto statements.

March 6, 2003

Re-worked tab order and keyboard navigation in the Options/Connection dialogue to accommodate the addition of AES encryption. Also, keyboard shortcut "B" was used twice--once for Blowfish and once for the Browse button for Key file encryption. I changed the latter's shortcut to "r".

We used to use .PIF files to allow the user to override the options and default execution modes when launching PGP. These days, PIF files start the 16 bit subsystem under 32-bit Windows and lead to a crash if you try to run a Win32 application from them. I changed the references to .PIF files to .LNK files, permitting the user to specify a Win32 shortcut to launch the external program. Since shortcuts specify an absolute path, pre-built shortcuts are no longer included in the distribution. Be default, the public key program will be launched directly.

Added the ability to use GPG to decrypt session keys. The selection of PGP or GPG is made by a new Options/Public Key Package menu item, and is persistent, saved in the profile. When GPG is enabled, the "PGP user name(s):" item in the Options/Connection dialogue is re-titled "GPG user name(s):" and GPG is invoked to encrypt and decrypt the session keys. As noted above, we first try "sfgpg.lnk" in the current directory, then "gpg" on the PATH.

When public key session key exchange was configured for PGP, the first character of the list of user names was dropped when passing the user names to PGP on the command line. Fixed.

Enabled the horizontal scroll bar in the Diagnostic Console dialogue. Echoing of PGP/GPG command lines made the need for this obvious.

Integrated code from mike.c in the Unix version to expand a list of GPG recipients to one "-r" option per recipient as required by the GPG command line syntax. This code, in Dialogs.c, expands the text field into a dynamically allocated buffer guaranteed to be large enough to hold the expanded command line fragment.

March 7, 2003

The code added on June 29, 1999 to display encryption modes tested the wrong flag for outbound PGP encryption. I corrected the code in connect.c to test the proper opgpkey field and indicate whether the currently specified key is being decoded by PGP or GPG.

Removed the unreferenced field in the LPCLIENT_DATA structure which stored the Windows tick count when the connection began (kind of). This was used by the ridiculous "money saved feature".

Naturally, the code in frame.c which stored the remote user's client program name had weird tabs. Fixed.

When the code to save messages from the answering machine in .WAV files was added, no keyboard navigation shortcuts were provided for any of the new functions. I added them.

In addition, several sequences of operations in the Answer.c dialogue could leave you with the keyboard focus set to a button which was disabled. Windows, with its inimitable style, not only allows this situation to occur (any reasonable system would automatically move the focus to the next enabled control in the tab order), but it causes the disabled control to suck up and discard all keyboard input, even keyboard navigation shortcuts! So, if you end up with focus on a disabled control, subsequent keyboard navigation is completely broken. I already had a work-around for this in the code which re-enabled the navigation buttons after playing a message, but I added a general routine, answerResetFocus(), which is now called after any operation which could leave the current button disabled. It detects this case and sets focus to the first of the "Next", "Previous", "Play", or "Close" buttons which is enabled. As the latter is never disabled (a motion to adjourn is always in order!), this guarantees the keyboard won't go dead.

Added logic to Speaker.c to pass the user's E-mail address and user name, if specified in the SDES packet for the connection, to answerSave() in Answer.c so it can be saved in answering machine messages. For backward compatibility, the E-mail address and user name are appended to the host name, delimited by semicolons. Should an old version of Speak Freely encounter an answering machine file in this format, it will simply display the host name with the additional information appended on the same line; no harm done.

I then broke out the code which displays the information on the current message (which occurred in three places!) into a new answerUpdateMessageInfo() function. This function parses the fields from the host name item and displays them on separate lines, blanking the line if the item isn't specified. I also added code to this function which displays the user's IP address. A new answerClearMessageInfo() function clears out all of these fields when no message is displayed. Calls to it have replaced all the hard-coded clears of the message information which previously occurred.

March 8, 2003

Added the ability in Answer.c to return a call to either the IP address whence it originated or the host name (for users with dynamic IP addresses which are registered with a DNS lookup service). When Answer.c displays a message, it sets two new buttons with the IP address and host names. These buttons, when pushed (they have craftily-chosen prefixes which guarantee invariant keyboard shortcuts for them), call newConnection() in Frame.c with a knownHost argument specifying the IP address or host name. I added the ability in newConnection() to resolve a host name for the knownHost argument, which previously accepted only an IP address. Note that newConnection() already worries about issues such as a connection's already being opened, so there's no need for Answer to fret over such details.

Added a facility which permits the user to perform an LWL search of an E-mail address (or whatever CNAME identification the user supplied) saved with a message in the answering machine. This will enable users who find a message waiting on the answering machine to determine if the caller is currently on-line with their identity posted on the LWL server the user queries. This works by having Answer.c initialise a button to the caller's E-mail address (if specified--otherwise the button is disabled). When pushed, the button passes the E-mail address to the new lwl_ask_set_target() function in Lwl.c which, if the LWL lookup dialogue is not currently displayed, saves the target for which to search and posts a message to the main hwndMDIFrame window faking a menu selection of Phonebook/Search. When the LWL Ask dialogue is initialised, code in Lwl.c detects the pre-specified target, plugs it into the query box, and launches an immediate search. If the LWL lookup dialogue is already active, lwl_ask_set_target() plugs the target into the query box and posts a press of the Search button itself, re-using the already open LWL lookup dialogue.

If we weren't able to resolve the domain name of a connecting host, Answer.c failed to disable the "Call Host Name" button. Fixed.

Re-made image for the answering machine page in the help file and added documentation for the call-back buttons to the help text.

Ohmygod--lookitthat! When he hammered in the answering machine save audio file (June 8--11, 1999) gimmick, not only did he copy and paste more than 300 lines of code in Speaker.c, replicating the entire packet decryption and decompression logic (thus requiring any change thereto to be made in two separate places, in the grand tradition of Microsoft), he blindly copied code from playSound() into his new decodeSound() which:

  1. Tested the outputPending queue length, which has nothing whatsoever to do with the process of writing a .WAV file from the answering machine and, if it was too long, returned from decodeSound() leaving a bad pointer and buffer length for the subsequent code which attempts to write it to the save file.
  2. If the allocation of the WAVEHDR structure (which was ephemeral in this case, used only to permit blithering through the copied code without looking closely at it) failed, returned with a bad pointer and length as above.
  3. Passed a NULL pointer for the pClientData argument, in the hope of whistling past all the references to it in the decryption and decompression code (which, of course, are completely irrelevant since playSound() calls answerSave() only after all of these operations have been performed and sound buffer is in canonical form). He clearly didn't understand this, since the comments on the calls to decodeSound() in Answer.c indicated it was being called to decrypt and decompress the packet, both of which have already been done for any packet in the answering machine incoming message file.
  4. If the Audio Monitor (hDlgSpectral) was displayed, allocated a buffer used when playing sound to relay the canonical samples to the monitor dialogue which, when writing an answering machine message save file would be orphaned for every sound packet saved.

First of all, I factored out all of the code common to playSound() and decodeSound() which was the vast majority of playSound() and all of decodeSound() except for a few lines, and created a new decodeSoundPacket() to contain it. I modified playSound() and decodeSound() to call this function, with a new saveSamp argument which playSound() uses to request the saving of canonical samples for the Audio Monitor. decodeSound() has been replaced by a small stub routine called decodeAnswerPacket() which invokes decodeSoundPacket() with a WAVEHDR on the stack to convert the canonical sound buffer from the answering machine incoming message file into a PCM format compatible with the machine's audio hardware. (N.b. even this is probably a poor idea--it would arguably be better to choose a generic .WAV format which is a superset of the canonical format which every Windows machine can play, for example 11050 sample/sec 16 bit PCM.) The dynamically allocated PCM buffer is returned to the caller via a pointer argument, along with its length. The "client data" argument, which was always passed in as NULL, has been jettisoned. I added Assert()s throughout decodeSound() to catch any attempt to decrypt or decompress a sound packet when the client data pointer is NULL.

Tested answering machine recording and replaying messages in assorted encryption and compression modes to make sure I hadn't overlooked something in the above code restructuring. Everything seems to be OK.

Re-tested automatic output rate adjustment with an overdriven broadcast feed from the Unix version--OK.

Release 7.6-A2.

March 9, 2003

One of the most persistent sources of confusion for new users has been specifying an incoming message file for the answering machine. To attempt to scrape the top off this speed bump, I made several changes to the Open File dialogue activated by the Browse button in the Answer.c dialogue. There is an inherent source of confusion in that while most users will simply create an incoming message file and use it forever, it is nonetheless possible to keep several incoming message files, opening existing files either to play messages in them or select them for new messages to be appended. Consequently, the selection of an incoming message file is a dual purpose operation which can either create a new file or open an existing one. To reinforce this in the mind of the user, I changed the title of the open file dialogue popped up by the Browse button to "Open Existing or Create New Incoming Message File". If no incoming message file was defined (the initial default), the file name field, which used to be blank, now defaults to "Messages". If no extension is entered when creating a new file, ".sfm" is now tacked on automatically. If an incoming message file was previously specified, the open file dialogue will continue to display it as before. Finally, I changed the label in the filter item for "*.sfm" files to "Incoming message files" from the rather enigmatic "Messages" shown before.

March 15, 2003

Added support for a "Taskbar Notification Icon", which everybody calls a "system tray" icon. The bulk of the code is in the new file Tray.c, which contains code which creates an invisible window to receive messages from the icon, adds the icon to the taskbar, processes messages from it, and gets rid of it when the program exits. At the moment, left clicking the icon pops up the main window if it was minimised and makes it the active window (top of z-order and keyboard focus). Right clicking the icon pops up a menu which permits you to open the main window (same as left clicking), launch the answering machine (if it isn't already open), or quit the program. Doubtless we'll add more functionality as time goes on. Development of this "API Classic" system tray implementation was expedited enormously by the tray42 sample code developed by Michael Smith.

March 16, 2003

Replaced the "Look Who's Talking" item on the Options menu, which was entirely too cute and obscure, with a "Window Display Modes" item which pops out a submenu with items labeled "Open on New Connection" (which does the same thing as "Look Who's Talking" used to do), "Open on Answering Machine Message", which pops up the program if minimised when a new message arrives at the answering machine, "Use Notification ("System Tray") Icon", which activates the notification icon described below, "Hide when Minimized", which causes the taskbar item to disappear when the program is minimised (this option is only available if you've enabled the notification icon, since otherwise there wouldn't be any obvious way to reopen the main window), and "Start Minimized", which causes the program to start minimised, intended for folks who want to launch Speak Freely from the StartUp folder so it's always available to receive calls.

All of these modes are persistent, saved in the profile under a new section "DisplayModes". Note that the Look Who's Talking mode used to be saved under the "Preferences" section, and consequently we'll forget that mode, defaulting it to off, when reading a profile from an older version.

When the Look Who's Talking mechanism (now renamed "Open on New Connections") opened the window, it failed to call SetForegroundWindow() to pop the window to the top of the z-order and give it keyboard focus. Fixed.

In implementing the "Start Minimised" option, I ran into another of those infuriating "can't do that" problems which so abound in Windows. It's simple enough to start with your window minimised, of course, but what if the user has also enabled the system tray icon and requested we hide the taskbar button whilst minimised? No can do, not at least straightforwardly. If you attempt to SW_HIDE the window in either the WM_CREATE or WM_SHOWWINDOW message handlers, it's completely ignored if the window is minimised. So, in the WM_SHOWWINDOW handler we post a message which triggers the WM_SIZE handler which performs the hide when an already-opened window is minimised. This gets the job done, but causes the button for the application to blink onto the taskbar and then immediately disappear, in classic Microsoft "did I see that, or was I imagining something" fashion. I have found no way to prevent this, so for the nonce we'll have to put up with it.

March 17, 2003

More gimmicks in the answering machine. I added the ability in Tray.c to blink the tray icon with an alternative, controlled by blinkTrayIcon(). This is used to set the tray icon blinking whenever a new message is received by the answering machine. The blinking is reset when the last message in the answering machine has been shown (with "Next"--not necessarily played), or when incoming messages have been erased or the incoming message file cleared (disabling the answering machine). I added support for the "Open on Answering Machine Message" option; we de-minimise and activate the window, as required, when a new message arrives and pop up the Answer.c dialogue. For some God-forsaken reason, on Windows 2000, when you make the normal calls to activate a window and pop it up to the top of the z-order, if you have another window on top in which you've clicked, the window you're attempting to display just sits there on the bozo bar highlighted, but doesn't actually appear until you click on the bozo bar. Beats me--I've seen the same behaviour when Palm Desktop launches BackupBuddy, so I know it's not just me. Naturally an hour and a half's twiddling with various far-fetched API calls to resolve this resulted in absolutely no progress. So, the blinking icon and highlighted bozo bar item will have to suffice for now. Note that if the application was minimised previously, it pops up to the top just fine--the problem appears only when another application is on top in the window order.

Updated the working version in the About dialogue to 7.6-B1 and fixed an infelicitous alignment of text in the No Crypto build of that dialogue.

March 31, 2003

Rewrote the item in faq.htm titled "How can I make sure Speak Freely is always running on my machine?" to reflect present-day "Startup" folder operation and the options now available on the Options/Window Display Modes menu.

Clarified the FAQ item about operation behind a firewall to distinguish ports 2074-2075 on which we use UDP and port 2076, which is TCP but used only when contacting a LWL server.

Added documentation for "Disable Adaptive Output Rate Adjustment" to the help file in workarounds.htm.

In February of 1996, I added a workaround to allow the user to choose whether RTCP packets in RTP protocol when encryption is enabled are sent encrypted (the default) or in the clear (as RTP also permits). This option was supported in the program, saved in the profile, and duly documented in the help file, but no menu item to enable it was ever added! For seven years this lacuna has escaped the attention of all involved. I added an Options/Workarounds/Protocol/No Encryption of RTP Control Packets menu item as described for all these many years in the help file and verified that RTP works with and without it checked.

Remade the Extended Status dialogue screen shot for the help file and added a description of the audio output queue length in seconds now included in that item.

If both AES and key file encryption were selected and the compression mode was one which resulted in packets which were not a multiple of 16 bytes, the last AES frame would be corrupted because key file decryption only rounded the data length to a multiple of 8 bytes, not 16. I fixed the decodeSoundPacket() in speaker.c to round the packet size to a multiple of 16 bytes for key file decryption. Note that this causes no harm if the packet has, in fact, only been padded to a multiple of 8 bytes or not at all, as the buffer is guaranteed to be sufficiently long and the pad data is ignored by downstream processing.

Release 7.6.