2022-02-06 11:08:34 +00:00
|
|
|
//
|
|
|
|
// ChunkList.m
|
|
|
|
// CogAudio Framework
|
|
|
|
//
|
|
|
|
// Created by Christopher Snowhill on 2/5/22.
|
|
|
|
//
|
|
|
|
|
2022-07-14 08:45:49 +00:00
|
|
|
#import <Accelerate/Accelerate.h>
|
|
|
|
|
2022-02-06 11:08:34 +00:00
|
|
|
#import "ChunkList.h"
|
|
|
|
|
2022-07-14 08:45:49 +00:00
|
|
|
#import "hdcd_decode2.h"
|
|
|
|
|
|
|
|
#if !DSD_DECIMATE
|
|
|
|
#import "dsd2float.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
#import "BadSampleCleaner.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if DSD_DECIMATE
|
|
|
|
/**
|
|
|
|
* DSD 2 PCM: Stage 1:
|
|
|
|
* Decimate by factor 8
|
|
|
|
* (one byte (8 samples) -> one float sample)
|
|
|
|
* The bits are processed from least signicifant to most signicicant.
|
|
|
|
* @author Sebastian Gesemann
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define dsd2pcm_FILTER_COEFFS_COUNT 64
|
|
|
|
static const float dsd2pcm_FILTER_COEFFS[64] = {
|
|
|
|
0.09712411121659f, 0.09613438994044f, 0.09417884216316f, 0.09130441727307f,
|
|
|
|
0.08757947648990f, 0.08309142055179f, 0.07794369263673f, 0.07225228745463f,
|
|
|
|
0.06614191680338f, 0.05974199351302f, 0.05318259916599f, 0.04659059631228f,
|
|
|
|
0.04008603356890f, 0.03377897290478f, 0.02776684382775f, 0.02213240062966f,
|
|
|
|
0.01694232798846f, 0.01224650881275f, 0.00807793792573f, 0.00445323755944f,
|
|
|
|
0.00137370697215f, -0.00117318019994f, -0.00321193033831f, -0.00477694265140f,
|
|
|
|
-0.00591028841335f, -0.00665946056286f, -0.00707518873201f, -0.00720940203988f,
|
|
|
|
-0.00711340642819f, -0.00683632603227f, -0.00642384017266f, -0.00591723006715f,
|
|
|
|
-0.00535273320457f, -0.00476118922548f, -0.00416794965654f, -0.00359301524813f,
|
|
|
|
-0.00305135909510f, -0.00255339111833f, -0.00210551956895f, -0.00171076760278f,
|
|
|
|
-0.00136940723130f, -0.00107957856005f, -0.00083786862365f, -0.00063983084245f,
|
|
|
|
-0.00048043272086f, -0.00035442550015f, -0.00025663481039f, -0.00018217573430f,
|
|
|
|
-0.00012659899635f, -0.00008597726991f, -0.00005694188820f, -0.00003668060332f,
|
|
|
|
-0.00002290670286f, -0.00001380895679f, -0.00000799057558f, -0.00000440385083f,
|
|
|
|
-0.00000228567089f, -0.00000109760778f, -0.00000047286430f, -0.00000017129652f,
|
|
|
|
-0.00000004282776f, 0.00000000119422f, 0.00000000949179f, 0.00000000747450f
|
|
|
|
};
|
|
|
|
|
|
|
|
struct dsd2pcm_state {
|
|
|
|
/*
|
|
|
|
* This is the 2nd half of an even order symmetric FIR
|
|
|
|
* lowpass filter (to be used on a signal sampled at 44100*64 Hz)
|
|
|
|
* Passband is 0-24 kHz (ripples +/- 0.025 dB)
|
|
|
|
* Stopband starts at 176.4 kHz (rejection: 170 dB)
|
|
|
|
* The overall gain is 2.0
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* These remain constant for the duration */
|
|
|
|
int FILT_LOOKUP_PARTS;
|
|
|
|
float *FILT_LOOKUP_TABLE;
|
|
|
|
uint8_t *REVERSE_BITS;
|
|
|
|
int FIFO_LENGTH;
|
|
|
|
int FIFO_OFS_MASK;
|
|
|
|
|
|
|
|
/* These are altered */
|
|
|
|
int *fifo;
|
|
|
|
int fpos;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void dsd2pcm_free(void *);
|
|
|
|
static void dsd2pcm_reset(void *);
|
|
|
|
|
|
|
|
static void *dsd2pcm_alloc() {
|
|
|
|
struct dsd2pcm_state *state = (struct dsd2pcm_state *)calloc(1, sizeof(struct dsd2pcm_state));
|
|
|
|
|
|
|
|
float *FILT_LOOKUP_TABLE;
|
|
|
|
double *temp;
|
|
|
|
uint8_t *REVERSE_BITS;
|
|
|
|
|
|
|
|
if(!state)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
state->FILT_LOOKUP_PARTS = (dsd2pcm_FILTER_COEFFS_COUNT + 7) / 8;
|
|
|
|
const int FILT_LOOKUP_PARTS = state->FILT_LOOKUP_PARTS;
|
|
|
|
// The current 128 tap FIR leads to an 8 KB lookup table
|
|
|
|
state->FILT_LOOKUP_TABLE = (float *)calloc(sizeof(float), FILT_LOOKUP_PARTS << 8);
|
|
|
|
if(!state->FILT_LOOKUP_TABLE)
|
|
|
|
goto fail;
|
|
|
|
FILT_LOOKUP_TABLE = state->FILT_LOOKUP_TABLE;
|
|
|
|
temp = (double *)calloc(sizeof(double), 0x100);
|
|
|
|
if(!temp)
|
|
|
|
goto fail;
|
|
|
|
for(int part = 0, sofs = 0, dofs = 0; part < FILT_LOOKUP_PARTS;) {
|
|
|
|
memset(temp, 0, 0x100 * sizeof(double));
|
|
|
|
for(int bit = 0, bitmask = 0x80; bit < 8 && sofs + bit < dsd2pcm_FILTER_COEFFS_COUNT;) {
|
|
|
|
double coeff = dsd2pcm_FILTER_COEFFS[sofs + bit];
|
|
|
|
for(int bite = 0; bite < 0x100; bite++) {
|
|
|
|
if((bite & bitmask) == 0) {
|
|
|
|
temp[bite] -= coeff;
|
|
|
|
} else {
|
|
|
|
temp[bite] += coeff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bit++;
|
|
|
|
bitmask >>= 1;
|
|
|
|
}
|
|
|
|
for(int s = 0; s < 0x100;) {
|
|
|
|
FILT_LOOKUP_TABLE[dofs++] = (float)temp[s++];
|
|
|
|
}
|
|
|
|
part++;
|
|
|
|
sofs += 8;
|
|
|
|
}
|
|
|
|
free(temp);
|
|
|
|
{ // calculate FIFO stuff
|
|
|
|
int k = 1;
|
|
|
|
while(k < FILT_LOOKUP_PARTS * 2) k <<= 1;
|
|
|
|
state->FIFO_LENGTH = k;
|
|
|
|
state->FIFO_OFS_MASK = k - 1;
|
|
|
|
}
|
|
|
|
state->REVERSE_BITS = (uint8_t *)calloc(1, 0x100);
|
|
|
|
if(!state->REVERSE_BITS)
|
|
|
|
goto fail;
|
|
|
|
REVERSE_BITS = state->REVERSE_BITS;
|
|
|
|
for(int i = 0, j = 0; i < 0x100; i++) {
|
|
|
|
REVERSE_BITS[i] = (uint8_t)j;
|
|
|
|
// "reverse-increment" of j
|
|
|
|
for(int bitmask = 0x80;;) {
|
|
|
|
if(((j ^= bitmask) & bitmask) != 0) break;
|
|
|
|
if(bitmask == 1) break;
|
|
|
|
bitmask >>= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
state->fifo = (int *)calloc(sizeof(int), state->FIFO_LENGTH);
|
|
|
|
if(!state->fifo)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
dsd2pcm_reset(state);
|
|
|
|
|
|
|
|
return (void *)state;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
dsd2pcm_free(state);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *dsd2pcm_dup(void *_state) {
|
|
|
|
struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
|
|
|
|
if(state) {
|
|
|
|
struct dsd2pcm_state *newstate = (struct dsd2pcm_state *)calloc(1, sizeof(struct dsd2pcm_state));
|
|
|
|
if(newstate) {
|
|
|
|
newstate->FILT_LOOKUP_PARTS = state->FILT_LOOKUP_PARTS;
|
|
|
|
newstate->FIFO_LENGTH = state->FIFO_LENGTH;
|
|
|
|
newstate->FIFO_OFS_MASK = state->FIFO_OFS_MASK;
|
|
|
|
newstate->fpos = state->fpos;
|
|
|
|
|
|
|
|
newstate->FILT_LOOKUP_TABLE = (float *)calloc(sizeof(float), state->FILT_LOOKUP_PARTS << 8);
|
|
|
|
if(!newstate->FILT_LOOKUP_TABLE)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
memcpy(newstate->FILT_LOOKUP_TABLE, state->FILT_LOOKUP_TABLE, sizeof(float) * (state->FILT_LOOKUP_PARTS << 8));
|
|
|
|
|
|
|
|
newstate->REVERSE_BITS = (uint8_t *)calloc(1, 0x100);
|
|
|
|
if(!newstate->REVERSE_BITS)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
memcpy(newstate->REVERSE_BITS, state->REVERSE_BITS, 0x100);
|
|
|
|
|
|
|
|
newstate->fifo = (int *)calloc(sizeof(int), state->FIFO_LENGTH);
|
|
|
|
if(!newstate->fifo)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
memcpy(newstate->fifo, state->fifo, sizeof(int) * state->FIFO_LENGTH);
|
|
|
|
|
|
|
|
return (void *)newstate;
|
|
|
|
}
|
|
|
|
|
|
|
|
fail:
|
|
|
|
dsd2pcm_free(newstate);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dsd2pcm_free(void *_state) {
|
|
|
|
struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
|
|
|
|
if(state) {
|
|
|
|
free(state->fifo);
|
|
|
|
free(state->REVERSE_BITS);
|
|
|
|
free(state->FILT_LOOKUP_TABLE);
|
|
|
|
free(state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dsd2pcm_reset(void *_state) {
|
|
|
|
struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
|
|
|
|
const int FILT_LOOKUP_PARTS = state->FILT_LOOKUP_PARTS;
|
|
|
|
int *fifo = state->fifo;
|
|
|
|
for(int i = 0; i < FILT_LOOKUP_PARTS; i++) {
|
|
|
|
fifo[i] = 0x55;
|
|
|
|
fifo[i + FILT_LOOKUP_PARTS] = 0xAA;
|
|
|
|
}
|
|
|
|
state->fpos = FILT_LOOKUP_PARTS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dsd2pcm_latency(void *_state) {
|
|
|
|
struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
|
|
|
|
if(state)
|
|
|
|
return state->FIFO_LENGTH;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dsd2pcm_process(void *_state, const uint8_t *src, size_t sofs, size_t sinc, float *dest, size_t dofs, size_t dinc, size_t len) {
|
|
|
|
struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
|
|
|
|
int bite1, bite2, temp;
|
|
|
|
float sample;
|
|
|
|
int *fifo = state->fifo;
|
|
|
|
const uint8_t *REVERSE_BITS = state->REVERSE_BITS;
|
|
|
|
const float *FILT_LOOKUP_TABLE = state->FILT_LOOKUP_TABLE;
|
|
|
|
const int FILT_LOOKUP_PARTS = state->FILT_LOOKUP_PARTS;
|
|
|
|
const int FIFO_OFS_MASK = state->FIFO_OFS_MASK;
|
|
|
|
int fpos = state->fpos;
|
|
|
|
while(len > 0) {
|
|
|
|
fifo[fpos] = REVERSE_BITS[fifo[fpos]] & 0xFF;
|
|
|
|
fifo[(fpos + FILT_LOOKUP_PARTS) & FIFO_OFS_MASK] = src[sofs] & 0xFF;
|
|
|
|
sofs += sinc;
|
|
|
|
temp = (fpos + 1) & FIFO_OFS_MASK;
|
|
|
|
sample = 0;
|
|
|
|
for(int k = 0, lofs = 0; k < FILT_LOOKUP_PARTS;) {
|
|
|
|
bite1 = fifo[(fpos - k) & FIFO_OFS_MASK];
|
|
|
|
bite2 = fifo[(temp + k) & FIFO_OFS_MASK];
|
|
|
|
sample += FILT_LOOKUP_TABLE[lofs + bite1] + FILT_LOOKUP_TABLE[lofs + bite2];
|
|
|
|
k++;
|
|
|
|
lofs += 0x100;
|
|
|
|
}
|
|
|
|
fpos = temp;
|
|
|
|
dest[dofs] = sample;
|
|
|
|
dofs += dinc;
|
|
|
|
len--;
|
|
|
|
}
|
|
|
|
state->fpos = fpos;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void convert_dsd_to_f32(float *output, const uint8_t *input, size_t count, size_t channels, void **dsd2pcm) {
|
|
|
|
for(size_t channel = 0; channel < channels; ++channel) {
|
|
|
|
dsd2pcm_process(dsd2pcm[channel], input, channel, channels, output, channel, channels, count);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static void convert_dsd_to_f32(float *output, const uint8_t *input, size_t count, size_t channels) {
|
|
|
|
const uint8_t *iptr = input;
|
|
|
|
float *optr = output;
|
|
|
|
for(size_t index = 0; index < count; ++index) {
|
|
|
|
for(size_t channel = 0; channel < channels; ++channel) {
|
|
|
|
uint8_t sample = *iptr++;
|
|
|
|
cblas_scopy(8, &dsd2float[sample][0], 1, optr++, (int)channels);
|
|
|
|
}
|
|
|
|
optr += channels * 7;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static void convert_u8_to_s16(int16_t *output, const uint8_t *input, size_t count) {
|
|
|
|
for(size_t i = 0; i < count; ++i) {
|
|
|
|
uint16_t sample = (input[i] << 8) | input[i];
|
|
|
|
sample ^= 0x8080;
|
|
|
|
output[i] = (int16_t)(sample);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void convert_s8_to_s16(int16_t *output, const uint8_t *input, size_t count) {
|
|
|
|
for(size_t i = 0; i < count; ++i) {
|
|
|
|
uint16_t sample = (input[i] << 8) | input[i];
|
|
|
|
output[i] = (int16_t)(sample);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void convert_u16_to_s16(int16_t *buffer, size_t count) {
|
|
|
|
for(size_t i = 0; i < count; ++i) {
|
|
|
|
buffer[i] ^= 0x8000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void convert_s16_to_hdcd_input(int32_t *output, const int16_t *input, size_t count) {
|
|
|
|
for(size_t i = 0; i < count; ++i) {
|
|
|
|
output[i] = input[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void convert_s24_to_s32(int32_t *output, const uint8_t *input, size_t count) {
|
|
|
|
for(size_t i = 0; i < count; ++i) {
|
|
|
|
int32_t sample = (input[i * 3] << 8) | (input[i * 3 + 1] << 16) | (input[i * 3 + 2] << 24);
|
|
|
|
output[i] = sample;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void convert_u24_to_s32(int32_t *output, const uint8_t *input, size_t count) {
|
|
|
|
for(size_t i = 0; i < count; ++i) {
|
|
|
|
int32_t sample = (input[i * 3] << 8) | (input[i * 3 + 1] << 16) | (input[i * 3 + 2] << 24);
|
|
|
|
output[i] = sample ^ 0x80000000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void convert_u32_to_s32(int32_t *buffer, size_t count) {
|
|
|
|
for(size_t i = 0; i < count; ++i) {
|
|
|
|
buffer[i] ^= 0x80000000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void convert_f64_to_f32(float *output, const double *input, size_t count) {
|
|
|
|
vDSP_vdpsp(input, 1, output, 1, count);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void convert_be_to_le(uint8_t *buffer, size_t bitsPerSample, size_t bytes) {
|
|
|
|
size_t i;
|
|
|
|
bitsPerSample = (bitsPerSample + 7) / 8;
|
|
|
|
switch(bitsPerSample) {
|
|
|
|
case 2:
|
|
|
|
for(i = 0; i < bytes; i += 2) {
|
|
|
|
*(int16_t *)buffer = __builtin_bswap16(*(int16_t *)buffer);
|
|
|
|
buffer += 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3: {
|
|
|
|
union {
|
|
|
|
vDSP_int24 int24;
|
|
|
|
uint32_t int32;
|
|
|
|
} intval;
|
|
|
|
intval.int32 = 0;
|
|
|
|
for(i = 0; i < bytes; i += 3) {
|
|
|
|
intval.int24 = *(vDSP_int24 *)buffer;
|
|
|
|
intval.int32 = __builtin_bswap32(intval.int32 << 8);
|
|
|
|
*(vDSP_int24 *)buffer = intval.int24;
|
|
|
|
buffer += 3;
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 4:
|
|
|
|
for(i = 0; i < bytes; i += 4) {
|
|
|
|
*(uint32_t *)buffer = __builtin_bswap32(*(uint32_t *)buffer);
|
|
|
|
buffer += 4;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 8:
|
|
|
|
for(i = 0; i < bytes; i += 8) {
|
|
|
|
*(uint64_t *)buffer = __builtin_bswap64(*(uint64_t *)buffer);
|
|
|
|
buffer += 8;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-06 11:08:34 +00:00
|
|
|
@implementation ChunkList
|
|
|
|
|
|
|
|
@synthesize listDuration;
|
|
|
|
@synthesize maxDuration;
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (id)initWithMaximumDuration:(double)duration {
|
|
|
|
self = [super init];
|
|
|
|
|
|
|
|
if(self) {
|
|
|
|
chunkList = [[NSMutableArray alloc] init];
|
|
|
|
listDuration = 0.0;
|
|
|
|
maxDuration = duration;
|
|
|
|
|
|
|
|
inAdder = NO;
|
|
|
|
inRemover = NO;
|
2022-02-08 03:18:45 +00:00
|
|
|
inPeeker = NO;
|
2022-02-07 05:49:27 +00:00
|
|
|
stopping = NO;
|
2022-07-14 08:45:49 +00:00
|
|
|
|
|
|
|
formatRead = NO;
|
|
|
|
|
|
|
|
inputBuffer = NULL;
|
|
|
|
inputBufferSize = 0;
|
|
|
|
|
|
|
|
#if DSD_DECIMATE
|
|
|
|
dsd2pcm = NULL;
|
|
|
|
dsd2pcmCount = 0;
|
|
|
|
dsd2pcmLatency = 0;
|
|
|
|
#endif
|
2022-02-07 05:49:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
2022-02-06 11:08:34 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)dealloc {
|
|
|
|
stopping = YES;
|
2022-02-08 03:18:45 +00:00
|
|
|
while(inAdder || inRemover || inPeeker) {
|
2022-02-07 05:49:27 +00:00
|
|
|
usleep(500);
|
|
|
|
}
|
2022-07-14 08:45:49 +00:00
|
|
|
if(hdcd_decoder) {
|
|
|
|
free(hdcd_decoder);
|
|
|
|
hdcd_decoder = NULL;
|
|
|
|
}
|
|
|
|
#if DSD_DECIMATE
|
|
|
|
if(dsd2pcm && dsd2pcmCount) {
|
|
|
|
for(size_t i = 0; i < dsd2pcmCount; ++i) {
|
|
|
|
dsd2pcm_free(dsd2pcm[i]);
|
|
|
|
dsd2pcm[i] = NULL;
|
|
|
|
}
|
|
|
|
free(dsd2pcm);
|
|
|
|
dsd2pcm = NULL;
|
|
|
|
}
|
|
|
|
#endif
|
2022-07-15 00:28:24 +00:00
|
|
|
if(tempData) {
|
|
|
|
free(tempData);
|
|
|
|
}
|
2022-02-06 11:08:34 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)reset {
|
|
|
|
@synchronized(chunkList) {
|
|
|
|
[chunkList removeAllObjects];
|
|
|
|
listDuration = 0.0;
|
|
|
|
}
|
2022-02-06 11:08:34 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (BOOL)isEmpty {
|
|
|
|
@synchronized(chunkList) {
|
|
|
|
return [chunkList count] == 0;
|
|
|
|
}
|
2022-02-06 11:08:34 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (BOOL)isFull {
|
2022-02-08 06:02:17 +00:00
|
|
|
return (maxDuration - listDuration) < 0.05;
|
2022-02-06 11:08:34 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)addChunk:(AudioChunk *)chunk {
|
|
|
|
if(stopping) return;
|
|
|
|
|
|
|
|
inAdder = YES;
|
|
|
|
|
|
|
|
const double chunkDuration = [chunk duration];
|
|
|
|
|
|
|
|
@synchronized(chunkList) {
|
|
|
|
[chunkList addObject:chunk];
|
|
|
|
listDuration += chunkDuration;
|
|
|
|
}
|
|
|
|
|
|
|
|
inAdder = NO;
|
2022-02-06 11:08:34 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (AudioChunk *)removeSamples:(size_t)maxFrameCount {
|
|
|
|
if(stopping) {
|
|
|
|
return [[AudioChunk alloc] init];
|
|
|
|
}
|
|
|
|
|
|
|
|
@synchronized(chunkList) {
|
|
|
|
inRemover = YES;
|
2022-02-07 23:31:57 +00:00
|
|
|
if(![chunkList count]) {
|
|
|
|
inRemover = NO;
|
2022-02-07 05:49:27 +00:00
|
|
|
return [[AudioChunk alloc] init];
|
2022-02-07 23:31:57 +00:00
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
AudioChunk *chunk = [chunkList objectAtIndex:0];
|
|
|
|
if([chunk frameCount] <= maxFrameCount) {
|
|
|
|
[chunkList removeObjectAtIndex:0];
|
|
|
|
listDuration -= [chunk duration];
|
|
|
|
inRemover = NO;
|
|
|
|
return chunk;
|
|
|
|
}
|
|
|
|
NSData *removedData = [chunk removeSamples:maxFrameCount];
|
|
|
|
AudioChunk *ret = [[AudioChunk alloc] init];
|
|
|
|
[ret setFormat:[chunk format]];
|
2022-02-07 08:56:05 +00:00
|
|
|
[ret setChannelConfig:[chunk channelConfig]];
|
2022-07-11 21:37:10 +00:00
|
|
|
[ret assignData:removedData];
|
2022-02-07 05:49:27 +00:00
|
|
|
listDuration -= [ret duration];
|
|
|
|
inRemover = NO;
|
|
|
|
return ret;
|
|
|
|
}
|
2022-02-06 11:08:34 +00:00
|
|
|
}
|
|
|
|
|
2022-07-14 08:45:49 +00:00
|
|
|
- (AudioChunk *)removeSamplesAsFloat32:(size_t)maxFrameCount {
|
|
|
|
if(stopping) {
|
|
|
|
return [[AudioChunk alloc] init];
|
|
|
|
}
|
|
|
|
|
|
|
|
@synchronized (chunkList) {
|
|
|
|
inRemover = YES;
|
|
|
|
if(![chunkList count]) {
|
|
|
|
inRemover = NO;
|
|
|
|
return [[AudioChunk alloc] init];
|
|
|
|
}
|
|
|
|
AudioChunk *chunk = [chunkList objectAtIndex:0];
|
2022-07-20 06:04:25 +00:00
|
|
|
#if !DSD_DECIMATE
|
|
|
|
AudioStreamBasicDescription asbd = [chunk format];
|
|
|
|
if(asbd.mBitsPerChannel == 1) {
|
|
|
|
maxFrameCount /= 8;
|
|
|
|
}
|
|
|
|
#endif
|
2022-07-14 08:45:49 +00:00
|
|
|
if([chunk frameCount] <= maxFrameCount) {
|
|
|
|
[chunkList removeObjectAtIndex:0];
|
|
|
|
listDuration -= [chunk duration];
|
|
|
|
inRemover = NO;
|
|
|
|
return [self convertChunk:chunk];
|
|
|
|
}
|
|
|
|
NSData *removedData = [chunk removeSamples:maxFrameCount];
|
|
|
|
AudioChunk *ret = [[AudioChunk alloc] init];
|
|
|
|
[ret setFormat:[chunk format]];
|
|
|
|
[ret setChannelConfig:[chunk channelConfig]];
|
2022-07-14 09:38:04 +00:00
|
|
|
[ret assignData:removedData];
|
2022-07-14 08:45:49 +00:00
|
|
|
listDuration -= [ret duration];
|
|
|
|
inRemover = NO;
|
|
|
|
return [self convertChunk:ret];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (AudioChunk *)convertChunk:(AudioChunk *)inChunk {
|
|
|
|
AudioStreamBasicDescription chunkFormat = [inChunk format];
|
|
|
|
uint32_t chunkConfig = [inChunk channelConfig];
|
|
|
|
BOOL chunkLossless = [inChunk lossless];
|
2022-07-20 06:05:40 +00:00
|
|
|
if(!formatRead || memcmp(&chunkFormat, &inputFormat, sizeof(chunkFormat)) != 0 ||
|
2022-07-14 08:45:49 +00:00
|
|
|
chunkConfig != inputChannelConfig || chunkLossless != inputLossless) {
|
|
|
|
formatRead = YES;
|
|
|
|
inputFormat = chunkFormat;
|
|
|
|
inputChannelConfig = chunkConfig;
|
|
|
|
inputLossless = chunkLossless;
|
|
|
|
|
|
|
|
BOOL isFloat = !!(inputFormat.mFormatFlags & kAudioFormatFlagIsFloat);
|
|
|
|
if((!isFloat && !(inputFormat.mBitsPerChannel >= 1 && inputFormat.mBitsPerChannel <= 32)) || (isFloat && !(inputFormat.mBitsPerChannel == 32 || inputFormat.mBitsPerChannel == 64)))
|
|
|
|
return [[AudioChunk alloc] init];
|
|
|
|
|
|
|
|
// These are really placeholders, as we're doing everything internally now
|
|
|
|
if(inputLossless &&
|
|
|
|
inputFormat.mBitsPerChannel == 16 &&
|
|
|
|
inputFormat.mChannelsPerFrame == 2 &&
|
|
|
|
inputFormat.mSampleRate == 44100) {
|
|
|
|
// possibly HDCD, run through decoder
|
|
|
|
if(hdcd_decoder) {
|
|
|
|
free(hdcd_decoder);
|
|
|
|
hdcd_decoder = NULL;
|
|
|
|
}
|
|
|
|
hdcd_decoder = calloc(1, sizeof(hdcd_state_stereo_t));
|
|
|
|
hdcd_reset_stereo((hdcd_state_stereo_t *)hdcd_decoder, 44100);
|
|
|
|
}
|
|
|
|
|
|
|
|
floatFormat = inputFormat;
|
|
|
|
floatFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
|
|
|
|
floatFormat.mBitsPerChannel = 32;
|
|
|
|
floatFormat.mBytesPerFrame = (32 / 8) * floatFormat.mChannelsPerFrame;
|
|
|
|
floatFormat.mBytesPerPacket = floatFormat.mBytesPerFrame * floatFormat.mFramesPerPacket;
|
|
|
|
|
|
|
|
#if DSD_DECIMATE
|
|
|
|
if(inputFormat.mBitsPerChannel == 1) {
|
|
|
|
// Decimate this for speed
|
|
|
|
floatFormat.mSampleRate *= 1.0 / 8.0;
|
|
|
|
if(dsd2pcm && dsd2pcmCount) {
|
|
|
|
for(size_t i = 0; i < dsd2pcmCount; ++i) {
|
|
|
|
dsd2pcm_free(dsd2pcm[i]);
|
|
|
|
dsd2pcm[i] = NULL;
|
|
|
|
}
|
|
|
|
free(dsd2pcm);
|
|
|
|
dsd2pcm = NULL;
|
|
|
|
}
|
|
|
|
dsd2pcmCount = floatFormat.mChannelsPerFrame;
|
|
|
|
dsd2pcm = (void **)calloc(dsd2pcmCount, sizeof(void *));
|
|
|
|
dsd2pcm[0] = dsd2pcm_alloc();
|
|
|
|
dsd2pcmLatency = dsd2pcm_latency(dsd2pcm[0]);
|
|
|
|
for(size_t i = 1; i < dsd2pcmCount; ++i) {
|
|
|
|
dsd2pcm[i] = dsd2pcm_dup(dsd2pcm[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
NSUInteger samplesRead = [inChunk frameCount];
|
|
|
|
|
|
|
|
if(!samplesRead) {
|
|
|
|
return [[AudioChunk alloc] init];
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOL isFloat = !!(inputFormat.mFormatFlags & kAudioFormatFlagIsFloat);
|
|
|
|
BOOL isUnsigned = !isFloat && !(inputFormat.mFormatFlags & kAudioFormatFlagIsSignedInteger);
|
|
|
|
size_t bitsPerSample = inputFormat.mBitsPerChannel;
|
|
|
|
BOOL isBigEndian = !!(inputFormat.mFormatFlags & kAudioFormatFlagIsBigEndian);
|
|
|
|
|
|
|
|
NSData *inputData = [inChunk removeSamples:samplesRead];
|
|
|
|
|
|
|
|
#if DSD_DECIMATE
|
2022-07-15 00:28:24 +00:00
|
|
|
const size_t sizeFactor = 3;
|
2022-07-14 08:45:49 +00:00
|
|
|
#else
|
2022-07-15 00:28:24 +00:00
|
|
|
const size_t sizeFactor = (bitsPerSample == 1) ? 9 : 3;
|
2022-07-14 08:45:49 +00:00
|
|
|
#endif
|
2022-07-15 00:28:24 +00:00
|
|
|
size_t newSize = samplesRead * floatFormat.mBytesPerPacket * sizeFactor + 64;
|
|
|
|
if(!tempData || tempDataSize < newSize)
|
|
|
|
tempData = realloc(tempData, tempDataSize = newSize); // Either two buffers plus padding, and/or double precision in case of endian flip
|
|
|
|
|
2022-07-14 08:45:49 +00:00
|
|
|
// double buffer system, with alignment
|
|
|
|
const size_t buffer_adder_base = (samplesRead * floatFormat.mBytesPerPacket + 31) & ~31;
|
2022-07-15 00:28:24 +00:00
|
|
|
|
2022-07-14 08:45:49 +00:00
|
|
|
NSUInteger bytesReadFromInput = samplesRead * inputFormat.mBytesPerPacket;
|
2022-07-15 00:28:24 +00:00
|
|
|
|
2022-07-14 08:45:49 +00:00
|
|
|
uint8_t *inputBuffer = (uint8_t *)[inputData bytes];
|
|
|
|
BOOL inputChanged = NO;
|
2022-07-15 00:28:24 +00:00
|
|
|
|
2022-07-14 08:45:49 +00:00
|
|
|
BOOL hdcdSustained = NO;
|
2022-07-15 00:28:24 +00:00
|
|
|
|
2022-07-14 08:45:49 +00:00
|
|
|
if(bytesReadFromInput && isBigEndian) {
|
|
|
|
// Time for endian swap!
|
|
|
|
memcpy(&tempData[0], [inputData bytes], bytesReadFromInput);
|
|
|
|
convert_be_to_le((uint8_t *)(&tempData[0]), inputFormat.mBitsPerChannel, bytesReadFromInput);
|
|
|
|
inputBuffer = &tempData[0];
|
|
|
|
inputChanged = YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(bytesReadFromInput && isFloat && bitsPerSample == 64) {
|
|
|
|
// Time for precision loss from weird inputs
|
2022-07-15 00:28:24 +00:00
|
|
|
const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base * 2 : 0;
|
2022-07-14 08:45:49 +00:00
|
|
|
samplesRead = bytesReadFromInput / sizeof(double);
|
2022-07-15 00:28:24 +00:00
|
|
|
convert_f64_to_f32((float *)(&tempData[buffer_adder]), (const double *)inputBuffer, samplesRead);
|
2022-07-14 08:45:49 +00:00
|
|
|
bytesReadFromInput = samplesRead * sizeof(float);
|
2022-07-15 00:28:24 +00:00
|
|
|
inputBuffer = &tempData[buffer_adder];
|
2022-07-14 08:45:49 +00:00
|
|
|
inputChanged = YES;
|
|
|
|
bitsPerSample = 32;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(bytesReadFromInput && !isFloat) {
|
|
|
|
float gain = 1.0;
|
|
|
|
if(bitsPerSample == 1) {
|
|
|
|
const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
|
|
|
|
samplesRead = bytesReadFromInput / inputFormat.mBytesPerPacket;
|
|
|
|
convert_dsd_to_f32((float *)(&tempData[buffer_adder]), (const uint8_t *)inputBuffer, samplesRead, inputFormat.mChannelsPerFrame
|
|
|
|
#if DSD_DECIMATE
|
|
|
|
,
|
|
|
|
dsd2pcm
|
|
|
|
#endif
|
|
|
|
);
|
|
|
|
#if !DSD_DECIMATE
|
|
|
|
samplesRead *= 8;
|
|
|
|
#endif
|
|
|
|
bitsPerSample = 32;
|
2022-07-20 06:04:25 +00:00
|
|
|
bytesReadFromInput = samplesRead * floatFormat.mBytesPerPacket;
|
2022-07-14 08:45:49 +00:00
|
|
|
isFloat = YES;
|
|
|
|
inputBuffer = &tempData[buffer_adder];
|
|
|
|
inputChanged = YES;
|
|
|
|
#if DSD_DECIMATE
|
|
|
|
float scaleFactor = 2.0f;
|
|
|
|
vDSP_vsdiv((float *)inputBuffer, 1, &scaleFactor, (float *)inputBuffer, 1, bytesReadFromInput / sizeof(float));
|
|
|
|
#endif
|
|
|
|
} else if(bitsPerSample <= 8) {
|
|
|
|
samplesRead = bytesReadFromInput;
|
|
|
|
const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
|
|
|
|
if(!isUnsigned)
|
|
|
|
convert_s8_to_s16((int16_t *)(&tempData[buffer_adder]), (const uint8_t *)inputBuffer, samplesRead);
|
|
|
|
else
|
|
|
|
convert_u8_to_s16((int16_t *)(&tempData[buffer_adder]), (const uint8_t *)inputBuffer, samplesRead);
|
|
|
|
bitsPerSample = 16;
|
|
|
|
bytesReadFromInput = samplesRead * 2;
|
|
|
|
isUnsigned = NO;
|
|
|
|
inputBuffer = &tempData[buffer_adder];
|
|
|
|
inputChanged = YES;
|
|
|
|
}
|
|
|
|
if(hdcd_decoder) { // implied bits per sample is 16, produces 32 bit int scale
|
|
|
|
samplesRead = bytesReadFromInput / 2;
|
|
|
|
const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
|
|
|
|
if(isUnsigned)
|
|
|
|
convert_u16_to_s16((int16_t *)inputBuffer, samplesRead);
|
|
|
|
convert_s16_to_hdcd_input((int32_t *)(&tempData[buffer_adder]), (int16_t *)inputBuffer, samplesRead);
|
|
|
|
hdcd_process_stereo((hdcd_state_stereo_t *)hdcd_decoder, (int32_t *)(&tempData[buffer_adder]), (int)(samplesRead / 2));
|
|
|
|
if(((hdcd_state_stereo_t *)hdcd_decoder)->channel[0].sustain &&
|
|
|
|
((hdcd_state_stereo_t *)hdcd_decoder)->channel[1].sustain) {
|
|
|
|
hdcdSustained = YES;
|
|
|
|
}
|
|
|
|
gain = 2.0;
|
|
|
|
bitsPerSample = 32;
|
|
|
|
bytesReadFromInput = samplesRead * 4;
|
|
|
|
isUnsigned = NO;
|
|
|
|
inputBuffer = &tempData[buffer_adder];
|
|
|
|
inputChanged = YES;
|
|
|
|
} else if(bitsPerSample <= 16) {
|
|
|
|
samplesRead = bytesReadFromInput / 2;
|
|
|
|
const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
|
|
|
|
if(isUnsigned) {
|
|
|
|
if(!inputChanged) {
|
|
|
|
memcpy(&tempData[buffer_adder], inputBuffer, samplesRead * 2);
|
|
|
|
inputBuffer = &tempData[buffer_adder];
|
|
|
|
inputChanged = YES;
|
|
|
|
}
|
|
|
|
convert_u16_to_s16((int16_t *)inputBuffer, samplesRead);
|
|
|
|
}
|
|
|
|
const size_t buffer_adder2 = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
|
|
|
|
vDSP_vflt16((const short *)inputBuffer, 1, (float *)(&tempData[buffer_adder2]), 1, samplesRead);
|
|
|
|
float scale = 1ULL << 15;
|
|
|
|
vDSP_vsdiv((const float *)(&tempData[buffer_adder2]), 1, &scale, (float *)(&tempData[buffer_adder2]), 1, samplesRead);
|
|
|
|
bitsPerSample = 32;
|
|
|
|
bytesReadFromInput = samplesRead * sizeof(float);
|
|
|
|
isUnsigned = NO;
|
|
|
|
isFloat = YES;
|
|
|
|
inputBuffer = &tempData[buffer_adder2];
|
|
|
|
inputChanged = YES;
|
|
|
|
} else if(bitsPerSample <= 24) {
|
|
|
|
const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
|
|
|
|
samplesRead = bytesReadFromInput / 3;
|
|
|
|
if(isUnsigned)
|
|
|
|
convert_u24_to_s32((int32_t *)(&tempData[buffer_adder]), (uint8_t *)inputBuffer, samplesRead);
|
|
|
|
else
|
|
|
|
convert_s24_to_s32((int32_t *)(&tempData[buffer_adder]), (uint8_t *)inputBuffer, samplesRead);
|
|
|
|
bitsPerSample = 32;
|
|
|
|
bytesReadFromInput = samplesRead * 4;
|
|
|
|
isUnsigned = NO;
|
|
|
|
inputBuffer = &tempData[buffer_adder];
|
|
|
|
inputChanged = YES;
|
|
|
|
}
|
|
|
|
if(!isFloat && bitsPerSample <= 32) {
|
|
|
|
samplesRead = bytesReadFromInput / 4;
|
|
|
|
if(isUnsigned) {
|
|
|
|
if(!inputChanged) {
|
|
|
|
memcpy(&tempData[0], inputBuffer, bytesReadFromInput);
|
|
|
|
inputBuffer = &tempData[0];
|
|
|
|
}
|
|
|
|
convert_u32_to_s32((int32_t *)inputBuffer, samplesRead);
|
|
|
|
}
|
|
|
|
const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0; // vDSP functions expect aligned to four elements
|
|
|
|
vDSP_vflt32((const int *)inputBuffer, 1, (float *)(&tempData[buffer_adder]), 1, samplesRead);
|
|
|
|
float scale = (1ULL << 31) / gain;
|
|
|
|
vDSP_vsdiv((const float *)(&tempData[buffer_adder]), 1, &scale, (float *)(&tempData[buffer_adder]), 1, samplesRead);
|
|
|
|
bitsPerSample = 32;
|
|
|
|
bytesReadFromInput = samplesRead * sizeof(float);
|
|
|
|
isUnsigned = NO;
|
|
|
|
isFloat = YES;
|
|
|
|
inputBuffer = &tempData[buffer_adder];
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
[BadSampleCleaner cleanSamples:(float *)inputBuffer
|
|
|
|
amount:bytesReadFromInput / sizeof(float)
|
|
|
|
location:@"post int to float conversion"];
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
AudioChunk *outChunk = [[AudioChunk alloc] init];
|
|
|
|
[outChunk setFormat:floatFormat];
|
|
|
|
[outChunk setChannelConfig:inputChannelConfig];
|
|
|
|
[outChunk setLossless:inputLossless];
|
|
|
|
if(hdcdSustained) [outChunk setHDCD];
|
|
|
|
|
|
|
|
[outChunk assignSamples:inputBuffer frameCount:bytesReadFromInput / floatFormat.mBytesPerPacket];
|
|
|
|
|
|
|
|
return outChunk;
|
|
|
|
}
|
|
|
|
|
2022-02-08 03:18:45 +00:00
|
|
|
- (BOOL)peekFormat:(AudioStreamBasicDescription *)format channelConfig:(uint32_t *)config {
|
|
|
|
if(stopping) return NO;
|
|
|
|
@synchronized(chunkList) {
|
|
|
|
if([chunkList count]) {
|
|
|
|
AudioChunk *chunk = [chunkList objectAtIndex:0];
|
|
|
|
*format = [chunk format];
|
|
|
|
*config = [chunk channelConfig];
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2022-02-06 11:08:34 +00:00
|
|
|
@end
|