diff --git a/sdk/SMX.h b/sdk/SMX.h
index eba1ac7..7ce3898 100644
--- a/sdk/SMX.h
+++ b/sdk/SMX.h
@@ -68,6 +68,11 @@ SMX_API uint16_t SMX_GetInputState(int pad);
// controlling lights should send light updates continually, even if the lights aren't changing.
SMX_API void SMX_SetLights(const char lightsData[864]);
+// This is the same as SMX_SetLights, but receives lights for each pad in separate
+// buffers of 432 colors each. lightsDataSize[pad] is the size of lightsData. If
+// it's not 432, that pad's lights will be left unchanged.
+SMX_API void SMX_SetLights2(const char *lightsData[2], int lightsDataSize[2]);
+
// By default, the panels light automatically when stepped on. If a lights command is sent by
// the application, this stops happening to allow the application to fully control lighting.
// If no lights update is received for a few seconds, automatic lighting is reenabled by the
diff --git a/sdk/Windows/SMX.cpp b/sdk/Windows/SMX.cpp
index de7f29c..9a14ea9 100644
--- a/sdk/Windows/SMX.cpp
+++ b/sdk/Windows/SMX.cpp
@@ -60,6 +60,21 @@ SMX_API void SMX_FactoryReset(int pad) { SMXManager::g_pSMX->GetDevice(pad)->Fac
SMX_API void SMX_ForceRecalibration(int pad) { SMXManager::g_pSMX->GetDevice(pad)->ForceRecalibration(); }
SMX_API void SMX_SetTestMode(int pad, SensorTestMode mode) { SMXManager::g_pSMX->GetDevice(pad)->SetSensorTestMode((SensorTestMode) mode); }
SMX_API bool SMX_GetTestData(int pad, SMXSensorTestModeData *data) { return SMXManager::g_pSMX->GetDevice(pad)->GetTestData(*data); }
-SMX_API void SMX_SetLights(const char lightsData[864]) { SMXManager::g_pSMX->SetLights(string(lightsData, 864)); }
+SMX_API void SMX_SetLights(const char lightsData[864])
+{
+ string lights[] = {
+ string(lightsData, 432),
+ string(lightsData+432, 432),
+ };
+ SMXManager::g_pSMX->SetLights(lights);
+}
+SMX_API void SMX_SetLights2(const char *lightsData[2], int lightsDataSize[2])
+{
+ string lights[] = {
+ string(lightsData[0], lightsDataSize[0]),
+ string(lightsData[1], lightsDataSize[1]),
+ };
+ SMXManager::g_pSMX->SetLights(lights);
+}
SMX_API void SMX_ReenableAutoLights() { SMXManager::g_pSMX->ReenableAutoLights(); }
SMX_API const char *SMX_Version() { return SMX_BUILD_VERSION; }
diff --git a/sdk/Windows/SMX.vcxproj b/sdk/Windows/SMX.vcxproj
index 0b77f94..3b648ea 100644
--- a/sdk/Windows/SMX.vcxproj
+++ b/sdk/Windows/SMX.vcxproj
@@ -18,6 +18,7 @@
+
@@ -29,6 +30,7 @@
+
diff --git a/sdk/Windows/SMX.vcxproj.filters b/sdk/Windows/SMX.vcxproj.filters
index a048bd5..68d2ac6 100644
--- a/sdk/Windows/SMX.vcxproj.filters
+++ b/sdk/Windows/SMX.vcxproj.filters
@@ -37,6 +37,9 @@
Source Files
+
+ Source Files
+
@@ -66,5 +69,8 @@
Source Files
+
+ Source Files
+
-
\ No newline at end of file
+
diff --git a/sdk/Windows/SMXGif.cpp b/sdk/Windows/SMXGif.cpp
new file mode 100644
index 0000000..b3b47f5
--- /dev/null
+++ b/sdk/Windows/SMXGif.cpp
@@ -0,0 +1,530 @@
+#include "SMXGif.h"
+#include
+#include
+#include
+using namespace std;
+
+// This is a simple animated GIF decoder. It always decodes to RGBA color,
+// discarding palettes, and decodes the whole file at once.
+
+class GIFError: public exception { };
+
+struct Palette
+{
+ SMXGif::Color color[256];
+};
+
+void SMXGif::GIFImage::Init(int width_, int height_)
+{
+ width = width_;
+ height = height_;
+ image.resize(width * height);
+}
+
+void SMXGif::GIFImage::Clear(const Color &color)
+{
+ for(int y = 0; y < height; ++y)
+ for(int x = 0; x < width; ++x)
+ get(x,y) = color;
+}
+
+void SMXGif::GIFImage::CropImage(SMXGif::GIFImage &dst, int crop_left, int crop_top, int crop_width, int crop_height) const
+{
+ dst.Init(crop_width, crop_height);
+
+ for(int y = 0; y < crop_height; ++y)
+ {
+ for(int x = 0; x < crop_width; ++x)
+ dst.get(x,y) = get(x + crop_left, y + crop_top);
+ }
+}
+
+void SMXGif::GIFImage::Blit(SMXGif::GIFImage &src, int dst_left, int dst_top, int dst_width, int dst_height)
+{
+ for(int y = 0; y < dst_height; ++y)
+ {
+ for(int x = 0; x < dst_width; ++x)
+ get(x + dst_left, y + dst_top) = src.get(x, y);
+ }
+}
+
+class DataStream
+{
+public:
+ DataStream(const string &data_):
+ data(data_)
+ {
+ }
+
+ uint8_t ReadByte()
+ {
+ if(pos >= data.size())
+ throw GIFError();
+
+ uint8_t result = data[pos];
+ pos++;
+ return result;
+ }
+
+ uint16_t ReadLE16()
+ {
+ uint8_t byte1 = ReadByte();
+ uint8_t byte2 = ReadByte();
+ return byte1 | (byte2 << 8);
+ }
+
+ void ReadBytes(string &s, int count)
+ {
+ s.clear();
+ while(count--)
+ s.push_back(ReadByte());
+ }
+
+ void skip(int bytes)
+ {
+ pos += bytes;
+ }
+
+private:
+ const string &data;
+ uint32_t pos = 0;
+};
+
+class LWZStream
+{
+public:
+ LWZStream(DataStream &stream_):
+ stream(stream_)
+ {
+ }
+
+ // Read one LZW code from the input data.
+ uint32_t ReadLZWCode(uint32_t bit_count)
+ {
+ while(bits_in_buffer < bit_count)
+ {
+ if(bytes_remaining == 0)
+ {
+ // Read the next block's byte count.
+ bytes_remaining = stream.ReadByte();
+ if(bytes_remaining == 0)
+ throw GIFError();
+ }
+
+ // Shift in another 8 bits into the end of self.bits.
+ bits |= stream.ReadByte() << bits_in_buffer;
+ bits_in_buffer += 8;
+ bytes_remaining -= 1;
+ }
+
+ // Shift out bit_count worth of data from the end.
+ uint32_t result = bits & ((1 << bit_count) - 1);
+ bits >>= bit_count;
+ bits_in_buffer -= bit_count;
+
+ return result;
+ }
+
+ // Skip the rest of the LZW data.
+ void Flush()
+ {
+ stream.skip(bytes_remaining);
+ bytes_remaining = 0;
+
+ // If there are any blocks past the end of data, skip them.
+ while(1)
+ {
+ uint8_t blocksize = stream.ReadByte();
+ stream.skip(blocksize);
+ if(bytes_remaining == 0)
+ break;
+ }
+ }
+
+private:
+ DataStream &stream;
+ uint32_t bits = 0;
+ int bytes_remaining = 0;
+ int bits_in_buffer = 0;
+};
+
+struct LWZDecoder
+{
+ LWZDecoder(DataStream &stream):
+ lzw_stream(LWZStream(stream))
+ {
+ // Each frame has a single bits field.
+ code_bits = stream.ReadByte();
+ }
+
+ string DecodeImage();
+
+private:
+ uint16_t code_bits;
+ LWZStream lzw_stream;
+};
+
+
+static const int GIFBITS = 12;
+
+string LWZDecoder::DecodeImage()
+{
+ uint32_t dictionary_bits = code_bits + 1;
+ int prev_code1 = -1;
+ int prev_code2 = -1;
+
+ uint32_t clear = 1 << code_bits;
+ uint32_t end = clear + 1;
+ uint32_t next_free_slot = clear + 2;
+
+ vector> dictionary;
+ dictionary.resize(1 << GIFBITS);
+
+ // We append to this buffer as we decode data, then append the data in reverse
+ // order.
+ string append_buffer;
+
+ string result;
+ while(1)
+ {
+ // Flush append_buffer.
+ for(int i = append_buffer.size() - 1; i >= 0; --i)
+ result.push_back(append_buffer[i]);
+ append_buffer.clear();
+
+ int code1 = lzw_stream.ReadLZWCode(dictionary_bits);
+ // printf("%02x");
+ if(code1 == end)
+ break;
+
+ if(code1 == clear)
+ {
+ // Clear the dictionary and reset.
+ dictionary_bits = code_bits + 1;
+ next_free_slot = clear + 2;
+ prev_code1 = -1;
+ prev_code2 = -1;
+ continue;
+ }
+
+ int code2;
+ if(code1 < next_free_slot)
+ code2 = code1;
+ else if(code1 == next_free_slot && prev_code2 != -1)
+ {
+ append_buffer.push_back(prev_code2);
+ code2 = prev_code1;
+ }
+ else
+ throw GIFError();
+
+ // Walk through the linked list of codes in the dictionary and append.
+ while(code2 >= clear + 2)
+ {
+ uint8_t append_char = dictionary[code2].first;
+ code2 = dictionary[code2].second;
+ append_buffer.push_back(append_char);
+ }
+ append_buffer.push_back(code2);
+
+ // If we're already at the last free slot, the dictionary is full and can't be expanded.
+ if(next_free_slot < (1 << dictionary_bits))
+ {
+ // If we have any free dictionary slots, save.
+ if(prev_code1 != -1)
+ {
+ dictionary[next_free_slot] = make_pair(code2, prev_code1);
+ next_free_slot += 1;
+ }
+ // If we've just filled the last dictionary slot, expand the dictionary size if possible.
+ if(next_free_slot >= (1 << dictionary_bits) && dictionary_bits < GIFBITS)
+ dictionary_bits += 1;
+ }
+
+ prev_code1 = code1;
+ prev_code2 = code2;
+ }
+
+ // Skip any remaining data in this block.
+ lzw_stream.Flush();
+
+ return result;
+}
+
+struct GlobalGIFData
+{
+ int width = 0, height = 0;
+ int background_index = -1;
+ bool use_transparency = false;
+ int transparency_index = -1;
+ int duration = 0;
+ int disposal_method = 0;
+ bool have_global_palette = false;
+ Palette palette;
+};
+
+class GIFDecoder
+{
+public:
+ GIFDecoder(DataStream &stream_):
+ stream(stream_)
+ {
+ }
+
+ void ReadAllFrames(vector &frames);
+
+private:
+ bool ReadPacket(string &packet);
+ Palette ReadPalette(int palette_size);
+ void DecodeImage(GlobalGIFData global_data, SMXGif::GIFImage &out);
+
+ DataStream &stream;
+ SMXGif::GIFImage image;
+ int frame;
+};
+
+// Read a palette with size colors.
+//
+// This is a simple string, with 4 RGBA bytes per color.
+Palette GIFDecoder::ReadPalette(int palette_size)
+{
+ Palette result;
+ for(int i = 0; i < palette_size; ++i)
+ {
+ result.color[i].color[0] = stream.ReadByte(); // R
+ result.color[i].color[1] = stream.ReadByte(); // G
+ result.color[i].color[2] = stream.ReadByte(); // B
+ result.color[i].color[3] = 0xFF;
+ }
+ return result;
+}
+
+bool GIFDecoder::ReadPacket(string &packet)
+{
+ uint8_t packet_size = stream.ReadByte();
+ if(packet_size == 0)
+ return false;
+
+ stream.ReadBytes(packet, packet_size);
+ return true;
+}
+
+void GIFDecoder::ReadAllFrames(vector &frames)
+{
+ string header;
+ stream.ReadBytes(header, 6);
+
+ if(header != "GIF87a" && header != "GIF89a")
+ throw GIFError();
+
+ GlobalGIFData global_data;
+
+ global_data.width = stream.ReadLE16();
+ global_data.height = stream.ReadLE16();
+ image.Init(global_data.width, global_data.height);
+
+ // Ignore the aspect ratio field. (Supporting pixel aspect ratios in a format
+ // this rudimentary was almost ambitious of them...)
+ uint8_t global_flags = stream.ReadByte();
+ global_data.background_index = stream.ReadByte();
+
+ // Ignore the aspect ratio field. (Supporting pixel aspect ratios in a format
+ // this rudimentary was almost ambitious of them...)
+ stream.ReadByte();
+
+ // Decode global_flags.
+ uint8_t global_palette_size = global_flags & 0x7;
+
+ global_data.have_global_palette = (global_flags >> 7) & 0x1;
+
+
+ // If there's no global palette, leave it empty.
+ if(global_data.have_global_palette)
+ global_data.palette = ReadPalette(1 << (global_palette_size + 1));
+
+ frame = 0;
+
+ // Save a copy of global data, so we can restore it after each frame.
+ GlobalGIFData saved_global_data = global_data;
+
+ // Decode all packets.
+ while(1)
+ {
+ uint8_t packet_type = stream.ReadByte();
+ if(packet_type == 0x21)
+ {
+ // Extension packet
+ uint8_t extension_type = stream.ReadByte();
+
+ if(extension_type == 0xF9)
+ {
+ string packet;
+ if(!ReadPacket(packet))
+ throw GIFError();
+
+ DataStream packet_buf(packet);
+
+ // Graphics control extension
+ uint8_t gce_flags = packet_buf.ReadByte();
+ global_data.duration = packet_buf.ReadLE16();
+ global_data.transparency_index = packet_buf.ReadByte();
+
+ global_data.use_transparency = bool(gce_flags & 1);
+ global_data.disposal_method = (gce_flags >> 2) & 0xF;
+ if(!global_data.use_transparency)
+ global_data.transparency_index = -1;
+ }
+
+ // Read any remaining packets in this extension packet.
+ while(1)
+ {
+ string packet;
+ if(!ReadPacket(packet))
+ break;
+ }
+ }
+ else if(packet_type == 0x2C)
+ {
+ // Image data
+ SMXGif::GIFImage frame_image;
+ DecodeImage(global_data, frame_image);
+
+ SMXGif::SMXGifFrame gif_frame;
+ gif_frame.width = global_data.width;
+ gif_frame.height = global_data.height;
+ gif_frame.milliseconds = global_data.duration * 10;
+ gif_frame.frame = frame_image;
+ frames.push_back(gif_frame);
+
+ frame++;
+
+ // Reset GCE (frame-specific) data.
+ global_data = saved_global_data;
+ }
+ else if(packet_type == 0x3B)
+ {
+ // EOF
+ return;
+ }
+ else
+ throw GIFError();
+ }
+}
+
+// Decode a single GIF image into out, leaving this->image ready for
+// the next frame (with this frame's dispose applied).
+void GIFDecoder::DecodeImage(GlobalGIFData global_data, SMXGif::GIFImage &out)
+{
+ uint16_t block_left = stream.ReadLE16();
+ uint16_t block_top = stream.ReadLE16();
+ uint16_t block_width = stream.ReadLE16();
+ uint16_t block_height = stream.ReadLE16();
+ uint8_t local_flags = stream.ReadByte();
+
+ // area = (block_left, block_top, block_left + block_width, block_top + block_height)
+ // Extract flags:
+ uint8_t have_local_palette = (local_flags >> 7) & 1;
+ // bool interlaced = (local_flags >> 6) & 1;
+ uint8_t local_palette_size = (local_flags >> 0) & 0x7;
+ // print 'Interlaced:', interlaced
+
+ // We don't support interlaced GIFs right now.
+ // assert interlaced == 0
+
+ // If this frame has a local palette, use it. Otherwise, use the global palette.
+ Palette active_palette = global_data.palette;
+ if(have_local_palette)
+ active_palette = ReadPalette(1 << (local_palette_size + 1));
+
+ if(!global_data.have_global_palette && !have_local_palette)
+ {
+ // We have no palette. This is an invalid file.
+ throw GIFError();
+ }
+
+ if(frame == 0)
+ {
+ // On the first frame, clear the buffer. If we have a transparency index,
+ // clear to transparent. Otherwise, clear to the background color.
+ if(global_data.transparency_index != -1)
+ image.Clear(SMXGif::Color(0,0,0,0));
+ else
+ image.Clear(active_palette.color[global_data.background_index]);
+ }
+
+ // Decode the compressed image data.
+ LWZDecoder decoder(stream);
+ string decompressed_data = decoder.DecodeImage();
+
+ if(decompressed_data.size() < block_width*block_height)
+ throw GIFError();
+
+ // Save the region to restore after decoding.
+ SMXGif::GIFImage dispose;
+ if(global_data.disposal_method <= 1)
+ {
+ // No disposal.
+ }
+ else if(global_data.disposal_method == 2)
+ {
+ // Clear the region to a background color afterwards.
+ dispose.Init(block_width, block_height);
+
+ if(global_data.transparency_index != -1)
+ dispose.Clear(SMXGif::Color(0,0,0,0));
+ else
+ {
+ uint8_t palette_idx = global_data.background_index;
+ dispose.Clear(active_palette.color[palette_idx]);
+ }
+
+ }
+ else if(global_data.disposal_method == 3)
+ {
+ // Restore the previous frame afterwards.
+ image.CropImage(dispose, block_left, block_top, block_width, block_height);
+ }
+ else
+ {
+ // Unknown disposal method
+ }
+
+ int pos = 0;
+ for(int y = block_top; y < block_top + block_height; ++y)
+ {
+ for(int x = block_left; x < block_left + block_width; ++x)
+ {
+ uint8_t palette_idx = decompressed_data[pos];
+ pos++;
+
+ if(palette_idx == global_data.transparency_index)
+ {
+ // If this pixel is transparent, leave the existing color in place.
+ }
+ else
+ {
+ image.get(x,y) = active_palette.color[palette_idx];
+ }
+ }
+ }
+
+ // Copy the image before we run dispose.
+ out = image;
+
+ // Restore the dispose area.
+ if(dispose.width != 0)
+ image.Blit(dispose, block_left, block_top, block_width, block_height);
+}
+
+bool SMXGif::DecodeGIF(string buf, vector &frames)
+{
+ DataStream stream(buf);
+ GIFDecoder gif(stream);
+ try {
+ gif.ReadAllFrames(frames);
+ } catch(GIFError &) {
+ // We don't return error strings for this, just success or failure.
+ return false;
+ }
+ return true;
+}
diff --git a/sdk/Windows/SMXGif.h b/sdk/Windows/SMXGif.h
new file mode 100644
index 0000000..e02f27c
--- /dev/null
+++ b/sdk/Windows/SMXGif.h
@@ -0,0 +1,70 @@
+#ifndef SMXGif_h
+#define SMXGif_h
+
+#include
+#include
+#include
+
+// This is a simple internal GIF decoder. It's only meant to be used by
+// SMXConfig.
+namespace SMXGif
+{
+ struct Color
+ {
+ uint8_t color[4];
+ Color()
+ {
+ memset(color, 0, sizeof(color));
+ }
+
+ Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+ {
+ color[0] = r;
+ color[1] = g;
+ color[2] = b;
+ color[3] = a;
+ }
+ bool operator==(const Color &rhs) const
+ {
+ return !memcmp(color, rhs.color, sizeof(*color));
+ }
+ };
+
+ struct GIFImage
+ {
+ int width = 0, height = 0;
+ void Init(int width, int height);
+
+ Color get(int x, int y) const { return image[y*width+x]; }
+ Color &get(int x, int y) { return image[y*width+x]; }
+
+ // Clear to a solid color.
+ void Clear(const Color &color);
+
+ // Copy a rectangle from this image into dst.
+ void CropImage(GIFImage &dst, int crop_left, int crop_top, int crop_width, int crop_height) const;
+
+ // Copy src into a rectangle in this image.
+ void Blit(GIFImage &src, int dst_left, int dst_top, int dst_width, int dst_height);
+
+ private:
+ std::vector image;
+ };
+
+ struct SMXGifFrame
+ {
+ int width = 0, height = 0;
+
+ // GIF images have a delay in 10ms units. We use 1ms for clarity.
+ int milliseconds = 0;
+
+ GIFImage frame;
+ };
+
+ // Decode a GIF into a list of frames.
+ bool DecodeGIF(std::string buf, std::vector &frames);
+}
+
+void gif_test();
+
+#endif
diff --git a/sdk/Windows/SMXManager.cpp b/sdk/Windows/SMXManager.cpp
index 90771de..3473474 100644
--- a/sdk/Windows/SMXManager.cpp
+++ b/sdk/Windows/SMXManager.cpp
@@ -241,24 +241,11 @@ void SMX::SMXManager::ThreadMain()
// that we don't send the second lights commands, since that may re-disable auto lights.
// - If we have two pads, the lights update is for both pads and we'll send both commands
// for both pads at the same time, so both pads update lights simultaneously.
-void SMX::SMXManager::SetLights(const string &sLightData)
+void SMX::SMXManager::SetLights(const string sPanelLights[2])
{
g_Lock.AssertNotLockedByCurrentThread();
LockMutex L(g_Lock);
- // Sanity check the lights data. It should have 18*16*3 bytes of data: RGB for each of 4x4
- // LEDs on 18 panels.
- if(sLightData.size() != 2*3*3*16*3)
- {
- Log(ssprintf("SetLights: Lights data should be %i bytes, received %i", 2*3*3*16*3, sLightData.size()));
- return;
- }
-
- // Split the lights data into P1 and P2.
- string sPanelLights[2];
- sPanelLights[0] = sLightData.substr(0, 9*16*3);
- sPanelLights[1] = sLightData.substr(9*16*3);
-
// Separate top and bottom lights commands.
//
// sPanelLights[iPad] is
@@ -281,42 +268,44 @@ void SMX::SMXManager::SetLights(const string &sLightData)
// Set sLightsCommand[iPad][0] to include 0123 4567, and [1] to 89AB CDEF.
string sLightCommands[2][2]; // sLightCommands[command][pad]
- auto addByte = [&sLightCommands](int iPanel, int iByte, uint8_t iColor) {
- // If iPanel is 0-8, this is for pad 0. For 9-17, it's for pad 1.
- // If the color byte within the panel is in the top half, it's the first
- // command, otherwise it's the second command.
- int iPad = iPanel < 9? 0:1;
- int iCommandIndex = iByte < 4*2*3? 0:1;
- sLightCommands[iCommandIndex][iPad].append(1, iColor);
- };
-
// Read the linearly arranged color data we've been given and split it into top and
// bottom commands for each pad.
- int iNextInputByte = 0;
- for(int iPanel = 0; iPanel < 18; ++iPanel)
+ for(int iPad = 0; iPad < 2; ++iPad)
{
- for(int iByte = 0; iByte < 4*4*3; ++iByte)
+ // If there's no data for this pad, leave the command empty.
+ const string &sLightsDataForPad = sPanelLights[iPad];
+ if(sLightsDataForPad.empty())
+ continue;
+
+ // Sanity check the lights data. It should have 9*4*4*3 bytes of data: RGB for each of 4x4
+ // LEDs on 9 panels.
+ if(sPanelLights[iPad].size() != 9*16*3)
{
- uint8_t iColor = sLightData[iNextInputByte++];
- addByte(iPanel, iByte, iColor);
+ Log(ssprintf("SetLights: Lights data should be %i bytes, received %i", 3*3*16*3, sPanelLights[iPad].size()));
+ continue;
}
- }
- for(int iPad = 0; iPad < 2; ++iPad)
- {
- for(int iCommand = 0; iCommand < 2; ++iCommand)
+ // The 2 command sends the top 4x2 lights, and 3 sends the bottom 4x2.
+ sLightCommands[0][iPad] = "2";
+ sLightCommands[1][iPad] = "3";
+ int iNextInputByte = 0;
+ for(int iPanel = 0; iPanel < 9; ++iPanel)
{
- string &sCommand = sLightCommands[iCommand][iPad];
-
- // Apply color scaling. Values over about 170 don't make the LEDs any brighter, so this
- // gives better contrast and draws less power.
- for(char &c: sCommand)
- c = char(uint8_t(c) * 0.6666f);
-
- // Add the command byte.
- sCommand.insert(sCommand.begin(), 1, iCommand == 0? '2':'3');
- sCommand.push_back('\n');
+ for(int iByte = 0; iByte < 4*4*3; ++iByte)
+ {
+ uint8_t iColor = sLightsDataForPad[iNextInputByte++];
+
+ // Apply color scaling. Values over about 170 don't make the LEDs any brighter, so this
+ // gives better contrast and draws less power.
+ iColor = uint8_t(iColor * 0.6666f);
+
+ int iCommandIndex = iByte < 4*2*3? 0:1;
+ sLightCommands[iCommandIndex][iPad].append(1, iColor);
+ }
}
+
+ sLightCommands[0][iPad].push_back('\n');
+ sLightCommands[1][iPad].push_back('\n');
}
// Each update adds two entries to m_aPendingCommands, one for the top half and one
@@ -356,13 +345,18 @@ void SMX::SMXManager::SetLights(const string &sLightData)
// Set the pad commands.
PendingCommand *pPendingCommands[2];
- pPendingCommands[0] = &m_aPendingCommands[m_aPendingCommands.size()-2];
- pPendingCommands[1] = &m_aPendingCommands[m_aPendingCommands.size()-1];
+ pPendingCommands[0] = &m_aPendingCommands[m_aPendingCommands.size()-2]; // 2
+ pPendingCommands[1] = &m_aPendingCommands[m_aPendingCommands.size()-1]; // 3
- pPendingCommands[0]->sPadCommand[0] = sLightCommands[0][0];
- pPendingCommands[0]->sPadCommand[1] = sLightCommands[0][1];
- pPendingCommands[1]->sPadCommand[0] = sLightCommands[1][0];
- pPendingCommands[1]->sPadCommand[1] = sLightCommands[1][1];
+ for(int iPad = 0; iPad < 2; ++iPad)
+ {
+ // If the command for this pad is empty, leave any existing pad command alone.
+ if(sLightCommands[0][iPad].empty())
+ continue;
+
+ pPendingCommands[0]->sPadCommand[iPad] = sLightCommands[0][iPad];
+ pPendingCommands[1]->sPadCommand[iPad] = sLightCommands[1][iPad];
+ }
}
void SMX::SMXManager::ReenableAutoLights()
@@ -395,7 +389,10 @@ void SMX::SMXManager::SendLightUpdates()
// Send the lights command for each pad. If either pad isn't connected, this won't do
// anything.
for(int iPad = 0; iPad < 2; ++iPad)
- m_pDevices[iPad]->SendCommandLocked(command.sPadCommand[iPad]);
+ {
+ if(!command.sPadCommand[iPad].empty())
+ m_pDevices[iPad]->SendCommandLocked(command.sPadCommand[iPad]);
+ }
// Remove the command we've sent.
m_aPendingCommands.erase(m_aPendingCommands.begin(), m_aPendingCommands.begin()+1);
diff --git a/sdk/Windows/SMXManager.h b/sdk/Windows/SMXManager.h
index 133977f..1c061f3 100644
--- a/sdk/Windows/SMXManager.h
+++ b/sdk/Windows/SMXManager.h
@@ -43,7 +43,7 @@ public:
void Shutdown();
shared_ptr GetDevice(int pad);
- void SetLights(const string &sLightData);
+ void SetLights(const string sLights[2]);
void ReenableAutoLights();
private: