Audio Chunks now have full timestamp accounting, including DSP playback speed ratio for the one DSP that can change play ratio, Rubber Band. Inputs which support looping and actually reporting the absolute play position now do so. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
337 lines
7.2 KiB
Objective-C
337 lines
7.2 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);
|
|
frame = 0;
|
|
|
|
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);
|
|
}
|
|
|
|
double streamTimestamp = (double)(frame) / frequency;
|
|
frame += samplesRead;
|
|
|
|
[chunk setStreamTimestamp:streamTimestamp];
|
|
[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);
|
|
|
|
self->frame = frame;
|
|
|
|
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
|