Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FR API: Get plugin latency & Set output latency #726

Open
MichaelPilyavskiy opened this issue Jun 1, 2015 · 5 comments
Open

FR API: Get plugin latency & Set output latency #726

MichaelPilyavskiy opened this issue Jun 1, 2015 · 5 comments

Comments

@MichaelPilyavskiy
Copy link

Sorry for FR crossposting beetween sws and cockos. (Maybe no need to post into Cockos FR Tracker, cause requests gonna implement faster here)

Subject: is it possible to get latency of plugin and set driver output latency?
For what? When I want to play something, I change asio latency to low value and make offline all FX currently worked with more than some_value latency. So, I want to make a script which offline all fx with big latency and turn down latency of audiodriver.

@Breeder
Copy link
Contributor

Breeder commented Jun 2, 2015

mpl, you sure love impossible/hard requests ;P

You can read driver latency via API GetInputOutputLatency()
However, it doesn't seem possible to set it via API- some drivers don't even allow REAPER requesting the latency...for example, to set RME Babyface you always have to use Fireface USB settings.

If your driver supports setting the latency by REAPER (by turning on the option to request block size in Preferences->Audio->Device), you could try setting "asio_bsize" config string with SNM_SetIntConfigVar() (I'm not sure if REAPER updates drivers after that. If not, maybe closing the device via manipulation of preferences somehow would achive that)

When it comes to plugin latency, we too don't have the access to that.

@Breeder
Copy link
Contributor

Breeder commented Jun 2, 2015

But regarding setting the latency of your device...you could maybe achieve it with python script that utilizes pywin32 library. So you could theoretically close audio device in REAPER, show your driver configuration window, set desired latency, close it and activate audio device. All with win32 api...

I would actually like to have that so I'll try to bash something of my own.

@Breeder
Copy link
Contributor

Breeder commented Jun 3, 2015

Something like this works for Babyface:

Note that it needs pywin32 (http://sourceforge.net/projects/pywin32/files/pywin32/ )
I will export these few win32 functions to SWS so it doesn't depends on pywin32, only SWS...and then I will share it publicly on the forums :)

This is the example script:

import DriverBufferSizeTools as audioDevice

audioDevice.SetBufferSize(48)
def NoUndoPoint (): pass   # Makes sure there is no necessary undo point created, see more
RPR_defer("NoUndoPoint()") # here: http://forum.cockos.com/showpost.php?p=1523953&postcount=67

And it needs this module in the same folder (name it "DriverBufferSizeTools.py"):

# (C) 2015, Dominik Martin Drzic

#########################################################################
# First of all, to even get this to work, you NEED pywin32 library      #
# which you can get here:                                               #
# http://sourceforge.net/projects/pywin32/files/pywin32/                #
#                                                                       #
# This module exports just one function that allows the user to change  #
# buffer size of RME Babyface directly from REAPER.                     #
#                                                                       #
# It should be possible to modify it so it works with other RME devices #
# by changing __driverExePath, __possibleBuffers and some window names  #
# in __GetBufferDropDownHwnd. It really depends on your own situation   #
# so you will have to play a bit to make it work with other RME devices #
#                                                                       #
# The way it works is by simple win32 API which manipulates the RME     #
# settings dialog to change the buffer size. During the manipulation    #
# things rely a lot on window names and their z-order so that makes it  #
# rather touchy - everything could break if RME changes something (it   #
# could also break in case RME uses localization for its dialogs, but I #
# haven't tested that). So yeah, no guarantees this will work at all... #
# as long as I use REAPER and Babyface, I will try to have this working #
# and updated. You've been warned - now go and make some music! :)      #
#########################################################################
from win32api import SendMessage, ShellExecute
from win32gui import FindWindowEx, SetForegroundWindow
from win32con import CB_ERR, CB_FINDSTRING, CB_GETCOUNT, CB_GETCURSEL, VK_UP, VK_DOWN, CB_SETCURSEL, WM_CLOSE, WM_KEYDOWN
from reaper_python import *

#########################################################################
# User settings (you may need to modify these)                          #
#########################################################################
__driverExePath   = "C:\\Windows\\System32\\firefaceusb.exe" # Set this to location of firefaceusb.exe
__possibleBuffers = [48, 64, 96, 128, 256, 512, 1024, 2048]  # Set this to all possible buffer sizes that driver allows setting

#########################################################################
# Various functions                                                     #
#########################################################################
def __OpenDriverConfig ():
    try:
        ShellExecute(0, None, __driverExePath, None, None, 0)
    except:
        return False
    return True

def __GetBufferDropDownHwnd ():

    ## Search for main fireface window
    driverMainHwnd = 0
    while True:
        driverMainHwnd = FindWindowEx(0, driverMainHwnd, "#32770", "Fireface USB Settings")
        if driverMainHwnd == 0: # While dangerous, it's necessary because we must wait for window to appear before searching further (ShellExecuteEx could
            pass                # tell us that process finished executing but not when driver window is shown). Danger exists in case no window is found
                                # because then we get infinite loop...however, __OpenDriverConfig(), which is called before this, lets us know if driver was
                                # found in the first place so the danger is a tad less and exists only if RME change their window name or disables the feature
                                # where driver window appears on running the executable
        else:

            ## Search for settings windows
            settingsHwnd = 0
            while True:
                settingsHwnd = FindWindowEx(driverMainHwnd, settingsHwnd, "#32770", "Fireface Settings") # Since I don't have multiple RME devices, I can't really know how other dialogs are named. But if you only have Babyface connected, this should be the name of its settings HWND
                if settingsHwnd != 0:

                    # Search for drop down which contains possible buffer entries
                    bufferHwnd = 0
                    while True:
                        bufferHwnd = FindWindowEx(settingsHwnd, bufferHwnd, "ComboBox", None)
                        if bufferHwnd != 0:
                            if SendMessage(bufferHwnd, CB_GETCOUNT, 0, 0) == len(__possibleBuffers):

                                __possibleBuffersIndexes = []
                                for i in range(len(__possibleBuffers)):
                                    __possibleBuffersIndexes.append(-1)

                                allEntriesFound = True
                                for index, item in enumerate(__possibleBuffers):
                                    comboIndex = SendMessage(bufferHwnd, CB_FINDSTRING, -1, str(item) + " Samples")
                                    if comboIndex != CB_ERR:
                                        __possibleBuffersIndexes[index] = comboIndex
                                    else:
                                        allEntriesFound = False
                                        break

                                if allEntriesFound:
                                    return driverMainHwnd, bufferHwnd, __possibleBuffersIndexes
                        else:
                            break
                else:
                    break
    return 0, 0, []

def __ChangeBufferSize (newBuffer):
    if not __OpenDriverConfig():
        RPR_MB("Cannot find the driver configuration at specified path.", "Error", 0)
    else:
        mainHwnd, dropDownHwnd, indexes = __GetBufferDropDownHwnd()

        # Hide driver configuration window
        reaperHwnd = int(RPR_GetMainHwnd().replace("(HWND)", ""), 16)
        SetForegroundWindow(reaperHwnd)

        if dropDownHwnd == 0:
            RPR_MB("Cannot find the buffer drop down list.", "Error", 0)
        else:
            try:
                dropDownIndex = indexes[__possibleBuffers.index(newBuffer)]
                if dropDownIndex != -1:
                    SendMessage(dropDownHwnd, CB_SETCURSEL, dropDownIndex, 0)

                    # Simply setting the drop down text isn't enough, so simulate up/down arrows to "refresh" the values
                    currentIndex = SendMessage(dropDownHwnd, CB_GETCURSEL, 0, 0)
                    SendMessage(dropDownHwnd, WM_KEYDOWN,   VK_UP, 0)
                    if SendMessage(dropDownHwnd, CB_GETCURSEL, 0, 0) == currentIndex:
                        SendMessage(dropDownHwnd, WM_KEYDOWN, VK_DOWN, 0)
                        SendMessage(dropDownHwnd, WM_KEYDOWN, VK_UP, 0)
                    else:
                        SendMessage(dropDownHwnd, WM_KEYDOWN, VK_DOWN, 0)

                    SendMessage(mainHwnd,     WM_CLOSE,       0, 0)

                else:
                    RPR_MB("Invalid buffer specified.", "Error", 0)
            except:
                RPR_MB("Invalid buffer specified.", "Error", 0)

#########################################################################
# Exported functions                                                    #
#########################################################################
def SetBufferSize (newBuffer):

    playState = RPR_GetPlayState()
    playing   = playState & 1 == 1
    paused    = playState & 2 == 2
    recording = playState & 4 == 4

    if not recording:
        cursorPos = -1
        playPos   = -1
        if playing or paused:
            RPR_PreventUIRefresh(1)
            cursorPos = RPR_GetCursorPosition()
            playPos   = RPR_GetPlayPosition()
            RPR_OnStopButton()

        __ChangeBufferSize (newBuffer)

        if playing or paused:
            if paused:
                RPR_OnPauseButton()
            elif playing:
                RPR_SetEditCurPos2(0, playPos, False, False)
                RPR_OnPlayButton()
            RPR_SetEditCurPos2(0, cursorPos, False, False);
            RPR_PreventUIRefresh(-1);

@morcOticon
Copy link

This is exactly what I need. But I have been trying to get it to work. It seems that I get a valid identifier "bufferHwnd"(it does not equal 0) If I use "EnumChildWindows(settingsHwnd, printClasses, None)" it seems that I am the right place. But using but the sendmessage commands does not work and return 0 not matter what I try:
SendMessage(dropDownHwnd, WM_KEYDOWN, VK_DOWN, 0)
SendMessage(bufferHwnd, WM_KEYDOWN, VK_UP, 0)
SendMessage(bufferHwnd, CB_GETCURSEL, 0, 0)
SendMessage(bufferHwnd, CB_GETCOUNT, 0, 0)
SendMessage(bufferHwnd, CB_FINDSTRING, -1, "1024 Samples")

Any help would be appreciated:-)

@morcOticon
Copy link

ARH.... I just re-started my programming environment "run as Admin" now it works:-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants