diff --git a/Builds/VisualStudio2013/Plugins/PCIeRhythm/PCIeRhythm.vcxproj b/Builds/VisualStudio2013/Plugins/PCIeRhythm/PCIeRhythm.vcxproj
new file mode 100644
index 0000000000000000000000000000000000000000..93416781cc19bfd1814abd5f032326e5ebc571fa
--- /dev/null
+++ b/Builds/VisualStudio2013/Plugins/PCIeRhythm/PCIeRhythm.vcxproj
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{8F019559-89D4-4C33-BA0F-FF330C57BA2B}</ProjectGuid>
+    <RootNamespace>PCIeRhythm</RootNamespace>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v120</PlatformToolset>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v120</PlatformToolset>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v120</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v120</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\Plugin_Debug32.props" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\Plugin_Debug64.props" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\Plugin_Release32.props" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\Plugin_Release64.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup />
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <SDLCheck>true</SDLCheck>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <SDLCheck>true</SDLCheck>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\OpenEphysLib.cpp" />
+    <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Editor.cpp" />
+    <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Thread.cpp" />
+    <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000datablock.cpp" />
+    <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000PCIe.cpp" />
+    <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000registers.cpp" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Editor.h" />
+    <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Thread.h" />
+    <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000datablock.h" />
+    <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000PCIe.h" />
+    <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000registers.h" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/Builds/VisualStudio2013/Plugins/PCIeRhythm/PCIeRhythm.vcxproj.filters b/Builds/VisualStudio2013/Plugins/PCIeRhythm/PCIeRhythm.vcxproj.filters
new file mode 100644
index 0000000000000000000000000000000000000000..c907b4318d918e6278f87cfa185936df52b88cd2
--- /dev/null
+++ b/Builds/VisualStudio2013/Plugins/PCIeRhythm/PCIeRhythm.vcxproj.filters
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Source Files">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Header Files">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
+    </Filter>
+    <Filter Include="Resource Files">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+    <Filter Include="Source Files\rhythm-api">
+      <UniqueIdentifier>{8ec4462a-3223-46ca-a2f1-cf88ba3c80f6}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Editor.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Thread.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000datablock.cpp">
+      <Filter>Source Files\rhythm-api</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000registers.cpp">
+      <Filter>Source Files\rhythm-api</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000PCIe.cpp">
+      <Filter>Source Files\rhythm-api</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\OpenEphysLib.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Editor.h">
+      <Filter>Source Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Thread.h">
+      <Filter>Source Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000datablock.h">
+      <Filter>Source Files\rhythm-api</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000registers.h">
+      <Filter>Source Files\rhythm-api</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000PCIe.h">
+      <Filter>Source Files\rhythm-api</Filter>
+    </ClInclude>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/Builds/VisualStudio2013/Plugins/Plugins.sln b/Builds/VisualStudio2013/Plugins/Plugins.sln
index b70f4cf7397fce5066b182b27149cc62325ec943..34827585fb2ec670b2f2559d1bb88a886551ff28 100644
--- a/Builds/VisualStudio2013/Plugins/Plugins.sln
+++ b/Builds/VisualStudio2013/Plugins/Plugins.sln
@@ -37,146 +37,230 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SpikeSorter", "SpikeSorter\
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ExampleProcessor", "ExampleProcessor\ExampleProcessor.vcxproj", "{767D282E-0BE5-4B35-874A-3B1ED925F06B}"
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PCIeRhythm", "PCIeRhythm\PCIeRhythm.vcxproj", "{8F019559-89D4-4C33-BA0F-FF330C57BA2B}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Mixed Platforms = Debug|Mixed Platforms
 		Debug|Win32 = Debug|Win32
 		Debug|x64 = Debug|x64
+		Release|Mixed Platforms = Release|Mixed Platforms
 		Release|Win32 = Release|Win32
 		Release|x64 = Release|x64
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{41BD734E-4939-47AD-9714-9629538F7206}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{41BD734E-4939-47AD-9714-9629538F7206}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{41BD734E-4939-47AD-9714-9629538F7206}.Debug|Win32.ActiveCfg = Debug|Win32
 		{41BD734E-4939-47AD-9714-9629538F7206}.Debug|Win32.Build.0 = Debug|Win32
 		{41BD734E-4939-47AD-9714-9629538F7206}.Debug|x64.ActiveCfg = Debug|x64
 		{41BD734E-4939-47AD-9714-9629538F7206}.Debug|x64.Build.0 = Debug|x64
+		{41BD734E-4939-47AD-9714-9629538F7206}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{41BD734E-4939-47AD-9714-9629538F7206}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{41BD734E-4939-47AD-9714-9629538F7206}.Release|Win32.ActiveCfg = Release|Win32
 		{41BD734E-4939-47AD-9714-9629538F7206}.Release|Win32.Build.0 = Release|Win32
 		{41BD734E-4939-47AD-9714-9629538F7206}.Release|x64.ActiveCfg = Release|x64
 		{41BD734E-4939-47AD-9714-9629538F7206}.Release|x64.Build.0 = Release|x64
+		{554C8744-32CD-427C-A9E5-BF9A44440CED}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{554C8744-32CD-427C-A9E5-BF9A44440CED}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{554C8744-32CD-427C-A9E5-BF9A44440CED}.Debug|Win32.ActiveCfg = Debug|Win32
 		{554C8744-32CD-427C-A9E5-BF9A44440CED}.Debug|Win32.Build.0 = Debug|Win32
 		{554C8744-32CD-427C-A9E5-BF9A44440CED}.Debug|x64.ActiveCfg = Debug|x64
 		{554C8744-32CD-427C-A9E5-BF9A44440CED}.Debug|x64.Build.0 = Debug|x64
+		{554C8744-32CD-427C-A9E5-BF9A44440CED}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{554C8744-32CD-427C-A9E5-BF9A44440CED}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{554C8744-32CD-427C-A9E5-BF9A44440CED}.Release|Win32.ActiveCfg = Release|Win32
 		{554C8744-32CD-427C-A9E5-BF9A44440CED}.Release|Win32.Build.0 = Release|Win32
 		{554C8744-32CD-427C-A9E5-BF9A44440CED}.Release|x64.ActiveCfg = Release|x64
 		{554C8744-32CD-427C-A9E5-BF9A44440CED}.Release|x64.Build.0 = Release|x64
+		{7B23828E-559F-4AD2-B75D-D05786F6329C}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{7B23828E-559F-4AD2-B75D-D05786F6329C}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{7B23828E-559F-4AD2-B75D-D05786F6329C}.Debug|Win32.ActiveCfg = Debug|Win32
 		{7B23828E-559F-4AD2-B75D-D05786F6329C}.Debug|Win32.Build.0 = Debug|Win32
 		{7B23828E-559F-4AD2-B75D-D05786F6329C}.Debug|x64.ActiveCfg = Debug|x64
 		{7B23828E-559F-4AD2-B75D-D05786F6329C}.Debug|x64.Build.0 = Debug|x64
+		{7B23828E-559F-4AD2-B75D-D05786F6329C}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{7B23828E-559F-4AD2-B75D-D05786F6329C}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{7B23828E-559F-4AD2-B75D-D05786F6329C}.Release|Win32.ActiveCfg = Release|Win32
 		{7B23828E-559F-4AD2-B75D-D05786F6329C}.Release|Win32.Build.0 = Release|Win32
 		{7B23828E-559F-4AD2-B75D-D05786F6329C}.Release|x64.ActiveCfg = Release|x64
 		{7B23828E-559F-4AD2-B75D-D05786F6329C}.Release|x64.Build.0 = Release|x64
