├── 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
--------------------------------------------------------------------------------