Thank you mattkhan! I was using a really old version of JRiver before the parameters for the ASIO Live-In was added.
I just wanted to give back to the community by sharing how I was able to accomplish automatic sample rate changes for Digital ASIO Live-In.
Here is some context:
I wanted to connect a iPhone, and use Apple Music to listen to HD music through JRiver. I have a active XO configured though JRiver, which is why I wanted to try to play music through it.
1.) To get digital out from the iPhone, I'm using a Apple Camera USB adapter connected to a SMSL PO100 PRO.
2.) I connect the digital out to a TASCAM US-366 unit for the ASIO Digital ASIO input. It supports sample rates from 44.1khz to 192khz.
3.) I found that the TASCAM software showed the current digital input sample rate in a status window.
4.) I used Cheat Engine for windows to find which memory address in the TASCAM software contained the current sample rate info and also the status text that states "no valid input" in the case there is no connection. (The reason I needed the "no valid input" value is because it remembers the last connected sample rate always, even without a signal.)
5.) I found out that the memory address for the above two values changed every time the computer restarts (or when the TASCAM program is forced to close, and is reopened).
6.) I again used Cheat Engine to find the pointers for the 2 values above so that I can find the correct memory address every time, even after rebooting.
7.) I created a simple command line program in C# (x64) that does the following:
- First, check whether "no valid input" is not currently valid.
- Next, checks current sample rate.
- Runs command line trigger to trigger the ASIO Live-in in JRiver.
Note: The command line to start ASIO Live-In in JRiver:
"mc32.exe /Play "live://asio?deviceindex=4&deviceid=%7BE19D4C7A-2A03-4484-B5A2-8ED346CC0969%7D&samplerate={SAMPLERATE HERE}&channels=2&channelshift=2\"
Note: your deviceindex and deviceid will most likely be different for your hardware. Also, the channel shift being "2" is the right setting for the TASCAM US-366.
-Keeps checking every second for changes in the sample rate. If a new sample rate is found, trigger a new command with the correct sample rate.
- If at any time, "no valid input" becomes TRUE, it will not check for any sample rate, and wait for the input to become valid again.
I run this program along side JRiver, and when I connect the digital connection from the iPhone, the ASIO Live-In automatically switches as needed.
A few notes:
-Depending on the version of the TASCAM software, the pointers and memory locations would probably be different. You will need to find the pointers yourself, if that is your case.
In my case, the pointer address for the two values were located at the following:
**For the Sample Rate (4 byte):
Base Address: "CPL_US322_US366.EXE+003E7A30"
offset: E84
**For the "not valid signal" status text (string):
Base Address: "CPL_US322_US366.EXE+003E7F90"
offset: BEC
FINAL NOTICE: I cannot take any responsibility for any harm this method and code may cause. Please use it as only as reference and at your own risk.
Finally, here is the source code:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
class MemoryMonitor
{
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll")]
public static extern bool ReadProcessMemory(int hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead);
[DllImport("kernel32.dll")]
public static extern bool CloseHandle(IntPtr hObject);
const int PROCESS_VM_READ = 0x0010;
private static bool monitorCurrentValue = true;
static void Main()
{
int processId = -1;
IntPtr processHandle = IntPtr.Zero;
int lastValue = 0;
while (true)
{
if (processId == -1)
{
processId = GetProcessIdByName("CPL_US322_US366");
if (processId != -1)
{
processHandle = OpenProcess(PROCESS_VM_READ, false, processId);
Console.WriteLine("Found process and obtained handle. Monitoring started.");
}
else
{
Console.WriteLine("Process not found. Retrying in 5 seconds...");
Thread.Sleep(5000);
continue;
}
}
IntPtr moduleBase = GetModuleBase(processId, "CPL_US322_US366.EXE");
IntPtr addressForCurrentValue = ResolveAddress(processHandle, moduleBase, 0x3E7A30, 0xE84);
IntPtr addressForSignalStatus = ResolveAddress(processHandle, moduleBase, 0x3E7F90, 0xBEC);
string signalStatus = ReadString(processHandle, addressForSignalStatus, 16);
if (signalStatus != "no valid signal")
{
if (monitorCurrentValue)
{
int currentValue = ReadMemory(processHandle, addressForCurrentValue);
if (currentValue != lastValue)
{
Console.WriteLine($"Value changed from {lastValue} to {currentValue}");
lastValue = currentValue;
TriggerAction(currentValue);
}
}
monitorCurrentValue = true; // Enable monitoring
}
else
{
monitorCurrentValue = false; // Disable monitoring
lastValue = 0;
Console.WriteLine("No valid signal detected. Monitoring paused.");
}
Thread.Sleep(1000);
}
}
static int GetProcessIdByName(string processName)
{
Process[] processes = Process.GetProcessesByName(processName);
if (processes.Length > 0)
{
return processes[0].Id;
}
return -1;
}
static IntPtr GetModuleBase(int processId, string moduleName)
{
Process proc = Process.GetProcessById(processId);
foreach (ProcessModule module in proc.Modules)
{
if (module.ModuleName == moduleName)
{
return module.BaseAddress;
}
}
return IntPtr.Zero;
}
static IntPtr ResolveAddress(IntPtr processHandle, IntPtr baseAddress, int firstOffset, int secondOffset)
{
IntPtr intermediateAddress;
byte[] buffer = new byte[IntPtr.Size]; // Use IntPtr.Size to handle both 32 and 64-bit architectures
int bytesRead = 0;
IntPtr firstLevelAddress = IntPtr.Add(baseAddress, firstOffset);
ReadProcessMemory((int)processHandle, firstLevelAddress, buffer, buffer.Length, ref bytesRead);
intermediateAddress = (IntPtr)BitConverter.ToInt32(buffer, 0); // Assumes we are on a 32-bit system
IntPtr finalAddress = IntPtr.Add(intermediateAddress, secondOffset);
return finalAddress;
}
static int ReadMemory(IntPtr processHandle, IntPtr address)
{
int bytesRead = 0;
byte[] buffer = new byte[4]; // Assuming the data is an integer
if (ReadProcessMemory((int)processHandle, address, buffer, buffer.Length, ref bytesRead))
{
return BitConverter.ToInt32(buffer, 0);
}
return -1; // Indicate an error or that the process may have closed
}
static string ReadString(IntPtr processHandle, IntPtr address, int length)
{
int bytesRead = 0;
byte[] buffer = new byte[length];
if (ReadProcessMemory((int)processHandle, address, buffer, length, ref bytesRead))
{
return Encoding.ASCII.GetString(buffer).TrimEnd('\0');
}
return null; // Indicate an error or that the process may have closed
}
static void TriggerAction(int currentValue)
{
// List of desired sample rates
int[] validSampleRates = { 44100, 48000, 88200, 96000, 176000, 192000 };
// Check if currentValue is in the list of validSampleRates
if (Array.IndexOf(validSampleRates, currentValue) != -1)
{
// Format the command to include the current value in the samplerate parameter
string command = "mc32.exe";
string args = $"/Play \"live://asio?deviceindex=4&deviceid=%7BE19D4C7A-2A03-4484-B5A2-8ED346CC0969%7D&samplerate={currentValue}&channels=2&channelshift=2\"";
// Start the external process
ProcessStartInfo startInfo = new ProcessStartInfo()
{
FileName = command,
Arguments = args,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
try
{
using (Process process = Process.Start(startInfo))
{
Console.WriteLine($"Executed command: {command} {args}");
// Optional: Read the output
string result = process.StandardOutput.ReadToEnd();
process.WaitForExit();
// Output the result if needed
Console.WriteLine(result);
}
}
catch (Exception ex)
{
Console.WriteLine("Failed to execute command. Error: " + ex.Message);
}
}
else
{
Console.WriteLine($"Current value {currentValue} is not a valid sample rate. Command not executed.");
}
}
}
I hope this may help someone who may be trying to accomplish the same thing. I hope in the future, JRiver will implement automatic sample rate detection for ASIO Live-In