-
Notifications
You must be signed in to change notification settings - Fork 33
/
SISinusWaveView.m
186 lines (151 loc) · 5.78 KB
/
SISinusWaveView.m
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
29
30
31
32
33
34
35
36
37
38
39
40
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
96
97
98
99
100
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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
//
// SISinusWaveView.m
//
// Created by Raffael Hannemann on 12/28/13.
// Copyright (c) 2013 Raffael Hannemann. All rights reserved.
//
#import "SISinusWaveView.h"
#import <AVFoundation/AVFoundation.h>
#import <CoreAudio/CoreAudio.h>
@implementation SISinusWaveView {
NSTimer *_levelTimer;
}
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Set up default values
_frequency = 1.5;
_phase = 0;
_amplitude = 1.0;
_waveColor = [NSColor whiteColor];
_backgroundColor = [NSColor clearColor];
_idleAmplitude = 0.1;
_dampingFactor = 0.8;
_waves = 5;
_phaseShift = -0.25;
_density = 15.0;
_marginLeft = 0;
_marginRight = 0;
_lineWidth = 2.0;
self.listen = YES;
// Create a recorder instance, recording to /dev/null to trash the data immediately
NSURL *url = [NSURL fileURLWithPath:@"/dev/null"];
NSError *error = nil;
_recorder = [[AVAudioRecorder alloc] initWithURL:url settings:@{
AVSampleRateKey: @44100,
AVFormatIDKey: @(kAudioFormatAppleLossless),
AVNumberOfChannelsKey: @1,
AVEncoderAudioQualityKey: @(AVAudioQualityMax)
} error:&error];
if (!_recorder || error) {
NSLog(@"WARNING: %@ could not create a recorder instance (%@).", self, error.localizedDescription);
} else {
[_recorder prepareToRecord];
_recorder.meteringEnabled = YES;
}
}
return self;
}
#pragma mark - Customize the Audio Plot
-(void)awakeFromNib {
[self setListen:YES];
}
- (void) setListen:(BOOL)listen {
_listen = listen;
if (_listen) {
[_recorder record];
if (_levelTimer)
[_levelTimer invalidate];
_levelTimer = [NSTimer scheduledTimerWithTimeInterval:0.025 target:self selector:@selector(recorderDidRecord:) userInfo: nil repeats: YES];
} else {
[_recorder stop];
if (_levelTimer)
[_levelTimer invalidate];
_amplitude = 0;
}
[self setNeedsDisplay:YES];
}
- (void) recorderDidRecord: (NSTimer *) timer {
[_recorder updateMeters];
int requiredTickes = 2; // Alter this to draw more or less often
tick = (tick+1)%requiredTickes;
// Get the recorder's current average power for the first channel, sanitize the value.
float value = pow(10, (0.05 * [_recorder averagePowerForChannel:0])) > 0.05 ? 0.1 : 0;
/// If we defined the current sound level as the amplitude of the wave, the wave would jitter very nervously.
/// To avoid this, we use an inert amplitude that lifts slowly if the value is currently high, and damps itself
/// if the value decreases.
if (value > _dampingAmplitude) _dampingAmplitude += (fmin(value,1.0)-_dampingAmplitude)/4.0;
else if (value<0.01) _dampingAmplitude *= _dampingFactor;
_phase += _phaseShift;
_amplitude = fmax( fmin(_dampingAmplitude*20, 1.0), _idleAmplitude);
[self setNeedsDisplay:tick==0];
}
#pragma mark - Drawing
- (void)drawRect:(NSRect)dirtyRect {
if ([self isHidden])
return;
if (!(self.window.occlusionState & NSWindowOcclusionStateVisible))
return;
if (_clearOnDraw) {
[_backgroundColor set];
NSRectFill(self.bounds);
}
float halfHeight = NSHeight(self.bounds)/2;
float width = NSWidth(self.bounds)-_marginLeft-_marginRight;
float mid = width /2.0;
float stepLength = _density / width;
// We draw multiple sinus waves, with equal phases but altered amplitudes, multiplied by a parable function.
for(int i=0;i<_waves+1;i++) {
[[NSGraphicsContext currentContext] saveGraphicsState];
NSGraphicsContext * nsGraphicsContext = [NSGraphicsContext currentContext];
CGContextRef context = (CGContextRef) [nsGraphicsContext graphicsPort];
// The first wave is drawn with a 2px stroke width, all others a with 1px stroke width.
CGContextSetLineWidth(context, (i==0)? _lineWidth:_lineWidth*.5 );
const float maxAmplitude = halfHeight-4; // 4 corresponds to twice the stroke width
// Progress is a value between 1.0 and -0.5, determined by the current wave idx, which is used to alter the wave's amplitude.
float progress = 1.0-(float)i/_waves;
float normedAmplitude = (1.5*progress-0.5)*_amplitude;
[[self colorForLineAtLocation:0 percentalLength:0] set];
CGContextMoveToPoint(context, 0, halfHeight);
CGContextAddLineToPoint(context, _marginLeft, halfHeight);
CGContextStrokePath(context);
CGFloat lastX = _marginLeft;
CGFloat lastY = halfHeight;
for(float x = 0; x<width+_density; x+=_density) {
CGContextMoveToPoint(context, lastX, lastY);
// We use a parable to scale the sinus wave, that has its peak in the middle of the view.
float scaling = -pow(1/mid*(x-mid),2)+1;
if (!_oscillating) {
normedAmplitude = _idleAmplitude;
}
float y = scaling *maxAmplitude *normedAmplitude *sinf(2 *M_PI *(x / width) *_frequency +_phase) + halfHeight;
CGContextAddLineToPoint(context, x+_marginLeft, y);
CGFloat location = x/(width+_density);
// Determine the color for this part of the wave, and alter its alpha value
NSColor *stepColor = [self colorForLineAtLocation:location percentalLength:stepLength];
CGFloat red = 0;
CGFloat green = 0;
CGFloat blue = 0;
CGFloat alpha = 0;
const CGFloat *components = CGColorGetComponents(stepColor.CGColor);
red = components[0];
green = components[1];
blue = components[2];
alpha = components[3];
NSColor *alteredColor = [NSColor colorWithCalibratedRed:red green:green blue:blue alpha:alpha *(progress/3.0*2+1.0/3.0)];
[alteredColor set];
CGContextStrokePath(context);
lastX = x+_marginLeft;
lastY = y;
}
[[self colorForLineAtLocation:1.0 percentalLength:0] set];
CGContextMoveToPoint(context, lastX, halfHeight);
CGContextAddLineToPoint(context, NSWidth(self.bounds), halfHeight);
CGContextStrokePath(context);
}
}
- (NSColor *) colorForLineAtLocation: (CGFloat) location percentalLength: (CGFloat) length {
return self.waveColor;
}
@end