+		{D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Debug|Win32.ActiveCfg = Debug|Win32
 		{D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Debug|Win32.Build.0 = Debug|Win32
 		{D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Debug|x64.ActiveCfg = Debug|x64
 		{D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Debug|x64.Build.0 = Debug|x64
+		{D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Release|Win32.ActiveCfg = Release|Win32
 		{D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Release|Win32.Build.0 = Release|Win32
 		{D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Release|x64.ActiveCfg = Release|x64
 		{D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Release|x64.Build.0 = Release|x64
+		{AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Debug|Win32.ActiveCfg = Debug|Win32
 		{AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Debug|Win32.Build.0 = Debug|Win32
 		{AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Debug|x64.ActiveCfg = Debug|x64
 		{AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Debug|x64.Build.0 = Debug|x64
+		{AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Release|Win32.ActiveCfg = Release|Win32
 		{AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Release|Win32.Build.0 = Release|Win32
 		{AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Release|x64.ActiveCfg = Release|x64
 		{AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Release|x64.Build.0 = Release|x64
+		{9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Debug|Win32.ActiveCfg = Debug|Win32
 		{9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Debug|Win32.Build.0 = Debug|Win32
 		{9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Debug|x64.ActiveCfg = Debug|x64
 		{9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Debug|x64.Build.0 = Debug|x64
+		{9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Release|Win32.ActiveCfg = Release|Win32
 		{9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Release|Win32.Build.0 = Release|Win32
 		{9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Release|x64.ActiveCfg = Release|x64
 		{9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Release|x64.Build.0 = Release|x64
+		{79703461-0C53-417B-BEA2-B3903A41C123}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{79703461-0C53-417B-BEA2-B3903A41C123}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{79703461-0C53-417B-BEA2-B3903A41C123}.Debug|Win32.ActiveCfg = Debug|Win32
 		{79703461-0C53-417B-BEA2-B3903A41C123}.Debug|Win32.Build.0 = Debug|Win32
 		{79703461-0C53-417B-BEA2-B3903A41C123}.Debug|x64.ActiveCfg = Debug|x64
 		{79703461-0C53-417B-BEA2-B3903A41C123}.Debug|x64.Build.0 = Debug|x64
+		{79703461-0C53-417B-BEA2-B3903A41C123}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{79703461-0C53-417B-BEA2-B3903A41C123}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{79703461-0C53-417B-BEA2-B3903A41C123}.Release|Win32.ActiveCfg = Release|Win32
 		{79703461-0C53-417B-BEA2-B3903A41C123}.Release|Win32.Build.0 = Release|Win32
 		{79703461-0C53-417B-BEA2-B3903A41C123}.Release|x64.ActiveCfg = Release|x64
 		{79703461-0C53-417B-BEA2-B3903A41C123}.Release|x64.Build.0 = Release|x64
+		{9C33B87F-24D7-4952-91C0-44759C77F491}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{9C33B87F-24D7-4952-91C0-44759C77F491}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{9C33B87F-24D7-4952-91C0-44759C77F491}.Debug|Win32.ActiveCfg = Debug|Win32
 		{9C33B87F-24D7-4952-91C0-44759C77F491}.Debug|Win32.Build.0 = Debug|Win32
 		{9C33B87F-24D7-4952-91C0-44759C77F491}.Debug|x64.ActiveCfg = Debug|x64
 		{9C33B87F-24D7-4952-91C0-44759C77F491}.Debug|x64.Build.0 = Debug|x64
+		{9C33B87F-24D7-4952-91C0-44759C77F491}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{9C33B87F-24D7-4952-91C0-44759C77F491}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{9C33B87F-24D7-4952-91C0-44759C77F491}.Release|Win32.ActiveCfg = Release|Win32
 		{9C33B87F-24D7-4952-91C0-44759C77F491}.Release|Win32.Build.0 = Release|Win32
 		{9C33B87F-24D7-4952-91C0-44759C77F491}.Release|x64.ActiveCfg = Release|x64
 		{9C33B87F-24D7-4952-91C0-44759C77F491}.Release|x64.Build.0 = Release|x64
+		{75DEED7F-17D0-4805-9272-96288B79BA53}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{75DEED7F-17D0-4805-9272-96288B79BA53}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{75DEED7F-17D0-4805-9272-96288B79BA53}.Debug|Win32.ActiveCfg = Debug|Win32
 		{75DEED7F-17D0-4805-9272-96288B79BA53}.Debug|Win32.Build.0 = Debug|Win32
 		{75DEED7F-17D0-4805-9272-96288B79BA53}.Debug|x64.ActiveCfg = Debug|x64
 		{75DEED7F-17D0-4805-9272-96288B79BA53}.Debug|x64.Build.0 = Debug|x64
+		{75DEED7F-17D0-4805-9272-96288B79BA53}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{75DEED7F-17D0-4805-9272-96288B79BA53}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{75DEED7F-17D0-4805-9272-96288B79BA53}.Release|Win32.ActiveCfg = Release|Win32
 		{75DEED7F-17D0-4805-9272-96288B79BA53}.Release|Win32.Build.0 = Release|Win32
 		{75DEED7F-17D0-4805-9272-96288B79BA53}.Release|x64.ActiveCfg = Release|x64
 		{75DEED7F-17D0-4805-9272-96288B79BA53}.Release|x64.Build.0 = Release|x64
+		{A29DFCB3-837D-491F-B9C4-3E0C93404128}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{A29DFCB3-837D-491F-B9C4-3E0C93404128}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{A29DFCB3-837D-491F-B9C4-3E0C93404128}.Debug|Win32.ActiveCfg = Debug|Win32
 		{A29DFCB3-837D-491F-B9C4-3E0C93404128}.Debug|Win32.Build.0 = Debug|Win32
 		{A29DFCB3-837D-491F-B9C4-3E0C93404128}.Debug|x64.ActiveCfg = Debug|x64
 		{A29DFCB3-837D-491F-B9C4-3E0C93404128}.Debug|x64.Build.0 = Debug|x64
+		{A29DFCB3-837D-491F-B9C4-3E0C93404128}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{A29DFCB3-837D-491F-B9C4-3E0C93404128}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{A29DFCB3-837D-491F-B9C4-3E0C93404128}.Release|Win32.ActiveCfg = Release|Win32
 		{A29DFCB3-837D-491F-B9C4-3E0C93404128}.Release|Win32.Build.0 = Release|Win32
 		{A29DFCB3-837D-491F-B9C4-3E0C93404128}.Release|x64.ActiveCfg = Release|x64
 		{A29DFCB3-837D-491F-B9C4-3E0C93404128}.Release|x64.Build.0 = Release|x64
+		{88971DC8-9416-4229-AAB8-870988D9A81A}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{88971DC8-9416-4229-AAB8-870988D9A81A}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{88971DC8-9416-4229-AAB8-870988D9A81A}.Debug|Win32.ActiveCfg = Debug|Win32
 		{88971DC8-9416-4229-AAB8-870988D9A81A}.Debug|Win32.Build.0 = Debug|Win32
 		{88971DC8-9416-4229-AAB8-870988D9A81A}.Debug|x64.ActiveCfg = Debug|x64
 		{88971DC8-9416-4229-AAB8-870988D9A81A}.Debug|x64.Build.0 = Debug|x64
+		{88971DC8-9416-4229-AAB8-870988D9A81A}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{88971DC8-9416-4229-AAB8-870988D9A81A}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{88971DC8-9416-4229-AAB8-870988D9A81A}.Release|Win32.ActiveCfg = Release|Win32
 		{88971DC8-9416-4229-AAB8-870988D9A81A}.Release|Win32.Build.0 = Release|Win32
 		{88971DC8-9416-4229-AAB8-870988D9A81A}.Release|x64.ActiveCfg = Release|x64
 		{88971DC8-9416-4229-AAB8-870988D9A81A}.Release|x64.Build.0 = Release|x64
+		{6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Debug|Win32.ActiveCfg = Debug|Win32
 		{6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Debug|Win32.Build.0 = Debug|Win32
 		{6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Debug|x64.ActiveCfg = Debug|x64
 		{6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Debug|x64.Build.0 = Debug|x64
+		{6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Release|Win32.ActiveCfg = Release|Win32
 		{6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Release|Win32.Build.0 = Release|Win32
 		{6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Release|x64.ActiveCfg = Release|x64
 		{6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Release|x64.Build.0 = Release|x64
+		{11C301E8-35E7-48AE-978F-4AC651CEFC67}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{11C301E8-35E7-48AE-978F-4AC651CEFC67}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{11C301E8-35E7-48AE-978F-4AC651CEFC67}.Debug|Win32.ActiveCfg = Debug|Win32
 		{11C301E8-35E7-48AE-978F-4AC651CEFC67}.Debug|Win32.Build.0 = Debug|Win32
 		{11C301E8-35E7-48AE-978F-4AC651CEFC67}.Debug|x64.ActiveCfg = Debug|x64
 		{11C301E8-35E7-48AE-978F-4AC651CEFC67}.Debug|x64.Build.0 = Debug|x64
+		{11C301E8-35E7-48AE-978F-4AC651CEFC67}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{11C301E8-35E7-48AE-978F-4AC651CEFC67}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{11C301E8-35E7-48AE-978F-4AC651CEFC67}.Release|Win32.ActiveCfg = Release|Win32
 		{11C301E8-35E7-48AE-978F-4AC651CEFC67}.Release|Win32.Build.0 = Release|Win32
 		{11C301E8-35E7-48AE-978F-4AC651CEFC67}.Release|x64.ActiveCfg = Release|x64
 		{11C301E8-35E7-48AE-978F-4AC651CEFC67}.Release|x64.Build.0 = Release|x64
+		{3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Debug|Win32.ActiveCfg = Debug|Win32
 		{3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Debug|Win32.Build.0 = Debug|Win32
 		{3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Debug|x64.ActiveCfg = Debug|x64
 		{3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Debug|x64.Build.0 = Debug|x64
+		{3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Release|Win32.ActiveCfg = Release|Win32
 		{3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Release|Win32.Build.0 = Release|Win32
 		{3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Release|x64.ActiveCfg = Release|x64
 		{3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Release|x64.Build.0 = Release|x64
+		{2A290337-FC79-4987-8488-A71F29252DB0}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{2A290337-FC79-4987-8488-A71F29252DB0}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{2A290337-FC79-4987-8488-A71F29252DB0}.Debug|Win32.ActiveCfg = Debug|Win32
 		{2A290337-FC79-4987-8488-A71F29252DB0}.Debug|Win32.Build.0 = Debug|Win32
 		{2A290337-FC79-4987-8488-A71F29252DB0}.Debug|x64.ActiveCfg = Debug|x64
 		{2A290337-FC79-4987-8488-A71F29252DB0}.Debug|x64.Build.0 = Debug|x64
+		{2A290337-FC79-4987-8488-A71F29252DB0}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{2A290337-FC79-4987-8488-A71F29252DB0}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{2A290337-FC79-4987-8488-A71F29252DB0}.Release|Win32.ActiveCfg = Release|Win32
 		{2A290337-FC79-4987-8488-A71F29252DB0}.Release|Win32.Build.0 = Release|Win32
 		{2A290337-FC79-4987-8488-A71F29252DB0}.Release|x64.ActiveCfg = Release|x64
 		{2A290337-FC79-4987-8488-A71F29252DB0}.Release|x64.Build.0 = Release|x64
+		{D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Debug|Win32.ActiveCfg = Debug|Win32
 		{D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Debug|Win32.Build.0 = Debug|Win32
 		{D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Debug|x64.ActiveCfg = Debug|x64
 		{D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Debug|x64.Build.0 = Debug|x64
+		{D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Release|Win32.ActiveCfg = Release|Win32
 		{D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Release|Win32.Build.0 = Release|Win32
 		{D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Release|x64.ActiveCfg = Release|x64
 		{D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Release|x64.Build.0 = Release|x64
+		{767D282E-0BE5-4B35-874A-3B1ED925F06B}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{767D282E-0BE5-4B35-874A-3B1ED925F06B}.Debug|Mixed Platforms.Build.0 = Debug|Win32
 		{767D282E-0BE5-4B35-874A-3B1ED925F06B}.Debug|Win32.ActiveCfg = Debug|Win32
 		{767D282E-0BE5-4B35-874A-3B1ED925F06B}.Debug|x64.ActiveCfg = Debug|x64
+		{767D282E-0BE5-4B35-874A-3B1ED925F06B}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{767D282E-0BE5-4B35-874A-3B1ED925F06B}.Release|Mixed Platforms.Build.0 = Release|Win32
 		{767D282E-0BE5-4B35-874A-3B1ED925F06B}.Release|Win32.ActiveCfg = Release|Win32
 		{767D282E-0BE5-4B35-874A-3B1ED925F06B}.Release|x64.ActiveCfg = Release|x64
+		{8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+		{8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|Mixed Platforms.Build.0 = Debug|Win32
+		{8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|Win32.ActiveCfg = Debug|Win32
+		{8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|Win32.Build.0 = Debug|Win32
+		{8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|x64.ActiveCfg = Debug|x64
+		{8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|x64.Build.0 = Debug|x64
+		{8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+		{8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|Mixed Platforms.Build.0 = Release|Win32
+		{8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|Win32.ActiveCfg = Release|Win32
+		{8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|Win32.Build.0 = Release|Win32
+		{8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|x64.ActiveCfg = Release|x64
+		{8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|x64.Build.0 = Release|x64
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/Source/Plugins/PCIeRhythm/OpenEphysLib.cpp b/Source/Plugins/PCIeRhythm/OpenEphysLib.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a29697965a124f8c8b3b1c47efedd87475bbef9e
--- /dev/null
+++ b/Source/Plugins/PCIeRhythm/OpenEphysLib.cpp
@@ -0,0 +1,69 @@
+/*
+------------------------------------------------------------------
+
+This file is part of the Open Ephys GUI
+Copyright (C) 2013 Open Ephys
+
+------------------------------------------------------------------
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include <PluginInfo.h>
+#include "RHD2000Thread.h"
+#include <string>
+#ifdef WIN32
+#include <Windows.h>
+#define EXPORT __declspec(dllexport)
+#else
+#define EXPORT
+#endif
+
+using namespace Plugin;
+#define NUM_PLUGINS 1
+
+extern "C" EXPORT void getLibInfo(Plugin::LibraryInfo* info)
+{
+	info->apiVersion = PLUGIN_API_VER;
+	info->name = "PCIe Rhythm interface";
+	info->libVersion = 1;
+	info->numPlugins = NUM_PLUGINS;
+}
+
+extern "C" EXPORT int getPluginInfo(int index, Plugin::PluginInfo* info)
+{
+	switch (index)
+	{
+	case 0:
+		info->type = Plugin::DatathreadPlugin;
+		info->dataThread.name = "PCIe Rhythm";
+		info->dataThread.creator = &createDataThread<PCIeRhythm::RHD2000Thread>;
+		break;
+	default:
+		return -1;
+		break;
+	}
+	return 0;
+}
+
+#ifdef WIN32
+BOOL WINAPI DllMain(IN HINSTANCE hDllHandle,
+	IN DWORD     nReason,
+	IN LPVOID    Reserved)
+{
+	return TRUE;
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/Plugins/PCIeRhythm/RHD2000Editor.cpp b/Source/Plugins/PCIeRhythm/RHD2000Editor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e8292c0642844e16b044d5cd1b5d39bb72157c49
--- /dev/null
+++ b/Source/Plugins/PCIeRhythm/RHD2000Editor.cpp
@@ -0,0 +1,1562 @@
+/*
+    ------------------------------------------------------------------
+
+    This file is part of the Open Ephys GUI
+    Copyright (C) 2014 Open Ephys
+
+    ------------------------------------------------------------------
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "RHD2000Editor.h"
+#include <cmath>
+#include "RHD2000Thread.h"
+using namespace PCIeRhythm;
+
+#ifdef WIN32
+#if (_MSC_VER < 1800) //round doesn't exist on MSVC prior to 2013 version
+inline double round(double x)
+{
+    return floor(x+0.5);
+}
+#endif
+#endif
+
+FPGAchannelList::FPGAchannelList(GenericProcessor* proc_, Viewport* p, FPGAcanvas* c) : chainUpdate(false), viewport(p), canvas(c)
+{
+    proc = (SourceNode*)proc_;
+    channelComponents.clear();
+
+    numberingSchemeLabel = new Label("Numbering scheme:","Numbering scheme:");
+    numberingSchemeLabel->setEditable(false);
+    numberingSchemeLabel->setBounds(10,10,150, 25);
+    numberingSchemeLabel->setColour(Label::textColourId,juce::Colours::white);
+    addAndMakeVisible(numberingSchemeLabel);
+
+    numberingScheme = new ComboBox("numberingScheme");
+    numberingScheme->addItem("Continuous",1);
+    numberingScheme->addItem("Per Stream",2);
+    numberingScheme->setBounds(160,10,100,25);
+    numberingScheme->addListener(this);
+    numberingScheme->setSelectedId(1, dontSendNotification);
+    addAndMakeVisible(numberingScheme);
+
+    impedanceButton = new UtilityButton("Measure Impedance", Font("Default", 13, Font::plain));
+    impedanceButton->setRadius(3);
+    impedanceButton->setBounds(280,10,140,25);
+    impedanceButton->addListener(this);
+    addAndMakeVisible(impedanceButton);
+
+    RHD2000Editor* e = static_cast<RHD2000Editor*>(proc->getEditor());
+    saveImpedanceButton = new ToggleButton("Save impedance measurements");
+    saveImpedanceButton->setBounds(430,10,110,25);
+    saveImpedanceButton->setToggleState(e->getSaveImpedance(),dontSendNotification);
+    saveImpedanceButton->addListener(this);
+    addAndMakeVisible(saveImpedanceButton);
+
+    autoMeasureButton = new ToggleButton("Measure impedance at acquisition start");
+    autoMeasureButton->setBounds(550,10,150,25);
+    autoMeasureButton->setToggleState(e->getAutoMeasureImpedance(),dontSendNotification);
+    autoMeasureButton->addListener(this);
+    addAndMakeVisible(autoMeasureButton);
+
+    gains.clear();
+    gains.add(0.01);
+    gains.add(0.1);
+    gains.add(1);
+    gains.add(2);
+    gains.add(5);
+    gains.add(10);
+    gains.add(20);
+    gains.add(50);
+    gains.add(100);
+    gains.add(500);
+    gains.add(1000);
+
+
+    update();
+}
+
+FPGAchannelList::~FPGAchannelList()
+{
+
+}
+
+void FPGAchannelList::paint(Graphics& g)
+{
+}
+
+void FPGAchannelList::buttonClicked(Button* btn)
+{
+    RHD2000Editor* p = (RHD2000Editor*)proc->getEditor();
+    if (btn == impedanceButton)
+    {
+        p->measureImpedance();
+    }
+    else if (btn == saveImpedanceButton)
+    {
+        p->setSaveImpedance(btn->getToggleState());
+    }
+    else if (btn == autoMeasureButton)
+    {
+        p->setAutoMeasureImpedance(btn->getToggleState());
+    }
+}
+
+void FPGAchannelList::update()
+{
+   // const int columnWidth = 330;
+	const int columnWidth = 250;
+    // Query processor for number of channels, types, gains, etc... and update the UI
+    channelComponents.clear();
+    staticLabels.clear();
+
+    RHD2000Thread* thread = (RHD2000Thread*)proc->getThread();
+    ChannelType type;
+
+    // find out which streams are active.
+    bool hsActive[MAX_NUM_HEADSTAGES+1];
+    //bool adcActive = false;
+    int numActiveHeadstages = 0;
+    int hsColumn[MAX_NUM_HEADSTAGES + 1];
+    int numChannelsPerHeadstage[MAX_NUM_HEADSTAGES + 1];
+    chainUpdate = false;
+
+    for (int k = 0; k<MAX_NUM_HEADSTAGES; k++)
+    {
+        if (thread->isHeadstageEnabled(k))
+        {
+            numChannelsPerHeadstage[k] = thread->getActiveChannelsInHeadstage(k);
+            hsActive[k] = true;
+            hsColumn[k] = numActiveHeadstages*columnWidth;
+            numActiveHeadstages++;
+        }
+        else
+        {
+            numChannelsPerHeadstage[k] = 0;
+            hsActive[k] = false;
+            hsColumn[k] = 0;
+        }
+    }
+
+    if (thread->getNumAdcOutputs() > 0)
+    {
+        numChannelsPerHeadstage[MAX_NUM_HEADSTAGES] = thread->getNumAdcOutputs();
+        hsActive[MAX_NUM_HEADSTAGES] = true;
+        hsColumn[MAX_NUM_HEADSTAGES] = numActiveHeadstages*columnWidth;
+        numActiveHeadstages++;
+    }
+    else
+    {
+        numChannelsPerHeadstage[MAX_NUM_HEADSTAGES] = 0;
+        hsActive[MAX_NUM_HEADSTAGES] = false;
+        hsColumn[MAX_NUM_HEADSTAGES] = 0;
+    }
+
+    StringArray streamNames;
+    streamNames.add("Port A1");
+    streamNames.add("Port A2");
+    streamNames.add("Port B1");
+    streamNames.add("Port B2");
+    streamNames.add("Port C1");
+    streamNames.add("Port C2");
+    streamNames.add("Port D1");
+    streamNames.add("Port D2");
+    streamNames.add("ADC");
+
+    for (int k = 0; k < MAX_NUM_HEADSTAGES + 1; k++)
+    {
+        if (hsActive[k])
+        {
+            Label* lbl = new Label(streamNames[k],streamNames[k]);
+            lbl->setEditable(false);
+            lbl->setBounds(10+hsColumn[k],40,columnWidth, 25);
+            lbl->setJustificationType(juce::Justification::centred);
+            lbl->setColour(Label::textColourId,juce::Colours::white);
+            staticLabels.add(lbl);
+            addAndMakeVisible(lbl);
+
+        }
+
+    }
+
+    for (int k = 0; k < MAX_NUM_HEADSTAGES + 1; k++)
+    {
+        if (hsActive[k])
+        {
+            for (int ch = 0; ch < numChannelsPerHeadstage[k]+ (k < MAX_NUM_HEADSTAGES ? 3 : 0); ch++)
+            {
+                int channelGainIndex = 1;
+                int realChan = thread->getChannelFromHeadstage(k, ch);
+                float ch_gain = proc->channels[realChan]->getBitVolts() / proc->getBitVolts(proc->channels[realChan]);
+                for (int j = 0; j < gains.size(); j++)
+                {
+                    if (fabs(gains[j] - ch_gain) < 1e-3)
+                    {
+                        channelGainIndex = j;
+                        break;
+                    }
+                }
+                if (k < MAX_NUM_HEADSTAGES)
+                    type = ch < numChannelsPerHeadstage[k] ? HEADSTAGE_CHANNEL : AUX_CHANNEL;
+                else
+                    type = ADC_CHANNEL;
+
+                FPGAchannelComponent* comp = new FPGAchannelComponent(this, realChan, channelGainIndex + 1, thread->getChannelName(realChan), gains,type);
+                comp->setBounds(10 + hsColumn[k], 70 + ch * 22, columnWidth, 22);
+                comp->setUserDefinedData(k);
+                addAndMakeVisible(comp);
+                channelComponents.add(comp);
+            }
+        }
+    }
+
+
+    StringArray ttlNames;
+    proc->getEventChannelNames(ttlNames);
+    // add buttons for TTL channels
+    for (int k=0; k<ttlNames.size(); k++)
+    {
+        FPGAchannelComponent* comp = new FPGAchannelComponent(this,k, -1, ttlNames[k],gains,EVENT_CHANNEL);
+        comp->setBounds(10+numActiveHeadstages*columnWidth,70+k*22,columnWidth,22);
+        comp->setUserDefinedData(k);
+        addAndMakeVisible(comp);
+        channelComponents.add(comp);
+    }
+
+    Label* lbl = new Label("TTL Events","TTL Events");
+    lbl->setEditable(false);
+    lbl->setBounds(numActiveHeadstages*columnWidth,40,columnWidth, 25);
+    lbl->setJustificationType(juce::Justification::centred);
+    lbl->setColour(Label::textColourId,juce::Colours::white);
+    staticLabels.add(lbl);
+    addAndMakeVisible(lbl);
+
+    chainUpdate = true;
+}
+
+void FPGAchannelList::disableAll()
+{
+    for (int k=0; k<channelComponents.size(); k++)
+    {
+        channelComponents[k]->disableEdit();
+    }
+	impedanceButton->setEnabled(false);
+	saveImpedanceButton->setEnabled(false);
+	autoMeasureButton->setEnabled(false);
+	numberingScheme->setEnabled(false);
+}
+
+void FPGAchannelList::enableAll()
+{
+    for (int k=0; k<channelComponents.size(); k++)
+    {
+        channelComponents[k]->enableEdit();
+    }
+	impedanceButton->setEnabled(true);
+	saveImpedanceButton->setEnabled(true);
+	autoMeasureButton->setEnabled(true);
+	numberingScheme->setEnabled(true);
+}
+
+void FPGAchannelList::setNewGain(int channel, float gain)
+{
+    RHD2000Thread* thread = (RHD2000Thread*)proc->getThread();
+    thread->modifyChannelGain(channel, gain);
+    if (chainUpdate)
+        proc->requestChainUpdate();
+}
+
+void FPGAchannelList::setNewName(int channel, String newName)
+{
+    RHD2000Thread* thread = (RHD2000Thread*)proc->getThread();
+    thread->modifyChannelName(channel, newName);
+    if (chainUpdate)
+        proc->requestChainUpdate();
+}
+
+void FPGAchannelList::updateButtons()
+{
+}
+
+int FPGAchannelList::getNumChannels()
+{
+    return 0;
+}
+
+void FPGAchannelList::comboBoxChanged(ComboBox* b)
+{
+    if (b == numberingScheme)
+    {
+        SourceNode* p = (SourceNode*)proc;
+        RHD2000Thread* thread = (RHD2000Thread*)p->getThread();
+        int scheme = numberingScheme->getSelectedId();
+        thread->setDefaultNamingScheme(scheme);
+        update();
+        p->requestChainUpdate();
+    }
+}
+
+void FPGAchannelList::updateImpedance(Array<int> streams, Array<int> channels, Array<float> magnitude, Array<float> phase)
+{
+	int i = 0;
+    for (int k = 0; k < streams.size(); k++)
+    {
+		if (i >= channelComponents.size())
+			break; //little safety
+
+		if (channelComponents[i]->type != HEADSTAGE_CHANNEL)
+		{
+			k--;
+		}
+		else
+		{
+			channelComponents[i]->setImpedanceValues(magnitude[k], phase[k]);
+		}
+		i++;
+    }
+
+}
+
+
+/****************************************************/
+FPGAchannelComponent::FPGAchannelComponent(FPGAchannelList* cl, int ch, int gainIndex_, String N, Array<float> gains_, ChannelType type_) :
+type(type_), gains(gains_), channelList(cl), channel(ch), name(N), gainIndex(gainIndex_)
+{
+    Font f = Font("Small Text", 13, Font::plain);
+
+    staticLabel = new Label("Channel","Channel");
+    staticLabel->setFont(f);
+    staticLabel->setEditable(false);
+    addAndMakeVisible(staticLabel);
+
+    editName = new Label(name,name);
+    editName->setFont(f);
+    editName->setEditable(true);
+    editName->setColour(Label::backgroundColourId,juce::Colours::lightgrey);
+    editName->addListener(this);
+    addAndMakeVisible(editName);
+/*    if (gainIndex > 0)
+    {
+
+        gainComboBox = new ComboBox("Gains");
+        for (int k=0; k<gains.size(); k++)
+        {
+            if (gains[k] < 1)
+            {
+                gainComboBox->addItem("x"+String(gains[k],2),k+1);
+            }
+            else
+            {
+                gainComboBox->addItem("x"+String((int)gains[k]),k+1);
+            }
+        }
+        gainComboBox->setSelectedId(gainIndex, sendNotificationSync);
+        gainComboBox->addListener(this);
+        addAndMakeVisible(gainComboBox);
+    }
+    else
+    {*/
+        gainComboBox = nullptr;
+    //}
+
+    if (type == HEADSTAGE_CHANNEL)
+    {
+        impedance = new Label("Impedance","? Ohm");
+        impedance->setFont(Font("Default", 13, Font::plain));
+        impedance->setEditable(false);
+        addAndMakeVisible(impedance);
+    }
+    else
+    {
+        impedance = nullptr;
+    }
+}
+FPGAchannelComponent::~FPGAchannelComponent()
+{
+
+}
+
+void FPGAchannelComponent::setImpedanceValues(float mag, float phase)
+{
+    if (impedance != nullptr)
+    {
+        if (mag > 10000)
+            impedance->setText(String(mag/1e6,2)+" mOhm, "+String((int)phase) + " deg",juce::NotificationType::dontSendNotification);
+        else if (mag > 1000)
+            impedance->setText(String(mag/1e3,0)+" kOhm, "+String((int)phase) + " deg" ,juce::NotificationType::dontSendNotification);
+        else
+            impedance->setText(String(mag,0)+" Ohm, "+String((int)phase) + " deg" ,juce::NotificationType::dontSendNotification);
+    }
+    else
+    {
+
+    }
+}
+
+void FPGAchannelComponent::comboBoxChanged(ComboBox* comboBox)
+{
+    if (comboBox == gainComboBox)
+    {
+        int newGainIndex = gainComboBox->getSelectedId();
+        float mult = gains[newGainIndex-1];
+        float bitvolts = channelList->proc->getBitVolts(channelList->proc->channels[channel]);
+        channelList->setNewGain(channel, mult*bitvolts);
+    }
+}
+void FPGAchannelComponent::labelTextChanged(Label* lbl)
+{
+    // channel name change
+    String newName = lbl->getText();
+    channelList->setNewName(channel, newName);
+}
+
+void FPGAchannelComponent::disableEdit()
+{
+    editName->setEnabled(false);
+}
+
+void FPGAchannelComponent::enableEdit()
+{
+    editName->setEnabled(true);
+}
+
+void FPGAchannelComponent::buttonClicked(Button* btn)
+{
+}
+
+void FPGAchannelComponent::setUserDefinedData(int d)
+{
+}
+
+int FPGAchannelComponent::getUserDefinedData()
+{
+    return 0;
+}
+
+void FPGAchannelComponent::resized()
+{
+    editName->setBounds(0,0,90,20);
+    if (gainComboBox != nullptr)
+    {
+        gainComboBox->setBounds(100,0,70,20);
+    }
+    if (impedance != nullptr)
+    {
+       // impedance->setBounds(180,0,130,20);
+		impedance->setBounds(100, 0, 130, 20);
+    }
+
+}
+
+
+
+/**********************************************/
+
+FPGAcanvas::FPGAcanvas(GenericProcessor* n)
+{
+    processor = (SourceNode*)n;
+    channelsViewport = new Viewport();
+    channelList = new FPGAchannelList(processor, channelsViewport, this);
+    channelsViewport->setViewedComponent(channelList, false);
+    channelsViewport->setScrollBarsShown(true, true);
+    addAndMakeVisible(channelsViewport);
+
+    resized();
+    update();
+}
+
+FPGAcanvas::~FPGAcanvas()
+{
+}
+
+void FPGAcanvas::setParameter(int x, float f)
+{
+
+}
+
+void FPGAcanvas::setParameter(int a, int b, int c, float d)
+{
+}
+
+void FPGAcanvas::paint(Graphics& g)
+{
+    g.fillAll(Colours::grey);
+
+}
+
+void FPGAcanvas::refresh()
+{
+    repaint();
+}
+
+void FPGAcanvas::refreshState()
+{
+    resized();
+}
+
+
+void FPGAcanvas::beginAnimation()
+{
+}
+
+void FPGAcanvas::endAnimation()
+{
+}
+
+void FPGAcanvas::update()
+{
+    // create channel buttons (name, gain, recording, impedance, ... ?)
+    channelList->update();
+	if (static_cast<RHD2000Thread*>(processor->getThread())->isAcquisitionActive())
+	{
+		channelList->disableAll();
+	}
+}
+
+void FPGAcanvas::resized()
+{
+    //int screenWidth = getWidth();
+    //int screenHeight = getHeight();
+
+    int scrollBarThickness = channelsViewport->getScrollBarThickness();
+    int numChannels = 35; // max channels per stream? (32+3)*2
+
+    channelsViewport->setBounds(0,0,getWidth(),getHeight());
+    channelList->setBounds(0,0,getWidth()-scrollBarThickness, 200+22*numChannels);
+}
+
+void FPGAcanvas::buttonClicked(Button* button)
+{
+}
+
+void FPGAcanvas::updateImpedance(Array<int> streams, Array<int> channels, Array<float> magnitude, Array<float> phase)
+{
+    channelList->updateImpedance(streams, channels,  magnitude, phase);
+}
+
+/***********************************************************************/
+
+RHD2000Editor::RHD2000Editor(GenericProcessor* parentNode,
+                             RHD2000Thread* board_,
+                             bool useDefaultParameterEditors
+                            )
+    : VisualizerEditor(parentNode, useDefaultParameterEditors), board(board_)
+{
+    canvas = nullptr;
+    desiredWidth = 340;
+    tabText = "FPGA";
+    measureWhenRecording = false;
+    saveImpedances = false;
+
+	impedanceData = new ImpedanceData();
+	impedanceData->valid = false;
+
+    // add headstage-specific controls (currently just an enable/disable button)
+    for (int i = 0; i < 4; i++)
+    {
+        HeadstageOptionsInterface* hsOptions = new HeadstageOptionsInterface(board, this, i);
+        headstageOptionsInterfaces.add(hsOptions);
+        addAndMakeVisible(hsOptions);
+        hsOptions->setBounds(3, 28+i*20, 70, 18);
+    }
+
+    // add sample rate selection
+    sampleRateInterface = new SampleRateInterface(board, this);
+    addAndMakeVisible(sampleRateInterface);
+    sampleRateInterface->setBounds(80, 25, 110, 50);
+
+    // add Bandwidth selection
+    bandwidthInterface = new BandwidthInterface(board, this);
+    addAndMakeVisible(bandwidthInterface);
+    bandwidthInterface->setBounds(80, 58, 80, 50);
+
+    // add DSP selection
+  //  dspInterface = new DSPInterface(board, this);
+  //  addAndMakeVisible(dspInterface);
+  //  dspInterface->setBounds(80, 58, 80, 50);
+
+    // add rescan button
+    rescanButton = new UtilityButton("RESCAN", Font("Small Text", 13, Font::plain));
+    rescanButton->setRadius(3.0f);
+    rescanButton->setBounds(6, 108,65,18);
+    rescanButton->addListener(this);
+    rescanButton->setTooltip("Check for connected headstages");
+    addAndMakeVisible(rescanButton);
+
+    for (int i = 0; i < 2; i++)
+    {
+        ElectrodeButton* button = new ElectrodeButton(-1);
+        electrodeButtons.add(button);
+
+        button->setBounds(200+i*25, 40, 25, 15);
+        button->setChannelNum(-1);
+        button->setToggleState(false, dontSendNotification);
+        button->setRadioGroupId(999);
+
+        addAndMakeVisible(button);
+        button->addListener(this);
+
+        if (i == 0)
+        {
+            button->setTooltip("Audio monitor left channel");
+        }
+        else
+        {
+            button->setTooltip("Audio monitor right channel");
+        }
+    }
+
+    audioLabel = new Label("audio label", "Audio out");
+    audioLabel->setBounds(190,25,75,15);
+    audioLabel->setFont(Font("Small Text", 10, Font::plain));
+    audioLabel->setColour(Label::textColourId, Colours::darkgrey);
+    addAndMakeVisible(audioLabel);
+
+    // add HW audio parameter selection
+    audioInterface = new AudioInterface(board, this);
+    addAndMakeVisible(audioInterface);
+    audioInterface->setBounds(179, 58, 70, 50);
+
+    clockInterface = new ClockDivideInterface(board, this);
+    addAndMakeVisible(clockInterface);
+    clockInterface->setBounds(179, 82, 70, 50);
+
+    adcButton = new UtilityButton("ADC 1-8", Font("Small Text", 13, Font::plain));
+    adcButton->setRadius(3.0f);
+    adcButton->setBounds(179,108,70,18);
+    adcButton->addListener(this);
+    adcButton->setClickingTogglesState(true);
+    adcButton->setTooltip("Enable/disable ADC channels");
+    addAndMakeVisible(adcButton);
+
+	ledButton = new UtilityButton("LED", Font("Very Small Text", 13, Font::plain));
+	ledButton->setRadius(3.0f);
+	ledButton->setBounds(140, 108, 30, 18);
+	ledButton->addListener(this);
+	ledButton->setClickingTogglesState(true);
+	ledButton->setTooltip("Enable/disable board LEDs");
+	addAndMakeVisible(ledButton);
+	ledButton->setToggleState(true, dontSendNotification);
+
+    // add DSP Offset Button
+    dspoffsetButton = new UtilityButton("DSP", Font("Very Small Text", 13, Font::plain));
+    dspoffsetButton->setRadius(3.0f); // sets the radius of the button's corners
+    dspoffsetButton->setBounds(80, 108,30,18); // sets the x position, y position, width, and height of the button
+    dspoffsetButton->addListener(this);
+    dspoffsetButton->setClickingTogglesState(true); // makes the button toggle its state when clicked
+    dspoffsetButton->setTooltip("Enable/disable DSP offset removal");
+    addAndMakeVisible(dspoffsetButton); // makes the button a child component of the editor and makes it visible
+    dspoffsetButton->setToggleState(true, dontSendNotification);
+
+    // add DSP Frequency Selection field
+    dspInterface = new DSPInterface(board, this);
+    addAndMakeVisible(dspInterface);
+    dspInterface->setBounds(110, 108, 30, 50);
+
+    ttlSettleLabel = new Label("TTL Settle","TTL Settle");
+    ttlSettleLabel->setFont(Font("Small Text", 11, Font::plain));
+    ttlSettleLabel->setBounds(255,80,100,20);
+    ttlSettleLabel->setColour(Label::textColourId, Colours::darkgrey);
+    addAndMakeVisible(ttlSettleLabel);
+
+
+    ttlSettleCombo = new ComboBox("FastSettleComboBox");
+    ttlSettleCombo->setBounds(260,100,60,18);
+    ttlSettleCombo->addListener(this);
+    ttlSettleCombo->addItem("-",1);
+    for (int k=0; k<8; k++)
+    {
+        ttlSettleCombo->addItem("TTL"+String(1+k),2+k);
+    }
+    ttlSettleCombo->setSelectedId(1, sendNotification);
+    addAndMakeVisible(ttlSettleCombo);
+
+    dacTTLButton = new UtilityButton("DAC TTL", Font("Small Text", 13, Font::plain));
+    dacTTLButton->setRadius(3.0f);
+    dacTTLButton->setBounds(260,25,65,18);
+    dacTTLButton->addListener(this);
+    dacTTLButton->setClickingTogglesState(true);
+    dacTTLButton->setTooltip("Enable/disable DAC Threshold TTL Output");
+    addAndMakeVisible(dacTTLButton);
+
+    dacHPFlabel = new Label("DAC HPF","DAC HPF");
+    dacHPFlabel->setFont(Font("Small Text", 11, Font::plain));
+    dacHPFlabel->setBounds(260,42,100,20);
+    dacHPFlabel->setColour(Label::textColourId, Colours::darkgrey);
+    addAndMakeVisible(dacHPFlabel);
+
+    dacHPFcombo = new ComboBox("dacHPFCombo");
+    dacHPFcombo->setBounds(260,60,60,18);
+    dacHPFcombo->addListener(this);
+    dacHPFcombo->addItem("OFF",1);
+    int HPFvalues[10] = {50,100,200,300,400,500,600,700,800,900};
+    for (int k=0; k<10; k++)
+    {
+        dacHPFcombo->addItem(String(HPFvalues[k])+" Hz",2+k);
+    }
+    dacHPFcombo->setSelectedId(1, sendNotification);
+    addAndMakeVisible(dacHPFcombo);
+
+}
+
+RHD2000Editor::~RHD2000Editor()
+{
+
+}
+
+void RHD2000Editor::scanPorts()
+{
+    rescanButton->triggerClick();
+}
+
+void RHD2000Editor::measureImpedance()
+{
+	impedanceData->valid = false;
+	board->runImpedanceTest(impedanceData);
+}
+
+void RHD2000Editor::handleAsyncUpdate()
+{
+	if (!impedanceData->valid)
+		return;
+    if (canvas == nullptr)
+        VisualizerEditor::canvas = createNewCanvas();
+    // update components...
+	canvas->updateImpedance(impedanceData->streams, impedanceData->channels, impedanceData->magnitudes, impedanceData->phases);
+    if (saveImpedances)
+    {
+		CoreServices::RecordNode::createNewrecordingDir();
+
+		String path(CoreServices::RecordNode::getRecordingPath().getFullPathName()
+                    + File::separatorString + "impedance_measurement.xml");
+        std::cout << "Saving impedance measurements in " << path << "\n";
+        File file(path);
+
+        if (!file.getParentDirectory().exists())
+            file.getParentDirectory().createDirectory();
+
+        XmlDocument doc(file);
+        ScopedPointer<XmlElement> xml = new XmlElement("CHANNEL_IMPEDANCES");
+		for (int i = 0; i < impedanceData->channels.size(); i++)
+        {
+            XmlElement* chan = new XmlElement("CHANNEL");
+            chan->setAttribute("name",board->getChannelName(i));
+			chan->setAttribute("stream", impedanceData->streams[i]);
+			chan->setAttribute("channel_number", impedanceData->channels[i]);
+			chan->setAttribute("magnitude", impedanceData->magnitudes[i]);
+			chan->setAttribute("phase", impedanceData->phases[i]);
+            xml->addChildElement(chan);
+        }
+        xml->writeToFile(file,String::empty);
+    }
+
+}
+
+void RHD2000Editor::setSaveImpedance(bool en)
+{
+    saveImpedances = en;
+}
+
+void RHD2000Editor::setAutoMeasureImpedance(bool en)
+{
+    measureWhenRecording = en;
+}
+
+bool RHD2000Editor::getSaveImpedance()
+{
+    return saveImpedances;
+}
+
+bool RHD2000Editor::getAutoMeasureImpedance()
+{
+    return measureWhenRecording;
+}
+
+void RHD2000Editor::comboBoxChanged(ComboBox* comboBox)
+{
+    if (comboBox == ttlSettleCombo)
+    {
+        int selectedChannel = ttlSettleCombo->getSelectedId();
+        if (selectedChannel == 1)
+        {
+            board->setFastTTLSettle(false,0);
+        }
+        else
+        {
+            board->setFastTTLSettle(true,selectedChannel-2);
+        }
+    }
+    else if (comboBox == dacHPFcombo)
+    {
+        int selection = dacHPFcombo->getSelectedId();
+        if (selection == 1)
+        {
+            board->setDAChpf(100,false);
+        }
+        else
+        {
+            int HPFvalues[10] = {50,100,200,300,400,500,600,700,800,900};
+            board->setDAChpf(HPFvalues[selection-2],true);
+        }
+    }
+}
+
+
+void RHD2000Editor::buttonEvent(Button* button)
+{
+    if (button == rescanButton && !acquisitionIsActive)
+    {
+        board->scanPorts();
+
+        for (int i = 0; i < 4; i++)
+        {
+            headstageOptionsInterfaces[i]->checkEnabledState();
+        }
+        // board->updateChannelNames();
+		CoreServices::updateSignalChain(this);
+    }
+    else if (button == electrodeButtons[0])
+    {
+        channelSelector->setRadioStatus(true);
+    }
+    else if (button == electrodeButtons[1])
+    {
+        channelSelector->setRadioStatus(true);
+    }
+    else if (button == adcButton && !acquisitionIsActive)
+    {
+        board->enableAdcs(button->getToggleState());
+        //        board->updateChannelNames();
+        std::cout << "ADC Button toggled" << "\n";
+		CoreServices::updateSignalChain(this);
+        std::cout << "Editor visible." << "\n";
+    }
+    else if (button == dacTTLButton)
+    {
+        board->setTTLoutputMode(dacTTLButton->getToggleState());
+    }
+    else if (button == dspoffsetButton && !acquisitionIsActive)
+    {
+        std::cout << "DSP offset " << button->getToggleState() << "\n";
+        board->setDSPOffset(button->getToggleState());
+    }
+	else if (button == ledButton)
+	{
+		board->enableBoardLeds(button->getToggleState());
+	}
+	else
+	{
+		VisualizerEditor::buttonEvent(button);
+	}
+
+}
+
+void RHD2000Editor::channelChanged (int channel, bool /*newState*/)
+{
+    for (int i = 0; i < 2; i++)
+    {
+        if (electrodeButtons[i]->getToggleState())
+        {
+            electrodeButtons[i]->setChannelNum (channel);
+            electrodeButtons[i]->repaint();
+            board->setDACchannel (i, channel);
+        }
+    }
+}
+
+void RHD2000Editor::startAcquisition()
+{
+    if (measureWhenRecording)
+        measureImpedance();
+
+    channelSelector->startAcquisition();
+
+    rescanButton->setEnabledState(false);
+    adcButton->setEnabledState(false);
+    dspoffsetButton-> setEnabledState(false);
+    acquisitionIsActive = true;
+	if (canvas != nullptr)
+		canvas->channelList->disableAll();
+        //canvas->channelList->setEnabled(false);
+}
+
+void RHD2000Editor::stopAcquisition()
+{
+
+    channelSelector->stopAcquisition();
+
+    rescanButton->setEnabledState(true);
+    adcButton->setEnabledState(true);
+    dspoffsetButton-> setEnabledState(true);
+
+    acquisitionIsActive = false;
+	if (canvas != nullptr)
+		canvas->channelList->enableAll();
+    //  canvas->channelList->setEnabled(true);
+}
+
+void RHD2000Editor::saveCustomParameters(XmlElement* xml)
+{
+    xml->setAttribute("SampleRate", sampleRateInterface->getSelectedId());
+    xml->setAttribute("LowCut", bandwidthInterface->getLowerBandwidth());
+    xml->setAttribute("HighCut", bandwidthInterface->getUpperBandwidth());
+    xml->setAttribute("ADCsOn", adcButton->getToggleState());
+    xml->setAttribute("SampleRate", sampleRateInterface->getSelectedId());
+    xml->setAttribute("LowCut", bandwidthInterface->getLowerBandwidth());
+    xml->setAttribute("HighCut", bandwidthInterface->getUpperBandwidth());
+    xml->setAttribute("ADCsOn", adcButton->getToggleState());
+    xml->setAttribute("AudioOutputL", electrodeButtons[0]->getChannelNum());
+    xml->setAttribute("AudioOutputR", electrodeButtons[1]->getChannelNum());
+    xml->setAttribute("NoiseSlicer", audioInterface->getNoiseSlicerLevel());
+    xml->setAttribute("TTLFastSettle", ttlSettleCombo->getSelectedId());
+    xml->setAttribute("DAC_TTL", dacTTLButton->getToggleState());
+    xml->setAttribute("DAC_HPF", dacHPFcombo->getSelectedId());
+    xml->setAttribute("DSPOffset", dspoffsetButton->getToggleState());
+    xml->setAttribute("DSPCutoffFreq", dspInterface->getDspCutoffFreq());
+    xml->setAttribute("save_impedance_measurements",saveImpedances);
+    xml->setAttribute("auto_measure_impedances",measureWhenRecording);
+	xml->setAttribute("LEDs", ledButton->getToggleState());
+	xml->setAttribute("ClockDivideRatio", clockInterface->getClockDivideRatio());
+}
+
+void RHD2000Editor::loadCustomParameters(XmlElement* xml)
+{
+
+    sampleRateInterface->setSelectedId(xml->getIntAttribute("SampleRate"));
+    bandwidthInterface->setLowerBandwidth(xml->getDoubleAttribute("LowCut"));
+    bandwidthInterface->setUpperBandwidth(xml->getDoubleAttribute("HighCut"));
+    adcButton->setToggleState(xml->getBoolAttribute("ADCsOn"), sendNotification);
+    //electrodeButtons[0]->setChannelNum(xml->getIntAttribute("AudioOutputL"));
+    //board->assignAudioOut(0, xml->getIntAttribute("AudioOutputL"));
+    //electrodeButtons[1]->setChannelNum(xml->getIntAttribute("AudioOutputR"));
+    //board->assignAudioOut(1, xml->getIntAttribute("AudioOutputR"));
+    audioInterface->setNoiseSlicerLevel(xml->getIntAttribute("NoiseSlicer"));
+    ttlSettleCombo->setSelectedId(xml->getIntAttribute("TTLFastSettle"));
+    dacTTLButton->setToggleState(xml->getBoolAttribute("DAC_TTL"), sendNotification);
+    dacHPFcombo->setSelectedId(xml->getIntAttribute("DAC_HPF"));
+    dspoffsetButton->setToggleState(xml->getBoolAttribute("DSPOffset"), sendNotification);
+    dspInterface->setDspCutoffFreq(xml->getDoubleAttribute("DSPCutoffFreq"));
+    saveImpedances = xml->getBoolAttribute("save_impedance_measurements");
+    measureWhenRecording = xml->getBoolAttribute("auto_measure_impedances");
+	ledButton->setToggleState(xml->getBoolAttribute("LEDs", true),sendNotification);
+    clockInterface->setClockDivideRatio(xml->getIntAttribute("ClockDivideRatio")); 
+}
+
+
+Visualizer* RHD2000Editor::createNewCanvas()
+{
+    GenericProcessor* processor = (GenericProcessor*) getProcessor();
+    canvas= new FPGAcanvas(processor);
+    //ActionListener* listener = (ActionListener*) canvas;
+    //getUIComponent()->registerAnimatedComponent(listener);
+    return canvas;
+}
+
+// Bandwidth Options --------------------------------------------------------------------
+
+BandwidthInterface::BandwidthInterface(RHD2000Thread* board_,
+                                       RHD2000Editor* editor_) :
+    board(board_), editor(editor_)
+{
+
+    name = "Bandwidth";
+
+    lastHighCutString = "7500";
+    lastLowCutString = "1";
+
+    actualUpperBandwidth = 7500.0f;
+    actualLowerBandwidth = 1.0f;
+
+    upperBandwidthSelection = new Label("UpperBandwidth",lastHighCutString); // this is currently set in RHD2000Thread, the cleaner would be to set it here again
+    upperBandwidthSelection->setEditable(true,false,false);
+    upperBandwidthSelection->addListener(this);
+    upperBandwidthSelection->setBounds(30,30,60,20);
+    upperBandwidthSelection->setColour(Label::textColourId, Colours::darkgrey);
+    addAndMakeVisible(upperBandwidthSelection);
+
+
+    lowerBandwidthSelection = new Label("LowerBandwidth",lastLowCutString);
+    lowerBandwidthSelection->setEditable(true,false,false);
+    lowerBandwidthSelection->addListener(this);
+    lowerBandwidthSelection->setBounds(25,10,60,20);
+    lowerBandwidthSelection->setColour(Label::textColourId, Colours::darkgrey);
+
+    addAndMakeVisible(lowerBandwidthSelection);
+
+
+
+}
+
+BandwidthInterface::~BandwidthInterface()
+{
+
+}
+
+
+void BandwidthInterface::labelTextChanged(Label* label)
+{
+
+    if (!(editor->acquisitionIsActive) && board->foundInputSource())
+    {
+        if (label == upperBandwidthSelection)
+        {
+
+            Value val = label->getTextValue();
+            double requestedValue = double(val.getValue());
+
+            if (requestedValue < 100.0 || requestedValue > 20000.0 || requestedValue < lastLowCutString.getFloatValue())
+            {
+                CoreServices::sendStatusMessage("Value out of range.");
+
+                label->setText(lastHighCutString, dontSendNotification);
+
+                return;
+            }
+
+            actualUpperBandwidth = board->setUpperBandwidth(requestedValue);
+
+            std::cout << "Setting Upper Bandwidth to " << requestedValue << "\n";
+            std::cout << "Actual Upper Bandwidth:  " <<  actualUpperBandwidth  << "\n";
+            label->setText(String(round(actualUpperBandwidth*10.f)/10.f), dontSendNotification);
+
+        }
+        else
+        {
+
+            Value val = label->getTextValue();
+            double requestedValue = double(val.getValue());
+
+            if (requestedValue < 0.1 || requestedValue > 500.0 || requestedValue > lastHighCutString.getFloatValue())
+            {
+				CoreServices::sendStatusMessage("Value out of range.");
+
+                label->setText(lastLowCutString, dontSendNotification);
+
+                return;
+            }
+
+            actualLowerBandwidth = board->setLowerBandwidth(requestedValue);
+
+            std::cout << "Setting Lower Bandwidth to " << requestedValue << "\n";
+            std::cout << "Actual Lower Bandwidth:  " <<  actualLowerBandwidth  << "\n";
+
+            label->setText(String(round(actualLowerBandwidth*10.f)/10.f), dontSendNotification);
+        }
+    }
+    else if (editor->acquisitionIsActive)
+    {
+		CoreServices::sendStatusMessage("Can't change bandwidth while acquisition is active!");
+        if (label == upperBandwidthSelection)
+            label->setText(lastHighCutString, dontSendNotification);
+        else
+            label->setText(lastLowCutString, dontSendNotification);
+        return;
+    }
+
+}
+
+void BandwidthInterface::setLowerBandwidth(double value)
+{
+    actualLowerBandwidth = board->setLowerBandwidth(value);
+    lowerBandwidthSelection->setText(String(round(actualLowerBandwidth*10.f)/10.f), dontSendNotification);
+}
+
+void BandwidthInterface::setUpperBandwidth(double value)
+{
+    actualUpperBandwidth = board->setUpperBandwidth(value);
+    upperBandwidthSelection->setText(String(round(actualUpperBandwidth*10.f)/10.f), dontSendNotification);
+}
+
+double BandwidthInterface::getLowerBandwidth()
+{
+    return actualLowerBandwidth;
+}
+
+double BandwidthInterface::getUpperBandwidth()
+{
+    return actualUpperBandwidth;
+}
+
+
+void BandwidthInterface::paint(Graphics& g)
+{
+
+    g.setColour(Colours::darkgrey);
+
+    g.setFont(Font("Small Text",10,Font::plain));
+
+    g.drawText(name, 0, 0, 200, 15, Justification::left, false);
+
+    g.drawText("Low: ", 0, 10, 200, 20, Justification::left, false);
+
+    g.drawText("High: ", 0, 30, 200, 20, Justification::left, false);
+
+}
+
+// Sample rate Options --------------------------------------------------------------------
+
+SampleRateInterface::SampleRateInterface(RHD2000Thread* board_,
+                                         RHD2000Editor* editor_) :
+    board(board_), editor(editor_)
+{
+
+    name = "Sample Rate";
+
+    sampleRateOptions.add("1.00 kS/s");
+    sampleRateOptions.add("1.25 kS/s");
+    sampleRateOptions.add("1.50 kS/s");
+    sampleRateOptions.add("2.00 kS/s");
+    sampleRateOptions.add("2.50 kS/s");
+    sampleRateOptions.add("3.00 kS/s");
+    sampleRateOptions.add("3.33 kS/s");
+    sampleRateOptions.add("4.00 kS/s");
+    sampleRateOptions.add("5.00 kS/s");
+    sampleRateOptions.add("6.25 kS/s");
+    sampleRateOptions.add("8.00 kS/s");
+    sampleRateOptions.add("10.0 kS/s");
+    sampleRateOptions.add("12.5 kS/s");
+    sampleRateOptions.add("15.0 kS/s");
+    sampleRateOptions.add("20.0 kS/s");
+    sampleRateOptions.add("25.0 kS/s");
+    sampleRateOptions.add("30.0 kS/s");
+
+
+    rateSelection = new ComboBox("Sample Rate");
+    rateSelection->addItemList(sampleRateOptions, 1);
+    rateSelection->setSelectedId(17, dontSendNotification);
+    rateSelection->addListener(this);
+
+    rateSelection->setBounds(0,15,300,20);
+    addAndMakeVisible(rateSelection);
+
+
+}
+
+SampleRateInterface::~SampleRateInterface()
+{
+
+}
+
+void SampleRateInterface::comboBoxChanged(ComboBox* cb)
+{
+    if (!(editor->acquisitionIsActive) && board->foundInputSource())
+    {
+        if (cb == rateSelection)
+        {
+            board->setSampleRate(cb->getSelectedId()-1);
+
+            std::cout << "Setting sample rate to index " << cb->getSelectedId()-1 << "\n";
+
+			CoreServices::updateSignalChain(editor);
+        }
+    }
+}
+
+int SampleRateInterface::getSelectedId()
+{
+    return rateSelection->getSelectedId();
+}
+
+void SampleRateInterface::setSelectedId(int id)
+{
+    rateSelection->setSelectedId(id);
+}
+
+
+void SampleRateInterface::paint(Graphics& g)
+{
+
+    g.setColour(Colours::darkgrey);
+
+    g.setFont(Font("Small Text",10,Font::plain));
+
+    g.drawText(name, 0, 0, 200, 15, Justification::left, false);
+
+}
+
+
+// Headstage Options --------------------------------------------------------------------
+
+HeadstageOptionsInterface::HeadstageOptionsInterface(RHD2000Thread* board_,
+                                                     RHD2000Editor* editor_,
+                                                     int hsNum) :
+    isEnabled(false), board(board_), editor(editor_)
+{
+
+    switch (hsNum)
+    {
+        case 0 :
+            name = "A";
+            break;
+        case 1:
+            name = "B";
+            break;
+        case 2:
+            name = "C";
+            break;
+        case 3:
+            name = "D";
+            break;
+        default:
+            name = "X";
+    }
+
+    hsNumber1 = hsNum*2; // data stream 1
+    hsNumber2 = hsNumber1+1; // data stream 2
+
+    channelsOnHs1 = 0;
+    channelsOnHs2 = 0;
+
+
+
+    hsButton1 = new UtilityButton(" ", Font("Small Text", 13, Font::plain));
+    hsButton1->setRadius(3.0f);
+    hsButton1->setBounds(23,1,20,17);
+    hsButton1->setEnabledState(false);
+    hsButton1->setCorners(true, false, true, false);
+    hsButton1->addListener(this);
+    addAndMakeVisible(hsButton1);
+
+    hsButton2 = new UtilityButton(" ", Font("Small Text", 13, Font::plain));
+    hsButton2->setRadius(3.0f);
+    hsButton2->setBounds(43,1,20,17);
+    hsButton2->setEnabledState(false);
+    hsButton2->setCorners(false, true, false, true);
+    hsButton2->addListener(this);
+    addAndMakeVisible(hsButton2);
+
+    checkEnabledState();
+}
+
+HeadstageOptionsInterface::~HeadstageOptionsInterface()
+{
+
+}
+
+void HeadstageOptionsInterface::checkEnabledState()
+{
+    isEnabled = (board->isHeadstageEnabled(hsNumber1) ||
+                 board->isHeadstageEnabled(hsNumber2));
+
+    if (board->isHeadstageEnabled(hsNumber1))
+    {
+        channelsOnHs1 = board->getActiveChannelsInHeadstage(hsNumber1);
+        hsButton1->setLabel(String(channelsOnHs1));
+        hsButton1->setEnabledState(true);
+    }
+    else
+    {
+        channelsOnHs1 = 0;
+        hsButton1->setLabel(" ");
+        hsButton1->setEnabledState(false);
+    }
+
+    if (board->isHeadstageEnabled(hsNumber2))
+    {
+        channelsOnHs2 = board->getActiveChannelsInHeadstage(hsNumber2);
+        hsButton2->setLabel(String(channelsOnHs2));
+        hsButton2->setEnabledState(true);
+    }
+    else
+    {
+        channelsOnHs2 = 0;
+        hsButton2->setLabel(" ");
+        hsButton2->setEnabledState(false);
+    }
+
+    repaint();
+
+}
+
+void HeadstageOptionsInterface::buttonClicked(Button* button)
+{
+
+    if (!(editor->acquisitionIsActive) && board->foundInputSource())
+    {
+
+        //std::cout << "Acquisition is not active" << "\n";
+        if ((button == hsButton1) && (board->getChannelsInHeadstage(hsNumber1) == 32))
+        {
+            if (channelsOnHs1 == 32)
+                channelsOnHs1 = 16;
+            else
+                channelsOnHs1 = 32;
+
+            //std::cout << "HS1 has " << channelsOnHs1 << " channels." << "\n";
+
+            hsButton1->setLabel(String(channelsOnHs1));
+            board->setNumChannels(hsNumber1, channelsOnHs1);
+
+            //board->updateChannels();
+            editor->updateSettings();
+
+        }
+        else if ((button == hsButton2) && (board->getChannelsInHeadstage(hsNumber2) == 32))
+        {
+            if (channelsOnHs2 == 32)
+                channelsOnHs2 = 16;
+            else
+                channelsOnHs2 = 32;
+
+            hsButton2->setLabel(String(channelsOnHs2));
+            board->setNumChannels(hsNumber2, channelsOnHs2);
+            //board->updateChannels();
+            editor->updateSettings();
+        }
+
+		CoreServices::updateSignalChain(editor);
+    }
+
+}
+
+
+void HeadstageOptionsInterface::paint(Graphics& g)
+{
+    g.setColour(Colours::lightgrey);
+
+    g.fillRoundedRectangle(5,0,getWidth()-10,getHeight(),4.0f);
+
+    if (isEnabled)
+        g.setColour(Colours::black);
+    else
+        g.setColour(Colours::grey);
+
+    g.setFont(Font("Small Text",15,Font::plain));
+
+    g.drawText(name, 8, 2, 200, 15, Justification::left, false);
+
+}
+
+
+// (Direct OpalKelly) Audio Options --------------------------------------------------------------------
+
+AudioInterface::AudioInterface(RHD2000Thread* board_,
+                               RHD2000Editor* editor_) :
+    board(board_), editor(editor_)
+{
+
+    name = "Noise Slicer";
+
+    lastNoiseSlicerString = "0";
+
+    actualNoiseSlicerLevel = 0.0f;
+
+    noiseSlicerLevelSelection = new Label("Noise Slicer",lastNoiseSlicerString); // this is currently set in RHD2000Thread, the cleaner would be to set it here again
+    noiseSlicerLevelSelection->setEditable(true,false,false);
+    noiseSlicerLevelSelection->addListener(this);
+    noiseSlicerLevelSelection->setBounds(30,10,30,20);
+    noiseSlicerLevelSelection->setColour(Label::textColourId, Colours::darkgrey);
+    addAndMakeVisible(noiseSlicerLevelSelection);
+
+
+}
+
+AudioInterface::~AudioInterface()
+{
+
+}
+
+
+void AudioInterface::labelTextChanged(Label* label)
+{
+    if (board->foundInputSource())
+    {
+        if (label == noiseSlicerLevelSelection)
+        {
+
+            Value val = label->getTextValue();
+            int requestedValue = int(val.getValue()); // Note that it might be nice to translate to actual uV levels (16*value)
+
+            if (requestedValue < 0 || requestedValue > 127)
+            {
+				CoreServices::sendStatusMessage("Value out of range.");
+
+                label->setText(lastNoiseSlicerString, dontSendNotification);
+
+                return;
+            }
+
+            actualNoiseSlicerLevel = board->setNoiseSlicerLevel(requestedValue);
+
+            std::cout << "Setting Noise Slicer Level to " << requestedValue << "\n";
+            label->setText(String((roundFloatToInt)(actualNoiseSlicerLevel)), dontSendNotification);
+
+        }
+    }
+    else
+    {
+        Value val = label->getTextValue();
+        int requestedValue = int(val.getValue()); // Note that it might be nice to translate to actual uV levels (16*value)
+        if (requestedValue < 0 || requestedValue > 127)
+        {
+			CoreServices::sendStatusMessage("Value out of range.");
+            label->setText(lastNoiseSlicerString, dontSendNotification);
+            return;
+        }
+    }
+}
+
+void AudioInterface::setNoiseSlicerLevel(int value)
+{
+    actualNoiseSlicerLevel = board->setNoiseSlicerLevel(value);
+    noiseSlicerLevelSelection->setText(String(roundFloatToInt(actualNoiseSlicerLevel)), dontSendNotification);
+}
+
+int AudioInterface::getNoiseSlicerLevel()
+{
+    return actualNoiseSlicerLevel;
+}
+
+
+void AudioInterface::paint(Graphics& g)
+{
+
+    g.setColour(Colours::darkgrey);
+    g.setFont(Font("Small Text",9,Font::plain));
+    g.drawText(name, 0, 0, 200, 15, Justification::left, false);
+    g.drawText("Level: ", 0, 10, 200, 20, Justification::left, false);
+}
+
+
+// Clock Divider options
+ClockDivideInterface::ClockDivideInterface(RHD2000Thread* board_,
+                                           RHD2000Editor* editor_) :
+   name("Clock Divider")
+ , lastDivideRatioString("1")
+ , board(board_)
+ , editor(editor_)
+ , actualDivideRatio(1)
+ 
+{
+    divideRatioSelection = new Label("Clock Divide", lastDivideRatioString); 
+    divideRatioSelection->setEditable(true,false,false);
+    divideRatioSelection->addListener(this);
+    divideRatioSelection->setBounds(30,10,30,20);
+    divideRatioSelection->setColour(Label::textColourId, Colours::darkgrey);
+    addAndMakeVisible(divideRatioSelection);
+}
+
+void ClockDivideInterface::labelTextChanged(Label* label)
+{
+    if (board->foundInputSource())
+    {
+        if (label == divideRatioSelection)
+        {
+            Value val = label->getTextValue();
+            int requestedValue = int(val.getValue()); 
+
+            if (requestedValue < 1 || requestedValue > 65534)
+            {
+				CoreServices::sendStatusMessage("Value must be between 1 and 65534.");
+                label->setText(lastDivideRatioString, dontSendNotification);
+                return;
+            }
+
+            actualDivideRatio = board->setClockDivider(requestedValue);
+            lastDivideRatioString = String(actualDivideRatio);
+
+            std::cout << "Setting clock divide ratio to " << actualDivideRatio << "\n";
+            label->setText(lastDivideRatioString, dontSendNotification);
+        }
+    }
+}
+
+void ClockDivideInterface::setClockDivideRatio(int value)
+{
+    actualDivideRatio = board->setClockDivider(value);
+    divideRatioSelection->setText(String(actualDivideRatio), dontSendNotification);
+}
+
+void ClockDivideInterface::paint(Graphics& g)
+{
+
+    g.setColour(Colours::darkgrey);
+    g.setFont(Font("Small Text",9,Font::plain));
+    g.drawText(name, 0, 0, 200, 15, Justification::left, false);
+    g.drawText("Ratio: ", 0, 10, 200, 20, Justification::left, false);
+}
+
+// DSP Options --------------------------------------------------------------------
+
+DSPInterface::DSPInterface(RHD2000Thread* board_,
+                           RHD2000Editor* editor_) :
+    board(board_), editor(editor_)
+{
+    name = "DSP";
+
+    dspOffsetSelection = new Label("DspOffsetSelection",String(round(board->getDspCutoffFreq()*10.f)/10.f));
+    dspOffsetSelection->setEditable(true,false,false);
+    dspOffsetSelection->addListener(this);
+    dspOffsetSelection->setBounds(0,0,30,20);
+    dspOffsetSelection->setColour(Label::textColourId, Colours::darkgrey);
+
+    addAndMakeVisible(dspOffsetSelection);
+
+}
+
+DSPInterface::~DSPInterface()
+{
+
+}
+
+
+void DSPInterface::labelTextChanged(Label* label)
+{
+
+    if (!(editor->acquisitionIsActive) && board->foundInputSource())
+    {
+        if (label == dspOffsetSelection)
+        {
+
+            Value val = label->getTextValue();
+            double requestedValue = double(val.getValue());
+
+            actualDspCutoffFreq = board->setDspCutoffFreq(requestedValue);
+
+            std::cout << "Setting DSP Cutoff Freq to " << requestedValue << "\n";
+            std::cout << "Actual DSP Cutoff Freq:  " <<  actualDspCutoffFreq  << "\n";
+            label->setText(String(round(actualDspCutoffFreq*10.f)/10.f), dontSendNotification);
+
+        }
+    }
+    else if (editor->acquisitionIsActive)
+    {
+		CoreServices::sendStatusMessage("Can't change DSP cutoff while acquisition is active!");
+    }
+
+}
+
+void DSPInterface::setDspCutoffFreq(double value)
+{
+    actualDspCutoffFreq = board->setDspCutoffFreq(value);
+    dspOffsetSelection->setText(String(round(actualDspCutoffFreq*10.f)/10.f), dontSendNotification);
+}
+
+
+double DSPInterface::getDspCutoffFreq()
+{
+    return actualDspCutoffFreq;
+}
+
+void DSPInterface::paint(Graphics& g)
+{
+
+    g.setColour(Colours::darkgrey);
+
+    g.setFont(Font("Small Text",10,Font::plain));
+
+}
diff --git a/Source/Plugins/PCIeRhythm/RHD2000Editor.h b/Source/Plugins/PCIeRhythm/RHD2000Editor.h
new file mode 100644
index 0000000000000000000000000000000000000000..9f4ef4f5d1c715bcddf4027f9dc33dca8acaf722
--- /dev/null
+++ b/Source/Plugins/PCIeRhythm/RHD2000Editor.h
@@ -0,0 +1,399 @@
+/*
+    ------------------------------------------------------------------
+
+    This file is part of the Open Ephys GUI
+    Copyright (C) 2014 Open Ephys
+
+    ------------------------------------------------------------------
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef __RHD2000EDITOR_H_2AD3C591__
+#define __RHD2000EDITOR_H_2AD3C591__
+
+#include <VisualizerEditorHeaders.h>
+
+class UtilityButton;
+/**
+
+User interface for the RHD2000 source module.
+
+@see SourceNode
+
+*/
+class SourceNode;
+
+namespace PCIeRhythm {
+	class HeadstageOptionsInterface;
+	class SampleRateInterface;
+	class BandwidthInterface;
+	class DSPInterface;
+	class AudioInterface;
+	class ClockDivideInterface;
+	class RHD2000Thread;
+	struct ImpedanceData;
+
+	
+
+	class FPGAchannelComponent;
+	class RHD2000Editor;
+	class FPGAcanvas;
+
+	class FPGAchannelList : public Component,
+		Button::Listener, ComboBox::Listener
+	{
+	public:
+
+		FPGAchannelList(GenericProcessor* proc, Viewport* p, FPGAcanvas* c);
+		~FPGAchannelList();
+		void setNewName(int channelIndex, String newName);
+		void setNewGain(int channel, float gain);
+		void disableAll();
+		void enableAll();
+		void paint(Graphics& g);
+		void buttonClicked(Button* btn);
+		void update();
+		void updateButtons();
+		int getNumChannels();
+		void comboBoxChanged(ComboBox* b);
+		void updateImpedance(Array<int> streams, Array<int> channels, Array<float> magnitude, Array<float> phase);
+		SourceNode* proc;
+
+	private:
+		Array<float> gains;
+		Array<ChannelType> types;
+
+		bool chainUpdate;
+
+		Viewport* viewport;
+		FPGAcanvas* canvas;
+		ScopedPointer<UtilityButton> impedanceButton;
+		ScopedPointer<ToggleButton> saveImpedanceButton;
+		ScopedPointer<ToggleButton> autoMeasureButton;
+		ScopedPointer<ComboBox> numberingScheme;
+		ScopedPointer<Label> numberingSchemeLabel;
+		OwnedArray<Label> staticLabels;
+		OwnedArray<FPGAchannelComponent> channelComponents;
+		JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FPGAchannelList);
+	};
+
+
+	class FPGAchannelComponent : public Component, Button::Listener, public ComboBox::Listener, public Label::Listener
+	{
+	public:
+		FPGAchannelComponent(FPGAchannelList* cl, int ch, int gainIndex_, String name_, Array<float> gains_, ChannelType type_);
+		~FPGAchannelComponent();
+		Colour getDefaultColor(int ID);
+		void setImpedanceValues(float mag, float phase);
+		void disableEdit();
+		void enableEdit();
+
+
+		void setEnabledState(bool);
+		bool getEnabledState()
+		{
+			return isEnabled;
+		}
+		void buttonClicked(Button* btn);
+		void setUserDefinedData(int d);
+		int getUserDefinedData();
+		void comboBoxChanged(ComboBox* comboBox);
+		void labelTextChanged(Label* lbl);
+
+		void resized();
+
+		const ChannelType type;
+	private:
+		Array<float> gains;
+		FPGAchannelList* channelList;
+		ScopedPointer<Label> staticLabel, editName, impedance;
+		ScopedPointer<ComboBox> gainComboBox;
+		int channel;
+		String name;
+		int gainIndex;
+		int userDefinedData;
+		Font font;
+		bool isEnabled;
+		JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FPGAchannelComponent);
+	};
+
+
+	class FPGAcanvas : public Visualizer, public Button::Listener
+	{
+	public:
+		FPGAcanvas(GenericProcessor* n);
+		~FPGAcanvas();
+
+		void paint(Graphics& g);
+
+		void refresh();
+
+		void beginAnimation();
+		void endAnimation();
+
+		void refreshState();
+		void update();
+
+		void setParameter(int, float);
+		void setParameter(int, int, int, float);
+
+		void updateImpedance(Array<int> streams, Array<int> channels, Array<float> magnitude, Array<float> phase);
+
+		void resized();
+		void buttonClicked(Button* button);
+		ScopedPointer<Viewport> channelsViewport;
+		SourceNode* processor;
+		ScopedPointer<FPGAchannelList> channelList;
+	};
+
+	class RHD2000Editor : public VisualizerEditor, public ComboBox::Listener, public AsyncUpdater
+
+	{
+	public:
+		RHD2000Editor(GenericProcessor* parentNode, RHD2000Thread*, bool useDefaultParameterEditors);
+		~RHD2000Editor();
+
+		void buttonEvent(Button* button);
+
+		void scanPorts();
+		void comboBoxChanged(ComboBox* comboBox);
+
+		void startAcquisition();
+		void stopAcquisition();
+
+		void channelChanged(int channel, bool newState) override;
+
+		void saveCustomParameters(XmlElement* xml);
+		void loadCustomParameters(XmlElement* xml);
+		Visualizer* createNewCanvas(void);
+		void measureImpedance();
+
+		void setSaveImpedance(bool en);
+		void setAutoMeasureImpedance(bool en);
+		bool getSaveImpedance();
+		bool getAutoMeasureImpedance();
+
+		void handleAsyncUpdate();
+
+	private:
+
+		OwnedArray<HeadstageOptionsInterface> headstageOptionsInterfaces;
+		OwnedArray<ElectrodeButton> electrodeButtons;
+
+		ScopedPointer<SampleRateInterface> sampleRateInterface;
+		ScopedPointer<BandwidthInterface> bandwidthInterface;
+		ScopedPointer<DSPInterface> dspInterface;
+
+		ScopedPointer<AudioInterface> audioInterface;
+		ScopedPointer<ClockDivideInterface> clockInterface;
+
+		ScopedPointer<UtilityButton> rescanButton, dacTTLButton;
+		ScopedPointer<UtilityButton> adcButton;
+		ScopedPointer<UtilityButton> ledButton;
+
+		ScopedPointer<UtilityButton> dspoffsetButton;
+		ScopedPointer<ComboBox> ttlSettleCombo, dacHPFcombo;
+
+
+		ScopedPointer<Label> audioLabel, ttlSettleLabel, dacHPFlabel;
+
+		bool saveImpedances, measureWhenRecording;
+
+		RHD2000Thread* board;
+		FPGAcanvas* canvas;
+
+		ScopedPointer<ImpedanceData> impedanceData;
+
+		JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RHD2000Editor);
+
+	};
+
+
+	class HeadstageOptionsInterface : public Component,
+		public Button::Listener
+	{
+	public:
+		HeadstageOptionsInterface(RHD2000Thread*, RHD2000Editor*, int hsNum);
+		~HeadstageOptionsInterface();
+
+		void paint(Graphics& g);
+
+		void buttonClicked(Button* button);
+
+		void checkEnabledState();
+
+	private:
+
+		int hsNumber1, hsNumber2;
+		int channelsOnHs1, channelsOnHs2;
+		String name;
+
+		bool isEnabled;
+
+		RHD2000Thread* board;
+		RHD2000Editor* editor;
+
+		ScopedPointer<UtilityButton> hsButton1;
+		ScopedPointer<UtilityButton> hsButton2;
+
+	};
+
+
+	class BandwidthInterface : public Component,
+		public Label::Listener
+	{
+	public:
+		BandwidthInterface(RHD2000Thread*, RHD2000Editor*);
+		~BandwidthInterface();
+
+		void paint(Graphics& g);
+		void labelTextChanged(Label* te);
+
+		void setLowerBandwidth(double value);
+		void setUpperBandwidth(double value);
+		double getLowerBandwidth();
+		double getUpperBandwidth();
+
+	private:
+
+		String name;
+
+		String lastLowCutString, lastHighCutString;
+
+		RHD2000Thread* board;
+		RHD2000Editor* editor;
+
+		ScopedPointer<Label> upperBandwidthSelection;
+		ScopedPointer<Label> lowerBandwidthSelection;
+
+		double actualUpperBandwidth;
+		double actualLowerBandwidth;
+
+	};
+
+	class DSPInterface : public Component,
+		public Label::Listener
+	{
+	public:
+		DSPInterface(RHD2000Thread*, RHD2000Editor*);
+		~DSPInterface();
+
+		void paint(Graphics& g);
+		void labelTextChanged(Label* te);
+
+		void setDspCutoffFreq(double value);
+		double getDspCutoffFreq();
+
+	private:
+
+		String name;
+
+		RHD2000Thread* board;
+		RHD2000Editor* editor;
+
+		ScopedPointer<Label> dspOffsetSelection;
+
+		double actualDspCutoffFreq;
+
+	};
+
+
+
+	class SampleRateInterface : public Component,
+		public ComboBox::Listener
+	{
+	public:
+		SampleRateInterface(RHD2000Thread*, RHD2000Editor*);
+		~SampleRateInterface();
+
+		int getSelectedId();
+		void setSelectedId(int);
+
+		void paint(Graphics& g);
+		void comboBoxChanged(ComboBox* cb);
+
+	private:
+
+		int sampleRate;
+		String name;
+
+		RHD2000Thread* board;
+		RHD2000Editor* editor;
+
+		ScopedPointer<ComboBox> rateSelection;
+		StringArray sampleRateOptions;
+
+	};
+
+	class AudioInterface : public Component,
+		public Label::Listener
+	{
+	public:
+		AudioInterface(RHD2000Thread*, RHD2000Editor*);
+		~AudioInterface();
+
+		void paint(Graphics& g);
+		void labelTextChanged(Label* te);
+
+		void setNoiseSlicerLevel(int value);
+		int getNoiseSlicerLevel();
+		//void setGain(double value);
+		//double getGain();
+
+	private:
+
+		String name;
+
+		String lastNoiseSlicerString;
+		String lastGainString;
+
+		RHD2000Thread* board;
+		RHD2000Editor* editor;
+
+		ScopedPointer<Label> noiseSlicerLevelSelection;
+		//ScopedPointer<Label> gainSelection;
+
+		int actualNoiseSlicerLevel;
+		//double actualGain;
+
+	};
+
+	class ClockDivideInterface : public Component,
+		public Label::Listener
+	{
+	public:
+		ClockDivideInterface(RHD2000Thread*, RHD2000Editor*);
+
+		void paint(Graphics& g);
+		void labelTextChanged(Label* te);
+
+		void setClockDivideRatio(int value);
+		int getClockDivideRatio() const { return actualDivideRatio; };
+
+	private:
+
+		String name;
+		String lastDivideRatioString;
+
+		RHD2000Thread * board;
+		RHD2000Editor * editor;
+
+		ScopedPointer<Label> divideRatioSelection;
+		int actualDivideRatio;
+
+	};
+};
+#endif  // __RHD2000EDITOR_H_2AD3C591__
diff --git a/Source/Plugins/PCIeRhythm/RHD2000Thread.cpp b/Source/Plugins/PCIeRhythm/RHD2000Thread.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c3b02ff8b3f60688dcdd5117fec107672a64cb90
--- /dev/null
+++ b/Source/Plugins/PCIeRhythm/RHD2000Thread.cpp
@@ -0,0 +1,2386 @@
+/*
+    ------------------------------------------------------------------
+
+    This file is part of the Open Ephys GUI
+    Copyright (C) 2014 Open Ephys
+
+    ------------------------------------------------------------------
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "RHD2000Thread.h"
+#include "RHD2000Editor.h"
+
+
+#if defined(_WIN32)
+#define okLIB_NAME "okFrontPanel.dll"
+#define okLIB_EXTENSION "*.dll"
+#elif defined(__APPLE__)
+#define okLIB_NAME "libokFrontPanel.dylib"
+#define okLIB_EXTENSION "*.dylib"
+#elif defined(__linux__)
+#define okLIB_NAME "./libokFrontPanel.so"
+#define okLIB_EXTENSION "*.so"
+#endif
+
+#define CHIP_ID_RHD2132  1
+#define CHIP_ID_RHD2216  2
+#define CHIP_ID_RHD2164  4
+#define CHIP_ID_RHD2164_B  1000
+#define REGISTER_59_MISO_A  53
+#define REGISTER_59_MISO_B  58
+#define RHD2132_16CH_OFFSET 8
+
+//#define DEBUG_EMULATE_HEADSTAGES 8
+//#define DEBUG_EMULATE_64CH
+
+#define INIT_STEP 256
+
+using namespace PCIeRhythm;
+
+// Allocates memory for a 3-D array of doubles.
+void allocateDoubleArray3D(std::vector<std::vector<std::vector<double> > >& array3D,
+                           int xSize, int ySize, int zSize)
+{
+    int i, j;
+
+    if (xSize == 0) return;
+    array3D.resize(xSize);
+    for (i = 0; i < xSize; ++i)
+    {
+        array3D[i].resize(ySize);
+        for (j = 0; j < ySize; ++j)
+        {
+            array3D[i][j].resize(zSize);
+        }
+    }
+}
+
+DataThread* RHD2000Thread::createDataThread(SourceNode *sn)
+{
+	return new RHD2000Thread(sn);
+}
+
+RHD2000Thread::RHD2000Thread(SourceNode* sn) : DataThread(sn),
+    chipRegisters(30000.0f),
+    numChannels(0),
+    deviceFound(false),
+    isTransmitting(false),
+    dacOutputShouldChange(false),
+    acquireAdcChannels(false),
+    acquireAuxChannels(true),
+    fastSettleEnabled(false),
+    fastTTLSettleEnabled(false),
+    fastSettleTTLChannel(-1),
+    ttlMode(false),
+    dspEnabled(true),
+    desiredDspCutoffFreq(0.5f),
+    desiredUpperBandwidth(7500.0f),
+    desiredLowerBandwidth(1.0f),
+    boardSampleRate(30000.0f),
+    savedSampleRateIndex(16),
+    cableLengthPortA(0.914f), cableLengthPortB(0.914f), cableLengthPortC(0.914f), cableLengthPortD(0.914f), // default is 3 feet (0.914 m),
+    audioOutputL(-1), audioOutputR(-1) ,numberingScheme(1),
+	newScan(true), ledsEnabled(true)
+{
+	impedanceThread = new RHDImpedanceMeasure(this);
+	memset(auxBuffer, 0, sizeof(auxBuffer));
+	memset(auxSamples, 0, sizeof(auxSamples));
+
+    for (int i=0; i < MAX_NUM_HEADSTAGES; i++)
+        headstagesArray.add(new RHDHeadstage(static_cast<rhd2000PCIe::BoardDataSource>(i)));
+
+    evalBoard = new rhd2000PCIe;
+    dataBuffer = new DataBuffer(2, 10000); // start with 2 channels and automatically resize
+
+    // Open Opal Kelly XEM6010 board.
+    // Returns 1 if successful, -1 if FrontPanel cannot be loaded, and -2 if XEM6010 can't be found.
+
+#if defined(__APPLE__)
+    File appBundle = File::getSpecialLocation(File::currentApplicationFile);
+    const String executableDirectory = appBundle.getChildFile("Contents/Resources").getFullPathName();
+#else
+    File executable = File::getSpecialLocation(File::currentExecutableFile);
+    const String executableDirectory = executable.getParentDirectory().getFullPathName();
+#endif
+
+    std::cout << executableDirectory << std::endl;
+
+
+    String dirName = executableDirectory;
+    libraryFilePath = dirName;
+    libraryFilePath += File::separatorString;
+    libraryFilePath += okLIB_NAME;
+
+    dacStream = nullptr;
+    dacChannels = nullptr;
+    dacThresholds = nullptr;
+    dacChannelsToUpdate = nullptr;
+    if (openBoard())
+    {
+		dataBlock = new Rhd2000DataBlock(1);
+        // upload bitfile and restore default settings
+        initializeBoard();
+
+        // automatically find connected headstages
+        scanPorts(); // things would appear to run more smoothly if this were done after the editor has been created
+
+        // probably better to do this with a thread, but a timer works for now:
+        // startTimer(10); // initialize the board in the background
+        dacStream = new int[8];
+        dacChannels = new int[8];
+        dacThresholds = new float[8];
+        dacChannelsToUpdate = new bool[8];
+        for (int k = 0; k < 8; k++)
+        {
+            dacChannelsToUpdate[k] = true;
+            dacStream[k] = 0;
+            setDACthreshold(k, 65534);
+            dacChannels[k] = 0;
+            dacThresholds[k] = 0;
+        }
+
+        // evalBoard->getDacInformation(dacChannels,dacThresholds);
+
+        //	setDefaultNamingScheme(numberingScheme);
+        //setDefaultChannelNamesAndType();
+    }
+}
+
+GenericEditor* RHD2000Thread::createEditor(SourceNode* sn)
+{
+	return new RHD2000Editor(sn, this, true);
+}
+
+void RHD2000Thread::timerCallback()
+{
+
+    stopTimer();
+
+}
+
+RHD2000Thread::~RHD2000Thread()
+{
+
+    std::cout << "RHD2000 interface destroyed." << std::endl;
+
+/*    if (deviceFound)
+    {
+        int ledArray[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+        evalBoard->setLedDisplay(ledArray);
+    }
+
+    if (deviceFound)
+        evalBoard->resetFpga();*/
+
+    //deleteAndZero(dataBlock);
+
+    delete[] dacStream;
+    delete[] dacChannels;
+    delete[] dacThresholds;
+    delete[] dacChannelsToUpdate;
+
+}
+
+bool RHD2000Thread::usesCustomNames()
+{
+    return true;
+}
+
+void RHD2000Thread::setDACthreshold(int dacOutput, float threshold)
+{
+    dacThresholds[dacOutput]= threshold;
+    dacChannelsToUpdate[dacOutput] = true;
+    dacOutputShouldChange = true;
+
+    //  evalBoard->setDacThresholdVoltage(dacOutput,threshold);
+}
+
+void RHD2000Thread::setDACchannel(int dacOutput, int channel)
+{
+    if (channel < getNumHeadstageOutputs())
+    {
+        int channelCount = 0;
+        for (int i = 0; i < enabledStreams.size(); i++)
+        {
+            if (channel < channelCount + numChannelsPerDataStream[i])
+            {
+                dacChannels[dacOutput] = channel - channelCount;
+                dacStream[dacOutput] = i;
+				break;
+            }
+            else
+            {
+                channelCount += numChannelsPerDataStream[i];
+            }
+        }
+        dacChannelsToUpdate[dacOutput] = true;
+        dacOutputShouldChange = true;
+    }
+}
+
+Array<int> RHD2000Thread::getDACchannels()
+{
+    Array<int> dacChannelsArray;
+    //dacChannelsArray.addArray(dacChannels,8);
+    for (int k=0; k<8; k++)
+    {
+        dacChannelsArray.add(dacChannels[k]);
+    }
+    return dacChannelsArray;
+
+}
+bool RHD2000Thread::openBoard()
+{
+    if (evalBoard->open())
+    {
+        deviceFound = true;
+    }
+    else    // board could not be opened
+    {
+        bool response = AlertWindow::showOkCancelBox(AlertWindow::NoIcon,
+                                                     "Acquisition board not found.",
+                                                     "An acquisition board could not be found. Please connect one now.",
+                                                     "OK", "Cancel", 0, 0);
+
+        if (response)
+        {
+            openBoard(); // call recursively
+        }
+        else
+        {
+            deviceFound = false;
+        }
+
+    }
+
+    return deviceFound;
+
+}
+
+
+void RHD2000Thread::initializeBoard()
+{
+
+    // Initialize the board
+    std::cout << "Initializing acquisition board." << std::endl;
+    evalBoard->initialize();
+    // This applies the following settings:
+    //  - sample rate to 30 kHz
+    //  - aux command banks to zero
+    //  - aux command lengths to zero
+    //  - continuous run mode to 'true'
+    //  - maxTimeStep to 2^32 - 1
+    //  - all cable lengths to 3 feet
+    //  - dspSettle to 'false'
+    //  - data source mapping as 0->PortA1, 1->PortB1, 2->PortC1, 3->PortD1, etc.
+    //  - enables all data streams
+    //  - clears the ttlOut
+    //  - disables all DACs and sets gain to 0
+
+    setSampleRate(rhd2000PCIe::SampleRate30000Hz);
+	evalBoard->setCableLengthMeters(rhd2000PCIe::PortA, cableLengthPortA);
+	evalBoard->setCableLengthMeters(rhd2000PCIe::PortB, cableLengthPortB);
+	evalBoard->setCableLengthMeters(rhd2000PCIe::PortC, cableLengthPortC);
+	evalBoard->setCableLengthMeters(rhd2000PCIe::PortD, cableLengthPortD);
+
+    // Select RAM Bank 0 for AuxCmd3 initially, so the ADC is calibrated.
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd3, 0);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd3, 0);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd3, 0);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd3, 0);
+
+    // Since our longest command sequence is 60 commands, run the SPI interface for
+    // 60 samples (64 for usb3 power-of two needs)
+	evalBoard->setMaxTimeStep(INIT_STEP);
+    evalBoard->setContinuousRunMode(false);
+
+    // Start SPI interface
+    evalBoard->run();
+
+    // Wait for the 60-sample run to complete
+    while (evalBoard->isRunning())
+    {
+        ;
+    }
+
+    // Read the resulting single data block from the USB interface. We don't
+    // need to do anything with this, since it was only used for ADC calibration
+    ScopedPointer<Rhd2000DataBlock> dataBlock = new Rhd2000DataBlock(evalBoard->getNumEnabledDataStreams());
+
+	evalBoard->readDataBlock(dataBlock, INIT_STEP);
+    // Now that ADC calibration has been performed, we switch to the command sequence
+    // that does not execute ADC calibration.
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd3,
+                                    fastSettleEnabled ? 2 : 1);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd3,
+                                    fastSettleEnabled ? 2 : 1);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd3,
+                                    fastSettleEnabled ? 2 : 1);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd3,
+                                    fastSettleEnabled ? 2 : 1);
+
+
+
+}
+
+void RHD2000Thread::scanPorts()
+{
+	if (!deviceFound) //Safety to avoid crashes if board not present
+	{
+		return;
+	}
+
+	impedanceThread->stopThreadSafely();
+	//Clear previous known streams
+	enabledStreams.clear();
+
+	// Scan SPI ports
+
+	int delay, hs, id;
+	int register59Value;
+	//int numChannelsOnPort[4] = {0, 0, 0, 0};
+	rhd2000PCIe::BoardDataSource initStreamPorts[8] =
+	{
+		rhd2000PCIe::PortA1,
+		rhd2000PCIe::PortA2,
+		rhd2000PCIe::PortB1,
+		rhd2000PCIe::PortB2,
+		rhd2000PCIe::PortC1,
+		rhd2000PCIe::PortC2,
+		rhd2000PCIe::PortD1,
+		rhd2000PCIe::PortD2
+	};
+
+	/*
+	Rhd2000EvalBoard::BoardDataSource initStreamDdrPorts[8] =
+	{
+	Rhd2000EvalBoard::PortA1Ddr,
+	Rhd2000EvalBoard::PortA2Ddr,
+	Rhd2000EvalBoard::PortB1Ddr,
+	Rhd2000EvalBoard::PortB2Ddr,
+	Rhd2000EvalBoard::PortC1Ddr,
+	Rhd2000EvalBoard::PortC2Ddr,
+	Rhd2000EvalBoard::PortD1Ddr,
+	Rhd2000EvalBoard::PortD2Ddr
+	};
+	*/
+
+	chipId.insertMultiple(0, -1, 8);
+	Array<int> tmpChipId(chipId);
+
+	setSampleRate(rhd2000PCIe::SampleRate30000Hz, true); // set to 30 kHz temporarily
+
+	// Enable all data streams, and set sources to cover one or two chips
+	// on Ports A-D.
+	evalBoard->setDataSource(0, initStreamPorts[0]);
+	evalBoard->setDataSource(1, initStreamPorts[1]);
+	evalBoard->setDataSource(2, initStreamPorts[2]);
+	evalBoard->setDataSource(3, initStreamPorts[3]);
+	evalBoard->setDataSource(4, initStreamPorts[4]);
+	evalBoard->setDataSource(5, initStreamPorts[5]);
+	evalBoard->setDataSource(6, initStreamPorts[6]);
+	evalBoard->setDataSource(7, initStreamPorts[7]);
+
+	evalBoard->enableDataStream(0, true);
+	evalBoard->enableDataStream(1, true);
+	evalBoard->enableDataStream(2, true);
+	evalBoard->enableDataStream(3, true);
+	evalBoard->enableDataStream(4, true);
+	evalBoard->enableDataStream(5, true);
+	evalBoard->enableDataStream(6, true);
+	evalBoard->enableDataStream(7, true);
+
+	std::cout << "Number of enabled data streams: " << evalBoard->getNumEnabledDataStreams() << std::endl;
+
+
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA,
+		rhd2000PCIe::AuxCmd3, 0);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB,
+		rhd2000PCIe::AuxCmd3, 0);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC,
+		rhd2000PCIe::AuxCmd3, 0);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD,
+		rhd2000PCIe::AuxCmd3, 0);
+
+	// Since our longest command sequence is 60 commands, we run the SPI
+	// interface for 60 samples. (64 for usb3 power-of two needs)
+	evalBoard->setMaxTimeStep(INIT_STEP);
+	evalBoard->setContinuousRunMode(false);
+
+	ScopedPointer<Rhd2000DataBlock> dataBlock =
+		new Rhd2000DataBlock(evalBoard->getNumEnabledDataStreams());
+
+	Array<int> sumGoodDelays;
+	sumGoodDelays.insertMultiple(0, 0, 8);
+
+	Array<int> indexFirstGoodDelay;
+	indexFirstGoodDelay.insertMultiple(0, -1, 8);
+
+	Array<int> indexSecondGoodDelay;
+	indexSecondGoodDelay.insertMultiple(0, -1, 8);
+
+
+	// Run SPI command sequence at all 16 possible FPGA MISO delay settings
+	// to find optimum delay for each SPI interface cable.
+
+	std::cout << "Checking for connected amplifier chips..." << std::endl;
+
+	for (delay = 0; delay < 16; delay++)//(delay = 0; delay < 16; ++delay)
+	{
+		evalBoard->setCableDelay(rhd2000PCIe::PortA, delay);
+		evalBoard->setCableDelay(rhd2000PCIe::PortB, delay);
+		evalBoard->setCableDelay(rhd2000PCIe::PortC, delay);
+		evalBoard->setCableDelay(rhd2000PCIe::PortD, delay);
+
+		// Start SPI interface.
+		evalBoard->run();
+
+		// Wait for the 60-sample run to complete.
+		while (evalBoard->isRunning())
+		{
+			;
+		}
+		// Read the resulting single data block from the USB interface.
+		evalBoard->readDataBlock(dataBlock, INIT_STEP);
+
+		// Read the Intan chip ID number from each RHD2000 chip found.
+		// Record delay settings that yield good communication with the chip.
+		for (hs = 0; hs < MAX_NUM_HEADSTAGES; ++hs)//MAX_NUM_DATA_STREAMS; ++stream)
+		{
+			// std::cout << "Stream number " << stream << ", delay = " << delay << std::endl;
+
+			id = deviceId(dataBlock, hs, register59Value);
+
+			if (id == CHIP_ID_RHD2132 || id == CHIP_ID_RHD2216 ||
+				(id == CHIP_ID_RHD2164 && register59Value == REGISTER_59_MISO_A))
+			{
+				//  std::cout << "Device ID found: " << id << std::endl;
+
+				sumGoodDelays.set(hs, sumGoodDelays[hs] + 1);
+
+				if (indexFirstGoodDelay[hs] == -1)
+				{
+					indexFirstGoodDelay.set(hs, delay);
+					tmpChipId.set(hs, id);
+				}
+				else if (indexSecondGoodDelay[hs] == -1)
+				{
+					indexSecondGoodDelay.set(hs, delay);
+					tmpChipId.set(hs, id);
+				}
+			}
+		}
+	}
+
+	// std::cout << "Chip IDs found: ";
+	// for (int i = 0; i < MAX_NUM_DATA_STREAMS; ++i)
+	// {
+	//     std::cout << chipId[i] << " ";
+	// }
+	//std::cout << std::endl;
+
+#if DEBUG_EMULATE_HEADSTAGES > 0
+	if (tmpChipId[0] > 0)
+	{
+		int chipIdx = 0;
+		for (int hs = 0; hs < DEBUG_EMULATE_HEADSTAGES && hs < MAX_NUM_HEADSTAGES ; ++hs)
+		{
+			if (enabledStreams.size() < MAX_NUM_DATA_STREAMS(evalBoard->isUSB3()))
+			{
+#ifdef DEBUG_EMULATE_64CH
+				chipId.set(chipIdx++,CHIP_ID_RHD2164);
+				chipId.set(chipIdx++,CHIP_ID_RHD2164_B);
+				enableHeadstage(hs, true, 2, 32);
+#else
+				chipId.set(chipIdx++,CHIP_ID_RHD2132);
+				enableHeadstage(hs, true, 1, 32);
+#endif
+			}
+		}
+		for (int i = 0; i < enabledStreams.size(); i++)
+		{
+			enabledStreams.set(i,Rhd2000EvalBoard::PortA1);
+		}
+	}
+        
+#else
+    // Now, disable data streams where we did not find chips present.
+    int chipIdx = 0;
+    for (hs = 0; hs < MAX_NUM_HEADSTAGES; ++hs)
+    {
+        if ((tmpChipId[hs] > 0) && (enabledStreams.size() < MAX_NUM_DATA_STREAMS))
+        {
+            chipId.set(chipIdx++,tmpChipId[hs]);
+            //std::cout << "Enabling headstage on stream " << stream << std::endl;
+            if (tmpChipId[hs] == CHIP_ID_RHD2164) //RHD2164
+            {
+                if (enabledStreams.size() < MAX_NUM_DATA_STREAMS - 1)
+                {
+                    enableHeadstage(hs,true,2,32);
+                    chipId.set(chipIdx++,CHIP_ID_RHD2164_B);
+                }
+                else //just one stream left
+                {
+                    enableHeadstage(hs,true,1,32);
+                }
+            }
+            else
+            {
+                enableHeadstage(hs, true,1,tmpChipId[hs] == 1 ? 32:16);
+            }
+        }
+        else
+        {
+            enableHeadstage(hs, false);
+        }
+    }
+#endif
+	updateBoardStreams();
+
+
+    std::cout << "Number of enabled data streams: " << evalBoard->getNumEnabledDataStreams() << std::endl;
+
+
+    // Set cable delay settings that yield good communication with each
+    // RHD2000 chip.
+    Array<int> optimumDelay;
+    optimumDelay.insertMultiple(0,0,8);
+
+    for (hs = 0; hs < MAX_NUM_HEADSTAGES; ++hs)
+    {
+        if (sumGoodDelays[hs] == 1 || sumGoodDelays[hs] == 2)
+        {
+            optimumDelay.set(hs,indexFirstGoodDelay[hs]);
+        }
+        else if (sumGoodDelays[hs] > 2)
+        {
+            optimumDelay.set(hs,indexSecondGoodDelay[hs]);
+        }
+    }
+
+	evalBoard->setCableDelay(rhd2000PCIe::PortA,
+                             max(optimumDelay[0],optimumDelay[1]));
+	evalBoard->setCableDelay(rhd2000PCIe::PortB,
+                             max(optimumDelay[2],optimumDelay[3]));
+	evalBoard->setCableDelay(rhd2000PCIe::PortC,
+                             max(optimumDelay[4],optimumDelay[5]));
+	evalBoard->setCableDelay(rhd2000PCIe::PortD,
+                             max(optimumDelay[6],optimumDelay[7]));
+
+    cableLengthPortA =
+        evalBoard->estimateCableLengthMeters(max(optimumDelay[0],optimumDelay[1]));
+    cableLengthPortB =
+        evalBoard->estimateCableLengthMeters(max(optimumDelay[2],optimumDelay[3]));
+    cableLengthPortC =
+        evalBoard->estimateCableLengthMeters(max(optimumDelay[4],optimumDelay[5]));
+    cableLengthPortD =
+        evalBoard->estimateCableLengthMeters(max(optimumDelay[6],optimumDelay[7]));
+
+    setSampleRate(savedSampleRateIndex); // restore saved sample rate
+    //updateRegisters();
+    newScan = true;
+}
+
+int RHD2000Thread::deviceId(Rhd2000DataBlock* dataBlock, int stream, int& register59Value)
+{
+    bool intanChipPresent;
+
+    // First, check ROM registers 32-36 to verify that they hold 'INTAN', and
+    // the initial chip name ROM registers 24-26 that hold 'RHD'.
+    // This is just used to verify that we are getting good data over the SPI
+    // communication channel.
+    intanChipPresent = ((char) dataBlock->auxiliaryData[stream][2][32] == 'I' &&
+                        (char) dataBlock->auxiliaryData[stream][2][33] == 'N' &&
+                        (char) dataBlock->auxiliaryData[stream][2][34] == 'T' &&
+                        (char) dataBlock->auxiliaryData[stream][2][35] == 'A' &&
+                        (char) dataBlock->auxiliaryData[stream][2][36] == 'N' &&
+                        (char) dataBlock->auxiliaryData[stream][2][24] == 'R' &&
+                        (char) dataBlock->auxiliaryData[stream][2][25] == 'H' &&
+                        (char) dataBlock->auxiliaryData[stream][2][26] == 'D');
+
+    // If the SPI communication is bad, return -1.  Otherwise, return the Intan
+    // chip ID number stored in ROM regstier 63.
+    if (!intanChipPresent)
+    {
+        register59Value = -1;
+        return -1;
+    }
+    else
+    {
+        register59Value = dataBlock->auxiliaryData[stream][2][23]; // Register 59
+        return dataBlock->auxiliaryData[stream][2][19]; // chip ID (Register 63)
+    }
+}
+
+
+
+bool RHD2000Thread::isAcquisitionActive()
+{
+    return isTransmitting;
+}
+
+void RHD2000Thread::setNumChannels(int hsNum, int numChannels)
+{
+    if (headstagesArray[hsNum]->getNumChannels() == 32)
+    {
+        if (numChannels < headstagesArray[hsNum]->getNumChannels())
+            headstagesArray[hsNum]->setHalfChannels(true);
+        else
+            headstagesArray[hsNum]->setHalfChannels(false);
+        numChannelsPerDataStream.set(headstagesArray[hsNum]->getStreamIndex(0), numChannels);
+    }
+}
+
+int RHD2000Thread::getHeadstageChannels(int hsNum)
+{
+    return headstagesArray[hsNum]->getNumChannels();
+}
+
+
+void RHD2000Thread::getEventChannelNames(StringArray& Names)
+{
+    Names.clear();
+    for (int k = 0; k < 8; k++)
+    {
+        Names.add("TTL"+String(k+1));
+    }
+}
+
+
+/* go over the old names and tests whether this particular channel name was changed.
+if so, return the old name */
+
+
+int RHD2000Thread::modifyChannelName(int channel, String newName)
+{
+    ChannelCustomInfo i = channelInfo[channel];
+    i.name = newName;
+    i.modified = true;
+    channelInfo.set(channel, i);
+    return 0;
+}
+
+String RHD2000Thread::getChannelName(int ch)
+{
+    return channelInfo[ch].name;
+}
+
+int RHD2000Thread::modifyChannelGain(int channel, float gain)
+{
+    ChannelCustomInfo i = channelInfo[channel];
+    i.gain = gain;
+    i.modified = true;
+    channelInfo.set(channel, i);
+    return 0;
+}
+
+void RHD2000Thread::setDefaultNamingScheme(int scheme)
+{
+    numberingScheme = scheme;
+    newScan = true; //if the scheme is changed, reset all names
+    setDefaultChannelNames();
+}
+
+/* This will give default names & gains to channels, unless they were manually modified by the user
+ In that case, the query channelModified, will return the values that need to be put */
+void RHD2000Thread::setDefaultChannelNames()
+{
+    int aux_counter = 1;
+    int channelNumber = 1;
+    String oldName;
+    //int dummy;
+    //float oldGain;
+    StringArray stream_prefix;
+    stream_prefix.add("A1");
+    stream_prefix.add("A2");
+    stream_prefix.add("B1");
+    stream_prefix.add("B2");
+    stream_prefix.add("C1");
+    stream_prefix.add("C2");
+    stream_prefix.add("D1");
+    stream_prefix.add("D2");
+
+    for (int i = 0; i < MAX_NUM_HEADSTAGES; i++)
+    {
+        if (headstagesArray[i]->isPlugged())
+        {
+            for (int k = 0; k < headstagesArray[i]->getNumActiveChannels(); k++)
+            {
+                if (newScan || !channelInfo[k].modified)
+                {
+                    ChannelCustomInfo in;
+                    if (numberingScheme == 1)
+                        in.name = "CH" + String(channelNumber);
+                    else
+                        in.name = "CH_" + stream_prefix[i] + "_" + String(1 + k);
+                    in.gain = getBitVolts(sn->channels[k]);
+                    channelInfo.set(channelNumber-1, in);
+
+                }
+                channelNumber++;
+            }
+        }
+    }
+    //Aux channels
+    for (int i = 0; i < MAX_NUM_HEADSTAGES; i++)
+    {
+        if (headstagesArray[i]->isPlugged())
+        {
+            for (int k = 0; k < 3; k++)
+            {
+                int chn = channelNumber - 1;
+
+                if (newScan || !channelInfo[chn].modified)
+                {
+                    ChannelCustomInfo in;
+                    if (numberingScheme == 1)
+                        in.name = "AUX" + String(aux_counter);
+                    else
+                        in.name = "AUX_" + stream_prefix[i] + "_" + String(1 + k);
+                    in.gain = getBitVolts(sn->channels[chn]);
+                    channelInfo.set(chn, in);
+
+                }
+                channelNumber++;
+                aux_counter++;
+            }
+        }
+    }
+    //ADC channels
+    if (acquireAdcChannels)
+    {
+        for (int k = 0; k < 8; k++)
+        {
+            int chn = channelNumber - 1;
+            if (newScan || !channelInfo[chn].modified)
+            {
+                ChannelCustomInfo in;
+                in.name = "ADC" + String(k + 1);
+                in.gain = getAdcBitVolts(k);
+                channelInfo.set(chn, in);
+            }
+            channelNumber++;
+        }
+    }
+    newScan = false;
+}
+
+int RHD2000Thread::getNumChannels()
+{
+    return getNumHeadstageOutputs() + getNumAdcOutputs() + getNumAuxOutputs();
+}
+
+int RHD2000Thread::getNumHeadstageOutputs()
+{
+    numChannels = 0;
+    for (int i = 0; i < MAX_NUM_HEADSTAGES; i++)
+    {
+
+        if (headstagesArray[i]->isPlugged())
+        {
+            numChannels += headstagesArray[i]->getNumActiveChannels();
+        }
+    }
+
+   // if (numChannels > 0)
+        return numChannels;
+    //else
+      //  return 1; // to prevent crashing with 0 channels
+}
+
+int RHD2000Thread::getNumAuxOutputs()
+{
+    int numAuxOutputs = 0;
+
+    for (int i = 0; i < MAX_NUM_HEADSTAGES; i++)
+    {
+        if (headstagesArray[i]->isPlugged() > 0)
+        {
+            numAuxOutputs += 3;
+        }
+    }
+
+    return numAuxOutputs;
+
+}
+
+int RHD2000Thread::getNumAdcOutputs()
+{
+    if (acquireAdcChannels)
+    {
+        return 8;
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+int RHD2000Thread::getNumEventChannels()
+{
+    return 16; // 8 inputs, 8 outputs
+}
+
+float RHD2000Thread::getSampleRate()
+{
+    return evalBoard->getSampleRate();
+}
+
+float RHD2000Thread::getBitVolts(Channel* ch)
+{
+    if (ch->type == ADC_CHANNEL)
+        return getAdcBitVolts(ch->index);
+    else if (ch->type == AUX_CHANNEL)
+        return 0.0000374;
+    else
+        return 0.195f;
+}
+
+float RHD2000Thread::getAdcBitVolts(int chan)
+{
+    if (chan < adcBitVolts.size())
+        return adcBitVolts[chan];
+    else
+        return 0.00015258789;
+}
+
+double RHD2000Thread::setUpperBandwidth(double upper)
+{
+	impedanceThread->stopThreadSafely();
+    desiredUpperBandwidth = upper;
+
+    updateRegisters();
+
+    return actualUpperBandwidth;
+}
+
+
+double RHD2000Thread::setLowerBandwidth(double lower)
+{
+	impedanceThread->stopThreadSafely();
+    desiredLowerBandwidth = lower;
+
+    updateRegisters();
+
+    return actualLowerBandwidth;
+}
+
+double RHD2000Thread::setDspCutoffFreq(double freq)
+{
+	impedanceThread->stopThreadSafely();
+    desiredDspCutoffFreq = freq;
+
+    updateRegisters();
+
+    return actualDspCutoffFreq;
+}
+
+double RHD2000Thread::getDspCutoffFreq()
+{
+
+    return actualDspCutoffFreq;
+}
+
+void RHD2000Thread::setDSPOffset(bool state)
+{
+	impedanceThread->stopThreadSafely();
+    dspEnabled = state;
+    updateRegisters();
+}
+
+void RHD2000Thread::setTTLoutputMode(bool state)
+{
+    ttlMode = state;
+    dacOutputShouldChange = true;
+
+}
+
+void RHD2000Thread::setDAChpf(float cutoff, bool enabled)
+{
+    dacOutputShouldChange = true;
+    desiredDAChpf = cutoff;
+    desiredDAChpfState = enabled;
+}
+
+void RHD2000Thread::setFastTTLSettle(bool state, int channel)
+{
+    fastTTLSettleEnabled = state;
+    fastSettleTTLChannel = channel;
+    dacOutputShouldChange = true;
+}
+
+int RHD2000Thread::setNoiseSlicerLevel(int level)
+{
+
+    desiredNoiseSlicerLevel = level;
+ /*   if (deviceFound)
+        evalBoard->setAudioNoiseSuppress(desiredNoiseSlicerLevel);*/
+
+    // Level has been checked once before this and then is checked again in setAudioNoiseSuppress.
+    // This may be overkill - maybe API should change so that the final function returns the value?
+    actualNoiseSlicerLevel = level;
+
+    return actualNoiseSlicerLevel;
+}
+
+
+bool RHD2000Thread::foundInputSource()
+{
+
+    return deviceFound;
+
+}
+
+bool RHD2000Thread::enableHeadstage(int hsNum, bool enabled, int nStr, int strChans)
+{
+
+    /*   evalBoard->enableDataStream(hsNum, enabled);*/
+
+    if (enabled)
+    {
+        headstagesArray[hsNum]->setNumStreams(nStr);
+        headstagesArray[hsNum]->setChannelsPerStream(strChans,enabledStreams.size());
+        enabledStreams.add(headstagesArray[hsNum]->getDataStream(0));
+        numChannelsPerDataStream.add(strChans);
+        if (nStr > 1)
+        {
+            enabledStreams.add(headstagesArray[hsNum]->getDataStream(1));
+            numChannelsPerDataStream.add(strChans);
+        }
+    }
+    else
+    {
+        int idx = enabledStreams.indexOf(headstagesArray[hsNum]->getDataStream(0));
+        if (idx >= 0)
+        {
+            enabledStreams.remove(idx);
+            numChannelsPerDataStream.remove(idx);
+        }
+        if (headstagesArray[hsNum]->getNumStreams() > 1)
+        {
+            idx = enabledStreams.indexOf(headstagesArray[hsNum]->getDataStream(1));
+            if (idx >= 0)
+            {
+                enabledStreams.remove(idx);
+                numChannelsPerDataStream.remove(idx);
+            }
+        }
+        headstagesArray[hsNum]->setNumStreams(0);
+    }
+
+    /*
+    std::cout << "Enabled channels: ";
+
+    for (int i = 0; i < MAX_NUM_DATA_STREAMS; i++)
+    {
+        std::cout << numChannelsPerDataStream[i] << " ";
+    }*/
+
+    dataBuffer->resize(getNumChannels(), 10000);
+
+    return true;
+}
+
+void RHD2000Thread::updateBoardStreams()
+{
+    for (int i=0; i <  MAX_NUM_DATA_STREAMS; i++)
+    {
+        if (i < enabledStreams.size())
+        {
+            evalBoard->enableDataStream(i,true);
+            evalBoard->setDataSource(i,enabledStreams[i]);
+        }
+        else
+        {
+            evalBoard->enableDataStream(i,false);
+        }
+    }
+}
+
+bool RHD2000Thread::isHeadstageEnabled(int hsNum)
+{
+
+    return headstagesArray[hsNum]->isPlugged();
+
+}
+
+bool RHD2000Thread::isReady()
+{
+	return deviceFound && (getNumChannels() > 0);
+}
+
+int RHD2000Thread::getActiveChannelsInHeadstage(int hsNum)
+{
+    return headstagesArray[hsNum]->getNumActiveChannels();
+}
+
+int RHD2000Thread::getChannelsInHeadstage(int hsNum)
+{
+    return headstagesArray[hsNum]->getNumChannels();
+}
+
+/*void RHD2000Thread::assignAudioOut(int dacChannel, int dataChannel)
+{
+    if (deviceFound)
+    {
+        if (dacChannel == 0)
+        {
+            audioOutputR = dataChannel;
+            dacChannels[0] = dataChannel;
+        }
+        else if (dacChannel == 1)
+        {
+            audioOutputL = dataChannel;
+            dacChannels[1] = dataChannel;
+        }
+
+        dacOutputShouldChange = true; // set a flag and take care of setting wires
+        // during the updateBuffer() method
+        // to avoid problems
+    }
+
+}*/
+
+void RHD2000Thread::enableAdcs(bool t)
+{
+
+    acquireAdcChannels = t;
+
+    dataBuffer->resize(getNumChannels(), 10000);
+
+}
+
+
+void RHD2000Thread::setSampleRate(int sampleRateIndex, bool isTemporary)
+{
+	impedanceThread->stopThreadSafely();
+    if (!isTemporary)
+    {
+        savedSampleRateIndex = sampleRateIndex;
+    }
+
+    int numUsbBlocksToRead = 0; // placeholder - make this change the number of blocks that are read in RHD2000Thread::updateBuffer()
+
+	rhd2000PCIe::AmplifierSampleRate sampleRate; // just for local use
+
+    switch (sampleRateIndex)
+    {
+        case 0:
+			sampleRate = rhd2000PCIe::SampleRate1000Hz;
+            numUsbBlocksToRead = 1;
+            boardSampleRate = 1000.0f;
+            break;
+        case 1:
+			sampleRate = rhd2000PCIe::SampleRate1250Hz;
+            numUsbBlocksToRead = 1;
+            boardSampleRate = 1250.0f;
+            break;
+        case 2:
+			sampleRate = rhd2000PCIe::SampleRate1500Hz;
+            numUsbBlocksToRead = 1;
+            boardSampleRate = 1500.0f;
+            break;
+        case 3:
+			sampleRate = rhd2000PCIe::SampleRate2000Hz;
+            numUsbBlocksToRead = 1;
+            boardSampleRate = 2000.0f;
+            break;
+        case 4:
+			sampleRate = rhd2000PCIe::SampleRate2500Hz;
+            numUsbBlocksToRead = 1;
+            boardSampleRate = 2500.0f;
+            break;
+        case 5:
+			sampleRate = rhd2000PCIe::SampleRate3000Hz;
+            numUsbBlocksToRead = 2;
+            boardSampleRate = 3000.0f;
+            break;
+        case 6:
+			sampleRate = rhd2000PCIe::SampleRate3333Hz;
+            numUsbBlocksToRead = 2;
+            boardSampleRate = 3333.0f;
+            break;
+        case 7:
+			sampleRate = rhd2000PCIe::SampleRate4000Hz;
+            numUsbBlocksToRead = 2;
+            boardSampleRate = 4000.0f;
+            break;
+        case 8:
+			sampleRate = rhd2000PCIe::SampleRate5000Hz;
+            numUsbBlocksToRead = 3;
+            boardSampleRate = 5000.0f;
+            break;
+        case 9:
+			sampleRate = rhd2000PCIe::SampleRate6250Hz;
+            numUsbBlocksToRead = 3;
+            boardSampleRate = 6250.0f;
+            break;
+        case 10:
+			sampleRate = rhd2000PCIe::SampleRate8000Hz;
+            numUsbBlocksToRead = 4;
+            boardSampleRate = 8000.0f;
+            break;
+        case 11:
+			sampleRate = rhd2000PCIe::SampleRate10000Hz;
+            numUsbBlocksToRead = 6;
+            boardSampleRate = 10000.0f;
+            break;
+        case 12:
+			sampleRate = rhd2000PCIe::SampleRate12500Hz;
+            numUsbBlocksToRead = 7;
+            boardSampleRate = 12500.0f;
+            break;
+        case 13:
+			sampleRate = rhd2000PCIe::SampleRate15000Hz;
+            numUsbBlocksToRead = 8;
+            boardSampleRate = 15000.0f;
+            break;
+        case 14:
+			sampleRate = rhd2000PCIe::SampleRate20000Hz;
+            numUsbBlocksToRead = 12;
+            boardSampleRate = 20000.0f;
+            break;
+        case 15:
+			sampleRate = rhd2000PCIe::SampleRate25000Hz;
+            numUsbBlocksToRead = 14;
+            boardSampleRate = 25000.0f;
+            break;
+        case 16:
+			sampleRate = rhd2000PCIe::SampleRate30000Hz;
+            numUsbBlocksToRead = 16;
+            boardSampleRate = 30000.0f;
+            break;
+        default:
+			sampleRate = rhd2000PCIe::SampleRate10000Hz;
+            numUsbBlocksToRead = 6;
+            boardSampleRate = 10000.0f;
+    }
+
+
+    // Select per-channel amplifier sampling rate.
+    evalBoard->setSampleRate(sampleRate);
+
+    std::cout << "Sample rate set to " << evalBoard->getSampleRate() << std::endl;
+
+    // Now that we have set our sampling rate, we can set the MISO sampling delay
+    // which is dependent on the sample rate.
+	evalBoard->setCableLengthMeters(rhd2000PCIe::PortA, cableLengthPortA);
+	evalBoard->setCableLengthMeters(rhd2000PCIe::PortB, cableLengthPortB);
+	evalBoard->setCableLengthMeters(rhd2000PCIe::PortC, cableLengthPortC);
+	evalBoard->setCableLengthMeters(rhd2000PCIe::PortD, cableLengthPortD);
+
+    updateRegisters();
+
+}
+
+void RHD2000Thread::updateRegisters()
+{
+
+    if (!deviceFound) //Safety to avoid crashes loading a chain with Rythm node withouth a board
+    {
+        return;
+    }
+    // Set up an RHD2000 register object using this sample rate to
+    // optimize MUX-related register settings.
+    chipRegisters.defineSampleRate(boardSampleRate);
+
+
+    int commandSequenceLength;
+    vector<int> commandList;
+
+    // Create a command list for the AuxCmd1 slot.  This command sequence will continuously
+    // update Register 3, which controls the auxiliary digital output pin on each RHD2000 chip.
+    // In concert with the v1.4 Rhythm FPGA code, this permits real-time control of the digital
+    // output pin on chips on each SPI port.
+    chipRegisters.setDigOutLow();   // Take auxiliary output out of HiZ mode.
+    commandSequenceLength = chipRegisters.createCommandListUpdateDigOut(commandList);
+	evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd1, 0);
+	evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd1, 0, commandSequenceLength - 1);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd1, 0);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd1, 0);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd1, 0);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd1, 0);
+
+    // // Next, we'll create a command list for the AuxCmd2 slot.  This command sequence
+    // // will sample the temperature sensor and other auxiliary ADC inputs.
+    commandSequenceLength = chipRegisters.createCommandListTempSensor(commandList);
+	evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd2, 0);
+	evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd2, 0, commandSequenceLength - 1);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd2, 0);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd2, 0);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd2, 0);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd2, 0);
+
+    // Before generating register configuration command sequences, set amplifier
+    // bandwidth paramters.
+    actualDspCutoffFreq = chipRegisters.setDspCutoffFreq(desiredDspCutoffFreq);
+    //std::cout << "DSP Cutoff Frequency " << actualDspCutoffFreq << std::endl;
+    actualLowerBandwidth = chipRegisters.setLowerBandwidth(desiredLowerBandwidth);
+    actualUpperBandwidth = chipRegisters.setUpperBandwidth(desiredUpperBandwidth);
+    chipRegisters.enableDsp(dspEnabled);
+    //std::cout << "DSP Offset Status " << dspEnabled << std::endl;
+
+    // turn on aux inputs
+    chipRegisters.enableAux1(true);
+    chipRegisters.enableAux2(true);
+    chipRegisters.enableAux3(true);
+
+    chipRegisters.createCommandListRegisterConfig(commandList, true);
+    // Upload version with ADC calibration to AuxCmd3 RAM Bank 0.
+	evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd3, 0);
+	evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd3, 0,
+                                      commandSequenceLength - 1);
+
+    commandSequenceLength = chipRegisters.createCommandListRegisterConfig(commandList, false);
+    // Upload version with no ADC calibration to AuxCmd3 RAM Bank 1.
+	evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd3, 1);
+	evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd3, 0,
+                                      commandSequenceLength - 1);
+
+
+    chipRegisters.setFastSettle(true);
+
+    commandSequenceLength = chipRegisters.createCommandListRegisterConfig(commandList, false);
+    // Upload version with fast settle enabled to AuxCmd3 RAM Bank 2.
+	evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd3, 2);
+	evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd3, 0,
+                                      commandSequenceLength - 1);
+
+    chipRegisters.setFastSettle(false);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd3,
+                                    fastSettleEnabled ? 2 : 1);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd3,
+                                    fastSettleEnabled ? 2 : 1);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd3,
+                                    fastSettleEnabled ? 2 : 1);
+	evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd3,
+                                    fastSettleEnabled ? 2 : 1);
+}
+
+void RHD2000Thread::setCableLength(int hsNum, float length)
+{
+    // Set the MISO sampling delay, which is dependent on the sample rate.
+
+    switch (hsNum)
+    {
+        case 0:
+			evalBoard->setCableLengthFeet(rhd2000PCIe::PortA, length);
+            break;
+        case 1:
+			evalBoard->setCableLengthFeet(rhd2000PCIe::PortB, length);
+            break;
+        case 2:
+			evalBoard->setCableLengthFeet(rhd2000PCIe::PortC, length);
+            break;
+        case 3:
+			evalBoard->setCableLengthFeet(rhd2000PCIe::PortD, length);
+            break;
+        default:
+            break;
+    }
+
+}
+
+bool RHD2000Thread::startAcquisition()
+{
+	impedanceThread->waitSafely();
+    dataBlock = new Rhd2000DataBlock(evalBoard->getNumEnabledDataStreams());
+
+    std::cout << "Expecting " << getNumChannels() << " channels." << std::endl;
+
+    //memset(filter_states,0,256*sizeof(double));
+
+    /*int ledArray[8] = {1, 1, 0, 0, 0, 0, 0, 0};
+    evalBoard->setLedDisplay(ledArray);
+
+    cout << "Number of 16-bit words in FIFO: " << evalBoard->numWordsInFifo() << endl;
+    cout << "Is eval board running: " << evalBoard->isRunning() << endl;*/
+
+
+    //std::cout << "Setting max timestep." << std::endl;
+    //evalBoard->setMaxTimeStep(100);
+
+
+    std::cout << "Starting acquisition." << std::endl;
+    if (1)
+    {
+        // evalBoard->setContinuousRunMode(false);
+        //  evalBoard->setMaxTimeStep(0);
+        std::cout << "Flushing FIFO." << std::endl;
+        evalBoard->flush();
+        evalBoard->setContinuousRunMode(true);
+		//evalBoard->printFIFOmetrics();
+        evalBoard->run();
+		//evalBoard->printFIFOmetrics();
+    }
+
+    blockSize = dataBlock->calculateDataBlockSizeInWords(evalBoard->getNumEnabledDataStreams());
+	std::cout << "Expecting blocksize of " << blockSize << " for " << evalBoard->getNumEnabledDataStreams() << " streams" << std::endl;
+	//evalBoard->printFIFOmetrics();
+    startThread();
+
+
+    isTransmitting = true;
+
+    return true;
+}
+
+bool RHD2000Thread::stopAcquisition()
+{
+
+    //  isTransmitting = false;
+    std::cout << "RHD2000 data thread stopping acquisition." << std::endl;
+
+    if (isThreadRunning())
+    {
+        signalThreadShouldExit();
+
+    }
+
+    if (waitForThreadToExit(500))
+    {
+        std::cout << "Thread exited." << std::endl;
+    }
+    else
+    {
+        std::cout << "Thread failed to exit, continuing anyway..." << std::endl;
+    }
+
+    if (deviceFound)
+    {
+        evalBoard->setContinuousRunMode(false);
+        evalBoard->setMaxTimeStep(0);
+        std::cout << "Flushing FIFO." << std::endl;
+        evalBoard->flush();
+        //   evalBoard->setContinuousRunMode(true);
+        //   evalBoard->run();
+
+    }
+
+    dataBuffer->clear();
+
+    /*if (deviceFound)
+    {
+        cout << "Number of 16-bit words in FIFO: " << evalBoard->numWordsInFifo() << endl;
+
+        // std::cout << "Stopped eval board." << std::endl;
+
+
+        int ledArray[8] = {1, 0, 0, 0, 0, 0, 0, 0};
+        evalBoard->setLedDisplay(ledArray);
+    }*/
+
+    isTransmitting = false;
+	dacOutputShouldChange = false;
+
+    return true;
+}
+
+bool RHD2000Thread::updateBuffer()
+{
+	//int chOffset;
+	unsigned char* bufferPtr;
+    //cout << "Number of 16-bit words in FIFO: " << evalBoard->numWordsInFifo() << endl;
+    //cout << "Block size: " << blockSize << endl;
+   
+	//std::cout << "Current number of words: " <<  evalBoard->numWordsInFifo() << " for " << blockSize << std::endl;
+    if (true)//evalBoard->isUSB3() || evalBoard->numWordsInFifo() >= blockSize)
+    {
+		bool return_code;
+
+		return_code = evalBoard->readRawDataBlock(&bufferPtr);
+
+		int index = 0;
+		int auxIndex, chanIndex;
+		int numStreams = enabledStreams.size();
+		int nSamps = Rhd2000DataBlock::getSamplesPerDataBlock();
+		
+		//evalBoard->printFIFOmetrics();
+        for (int samp = 0; samp < nSamps; samp++)
+        {
+            int channel = -1;
+
+			if (!Rhd2000DataBlock::checkUsbHeader(bufferPtr, index))
+			{
+				cerr << "Error in Rhd2000EvalBoard::readDataBlock: Incorrect header." << endl;
+				break;
+			}
+
+			index += 8;
+			timestamp = Rhd2000DataBlock::convertUsbTimeStamp(bufferPtr,index);
+			index += 4;
+			auxIndex = index;
+			//skip the aux channels
+			index += numStreams * 6;
+			// do the neural data channels first
+			for (int dataStream = 0; dataStream < numStreams; dataStream++)
+			{
+				int nChans = numChannelsPerDataStream[dataStream];
+				chanIndex = index + 2*dataStream;
+				if ((chipId[dataStream] == CHIP_ID_RHD2132) && (nChans == 16)) //RHD2132 16ch. headstage
+				{
+					chanIndex += 2 * RHD2132_16CH_OFFSET*numStreams;
+				}
+				for (int chan = 0; chan < nChans; chan++)
+				{
+					channel++;
+					thisSample[channel] = float(*(uint16*)(bufferPtr + chanIndex) - 32768)*0.195f;
+					chanIndex += 2*numStreams;
+				}
+			}
+			index += 64 * numStreams;
+			//now we can do the aux channels
+			auxIndex += 2*numStreams;
+			for (int dataStream = 0; dataStream < numStreams; dataStream++)
+			{
+				if (chipId[dataStream] != CHIP_ID_RHD2164_B)
+				{
+					int auxNum = (samp+3) % 4;
+					if (auxNum < 3)
+					{
+						auxSamples[dataStream][auxNum] = float(*(uint16*)(bufferPtr + auxIndex) - 32768)*0.0000374;
+					}
+					for (int chan = 0; chan < 3; chan++)
+					{
+						channel++;
+						if (auxNum == 3)
+						{
+							auxBuffer[channel] = auxSamples[dataStream][chan];
+						}
+						thisSample[channel] = auxBuffer[channel];
+					}
+				}
+				auxIndex += 2;
+
+			}
+			index += 2 * numStreams;
+			if (acquireAdcChannels)
+			{
+				for (int adcChan = 0; adcChan < 8; ++adcChan)
+				{
+
+					channel++;
+					// ADC waveform units = volts
+					thisSample[channel] =
+						//0.000050354 * float(dataBlock->boardAdcData[adcChan][samp]);
+						0.00015258789 * float(*(uint16*)(bufferPtr + index)) - 5 - 0.4096; // account for +/-5V input range and DC offset
+					index += 2;
+				}
+			}
+			else
+			{
+				index += 16;
+			}
+			eventCode = *(uint16*)(bufferPtr + index);
+			index += 4;
+			dataBuffer->addToBuffer(thisSample, &timestamp, &eventCode, 1);
+#if 0
+            // do the neural data channels first
+            for (int dataStream = 0; dataStream < enabledStreams.size(); dataStream++)
+            {
+				if ((chipId[dataStream] == CHIP_ID_RHD2132) && (numChannelsPerDataStream[dataStream] == 16)) //RHD2132 16ch. headstage
+					chOffset = RHD2132_16CH_OFFSET;
+				else
+					chOffset = 0;
+                for (int chan = 0; chan < numChannelsPerDataStream[dataStream]; chan++)
+                {
+
+                    //  std::cout << "reading sample stream " << streamNumber << " chan " << chan << " sample "<< samp << std::endl;
+
+                    channel++;
+
+                    int value = dataBlock->amplifierData[dataStream][chan+chOffset][samp];
+
+                    thisSample[channel] = float(value-32768)*0.195f;
+                }
+
+
+            }
+
+
+            // then do the Intan AUX channels
+            for (int dataStream = 0; dataStream < enabledStreams.size(); dataStream++)
+            {
+                if (chipId[dataStream] != CHIP_ID_RHD2164_B) //Channel B of 2164 shouldn't be copied
+                {
+                    if (samp % 4 == 1)   // every 4th sample should have auxiliary input data
+                    {
+
+                        // std::cout << "reading sample stream " << streamNumber << " aux ADCs " << std::endl;
+
+                        channel++;
+						thisSample[channel] = 0.0000374 *
+							float(dataBlock->auxiliaryData[dataStream][1][samp + 0] - 32768);
+                        // constant offset keeps the values visible in the LFP Viewer
+
+                        auxBuffer[channel] = thisSample[channel];
+
+                        channel++;
+						thisSample[channel] = 0.0000374 *
+							float(dataBlock->auxiliaryData[dataStream][1][samp + 1] - 32768);
+                        // constant offset keeps the values visible in the LFP Viewer
+
+                        auxBuffer[channel] = thisSample[channel];
+
+
+                        channel++;
+						thisSample[channel] = 0.0000374 *
+							float(dataBlock->auxiliaryData[dataStream][1][samp + 2] - 32768);
+                        // constant offset keeps the values visible in the LFP Viewer
+
+                        auxBuffer[channel] = thisSample[channel];
+
+                    }
+                    else    // repeat last values from buffer
+                    {
+
+                        //std::cout << "reading sample stream " << streamNumber << " aux ADCs " << std::endl;
+
+                        channel++;
+                        thisSample[channel] = auxBuffer[channel];
+                        channel++;
+                        thisSample[channel] = auxBuffer[channel];
+                        channel++;
+                        thisSample[channel] = auxBuffer[channel];
+                    }
+                }
+
+            }
+
+            // finally, loop through acquisition board ADC channels if necessary
+            if (acquireAdcChannels)
+            {
+                for (int adcChan = 0; adcChan < 8; ++adcChan)
+                {
+
+                    channel++;
+                    // ADC waveform units = volts
+                    thisSample[channel] =
+                        //0.000050354 * float(dataBlock->boardAdcData[adcChan][samp]);
+                        0.00015258789 * float(dataBlock->boardAdcData[adcChan][samp]) - 5 - 0.4096; // account for +/-5V input range and DC offset
+                }
+            }
+            // std::cout << channel << std::endl;
+
+            timestamp = dataBlock->timeStamp[samp];
+            //timestamp = timestamp;
+            eventCode = dataBlock->ttlIn[samp];
+            dataBuffer->addToBuffer(thisSample, &timestamp, &eventCode, 1);
+#endif
+        }
+
+    }
+
+	
+    if (dacOutputShouldChange)
+    {
+		std::cout << "DAC" << std::endl;
+        for (int k=0; k<8; k++)
+        {
+            if (dacChannelsToUpdate[k])
+            {
+                dacChannelsToUpdate[k] = false;
+                if (dacChannels[k] >= 0)
+                {
+                  /*  evalBoard->enableDac(k, true);
+                    evalBoard->selectDacDataStream(k, dacStream[k]);
+                    evalBoard->selectDacDataChannel(k, dacChannels[k]);
+                    evalBoard->setDacThreshold(k, (int)abs((dacThresholds[k]/0.195) + 32768),dacThresholds[k] >= 0);
+                   // evalBoard->setDacThresholdVoltage(k, (int) dacThresholds[k]);*/
+                }
+                else
+                {
+                  //  evalBoard->enableDac(k, false);
+                }
+            }
+        }
+
+      /*  evalBoard->setTtlMode(ttlMode ? 1 : 0);
+        evalBoard->enableExternalFastSettle(fastTTLSettleEnabled);
+        evalBoard->setExternalFastSettleChannel(fastSettleTTLChannel);
+        evalBoard->setDacHighpassFilter(desiredDAChpf);
+        evalBoard->enableDacHighpassFilter(desiredDAChpfState);
+		evalBoard->enableBoardLeds(ledsEnabled);
+        evalBoard->setClockDivider(clockDivideFactor);*/
+
+        dacOutputShouldChange = false;
+    }
+	
+    return true;
+
+}
+
+int RHD2000Thread::getChannelFromHeadstage(int hs, int ch)
+{
+    int channelCount = 0;
+    int hsCount = 0;
+    if (hs < 0 || hs >= MAX_NUM_HEADSTAGES+1)
+        return -1;
+    if (hs == MAX_NUM_HEADSTAGES) //let's consider this the ADC channels
+    {
+        if (getNumAdcOutputs() > 0)
+        {
+            return getNumHeadstageOutputs() + getNumAuxOutputs() + ch;
+        }
+        else
+            return -1;
+    }
+    if (headstagesArray[hs]->isPlugged())
+    {
+        if (ch < 0)
+            return -1;
+        if (ch < headstagesArray[hs]->getNumActiveChannels())
+        {
+            for (int i = 0; i < hs; i++)
+            {
+                channelCount += headstagesArray[i]->getNumActiveChannels();
+            }
+            return channelCount + ch;
+        }
+        else if (ch < headstagesArray[hs]->getNumActiveChannels() + 3)
+        {
+            for (int i = 0; i < MAX_NUM_HEADSTAGES; i++)
+            {
+                if (headstagesArray[i]->isPlugged())
+                {
+                    channelCount += headstagesArray[i]->getNumActiveChannels();
+                    if (i < hs)
+                        hsCount++;
+                }
+            }
+			return channelCount + hsCount * 3 + ch-headstagesArray[hs]->getNumActiveChannels();
+        }
+        else
+        {
+            return -1;
+        }
+
+    }
+    else
+    {
+        return -1;
+    }
+}
+
+int RHD2000Thread::getHeadstageChannel(int& hs, int ch)
+{
+    int channelCount = 0;
+    int hsCount = 0;
+
+    if (ch < 0)
+        return -1;
+
+    for (int i = 0; i < MAX_NUM_HEADSTAGES; i++)
+    {
+        if (headstagesArray[i]->isPlugged())
+        {
+            int chans = headstagesArray[hs]->getNumActiveChannels();
+            if (ch >= channelCount && ch < channelCount + chans)
+            {
+                hs = i;
+                return ch - channelCount;
+            }
+            channelCount += chans;
+            hsCount++;
+        }
+    }
+    if (ch < (channelCount + hsCount * 3)) //AUX
+    {
+        hsCount = (ch - channelCount) / 3;
+        for (int i = 0; i < MAX_NUM_HEADSTAGES; i++)
+        {
+            if (headstagesArray[i]->isPlugged())
+            {
+                if (hsCount == 0)
+                {
+                    hs = i;
+                    return ch - channelCount;
+                }
+                hsCount--;
+                channelCount++;
+            }
+        }
+    }
+    return -1;
+}
+
+void RHD2000Thread::enableBoardLeds(bool enable)
+{
+#if 0
+	ledsEnabled = enable;
+	if (isAcquisitionActive())
+		dacOutputShouldChange = true;
+	else
+		evalBoard->enableBoardLeds(enable);
+#endif
+}
+
+int RHD2000Thread::setClockDivider(int divide_ratio)
+{
+#if 0
+    // Divide ratio should be 1 or an even number
+    if (divide_ratio != 1 && divide_ratio % 2) 
+        divide_ratio--;
+
+    // Format the divide ratio from its true value to the 
+    // format required by the firmware
+    // Ratio    N
+    // 1        0
+    // >=2      Ratio/2
+    if (divide_ratio == 1)
+        clockDivideFactor = 0;
+    else
+        clockDivideFactor = static_cast<uint16>(divide_ratio/2);
+
+	if (isAcquisitionActive())
+        dacOutputShouldChange = true;
+    else 
+        evalBoard->setClockDivider(clockDivideFactor);
+#endif
+    return divide_ratio;
+
+}
+
+void RHD2000Thread::runImpedanceTest(ImpedanceData* data)
+{
+	impedanceThread->stopThreadSafely();
+	impedanceThread->prepareData(data);
+	impedanceThread->startThread();
+}
+
+
+RHDHeadstage::RHDHeadstage(rhd2000PCIe::BoardDataSource stream) :
+    dataStream(stream), numStreams(0), channelsPerStream(32), halfChannels(false)
+{
+	streamIndex = -1;
+}
+
+RHDHeadstage::~RHDHeadstage()
+{
+}
+
+void RHDHeadstage::setNumStreams(int num)
+{
+    numStreams = num;
+}
+
+void RHDHeadstage::setChannelsPerStream(int nchan, int index)
+{
+    channelsPerStream = nchan;
+	streamIndex = index;
+}
+
+int RHDHeadstage::getStreamIndex(int index)
+{
+	return streamIndex + index;
+}
+
+int RHDHeadstage::getNumChannels()
+{
+    return channelsPerStream*numStreams;
+}
+
+int RHDHeadstage::getNumStreams()
+{
+    return numStreams;
+}
+
+void RHDHeadstage::setHalfChannels(bool half)
+{
+    halfChannels = half;
+}
+
+int RHDHeadstage::getNumActiveChannels()
+{
+    return (int)(getNumChannels() / (halfChannels ? 2 : 1));
+}
+
+rhd2000PCIe::BoardDataSource RHDHeadstage::getDataStream(int index)
+{
+    if (index < 0 || index > 1) index = 0;
+    return static_cast<rhd2000PCIe::BoardDataSource>(dataStream+MAX_NUM_HEADSTAGES*index);
+}
+
+bool RHDHeadstage::isPlugged()
+{
+    return (numStreams > 0);
+}
+
+/***********************************/
+/* Below is code for impedance measurements */
+
+RHDImpedanceMeasure::RHDImpedanceMeasure(RHD2000Thread* b) : Thread(""), data(nullptr), board(b)
+{
+	// to perform electrode impedance measurements at very low frequencies.
+	const int maxNumBlocks = 120;
+	int numStreams = 8;
+	allocateDoubleArray3D(amplifierPreFilter, numStreams, 32, SAMPLES_PER_DATA_BLOCK_PCIE * maxNumBlocks);
+}
+
+RHDImpedanceMeasure::~RHDImpedanceMeasure()
+{
+	stopThreadSafely();
+}
+
+void RHDImpedanceMeasure::stopThreadSafely()
+{
+	if (isThreadRunning())
+	{
+		CoreServices::sendStatusMessage("Impedance measure in progress. Stopping it.");
+		if (!stopThread(3000)) //wait three seconds max for it to exit gracefully
+		{
+			std::cerr << "ERROR: Impedance measurement thread did not exit. Force killed it. This might led to crashes." << std::endl;
+		}
+	}
+}
+
+void RHDImpedanceMeasure::waitSafely()
+{
+	if (!waitForThreadToExit(120000)) //two minutes should be enough for completing a scan
+	{
+		CoreServices::sendStatusMessage("Impedance measurement took too much. Aborting.");
+		if (!stopThread(3000)) //wait three seconds max for it to exit gracefully
+		{
+			std::cerr << "ERROR: Impedance measurement thread did not exit. Force killed it. This might led to crashes." << std::endl;
+		}
+	}
+}
+
+void RHDImpedanceMeasure::prepareData(ImpedanceData* d)
+{
+	data = d;
+}
+
+
+// Update electrode impedance measurement frequency, after checking that
+// requested test frequency lies within acceptable ranges based on the
+// amplifier bandwidth and the sampling rate.  See impedancefreqdialog.cpp
+// for more information.
+float RHDImpedanceMeasure::updateImpedanceFrequency(float desiredImpedanceFreq, bool& impedanceFreqValid)
+{
+	int impedancePeriod;
+	double lowerBandwidthLimit, upperBandwidthLimit;
+	float actualImpedanceFreq;
+
+	upperBandwidthLimit = board->actualUpperBandwidth / 1.5;
+	lowerBandwidthLimit = board->actualLowerBandwidth * 1.5;
+	if (board->dspEnabled)
+	{
+		if (board->actualDspCutoffFreq > board->actualLowerBandwidth)
+		{
+			lowerBandwidthLimit = board->actualDspCutoffFreq * 1.5;
+		}
+	}
+
+	if (desiredImpedanceFreq > 0.0)
+	{
+		impedancePeriod = (board->boardSampleRate / desiredImpedanceFreq);
+		if (impedancePeriod >= 4 && impedancePeriod <= 1024 &&
+			desiredImpedanceFreq >= lowerBandwidthLimit &&
+			desiredImpedanceFreq <= upperBandwidthLimit)
+		{
+			actualImpedanceFreq = board->boardSampleRate / impedancePeriod;
+			impedanceFreqValid = true;
+		}
+		else
+		{
+			actualImpedanceFreq = 0.0;
+			impedanceFreqValid = false;
+		}
+	}
+	else
+	{
+		actualImpedanceFreq = 0.0;
+		impedanceFreqValid = false;
+	}
+
+	return actualImpedanceFreq;
+}
+
+
+// Reads numBlocks blocks of raw USB data stored in a queue of Rhd2000DataBlock
+// objects, loads this data into this SignalProcessor object, scaling the raw
+// data to generate waveforms with units of volts or microvolts.
+int RHDImpedanceMeasure::loadAmplifierData(queue<Rhd2000DataBlock>& dataQueue,
+	int numBlocks, int numDataStreams)
+{
+
+	int block, t, channel, stream;
+	int indexAmp = 0;
+    /*
+	int indexAux = 0;
+	int indexSupply = 0;
+	int indexAdc = 0;
+	int indexDig = 0;
+	int numWordsWritten = 0;
+
+	int bufferIndex;
+	int16 tempQint16;
+	uint16 tempQuint16;
+	int32 tempQint32;
+
+	bool triggerFound = false;
+	const double AnalogTriggerThreshold = 1.65;
+     */
+
+
+	for (block = 0; block < numBlocks; ++block)
+	{
+
+		// Load and scale RHD2000 amplifier waveforms
+		// (sampled at amplifier sampling rate)
+		for (t = 0; t < SAMPLES_PER_DATA_BLOCK_PCIE; ++t)
+		{
+			for (channel = 0; channel < 32; ++channel)
+			{
+				for (stream = 0; stream < numDataStreams; ++stream)
+				{
+					// Amplifier waveform units = microvolts
+					amplifierPreFilter[stream][channel][indexAmp] = 0.195 *
+						(dataQueue.front().amplifierData[stream][channel][t] - 32768);
+				}
+			}
+			++indexAmp;
+		}
+		// We are done with this Rhd2000DataBlock object; remove it from dataQueue
+		dataQueue.pop();
+	}
+
+	return 0;
+}
+
+#define PI  3.14159265359
+#define TWO_PI  6.28318530718
+#define DEGREES_TO_RADIANS  0.0174532925199
+#define RADIANS_TO_DEGREES  57.2957795132
+
+// Return the magnitude and phase (in degrees) of a selected frequency component (in Hz)
+// for a selected amplifier channel on the selected USB data stream.
+void RHDImpedanceMeasure::measureComplexAmplitude(std::vector<std::vector<std::vector<double>>>& measuredMagnitude,
+	std::vector<std::vector<std::vector<double>>>& measuredPhase,
+	int capIndex, int stream, int chipChannel, int numBlocks,
+	double sampleRate, double frequency, int numPeriods)
+{
+	int period = (sampleRate / frequency);
+	int startIndex = 0;
+	int endIndex = startIndex + numPeriods * period - 1;
+
+	// Move the measurement window to the end of the waveform to ignore start-up transient.
+	while (endIndex < SAMPLES_PER_DATA_BLOCK_PCIE * numBlocks - period)
+	{
+		startIndex += period;
+		endIndex += period;
+	}
+
+	double iComponent, qComponent;
+
+	// Measure real (iComponent) and imaginary (qComponent) amplitude of frequency component.
+	amplitudeOfFreqComponent(iComponent, qComponent, amplifierPreFilter[stream][chipChannel],
+		startIndex, endIndex, sampleRate, frequency);
+	// Calculate magnitude and phase from real (I) and imaginary (Q) components.
+	measuredMagnitude[stream][chipChannel][capIndex] =
+		sqrt(iComponent * iComponent + qComponent * qComponent);
+	measuredPhase[stream][chipChannel][capIndex] =
+		RADIANS_TO_DEGREES *atan2(qComponent, iComponent);
+}
+
+// Returns the real and imaginary amplitudes of a selected frequency component in the vector
+// data, between a start index and end index.
+void RHDImpedanceMeasure::amplitudeOfFreqComponent(double& realComponent, double& imagComponent,
+	const std::vector<double>& data, int startIndex,
+	int endIndex, double sampleRate, double frequency)
+{
+	int length = endIndex - startIndex + 1;
+	const double k = TWO_PI * frequency / sampleRate;  // precalculate for speed
+
+	// Perform correlation with sine and cosine waveforms.
+	double meanI = 0.0;
+	double meanQ = 0.0;
+	for (int t = startIndex; t <= endIndex; ++t)
+	{
+		meanI += data.at(t) * cos(k * t);
+		meanQ += data.at(t) * -1.0 * sin(k * t);
+	}
+	meanI /= (double)length;
+	meanQ /= (double)length;
+
+	realComponent = 2.0 * meanI;
+	imagComponent = 2.0 * meanQ;
+}
+
+
+
+// Given a measured complex impedance that is the result of an electrode impedance in parallel
+// with a parasitic capacitance (i.e., due to the amplifier input capacitance and other
+// capacitances associated with the chip bondpads), this function factors out the effect of the
+// parasitic capacitance to return the acutal electrode impedance.
+void RHDImpedanceMeasure::factorOutParallelCapacitance(double& impedanceMagnitude, double& impedancePhase,
+	double frequency, double parasiticCapacitance)
+{
+	// First, convert from polar coordinates to rectangular coordinates.
+	double measuredR = impedanceMagnitude * cos(DEGREES_TO_RADIANS * impedancePhase);
+	double measuredX = impedanceMagnitude * sin(DEGREES_TO_RADIANS * impedancePhase);
+
+	double capTerm = TWO_PI * frequency * parasiticCapacitance;
+	double xTerm = capTerm * (measuredR * measuredR + measuredX * measuredX);
+	double denominator = capTerm * xTerm + 2 * capTerm * measuredX + 1;
+	double trueR = measuredR / denominator;
+	double trueX = (measuredX + xTerm) / denominator;
+
+	// Now, convert from rectangular coordinates back to polar coordinates.
+	impedanceMagnitude = sqrt(trueR * trueR + trueX * trueX);
+	impedancePhase = RADIANS_TO_DEGREES * atan2(trueX, trueR);
+}
+
+// This is a purely empirical function to correct observed errors in the real component
+// of measured electrode impedances at sampling rates below 15 kS/s.  At low sampling rates,
+// it is difficult to approximate a smooth sine wave with the on-chip voltage DAC and 10 kHz
+// 2-pole lowpass filter.  This function attempts to somewhat correct for this, but a better
+// solution is to always run impedance measurements at 20 kS/s, where they seem to be most
+// accurate.
+void RHDImpedanceMeasure::empiricalResistanceCorrection(double& impedanceMagnitude, double& impedancePhase,
+	double boardSampleRate)
+{
+	// First, convert from polar coordinates to rectangular coordinates.
+	double impedanceR = impedanceMagnitude * cos(DEGREES_TO_RADIANS * impedancePhase);
+	double impedanceX = impedanceMagnitude * sin(DEGREES_TO_RADIANS * impedancePhase);
+
+	// Emprically derived correction factor (i.e., no physical basis for this equation).
+	impedanceR /= 10.0 * exp(-boardSampleRate / 2500.0) * cos(TWO_PI * boardSampleRate / 15000.0) + 1.0;
+
+	// Now, convert from rectangular coordinates back to polar coordinates.
+	impedanceMagnitude = sqrt(impedanceR * impedanceR + impedanceX * impedanceX);
+	impedancePhase = RADIANS_TO_DEGREES * atan2(impedanceX, impedanceR);
+}
+
+void RHDImpedanceMeasure::run()
+{
+	RHD2000Editor* ed;
+	ed = (RHD2000Editor*)board->sn->editor.get();
+	if (data == nullptr)
+		return;
+	runImpedanceMeasurement();
+	restoreFPGA();
+	ed->triggerAsyncUpdate();
+	data = nullptr;
+}
+
+#define CHECK_EXIT if (threadShouldExit()) return
+
+void RHDImpedanceMeasure::runImpedanceMeasurement()
+{
+	int commandSequenceLength, stream, channel, capRange;
+	double cSeries;
+	vector<int> commandList;
+	//int triggerIndex;                       // dummy reference variable; not used
+	queue<Rhd2000DataBlock> bufferQueue;    // dummy reference variable; not used
+	int numdataStreams = board->evalBoard->getNumEnabledDataStreams();
+
+	bool rhd2164ChipPresent = false;
+	int chOffset;
+
+	Array<int> enabledStreams;
+	for (stream = 0; stream < MAX_NUM_DATA_STREAMS; ++stream)
+	{
+		CHECK_EXIT;
+		if (board->evalBoard->isStreamEnabled(stream))
+		{
+			enabledStreams.add(stream);
+		}
+
+		if (board->chipId[stream] == CHIP_ID_RHD2164_B)
+		{
+			rhd2164ChipPresent = true;
+		}
+	}
+
+	bool validImpedanceFreq;
+	float actualImpedanceFreq = updateImpedanceFrequency(1000.0, validImpedanceFreq);
+	if (!validImpedanceFreq)
+	{
+		return;
+	}
+	// Create a command list for the AuxCmd1 slot.
+	commandSequenceLength = board->chipRegisters.createCommandListZcheckDac(commandList, actualImpedanceFreq, 128.0);
+	CHECK_EXIT;
+	board->evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd1, 1);
+	board->evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd1,
+		0, commandSequenceLength - 1);
+	/*if (board->fastTTLSettleEnabled)
+	{
+		board->evalBoard->enableExternalFastSettle(false);
+	}*/
+	CHECK_EXIT;
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA,
+		rhd2000PCIe::AuxCmd1, 1);
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB,
+		rhd2000PCIe::AuxCmd1, 1);
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC,
+		rhd2000PCIe::AuxCmd1, 1);
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD,
+		rhd2000PCIe::AuxCmd1, 1);
+
+	// Select number of periods to measure impedance over
+	int numPeriods = (0.020 * actualImpedanceFreq); // Test each channel for at least 20 msec...
+	if (numPeriods < 5) numPeriods = 5; // ...but always measure across no fewer than 5 complete periods
+	double period = board->boardSampleRate / actualImpedanceFreq;
+	int numBlocks = ceil((numPeriods + 2.0) * period / 60.0);  // + 2 periods to give time to settle initially
+	if (numBlocks < 2) numBlocks = 2;   // need first block for command to switch channels to take effect.
+
+	CHECK_EXIT;
+	board->actualDspCutoffFreq = board->chipRegisters.setDspCutoffFreq(board->desiredDspCutoffFreq);
+	board->actualLowerBandwidth = board->chipRegisters.setLowerBandwidth(board->desiredLowerBandwidth);
+	board->actualUpperBandwidth = board->chipRegisters.setUpperBandwidth(board->desiredUpperBandwidth);
+	board->chipRegisters.enableDsp(board->dspEnabled);
+	board->chipRegisters.enableZcheck(true);
+	commandSequenceLength = board->chipRegisters.createCommandListRegisterConfig(commandList, false);
+	CHECK_EXIT;
+	// Upload version with no ADC calibration to AuxCmd3 RAM Bank 1.
+	board->evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd3, 3);
+	board->evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd3, 0, commandSequenceLength - 1);
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd3, 3);
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd3, 3);
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd3, 3);
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd3, 3);
+
+	CHECK_EXIT;
+	board->evalBoard->setContinuousRunMode(false);
+	board->evalBoard->setMaxTimeStep(SAMPLES_PER_DATA_BLOCK_PCIE * numBlocks);
+
+	// Create matrices of doubles of size (numStreams x 32 x 3) to store complex amplitudes
+	// of all amplifier channels (32 on each data stream) at three different Cseries values.
+	std::vector<std::vector<std::vector<double>>>  measuredMagnitude;
+	std::vector<std::vector<std::vector<double>>>  measuredPhase;
+
+	measuredMagnitude.resize(board->evalBoard->getNumEnabledDataStreams());
+	measuredPhase.resize(board->evalBoard->getNumEnabledDataStreams());
+	for (int i = 0; i < board->evalBoard->getNumEnabledDataStreams(); ++i)
+	{
+		measuredMagnitude[i].resize(32);
+		measuredPhase[i].resize(32);
+		for (int j = 0; j < 32; ++j)
+		{
+			measuredMagnitude[i][j].resize(3);
+			measuredPhase[i][j].resize(3);
+		}
+	}
+
+
+
+	double distance, minDistance, current, Cseries;
+	double impedanceMagnitude, impedancePhase;
+
+	const double bestAmplitude = 250.0;  // we favor voltage readings that are closest to 250 uV: not too large,
+	// and not too small.
+	const double dacVoltageAmplitude = 128 * (1.225 / 256);  // this assumes the DAC amplitude was set to 128
+	const double parasiticCapacitance = 14.0e-12;  // 14 pF: an estimate of on-chip parasitic capacitance,
+	// including 10 pF of amplifier input capacitance.
+	double relativeFreq = actualImpedanceFreq / board->boardSampleRate;
+
+	int bestAmplitudeIndex;
+
+	// We execute three complete electrode impedance measurements: one each with
+	// Cseries set to 0.1 pF, 1 pF, and 10 pF.  Then we select the best measurement
+	// for each channel so that we achieve a wide impedance measurement range.
+	for (capRange = 0; capRange < 3; ++capRange)
+	{
+
+		switch (capRange)
+		{
+		case 0:
+			board->chipRegisters.setZcheckScale(Rhd2000Registers::ZcheckCs100fF);
+			cSeries = 0.1e-12;
+			cout << "setting capacitance to 0.1pF" << endl;
+			break;
+		case 1:
+			board->chipRegisters.setZcheckScale(Rhd2000Registers::ZcheckCs1pF);
+			cSeries = 1.0e-12;
+			cout << "setting capacitance to 1pF" << endl;
+			break;
+		case 2:
+			board->chipRegisters.setZcheckScale(Rhd2000Registers::ZcheckCs10pF);
+			cSeries = 10.0e-12;
+			cout << "setting capacitance to 10pF" << endl;
+			break;
+		}
+
+		// Check all 32 channels across all active data streams.
+		for (channel = 0; channel < 32; ++channel)
+		{
+			CHECK_EXIT;
+			cout << "running impedance on channel " << channel << endl;
+
+			board->chipRegisters.setZcheckChannel(channel);
+			commandSequenceLength =
+				board->chipRegisters.createCommandListRegisterConfig(commandList, false);
+			// Upload version with no ADC calibration to AuxCmd3 RAM Bank 1.
+			board->evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd3, 3);
+
+			board->evalBoard->run();
+			while (board->evalBoard->isRunning())
+			{
+
+			}
+			queue<Rhd2000DataBlock> dataQueue;
+			board->evalBoard->readDataBlocks(numBlocks, dataQueue);
+			loadAmplifierData(dataQueue, numBlocks, numdataStreams);
+			for (stream = 0; stream < numdataStreams; ++stream)
+			{
+				if (board->chipId[stream] != CHIP_ID_RHD2164_B)
+				{
+					measureComplexAmplitude(measuredMagnitude, measuredPhase,
+						capRange, stream, channel, numBlocks, board->boardSampleRate,
+						actualImpedanceFreq, numPeriods);
+				}
+			}
+
+			// If an RHD2164 chip is plugged in, we have to set the Zcheck select register to channels 32-63
+			// and repeat the previous steps.
+			if (rhd2164ChipPresent)
+			{
+				CHECK_EXIT;
+				board->chipRegisters.setZcheckChannel(channel + 32); // address channels 32-63
+				commandSequenceLength =
+					board->chipRegisters.createCommandListRegisterConfig(commandList, false);
+				// Upload version with no ADC calibration to AuxCmd3 RAM Bank 1.
+				board->evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd3, 3);
+
+				board->evalBoard->run();
+				while (board->evalBoard->isRunning())
+				{
+
+				}
+				board->evalBoard->readDataBlocks(numBlocks, dataQueue);
+				loadAmplifierData(dataQueue, numBlocks, numdataStreams);
+
+				for (stream = 0; stream < board->evalBoard->getNumEnabledDataStreams(); ++stream)
+				{
+					if (board->chipId[stream] == CHIP_ID_RHD2164_B)
+					{
+						measureComplexAmplitude(measuredMagnitude, measuredPhase,
+							capRange, stream, channel, numBlocks, board->boardSampleRate,
+							actualImpedanceFreq, numPeriods);
+					}
+				}
+			}
+		}
+	}
+
+	data->streams.clear();
+	data->channels.clear();
+	data->magnitudes.clear();
+	data->phases.clear();
+
+	for (stream = 0; stream < board->evalBoard->getNumEnabledDataStreams(); ++stream)
+	{
+		if ((board->chipId[stream] == CHIP_ID_RHD2132) && (board->numChannelsPerDataStream[stream] == 16))
+			chOffset = RHD2132_16CH_OFFSET;
+		else
+			chOffset = 0;
+
+		for (channel = 0; channel < board->numChannelsPerDataStream[stream]; ++channel)
+		{
+			if (1)
+			{
+				minDistance = 9.9e99;  // ridiculously large number
+				for (capRange = 0; capRange < 3; ++capRange)
+				{
+					// Find the measured amplitude that is closest to bestAmplitude on a logarithmic scale
+					distance = abs(log(measuredMagnitude[stream][channel+chOffset][capRange] / bestAmplitude));
+					if (distance < minDistance)
+					{
+						bestAmplitudeIndex = capRange;
+						minDistance = distance;
+					}
+				}
+				switch (bestAmplitudeIndex)
+				{
+				case 0:
+					Cseries = 0.1e-12;
+					break;
+				case 1:
+					Cseries = 1.0e-12;
+					break;
+				case 2:
+					Cseries = 10.0e-12;
+					break;
+				}
+
+				// Calculate current amplitude produced by on-chip voltage DAC
+				current = TWO_PI * actualImpedanceFreq * dacVoltageAmplitude * Cseries;
+
+				// Calculate impedance magnitude from calculated current and measured voltage.
+				impedanceMagnitude = 1.0e-6 * (measuredMagnitude[stream][channel + chOffset][bestAmplitudeIndex] / current) *
+					(18.0 * relativeFreq * relativeFreq + 1.0);
+
+				// Calculate impedance phase, with small correction factor accounting for the
+				// 3-command SPI pipeline delay.
+				impedancePhase = measuredPhase[stream][channel + chOffset][bestAmplitudeIndex] + (360.0 * (3.0 / period));
+
+				// Factor out on-chip parasitic capacitance from impedance measurement.
+				factorOutParallelCapacitance(impedanceMagnitude, impedancePhase, actualImpedanceFreq,
+					parasiticCapacitance);
+
+				// Perform empirical resistance correction to improve accuarcy at sample rates below
+				// 15 kS/s.
+				empiricalResistanceCorrection(impedanceMagnitude, impedancePhase,
+					board->boardSampleRate);
+
+				data->streams.add(enabledStreams[stream]);
+				data->channels.add(channel + chOffset);
+				data->magnitudes.add(impedanceMagnitude);
+				data->phases.add(impedancePhase);
+
+				if (impedanceMagnitude > 1000000)
+					cout << "stream " << stream << " channel " << 1 + channel << " magnitude: " << String(impedanceMagnitude / 1e6, 2) << " MOhm , phase : " << impedancePhase << endl;
+				else
+					cout << "stream " << stream << " channel " << 1 + channel << " magnitude: " << String(impedanceMagnitude / 1e3, 2) << " kOhm , phase : " << impedancePhase << endl;
+
+			}
+		}
+	}
+	data->valid = true;
+	
+}
+
+void RHDImpedanceMeasure::restoreFPGA()
+{
+	board->evalBoard->setContinuousRunMode(false);
+	board->evalBoard->setMaxTimeStep(0);
+	board->evalBoard->flush();
+
+	// Switch back to flatline
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd1, 0);
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd1, 0);
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd1, 0);
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd1, 0);
+	board->evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd1, 0, 1);
+
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd3,
+		board->fastSettleEnabled ? 2 : 1);
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd3,
+		board->fastSettleEnabled ? 2 : 1);
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd3,
+		board->fastSettleEnabled ? 2 : 1);
+	board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd3,
+		board->fastSettleEnabled ? 2 : 1);
+
+	/*if (board->fastTTLSettleEnabled)
+	{
+		board->evalBoard->enableExternalFastSettle(true);
+	}*/
+}
diff --git a/Source/Plugins/PCIeRhythm/RHD2000Thread.h b/Source/Plugins/PCIeRhythm/RHD2000Thread.h
new file mode 100644
index 0000000000000000000000000000000000000000..66fe3a2167653afa9d6373544cbfde3a6ffcb594
--- /dev/null
+++ b/Source/Plugins/PCIeRhythm/RHD2000Thread.h
@@ -0,0 +1,274 @@
+/*
+    ------------------------------------------------------------------
+
+    This file is part of the Open Ephys GUI
+    Copyright (C) 2014 Open Ephys
+
+    ------------------------------------------------------------------
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef __RHD2000THREAD_H_2C4CBD67__
+#define __RHD2000THREAD_H_2C4CBD67__
+
+#include <DataThreadHeaders.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "rhythm-api/rhd2000PCIe.h"
+#include "rhythm-api/rhd2000registers.h"
+#include "rhythm-api/rhd2000datablock.h"
+
+
+#define MAX_NUM_DATA_STREAMS_PCIE 16
+#define MAX_NUM_HEADSTAGES 8
+
+#define MAX_NUM_CHANNELS MAX_NUM_DATA_STREAMS_PCIE*35
+
+class SourceNode;
+
+namespace PCIeRhythm {
+	class RHDHeadstage;
+	class RHDImpedanceMeasure;
+
+	struct ImpedanceData
+	{
+		Array<int> streams;
+		Array<int> channels;
+		Array<float> magnitudes;
+		Array<float> phases;
+		bool valid;
+	};
+
+	/**
+
+	  Communicates with the RHD2000 Evaluation Board from Intan Technologies
+
+	  @see DataThread, SourceNode
+
+	  */
+
+	class RHD2000Thread : public DataThread, public Timer
+	{
+		friend class RHDImpedanceMeasure;
+	public:
+		RHD2000Thread(SourceNode* sn);
+		~RHD2000Thread();
+
+		// for communication with SourceNode processors:
+		bool foundInputSource();
+		int getNumChannels();
+		int getNumHeadstageOutputs();
+		int getNumAuxOutputs();
+		int getNumAdcOutputs();
+		float getSampleRate();
+		float getBitVolts(Channel* chan);
+		float getAdcBitVolts(int channelNum);
+
+		bool isHeadstageEnabled(int hsNum);
+		int getChannelsInHeadstage(int hsNum);
+
+		void setSampleRate(int index, bool temporary = false);
+
+		double setUpperBandwidth(double upper); // set desired BW, returns actual BW
+		double setLowerBandwidth(double lower);
+
+		double setDspCutoffFreq(double freq);
+		double getDspCutoffFreq();
+
+		void setDSPOffset(bool state);
+
+		int setNoiseSlicerLevel(int level);
+		void setFastTTLSettle(bool state, int channel);
+		void setTTLoutputMode(bool state);
+		void setDAChpf(float cutoff, bool enabled);
+
+		void scanPorts();
+		int getNumEventChannels();
+
+		void enableAdcs(bool);
+
+		bool isAcquisitionActive();
+		bool isReady();
+
+		int modifyChannelGain(int channel, float gain);
+		int modifyChannelName(int channel, String newName);
+		void getEventChannelNames(StringArray& Names);
+		Array<int> getDACchannels();
+		void setDACchannel(int dacOutput, int channel);
+		void setDACthreshold(int dacOutput, float threshold);
+		void setDefaultNamingScheme(int scheme);
+
+		String getChannelName(int ch);
+		void setNumChannels(int hsNum, int nChannels);
+		int getHeadstageChannels(int hsNum);
+		int getActiveChannelsInHeadstage(int hsNum);
+		bool usesCustomNames();
+
+		/* Gets the absolute channel index from the headstage channel index*/
+		int getChannelFromHeadstage(int hs, int ch);
+		/*Gets the headstage relative channel index from the absolute channel index*/
+		int getHeadstageChannel(int& hs, int ch);
+
+		void runImpedanceTest(ImpedanceData* data);
+		void enableBoardLeds(bool enable);
+		int setClockDivider(int divide_ratio);
+		GenericEditor* createEditor(SourceNode* sn);
+
+		static DataThread* createDataThread(SourceNode* sn);
+
+	private:
+
+		bool enableHeadstage(int hsNum, bool enabled, int nStr = 1, int strChans = 32);
+		void updateBoardStreams();
+		void setCableLength(int hsNum, float length);
+
+		void setDefaultChannelNames();
+
+		ScopedPointer<rhd2000PCIe> evalBoard;
+		Rhd2000Registers chipRegisters;
+		ScopedPointer<Rhd2000DataBlock> dataBlock;
+
+		int numChannels;
+		bool deviceFound;
+
+		float thisSample[MAX_NUM_CHANNELS];
+		float auxBuffer[MAX_NUM_CHANNELS]; // aux inputs are only sampled every 4th sample, so use this to buffer the samples so they can be handles just like the regular neural channels later
+		float auxSamples[MAX_NUM_DATA_STREAMS_PCIE][3];
+
+		unsigned int blockSize;
+
+		bool isTransmitting;
+
+		bool dacOutputShouldChange;
+		bool acquireAdcChannels;
+		bool acquireAuxChannels;
+
+		bool fastSettleEnabled;
+		bool fastTTLSettleEnabled;
+		bool fastSettleTTLChannel;
+		bool ttlMode;
+		bool desiredDAChpfState;
+		double desiredDAChpf;
+
+		bool dspEnabled;
+		double actualDspCutoffFreq, desiredDspCutoffFreq;
+		double actualUpperBandwidth, desiredUpperBandwidth;
+		double actualLowerBandwidth, desiredLowerBandwidth;
+		int actualNoiseSlicerLevel, desiredNoiseSlicerLevel;
+		double boardSampleRate;
+		int savedSampleRateIndex;
+
+		String libraryFilePath;
+
+		void timerCallback();
+
+		bool startAcquisition();
+		bool stopAcquisition();
+
+		bool openBoard();
+		void initializeBoard();
+
+		void updateRegisters();
+
+		int deviceId(Rhd2000DataBlock* dataBlock, int stream, int& register59Value);
+
+		bool updateBuffer();
+
+		double cableLengthPortA, cableLengthPortB, cableLengthPortC, cableLengthPortD;
+
+		int audioOutputL, audioOutputR;
+		int* dacChannels, *dacStream;
+		float* dacThresholds;
+		bool* dacChannelsToUpdate;
+		Array<int> chipId;
+		OwnedArray<RHDHeadstage> headstagesArray;
+		Array<rhd2000PCIe::BoardDataSource> enabledStreams;
+		Array<int> numChannelsPerDataStream;
+
+		// used for data stream names...
+		int numberingScheme;
+		Array<float> adcBitVolts;
+		bool newScan;
+		ScopedPointer<RHDImpedanceMeasure> impedanceThread;
+		bool ledsEnabled;
+
+		// Sync ouput divide factor
+		uint16 clockDivideFactor;
+
+		JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RHD2000Thread);
+	};
+
+	class RHDHeadstage
+	{
+	public:
+		RHDHeadstage(rhd2000PCIe::BoardDataSource stream);
+		~RHDHeadstage();
+		void setNumStreams(int num);
+		void setChannelsPerStream(int nchan, int index);
+		int getStreamIndex(int index);
+		int getNumChannels();
+		int getNumStreams();
+		void setHalfChannels(bool half); //mainly used for de 16ch rhd2132 board
+		int getNumActiveChannels();
+		rhd2000PCIe::BoardDataSource getDataStream(int index);
+		bool isPlugged();
+	private:
+		rhd2000PCIe::BoardDataSource dataStream;
+		int streamIndex;
+		int numStreams;
+		int channelsPerStream;
+		bool halfChannels;
+		JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RHDHeadstage);
+	};
+
+	class RHDImpedanceMeasure : public Thread
+	{
+	public:
+		RHDImpedanceMeasure(RHD2000Thread* b);
+		~RHDImpedanceMeasure();
+		void prepareData(ImpedanceData* d);
+		void stopThreadSafely();
+		void waitSafely();
+		void run();
+	private:
+		void runImpedanceMeasurement();
+		void restoreFPGA();
+		void measureComplexAmplitude(std::vector<std::vector<std::vector<double>>>& measuredMagnitude,
+			std::vector<std::vector<std::vector<double>>>& measuredPhase,
+			int capIndex, int stream, int chipChannel, int numBlocks,
+			double sampleRate, double frequency, int numPeriods);
+		void amplitudeOfFreqComponent(double& realComponent, double& imagComponent,
+			const std::vector<double>& data, int startIndex,
+			int endIndex, double sampleRate, double frequency);
+		float updateImpedanceFrequency(float desiredImpedanceFreq, bool& impedanceFreqValid);
+		void factorOutParallelCapacitance(double& impedanceMagnitude, double& impedancePhase,
+			double frequency, double parasiticCapacitance);
+		void empiricalResistanceCorrection(double& impedanceMagnitude, double& impedancePhase,
+			double boardSampleRate);
+		int loadAmplifierData(queue<Rhd2000DataBlock>& dataQueue,
+			int numBlocks, int numDataStreams);
+
+		std::vector<std::vector<std::vector<double>>> amplifierPreFilter;
+
+		ImpedanceData* data;
+		RHD2000Thread* board;
+
+		JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RHDImpedanceMeasure);
+	};
+};
+#endif  // __RHD2000THREAD_H_2C4CBD67__
diff --git a/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000PCIe.cpp b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000PCIe.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..923344b62b6bb42a582fe826566ec736f92f94be
--- /dev/null
+++ b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000PCIe.cpp
@@ -0,0 +1,974 @@
+#include "rhd2000PCIe.h"
+#include <iostream>
+#include <iomanip>
+#include <fstream>
+#include <queue>
+#include <cmath>
+#include <io.h>
+#include <fcntl.h>
+
+#ifdef _WIN32
+#define Open _open
+#define Seek _lseek
+#define Read _read
+#define Write _write
+#define Close _close
+#else
+#define Open open
+#define Seek lseek
+#define Read read
+#define Write write
+#define Close close
+#endif
+
+#include "rhd2000datablock.h"
+using namespace PCIeRhythm;
+
+rhd2000PCIe::rhd2000PCIe()
+{
+	int i;
+	sampleRate = SampleRate30000Hz; // Rhythm FPGA boots up with 30.0 kS/s/channel sampling rate
+	numDataStreams = 0;
+	fidControl = -1;
+	fidStatus = -1;
+	fidFIFO = -1;
+
+	for (i = 0; i < MAX_NUM_DATA_STREAMS; ++i) {
+		dataStreamEnabled[i] = 0;
+	}
+
+	cableDelay.resize(4, -1);
+}
+
+
+rhd2000PCIe::~rhd2000PCIe()
+{
+	if (fidControl >= 0)
+		Close(fidControl);
+	if (fidStatus >= 0)
+		Close(fidStatus);
+	if (fidFIFO >= 0)
+		Close(fidFIFO);
+}
+
+void rhd2000PCIe::writeRegister(controlAddr reg, int16_t value, int16_t mask)
+{
+	int regAddr = static_cast<int>(reg);
+
+	
+	if (Seek(fidControl, regAddr, SEEK_SET) < 0)
+	{
+		std::cerr << "Error seeking control to addr " << regAddr << std::endl;
+		return;
+	}
+
+	int16_t writeVal;
+	if (mask != 0xFFFF)
+	{
+		int16_t curVal;
+		int rd = Read(fidControl, &curVal, 2);
+		{
+			std::cerr << "Unsuccesful read to control addr " << regAddr << " code: " << rd << std::endl;
+			return;
+		}
+		if (Seek(fidControl, regAddr, SEEK_SET) < 0)
+		{
+			std::cerr << "Error re-seeking control to addr " << regAddr << std::endl;
+			return;
+		}
+		writeVal = (curVal & ~mask) | (value & mask);
+	}
+	else
+		writeVal = value;
+
+	int wd = Write(fidControl, &writeVal, 2);
+	if (wd < 2)
+	{
+		std::cerr << "Unsuccesful write to control addr " << regAddr << " code: " << wd << std::endl;
+		return;
+	}
+}
+
+int16_t rhd2000PCIe::readRegister(statusAddr reg) const
+{
+	int16_t value = -1;
+	int regAddr = static_cast<int>(reg);
+	if (Seek(fidStatus, regAddr, SEEK_SET) < 0)
+	{
+		std::cerr << "Error seeking status to addr " << regAddr << std::endl;
+		return value;
+	}
+	int rd = Read(fidStatus, &value, 2);
+	if (rd < 2)
+	{
+		std::cerr << "Unsuccesful read to status addr " << regAddr << " code: " << rd << std::endl;
+		return value;
+	}
+	return value;
+}
+
+bool rhd2000PCIe::open()
+{
+	fidControl = Open("\\\\.\\xillybus_control_regs_16", O_RDWR | O_BINARY);
+	if (fidControl < 0)
+	{
+		std::cerr << "Error opening control file" << std::endl;
+		return false;
+	}
+
+	fidStatus = Open("\\\\.\\xillybus_status_regs_16", O_RDONLY | O_BINARY);
+	if (fidStatus < 0)
+	{
+		std::cerr << "Error opening status file" << std::endl;
+		Close(fidControl);
+		fidControl = -1;
+		return false;
+	}
+
+	fidFIFO = Open("\\\\.\\xillybus_neural_data_32", O_RDONLY | O_BINARY);
+	if (fidFIFO < 0)
+	{
+		std:cerr << "Error opening data FIFO" << std::endl;
+		Close(fidControl);
+		Close(fidStatus);
+		fidControl = -1;
+		fidStatus = -1;
+	}
+	std::cout << "Device files opened" << std::endl;
+	return true;
+}
+
+// Initialize Rhythm FPGA to default starting values.
+void rhd2000PCIe::initialize()
+{
+	int i;
+
+	resetBoard();
+	setSampleRate(SampleRate30000Hz);
+	selectAuxCommandBank(PortA, AuxCmd1, 0);
+	selectAuxCommandBank(PortB, AuxCmd1, 0);
+	selectAuxCommandBank(PortC, AuxCmd1, 0);
+	selectAuxCommandBank(PortD, AuxCmd1, 0);
+	selectAuxCommandBank(PortA, AuxCmd2, 0);
+	selectAuxCommandBank(PortB, AuxCmd2, 0);
+	selectAuxCommandBank(PortC, AuxCmd2, 0);
+	selectAuxCommandBank(PortD, AuxCmd2, 0);
+	selectAuxCommandBank(PortA, AuxCmd3, 0);
+	selectAuxCommandBank(PortB, AuxCmd3, 0);
+	selectAuxCommandBank(PortC, AuxCmd3, 0);
+	selectAuxCommandBank(PortD, AuxCmd3, 0);
+	selectAuxCommandLength(AuxCmd1, 0, 0);
+	selectAuxCommandLength(AuxCmd2, 0, 0);
+	selectAuxCommandLength(AuxCmd3, 0, 0);
+	setContinuousRunMode(true);
+	setMaxTimeStep(4294967295);  // 4294967295 == (2^32 - 1)
+
+	setCableLengthFeet(PortA, 3.0);  // assume 3 ft cables
+	setCableLengthFeet(PortB, 3.0);
+	setCableLengthFeet(PortC, 3.0);
+	setCableLengthFeet(PortD, 3.0);
+
+	setDspSettle(false);
+
+	setDataSource(0, PortA1);
+	setDataSource(1, PortB1);
+	setDataSource(2, PortC1);
+	setDataSource(3, PortD1);
+	setDataSource(4, PortA2);
+	setDataSource(5, PortB2);
+	setDataSource(6, PortC2);
+	setDataSource(7, PortD2);
+	setDataSource(8, PortA1);
+	setDataSource(9, PortB1);
+	setDataSource(10, PortC1);
+	setDataSource(11, PortD1);
+	setDataSource(12, PortA2);
+	setDataSource(13, PortB2);
+	setDataSource(14, PortC2);
+	setDataSource(15, PortD2);
+
+	enableDataStream(0, true);        // start with only one data stream enabled
+	for (i = 1; i < MAX_NUM_DATA_STREAMS; i++) {
+		enableDataStream(i, false);
+	}	
+}
+
+void rhd2000PCIe::resetBoard()
+{
+	writeRegister(ResetRun, 0x1, 0x1);
+	writeRegister(ResetRun, 0x0, 0x1);
+}
+
+// Set the per-channel sampling rate of the RHD2000 chips connected to the FPGA.
+bool rhd2000PCIe::setSampleRate(AmplifierSampleRate newSampleRate)
+{
+	// Assuming a 100 MHz reference clock is provided to the FPGA, the programmable FPGA clock frequency
+	// is given by:
+	//
+	//       FPGA internal clock frequency = 100 MHz * (M/D) / 2
+	//
+	// M and D are "multiply" and "divide" integers used in the FPGA's digital clock manager (DCM) phase-
+	// locked loop (PLL) frequency synthesizer, and are subject to the following restrictions:
+	//
+	//                M must have a value in the range of 2 - 256
+	//                D must have a value in the range of 1 - 256
+	//                M/D must fall in the range of 0.05 - 3.33
+	//
+	// (See pages 85-86 of Xilinx document UG382 "Spartan-6 FPGA Clocking Resources" for more details.)
+	//
+	// This variable-frequency clock drives the state machine that controls all SPI communication
+	// with the RHD2000 chips.  A complete SPI cycle (consisting of one CS pulse and 16 SCLK pulses)
+	// takes 80 clock cycles.  The SCLK period is 4 clock cycles; the CS pulse is high for 14 clock
+	// cycles between commands.
+	//
+	// Rhythm samples all 32 channels and then executes 3 "auxiliary" commands that can be used to read
+	// and write from other registers on the chip, or to sample from the temperature sensor or auxiliary ADC
+	// inputs, for example.  Therefore, a complete cycle that samples from each amplifier channel takes
+	// 80 * (32 + 3) = 80 * 35 = 2800 clock cycles.
+	//
+	// So the per-channel sampling rate of each amplifier is 2800 times slower than the clock frequency.
+	//
+	// Based on these design choices, we can use the following values of M and D to generate the following
+	// useful amplifier sampling rates for electrophsyiological applications:
+	//
+	//   M    D     clkout frequency    per-channel sample rate     per-channel sample period
+	//  ---  ---    ----------------    -----------------------     -------------------------
+	//    7  125          2.80 MHz               1.00 kS/s                 1000.0 usec = 1.0 msec
+	//    7  100          3.50 MHz               1.25 kS/s                  800.0 usec
+	//   21  250          4.20 MHz               1.50 kS/s                  666.7 usec
+	//   14  125          5.60 MHz               2.00 kS/s                  500.0 usec
+	//   35  250          7.00 MHz               2.50 kS/s                  400.0 usec
+	//   21  125          8.40 MHz               3.00 kS/s                  333.3 usec
+	//   14   75          9.33 MHz               3.33 kS/s                  300.0 usec
+	//   28  125         11.20 MHz               4.00 kS/s                  250.0 usec
+	//    7   25         14.00 MHz               5.00 kS/s                  200.0 usec
+	//    7   20         17.50 MHz               6.25 kS/s                  160.0 usec
+	//  112  250         22.40 MHz               8.00 kS/s                  125.0 usec
+	//   14   25         28.00 MHz              10.00 kS/s                  100.0 usec
+	//    7   10         35.00 MHz              12.50 kS/s                   80.0 usec
+	//   21   25         42.00 MHz              15.00 kS/s                   66.7 usec
+	//   28   25         56.00 MHz              20.00 kS/s                   50.0 usec
+	//   35   25         70.00 MHz              25.00 kS/s                   40.0 usec
+	//   42   25         84.00 MHz              30.00 kS/s                   33.3 usec
+	//
+	// To set a new clock frequency, assert new values for M and D (e.g., using okWireIn modules) and
+	// pulse DCM_prog_trigger high (e.g., using an okTriggerIn module).  If this module is reset, it
+	// reverts to a per-channel sampling rate of 30.0 kS/s.
+
+	unsigned long M, D;
+
+	switch (newSampleRate) {
+	case SampleRate1000Hz:
+		M = 7;
+		D = 125;
+		break;
+	case SampleRate1250Hz:
+		M = 7;
+		D = 100;
+		break;
+	case SampleRate1500Hz:
+		M = 21;
+		D = 250;
+		break;
+	case SampleRate2000Hz:
+		M = 14;
+		D = 125;
+		break;
+	case SampleRate2500Hz:
+		M = 35;
+		D = 250;
+		break;
+	case SampleRate3000Hz:
+		M = 21;
+		D = 125;
+		break;
+	case SampleRate3333Hz:
+		M = 14;
+		D = 75;
+		break;
+	case SampleRate4000Hz:
+		M = 28;
+		D = 125;
+		break;
+	case SampleRate5000Hz:
+		M = 7;
+		D = 25;
+		break;
+	case SampleRate6250Hz:
+		M = 7;
+		D = 20;
+		break;
+	case SampleRate8000Hz:
+		M = 112;
+		D = 250;
+		break;
+	case SampleRate10000Hz:
+		M = 14;
+		D = 25;
+		break;
+	case SampleRate12500Hz:
+		M = 7;
+		D = 10;
+		break;
+	case SampleRate15000Hz:
+		M = 21;
+		D = 25;
+		break;
+	case SampleRate20000Hz:
+		M = 28;
+		D = 25;
+		break;
+	case SampleRate25000Hz:
+		M = 35;
+		D = 25;
+		break;
+	case SampleRate30000Hz:
+		M = 42;
+		D = 25;
+		break;
+	default:
+		return(false);
+	}
+
+	sampleRate = newSampleRate;
+
+	// Wait for DcmProgDone = 1 before reprogramming clock synthesizer
+	while (isDcmProgDone() == false) {}
+
+	// Reprogram clock synthesizer
+	writeRegister(DataFreqPll, (256 * M + D));
+
+	// Wait for DataClkLocked = 1 before allowing data acquisition to continue
+	while (isDataClockLocked() == false) {}
+
+	return(true);
+}
+
+double rhd2000PCIe::getSampleRate() const
+{
+	switch (sampleRate) {
+	case SampleRate1000Hz:
+		return 1000.0;
+		break;
+	case SampleRate1250Hz:
+		return 1250.0;
+		break;
+	case SampleRate1500Hz:
+		return 1500.0;
+		break;
+	case SampleRate2000Hz:
+		return 2000.0;
+		break;
+	case SampleRate2500Hz:
+		return 2500.0;
+		break;
+	case SampleRate3000Hz:
+		return 3000.0;
+		break;
+	case SampleRate3333Hz:
+		return (10000.0 / 3.0);
+		break;
+	case SampleRate4000Hz:
+		return 4000.0;
+		break;
+	case SampleRate5000Hz:
+		return 5000.0;
+		break;
+	case SampleRate6250Hz:
+		return 6250.0;
+		break;
+	case SampleRate8000Hz:
+		return 8000.0;
+		break;
+	case SampleRate10000Hz:
+		return 10000.0;
+		break;
+	case SampleRate12500Hz:
+		return 12500.0;
+		break;
+	case SampleRate15000Hz:
+		return 15000.0;
+		break;
+	case SampleRate20000Hz:
+		return 20000.0;
+		break;
+	case SampleRate25000Hz:
+		return 25000.0;
+		break;
+	case SampleRate30000Hz:
+		return 30000.0;
+		break;
+	default:
+		return -1.0;
+	}
+}
+
+rhd2000PCIe::AmplifierSampleRate rhd2000PCIe::getSampleRateEnum() const
+{
+	return sampleRate;
+}
+
+// Upload an auxiliary command list to a particular command slot (AuxCmd1, AuxCmd2, or AuxCmd3) and RAM bank (0-15)
+// on the FPGA.
+void rhd2000PCIe::uploadCommandList(const vector<int> &commandList, AuxCmdSlot auxCommandSlot, int bank)
+{
+	unsigned int i;
+	const char* devFile;
+	int bankPos;
+
+	if (auxCommandSlot != AuxCmd1 && auxCommandSlot != AuxCmd2 && auxCommandSlot != AuxCmd3) {
+		cerr << "Error in Rhd2000EvalBoard::uploadCommandList: auxCommandSlot out of range." << endl;
+		return;
+	}
+
+	if (bank < 0 || bank > 15) {
+		cerr << "Error in Rhd2000EvalBoard::uploadCommandList: bank out of range." << endl;
+		return;
+	}
+
+	switch (auxCommandSlot)
+	{
+	case AuxCmd1:
+		devFile = "\\\\.\\xillybus_auxcmd1_membank_16";
+		break;
+	case AuxCmd2:
+		devFile = "\\\\.\\xillybus_auxcmd2_membank_16";
+		break;
+	case AuxCmd3:
+		devFile = "\\\\.\\xillybus_auxcmd3_membank_16";
+		break;
+	default:
+		devFile = "";
+	}
+	bankPos = 2048 * bank;
+	int fidMem = Open(devFile, O_WRONLY | O_BINARY);
+	if (fidMem < 0)
+	{
+		std::cerr << "Error opening auxcmd device " << auxCommandSlot << std::endl;
+		return;
+	}
+	if (Seek(fidMem, bankPos, SEEK_SET) < 0)
+	{
+		std::cerr << "Error seeking auxcmd " << auxCommandSlot <<" to addr " << bankPos << std::endl;
+		Close(fidMem);
+		return;
+	}
+
+	for (i = 0; i < commandList.size(); ++i) {
+		int16_t value = commandList[i];
+		int wd = Write(fidControl, &value, 2);
+		if (wd < 2)
+		{
+			std::cerr << "Error writing auxcmd " << auxCommandSlot << " index " << i << std::endl;
+		}
+	}
+	Close(fidMem);
+}
+
+// Select an auxiliary command slot (AuxCmd1, AuxCmd2, or AuxCmd3) and bank (0-15) for a particular SPI port
+// (PortA, PortB, PortC, or PortD) on the FPGA.
+void rhd2000PCIe::selectAuxCommandBank(BoardPort port, AuxCmdSlot auxCommandSlot, int bank)
+{
+	int bitShift;
+
+	if (auxCommandSlot != AuxCmd1 && auxCommandSlot != AuxCmd2 && auxCommandSlot != AuxCmd3) {
+		cerr << "Error in Rhd2000EvalBoard::selectAuxCommandBank: auxCommandSlot out of range." << endl;
+		return;
+	}
+	if (bank < 0 || bank > 15) {
+		cerr << "Error in Rhd2000EvalBoard::selectAuxCommandBank: bank out of range." << endl;
+		return;
+	}
+
+	switch (port) {
+	case PortA:
+		bitShift = 0;
+		break;
+	case PortB:
+		bitShift = 4;
+		break;
+	case PortC:
+		bitShift = 8;
+		break;
+	case PortD:
+		bitShift = 12;
+		break;
+	}
+
+	switch (auxCommandSlot) {
+	case AuxCmd1:
+		writeRegister(AuxCmdBank1, bank << bitShift, 0x000f << bitShift);
+		break;
+	case AuxCmd2:
+		writeRegister(AuxCmdBank2, bank << bitShift, 0x000f << bitShift);
+		break;
+	case AuxCmd3:
+		writeRegister(AuxCmdBank3, bank << bitShift, 0x000f << bitShift);
+		break;
+	}
+}
+
+// Specify a command sequence length (endIndex = 0-1023) and command loop index (0-1023) for a particular
+// auxiliary command slot (AuxCmd1, AuxCmd2, or AuxCmd3).
+void rhd2000PCIe::selectAuxCommandLength(AuxCmdSlot auxCommandSlot, int loopIndex, int endIndex)
+{
+	if (auxCommandSlot != AuxCmd1 && auxCommandSlot != AuxCmd2 && auxCommandSlot != AuxCmd3) {
+		cerr << "Error in Rhd2000EvalBoard::selectAuxCommandLength: auxCommandSlot out of range." << endl;
+		return;
+	}
+
+	if (loopIndex < 0 || loopIndex > 1023) {
+		cerr << "Error in Rhd2000EvalBoard::selectAuxCommandLength: loopIndex out of range." << endl;
+		return;
+	}
+
+	if (endIndex < 0 || endIndex > 1023) {
+		cerr << "Error in Rhd2000EvalBoard::selectAuxCommandLength: endIndex out of range." << endl;
+		return;
+	}
+
+	switch (auxCommandSlot) {
+	case AuxCmd1:
+		writeRegister(AuxCmdLoop1, loopIndex);
+		writeRegister(AuxCmdLength1, endIndex);
+		break;
+	case AuxCmd2:
+		writeRegister(AuxCmdLoop2, loopIndex);
+		writeRegister(AuxCmdLength2, endIndex);
+		break;
+	case AuxCmd3:
+		writeRegister(AuxCmdLoop3, loopIndex);
+		writeRegister(AuxCmdLength3, endIndex);
+		break;
+	}
+}
+
+// Set the FPGA to run continuously once started (if continuousMode == true) or to run until
+// maxTimeStep is reached (if continuousMode == false).
+void rhd2000PCIe::setContinuousRunMode(bool continuousMode)
+{
+	if (continuousMode) {
+		writeRegister(ResetRun, 0x02, 0x2);
+	}
+	else {
+		writeRegister(ResetRun, 0x00, 0x2);
+	}
+}
+
+void rhd2000PCIe::setMaxTimeStep(unsigned int maxTimeStep)
+{
+	unsigned int maxTimeStepLsb, maxTimeStepMsb;
+
+	maxTimeStepLsb = maxTimeStep & 0x0000ffff;
+	maxTimeStepMsb = maxTimeStep & 0xffff0000;
+
+	writeRegister(MaxTimeStepLsb, maxTimeStepLsb);
+	writeRegister(MaxTimeStepMsb, maxTimeStepMsb >> 16);
+}
+
+// Initiate SPI data acquisition.
+void rhd2000PCIe::run()
+{
+	writeRegister(StartTrigger, 0x1);
+}
+
+// Is the FPGA currently running?
+bool rhd2000PCIe::isRunning() const
+{
+	int value;
+
+	value = readRegister(SpiRunning);
+
+	if ((value & 0x01) == 0) {
+		return false;
+	}
+	else {
+		return true;
+	}
+}
+
+// Set the delay for sampling the MISO line on a particular SPI port (PortA - PortD), in integer clock
+// steps, where each clock step is 1/2800 of a per-channel sampling period.
+// Note: Cable delay must be updated after sampleRate is changed, since cable delay calculations are
+// based on the clock frequency!
+void rhd2000PCIe::setCableDelay(BoardPort port, int delay)
+{
+	int bitShift;
+
+	if (delay < 0 || delay > 15) {
+		cerr << "Warning in Rhd2000EvalBoard::setCableDelay: delay out of range: " << delay << endl;
+	}
+
+	if (delay < 0) delay = 0;
+	if (delay > 15) delay = 15;
+
+	switch (port) {
+	case PortA:
+		bitShift = 0;
+		cableDelay[0] = delay;
+		break;
+	case PortB:
+		bitShift = 4;
+		cableDelay[1] = delay;
+		break;
+	case PortC:
+		bitShift = 8;
+		cableDelay[2] = delay;
+		break;
+	case PortD:
+		bitShift = 12;
+		cableDelay[3] = delay;
+		break;
+	default:
+		cerr << "Error in RHD2000EvalBoard::setCableDelay: unknown port." << endl;
+	}
+
+	writeRegister(MisoDelay, delay << bitShift, 0x000f << bitShift);
+}
+
+// Set the delay for sampling the MISO line on a particular SPI port (PortA - PortD) based on the length
+// of the cable between the FPGA and the RHD2000 chip (in meters).
+// Note: Cable delay must be updated after sampleRate is changed, since cable delay calculations are
+// based on the clock frequency!
+void rhd2000PCIe::setCableLengthMeters(BoardPort port, double lengthInMeters)
+{
+	int delay;
+	double tStep, cableVelocity, distance, timeDelay;
+	const double speedOfLight = 299792458.0;  // units = meters per second
+	const double xilinxLvdsOutputDelay = 1.9e-9;    // 1.9 ns Xilinx LVDS output pin delay
+	const double xilinxLvdsInputDelay = 1.4e-9;     // 1.4 ns Xilinx LVDS input pin delay
+	const double rhd2000Delay = 9.0e-9;             // 9.0 ns RHD2000 SCLK-to-MISO delay
+	const double misoSettleTime = 6.7e-9;           // 6.7 ns delay after MISO changes, before we sample it
+
+	tStep = 1.0 / (2800.0 * getSampleRate());  // data clock that samples MISO has a rate 35 x 80 = 2800x higher than the sampling rate
+	// cableVelocity = 0.67 * speedOfLight;  // propogation velocity on cable: version 1.3 and earlier
+	cableVelocity = 0.555 * speedOfLight;  // propogation velocity on cable: version 1.4 improvement based on cable measurements
+	distance = 2.0 * lengthInMeters;      // round trip distance data must travel on cable
+	timeDelay = (distance / cableVelocity) + xilinxLvdsOutputDelay + rhd2000Delay + xilinxLvdsInputDelay + misoSettleTime;
+
+	delay = (int)floor(((timeDelay / tStep) + 1.0) + 0.5);
+
+	if (delay < 1) delay = 1;   // delay of zero is too short (due to I/O delays), even for zero-length cables
+
+	setCableDelay(port, delay);
+}
+
+// Same function as above, but accepts lengths in feet instead of meters
+void rhd2000PCIe::setCableLengthFeet(BoardPort port, double lengthInFeet)
+{
+	setCableLengthMeters(port, 0.3048 * lengthInFeet);   // convert feet to meters
+}
+
+// Estimate cable length based on a particular delay used in setCableDelay.
+// (Note: Depends on sample rate.)
+double rhd2000PCIe::estimateCableLengthMeters(int delay) const
+{
+	double tStep, cableVelocity, distance;
+	const double speedOfLight = 299792458.0;  // units = meters per second
+	const double xilinxLvdsOutputDelay = 1.9e-9;    // 1.9 ns Xilinx LVDS output pin delay
+	const double xilinxLvdsInputDelay = 1.4e-9;     // 1.4 ns Xilinx LVDS input pin delay
+	const double rhd2000Delay = 9.0e-9;             // 9.0 ns RHD2000 SCLK-to-MISO delay
+	const double misoSettleTime = 6.7e-9;           // 6.7 ns delay after MISO changes, before we sample it
+
+	tStep = 1.0 / (2800.0 * getSampleRate());  // data clock that samples MISO has a rate 35 x 80 = 2800x higher than the sampling rate
+	// cableVelocity = 0.67 * speedOfLight;  // propogation velocity on cable: version 1.3 and earlier
+	cableVelocity = 0.555 * speedOfLight;  // propogation velocity on cable: version 1.4 improvement based on cable measurements
+
+	// distance = cableVelocity * (delay * tStep - (xilinxLvdsOutputDelay + rhd2000Delay + xilinxLvdsInputDelay));  // version 1.3 and earlier
+	distance = cableVelocity * ((((double)delay) - 1.0) * tStep - (xilinxLvdsOutputDelay + rhd2000Delay + xilinxLvdsInputDelay + misoSettleTime));  // version 1.4 improvement
+	if (distance < 0.0) distance = 0.0;
+
+	return (distance / 2.0);
+}
+
+// Same function as above, but returns length in feet instead of meters
+double rhd2000PCIe::estimateCableLengthFeet(int delay) const
+{
+	return 3.2808 * estimateCableLengthMeters(delay);
+}
+
+// Turn on or off DSP settle function in the FPGA.  (Only executes when CONVERT commands are sent.)
+void rhd2000PCIe::setDspSettle(bool enabled)
+{
+	writeRegister(ResetRun, (enabled ? 0x04 : 0x00), 0x04);
+}
+
+// Assign a particular data source (e.g., PortA1, PortA2, PortB1,...) to one of the eight
+// available USB data streams (0-7).
+void rhd2000PCIe::setDataSource(int stream, BoardDataSource dataSource)
+{
+	int bitShift;
+	controlAddr endPoint;
+
+	if (stream < 0 || stream >(MAX_NUM_DATA_STREAMS - 1)) {
+		cerr << "Error in Rhd2000EvalBoard::setDataSource: stream out of range." << endl;
+		return;
+	}
+
+	switch (stream) {
+	case 0:
+		endPoint = DataStreamSel1234;
+		bitShift = 0;
+		break;
+	case 1:
+		endPoint = DataStreamSel1234;
+		bitShift = 4;
+		break;
+	case 2:
+		endPoint = DataStreamSel1234;
+		bitShift = 8;
+		break;
+	case 3:
+		endPoint = DataStreamSel1234;
+		bitShift = 12;
+		break;
+	case 4:
+		endPoint = DataStreamSel5678;
+		bitShift = 0;
+		break;
+	case 5:
+		endPoint = DataStreamSel5678;
+		bitShift = 4;
+		break;
+	case 6:
+		endPoint = DataStreamSel5678;
+		bitShift = 8;
+		break;
+	case 7:
+		endPoint = DataStreamSel5678;
+		bitShift = 12;
+		break;
+	case 8:
+		endPoint = DataStreamSel9ABC;
+		bitShift = 0;
+		break;
+	case 9:
+		endPoint = DataStreamSel9ABC;
+		bitShift = 4;
+		break;
+	case 10:
+		endPoint = DataStreamSel9ABC;
+		bitShift = 8;
+		break;
+	case 11:
+		endPoint = DataStreamSel9ABC;
+		bitShift = 12;
+		break;
+	case 12:
+		endPoint = DataStreamSelDEF10;
+		bitShift = 0;
+		break;
+	case 13:
+		endPoint = DataStreamSelDEF10;
+		bitShift = 4;
+		break;
+	case 14:
+		endPoint = DataStreamSelDEF10;
+		bitShift = 8;
+		break;
+	case 15:
+		endPoint = DataStreamSelDEF10;
+		bitShift = 12;
+		break;
+	}
+
+	writeRegister(endPoint, dataSource << bitShift, 0x000f << bitShift);
+}
+
+// Enable or disable one of the eight available USB data streams (0-7).
+void rhd2000PCIe::enableDataStream(int stream, bool enabled)
+{
+	if (stream < 0 || stream >(MAX_NUM_DATA_STREAMS - 1)) {
+		cerr << "Error in Rhd2000EvalBoard::setDataSource: stream out of range." << endl;
+		return;
+	}
+
+	if (enabled) {
+		if (dataStreamEnabled[stream] == 0) {
+			writeRegister(DataStreamEn, 0x0001 << stream, 0x0001 << stream);
+			dataStreamEnabled[stream] = 1;
+			++numDataStreams;
+		}
+	}
+	else {
+		if (dataStreamEnabled[stream] == 1) {
+			writeRegister(DataStreamEn, 0x0000 << stream, 0x0001 << stream);
+			dataStreamEnabled[stream] = 0;
+			numDataStreams--;
+		}
+	}
+}
+
+// Returns the number of enabled data streams.
+int rhd2000PCIe::getNumEnabledDataStreams() const
+{
+	return numDataStreams;
+}
+
+// Is variable-frequency clock DCM programming done?
+bool rhd2000PCIe::isDcmProgDone() const
+{
+	int value;
+
+	value = readRegister(DataClkLocked);
+
+	return ((value & 0x0002) > 1);
+}
+
+// Is variable-frequency clock PLL locked?
+bool rhd2000PCIe::isDataClockLocked() const
+{
+	int value;
+
+	value = readRegister(DataClkLocked);
+
+	return ((value & 0x0001) > 0);
+}
+
+// Return FPGA cable delay for selected SPI port.
+int rhd2000PCIe::getCableDelay(BoardPort port) const
+{
+	switch (port) {
+	case PortA:
+		return cableDelay[0];
+	case PortB:
+		return cableDelay[1];
+	case PortC:
+		return cableDelay[2];
+	case PortD:
+		return cableDelay[3];
+	default:
+		cerr << "Error in RHD2000EvalBoard::getCableDelay: unknown port." << endl;
+		return -1;
+	}
+}
+
+// Return FPGA cable delays for all SPI ports.
+void rhd2000PCIe::getCableDelay(vector<int> &delays) const
+{
+	if (delays.size() != 4) {
+		delays.resize(4);
+	}
+	for (int i = 0; i < 4; ++i) {
+		delays[i] = cableDelay[i];
+	}
+}
+
+bool rhd2000PCIe::isStreamEnabled(int streamIndex)
+{
+	if (streamIndex < 0 || streamIndex >(MAX_NUM_DATA_STREAMS - 1))
+		return false;
+
+	return dataStreamEnabled[streamIndex];
+}
+
+bool rhd2000PCIe::readRawDataBlock(unsigned char** bufferPtr, int nSamples)
+{
+	unsigned int numBytesToRead;
+
+	numBytesToRead = 2 * Rhd2000DataBlock::calculateDataBlockSizeInWords(numDataStreams, nSamples);
+
+	if (numBytesToRead > DATA_BUFFER_SIZE) {
+		cerr << "Error in rhd2000PCIe::readRawDataBlock: Data buffer size exceeded.  " <<
+			"Increase value of DATA_BUFFER_SIZE." << endl;
+		*bufferPtr = nullptr;
+		return false;
+	}
+
+	int nr = Read(fidFIFO, dataBuffer, numBytesToRead);
+
+	if (nr < 0)
+	{
+		std::cerr << "Error reading from pipe" << std::endl;
+	}
+	else if (nr < numBytesToRead)
+	{
+		cerr << "CRITICAL: Timeout on pipe read. Check block and buffer sizes." << endl;
+	}
+
+	*bufferPtr = dataBuffer;
+	return true;
+}
+
+void rhd2000PCIe::flush()
+{
+	int nr = 0;
+	do
+	{
+		nr = Read(fidFIFO, &dataBuffer, DATA_BUFFER_SIZE);
+
+	} while (nr > 0);
+
+}
+
+// Read data block from the USB interface, if one is available.  Returns true if data block
+// was available.
+bool rhd2000PCIe::readDataBlock(Rhd2000DataBlock *dataBlock, int nSamples)
+{
+	unsigned int numBytesToRead;
+	long res;
+
+	numBytesToRead = 2 * dataBlock->calculateDataBlockSizeInWords(numDataStreams, nSamples);
+
+	if (numBytesToRead > DATA_BUFFER_SIZE) {
+		cerr << "Error in rhd2000PCIe::readDataBlock: Data buffer size exceeded.  " <<
+			"Increase value of DATA_BUFFER_SIZE." << endl;
+		return false;
+	}
+
+	int nr = Read(fidFIFO, dataBuffer, numBytesToRead);
+
+	if (nr < 0)
+	{
+		std::cerr << "Error reading from pipe" << std::endl;
+	}
+	else if (nr < numBytesToRead)
+	{
+		cerr << "CRITICAL: Timeout on pipe read. Check block and buffer sizes." << endl;
+	}
+
+	dataBlock->fillFromUsbBuffer(dataBuffer, 0, numDataStreams, nSamples);
+
+	return true;
+}
+
+// Reads a certain number of USB data blocks, if the specified number is available, and appends them
+// to queue.  Returns true if data blocks were available.
+bool rhd2000PCIe::readDataBlocks(int numBlocks, queue<Rhd2000DataBlock> &dataQueue)
+{
+	unsigned int numWordsToRead, numBytesToRead;
+	int i;
+	Rhd2000DataBlock *dataBlock;
+
+
+	numWordsToRead = numBlocks * dataBlock->calculateDataBlockSizeInWords(numDataStreams);
+
+
+	numBytesToRead = 2 * numWordsToRead;
+
+	if (numBytesToRead > DATA_BUFFER_SIZE) {
+		cerr << "Error in rhd2000PCIe::readDataBlocks: Data buffer size exceeded.  " <<
+			"Increase value of DATA_BUFFER_SIZE." << endl;
+		return false;
+	}
+
+	int nr = Read(fidFIFO, dataBuffer, numBytesToRead);
+
+	if (nr < 0)
+	{
+		std::cerr << "Error reading from pipe" << std::endl;
+	}
+	else if (nr < numBytesToRead)
+	{
+		cerr << "CRITICAL: Timeout on pipe read. Check block and buffer sizes." << endl;
+	}
+
+	dataBlock = new Rhd2000DataBlock(numDataStreams);
+	for (i = 0; i < numBlocks; ++i) {
+		dataBlock->fillFromUsbBuffer(dataBuffer, i, numDataStreams);
+		dataQueue.push(*dataBlock);
+	}
+	delete dataBlock;
+
+	return true;
+}
\ No newline at end of file
diff --git a/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000PCIe.h b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000PCIe.h
new file mode 100644
index 0000000000000000000000000000000000000000..638728a7157d82933befdf61974771e9dbf855cf
--- /dev/null
+++ b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000PCIe.h
@@ -0,0 +1,164 @@
+#ifndef RHD2000PCIE_H
+#define RHD2000PCIE_H
+
+#include <vector>
+#include <cstdint>
+#include <queue>
+
+#define MAX_NUM_DATA_STREAMS 16
+#define DATA_BUFFER_SIZE 2560000
+
+using namespace std;
+
+namespace PCIeRhythm {
+	class Rhd2000DataBlock;
+
+	class rhd2000PCIe
+	{
+	public:
+		rhd2000PCIe();
+		~rhd2000PCIe();
+
+		enum AmplifierSampleRate {
+			SampleRate1000Hz,
+			SampleRate1250Hz,
+			SampleRate1500Hz,
+			SampleRate2000Hz,
+			SampleRate2500Hz,
+			SampleRate3000Hz,
+			SampleRate3333Hz,
+			SampleRate4000Hz,
+			SampleRate5000Hz,
+			SampleRate6250Hz,
+			SampleRate8000Hz,
+			SampleRate10000Hz,
+			SampleRate12500Hz,
+			SampleRate15000Hz,
+			SampleRate20000Hz,
+			SampleRate25000Hz,
+			SampleRate30000Hz
+		};
+
+		enum BoardDataSource {
+			PortA1 = 0,
+			PortA2 = 1,
+			PortB1 = 2,
+			PortB2 = 3,
+			PortC1 = 4,
+			PortC2 = 5,
+			PortD1 = 6,
+			PortD2 = 7,
+			PortA1Ddr = 8,
+			PortA2Ddr = 9,
+			PortB1Ddr = 10,
+			PortB2Ddr = 11,
+			PortC1Ddr = 12,
+			PortC2Ddr = 13,
+			PortD1Ddr = 14,
+			PortD2Ddr = 15
+		};
+
+		enum AuxCmdSlot {
+			AuxCmd1,
+			AuxCmd2,
+			AuxCmd3
+		};
+
+		enum BoardPort {
+			PortA,
+			PortB,
+			PortC,
+			PortD
+		};
+
+		bool open();
+		void initialize();
+		void resetBoard();
+
+		bool setSampleRate(AmplifierSampleRate newSampleRate);
+		double getSampleRate() const;
+		AmplifierSampleRate getSampleRateEnum() const;
+
+		void uploadCommandList(const vector<int> &commandList, AuxCmdSlot auxCommandSlot, int bank);
+		void selectAuxCommandBank(BoardPort port, AuxCmdSlot auxCommandSlot, int bank);
+		void selectAuxCommandLength(AuxCmdSlot auxCommandSlot, int loopIndex, int endIndex);
+
+		void setContinuousRunMode(bool continuousMode);
+		void setMaxTimeStep(unsigned int maxTimeStep);
+
+		void run();
+		bool isRunning() const;
+
+		void setCableDelay(BoardPort port, int delay);
+		void setCableLengthMeters(BoardPort port, double lengthInMeters);
+		void setCableLengthFeet(BoardPort port, double lengthInFeet);
+		double estimateCableLengthMeters(int delay) const;
+		double estimateCableLengthFeet(int delay) const;
+
+		void setDspSettle(bool enabled);
+
+		void setDataSource(int stream, BoardDataSource dataSource);
+		void enableDataStream(int stream, bool enabled);
+		int getNumEnabledDataStreams() const;
+
+		int getCableDelay(BoardPort port) const;
+		void getCableDelay(vector<int> &delays) const;
+		bool isStreamEnabled(int streamIndex);
+
+		bool readRawDataBlock(unsigned char** bufferPtr, int nSamples = -1);
+		bool readDataBlock(Rhd2000DataBlock *dataBlock, int nSamples = -1);
+		bool readDataBlocks(int numBlocks, queue<Rhd2000DataBlock> &dataQueue);
+
+		void flush();
+
+
+	private:
+		int fidControl, fidStatus, fidFIFO;
+
+		enum controlAddr {
+			ResetRun = 0x00,
+			MaxTimeStepLsb = 0x02,
+			MaxTimeStepMsb = 0x04,
+			DataFreqPll = 0x06,
+			MisoDelay = 0x08,
+			AuxCmdBank1 = 0x10,
+			AuxCmdBank2 = 0x12,
+			AuxCmdBank3 = 0x14,
+			AuxCmdLength1 = 0x16,
+			AuxCmdLength2 = 0x18,
+			AuxCmdLength3 = 0x1A,
+			AuxCmdLoop1 = 0x1C,
+			AuxCmdLoop2 = 0x1E,
+			AuxCmdLoop3 = 0x20,
+			DataStreamSel1234 = 0x24,
+			DataStreamSel5678 = 0x26,
+			DataStreamSel9ABC = 0x28,
+			DataStreamSelDEF10 = 0x2A,
+			DataStreamEn = 0x2C,
+			StartTrigger = 0x3E
+		};
+		enum statusAddr {
+			NumWordsLsb = 0x00,
+			NumWordsMsb = 0x02,
+			SpiRunning = 0x04,
+			DataClkLocked = 0x08,
+			BoardId = 0x0A,
+			BoardVersion = 0x0C
+		};
+
+		AmplifierSampleRate sampleRate;
+		int numDataStreams; // total number of data streams currently enabled
+		int dataStreamEnabled[MAX_NUM_DATA_STREAMS]; // 0 (disabled) or 1 (enabled), set for maximum stream number
+		vector<int> cableDelay;
+
+		void writeRegister(controlAddr reg, int16_t value, int16_t mask = 0xFFFF);
+		int16_t readRegister(statusAddr reg) const;
+
+		bool isDcmProgDone() const;
+		bool isDataClockLocked() const;
+
+		unsigned char dataBuffer[DATA_BUFFER_SIZE];
+
+	};
+};
+#endif // !RHD2000PCIE_H
\ No newline at end of file
diff --git a/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000datablock.cpp b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000datablock.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9363fb196c41554ce5849249cce7448d1360119d
--- /dev/null
+++ b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000datablock.cpp
@@ -0,0 +1,390 @@
+//----------------------------------------------------------------------------------
+// rhd2000datablock.cpp
+//
+// Intan Technoloies RHD2000 Rhythm Interface API
+// Rhd2000DataBlock Class
+// Version 1.4 (26 February 2014)
+//
+// Copyright (c) 2013-2014 Intan Technologies LLC
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the
+// use of this software.
+//
+// Permission is granted to anyone to use this software for any applications that
+// use Intan Technologies integrated circuits, and to alter it and redistribute it
+// freely.
+//
+// See http://www.intantech.com for documentation and product information.
+//----------------------------------------------------------------------------------
+
+#include <iostream>
+#include <fstream>
+#include <iomanip>
+#include <vector>
+
+#include "rhd2000datablock.h"
+
+using namespace std;
+using namespace PCIeRhythm;
+
+// This class creates a data structure storing SAMPLES_PER_DATA_BLOCK data frames
+// from a Rhythm FPGA interface controlling up to eight RHD2000 chips.
+
+// Constructor.  Allocates memory for data block.
+Rhd2000DataBlock::Rhd2000DataBlock(int numDataStreams) : samplesPerBlock(SAMPLES_PER_DATA_BLOCK_PCIE)
+{
+    allocateUIntArray1D(timeStamp, samplesPerBlock);
+	allocateIntArray3D(amplifierData, numDataStreams, 32, samplesPerBlock);
+	allocateIntArray3D(auxiliaryData, numDataStreams, 3, samplesPerBlock);
+	allocateIntArray2D(boardAdcData, 8, samplesPerBlock);
+	allocateIntArray1D(ttlIn, samplesPerBlock);
+	allocateIntArray1D(ttlOut, samplesPerBlock);
+}
+
+// Allocates memory for a 1-D array of integers.
+void Rhd2000DataBlock::allocateIntArray1D(vector<int> &array1D, int xSize)
+{
+    array1D.resize(xSize);
+}
+
+// Allocates memory for a 1-D array of unsigned integers.
+void Rhd2000DataBlock::allocateUIntArray1D(vector<unsigned int> &array1D, int xSize)
+{
+    array1D.resize(xSize);
+}
+
+// Allocates memory for a 2-D array of integers.
+void Rhd2000DataBlock::allocateIntArray2D(vector<vector<int> > & array2D, int xSize, int ySize)
+{
+    int i;
+
+    array2D.resize(xSize);
+    for (i = 0; i < xSize; ++i)
+        array2D[i].resize(ySize);
+}
+
+// Allocates memory for a 3-D array of integers.
+void Rhd2000DataBlock::allocateIntArray3D(vector<vector<vector<int> > > &array3D, int xSize, int ySize, int zSize)
+{
+    int i, j;
+
+    array3D.resize(xSize);
+    for (i = 0; i < xSize; ++i) {
+        array3D[i].resize(ySize);
+
+        for (j = 0; j < ySize; ++j) {
+            array3D[i][j].resize(zSize);
+        }
+    }
+}
+
+// Returns the number of samples in a USB data block.
+unsigned int Rhd2000DataBlock::getSamplesPerDataBlock()
+{
+	return SAMPLES_PER_DATA_BLOCK_PCIE;
+}
+
+// Returns the number of 16-bit words in a USB data block with numDataStreams data streams enabled.
+unsigned int Rhd2000DataBlock::calculateDataBlockSizeInWords(int numDataStreams, int nSamples)
+{
+	unsigned int samps = nSamples <= 0 ? SAMPLES_PER_DATA_BLOCK_PCIE : nSamples;
+	return samps * (4 + 2 + numDataStreams * 36 + 8 + 2);
+    // 4 = magic number; 2 = time stamp; 36 = (32 amp channels + 3 aux commands + 1 filler word); 8 = ADCs; 2 = TTL in/out
+}
+
+// Check first 64 bits of USB header against the fixed Rhythm "magic number" to verify data sync.
+bool Rhd2000DataBlock::checkUsbHeader(unsigned char usbBuffer[], int index)
+{
+    unsigned long long x1, x2, x3, x4, x5, x6, x7, x8;
+    unsigned long long header;
+
+    x1 = usbBuffer[index];
+    x2 = usbBuffer[index + 1];
+    x3 = usbBuffer[index + 2];
+    x4 = usbBuffer[index + 3];
+    x5 = usbBuffer[index + 4];
+    x6 = usbBuffer[index + 5];
+    x7 = usbBuffer[index + 6];
+    x8 = usbBuffer[index + 7];
+
+    header = (x8 << 56) + (x7 << 48) + (x6 << 40) + (x5 << 32) + (x4 << 24) + (x3 << 16) + (x2 << 8) + (x1 << 0);
+
+    return (header == RHD2000_HEADER_MAGIC_NUMBER);
+}
+
+// Read 32-bit time stamp from USB data frame.
+unsigned int Rhd2000DataBlock::convertUsbTimeStamp(unsigned char usbBuffer[], int index)
+{
+    unsigned int x1, x2, x3, x4;
+    x1 = usbBuffer[index];
+    x2 = usbBuffer[index + 1];
+    x3 = usbBuffer[index + 2];
+    x4 = usbBuffer[index + 3];
+
+    return (x4 << 24) + (x3 << 16) + (x2 << 8) + (x1 << 0);
+}
+
+// Convert two USB bytes into 16-bit word.
+int Rhd2000DataBlock::convertUsbWord(unsigned char usbBuffer[], int index)
+{
+    unsigned int x1, x2, result;
+
+    x1 = (unsigned int) usbBuffer[index];
+    x2 = (unsigned int) usbBuffer[index + 1];
+
+    result = (x2 << 8) | (x1 << 0);
+
+    return (int) result;
+}
+
+// Fill data block with raw data from USB input buffer.
+void Rhd2000DataBlock::fillFromUsbBuffer(unsigned char usbBuffer[], int blockIndex, int numDataStreams, int nSamples)
+{
+    int index, t, channel, stream, i;
+	int samplesToRead = nSamples <= 0 ? samplesPerBlock : nSamples;
+	int num = 0;
+
+    index = blockIndex * 2 * calculateDataBlockSizeInWords(numDataStreams, usb3);
+	for (t = 0; t < samplesToRead; ++t) {
+		if (!checkUsbHeader(usbBuffer, index)) {
+			cerr << "Error in Rhd2000EvalBoard::readDataBlock: Incorrect header." << endl;
+			break;
+		}
+		else
+			num++;
+		//else cerr << "Block ok" << endl;
+        index += 8;
+        timeStamp[t] = convertUsbTimeStamp(usbBuffer, index);
+        index += 4;
+
+        // Read auxiliary results
+        for (channel = 0; channel < 3; ++channel) {
+            for (stream = 0; stream < numDataStreams; ++stream) {
+                auxiliaryData[stream][channel][t] = convertUsbWord(usbBuffer, index);
+                index += 2;
+            }
+        }
+
+        // Read amplifier channels
+        for (channel = 0; channel < 32; ++channel) {
+            for (stream = 0; stream < numDataStreams; ++stream) {
+                amplifierData[stream][channel][t] = convertUsbWord(usbBuffer, index);
+                index += 2;
+            }
+        }
+
+        // skip 36th filler word in each data stream
+        index += 2 * numDataStreams;
+
+        // Read from AD5662 ADCs
+        for (i = 0; i < 8; ++i) {
+            boardAdcData[i][t] = convertUsbWord(usbBuffer, index);
+            index += 2;
+        }
+
+        // Read TTL input and output values
+        ttlIn[t] = convertUsbWord(usbBuffer, index);
+        index += 2;
+
+        ttlOut[t] = convertUsbWord(usbBuffer, index);
+        index += 2;
+    }
+	//cout << "Read " << num << " valid samples with " << numDataStreams << " streams. Usb mode status: " << usb3 << endl;
+}
+
+// Print the contents of RHD2000 registers from a selected USB data stream (0-7)
+// to the console.
+void Rhd2000DataBlock::print(int stream) const
+{
+    const int RamOffset = 37;
+
+    cout << endl;
+    cout << "RHD 2000 Data Block contents:" << endl;
+    cout << "  ROM contents:" << endl;
+    cout << "    Chip Name: " <<
+           (char) auxiliaryData[stream][2][24] <<
+           (char) auxiliaryData[stream][2][25] <<
+           (char) auxiliaryData[stream][2][26] <<
+           (char) auxiliaryData[stream][2][27] <<
+           (char) auxiliaryData[stream][2][28] <<
+           (char) auxiliaryData[stream][2][29] <<
+           (char) auxiliaryData[stream][2][30] <<
+           (char) auxiliaryData[stream][2][31] << endl;
+    cout << "    Company Name:" <<
+           (char) auxiliaryData[stream][2][32] <<
+           (char) auxiliaryData[stream][2][33] <<
+           (char) auxiliaryData[stream][2][34] <<
+           (char) auxiliaryData[stream][2][35] <<
+           (char) auxiliaryData[stream][2][36] << endl;
+    cout << "    Intan Chip ID: " << auxiliaryData[stream][2][19] << endl;
+    cout << "    Number of Amps: " << auxiliaryData[stream][2][20] << endl;
+    cout << "    Unipolar/Bipolar Amps: ";
+    switch (auxiliaryData[stream][2][21]) {
+        case 0:
+            cout << "bipolar";
+            break;
+        case 1:
+            cout << "unipolar";
+            break;
+        default:
+            cout << "UNKNOWN";
+    }
+    cout << endl;
+    cout << "    Die Revision: " << auxiliaryData[stream][2][22] << endl;
+    cout << "    Future Expansion Register: " << auxiliaryData[stream][2][23] << endl;
+
+    cout << "  RAM contents:" << endl;
+    cout << "    ADC reference BW:      " << ((auxiliaryData[stream][2][RamOffset + 0] & 0xc0) >> 6) << endl;
+    cout << "    amp fast settle:       " << ((auxiliaryData[stream][2][RamOffset + 0] & 0x20) >> 5) << endl;
+    cout << "    amp Vref enable:       " << ((auxiliaryData[stream][2][RamOffset + 0] & 0x10) >> 4) << endl;
+    cout << "    ADC comparator bias:   " << ((auxiliaryData[stream][2][RamOffset + 0] & 0x0c) >> 2) << endl;
+    cout << "    ADC comparator select: " << ((auxiliaryData[stream][2][RamOffset + 0] & 0x03) >> 0) << endl;
+    cout << "    VDD sense enable:      " << ((auxiliaryData[stream][2][RamOffset + 1] & 0x40) >> 6) << endl;
+    cout << "    ADC buffer bias:       " << ((auxiliaryData[stream][2][RamOffset + 1] & 0x3f) >> 0) << endl;
+    cout << "    MUX bias:              " << ((auxiliaryData[stream][2][RamOffset + 2] & 0x3f) >> 0) << endl;
+    cout << "    MUX load:              " << ((auxiliaryData[stream][2][RamOffset + 3] & 0xe0) >> 5) << endl;
+    cout << "    tempS2, tempS1:        " << ((auxiliaryData[stream][2][RamOffset + 3] & 0x10) >> 4) << "," <<
+           ((auxiliaryData[stream][2][RamOffset + 3] & 0x08) >> 3) << endl;
+    cout << "    tempen:                " << ((auxiliaryData[stream][2][RamOffset + 3] & 0x04) >> 2) << endl;
+    cout << "    digout HiZ:            " << ((auxiliaryData[stream][2][RamOffset + 3] & 0x02) >> 1) << endl;
+    cout << "    digout:                " << ((auxiliaryData[stream][2][RamOffset + 3] & 0x01) >> 0) << endl;
+    cout << "    weak MISO:             " << ((auxiliaryData[stream][2][RamOffset + 4] & 0x80) >> 7) << endl;
+    cout << "    twoscomp:              " << ((auxiliaryData[stream][2][RamOffset + 4] & 0x40) >> 6) << endl;
+    cout << "    absmode:               " << ((auxiliaryData[stream][2][RamOffset + 4] & 0x20) >> 5) << endl;
+    cout << "    DSPen:                 " << ((auxiliaryData[stream][2][RamOffset + 4] & 0x10) >> 4) << endl;
+    cout << "    DSP cutoff freq:       " << ((auxiliaryData[stream][2][RamOffset + 4] & 0x0f) >> 0) << endl;
+    cout << "    Zcheck DAC power:      " << ((auxiliaryData[stream][2][RamOffset + 5] & 0x40) >> 6) << endl;
+    cout << "    Zcheck load:           " << ((auxiliaryData[stream][2][RamOffset + 5] & 0x20) >> 5) << endl;
+    cout << "    Zcheck scale:          " << ((auxiliaryData[stream][2][RamOffset + 5] & 0x18) >> 3) << endl;
+    cout << "    Zcheck conn all:       " << ((auxiliaryData[stream][2][RamOffset + 5] & 0x04) >> 2) << endl;
+    cout << "    Zcheck sel pol:        " << ((auxiliaryData[stream][2][RamOffset + 5] & 0x02) >> 1) << endl;
+    cout << "    Zcheck en:             " << ((auxiliaryData[stream][2][RamOffset + 5] & 0x01) >> 0) << endl;
+    cout << "    Zcheck DAC:            " << ((auxiliaryData[stream][2][RamOffset + 6] & 0xff) >> 0) << endl;
+    cout << "    Zcheck select:         " << ((auxiliaryData[stream][2][RamOffset + 7] & 0x3f) >> 0) << endl;
+    cout << "    ADC aux1 en:           " << ((auxiliaryData[stream][2][RamOffset + 9] & 0x80) >> 7) << endl;
+    cout << "    ADC aux2 en:           " << ((auxiliaryData[stream][2][RamOffset + 11] & 0x80) >> 7) << endl;
+    cout << "    ADC aux3 en:           " << ((auxiliaryData[stream][2][RamOffset + 13] & 0x80) >> 7) << endl;
+    cout << "    offchip RH1:           " << ((auxiliaryData[stream][2][RamOffset + 8] & 0x80) >> 7) << endl;
+    cout << "    offchip RH2:           " << ((auxiliaryData[stream][2][RamOffset + 10] & 0x80) >> 7) << endl;
+    cout << "    offchip RL:            " << ((auxiliaryData[stream][2][RamOffset + 12] & 0x80) >> 7) << endl;
+
+    int rH1Dac1 = auxiliaryData[stream][2][RamOffset + 8] & 0x3f;
+    int rH1Dac2 = auxiliaryData[stream][2][RamOffset + 9] & 0x1f;
+    int rH2Dac1 = auxiliaryData[stream][2][RamOffset + 10] & 0x3f;
+    int rH2Dac2 = auxiliaryData[stream][2][RamOffset + 11] & 0x1f;
+    int rLDac1 = auxiliaryData[stream][2][RamOffset + 12] & 0x7f;
+    int rLDac2 = auxiliaryData[stream][2][RamOffset + 13] & 0x3f;
+    int rLDac3 = auxiliaryData[stream][2][RamOffset + 13] & 0x40 >> 6;
+
+    double rH1 = 2630.0 + rH1Dac2 * 30800.0 + rH1Dac1 * 590.0;
+    double rH2 = 8200.0 + rH2Dac2 * 38400.0 + rH2Dac1 * 730.0;
+    double rL = 3300.0 + rLDac3 * 3000000.0 + rLDac2 * 15400.0 + rLDac1 * 190.0;
+
+    cout << fixed << setprecision(2);
+
+    cout << "    RH1 DAC1, DAC2:        " << rH1Dac1 << " " << rH1Dac2 << " = " << (rH1 / 1000) <<
+            " kOhm" << endl;
+    cout << "    RH2 DAC1, DAC2:        " << rH2Dac1 << " " << rH2Dac2 << " = " << (rH2 / 1000) <<
+            " kOhm" << endl;
+    cout << "    RL DAC1, DAC2, DAC3:   " << rLDac1 << " " << rLDac2 << " " << rLDac3 << " = " <<
+            (rL / 1000) << " kOhm" << endl;
+
+    cout << "    amp power[31:0]:       " <<
+           ((auxiliaryData[stream][2][RamOffset + 17] & 0x80) >> 7) <<
+           ((auxiliaryData[stream][2][RamOffset + 17] & 0x40) >> 6) <<
+           ((auxiliaryData[stream][2][RamOffset + 17] & 0x20) >> 5) <<
+           ((auxiliaryData[stream][2][RamOffset + 17] & 0x10) >> 4) <<
+           ((auxiliaryData[stream][2][RamOffset + 17] & 0x08) >> 3) <<
+           ((auxiliaryData[stream][2][RamOffset + 17] & 0x04) >> 2) <<
+           ((auxiliaryData[stream][2][RamOffset + 17] & 0x02) >> 1) <<
+           ((auxiliaryData[stream][2][RamOffset + 17] & 0x01) >> 0) << " " <<
+           ((auxiliaryData[stream][2][RamOffset + 16] & 0x80) >> 7) <<
+           ((auxiliaryData[stream][2][RamOffset + 16] & 0x40) >> 6) <<
+           ((auxiliaryData[stream][2][RamOffset + 16] & 0x20) >> 5) <<
+           ((auxiliaryData[stream][2][RamOffset + 16] & 0x10) >> 4) <<
+           ((auxiliaryData[stream][2][RamOffset + 16] & 0x08) >> 3) <<
+           ((auxiliaryData[stream][2][RamOffset + 16] & 0x04) >> 2) <<
+           ((auxiliaryData[stream][2][RamOffset + 16] & 0x02) >> 1) <<
+           ((auxiliaryData[stream][2][RamOffset + 16] & 0x01) >> 0) << " " <<
+           ((auxiliaryData[stream][2][RamOffset + 15] & 0x80) >> 7) <<
+           ((auxiliaryData[stream][2][RamOffset + 15] & 0x40) >> 6) <<
+           ((auxiliaryData[stream][2][RamOffset + 15] & 0x20) >> 5) <<
+           ((auxiliaryData[stream][2][RamOffset + 15] & 0x10) >> 4) <<
+           ((auxiliaryData[stream][2][RamOffset + 15] & 0x08) >> 3) <<
+           ((auxiliaryData[stream][2][RamOffset + 15] & 0x04) >> 2) <<
+           ((auxiliaryData[stream][2][RamOffset + 15] & 0x02) >> 1) <<
+           ((auxiliaryData[stream][2][RamOffset + 15] & 0x01) >> 0) << " " <<
+           ((auxiliaryData[stream][2][RamOffset + 14] & 0x80) >> 7) <<
+           ((auxiliaryData[stream][2][RamOffset + 14] & 0x40) >> 6) <<
+           ((auxiliaryData[stream][2][RamOffset + 14] & 0x20) >> 5) <<
+           ((auxiliaryData[stream][2][RamOffset + 14] & 0x10) >> 4) <<
+           ((auxiliaryData[stream][2][RamOffset + 14] & 0x08) >> 3) <<
+           ((auxiliaryData[stream][2][RamOffset + 14] & 0x04) >> 2) <<
+           ((auxiliaryData[stream][2][RamOffset + 14] & 0x02) >> 1) <<
+           ((auxiliaryData[stream][2][RamOffset + 14] & 0x01) >> 0) << endl;
+
+    cout << endl;
+
+    int tempA = auxiliaryData[stream][1][12];
+    int tempB = auxiliaryData[stream][1][20];
+    int vddSample = auxiliaryData[stream][1][28];
+
+    double tempUnitsC = ((double)(tempB - tempA)) / 98.9 - 273.15;
+    double tempUnitsF = (9.0/5.0) * tempUnitsC + 32.0;
+
+    double vddSense = 0.0000748 * ((double) vddSample);
+
+    cout << setprecision(1);
+    cout << "  Temperature sensor (only one reading): " << tempUnitsC << " C (" <<
+            tempUnitsF << " F)" << endl;
+
+    cout << setprecision(2);
+    cout << "  Supply voltage sensor                : " << vddSense << " V" << endl;
+
+    cout << setprecision(6);
+    cout.unsetf(ios::floatfield);
+    cout << endl;
+}
+
+// Write a 16-bit dataWord to an outputStream in "little endian" format (i.e., least significant
+// byte first).  We must do this explicitly for cross-platform consistency.  For example, Windows
+// is a little-endian OS, while Mac OS X and Linux can be little-endian or big-endian depending on
+// the processor running the operating system.
+//
+// (See "Endianness" article in Wikipedia for more information.)
+void Rhd2000DataBlock::writeWordLittleEndian(ofstream &outputStream, int dataWord) const
+{
+    unsigned short msb, lsb;
+
+    lsb = ((unsigned short) dataWord) & 0x00ff;
+    msb = (((unsigned short) dataWord) & 0xff00) >> 8;
+
+    outputStream << (unsigned char) lsb;
+    outputStream << (unsigned char) msb;
+}
+
+// Write contents of data block to a binary output stream (saveOut) in little endian format.
+void Rhd2000DataBlock::write(ofstream &saveOut, int numDataStreams) const
+{
+    int t, channel, stream, i;
+
+	for (t = 0; t < samplesPerBlock; ++t) {
+        writeWordLittleEndian(saveOut, timeStamp[t]);
+        for (channel = 0; channel < 32; ++channel) {
+            for (stream = 0; stream < numDataStreams; ++stream) {
+                writeWordLittleEndian(saveOut, amplifierData[stream][channel][t]);
+            }
+        }
+        for (channel = 0; channel < 3; ++channel) {
+            for (stream = 0; stream < numDataStreams; ++stream) {
+                writeWordLittleEndian(saveOut, auxiliaryData[stream][channel][t]);
+            }
+        }
+        for (i = 0; i < 8; ++i) {
+            writeWordLittleEndian(saveOut, boardAdcData[i][t]);
+        }
+        writeWordLittleEndian(saveOut, ttlIn[t]);
+        writeWordLittleEndian(saveOut, ttlOut[t]);
+    }
+}
diff --git a/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000datablock.h b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000datablock.h
new file mode 100644
index 0000000000000000000000000000000000000000..b126b4f51fd135fa2115d2d6e1dc759250172705
--- /dev/null
+++ b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000datablock.h
@@ -0,0 +1,68 @@
+//----------------------------------------------------------------------------------
+// rhd2000datablock.h
+//
+// Intan Technoloies RHD2000 Rhythm Interface API
+// Rhd2000DataBlock Class Header File
+// Version 1.4 (26 February 2014)
+//
+// Copyright (c) 2013-2014 Intan Technologies LLC
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the
+// use of this software.
+//
+// Permission is granted to anyone to use this software for any applications that
+// use Intan Technologies integrated circuits, and to alter it and redistribute it
+// freely.
+//
+// See http://www.intantech.com for documentation and product information.
+//----------------------------------------------------------------------------------
+
+#ifndef RHD2000DATABLOCK_H
+#define RHD2000DATABLOCK_H
+
+#define SAMPLES_PER_DATA_BLOCK_PCIE 16
+#define RHD2000_HEADER_MAGIC_NUMBER 0xc691199927021942
+
+using namespace std;
+
+namespace PCIeRhythm {
+
+	class Rhd2000EvalBoard;
+
+	class Rhd2000DataBlock
+	{
+	public:
+		Rhd2000DataBlock(int numDataStreams);
+
+		vector<unsigned int> timeStamp;
+		vector<vector<vector<int> > > amplifierData;
+		vector<vector<vector<int> > > auxiliaryData;
+		vector<vector<int> > boardAdcData;
+		vector<int> ttlIn;
+		vector<int> ttlOut;
+
+		static unsigned int calculateDataBlockSizeInWords(int numDataStreams, int nSamples = -1);
+		static unsigned int getSamplesPerDataBlock();
+		void fillFromUsbBuffer(unsigned char usbBuffer[], int blockIndex, int numDataStreams, int nSamples = -1);
+		void print(int stream) const;
+		void write(ofstream &saveOut, int numDataStreams) const;
+
+		static bool checkUsbHeader(unsigned char usbBuffer[], int index);
+		static unsigned int convertUsbTimeStamp(unsigned char usbBuffer[], int index);
+		static int convertUsbWord(unsigned char usbBuffer[], int index);
+
+	private:
+		void allocateIntArray3D(vector<vector<vector<int> > > &array3D, int xSize, int ySize, int zSize);
+		void allocateIntArray2D(vector<vector<int> > &array2D, int xSize, int ySize);
+		void allocateIntArray1D(vector<int> &array1D, int xSize);
+		void allocateUIntArray1D(vector<unsigned int> &array1D, int xSize);
+
+		void writeWordLittleEndian(ofstream &outputStream, int dataWord) const;
+
+
+		const unsigned int samplesPerBlock;
+		bool usb3;
+	};
+};
+#endif // RHD2000DATABLOCK_H
diff --git a/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000registers.cpp b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000registers.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8fc819c5d8a7537dcfbb5b60251620dcf15e64cc
--- /dev/null
+++ b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000registers.cpp
@@ -0,0 +1,1025 @@
+//----------------------------------------------------------------------------------
+// rhd2000registers.cpp
+//
+// Intan Technoloies RHD2000 Rhythm Interface API
+// Rhd2000Registers Class
+// Version 1.4 (26 February 2014)
+//
+// Copyright (c) 2013-2014 Intan Technologies LLC
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the
+// use of this software.
+//
+// Permission is granted to anyone to use this software for any applications that
+// use Intan Technologies integrated circuits, and to alter it and redistribute it
+// freely.
+//
+// See http://www.intantech.com for documentation and product information.
+//----------------------------------------------------------------------------------
+
+#include <iostream>
+#include <iomanip>
+#include <cmath>
+#include <vector>
+#include <queue>
+
+#include "rhd2000registers.h"
+
+using namespace std;
+using namespace PCIeRhythm;
+
+// This class creates and manages a data structure representing the internal RAM registers on
+// a RHD2000 chip, and generates command lists to configure the chip and perform other functions.
+// Changing the value of variables within an instance of this class does not directly affect a
+// RHD2000 chip connected to the FPGA; rather, a command list must be generated from this class
+// and then downloaded to the FPGA board using Rhd2000EvalBoard::uploadCommandList.
+
+// Constructor.  Set RHD2000 register variables to default values.
+Rhd2000Registers::Rhd2000Registers(double sampleRate)
+{
+    aPwr.resize(64);
+
+    defineSampleRate(sampleRate);
+
+    // Set default values for all register settings
+    adcReferenceBw = 3;         // ADC reference generator bandwidth (0 [highest BW] - 3 [lowest BW]);
+                                // always set to 3
+    setFastSettle(false);       // amplifier fast settle (off = normal operation)
+    ampVrefEnable = 1;          // enable amplifier voltage references (0 = power down; 1 = enable);
+                                // 1 = normal operation
+    adcComparatorBias = 3;      // ADC comparator preamp bias current (0 [lowest] - 3 [highest], only
+                                // valid for comparator select = 2,3); always set to 3
+    adcComparatorSelect = 2;    // ADC comparator select; always set to 2
+
+    vddSenseEnable = 1;         // supply voltage sensor enable (0 = disable; 1 = enable)
+    // adcBufferBias = 32;      // ADC reference buffer bias current (0 [highest current] - 63 [lowest current]);
+                                // This value should be set according to ADC sampling rate; set in setSampleRate()
+
+    // muxBias = 40;            // ADC input MUX bias current (0 [highest current] - 63 [lowest current]);
+                                // This value should be set according to ADC sampling rate; set in setSampleRate()
+
+    // muxLoad = 0;             // MUX capacitance load at ADC input (0 [min CL] - 7 [max CL]); LSB = 3 pF
+                                // Set in setSampleRate()
+
+    tempS1 = 0;                 // temperature sensor S1 (0-1); 0 = power saving mode when temperature sensor is
+                                // not in use
+    tempS2 = 0;                 // temperature sensor S2 (0-1); 0 = power saving mode when temperature sensor is
+                                // not in use
+    tempEn = 0;                 // temperature sensor enable (0 = disable; 1 = enable)
+    setDigOutHiZ();             // auxiliary digital output state
+
+    weakMiso = 1;               // weak MISO (0 = MISO line is HiZ when CS is inactive; 1 = MISO line is weakly
+                                // driven when CS is inactive)
+    twosComp = 0;               // two's complement ADC results (0 = unsigned offset representation; 1 = signed
+                                // representation)
+    absMode = 0;                // absolute value mode (0 = normal output; 1 = output passed through abs(x) function)
+    enableDsp(true);            // DSP offset removal enable/disable
+    setDspCutoffFreq(1.0);      // DSP offset removal HPF cutoff freqeuncy
+
+    zcheckDacPower = 1;         // impedance testing DAC power-up (0 = power down; 1 = power up)
+    zcheckLoad = 0;             // impedance testing dummy load (0 = normal operation; 1 = insert 60 pF to ground)
+    setZcheckScale(ZcheckCs100fF);  // impedance testing scale factor (100 fF, 1.0 pF, or 10.0 pF)
+    zcheckConnAll = 0;          // impedance testing connect all (0 = normal operation; 1 = connect all electrodes together)
+    setZcheckPolarity(ZcheckPositiveInput); // impedance testing polarity select (RHD2216 only) (0 = test positive inputs;
+                                // 1 = test negative inputs)
+    enableZcheck(false);        // impedance testing enable/disable
+
+    setZcheckChannel(0);        // impedance testing amplifier select (0-63)
+
+    offChipRH1 = 0;             // bandwidth resistor RH1 on/off chip (0 = on chip; 1 = off chip)
+    offChipRH2 = 0;             // bandwidth resistor RH2 on/off chip (0 = on chip; 1 = off chip)
+    offChipRL = 0;              // bandwidth resistor RL on/off chip (0 = on chip; 1 = off chip)
+    adcAux1En = 1;              // enable ADC aux1 input (when RH1 is on chip) (0 = disable; 1 = enable)
+    adcAux2En = 1;              // enable ADC aux2 input (when RH2 is on chip) (0 = disable; 1 = enable)
+    adcAux3En = 1;              // enable ADC aux3 input (when RL is on chip) (0 = disable; 1 = enable)
+
+    setUpperBandwidth(10000.0); // set upper bandwidth of amplifiers
+    setLowerBandwidth(1.0);     // set lower bandwidth of amplifiers
+
+    powerUpAllAmps();           // turn on all amplifiers
+}
+
+// Define RHD2000 per-channel sampling rate so that certain sampling-rate-dependent registers are set correctly
+// (This function does not change the sampling rate of the FPGA; for this, use Rhd2000EvalBoard::setSampleRate.)
+void Rhd2000Registers::defineSampleRate(double newSampleRate)
+{
+    sampleRate = newSampleRate;
+
+    muxLoad = 0;
+
+    if (sampleRate < 3334.0) {
+        muxBias = 40;
+        adcBufferBias = 32;
+    } else if (sampleRate < 4001.0) {
+        muxBias = 40;
+        adcBufferBias = 16;
+    } else if (sampleRate < 5001.0) {
+        muxBias = 40;
+        adcBufferBias = 8;
+    } else if (sampleRate < 6251.0) {
+        muxBias = 32;
+        adcBufferBias = 8;
+    } else if (sampleRate < 8001.0) {
+        muxBias = 26;
+        adcBufferBias = 8;
+    } else if (sampleRate < 10001.0) {
+        muxBias = 18;
+        adcBufferBias = 4;
+    } else if (sampleRate < 12501.0) {
+        muxBias = 16;
+        adcBufferBias = 3;
+    } else if (sampleRate < 15001.0) {
+        muxBias = 7;
+        adcBufferBias = 3;
+    } else {
+        muxBias = 4;
+        adcBufferBias = 2;
+    }
+}
+
+// Enable or disable amplifier fast settle function; drive amplifiers to baseline
+// if enabled.
+void Rhd2000Registers::setFastSettle(bool enabled)
+{
+    ampFastSettle = (enabled ? 1 : 0);
+}
+
+// Drive auxiliary digital output low
+void Rhd2000Registers::setDigOutLow()
+{
+    digOut = 0;
+    digOutHiZ = 0;
+}
+
+// Drive auxiliary digital output high
+void Rhd2000Registers::setDigOutHigh()
+{
+    digOut = 1;
+    digOutHiZ = 0;
+}
+
+// Set auxiliary digital output to high-impedance (HiZ) state
+void Rhd2000Registers::setDigOutHiZ()
+{
+    digOut = 0;
+    digOutHiZ = 1;
+}
+
+// Enable or disable ADC auxiliary input 1
+void Rhd2000Registers::enableAux1(bool enabled)
+{
+    adcAux1En = (enabled ? 1 : 0);
+}
+
+// Enable or disable ADC auxiliary input 2
+void Rhd2000Registers::enableAux2(bool enabled)
+{
+    adcAux2En = (enabled ? 1 : 0);
+}
+
+// Enable or disable ADC auxiliary input 3
+void Rhd2000Registers::enableAux3(bool enabled)
+{
+    adcAux3En = (enabled ? 1 : 0);
+}
+
+// Enable or disable DSP offset removal filter
+void Rhd2000Registers::enableDsp(bool enabled)
+{
+    dspEn = (enabled ? 1 : 0);
+}
+
+// Set the DSP offset removal filter cutoff frequency as closely to the requested
+// newDspCutoffFreq (in Hz) as possible; returns the actual cutoff frequency (in Hz).
+double Rhd2000Registers::setDspCutoffFreq(double newDspCutoffFreq)
+{
+    int n;
+    double x, fCutoff[16], logNewDspCutoffFreq, logFCutoff[16], minLogDiff;
+    const double Pi = 2*acos(0.0);
+
+    fCutoff[0] = 0.0;   // We will not be using fCutoff[0], but we initialize it to be safe
+
+    logNewDspCutoffFreq = log10(newDspCutoffFreq);
+
+    // Generate table of all possible DSP cutoff frequencies
+    for (n = 1; n < 16; ++n) {
+        x = pow(2.0, (double) n);
+        fCutoff[n] = sampleRate * log(x / (x - 1.0)) / (2*Pi);
+        logFCutoff[n] = log10(fCutoff[n]);
+        // cout << "  fCutoff[" << n << "] = " << fCutoff[n] << " Hz" << endl;
+    }
+
+    // Now find the closest value to the requested cutoff frequency (on a logarithmic scale)
+    if (newDspCutoffFreq > fCutoff[1]) {
+        dspCutoffFreq = 1;
+    } else if (newDspCutoffFreq < fCutoff[15]) {
+        dspCutoffFreq = 15;
+    } else {
+        minLogDiff = 10000000.0;
+        for (n = 1; n < 16; ++n) {
+            if (abs(logNewDspCutoffFreq - logFCutoff[n]) < minLogDiff) {
+                minLogDiff = abs(logNewDspCutoffFreq - logFCutoff[n]);
+                dspCutoffFreq = n;
+            }
+        }
+    }
+
+    return fCutoff[dspCutoffFreq];
+}
+
+// Returns the current value of the DSP offset removal cutoff frequency (in Hz).
+double Rhd2000Registers::getDspCutoffFreq() const
+{
+    double x;
+    const double Pi = 2*acos(0.0);
+
+    x = pow(2.0, (double) dspCutoffFreq);
+
+    return sampleRate * log(x / (x - 1.0)) / (2*Pi);
+}
+
+// Enable or disable impedance checking mode
+void Rhd2000Registers::enableZcheck(bool enabled)
+{
+    zcheckEn = (enabled ? 1: 0);
+}
+
+// Power up or down impedance checking DAC
+void Rhd2000Registers::setZcheckDacPower(bool enabled)
+{
+    zcheckDacPower = (enabled ? 1 : 0);
+}
+
+// Select the series capacitor used to convert the voltage waveform generated by the on-chip
+// DAC into an AC current waveform that stimulates a selected electrode for impedance testing
+// (ZcheckCs100fF, ZcheckCs1pF, or Zcheck10pF).
+void Rhd2000Registers::setZcheckScale(ZcheckCs scale)
+{
+    switch (scale) {
+        case ZcheckCs100fF:
+            zcheckScale = 0x00;     // Cs = 0.1 pF
+            break;
+        case ZcheckCs1pF:
+            zcheckScale = 0x01;     // Cs = 1.0 pF
+            break;
+        case ZcheckCs10pF:
+            zcheckScale = 0x03;     // Cs = 10.0 pF
+            break;
+    }
+}
+
+// Select impedance testing of positive or negative amplifier inputs (RHD2216 only), based
+// on the variable polarity (ZcheckPositiveInput or ZcheckNegativeInput)
+void Rhd2000Registers::setZcheckPolarity(ZcheckPolarity polarity)
+{
+    switch (polarity) {
+        case ZcheckPositiveInput:
+            zcheckSelPol = 0;
+            break;
+    case ZcheckNegativeInput:
+            zcheckSelPol = 1;
+            break;
+    }
+}
+
+// Select the amplifier channel (0-63) for impedance testing.
+int Rhd2000Registers::setZcheckChannel(int channel)
+{
+    if (channel < 0 || channel > 63) {
+        return -1;
+    } else {
+        zcheckSelect = channel;
+        return zcheckSelect;
+    }
+}
+
+// Power up or down selected amplifier on chip
+void Rhd2000Registers::setAmpPowered(int channel, bool powered)
+{
+    if (channel >= 0 && channel <= 63) {
+        aPwr[channel] = (powered ? 1 : 0);
+    }
+}
+
+// Power up all amplifiers on chip
+void Rhd2000Registers::powerUpAllAmps()
+{
+    for (int channel = 0; channel < 64; ++channel) {
+        aPwr[channel] = 1;
+    }
+}
+
+// Power down all amplifiers on chip
+void Rhd2000Registers::powerDownAllAmps()
+{
+    for (int channel = 0; channel < 64; ++channel) {
+        aPwr[channel] = 0;
+    }
+}
+
+// Returns the value of a selected RAM register (0-17) on the RHD2000 chip, based
+// on the current register variables in the class instance.
+int Rhd2000Registers::getRegisterValue(int reg) const
+{
+    int regout;
+    const int zcheckDac = 128;  // midrange
+
+    switch (reg) {
+    case 0:
+        regout = (adcReferenceBw << 6) + (ampFastSettle << 5) + (ampVrefEnable << 4) +
+                (adcComparatorBias << 2) + adcComparatorSelect;
+        break;
+    case 1:
+        regout = (vddSenseEnable << 6) + adcBufferBias;
+        break;
+    case 2:
+        regout = muxBias;
+        break;
+    case 3:
+        regout = (muxLoad << 5) + (tempS2 << 4) + (tempS1 << 3) + (tempEn << 2) +
+                (digOutHiZ << 1) + digOut;
+        break;
+    case 4:
+        regout = (weakMiso << 7) + (twosComp << 6) + (absMode << 5) + (dspEn << 4) +
+                dspCutoffFreq;
+        break;
+    case 5:
+        regout = (zcheckDacPower << 6) + (zcheckLoad << 5) + (zcheckScale << 3) +
+                (zcheckConnAll << 2) + (zcheckSelPol << 1) + zcheckEn;
+        break;
+    case 6:
+        regout = zcheckDac;
+        break;
+    case 7:
+        regout = zcheckSelect;
+        break;
+    case 8:
+        regout = (offChipRH1 << 7) + rH1Dac1;
+        break;
+    case 9:
+        regout = (adcAux1En << 7) + rH1Dac2;
+        break;
+    case 10:
+        regout = (offChipRH2 << 7) + rH2Dac1;
+        break;
+    case 11:
+        regout = (adcAux2En << 7) + rH2Dac2;
+        break;
+    case 12:
+        regout = (offChipRL << 7) + rLDac1;
+        break;
+    case 13:
+        regout = (adcAux3En << 7) + (rLDac3 << 6) + rLDac2;
+        break;
+    case 14:
+        regout = (aPwr[7] << 7) + (aPwr[6] << 6) + (aPwr[5] << 5) + (aPwr[4] << 4) +
+                (aPwr[3] << 3) + (aPwr[2] << 2) + (aPwr[1] << 1) + aPwr[0];
+        break;
+    case 15:
+        regout = (aPwr[15] << 7) + (aPwr[14] << 6) + (aPwr[13] << 5) + (aPwr[12] << 4) +
+                (aPwr[11] << 3) + (aPwr[10] << 2) + (aPwr[9] << 1) + aPwr[0];
+        break;
+    case 16:
+        regout = (aPwr[23] << 7) + (aPwr[22] << 6) + (aPwr[21] << 5) + (aPwr[20] << 4) +
+                (aPwr[19] << 3) + (aPwr[18] << 2) + (aPwr[17] << 1) + aPwr[16];
+        break;
+    case 17:
+        regout = (aPwr[31] << 7) + (aPwr[30] << 6) + (aPwr[29] << 5) + (aPwr[28] << 4) +
+                (aPwr[27] << 3) + (aPwr[26] << 2) + (aPwr[25] << 1) + aPwr[24];
+        break;
+    case 18:
+        regout = (aPwr[39] << 7) + (aPwr[38] << 6) + (aPwr[37] << 5) + (aPwr[36] << 4) +
+                (aPwr[35] << 3) + (aPwr[34] << 2) + (aPwr[33] << 1) + aPwr[32];
+        break;
+    case 19:
+        regout = (aPwr[47] << 7) + (aPwr[46] << 6) + (aPwr[45] << 5) + (aPwr[44] << 4) +
+                (aPwr[43] << 3) + (aPwr[42] << 2) + (aPwr[41] << 1) + aPwr[40];
+        break;
+    case 20:
+        regout = (aPwr[55] << 7) + (aPwr[54] << 6) + (aPwr[53] << 5) + (aPwr[52] << 4) +
+                (aPwr[51] << 3) + (aPwr[50] << 2) + (aPwr[49] << 1) + aPwr[48];
+        break;
+    case 21:
+        regout = (aPwr[63] << 7) + (aPwr[62] << 6) + (aPwr[61] << 5) + (aPwr[60] << 4) +
+                (aPwr[59] << 3) + (aPwr[58] << 2) + (aPwr[57] << 1) + aPwr[56];
+        break;
+    default:
+        regout = -1;
+    }
+    return regout;
+}
+
+// Returns the value of the RH1 resistor (in ohms) corresponding to a particular upper
+// bandwidth value (in Hz).
+double Rhd2000Registers::rH1FromUpperBandwidth(double upperBandwidth) const
+{
+    double log10f = log10(upperBandwidth);
+
+    return 0.9730 * pow(10.0, (8.0968 - 1.1892 * log10f + 0.04767 * log10f * log10f));
+}
+
+// Returns the value of the RH2 resistor (in ohms) corresponding to a particular upper
+// bandwidth value (in Hz).
+double Rhd2000Registers::rH2FromUpperBandwidth(double upperBandwidth) const
+{
+    double log10f = log10(upperBandwidth);
+
+    return 1.0191 * pow(10.0, (8.1009 - 1.0821 * log10f + 0.03383 * log10f * log10f));
+}
+
+// Returns the value of the RL resistor (in ohms) corresponding to a particular lower
+// bandwidth value (in Hz).
+double Rhd2000Registers::rLFromLowerBandwidth(double lowerBandwidth) const
+{
+    double log10f = log10(lowerBandwidth);
+
+    if (lowerBandwidth < 4.0) {
+        return 1.0061 * pow(10.0, (4.9391 - 1.2088 * log10f + 0.5698 * log10f * log10f +
+                                   0.1442 * log10f * log10f * log10f));
+    } else {
+        return 1.0061 * pow(10.0, (4.7351 - 0.5916 * log10f + 0.08482 * log10f * log10f));
+    }
+}
+
+// Returns the amplifier upper bandwidth (in Hz) corresponding to a particular value
+// of the resistor RH1 (in ohms).
+double Rhd2000Registers::upperBandwidthFromRH1(double rH1) const
+{
+    double a, b, c;
+
+    a = 0.04767;
+    b = -1.1892;
+    c = 8.0968 - log10(rH1/0.9730);
+
+    return pow(10.0, ((-b - sqrt(b * b - 4 * a * c))/(2 * a)));
+}
+
+// Returns the amplifier upper bandwidth (in Hz) corresponding to a particular value
+// of the resistor RH2 (in ohms).
+double Rhd2000Registers::upperBandwidthFromRH2(double rH2) const
+{
+    double a, b, c;
+
+    a = 0.03383;
+    b = -1.0821;
+    c = 8.1009 - log10(rH2/1.0191);
+
+    return pow(10.0, ((-b - sqrt(b * b - 4 * a * c))/(2 * a)));
+}
+
+// Returns the amplifier lower bandwidth (in Hz) corresponding to a particular value
+// of the resistor RL (in ohms).
+double Rhd2000Registers::lowerBandwidthFromRL(double rL) const
+{
+    double a, b, c;
+
+    // Quadratic fit below is invalid for values of RL less than 5.1 kOhm
+    if (rL < 5100.0) {
+        rL = 5100.0;
+    }
+
+    if (rL < 30000.0) {
+        a = 0.08482;
+        b = -0.5916;
+        c = 4.7351 - log10(rL/1.0061);
+    } else {
+        a = 0.3303;
+        b = -1.2100;
+        c = 4.9873 - log10(rL/1.0061);
+    }
+
+    return pow(10.0, ((-b - sqrt(b * b - 4 * a * c))/(2 * a)));
+}
+
+// Sets the on-chip RH1 and RH2 DAC values appropriately to set a particular amplifier
+// upper bandwidth (in Hz).  Returns an estimate of the actual upper bandwidth achieved.
+double Rhd2000Registers::setUpperBandwidth(double upperBandwidth)
+{
+    const double RH1Base = 2200.0;
+    const double RH1Dac1Unit = 600.0;
+    const double RH1Dac2Unit = 29400.0;
+    const int RH1Dac1Steps = 63;
+    const int RH1Dac2Steps = 31;
+
+    const double RH2Base = 8700.0;
+    const double RH2Dac1Unit = 763.0;
+    const double RH2Dac2Unit = 38400.0;
+    const int RH2Dac1Steps = 63;
+    const int RH2Dac2Steps = 31;
+
+    double actualUpperBandwidth;
+    double rH1Target, rH2Target;
+    double rH1Actual, rH2Actual;
+    int i;
+
+    // Upper bandwidths higher than 30 kHz don't work well with the RHD2000 amplifiers
+    if (upperBandwidth > 30000.0) {
+        upperBandwidth = 30000.0;
+    }
+
+    rH1Target = rH1FromUpperBandwidth(upperBandwidth);
+
+    rH1Dac1 = 0;
+    rH1Dac2 = 0;
+    rH1Actual = RH1Base;
+
+    for (i = 0; i < RH1Dac2Steps; ++i) {
+        if (rH1Actual < rH1Target - (RH1Dac2Unit - RH1Dac1Unit / 2)) {
+            rH1Actual += RH1Dac2Unit;
+            ++rH1Dac2;
+        }
+    }
+
+    for (i = 0; i < RH1Dac1Steps; ++i) {
+        if (rH1Actual < rH1Target - (RH1Dac1Unit / 2)) {
+            rH1Actual += RH1Dac1Unit;
+            ++rH1Dac1;
+        }
+    }
+
+    rH2Target = rH2FromUpperBandwidth(upperBandwidth);
+
+    rH2Dac1 = 0;
+    rH2Dac2 = 0;
+    rH2Actual = RH2Base;
+
+    for (i = 0; i < RH2Dac2Steps; ++i) {
+        if (rH2Actual < rH2Target - (RH2Dac2Unit - RH2Dac1Unit / 2)) {
+            rH2Actual += RH2Dac2Unit;
+            ++rH2Dac2;
+        }
+    }
+
+    for (i = 0; i < RH2Dac1Steps; ++i) {
+        if (rH2Actual < rH2Target - (RH2Dac1Unit / 2)) {
+            rH2Actual += RH2Dac1Unit;
+            ++rH2Dac1;
+        }
+    }
+
+    double actualUpperBandwidth1, actualUpperBandwidth2;
+
+    actualUpperBandwidth1 = upperBandwidthFromRH1(rH1Actual);
+    actualUpperBandwidth2 = upperBandwidthFromRH2(rH2Actual);
+
+    // Upper bandwidth estimates calculated from actual RH1 value and acutal RH2 value
+    // should be very close; we will take their geometric mean to get a single
+    // number.
+    actualUpperBandwidth = sqrt(actualUpperBandwidth1 * actualUpperBandwidth2);
+
+    /*
+    cout << endl;
+    cout << "Rhd2000Registers::setUpperBandwidth" << endl;
+    cout << fixed << setprecision(1);
+
+    cout << "RH1 DAC2 = " << rH1Dac2 << ", DAC1 = " << rH1Dac1 << endl;
+    cout << "RH1 target: " << rH1Target << " Ohms" << endl;
+    cout << "RH1 actual: " << rH1Actual << " Ohms" << endl;
+
+    cout << "RH2 DAC2 = " << rH2Dac2 << ", DAC1 = " << rH2Dac1 << endl;
+    cout << "RH2 target: " << rH2Target << " Ohms" << endl;
+    cout << "RH2 actual: " << rH2Actual << " Ohms" << endl;
+
+    cout << "Upper bandwidth target: " << upperBandwidth << " Hz" << endl;
+    cout << "Upper bandwidth actual: " << actualUpperBandwidth << " Hz" << endl;
+
+    cout << endl;
+    cout << setprecision(6);
+    cout.unsetf(ios::floatfield);
+    */
+
+    return actualUpperBandwidth;
+}
+
+// Sets the on-chip RL DAC values appropriately to set a particular amplifier
+// lower bandwidth (in Hz).  Returns an estimate of the actual lower bandwidth achieved.
+double Rhd2000Registers::setLowerBandwidth(double lowerBandwidth)
+{
+    const double RLBase = 3500.0;
+    const double RLDac1Unit = 175.0;
+    const double RLDac2Unit = 12700.0;
+    const double RLDac3Unit = 3000000.0;
+    const int RLDac1Steps = 127;
+    const int RLDac2Steps = 63;
+
+    double actualLowerBandwidth;
+    double rLTarget;
+    double rLActual;
+    int i;
+
+    // Lower bandwidths higher than 1.5 kHz don't work well with the RHD2000 amplifiers
+    if (lowerBandwidth > 1500.0) {
+        lowerBandwidth = 1500.0;
+    }
+
+    rLTarget = rLFromLowerBandwidth(lowerBandwidth);
+
+    rLDac1 = 0;
+    rLDac2 = 0;
+    rLDac3 = 0;
+    rLActual = RLBase;
+
+    if (lowerBandwidth < 0.15) {
+        rLActual += RLDac3Unit;
+        ++rLDac3;
+    }
+
+    for (i = 0; i < RLDac2Steps; ++i) {
+        if (rLActual < rLTarget - (RLDac2Unit - RLDac1Unit / 2)) {
+            rLActual += RLDac2Unit;
+            ++rLDac2;
+        }
+    }
+
+    for (i = 0; i < RLDac1Steps; ++i) {
+        if (rLActual < rLTarget - (RLDac1Unit / 2)) {
+            rLActual += RLDac1Unit;
+            ++rLDac1;
+        }
+    }
+
+    actualLowerBandwidth = lowerBandwidthFromRL(rLActual);
+
+    /*
+    cout << endl;
+    cout << fixed << setprecision(1);
+    cout << "Rhd2000Registers::setLowerBandwidth" << endl;
+
+    cout << "RL DAC3 = " << rLDac3 << ", DAC2 = " << rLDac2 << ", DAC1 = " << rLDac1 << endl;
+    cout << "RL target: " << rLTarget << " Ohms" << endl;
+    cout << "RL actual: " << rLActual << " Ohms" << endl;
+
+    cout << setprecision(3);
+
+    cout << "Lower bandwidth target: " << lowerBandwidth << " Hz" << endl;
+    cout << "Lower bandwidth actual: " << actualLowerBandwidth << " Hz" << endl;
+
+    cout << endl;
+    cout << setprecision(6);
+    cout.unsetf(ios::floatfield);
+    */
+
+    return actualLowerBandwidth;
+}
+
+// Return a 16-bit MOSI command (CALIBRATE or CLEAR)
+int Rhd2000Registers::createRhd2000Command(Rhd2000CommandType commandType)
+{
+    switch (commandType) {
+        case Rhd2000CommandCalibrate:
+            return 0x5500;   // 0101010100000000
+            break;
+        case Rhd2000CommandCalClear:
+            return 0x6a00;   // 0110101000000000
+            break;
+        default:
+        cerr << "Error in Rhd2000Registers::createRhd2000Command: " <<
+                "Only 'Calibrate' or 'Clear Calibration' commands take zero arguments." << endl;
+            return -1;
+    }
+}
+
+// Return a 16-bit MOSI command (CONVERT or READ)
+int Rhd2000Registers::createRhd2000Command(Rhd2000CommandType commandType, int arg1)
+{
+    switch (commandType) {
+        case Rhd2000CommandConvert:
+            if (arg1 < 0 || arg1 > 63) {
+                cerr << "Error in Rhd2000Registers::createRhd2000Command: " <<
+                        "Channel number out of range." << endl;
+                return -1;
+            }
+            return 0x0000 + (arg1 << 8);  // 00cccccc0000000h; if the command is 'Convert',
+                                          // arg1 is the channel number
+        case Rhd2000CommandRegRead:
+            if (arg1 < 0 || arg1 > 63) {
+                cerr << "Error in Rhd2000Registers::createRhd2000Command: " <<
+                        "Register address out of range." << endl;
+                return -1;
+            }
+            return 0xc000 + (arg1 << 8);  // 11rrrrrr00000000; if the command is 'Register Read',
+                                          // arg1 is the register address
+            break;
+        default:
+            cerr << "Error in Rhd2000Registers::createRhd2000Command: " <<
+                    "Only 'Convert' and 'Register Read' commands take one argument." << endl;
+            return -1;
+    }
+}
+
+// Return a 16-bit MOSI command (WRITE)
+int Rhd2000Registers::createRhd2000Command(Rhd2000CommandType commandType, int arg1, int arg2)
+{
+    switch (commandType) {
+        case Rhd2000CommandRegWrite:
+            if (arg1 < 0 || arg1 > 63) {
+                cerr << "Error in Rhd2000Registers::createRhd2000Command: " <<
+                        "Register address out of range." << endl;
+                return -1;
+            }
+            if (arg2 < 0 || arg2 > 255) {
+                cerr << "Error in Rhd2000Registers::createRhd2000Command: " <<
+                        "Register data out of range." << endl;
+                return -1;
+            }
+            return 0x8000 + (arg1 << 8) + arg2; // 10rrrrrrdddddddd; if the command is 'Register Write',
+                                                // arg1 is the register address and arg1 is the data
+            break;
+        default:
+            cerr << "Error in Rhd2000Registers::createRhd2000Command: " <<
+                    "Only 'Register Write' commands take two arguments." << endl;
+            return -1;
+    }
+}
+
+
+// Create a list of 60 commands to program most RAM registers on a RHD2000 chip, read those values
+// back to confirm programming, read ROM registers, and (if calibrate == true) run ADC calibration.
+// Returns the length of the command list.
+int Rhd2000Registers::createCommandListRegisterConfig(vector<int> &commandList, bool calibrate)
+{
+    commandList.clear();    // if command list already exists, erase it and start a new one
+
+    // Start with a few dummy commands in case chip is still powering up
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 63));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 63));
+
+    // Program RAM registers
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite,  0, getRegisterValue( 0)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite,  1, getRegisterValue( 1)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite,  2, getRegisterValue( 2)));
+    // Don't program Register 3 (MUX Load, Temperature Sensor, and Auxiliary Digital Output);
+    // control temperature sensor in another command stream
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite,  4, getRegisterValue( 4)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite,  5, getRegisterValue( 5)));
+    // Don't program Register 6 (Impedance Check DAC) here; create DAC waveform in another command stream
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite,  7, getRegisterValue( 7)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite,  8, getRegisterValue( 8)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite,  9, getRegisterValue( 9)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 10, getRegisterValue(10)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 11, getRegisterValue(11)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 12, getRegisterValue(12)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 13, getRegisterValue(13)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 14, getRegisterValue(14)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 15, getRegisterValue(15)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 16, getRegisterValue(16)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 17, getRegisterValue(17)));
+
+    // Read ROM registers
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 63));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 62));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 61));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 60));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 59));
+
+    // Read chip name from ROM
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 48));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 49));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 50));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 51));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 52));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 53));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 54));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 55));
+
+    // Read Intan name from ROM
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 40));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 41));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 42));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 43));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 44));
+
+    // Read back RAM registers to confirm programming
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead,  0));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead,  1));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead,  2));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead,  3));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead,  4));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead,  5));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead,  6));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead,  7));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead,  8));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead,  9));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 10));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 11));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 12));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 13));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 14));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 15));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 16));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 17));
+
+    // Optionally, run ADC calibration (should only be run once after board is plugged in)
+    if (calibrate) {
+        commandList.push_back(createRhd2000Command(Rhd2000CommandCalibrate));
+    } else {
+        commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 63));
+    }
+
+    // Added in Version 1.2:
+    // Program amplifier 31-63 power up/down registers in case a RHD2164 is connected
+    // Note: We don't read these registers back, since they are only 'visible' on MISO B.
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 18, getRegisterValue(18)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 19, getRegisterValue(19)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 20, getRegisterValue(20)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 21, getRegisterValue(21)));
+
+    // End with a dummy command
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 63));
+
+
+    return commandList.size();
+}
+
+// Create a list of 60 commands to sample auxiliary ADC inputs, temperature sensor, and supply
+// voltage sensor.  One temperature reading (one sample of ResultA and one sample of ResultB)
+// is taken during this 60-command sequence.  One supply voltage sample is taken.  Auxiliary
+// ADC inputs are continuously sampled at 1/4 the amplifier sampling rate.
+//
+// Since this command list consists of writing to Register 3, it also sets the state of the
+// auxiliary digital output.  If the digital output value needs to be changed dynamically,
+// then variations of this command list need to be generated for each state and programmed into
+// different RAM banks, and the appropriate command list selected at the right time.
+//
+// Returns the length of the command list.
+int Rhd2000Registers::createCommandListTempSensor(vector<int> &commandList)
+{
+    int i;
+
+    commandList.clear();    // if command list already exists, erase it and start a new one
+
+    tempEn = 1;
+
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32));     // sample AuxIn1
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33));     // sample AuxIn2
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34));     // sample AuxIn3
+    tempS1 = tempEn;
+    tempS2 = 0;
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32));     // sample AuxIn1
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33));     // sample AuxIn2
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34));     // sample AuxIn3
+    tempS1 = tempEn;
+    tempS2 = tempEn;
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32));     // sample AuxIn1
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33));     // sample AuxIn2
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34));     // sample AuxIn3
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 49));     // sample Temperature Sensor
+
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32));     // sample AuxIn1
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33));     // sample AuxIn2
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34));     // sample AuxIn3
+    tempS1 = 0;
+    tempS2 = tempEn;
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32));     // sample AuxIn1
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33));     // sample AuxIn2
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34));     // sample AuxIn3
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 49));     // sample Temperature Sensor
+
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32));     // sample AuxIn1
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33));     // sample AuxIn2
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34));     // sample AuxIn3
+    tempS1 = 0;
+    tempS2 = 0;
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32));     // sample AuxIn1
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33));     // sample AuxIn2
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34));     // sample AuxIn3
+    commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 48));     // sample Supply Voltage Sensor
+
+    for (i = 0; i < 8; ++i) {
+        commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32));     // sample AuxIn1
+        commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33));     // sample AuxIn2
+        commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34));     // sample AuxIn3
+        commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 63));      // dummy command
+    }
+
+    return commandList.size();
+}
+
+// Create a list of 60 commands to update Register 3 (controlling the auxiliary digital ouput
+// pin) every sampling period.
+//
+// Since this command list consists of writing to Register 3, it also sets the state of the
+// on-chip temperature sensor.  The temperature sensor settings are therefore changed throughout
+// this command list to coordinate with the 60-command list generated by createCommandListTempSensor().
+//
+// Returns the length of the command list.
+int Rhd2000Registers::createCommandListUpdateDigOut(vector<int> &commandList)
+{
+    int i;
+
+    commandList.clear();    // if command list already exists, erase it and start a new one
+
+    tempEn = 1;
+
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    tempS1 = tempEn;
+    tempS2 = 0;
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    tempS1 = tempEn;
+    tempS2 = tempEn;
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    tempS1 = 0;
+    tempS2 = tempEn;
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    tempS1 = 0;
+    tempS2 = 0;
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+
+    for (i = 0; i < 8; ++i) {
+        commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+        commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+        commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+        commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3)));
+    }
+
+    return commandList.size();
+}
+
+// Create a list of up to 1024 commands to generate a sine wave of particular frequency (in Hz) and
+// amplitude (in DAC steps, 0-128) using the on-chip impedance testing voltage DAC.  If frequency is set to zero,
+// a DC baseline waveform is created.
+// Returns the length of the command list.
+int Rhd2000Registers::createCommandListZcheckDac(vector<int> &commandList, double frequency, double amplitude)
+{
+    int i, period, value;
+    double t;
+    const double Pi = 2*acos(0.0);
+
+    commandList.clear();    // if command list already exists, erase it and start a new one
+
+    if (amplitude < 0.0 || amplitude > 128.0) {
+        cerr << "Error in Rhd2000Registers::createCommandListZcheckDac: Amplitude out of range." << endl;
+        return -1;
+    }
+    if (frequency < 0.0) {
+        cerr << "Error in Rhd2000Registers::createCommandListZcheckDac: Negative frequency not allowed." << endl;
+        return -1;
+    } else if (frequency > sampleRate / 4.0) {
+        cerr << "Error in Rhd2000Registers::createCommandListZcheckDac: " <<
+                "Frequency too high relative to sampling rate." << endl;
+        return -1;
+    }
+    if (frequency == 0.0) {
+        for (i = 0; i < MaxCommandLength; ++i) {
+            commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 6, 128));
+        }
+    } else {
+        period = (int) floor(sampleRate / frequency + 0.5);
+        if (period > MaxCommandLength) {
+            cerr << "Error in Rhd2000Registers::createCommandListZcheckDac: Frequency too low." << endl;
+            return -1;
+        } else {
+            t = 0.0;
+            for (i = 0; i < period; ++i) {
+                value = (int) floor(amplitude * sin(2 * Pi * frequency * t) + 128.0 + 0.5);
+                if (value < 0) {
+                    value = 0;
+                } else if (value > 255) {
+                    value = 255;
+                }
+                commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 6, value));
+                t += 1.0 / sampleRate;
+            }
+        }
+    }
+
+    return commandList.size();
+}
diff --git a/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000registers.h b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000registers.h
new file mode 100644
index 0000000000000000000000000000000000000000..6c47449456cd0a1877a2d8ee819f7a970012bbca
--- /dev/null
+++ b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000registers.h
@@ -0,0 +1,170 @@
+//----------------------------------------------------------------------------------
+// rhd2000registers.h
+//
+// Intan Technoloies RHD2000 Rhythm Interface API
+// Rhd2000Registers Class Header File
+// Version 1.4 (26 February 2014)
+//
+// Copyright (c) 2013-2014 Intan Technologies LLC
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the
+// use of this software.
+//
+// Permission is granted to anyone to use this software for any applications that
+// use Intan Technologies integrated circuits, and to alter it and redistribute it
+// freely.
+//
+// See http://www.intantech.com for documentation and product information.
+//----------------------------------------------------------------------------------
+
+#ifndef RHD2000REGISTERS_H
+#define RHD2000REGISTERS_H
+
+using namespace std;
+
+namespace PCIeRhythm {
+	class Rhd2000Registers
+	{
+
+	public:
+		Rhd2000Registers(double sampleRate);
+
+		void defineSampleRate(double newSampleRate);
+
+		void setFastSettle(bool enabled);
+
+		void setDigOutLow();
+		void setDigOutHigh();
+		void setDigOutHiZ();
+
+		void enableAux1(bool enabled);
+		void enableAux2(bool enabled);
+		void enableAux3(bool enabled);
+
+		void enableDsp(bool enabled);
+		void disableDsp();
+		double setDspCutoffFreq(double newDspCutoffFreq);
+		double getDspCutoffFreq() const;
+
+		void enableZcheck(bool enabled);
+		void setZcheckDacPower(bool enabled);
+
+		enum ZcheckCs {
+			ZcheckCs100fF,
+			ZcheckCs1pF,
+			ZcheckCs10pF
+		};
+
+		enum ZcheckPolarity {
+			ZcheckPositiveInput,
+			ZcheckNegativeInput
+		};
+
+		void setZcheckScale(ZcheckCs scale);
+		void setZcheckPolarity(ZcheckPolarity polarity);
+		int setZcheckChannel(int channel);
+
+		void setAmpPowered(int channel, bool powered);
+		void powerUpAllAmps();
+		void powerDownAllAmps();
+
+		int getRegisterValue(int reg) const;
+
+		double setUpperBandwidth(double upperBandwidth);
+		double setLowerBandwidth(double lowerBandwidth);
+
+		int createCommandListRegisterConfig(vector<int> &commandList, bool calibrate);
+		int createCommandListTempSensor(vector<int> &commandList);
+		int createCommandListUpdateDigOut(vector<int> &commandList);
+		int createCommandListZcheckDac(vector<int> &commandList, double frequency, double amplitude);
+
+		enum Rhd2000CommandType {
+			Rhd2000CommandConvert,
+			Rhd2000CommandCalibrate,
+			Rhd2000CommandCalClear,
+			Rhd2000CommandRegWrite,
+			Rhd2000CommandRegRead
+		};
+
+		int createRhd2000Command(Rhd2000CommandType commandType);
+		int createRhd2000Command(Rhd2000CommandType commandType, int arg1);
+		int createRhd2000Command(Rhd2000CommandType commandType, int arg1, int arg2);
+
+	private:
+		double sampleRate;
+
+		// RHD2000 Register 0 variables
+		int adcReferenceBw;
+		int ampFastSettle;
+		int ampVrefEnable;
+		int adcComparatorBias;
+		int adcComparatorSelect;
+
+		// RHD2000 Register 1 variables
+		int vddSenseEnable;
+		int adcBufferBias;
+
+		// RHD2000 Register 2 variables
+		int muxBias;
+
+		// RHD2000 Register 3 variables
+		int muxLoad;
+		int tempS1;
+		int tempS2;
+		int tempEn;
+		int digOutHiZ;
+		int digOut;
+
+		// RHD2000 Register 4 variables
+		int weakMiso;
+		int twosComp;
+		int absMode;
+		int dspEn;
+		int dspCutoffFreq;
+
+		// RHD2000 Register 5 variables
+		int zcheckDacPower;
+		int zcheckLoad;
+		int zcheckScale;
+		int zcheckConnAll;
+		int zcheckSelPol;
+		int zcheckEn;
+
+		// RHD2000 Register 6 variables
+		//int zcheckDac;     // handle Zcheck DAC waveform elsewhere
+
+		// RHD2000 Register 7 variables
+		int zcheckSelect;
+
+		// RHD2000 Register 8-13 variables
+		int offChipRH1;
+		int offChipRH2;
+		int offChipRL;
+		int adcAux1En;
+		int adcAux2En;
+		int adcAux3En;
+		int rH1Dac1;
+		int rH1Dac2;
+		int rH2Dac1;
+		int rH2Dac2;
+		int rLDac1;
+		int rLDac2;
+		int rLDac3;
+
+		// RHD2000 Register 14-17 variables
+		vector<int> aPwr;
+
+		double rH1FromUpperBandwidth(double upperBandwidth) const;
+		double rH2FromUpperBandwidth(double upperBandwidth) const;
+		double rLFromLowerBandwidth(double lowerBandwidth) const;
+		double upperBandwidthFromRH1(double rH1) const;
+		double upperBandwidthFromRH2(double rH2) const;
+		double lowerBandwidthFromRL(double rL) const;
+
+		static const int MaxCommandLength = 1024; // size of on-FPGA auxiliary command RAM banks
+
+	};
+};
+
+#endif // RHD2000REGISTERS_H