presets   chat   music   threads  

Frequency response curve source

deer

Just in case you need to know exactly what's going on. This isn't perfect, it might have some bugs.

#import "EQEFrequencyResponseCurve.h"
#import "macros.h"
#import <syslog.h>
#import "ipc.h"
#import "EQEMainView.h"

struct plot_t {
    CGFloat x;
    CGFloat y;
};

void eqe_calculate_curve(struct plot_t *plot, size_t len, bool first, float a0, float a1, float a2, float b1, float b2)
{
    for(int i = 0; i < len; i++) {
        float w = exp(log(1/0.001) * i/(len-1))*0.001*M_PI;
        float phi = pow(sin(w/2), 2);
        float y = log(pow(a0+a1+a2, 2) - 4*(a0*a1 + 4*a0*a2 + a1*a2)*phi + 16*a0*a2*phi*phi) - log(pow(1+b1+b2, 2) - 4*(b1 + 4*b2 + b1*b2)*phi + 16*b2*phi*phi);
        y = y * 10 / log(10);
        if(isnan(y)) {
            y = -200;
        }

        if(first) {
            plot[i].x = (CGFloat)i / (len - 1) / 2;
            plot[i].y = y;
        } else {
            plot[i].y = plot[i].y + y;
        }
    }
}

@interface EQEFrequencyResponseCurve()
{
    float _preamp;
    struct plot_t *_plots;
    size_t _plotCount;
    BOOL _isFlat;
}
@end

@implementation EQEFrequencyResponseCurve

-(instancetype)init
{
    self = [super init];

    self.backgroundColor = UIColor.clearColor;

    return self;
}

-(void)update
{
    float preamp = [lua_rpc("return eqe.preamp") floatValue];
    _preamp = preamp;

    NSArray<NSArray<NSNumber *> *> *response = lua_rpc("return getcoefs()");

    _plotCount = (int)(self.frame.size.width/4);
    _plots = calloc(_plotCount, sizeof(struct plot_t));
    _isFlat = true;

    for(int i = 0; i < response.count; i++) {
        NSArray<NSNumber *> *coefs = response[i];

        if(coefs.count >= 5) {
            eqe_calculate_curve(_plots, _plotCount, _isFlat, coefs[0].floatValue, coefs[1].floatValue, coefs[2].floatValue, coefs[3].floatValue, coefs[4].floatValue);
            _isFlat = false;
        }
    }
}

-(float)ztransy:(float)y
{
    float MAX = 70;
    return EQETopOffset - y*self.frame.size.height/MAX;
}

-(struct plot_t)ztransform:(struct plot_t)p
{
    struct plot_t r;
    r.x = p.x * self.frame.size.width * 2;
    r.y = [self ztransy:(p.y + _preamp)];
    return r;
}

static void contextSetRGB(float r, float g, float b)
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetRGBStrokeColor(context, r, g, b, 1);
    CGContextSetRGBFillColor(context, r, g, b, 1);
}

static void setcolor(float y)
{
    float scale = 20;
    float scaledY = y/scale + 0.5;
    if(scaledY < 0.5) {
        // green
        contextSetRGB(scaledY*2, 1, 0);
    } else if(scaledY > 0.5) {
        // red
        contextSetRGB(1, (1-scaledY)*2, 0);
    } else {
        // yellow
        contextSetRGB(1, 1, 0);
    }
}

static const float dotFrequencies[] = {
    // This should be 100Hz, 1kHz and 10kHz. The formula in frequencyToX() is bad
    99,
    990,
    9200,
};
static float frequencyToX(EQEFrequencyResponseCurve *self, float f)
{
    static const float MIN_F = 20;
    static const float MAX_F = 20000;

    // this formula is bad
    return self.frame.size.width*log((f - MIN_F)*exp(6.75)/(MAX_F - MIN_F) + 1)/6.75;
}
static float shouldHaveDot(EQEFrequencyResponseCurve *self, float x0, float x1)
{
    static const int count = sizeof(dotFrequencies) / sizeof(float);

    for(int i = 0; i < count; i++) {
        float x = frequencyToX(self, dotFrequencies[i]);
        if(x >= x0 && x <= x1) {
            return x;
        }
    }
    return -1;
}

-(void)drawRect:(CGRect)rect
{
    [super drawRect:rect];

    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
    CGContextSetLineJoin(context, kCGLineJoinRound);
    CGContextSetLineWidth(context, 1);

    static const float dotSize = 3;

    if(_isFlat) {
        float y = [self ztransy:_preamp];
        setcolor(_preamp);
        CGContextMoveToPoint(context, 0, y);
        CGContextAddLineToPoint(context, self.frame.size.width, y);
        CGContextStrokePath(context);

        static const int count = sizeof(dotFrequencies) / sizeof(float);
        for(int i = 0; i < count; i++) {
            float x = frequencyToX(self, dotFrequencies[i]);
            CGContextFillEllipseInRect(context, CGRectMake(x - dotSize/2, y - dotSize/2, dotSize, dotSize));
        }
    } else {
        float lasty;
        for(int i = 0; i < _plotCount; i++) {
            struct plot_t t = [self ztransform:_plots[i]];
            if(i != 0) {
                CGContextAddLineToPoint(context, t.x, t.y);
                setcolor((_plots[i].y + lasty)/2 + _preamp);
                CGContextStrokePath(context);

                struct plot_t lastT = [self ztransform:_plots[i - 1]];
                float x = shouldHaveDot(self, lastT.x, t.x);
                if(x > 0) {
                    float y = (lastT.y + t.y) / 2;
                    CGContextFillEllipseInRect(context, CGRectMake(x - dotSize/2, y - dotSize/2, dotSize, dotSize));
                }
            }
            CGContextMoveToPoint(context, t.x, t.y);
            lasty = _plots[i].y;
        }
    }
}

@end