-
-
Save patrick99e99/72712182158ecbfcbfea to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "Sampler.h" | |
#import <AudioToolbox/AudioToolbox.h> | |
#import "AudioHelpers.h" | |
#import "Buffer.h" | |
#import "SamplerDelegate.h" | |
#import "PlayheadView.h" | |
@interface Sampler () | |
@property (nonatomic) NSUInteger sampleRate; | |
@property (nonatomic, weak) id<SamplerDelegate>delegate; | |
@property (nonatomic) BOOL streaming; | |
@property (nonatomic, strong) NSTimer *timer; | |
-(void)didFinishStreaming:(Buffer *)buffer; | |
@end | |
static NSUInteger const sampleRate = 48000; | |
typedef struct SamplerPlayer | |
{ | |
AudioUnit outputUnit; | |
NSUInteger counter; | |
NSUInteger index; | |
float ratio; | |
Buffer *buffer; | |
__unsafe_unretained Sampler *sampler; | |
} SamplerPlayer; | |
OSStatus CallbackRenderProc(void *inRefCon, | |
AudioUnitRenderActionFlags *ioActionFlags, | |
const AudioTimeStamp *inTimeStamp, | |
UInt32 inBusNumber, | |
UInt32 inNumberFrames, | |
AudioBufferList * ioData); | |
void CreateAndConnectOutputUnit (SamplerPlayer *player); | |
OSStatus CallbackRenderProc(void *inRefCon, | |
AudioUnitRenderActionFlags *ioActionFlags, | |
const AudioTimeStamp *inTimeStamp, | |
UInt32 inBusNumber, | |
UInt32 inNumberFrames, | |
AudioBufferList * ioData) { | |
struct SamplerPlayer *player = (struct SamplerPlayer *)inRefCon; | |
if (!player->sampler.streaming) return noErr; | |
for (int i = 0; i < inNumberFrames; i++) { | |
NSUInteger index = floor(player->counter / player->ratio); | |
player->index = index; | |
SInt16 sample = player->buffer.samples[index] * (1 << 15); | |
((SInt16 *)ioData->mBuffers[0].mData)[i] = sample; | |
if (index == player->buffer.size - 1) { | |
[player->sampler performSelectorOnMainThread:@selector(didFinishStreaming:) | |
withObject:player->buffer | |
waitUntilDone:NO]; | |
break; | |
} else { | |
player->counter += 1; | |
} | |
} | |
return noErr; | |
} | |
@implementation Sampler { | |
SamplerPlayer player; | |
} | |
-(instancetype)initWithDelegate:(id<SamplerDelegate>)delegate { | |
if (self = [super init]) { | |
self.delegate = delegate; | |
} | |
return self; | |
} | |
-(void)stream:(Buffer *)buffer { | |
if (!buffer) return; | |
[self stop]; | |
self.streaming = YES; | |
player = {0}; | |
player.ratio = (float)sampleRate / buffer.sampleRate; | |
player.buffer = buffer; | |
player.sampler = self; | |
self.timer = [NSTimer scheduledTimerWithTimeInterval:(1.0f / 60) target:self selector:@selector(updatePlayhead) userInfo:nil repeats:YES]; | |
[self createAndConnectOutputUnit]; | |
CheckError(AudioOutputUnitStart(player.outputUnit), "Couldn't start output unit"); | |
} | |
-(void)createAndConnectOutputUnit { | |
AudioComponentDescription outputcd = {0}; | |
outputcd.componentType = kAudioUnitType_Output; | |
outputcd.componentSubType = kAudioUnitSubType_DefaultOutput; | |
outputcd.componentManufacturer = kAudioUnitManufacturer_Apple; | |
AudioComponent comp = AudioComponentFindNext (NULL, &outputcd); | |
if (comp == NULL) { | |
printf ("can't get output unit"); | |
exit (-1); | |
} | |
AudioComponentInstanceNew(comp, &player.outputUnit); | |
// register render callback | |
AURenderCallbackStruct input; | |
input.inputProc = CallbackRenderProc; | |
input.inputProcRefCon = &player; | |
CheckError(AudioUnitSetProperty(player.outputUnit, | |
kAudioUnitProperty_SetRenderCallback, | |
kAudioUnitScope_Input, | |
0, | |
&input, | |
sizeof(input)), | |
"AudioUnitSetProperty failed"); | |
AudioStreamBasicDescription asbd; | |
asbd.mSampleRate = sampleRate; | |
asbd.mFormatID = kAudioFormatLinearPCM; | |
asbd.mFormatFlags = kAudioFormatFlagIsSignedInteger; | |
asbd.mFramesPerPacket = 1; | |
asbd.mChannelsPerFrame = 1; | |
asbd.mBitsPerChannel = 16; | |
asbd.mBytesPerPacket = 2; | |
asbd.mBytesPerFrame = 2; | |
CheckError(AudioUnitSetProperty(player.outputUnit, | |
kAudioUnitProperty_StreamFormat, | |
kAudioUnitScope_Input, | |
0, | |
&asbd, | |
sizeof(asbd)), | |
"Couldn't set description for stream"); | |
CheckError(AudioUnitInitialize(player.outputUnit), | |
"Couldn't initialize output unit"); | |
} | |
-(void)didFinishStreaming:(Buffer *)buffer { | |
[self stop]; | |
[self.delegate didFinishStreaming:buffer]; | |
} | |
-(void)updatePlayhead { | |
player.sampler.playheadView.position = (player.sampler.playheadView.containerWidth / player.buffer.size) * player.index; | |
} | |
-(void)stop { | |
if (!self.streaming) return; | |
self.streaming = NO; | |
[self.timer invalidate]; | |
player.sampler.playheadView.position = 0; | |
AudioOutputUnitStop(player.outputUnit); | |
AudioUnitUninitialize(player.outputUnit); | |
AudioComponentInstanceDispose(player.outputUnit); | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment