From 276e840682abbe759e98f097d2e416edfea561dd Mon Sep 17 00:00:00 2001 From: Joshua Moerman Date: Thu, 27 Feb 2014 17:03:32 +0100 Subject: [PATCH] Adds keys and minute-ticks. Adds fade. Starting to get useful --- SpectogramPrototype.xcodeproj/project.pbxproj | 12 ++ SpectogramPrototype/RuledScrollView.h | 6 +- SpectogramPrototype/RuledScrollView.m | 56 +++------ SpectogramPrototype/Ruler.h | 24 ++++ SpectogramPrototype/Ruler.m | 114 ++++++++++++++++++ SpectogramPrototype/UIScreen+Util.h | 15 +++ SpectogramPrototype/UIScreen+Util.m | 27 +++++ SpectogramPrototype/ViewController.m | 16 ++- 8 files changed, 222 insertions(+), 48 deletions(-) create mode 100644 SpectogramPrototype/Ruler.h create mode 100644 SpectogramPrototype/Ruler.m create mode 100644 SpectogramPrototype/UIScreen+Util.h create mode 100644 SpectogramPrototype/UIScreen+Util.m diff --git a/SpectogramPrototype.xcodeproj/project.pbxproj b/SpectogramPrototype.xcodeproj/project.pbxproj index c63fa97..d10f579 100644 --- a/SpectogramPrototype.xcodeproj/project.pbxproj +++ b/SpectogramPrototype.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 424374B818BCD3BC00021EC2 /* Ruler.m in Sources */ = {isa = PBXBuildFile; fileRef = 424374B718BCD3BC00021EC2 /* Ruler.m */; }; + 424374BB18BD2DD800021EC2 /* UIScreen+Util.m in Sources */ = {isa = PBXBuildFile; fileRef = 424374BA18BD2DD800021EC2 /* UIScreen+Util.m */; }; 424F84A918661F8000687D3B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 424F84A818661F8000687D3B /* Foundation.framework */; }; 424F84AB18661F8000687D3B /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 424F84AA18661F8000687D3B /* CoreGraphics.framework */; }; 424F84AD18661F8000687D3B /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 424F84AC18661F8000687D3B /* UIKit.framework */; }; @@ -41,6 +43,10 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 424374B618BCD3BC00021EC2 /* Ruler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Ruler.h; sourceTree = ""; }; + 424374B718BCD3BC00021EC2 /* Ruler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Ruler.m; sourceTree = ""; }; + 424374B918BD2DD800021EC2 /* UIScreen+Util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScreen+Util.h"; sourceTree = ""; }; + 424374BA18BD2DD800021EC2 /* UIScreen+Util.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScreen+Util.m"; sourceTree = ""; }; 424F84A518661F8000687D3B /* SpectogramPrototype.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SpectogramPrototype.app; sourceTree = BUILT_PRODUCTS_DIR; }; 424F84A818661F8000687D3B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 424F84AA18661F8000687D3B /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; @@ -143,6 +149,10 @@ 42E12A93186F1F4C00866CB3 /* AudioFile.m */, 426665B31869A7CC005D62AC /* RuledScrollView.h */, 426665B41869A7CC005D62AC /* RuledScrollView.m */, + 424374B618BCD3BC00021EC2 /* Ruler.h */, + 424374B718BCD3BC00021EC2 /* Ruler.m */, + 424374B918BD2DD800021EC2 /* UIScreen+Util.h */, + 424374BA18BD2DD800021EC2 /* UIScreen+Util.m */, 426665B61869CEFE005D62AC /* FFTTest.h */, 426665B71869CEFE005D62AC /* FFTTest.m */, 4261F08718671ECD00AA0EF9 /* ContentView.xib */, @@ -282,9 +292,11 @@ buildActionMask = 2147483647; files = ( 42E12A94186F1F4C00866CB3 /* AudioFile.m in Sources */, + 424374BB18BD2DD800021EC2 /* UIScreen+Util.m in Sources */, 42ABF54B187035F900B20D82 /* Spectographer.m in Sources */, 426665B81869CEFE005D62AC /* FFTTest.m in Sources */, 424F84C218661F8000687D3B /* ViewController.m in Sources */, + 424374B818BCD3BC00021EC2 /* Ruler.m in Sources */, 424F84B918661F8000687D3B /* AppDelegate.m in Sources */, 426665B51869A7CC005D62AC /* RuledScrollView.m in Sources */, 424F84B518661F8000687D3B /* main.m in Sources */, diff --git a/SpectogramPrototype/RuledScrollView.h b/SpectogramPrototype/RuledScrollView.h index 9b40647..d9d21c8 100644 --- a/SpectogramPrototype/RuledScrollView.h +++ b/SpectogramPrototype/RuledScrollView.h @@ -8,8 +8,10 @@ #import +@class AbstractRuler; + @interface RuledScrollView : UIScrollView @property (nonatomic, readonly) UIView * content; -@property (nonatomic, readonly) UIView * topRuler; -@property (nonatomic, readonly) UIView * leftRuler; +@property (nonatomic, readonly) AbstractRuler * topRuler; +@property (nonatomic, readonly) AbstractRuler * leftRuler; @end diff --git a/SpectogramPrototype/RuledScrollView.m b/SpectogramPrototype/RuledScrollView.m index 3c2211f..94dbd91 100644 --- a/SpectogramPrototype/RuledScrollView.m +++ b/SpectogramPrototype/RuledScrollView.m @@ -7,13 +7,13 @@ // #import "RuledScrollView.h" +#import "Ruler.h" @interface RuledScrollView () { CGFloat rulerHeight; CGFloat rulerWidth; } - (void) initView; -- (void) placeTicks; @end @implementation RuledScrollView @@ -46,62 +46,44 @@ CGRect frame2 = self.frame; frame2.size.height = rulerHeight; - topRuler = [[UIView alloc] initWithFrame:frame2]; + topRuler = [[HorizontalRuler alloc] initWithFrame:frame2]; topRuler.layer.opacity = 0.5; - topRuler.backgroundColor = self.backgroundColor; [self addSubview:topRuler]; CGRect frame3 = self.frame; frame3.size.width = rulerWidth; - leftRuler = [[UIView alloc] initWithFrame:frame3]; + leftRuler = [[VerticalRuler alloc] initWithFrame:frame3]; leftRuler.layer.opacity = 0.5; - leftRuler.backgroundColor = self.backgroundColor; [self addSubview:leftRuler]; } -/* we should draw the ticks in drawRect: - [[UIColor blackColor] setFill]; - UIRectFill((CGRect){0,200,rect.size.width,1}); - - and do tiling for the text - */ - -- (void)placeTicks { - [topRuler.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; - CGFloat spacing = 600.0f; - for (CGFloat x = rulerWidth + 0.5f*spacing; x < topRuler.frame.size.width; x += spacing){ - UIView * line = [[UIView alloc] initWithFrame:CGRectMake(x, 0, 1, rulerHeight)]; - line.backgroundColor = [UIColor blackColor]; - [topRuler addSubview:line]; - - CGFloat width = 100; - UILabel * text = [[UILabel alloc] initWithFrame:CGRectMake(x - width/2, 0, width, rulerHeight)]; - text.text = [NSString stringWithFormat:@"%.0f", 0.1 * x]; - text.textAlignment = NSTextAlignmentCenter; - [topRuler addSubview:text]; - } -} - - (void)layoutSubviews { [super layoutSubviews]; + const CGRect frame = {.origin = self.contentOffset, .size = self.bounds.size}; + const CGRect vis_frame = [self convertRect:self.bounds toView:content]; { - CGRect frame = self.topRuler.frame; - frame.origin.y = self.contentOffset.y; - self.topRuler.frame = frame; - [self placeTicks]; + CGRect frame2 = frame; + frame2.size.height = rulerHeight; + self.topRuler.frame = frame2; + + CGFloat begin = vis_frame.origin.x; + CGFloat end = vis_frame.origin.x + vis_frame.size.width; + topRuler.visibleInterval = (Interval){.begin = begin, .end = end}; } { - CGRect frame = self.leftRuler.frame; - frame.origin.x = self.contentOffset.x; - self.leftRuler.frame = frame; + CGRect frame2 = frame; + frame2.size.width = rulerWidth; + self.leftRuler.frame = frame2; + + CGFloat begin = vis_frame.origin.y; + CGFloat end = vis_frame.origin.y + vis_frame.size.height; + leftRuler.visibleInterval = (Interval){.begin = begin, .end = end}; } } - (void)setContentSize:(CGSize)contentSize{ [super setContentSize:CGSizeMake(contentSize.width + rulerWidth, contentSize.height + rulerHeight)]; content.frame = CGRectMake(rulerWidth, rulerHeight, contentSize.width, contentSize.height); - topRuler.frame = CGRectMake(0, 0, contentSize.width + rulerWidth, rulerHeight); - leftRuler.frame = CGRectMake(0, 0, rulerWidth, contentSize.height + rulerHeight); } - (UIView *)viewForZoomingInScrollView:(UIScrollView *)__unused scrollView{ diff --git a/SpectogramPrototype/Ruler.h b/SpectogramPrototype/Ruler.h new file mode 100644 index 0000000..6f53877 --- /dev/null +++ b/SpectogramPrototype/Ruler.h @@ -0,0 +1,24 @@ +// +// Ruler.h +// SpectogramPrototype +// +// Created by Joshua Moerman on 25/02/14. +// Copyright (c) 2014 Joshua Moerman. All rights reserved. +// + +#import + +typedef struct Interval { + CGFloat begin; + CGFloat end; +} Interval; + +@interface AbstractRuler : UIView +@property (nonatomic) Interval visibleInterval; +@end + +@interface HorizontalRuler : AbstractRuler +@end + +@interface VerticalRuler : AbstractRuler +@end diff --git a/SpectogramPrototype/Ruler.m b/SpectogramPrototype/Ruler.m new file mode 100644 index 0000000..03266ac --- /dev/null +++ b/SpectogramPrototype/Ruler.m @@ -0,0 +1,114 @@ +// +// Ruler.m +// SpectogramPrototype +// +// Created by Joshua Moerman on 25/02/14. +// Copyright (c) 2014 Joshua Moerman. All rights reserved. +// + +#import "Ruler.h" +#import "UIScreen+Util.h" + + +@implementation AbstractRuler +@synthesize visibleInterval; + +- (void)setVisibleInterval:(Interval)visible_interval_{ + if (visibleInterval.begin != visible_interval_.begin || visibleInterval.end != visible_interval_.end) { + [self setNeedsDisplay]; + visibleInterval = visible_interval_; + } +} + +@end + + +@implementation HorizontalRuler + +- (void)drawRect:(CGRect) __unused rect{ + [[UIColor grayColor] setFill]; + UIRectFill(self.bounds); + + const Interval visibleInterval = self.visibleInterval; + if(visibleInterval.end <= visibleInterval.begin) return; + + const CGFloat f = 60 * 44100.0 / 4096.0; + const CGFloat start = f * ceil(visibleInterval.begin / f); + const CGFloat d = visibleInterval.end - visibleInterval.begin; + const int n = (int)floor(d / f) + 1; + + for(int i = 0; i < n; ++i){ + const CGFloat x = start + i * f; + const CGFloat r = (x - visibleInterval.begin) / d; + const CGFloat gx = r * self.bounds.size.width; + + [[UIColor blackColor] setFill]; + if([UIScreen isRetinaDisplay]){ + UIRectFill(CGRectMake(gx, 0, 0.5, self.bounds.size.height)); + } else { + UIRectFill(CGRectMake(gx, 0, 1.0, self.bounds.size.height)); + } + + NSString* string = [NSString stringWithFormat:@"%.0f:00", x/f]; + NSMutableParagraphStyle* style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + style.alignment = NSTextAlignmentCenter; + UIFont* font = [UIFont boldSystemFontOfSize:14]; + [string drawInRect:CGRectMake(gx-50, 20, 100, self.bounds.size.height-20) + withAttributes:@{NSParagraphStyleAttributeName: style, + NSFontAttributeName: font}]; + } +} + +@end + + +@implementation VerticalRuler + +- (UIColor*)colorForKey:(int)j{ + switch (j) { + case 0: + return [UIColor redColor]; + case 2: + case 3: + case 5: + case 7: + case 8: + case 10: + return [UIColor whiteColor]; + + default: + return [UIColor blackColor]; + } +} + +- (void)drawRect:(CGRect) __unused rect{ + [[UIColor grayColor] setFill]; + UIRectFill(self.bounds); + + const Interval visibleInterval = self.visibleInterval; + if(visibleInterval.end <= visibleInterval.begin) return; + + const CGFloat d = visibleInterval.end - visibleInterval.begin; + const CGFloat f = 44100; + const CGFloat N = 1 << 12; + const CGFloat height = (1 << 12) >> 3; + + for(int i = 220; i < 44100/8; i *= 2){ + for(int j = 0; j < 12; ++j){ + const CGFloat k = i * pow(2, j/12.0); + const CGFloat y = N*k/f; + const CGFloat y2 = (height - y); + const CGFloat gy = 1024 * (y2 - visibleInterval.begin) / d; + + [[self colorForKey:j] setFill]; + + if([UIScreen isRetinaDisplay]){ + UIRectFill(CGRectMake(0, gy-0.5, self.bounds.size.width, 1.0)); + } else { + UIRectFill(CGRectMake(0, gy-1, self.bounds.size.width, 2.0)); + } + } + } +} + +@end diff --git a/SpectogramPrototype/UIScreen+Util.h b/SpectogramPrototype/UIScreen+Util.h new file mode 100644 index 0000000..e30dc79 --- /dev/null +++ b/SpectogramPrototype/UIScreen+Util.h @@ -0,0 +1,15 @@ +// +// UIScreen+Util.h +// SpectogramPrototype +// +// Created by Joshua Moerman on 25/02/14. +// Copyright (c) 2014 Joshua Moerman. All rights reserved. +// + +#import + +@interface UIScreen (Util) + ++ (BOOL) isRetinaDisplay; + +@end diff --git a/SpectogramPrototype/UIScreen+Util.m b/SpectogramPrototype/UIScreen+Util.m new file mode 100644 index 0000000..40eae7e --- /dev/null +++ b/SpectogramPrototype/UIScreen+Util.m @@ -0,0 +1,27 @@ +// +// UIScreen+Util.m +// SpectogramPrototype +// +// Created by Joshua Moerman on 25/02/14. +// Copyright (c) 2014 Joshua Moerman. All rights reserved. +// + +#import "UIScreen+Util.h" + +@implementation UIScreen (Util) + +// from http://stackoverflow.com/questions/3504173/detect-retina-display ++ (BOOL) isRetinaDisplay { + static BOOL retina = NO; + static BOOL alreadyChecked = NO; + if (!alreadyChecked) { + UIScreen *mainScreen = self.mainScreen; + if (mainScreen) { + retina = mainScreen.scale > 1.0; + alreadyChecked = YES; + } + } + return retina; +} + +@end diff --git a/SpectogramPrototype/ViewController.m b/SpectogramPrototype/ViewController.m index 09eff1f..03e6dad 100644 --- a/SpectogramPrototype/ViewController.m +++ b/SpectogramPrototype/ViewController.m @@ -72,6 +72,7 @@ device get_device(){ - (void)openAudioFile:(NSURL *)filePath{ [self removeAllSlices]; + [self resetView:nil]; if(!spectographer) spectographer = [[Spectographer alloc] init]; @@ -87,25 +88,22 @@ device get_device(){ [self addSlice:view]; }); }]; - -// [contentView removeFromSuperview]; -// contentView = view; -// scrollView.zoomScale = 1.0; -// -// [scrollView.content addSubview:contentView]; -// contentView.autoresizingMask = UIViewAutoresizingNone; -// scrollView.contentSize = contentView.bounds.size; } - (void)addSlice:(UIImageView *)view{ if(slices.count){ UIImageView * lastSlice = slices.lastObject; - float x = CGRectGetMaxX(lastSlice.frame); + CGFloat x = CGRectGetMaxX(lastSlice.frame); view.frame = CGRectOffset(view.frame, x, 0); } // else leave the origin at 0 [contentView addSubview:view]; [slices addObject:view]; + + view.layer.opacity = 0; + [UIView animateWithDuration:0.5 animations:^{ + view.layer.opacity = 1; + }]; UIImageView * lastSlice = slices.lastObject; scrollView.contentSize = CGSizeMake(CGRectGetMaxX(lastSlice.frame), CGRectGetMaxY(lastSlice.frame));