274 lines
8.3 KiB
C++
274 lines
8.3 KiB
C++
/**
|
|
* Copyright (c) 2024 Enzien Audio, Ltd.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
* are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions, and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the phrase "powered by heavy",
|
|
* the heavy logo, and a hyperlink to https://enzienaudio.com, all in a visible
|
|
* form.
|
|
*
|
|
* 2.1 If the Application is distributed in a store system (for example,
|
|
* the Apple "App Store" or "Google Play"), the phrase "powered by heavy"
|
|
* shall be included in the app description or the copyright text as well as
|
|
* the in the app itself. The heavy logo will shall be visible in the app
|
|
* itself as well.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#include "Heavy_Adc2Dac.hpp"
|
|
|
|
#include <new>
|
|
|
|
#define Context(_c) static_cast<Heavy_Adc2Dac *>(_c)
|
|
|
|
|
|
/*
|
|
* C Functions
|
|
*/
|
|
|
|
extern "C" {
|
|
HV_EXPORT HeavyContextInterface *hv_Adc2Dac_new(double sampleRate) {
|
|
// allocate aligned memory
|
|
void *ptr = hv_malloc(sizeof(Heavy_Adc2Dac));
|
|
// ensure non-null
|
|
if (!ptr) return nullptr;
|
|
// call constructor
|
|
new(ptr) Heavy_Adc2Dac(sampleRate);
|
|
return Context(ptr);
|
|
}
|
|
|
|
HV_EXPORT HeavyContextInterface *hv_Adc2Dac_new_with_options(double sampleRate,
|
|
int poolKb, int inQueueKb, int outQueueKb) {
|
|
// allocate aligned memory
|
|
void *ptr = hv_malloc(sizeof(Heavy_Adc2Dac));
|
|
// ensure non-null
|
|
if (!ptr) return nullptr;
|
|
// call constructor
|
|
new(ptr) Heavy_Adc2Dac(sampleRate, poolKb, inQueueKb, outQueueKb);
|
|
return Context(ptr);
|
|
}
|
|
|
|
HV_EXPORT void hv_Adc2Dac_free(HeavyContextInterface *instance) {
|
|
// call destructor
|
|
Context(instance)->~Heavy_Adc2Dac();
|
|
// free memory
|
|
hv_free(instance);
|
|
}
|
|
} // extern "C"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Class Functions
|
|
*/
|
|
|
|
Heavy_Adc2Dac::Heavy_Adc2Dac(double sampleRate, int poolKb, int inQueueKb, int outQueueKb)
|
|
: HeavyContext(sampleRate, poolKb, inQueueKb, outQueueKb) {
|
|
|
|
}
|
|
|
|
Heavy_Adc2Dac::~Heavy_Adc2Dac() {
|
|
// nothing to free
|
|
}
|
|
|
|
HvTable *Heavy_Adc2Dac::getTableForHash(hv_uint32_t tableHash) {
|
|
return nullptr;
|
|
}
|
|
|
|
void Heavy_Adc2Dac::scheduleMessageForReceiver(hv_uint32_t receiverHash, HvMessage *m) {
|
|
switch (receiverHash) {
|
|
default: return;
|
|
}
|
|
}
|
|
|
|
int Heavy_Adc2Dac::getParameterInfo(int index, HvParameterInfo *info) {
|
|
if (info != nullptr) {
|
|
switch (index) {
|
|
default: {
|
|
info->name = "invalid parameter index";
|
|
info->hash = 0;
|
|
info->type = HvParameterType::HV_PARAM_TYPE_PARAMETER_IN;
|
|
info->minVal = 0.0f;
|
|
info->maxVal = 0.0f;
|
|
info->defaultVal = 0.0f;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Send Function Implementations
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Context Process Implementation
|
|
*/
|
|
|
|
int Heavy_Adc2Dac::process(float **inputBuffers, float **outputBuffers, int n) {
|
|
while (hLp_hasData(&inQueue)) {
|
|
hv_uint32_t numBytes = 0;
|
|
ReceiverMessagePair *p = reinterpret_cast<ReceiverMessagePair *>(hLp_getReadBuffer(&inQueue, &numBytes));
|
|
hv_assert(numBytes >= sizeof(ReceiverMessagePair));
|
|
scheduleMessageForReceiver(p->receiverHash, &p->msg);
|
|
hLp_consume(&inQueue);
|
|
}
|
|
|
|
sendBangToReceiver(0xDD21C0EB); // send to __hv_bang~ on next cycle
|
|
const int n4 = n & ~HV_N_SIMD_MASK; // ensure that the block size is a multiple of HV_N_SIMD
|
|
|
|
// temporary signal vars
|
|
|
|
// input and output vars
|
|
hv_bufferf_t O0, O1;
|
|
hv_bufferf_t I0, I1;
|
|
|
|
// declare and init the zero buffer
|
|
hv_bufferf_t ZERO; __hv_zero_f(VOf(ZERO));
|
|
|
|
hv_uint32_t nextBlock = blockStartTimestamp;
|
|
for (int n = 0; n < n4; n += HV_N_SIMD) {
|
|
|
|
// process all of the messages for this block
|
|
nextBlock += HV_N_SIMD;
|
|
while (mq_hasMessageBefore(&mq, nextBlock)) {
|
|
MessageNode *const node = mq_peek(&mq);
|
|
node->sendMessage(this, node->let, node->m);
|
|
mq_pop(&mq);
|
|
}
|
|
|
|
// load input buffers
|
|
__hv_load_f(inputBuffers[0]+n, VOf(I0));
|
|
__hv_load_f(inputBuffers[1]+n, VOf(I1));
|
|
|
|
// zero output buffers
|
|
__hv_zero_f(VOf(O0));
|
|
__hv_zero_f(VOf(O1));
|
|
|
|
// process all signal functions
|
|
__hv_add_f(VIf(I0), VIf(O0), VOf(O0));
|
|
__hv_add_f(VIf(I1), VIf(O1), VOf(O1));
|
|
|
|
// save output vars to output buffer
|
|
__hv_store_f(outputBuffers[0]+n, VIf(O0));
|
|
__hv_store_f(outputBuffers[1]+n, VIf(O1));
|
|
}
|
|
|
|
blockStartTimestamp = nextBlock;
|
|
|
|
return n4; // return the number of frames processed
|
|
|
|
}
|
|
|
|
int Heavy_Adc2Dac::processInline(float *inputBuffers, float *outputBuffers, int n4) {
|
|
hv_assert(!(n4 & HV_N_SIMD_MASK)); // ensure that n4 is a multiple of HV_N_SIMD
|
|
|
|
// define the heavy input buffer for 2 channel(s)
|
|
float **const bIn = reinterpret_cast<float **>(hv_alloca(2*sizeof(float *)));
|
|
bIn[0] = inputBuffers+(0*n4);
|
|
bIn[1] = inputBuffers+(1*n4);
|
|
|
|
// define the heavy output buffer for 2 channel(s)
|
|
float **const bOut = reinterpret_cast<float **>(hv_alloca(2*sizeof(float *)));
|
|
bOut[0] = outputBuffers+(0*n4);
|
|
bOut[1] = outputBuffers+(1*n4);
|
|
|
|
int n = process(bIn, bOut, n4);
|
|
return n;
|
|
}
|
|
|
|
int Heavy_Adc2Dac::processInlineInterleaved(float *inputBuffers, float *outputBuffers, int n4) {
|
|
hv_assert(n4 & ~HV_N_SIMD_MASK); // ensure that n4 is a multiple of HV_N_SIMD
|
|
|
|
// define the heavy input buffer for 2 channel(s), uninterleave
|
|
float *const bIn = reinterpret_cast<float *>(hv_alloca(2*n4*sizeof(float)));
|
|
#if HV_SIMD_SSE || HV_SIMD_AVX
|
|
for (int i = 0, j = 0; j < n4; j += 4, i += 8) {
|
|
__m128 a = _mm_load_ps(inputBuffers+i); // LRLR
|
|
__m128 b = _mm_load_ps(inputBuffers+4+i); // LRLR
|
|
__m128 x = _mm_shuffle_ps(a, b, _MM_SHUFFLE(2,0,2,0)); // LLLL
|
|
__m128 y = _mm_shuffle_ps(a, b, _MM_SHUFFLE(3,1,3,1)); // RRRR
|
|
_mm_store_ps(bIn+j, x);
|
|
_mm_store_ps(bIn+n4+j, y);
|
|
}
|
|
#elif HV_SIMD_NEON
|
|
for (int i = 0, j = 0; j < n4; j += 4, i += 8) {
|
|
float32x4x2_t a = vld2q_f32(inputBuffers+i); // load and uninterleave
|
|
vst1q_f32(bIn+j, a.val[0]);
|
|
vst1q_f32(bIn+n4+j, a.val[1]);
|
|
}
|
|
#else // HV_SIMD_NONE
|
|
for (int j = 0; j < n4; ++j) {
|
|
bIn[0*n4+j] = inputBuffers[0+2*j];
|
|
bIn[1*n4+j] = inputBuffers[1+2*j];
|
|
}
|
|
#endif
|
|
|
|
// define the heavy output buffer for 2 channel(s)
|
|
float *const bOut = reinterpret_cast<float *>(hv_alloca(2*n4*sizeof(float)));
|
|
|
|
int n = processInline(bIn, bOut, n4);
|
|
|
|
// interleave the heavy output into the output buffer
|
|
#if HV_SIMD_AVX
|
|
for (int i = 0, j = 0; j < n4; j += 8, i += 16) {
|
|
__m256 x = _mm256_load_ps(bOut+j); // LLLLLLLL
|
|
__m256 y = _mm256_load_ps(bOut+n4+j); // RRRRRRRR
|
|
__m256 a = _mm256_unpacklo_ps(x, y); // LRLRLRLR
|
|
__m256 b = _mm256_unpackhi_ps(x, y); // LRLRLRLR
|
|
_mm256_store_ps(outputBuffers+i, a);
|
|
_mm256_store_ps(outputBuffers+8+i, b);
|
|
}
|
|
#elif HV_SIMD_SSE
|
|
for (int i = 0, j = 0; j < n4; j += 4, i += 8) {
|
|
__m128 x = _mm_load_ps(bOut+j); // LLLL
|
|
__m128 y = _mm_load_ps(bOut+n4+j); // RRRR
|
|
__m128 a = _mm_unpacklo_ps(x, y); // LRLR
|
|
__m128 b = _mm_unpackhi_ps(x, y); // LRLR
|
|
_mm_store_ps(outputBuffers+i, a);
|
|
_mm_store_ps(outputBuffers+4+i, b);
|
|
}
|
|
#elif HV_SIMD_NEON
|
|
// https://community.arm.com/groups/processors/blog/2012/03/13/coding-for-neon--part-5-rearranging-vectors
|
|
for (int i = 0, j = 0; j < n4; j += 4, i += 8) {
|
|
float32x4_t x = vld1q_f32(bOut+j);
|
|
float32x4_t y = vld1q_f32(bOut+n4+j);
|
|
float32x4x2_t z = {x, y};
|
|
vst2q_f32(outputBuffers+i, z); // interleave and store
|
|
}
|
|
#else // HV_SIMD_NONE
|
|
for (int i = 0; i < 2; ++i) {
|
|
for (int j = 0; j < n4; ++j) {
|
|
outputBuffers[i+2*j] = bOut[i*n4+j];
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return n;
|
|
}
|