2005-06-06 17:47:29 +00:00
|
|
|
//
|
|
|
|
// WavPackFile.m
|
|
|
|
// Cog
|
|
|
|
//
|
2005-07-02 21:02:06 +00:00
|
|
|
// Created by Vincent Spader on 6/6/05.
|
|
|
|
// Copyright 2005 Vincent Spader All rights reserved.
|
2005-06-06 17:47:29 +00:00
|
|
|
//
|
|
|
|
|
2007-02-24 20:36:27 +00:00
|
|
|
#import "WavPackDecoder.h"
|
2005-06-06 17:47:29 +00:00
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
#import "Logging.h"
|
2005-06-06 17:47:29 +00:00
|
|
|
|
2016-11-04 05:43:37 +00:00
|
|
|
@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
|
|
|
|
|
2007-02-24 20:36:27 +00:00
|
|
|
@implementation WavPackDecoder
|
2005-06-06 17:47:29 +00:00
|
|
|
|
2007-03-04 20:47:49 +00:00
|
|
|
int32_t ReadBytesProc(void *ds, void *data, int32_t bcount)
|
|
|
|
{
|
2016-11-04 05:43:37 +00:00
|
|
|
WavPackReader *wv = (__bridge WavPackReader *)ds;
|
2007-03-04 20:47:49 +00:00
|
|
|
|
2016-11-04 05:43:37 +00:00
|
|
|
return (int32_t) [[wv source] read:data amount:bcount];
|
2007-03-04 20:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t GetPosProc(void *ds)
|
|
|
|
{
|
2016-11-04 05:43:37 +00:00
|
|
|
WavPackReader *wv = (__bridge WavPackReader *)ds;
|
2007-03-04 20:47:49 +00:00
|
|
|
|
2016-11-04 05:43:37 +00:00
|
|
|
return (uint32_t) [[wv source] tell];
|
2007-03-04 20:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int SetPosAbsProc(void *ds, uint32_t pos)
|
|
|
|
{
|
2016-11-04 05:43:37 +00:00
|
|
|
WavPackReader *wv = (__bridge WavPackReader *)ds;
|
2007-03-04 20:47:49 +00:00
|
|
|
|
2016-11-04 05:43:37 +00:00
|
|
|
return ([[wv source] seek:pos whence:SEEK_SET] ? 0: -1);
|
2007-03-04 20:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int SetPosRelProc(void *ds, int32_t delta, int mode)
|
|
|
|
{
|
2016-11-04 05:43:37 +00:00
|
|
|
WavPackReader *wv = (__bridge WavPackReader *)ds;
|
2007-03-04 20:47:49 +00:00
|
|
|
|
2016-11-04 05:43:37 +00:00
|
|
|
return ([[wv source] seek:delta whence:mode] ? 0: -1);
|
2007-03-04 20:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int PushBackByteProc(void *ds, int c)
|
|
|
|
{
|
2016-11-04 05:43:37 +00:00
|
|
|
WavPackReader *wv = (__bridge WavPackReader *)ds;
|
2007-03-04 20:47:49 +00:00
|
|
|
|
2016-11-04 05:43:37 +00:00
|
|
|
if ([[wv source] seekable]) {
|
|
|
|
[[wv source] seek:-1 whence:SEEK_CUR];
|
2007-03-04 20:47:49 +00:00
|
|
|
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t GetLengthProc(void *ds)
|
|
|
|
{
|
2016-11-04 05:43:37 +00:00
|
|
|
WavPackReader *wv = (__bridge WavPackReader *)ds;
|
2007-03-04 20:47:49 +00:00
|
|
|
|
2016-11-04 05:43:37 +00:00
|
|
|
if ([[wv source] seekable]) {
|
|
|
|
long currentPos = [[wv source] tell];
|
2007-03-04 20:47:49 +00:00
|
|
|
|
2016-11-04 05:43:37 +00:00
|
|
|
[[wv source] seek:0 whence:SEEK_END];
|
|
|
|
long size = [[wv source] tell];
|
2007-03-04 20:47:49 +00:00
|
|
|
|
2016-11-04 05:43:37 +00:00
|
|
|
[[wv source] seek:currentPos whence:SEEK_SET];
|
2007-03-04 20:47:49 +00:00
|
|
|
|
2016-06-30 05:10:29 +00:00
|
|
|
return (uint32_t) size;
|
2007-03-04 20:47:49 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int CanSeekProc(void *ds)
|
|
|
|
{
|
2016-11-04 05:43:37 +00:00
|
|
|
WavPackReader *wv = (__bridge WavPackReader *)ds;
|
2007-03-04 20:47:49 +00:00
|
|
|
|
2016-11-04 05:43:37 +00:00
|
|
|
return [[wv source] seekable];
|
2007-03-04 20:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int32_t WriteBytesProc(void *ds, void *data, int32_t bcount)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2016-06-19 19:57:18 +00:00
|
|
|
- (id)init
|
|
|
|
{
|
|
|
|
self = [super init];
|
|
|
|
if (self)
|
|
|
|
{
|
|
|
|
wpc = NULL;
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
2007-03-04 20:47:49 +00:00
|
|
|
|
|
|
|
- (BOOL)open:(id<CogSource>)s
|
2005-06-06 17:47:29 +00:00
|
|
|
{
|
|
|
|
int open_flags = 0;
|
|
|
|
char error[80];
|
|
|
|
|
2016-11-04 05:43:37 +00:00
|
|
|
wv = [[WavPackReader alloc] initWithSource:s];
|
|
|
|
|
2020-03-22 07:15:45 +00:00
|
|
|
if ([s seekable])
|
2016-11-04 05:43:37 +00:00
|
|
|
{
|
2020-03-22 07:15:45 +00:00
|
|
|
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;
|
|
|
|
}
|
2016-11-04 05:43:37 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
wvc = nil;
|
|
|
|
}
|
|
|
|
|
2007-03-04 20:47:49 +00:00
|
|
|
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;
|
2016-08-28 20:03:54 +00:00
|
|
|
|
|
|
|
open_flags |= OPEN_DSD_AS_PCM | OPEN_ALT_TYPES;
|
2020-03-22 07:15:45 +00:00
|
|
|
|
|
|
|
if (![s seekable])
|
|
|
|
open_flags |= OPEN_STREAMING;
|
2007-03-04 20:47:49 +00:00
|
|
|
|
2016-11-04 05:43:37 +00:00
|
|
|
wpc = WavpackOpenFileInputEx(&reader, (__bridge void *)(wv), (__bridge void *)(wvc), error, open_flags, 0);
|
2007-03-04 20:47:49 +00:00
|
|
|
if (!wpc) {
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"Unable to open file..");
|
2005-06-06 17:47:29 +00:00
|
|
|
return NO;
|
2007-03-04 20:47:49 +00:00
|
|
|
}
|
2005-06-06 17:47:29 +00:00
|
|
|
|
|
|
|
channels = WavpackGetNumChannels(wpc);
|
2006-04-21 20:43:47 +00:00
|
|
|
bitsPerSample = WavpackGetBitsPerSample(wpc);
|
2005-06-29 15:28:20 +00:00
|
|
|
|
|
|
|
frequency = WavpackGetSampleRate(wpc);
|
2005-06-06 17:47:29 +00:00
|
|
|
|
2007-11-24 20:16:27 +00:00
|
|
|
totalFrames = WavpackGetNumSamples(wpc);
|
2005-06-06 17:47:29 +00:00
|
|
|
|
2006-06-19 00:39:41 +00:00
|
|
|
bitrate = (int)(WavpackGetAverageBitrate(wpc, TRUE)/1000.0);
|
2016-08-28 20:03:54 +00:00
|
|
|
|
|
|
|
floatingPoint = MODE_FLOAT & WavpackGetMode(wpc) && 127 == WavpackGetFloatNormExp(wpc);
|
2005-06-06 17:47:29 +00:00
|
|
|
|
2007-03-04 20:47:49 +00:00
|
|
|
[self willChangeValueForKey:@"properties"];
|
|
|
|
[self didChangeValueForKey:@"properties"];
|
|
|
|
|
2005-06-29 15:28:20 +00:00
|
|
|
return YES;
|
2005-06-06 17:47:29 +00:00
|
|
|
}
|
2007-03-04 20:47:49 +00:00
|
|
|
/*
|
2005-06-06 17:47:29 +00:00
|
|
|
- (int)fillBuffer:(void *)buf ofSize:(UInt32)size
|
|
|
|
{
|
|
|
|
int numsamples;
|
|
|
|
int n;
|
2006-04-21 20:43:47 +00:00
|
|
|
void *sampleBuf = malloc(size*2);
|
2005-06-06 17:47:29 +00:00
|
|
|
|
2006-04-21 20:43:47 +00:00
|
|
|
numsamples = size/(bitsPerSample/8)/channels;
|
|
|
|
n = WavpackUnpackSamples(wpc, sampleBuf, numsamples);
|
2005-06-06 17:47:29 +00:00
|
|
|
|
2005-06-29 15:28:20 +00:00
|
|
|
int i;
|
2006-04-21 20:43:47 +00:00
|
|
|
for (i = 0; i < n*channels; i++)
|
2005-06-29 15:28:20 +00:00
|
|
|
{
|
2006-04-21 20:43:47 +00:00
|
|
|
((UInt16 *)buf)[i] = ((UInt32 *)sampleBuf)[i];
|
2005-06-29 15:28:20 +00:00
|
|
|
}
|
2007-03-04 20:47:49 +00:00
|
|
|
|
2006-04-21 20:43:47 +00:00
|
|
|
n *= (bitsPerSample/8)*channels;
|
2005-06-29 15:28:20 +00:00
|
|
|
|
2006-04-28 23:19:14 +00:00
|
|
|
free(sampleBuf);
|
|
|
|
|
2005-06-06 17:47:29 +00:00
|
|
|
return n;
|
|
|
|
}
|
2007-03-04 20:47:49 +00:00
|
|
|
*/
|
2007-11-24 20:16:27 +00:00
|
|
|
- (int)readAudio:(void *)buf frames:(UInt32)frames
|
2007-03-04 20:47:49 +00:00
|
|
|
{
|
|
|
|
uint32_t sample;
|
|
|
|
int32_t audioSample;
|
|
|
|
uint32_t samplesRead;
|
|
|
|
int8_t *alias8;
|
|
|
|
int16_t *alias16;
|
|
|
|
int32_t *alias32;
|
|
|
|
|
2007-11-24 20:16:27 +00:00
|
|
|
int32_t *inputBuffer = malloc(frames*sizeof(int32_t));
|
2007-03-04 20:47:49 +00:00
|
|
|
|
|
|
|
// Wavpack uses "complete" samples (one sample across all channels), i.e. a Core Audio frame
|
2007-11-24 20:16:27 +00:00
|
|
|
samplesRead = WavpackUnpackSamples(wpc, inputBuffer, frames/channels);
|
2007-03-04 20:47:49 +00:00
|
|
|
|
|
|
|
switch(bitsPerSample) {
|
|
|
|
case 8:
|
|
|
|
// No need for byte swapping
|
|
|
|
alias8 = buf;
|
|
|
|
for(sample = 0; sample < samplesRead * channels; ++sample) {
|
|
|
|
*alias8++ = (int8_t)inputBuffer[sample];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 16:
|
2009-03-03 16:19:46 +00:00
|
|
|
// Convert to little endian byte order
|
2007-03-04 20:47:49 +00:00
|
|
|
alias16 = buf;
|
|
|
|
for(sample = 0; sample < samplesRead*channels; ++sample) {
|
2009-03-03 16:19:46 +00:00
|
|
|
*alias16++ = OSSwapHostToLittleInt16((int16_t)inputBuffer[sample]);
|
2007-03-04 20:47:49 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 24:
|
2009-03-03 16:19:46 +00:00
|
|
|
// Convert to little endian byte order
|
2007-03-04 20:47:49 +00:00
|
|
|
alias8 = buf;
|
|
|
|
for(sample = 0; sample < samplesRead * channels; ++sample) {
|
2007-03-04 20:52:39 +00:00
|
|
|
audioSample = inputBuffer[sample];
|
2007-03-04 20:47:49 +00:00
|
|
|
*alias8++ = (int8_t)audioSample;
|
2009-03-03 16:19:46 +00:00
|
|
|
*alias8++ = (int8_t)(audioSample >> 8);
|
|
|
|
*alias8++ = (int8_t)(audioSample >> 16);
|
2007-03-04 20:47:49 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 32:
|
2009-03-03 16:19:46 +00:00
|
|
|
// Convert to little endian byte order
|
2007-03-04 20:47:49 +00:00
|
|
|
alias32 = buf;
|
|
|
|
for(sample = 0; sample < samplesRead * channels; ++sample) {
|
2009-03-03 16:19:46 +00:00
|
|
|
*alias32++ = OSSwapHostToLittleInt32(inputBuffer[sample]);
|
2007-03-04 20:47:49 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"Unsupported sample size: %d", bitsPerSample);
|
2007-03-04 20:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
free(inputBuffer);
|
|
|
|
|
2007-11-24 20:16:27 +00:00
|
|
|
return samplesRead;
|
2007-03-04 20:47:49 +00:00
|
|
|
}
|
2005-06-06 17:47:29 +00:00
|
|
|
|
2007-11-24 20:16:27 +00:00
|
|
|
- (long)seek:(long)frame
|
2005-06-06 17:47:29 +00:00
|
|
|
{
|
2016-06-30 05:10:29 +00:00
|
|
|
WavpackSeekSample(wpc, (uint32_t) frame);
|
2005-06-06 17:47:29 +00:00
|
|
|
|
2007-11-24 20:16:27 +00:00
|
|
|
return frame;
|
2005-06-06 17:47:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)close
|
|
|
|
{
|
2016-06-19 19:57:18 +00:00
|
|
|
if (wpc) {
|
|
|
|
WavpackCloseFile(wpc);
|
|
|
|
wpc = NULL;
|
|
|
|
}
|
2016-11-04 05:43:37 +00:00
|
|
|
wvc = nil;
|
|
|
|
wv = nil;
|
2005-06-06 17:47:29 +00:00
|
|
|
}
|
|
|
|
|
2016-06-19 19:57:18 +00:00
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
[self close];
|
|
|
|
}
|
|
|
|
|
2007-02-24 20:36:27 +00:00
|
|
|
- (NSDictionary *)properties
|
|
|
|
{
|
|
|
|
return [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
[NSNumber numberWithInt:channels],@"channels",
|
|
|
|
[NSNumber numberWithInt:bitsPerSample],@"bitsPerSample",
|
|
|
|
[NSNumber numberWithInt:bitrate],@"bitrate",
|
|
|
|
[NSNumber numberWithFloat:frequency],@"sampleRate",
|
2016-08-28 20:03:54 +00:00
|
|
|
[NSNumber numberWithBool:floatingPoint],@"floatingPoint",
|
2007-11-24 20:16:27 +00:00
|
|
|
[NSNumber numberWithDouble:totalFrames],@"totalFrames",
|
2016-11-04 05:43:37 +00:00
|
|
|
[NSNumber numberWithBool:[[wv source] seekable]], @"seekable",
|
2009-03-03 16:19:46 +00:00
|
|
|
@"little",@"endian",
|
2007-02-24 20:36:27 +00:00
|
|
|
nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)fileTypes
|
|
|
|
{
|
2007-10-14 18:39:58 +00:00
|
|
|
return [NSArray arrayWithObjects:@"wv", @"wvp", nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)mimeTypes
|
|
|
|
{
|
|
|
|
return [NSArray arrayWithObjects:@"audio/x-wavpack", nil];
|
2007-02-24 20:36:27 +00:00
|
|
|
}
|
|
|
|
|
Implemented support for multiple decoders per file name extension, with a floating point priority control per interface. In the event that more than one input is registered to a given extension, and we match that extension, it will be passed off to an instance of the multi-decoder wrapper, which will try opening the file with all of the decoders in order of priority, until either one of them accepts it, or all of them have failed. This paves the way for adding a VGMSTREAM input, so I can give it a very low priority, since it has several formats that are verified by file name extension only. All current inputs have been given a priority of 1.0, except for CoreAudio, which was given a priority of 0.5, because it contains an MP3 and AC3 decoders that I'd rather not use if I don't have to.
2013-10-21 17:54:11 +00:00
|
|
|
+ (float)priority
|
|
|
|
{
|
|
|
|
return 1.0;
|
|
|
|
}
|
|
|
|
|
2007-02-24 20:36:27 +00:00
|
|
|
|
2005-06-06 17:47:29 +00:00
|
|
|
@end
|