当时写这个demo,是现公司出的面试题,题目的大致意思是通过随机数映射成实物,例如0~255,可以映射成颜色,然后问还有没有其他好的点子。
我当时想了很多的点子,我开始想模仿 GarageBand ,自己组合一些乐器来变成不同的音乐,但是我发现实现起来有很多的难点,可能来不及完成,就放弃了。还想了一些点子,但是没有算法支持,也放弃了,最后我还是决定通过已有的函数来写个 demo,最下面有 demo 的地址,可以感受一下,波形与音乐结合起来比较优美。
根据音乐中产生的分贝数值映射成波浪线的振幅
原理说明:
1.根据正弦函数:f(x) = Asin(2πωx+φ);
2.设置默认周期 T = 1;
3.曲线往右移平移,φ值需要减小;
4.如果φ值每次都减小固定的数值,视觉上看起来曲线是匀速往右移动;
#import <UIKit/UIKit.h>
@interface SoundWaveView : UIView
@property (nonatomic, copy) void (^levelCallback)(SoundWaveView * waveView);
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, assign) CGFloat level;
@end
#import "SoundWaveView.h"
@interface SoundWaveView ()
//相位变化,用来移动曲线
@property (nonatomic, assign) CGFloat offX;
//存放线的数组
@property (nonatomic, strong) NSMutableArray * linesArr;
//高度
@property (nonatomic, assign) CGFloat maxHeight;
//宽度
@property (nonatomic, assign) CGFloat maxWidth;
//最大振幅
@property (nonatomic, assign) CGFloat maxAmplitude;
//振幅系数
@property (nonatomic, assign) CGFloat amplitudeLevel;
@end
@implementation SoundWaveView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self setup];
}
return self;
}
- (void)setup
{
self.linesArr = [[NSMutableArray alloc]init];
self.maxHeight = CGRectGetHeight(self.bounds);
self.maxWidth = CGRectGetWidth(self.bounds);
self.maxAmplitude = self.maxHeight - 2;
self.amplitudeLevel = 1;
}
- (void)setLevelCallback:(void (^)(SoundWaveView *waveView))levelCallback
{
_levelCallback = levelCallback;
[self.displayLink invalidate];
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(invokeLevelCallback)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
for(int i=0; i < 5; i++){
CAShapeLayer *line = [CAShapeLayer layer];
line.fillColor = [[UIColor clearColor] CGColor];
line.lineCap = kCALineCapSquare;
line.lineJoin = kCALineJoinRound;
line.lineWidth = i == 0 ? 2 : 1;
CGFloat progress = 1.0f - (CGFloat)i / 5;
UIColor *color = [[UIColor whiteColor]colorWithAlphaComponent:(i == 0 ? 1.0 : progress *progress)];
line.strokeColor = color.CGColor;
[self.layer addSublayer:line];
[self.linesArr addObject:line];
}
}
- (void)invokeLevelCallback
{
self.levelCallback(self);
}
- (void)setLevel:(CGFloat)level
{
_level = level;
self.offX -= 0.25f;
self.amplitudeLevel = fmax(level, 0.01f);
[self refreshLines];
}
- (void)refreshLines
{
UIGraphicsBeginImageContext(self.frame.size);
for(int i=0; i < 5; i++) {
UIBezierPath *wavelinePath = [UIBezierPath bezierPath];
CGFloat progress = 1.0f - (CGFloat)i / 5;
CGFloat nowAmplitudeLevel = (1.5f * progress - 0.5f) * self.amplitudeLevel;
for(CGFloat x = 0; x < self.maxWidth; x ++) {
CGFloat midScale = 1 - pow(x / (self.maxWidth / 2) - 1, 2);
CGFloat y = midScale * self.maxAmplitude * nowAmplitudeLevel * sinf(2 * M_PI *(x / self.maxWidth) * 1 + self.offX) + (self.maxHeight * 0.5);
if (x==0) {
[wavelinePath moveToPoint:CGPointMake(x, y)];
}
else {
[wavelinePath addLineToPoint:CGPointMake(x, y)];
}
}
CAShapeLayer *waveline = [self.linesArr objectAtIndex:i];
waveline.path = [wavelinePath CGPath];
}
UIGraphicsEndImageContext();
}
- (void)dealloc
{
}
@end
其中的 pow 函数(1 - pow(x / (self.maxWidth / 2) - 1, 2))
是为了让越靠近屏幕中间的波形振幅越明显。