如何评估用户定义的表达式每秒44100次

时间:2016-07-07 23:50:17

标签: c++ signal-processing

我正在使用C ++开发VST插件。该插件将允许用户输入数学表达式,然后每秒运行44100次以产生声音。我不熟悉这样的实时内容,也解释用户输入的表达式。

问题在于我找不到一种方法来评估可以快速运行的用户定义函数。我最好的尝试是在输入时将用户输入的表达式转换为RPN,然后使用函数评估RPN表达式以生成音频。我实现了RPN评估功能并对RPN表达式进行了硬编码以对其进行测试。虽然它似乎正确评估,但它似乎没有做得足够快。

除了几个RPN表达式之外,这是我的评估函数:

#include <string>
#include <stack>
#include <deque>


/*
 * an RPN expression is stored as a deque of strings
 * 
 * each string is either an operator, a number, or the single variable t
 * 
 * the deque is read from front to back
*/

std::deque<std::string> simpleCase, complexCase;

//simple expression, just the variable t
simpleCase.push_back("t");

//more complex expression, t*(42&(t>>11))
complexCase.push_back("t");
complexCase.push_back("42");
complexCase.push_back("t");
complexCase.push_back("11");
complexCase.push_back(">>");
complexCase.push_back("&");
complexCase.push_back("*");

/*
 * The evalRPN function takes an RPN deque, plugs in a supplied t,
 * and evaluates it.
 * 
 * The idea is that t increases continually, and that the integer overflow
 * causes the output to oscillate between 0 and 255.
 * 
 * t is a double, but I convert it to a uint32_t.
 * 
 * Allowed operators: bitwise logic (&, |, ^), bitshifts (<<, >>),
 * and math (+, -, *, /, %)
 * 
 * Allowed vars: t
 * 
 * Supplied numbers are converted from string to char arrays then to an int
 * 
 * This also assumes the RPN is not ill-formatted.
*/
uint8_t evalRPN(std::deque<std::string> rpnExpr, double tVal)
{
    std::stack<uint8_t> numberStack;
    std::string token;

    while(rpnExpr.size() > 0)
    {

        token = rpnExpr.front();
        rpnExpr.pop_front();

        if(token.find_first_not_of("0123456789") == std::string::npos)
        {
            //if token is a number
            numberStack.push((uint8_t)atoi(token.c_str()));
        }
        else if (token == "t")
        {
            numberStack.push((uint8_t)tVal);
        }
        else
        {
            uint8_t last = numberStack.top();
            numberStack.pop();
            uint8_t first = numberStack.top();
            numberStack.pop();

            if(token == "^")
            {
                numberStack.push(first ^ last);
            }
            else if (token == "&")
            {
                numberStack.push(first & last);
            }
            else if (token == "|")
            {
                numberStack.push(first | last);
            }
            else if (token == "<<")
            {
                numberStack.push(first >> last);
            }
            else if (token == ">>")
            {
                numberStack.push(first >> last);
            }
            else if (token == "+")
            {
                numberStack.push(first + last);
            }
            else if (token == "-")
            {
                numberStack.push(first - last);
            }
            else if (token == "*")
            {
                numberStack.push(first * last);
            }
            else if (token == "/")
            {
                numberStack.push(first / last);
            }
            else if (token == "%")
            {
                numberStack.push(first % last);
            }
        }
    }

    //assume one left in numberStack
    return(numberStack.top());
}

我在RPN处理中是否可以进行任何优化以使其可以足够快地运行?或者,是否有另一种处理RPN计算的方法更有效?

此外,还有另一种方法是C ++兼容,用于输入用户输入的表示标准数学表达式的字符串,然后以足够快的速度运行该表达式,以便在1/44100秒内完成?

4 个答案:

答案 0 :(得分:1)

这是一个很好的问题。

将你的表达式编译成RPN是一个好的开始,实际上它看起来像我的代码应该能够每秒执行超过88K的表达式,除非它们很长。

但是,你可以毫不费力地做得更好。

我会创建一个这样的界面:

class Expression
{
    public:
    virtual uint32_t eval(uint32_t tVal) = 0;
};

然后,您将表达式编译为此接口的实现。

您可以拥有常量的实现:

