init
This commit is contained in:
commit
7a7d83dde7
23
CMakeLists.txt
Normal file
23
CMakeLists.txt
Normal file
@ -0,0 +1,23 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(bit_alchemy CXX C)
|
||||
|
||||
include(cmake/retrieve_files.cmake)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_C_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
if (MSVC)
|
||||
add_compile_options(/std:c++17)
|
||||
add_compile_options(/Zc:__cplusplus)
|
||||
endif()
|
||||
|
||||
find_package(JUCE REQUIRED)
|
||||
find_package(xtensor REQUIRED)
|
||||
|
||||
#find_path(CPP_PEGLIB_INCLUDE_DIRS ")peglib.h")
|
||||
|
||||
set(src_files "")
|
||||
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src src_files)
|
||||
add_library(bit_alchemy STATIC ${src_files})
|
||||
target_link_libraries(bit_alchemy PUBLIC xtensor juce::juce_dsp juce::juce_audio_processors juce::juce_gui_basics)
|
||||
target_include_directories(bit_alchemy PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
61
cmake/retrieve_files.cmake
Normal file
61
cmake/retrieve_files.cmake
Normal file
@ -0,0 +1,61 @@
|
||||
|
||||
function(retrieve_files_custom path extension out_files)
|
||||
message(STATUS "Retrieving files in ${path}")
|
||||
set(EXTENSIONS "")
|
||||
foreach(ext ${extension})
|
||||
list(APPEND EXTENSIONS "${path}/*.${ext}")
|
||||
endforeach ()
|
||||
|
||||
# 递归查找文件夹下的 .h .hpp. ini 文件保存到 HEAD_FILES
|
||||
file(GLOB_RECURSE FIND_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS ${EXTENSIONS})
|
||||
# 将 HEDADER_FILES 和 SRC_FILES 保存到 ALL_FILES 变量
|
||||
set(ALL_FILES ${FIND_FILES})
|
||||
|
||||
set(RESULT "")
|
||||
|
||||
# 对 ALL_FILES 变量里面的所有文件分类(保留资源管理器的目录结构)
|
||||
foreach(fileItem ${ALL_FILES})
|
||||
# Get the directory of the source file
|
||||
get_filename_component(PARENT_DIR "${fileItem}" DIRECTORY)
|
||||
|
||||
# 用于检查平台的条件
|
||||
if(PARENT_DIR STREQUAL "windows")
|
||||
if(WIN32)
|
||||
set(RESULT "${RESULT};${fileItem}")
|
||||
else()
|
||||
continue()
|
||||
endif()
|
||||
elseif(PARENT_DIR STREQUAL "linux")
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(RESULT "${RESULT};${fileItem}")
|
||||
else()
|
||||
continue()
|
||||
endif()
|
||||
elseif(PARENT_DIR STREQUAL "mac")
|
||||
if(APPLE)
|
||||
set(RESULT "${RESULT};${fileItem}")
|
||||
else()
|
||||
continue()
|
||||
endif()
|
||||
else()
|
||||
# 如果文件夹名称不是平台,则始终包含
|
||||
set(RESULT "${RESULT};${fileItem}")
|
||||
endif()
|
||||
|
||||
# Remove common directory prefix to make the group
|
||||
string(REPLACE "${path}" "" GROUP "${PARENT_DIR}")
|
||||
# Make sure we are using windows slashes
|
||||
string(REPLACE "/" "\\" GROUP "${GROUP}")
|
||||
# Group into "Source Files" and "Header Files"
|
||||
set(GROUP "${GROUP}")
|
||||
source_group("${GROUP}" FILES "${fileItem}")
|
||||
endforeach()
|
||||
|
||||
set(${out_files} ${RESULT} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(retrieve_files path out_files)
|
||||
set(temp_files "")
|
||||
retrieve_files_custom(${path} "h;hpp;ini;cpp;c;ixx" temp_files)
|
||||
set(${out_files} ${${out_files}} ${temp_files} PARENT_SCOPE)
|
||||
endfunction()
|
517
src/FormulaParser.cpp
Normal file
517
src/FormulaParser.cpp
Normal file
@ -0,0 +1,517 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include <peglib.h>
|
||||
#include <xtensor/xarray.hpp>
|
||||
#include <xtensor/xio.hpp>
|
||||
#include <xtensor/xindex_view.hpp>
|
||||
#include <xtensor/xrandom.hpp>
|
||||
|
||||
#include "FormulaParser.h"
|
||||
|
||||
using namespace fparse;
|
||||
using namespace peg;
|
||||
using namespace std;
|
||||
|
||||
// 语法
|
||||
const char* FormulaParser::grammar = R"(
|
||||
INPUT <- EXPRESSION {no_ast_opt}
|
||||
EXPRESSION <- ATOM (OPERATOR ATOM)* {
|
||||
precedence
|
||||
L ^
|
||||
L & |
|
||||
L + -
|
||||
L * / %
|
||||
L << >>
|
||||
}
|
||||
ATOM <- NUMBER / FUNCCALL / VAR / '(' EXPRESSION ')'
|
||||
FUNCCALL <- FUNCNAME '(' ( EXPRESSION ( ',' EXPRESSION )* )? ')' { no_ast_opt }
|
||||
OPERATOR <- < '+' | '-' | '*' | '/' | '%' | '^' | '&' | '|' | '>>' | '<<' >
|
||||
NUMBER <- < '-'? [0-9]+ >
|
||||
FUNCNAME <- < [a-zA-Z_] [0-9a-zA-Z_]* > & '('
|
||||
VAR <- < [a-zA-Z_] [0-9a-zA-Z_]* > ! '('
|
||||
%whitespace <- [ \t\n\r]* ( ( ('//' [^\n\r]* [\n\r]*) / ('/*' (!'*/' .)* '*/') ) [ \t\n\r]* )*
|
||||
)";
|
||||
|
||||
const EvaluationResult FormulaParser::sine_table({
|
||||
127, 130, 133, 136, 139, 143, 146, 149, 152, 155, 158, 161, 164,
|
||||
167, 170, 173, 176, 179, 182, 184, 187, 190, 193, 195, 198, 200,
|
||||
203, 205, 208, 210, 213, 215, 217, 219, 221, 224, 226, 228, 229,
|
||||
231, 233, 235, 236, 238, 239, 241, 242, 244, 245, 246, 247, 248,
|
||||
249, 250, 251, 251, 252, 253, 253, 254, 254, 254, 254, 254, 255,
|
||||
254, 254, 254, 254, 254, 253, 253, 252, 251, 251, 250, 249, 248,
|
||||
247, 246, 245, 244, 242, 241, 239, 238, 236, 235, 233, 231, 229,
|
||||
228, 226, 224, 221, 219, 217, 215, 213, 210, 208, 205, 203, 200,
|
||||
198, 195, 193, 190, 187, 184, 182, 179, 176, 173, 170, 167, 164,
|
||||
161, 158, 155, 152, 149, 146, 143, 139, 136, 133, 130, 127, 124,
|
||||
121, 118, 115, 111, 108, 105, 102, 99, 96, 93, 90, 87, 84,
|
||||
81, 78, 75, 72, 70, 67, 64, 61, 59, 56, 54, 51, 49,
|
||||
46, 44, 41, 39, 37, 35, 33, 30, 28, 26, 25, 23, 21,
|
||||
19, 18, 16, 15, 13, 12, 10, 9, 8, 7, 6, 5, 4,
|
||||
3, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 6, 7, 8,
|
||||
9, 10, 12, 13, 15, 16, 18, 19, 21, 23, 25, 26, 28,
|
||||
30, 33, 35, 37, 39, 41, 44, 46, 49, 51, 54, 56, 59,
|
||||
61, 64, 67, 70, 72, 75, 78, 81, 84, 87, 90, 93, 96,
|
||||
99, 102, 105, 108, 111, 115, 118, 121, 124 });
|
||||
|
||||
const EvaluationResult FormulaParser::triangle_table({
|
||||
127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151,
|
||||
153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177,
|
||||
179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203,
|
||||
205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229,
|
||||
231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255,
|
||||
253, 251, 249, 247, 245, 243, 241, 239, 237, 235, 233, 231, 229,
|
||||
227, 225, 223, 221, 219, 217, 215, 213, 211, 209, 207, 205, 203,
|
||||
201, 199, 197, 195, 193, 191, 189, 187, 185, 183, 181, 179, 177,
|
||||
175, 173, 171, 169, 167, 165, 163, 161, 159, 157, 155, 153, 151,
|
||||
149, 147, 145, 143, 141, 139, 137, 135, 133, 131, 129, 127, 125,
|
||||
123, 121, 119, 117, 115, 113, 111, 109, 107, 105, 103, 101, 99,
|
||||
97, 95, 93, 91, 89, 87, 85, 83, 81, 79, 77, 75, 73,
|
||||
71, 69, 67, 65, 63, 61, 59, 57, 55, 53, 51, 49, 47,
|
||||
45, 43, 41, 39, 37, 35, 33, 31, 29, 27, 25, 23, 21,
|
||||
19, 17, 15, 13, 11, 9, 7, 5, 3, 1, 0, 1, 3,
|
||||
5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29,
|
||||
31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55,
|
||||
57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81,
|
||||
83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107,
|
||||
109, 111, 113, 115, 117, 119, 121, 123, 125 });
|
||||
|
||||
// 合法变量名及常数化简求值用的暂时变量值
|
||||
unordered_map<string, EvaluationResult> FormulaParser::temp_vars = {
|
||||
{"T", EvaluationResult(0)},
|
||||
{"t", EvaluationResult(0)},
|
||||
{"w", EvaluationResult(0)},
|
||||
{"x", EvaluationResult(0)},
|
||||
{"y", EvaluationResult(0)},
|
||||
{"z", EvaluationResult(0)},
|
||||
//{"env1", EvaluationResult(0)},
|
||||
//{"env2", EvaluationResult(0)},
|
||||
//{"env3", EvaluationResult(0)},
|
||||
//{"env4", EvaluationResult(0)}
|
||||
};
|
||||
|
||||
// 合法的函数名及实现
|
||||
unordered_map<string, FunctionWithBound> FormulaParser::function_dictionary = {
|
||||
{
|
||||
"sin",
|
||||
{ [](const vector<shared_ptr<Expression>>& args, const unordered_map<string, EvaluationResult>& vars, size_t block_size) -> EvaluationResult {
|
||||
return xt::index_view(FormulaParser::sine_table, args[0]->evaluate(vars, block_size) & 255);
|
||||
}, 1 , 1}
|
||||
},
|
||||
{
|
||||
"cos",
|
||||
{ [](const vector<shared_ptr<Expression>>& args, const unordered_map<string, EvaluationResult>& vars, size_t block_size) -> EvaluationResult {
|
||||
return xt::index_view(FormulaParser::sine_table, (args[0]->evaluate(vars, block_size) + 64) & 255);
|
||||
}, 1 , 1}
|
||||
},
|
||||
{
|
||||
"tri",
|
||||
{ [](const vector<shared_ptr<Expression>>& args, const unordered_map<string, EvaluationResult>& vars, size_t block_size) -> EvaluationResult {
|
||||
return xt::index_view(FormulaParser::triangle_table, args[0]->evaluate(vars, block_size) & 255);
|
||||
}, 1 , 1}
|
||||
},
|
||||
{
|
||||
"rand",
|
||||
{ [](const vector<shared_ptr<Expression>>& args, const unordered_map<string, EvaluationResult>& vars, size_t block_size) -> EvaluationResult {
|
||||
return xt::random::randint({block_size}, 0, 255);
|
||||
}, 0 , 0}
|
||||
},
|
||||
{
|
||||
"abs",
|
||||
{ [](const vector<shared_ptr<Expression>>& args, const unordered_map<string, EvaluationResult>& vars, size_t block_size) -> EvaluationResult {
|
||||
return xt::abs(args[0]->evaluate(vars, block_size));
|
||||
}, 1 , 1}
|
||||
},
|
||||
{
|
||||
"srand",
|
||||
{ [](const vector<shared_ptr<Expression>>& args, const unordered_map<string, EvaluationResult>& vars, size_t block_size) -> EvaluationResult {
|
||||
EvaluationResult r = args[0]->evaluate(vars, block_size);
|
||||
r = ((r + 3463) * 2971);
|
||||
r = r ^ (r << 13);
|
||||
r = r ^ (r >> 17);
|
||||
r = r ^ (r << 5);
|
||||
r = ((r + 2551) * 3571);
|
||||
r = r ^ (r << 13);
|
||||
r = r ^ (r >> 17);
|
||||
return r ^ (r << 5);
|
||||
}, 1 , 1}
|
||||
}
|
||||
};
|
||||
|
||||
// 变量类
|
||||
Variable::Variable(const string& name) : name(name) {};
|
||||
|
||||
EvaluationResult Variable::evaluate(const unordered_map<string, EvaluationResult>& vars, size_t block_size) const { // evaluation
|
||||
// vars 应存放 broadcast 后的向量
|
||||
return vars.at(name);
|
||||
}
|
||||
|
||||
|
||||
// 常量类
|
||||
Constant::Constant(int32_t value) : value(value) {};
|
||||
|
||||
EvaluationResult Constant::evaluate(const unordered_map<string, EvaluationResult>&, size_t block_size) const { // evaluation
|
||||
return xt::broadcast(value, { block_size });
|
||||
}
|
||||
|
||||
|
||||
// 二元表达式类
|
||||
CompoundExpression::CompoundExpression(Operation op, shared_ptr<Expression> lhs, shared_ptr<Expression> rhs)
|
||||
: l(lhs), r(rhs), operation(op) {}
|
||||
|
||||
inline EvaluationResult xdiv(EvaluationResult left_value, EvaluationResult right_value) {
|
||||
if (left_value.shape() != right_value.shape())
|
||||
right_value = xt::broadcast(right_value, left_value.shape());
|
||||
auto&& safe_right_value = xt::where(xt::equal(right_value, 0), 1, right_value);
|
||||
return xt::where(xt::equal(right_value, 0), 0, left_value / safe_right_value);
|
||||
}
|
||||
|
||||
inline EvaluationResult xmod(EvaluationResult left_value, EvaluationResult right_value) {
|
||||
if (left_value.shape() != right_value.shape())
|
||||
right_value = xt::broadcast(right_value, left_value.shape());
|
||||
auto&& safe_right_value = xt::where(xt::equal(right_value, 0), 1, right_value);
|
||||
return xt::where(xt::equal(right_value, 0), 0, left_value % safe_right_value);
|
||||
}
|
||||
|
||||
EvaluationResult CompoundExpression::evaluate(const unordered_map<string, EvaluationResult>& vars, size_t block_size) const {
|
||||
EvaluationResult left_value = l->evaluate(vars, block_size); // l operand
|
||||
EvaluationResult right_value = r->evaluate(vars, block_size); // r operand
|
||||
|
||||
switch (operation) {
|
||||
case Operation::ADD: return left_value + right_value;
|
||||
case Operation::SUBTRACT: return left_value - right_value;
|
||||
case Operation::MULTIPLY: return left_value * right_value;
|
||||
case Operation::DIVIDE: return xdiv(left_value, right_value);
|
||||
case Operation::MOD: return xmod(left_value, right_value);
|
||||
case Operation::AND: return left_value & right_value;
|
||||
case Operation::OR: return left_value | right_value;
|
||||
case Operation::XOR: return left_value ^ right_value;
|
||||
case Operation::SHIFT_LEFT: return left_value << (right_value & 15);
|
||||
case Operation::SHIFT_RIGHT: return left_value >> (right_value & 15);
|
||||
default: throw invalid_argument("Invalid operation"); // invalid operation
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 函数表达式类
|
||||
FunctionExpression::FunctionExpression(string function_name, vector<shared_ptr<Expression>> function_args)
|
||||
:name(function_name), function(FormulaParser::function_dictionary.at(function_name)), args(function_args) {
|
||||
}
|
||||
|
||||
EvaluationResult FunctionExpression::evaluate(const unordered_map<string, EvaluationResult>& vars, size_t block_size) const {
|
||||
return function.function(args, vars, block_size);
|
||||
}
|
||||
|
||||
|
||||
// +
|
||||
shared_ptr<Expression> operator+(shared_ptr<Expression> lhs, shared_ptr<Expression> rhs) {
|
||||
// Constant simplify
|
||||
if (lhs->isConstant() && rhs->isConstant()) {
|
||||
auto l = dynamic_pointer_cast<Constant>(lhs);
|
||||
auto r = dynamic_pointer_cast<Constant>(rhs);
|
||||
return make_shared<Constant>(l->value + r->value);
|
||||
} // preprocess 0
|
||||
else if (lhs->isConstant() && dynamic_pointer_cast<Constant>(lhs)->value == 0)
|
||||
return rhs;
|
||||
else if (rhs->isConstant() && dynamic_pointer_cast<Constant>(rhs)->value == 0)
|
||||
return lhs;
|
||||
// build new expression object
|
||||
return make_shared<CompoundExpression>(Operation::ADD, lhs, rhs);
|
||||
}
|
||||
// -
|
||||
shared_ptr<Expression> operator-(shared_ptr<Expression> lhs, shared_ptr<Expression> rhs) {
|
||||
if (lhs->isConstant() && rhs->isConstant()) {
|
||||
auto l = dynamic_pointer_cast<Constant>(lhs);
|
||||
auto r = dynamic_pointer_cast<Constant>(rhs);
|
||||
return make_shared<Constant>(l->value - r->value);
|
||||
}
|
||||
else if (rhs->isConstant() && dynamic_pointer_cast<Constant>(rhs)->value == 0)
|
||||
return lhs;
|
||||
|
||||
return make_shared<CompoundExpression>(Operation::SUBTRACT, lhs, rhs);
|
||||
}
|
||||
// *
|
||||
shared_ptr<Expression> operator*(shared_ptr<Expression> lhs, shared_ptr<Expression> rhs) {
|
||||
if (lhs->isConstant() && rhs->isConstant()) {
|
||||
auto l = dynamic_pointer_cast<Constant>(lhs);
|
||||
auto r = dynamic_pointer_cast<Constant>(rhs);
|
||||
return make_shared<Constant>(l->value * r->value);
|
||||
}
|
||||
else if (lhs->isConstant() && dynamic_pointer_cast<Constant>(lhs)->value == 0)
|
||||
return make_shared<Constant>(0);
|
||||
else if (rhs->isConstant() && dynamic_pointer_cast<Constant>(rhs)->value == 0)
|
||||
return make_shared<Constant>(0);
|
||||
|
||||
return make_shared<CompoundExpression>(Operation::MULTIPLY, lhs, rhs);
|
||||
}
|
||||
// /
|
||||
shared_ptr<Expression> operator/(shared_ptr<Expression> lhs, shared_ptr<Expression> rhs) {
|
||||
if (lhs->isConstant() && rhs->isConstant()) {
|
||||
auto l = dynamic_pointer_cast<Constant>(lhs);
|
||||
auto r = dynamic_pointer_cast<Constant>(rhs);
|
||||
if (r->value == 0)
|
||||
return make_shared<Constant>(0);
|
||||
return make_shared<Constant>(l->value / r->value);
|
||||
}
|
||||
else if (lhs->isConstant() && dynamic_pointer_cast<Constant>(lhs)->value == 0)
|
||||
return make_shared<Constant>(0);
|
||||
else if (rhs->isConstant() && dynamic_pointer_cast<Constant>(rhs)->value == 0)
|
||||
return make_shared<Constant>(0);
|
||||
|
||||
return make_shared<CompoundExpression>(Operation::DIVIDE, lhs, rhs);
|
||||
}
|
||||
// %
|
||||
shared_ptr<Expression> operator%(shared_ptr<Expression> lhs, shared_ptr<Expression> rhs) {
|
||||
if (lhs->isConstant() && rhs->isConstant()) {
|
||||
auto l = dynamic_pointer_cast<Constant>(lhs);
|
||||
auto r = dynamic_pointer_cast<Constant>(rhs);
|
||||
if (r->value == 0)
|
||||
return make_shared<Constant>(0);
|
||||
return make_shared<Constant>(l->value % r->value);
|
||||
}
|
||||
else if (lhs->isConstant() && dynamic_pointer_cast<Constant>(lhs)->value == 0)
|
||||
return make_shared<Constant>(0);
|
||||
else if (rhs->isConstant() && dynamic_pointer_cast<Constant>(rhs)->value == 0)
|
||||
return make_shared<Constant>(0);
|
||||
|
||||
return make_shared<CompoundExpression>(Operation::MOD, lhs, rhs);
|
||||
}
|
||||
// &
|
||||
shared_ptr<Expression> operator&(shared_ptr<Expression> lhs, shared_ptr<Expression> rhs) {
|
||||
if (lhs->isConstant() && rhs->isConstant()) {
|
||||
auto l = dynamic_pointer_cast<Constant>(lhs);
|
||||
auto r = dynamic_pointer_cast<Constant>(rhs);
|
||||
return make_shared<Constant>(l->value & r->value);
|
||||
}
|
||||
else if (lhs->isConstant() && dynamic_pointer_cast<Constant>(lhs)->value == 0)
|
||||
return make_shared<Constant>(0);
|
||||
else if (rhs->isConstant() && dynamic_pointer_cast<Constant>(rhs)->value == 0)
|
||||
return make_shared<Constant>(0);
|
||||
|
||||
return make_shared<CompoundExpression>(Operation::AND, lhs, rhs);
|
||||
}
|
||||
// |
|
||||
shared_ptr<Expression> operator|(shared_ptr<Expression> lhs, shared_ptr<Expression> rhs) {
|
||||
if (lhs->isConstant() && rhs->isConstant()) {
|
||||
auto l = dynamic_pointer_cast<Constant>(lhs);
|
||||
auto r = dynamic_pointer_cast<Constant>(rhs);
|
||||
return make_shared<Constant>(l->value | r->value);
|
||||
}
|
||||
else if (lhs->isConstant() && dynamic_pointer_cast<Constant>(lhs)->value == 0)
|
||||
return rhs;
|
||||
else if (rhs->isConstant() && dynamic_pointer_cast<Constant>(rhs)->value == 0)
|
||||
return lhs;
|
||||
|
||||
return make_shared<CompoundExpression>(Operation::OR, lhs, rhs);
|
||||
}
|
||||
// ^
|
||||
shared_ptr<Expression> operator^(shared_ptr<Expression> lhs, shared_ptr<Expression> rhs) {
|
||||
if (lhs->isConstant() && rhs->isConstant()) {
|
||||
auto l = dynamic_pointer_cast<Constant>(lhs);
|
||||
auto r = dynamic_pointer_cast<Constant>(rhs);
|
||||
return make_shared<Constant>(l->value ^ r->value);
|
||||
}
|
||||
else if (lhs->isConstant() && dynamic_pointer_cast<Constant>(lhs)->value == 0)
|
||||
return rhs;
|
||||
else if (rhs->isConstant() && dynamic_pointer_cast<Constant>(rhs)->value == 0)
|
||||
return lhs;
|
||||
|
||||
return make_shared<CompoundExpression>(Operation::XOR, lhs, rhs);
|
||||
}
|
||||
// <<
|
||||
shared_ptr<Expression> operator<<(shared_ptr<Expression> lhs, shared_ptr<Expression> rhs) {
|
||||
if (lhs->isConstant() && rhs->isConstant()) {
|
||||
auto l = dynamic_pointer_cast<Constant>(lhs);
|
||||
auto r = dynamic_pointer_cast<Constant>(rhs);
|
||||
return make_shared<Constant>(l->value << (r->value & 15));
|
||||
}
|
||||
else if (lhs->isConstant() && dynamic_pointer_cast<Constant>(lhs)->value == 0)
|
||||
return make_shared<Constant>(0);
|
||||
else if (rhs->isConstant() && dynamic_pointer_cast<Constant>(rhs)->value % 16 == 0)
|
||||
return lhs;
|
||||
|
||||
return make_shared<CompoundExpression>(Operation::SHIFT_LEFT, lhs, rhs);
|
||||
}
|
||||
// >>
|
||||
shared_ptr<Expression> operator>>(shared_ptr<Expression> lhs, shared_ptr<Expression> rhs) {
|
||||
if (lhs->isConstant() && rhs->isConstant()) {
|
||||
auto l = dynamic_pointer_cast<Constant>(lhs);
|
||||
auto r = dynamic_pointer_cast<Constant>(rhs);
|
||||
return make_shared<Constant>(l->value >> (r->value & 15));
|
||||
}
|
||||
else if (lhs->isConstant() && dynamic_pointer_cast<Constant>(lhs)->value == 0)
|
||||
return make_shared<Constant>(0);
|
||||
else if (rhs->isConstant() && dynamic_pointer_cast<Constant>(rhs)->value % 16 == 0)
|
||||
return lhs;
|
||||
|
||||
return make_shared<CompoundExpression>(Operation::SHIFT_RIGHT, lhs, rhs);
|
||||
}
|
||||
|
||||
shared_ptr<Expression> castToExpression(const any& value) {
|
||||
if (value.type() == typeid(shared_ptr<Constant>))
|
||||
return any_cast<shared_ptr<Constant>>(value);
|
||||
|
||||
if (value.type() == typeid(shared_ptr<Variable>))
|
||||
return any_cast<shared_ptr<Variable>>(value);
|
||||
|
||||
if (value.type() == typeid(shared_ptr<CompoundExpression>))
|
||||
return any_cast<shared_ptr<CompoundExpression>>(value);
|
||||
|
||||
if (value.type() == typeid(shared_ptr<FunctionExpression>))
|
||||
return any_cast<shared_ptr<FunctionExpression>>(value);
|
||||
|
||||
if (value.type() == typeid(shared_ptr<Expression>))
|
||||
return any_cast<shared_ptr<Expression>>(value);
|
||||
|
||||
cout << value.type().name() << ";" << endl;
|
||||
throw bad_any_cast();
|
||||
}
|
||||
|
||||
|
||||
// 解析器类
|
||||
FormulaParser::FormulaParser() : parser(grammar) {
|
||||
assert(static_cast<bool>(parser) == true); // debug
|
||||
|
||||
// INPUT pattern
|
||||
parser["INPUT"] = [](const SemanticValues& vs) {
|
||||
return castToExpression(vs[0]);
|
||||
};
|
||||
|
||||
// EXPRESSION pattern
|
||||
parser["EXPRESSION"] = [](const SemanticValues& vs) {
|
||||
auto result = castToExpression(vs[0]);
|
||||
if (vs.size() > 1) {
|
||||
auto ope = any_cast<char>(vs[1]);
|
||||
auto expr = castToExpression(vs[2]);
|
||||
switch (ope) {
|
||||
case '+': result = result + expr; break;
|
||||
case '-': result = result - expr; break;
|
||||
case '*': result = result * expr; break;
|
||||
case '/': result = result / expr; break;
|
||||
case '%': result = result % expr; break;
|
||||
case '&': result = result & expr; break;
|
||||
case '|': result = result | expr; break;
|
||||
case '^': result = result ^ expr; break;
|
||||
case '<': result = result << expr; break;
|
||||
case '>': result = result >> expr; break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// FUNCCALL pattern
|
||||
parser["FUNCCALL"] = [](const SemanticValues& vs) -> shared_ptr<Expression> {
|
||||
auto name = any_cast<string>(vs[0]);
|
||||
|
||||
vector<shared_ptr<Expression>> args;
|
||||
|
||||
if (vs.size() > 1) {
|
||||
bool constant_flag = true;
|
||||
|
||||
for (size_t i = 1; i < vs.size(); i++) {
|
||||
shared_ptr<Expression> expr = castToExpression(vs[i]);
|
||||
// 添加参数
|
||||
args.push_back(expr);
|
||||
// 检查函数的所有参数是否均为常数
|
||||
if (!expr->isConstant()) constant_flag = false;
|
||||
}
|
||||
|
||||
// 如果所有参数均为常数
|
||||
if (constant_flag) {
|
||||
std::unordered_map<std::string, EvaluationResult> empty_map; // 空的 unordered_map, 不占用实际空间
|
||||
FunctionType function = FormulaParser::function_dictionary.at(name).function;
|
||||
return make_shared<Constant>(function(args, empty_map, 1)[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return make_shared<FunctionExpression>(name, args);
|
||||
};
|
||||
|
||||
parser["FUNCCALL"].predicate = [](const SemanticValues& vs, const any&, string& msg) {
|
||||
auto name = any_cast<string>(vs[0]);
|
||||
|
||||
// 检查函数的参数量是否合适
|
||||
FunctionWithBound function = FormulaParser::function_dictionary.at(name);
|
||||
|
||||
if ((function.lower_bound >= 0 && vs.size() - 1 < function.lower_bound) || (function.upper_bound >= 0 && vs.size() - 1 > function.upper_bound)) {
|
||||
msg = "The " + name + " function has an inappropriate number of parameters: " + to_string(vs.size() - 1) + ".";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// OPERATOR token
|
||||
parser["OPERATOR"] = [](const SemanticValues& vs) {
|
||||
return vs.token_to_string()[0];
|
||||
};
|
||||
|
||||
// NUMBER token
|
||||
parser["NUMBER"] = [](const SemanticValues& vs) {
|
||||
return make_shared<Constant>(vs.token_to_number<int32_t>());
|
||||
};
|
||||
|
||||
// VAR token
|
||||
parser["VAR"] = [](const SemanticValues& vs) {
|
||||
return make_shared<Variable>(vs.token_to_string());
|
||||
};
|
||||
|
||||
parser["VAR"].predicate = [](const SemanticValues& vs, const any&, string& msg) {
|
||||
// 检查是否存在该名字的函数
|
||||
auto name = any_cast<string>(vs.token_to_string());
|
||||
|
||||
if (FormulaParser::temp_vars.count(name) == 0) {
|
||||
msg = "Unknown variable " + name + ".";
|
||||
return false;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// FUNCNAME token
|
||||
parser["FUNCNAME"] = [](const SemanticValues& vs) {
|
||||
return vs.token_to_string();
|
||||
};
|
||||
|
||||
parser["FUNCNAME"].predicate = [](const SemanticValues& vs, const any&, string& msg) {
|
||||
// 检查是否存在该名字的函数
|
||||
auto name = any_cast<string>(vs.token_to_string());
|
||||
|
||||
if (FormulaParser::function_dictionary.count(name) == 0) {
|
||||
msg = "Unknown function " + name + ".";
|
||||
return false;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
parser.enable_packrat_parsing();
|
||||
};
|
||||
|
||||
ParseResult FormulaParser::parse(string& input) {
|
||||
|
||||
ParseResult result = { false, nullptr, 0, 0, "", "" };
|
||||
|
||||
parser.set_logger([&result](size_t line, size_t col, const string& msg, const string& rule) {
|
||||
result = { false, nullptr, line, col, msg, rule };
|
||||
});
|
||||
|
||||
shared_ptr<Expression> expr;
|
||||
|
||||
try {
|
||||
bool parse_success = parser.parse(input, expr); // logger 在此处被调用
|
||||
if (parse_success)
|
||||
result = { true, expr, 0, 0, "", "" }; // 解析错误不会作为异常被抛出
|
||||
}
|
||||
catch (const std::exception& e) { // 标准异常
|
||||
result = { false, nullptr, 0, 0, e.what(), "" };
|
||||
}
|
||||
catch (...) { // 未知的潜在异常
|
||||
result = { false, nullptr, 0, 0, "Unknown Exception", "" };
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
123
src/FormulaParser.h
Normal file
123
src/FormulaParser.h
Normal file
@ -0,0 +1,123 @@
|
||||
#ifndef PARSER_H
|
||||
#define PARSER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <peglib.h>
|
||||
#include <xtensor/xarray.hpp>
|
||||
|
||||
namespace fparse {
|
||||
// ¶¨Òå²Ù×÷·ûµÄö¾Ù
|
||||
enum class Operation {
|
||||
NONE, // ´ú±íûÓвÙ×÷
|
||||
ADD,
|
||||
SUBTRACT,
|
||||
MULTIPLY,
|
||||
DIVIDE,
|
||||
MOD,
|
||||
AND,
|
||||
OR,
|
||||
XOR,
|
||||
SHIFT_LEFT,
|
||||
SHIFT_RIGHT
|
||||
};
|
||||
|
||||
using EvaluationResult = xt::xarray<int32_t>;
|
||||
|
||||
// ±í´ïʽ»ùÀà
|
||||
class Expression {
|
||||
public:
|
||||
virtual ~Expression() = default;
|
||||
virtual bool isConstant() const = 0; // constant simplify
|
||||
virtual EvaluationResult evaluate(const std::unordered_map<std::string, EvaluationResult>& vars, size_t block_size) const = 0; // evaluation
|
||||
};
|
||||
|
||||
// ÄäÃûº¯ÊýµÄÀàÐÍ
|
||||
using FunctionType = std::function<EvaluationResult(const std::vector<std::shared_ptr<Expression>>&, const std::unordered_map<std::string, EvaluationResult>&, size_t)>;
|
||||
|
||||
// ÄäÃûº¯ÊýµÄº¯Êý²ÎÊý¶¨ÒåÀàÐÍ
|
||||
struct FunctionWithBound {
|
||||
FunctionType function; // º¯Êý±¾Ìå
|
||||
int16_t lower_bound; // ²ÎÊýÁ¿ÉϽç
|
||||
int16_t upper_bound; // ²ÎÊýÁ¿Ï½ç
|
||||
};
|
||||
|
||||
|
||||
// ±äÁ¿Àà
|
||||
class Variable : public Expression {
|
||||
public:
|
||||
std::string name; // Var name
|
||||
|
||||
Variable(const std::string& name);
|
||||
~Variable() override {};
|
||||
bool isConstant() const override { return false; } // constant simplify
|
||||
EvaluationResult evaluate(const std::unordered_map<std::string, EvaluationResult>& vars, size_t block_size) const override; // evaluation
|
||||
};
|
||||
|
||||
// ³£Á¿Àà
|
||||
class Constant : public Expression {
|
||||
public:
|
||||
int32_t value;
|
||||
|
||||
Constant(int32_t value);
|
||||
~Constant() override {}
|
||||
bool isConstant() const override { return true; } // constant simplify
|
||||
EvaluationResult evaluate(const std::unordered_map<std::string, EvaluationResult>&, size_t block_size) const override; // evaluation
|
||||
};
|
||||
|
||||
// ¶þÔª±í´ïʽÀà
|
||||
class CompoundExpression : public Expression {
|
||||
public:
|
||||
std::shared_ptr<Expression> l, r; // operands
|
||||
Operation operation;
|
||||
|
||||
CompoundExpression(Operation op, std::shared_ptr<Expression> lhs, std::shared_ptr<Expression> rhs);
|
||||
~CompoundExpression() override {}
|
||||
bool isConstant() const override { return false; } // constant simplify
|
||||
EvaluationResult evaluate(const std::unordered_map<std::string, EvaluationResult>& vars, size_t block_size) const override; // evaluation
|
||||
};
|
||||
|
||||
// º¯Êý±í´ïʽÀà
|
||||
class FunctionExpression : public Expression {
|
||||
public:
|
||||
std::string name; // function name
|
||||
FunctionWithBound function; // function
|
||||
std::vector<std::shared_ptr<Expression>> args; // function arguments
|
||||
|
||||
FunctionExpression(std::string function_name, std::vector<std::shared_ptr<Expression>> function_args);
|
||||
~FunctionExpression() override {}
|
||||
bool isConstant() const override { return false; } // constant simplify
|
||||
EvaluationResult evaluate(const std::unordered_map<std::string, EvaluationResult>& vars, size_t block_size) const override; // evaluation
|
||||
};
|
||||
|
||||
// ½âÎö½á¹û
|
||||
struct ParseResult {
|
||||
bool success;
|
||||
std::shared_ptr<Expression> expr;
|
||||
size_t line;
|
||||
size_t col;
|
||||
std::string msg;
|
||||
std::string rule;
|
||||
};
|
||||
|
||||
// ½âÎöÆ÷Àà
|
||||
class FormulaParser {
|
||||
public:
|
||||
static const char* grammar;
|
||||
|
||||
static const EvaluationResult sine_table, triangle_table;
|
||||
|
||||
static std::unordered_map<std::string, EvaluationResult> temp_vars;
|
||||
static std::unordered_map<std::string, FunctionWithBound> function_dictionary;
|
||||
|
||||
FormulaParser();
|
||||
ParseResult parse(std::string& input);
|
||||
|
||||
private:
|
||||
peg::parser parser;
|
||||
};
|
||||
};
|
||||
#endif
|
51
src/JuceHeader.h
Normal file
51
src/JuceHeader.h
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
|
||||
IMPORTANT! This file is auto-generated each time you save your
|
||||
project - if you alter its contents, your changes may be overwritten!
|
||||
|
||||
This is the header file that your files should include in order to get all the
|
||||
JUCE library headers. You should avoid including the JUCE headers directly in
|
||||
your own source files, because that wouldn't pick up the correct configuration
|
||||
options for your app.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <juce_audio_basics/juce_audio_basics.h>
|
||||
#include <juce_audio_devices/juce_audio_devices.h>
|
||||
#include <juce_audio_formats/juce_audio_formats.h>
|
||||
#include <juce_audio_plugin_client/juce_audio_plugin_client.h>
|
||||
#include <juce_audio_processors/juce_audio_processors.h>
|
||||
#include <juce_audio_utils/juce_audio_utils.h>
|
||||
#include <juce_core/juce_core.h>
|
||||
#include <juce_data_structures/juce_data_structures.h>
|
||||
#include <juce_dsp/juce_dsp.h>
|
||||
#include <juce_events/juce_events.h>
|
||||
#include <juce_graphics/juce_graphics.h>
|
||||
#include <juce_gui_basics/juce_gui_basics.h>
|
||||
#include <juce_gui_extra/juce_gui_extra.h>
|
||||
#include <juce_midi_ci/juce_midi_ci.h>
|
||||
#include <juce_osc/juce_osc.h>
|
||||
|
||||
|
||||
#if defined (JUCE_PROJUCER_VERSION) && JUCE_PROJUCER_VERSION < JUCE_VERSION
|
||||
/** If you've hit this error then the version of the Projucer that was used to generate this project is
|
||||
older than the version of the JUCE modules being included. To fix this error, re-save your project
|
||||
using the latest version of the Projucer or, if you aren't using the Projucer to manage your project,
|
||||
remove the JUCE_PROJUCER_VERSION define.
|
||||
*/
|
||||
#error "This project was last saved using an outdated version of the Projucer! Re-save this project with the latest version to fix this error."
|
||||
#endif
|
||||
|
||||
|
||||
#if ! JUCE_DONT_DECLARE_PROJECTINFO
|
||||
namespace ProjectInfo
|
||||
{
|
||||
const char* const projectName = "BitAlchemy";
|
||||
const char* const companyName = "";
|
||||
const char* const versionString = "1.0.0";
|
||||
const int versionNumber = 0x10000;
|
||||
}
|
||||
#endif
|
164
src/PluginEditor.cpp
Normal file
164
src/PluginEditor.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file contains the basic framework code for a JUCE plugin editor.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include "PluginProcessor.h"
|
||||
#include "PluginEditor.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
//==============================================================================
|
||||
FormulaEditor::FormulaEditor(FormulaManager& m) : juce::TextEditor("FormulaEditor")
|
||||
{
|
||||
setMultiLine(true, false); // 多行模式,不强制断行
|
||||
setScrollbarsShown(true); // 显示滚动条
|
||||
setTabKeyUsedAsCharacter(true); // 允许键入Tab
|
||||
setReturnKeyStartsNewLine(true); // 允许换行
|
||||
|
||||
manager = &m; // audio processor
|
||||
|
||||
updateText(); // 更新文本内容
|
||||
|
||||
onTextChange = [this, &m]() -> void { // 定义文本内容变化时的操作
|
||||
auto formula_string = getText().toStdString();
|
||||
m.setFormula(formula_string); // 写入 Formula
|
||||
setOutlineColourToYellow();
|
||||
repaint();
|
||||
};
|
||||
|
||||
if (manager->isFormulaParsed()) // 初始化时更新颜色
|
||||
setOutlineColourToGreen();
|
||||
else
|
||||
setOutlineColourToYellow();
|
||||
|
||||
repaint();
|
||||
};
|
||||
|
||||
// 处理特殊按键
|
||||
bool FormulaEditor::keyPressed(const juce::KeyPress& key) {
|
||||
// 按下 shift + enter 的逻辑
|
||||
if (key == juce::KeyPress(juce::KeyPress::returnKey, juce::ModifierKeys::shiftModifier, NULL)) {
|
||||
|
||||
auto result = manager->parse();
|
||||
|
||||
if (result.success) {
|
||||
// parse 成功时的逻辑
|
||||
setOutlineColourToGreen();
|
||||
}
|
||||
else {
|
||||
// parse 失败时的逻辑
|
||||
setOutlineColourToRed();
|
||||
// 显示错误信息
|
||||
// ...
|
||||
}
|
||||
repaint();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 按下其它按键的逻辑
|
||||
return juce::TextEditor::keyPressed(key);
|
||||
};
|
||||
|
||||
// 更新文本
|
||||
inline void FormulaEditor::updateText() {
|
||||
juce::TextEditor::setText(manager->getFormula());
|
||||
};
|
||||
|
||||
// 设置边框颜色
|
||||
inline void FormulaEditor::setOutlineColourToGreen() {
|
||||
setColour(focusedOutlineColourId, juce::Colour(98, 228, 117));
|
||||
setColour(outlineColourId, juce::Colour(31, 183, 53));
|
||||
}
|
||||
|
||||
inline void FormulaEditor::setOutlineColourToRed() {
|
||||
setColour(focusedOutlineColourId, juce::Colour(228, 98, 98));
|
||||
setColour(outlineColourId, juce::Colour(183, 31, 31));
|
||||
}
|
||||
|
||||
inline void FormulaEditor::setOutlineColourToYellow() {
|
||||
setColour(focusedOutlineColourId, juce::Colour(231, 228, 98));
|
||||
setColour(outlineColourId, juce::Colour(187, 183, 31));
|
||||
}
|
||||
|
||||
inline void FormulaEditor::attachToNewFormulaManager(FormulaManager& m) {
|
||||
manager = &m;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
_8BitSynthAudioProcessorEditor::_8BitSynthAudioProcessorEditor(_8BitSynthAudioProcessor& p)
|
||||
: AudioProcessorEditor(&p), audioProcessor(p), formula_editor(p.formula_manager),
|
||||
w_attachment(p.apvts, "w", w_slider),
|
||||
x_attachment(p.apvts, "x", x_slider),
|
||||
y_attachment(p.apvts, "y", y_slider),
|
||||
z_attachment(p.apvts, "z", z_slider)
|
||||
{
|
||||
for (auto slider : getRotarySliders())
|
||||
addAndMakeVisible(slider);
|
||||
addAndMakeVisible(formula_editor);
|
||||
|
||||
setSize (600, 450);
|
||||
}
|
||||
|
||||
|
||||
|
||||
_8BitSynthAudioProcessorEditor::~_8BitSynthAudioProcessorEditor()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void _8BitSynthAudioProcessorEditor::paint (juce::Graphics& g)
|
||||
{
|
||||
// (Our component is opaque, so we must completely fill the background with a solid colour)
|
||||
g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));
|
||||
|
||||
g.setColour (juce::Colours::white);
|
||||
// g.setFont (juce::FontOptions (15.0f));
|
||||
//g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1);
|
||||
}
|
||||
|
||||
juce::Rectangle<int> CenterBox(juce::Rectangle<int>& area, double border_rate) {
|
||||
auto border_width = area.getWidth() * border_rate;
|
||||
auto border_height = area.getHeight() * border_rate;
|
||||
|
||||
area.removeFromTop(border_height);
|
||||
area.removeFromBottom(border_height);
|
||||
area.removeFromLeft(border_width);
|
||||
area.removeFromRight(border_width);
|
||||
|
||||
return area;
|
||||
}
|
||||
|
||||
void _8BitSynthAudioProcessorEditor::resized()
|
||||
{
|
||||
// This is generally where you'll want to lay out the positions of any
|
||||
// subcomponents in your editor..
|
||||
auto bounds = getLocalBounds();
|
||||
|
||||
auto border_width = bounds.getWidth() * 0.05;
|
||||
|
||||
auto formula_editor_area = bounds.removeFromTop(bounds.getHeight() * 0.75);
|
||||
formula_editor_area.removeFromLeft(border_width);
|
||||
formula_editor_area.removeFromRight(border_width);
|
||||
formula_editor.setBounds(formula_editor_area);
|
||||
|
||||
auto rotary_slider_area_width = bounds.getWidth() * 0.25;
|
||||
|
||||
auto area = bounds.removeFromLeft((int)rotary_slider_area_width);
|
||||
w_slider.setBounds(CenterBox(area, 0.2));
|
||||
x_slider.setBounds(CenterBox(area, 0.2));
|
||||
y_slider.setBounds(CenterBox(area, 0.2));
|
||||
z_slider.setBounds(CenterBox(area, 0.2));
|
||||
}
|
||||
|
||||
std::vector<RotarySlider*> _8BitSynthAudioProcessorEditor::getRotarySliders() {
|
||||
return {
|
||||
&w_slider,
|
||||
&x_slider,
|
||||
&y_slider,
|
||||
&z_slider
|
||||
};
|
||||
}
|
77
src/PluginEditor.h
Normal file
77
src/PluginEditor.h
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file contains the basic framework code for a JUCE plugin editor.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
#include "PluginProcessor.h"
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
*/
|
||||
|
||||
class RotarySlider : public juce::Slider {
|
||||
public:
|
||||
RotarySlider() : juce::Slider(
|
||||
juce::Slider::SliderStyle::RotaryHorizontalVerticalDrag,
|
||||
juce::Slider::TextEntryBoxPosition::TextBoxAbove
|
||||
) {};
|
||||
};
|
||||
|
||||
class FormulaEditor : public juce::TextEditor {
|
||||
public:
|
||||
FormulaEditor(FormulaManager& m);
|
||||
|
||||
bool keyPressed(const juce::KeyPress& key); // 处理按键事件
|
||||
|
||||
inline void updateText(); // 从 Formula Manager 获取文本并更新
|
||||
|
||||
inline void attachToNewFormulaManager(FormulaManager& m); // 绑定到新的 FormulaManager 上
|
||||
|
||||
inline void setOutlineColourToGreen(); // 设置边框颜色
|
||||
inline void setOutlineColourToRed();
|
||||
inline void setOutlineColourToYellow();
|
||||
|
||||
private:
|
||||
FormulaManager* manager;
|
||||
};
|
||||
|
||||
|
||||
class _8BitSynthAudioProcessorEditor : public juce::AudioProcessorEditor
|
||||
{
|
||||
public:
|
||||
_8BitSynthAudioProcessorEditor (_8BitSynthAudioProcessor&);
|
||||
~_8BitSynthAudioProcessorEditor() override;
|
||||
|
||||
//==============================================================================
|
||||
void paint (juce::Graphics&) override;
|
||||
void resized() override;
|
||||
|
||||
private:
|
||||
_8BitSynthAudioProcessor& audioProcessor;
|
||||
|
||||
RotarySlider
|
||||
w_slider,
|
||||
x_slider,
|
||||
y_slider,
|
||||
z_slider; // wxyz 旋钮
|
||||
|
||||
FormulaEditor formula_editor; // 框
|
||||
|
||||
juce::AudioProcessorValueTreeState::SliderAttachment
|
||||
w_attachment,
|
||||
x_attachment,
|
||||
y_attachment,
|
||||
z_attachment; // 绑定参数
|
||||
|
||||
std::vector<RotarySlider*> getRotarySliders(); // 便于获取 4 个旋钮的方法
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (_8BitSynthAudioProcessorEditor)
|
||||
};
|
||||
|
||||
|
219
src/PluginProcessor.cpp
Normal file
219
src/PluginProcessor.cpp
Normal file
@ -0,0 +1,219 @@
|
||||
#include "PluginProcessor.h"
|
||||
#include "PluginEditor.h"
|
||||
#include <cmath>
|
||||
|
||||
|
||||
//==============================================================================
|
||||
_8BitSynthAudioProcessor::_8BitSynthAudioProcessor()
|
||||
#ifndef JucePlugin_PreferredChannelConfigurations
|
||||
: AudioProcessor (BusesProperties()
|
||||
#if ! JucePlugin_IsMidiEffect
|
||||
#if ! JucePlugin_IsSynth
|
||||
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
|
||||
#endif
|
||||
.withOutput ("Output", juce::AudioChannelSet::stereo(), true)
|
||||
#endif
|
||||
)
|
||||
#endif
|
||||
, formula_manager(parser) {
|
||||
for (auto i = 0; i < 16; ++i)
|
||||
synth.addVoice(new _8BitSynthVoice(formula_manager, apvts, bpm));
|
||||
|
||||
synth.addSound(new _8BitSynthSound());
|
||||
}
|
||||
|
||||
_8BitSynthAudioProcessor::~_8BitSynthAudioProcessor()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const juce::String _8BitSynthAudioProcessor::getName() const
|
||||
{
|
||||
return "bit_alchemy";
|
||||
}
|
||||
|
||||
bool _8BitSynthAudioProcessor::acceptsMidi() const
|
||||
{
|
||||
#if JucePlugin_WantsMidiInput
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool _8BitSynthAudioProcessor::producesMidi() const
|
||||
{
|
||||
#if JucePlugin_ProducesMidiOutput
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool _8BitSynthAudioProcessor::isMidiEffect() const
|
||||
{
|
||||
#if JucePlugin_IsMidiEffect
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
double _8BitSynthAudioProcessor::getTailLengthSeconds() const
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
int _8BitSynthAudioProcessor::getNumPrograms()
|
||||
{
|
||||
return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs,
|
||||
// so this should be at least 1, even if you're not really implementing programs.
|
||||
}
|
||||
|
||||
int _8BitSynthAudioProcessor::getCurrentProgram()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void _8BitSynthAudioProcessor::setCurrentProgram (int index)
|
||||
{
|
||||
}
|
||||
|
||||
const juce::String _8BitSynthAudioProcessor::getProgramName (int index)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void _8BitSynthAudioProcessor::changeProgramName (int index, const juce::String& newName)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void _8BitSynthAudioProcessor::prepareToPlay (double currentSampleRate, int currentSamplesPerBlock)
|
||||
{
|
||||
uint8_t oversampling_factor = apvts.getRawParameterValue("oversampling_factor")->load();
|
||||
|
||||
synth.setCurrentPlaybackSampleRate(currentSampleRate * oversampling_factor);
|
||||
|
||||
constexpr auto filterType = juce::dsp::Oversampling<float>::filterHalfBandPolyphaseIIR;
|
||||
|
||||
oversampler = std::make_unique<juce::dsp::Oversampling<float>>(2, oversampling_factor, filterType);
|
||||
oversampler->initProcessing(currentSamplesPerBlock);
|
||||
}
|
||||
|
||||
void _8BitSynthAudioProcessor::releaseResources()
|
||||
{
|
||||
// When playback stops, you can use this as an opportunity to free up any
|
||||
// spare memory, etc.
|
||||
}
|
||||
|
||||
#ifndef JucePlugin_PreferredChannelConfigurations
|
||||
bool _8BitSynthAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
|
||||
{
|
||||
#if JucePlugin_IsMidiEffect
|
||||
juce::ignoreUnused (layouts);
|
||||
return true;
|
||||
#else
|
||||
// This is the place where you check if the layout is supported.
|
||||
// In this template code we only support mono or stereo.
|
||||
// Some plugin hosts, such as certain GarageBand versions, will only
|
||||
// load plugins that support stereo bus layouts.
|
||||
if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono()
|
||||
&& layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
|
||||
return false;
|
||||
|
||||
// This checks if the input layout matches the output layout
|
||||
#if ! JucePlugin_IsSynth
|
||||
if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
|
||||
return false;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
void _8BitSynthAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
|
||||
{
|
||||
buffer.clear();
|
||||
|
||||
// 获取播放头
|
||||
auto position = getPlayHead()->getPosition();
|
||||
|
||||
|
||||
// 获取 bpm
|
||||
bpm = -1.;
|
||||
|
||||
if (position.hasValue()) {
|
||||
auto bpm_info = position->getBpm();
|
||||
if (bpm_info.hasValue())
|
||||
if (*bpm_info != 0.)
|
||||
bpm = *bpm_info;
|
||||
};
|
||||
|
||||
|
||||
// 重新设置滤波器
|
||||
auto currentSampleRate = getSampleRate();
|
||||
auto currentSamplesPerBlock = buffer.getNumSamples();
|
||||
|
||||
uint8_t oversampling_factor = apvts.getRawParameterValue("oversampling_factor")->load();
|
||||
|
||||
|
||||
// 过采样
|
||||
juce::dsp::AudioBlock<float> block(buffer);
|
||||
juce::dsp::AudioBlock<float> osBlock = oversampler->processSamplesUp(block);
|
||||
float* p[] = {osBlock.getChannelPointer(0), osBlock.getChannelPointer(1)};
|
||||
juce::AudioBuffer<float> osBuffer(p, 2, static_cast<int> (osBlock.getNumSamples()));
|
||||
synth.setCurrentPlaybackSampleRate(currentSampleRate * oversampling_factor);
|
||||
synth.renderNextBlock(osBuffer, midiMessages, 0, osBlock.getNumSamples());
|
||||
oversampler->processSamplesDown(block);
|
||||
|
||||
osBlock.clear();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool _8BitSynthAudioProcessor::hasEditor() const
|
||||
{
|
||||
return true; // (change this to false if you choose to not supply an editor)
|
||||
}
|
||||
|
||||
juce::AudioProcessorEditor* _8BitSynthAudioProcessor::createEditor()
|
||||
{
|
||||
return new _8BitSynthAudioProcessorEditor (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void _8BitSynthAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
|
||||
{
|
||||
// You should use this method to store your parameters in the memory block.
|
||||
// You could do that either as raw data, or use the XML or ValueTree classes
|
||||
// as intermediaries to make it easy to save and load complex data.
|
||||
}
|
||||
|
||||
void _8BitSynthAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
|
||||
{
|
||||
// You should use this method to restore your parameters from this memory block,
|
||||
// whose contents will have been created by the getStateInformation() call.
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// This creates new instances of the plugin..
|
||||
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
|
||||
{
|
||||
return new _8BitSynthAudioProcessor();
|
||||
}
|
||||
|
||||
juce::AudioProcessorValueTreeState::ParameterLayout _8BitSynthAudioProcessor::createParameterLayout() {
|
||||
|
||||
juce::AudioProcessorValueTreeState::ParameterLayout layout;
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterInt>("w", "w", 0, 255, 0));
|
||||
layout.add(std::make_unique<juce::AudioParameterInt>("x", "x", 0, 255, 0));
|
||||
layout.add(std::make_unique<juce::AudioParameterInt>("y", "y", 0, 255, 0));
|
||||
layout.add(std::make_unique<juce::AudioParameterInt>("z", "z", 0, 255, 0));
|
||||
|
||||
layout.add(std::make_unique<juce::AudioParameterInt>("oversampling_factor", "oversampling_factor", 1, 16, 2));
|
||||
|
||||
return layout;
|
||||
};
|
256
src/PluginProcessor.h
Normal file
256
src/PluginProcessor.h
Normal file
@ -0,0 +1,256 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
#include <xtensor/xarray.hpp>
|
||||
#include <xtensor/xview.hpp>
|
||||
#include <JuceHeader.h>
|
||||
|
||||
#include "FormulaParser.h"
|
||||
|
||||
|
||||
|
||||
//==============================================================================
|
||||
// 管理公式
|
||||
class FormulaManager {
|
||||
private:
|
||||
fparse::FormulaParser* parser; // parser
|
||||
std::string formula; // formula
|
||||
bool parsed; // 当前 formula 是否已被 parse 过
|
||||
std::shared_ptr<fparse::Expression> cur_expr; // 当前使用的 parse 结果
|
||||
std::shared_ptr<fparse::Expression> new_expr; // 即将更新的 parse 结果
|
||||
std::mutex new_expr_mutex; // new_expr 的锁
|
||||
|
||||
inline void update_expr() {
|
||||
if (new_expr == nullptr)
|
||||
return;
|
||||
new_expr_mutex.lock(); // 锁
|
||||
cur_expr = new_expr; // 更新 cur_expr
|
||||
new_expr = nullptr; // 更新 new_expr
|
||||
new_expr_mutex.unlock(); // 解锁
|
||||
}
|
||||
|
||||
public:
|
||||
FormulaManager(fparse::FormulaParser& formula_parser) { // 构造函数
|
||||
parser = &formula_parser;
|
||||
formula = "";
|
||||
parsed = false;
|
||||
cur_expr = nullptr;
|
||||
new_expr = nullptr;
|
||||
};
|
||||
|
||||
inline std::string getFormula() { // 获取当前 formula
|
||||
return formula;
|
||||
};
|
||||
|
||||
inline void setFormula(std::string& formula_string) { // 设置当前 formula
|
||||
formula = formula_string;
|
||||
parsed = false; // 当前 formula 未被 parse
|
||||
};
|
||||
|
||||
inline fparse::ParseResult parse() { // parse 当前 formula
|
||||
fparse::ParseResult result = parser->parse(formula);
|
||||
if (result.success) { // 如果执行成功,则当前 formula 已被 parse,储存 parse 的结果
|
||||
parsed = true;
|
||||
new_expr_mutex.lock(); // 锁
|
||||
new_expr = result.expr; // 更新 new_expr
|
||||
new_expr_mutex.unlock(); // 解锁
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
inline bool isFormulaParsed() { // 返回当前 formula 是否已被 parse 过
|
||||
return parsed;
|
||||
};
|
||||
|
||||
inline bool hasExpr() { // 返回当前 expr 对象是否为空指针
|
||||
return cur_expr != nullptr || new_expr != nullptr;
|
||||
}
|
||||
|
||||
inline fparse::EvaluationResult evaluate(const std::unordered_map<std::string, fparse::EvaluationResult>& vars, size_t block_size) {
|
||||
update_expr();
|
||||
return cur_expr->evaluate(vars, block_size);
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Sound 类
|
||||
class _8BitSynthSound : public juce::SynthesiserSound {
|
||||
public:
|
||||
_8BitSynthSound() {}
|
||||
|
||||
bool appliesToNote(int) override { return true; }
|
||||
bool appliesToChannel(int) override { return true; }
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
// Voice 类
|
||||
class _8BitSynthVoice : public juce::SynthesiserVoice {
|
||||
public:
|
||||
_8BitSynthVoice(FormulaManager& m, juce::AudioProcessorValueTreeState& s, double& b)
|
||||
: manager(m), apvts(s), bpm(b){
|
||||
vars["T"] = fparse::EvaluationResult(0);
|
||||
vars["t"] = fparse::EvaluationResult(0);
|
||||
vars["w"] = fparse::EvaluationResult(0);
|
||||
vars["x"] = fparse::EvaluationResult(0);
|
||||
vars["y"] = fparse::EvaluationResult(0);
|
||||
vars["z"] = fparse::EvaluationResult(0);
|
||||
};
|
||||
|
||||
|
||||
|
||||
bool canPlaySound(juce::SynthesiserSound* sound) override
|
||||
{
|
||||
return dynamic_cast<_8BitSynthSound*> (sound) != nullptr;
|
||||
}
|
||||
|
||||
void startNote(int midiNoteNumber, float velocity,
|
||||
juce::SynthesiserSound*, int /*currentPitchWheelPosition*/) override {
|
||||
frequency = juce::MidiMessage::getMidiNoteInHertz(midiNoteNumber);
|
||||
time = 0.;
|
||||
standard_time = 0.;
|
||||
}
|
||||
|
||||
void stopNote(float /*velocity*/, bool allowTailOff) override
|
||||
{
|
||||
//if (allowTailOff)
|
||||
//{
|
||||
// // 音符结束的逻辑
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
//
|
||||
//}
|
||||
clearCurrentNote();
|
||||
frequency = 0.;
|
||||
time = 0.;
|
||||
standard_time = 0.;
|
||||
}
|
||||
|
||||
void pitchWheelMoved(int) override {};
|
||||
void controllerMoved(int, int) override {};
|
||||
|
||||
// 修改 w x y z 的值
|
||||
inline void setMacro(const std::string macro_name, int value) {
|
||||
vars[macro_name] = { value };
|
||||
};
|
||||
|
||||
void renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int startSample, int numSamples) override {
|
||||
if (!manager.hasExpr()) // Expr 对象为空指针
|
||||
return;
|
||||
if (frequency == 0.) // 音符未按下
|
||||
return;
|
||||
|
||||
double sample_rate = getSampleRate();
|
||||
|
||||
// 设置 t 参数
|
||||
double end_time = time + (numSamples - 1) * 256.0 * frequency / sample_rate;
|
||||
double next_time = end_time + 256.0 * frequency / sample_rate;
|
||||
vars["t"] = xt::cast<int32_t>(xt::linspace<double>(time, end_time, numSamples));
|
||||
|
||||
double block_bpm = bpm;
|
||||
if (block_bpm == -1.) {
|
||||
block_bpm = 150.; // 默认bpm
|
||||
}
|
||||
|
||||
// 设置 T 参数
|
||||
double end_standard_time = standard_time + (numSamples - 1) * 256.0 * block_bpm / (sample_rate * 60.);
|
||||
double next_standard_time = end_standard_time + 256.0 * block_bpm / (sample_rate * 60.);
|
||||
vars["T"] = xt::cast<int32_t>(xt::linspace<double>(standard_time, end_standard_time, numSamples));
|
||||
|
||||
// 设置 w x y z 参数
|
||||
vars["w"][0] = apvts.getRawParameterValue("w")->load();
|
||||
vars["x"][0] = apvts.getRawParameterValue("x")->load();
|
||||
vars["y"][0] = apvts.getRawParameterValue("y")->load();
|
||||
vars["z"][0] = apvts.getRawParameterValue("z")->load();
|
||||
|
||||
// 计算输出
|
||||
xt::xarray<float> result = xt::cast<float>(manager.evaluate(vars, numSamples) & 255) / 510.0f - 0.25f;
|
||||
|
||||
// 将输出写入 buffer
|
||||
for (size_t i = 0; i < numSamples; i++) {
|
||||
for (auto channel = outputBuffer.getNumChannels(); --channel >= 0;)
|
||||
outputBuffer.addSample(channel, startSample + i, result[i]);
|
||||
}
|
||||
|
||||
// 更新时间
|
||||
time = next_time;
|
||||
standard_time = next_standard_time;
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
double frequency = 0.;
|
||||
double time = 0.;
|
||||
double standard_time = 0.;
|
||||
double& bpm;
|
||||
|
||||
FormulaManager& manager;
|
||||
std::unordered_map<std::string, fparse::EvaluationResult> vars;
|
||||
juce::AudioProcessorValueTreeState& apvts;
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
// Audio Processor 类
|
||||
class _8BitSynthAudioProcessor : public juce::AudioProcessor
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
_8BitSynthAudioProcessor();
|
||||
~_8BitSynthAudioProcessor() override;
|
||||
|
||||
//==============================================================================
|
||||
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
|
||||
void releaseResources() override;
|
||||
|
||||
#ifndef JucePlugin_PreferredChannelConfigurations
|
||||
bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
|
||||
#endif
|
||||
|
||||
void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override;
|
||||
|
||||
//==============================================================================
|
||||
juce::AudioProcessorEditor* createEditor() override;
|
||||
bool hasEditor() const override;
|
||||
|
||||
//==============================================================================
|
||||
const juce::String getName() const override;
|
||||
|
||||
bool acceptsMidi() const override;
|
||||
bool producesMidi() const override;
|
||||
bool isMidiEffect() const override;
|
||||
double getTailLengthSeconds() const override;
|
||||
|
||||
//==============================================================================
|
||||
int getNumPrograms() override;
|
||||
int getCurrentProgram() override;
|
||||
void setCurrentProgram (int index) override;
|
||||
const juce::String getProgramName (int index) override;
|
||||
void changeProgramName (int index, const juce::String& newName) override;
|
||||
|
||||
//==============================================================================
|
||||
void getStateInformation (juce::MemoryBlock& destData) override;
|
||||
void setStateInformation (const void* data, int sizeInBytes) override;
|
||||
|
||||
|
||||
static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
|
||||
juce::AudioProcessorValueTreeState apvts{ *this, nullptr, "Parameters", createParameterLayout() };
|
||||
|
||||
//==============================================================================
|
||||
FormulaManager formula_manager; // 公式管理器
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
fparse::FormulaParser parser; // parser
|
||||
juce::Synthesiser synth; // synth
|
||||
double bpm = 0.; // bpm
|
||||
|
||||
std::unique_ptr<juce::dsp::Oversampling<float>> oversampler;
|
||||
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (_8BitSynthAudioProcessor)
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user