Cog/Plugins/Musepack/MusepackDecoder.m
Christopher Snowhill 7994929a80 Audio: Add full timestamp accounting to playback
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>
2025-02-12 14:08:43 -08:00

229 lines
5 KiB
Objective-C

//
// MusepackFile.m
// zyVorbis
//
// Created by Vincent Spader on 1/23/05.
// Copyright 2005 Vincent Spader All rights reserved.
//
#import "MusepackDecoder.h"
#import "Logging.h"
@implementation MusepackDecoder
mpc_int32_t ReadProc(mpc_reader *p_reader, void *ptr, mpc_int32_t size) {
MusepackDecoder *decoder = (__bridge MusepackDecoder *)p_reader->data;
return (mpc_int32_t)[[decoder source] read:ptr amount:size];
}
mpc_bool_t SeekProc(mpc_reader *p_reader, mpc_int32_t offset) {
MusepackDecoder *decoder = (__bridge MusepackDecoder *)p_reader->data;
return [[decoder source] seek:offset whence:SEEK_SET];
}
mpc_int32_t TellProc(mpc_reader *p_reader) {
MusepackDecoder *decoder = (__bridge MusepackDecoder *)p_reader->data;
return (mpc_int32_t)[[decoder source] tell];
}
mpc_int32_t GetSizeProc(mpc_reader *p_reader) {
MusepackDecoder *decoder = (__bridge MusepackDecoder *)p_reader->data;
if([[decoder source] seekable]) {
long currentPos = [[decoder source] tell];
[[decoder source] seek:0 whence:SEEK_END];
long size = [[decoder source] tell];
[[decoder source] seek:currentPos whence:SEEK_SET];
if(size > INT32_MAX)
size = INT32_MAX;
return (mpc_int32_t)size;
} else {
return 0;
}
}
mpc_bool_t CanSeekProc(mpc_reader *p_reader) {
MusepackDecoder *decoder = (__bridge MusepackDecoder *)p_reader->data;
return [[decoder source] seekable];
}
- (BOOL)open:(id<CogSource>)s {
[self setSource:s];
reader.read = ReadProc;
reader.seek = SeekProc;
reader.tell = TellProc;
reader.get_size = GetSizeProc;
reader.canseek = CanSeekProc;
reader.data = (__bridge void *)(self);
/* instantiate a demuxer with our reader */
demux = mpc_demux_init(&reader);
if(!demux) {
DLog(@"Error initializing decoder.");
return NO;
}
mpc_demux_get_info(demux, &info);
bitrate = (int)(info.average_bitrate / 1000.0);
frequency = info.sample_freq;
totalFrames = mpc_streaminfo_get_length_samples(&info);
frame = 0;
[self willChangeValueForKey:@"properties"];
[self didChangeValueForKey:@"properties"];
return YES;
}
- (BOOL)writeToBuffer:(float *)sample_buffer fromBuffer:(const MPC_SAMPLE_FORMAT *)p_buffer frames:(unsigned)frames {
unsigned n;
unsigned p_size = frames * 2; // 2 = stereo
#ifdef MPC_FIXED_POINT
const float float_scale = 1.0 / MPC_FIXED_POINT_SCALE;
#endif
for(n = 0; n < p_size; n++) {
float val;
#ifdef MPC_FIXED_POINT
val = p_buffer[n] * float_scale;
#else
val = p_buffer[n];
#endif
// sample_buffer[n] = CFSwapInt16LittleToHost(val);
sample_buffer[n] = val;
}
// m_data_bytes_written += p_size * (m_bps >> 3);
return YES;
}
- (AudioChunk *)readAudio {
int frames = 1024;
void *buf = (void *)floatBuffer;
int framesRead = 0;
int bytesPerFrame = sizeof(float) * 2; // bitsPerSample == 32, channels == 2
while(framesRead < frames) {
// Fill from buffer, going by bufferFrames
// if still needs more, decode and repeat
if(bufferFrames == 0) {
/* returns the length of the samples*/
mpc_frame_info frame;
frame.buffer = sampleBuffer;
mpc_status err = mpc_demux_decode(demux, &frame);
if(frame.bits == -1) {
// decode error
if(err != MPC_STATUS_OK)
DLog(@"Decode error");
return 0;
} else // status>0 /* status == MPC_FRAME_LENGTH */
{
}
bufferFrames = frame.samples;
}
int framesToRead = bufferFrames;
if(bufferFrames > frames) {
framesToRead = frames;
}
[self writeToBuffer:((float *)(buf + (framesRead * bytesPerFrame))) fromBuffer:sampleBuffer frames:framesToRead];
frames -= framesToRead;
framesRead += framesToRead;
bufferFrames -= framesToRead;
if(bufferFrames > 0) {
memmove((uint8_t *)sampleBuffer, ((uint8_t *)sampleBuffer) + (framesToRead * bytesPerFrame), bufferFrames * bytesPerFrame);
}
}
double streamTimestamp = (double)(frame) / frequency;
frame += framesRead;
id audioChunkClass = NSClassFromString(@"AudioChunk");
AudioChunk *chunk = [[audioChunkClass alloc] initWithProperties:[self properties]];
[chunk setStreamTimestamp:streamTimestamp];
[chunk assignSamples:floatBuffer frameCount:framesRead];
return chunk;
}
- (void)close {
if(demux) {
mpc_demux_exit(demux);
demux = NULL;
}
}
- (void)dealloc {
[self close];
}
- (long)seek:(long)sample {
mpc_demux_seek_sample(demux, sample);
frame = sample;
return sample;
}
- (void)setSource:(id<CogSource>)s {
source = s;
}
- (id<CogSource>)source {
return source;
}
- (NSDictionary *)properties {
return @{ @"bitrate": @(bitrate),
@"sampleRate": @(frequency),
@"totalFrames": @(totalFrames),
@"bitsPerSample": @(32),
@"floatingPoint": @(YES),
@"channels": @(2),
@"seekable": @([source seekable]),
@"codec": @"Musepack",
@"endian": @"host",
@"encoding": @"lossy" };
}
- (NSDictionary *)metadata {
return @{};
}
+ (NSArray *)fileTypes {
return @[@"mpc"];
}
+ (NSArray *)mimeTypes {
return @[@"audio/x-musepack"];
}
+ (float)priority {
return 1.0;
}
+ (NSArray *)fileTypeAssociations {
return @[
@[@"Musepack Audio File", @"mpc.icns", @"mpc"]
];
}
@end