class ConstExpression : public Expression
{
    private:
    uint32_t m_constVal;

    public:
    // ...

    uint32_t eval(uint32_t tVal)
    {
         return m_constVal;
    }
};

......引用t

的实现
class RefExpression : public Expression
{
    public:
    // ...

    uint32_t eval(uint32_t tVal)
    {
         return m_tVal;
    }
};

...以及二元运算符的实现

class AddExpression : public Expression
{
    private:
    auto_ptr<Expression> m_left;
    auto_ptr<Expression> m_right;

    public:
    // ...

    uint32_t eval(uint32_t tVal)
    {
         return m_left->eval(tVal) + m_right->eval(tVal);
    }
};

...也许你想做一些模板魔法,以避免必须手工编写如此多的操作符类。

无论如何,在将表达式编译成表达式之后,您可以像表达式&gt; eval(t)一样评估它,并且所有代码都以合理有效的方式执行,无需解析,字符串比较,堆栈操作等。

答案 1 :(得分:0)

如何实时生成代码,编译它然后用作二进制库函数?我相信它会更快地运作。

答案 2 :(得分:0)

你不必。如果要构建vst,则必须使用vst库。主机daw将自动调用更新功能。

答案 3 :(得分:0)

感谢您的所有建议!我已经实施了一个我非常满意的解决方案。虽然这里没有任何答案直接告诉我这种方法,但他们确实激励了我。

我决定将用户输入的中缀表达式解析为波兰表示法(前缀),然后解析为二叉树。我专门为此实现了一个TreeNode结构,它本质上是一个双向链表,但有2个子节点而不是1.每个TreeNode可以设置为变量,固定数字或运算符。如果将其设置为运算符,则它的opFunc函数指针成员将设置为执行该运算符的预定义函数。

每个节点都有一个evaluate(tVal)成员函数。如果它是固定数字,则只返回该数字。如果它是一个变量,它只返回tVal。如果它是一个运算符,它返回opFunc(first-&gt; evaluate(tVal),last-&gt; evaluate(tVal)),它递归地计算它下面的两个节点,然后执行它的运算符在他们身上。

我还实现了一个ExpressionTree类,它管理树并将用户输入解析为树。它有公共成员用于递归地销毁树,从用户输入字符串构建树,以及评估树(它只需要评估根节点)。

因为它在一个简单的递归树中使用函数指针,所以即使公式变得非常大,评估ExpressionTree的速度也非常快。基本上,在评估函数时以及输入公式时,我已经或多或少地移动了尽可能多的处理。

下面是我为表达式树写的代码。请注意,只存在非常有限的运算符,但是通过将它们包含在优先级映射中,为它们添加函数以及在setOp中包含一个开关,可以很容易地添加新的二元运算符。也只允许一个变量,这是我的应用程序将使用的所有变量。

此时也没有执行错误检查,因此无效的运算符,空格或不匹配的括号将导致ATM的未定义行为。

另请注意,表达式将作为uint32_t处理,并在最后变为uint8_t,就像我的应用程序所要求的那样。当我完成这个项目时,我可以将其概括为一个库并发布它。

TreeExpressions.hpp:

#include <cstdint>
#include <string>
#include <map>
#include <stack>
#include <vector>
#include <algorithm>

struct TreeNode {
private:
    bool isOp = false;
    bool isVar = false;
    uint32_t value = 0;
    uint32_t(*opFunc)(uint32_t, uint32_t) = NULL;

    static inline uint32_t xorOp(uint32_t a, uint32_t b) { return(a ^ b); };
    static inline uint32_t andOp(uint32_t a, uint32_t b) { return(a & b); };
    static inline uint32_t orOp(uint32_t a, uint32_t b) { return(a | b); };
    static inline uint32_t lshiftOp(uint32_t a, uint32_t b) { return(a << b); };
    static inline uint32_t rshiftOp(uint32_t a, uint32_t b) { return(a >> b); };
    static inline uint32_t addOp(uint32_t a, uint32_t b) { return(a + b); };
    static inline uint32_t subOp(uint32_t a, uint32_t b) { return(a - b); };
    static inline uint32_t multOp(uint32_t a, uint32_t b) { return(a * b); };
    static inline uint32_t divOp(uint32_t a, uint32_t b) { return(a / b); };
    static inline uint32_t modOp(uint32_t a, uint32_t b) { return(a % b); };
public:
    TreeNode *first = NULL;
    TreeNode *last = NULL;
    TreeNode *parent = NULL;
    uint32_t evaluate(uint32_t tVal);
    void setOp(std::string op);
    void setVal(uint32_t val);
    void setVar();
};

