├── Example ├── SRC │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── list_camera_icon.imageset │ │ │ ├── Contents.json │ │ │ └── element_5.pdf │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── VideoCollectionReusableView.swift │ ├── VideoCollectionViewCell.swift │ ├── VideoCollectionViewController.swift │ ├── ViewController.swift │ └── Waveform-Bridging-Header.h └── Waveform.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ └── contents.xcworkspacedata │ └── xcuserdata │ ├── developer.xcuserdatad │ └── xcschemes │ │ ├── Release Waveform.xcscheme │ │ ├── Waveform.xcscheme │ │ └── xcschememanagement.plist │ └── qqqqq.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── LICENSE ├── README.md ├── Source ├── DVGAudioWaveformDiagram.swift ├── DVGAudioWaveformDiagramModel.swift ├── DVGWaveformView.swift ├── Data Source │ ├── AudioSamplesReader.swift │ └── ScalableChannelsContainer.swift ├── Utility │ ├── AudioReaderSettings.swift │ ├── AudioSamplesContainer.swift │ ├── Buffer.swift │ ├── Channel.swift │ ├── DiagramGeometry.swift │ ├── Dispatch.swift │ ├── Error.swift │ ├── LogicProvider.swift │ ├── NumberTypes.swift │ ├── Protocols.swift │ └── UIKit+extensions.swift ├── View Model │ ├── ChannelSourceMapper.swift │ ├── DiagramModel.swift │ └── PlotModel.swift └── View │ ├── Diagram.swift │ ├── PlaybackPositionView.swift │ ├── Plot.swift │ └── SelectionView.swift └── Waveform.podspec /Example/SRC/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Waveform 4 | // 5 | // Created by developer on 18/12/15. 6 | // Copyright © 2015 developer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/SRC/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /Example/SRC/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/SRC/Assets.xcassets/list_camera_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "element_5.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/SRC/Assets.xcassets/list_camera_icon.imageset/element_5.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denivip/Waveform/e486197b7b44779aa6b78cb69e740e7fc699c18f/Example/SRC/Assets.xcassets/list_camera_icon.imageset/element_5.pdf -------------------------------------------------------------------------------- /Example/SRC/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/SRC/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 144 | 151 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /Example/SRC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Example/SRC/VideoCollectionReusableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoCollectionReusableView.swift 3 | // Waveform 4 | // 5 | // Created by qqqqq on 18/04/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class VideoCollectionReusableView: UICollectionReusableView { 13 | 14 | @IBOutlet weak var titleLabel: UILabel! 15 | @IBOutlet weak var subtitleLabel: UILabel! 16 | @IBOutlet weak var dateLabel: UILabel! 17 | 18 | override func awakeFromNib() { 19 | super.awakeFromNib() 20 | 21 | self.titleLabel.adjustsFontSizeToFitWidth = true 22 | self.subtitleLabel.adjustsFontSizeToFitWidth = true 23 | self.dateLabel.adjustsFontSizeToFitWidth = true 24 | 25 | self.titleLabel.text = nil; 26 | self.subtitleLabel.text = nil; 27 | self.dateLabel.text = nil; 28 | } 29 | 30 | override func prepareForReuse() { 31 | super.prepareForReuse(); 32 | 33 | self.titleLabel.text = nil; 34 | self.subtitleLabel.text = nil; 35 | self.dateLabel.text = nil; 36 | } 37 | 38 | func configureWithCollection(collection: PHAssetCollection) { 39 | 40 | if collection.localizedTitle != nil { 41 | self.titleLabel.text = collection.localizedTitle 42 | } 43 | 44 | if collection.localizedLocationNames.count > 0 { 45 | if self.titleLabel.text != nil { 46 | self.subtitleLabel.text = collection.localizedLocationNames.first 47 | } else { 48 | self.titleLabel.text = collection.localizedLocationNames.first 49 | } 50 | } 51 | 52 | let date = NSDateFormatter.localizedStringFromDate(collection.startDate!, dateStyle:.LongStyle, timeStyle:.NoStyle) 53 | 54 | if self.titleLabel.text != nil { 55 | self.dateLabel.text = date 56 | } else { 57 | self.titleLabel.text = date; 58 | } 59 | 60 | self.layoutIfNeeded() 61 | } 62 | 63 | override func layoutSubviews() { 64 | super.layoutSubviews() 65 | 66 | self.titleLabel.sizeToFit() 67 | self.subtitleLabel.sizeToFit() 68 | self.dateLabel.sizeToFit() 69 | 70 | let titleLabelTopOffsetWithNoSubtitle: CGFloat = 10; 71 | 72 | let titleLabelTopOffset: CGFloat = 20; 73 | let subtitleLabelBottomOffset: CGFloat = 10; 74 | let horizontalBordersOffset: CGFloat = 15; 75 | let dateLabelWidth: CGFloat = 100 - horizontalBordersOffset; 76 | 77 | if (self.subtitleLabel.text?.characters.count == 0) { 78 | 79 | self.titleLabel.frame = CGRectMake(horizontalBordersOffset, 80 | titleLabelTopOffsetWithNoSubtitle, 81 | CGRectGetWidth(self.bounds) - dateLabelWidth, 82 | CGRectGetHeight(self.bounds) - titleLabelTopOffsetWithNoSubtitle) 83 | 84 | self.dateLabel.frame = CGRectMake(CGRectGetWidth(self.bounds) - dateLabelWidth - horizontalBordersOffset, 85 | 0 + titleLabelTopOffsetWithNoSubtitle, 86 | dateLabelWidth, 87 | CGRectGetHeight(self.bounds) - titleLabelTopOffsetWithNoSubtitle) 88 | } else { 89 | self.titleLabel.frame = CGRectMake(horizontalBordersOffset, 90 | titleLabelTopOffset, 91 | CGRectGetWidth(self.bounds) - dateLabelWidth - 2 * horizontalBordersOffset, 92 | CGRectGetHeight(self.titleLabel.bounds)) 93 | 94 | self.subtitleLabel.frame = CGRectMake(horizontalBordersOffset, 95 | CGRectGetHeight(self.bounds) - subtitleLabelBottomOffset - CGRectGetHeight(self.subtitleLabel.bounds), 96 | CGRectGetWidth(self.bounds) - dateLabelWidth - 2 * horizontalBordersOffset, 97 | CGRectGetHeight(self.subtitleLabel.bounds)) 98 | 99 | self.dateLabel.frame = CGRectMake(CGRectGetWidth(self.bounds) - dateLabelWidth - horizontalBordersOffset, 100 | titleLabelTopOffset, 101 | dateLabelWidth, 102 | CGRectGetHeight(self.dateLabel.bounds) ) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Example/SRC/VideoCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoCollectionViewCell.swift 3 | // Waveform 4 | // 5 | // Created by qqqqq on 18/04/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class VideoCollectionViewCell: UICollectionViewCell { 13 | @IBOutlet weak var imageView: UIImageView! 14 | @IBOutlet weak var timeLabel: UILabel! 15 | @IBOutlet weak var timeLabelContainer: UIView! 16 | 17 | var imageManager = PHCachingImageManager() 18 | var videoSource: PHAsset? { 19 | didSet { 20 | guard let videoSource = videoSource else { 21 | return 22 | } 23 | self.timeLabel.text = self.stringWithTime(videoSource.duration) 24 | self.timeLabelContainer.layer.cornerRadius = self.timeLabelContainer.bounds.size.height/2; 25 | 26 | 27 | imageManager.requestImageForAsset(videoSource, targetSize:self.bounds.size, contentMode:.AspectFill, options:nil, resultHandler: {result, _ in 28 | self.imageView.image = result; 29 | }) 30 | } 31 | } 32 | 33 | func stringWithTime(time:NSTimeInterval) -> String { 34 | var seconds = time; 35 | var minutes = seconds/60; 36 | seconds = seconds%60; 37 | let hours = minutes/60; 38 | minutes = minutes%60; 39 | 40 | if hours > 0 { 41 | return String(format: "%d:%02d:%02d", Int(hours), Int(minutes), Int(seconds)); 42 | } else if minutes > 0 { 43 | return String(format:"%d:%02d", Int(minutes), Int(seconds)); 44 | } else { 45 | return String(format:"0:%02d", Int(seconds)); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Example/SRC/VideoCollectionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Video Editing Template 4 | // 5 | // Created by developer on 03/02/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | @objc 13 | class VideoCollectionViewController: UICollectionViewController { 14 | 15 | var assetsFetchResults: [PHFetchResult] = [] 16 | var moments: [PHAssetCollection] = [] 17 | 18 | var userAlbumsFetchPredicate = NSPredicate(format: "estimatedAssetCount > 0") 19 | var userAlbumsFetchSortDescriptors = [NSSortDescriptor(key: "startDate", ascending: false)] 20 | var inAlbumItemsFetchPredicate = NSPredicate(format: "mediaType == %d", PHAssetMediaType.Video.rawValue) 21 | 22 | var selectedSnapshotView: UIView? 23 | 24 | private struct Constants { 25 | static let collectionViewCellReuseId = "video_collection_view_cell" 26 | static let collectionHeaderReuseId = "video_collection_view_header" 27 | static let collectionFooterReuseId = "FooterView" 28 | 29 | static func collectionSupplementaryElementReuseIdForKind(kind: String) -> String { 30 | switch kind { 31 | case UICollectionElementKindSectionHeader: 32 | return self.collectionHeaderReuseId 33 | case UICollectionElementKindSectionFooter: 34 | return self.collectionFooterReuseId 35 | default: 36 | fatalError() 37 | } 38 | } 39 | 40 | static let preview_width: CGFloat = 150.0 41 | static let preview_height: CGFloat = preview_width * 3/4 42 | } 43 | 44 | // MARK: - Constuctor/Destructor 45 | required init?(coder aDecoder: NSCoder) { 46 | super.init(coder: aDecoder) 47 | PHPhotoLibrary.sharedPhotoLibrary().registerChangeObserver(self) 48 | } 49 | 50 | deinit{ 51 | PHPhotoLibrary.sharedPhotoLibrary().unregisterChangeObserver(self) 52 | } 53 | 54 | // MARK: - View Controller Lifetime 55 | override func viewDidLoad() { 56 | super.viewDidLoad() 57 | 58 | // Hide nav bar bottom line 59 | let navBarHairlineImageView: UIImageView? = navigationController?.barHairlineImageView() 60 | navBarHairlineImageView?.hidden = true; 61 | } 62 | 63 | override func viewDidAppear(animated: Bool) { 64 | super.viewDidAppear(animated) 65 | clearApplicationTmpDirectory() 66 | 67 | if self.assetsFetchResults.count == 0 { 68 | updateAssetsFetchResultsAndMoments() 69 | collectionView?.reloadData() 70 | } 71 | } 72 | } 73 | 74 | 75 | // MARK: - UICollectionViewDataSource 76 | extension VideoCollectionViewController { 77 | override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { 78 | return assetsFetchResults.count 79 | } 80 | 81 | override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 82 | return assetsFetchResults[section].count 83 | } 84 | 85 | override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 86 | 87 | let cell = collectionView.dequeueReusableCellWithReuseIdentifier(Constants.collectionViewCellReuseId, forIndexPath:indexPath) 88 | 89 | if let cell = cell as? VideoCollectionViewCell, 90 | let asset = self.assetsFetchResults[indexPath.section][indexPath.row] as? PHAsset { 91 | cell.videoSource = asset 92 | } else { 93 | fatalError() 94 | } 95 | 96 | return cell 97 | } 98 | 99 | override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView { 100 | 101 | let reuseId = Constants.collectionSupplementaryElementReuseIdForKind(kind) 102 | let reusableView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: reuseId, forIndexPath: indexPath) 103 | 104 | switch (kind, reusableView) { 105 | case (UICollectionElementKindSectionHeader, let headerView as VideoCollectionReusableView): 106 | headerView.configureWithCollection(self.moments[indexPath.section]) 107 | case (UICollectionElementKindSectionFooter, _): () 108 | default: 109 | fatalError() 110 | } 111 | 112 | return reusableView 113 | } 114 | } 115 | 116 | 117 | // MARK: - UICollectionViewDelegateFlowLayout 118 | extension VideoCollectionViewController: UICollectionViewDelegateFlowLayout { 119 | func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { 120 | 121 | let count = floor(self.view.bounds.width/Constants.preview_width); 122 | let width = CGRectGetWidth(self.view.bounds)/count; 123 | return CGSizeMake(width, Constants.preview_height); 124 | } 125 | 126 | func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets { 127 | return UIEdgeInsetsZero 128 | } 129 | } 130 | 131 | // MARK: - Navigation/Transition 132 | extension VideoCollectionViewController { 133 | 134 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 135 | let controller = segue.destinationViewController as! ViewController 136 | let cell = sender as! VideoCollectionViewCell 137 | controller.phAsset = cell.videoSource 138 | } 139 | } 140 | 141 | 142 | // MARK: - Content Update 143 | extension VideoCollectionViewController { 144 | 145 | func updateAssetsFetchResultsAndMoments() { 146 | var assets = [PHFetchResult]() 147 | var moments = [PHAssetCollection]() 148 | 149 | let userAlbumsFetchOptions = PHFetchOptions() 150 | userAlbumsFetchOptions.predicate = userAlbumsFetchPredicate 151 | userAlbumsFetchOptions.sortDescriptors = userAlbumsFetchSortDescriptors 152 | 153 | let userAlbumsFetchResult = PHAssetCollection.fetchMomentsWithOptions(userAlbumsFetchOptions) 154 | 155 | let inAlbumItemsFetchOptions = PHFetchOptions() 156 | inAlbumItemsFetchOptions.predicate = inAlbumItemsFetchPredicate 157 | 158 | userAlbumsFetchResult.enumerateObjectsUsingBlock { (collection, _, _) -> Void in 159 | guard let collection = collection as? PHAssetCollection else { 160 | return 161 | } 162 | 163 | let assetsFetchResult = PHAsset.fetchAssetsInAssetCollection(collection, options: inAlbumItemsFetchOptions) 164 | 165 | if assetsFetchResult.count > 0 { 166 | assets.append(assetsFetchResult) 167 | moments.append(collection) 168 | } 169 | } 170 | 171 | self.moments = moments 172 | self.assetsFetchResults = assets 173 | } 174 | } 175 | 176 | 177 | // MARK: - View Controller Auto Rotation 178 | extension VideoCollectionViewController { 179 | override func shouldAutorotate() -> Bool { 180 | return false 181 | } 182 | 183 | override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { 184 | return .Portrait 185 | } 186 | } 187 | 188 | 189 | // MARK: - PHPhotoLibraryChangeObserver 190 | extension VideoCollectionViewController: PHPhotoLibraryChangeObserver { 191 | func photoLibraryDidChange(changeInstance: PHChange) { 192 | dispatch_async(dispatch_get_main_queue()) { () -> Void in 193 | self.updateAssetsFetchResultsAndMoments() 194 | self.collectionView?.reloadData() 195 | } 196 | } 197 | } 198 | 199 | 200 | // MARK: - 201 | // MARK: - UINavigationController ex 202 | extension UINavigationController { 203 | func barHairlineImageView() -> UIImageView? { 204 | return view.findSubview { $0.bounds.height <= 1.0 } 205 | } 206 | } 207 | 208 | 209 | // MARK: - UIView ex 210 | extension UIView { 211 | func findSubview(predicate: (T) -> (Bool)) -> T? { 212 | 213 | if let self_ = self as? T where predicate(self_) { 214 | return self_ 215 | } 216 | 217 | for subview in subviews { 218 | if let targetView = subview.findSubview(predicate) { 219 | return targetView 220 | } 221 | } 222 | return nil 223 | } 224 | } 225 | 226 | // MARK: - Utility 227 | func clearApplicationTmpDirectory() { 228 | do { 229 | let tmpDirectoryContent = try NSFileManager.defaultManager().contentsOfDirectoryAtPath(NSTemporaryDirectory()) 230 | for file in tmpDirectoryContent { 231 | let filePath = NSTemporaryDirectory() + file 232 | try NSFileManager.defaultManager().removeItemAtPath(filePath) 233 | } 234 | } catch (let error) { 235 | print("\(#function), catched error:\(error)") 236 | } 237 | } 238 | 239 | 240 | -------------------------------------------------------------------------------- /Example/SRC/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Waveform 4 | // 5 | // Created by developer on 18/12/15. 6 | // Copyright © 2015 developer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class ViewController: UIViewController, DVGDiagramMovementsDelegate { 13 | 14 | var phAsset: PHAsset? 15 | @IBOutlet weak var waveformContainerView: UIView! 16 | var waveform: DVGWaveformController! 17 | 18 | override func viewDidLoad() { 19 | 20 | super.viewDidLoad() 21 | 22 | // Waveform Instatiation 23 | self.waveform = DVGWaveformController(containerView: self.waveformContainerView) 24 | 25 | // Get AVAsset from PHAsset 26 | if let phAsset = self.phAsset { 27 | let options = PHContentEditingInputRequestOptions() 28 | options.canHandleAdjustmentData = {_ in return false} 29 | 30 | phAsset.requestContentEditingInputWithOptions(options) { contentEditingInput, info in 31 | print(contentEditingInput, info) 32 | dispatch_async(dispatch_get_main_queue()) { 33 | if let asset = contentEditingInput?.avAsset { 34 | self.waveform.asset = asset 35 | self.configureWaveform() 36 | } else { 37 | print(info[PHContentEditingInputResultIsInCloudKey]) 38 | if let value = info[PHContentEditingInputResultIsInCloudKey] as? Int where value == 1 { 39 | self.showAlert("Load video from iCloud first") 40 | } else { 41 | self.showAlert("Can't get audio from this video.") 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | func showAlert(message: String) { 50 | let alert = UIAlertController(title: nil, message: message, preferredStyle: .Alert) 51 | alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil)) 52 | self.presentViewController(alert, animated: true, completion: nil) 53 | } 54 | 55 | func configureWaveform() { 56 | self.waveform.movementDelegate = self 57 | let waveform1 = self.waveform.maxValuesWaveform() 58 | waveform1?.lineColor = UIColor.redColor() 59 | 60 | let waveform2 = self.waveform.avgValuesWaveform() 61 | waveform2?.lineColor = UIColor.greenColor() 62 | self.waveform.numberOfPointsOnThePlot = 2000 63 | } 64 | 65 | func diagramDidSelect(dataRange: DataRange) { 66 | print("\(#function), dataRange: \(dataRange)") 67 | } 68 | 69 | func diagramMoved(scale scale: Double, start: Double) { 70 | print("\(#function), scale: \(scale), start: \(start)") 71 | } 72 | 73 | @IBAction func readAudioAndDrawWaveform() { 74 | self.waveform.readAndDrawSynchronously({[weak self] in 75 | if $0 != nil { 76 | print("error:", $0!) 77 | self?.showAlert("Can't read asset") 78 | } else { 79 | print("waveform finished drawing") 80 | } 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Example/SRC/Waveform-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /Example/Waveform.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8BD39F7C1CC5648B005E8947 /* AudioSamplesReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F621CC5648B005E8947 /* AudioSamplesReader.swift */; }; 11 | 8BD39F7D1CC5648B005E8947 /* ScalableChannelsContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F631CC5648B005E8947 /* ScalableChannelsContainer.swift */; }; 12 | 8BD39F7E1CC5648B005E8947 /* DVGAudioWaveformDiagram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F641CC5648B005E8947 /* DVGAudioWaveformDiagram.swift */; }; 13 | 8BD39F7F1CC5648B005E8947 /* DVGAudioWaveformDiagramModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F651CC5648B005E8947 /* DVGAudioWaveformDiagramModel.swift */; }; 14 | 8BD39F801CC5648B005E8947 /* DVGWaveformView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F661CC5648B005E8947 /* DVGWaveformView.swift */; }; 15 | 8BD39F811CC5648B005E8947 /* AudioReaderSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F681CC5648B005E8947 /* AudioReaderSettings.swift */; }; 16 | 8BD39F821CC5648B005E8947 /* AudioSamplesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F691CC5648B005E8947 /* AudioSamplesContainer.swift */; }; 17 | 8BD39F831CC5648B005E8947 /* Buffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F6A1CC5648B005E8947 /* Buffer.swift */; }; 18 | 8BD39F841CC5648B005E8947 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F6B1CC5648B005E8947 /* Channel.swift */; }; 19 | 8BD39F851CC5648B005E8947 /* DiagramGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F6C1CC5648B005E8947 /* DiagramGeometry.swift */; }; 20 | 8BD39F861CC5648B005E8947 /* Dispatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F6D1CC5648B005E8947 /* Dispatch.swift */; }; 21 | 8BD39F871CC5648B005E8947 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F6E1CC5648B005E8947 /* Error.swift */; }; 22 | 8BD39F881CC5648B005E8947 /* LogicProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F6F1CC5648B005E8947 /* LogicProvider.swift */; }; 23 | 8BD39F891CC5648B005E8947 /* NumberTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F701CC5648B005E8947 /* NumberTypes.swift */; }; 24 | 8BD39F8A1CC5648B005E8947 /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F711CC5648B005E8947 /* Protocols.swift */; }; 25 | 8BD39F8B1CC5648B005E8947 /* UIKit+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F721CC5648B005E8947 /* UIKit+extensions.swift */; }; 26 | 8BD39F8C1CC5648B005E8947 /* Diagram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F741CC5648B005E8947 /* Diagram.swift */; }; 27 | 8BD39F8D1CC5648B005E8947 /* PlaybackPositionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F751CC5648B005E8947 /* PlaybackPositionView.swift */; }; 28 | 8BD39F8E1CC5648B005E8947 /* Plot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F761CC5648B005E8947 /* Plot.swift */; }; 29 | 8BD39F8F1CC5648B005E8947 /* SelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F771CC5648B005E8947 /* SelectionView.swift */; }; 30 | 8BD39F901CC5648B005E8947 /* ChannelSourceMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F791CC5648B005E8947 /* ChannelSourceMapper.swift */; }; 31 | 8BD39F911CC5648B005E8947 /* DiagramModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F7A1CC5648B005E8947 /* DiagramModel.swift */; }; 32 | 8BD39F921CC5648B005E8947 /* PlotModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F7B1CC5648B005E8947 /* PlotModel.swift */; }; 33 | 8BD39FA01CC565E9005E8947 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F931CC565E9005E8947 /* AppDelegate.swift */; }; 34 | 8BD39FA11CC565E9005E8947 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8BD39F941CC565E9005E8947 /* Assets.xcassets */; }; 35 | 8BD39FA21CC565E9005E8947 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8BD39F961CC565E9005E8947 /* LaunchScreen.storyboard */; }; 36 | 8BD39FA31CC565E9005E8947 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8BD39F981CC565E9005E8947 /* Main.storyboard */; }; 37 | 8BD39FA41CC565E9005E8947 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 8BD39F9A1CC565E9005E8947 /* Info.plist */; }; 38 | 8BD39FA51CC565E9005E8947 /* VideoCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F9B1CC565E9005E8947 /* VideoCollectionReusableView.swift */; }; 39 | 8BD39FA61CC565E9005E8947 /* VideoCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F9C1CC565E9005E8947 /* VideoCollectionViewCell.swift */; }; 40 | 8BD39FA71CC565E9005E8947 /* VideoCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F9D1CC565E9005E8947 /* VideoCollectionViewController.swift */; }; 41 | 8BD39FA81CC565E9005E8947 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD39F9E1CC565E9005E8947 /* ViewController.swift */; }; 42 | /* End PBXBuildFile section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 8B3F34EE1C243BA6006A4B67 /* Waveform.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Waveform.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 8BD39F621CC5648B005E8947 /* AudioSamplesReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioSamplesReader.swift; sourceTree = ""; }; 47 | 8BD39F631CC5648B005E8947 /* ScalableChannelsContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScalableChannelsContainer.swift; sourceTree = ""; }; 48 | 8BD39F641CC5648B005E8947 /* DVGAudioWaveformDiagram.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DVGAudioWaveformDiagram.swift; sourceTree = ""; }; 49 | 8BD39F651CC5648B005E8947 /* DVGAudioWaveformDiagramModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DVGAudioWaveformDiagramModel.swift; sourceTree = ""; }; 50 | 8BD39F661CC5648B005E8947 /* DVGWaveformView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DVGWaveformView.swift; sourceTree = ""; }; 51 | 8BD39F681CC5648B005E8947 /* AudioReaderSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioReaderSettings.swift; sourceTree = ""; }; 52 | 8BD39F691CC5648B005E8947 /* AudioSamplesContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioSamplesContainer.swift; sourceTree = ""; }; 53 | 8BD39F6A1CC5648B005E8947 /* Buffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Buffer.swift; sourceTree = ""; }; 54 | 8BD39F6B1CC5648B005E8947 /* Channel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Channel.swift; sourceTree = ""; }; 55 | 8BD39F6C1CC5648B005E8947 /* DiagramGeometry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiagramGeometry.swift; sourceTree = ""; }; 56 | 8BD39F6D1CC5648B005E8947 /* Dispatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dispatch.swift; sourceTree = ""; }; 57 | 8BD39F6E1CC5648B005E8947 /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; 58 | 8BD39F6F1CC5648B005E8947 /* LogicProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogicProvider.swift; sourceTree = ""; }; 59 | 8BD39F701CC5648B005E8947 /* NumberTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberTypes.swift; sourceTree = ""; }; 60 | 8BD39F711CC5648B005E8947 /* Protocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Protocols.swift; sourceTree = ""; }; 61 | 8BD39F721CC5648B005E8947 /* UIKit+extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIKit+extensions.swift"; sourceTree = ""; }; 62 | 8BD39F741CC5648B005E8947 /* Diagram.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Diagram.swift; sourceTree = ""; }; 63 | 8BD39F751CC5648B005E8947 /* PlaybackPositionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaybackPositionView.swift; sourceTree = ""; }; 64 | 8BD39F761CC5648B005E8947 /* Plot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Plot.swift; sourceTree = ""; }; 65 | 8BD39F771CC5648B005E8947 /* SelectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectionView.swift; sourceTree = ""; }; 66 | 8BD39F791CC5648B005E8947 /* ChannelSourceMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelSourceMapper.swift; sourceTree = ""; }; 67 | 8BD39F7A1CC5648B005E8947 /* DiagramModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiagramModel.swift; sourceTree = ""; }; 68 | 8BD39F7B1CC5648B005E8947 /* PlotModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlotModel.swift; sourceTree = ""; }; 69 | 8BD39F931CC565E9005E8947 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = SRC/AppDelegate.swift; sourceTree = SOURCE_ROOT; }; 70 | 8BD39F941CC565E9005E8947 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = SRC/Assets.xcassets; sourceTree = SOURCE_ROOT; }; 71 | 8BD39F971CC565E9005E8947 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = LaunchScreen.storyboard; sourceTree = ""; }; 72 | 8BD39F991CC565E9005E8947 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Main.storyboard; sourceTree = ""; }; 73 | 8BD39F9A1CC565E9005E8947 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = SRC/Info.plist; sourceTree = SOURCE_ROOT; }; 74 | 8BD39F9B1CC565E9005E8947 /* VideoCollectionReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VideoCollectionReusableView.swift; path = SRC/VideoCollectionReusableView.swift; sourceTree = SOURCE_ROOT; }; 75 | 8BD39F9C1CC565E9005E8947 /* VideoCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VideoCollectionViewCell.swift; path = SRC/VideoCollectionViewCell.swift; sourceTree = SOURCE_ROOT; }; 76 | 8BD39F9D1CC565E9005E8947 /* VideoCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VideoCollectionViewController.swift; path = SRC/VideoCollectionViewController.swift; sourceTree = SOURCE_ROOT; }; 77 | 8BD39F9E1CC565E9005E8947 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ViewController.swift; path = SRC/ViewController.swift; sourceTree = SOURCE_ROOT; }; 78 | 8BD39F9F1CC565E9005E8947 /* Waveform-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Waveform-Bridging-Header.h"; path = "SRC/Waveform-Bridging-Header.h"; sourceTree = SOURCE_ROOT; }; 79 | /* End PBXFileReference section */ 80 | 81 | /* Begin PBXFrameworksBuildPhase section */ 82 | 8B3F34EB1C243BA6006A4B67 /* Frameworks */ = { 83 | isa = PBXFrameworksBuildPhase; 84 | buildActionMask = 2147483647; 85 | files = ( 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | /* End PBXFrameworksBuildPhase section */ 90 | 91 | /* Begin PBXGroup section */ 92 | 8B3F34E51C243BA6006A4B67 = { 93 | isa = PBXGroup; 94 | children = ( 95 | 8BD39F601CC5648B005E8947 /* Source */, 96 | 8BD39ED91CC559FA005E8947 /* Waveform */, 97 | 8B3F34EF1C243BA6006A4B67 /* Products */, 98 | ); 99 | sourceTree = ""; 100 | }; 101 | 8B3F34EF1C243BA6006A4B67 /* Products */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 8B3F34EE1C243BA6006A4B67 /* Waveform.app */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | 8BD39ED91CC559FA005E8947 /* Waveform */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 8BD39F931CC565E9005E8947 /* AppDelegate.swift */, 113 | 8BD39F941CC565E9005E8947 /* Assets.xcassets */, 114 | 8BD39F951CC565E9005E8947 /* Base.lproj */, 115 | 8BD39F9A1CC565E9005E8947 /* Info.plist */, 116 | 8BD39F9B1CC565E9005E8947 /* VideoCollectionReusableView.swift */, 117 | 8BD39F9C1CC565E9005E8947 /* VideoCollectionViewCell.swift */, 118 | 8BD39F9D1CC565E9005E8947 /* VideoCollectionViewController.swift */, 119 | 8BD39F9E1CC565E9005E8947 /* ViewController.swift */, 120 | 8BD39F9F1CC565E9005E8947 /* Waveform-Bridging-Header.h */, 121 | ); 122 | path = Waveform; 123 | sourceTree = ""; 124 | }; 125 | 8BD39F601CC5648B005E8947 /* Source */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 8BD39F611CC5648B005E8947 /* Data Source */, 129 | 8BD39F641CC5648B005E8947 /* DVGAudioWaveformDiagram.swift */, 130 | 8BD39F651CC5648B005E8947 /* DVGAudioWaveformDiagramModel.swift */, 131 | 8BD39F661CC5648B005E8947 /* DVGWaveformView.swift */, 132 | 8BD39F671CC5648B005E8947 /* Utility */, 133 | 8BD39F731CC5648B005E8947 /* View */, 134 | 8BD39F781CC5648B005E8947 /* View Model */, 135 | ); 136 | name = Source; 137 | path = ../Source; 138 | sourceTree = ""; 139 | }; 140 | 8BD39F611CC5648B005E8947 /* Data Source */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 8BD39F621CC5648B005E8947 /* AudioSamplesReader.swift */, 144 | 8BD39F631CC5648B005E8947 /* ScalableChannelsContainer.swift */, 145 | ); 146 | path = "Data Source"; 147 | sourceTree = ""; 148 | }; 149 | 8BD39F671CC5648B005E8947 /* Utility */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 8BD39F681CC5648B005E8947 /* AudioReaderSettings.swift */, 153 | 8BD39F691CC5648B005E8947 /* AudioSamplesContainer.swift */, 154 | 8BD39F6A1CC5648B005E8947 /* Buffer.swift */, 155 | 8BD39F6B1CC5648B005E8947 /* Channel.swift */, 156 | 8BD39F6C1CC5648B005E8947 /* DiagramGeometry.swift */, 157 | 8BD39F6D1CC5648B005E8947 /* Dispatch.swift */, 158 | 8BD39F6E1CC5648B005E8947 /* Error.swift */, 159 | 8BD39F6F1CC5648B005E8947 /* LogicProvider.swift */, 160 | 8BD39F701CC5648B005E8947 /* NumberTypes.swift */, 161 | 8BD39F711CC5648B005E8947 /* Protocols.swift */, 162 | 8BD39F721CC5648B005E8947 /* UIKit+extensions.swift */, 163 | ); 164 | path = Utility; 165 | sourceTree = ""; 166 | }; 167 | 8BD39F731CC5648B005E8947 /* View */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 8BD39F741CC5648B005E8947 /* Diagram.swift */, 171 | 8BD39F751CC5648B005E8947 /* PlaybackPositionView.swift */, 172 | 8BD39F761CC5648B005E8947 /* Plot.swift */, 173 | 8BD39F771CC5648B005E8947 /* SelectionView.swift */, 174 | ); 175 | path = View; 176 | sourceTree = ""; 177 | }; 178 | 8BD39F781CC5648B005E8947 /* View Model */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | 8BD39F791CC5648B005E8947 /* ChannelSourceMapper.swift */, 182 | 8BD39F7A1CC5648B005E8947 /* DiagramModel.swift */, 183 | 8BD39F7B1CC5648B005E8947 /* PlotModel.swift */, 184 | ); 185 | path = "View Model"; 186 | sourceTree = ""; 187 | }; 188 | 8BD39F951CC565E9005E8947 /* Base.lproj */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | 8BD39F961CC565E9005E8947 /* LaunchScreen.storyboard */, 192 | 8BD39F981CC565E9005E8947 /* Main.storyboard */, 193 | ); 194 | name = Base.lproj; 195 | path = SRC/Base.lproj; 196 | sourceTree = SOURCE_ROOT; 197 | }; 198 | /* End PBXGroup section */ 199 | 200 | /* Begin PBXNativeTarget section */ 201 | 8B3F34ED1C243BA6006A4B67 /* Waveform */ = { 202 | isa = PBXNativeTarget; 203 | buildConfigurationList = 8B3F35001C243BA7006A4B67 /* Build configuration list for PBXNativeTarget "Waveform" */; 204 | buildPhases = ( 205 | 8B3F34EA1C243BA6006A4B67 /* Sources */, 206 | 8B3F34EB1C243BA6006A4B67 /* Frameworks */, 207 | 8B3F34EC1C243BA6006A4B67 /* Resources */, 208 | ); 209 | buildRules = ( 210 | ); 211 | dependencies = ( 212 | ); 213 | name = Waveform; 214 | productName = Waveform; 215 | productReference = 8B3F34EE1C243BA6006A4B67 /* Waveform.app */; 216 | productType = "com.apple.product-type.application"; 217 | }; 218 | /* End PBXNativeTarget section */ 219 | 220 | /* Begin PBXProject section */ 221 | 8B3F34E61C243BA6006A4B67 /* Project object */ = { 222 | isa = PBXProject; 223 | attributes = { 224 | LastSwiftUpdateCheck = 0720; 225 | LastUpgradeCheck = 0730; 226 | ORGANIZATIONNAME = developer; 227 | TargetAttributes = { 228 | 8B3F34ED1C243BA6006A4B67 = { 229 | CreatedOnToolsVersion = 7.2; 230 | }; 231 | }; 232 | }; 233 | buildConfigurationList = 8B3F34E91C243BA6006A4B67 /* Build configuration list for PBXProject "Waveform" */; 234 | compatibilityVersion = "Xcode 3.2"; 235 | developmentRegion = English; 236 | hasScannedForEncodings = 0; 237 | knownRegions = ( 238 | en, 239 | Base, 240 | ); 241 | mainGroup = 8B3F34E51C243BA6006A4B67; 242 | productRefGroup = 8B3F34EF1C243BA6006A4B67 /* Products */; 243 | projectDirPath = ""; 244 | projectRoot = ""; 245 | targets = ( 246 | 8B3F34ED1C243BA6006A4B67 /* Waveform */, 247 | ); 248 | }; 249 | /* End PBXProject section */ 250 | 251 | /* Begin PBXResourcesBuildPhase section */ 252 | 8B3F34EC1C243BA6006A4B67 /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | 8BD39FA41CC565E9005E8947 /* Info.plist in Resources */, 257 | 8BD39FA31CC565E9005E8947 /* Main.storyboard in Resources */, 258 | 8BD39FA11CC565E9005E8947 /* Assets.xcassets in Resources */, 259 | 8BD39FA21CC565E9005E8947 /* LaunchScreen.storyboard in Resources */, 260 | ); 261 | runOnlyForDeploymentPostprocessing = 0; 262 | }; 263 | /* End PBXResourcesBuildPhase section */ 264 | 265 | /* Begin PBXSourcesBuildPhase section */ 266 | 8B3F34EA1C243BA6006A4B67 /* Sources */ = { 267 | isa = PBXSourcesBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | 8BD39FA01CC565E9005E8947 /* AppDelegate.swift in Sources */, 271 | 8BD39F7F1CC5648B005E8947 /* DVGAudioWaveformDiagramModel.swift in Sources */, 272 | 8BD39FA61CC565E9005E8947 /* VideoCollectionViewCell.swift in Sources */, 273 | 8BD39F7D1CC5648B005E8947 /* ScalableChannelsContainer.swift in Sources */, 274 | 8BD39F811CC5648B005E8947 /* AudioReaderSettings.swift in Sources */, 275 | 8BD39F8E1CC5648B005E8947 /* Plot.swift in Sources */, 276 | 8BD39FA71CC565E9005E8947 /* VideoCollectionViewController.swift in Sources */, 277 | 8BD39F901CC5648B005E8947 /* ChannelSourceMapper.swift in Sources */, 278 | 8BD39F921CC5648B005E8947 /* PlotModel.swift in Sources */, 279 | 8BD39F821CC5648B005E8947 /* AudioSamplesContainer.swift in Sources */, 280 | 8BD39F841CC5648B005E8947 /* Channel.swift in Sources */, 281 | 8BD39F851CC5648B005E8947 /* DiagramGeometry.swift in Sources */, 282 | 8BD39F871CC5648B005E8947 /* Error.swift in Sources */, 283 | 8BD39F861CC5648B005E8947 /* Dispatch.swift in Sources */, 284 | 8BD39F8C1CC5648B005E8947 /* Diagram.swift in Sources */, 285 | 8BD39F801CC5648B005E8947 /* DVGWaveformView.swift in Sources */, 286 | 8BD39F831CC5648B005E8947 /* Buffer.swift in Sources */, 287 | 8BD39F8D1CC5648B005E8947 /* PlaybackPositionView.swift in Sources */, 288 | 8BD39FA81CC565E9005E8947 /* ViewController.swift in Sources */, 289 | 8BD39F7C1CC5648B005E8947 /* AudioSamplesReader.swift in Sources */, 290 | 8BD39F8B1CC5648B005E8947 /* UIKit+extensions.swift in Sources */, 291 | 8BD39F8A1CC5648B005E8947 /* Protocols.swift in Sources */, 292 | 8BD39F911CC5648B005E8947 /* DiagramModel.swift in Sources */, 293 | 8BD39FA51CC565E9005E8947 /* VideoCollectionReusableView.swift in Sources */, 294 | 8BD39F891CC5648B005E8947 /* NumberTypes.swift in Sources */, 295 | 8BD39F881CC5648B005E8947 /* LogicProvider.swift in Sources */, 296 | 8BD39F7E1CC5648B005E8947 /* DVGAudioWaveformDiagram.swift in Sources */, 297 | 8BD39F8F1CC5648B005E8947 /* SelectionView.swift in Sources */, 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | }; 301 | /* End PBXSourcesBuildPhase section */ 302 | 303 | /* Begin PBXVariantGroup section */ 304 | 8BD39F961CC565E9005E8947 /* LaunchScreen.storyboard */ = { 305 | isa = PBXVariantGroup; 306 | children = ( 307 | 8BD39F971CC565E9005E8947 /* Base */, 308 | ); 309 | name = LaunchScreen.storyboard; 310 | sourceTree = ""; 311 | }; 312 | 8BD39F981CC565E9005E8947 /* Main.storyboard */ = { 313 | isa = PBXVariantGroup; 314 | children = ( 315 | 8BD39F991CC565E9005E8947 /* Base */, 316 | ); 317 | name = Main.storyboard; 318 | sourceTree = ""; 319 | }; 320 | /* End PBXVariantGroup section */ 321 | 322 | /* Begin XCBuildConfiguration section */ 323 | 8B3F34FE1C243BA7006A4B67 /* Debug */ = { 324 | isa = XCBuildConfiguration; 325 | buildSettings = { 326 | ALWAYS_SEARCH_USER_PATHS = NO; 327 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 328 | CLANG_CXX_LIBRARY = "libc++"; 329 | CLANG_ENABLE_MODULES = YES; 330 | CLANG_ENABLE_OBJC_ARC = YES; 331 | CLANG_WARN_BOOL_CONVERSION = YES; 332 | CLANG_WARN_CONSTANT_CONVERSION = YES; 333 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 334 | CLANG_WARN_EMPTY_BODY = YES; 335 | CLANG_WARN_ENUM_CONVERSION = YES; 336 | CLANG_WARN_INT_CONVERSION = YES; 337 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 338 | CLANG_WARN_UNREACHABLE_CODE = YES; 339 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 340 | CODE_SIGN_IDENTITY = "iPhone Developer"; 341 | COPY_PHASE_STRIP = NO; 342 | DEBUG_INFORMATION_FORMAT = dwarf; 343 | ENABLE_STRICT_OBJC_MSGSEND = YES; 344 | ENABLE_TESTABILITY = YES; 345 | GCC_C_LANGUAGE_STANDARD = gnu99; 346 | GCC_DYNAMIC_NO_PIC = NO; 347 | GCC_NO_COMMON_BLOCKS = YES; 348 | GCC_OPTIMIZATION_LEVEL = 0; 349 | GCC_PREPROCESSOR_DEFINITIONS = ( 350 | "DEBUG=1", 351 | "$(inherited)", 352 | ); 353 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 354 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 355 | GCC_WARN_UNDECLARED_SELECTOR = YES; 356 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 357 | GCC_WARN_UNUSED_FUNCTION = YES; 358 | GCC_WARN_UNUSED_VARIABLE = YES; 359 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 360 | MTL_ENABLE_DEBUG_INFO = YES; 361 | ONLY_ACTIVE_ARCH = YES; 362 | SDKROOT = iphoneos; 363 | SWIFT_OBJC_INTERFACE_HEADER_NAME = "Waveform-Swift.h"; 364 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 365 | TARGETED_DEVICE_FAMILY = "1,2"; 366 | }; 367 | name = Debug; 368 | }; 369 | 8B3F34FF1C243BA7006A4B67 /* Release */ = { 370 | isa = XCBuildConfiguration; 371 | buildSettings = { 372 | ALWAYS_SEARCH_USER_PATHS = NO; 373 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 374 | CLANG_CXX_LIBRARY = "libc++"; 375 | CLANG_ENABLE_MODULES = YES; 376 | CLANG_ENABLE_OBJC_ARC = YES; 377 | CLANG_WARN_BOOL_CONVERSION = YES; 378 | CLANG_WARN_CONSTANT_CONVERSION = YES; 379 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 380 | CLANG_WARN_EMPTY_BODY = YES; 381 | CLANG_WARN_ENUM_CONVERSION = YES; 382 | CLANG_WARN_INT_CONVERSION = YES; 383 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 384 | CLANG_WARN_UNREACHABLE_CODE = YES; 385 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 386 | COPY_PHASE_STRIP = NO; 387 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 388 | ENABLE_NS_ASSERTIONS = NO; 389 | ENABLE_STRICT_OBJC_MSGSEND = YES; 390 | GCC_C_LANGUAGE_STANDARD = gnu99; 391 | GCC_NO_COMMON_BLOCKS = YES; 392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 393 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 394 | GCC_WARN_UNDECLARED_SELECTOR = YES; 395 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 396 | GCC_WARN_UNUSED_FUNCTION = YES; 397 | GCC_WARN_UNUSED_VARIABLE = YES; 398 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 399 | MTL_ENABLE_DEBUG_INFO = NO; 400 | SDKROOT = iphoneos; 401 | SWIFT_OBJC_INTERFACE_HEADER_NAME = "Waveform-Swift.h"; 402 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 403 | TARGETED_DEVICE_FAMILY = "1,2"; 404 | VALIDATE_PRODUCT = YES; 405 | }; 406 | name = Release; 407 | }; 408 | 8B3F35011C243BA7006A4B67 /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | buildSettings = { 411 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 412 | CLANG_ENABLE_MODULES = YES; 413 | CODE_SIGN_IDENTITY = "iPhone Developer"; 414 | INFOPLIST_FILE = SRC/Info.plist; 415 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 416 | PRODUCT_BUNDLE_IDENTIFIER = denivip.Waveform; 417 | PRODUCT_NAME = "$(TARGET_NAME)"; 418 | SWIFT_OBJC_BRIDGING_HEADER = "SRC/Waveform-Bridging-Header.h"; 419 | }; 420 | name = Debug; 421 | }; 422 | 8B3F35021C243BA7006A4B67 /* Release */ = { 423 | isa = XCBuildConfiguration; 424 | buildSettings = { 425 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 426 | CLANG_ENABLE_MODULES = YES; 427 | CODE_SIGN_IDENTITY = "iPhone Developer"; 428 | INFOPLIST_FILE = SRC/Info.plist; 429 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 430 | PRODUCT_BUNDLE_IDENTIFIER = denivip.Waveform; 431 | PRODUCT_NAME = "$(TARGET_NAME)"; 432 | SWIFT_OBJC_BRIDGING_HEADER = "SRC/Waveform-Bridging-Header.h"; 433 | }; 434 | name = Release; 435 | }; 436 | /* End XCBuildConfiguration section */ 437 | 438 | /* Begin XCConfigurationList section */ 439 | 8B3F34E91C243BA6006A4B67 /* Build configuration list for PBXProject "Waveform" */ = { 440 | isa = XCConfigurationList; 441 | buildConfigurations = ( 442 | 8B3F34FE1C243BA7006A4B67 /* Debug */, 443 | 8B3F34FF1C243BA7006A4B67 /* Release */, 444 | ); 445 | defaultConfigurationIsVisible = 0; 446 | defaultConfigurationName = Release; 447 | }; 448 | 8B3F35001C243BA7006A4B67 /* Build configuration list for PBXNativeTarget "Waveform" */ = { 449 | isa = XCConfigurationList; 450 | buildConfigurations = ( 451 | 8B3F35011C243BA7006A4B67 /* Debug */, 452 | 8B3F35021C243BA7006A4B67 /* Release */, 453 | ); 454 | defaultConfigurationIsVisible = 0; 455 | defaultConfigurationName = Release; 456 | }; 457 | /* End XCConfigurationList section */ 458 | }; 459 | rootObject = 8B3F34E61C243BA6006A4B67 /* Project object */; 460 | } 461 | -------------------------------------------------------------------------------- /Example/Waveform.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Waveform.xcodeproj/xcuserdata/developer.xcuserdatad/xcschemes/Release Waveform.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Example/Waveform.xcodeproj/xcuserdata/developer.xcuserdatad/xcschemes/Waveform.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Example/Waveform.xcodeproj/xcuserdata/developer.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Release Waveform.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | Waveform.xcscheme 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 5B7003BC1C4FFB7B00EA6313 21 | 22 | primary 23 | 24 | 25 | 8B3F34ED1C243BA6006A4B67 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Example/Waveform.xcodeproj/xcuserdata/qqqqq.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Waveform WMO.xcscheme 8 | 9 | orderHint 10 | 2 11 | 12 | Waveform.xcscheme 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 5B7003BC1C4FFB7B00EA6313 21 | 22 | primary 23 | 24 | 25 | 8B3F34ED1C243BA6006A4B67 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 DENIVIP Media 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Waveform 2 | 3 | Waveform is an lightweight framework for drawing waveform from AVAsset written in Swift. It was developed for a high performance waveforms rendering and manipulation in the [Background audio noise removal app - Denoise](https://itunes.apple.com/us/app/denoise-background-noise-removal/id946423200?mt=8) 4 | 5 | ## Installation 6 | The easiest way to use Waveform is via `Cocoapods`. Simply add Waveform to your Podfile like so 7 | 8 | ``` 9 | use_frameworks! 10 | pod 'Waveform' 11 | ``` 12 | 13 | ## How to use 14 | TODO 15 | -------------------------------------------------------------------------------- /Source/DVGAudioWaveformDiagram.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DVGDiagram.swift 3 | // Waveform 4 | // 5 | // Created by developer on 26/01/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DVGAudioWaveformDiagram: UIView { 12 | 13 | var panToSelect: UIPanGestureRecognizer! 14 | var tapToSelect: UILongPressGestureRecognizer! 15 | var pan: UIPanGestureRecognizer! 16 | var pinch: UIPinchGestureRecognizer! 17 | 18 | var selectionView: SelectionView! 19 | var playbackPositionView: PlaybackPositionView! 20 | var waveformDiagramView: Diagram! 21 | 22 | weak var delegate: DVGDiagramDelegate? { 23 | didSet { 24 | waveformDiagramView.delegate = delegate 25 | } 26 | } 27 | weak var dataSource: DiagramDataSource? { 28 | didSet { 29 | waveformDiagramView.dataSource = dataSource 30 | } 31 | } 32 | 33 | override init(frame: CGRect) { 34 | super.init(frame: frame) 35 | self.setup() 36 | } 37 | 38 | required init?(coder aDecoder: NSCoder) { 39 | super.init(coder: aDecoder) 40 | self.setup() 41 | } 42 | 43 | func setup(){ 44 | self.setupAudioWaveformView() 45 | self.setupGestures() 46 | self.setupSelectionView() 47 | self.setupPlaybackPositionView() 48 | } 49 | 50 | func setupAudioWaveformView() { 51 | let waveformDiagramView = Diagram() 52 | addSubview(waveformDiagramView) 53 | waveformDiagramView.translatesAutoresizingMaskIntoConstraints = false 54 | waveformDiagramView.attachBoundsOfSuperview() 55 | self.waveformDiagramView = waveformDiagramView 56 | } 57 | 58 | func setupGestures() { 59 | // New gestures 60 | let panToSelect = UIPanGestureRecognizer(target: self, action: #selector(DVGAudioWaveformDiagram.handlePanToSelect(_:))) 61 | panToSelect.delegate = self 62 | self.addGestureRecognizer(panToSelect) 63 | self.panToSelect = panToSelect 64 | panToSelect.maximumNumberOfTouches = 1 65 | 66 | let tap = UILongPressGestureRecognizer(target: self, action: #selector(DVGAudioWaveformDiagram.handleTapToSelect(_:))) 67 | tap.delegate = self 68 | tap.minimumPressDuration = 0.08 69 | self.addGestureRecognizer(tap) 70 | self.tapToSelect = tap 71 | tap.numberOfTouchesRequired = 1 72 | 73 | let pinch = UIPinchGestureRecognizer(target: self, action: #selector(DVGAudioWaveformDiagram.handlePinch(_:))) 74 | pinch.delegate = self 75 | self.addGestureRecognizer(pinch) 76 | self.pinch = pinch 77 | 78 | let pan = UIPanGestureRecognizer(target: self, action: #selector(DVGAudioWaveformDiagram.handlePan(_:))) 79 | self.addGestureRecognizer(pan) 80 | self.pan = pan 81 | self.pan.minimumNumberOfTouches = 2 82 | } 83 | 84 | func setupSelectionView() { 85 | let selectionView = SelectionView() 86 | self.addSubview(selectionView) 87 | selectionView.translatesAutoresizingMaskIntoConstraints = false 88 | selectionView.attachBoundsOfSuperview() 89 | self.selectionView = selectionView 90 | } 91 | 92 | func setupPlaybackPositionView() { 93 | let playbackPositionView = PlaybackPositionView() 94 | self.addSubview(playbackPositionView) 95 | playbackPositionView.translatesAutoresizingMaskIntoConstraints = false 96 | playbackPositionView.attachBoundsOfSuperview() 97 | self.playbackPositionView = playbackPositionView 98 | } 99 | 100 | private var panStartLocation: CGFloat? 101 | 102 | func handlePanToSelect(pan: UIPanGestureRecognizer) { 103 | switch pan.state { 104 | case .Began: 105 | if self.panStartLocation == nil { 106 | self.panStartLocation = pan.locationInView(self).x 107 | } 108 | self.configureSelectionFromPosition(panStartLocation!, toPosition: pan.locationInView(self).x) 109 | case .Failed: 110 | print("pan failed") 111 | case .Ended: 112 | // notify delegate 113 | self.panStartLocation = nil 114 | if let selection = self.selection { 115 | self.delegate?.diagramDidSelect(selection) 116 | } 117 | break 118 | case .Changed: 119 | self.configureSelectionFromPosition(panStartLocation!, toPosition: pan.locationInView(self).x) 120 | default: 121 | break 122 | } 123 | } 124 | 125 | func handleTapToSelect(tap: UILongPressGestureRecognizer) { 126 | 127 | switch self.pinch.state { 128 | case .Began, .Changed: 129 | return 130 | default: 131 | break 132 | } 133 | 134 | switch tap.state { 135 | case .Began: 136 | self.panStartLocation = pan.locationInView(self).x 137 | self.configureSelectionFromPosition(tap.locationInView(self).x) 138 | case .Failed: 139 | print("tap failed") 140 | case .Ended: 141 | self.configureSelectionFromPosition(tap.locationInView(self).x) 142 | if let selection = self.selection { 143 | self.delegate?.diagramDidSelect(selection) 144 | } 145 | default:() 146 | } 147 | } 148 | 149 | func handlePinch(gesture: UIPinchGestureRecognizer) { 150 | self.selectionView.selection = nil 151 | 152 | let k = self.playbackRelativePosition 153 | self.playbackRelativePosition = k 154 | 155 | let l = self.selection 156 | self.selection = l 157 | 158 | switch gesture.state { 159 | case .Changed: 160 | let scale = gesture.scale 161 | let locationX = gesture.locationInView(gesture.view).x 162 | let relativeLocation = locationX/gesture.view!.bounds.width 163 | self.delegate?.zoomAt(relativeLocation, relativeScale: scale) 164 | gesture.scale = 1.0 165 | case .Ended: 166 | print(self.dataSource?.geometry ) 167 | default:() 168 | } 169 | } 170 | 171 | func handlePan(gesture: UIPanGestureRecognizer) { 172 | self.selectionView.selection = nil 173 | switch gesture.state { 174 | case .Changed: 175 | let deltaX = gesture.translationInView(gesture.view).x 176 | let relativeDeltaX = deltaX/gesture.view!.bounds.width 177 | self.delegate?.moveByDistance(relativeDeltaX) 178 | gesture.setTranslation(.zero, inView: gesture.view) 179 | default:() 180 | } 181 | 182 | let k = self.playbackRelativePosition 183 | self.playbackRelativePosition = k 184 | 185 | let l = self.selection 186 | self.selection = l 187 | } 188 | 189 | var minSelectionWidth: CGFloat = 40.0 190 | var playbackRelativePosition: CGFloat? = nil { 191 | didSet { 192 | if let playbackRelativePosition = playbackRelativePosition, 193 | let viewModel = self.dataSource { 194 | self.playbackPositionView.position = playbackRelativePosition.convertToGeometry(viewModel.geometry) 195 | } else { 196 | self.playbackPositionView.position = nil 197 | } 198 | } 199 | } 200 | var selection: DataRange? = nil { 201 | didSet { 202 | if let relativeSelection = selection, 203 | let viewModel = self.dataSource { 204 | self.selectionView.selection = relativeSelection.convertToGeometry(viewModel.geometry) 205 | } else { 206 | self.selectionView.selection = nil 207 | } 208 | } 209 | } 210 | func configureSelectionFromPosition(_startPosition: CGFloat) { 211 | self.configureSelectionFromPosition(_startPosition, toPosition: _startPosition) 212 | } 213 | 214 | func configureSelectionFromPosition(_startPosition: CGFloat, toPosition _endPosition: CGFloat) { 215 | 216 | //TODO: move geometry logic to viewModel (create it first) 217 | var startPosition = min(_endPosition, _startPosition) 218 | var endPosition = max(_endPosition, _startPosition) 219 | 220 | startPosition = startPosition - minSelectionWidth/2 221 | endPosition = endPosition + minSelectionWidth/2 222 | 223 | startPosition = max(0, startPosition) 224 | endPosition = min(endPosition, self.bounds.width) 225 | 226 | let width = max(endPosition - startPosition, minSelectionWidth) 227 | 228 | let range = DataRange( 229 | location: startPosition / self.bounds.width, 230 | length: width / self.bounds.width) 231 | 232 | self.selection = range.convertFromGeometry(self.dataSource!.geometry) 233 | } 234 | 235 | } 236 | 237 | extension DVGAudioWaveformDiagram: UIGestureRecognizerDelegate { 238 | func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { 239 | switch (gestureRecognizer, otherGestureRecognizer) { 240 | case (panToSelect, tapToSelect): 241 | fallthrough 242 | case (tapToSelect, panToSelect): 243 | return true 244 | default: 245 | return false 246 | } 247 | } 248 | 249 | override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool { 250 | if gestureRecognizer == self.panToSelect { 251 | self.tapToSelect.enabled = false 252 | self.tapToSelect.enabled = true 253 | } 254 | return true 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /Source/DVGAudioWaveformDiagramModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DVGDiagramModel.swift 3 | // Waveform 4 | // 5 | // Created by qqqqq on 17/04/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | protocol DVGDiagramMovementsDelegate: class { 13 | func diagramDidSelect(dataRange: DataRange) 14 | func diagramMoved(scale scale: Double, start: Double) 15 | } 16 | 17 | class DVGAudioWaveformDiagramModel: DiagramModel, DVGDiagramDelegate { 18 | 19 | weak var movementsDelegate: DVGDiagramMovementsDelegate? 20 | 21 | var originalSelection: DataRange? 22 | 23 | func diagramDidSelect(dataRange: DataRange) { 24 | self.originalSelection = dataRange 25 | self.movementsDelegate?.diagramDidSelect(dataRange) 26 | } 27 | 28 | override func zoom(start start: CGFloat, scale: CGFloat) { 29 | super.zoom(start: start, scale: scale) 30 | self.movementsDelegate?.diagramMoved(scale: self.geometry.scale, start: self.geometry.start) 31 | } 32 | override func moveToPosition(start: CGFloat) { 33 | super.moveToPosition(start) 34 | self.movementsDelegate?.diagramMoved(scale: self.geometry.scale, start: self.geometry.start) 35 | } 36 | } -------------------------------------------------------------------------------- /Source/DVGWaveformView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DVGWaveformView.swift 3 | // Waveform 4 | // 5 | // Created by developer on 22/01/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | import AVFoundation 12 | 13 | /// Entry point for Waveform UI Component 14 | /// Creates all needed data sources, view models and views and sets needed dependencies between them 15 | /// By default draws waveforms for max values and average values (see. LogicProvider class) 16 | 17 | class DVGWaveformController: NSObject { 18 | 19 | //MARK: - Initialization 20 | 21 | convenience init(containerView: UIView) { 22 | self.init() 23 | self.addPlotViewToContainerView(containerView) 24 | } 25 | override init() { 26 | super.init() 27 | } 28 | 29 | //MARK: - 30 | //MARK: - Configuration 31 | //MARK: - Internal configuration 32 | func addPlotViewToContainerView(containerView: UIView) { 33 | let diagram = DVGAudioWaveformDiagram() 34 | self.diagram = diagram 35 | self.diagram.translatesAutoresizingMaskIntoConstraints = false 36 | containerView.addSubview(self.diagram) 37 | self.diagram.attachBoundsOfSuperview() 38 | } 39 | 40 | func configure() { 41 | waveformDataSource.neededSamplesCount = numberOfPointsOnThePlot 42 | // Prepare Plot Model with DataSource 43 | self.addDataSource(waveformDataSource) 44 | self.diagramViewModel.channelsSource = channelSourceMapper 45 | 46 | // Set plot model to plot view 47 | diagram.delegate = diagramViewModel 48 | diagram.dataSource = diagramViewModel 49 | 50 | diagramViewModel.movementsDelegate = self 51 | } 52 | 53 | //MARK: - For external configuration 54 | func waveformWithIdentifier(identifier: String) -> Plot? { 55 | return self.diagram.waveformDiagramView.plotWithIdentifier(identifier) 56 | } 57 | 58 | func maxValuesWaveform() -> Plot? { 59 | return waveformWithIdentifier(waveformDataSource.identifier + "." + "AudioMaxValueLogicProvider") 60 | } 61 | 62 | func avgValuesWaveform() -> Plot? { 63 | return waveformWithIdentifier(waveformDataSource.identifier + "." + "AudioAverageValueLogicProvider") 64 | } 65 | 66 | //MARK: - 67 | //MARK: - Reading 68 | func readAndDrawSynchronously(completion: (ErrorType?) -> ()) { 69 | 70 | if self.samplesReader == nil { 71 | completion(NSError(domain: "",code: -1, userInfo: nil)) 72 | return 73 | } 74 | 75 | self.diagram.waveformDiagramView.startSynchingWithDataSource() 76 | let date = NSDate() 77 | 78 | self.samplesReader.readAudioFormat { 79 | [weak self] (format, error) in 80 | 81 | guard let _ = format else { 82 | completion(error) 83 | self?.diagram.waveformDiagramView.stopSynchingWithDataSource() 84 | return 85 | } 86 | 87 | self?.samplesReader.readSamples(completion: { (error) in 88 | completion(error) 89 | print("time: \(-date.timeIntervalSinceNow)") 90 | }) 91 | } 92 | } 93 | 94 | func addDataSource(dataSource: ChannelSource) { 95 | channelSourceMapper.addChannelSource(dataSource) 96 | } 97 | 98 | //MARK: - 99 | //MARK: - Private vars 100 | private var diagram: DVGAudioWaveformDiagram! 101 | private var diagramViewModel = DVGAudioWaveformDiagramModel() 102 | private var samplesReader: AudioSamplesReader! 103 | private var waveformDataSource = ScalableChannelsContainer() 104 | private var channelSourceMapper = ChannelSourceMapper() 105 | 106 | //MARK: - Public vars 107 | weak var movementDelegate: DVGDiagramMovementsDelegate? 108 | var asset: AVAsset? { 109 | didSet { 110 | if let asset = asset { 111 | self.samplesReader = AudioSamplesReader(asset: asset) 112 | self.configure() 113 | self.samplesReader.samplesHandler = waveformDataSource 114 | } 115 | } 116 | } 117 | var numberOfPointsOnThePlot = 512 { 118 | didSet { 119 | waveformDataSource.neededSamplesCount = numberOfPointsOnThePlot 120 | } 121 | } 122 | var start: CGFloat = 0.0 123 | var scale: CGFloat = 1.0 124 | 125 | @objc var playbackRelativePosition: NSNumber? { 126 | get { return self._playbackRelativePosition } 127 | set { self._playbackRelativePosition = newValue == nil ? nil : CGFloat(newValue!) } 128 | } 129 | 130 | var _playbackRelativePosition: CGFloat? { 131 | get { return self.diagram.playbackRelativePosition } 132 | set { self.diagram.playbackRelativePosition = newValue } 133 | } 134 | 135 | var progress: NSProgress { 136 | return self.samplesReader.progress 137 | } 138 | } 139 | 140 | ////MARK: - DiagramViewModelDelegate 141 | extension DVGWaveformController: DVGDiagramMovementsDelegate { 142 | func diagramDidSelect(dataRange: DataRange) { 143 | self.movementDelegate?.diagramDidSelect(dataRange) 144 | } 145 | func diagramMoved(scale scale: Double, start: Double) { 146 | self.waveformDataSource.reset(DataRange(location: start, length: 1/scale)) 147 | self.movementDelegate?.diagramMoved(scale: scale, start: start) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Source/Data Source/AudioSamplesReader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DVGAudioSource.swift 3 | // Waveform 4 | // 5 | // Created by developer on 22/12/15. 6 | // Copyright © 2015 developer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AVFoundation 11 | 12 | struct Constants { 13 | static var DefaultAudioFormat = AudioFormat.init(samplesRate: 44100, bitsDepth: 16, numberOfChannels: 2) 14 | } 15 | 16 | @objc final 17 | class AudioSamplesReader: NSObject { 18 | 19 | var asset: AVAsset 20 | init(asset: AVAsset) { 21 | self.asset = asset 22 | super.init() 23 | } 24 | 25 | private var readingRoutine: SamplesReadingRoutine? 26 | 27 | weak var samplesHandler: AudioSamplesHandler? 28 | 29 | var nativeAudioFormat: AudioFormat? 30 | var samplesReadAudioFormat = Constants.DefaultAudioFormat 31 | 32 | var progress = NSProgress() 33 | 34 | func readAudioFormat(completionBlock: (AudioFormat?, SamplesReaderError?) -> ()) { 35 | dispatch_asynch_on_global_processing_queue { 36 | do { 37 | self.nativeAudioFormat = try self.readAudioFormat() 38 | completionBlock(self.nativeAudioFormat, nil) 39 | 40 | } catch let error as SamplesReaderError { 41 | 42 | completionBlock(nil, error) 43 | 44 | } catch let error { 45 | 46 | fatalError("unknown error:\(error)") 47 | } 48 | } 49 | } 50 | 51 | func readAudioFormat() throws -> AudioFormat { 52 | 53 | let formatDescription = try soundFormatDescription() 54 | 55 | print("DEBUG Audio format description => \(formatDescription)") 56 | let asbd = CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription).memory 57 | let format = AudioFormat(samplesRate: Int(asbd.mSampleRate), bitsDepth: Int(asbd.mBitsPerChannel), numberOfChannels: Int(asbd.mChannelsPerFrame)) 58 | nativeAudioFormat = format 59 | return format 60 | } 61 | 62 | func assetAudioTrack() throws -> AVAssetTrack { 63 | guard let sound = asset.tracksWithMediaType(AVMediaTypeAudio).first else { 64 | throw SamplesReaderError.NoSound 65 | } 66 | return sound 67 | } 68 | 69 | func soundFormatDescription() throws -> CMAudioFormatDescription { 70 | guard let formatDescription = try assetAudioTrack().formatDescriptions.first else { 71 | throw SamplesReaderError.InvalidAudioFormat 72 | } 73 | return formatDescription as! CMAudioFormatDescription 74 | } 75 | 76 | private func audioReadingSettingsForFormat(audioFormat: AudioFormat) -> [String: AnyObject] { 77 | return [ 78 | AVFormatIDKey : NSNumber(unsignedInt: kAudioFormatLinearPCM), 79 | AVSampleRateKey : audioFormat.samplesRate, 80 | AVNumberOfChannelsKey : audioFormat.numberOfChannels, 81 | AVLinearPCMBitDepthKey : audioFormat.bitsDepth > 0 ? audioFormat.bitsDepth : 16, 82 | AVLinearPCMIsBigEndianKey : false, 83 | AVLinearPCMIsFloatKey : false, 84 | AVLinearPCMIsNonInterleaved : false 85 | ] 86 | } 87 | 88 | func readSamples(audioFormat: AudioFormat? = nil, completion: (ErrorType?) -> ()) { 89 | dispatch_asynch_on_global_processing_queue({ 90 | try self.readSamples(audioFormat) }, onCatch: completion) 91 | } 92 | 93 | func readSamples(audioFormat: AudioFormat? = nil) throws { 94 | if let format = audioFormat { 95 | samplesReadAudioFormat = format 96 | } 97 | try self.prepareForReading() 98 | try self.read() 99 | } 100 | 101 | private func prepareForReading() throws { 102 | 103 | let sound = try assetAudioTrack() 104 | 105 | let assetReader: AVAssetReader 106 | do { 107 | assetReader = try AVAssetReader(asset: asset) 108 | } catch let error as NSError { 109 | throw SamplesReaderError.UnknownError(error) 110 | } 111 | 112 | let settings = audioReadingSettingsForFormat(samplesReadAudioFormat) 113 | 114 | let readerOutput = AVAssetReaderTrackOutput(track: sound, outputSettings: settings) 115 | 116 | assetReader.addOutput(readerOutput) 117 | assetReader.timeRange = CMTimeRange(start: kCMTimeZero, duration: asset.duration) 118 | 119 | if samplesHandler == nil { 120 | print("\(#function)[\(#line)] Caution!!! There is no samples handler") 121 | } 122 | 123 | self.readingRoutine = SamplesReadingRoutine(assetReader: assetReader, readerOutput: readerOutput, audioFormat: samplesReadAudioFormat, samplesHandler: samplesHandler, progress: self.progress) 124 | } 125 | 126 | private func read() throws { 127 | 128 | guard let readingRoutine = readingRoutine else { 129 | throw SamplesReaderError.SampleReaderNotReady 130 | } 131 | try readingRoutine.readSamples() 132 | } 133 | } 134 | 135 | final class SamplesReadingRoutine { 136 | 137 | let assetReader: AVAssetReader 138 | let readerOutput: AVAssetReaderOutput 139 | let audioFormat: AudioFormat 140 | weak var samplesHandler: AudioSamplesHandler? 141 | 142 | let progress: NSProgress 143 | 144 | lazy var estimatedSamplesCount: Int = { 145 | return Int(self.assetReader.asset.duration.seconds * Double(self.audioFormat.samplesRate)) 146 | }() 147 | 148 | init(assetReader: AVAssetReader, readerOutput: AVAssetReaderOutput, audioFormat: AudioFormat, samplesHandler: AudioSamplesHandler?, progress: NSProgress) { 149 | self.assetReader = assetReader 150 | self.readerOutput = readerOutput 151 | self.audioFormat = audioFormat 152 | self.samplesHandler = samplesHandler 153 | self.progress = progress 154 | progress.totalUnitCount = Int64(self.estimatedSamplesCount) 155 | } 156 | 157 | var isReading: Bool { 158 | return assetReader.status == .Reading 159 | } 160 | 161 | func startReading() throws { 162 | if !assetReader.startReading() { 163 | throw SamplesReaderError.CantReadSamples(assetReader.error) 164 | } 165 | } 166 | 167 | func cancelReading() { 168 | assetReader.cancelReading() 169 | } 170 | 171 | func readSamples() throws { 172 | self.samplesHandler?.willStartReadSamples(estimatedSampleCount: estimatedSamplesCount) 173 | try startReading() 174 | while isReading { 175 | do { 176 | try readNextSamples() 177 | } catch (_ as NoMoreSampleBuffersAvailable) { 178 | break 179 | } catch { 180 | cancelReading() 181 | throw error 182 | } 183 | } 184 | try checkStatusOfAssetReaderOnComplete() 185 | self.samplesHandler?.didStopReadSamples(Int(self.progress.completedUnitCount)) 186 | } 187 | 188 | func readNextSamples() throws { 189 | guard let sampleBuffer = readerOutput.copyNextSampleBuffer() else { 190 | throw NoMoreSampleBuffersAvailable() 191 | } 192 | 193 | // Get buffer 194 | guard let buffer = CMSampleBufferGetDataBuffer(sampleBuffer) else { 195 | throw SamplesReaderError.UnknownError(nil) 196 | } 197 | 198 | let length = CMBlockBufferGetDataLength(buffer) 199 | 200 | // Append new data 201 | let tempBytes = UnsafeMutablePointer.alloc(length) 202 | var returnedPointer: UnsafeMutablePointer = nil 203 | 204 | if CMBlockBufferAccessDataBytes(buffer, 0, length, tempBytes, &returnedPointer) != kCMBlockBufferNoErr { 205 | throw NoEnoughData() 206 | } 207 | 208 | tempBytes.destroy(length) 209 | tempBytes.dealloc(length) 210 | 211 | let samplesContainer = AudioSamplesContainer(buffer: returnedPointer, length: length, numberOfChannels: audioFormat.numberOfChannels) 212 | samplesHandler?.handleSamples(samplesContainer) 213 | progress.completedUnitCount += samplesContainer.samplesCount 214 | } 215 | 216 | func checkStatusOfAssetReaderOnComplete() throws { 217 | switch assetReader.status { 218 | case .Unknown, .Failed, .Reading: 219 | throw SamplesReaderError.UnknownError(assetReader.error) 220 | case .Cancelled, .Completed: 221 | return 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /Source/Data Source/ScalableChannelsContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DVGAudioAnalyzer.swift 3 | // Denoise 4 | // 5 | // Created by developer on 16/12/15. 6 | // Copyright © 2015 DENIVIP Group. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AVFoundation 11 | 12 | @objc 13 | final 14 | class ScalableChannelsContainer: NSObject, ChannelSource, AudioSamplesHandler { 15 | 16 | override init() { 17 | super.init() 18 | self.createChannelsForDefaultLogicTypes() 19 | } 20 | 21 | //MARK: - 22 | //MARK: - Inner configuration 23 | func configure(neededSamplesCount neededSamplesCount: Int, estimatedSampleCount: Int) { 24 | 25 | print("estimatedSampleCount ", estimatedSampleCount) 26 | 27 | for index in 0..()) 51 | channel.identifier = self.identifier + "." + channel.identifier 52 | channels.append(channel) 53 | 54 | //???: Is there any reason to store Float? 55 | let channel_ = Channel(logicProvider: AudioAverageValueLogicProvider(), buffer: GenericBuffer()) 56 | channel_.identifier = self.identifier + "." + channel_.identifier 57 | channels.append(channel_) 58 | } 59 | 60 | self.channels = channels 61 | } 62 | 63 | 64 | func reset(dataRange: DataRange) { 65 | assert(self.channels.count > 0, "you should configure channels first. see method above") 66 | 67 | let scale = 1.0 / dataRange.length 68 | var scaleIndex = Int(floor(log(scale)/log(Double(scaleInterLevelFactor)))) 69 | scaleIndex = min(self.numberOfScaleLevels - 1, scaleIndex) 70 | if scaleIndex != self.scaleIndex { 71 | self.scaleIndex = scaleIndex 72 | self.onChannelsChanged() 73 | } 74 | } 75 | 76 | func willStartReadSamples(estimatedSampleCount estimatedSampleCount: Int) { 77 | configure(neededSamplesCount: neededSamplesCount, estimatedSampleCount: estimatedSampleCount) 78 | } 79 | 80 | func didStopReadSamples(count: Int) { 81 | for channel in channels { 82 | channel.complete() 83 | } 84 | } 85 | 86 | func handleSamples(samplesContainer: AudioSamplesContainer) { 87 | 88 | for channelIndex in 0.. () = {_ in} 119 | 120 | var channelsCount: Int = 2 121 | 122 | func channelAtIndex(index: Int) -> Channel { 123 | return channels[index + scaleIndex * channelsCount] 124 | } 125 | } 126 | 127 | //MARK: - 128 | //MARK: - Utility 129 | struct DataRange { 130 | let location: Double 131 | let length: Double 132 | 133 | init(location: Double, length: Double) { 134 | self.location = location 135 | self.length = length 136 | } 137 | 138 | init(location: CGFloat, length: CGFloat) { 139 | self.location = Double(location) 140 | self.length = Double(length) 141 | } 142 | 143 | init() { 144 | self.location = 0.0 145 | self.length = 1.0 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Source/Utility/AudioReaderSettings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioReaderSettings.swift 3 | // Waveform 4 | // 5 | // Created by developer on 11/04/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct AudioFormat { 12 | let samplesRate: Int 13 | let bitsDepth: Int 14 | let numberOfChannels: Int 15 | } -------------------------------------------------------------------------------- /Source/Utility/AudioSamplesContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioSamplesContainer.swift 3 | // Waveform 4 | // 5 | // Created by developer on 11/04/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct AudioSamplesContainer { 12 | let buffer: UnsafePointer 13 | let samplesCount: Int 14 | let numberOfChannels: Int 15 | 16 | init(buffer: UnsafePointer, length: Int, numberOfChannels: Int) { 17 | self.buffer = UnsafePointer(buffer) 18 | self.samplesCount = length * sizeof(T)/sizeof(Int16) / numberOfChannels 19 | self.numberOfChannels = numberOfChannels 20 | } 21 | 22 | func sample(channelIndex channelIndex: Int, sampleIndex: Int) -> Int16 { 23 | assert(channelIndex < numberOfChannels) 24 | assert(sampleIndex < samplesCount) 25 | return buffer[numberOfChannels * sampleIndex + channelIndex] 26 | } 27 | } -------------------------------------------------------------------------------- /Source/Utility/Buffer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Buffer.swift 3 | // Channel Performance test 4 | // 5 | // Created by developer on 15/04/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public 12 | class Buffer { 13 | typealias DefaultNumberType = Double 14 | var maxValue = -Double.infinity 15 | var minValue = Double.infinity 16 | var count = 0 17 | var buffer: UnsafeMutablePointer = nil 18 | var _buffer: UnsafeMutablePointer = nil 19 | private var space = 0 20 | 21 | func appendValue(value: Double) { 22 | if maxValue < value { maxValue = value } 23 | if minValue > value { minValue = value } 24 | 25 | if space == count { 26 | let newSpace = max(space * 2, 16) 27 | self.moveSpaceTo(newSpace) 28 | _buffer = UnsafeMutablePointer(buffer) 29 | } 30 | (UnsafeMutablePointer(buffer) + count).initialize(value) 31 | count += 1 32 | } 33 | private 34 | func moveSpaceTo(newSpace: Int) { 35 | let newPtr = UnsafeMutablePointer.alloc(newSpace) 36 | 37 | newPtr.moveInitializeFrom(UnsafeMutablePointer(buffer), count: count) 38 | 39 | buffer.dealloc(count) 40 | 41 | buffer = UnsafeMutablePointer(newPtr) 42 | space = newSpace 43 | } 44 | func valueAtIndex(index: Int) -> Double { 45 | return _buffer[index] 46 | } 47 | } 48 | 49 | final 50 | class GenericBuffer: Buffer { 51 | var __buffer: UnsafeMutablePointer = nil 52 | 53 | override final func appendValue(value: Double) { 54 | 55 | if maxValue < value { maxValue = value } 56 | if minValue > value { minValue = value } 57 | 58 | if space == count { 59 | let newSpace = max(space * 2, 16) 60 | self.moveSpaceTo(newSpace) 61 | } 62 | (__buffer + count).initialize(T(value)) 63 | count += 1 64 | } 65 | 66 | override final func moveSpaceTo(newSpace: Int) { 67 | let newPtr = UnsafeMutablePointer.alloc(newSpace) 68 | 69 | newPtr.moveInitializeFrom(__buffer, count: count) 70 | 71 | __buffer.dealloc(count) 72 | 73 | __buffer = newPtr 74 | space = newSpace 75 | } 76 | override final func valueAtIndex(index: Int) -> Double { 77 | return __buffer[index].double 78 | } 79 | deinit { 80 | __buffer.destroy(space) 81 | __buffer.dealloc(space) 82 | } 83 | } -------------------------------------------------------------------------------- /Source/Utility/Channel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Types.swift 3 | // Virtual Func Test 4 | // 5 | // Created by qqqqq on 09/01/16. 6 | // Copyright © 2016 qqqqq. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | public 11 | final 12 | class Channel { 13 | 14 | let logicProvider: LogicProvider 15 | let buffer: Buffer 16 | public init(logicProvider: LogicProvider, buffer: Buffer = GenericBuffer()) { 17 | self.logicProvider = logicProvider 18 | self.buffer = buffer 19 | self.logicProvider.channel = self 20 | } 21 | 22 | public var blockSize = 1 23 | public var count: Int { return buffer.count } 24 | public var totalCount: Int = 0 25 | lazy public var identifier: String = { return "\(self.logicProvider.dynamicType)" }() 26 | 27 | private var currentBlockSize = 0 28 | public var maxValue: Double { return buffer.maxValue } 29 | public var minValue: Double { return buffer.minValue } 30 | var onUpdate: () -> () = {} 31 | 32 | public subscript(index: Int) -> Double { 33 | get { 34 | return buffer.valueAtIndex(index) 35 | } 36 | } 37 | 38 | public func handleValue(value: U) { 39 | if currentBlockSize == blockSize { 40 | self.clear() 41 | currentBlockSize = 0 42 | } 43 | currentBlockSize += 1 44 | self.logicProvider.handleValue(value.double) 45 | } 46 | 47 | func appendValueToBuffer(value: Double) { 48 | buffer.appendValue(value) 49 | onUpdate() 50 | } 51 | 52 | private func clear() { 53 | self.logicProvider.clear() 54 | } 55 | 56 | public func complete() { 57 | 58 | self.totalCount = self.count 59 | print(self.blockSize, self.count, self.totalCount) 60 | //TODO: Clear odd space 61 | self.clear() 62 | } 63 | } -------------------------------------------------------------------------------- /Source/Utility/DiagramGeometry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Geometry.swift 3 | // Waveform 4 | // 5 | // Created by qqqqq on 17/04/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | struct DiagramGeometry { 13 | var start = 0.0 14 | var scale = 1.0 15 | } 16 | 17 | extension Double { 18 | func convertToGeometry(geometry: DiagramGeometry) -> Double { 19 | return (self - geometry.start) * geometry.scale 20 | } 21 | func convertFromGeometry(geometry: DiagramGeometry) -> Double { 22 | return self/geometry.scale + geometry.start 23 | } 24 | } 25 | 26 | extension CGFloat { 27 | func convertToGeometry(geometry: DiagramGeometry) -> CGFloat { 28 | return CGFloat((Double(self) - geometry.start) * geometry.scale) 29 | } 30 | func convertFromGeometry(geometry: DiagramGeometry) -> CGFloat { 31 | return CGFloat(Double(self)/geometry.scale + geometry.start) 32 | } 33 | } 34 | 35 | extension DataRange { 36 | func convertToGeometry(geometry: DiagramGeometry) -> DataRange { 37 | let location = self.location.convertToGeometry(geometry) 38 | let length = self.length * geometry.scale 39 | return DataRange(location: location, length: length) 40 | } 41 | func convertFromGeometry(geometry: DiagramGeometry) -> DataRange { 42 | let location = self.location.convertFromGeometry(geometry) 43 | let length = self.length / geometry.scale 44 | return DataRange(location: location, length: length) 45 | } 46 | } -------------------------------------------------------------------------------- /Source/Utility/Dispatch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dispatch.swift 3 | // Waveform 4 | // 5 | // Created by developer on 13/04/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let processingQueue = dispatch_queue_create("ru.denivip.waveform.processing", DISPATCH_QUEUE_SERIAL) 12 | 13 | public func dispatch_asynch_on_global_processing_queue(block: dispatch_block_t) { 14 | if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(processingQueue)) { 15 | autoreleasepool(block) 16 | } else { 17 | dispatch_async(processingQueue, block); 18 | } 19 | } 20 | 21 | public func dispatch_asynch_on_global_processing_queue(body: () throws -> (), onCatch: (ErrorType?) -> ()) { 22 | dispatch_asynch_on_global_processing_queue { 23 | do { 24 | try body() 25 | onCatch(nil) 26 | } 27 | catch { 28 | onCatch(error) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Source/Utility/Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Error.swift 3 | // Waveform 4 | // 5 | // Created by developer on 11/04/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum SamplesReaderError: ErrorType { 12 | case NoSound 13 | case InvalidAudioFormat 14 | case CantReadSamples(NSError?) 15 | case UnknownError(NSError?) 16 | case SampleReaderNotReady 17 | } 18 | 19 | struct NoMoreSampleBuffersAvailable: ErrorType {} 20 | struct NoEnoughData: ErrorType {} -------------------------------------------------------------------------------- /Source/Utility/LogicProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogicProvider.swift 3 | // Waveform 4 | // 5 | // Created by developer on 25/01/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public 12 | class LogicProvider { 13 | weak internal var channel: Channel? 14 | public func handleValue(value: Double) {} 15 | public func clear() {} 16 | } 17 | 18 | public 19 | final 20 | class MaxValueLogicProvider: LogicProvider { 21 | private var max: Double? 22 | public override init(){} 23 | 24 | public override func handleValue(value: Double) { 25 | if max == nil { 26 | max = value 27 | } else if value > max! { 28 | max = value 29 | } 30 | } 31 | 32 | public override func clear() { 33 | self.channel?.appendValueToBuffer(max ?? 0) 34 | max = nil 35 | } 36 | } 37 | 38 | public 39 | final 40 | class AverageValueLogicProvider: LogicProvider { 41 | private var summ = 0.0 42 | var count = 0 43 | public override init(){} 44 | 45 | public override func handleValue(value: Double) { 46 | summ = summ + value 47 | count += 1 48 | } 49 | 50 | public override func clear() { 51 | self.channel?.appendValueToBuffer(summ/Double(count)) 52 | summ = 0.0 53 | count = 0 54 | } 55 | } 56 | 57 | 58 | public 59 | final 60 | class AudioMaxValueLogicProvider: LogicProvider { 61 | private var max = Double(Int16.min)//-40.0 62 | public override init(){} 63 | 64 | public override func handleValue(value: Double) { 65 | let value = abs(value) 66 | if value > max { 67 | max = value 68 | } 69 | } 70 | 71 | public override func clear() { 72 | self.channel?.appendValueToBuffer(min(max, Double(Int16.max))) 73 | max = Double(Int16.min)//-40.0 74 | } 75 | } 76 | 77 | public 78 | final 79 | class AudioAverageValueLogicProvider: LogicProvider { 80 | private var summ = 0.0 81 | var count = 0 82 | public override init(){} 83 | 84 | public override func handleValue(value: Double) { 85 | summ = summ + abs(value) 86 | count += 1 87 | } 88 | 89 | public override func clear() { 90 | self.channel?.appendValueToBuffer(min(summ/Double(count), Double(Int16.max))) 91 | summ = 0.0 92 | count = 0 93 | } 94 | } 95 | 96 | public 97 | func measure(block: () -> ()) { 98 | let date = NSDate() 99 | block() 100 | let s = String(format:"%.4f", -date.timeIntervalSinceNow) 101 | print(s) 102 | } 103 | -------------------------------------------------------------------------------- /Source/Utility/NumberTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumberTypes.swift 3 | // Waveform 4 | // 5 | // Created by qqqqq on 19/01/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public 13 | protocol NumberType { 14 | init(_ v: Int) 15 | init(_ v: Int16) 16 | init(_ v: Double) 17 | init(_ v: Float) 18 | init(_ v: CGFloat) 19 | var int: Int { get } 20 | var int16: Int16 { get } 21 | var double: Double { get } 22 | var float: Float { get } 23 | var cgfloat: CGFloat { get } 24 | } 25 | 26 | extension Int: NumberType { 27 | public var int: Int { return Int(self) } 28 | public var int16: Int16 { return Int16(self) } 29 | public var double: Double { return Double(self) } 30 | public var float: Float { return Float(self) } 31 | public var cgfloat: CGFloat { return CGFloat(self) } 32 | } 33 | 34 | extension Int16: NumberType { 35 | public var int: Int { return Int(self) } 36 | public var int16: Int16 { return Int16(self) } 37 | public var double: Double { return Double(self) } 38 | public var float: Float { return Float(self) } 39 | public var cgfloat: CGFloat { return CGFloat(self) } 40 | } 41 | 42 | extension Double: NumberType { 43 | public var int: Int { return Int(self) } 44 | public var int16: Int16 { return Int16(self) } 45 | public var double: Double { return Double(self) } 46 | public var float: Float { return Float(self) } 47 | public var cgfloat: CGFloat { return CGFloat(self) } 48 | } 49 | 50 | extension Float: NumberType { 51 | public var int: Int { return Int(self) } 52 | public var int16: Int16 { return Int16(self) } 53 | public var double: Double { return Double(self) } 54 | public var float: Float { return Float(self) } 55 | public var cgfloat: CGFloat { return CGFloat(self) } 56 | } 57 | 58 | extension CGFloat: NumberType { 59 | public init(_ v: CGFloat) { self = v } 60 | public var int: Int { return Int(self) } 61 | public var int16: Int16 { return Int16(self) } 62 | public var double: Double { return Double(self) } 63 | public var float: Float { return Float(self) } 64 | public var cgfloat: CGFloat { return CGFloat(self) } 65 | } 66 | 67 | public 68 | enum NumberWrapper { 69 | case int(Int) 70 | case double(Double) 71 | 72 | public func value() -> T { 73 | switch self { 74 | case .int(let v): 75 | return T(v) 76 | case .double(let v): 77 | return T(v) 78 | } 79 | } 80 | } 81 | 82 | func abs(number: NumberWrapper) -> NumberWrapper { 83 | switch number { 84 | case .int(let v): 85 | return NumberWrapper( abs(v)) 86 | case .double(let v): 87 | return NumberWrapper( abs(v)) 88 | } 89 | } 90 | 91 | public 92 | extension NumberWrapper { 93 | public init(_ value: Int) { 94 | self = .int(value) 95 | } 96 | public init(_ value: Int16) { 97 | self = .int(Int(value)) 98 | } 99 | public init(_ value: Float) { 100 | self = .double(Double(value)) 101 | } 102 | public init(_ value: CGFloat) { 103 | self = .double(Double(value)) 104 | } 105 | public init(_ value: Double) { 106 | self = .double(value) 107 | } 108 | } 109 | 110 | public 111 | func +(l:NumberWrapper, r: NumberWrapper) -> NumberWrapper { 112 | switch (l, r) { 113 | case (.int(let left), .int(let right)): 114 | return NumberWrapper.int(left + right) 115 | case (.int(let left), .double(let right)): 116 | return NumberWrapper.double(Double(left) + right) 117 | case (.double(let left), .int(let right)): 118 | return NumberWrapper.double(left + Double(right)) 119 | case (.double(let left), .double(let right)): 120 | return NumberWrapper.double(left + right) 121 | } 122 | } 123 | 124 | func /(l:NumberWrapper, r: Int) -> NumberWrapper { 125 | switch l { 126 | case .int(let left): 127 | return NumberWrapper.double(Double(left) / Double(r)) 128 | case .double(let left): 129 | return NumberWrapper.double(left / Double(r)) 130 | } 131 | } 132 | 133 | func /(l:NumberWrapper, r: Double) -> NumberWrapper { 134 | switch l { 135 | case .int(let left): 136 | return NumberWrapper.double(Double(left) / r) 137 | case .double(let left): 138 | return NumberWrapper.double(left / Double(r)) 139 | } 140 | } 141 | 142 | public 143 | func <(l:NumberWrapper, r: NumberWrapper) -> Bool { 144 | switch (l, r) { 145 | case (.int(let left), .int(let right)): 146 | return left < right 147 | case (.int(let left), .double(let right)): 148 | return Double(left) < right 149 | case (.double(let left), .int(let right)): 150 | return left < Double(right) 151 | case (.double(let left), .double(let right)): 152 | return left < right 153 | } 154 | } 155 | 156 | public 157 | func >(l:NumberWrapper, r: NumberWrapper) -> Bool { 158 | switch (l, r) { 159 | case (.int(let left), .int(let right)): 160 | return left > right 161 | case (.int(let left), .double(let right)): 162 | return Double(left) > right 163 | case (.double(let left), .int(let right)): 164 | return left > Double(right) 165 | case (.double(let left), .double(let right)): 166 | return left > right 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /Source/Utility/Protocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiagramView.swift 3 | // Waveform 4 | // 5 | // Created by developer on 22/12/15. 6 | // Copyright © 2015 developer. All rights reserved. 7 | // 8 | 9 | import UIKit.UIView 10 | 11 | protocol PlotDataSource: class { 12 | var identifier: String { get } 13 | var dataSourceFrame: CGRect { get } 14 | var pointsCount: Int { get } 15 | var needsRedraw: Bool { get set } 16 | func updateGeometry() 17 | func pointAtIndex(index: Int) -> CGPoint 18 | } 19 | 20 | protocol DiagramDataSource: class { 21 | var geometry: DiagramGeometry { get } 22 | var onPlotUpdate: () -> () { get set } 23 | var plotDataSourcesCount: Int { get } 24 | func plotDataSourceAtIndex(index: Int) -> PlotDataSource 25 | } 26 | 27 | protocol DiagramDelegate: class { 28 | func zoomAt(zoomAreaCenter: CGFloat, relativeScale: CGFloat) 29 | func moveByDistance(relativeDeltaX: CGFloat) 30 | } 31 | 32 | protocol DVGDiagramDelegate: class, DiagramDelegate { 33 | func diagramDidSelect(dataRange: DataRange) 34 | } 35 | 36 | protocol ChannelSource: class { 37 | var channelsCount: Int { get } 38 | var onChannelsChanged: () -> () { get set } 39 | func channelAtIndex(index: Int) -> Channel 40 | } 41 | 42 | protocol AbstractChannel: class, Identifiable { 43 | var totalCount: Int { get } 44 | var count: Int { get } 45 | var identifier: String { get } 46 | var maxValue: Double { get } 47 | var minValue: Double { get } 48 | 49 | subscript(index: Int) -> Double { get } 50 | func handleValue(value: U) 51 | } 52 | 53 | protocol AudioSamplesHandler: class { 54 | func willStartReadSamples(estimatedSampleCount estimatedSampleCount: Int) 55 | func didStopReadSamples(count: Int) 56 | func handleSamples(samplesContainer: AudioSamplesContainer) 57 | } 58 | 59 | extension AudioSamplesHandler { 60 | func handleSamples(buffer: UnsafePointer, bufferLength: Int, numberOfChannels: Int) { 61 | return self.handleSamples(AudioSamplesContainer.init(buffer: buffer, length: bufferLength, numberOfChannels: numberOfChannels)) 62 | } 63 | } 64 | 65 | protocol Identifiable { 66 | var identifier: String { get } 67 | } -------------------------------------------------------------------------------- /Source/Utility/UIKit+extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIKit+extensions.swift 3 | // Waveform 4 | // 5 | // Created by developer on 22/01/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | func attachBoundsOfSuperview(){ 13 | assert(self.superview != nil, "There are no superview") 14 | let views = ["view": self] 15 | let horizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("|[view]|", options: [], metrics: nil, views: views) 16 | self.superview!.addConstraints(horizontalConstraints) 17 | let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: [], metrics: nil, views: views) 18 | self.superview!.addConstraints(verticalConstraints) 19 | } 20 | } -------------------------------------------------------------------------------- /Source/View Model/ChannelSourceMapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataSourceMapper.swift 3 | // Waveform 4 | // 5 | // Created by qqqqq on 17/04/16. 6 | // Copyright © 2016 developer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ChannelSourceMapper: ChannelSource { 12 | 13 | var channelSources: [ChannelSource] = [] 14 | func addChannelSource(channelSource: ChannelSource) { 15 | channelSources.append(channelSource) 16 | channelSource.onChannelsChanged = { [weak self] in self?.onChannelsChanged() } 17 | } 18 | 19 | var channelsCount: Int { 20 | return channelSources.reduce(0) { $0.0 + $0.1.channelsCount } 21 | } 22 | 23 | var onChannelsChanged: () -> () = {_ in} 24 | func channelAtIndex(index: Int) -> Channel { 25 | var tmpIndex = index 26 | for channelSource in channelSources { 27 | if tmpIndex < channelSource.channelsCount { 28 | return channelSource.channelAtIndex(tmpIndex) 29 | } 30 | tmpIndex -= channelSource.channelsCount 31 | } 32 | fatalError() 33 | } 34 | } -------------------------------------------------------------------------------- /Source/View Model/DiagramModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiagramModel.swift 3 | // Waveform 4 | // 5 | // Created by developer on 22/12/15. 6 | // Copyright © 2015 developer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class DiagramModel: NSObject, DiagramDataSource { 13 | 14 | weak var channelsSource: ChannelSource? { 15 | didSet{ 16 | channelsSource?.onChannelsChanged = { 17 | [weak self] in 18 | self?.resetChannelsFromDataSources() 19 | } 20 | self.resetChannelsFromDataSources() 21 | } 22 | } 23 | 24 | private var viewModels = [PlotModel]() 25 | 26 | var geometry = DiagramGeometry() 27 | var onPlotUpdate: () -> () = {} 28 | var onGeometryUpdate: () -> () = {} 29 | var plotDataSourcesCount: Int { return self.viewModels.count } 30 | func plotDataSourceAtIndex(index: Int) -> PlotDataSource { 31 | return self.viewModels[index] 32 | } 33 | 34 | func resetChannelsFromDataSources() { 35 | guard let channelsSource = channelsSource else { 36 | onPlotUpdate() 37 | return 38 | } 39 | self.adjustViewModelsCountWithCount(channelsSource.channelsCount) 40 | for index in 0.. count { 60 | for _ in count.. PlotModel? { 67 | for viewModel in self.viewModels { 68 | if viewModel.identifier == identifier { 69 | return viewModel 70 | } 71 | } 72 | return nil 73 | } 74 | 75 | func maxWafeformBounds() -> CGRect { 76 | var maxHeight: CGFloat = 0.1 77 | guard let channelsSource = channelsSource else { 78 | return .zero 79 | } 80 | for index in 0.. maxHeight { 83 | maxHeight = CGFloat(channel.maxValue) 84 | } 85 | } 86 | return CGRect(origin: .zero, size: CGSize(width: 1.0, height: maxHeight)) 87 | } 88 | 89 | func absoluteRangeFromRelativeRange(range: DataRange) -> DataRange { 90 | return DataRange(location: range.location.convertFromGeometry(self.geometry), length: range.length/self.geometry.scale) 91 | } 92 | } 93 | 94 | extension DiagramModel: DiagramDelegate { 95 | func zoom(start start: CGFloat, scale: CGFloat) { 96 | self.geometry = DiagramGeometry(start: Double(start), scale: Double(scale)) 97 | for viewModel in self.viewModels { 98 | viewModel.updateGeometry() 99 | } 100 | } 101 | 102 | func zoomAt(zoomAreaCenter: CGFloat, relativeScale: CGFloat) { 103 | let newScale = max(1.0, relativeScale * CGFloat(self.geometry.scale)) 104 | var start = CGFloat(self.geometry.start) + zoomAreaCenter * (1/CGFloat(self.geometry.scale) - 1/newScale) 105 | start = max(0, min(start, 1 - 1/newScale)) 106 | self.zoom(start: start, scale: newScale) 107 | } 108 | 109 | func moveToPosition(start: CGFloat) { 110 | self.geometry.start = max(0, min(Double(start), 1 - 1/self.geometry.scale)) 111 | for viewModel in self.viewModels { 112 | viewModel.updateGeometry() 113 | } 114 | } 115 | 116 | func moveByDistance(relativeDeltaX: CGFloat) { 117 | let relativeStart = CGFloat(self.geometry.start) - relativeDeltaX / CGFloat(self.geometry.scale) 118 | self.moveToPosition(relativeStart) 119 | } 120 | } -------------------------------------------------------------------------------- /Source/View Model/PlotModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlotModel.swift 3 | // Waveform 4 | // 5 | // Created by developer on 22/12/15. 6 | // Copyright © 2015 developer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | 12 | class PlotModel: NSObject, PlotDataSource { 13 | 14 | weak var diagramModel: DiagramModel? 15 | var channel: Channel? { 16 | didSet{ 17 | self.identifier = channel?.identifier ?? "" 18 | channel?.onUpdate = { [weak self] in 19 | self?.updateGeometry() 20 | } 21 | self.updateGeometry() 22 | } 23 | } 24 | 25 | var pointsCount = 0 26 | var bounds = CGSize(width: 1.0, height: 1.0) 27 | var scale: CGFloat { return CGFloat(self.diagramModel?.geometry.scale ?? 1.0) } 28 | var start: CGFloat { return CGFloat(self.diagramModel?.geometry.start ?? 0.0) } 29 | 30 | var scaledDx: CGFloat = 0 31 | var scaledStartX: CGFloat = 0 32 | var startIndex: Int = 0 33 | 34 | var dataSourceFrame: CGRect { 35 | 36 | if let diagramModel = self.diagramModel { 37 | return diagramModel.maxWafeformBounds() 38 | } 39 | 40 | if let valuesCollection = self.channel { 41 | return CGRect(x: 0, y: 0, width: 1, height: valuesCollection.maxValue) 42 | } 43 | 44 | return .zero 45 | } 46 | var identifier = "" 47 | var needsRedraw = false 48 | 49 | func pointAtIndex(index: Int) -> CGPoint { 50 | guard let channel = self.channel else { 51 | return .zero 52 | } 53 | 54 | let pointX = scaledStartX + scaledDx * CGFloat(index) 55 | let pointIndex = startIndex + index 56 | 57 | let pointY: CGFloat 58 | if pointIndex < channel.count { 59 | pointY = CGFloat(channel[pointIndex]) 60 | } else { 61 | pointY = 0 62 | } 63 | 64 | return CGPoint(x: pointX, y: CGFloat(pointY)) 65 | } 66 | 67 | func updateGeometry() { 68 | 69 | if channel == nil { 70 | self.pointsCount = 0 71 | return 72 | } 73 | 74 | if channel!.totalCount < 2 { 75 | self.pointsCount = 0 76 | return 77 | } 78 | 79 | let dx = 1.0/(CGFloat(channel!.totalCount) - 1) 80 | scaledDx = dx * scale 81 | startIndex = Int(ceil(start/dx)) 82 | 83 | scaledStartX = (CGFloat(startIndex) * dx - start) * scale 84 | 85 | var count = Int(ceil((1 - scaledStartX)/scaledDx + 0.000001)) 86 | count = max(0, min(count, self.channel!.count - startIndex)) 87 | 88 | self.pointsCount = count 89 | 90 | //TODO: syncronization needed 91 | self.needsRedraw = true 92 | } 93 | 94 | deinit { 95 | self.channel = nil 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Source/View/Diagram.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Diagram.swift 3 | // Waveform 4 | // 5 | // Created by developer on 22/12/15. 6 | // Copyright © 2015 developer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class Diagram: UIView { 12 | 13 | var containerView: UIView! 14 | 15 | weak var delegate: DiagramDelegate? 16 | weak var dataSource: DiagramDataSource? { 17 | didSet{ 18 | self.resetWaveforms() 19 | dataSource?.onPlotUpdate = { [weak self] in self?.resetWaveforms() } 20 | } 21 | } 22 | 23 | func resetWaveforms() { 24 | 25 | guard let dataSource = self.dataSource else { 26 | self.plots.forEach{ $0.removeFromSuperview() } 27 | self.plots.removeAll() 28 | return 29 | } 30 | 31 | adjustPlotsNumberWithCount(dataSource.plotDataSourcesCount) 32 | 33 | for index in 0.. Plot { 91 | 92 | let plot = Plot(frame: self.bounds) 93 | plot.dataSource = dataSource 94 | 95 | self.plots.append(plot) 96 | 97 | self.containerView.addSubview(plot) 98 | 99 | plot.translatesAutoresizingMaskIntoConstraints = false 100 | plot.attachBoundsOfSuperview() 101 | 102 | return plot 103 | } 104 | 105 | func plotWithIdentifier(identifier: String) -> Plot? { 106 | for plot in self.plots { 107 | if plot.identifier == identifier { 108 | return plot 109 | } 110 | } 111 | return nil 112 | } 113 | 114 | func redraw() { 115 | for plot in self.plots { 116 | if let dataSource = plot.dataSource where dataSource.needsRedraw { 117 | plot.redraw() 118 | dataSource.needsRedraw = false 119 | } 120 | } 121 | } 122 | 123 | func startSynchingWithDataSource() { 124 | if self.displayLink != nil { 125 | self.displayLink?.invalidate() 126 | self.displayLink = nil 127 | } 128 | let displayLink = CADisplayLink.init(target: self, selector: #selector(Diagram.redraw)) 129 | displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes) 130 | self.displayLink = displayLink 131 | } 132 | 133 | func stopSynchingWithDataSource() { 134 | self.displayLink?.invalidate() 135 | self.displayLink = nil 136 | } 137 | } -------------------------------------------------------------------------------- /Source/View/PlaybackPositionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DVGPlaybackPositionView.swift 3 | // Denoise 4 | // 5 | // Created by developer on 29/01/16. 6 | // Copyright © 2016 DENIVIP Group. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PlaybackPositionView: UIView { 12 | 13 | init() { 14 | super.init(frame: .zero) 15 | self.opaque = false 16 | } 17 | 18 | required init?(coder aDecoder: NSCoder) { 19 | super.init(coder: aDecoder) 20 | self.opaque = false 21 | } 22 | 23 | override func drawRect(rect: CGRect) { 24 | super.drawRect(rect) 25 | guard let relativePosition = self.position else { 26 | return 27 | } 28 | 29 | if relativePosition < 0 || relativePosition > 1 { 30 | return 31 | } 32 | 33 | let position = (self.bounds.width - lineWidth) * relativePosition + lineWidth/2 34 | 35 | guard let context = UIGraphicsGetCurrentContext() else { 36 | fatalError("No context") 37 | } 38 | 39 | CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor) 40 | CGContextSetLineWidth(context, lineWidth) 41 | 42 | 43 | 44 | let cursor = CGPathCreateMutable() 45 | CGPathMoveToPoint(cursor, nil, position, 0) 46 | CGPathAddLineToPoint(cursor, nil, position, self.bounds.height) 47 | CGContextAddPath(context, cursor) 48 | 49 | CGContextStrokePath(context) 50 | 51 | } 52 | 53 | /// Value from 0 to 1 54 | /// Setting value causes setNeedsDisplay method call 55 | /// Setting nil causes removing cursor 56 | var position: CGFloat? { 57 | didSet { self.setNeedsDisplay() } 58 | } 59 | var lineColor = UIColor.whiteColor() 60 | var lineWidth: CGFloat = 2.0 61 | } 62 | -------------------------------------------------------------------------------- /Source/View/Plot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Plot.swift 3 | // Waveform 4 | // 5 | // Created by developer on 18/12/15. 6 | // Copyright © 2015 developer. All rights reserved. 7 | // 8 | 9 | import UIKit.UIControl 10 | 11 | class Plot: UIView { 12 | 13 | weak var dataSource: PlotDataSource? { 14 | didSet { 15 | identifier = dataSource?.identifier ?? "" 16 | } 17 | } 18 | 19 | var lineColor: UIColor = .blackColor() { 20 | didSet{ 21 | // self.pathLayer.strokeColor = lineColor.CGColor 22 | } 23 | } 24 | 25 | var identifier: String = "" 26 | 27 | private var pathLayer: CAShapeLayer! 28 | 29 | required init?(coder aDecoder: NSCoder) { 30 | super.init(coder: aDecoder) 31 | self.opaque = false 32 | } 33 | 34 | convenience init(){ 35 | self.init(frame: CGRectZero) 36 | self.opaque = false 37 | } 38 | 39 | override init(frame: CGRect) { 40 | super.init(frame: frame) 41 | self.opaque = false 42 | } 43 | 44 | func setupPathLayer() { 45 | 46 | self.pathLayer = CAShapeLayer() 47 | self.pathLayer.strokeColor = UIColor.blackColor().CGColor 48 | self.pathLayer.lineWidth = 1.0 49 | self.layer.addSublayer(self.pathLayer) 50 | 51 | self.pathLayer.drawsAsynchronously = true 52 | } 53 | 54 | override func layoutSubviews() { 55 | super.layoutSubviews() 56 | self.redraw() 57 | } 58 | 59 | func redraw() { 60 | self.setNeedsDisplay() 61 | } 62 | 63 | override func drawRect(rect: CGRect) { 64 | guard let context = UIGraphicsGetCurrentContext() else { 65 | return 66 | } 67 | 68 | CGContextSetLineWidth(context, 1)///UIScreen.mainScreen().scale) 69 | CGContextAddPath(context, self.newPathPart()) 70 | CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor) 71 | CGContextSetInterpolationQuality(context, .None); 72 | CGContextSetAllowsAntialiasing(context, false); 73 | CGContextSetShouldAntialias(context, false); 74 | CGContextStrokePath(context) 75 | } 76 | 77 | private func newPathPart() -> CGPathRef { 78 | 79 | let lineWidth: CGFloat = 1 80 | 81 | guard let dataSource = self.dataSource else { 82 | return CGPathCreateMutable() 83 | } 84 | 85 | let currentCount = dataSource.pointsCount 86 | let sourceBounds = dataSource.dataSourceFrame.size 87 | 88 | let mPath = CGPathCreateMutable() 89 | CGPathMoveToPoint(mPath, nil, 0, self.bounds.midY - lineWidth/2) 90 | 91 | let wProportion = self.bounds.size.width / sourceBounds.width 92 | let hPropostion = self.bounds.size.height / sourceBounds.height 93 | 94 | for index in 0.. "MIT", :file => "LICENSE" } 11 | s.author = { "Anton Belousov" => "belousov@denivip.ru" } 12 | 13 | s.homepage = "https://github.com/denivip/Waveform" 14 | 15 | s.source = { :git => "https://github.com/denivip/Waveform.git", :tag => "0.0.2" } 16 | s.source_files = "Source/**/*.{swift}" 17 | s.frameworks = "Foundation", "UIKit", "AVFoundation", "CoreMedia" 18 | end --------------------------------------------------------------------------------