2016-07-07 12 views
2

私はC++を使ってVSTプラグインを開発しています。プラグインを使用すると、数式を入力することができます。数式は、音を生成するために1秒あたり44100回実行されます。私はこのようなリアルタイムのものには新しく、ユーザーが入力した式を解釈するだけです。ユーザ定義の式を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("") == 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計算を扱う別の方法がありますか?

さらに、標準的な数式を表すユーザー入力文字列を取得し、その式を1/44100秒未満で完了するのに十分速く実行するためのC++互換メソッドがありますか?

+0

私は本当にあなたが達成しようとしているのか理解していないが、多分それがお手伝いします:なぜあなたは計算しています同じ表現を何回も繰り返します(正確には44100回)。なぜ結果を保存して一度実行しないのですか? – Rakete1111

+0

これはオプションです。 LLVMはある程度あなたを助けてくれるでしょう。確かに、それは最終的にあなたのインタプリタをコンパイラに変え、残りを心配する必要がありますが、パフォーマンスは良くなるので、余分な作業です。私はVSTやサウンドについて何も知らない。しかし、JITingよりも解釈が間違いなくあなたを遅らせるでしょう。 – Taywee

+0

JITをしたくない場合は、RPN式を何らかのASTなどに分解する必要があります。そのため、1秒間に何千回も文字列を比較しません。 – Taywee

答えて

1

これは大きな質問です。

あなたの式をRPNにコンパイルするのは良いスタートです。実際には、あなたのコードは非常に長い場合を除いて、1秒あたり88Kを超えるコードを実行できるはずです。

しかし、あなたは確かにあまりにも多くのトラブルなしではるかに良いことができます。

私はこのようなインターフェイスになるだろう:あなたは、このインタフェースの実装にあなたの表現をコンパイルします

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

を。

あなたは定数の実装持つことができます... t

class RefExpression : public Expression 
{ 
    public: 
    // ... 

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

への参照のための実装を

class ConstExpression : public Expression 
{ 
    private: 
    uint32_t m_constVal; 

    public: 
    // ... 

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

を...と二項演算子

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); 
    } 
}; 
の実装

...多分、あなたは、ハットするのを避けるために、いくつかのテンプレートマジックをしたい非常に多くの演算子クラスをコードします。

式をExpressionにコンパイルした後は、Expression-> eval(t)と同じように評価することができ、すべてのコードは解析、文字列比較、スタック操作などを行うことなく合理的に効率的に実行されます。

0

リアルタイム生成コードはどのようにコンパイルし、バイナリライブラリ関数として使用しますか?私はそれがはるかに速く動作すると確信しています。

0

あなたはそうする必要はありません。 vstを構築する場合は、vstライブラリを使用する必要があります。ホストdawは更新機能を自動的に呼び出します。

0

あなたのアドバイスありがとうございました!私は以来、私は非常に満足している解決策を実装しています。ここの答えのどれもが私にこのアプローチを直接知らせてくれなかったが、彼らは私にインスピレーションを与えた。

私はユーザー入力の中置式をポーランド表記(接頭辞)、次にバイナリツリーに解析することに決めました。私は本質的には二重にリンクされたリストですが、1ではなく2つの子を持つTreeNode構造体を実装しました。各TreeNodeは、変数、固定数、または演算子として設定できます。演算子として設定されている場合は、関数opFuncです。ポインタメンバーは、その演算子を実行する定義済みの関数に設定されています。

各ノードにはevaluate(tVal)メンバー関数があります。固定数の場合、これはその数値を返します。変数の場合は、tValだけを返します。それが演算子の場合、opFunc(first-> evaluate(tVal)、last-> evaluate(tVal))を返します。これは、その下の2つのノードを再帰的に計算してから演算子を実行します。

また、ツリーを管理し、ユーザーの入力をツリーに解析するExpressionTreeクラスも実装しました。ツリーを再帰的に破棄し、ユーザー入力文字列からツリーを構築し、ツリーを評価するためのパブリックメンバーを持っています(ルートノードを評価するだけです)。

これは単純な再帰的なツリーで関数ポインタを使用するため、数式が非常に大きくなってもExpressionTreeを評価するのが非常に高速です。基本的には、関数が評価されているときから、式が入力されたときまで、可能な限り多かれ少なかれ処理を移しました。

以下は、私が表現木のために書いたコードです。非常に限られた演算子しか存在しないことに注意してください。ただし、新しいバイナリ演算子を優先順位マップに追加し、それらのための関数を追加し、setOpにスイッチを含めることで、簡単に追加できます。また、1つの変数のみが許可されています。これは、すべてのアプリケーションで使用されます。

現時点ではエラーチェックも実装されていないため、無効な演算子、空白、またはかっこの不一致により、未定義の動作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)); 
}