class ExpressionTree
{
private:
    std::map<std::string, int> precedence;
    TreeNode *treeRoot;
    TreeNode *insertNode(TreeNode *leaf);
    void destroyTree(TreeNode *leaf);
public:
    ExpressionTree();
    ~ExpressionTree();
    void destroyTree();
    bool build(std::string formulaStr);
    uint8_t evaluate(uint32_t tVal);
};

TreeExpressions.cpp:

#include "TreeExpressions.h"

void TreeNode::setOp(std::string op)
{
    isVar = false;
    isOp = true;


    if (op == "^")
    {
        opFunc = &xorOp;
    }
    else if (op == "&")
    {
        opFunc = &andOp;
    }
    else if (op == "|")
    {
        opFunc = &orOp;
    }
    else if (op == "<<")
    {
        opFunc = &lshiftOp;
    }
    else if (op == ">>")
    {
        opFunc = &rshiftOp;
    }
    else if (op == "+")
    {
        opFunc = &addOp;
    }
    else if (op == "-")
    {
        opFunc = &subOp;
    }
    else if (op == "*")
    {
        opFunc = &multOp;
    }
    else if (op == "/")
    {
        opFunc = &divOp;
    }
    else if (op == "%")
    {
        opFunc = &modOp;
    }

}

void TreeNode::setVal(uint32_t val)
{
    isVar = false;
    isOp = false;

    value = val;
}

void TreeNode::setVar()
{
    isVar = true;
    isOp = false;
}

uint32_t TreeNode::evaluate(uint32_t tVal)
{
    if (isOp)
    {
        //if it's an op
        return( opFunc( first->evaluate(tVal), last->evaluate(tVal) ) );
    }
    else if (isVar)
    {
        //if it's a var
        return(tVal);
    }
    else
    {
        //if it's a number
        return(value);
    }
}



ExpressionTree::ExpressionTree()
{
    treeRoot = NULL;

    // http://en.cppreference.com/w/cpp/language/operator_precedence
    precedence["*"] = 5;
    precedence["/"] = 5;
    precedence["%"] = 5;
    precedence["+"] = 6;
    precedence["-"] = 6;
    precedence["<<"] = 7;
    precedence[">>"] = 7;
    precedence["&"] = 10;
    precedence["^"] = 11;
    precedence["|"] = 12;
}

ExpressionTree::~ExpressionTree()
{
    destroyTree();
}

void ExpressionTree::destroyTree(TreeNode *leaf)
{
    if (leaf != NULL)
    {
        destroyTree(leaf->first);
        destroyTree(leaf->last);
        delete leaf;
    }
}

TreeNode *ExpressionTree::insertNode(TreeNode *leaf)
{
    if (leaf->first == NULL)
    {
        leaf->first = new TreeNode;
        leaf->first->parent = leaf;
        return(leaf->first);
    }
    else
    {
        leaf->last = new TreeNode;
        leaf->last->parent = leaf;
        return(leaf->last);
    }
}

void ExpressionTree::destroyTree()
{
    destroyTree(treeRoot);
}

bool ExpressionTree::build(std::string formulaStr)
{
    std::string::iterator stringIterator;
    std::vector<std::string>::iterator stringVectorIterator;

    std::vector<std::string> formulaTokens;
    std::vector<std::string> pnTokens;
    std::stack<std::string> stringStack;
    std::string currentNumString = "";
    std::string currentTokenString = "";
    std::stack<TreeNode> nodeStack;
    TreeNode *currentNode;
    std::string currToken;
    bool treeIsDone;

    //tokenization

    for (stringIterator = formulaStr.begin(); stringIterator != formulaStr.end(); stringIterator++)
    {
        std::string currCharString(1, *stringIterator);

        currentTokenString.push_back(*stringIterator);

        if ((precedence.find(currentTokenString) != precedence.end()) || (currentTokenString == "(") || (currentTokenString == ")"))
        {
            //if the current token string is found in the precedence list (or is a parentheses)
            if (currentNumString != "")
            {
                formulaTokens.push_back(currentNumString);
                currentNumString = "";
            }

            formulaTokens.push_back(currentTokenString);
            currentTokenString = "";
        }
        else if (std::all_of(currentTokenString.begin(), currentTokenString.end(), ::isdigit))
        {
            //if the current token string is all digits
            currentNumString.append(currentTokenString);
            currentTokenString = "";
        }
        else if (currentTokenString == "t")
        {
            //if the current token string is the t variable
            formulaTokens.push_back(currentTokenString);
            currentTokenString = "";
        }
    }

    //convert to polish notation

    std::reverse(formulaTokens.begin(), formulaTokens.end());

    stringStack.push(")");

    for (stringVectorIterator = formulaTokens.begin(); stringVectorIterator != formulaTokens.end(); stringVectorIterator++)
    {
        currToken = *stringVectorIterator;
        if ((precedence.find(currToken) == precedence.end()) && (currToken != "(") && (currToken != ")"))
        {
            pnTokens.push_back(currToken);
        }
        else if (currToken == ")")
        {
            stringStack.push(currToken);
        }
        else if (precedence.find(currToken) != precedence.end())
        {
            if (stringStack.size() > 0)
            {
                if (stringStack.top() != ")")
                {
                    while (precedence[stringStack.top()] <= precedence[currToken])
                    {
                        if (stringStack.top() != ")")
                        {
                            pnTokens.push_back(stringStack.top());
                            stringStack.pop();
                        }

                        if (stringStack.size() <= 0)
                        {
                            break;
                        }

                        if (stringStack.top() == ")")
                        {
                            break;
                        }
                    }
                }
            }
            stringStack.push(currToken);
        }
        else if (currToken == "(")
        {
            if (stringStack.size() > 0)
            {
                while (stringStack.top() != ")")
                {
                    pnTokens.push_back(stringStack.top());
                    stringStack.pop();

                    if (stringStack.size() <= 0)
                    {
                        break;
                    }
                }

                stringStack.pop();
            }
        }
    }

    while (stringStack.size() > 0)
    {
        if (stringStack.top() != ")")
        {
            pnTokens.push_back(stringStack.top());
        }
        stringStack.pop();
    }

    std::reverse(pnTokens.begin(), pnTokens.end());

    //if it's gotten this far, the formula was valid
    //destroy the current tree to make room

    destroyTree();

    //parse polish notation into tree
    treeRoot = new TreeNode;
    currentNode = treeRoot;
    treeIsDone = false;

    for (stringVectorIterator = pnTokens.begin(); stringVectorIterator != pnTokens.end(); stringVectorIterator++)
    {
        currToken = *stringVectorIterator;
        if (precedence.find(currToken) != precedence.end())
        {
            //if the token is an operator
            currentNode->setOp(currToken);

            currentNode = insertNode(currentNode);
        }
        else
        {
            //if it's a number or a variable
            if (currentNode->first != NULL)
            {
                //if the current node has it's first branch initialized
                while (currentNode->last != NULL)
                {
                    //while the last branch is initialized
                    currentNode = currentNode->parent;
                }

                currentNode = insertNode(currentNode);
            }

            if (std::all_of(currToken.begin(), currToken.end(), ::isdigit))
            {
                //if it's a number
                currentNode->setVal((uint32_t)atoi(currToken.c_str()));
            }
            else
            {
                //if it's something else, a variable
                currentNode->setVar();
            }

            if (currentNode != treeRoot)
            {
                currentNode = currentNode->parent;
            }

            //since we just moved up, we know at least the first branch is used
            //so only check the last
            while (currentNode->last != NULL)
            {
                //if the last node is not free
                if (currentNode == treeRoot)
                {
                    //if we're at the root, and it's totally populated
                    treeIsDone = true;
                    break;
                }

                currentNode = currentNode->parent;
            }

            if (!treeIsDone)
            {

                currentNode = insertNode(currentNode);
            }
        }
    }
    return(true);
}

uint8_t ExpressionTree::evaluate(uint32_t tVal)
{
    return((uint8_t)treeRoot->evaluate(tVal));
}