An I/O controller for virtual pinball machines: accelerometer nudge sensing, analog plunger input, button input encoding, LedWiz compatible output controls, and more.

Dependencies:   mbed FastIO FastPWM USBDevice

Fork of Pinscape_Controller by Mike R

/media/uploads/mjr/pinscape_no_background_small_L7Miwr6.jpg

This is Version 2 of the Pinscape Controller, an I/O controller for virtual pinball machines. (You can find the old version 1 software here.) Pinscape is software for the KL25Z that turns the board into a full-featured I/O controller for virtual pinball, with support for accelerometer-based nudging, a mechanical plunger, button inputs, and feedback device control.

In case you haven't heard of the idea before, a "virtual pinball machine" is basically a video pinball simulator that's built into a real pinball machine body. A TV monitor goes in place of the pinball playfield, and a second TV goes in the backbox to show the backglass artwork. Some cabs also include a third monitor to simulate the DMD (Dot Matrix Display) used for scoring on 1990s machines, or even an original plasma DMD. A computer (usually a Windows PC) is hidden inside the cabinet, running pinball emulation software that displays a life-sized playfield on the main TV. The cabinet has all of the usual buttons, too, so it not only looks like the real thing, but plays like it too. That's a picture of my own machine to the right. On the outside, it's built exactly like a real arcade pinball machine, with the same overall dimensions and all of the standard pinball cabinet trim hardware.

It's possible to buy a pre-built virtual pinball machine, but it also makes a great DIY project. If you have some basic wood-working skills and know your way around PCs, you can build one from scratch. The computer part is just an ordinary Windows PC, and all of the pinball emulation can be built out of free, open-source software. In that spirit, the Pinscape Controller is an open-source software/hardware project that offers a no-compromises, all-in-one control center for all of the unique input/output needs of a virtual pinball cabinet. If you've been thinking about building one of these, but you're not sure how to connect a plunger, flipper buttons, lights, nudge sensor, and whatever else you can think of, this project might be just what you're looking for.

You can find much more information about DIY Pin Cab building in general in the Virtual Cabinet Forum on vpforums.org. Also visit my Pinscape Resources page for more about this project and other virtual pinball projects I'm working on.

Downloads

  • Pinscape Release Builds: This page has download links for all of the Pinscape software. To get started, install and run the Pinscape Config Tool on your Windows computer. It will lead you through the steps for installing the Pinscape firmware on the KL25Z.
  • Config Tool Source Code. The complete C# source code for the config tool. You don't need this to run the tool, but it's available if you want to customize anything or see how it works inside.

Documentation

The new Version 2 Build Guide is now complete! This new version aims to be a complete guide to building a virtual pinball machine, including not only the Pinscape elements but all of the basics, from sourcing parts to building all of the hardware.

You can also refer to the original Hardware Build Guide (PDF), but that's out of date now, since it refers to the old version 1 software, which was rather different (especially when it comes to configuration).

System Requirements

The new Config Tool requires a fairly up-to-date Microsoft .NET installation. If you use Windows Update to keep your system current, you should be fine. A modern version of Internet Explorer (IE) is required, even if you don't use it as your main browser, because the Config Tool uses some system components that Microsoft packages into the IE install set. I test with IE11, so that's known to work. IE8 doesn't work. IE9 and 10 are unknown at this point.

The Windows requirements are only for the config tool. The firmware doesn't care about anything on the Windows side, so if you can make do without the config tool, you can use almost any Windows setup.

Main Features

Plunger: The Pinscape Controller started out as a "mechanical plunger" controller: a device for attaching a real pinball plunger to the video game software so that you could launch the ball the natural way. This is still, of course, a central feature of the project. The software supports several types of sensors: a high-resolution optical sensor (which works by essentially taking pictures of the plunger as it moves); a slide potentiometer (which determines the position via the changing electrical resistance in the pot); a quadrature sensor (which counts bars printed on a special guide rail that it moves along); and an IR distance sensor (which determines the position by sending pulses of light at the plunger and measuring the round-trip travel time). The Build Guide explains how to set up each type of sensor.

Nudging: The KL25Z (the little microcontroller that the software runs on) has a built-in accelerometer. The Pinscape software uses it to sense when you nudge the cabinet, and feeds the acceleration data to the pinball software on the PC. This turns physical nudges into virtual English on the ball. The accelerometer is quite sensitive and accurate, so we can measure the difference between little bumps and hard shoves, and everything in between. The result is natural and immersive.

Buttons: You can wire real pinball buttons to the KL25Z, and the software will translate the buttons into PC input. You have the option to map each button to a keyboard key or joystick button. You can wire up your flipper buttons, Magna Save buttons, Start button, coin slots, operator buttons, and whatever else you need.

Feedback devices: You can also attach "feedback devices" to the KL25Z. Feedback devices are things that create tactile, sound, and lighting effects in sync with the game action. The most popular PC pinball emulators know how to address a wide variety of these devices, and know how to match them to on-screen action in each virtual table. You just need an I/O controller that translates commands from the PC into electrical signals that turn the devices on and off. The Pinscape Controller can do that for you.

Expansion Boards

There are two main ways to run the Pinscape Controller: standalone, or using the "expansion boards".

In the basic standalone setup, you just need the KL25Z, plus whatever buttons, sensors, and feedback devices you want to attach to it. This mode lets you take advantage of everything the software can do, but for some features, you'll have to build some ad hoc external circuitry to interface external devices with the KL25Z. The Build Guide has detailed plans for exactly what you need to build.

The other option is the Pinscape Expansion Boards. The expansion boards are a companion project, which is also totally free and open-source, that provides Printed Circuit Board (PCB) layouts that are designed specifically to work with the Pinscape software. The PCB designs are in the widely used EAGLE format, which many PCB manufacturers can turn directly into physical boards for you. The expansion boards organize all of the external connections more neatly than on the standalone KL25Z, and they add all of the interface circuitry needed for all of the advanced software functions. The big thing they bring to the table is lots of high-power outputs. The boards provide a modular system that lets you add boards to add more outputs. If you opt for the basic core setup, you'll have enough outputs for all of the toys in a really well-equipped cabinet. If your ambitions go beyond merely well-equipped and run to the ridiculously extravagant, just add an extra board or two. The modular design also means that you can add to the system over time.

Expansion Board project page

Update notes

If you have a Pinscape V1 setup already installed, you should be able to switch to the new version pretty seamlessly. There are just a couple of things to be aware of.

First, the "configuration" procedure is completely different in the new version. Way better and way easier, but it's not what you're used to from V1. In V1, you had to edit the project source code and compile your own custom version of the program. No more! With V2, you simply install the standard, pre-compiled .bin file, and select options using the Pinscape Config Tool on Windows.

Second, if you're using the TSL1410R optical sensor for your plunger, there's a chance you'll need to boost your light source's brightness a little bit. The "shutter speed" is faster in this version, which means that it doesn't spend as much time collecting light per frame as before. The software actually does "auto exposure" adaptation on every frame, so the increased shutter speed really shouldn't bother it, but it does require a certain minimum level of contrast, which requires a certain minimal level of lighting. Check the plunger viewer in the setup tool if you have any problems; if the image looks totally dark, try increasing the light level to see if that helps.

New Features

V2 has numerous new features. Here are some of the highlights...

Dynamic configuration: as explained above, configuration is now handled through the Config Tool on Windows. It's no longer necessary to edit the source code or compile your own modified binary.

Improved plunger sensing: the software now reads the TSL1410R optical sensor about 15x faster than it did before. This allows reading the sensor at full resolution (400dpi), about 400 times per second. The faster frame rate makes a big difference in how accurately we can read the plunger position during the fast motion of a release, which allows for more precise position sensing and faster response. The differences aren't dramatic, since the sensing was already pretty good even with the slower V1 scan rate, but you might notice a little better precision in tricky skill shots.

Keyboard keys: button inputs can now be mapped to keyboard keys. The joystick button option is still available as well, of course. Keyboard keys have the advantage of being closer to universal for PC pinball software: some pinball software can be set up to take joystick input, but nearly all PC pinball emulators can take keyboard input, and nearly all of them use the same key mappings.

Local shift button: one physical button can be designed as the local shift button. This works like a Shift button on a keyboard, but with cabinet buttons. It allows each physical button on the cabinet to have two PC keys assigned, one normal and one shifted. Hold down the local shift button, then press another key, and the other key's shifted key mapping is sent to the PC. The shift button can have a regular key mapping of its own as well, so it can do double duty. The shift feature lets you access more functions without cluttering your cabinet with extra buttons. It's especially nice for less frequently used functions like adjusting the volume or activating night mode.

Night mode: the output controller has a new "night mode" option, which lets you turn off all of your noisy devices with a single button, switch, or PC command. You can designate individual ports as noisy or not. Night mode only disables the noisemakers, so you still get the benefit of your flashers, button lights, and other quiet devices. This lets you play late into the night without disturbing your housemates or neighbors.

Gamma correction: you can designate individual output ports for gamma correction. This adjusts the intensity level of an output to make it match the way the human eye perceives brightness, so that fades and color mixes look more natural in lighting devices. You can apply this to individual ports, so that it only affects ports that actually have lights of some kind attached.

IR Remote Control: the controller software can transmit and/or receive IR remote control commands if you attach appropriate parts (an IR LED to send, an IR sensor chip to receive). This can be used to turn on your TV(s) when the system powers on, if they don't turn on automatically, and for any other functions you can think of requiring IR send/receive capabilities. You can assign IR commands to cabinet buttons, so that pressing a button on your cabinet sends a remote control command from the attached IR LED, and you can have the controller generate virtual key presses on your PC in response to received IR commands. If you have the IR sensor attached, the system can use it to learn commands from your existing remotes.

Yet more USB fixes: I've been gradually finding and fixing USB bugs in the mbed library for months now. This version has all of the fixes of the last couple of releases, of course, plus some new ones. It also has a new "last resort" feature, since there always seems to be "just one more" USB bug. The last resort is that you can tell the device to automatically reboot itself if it loses the USB connection and can't restore it within a given time limit.

More Downloads

  • Custom VP builds: I created modified versions of Visual Pinball 9.9 and Physmod5 that you might want to use in combination with this controller. The modified versions have special handling for plunger calibration specific to the Pinscape Controller, as well as some enhancements to the nudge physics. If you're not using the plunger, you might still want it for the nudge improvements. The modified version also works with any other input controller, so you can get the enhanced nudging effects even if you're using a different plunger/nudge kit. The big change in the modified versions is a "filter" for accelerometer input that's designed to make the response to cabinet nudges more realistic. It also makes the response more subdued than in the standard VP, so it's not to everyone's taste. The downloads include both the updated executables and the source code changes, in case you want to merge the changes into your own custom version(s).

    Note! These features are now standard in the official VP releases, so you don't need my custom builds if you're using 9.9.1 or later and/or VP 10. I don't think there's any reason to use my versions instead of the latest official ones, and in fact I'd encourage you to use the official releases since they're more up to date, but I'm leaving my builds available just in case. In the official versions, look for the checkbox "Enable Nudge Filter" in the Keys preferences dialog. My custom versions don't include that checkbox; they just enable the filter unconditionally.
  • Output circuit shopping list: This is a saved shopping cart at mouser.com with the parts needed to build one copy of the high-power output circuit for the LedWiz emulator feature, for use with the standalone KL25Z (that is, without the expansion boards). The quantities in the cart are for one output channel, so if you want N outputs, simply multiply the quantities by the N, with one exception: you only need one ULN2803 transistor array chip for each eight output circuits. If you're using the expansion boards, you won't need any of this, since the boards provide their own high-power outputs.
  • Cary Owens' optical sensor housing: A 3D-printable design for a housing/mounting bracket for the optical plunger sensor, designed by Cary Owens. This makes it easy to mount the sensor.
  • Lemming77's potentiometer mounting bracket and shooter rod connecter: Sketchup designs for 3D-printable parts for mounting a slide potentiometer as the plunger sensor. These were designed for a particular slide potentiometer that used to be available from an Aliexpress.com seller but is no longer listed. You can probably use this design as a starting point for other similar devices; just check the dimensions before committing the design to plastic.

Copyright and License

The Pinscape firmware is copyright 2014, 2021 by Michael J Roberts. It's released under an MIT open-source license. See License.

Warning to VirtuaPin Kit Owners

This software isn't designed as a replacement for the VirtuaPin plunger kit's firmware. If you bought the VirtuaPin kit, I recommend that you don't install this software. The KL25Z can only run one firmware program at a time, so if you install the Pinscape firmware on your KL25Z, it will replace and erase your existing VirtuaPin proprietary firmware. If you do this, the only way to restore your VirtuaPin firmware is to physically ship the KL25Z back to VirtuaPin and ask them to re-flash it. They don't allow you to do this at home, and they don't even allow you to back up your firmware, since they want to protect their proprietary software from copying. For all of these reasons, if you want to run the Pinscape software, I strongly recommend that you buy a "blank" retail KL25Z to use with Pinscape. They only cost about $15 and are available at several online retailers, including Amazon, Mouser, and eBay. The blank retail boards don't come with any proprietary firmware pre-installed, so installing Pinscape won't delete anything that you paid extra for.

