Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Working on a scrollable waveform that builds itself as you record audio, similar to iOS Voice Memos
//
// ScrollableWaveform.m
//
// Created by Matthew Nearents on 1/20/15.
// Copyright (c) 2015 Matt. All rights reserved.
#import "ScrollableWaveform.h"
//Gain applied to incoming samples
static CGFloat kGain = 5.0;
static CGFloat kOFFSetDueToIncrement = 4.0;
@interface ScrollableWaveform()
@property (nonatomic) BOOL addToBuffer;
@property (strong, nonatomic) NSDictionary *timeline;
@property (nonatomic) float xValue; //keep track of x to know if overwriting
@property (nonatomic) BOOL isOverwriting; //are we overwriting the wave when recording
//Holds kMaxWaveforms number of incoming samples,
//80 is based on half the width of iPhone, adding a 1 pixel line between samples
@property (nonatomic) CGFloat overWritingOffSet;
@property (nonatomic) NSInteger count;
@property (nonatomic) NSInteger start;
@property (nonatomic) NSInteger end;
+ (float)RMS:(float *)buffer length:(int)bufferSize;
@end
@implementation TTTWaveForm
- (void)awakeFromNib
{
[super awakeFromNib];
self.bufferArray = [NSMutableArray array];
self.timeline = [[NSMutableDictionary alloc] init];
}
-(void)updateBuffer:(float *)buffer withBufferSize:(UInt32)bufferSize
{
if(!self.start) {
self.start = 0;
}
if(!self.end) {
self.end = 0;
}
if(!self.bufferArray) {
self.bufferArray = [[NSMutableArray alloc] init];
}
float rms = [TTTWaveForm RMS:buffer length:bufferSize];
//overwriting
if(self.recordAtIndex && (self.recordAtIndex < [self.bufferArray count])) {
self.isOverwriting = YES;
[self.bufferArray replaceObjectAtIndex:self.recordAtIndex withObject:@(rms * kGain)];
self.recordAtIndex++;
} else {
self.isOverwriting = NO;
[self.bufferArray addObject:@(rms * kGain)];
self.recordAtIndex++;
}
//whether we are at the center or not
if ([self.bufferArray count] >= self.kMaxWaveforms) {
self.drawSeekerAtCenter = YES;
[self adjustContentSize];
}
else {
self.drawSeekerAtCenter = NO;
}
if (self.isOverwriting) {
if([self.bufferArray count] < self.kMaxWaveforms ) {
self.end++;
} else {
if (self.start ==0 && ((self.end-self.start)>self.kMaxWaveforms) &&( (self.contentOffset.x ) < self.start+ self.increment)) {
self.end = [self.bufferArray count];
}else{
self.end++;
self.start++;
}
}
}else{
if(([self.bufferArray count] < self.kMaxWaveforms)) {
self.end++;
}
else {
self.end++;
self.start++;
}
}
if(!self.widthOfWave) {
self.widthOfWave = 0.0;
}
if (self.isOverwriting && !self.isScrolling) {
[self setContentOffset:CGPointMake((self.contentOffset.x+ self.increment) , self.contentOffset.y) animated:NO];
self.drawAtOffset = self.contentOffset.x;
}
[self setNeedsDisplay];
}
-(void)adjustContentSize{
if (!self.isScrolling) {
// Increment Start
if (!self.isOverwriting) {
[self setContentSize:CGSizeMake(self.contentSize.width + self.increment, self.contentSize.height)];
[self setContentOffset:CGPointMake((self.contentSize.width - self.pixels) , self.contentOffset.y) animated:NO];
self.drawAtOffset = self.contentOffset.x+ self.increment;
self.overWritingOffSet = self.contentOffset.x+ self.increment;
}
// Increment END
}
}
- (void)drawRect:(CGRect)rect
{
CGFloat midY = CGRectGetMidY(rect);
CGContextRef context = UIGraphicsGetCurrentContext();
// Draw out center line
CGContextSetStrokeColorWithColor(context, self.waveColor.CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextMoveToPoint(context, 0.0, midY);
// CGContextAddLineToPoint(context, maxX, midY); //the y axis line
CGContextStrokePath(context);
// Waveform start
CGFloat x = self.drawAtOffset;
if(!x || x < 0) {
x = 0.0;
}
if(self.end > [self.bufferArray count]) {
self.end = [self.bufferArray count];
}
if(self.start < 0) {
self.start = 0.0;
}
for (NSInteger i = self.start; i < self.end; i++) {
NSNumber *n =[self.bufferArray objectAtIndex:i];
CGFloat height = 50 * [n floatValue];
CGContextMoveToPoint(context, x, midY - height);
CGContextAddLineToPoint(context, x, midY + height);
CGContextStrokePath(context);
x += self.increment;
}
// Waveform END
// SEEKER start
//seeker is at the half-way point on the screen
if ([self.bufferArray count] >= (self.isScrolling?(self.kMaxWaveforms/2):self.kMaxWaveforms))
{
[self addMarkerInContext:context forX:CGRectGetMidX(rect) forRect:rect] ;
self.seekerPosition = CGRectGetMidX(rect);
self.widthOfWave = self.contentSize.width - CGRectGetMidX(rect);
} else { //seeker not half way across screen (like when you start recording)
if(self.isScrolling) {
[self addMarkerInContext:context forX:(self.seekerPosition + self.contentOffset.x) forRect:rect];
}else{
self.seekerPosition = x + self.contentOffset.x;
if (self.contentSize.width > self.bounds.size.width){
[self addMarkerInContext:context forX:(CGRectGetMidX(rect) + self.contentOffset.x) forRect:rect];
} else{
[self addMarkerInContext:context forX:(x + self.contentOffset.x) forRect:rect];
}
}
// ******** seeker **********
self.widthOfWave = x;
}
if(!self.isScrolling) {
if(!self.xValue) {
self.xValue = x;
} else {
if(x > self.xValue) {
self.xValue = x;
}
}
}else{
self.overWritingOffSet = self.drawAtOffset;
}
// SEEKER END
}
//this is where the seeker is drawn. The user cannot move the seeker. It stays wherever it was last drawn when you stopped recording.
- (void)addMarkerInContext:(CGContextRef)context forX:(CGFloat)x forRect:(CGRect)rect
{
CGFloat maxY = CGRectGetMaxY(rect);
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextFillEllipseInRect(context, CGRectMake(x - 2, 0, 4, 4));
CGContextMoveToPoint(context, x, 0 + 3);
CGContextAddLineToPoint(context, x, maxY - 3 );
CGContextStrokePath(context);
CGContextFillEllipseInRect(context, CGRectMake(x - 2, maxY - 4, 4, 4));
}
//prep waveform to start recording
- (void)startRecording
{
self.isScrolling = NO;
self.userInteractionEnabled = NO;
self.kMaxWaveforms = self.pixels/(self.increment*2);
}
//prep waveform to start scrolling
- (void)stopRecording
{
self.isScrolling = YES;
self.userInteractionEnabled = YES;
self.kMaxWaveforms = (self.pixels/(self.increment*2)) * 2;
if (self.isOverwriting) {
}else{
[self setContentOffset:CGPointMake((self.contentSize.width - self.pixels) , self.contentOffset.y) animated:NO];
}
[self setContentSize:CGSizeMake(self.contentSize.width , self.contentSize.height)];
if(self.widthOfWave < self.pixels/2) {
[self setContentInset:UIEdgeInsetsMake(self.contentInset.top, self.seekerPosition, self.contentInset.bottom, 0.0)];
} else {
[self setContentInset:UIEdgeInsetsMake(self.contentInset.top, (self.pixels/2) + (kOFFSetDueToIncrement/2), self.contentInset.bottom, 0.0)];
}
}
//gets called when scrolling the waveform
- (void)scrollWaveformAtOffset:(CGFloat)offset startIndex:(NSInteger)start endIndex:(NSInteger)end
{
self.drawAtOffset = offset ;
self.start = start;
self.end = end;
[self setNeedsDisplay];
}
+ (float)RMS:(float *)buffer length:(int)bufferSize {
float sum = 0.0;
for(int i = 0; i < bufferSize; i++) {
sum += buffer[i] * buffer[i];
}
return sqrtf( sum / bufferSize );
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment