Cog/Plugins/WavPack/WavPackDecoder.m
Christopher Snowhill 3c351f6968 [Input API] Change input readAudio method
readAudio now returns an AudioChunk object directly, and all inputs have
been changed to accomodate this. Also, input and converter processing
have been altered to better work with this.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-07-10 15:14:47 -07:00

330 lines
7 KiB
Objective-C

//
// WavPackFile.m
// Cog
//
// Created by Vincent Spader on 6/6/05.
// Copyright 2005 Vincent Spader All rights reserved.
//
#import "WavPackDecoder.h"
#import "Logging.h"
@implementation WavPackReader
- (id)initWithSource:(id<CogSource>)s {
self = [super init];
if(self) {
source = s;
}
return self;
}
- (void)setSource:(id<CogSource>)s {
source = s;
}
- (id<CogSource>)source {
return source;
}
@end
@implementation WavPackDecoder
int32_t ReadBytesProc(void *ds, void *data, int32_t bcount) {
WavPackReader *wv = (__bridge WavPackReader *)ds;
return (int32_t)[[wv source] read:data amount:bcount];
}
uint32_t GetPosProc(void *ds) {
WavPackReader *wv = (__bridge WavPackReader *)ds;
return (uint32_t)[[wv source] tell];
}
int SetPosAbsProc(void *ds, uint32_t pos) {
WavPackReader *wv = (__bridge WavPackReader *)ds;
return ([[wv source] seek:pos whence:SEEK_SET] ? 0 : -1);
}
int SetPosRelProc(void *ds, int32_t delta, int mode) {
WavPackReader *wv = (__bridge WavPackReader *)ds;
return ([[wv source] seek:delta whence:mode] ? 0 : -1);
}
int PushBackByteProc(void *ds, int c) {
WavPackReader *wv = (__bridge WavPackReader *)ds;
if([[wv source] seekable]) {
[[wv source] seek:-1 whence:SEEK_CUR];
return c;
} else {
return -1;
}
}
uint32_t GetLengthProc(void *ds) {
WavPackReader *wv = (__bridge WavPackReader *)ds;
if([[wv source] seekable]) {
long currentPos = [[wv source] tell];
[[wv source] seek:0 whence:SEEK_END];
long size = [[wv source] tell];
[[wv source] seek:currentPos whence:SEEK_SET];
return (uint32_t)size;
} else {
return 0;
}
}
int CanSeekProc(void *ds) {
WavPackReader *wv = (__bridge WavPackReader *)ds;
return [[wv source] seekable];
}
int32_t WriteBytesProc(void *ds, void *data, int32_t bcount) {
return -1;
}
- (id)init {
self = [super init];
if(self) {
wpc = NULL;
inputBuffer = NULL;
inputBufferSize = 0;
}
return self;
}
- (BOOL)open:(id<CogSource>)s {
int open_flags = 0;
char error[80];
wv = [[WavPackReader alloc] initWithSource:s];
if([s seekable]) {
id audioSourceClass = NSClassFromString(@"AudioSource");
NSURL *wvcurl = [[s url] URLByDeletingPathExtension];
wvcurl = [wvcurl URLByAppendingPathExtension:@"wvc"];
id<CogSource> wvcsrc = [audioSourceClass audioSourceForURL:wvcurl];
if([wvcsrc open:wvcurl]) {
wvc = [[WavPackReader alloc] initWithSource:wvcsrc];
} else {
wvc = nil;
}
} else {
wvc = nil;
}
reader.read_bytes = ReadBytesProc;
reader.get_pos = GetPosProc;
reader.set_pos_abs = SetPosAbsProc;
reader.set_pos_rel = SetPosRelProc;
reader.push_back_byte = PushBackByteProc;
reader.get_length = GetLengthProc;
reader.can_seek = CanSeekProc;
reader.write_bytes = WriteBytesProc;
open_flags |= OPEN_DSD_NATIVE | OPEN_ALT_TYPES;
if(![s seekable])
open_flags |= OPEN_STREAMING;
wpc = WavpackOpenFileInputEx(&reader, (__bridge void *)(wv), (__bridge void *)(wvc), error, open_flags, 0);
if(!wpc) {
DLog(@"Unable to open file..");
return NO;
}
channels = WavpackGetNumChannels(wpc);
channelConfig = WavpackGetChannelMask(wpc);
bitsPerSample = WavpackGetBitsPerSample(wpc);
frequency = WavpackGetSampleRate(wpc);
totalFrames = WavpackGetNumSamples(wpc);
isDSD = NO;
float nativeFrequency = WavpackGetNativeSampleRate(wpc);
if(nativeFrequency != frequency && bitsPerSample == 8) {
isDSD = YES;
frequency = nativeFrequency;
bitsPerSample = 1;
totalFrames *= 8;
}
bitrate = (int)(WavpackGetAverageBitrate(wpc, TRUE) / 1000.0);
floatingPoint = MODE_FLOAT & WavpackGetMode(wpc) && 127 == WavpackGetFloatNormExp(wpc);
isLossy = !(WavpackGetMode(wpc) & MODE_LOSSLESS);
[self willChangeValueForKey:@"properties"];
[self didChangeValueForKey:@"properties"];
return YES;
}
/*
- (int)fillBuffer:(void *)buf ofSize:(UInt32)size
{
int numsamples;
int n;
void *sampleBuf = malloc(size*2);
numsamples = size/(bitsPerSample/8)/channels;
n = WavpackUnpackSamples(wpc, sampleBuf, numsamples);
int i;
for (i = 0; i < n*channels; i++)
{
((UInt16 *)buf)[i] = ((UInt32 *)sampleBuf)[i];
}
n *= (bitsPerSample/8)*channels;
free(sampleBuf);
return n;
}
*/
- (AudioChunk *)readAudio {
int32_t frames = 1024;
id audioChunkClass = NSClassFromString(@"AudioChunk");
AudioChunk *chunk = [[audioChunkClass alloc] initWithProperties:[self properties]];
uint32_t sample;
int32_t audioSample;
uint32_t samplesRead;
int8_t *alias8;
int16_t *alias16;
int32_t *alias32;
const size_t bufferSize = frames * [chunk format].mBytesPerFrame;
uint8_t buffer[bufferSize];
void *buf = (void *)buffer;
size_t newSize = frames * sizeof(int32_t) * channels;
if(!inputBuffer || newSize > inputBufferSize) {
inputBuffer = realloc(inputBuffer, inputBufferSize = newSize);
}
samplesRead = WavpackUnpackSamples(wpc, inputBuffer, frames);
switch(bitsPerSample) {
case 1:
case 8:
// No need for byte swapping
alias8 = buf;
for(sample = 0; sample < samplesRead * channels; ++sample) {
*alias8++ = (int8_t)inputBuffer[sample];
}
break;
case 16:
// Convert to little endian byte order
alias16 = buf;
for(sample = 0; sample < samplesRead * channels; ++sample) {
*alias16++ = OSSwapHostToLittleInt16((int16_t)inputBuffer[sample]);
}
break;
case 24:
// Convert to little endian byte order
alias8 = buf;
for(sample = 0; sample < samplesRead * channels; ++sample) {
audioSample = inputBuffer[sample];
*alias8++ = (int8_t)audioSample;
*alias8++ = (int8_t)(audioSample >> 8);
*alias8++ = (int8_t)(audioSample >> 16);
}
break;
case 32:
// Convert to little endian byte order
alias32 = buf;
for(sample = 0; sample < samplesRead * channels; ++sample) {
*alias32++ = OSSwapHostToLittleInt32(inputBuffer[sample]);
}
break;
default:
ALog(@"Unsupported sample size: %d", bitsPerSample);
}
[chunk assignSamples:buffer frameCount:samplesRead];
return chunk;
}
- (long)seek:(long)frame {
uint32_t trueFrame = (uint32_t)frame;
if(isDSD) {
trueFrame /= 8;
frame = trueFrame * 8;
}
WavpackSeekSample(wpc, trueFrame);
return frame;
}
- (void)close {
if(wpc) {
WavpackCloseFile(wpc);
wpc = NULL;
}
if(inputBuffer) {
free(inputBuffer);
inputBuffer = NULL;
inputBufferSize = 0;
}
wvc = nil;
wv = nil;
}
- (void)dealloc {
[self close];
}
- (NSDictionary *)properties {
return @{ @"channels": @(channels),
@"channelConfig": @(channelConfig),
@"bitsPerSample": @(bitsPerSample),
@"bitrate": @(bitrate),
@"sampleRate": @(frequency),
@"floatingPoint": @(floatingPoint),
@"totalFrames": @(totalFrames),
@"seekable": @([[wv source] seekable]),
@"codec": @"WavPack",
@"endian": @"little",
@"encoding": isLossy ? @"lossy" : @"lossless" };
}
- (NSDictionary *)metadata {
return @{};
}
+ (NSArray *)fileTypes {
return @[@"wv", @"wvp"];
}
+ (NSArray *)mimeTypes {
return @[@"audio/x-wavpack"];
}
+ (float)priority {
return 1.0;
}
+ (NSArray *)fileTypeAssociations {
return @[
@[@"Wavpack File", @"wv.icns", @"wv", @"wvp"]
];
}
@end