With those warnings in mind, if you're absolutely sure that you don't mind permanently erasing your VirtuaPin firmware, it is at least possible to use Pinscape as a replacement for the VirtuaPin firmware. Pinscape uses the same button wiring conventions as the VirtuaPin setup, so you can keep your buttons (although you'll have to update the GPIO pin mappings in the Config Tool to match your physical wiring). As of the June, 2021 firmware, the Vishay VCNL4010 plunger sensor that comes with the VirtuaPin v3 plunger kit is supported, so you can also keep your plunger, if you have that chip. (You should check to be sure that's the sensor chip you have before committing to this route, if keeping the plunger sensor is important to you. The older VirtuaPin plunger kits came with different IR sensors that the Pinscape software doesn't handle.)

Revision:
77:0b96f6867312
Child:
97:fc7727303038
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/IRRemote/IRProtocols.h	Fri Mar 17 22:02:08 2017 +0000
@@ -0,0 +1,2981 @@
+// IR Remote Protocols
+//
+// This file implements a handlers for specific IR protocols.  A protocol
+// is the set of rules that a particular IR remote uses to convert binary
+// data to and from a series of timed infrared pulses.  A protocol generally
+// encompasses a bit-level encoding scheme (how each data bit is represented 
+// as one or more pulses of IR light) and a message or packet format (how a 
+// series of bits is assembled to form a higher level datatype, which in the 
+// case of an IR remote usually amounts to a representation of a key press 
+// on a remote control).
+//
+// Lots of companies make CE products with remotes, and many of them use
+// their own custom protocols.  There's some commonality; a few proprietary
+// protocols, such as those from NEC and Philips, are quasi industry
+// standards that multiple companies adopted, more or less intact.  On
+// the other hand, other companies not only have their own systems but
+// use multiple proprietary systems across different products.  So it'll
+// never be possible for us to recognize every remote code out there.
+// So we'll cover the widely used ones, and then add the rarer ones as
+// needed.
+//
+// For each protocol we recognize, we define a subclass of IRReceiver
+// that implements the code to encode and decode signals for the protocol.
+// To send a command, we call the sender function for the protocol we want
+// to use for the transmission.  When we receive a signal, we run it through
+// each protocol class's decode routine, to determine which protocol the
+// signal was encoded with and what the signal means.  The decoders can
+// usually tell if a signal uses their protocol, since there's enough
+// structure in most of the protocols that you can distinguish signals that
+// use the protocol from those that don't.  This allows the decoders to 
+// serve the dual purposes of decoding signals and also classifying them
+// by protocol.
+//
+// To add support for a new protocol, we (a) define a class for it here,
+// and (b) add an entry for the class to IRProtocolList.h.  The list entry
+// automatically adds the new protocol to the tables we use to look up the
+// desired protocol class when sending, and to check each supported protocol
+// when receiving.
+//
+// The protocol decoders operate in parallel: as a transmission is received,
+// we run the signal through all of the decoders at the same time.  This
+// allows each decoder to keep track of the incoming pulse stream and 
+// recognize messages that conform to its protocol.  The parallel operation
+// means that each protocol object needs its own complete, independent
+// receiver state.
+//
+// In contrast, there can only be one transmission in progress at a time,
+// since a transmission obviously requires exclusive access to the IR LED.
+// (You can't eve interleave two transmissions in theory, since the coding 
+// is all about pulse timing and order.)  That means that we *don't* need
+// separate independent transmitter state per object.  That lets us save
+// a little memory by using a single, shared transmitter state object,
+// managed by the global transmitter class and passed to the current
+// transmitter on each send.  The exception would be any state that has
+// to be maintained across, which would have to be tracked per protocol.
+// The only common example is "toggle bits".
+//
+
+#ifndef _IRPROTOCOLS_H_
+#define _IRPROTOCOLS_H_
+
+#include <ctype.h>
+#include "NewPwm.h"
+#include "IRRemote.h"
+#include "IRReceiver.h"
+#include "IRProtocolID.h"
+#include "IRCommand.h"
+
+using namespace IRRemote;
+
+struct DebugItem { char c; int t; DebugItem(char c, int t) : c(c),t(t) {} 
+    DebugItem(char c) : c(c), t(0) { } DebugItem() : c(0), t(0) { }
+};
+extern CircBuf<DebugItem,256> debug;
+
+
+// IR transmitter state object.
+//
+// We can only transmit one signal at a time, so we only need to keep
+// track of the state of one transmission at a time.  This lets us 
+// save a little memory by using a single combined state object that's
+// shared by all transmitters.  The transmitter currently operating 
+// uses the shared object for as long as the transmission takes.
+struct IRTXState
+{
+    // Time since the transmission started
+    Timer txTime;
+    
+    // The command code we're transmitting
+    uint64_t cmdCode;
+    
+    // Bit stream to transmit.  Many of the protocols use a universal
+    // command representation (in IRCommand.code) that rearranges the
+    // bits from the transmission order.  This is a scratch pad where the
+    // protocol handler can translate a command code back into transmission
+    // order once at the start of the transmission, then just shift bits out 
+    // of here to transmit them in sequence.
+    uint64_t bitstream;
+    
+    // The IR LED control pin
+    NewPwmOut *pin;
+    
+    // Protocol ID
+    uint8_t protocolId;
+    
+    // Transmission step.  The meaning is up to the individual protocols,
+    // but generally this is used to keep track of the current structural
+    // part of the pulse stream we're generating.  E.g., a protocol might
+    // use 0 for the header mark, 1 for the header space, etc.
+    uint8_t step;
+    
+    // number of bits to transmit
+    uint8_t nbits;
+    
+    // Current bit position within the data
+    uint8_t bit;
+    
+    // Substep within the current bit.  Many of the protocols represent each 
+    // bit as multiple IR symbols, such as mark+space.  This keeps track of 
+    // the step within the current bit.
+    uint8_t bitstep;
+    
+    // Repeat phase.  Some protocols have rules about minimum repeat counts,
+    // or use different coding for auto-repeats.  This lets the sender keep
+    // track of the current step.  For example, the Sony protocol requires
+    // each message to be sent a minimum of 3 times.  The NEC protocol uses
+    // a "ditto" code for each repeat of a code after the initial command.
+    // The OrtekMCE protocol requires a minimum of 2 sends per code, and has 
+    // a position counter within the code that indicates which copy we're on.
+    uint8_t rep;
+    
+    // Is the virtual button that initiated this transmission still pressed?  
+    // The global transmitter sets this before each call to txStep() to let
+    // the protocol know if it should auto-repeat at the end of the code.
+    uint8_t pressed : 1;
+
+    // Use "ditto" codes when sending repeats?  Some protocols use special
+    // coding for auto-repeats, so that receivers can tell whether a key
+    // is being held down or pressed repeatedly.  But in some cases, the
+    // same protocol may be used with dittos on some devices but without
+    // dittos on other devices.  It's therefore not always enough to know
+    // that the protocol supports dittos; we have to know separately whether
+    // the device we're sending to wants them.  This flag lets the caller
+    // tell us which format to use.  This is ignored if the protocol either
+    // never uses dittos or always does: in that case we'll do whatever the
+    // protocol specifies.  To implement a "learning remote", you should
+    // make sure that the user holds down each key long enough for several
+    // repeats when learning codes, so that the learning remote can determine
+    // when dittos are used by observing how the repeats are sent from the
+    // reference remote.  Then you can set this bit if you saw any ditto
+    // codes during training for a given key.
+    uint8_t dittos : 1;
+    
+    // TX toggle bit.  We provide this for protocols that need it.  Note
+    // that this is a global toggle bit, so if we switch from transmitting
+    // one protocol to another and then return to the first, we'll lose 
+    // continuity with the toggle sequence in the original protocol.  But
+    // that shouldn't be a problem: the protocols with toggles only use it
+    // to distinguish two rapid presses of the same key in succession from 
+    // auto-repeat while the key is held down.  If we had an intervening
+    // transmission to another protocol, the original receiver will see a
+    // long gap between the earlier and later messages; the toggle bit isn't
+    // necessary in this case to tell that these were two key presses.
+    uint8_t toggle : 1;    
+};
+
+
+// Base class for all protocols
+//
+// Note that most of the data parameters are set through virtuals rather
+// than member variables.  This helps minimize the RAM footprint.
+//
+class IRProtocol
+{
+public:
+    IRProtocol() { }
+    virtual ~IRProtocol() { }
+    
+    // look up a protocol by ID
+    static IRProtocol *senderForId(int id);
+    
+    // name and ID
+    virtual const char *name() const = 0;
+    virtual int id() const = 0;
+    
+    // Are we a transmitter for the given protocol?  Some protocol
+    // handlers send and receive variations on a protocol that have
+    // distinct protocol IDs, such as the various Sony codes at
+    // different bit sizes.  By default, we assume we handle only
+    // our nominal protocol as returned by id().
+    virtual bool isSenderFor(int protocolId) const { return protocolId == id(); }
+
+    // parse a pulse on receive
+    virtual void rxPulse(IRRecvProIfc *receiver, uint32_t t, bool mark) = 0;
+    
+    // PWM carrier frequency used for the IR signal, expressed as a PWM
+    // cycle period in seconds.  We use this to set the appropriate pWM
+    // frequency for transmissions.  The most common carrier is 38kHz.
+    //
+    // We can't use adjust the carrier frequency for receiving signals, since 
+    // the TSOP sensor we use does the demodulation at a fixed frequency.
+    // You can choose a frequency by choosing your sensor, since TSOP sensors
+    // are available in a range of carrier frequencies, but once you choose a
+    // sensor we can't set the frequency in software.  Fortunately, the 
+    // TSOP384xx seems tolerant in practice of a fairly wide range around
+    // its nominal 38kHz carrier.  I've successfull tested it with remotes
+    // documented to use a few other frequencies from 36 to 40 kHz.)
+    virtual float pwmPeriod(IRTXState *state) const { return 26.31578947e-6f; } // 38kHz
+    
+    // PWM duty cycle when transmitting
+    virtual float pwmDutyCycle() const { return .3f; }
+    
+    // Begin transmitting the given command.  Before calling, the caller
+    // turns off the IR LED and sets its PWM period to the one given by
+    // our pwmPeriod() method.  The caller also clears 'state', and then
+    // sets the 'cmd', 'pin', and 'pressed' fields.  The rest of the struct
+    // is for the protocol handler's private use during the transmission.
+    // handler is free to store its interim state here to pass information
+    // from txStart() to txStep() and from one txStep() to the next.
+    // 
+    // Subclass implementations should start by setting up 'state' with any
+    // data they need during the transmission.  E.g., convert the code word
+    // value into a series of bits to transmit, and store this in the
+    // 'bitstream' field.  Once that's set up, determine how long the initial
+    // gap should be (the IR off time between transmissions in the protocol),
+    // and return this time in microseconds.  The caller will return to
+    // other work while the gap time is elapsing, then it will call txStep()
+    // to advance to the next step of the transmission.
+    //
+    // DON'T do a timer pause or spin wait here.  This routine is called in
+    // interrupt context, so it must return as quickly as possible.  All
+    // waits should be done simply by returning the desired wait time.
+    //
+    // By convention, implementations should start each transmission with a
+    // sufficient gap time (IR off) to allow senders to recognize the new
+    // transmission.  It's best to put the gap time at the *start* of each
+    // new transmission rather than at the end because two consecutive
+    // transmissions might use different protocols with different timing
+    // requirements.  If the first protocol has a short gap time and the
+    // second has a long gap time, putting the gap at the end of the first
+    // transmission would only use the shorter time, which might not be
+    // sufficient for the second protocol's receiver.
+    virtual int txStart(IRTXState *state) = 0;
+        
+    // Continue a transmission with the next pulse step.  This carries
+    // out the next step of the transmission, then returns a time value
+    // with the number of microseconds until the next event.  For example,
+    // if the current step in the transmission is a 600us mark, turn on the
+    // IR transmitter, update 'state' to indicate that we're in the mark,
+    // and return 600 to tell the caller to go do other work while the
+    // mark is being sent.  Don't wait or spin here, since this is called
+    // in interrupt context and should thus return as quickly as possible.
+    //
+    // Before calling this, the caller will update the 'pressed' field in
+    // 'state' to let us know if the virtual button is still being pressed.
+    // The protocol handler can auto-repeat if the button is still pressed
+    // at the end of the current transmission, if that's appropriate for
+    // the protocol.  We let the handlers choose how to handle auto-repeat
+    // rather than trying to manage it globally, since there's a lot of
+    // variation in how it needs to be handled.  Some protocols, for example,
+    // use "ditto" codes (distinct codes that mean "same as last time"
+    // rather than actually re-transmitting a full command), while others
+    // have internal position markers to indicate a series of repeats
+    // for one key press.
+    virtual int txStep(IRTXState *state) = 0;
+    
+    // protocol singletons
+    static class IRProtocols *protocols;
+    
+    // allocate the protocol singletons, if we haven't already
+    static void allocProtocols();
+        
+protected:
+    // report code with a specific protocol and sub-protocol
+    void reportCode(IRRecvProIfc *receiver, int pro, uint64_t code, bool3 toggle, bool3 ditto);
+};
+
+// -----------------------------------------------------------------------
+//
+// Protocol containing a decoded command value
+//
+template<class CodeType> class IRPWithCode: public IRProtocol
+{
+public:
+    IRPWithCode() 
+    {
+        rxState = 0;
+        bit = 0;
+        code = 0;
+    }
+    
+    // Minimum gap on receive (space before header)
+    virtual uint32_t minRxGap() const = 0;
+    
+    // Gap time to send on transmit between before the first code, and
+    // between repeats.  By default, we use the the generic txGap() in
+    // both cases.
+    virtual uint32_t txGap(IRTXState *state) const = 0;
+    virtual uint32_t txPreGap(IRTXState *state) const { return txGap(state); }
+    virtual uint32_t txPostGap(IRTXState *state) const { return txGap(state); }
+    
+    // Minimum number of repetitions when transmitting.  Some codes
+    // (e.g., Sony) require a minimum repeat count, no matter how
+    // quickly the button was released.  1 means that only a single
+    // transmission is required, which is true of most codes.
+    virtual int txMinReps(IRTXState *state) const { return 1; }
+    
+    // Header mark and space length.  Most IR protocols have an initial
+    // mark and space of fixed length as a lead-in or header.  Being of
+    // fixed length, they carry no data themselves, but they serve three
+    // useful functions: the initial mark can be used to set an AGC level
+    // if the receiver needs it (the TSOP sensors don't, but some older
+    // devices did); they can help distinguish one protocol from another
+    // by observing the distinctive timing of the header; and they serve
+    // as synchronization markers, by using timing that can't possibly
+    // occur in the middle of a code word to tell us unambiguously that
+    // we're at the start of a code word.
+    //
+    // Not all protocols have the lead-in mark and/or space.  Some
+    // protocols simply open with the initial data bit, and others use
+    // an AGC mark but follow it immediately with a data bit, with no
+    // intervening fixed space.
+    //
+    // * If the protocol doesn't use a header mark at all but opens with
+    // a mark that's part of a data bit, set tHeaderMark() to 0.
+    //
+    // * If the protocol has a fixed AGC mark, but follows it with a 
+    // varying-length space that's part of the first data bit, set 
+    // tHeaderMark() to the fixed mark length and set tHeaderSpace() to 0.
+    //
+    virtual uint32_t tHeaderMark() const = 0;
+    virtual uint32_t tHeaderSpace() const = 0;
+    
+    // Can the header space be adjacent to a data space?  In most protocols,
+    // the header space is of constant length because it's always followed
+    // by a mark.  This is accomplished in some protocols with explicit
+    // structural marks; in some, it happens naturally because all bits start
+    // with marks (e.g., NEC, Sony); and in others, the protocol requires a
+    // a "start bit" with a fixed 0 or 1 value whose representation starts
+    // with a mark (e.g., RC6).  But in a few protocols (e.g., OrtekMCE),
+    // there's no such care taken, so the header space can flow into a space 
+    // at the start of the first data bit.  Set this to true for such
+    // protocols, and we'll divvy up the space after the header mark into
+    // a fixed part and a data portion for the first bit.
+    virtual bool headerSpaceToData() const { return false; }
+    
+    // Ditto header.  For codes with dittos that use a distinct header
+    // format, this gives the header timing that identifies a ditto.
+    // Return zero for a code that doesn't use dittos at all or encodes
+    // them in some other way than a distinctive header (e.g., in the
+    // payload data).
+    virtual uint32_t tDittoMark() const { return 0; }
+    virtual uint32_t tDittoSpace() const { return 0; }
+    
+    // Stop mark length.  Many codes have a fixed-length mark following
+    // the data bits.  Return 0 if there's no final mark.
+    virtual uint32_t tStopMark() const { return 0; }
+    
+    // Number of bits in the code.  For protocols with multiple bit
+    // lengths, use the longest here.
+    virtual int nbits() const = 0;
+    
+    // true -> bits arrive LSB-first, false -> MSB first
+    virtual bool lsbFirst() const { return true; }
+
+    // Pulse processing state machine.
+    // This state machine handles the basic protocol structure used by
+    // most IR remotes:
+    //
+    //   Header mark of fixed duration
+    //   Header space (possibly followed directly by the first bit's space)
+    //   Data bits
+    //   Stop mark
+    //   Gap between codes
+    //   Ditto header mark   }  a pattern that's distinguishable from
+    //   Ditto header space  }   the standard header mark
+    //   Ditto data bits
+    //   Gap betwee codes
+    //
+    // The state machine can handle protocols that use all of these sections,
+    // or only a subset of them.  For example, a few protocols (Denon, RC5)
+    // have no header at all and start directly wtih the data bits.  Most
+    // protocols have no "ditto" coding and just repeat the main code to
+    // signal auto-repeat.  
+    //
+    // The monolithic state machine switch looks kind of ugly, but it seems
+    // to be the cleanest way to handle this.  My initial design was more OO,
+    // using virtual subroutines to handle each step.  But that turned out to
+    // be fragile, because there was too much interaction between the handlers
+    // and the overall state machine sequencing.  The monolithic switch actually
+    // seems much cleaner in practice.  The variations are all handled through
+    // simple data parameters.  The resulting design isn't as flexible in
+    // principle as something more virtualized at each step, but nearly all
+    // of the IR protocols I've seen so far are so close to the same basic
+    // structure that this routine works for practically everything.  Any
+    // protocols that don't fit the same structure will be best served by
+    // replacing the whole state machine for the individual protocols.
+    virtual void rxPulse(IRRecvProIfc *receiver, uint32_t t, bool mark)
+    {
+        uint32_t tRef;
+        switch (rxState)
+        {
+        case 0:
+        s0:
+            // Initial gap or inter-code gap.  When we see a space of
+            // sufficient length, switch to Header Mark mode.
+            rxState = (!mark && t > minRxGap() ? 1 : 0);
+            break;
+            
+        case 1:
+        s1:
+            // Header mark.  If the protocol has no header mark, go
+            // straight to the data section.  Otherwise, if we have
+            // a mark that matches the header mark we're expecting, 
+            // go to Header Space mode.  Otherwise, we're probably in
+            // the middle of a code that we missed the beginning of, or
+            // we're just receiving a code using another protocol.  Go
+            // back to Gap mode - that will ignore everything until we
+            // get radio silence for long enough to be sure we're 
+            // starting a brand new code word.
+            if ((tRef = tHeaderMark()) == 0)
+                goto s3;
+            rxState = (mark && inRange(t, tRef) ? 2 : 0);
+            break;
+            
+        case 2:
+        s2:
+            // Header space.  If the protocol doesn't have a header
+            // space, go straight to the data.
+            if ((tRef = tHeaderSpace()) == 0)
+                goto s3;            
+                
+            // If this protocol has an undelimited header space, make
+            // sure this space is long enough to qualify, but allow it
+            // to be longer.  If it qualifies, deduct the header space
+            // span from it and apply the balance as the first data bit.
+            if (headerSpaceToData() && aboveRange(t, tRef, tRef))
+            {
+                t -= tRef;
+                goto s3;
+            }
+            
+            // If we have a space matching the header space, enter the
+            // data section. 
+            if (!mark && inRange(t, tRef))
+            {
+                rxState = 3;
+                break;
+            }
+            
+            // Not a match - go back to gap mode
+            goto s0;
+            
+        case 3:
+        s3:
+            // enter data section
+            rxReset();
+            if (mark) 
+                goto s4;
+            else 
+                goto s5;
+            
+        case 4:
+        s4:
+            // data mark
+            if (mark && rxMark(receiver, t))
+            {
+                rxState = bit < nbits() ? 5 : 7;
+                break;
+            }
+            goto s0;
+            
+        case 5:
+        s5:
+            // data space
+            if (!mark && rxSpace(receiver, t))
+            {
+                rxState = bit < nbits() ? 4 : 6;
+                break;
+            }
+            else if (!mark && t > minRxGap())
+                goto s7;
+            goto s0;
+            
+        case 6:
+            // stop mark - if we don't have a mark, go to the gap instead
+            if (!mark)
+                goto s7;
+                
+            // check to see if the protocol even has a stop mark
+            if ((tRef = tStopMark()) == 0)
+            {
+                // The protocol has no stop mark, and we've processed
+                // the last data bit.  Close the data section and go
+                // straight to the next header.
+                rxClose(receiver, false);
+                goto s8;
+            }
+            
+            // there is a mark - make sure it's in range
+            if (inRange(t, tRef))
+            {
+                rxState = 7;
+                break;
+            }
+            goto s0;
+            
+        case 7: 
+        s7:
+            // end of data - require minimum gap
+            if (!mark && t > minRxGap())
+            {
+                // close the data section
+                rxClose(receiver, false);
+                rxState = 8;
+                break;
+            }
+            goto s0;
+                
+        case 8:
+        s8:
+            // Ditto header.  If the protocol has a ditto header at all,
+            // and this mark matches, proceed to the ditto space.  Otherwise
+            // try interepreting this as a new regular header instead.
+            if (mark && (tRef = tDittoMark()) != 0 && inRange(t, tRef))
+            {
+                rxState = 9;
+                break;
+            }
+            goto s1;
+            
+        case 9:
+            // Ditto space.  If this doesn't match the ditto space, and
+            // the ditto header and regular header are the same, try
+            // re-interpreting the space as a new regular header space.
+            if (!mark && (tRef = tDittoSpace()) != 0 && inRange(t, tRef))
+            {
+                rxState = 10;
+                break;
+            }
+            else if (!mark && tDittoMark() == tHeaderMark())
+                goto s2;
+            goto s0;
+                
+        case 10:
+            // Enter ditto data
+            rxDittoReset();
+            goto s11;
+            
+        case 11:
+        s11:
+            // Ditto data - mark
+            if (mark && rxMark(receiver, t))
+            {
+                rxState = bit < nbits() ? 12 : 13;
+                break;
+            }
+            goto s0;
+            
+        case 12:
+            // data space
+            if (!mark && rxSpace(receiver, t))
+            {
+                rxState = bit < nbits() ? 11 : 13;
+                break;
+            }
+            else if (!mark && t > minRxGap())
+                goto s13;
+            goto s0;
+            
+        case 13:
+        s13:
+            // end ditto data
+            if (!mark && t > minRxGap())
+            {
+                // close the ditto data section
+                rxClose(receiver, true);
+                rxState = 8;
+                break;
+            }
+            goto s0;
+        }
+
+        // if this is a space longer than the timeout, go into idle mode
+        if (!mark && t >= IRReceiver::MAX_PULSE)
+            rxIdle(receiver);
+    }
+
+    // Start transmission.  By convention, we start each transmission with
+    // a gap of sufficient length to allow receivers to recognize a new
+    // transmission.  The only protocol-specific work we usually have to
+    // do here is to prepare a bit string to send.
+    virtual int txStart(IRTXState *state)
+    {
+        // convert the code into a bitstream to send
+        codeToBitstream(state);
+        
+        // transmit the initial gap to make sure we've been silent long enough
+        return txPreGap(state);
+    }
+    
+    // Continue transmission.  Most protocols have a similar structure,
+    // with a header mark, header gap, data section, and trailing gap.
+    // We implement the framework for this common structure with a
+    // simple state machine:
+    //
+    //   state 0 = done with gap, transmitting header mark
+    //   state 1 = done with header, transmitting header space
+    //   state 2 = transmitting data bits, via txDataStep() in subclasses
+    //   state 3 = done with data, sending post-code gap
+    //
+    // Subclasses for protocols that don't follow the usual structure can 
+    // override this entire routine as needed and redefine these internal 
+    // states.  Protocols that match the common structure will only have
+    // to define txDataStep().
+    //
+    // Returns the time to the next step, or a negative value if we're
+    // done with the transmission.
+    virtual int txStep(IRTXState *state)
+    {
+        // The individual step handlers can return 0 to indicate
+        // that we should go immediately to the next step without
+        // a delay, so iterate as long as they return 0.
+        for (;;)
+        {
+            // Use the first-code or "ditto" handling, as appropriate
+            int t = state->rep > 0 && state->dittos ?
+                txDittoStep(state) : 
+                txMainStep(state);
+            
+            // If it's a positive time, it's a delay; if it's a negative
+            // time, it's the end of the transmission.  In either case,
+            // return the time to the main transmitter routine.  If it's
+            // zero, though, it means to proceed without delay, so we'll
+            // simply continue iterating.
+            if (t != 0)
+                return t;
+        }
+    }
+    
+    // Main transmission handler.  This handles the txStep() work for
+    // the first code in a repeat group.
+    virtual int txMainStep(IRTXState *state)
+    {        
+        // One state might transition directly to the next state
+        // without any time delay, so keep going until we come to
+        // a wait state.
+        int t;
+        for (;;)
+        {
+            switch (state->step)
+            {
+            case 0:
+                // Finished the pre-code gap.  This is the actual start
+                // of the transmission, so mark the time.
+                state->txTime.reset();
+                
+                // if there's a header mark, transmit it
+                state->step++;
+                if ((t = this->tHeaderMark()) > 0)
+                {
+                    state->pin->write(pwmDutyCycle());
+                    return t;
+                }
+                break;
+                
+            case 1:
+                // finished header mark, start header space
+                state->step++;
+                if ((t = this->tHeaderSpace()) > 0)
+                {
+                    state->pin->write(0);
+                    return this->tHeaderSpace();
+                }
+                break;
+                
+            case 2:
+                // data section - this is up to the subclass
+                if ((t = txDataStep(state)) != 0)
+                    return t;
+                break;
+                
+            case 3:
+                // done with data; send the stop mark, if applicable
+                state->step++;
+                if ((t = tStopMark()) > 0)
+                {
+                    state->pin->write(pwmDutyCycle());
+                    return t;
+                }
+                break;
+                
+            case 4:
+                // post-code gap
+                state->step++;
+                state->pin->write(0);
+                if ((t = this->txPostGap(state)) > 0)
+                    return t;
+                break;
+                
+            default:
+                // Done with the iteration.  Finalize the transmission;
+                // this will figure out if we're going to repeat.
+                return this->txEnd(state);
+            }
+        }
+    }
+    
+    // Ditto step handler.  This handles txStep() work for a repeated
+    // code.  Most protocols just re-transmit the same code each time,
+    // so by default we use the main step handling.  Subclasses for
+    // protocols that transmit different codes on repeat (such as NEC)
+    // can override this to send the ditto code instead.
+    virtual int txDittoStep(IRTXState *state)
+    {
+        return txMainStep(state);
+    }
+    
+    // Handle a txStep() iteration for a data bit.  Subclasses must
+    // override this to handle the particulars of sending the data bits.
+    // At the end of the data transmission, the subclass should increment
+    // state->step to tell us that we've reached the post-code gap.
+    virtual int txDataStep(IRTXState *state)
+    {
+        state->step++;
+        return 0;
+    }
+    
+    // Send the stop bit, if applicable.  If there's no stop bit or
+    // equivalent, simply increment state->step and return 0;
+    int txStopBit(IRTXState *state)
+    {
+        state->step++;
+        return 0;
+    }
+    
+    // Handle the end of a transmission.  This figures out if we're
+    // going to auto-repeat, and if so, resets for the next iteration.
+    // If we're going to repeat, we return the time to the next tx step,
+    // as usual.  If the transmission is done, we return -1 to indicate
+    // that no more steps are required.
+    int txEnd(IRTXState *state)
+    {
+        // count the repeat
+        state->rep++;
+
+        // If the button is still down, or we haven't reached our minimum
+        // repetition count, repeat the code.
+        if (state->pressed || state->rep < txMinReps(state))
+        {
+            // return to the first transmission step
+            state->step = 0;
+            state->bit = 0;
+            state->bitstep = 0;
+            
+            // re-generate the bitstream, in case we need to encode positional
+            // information such as a toggle bit or position counter
+            codeToBitstream(state);
+            
+            // restart the transmission timer
+            state->txTime.reset();
+            
+            // we can go immediately to the next step, so return a zero delay
+            return 0;
+        }
+        
+        // we're well and truly done - tell the caller not to call us
+        // again by returning a negative time interval
+        return -1;
+    }
+
+protected:
+    // Reset the receiver.  This is called when the receiver enters
+    // the data section of the frame, after parsing the header (or
+    // after a gap, if the protocol doesn't use a header).
+    virtual void rxReset()
+    {
+        bit = 0;
+        code = 0;
+    }
+    
+    // Reset on entering a new ditto frame.  This is called after
+    // parsing a "ditto" header.  This is only needed for protocols
+    // that use distinctive ditto framing.
+    virtual void rxDittoReset() { }
+    
+    // Receiver is going idle.  This is called any time we get a space
+    // (IR OFF) that exceeds the general receiver timeout, regardless
+    // of protocol state.  
+    virtual void rxIdle(IRRecvProIfc *receiver) { }
+    
+    // Parse a data mark or space.  If the symbol is valid, shifts the
+    // bit into 'code' (the code word under construction) and returns
+    // true.  If the symbol is invalid, returns false.  Updates the
+    // bit counter in 'bit' if this symbol finishes a bit.
+    virtual bool rxMark(IRRecvProIfc *receiver, uint32_t t) { return false; }
+    virtual bool rxSpace(IRRecvProIfc *receiver, uint32_t t) { return false; }
+    
+    // Report the decoded value in our internal register, if it's valid.
+    // By default, we'll report the code value as stored in 'code', with
+    // no toggle bit, if the number of bits we've decoded matches the
+    // expected number of bits as given by nbits().  Subclasses can 
+    // override as needed to do other validation checks; e.g., protocols
+    // with varying bit lengths will need to check for all of the valid
+    // bit lengths, and protocols that contain error-checking information
+    // can validate that.
+    //
+    // Unless the protocol subclass overrides the basic pulse handler
+    // (rxPulse()), this is called when we end the data section of the
+    // code.
+    virtual void rxClose(IRRecvProIfc *receiver, bool ditto)
+    {
+        if (bit == nbits())
+            reportCode(receiver, id(), code, bool3::null, ditto);
+    }
+    
+    // Encode a universal code value into a bit string in preparation
+    // for transmission.  This should take the code value in state->cmdCode,
+    // convert it to the string of bits to serially, and store the result
+    // in state->bitstream.
+    virtual void codeToBitstream(IRTXState *state)
+    {
+        // by default, simply store the code as-is
+        state->bitstream = state->cmdCode;
+        state->nbits = nbits();
+    }
+    
+    // Get the next bit for transmission from the bitstream object
+    int getBit(IRTXState *state)
+    {
+        // get the number of bits and the current bit position
+        int nbits = state->nbits;
+        int bit = state->bit;
+        
+        // figure the bit position according to the bit order
+        int bitpos = (lsbFirst() ? bit : nbits - bit - 1);
+        
+        // pull out the bit
+        return int((state->bitstream >> bitpos) & 1);
+    }
+    
+    // Set the next bit to '1', optionally incrementing the bit counter
+    void setBit(bool inc = true)
+    {
+        // ignore overflow
+        int nbits = this->nbits();
+        if (bit >= nbits)
+            return;
+
+        // Figure the starting bit position in 'code' for the bit,
+        // according to whether we're adding them LSB-first or MSB-first.
+        int bitpos = (lsbFirst() ? bit : nbits - bit - 1);
+        
+        // mask in the bit
+        code |= (CodeType(1) << bitpos);
+        
+        // advance the bit position
+        if (inc)
+            ++bit;
+    }
+    
+    // Set the next N bits to '1'
+    void setBits(int n)
+    {
+        // ignore overflow
+        int nbits = this->nbits();
+        if (bit + n - 1 >= nbits)
+            return;
+        
+        // Figure the starting bit position in 'code' for the bits we're 
+        // setting.  If bits arrive LSB-first, the 'bit' counter gives us
+        // the bit position directly.  If bits arrive MSB-first, we need
+        // to start from the high end instead, so we're starting at
+        // ((nbits()-1) - bit).  However, we want to set multiple bits
+        // left-to-right in any case, so move our starting position to
+        // the "end" of the window by moving right n-1 addition bits.
+        int bitpos = (lsbFirst() ?  bit : nbits - bit - n);
+        
+        // turn that into a bit mask for the first bit
+        uint64_t mask = (CodeType(1) << bitpos);
+        
+        // advance our 'bit' counter past the added bits
+        bit += n;
+        
+        // set each added bit to 1
+        for ( ; n != 0 ; --n, mask <<= 1)
+            code |= mask;
+    }
+
+    // decoding state   
+    uint8_t rxState; 
+
+    // next bit position
+    uint8_t bit;
+    
+    // command code under construction
+    CodeType code;
+};
+
+// -----------------------------------------------------------------------
+//
+// Basic asynchronous encoding
+// 
+// This is essentially the IR equivalent of a wired UART.  The transmission 
+// for a code word is divided into a fixed number of bit time periods of 
+// fixed length, with each period containing one bit, signified by IR ON for 
+// 1 or IR OFF for 0.
+// 
+// Simple async coding doesn't have any inherent structure other than the
+// bit length.  That makes it hard to distinguish from other IR remotes that
+// might share the environment, and even from random noise, which is why
+// most CE manufacturers use more structured protocols.  In practice, remotes
+// using this coding are likely have to impose some sort of structure on the
+// bits, such as long bit strings, error checking bits, or distinctive prefixes
+// or suffixes.  The only example of a pure async code I've seen in the wild 
+// is Lutron, and they do in fact use fairly long codes (36 bits) with set 
+// prefixes.  Their prefixes aren't only distinctive as bit sequences, but
+// also in the raw space/mark timing, which makes them effectively serve the
+// function that header marks do in most of the structured protocols.
+//
+template<class CodeType> class IRPAsync: public IRPWithCode<CodeType>
+{
+public:
+    IRPAsync() { }
+    
+    // duration of each bit in microseconds
+    virtual int tBit() const = 0;
+    
+    // maximum legal mark in a data section, if applicable
+    virtual uint32_t maxMark() const { return IRReceiver::MAX_PULSE; }
+    
+    // Async codes lack the structure of the more typical codes, so we
+    // use a completely custom pulse handler
+    virtual void rxPulse(IRRecvProIfc *receiver, uint32_t t, bool mark)
+    {
+        uint32_t tRef, tRef2;
+        switch (this->rxState)
+        {
+        case 0:
+        s0:
+            // Gap - if this is a long enough space, switch to header mode.
+            this->rxState = (!mark && t > this->minRxGap() ? 1 : 0);
+            break;
+            
+        case 1:
+            // Header mode.  Async protocols don't necessarily have headers,
+            // but some (e.g., Lutron) do start all codes with a distinctively
+            // long series of bits.  If the subclass defines a header space,
+            // apply it to sense the start of a code.
+            if ((tRef = this->tHeaderMark()) == 0)
+                goto s2;
+                
+            // we have a defined header mark - make sure this is long enough
+            tRef2 = tBit();
+            if (mark && inRangeOrAbove(t, tRef, tRef2))
+            {
+                // deduct the header time
+                t = t > tRef ? t - tRef : 0;
+                
+                // if that leaves us with a single bit time or better,
+                // treat it as the first data bit
+                if (inRangeOrAbove(t, tRef2, tRef2))
+                    goto s2;
+                    
+                // that consumes the whole mark, so just switch to data
+                // mode starting with the next space
+                this->rxState = 2;
+                break;
+            }
+            
+            // doesn't look like the start of a code; back to gap mode
+            goto s0;
+                
+        case 2:
+        s2:
+            // Enter data mode
+            this->rxReset();
+            goto s3;
+            
+        case 3:
+        s3:
+            // Data mode.  Process the mark or space as a number of bits.
+            {
+                // figure how many bits this symbol represents
+                int tb = tBit();
+                int n = (t + tb/2)/tb;
+                
+                // figure how many bits remain in the code
+                int rem = this->nbits() - this->bit;
+                
+                // check to see if this symbol overflows the bits remaining
+                if (n > rem)
+                {
+                    // marks simply can't exceed the bits remaining
+                    if (mark)
+                        goto s0;
+                        
+                    // Spaces can exceed the remaining bits, since we can 
+                    // have a string of 0 bits followed by a gap between 
+                    // codes.  Use up the remaining bits as 0's, and apply 
+                    // the balance as a gap.
+                    this->bit += rem;
+                    t -= rem*tb;
+                    goto s4;
+                }
+                
+                // check if it exceeds the code's maximum mark length
+                if (mark && t > maxMark())
+                    goto s0;
+                
+                // Make sure that it actually looks like an integral
+                // number of bits.  If it's not, take it as a bad code.
+                if (!inRange(t, n*tb, tb))
+                    goto s0;
+                    
+                // Add the bits
+                if (mark)
+                    this->setBits(n);
+                else
+                    this->bit += n;
+                    
+                // we've consumed the whole interval as bits
+                t = 0;
+                
+                // if that's enough bits, we have a decode
+                if (this->bit == this->nbits())
+                    goto s4;
+                    
+                // stay in data mode
+                this->rxState = 3;
+            }
+            break;
+            
+        case 4:
+        s4:
+            // done with the code - close it out and start over
+            this->rxClose(receiver, false);
+            goto s0;
+        }
+    }
+    
+    // send data
+    virtual int txDataStep(IRTXState *state) 
+    { 
+        // get the next bit
+        int b = this->getBit(state);
+        state->bit++;
+        
+        // count how many consecutive matching bits follow
+        int n = 1;
+        int nbits = state->nbits;
+        for ( ; state->bit < nbits && this->getBit(state) == b ; 
+            ++n, ++state->bit) ;
+        
+        // if we're out of bits, advance to the next step
+        if (state->bit >= nbits)
+            ++state->step;
+        
+        // 0 bits are IR OFF and 1 bits are IR ON
+        state->pin->write(b ? this->pwmDutyCycle() : 0);
+        
+        // stay on for the number of bits times the time per bit
+        return n * this->tBit();
+    }
+};
+
+
+// -----------------------------------------------------------------------
+//
+// Space-length encoding
+//
+// This type of encoding uses the lengths of the spaces to encode the bit
+// values.  Marks are all the same length, and spaces come in two lengths,
+// short and long, usually T and 2T for a vendor-specific time T (typically
+// on the order of 500us).  The short space encodes 0 and the long space
+// encodes 1, or vice versa.
+//
+// The widely used NEC protocol is a space-length encoding, and in practice
+// it seems that most of the ad hoc proprietary protocols are also space-
+// length encodings, mostly with similar parameters to NEC.
+//
+template<class CodeType> class IRPSpaceLength: public IRPWithCode<CodeType>
+{
+public:
+    IRPSpaceLength() { }
+
+    // mark length, in microseconds
+    virtual int tMark() const = 0;
+    
+    // 0 and 1 bit space lengths, in microseconds
+    virtual int tZero() const = 0;
+    virtual int tOne() const = 0;
+    
+    // Space-length codings almost always need a mark after the last
+    // bit, since otherwise the last bit's space (the significant part)
+    // would just flow into the gap that follows.
+    virtual uint32_t tStopMark() const { return tMark(); }
+
+    // process a mark
+    virtual bool rxMark(IRRecvProIfc *receiver, uint32_t t)
+    {
+        // marks simply delimit spaces in this protocol and thus
+        // carry no bit information
+        if (inRange(t, tMark()))
+            return true;
+        else
+            return false;
+    }
+     
+    // process a space   
+    virtual bool rxSpace(IRRecvProIfc *receiver, uint32_t t)
+    {
+        // a short space represents a '0' bit, a long space is a '1'
+        if (inRange(t, tZero()))
+            return this->bit++, true;
+        else if (inRange(t, tOne()))
+            return this->setBit(), true;
+        else
+            return false;
+    }
+
+    // continue a transmission
+    virtual int txDataStep(IRTXState *state)
+    {
+        // Data section.
+        if (state->bitstep == 0)
+        {
+            // mark - these are fixed length
+            state->pin->write(this->pwmDutyCycle());
+            state->bitstep = 1;
+            return tMark();
+        }
+        else
+        {
+            // space - these are variable length according to the data
+            state->pin->write(0);
+            int t = this->getBit(state) ? tOne() : tZero();
+            state->bitstep = 0;
+
+            // advance to the next bit; stop if we're done
+            if (++state->bit >= state->nbits)
+                ++state->step;
+                
+            // return the space time
+            return t;
+        }
+    }
+    
+};
+
+
+// -----------------------------------------------------------------------
+//
+// Mark-length encoding
+//
+// This is the inverse of space-length coding.  In this scheme, the bit
+// values are encoded in the mark length.  Spaces are fixed length, and
+// marks come in short (0) and long (1) lengths, usually of time T and 2T
+// for a protocol-specific time T.
+//
+// Sony uses this type of encoding.
+template<class CodeType> class IRPMarkLength: public IRPWithCode<CodeType>
+{
+public:
+    IRPMarkLength() { }
+
+    // space length, in microseconds
+    virtual int tSpace() const = 0;
+    
+    // 0 and 1 bit mark lengths, in microseconds
+    virtual int tZero() const = 0;
+    virtual int tOne() const = 0;
+
+    // process a mark
+    virtual bool rxMark(IRRecvProIfc *receiver, uint32_t t)
+    {
+        // a short mark represents a '0' bit, a long space is a '1'
+        if (inRange(t, tZero()))
+            this->bit++;
+        else if (inRange(t, tOne()))
+            this->setBit();
+        else
+            return false;
+        return true;
+    }
+     
+    // process a space   
+    virtual bool rxSpace(IRRecvProIfc *receiver, uint32_t t)
+    {
+        // spaces simply delimit marks in this protocol and carry
+        // no bit information of their own
+        return inRange(t, tSpace());
+    }
+
+    // continue a transmission
+    virtual int txDataStep(IRTXState *state)
+    {
+        // check if we're on a mark (step 0) or space (step 1)
+        if (state->bitstep == 0)
+        {
+            // space - these are variable length according to the data
+            state->pin->write(this->pwmDutyCycle());
+            int t = this->getBit(state) ? tOne() : tZero();
+            state->bitstep = 1;
+                
+            // return the mark time
+            return t;
+        }
+        else
+        {
+            // Space - fixed length
+            state->pin->write(0);
+            state->bitstep = 0;
+            
+            // advance to the next bit; stop if we're done
+            if (++state->bit >= state->nbits)
+                state->step = 3;
+            return tSpace();
+        }
+    }
+    
+};
+
+
+// -----------------------------------------------------------------------
+//
+// Manchester coding
+//
+// This type of coding uses a fixed time per bit, and encodes the bit
+// value in a mark/space or space/mark transition within each bit's
+// time window.  
+//
+// The decoding process is a little tricky to grap when you're looking
+// at just the raw data, because the raw data renders things in terms of 
+// monolithic marks and spaces of different lengths, whereas the coding 
+// divides the time axis into even chunks and looks at what's going on
+// in each chunk.  In terms of the raw data, we can think of it this way.
+// Because every bit time window has a transition (mark/space or space/mark)
+// in the middle of it, there has to be at least one transition per window.
+// There can also be a transition between each window, or not, as needed
+// to get the transmitter into the right initial state for the next bit.  
+// This means that each mark and each space is either T or 2T long, where
+// T is the half the bit window time.  So we can simply count these units.
+// If we see a mark or space of approximate length T, we count one unit;
+// if the length is around 2T, we count two units.  On each ODD count, we
+// look at the state just before the count.  If we were in a space just
+// before the count, the bit is a 1; if it was a mark, the bit is a 0.
+//
+// Manchester coding is used in the Philips RC5 and RC6 protocols, which
+// are in turn used by most other European CE companies.
+template<class CodeType> class IRPManchester: public IRPWithCode<CodeType>
+{
+public:
+    IRPManchester() { }
+    
+    // Half-bit time.  This is half of the time interval of one bit,
+    // so it's equal to the time on each side of the mark/space or
+    // space/mark transition in the middle of each bit.
+    virtual int tHalfBit(int bit) const = 0;
+    
+    // Bit value (0 or 1) of a space-to-mark transition.  A mark-to-space
+    // transition always has the opposite sense.
+    virtual int spaceToMarkBit() const { return 1; }
+    inline int markToSpaceBit() const { return !spaceToMarkBit(); }
+    
+    // reset the decoder state
+    virtual void rxReset()
+    {
+        IRPWithCode<CodeType>::rxReset();
+        halfBitPos = 0;
+    }
+
+    // process a mark
+    virtual bool rxMark(IRRecvProIfc *receiver, uint32_t t)
+    {
+        // transitioning from mark to space, so this is a 
+        return processTransition(t, spaceToMarkBit());
+    }
+    
+    // process a space
+    virtual bool rxSpace(IRRecvProIfc *receiver, uint32_t t)
+    {
+        return (processTransition(t, markToSpaceBit()));
+    }
+    
+    // Process a space/mark or mark/space transition.  Returns true on
+    // success, false on failure.
+    bool processTransition(uint32_t t, int bitval)
+    {
+        // If this time is close to zero, ignore it.
+        int thb = tHalfBit(this->bit);
+        if (t < ((thb*toleranceShl8) >> 8))
+            return true;
+        
+        // If the current time is the middle of a bit, the transition
+        // specifies a bit value, so set the bit.  Transitions between
+        // bits are just clocking.
+        if (halfBitPos == 1)
+        {
+            if (bitval)
+            {
+                // set the bit, but keep the bit counter where it is, since
+                // we manage it ourselves
+                this->setBit();
+                this->bit--;
+            }
+        }
+        
+        // Advance by the time interval.  Check that we have at least one
+        // half-bit interval to work with.
+        if (t < ((thb * (256 - toleranceShl8)) >> 8))
+            return false;
+        
+        // If we're at a half-bit position, start by advancing to the next
+        // bit boundary.
+        if (halfBitPos)
+        {
+            // deduct the half-bit time
+            t = (t > thb ? t - thb : 0);
+
+            // advance our position counters to the next bit boundary
+            halfBitPos = 0;
+            this->bit++;
+            
+            // Some subprotocols (e.g., RC6) have variable bit timing,
+            // so the timing for this bit might be different than for
+            // the previous bit.  Re-fetch the time.
+            thb = tHalfBit(this->bit);
+        }
+        
+        // If we have another half-interval left to process, advance
+        // to the middle of the current bit.
+        if (t < ((thb * toleranceShl8) >> 8))
+        {
+            // we already used up the symbol time, so we're done
+            return true;
+        }
+        else if (inRange(t, thb))
+        {
+            // we have another half-bit time to use, so advance to
+            // the middle of the bit
+            halfBitPos = true;
+            return true;
+        }
+        else
+        {
+            // The time remaining is wrong, so terminate decoding.
+            // Note that this could simply be because we've reached
+            // the gap at the end of the code word, in which case we'll
+            // already have all of the bits stored and will generate
+            // the finished code value.
+            return false;
+        }
+    }
+    
+    virtual int txDataStep(IRTXState *state) 
+    { 
+        // Get the current bit
+        int b = this->getBit(state);
+        
+        // Determine if this bit uses a space-to-mark or mark-to-space
+        // transition.  It uses a space-to-mark transition if it matches
+        // the space-to-mark bit.
+        int stm = (b == spaceToMarkBit());
+        
+        // Check to see if we're at the start or middle of the bit
+        if (state->bitstep == 0)
+        {
+            // Start of the current bit.  Set the level for the first
+            // half of the bit.  If we're doing a space-to-mark bit,
+            // the first half is a space, otherwise it's a mark.
+            state->pin->write(stm ? 0 : this->pwmDutyCycle());
+            
+            // leave this on for a half-bit time to get to the 
+            // middle of the bit
+            state->bitstep = 1;
+            return tHalfBit(state->bit);
+        }
+        else
+        {
+            // Middle of the current bit.  Set the level for the second
+            // half of the bit.  If we're in a space-to-mark bit, the
+            // second half is the mark, otherwise it's the space.
+            state->pin->write(stm ? this->pwmDutyCycle() : 0);
+            
+            // figure the time to the start of the next bit
+            int t = tHalfBit(state->bit);
+            
+            // advance to the start of the next bit
+            state->bit++;
+            state->bitstep = 0;
+            
+            // If the next bit is the inverse of the current bit, it will
+            // lead in with the same level we're going out with.  That 
+            // means we can go straight to the middle of the next bit
+            // without another interrupt.
+            if (state->bit < state->nbits && this->getBit(state) != b)
+            {
+                // proceed to the middle of the next bit
+                state->bitstep = 1;
+                
+                // add the half-bit time
+                t += tHalfBit(state->bit);
+            }
+            
+            // if this was the last bit, advance to the next state
+            if (state->bit >= state->nbits)
+                state->step++;
+            
+            // return the time to the next transition
+            return t;
+        }
+    }
+
+    // Current half-bit position.  If the last transition was on the
+    // border between two bits, this is 0.  If it was in the middle
+    // of a bit, this is 1.
+    uint8_t halfBitPos : 1;
+};
+
+// -----------------------------------------------------------------------
+// 
+// NEC protocol family.  This is one of the more important proprietary 
+// protocols, since many CE companies use the standard NEC code or a 
+// variation of it.  This class handles the following variations on the
+// basic NEC code:
+//
+//   NEC-32:  32-bit payload, 9000us header mark, 4500us header space
+//   NEC-32X: 32-bit payload, 4500us header mark, 4500us header space
+//   NEC-48:  48-bit payload, 9000us header mark, 4500us header space
+//   Pioneer: NEC-32 with address A0..AF + possible "shift" prefixes
+//
+// Each of the three NEC-nn protocol types comes in two sub-types: one
+// that uses "ditto" codes for repeated keys, and one that simply sends
+// the same code again on repeats.  The ditto code, when used, varies with 
+// the main protocol type:
+//
+//   NEC-32:  9000us mark + 2250us space + 564us mark
+//   NEC-32x: 4500us mark + 4500us space + one data bit + 564us mark
+//   NEC-48:  9000us mark + 2250us space + 564us mark
+//   Pioneer: no dittos
+//
+// The NEC-32 and NEC-48 dittos can be detected from the header space
+// length.  The NEC-32x dittos can be detected by the one-bit code length.
+//
+// All variations of the NEC code are space-length encodings with 564us
+// marks between bits, 564us '0' spaces, and 3*564us '1' spaces.  All
+// variations use a long header mark and a 564us stop mark.  With those
+// fixed features, there are three main variations:
+//
+// The bits in the NEC 32-bit codes are structured into four 8-bit fields, 
+// with the first bit transmitted in the most significant position:
+//
+//   A1 A0 C1 C0
+//
+// A1 is the high 8 bits of the address, and A0 is the low 8 bits of the
+// address.  The address specifies a particular type of device, such as
+// "NEC VCR" or "Vizio TV".  These are assigned by NEC.  C1 and C0 form the
+// command code, which has a meaning specific to the device type specified 
+// by the address field.
+//
+// In the original NEC protocol, the nominal address is in A1, and A0 is
+// the 1's complement of A1 (A0 = ~A1), for error checking.  This was removed
+// in a later revision to expand the address space.  Most modern equipment
+// uses the newer system, so A0 is typically an independent value in remotes
+// you'll find in use today.
+//
+// In the official version of the protocol, C1 is the nominal command code,
+// and C0 = ~C1, for error checking.  However, some other manufacturers who
+// use the basic NEC protocol, notably Yamaha and Onkyo, violate this by using
+// C0 as an independent byte to expand the command space.  We therefore don't
+// test for the complemented byte, so that we don't reject codes from devices
+// that treat it as independent. 
+// 
+// Pioneer uses the NEC protocol with two changes.  First, the carrier is
+// 40kHz instead of 38kHz.  The TSOP384xx seems to receive the 40kHz signal
+// reliably, so the frequency doesn't matter on receive, but it might matter
+// on transmit if the target equipment isn't as tolerant.  Second, Pioneer
+// sometimes transmits a second code for the same key.  In these cases, the
+// first code is a sort of "shift" code (shared among many keys), and the
+// second code has the actual key-specific meaning.  To learn or recognize
+// these extended codes, we have to treat the pair of code words as a single 
+// command.  We sense Pioneer codes based on the address field, and use
+// special handling when we find a Pioneer address code.
+//
+class IRPNEC: public IRPSpaceLength<uint64_t>
+{
+public:
+    // code parameters
+    virtual uint32_t minRxGap() const { return 3400; }
+    virtual uint32_t txGap(IRTXState *state) const 
+        { return 108000 - state->txTime.read_us(); }
+        
+    // space length encoding parameters
+    virtual int tMark() const { return 560; }
+    virtual int tZero() const { return 560; }
+    virtual int tOne() const { return 1680; }
+    
+    // PWM period is 40kHz for Pioneer, 38kHz for everyone else
+    virtual float pwmPeriod(IRTXState *state) const 
+    { 
+        return state->protocolId == IRPRO_PIONEER ? 25.0e-6f : 26.31578947e-6f; 
+    }
+    
+    // for Pioneer, we have to send two codes if we have a shift field
+    virtual int txMinReps(IRTXState *state) const
+    {
+        if (state->protocolId == IRPRO_PIONEER 
+            && (state->cmdCode & 0xFFFF0000) != 0)
+            return 2;
+        else
+            return 1;
+    }
+    
+    // get the protocol to report for a given data packet bit count
+    virtual int necPro(int bits) const = 0;
+    
+    // close out a received bitstream
+    virtual void rxClose(IRRecvProIfc *receiver, bool ditto)
+    {
+        // Check the bit count.  If nbits() says we can accept 48 bits,
+        // accept 48 bits, otherwise only accept 32.
+        if (bit == 32 || (nbits() >= 48 && bit == 48))
+        {
+            uint64_t codeOut;
+            if (ditto)
+            {
+                // report 0 for dittos
+                codeOut = 0;
+            }
+            else
+            {
+                // Put the bytes in the right order.  The bits are LSB-first, but
+                // we want the bytes to be ordered within the uint32 as (high to
+                // low) A0 A1 C0 C1.
+                uint8_t c1 = uint8_t((code >> 24) & 0xff);
+                uint8_t c0 = uint8_t((code >> 16) & 0xff);
+                uint8_t a1 = uint8_t((code >> 8) & 0xff);
+                uint8_t a0 = uint8_t(code & 0xff);
+                codeOut = (uint64_t(a0) << 24) | (uint64_t(a1) << 16)
+                    | (uint64_t(c0) << 8) | uint64_t(c1);
+                    
+                // If it's a 48-bit code, add the additional 16 bits for E0 E1
+                // (the extended command code) at the low end.
+                if (bit == 48)
+                {
+                    // get the E1 and E0 bytes from the top end of the code                
+                    uint8_t e1 = uint8_t((code >> 40) & 0xff);
+                    uint8_t e0 = uint8_t((code >> 32) & 0xff);
+                    
+                    // insert them at the low end in E0 E1 order
+                    codeOut <<= 16;
+                    codeOut |= (uint64_t(e0) << 8) | uint64_t(e1);
+                }
+            }
+    
+            // report it
+            reportCode(receiver, necPro(bit), codeOut, bool3::null, ditto);
+        }
+    }
+
+    // convert a code to a bitstream for sending    
+    virtual void codeToBitstream(IRTXState *state)
+    {
+        if (state->protocolId == IRPRO_PIONEER)
+        {
+            // Check if we have an extended code
+            uint32_t c;
+            if ((state->cmdCode & 0xFFFF0000) != 0)
+            {
+                // Extended code.  These are transmitted as two codes in
+                // a row, one for the shift code in the high 16 bits, and
+                // one for the subcode in the low 16 bits.  Transmit the
+                // shift code on even reps and the subcode on odd reps.
+                if (state->rep == 0 || state->rep == 2)
+                {
+                    // even rep - use the shift code
+                    c = (state->cmdCode >> 16) & 0xFFFF;
+                    
+                    // wrap back to rep 0 on rep 2
+                    state->rep = 0;
+                }
+                else
+                {
+                    // odd rep - use the subcode
+                    c = state->cmdCode & 0xFFFF;
+                }
+            }
+            else
+            {
+                // it's a single-part code
+                c = state->cmdCode;
+            }
+            
+            // encode it in the 32-bit original NEC format with the address
+            // and command byte complemented
+            uint8_t a0 = uint8_t((c >> 8) & 0xff);
+            uint8_t a1 = uint8_t(~a0);
+            uint8_t c0 = uint8_t(c & 0xff);
+            uint8_t c1 = uint8_t(~c0);
+            state->bitstream = (uint64_t(c1) << 24) | (uint64_t(c0) << 16)
+                | (uint64_t(a1) << 8) | uint64_t(a0);
+            state->nbits = 32;
+            
+            // Pioneer *can't* use NEC dittos even if the caller thinks we
+            // should, because that breaks the shift-code model Pioneer uses
+            state->dittos = false;
+        }
+        else if (state->protocolId == IRPRO_NEC48)
+        {
+            // NEC 48-bit code.  We store the bytes in the universal 
+            // representation in order A0 A1 C0 C1 E0 E1.  Reverse this
+            // order for transmission.
+            uint64_t code = state->cmdCode;
+            uint8_t a0 = uint8_t((code >> 40) & 0xff);
+            uint8_t a1 = uint8_t((code >> 32) & 0xff);
+            uint8_t c0 = uint8_t((code >> 24) & 0xff);
+            uint8_t c1 = uint8_t((code >> 16)& 0xff);
+            uint8_t e0 = uint8_t((code >> 8) & 0xff);
+            uint8_t e1 = uint8_t((code) & 0xff);
+            state->bitstream = 
+                (uint64_t(e1) << 40) | (uint64_t(e0) << 32)
+                | (uint64_t(c1) << 24) | (uint64_t(c0) << 16)
+                | (uint64_t(a1) << 8) | uint64_t(a0);
+            state->nbits = 48; 
+        }
+        else
+        {
+            // NEC 32-bit code.  The universal representation stores
+            // the bytes in order A0 A1 C0 C1.  For transmission, we
+            // need to reverse this to C1 C0 A1 A0.
+            uint32_t code = uint32_t(state->cmdCode);
+            uint8_t a0 = uint8_t((code >> 24) & 0xff);
+            uint8_t a1 = uint8_t((code >> 16) & 0xff);
+            uint8_t c0 = uint8_t((code >> 8) & 0xff);
+            uint8_t c1 = uint8_t(code & 0xff);
+            state->bitstream = 
+                (uint64_t(c1) << 24) | (uint64_t(c0) << 16)
+                | (uint64_t(a1) << 8) | uint64_t(a0);
+            state->nbits = 32; 
+        }
+    }
+
+    // NEC uses a special "ditto" code for repeats.  The ditto consists
+    // of the normal header mark, half a header space, a regular data
+    // mark.  After this, a standard inter-message gap follows, and then
+    // we repeat the ditto as long as the button is held down.
+    virtual int txDittoStep(IRTXState *state)
+    {
+        // send the ditto
+        uint32_t t;
+        switch (state->step)
+        {
+        case 0:
+            // Ditto header mark
+            state->step++;
+            state->pin->write(pwmDutyCycle());
+            
+            // use the special ditto mark timing if it's different; 0 means
+            // that we use the same timing as the standard data frame header
+            return (t = tDittoMark()) != 0 ? t : tHeaderMark();
+            
+        case 1:
+            // Ditto header space
+            state->step++;
+            state->pin->write(0);
+            
+            // use the special ditto timing if it's different
+            return (t = tDittoSpace()) != 0 ? t : tHeaderSpace();
+            
+        case 2:
+            // Data section.  NEC-32X sends one data bit.  The others
+            // send no data bits, so go straight to the stop bit.
+            if (state->protocolId == IRPRO_NEC32X && state->bit == 0)
+                return txDataStep(state);
+                
+            // for others, fall through to the stop mark
+            state->step++;
+            // FALL THROUGH...
+            
+        case 3:
+            // stop mark
+            state->step++;
+            state->pin->write(pwmDutyCycle());
+            return tMark();
+            
+        case 4:
+            // send a gap
+            state->step++;
+            state->pin->write(0);
+            return 108000 - state->txTime.read_us();
+            
+        default:
+            // done
+            return txEnd(state);
+        }
+    }
+
+};
+
+// NEC-32, NEC-48, and Pioneer
+class IRPNEC_32_48: public IRPNEC
+{
+public:
+    IRPNEC_32_48()
+    {
+        pioneerPrvCode = 0;
+    }
+
+    // name and ID
+    virtual const char *name() const { return "NEC"; }
+    virtual int id() const { return IRPRO_NEC32; }
+    virtual int necPro(int bits) const 
+    { 
+        return bits == 48 ? IRPRO_NEC48 : IRPRO_NEC32; 
+    }
+    
+    // we encode several protocols
+    virtual bool isSenderFor(int pro) const
+    {
+        return pro == IRPRO_NEC32 || pro == IRPRO_NEC48 || pro == IRPRO_PIONEER;
+    }
+
+    // NEC-32 and NEC-48 use the same framing
+    virtual uint32_t tHeaderMark() const { return 9000; }
+    virtual uint32_t tHeaderSpace() const { return 4500; }
+    virtual uint32_t tDittoMark() const { return 9000; }
+    virtual uint32_t tDittoSpace() const { return 2250; }
+    virtual int nbits() const { return 48; }
+
+    // receiver format descriptor
+    // decode, check, and report a code value
+    virtual void rxClose(IRRecvProIfc *receiver, bool ditto)
+    {
+        // If we're in Pioneer mode, use special handling
+        if (isPioneerCode())
+        {
+            rxClosePioneer(receiver);
+            return;
+        }
+        
+        // The new code isn't a pioneer code, so if we had a Pioneer
+        // code stashed, it doesn't have a second half.  Report is as
+        // a standalone code.
+        if (pioneerPrvCode != 0)
+        {
+            reportPioneerFormat(receiver, pioneerPrvCode);
+            pioneerPrvCode = 0;
+        }
+
+        // use the generic NEC handling
+        IRPNEC::rxClose(receiver, ditto);
+    }
+    
+    virtual void rxIdle(IRRecvProIfc *receiver)
+    {
+        // if we have a stashed prior pioneer code, close it out, since
+        // no more codes are forthcoming
+        if (pioneerPrvCode != 0)
+        {
+            reportPioneerFormat(receiver, pioneerPrvCode);
+            pioneerPrvCode = 0;
+        }
+    }
+
+    // close out a Pioneer code
+    virtual void rxClosePioneer(IRRecvProIfc *receiver)
+    {
+        // Check to see if we have a valid previous code and/or
+        // a valid new code.
+        if (pioneerPrvCode != 0)
+        {
+            // We have a stashed Pioneer code plus the new one.  If
+            // they're different, we must have an extended code with
+            // a "shift" prefix.
+            if (pioneerPrvCode != code)
+            {
+                // distinct code - it's an extended code with a shift
+                reportPioneerFormat(receiver, pioneerPrvCode, code);
+            }
+            else
+            {
+                // same code - it's just a repeat, so report it twice
+                reportPioneerFormat(receiver, code);
+                reportPioneerFormat(receiver, code);
+            }
+            
+            // we've now consumed the previous code
+            pioneerPrvCode = 0;
+        }
+        else
+        {
+            // There's no stashed code.  Don't report the new one yet,
+            // since it might be a "shift" prefix.  Stash it until we
+            // find out if another code follows.
+            pioneerPrvCode = code;
+            bit = 0;
+            code = 0;
+        }
+    }
+    
+    // determine if we have Pioneer address
+    bool isPioneerCode()
+    {
+        // pull out the command and address fields fields
+        uint8_t c1 = uint8_t((code >> 24) & 0xff);
+        uint8_t c0 = uint8_t((code >> 16) & 0xff);
+        uint8_t a1 = uint8_t((code >> 8) & 0xff);
+        uint8_t a0 = uint8_t(code & 0xff);
+            
+        // Pioneer uses device codes A0..AF, with A1 complemented, and
+        // uses only the 32-bit code format.  Pioneer also always uses
+        // a complemented C0-C1 pair.
+        return bit == 32 
+            && (a0 >= 0xA0 && a0 <= 0xAF) 
+            && a0 == uint8_t(~a1)
+            && c0 == uint8_t(~c1);        
+    }
+
+    // Report a code in Pioneer format.  This takes the first address
+    // byte and combines it with the first command byte to form a 16-bit
+    // code.  Pioneer writes codes in this format because the second
+    // address and command bytes are always the complements of the first,
+    // so they contain no information, so it makes the codes more readable
+    // for human consumption to drop the redundant bits.  
+    void reportPioneerFormat(IRRecvProIfc *receiver, uint32_t code)
+    {
+        uint8_t a0 = uint8_t(code & 0xff);
+        uint8_t c0 = uint8_t((code >> 16) & 0xff);
+        reportCode(receiver, IRPRO_PIONEER, (uint64_t(a0) << 8) | c0, 
+            bool3::null, bool3::null);
+    }
+    
+    // Report an extended two-part code in Pioneer format.  code1 is the
+    // first code received (the "shift" prefix code), and code2 is the
+    // second (the key-specific subcode).  We'll convert each code to
+    // the standard Pioneer 16-bit format (<address>:<key>), then pack
+    // the two into a 32-bit int with the shift code in the high half.
+    void reportPioneerFormat(IRRecvProIfc *receiver, uint32_t code1, uint32_t code2)
+    {
+        uint8_t a1 = code1 & 0xff;
+        uint8_t c1 = (code1 >> 16) & 0xff;
+        uint8_t a2 = code2 & 0xff;
+        uint8_t c2 = (code2 >> 16) & 0xff;
+        reportCode(
+            receiver, IRPRO_PIONEER,
+            (uint64_t(a1) << 24) | (uint64_t(c1) << 16) 
+            | (uint64_t(a2) << 8) | c2,
+             bool3::null, bool3::null);
+    }
+   
+    // The previous code value.  This is used in decoding Pioneer
+    // codes, since some keys in the Pioneer scheme send a "shift"
+    // prefix code plus a subcode.
+    uint32_t pioneerPrvCode;
+};
+
+// NEC-32x.  This is a minor variation on the standard NEC-32 protocol,
+// with a slightly different header signature and a different ditto
+// pattern.
+class IRPNEC_32x: public IRPNEC
+{
+public:
+    virtual int id() const { return IRPRO_NEC32X; }
+    virtual const char *name() const { return "NEC32x"; }
+    virtual int necPro(int bits) const { return IRPRO_NEC32X; }
+    
+    virtual int nbits() const { return 32; }
+    virtual uint32_t tHeaderMark() const { return 4500; }
+    virtual uint32_t tHeaderSpace() const { return 4500; }
+
+    // Close out the code.  NEC-32x has an unusual variation of the
+    // NEC ditto: it uses the same header as a regular code, but only
+    // has a 1-bit payload.
+    virtual void rxClose(IRRecvProIfc *receiver, bool ditto)
+    {
+        if (bit == 1)
+            reportCode(receiver, IRPRO_NEC32X, code, bool3::null, true);
+        else
+            IRPNEC::rxClose(receiver, ditto);
+    }
+    
+};
+
+// -----------------------------------------------------------------------
+// 
+// Kaseikyo protocol handler.  Like NEC, this is a quasi industry standard
+// used by many companies.  Unlike NEC, it seems to be used consistently
+// by just about everyone.  There are only two main variations: a 48-bit
+// coding and a 56-bit coding.
+//
+// For all versions, the first 16 bits in serial order provide an OEM ID.
+// We use this to report manufacturer-specific protocol IDs, even though
+// the low-level coding is the same for all of them.  Differentiating
+// by manufacturer is mostly for cosmetic reasons, so that human users
+// looking at learned codes or looking for codes to program will see
+// names matching their equipment.  In some cases it's also useful for
+// interpreting the internal data fields within the bit string; some
+// OEMs use checksums or other fields that clients might want to 
+// interpret.
+//
+class IRPKaseikyo: public IRPSpaceLength<uint64_t>
+{
+public:
+    IRPKaseikyo() { }
+
+    // name and ID
+    virtual const char *name() const { return "Kaseikyo"; }
+    virtual int id() const { return IRPRO_KASEIKYO48; }
+    
+    // we handle all of the OEM-specific protocols
+    virtual bool isSenderFor(int id) const
+    {
+        switch (id)
+        {
+        case IRPRO_KASEIKYO48:
+        case IRPRO_KASEIKYO56:
+        case IRPRO_DENONK:
+        case IRPRO_FUJITSU48:
+        case IRPRO_FUJITSU56:
+        case IRPRO_JVC48:
+        case IRPRO_JVC56:
+        case IRPRO_MITSUBISHIK:
+        case IRPRO_PANASONIC48:
+        case IRPRO_PANASONIC56:
+        case IRPRO_SHARPK:
+        case IRPRO_TEACK:
+            return true;
+            
+        default:
+            return false;
+        }
+    }
+    
+    // code boundary parameters
+    virtual uint32_t tHeaderMark() const { return 3500; }
+    virtual uint32_t tHeaderSpace() const { return 1750; }
+    virtual uint32_t minRxGap() const { return 2500; }
+    virtual uint32_t txGap(IRTXState *state) const { return 173000; }
+    
+    // space length coding
+    virtual int nbits() const { return 56; }
+    virtual int tMark() const { return 420; }
+    virtual int tZero() const { return 420; }
+    virtual int tOne() const { return 1300; }
+    
+    // protocol/OEM mappings
+    struct OEMMap
+    {
+        uint16_t oem;
+        uint8_t pro;
+        uint8_t bits;
+    };
+    static const OEMMap oemMap[];
+    static const int nOemMap;
+
+    // close code reception
+    virtual void rxClose(IRRecvProIfc *receiver, bool ditto)
+    {
+        // it must be a 48-bit or 56-bit code
+        if (bit != 48 && bit != 56)
+            return;
+            
+        // pull out the OEM code in the low 16 bits
+        int oem = int(code & 0xFFFF);
+        
+        // Find the protocol based on the OEM
+        uint8_t pro = bit == 48 ? IRPRO_KASEIKYO48 : IRPRO_KASEIKYO56;
+        for (int i = 0 ; i < nOemMap ; ++i)
+        {
+            if (oemMap[i].oem == oem && oemMap[i].bits == bit)
+            {
+                pro = oemMap[i].pro;
+                break;
+            }
+        }
+                
+        // report the code
+        reportCode(receiver, pro, code, bool3::null, bool3::null);
+    }
+
+    virtual void codeToBitstream(IRTXState *state)
+    {
+        // presume no OEM and a 48-bit version of the protocol
+        uint16_t oem = 0;
+        state->nbits = 48;
+
+        // find the protocol variation in the table
+        for (int i = 0 ; i < nOemMap ; ++i)
+        {
+            if (state->protocolId == oemMap[i].pro)
+            {
+                state->nbits = oemMap[i].bits;
+                oem = oemMap[i].oem;
+                break;
+            }
+        }
+        
+        // if we found a non-zero OEM code, and it doesn't match the
+        // low-order 16 data bits, replace the OEM code in the data
+        uint64_t code = state->cmdCode;
+        if (oem != 0 && int(code & 0xFFFF) != oem)
+            code = (code & ~0xFFFFLL) | oem;
+        
+        // store the code (with possibly updated OEM coding)
+        state->bitstream = code;
+    }
+};
+
+// -----------------------------------------------------------------------
+// 
+// Philips RC5 protocol handler.  This (along with RC6) is a quasi industry 
+// standard among European CE companies, so this protocol gives us 
+// compatibility with many devices from companies besides Philips.
+//
+// RC5 is a 14-bit Manchester-coded protocol.  '1' bits are encoded as
+// low->high transitions.
+//
+// The 14 bits of the command are internally structured as follows:
+//
+//   S F T AAAAA CCCCCC
+//
+// S = "start bit".  Always 1.  We omit this from the reported code
+//     since it's always the same.
+//
+// F = "field bit", which selects a default (1) or extended (0) set of 
+//     commands.  Note the reverse sensing, with '1' being the default.
+//     This is because this position was a second stop bit in the original
+//     code, always set to '1'.  When Philips repurposed the bit as the
+//     field code in a later version of the protocol, they used '1' as
+//     the default for compatibility with older devices.  We pass the bit
+//     through as-is to the universal representation, so be aware that 
+//     you might have to flip it to match some published code tables.
+//
+// T = "toggle bit".  This changes on each successive key press, to allow
+//     the receiver to distinguish pressing the same key twice from auto-
+//     repeat due to holding down the key.
+//
+// A = "address", most significant bit first; specifies which type of
+//     device the command is for (e.g., "TV", "VCR", etc).  The meanings
+//     of the possible numeric values are arbitrarily assigned by Philips; 
+//     you can Philips; you can find tables online (e.g., at Wikipedia)
+//     with the published assignments.
+//
+// C = "command", most significant bit first; the command code.  The
+//     meaning of the command code varies according to the type of device 
+//     in the address field.  Published tables with the standard codes can
+//     be found online.
+//
+// Note that this protocol doesn't have a "header" per se; it just starts
+// in directly with the first bit.  As soon as we see a long enough gap,
+// we're ready for the start bit.
+//
+class IRPRC5: public IRPManchester<uint16_t>
+{
+public:
+    IRPRC5() { }
+    
+    // name and ID
+    virtual const char *name() const { return "Philips RC5"; }
+    virtual int id() const { return IRPRO_RC5; }
+    
+    // code parameters
+    virtual float pwmPeriod(IRTXState *state) const { return 27.7777778e-6; } // 36kHz
+    virtual uint32_t minRxGap() const { return 3600; }
+    virtual uint32_t txGap(IRTXState *state) const { return 114000; }
+    virtual bool lsbFirst() const { return false; }
+    virtual int nbits() const { return 14; }
+    
+    // RC5 has no header; the start bit immediately follows the gap
+    virtual uint32_t tHeaderMark() const { return 0; }
+    virtual uint32_t tHeaderSpace() const { return 0; }
+    
+    // Manchester coding parameters
+    virtual int tHalfBit(int bit) const { return 1778/2; }
+    
+    // After the gap, the start of the next mark is in the middle
+    // of the start bit.  A '1' start bit always follows the gap,
+    // and a '1' bit is represented by a space-to-mark transition,
+    // so the end of the gap is the middle of the start bit.
+    virtual void rxReset()
+    {
+        IRPManchester<uint16_t>::rxReset();
+        halfBitPos = 1;
+    }
+
+    virtual void codeToBitstream(IRTXState *state)
+    {
+        // add the start bit and toggle bit to the command code
+        state->nbits = nbits();
+        state->bitstream = (state->cmdCode & DataMask) | StartMask;
+        if (state->toggle)
+            state->bitstream |= ToggleMask;
+    }
+    
+    // report the code 
+    virtual void rxClose(IRRecvProIfc *receiver, bool ditto)
+    {
+        // make sure we have the full code
+        if (bit == 14)
+        {
+            // Pull out the toggle bit to report separately, and zero it
+            // out in the code word, so that a given key always reports
+            // the same code value.  Also zero out the start bit, since
+            // it's really just structural.
+            reportCode(
+                receiver, id(),
+                code & ~(StartMask | ToggleMask),
+                (code & ToggleMask) != 0, bool3::null);
+        }
+    }
+
+    // masks for the internal fields
+    static const int CmdMask = 0x3F;
+    static const int AddrMask = 0x1F << 6;
+    static const int ToggleMask = 1 << 11;
+    static const int FieldMask = 1 << 12;
+    static const int StartMask = 1 << 13;
+    static const int DataMask = FieldMask | AddrMask | CmdMask;
+};
+
+// -----------------------------------------------------------------------
+// 
+// RC6 protocol handler.  This (along with RC5) is a quasi industry 
+// standard among European CE companies, so this protocol gives us 
+// compatibility with many devices from companies besides Philips.
+//
+// RC6 is a 21-bit Manchester-coded protocol.  '1' bits are coded as
+// High->Low transitions.  The bits are nominally structured into
+// fields as follows:
+//
+//   S FFF T AAAAAAAA CCCCCCCC
+//
+// The fields are:
+//
+//   S = start bit; always 1.  We omit this from the reported value since
+//       it's always the same.
+//
+//   F = "field".  These bits are used to select different command sets,
+//       so they're effectively three additional bits (added as the three
+//       most significant bits) for the command code.
+//
+//   A = "address", specifying the type of device the command is for.
+//       This has the same meanings as the address field in RC5.
+//
+//   C = "command".  The command code, specific to the device type
+//       in the address field.
+//
+// As with all protocols, we don't reproduce the internal field structure
+// in the decoded command value; we simply pack all of the bits into a
+// 18-bit integer, in the order shown above, field bits at the high end.
+// (We omit the start bit, since it's a fixed element that's more properly
+// part of the protocol than part of the code.)
+//
+// Note that this protocol contains an odd exception to normal Manchester 
+// coding for the "toggle" bit.  This bit has a period 2X that of the other
+// bits.
+//
+class IRPRC6: public IRPManchester<uint32_t>
+{
+public:
+    IRPRC6() { }
+
+    // name and ID
+    virtual const char *name() const { return "Philips RC6"; }
+    virtual int id() const { return IRPRO_RC6; }
+
+    // code parameters
+    virtual float pwmPeriod(IRTXState *state) const { return 27.7777778e-6; } // 36kHz
+    virtual uint32_t tHeaderMark() const { return 2695; }
+    virtual uint32_t tHeaderSpace() const { return 895; }
+    virtual uint32_t minRxGap() const { return 2650; }
+    virtual uint32_t txGap(IRTXState *state) const { return 107000; }
+    virtual bool lsbFirst() const { return false; }
+    virtual int nbits() const { return 21; }
+    
+    // Manchester coding parameters
+    virtual int spaceToMarkBit() const { return 0; }
+
+    // RC6 has the weird exception to the bit timing in the Toggle bit,
+    // which is twice as long as the other bits.  The toggle bit is the
+    // 5th bit (bit==4).
+    virtual int tHalfBit(int bit) const { return bit == 4 ? 895 : 895/2; }
+
+    // create the bit stream for the command code
+    virtual void codeToBitstream(IRTXState *state)
+    {
+        // add the start bit and toggle bit to the command code
+        state->nbits = nbits();
+        state->bitstream = (state->cmdCode & DataMask) | StartMask;
+        if (state->toggle)
+            state->bitstream |= ToggleMask;
+    }
+    
+    // report the code 
+    virtual void rxClose(IRRecvProIfc *receiver, bool ditto)
+    {
+        // make sure we have the full code
+        if (bit == nbits())
+        {
+            // Pull out the toggle bit to report separately, and zero it
+            // out in the code word, so that a given key always reports
+            // the same code value.  Also clear the start bit, since it's
+            // just structural.
+            reportCode(
+                receiver, id(),
+                code & ~(ToggleMask | StartMask), 
+                (code & ToggleMask) != 0, bool3::null);
+        }
+    }
+
+    // field masks
+    static const int CmdMask =   0xFF;
+    static const int AddrMask =  0xFF << 8;
+    static const int ToggleMask = 1 << 16;
+    static const int FieldMask = 0x07 << 17;
+    static const int StartMask = 1 << 20;
+    static const int DataMask =  FieldMask | AddrMask | CmdMask;
+};
+
+// -----------------------------------------------------------------------
+//
+// Ortek MCE remote.  This device uses Manchester coding, with either 16
+// or 17 bits, depending on which keys are pressed.  The low-order 5 bits
+// of either code are the device code.  The interpretation of the rest of
+// the bits depends on the device code.  Bits are sent least-significant
+// first (little-endian) for interpretation as integer values.
+//
+// Device code = 0x14 = Mouse.  This is a 16-bit code, with the fields
+// as follows (low bit first):
+//
+//  DDDDD L R MMMMM CCCC
+//
+//  D = device code (common field for all codes)
+//  L = left-click (1=pressed, 0=not pressed)
+//  R = right-click (1=pressed, 0=not pressed)
+//  M = mouse movement direction.  This type of device is a mouse stick
+//      rather than a mouse, so it gives only the direction of travel rather
+//      than X/Y motion in pixels.  There are 15 increments of motion numbered
+//      0 to 15, starting with 0 at North (straight up), going clockwise:
+//        0 = N
+//        1 = NNE
+//        2 = NE
+//        3 = ENE
+//        4 = E
+//        5 = ESE
+//        6 = SE
+//        7 = SSE
+//        8 = S
+//        9 = SSW
+//        10 = SW
+//        11 = WSW
+//        12 = W
+//        13 = WNW
+//        14 = NW
+//        15 = NNW
+//     The MMMMM field contains 0x10 | the value above when a mouse motion
+//     direction is pressed, so the codes will be 0x10 (N), 0x11 (NNE), ...,
+//     0x1F (NNW).  These are shifted left by two in the reported function
+//     code, so you'll actually see 0x40 ... 0x7C.
+//  C = checksum
+//
+// There's no equivalent of the "position" code (see device 0x15 below) for
+// the mouse commands, so there's no coding for auto-repeat per se.  The
+// remote does let the receiver when the last key is released, though, by
+// sending one code word with the L, R, and M bits all set to zero; this
+// apparently signifies "key up".  One of these is always sent after the 
+// last key is released, but it's not sent between auto-repeated codes,
+// so if you hold a key down you'll see a sequence of repeating codes
+// for that key (or key combination), followed by one "key up" code when
+// you release the last key.
+//
+// Receivers who interpret these codes will probably want to separate the
+// L, R, and M bits and treat them separately, rather than treating any given
+// combination as a discrete command.  The reason is that the L and R bits
+// can combine with the mouse movement field when a left-click or right-click
+// button is held down while the movement keys are pressed.  This can be used
+// to perform click-and-drag operations on a GUI.
+//
+// Device code = 0x15 = MCE buttons.  This is a 17-bit code, with the
+// fields as follows (low bit first):
+//
+//  DDDDD PP FFFFFF CCCC
+//
+//  D = device code (common field for all codes)
+//  P = "position code", for sensing repeats: 00=first, 01=middle, 10=last
+//  F = function (key code)
+//  C = checksum
+//
+// The checksum is the 3 + the total number of '1' bits in the other fields
+// combined.
+//
+// We report these codes in our own 16-bit format, with D in the high byte,
+// and the function code in the low byte.  For the mouse commands (device 0x14),
+// the low byte is (high bit to low bit) 0MMMMMRL.  For the MCE buttons, the
+// low byte is the function code (F).  We drop the position code for uniformity
+// with other protocols and instead report a synthetic toggle bit, which we
+// flip whenever we see a new transmission as signified by P=0.  We don't do
+// anything with the toggle bit on mouse commands.
+//
+class IRPOrtekMCE: public IRPManchester<uint32_t>
+{
+public:
+    IRPOrtekMCE() 
+    {
+        toggle = 0;
+    }
+
+    // name and ID
+    virtual const char *name() const { return "OrtekMCE"; }
+    virtual int id() const { return IRPRO_ORTEKMCE; }
+    
+    // code parameters
+    virtual float pwmPeriod(IRTXState *state) const { return 25.906736e-6; } // 38.6kHz
+    virtual uint32_t tHeaderMark() const { return 4*480; }
+    virtual uint32_t tHeaderSpace() const { return 1*480; }
+    virtual bool headerSpaceToData() const { return true; }
+    virtual uint32_t minRxGap() const { return 32000; }
+    virtual uint32_t txGap(IRTXState *state) const { return 40000; }
+    
+    // We always require a final rep with position code 2, so
+    // ask for a minimum of 3 reps (0, 1, 2).
+    virtual int txMinReps(IRTXState *) const { return 3; }
+    
+    // Manchester coding parameters
+    virtual int nbits() const { return 17; }
+    virtual int spaceToMarkBit() const { return 1; }
+    virtual int tHalfBit(int bit) const { return 480; }
+    
+    // encode the bitstream for a given code
+    virtual void codeToBitstream(IRTXState *state)
+    {
+        // Set the repeat count.  If we're on any repeat > 0 and 
+        // the key is still pressed, reset the repeat counter to 1.
+        // All repeats are "1" as long as the key is down.  As soon
+        // as the key is up, advance directly to "2", even if there
+        // was never a "1".  Our txMinRep() count is 2, so this will
+        // ensure that we always transmit that last position 2 code,
+        // as required by the protocol.
+        if (state->rep > 0)
+            state->rep = state->pressed ? 1 : 2;
+
+        // Function field and checksum bit position - we'll fill
+        // these in momentarily according to the device type.
+        uint32_t f;
+        int c_shift;
+
+        // check the product code to determine the encoding
+        uint32_t cmdcode = uint32_t(state->cmdCode);
+        int dev = (int(cmdcode) >> 8) & 0xff;
+        if (dev == 0x14)
+        {
+            // Mouse function:  DDDDD L R MMMMM CCCC
+            // There's no position field in the mouse messages, but
+            // we always have to send a final message with all bits
+            // zeroed.  Do this when our rep count is 2, indicating
+            // that this is the final close-out rep.
+            if (state->rep == 2)
+                f = 0;
+            else
+                f = cmdcode & 0xff;
+                
+            // the checksum starts at the 12th bit
+            c_shift = 12;
+        }
+        else if (dev == 0x15)
+        {
+            // MCE button:  DDDDD PP FFFFFF CCCC
+            
+            // Set the position field to the rep counter
+            int p = state->rep;
+            
+            // fill in the function fields: PP FFFFFF
+            f = (p) | ((cmdcode & 0x3F) << 2);
+            
+            // the checksum starts at the 13th bit
+            c_shift = 13;
+        }
+        else
+        {
+            // unknown device code - just transmit the low byte as given
+            f = cmdcode & 0xff;
+            c_shift = 13;
+        }
+        
+        // construct the bit vector with the device code in the low 5
+        // bits and code in the next 7 or 8 bits
+        uint32_t bitvec = dev | (f << 5);
+            
+        // figure the checksum: it's the number of '1' bits in
+        // the rest of the fields, plus 3
+        uint32_t checksum = 3;
+        for (uint32_t v = bitvec ; v != 0 ; checksum += (v & 1), v >>= 1) ;
+        
+        // construct the bit stream
+        state->bitstream = bitvec | (checksum << c_shift);
+        state->nbits = c_shift + 4;
+    }
+        
+    // report the code value    
+    virtual void rxClose(IRRecvProIfc *receiver, bool ditto)
+    {
+        // common field masks
+        const uint32_t D_mask = 0x001F;   // lowest 5 bits = device code
+        
+        // pull out the common fields
+        uint32_t d = code & D_mask;
+        
+        // assume no post-toggle
+        bool postToggle = false;
+        
+        // check for the different code types, and pull out the fields
+        uint32_t f, c, checkedBits;
+        if (bit == 16 && d == 0x14)
+        {
+            // field masks for the mouse codes
+            const int      F_shift = 5;
+            const uint32_t F_mask = 0x7f << F_shift;  // next 7 bits
+            const int      C_shift = 12;
+            const uint32_t C_mask = 0x3F << C_shift;  // top 4 bits
+
+            // separate the fields
+            f = (code & F_mask) >> F_shift;
+            c = (code & C_mask) >> C_shift;
+            checkedBits = code & ~C_mask;
+            
+            // if the F bits are all zero, take it as a "key up" sequence,
+            // so flip the toggle bit next time
+            if (f == 0)
+                postToggle = true;
+        }
+        else if (bit == 17 && d == 0x15)
+        {
+            // 17 bits with device code 0x15 = MCE keyboard commands
+            
+            // field masks for the keyboard codes
+            const int      P_shift = 5;
+            const uint32_t P_mask = 0x0003 << P_shift;  // next 2 bits
+            const int      F_shift = 7;
+            const uint32_t F_mask = 0x3F << F_shift;    // next 6 bits
+            const int      C_shift = 13;
+            const uint32_t C_mask = 0x0F << C_shift;    // top 4 bits
+
+            // separate the fields
+            uint32_t p = (code & P_mask) >> P_shift;
+            f = (code & F_mask) >> F_shift;
+            c = (code & C_mask) >> C_shift;
+            checkedBits = code & ~C_mask;
+
+            /// validate the position code - 0,1,2 are valid, 3 is invalid
+            if (p == (0x03 << P_shift))
+                return;
+                
+            // flip the toggle bit if this is the first frame in a group
+            // as signified by P=0
+            if (p == 0)
+                toggle ^= 1;                
+        }
+        else
+        {
+            // invalid bit length or device code - reject the code
+            return;
+        }
+            
+        // count the '1' bits in the other fields to get the checksum value
+        int ones = 0;
+        for ( ; checkedBits != 0 ; checkedBits >>= 1)
+            ones += (checkedBits & 1);
+            
+        // check the checksum
+        if (c != ones + 3)
+            return;
+                
+        // rearrange the code into our canonical format and report it
+        // along with the synthetic toggle
+        reportCode(receiver, id(), (d << 8) | f, toggle, bool3::null);
+        
+        // flip the toggle for next time, if desired
+        if (postToggle)
+            toggle ^= 1;
+    }
+    
+    // synthetic toggle bit
+    uint8_t toggle : 1;
+};
+
+// -----------------------------------------------------------------------
+// 
+// Sony protocol handler.  Sony uses mark-length encoding, with
+// 8, 12, 15, and 20 bit code words.  We use a common receiver
+// base class that determines how many bits are in the code from
+// the input itself, plus separate base classes for each bit size.
+// The common receiver class reports the appropriate sub-protocol
+// ID for the bit size, so that the appropriate sender class is
+// used if we want to transmit the captured code.
+//
+class IRPSony: public IRPMarkLength<uint32_t>
+{
+public:
+    IRPSony() { }
+
+    // Name and ID.  We handle all of the Sony bit size variations,
+    // so we use IRPRO_NONE as our nominal ID, but set the actual code
+    // on a successful receive based on the actual bit size.  We
+    // transmit any of the Sony codes.
+    virtual const char *name() const { return "Sony"; }
+    virtual int id() const { return IRPRO_NONE; }
+    virtual bool isSenderFor(int protocolId) const
+    {
+        return protocolId == IRPRO_SONY8
+            || protocolId == IRPRO_SONY12
+            || protocolId == IRPRO_SONY15
+            || protocolId == IRPRO_SONY20;
+    }
+    
+    // code boundary parameters
+    virtual uint32_t tHeaderMark() const { return 2400; }
+    virtual uint32_t tHeaderSpace() const { return 600; }
+    virtual uint32_t minRxGap() const { return 5000; }
+    virtual uint32_t txGap(IRTXState *state) const { return 45000; }
+    
+    // mark-length coding parameters
+    virtual int nbits() const { return 20; }  // maximum - can also be 8, 12, or 15
+    virtual int tSpace() const { return 600; }
+    virtual int tZero() const { return 600; }
+    virtual int tOne() const { return 1200; }
+    
+    // Sony requires at least 3 sends per key press
+    virtual int txMinReps(IRTXState *state) const { return 3; }
+
+    // set up the bitstream for a code value    
+    virtual void codeToBitstream(IRTXState *state)
+    {
+        // store the code, and set the bit counter according to
+        // the Sony protocol subtype
+        state->bitstream = state->cmdCode;
+        switch (state->protocolId)
+        {
+        case IRPRO_SONY8:
+            state->nbits = 8;
+            break;
+            
+        case IRPRO_SONY12:
+            state->nbits = 12;
+            break;
+            
+        case IRPRO_SONY15:
+            state->nbits = 15;
+            break;
+            
+        case IRPRO_SONY20:
+        default:
+            state->nbits = 20;
+            break;
+        }
+    }
+    
+    // report a code value
+    virtual void rxClose(IRRecvProIfc *receiver, bool ditto)
+    {
+        // we have a valid code if we have 8, 12, 15, or 20 bits
+        switch (bit)
+        {
+        case 8:
+            reportCode(receiver, IRPRO_SONY8, code, bool3::null, bool3::null);
+            break;
+            
+        case 12:
+            reportCode(receiver, IRPRO_SONY12, code, bool3::null, bool3::null);
+            break;
+            
+        case 15:
+            reportCode(receiver, IRPRO_SONY15, code, bool3::null, bool3::null);
+            break;
+            
+        case 20:
+            reportCode(receiver, IRPRO_SONY20, code, bool3::null, bool3::null);
+            break;
+        }
+    }
+};
+
+// -----------------------------------------------------------------------
+// 
+// Denon protocol handler.  Denon uses a 15-bit space-length coding, but 
+// has two unusual features.  First, it has no header; it just starts
+// right off with the data bits.  Second, every code is transmitted a
+// second time, starting 65ms after the start of the first iteration,
+// with the "F" and "S" fields inverted:
+//
+//   DDDDD FFFFFFFF SS
+//
+//   D = device code
+//   F = function code (key)
+//   S = sequence; 0 for the first half of the pair, 1 for the second half
+//
+// The first half-code is transmitted with the actual function code and 
+// SS=00; the second half uses ~F (1's complement of the function code)
+// and SS=11.
+//
+// Many learning remotes get this wrong, only learning one or the other
+// half.  That's understandable, since learning remotes often only collect
+// the raw bit codes and thus wouldn't be able to detect the multi-code
+// structure.  It's a little less forgiveable that some pre-programmed
+// universal remotes, such as the Logitech Harmony series, also miss the
+// half-code nuance.  To be robust against errant remotes, we'll accept
+// and report lone half-codes, and we'll invert the F bits if the S bits 
+// indicate that a second half was learned, to ensure that the client sees
+// the correct version of the codes.  We'll also internally track the
+// pairs so that, when we *do* see a properly formed inverted pair, we'll
+// only report one code out of the pair to the client.
+//
+class IRPDenon: public IRPSpaceLength<uint16_t>
+{
+public:
+    IRPDenon() 
+    {
+        prvCode = 0;
+    }
+
+    // name and ID
+    virtual const char *name() const { return "Denon"; }
+    virtual int id() const { return IRPRO_DENON; }
+    
+    // Code parameters.  The Denon protocol has no header; it just
+    // jumps right in with the first bit.
+    virtual uint32_t tHeaderMark() const { return 0; }
+    virtual uint32_t tHeaderSpace() const { return 0; }
+    virtual uint32_t minRxGap() const { return 30000; }
+    
+    // use a short gap between paired complemented codes, and a longer
+    // gap between repeats
+    virtual uint32_t txGap(IRTXState *state) const { return 165000; }
+    virtual uint32_t txPostGap(IRTXState *state) const 
+    {
+        if (state->rep == 0)
+        {
+            // Even rep - this is the gap between the two halves of a
+            // complemented pair.  The next code starts 65ms from the
+            // *start* of the last code, so the gap is 65ms minus the
+            // transmission time for the last code.
+            return 65000 - state->txTime.read_us();
+        }
+        else
+        {
+            // odd rep - use the normal long gap
+            return 165000; 
+        }
+    }
+    
+    // on idle, clear the previous code
+    virtual void rxIdle(IRRecvProIfc *receiver)
+    {
+        prvCode = 0;
+    }
+    
+    // space length coding
+    virtual int nbits() const { return 15; }
+    virtual int tMark() const { return 264; }
+    virtual int tZero() const { return 792; }
+    virtual int tOne() const { return 1848; }
+    
+    // handle a space
+    virtual bool rxSpace(IRRecvProIfc *receiver, uint32_t t)
+    {
+        // If this space is longer than the standard space between
+        // adjacent codes, clear out any previous first-half code 
+        // we've stored.  Complementary pairs have to be transmitted
+        // with the standard inter-code gap between them.  Anything
+        // longer must be separate key presses.
+        if (t > 65000)
+            prvCode = 0;
+
+        // do the normal work
+        return IRPSpaceLength<uint16_t>::rxSpace(receiver, t);
+    }
+    
+    // always send twice, once for the base version, once for the
+    // inverted follow-up version
+    virtual int txMinReps(IRTXState *state) const { return 2; }
+    
+    // encode the bitstream
+    virtual void codeToBitstream(IRTXState *state)
+    {
+        // If we're on an even repetition, just send the code
+        // exactly as given.  If we're on an odd repetition,
+        // send the inverted F and S fields.
+        state->nbits = 15;
+        if (state->rep == 0 || state->rep == 2)
+        {
+            // even rep - send the base version
+            state->bitstream = state->cmdCode & (D_mask | F_mask);
+            
+            // If we're on rep 2, switch back to rep 0.  This will
+            // combine with our minimum rep count of 2 to ensure that
+            // we always transmit an even number of copies.  Note that
+            // this loop terminates: when the key is up after we finish
+            // rep 1, the rep counter will advance to 2 and we'll stop.
+            // We'll only reset the rep counter here if we actually
+            // enter rep 2, which means the key is still down at that
+            // point, which means that we'll have to go at least another
+            // two iterations.
+            state->rep = 0;
+        }
+        else
+        {
+            // odd rep - invert the F field, and use all 1's in the S field
+            uint32_t d = state->cmdCode & D_mask;
+            uint32_t f = (~state->cmdCode) & F_mask;
+            state->bitstream = d | f | S_mask;
+        }
+    }
+            
+    // report the code
+    virtual void rxClose(IRRecvProIfc *receiver, bool ditto)
+    {
+        // If this code matches the inverted prior code, we have
+        // a matching pair.  Otherwise, hang onto this code until
+        // next time.
+        if (bit == 15)
+        {
+            // get the current and previous code's subfields
+            uint16_t curD = code & D_mask;
+            uint16_t curF = code & F_mask;
+            uint16_t curS = code & S_mask;
+            uint16_t prvD = prvCode & D_mask;
+            uint16_t prvF = prvCode & F_mask;
+            uint16_t prvS = prvCode & S_mask;
+            
+            // check to see if the current FS fields match the inverted
+            // prior FS fields, to make up a complementary pair as required
+            // by the protocol
+            if (curD == prvD && curF == (~prvF & F_mask) && curS == (~prvS & S_mask))
+            {
+                // The current code is the second half of the first code.
+                // Don't report the current code, since it's just an 
+                // error-checking copy of the previous code, which we already
+                // reported.  We've now seen both halves, so clear the 
+                // previous code.
+                prvCode = 0;
+            }
+            else
+            {
+                // This isn't the second half of an complementary pair, so
+                // report it as a separate code.  If the 'S' bits are 1's
+                // in this code, it's the second half of the pair, so invert
+                // the F and S fields to get the original code.  It might
+                // have been sent by a learning remote or universal remote
+                // that only captured the second half field.
+                if (curS == S_mask)
+                {
+                    // The S bits are 1's, so it's a second-half code.  We
+                    // don't have a matching first-half code, so either the
+                    // first half was lost, or it was never transmitted.  In
+                    // either case, reconstruct the original first-half code
+                    // by inverting the F bits and clearing the S bits.
+                    reportCode(receiver, id(), curD | (~curF & F_mask),
+                        bool3::null, bool3::null);
+                    
+                    // Forget any previous code.  This is a second-half
+                    // code, so if we do get a first-half code after this,
+                    // it's a brand new key press or repetition.
+                    prvCode = 0;
+                }
+                else if (curS == 0)
+                {
+                    // The S bits are 0, so it's a first-half code.  Report
+                    // the code, and save it for next time, so that we can
+                    // check the next code to see if it's the second half.
+                    reportCode(receiver, id(), code, bool3::null, bool3::null);
+                    prvCode = code;
+                }
+                else
+                {
+                    // The S bits are invalid, so this isn't a valid code.
+                    // Clear out any previous code and reject this one.
+                    prvCode = 0;
+                }
+            }
+        }
+        else
+        {
+            // we seem to have an invalid code; clear out any
+            // previous code so we can start from scratch
+            prvCode = 0;
+        }
+    }
+        
+    // stored first code - we hang onto this until we see the
+    // inverted second copy, so that we can verify a matching pair
+    uint16_t prvCode;
+    
+    // masks for the subfields
+    static const int F_shift = 5;
+    static const int S_shift = 13;
+    static const uint16_t D_mask = 0x1F;
+    static const uint16_t F_mask = 0x00FF << F_shift;
+    static const uint16_t S_mask = 0x0003 << S_shift;
+};
+
+// -----------------------------------------------------------------------
+// 
+// Samsung 20-bit protocol handler.  This is a simple space-length 
+// encoding.
+//
+class IRPSamsung20: public IRPSpaceLength<uint32_t>
+{
+public:
+    IRPSamsung20() { }
+    
+    // name and ID
+    virtual const char *name() const { return "Samsung20"; }
+    virtual int id() const { return IRPRO_SAMSUNG20; }
+    
+    // code parameters
+    virtual float pwmPeriod(IRTXState *state) const { return 26.0416667e-6; } // 38.4 kHz
+    virtual uint32_t tHeaderMark() const { return 8*564; }
+    virtual uint32_t tHeaderSpace() const { return 8*564; }
+    virtual uint32_t minRxGap() const { return 2*564; }
+    virtual uint32_t txGap(IRTXState *state) const { return 118000; }
+    
+    // space length coding
+    virtual int nbits() const { return 20; }
+    virtual int tMark() const { return 1*564; }
+    virtual int tZero() const { return 1*564; }
+    virtual int tOne() const { return 3*564; }
+};
+
+// Samsung 36-bit protocol.  This is similar to the NEC protocol,
+// with different header timing.
+class IRPSamsung36: public IRPSpaceLength<uint32_t>
+{
+public:
+    IRPSamsung36() { }
+
+    // name and ID
+    virtual const char *name() const { return "Samsung36"; }
+    virtual int id() const { return IRPRO_SAMSUNG36; }
+    
+    // code parameters
+    virtual uint32_t tHeaderMark() const { return 9*500; }
+    virtual uint32_t tHeaderSpace() const { return 9*500; }
+    virtual uint32_t minRxGap() const { return 40000; }
+    virtual uint32_t txGap(IRTXState *state) const { return 118000; }
+    
+    // space length coding
+    virtual int nbits() const { return 36; }
+    virtual int tMark() const { return 1*500; }
+    virtual int tZero() const { return 1*500; }
+    virtual int tOne() const { return 3*500; }    
+};
+
+// -----------------------------------------------------------------------
+//
+// Lutron lights, fans, and home automation.  Lutron uses a simple async
+// bit coding: a 2280us space represents '0', a 2280us mark represents '1'.
+// Each code consists of 36 bits.  The start of a code word is indicated
+// by a space of at least 4*2280us followed by a mark of at least 8*2280us.
+// The mark amounts to 8 consecutive '1' bits, which Lutron includes as
+// digits in the published codes, so we'll include them in our representation 
+// as well (as opposed to treating them as a separate header).
+
+// These raw 36-bit strings use an internal error correction coding.  This
+// isn't mentioned in Lutron's technical documentation (they just list the
+// raw async bit strings), but there's a description on the Web (see, e.g.,
+// hifi-remote.com; the original source is unclear).  According to that,
+// you start with a pair of 8-bit values (device code + function).  Append
+// two parity bits for even parity in each byte.  This gives us 18 bits.
+// Divide the 18 bits into 6 groups of 3 bits.  For each 3-bit group, take
+// the binary value (0..7), recode the bits as a reflected Gray code for
+// the same number (e.g., '111' binary = 7 = '100' Gray coded), then add
+// an even parity bit.  We now have 24 bits.  Concatenate these together
+// and sandwich them between the 8 x '1' start bits and the 4 x '0' stop
+// bits, and we have the 36-bit async bit stream.
+//
+// The error correction code lets us apply at least one validation check:
+// we can verify that each 4-bit group in the raw data has odd parity.  If
+// it doesn't, we'll reject the code.  Ideally, we could also reverse the
+// whole coding scheme above and check the two even parity bits for the
+// recovered bytes.  Unfortunately, I haven't figured out how to do this;
+// taking the Web description of the coding at face value doesn't yield
+// correct parity bits for all of the codes published by Lutron.  Either
+// the description is wrong, or I'm just misinterpreting it (which is
+// likely given that it's pretty sketchy).  
+// 
+class IRPLutron: public IRPAsync<uint32_t>
+{
+public:
+    IRPLutron() { }
+
+    // name and ID
+    virtual const char *name() const { return "Lutron"; }
+    virtual int id() const { return IRPRO_LUTRON; }
+    
+    // carrier is 40kHz
+    virtual float pwmPeriod(IRTXState *state) const { return 25.0e-6f; } // 40kHz
+
+    // All codes end with 4 '0' bits, which is as much of a gap as these
+    // remotes provide.
+    virtual uint32_t minRxGap() const { return 2280*4 - 700; }
+    virtual uint32_t txGap(IRTXState *state) const { return 2280*16; }
+    
+    // Lutron doesn't have a formal header, but there's a long mark at 
+    // the beginning of every code.
+    virtual uint32_t tHeaderMark() const { return 2280*8; }
+    virtual uint32_t tHeaderSpace() const { return 0; }
+    
+    // Bit code parameters.  The Lutron codes are 36-bits, each bit
+    // is 2280us long, and bits are stored MSB first.
+    virtual bool lsbFirst() const { return false; }
+    virtual int nbits() const { return 24; }
+    virtual int tBit() const { return 2280; }
+    
+    // Validate a received code value, using the decoding process
+    // described above.  The code value given here is the low-order
+    // 32 bits of the 36-bit code, with the initial FF stripped.
+    bool rxValidate(uint32_t c)
+    {
+        // the low 4 bits must be zeroes
+        if ((c & 0x0000000F) != 0)
+            return false;
+            
+        // drop the 4 '0' bits at the end
+        c >>= 4;
+        
+        // parity table for 4-bit values 0x00..0x0F - 0=even, 1=odd
+        static const int parity[] = {
+            0, 1, 1, 0,     // 0, 1, 2, 3
+            1, 0, 0, 1,     // 4, 5, 6, 7
+            1, 0, 0, 1,     // 8, 9, A, B
+            0, 1, 1, 0      // C, D, E, F
+        };
+        
+        // add up the number of groups with odd parity
+        int odds = 0;
+        for (int i = 0 ; i < 6 ; ++i, c >>= 4)
+            odds += parity[c & 0x0F];
+            
+        // we need all 6 groups to have odd parity
+        return odds == 6;
+    }
+
+    // Return the code.  Lutron's code tables have all codes starting
+    // with FF (8 consecutive '1' bits), but we treat this as a header,
+    // since it can never occur within a code, so it doesn't show up
+    // in the bit string.  We also count the tailing four '0' bits,
+    // which Lutron also encodes in each string, as a gap.  So we only
+    // actually capture 24 data bits.  To make our codes match the
+    // Lutron tables, add the structural bits back in for reporting.
+    virtual void rxClose(IRRecvProIfc *receiver, bool ditto)
+    {
+        if (bit == 24)
+        {
+            uint64_t report = 0xFF0000000LL | (code << 4);
+            if (rxValidate(report))
+                reportCode(receiver, id(), report, bool3::null, bool3::null);
+        }
+    }
+    
+    // For transmission, convert back to the 24 data bits, stripping out
+    // the structural leading 8 '1' bits and trailing 4 '0' bits.
+    virtual void codeToBitstream(IRTXState *state)
+    {
+        state->bitstream = (state->cmdCode >> 4) & 0x00FFFFFF;
+        state->nbits = 24;
+    }
+};
+
+// -------------------------------------------------------------------------
+//
+// Protocol singletons.  We combine these into a container structure
+// so that we can allocate the whole set of protocol handlers in one
+// 'new'.
+//
+struct IRProtocols
+{
+    #define IR_PROTOCOL_RXTX(cls) cls s_##cls;
+    #include "IRProtocolList.h"
+};
+
+#endif