Pre loader

iOS & macOS Audio, Radio frequency and Spectrum Analyzer

iOS & macOS charts - Examples

Please note! These examples are new to SciChart iOS v4 release! SciChart’s OpenGL ES and Metal iOS and Metal macOS Chart library ships with hundred of Objective-C and Swift iOS & macOS Chart Examples which you can browse, play with and view the source-code. All of this is possible with the new and improved SciChart iOS Examples Suite and demo application for Mac, which ships as part of the SciChart SDK.

Download Examples

The Audio analyzer demo showcases how to use SciChart iOS charts in a scientific context.

Download the examples and enable your microphone to see this demo at work.

In this example we listen to the microphone on your iPad/iPhone device and create a waveform of the sound recorded in the top chart. This chart has data-points drawn in real-time on our High Performance charts. The example application then performs a Fourier Transform, creating a spectral / frequency analysis of the audio waveform and plots in the lower left chart. Finally, the histogram of the fourier transform, known as a spectrogram, is plotted in a SciChart iOS Heatmap control in the bottom right of the example.

If you are creating an app that needs to visualize scientific data from data-acquisition devices, audio spectra, or visualize radio frequency or spectral analysis choose SciChart to shortcut your development time & get to market faster with our well-tested, reliable iOS Chart library.

The Swift and Objective-C source code for the iOS and macOS Audio, Radio frequency & Spectrum Analyzer Example example is included below (Scroll down!).

Did you know that we have the source code for all our example available for free on Github?

Clone the SciChart.iOS.Examples from Github.

Also the SciChart iOS and Scichart macOS Trials contain the full source for the examples (link below).

DOWNLOAD THE IOS CHART EXAMPLES

AudioAnalyzerChartView.swift
View source code
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales:   sales@scichart.com
//
// AudioAnalyzerChartView.swift is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************

import Foundation
import RxSwift

class AudioAnalyzerChartView: SCDAudioAnalyzerLayoutViewController {
    
    private let AUDIO_STREAM_BUFFER_SIZE: Int = 500000
    private let MAX_FREQUENCY: Int = 10000
    private let minDB: Double = -30
    private let maxDB: Double = 70
    
    private var bufferSize: Int {
        return dataProvider.bufferSize
    }
    
    private var sampleRate: Int {
        return dataProvider.sampleRate
    }
    
    private var hzPerDataPoint: Int {
        return sampleRate / bufferSize
    }
    
    private var fftCount: Int {
        return AUDIO_STREAM_BUFFER_SIZE / bufferSize
    }
    
    private var fftSize: Int {
        return MAX_FREQUENCY / hzPerDataPoint
    }
    
    private var fftValuesCount: Int {
        return fftSize * fftCount
    }
    
    private var dataProvider: IAudioAnalyzerDataProvider!
    private var audioDS: SCIXyDataSeries!
    private var fftDS: SCIXyDataSeries!
    private var spectrogramDS: SCIUniformHeatmapDataSeries!
    private var spectogramValues: SCDSpectogramItems!
    private var disposable: Disposable?
        
    deinit {
        disposable?.dispose()
    }
    
    override func initExample() {
        
        SCDPermissionRequestHelper.requestPermission { [weak self] granted in
            if granted {
                self?.dataProvider = DefaultAudioAnalyzerDataProvider()
            } else {
                self?.dataProvider = StubAudioAnalyzerDataProvider()
            }
            DispatchQueue.main.async { [weak self] in
                self?.proceedWithInit()
            }
        }
    }
    
    private func proceedWithInit() {
        audioDS = SCIXyDataSeries(xType: .long, yType: .int)
        fftDS = SCIXyDataSeries(xType: .double, yType: .float)
        spectrogramDS = SCIUniformHeatmapDataSeries(xType: .long, yType: .long, zType: .float, xSize: fftSize, ySize: fftCount)
        
        initAudioStreamChart()
        initFFTChart()
        initSpectrogramChart()
            
        disposable = dataProvider
            .getData()
            .do(onNext: { [weak self] audioData in
                guard let self = self else { return }
                
                self.audioDS.append(x: audioData.xData, y: audioData.yData)
                
                let fftData = audioData.fftData
                fftData.count = self.fftSize
                self.fftDS.update(y: fftData, at: 0)
                
                self.spectogramValues.replace(withNewItems: fftData)
                self.spectrogramDS.update(z: self.spectogramValues.values)
            })
            .subscribe()
    }
    
    private func initAudioStreamChart() {
        let xAxis = SCINumericAxis()
        xAxis.autoRange = .always
        xAxis.drawLabels = false
        xAxis.drawMinorTicks = false
        xAxis.drawMajorTicks = false
        xAxis.drawMajorBands = false
        xAxis.drawMinorGridLines = false
        xAxis.drawMajorGridLines = false
        
        let yAxis = SCINumericAxis()
        yAxis.visibleRange = SCIDoubleRange(min: Double(Int32.min), max: Double(Int32.max))
        yAxis.drawLabels = false
        yAxis.drawMinorTicks = false
        yAxis.drawMajorTicks = false
        yAxis.drawMajorBands = false
        yAxis.drawMinorGridLines = false
        yAxis.drawMajorGridLines = false
        
        audioDS.fifoCapacity = AUDIO_STREAM_BUFFER_SIZE
        
        let rSeries = SCIFastLineRenderableSeries()
        rSeries.dataSeries = audioDS
        rSeries.strokeStyle = SCISolidPenStyle(color: .gray, thickness: 1.0)
        
        SCIUpdateSuspender.usingWith(audioStreamChart) {
            self.audioStreamChart.xAxes.add(items: xAxis)
            self.audioStreamChart.yAxes.add(items: yAxis)
            self.audioStreamChart.renderableSeries.add(items: rSeries)
        }
    }
    
    private func initFFTChart() {
        let xAxis = SCINumericAxis()
        xAxis.drawMajorBands = false
        xAxis.drawMinorGridLines = false
        xAxis.maxAutoTicks = 5
        xAxis.axisTitle = "Hz"
        xAxis.axisTitlePlacement = .right
        xAxis.axisTitleOrientation = .horizontal
        
        let yAxis = SCINumericAxis()
        yAxis.axisAlignment = .left
        yAxis.visibleRange = SCIDoubleRange(min: minDB, max: maxDB)
        yAxis.growBy = SCIDoubleRange(min: 0.1, max: 0.1)
        yAxis.drawMinorTicks = false
        yAxis.drawMinorGridLines = false
        yAxis.drawMajorBands = false
        yAxis.axisTitle = "dB"
        yAxis.axisTitlePlacement = .top
        yAxis.axisTitleOrientation = .horizontal
        
        fftDS.fifoCapacity = fftSize
        for i in 0 ..< fftSize {
            fftDS.append(x: i * Int(hzPerDataPoint), y: Float.nan)
        }
        
        let rSeries = SCIFastColumnRenderableSeries()
        rSeries.dataSeries = fftDS
        rSeries.zeroLineY = minDB
        rSeries.paletteProvider = FFTPaletteProvider()
        
        SCIUpdateSuspender.usingWith(fftChart) {
            self.fftChart.xAxes.add(items: xAxis)
            self.fftChart.yAxes.add(items: yAxis)
            self.fftChart.renderableSeries.add(rSeries)
        }
    }
    
    private func initSpectrogramChart() {
        
        spectogramValues = SCDSpectogramItems(capacity: fftValuesCount)
        
        let xAxis = SCINumericAxis()
        xAxis.autoRange = .always
        xAxis.drawLabels = false
        xAxis.drawMinorTicks = false
        xAxis.drawMajorTicks = false
        xAxis.drawMajorBands = false
        xAxis.drawMinorGridLines = false
        xAxis.drawMajorGridLines = false
        xAxis.axisAlignment = .left
        xAxis.flipCoordinates = true
        
        let yAxis = SCINumericAxis()
        yAxis.autoRange = .always
        yAxis.drawLabels = false
        yAxis.drawMinorTicks = false
        yAxis.drawMajorTicks = false
        yAxis.drawMajorBands = false
        yAxis.drawMinorGridLines = false
        yAxis.drawMajorGridLines = false
        yAxis.axisAlignment = .bottom
        
        let rSeries = SCIFastUniformHeatmapRenderableSeries()
        rSeries.dataSeries = spectrogramDS
        rSeries.minimum = minDB
        rSeries.maximum = maxDB
        
        rSeries.colorMap = SCIColorMap(colors: [.clear, SCIColor.fromARGBColorCode(0xFF00008B), .purple, .red, .yellow, .white], andStops: [0, 0.0001, 0.25, 0.50, 0.75, 1])
        
        SCIUpdateSuspender.usingWith(spectrogramChart) {
            self.spectrogramChart.xAxes.add(xAxis)
            self.spectrogramChart.yAxes.add(yAxis)
            self.spectrogramChart.renderableSeries.add(rSeries)
        }
    }
}
AudioRecorder.m
View source code
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales:   sales@scichart.com
//
// AudioRecorder.m is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************

#import "AudioRecorder.h"
#import <Accelerate/Accelerate.h>

#define AUDIO_DATA_TYPE_FORMAT int

@implementation AudioRecorder {
    FFTSetup fftSetup;
    unsigned int length;
    float _max;
    float _min;
}

void *refToSelf;

void AudioInputCallback(void *inUserData,
                        AudioQueueRef inAQ,
                        AudioQueueBufferRef inBuffer,
                        const AudioTimeStamp *inStartTime,
                        unsigned int inNumberPacketDescriptions,
                        const AudioStreamPacketDescription *inPacketDescs) {
    __weak AudioRecorder *rec = (__bridge AudioRecorder *)refToSelf;
    RecordState *recordState = (RecordState *)inUserData;
    
    if (!recordState->recording) return;
    
    AudioQueueEnqueueBuffer(recordState->queue, inBuffer, 0, NULL);
    
    int *samples = (AUDIO_DATA_TYPE_FORMAT *)inBuffer->mAudioData;
    if (inNumberPacketDescriptions != 2048) return;
    
    [rec formSamplesToEngine:inNumberPacketDescriptions samples:samples];
}

- (instancetype)init {
    self = [super init];
    if (self) {
        refToSelf = (__bridge void *)(self);
        _max = 0.0f;
        _min = 0.0f;
    }
    return self;
}

- (void)startRecording:(int)sampleRate andMinBufferSize:(int)minBufferSize {
    // Configure audio session for recording
    
#if TARGET_OS_IOS
    AVAudioSession *session = [AVAudioSession sharedInstance];
    NSError *error = nil;
    [session setCategory:AVAudioSessionCategoryRecord error:&error];
    if (error) {
        NSLog(@"Error setting category: %@", error.localizedDescription);
    }
    [session setActive:YES error:&error];
    if (error) {
        NSLog(@"Error activating session: %@", error.localizedDescription);
    }
#endif
    [self setupAudioFormat:&recordState.dataFormat withSampleRate:sampleRate];
    recordState.currentPacket = 0;
    
    OSStatus status = AudioQueueNewInput(&recordState.dataFormat, AudioInputCallback, &recordState, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &recordState.queue);
    if (status == noErr) {
        for (int i = 0; i < NUM_BUFFERS; i++) {
            AudioQueueAllocateBuffer(recordState.queue, minBufferSize * recordState.dataFormat.mBytesPerFrame, &recordState.buffers[i]);
            AudioQueueEnqueueBuffer(recordState.queue, recordState.buffers[i], 0, NULL);
        }
    }
    
    recordState.recording = true;
    status = AudioQueueStart(recordState.queue, NULL);
    if (status == noErr) {
        length = (unsigned int)floor(log2(minBufferSize));
        fftSetup = vDSP_create_fftsetup(length, kFFTRadix2);
    }
}

- (void)setupAudioFormat:(AudioStreamBasicDescription *)format withSampleRate:(int)sampleRate {
    format->mSampleRate       = sampleRate;
    format->mFormatID         = kAudioFormatLinearPCM;
    format->mFormatFlags      = kAudioFormatFlagIsSignedInteger;
    format->mFramesPerPacket  = 1;
    format->mChannelsPerFrame = 1;
    format->mBytesPerFrame    = sizeof(AUDIO_DATA_TYPE_FORMAT);
    format->mBytesPerPacket   = sizeof(AUDIO_DATA_TYPE_FORMAT);
    format->mBitsPerChannel   = sizeof(AUDIO_DATA_TYPE_FORMAT) * 8;
}

- (void)stopRecording {
    recordState.recording = false;
    
    vDSP_destroy_fftsetup(fftSetup);
    AudioQueueStop(recordState.queue, true);
    
    for (int i = 0; i < NUM_BUFFERS; i++) {
        AudioQueueFreeBuffer(recordState.queue, recordState.buffers[i]);
    }
    
    AudioQueueDispose(recordState.queue, true);
    AudioFileClose(recordState.audioFile);
}

- (void)formSamplesToEngine:(int)capacity samples: (int*)samples {
    self.samples = samples;
    self.fftData = [self calculateFFT:samples size:capacity];
}

- (float *)calculateFFT:(int *)data size:(unsigned int)numSamples {
    float *dataFloat = malloc(sizeof(float) * numSamples);
    vDSP_vflt32(data, 1, dataFloat, 1, numSamples);
    
    DSPSplitComplex tempSplitComplex;
    tempSplitComplex.imagp = malloc(sizeof(float) * numSamples);
    tempSplitComplex.realp = malloc(sizeof(float) * numSamples);
    
    DSPComplex *audioBufferComplex = malloc(sizeof(DSPComplex) * numSamples);
    
    for (unsigned int i = 0; i < numSamples; i++) {
        audioBufferComplex[i].real = dataFloat[i];
        audioBufferComplex[i].imag = 0.0f;
    }
    
    vDSP_ctoz(audioBufferComplex, 2, &tempSplitComplex, 1, numSamples);
    vDSP_fft_zip(fftSetup, &tempSplitComplex, 1, length, FFT_FORWARD);
    
    float *result = malloc(sizeof(float) * numSamples);
    for (unsigned int i = 0 ; i < numSamples; i++) {
        float current = sqrt(tempSplitComplex.realp[i] * tempSplitComplex.realp[i] + tempSplitComplex.imagp[i] * tempSplitComplex.imagp[i]) * 0.000025;
        current = log10(current) * 10;
        result[i] = current;
    }
    
    free(dataFloat);
    free(audioBufferComplex);
    free(tempSplitComplex.imagp);
    free(tempSplitComplex.realp);
    
    return result;
}

@end
DefaultAudioAnalyzerDataProvider.swift
View source code
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales:   sales@scichart.com
//
// DefaultAudioAnalyzerDataProvider.swift is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************

class DefaultAudioAnalyzerDataProvider: DataProviderBase<AudioData>, IAudioAnalyzerDataProvider {
    
    let bufferSize: Int
    let sampleRate: Int
    private var time: Int64 = 0
    
    private lazy var audioData: AudioData = {
        return AudioData(pointsCount: bufferSize)
    }()
        
    private let audioRecorder: AudioRecorder = AudioRecorder()
    
    var samplesValues: samplesToEngine!
    var fftValues: samplesToEngineFloat!
    
    convenience init() {
        self.init(sampleRate: 44100, minBufferSize: 2048)
    }
    
    init(sampleRate: Int, minBufferSize: Int) {
        self.sampleRate = sampleRate
        self.bufferSize = minBufferSize

        super.init(dispatchTimeInterval: .milliseconds(sampleRate / minBufferSize))
    }
    
    override func onStart() {
        audioRecorder.startRecording(Int32(sampleRate), andMinBufferSize: Int32(bufferSize))
    }
    
    override func onStop() {
        audioRecorder.stopRecording()
    }
    
    override func onNext() -> AudioData {
        audioData.xData.clear()
        audioData.yData.clear()
        audioData.fftData.clear()
        
        if let samples = audioRecorder.samples {
            let sequence = time ..< time + Int64(bufferSize)
            audioData.xData.add(sequence)
            audioData.yData.addValues(samples, count: bufferSize)
            time += Int64(bufferSize)
        }

        audioData.yData.withUnsafeMutablePointer { [weak self] pointer in
            let fftData: UnsafeMutablePointer<Float>! = self?.audioRecorder.calculateFFT(pointer, size: UInt32(self?.bufferSize ?? 0))
            self?.audioData.fftData.addValues(fftData, count: self?.bufferSize ?? 0)
        }
        
        return audioData
    }
}
FFTPaletteProvider.swift
View source code
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales:   sales@scichart.com
//
// FFTPaletteProvider.swift is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************

import Foundation

class FFTPaletteProvider: SCIPaletteProviderBase<SCIFastColumnRenderableSeries>, ISCIFillPaletteProvider, ISCIStrokePaletteProvider {
    
    private struct Colors {
        static let minColor: UInt32 = 0xFF008000
        static let maxColor: UInt32 = 0xFFFF0000
        
        // RGB chanel values for min color
        static let minColorRed = SCIColor.red(minColor)
        static let minColorGreen = SCIColor.green(minColor)
        static let minColorBlue = SCIColor.blue(minColor)
        
        // RGB chanel values for max color
        static let maxColorRed = SCIColor.red(maxColor)
        static let maxColorGreen = SCIColor.green(maxColor)
        static let maxColorBlue = SCIColor.blue(maxColor)

        static let diffRed = Int(maxColorRed) - Int(minColorRed)
        static let diffGreen = Int(maxColorGreen) - Int(minColorGreen)
        static let diffBlue = Int(maxColorBlue) - Int(minColorBlue)
    }
    
    private let colors = SCIUnsignedIntegerValues()
    var fillColors: SCIUnsignedIntegerValues { return colors }
    var strokeColors: SCIUnsignedIntegerValues { return colors }
    
    init() {
        super.init(renderableSeriesType: SCIFastColumnRenderableSeries.self)
    }
    
    override func update() {
        let xyRenderPassData = renderableSeries!.currentRenderPassData as! SCIXyRenderPassData
        let yCalc = xyRenderPassData.yCoordinateCalculator!
        let min: Double = 0
        let max: Double = yCalc.maxAsDouble
        let diff = max - min
        
        let yValues = xyRenderPassData.yValues
        let size = xyRenderPassData.pointsCount
        colors.count = size
        
        for i in 0 ..< size {
            let yValue = yValues.getValueAt(i)
            let fraction = (yValue - min) / diff
            
            let red = lerp(Colors.minColorRed, Colors.diffRed, fraction)
            let green = lerp(Colors.minColorGreen, Colors.diffGreen, fraction)
            let blue = lerp(Colors.minColorBlue, Colors.diffBlue, fraction)
            
            let color = SCIColor(red: red, green: green, blue: blue, alpha: 1)
            colors.set(color.colorARGBCode(), at: i)
        }
    }
    
    private func lerp(_ minColor: UInt8, _ diffColor: Int, _ fraction: Double) -> CGFloat {
        let interpolatedValue = Double(minColor) + fraction * Double(diffColor)
        return interpolatedValue < 0 ? 0 : interpolatedValue > 255 ? 1 : CGFloat(interpolatedValue / 255)
    }
}
SCDAudioAnalyzerLayout.m
View source code
Failed to load code from GitHub.
SCDAudioAnalyzerLayout.xib
View source code
Failed to load code from GitHub.
SCDSpectogramItems.m
View source code
Failed to load code from GitHub.
StubAudioAnalyzerDataProvider.swift
View source code
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales:   sales@scichart.com
//
// StubAudioAnalyzerDataProvider.swift is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************

import Foundation

class StubAudioAnalyzerDataProvider: DataProviderBase<AudioData>, IAudioAnalyzerDataProvider {
    
    let bufferSize: Int = 2048
    let sampleRate: Int = 44100
    private var time: Int64 = 0
    
    init() {
        super.init(dispatchTimeInterval: .milliseconds(20))
    }
    
    private let audioRecorder: AudioRecorder = AudioRecorder()
    
    private lazy var audioData: AudioData = {
        return AudioData(pointsCount: bufferSize)
    }()
    
    private lazy var provider: IYValuesProvider = {
        let providers: [IYValuesProvider] = [
            FrequencySinewaveYValueProvider(amplitude: 8000, phase: 0, minFrequency: 0, maxFrequency: 1, step: 0.0000005),
            NoisySinewaveYValueProvider(amplitude: 8000, phase: 0, frequency: 0.000032, noiseAmplitude: 200),
            NoisySinewaveYValueProvider(amplitude: 6000, phase: 0, frequency: 0.000016, noiseAmplitude: 100),
            NoisySinewaveYValueProvider(amplitude: 4000, phase: 0, frequency: 0.000064, noiseAmplitude: 100)
        ]
        return AggregateYValueProvider(providers: providers)
    }()
    
    override func onNext() -> AudioData {
        audioData.xData.clear()
        audioData.yData.clear()
        audioData.fftData.clear()
        
        for _ in 0 ..< bufferSize {
            audioData.xData.add(Int64(time))
            audioData.yData.add(provider.getYValueForIndex(Int(time)))
            time += 1
        }
        
        audioData.yData.withUnsafeMutablePointer { [weak self] pointer in
            let fftData: UnsafeMutablePointer<Float>! = self?.audioRecorder.calculateFFT(pointer, size: UInt32(self?.bufferSize ?? 0))
            self?.audioData.fftData.addValues(fftData, count: self?.bufferSize ?? 0)
        }
        
        return audioData
    }
}

protocol IYValuesProvider {
    func getYValueForIndex(_ index: Int) -> Int32
}

class AggregateYValueProvider: IYValuesProvider {
    let providers: [IYValuesProvider]
    
    init(providers: [IYValuesProvider]) {
        self.providers = providers
    }
    
    func getYValueForIndex(_ index: Int) -> Int32 {
        var sum: Int32 = 0
        for provider in providers {
            sum += provider.getYValueForIndex(index) * 30000
        }
        return sum
    }
}

class FrequencySinewaveYValueProvider: IYValuesProvider {
    private let amplitude: Double
    private let phase: Double
    private let minFrequency: Double
    private let maxFrequency: Double
    private let step: Double
    
    private var frequency: Double
    
    init(amplitude: Double, phase: Double, minFrequency: Double, maxFrequency: Double, step: Double) {
        self.amplitude = amplitude
        self.phase = phase
        self.minFrequency = minFrequency
        self.maxFrequency = maxFrequency
        self.step = step
        
        self.frequency = minFrequency
    }
    
    func getYValueForIndex(_ index: Int) -> Int32 {
        frequency = frequency <= maxFrequency ? frequency + step : minFrequency
        let wn = 2 * Double.pi * frequency
        return Int32(amplitude * sin(Double(index) * wn + phase))
    }
}

class NoisySinewaveYValueProvider: IYValuesProvider {
    private let amplitude: Double
    private let phase: Double
    private let noiseAmplitude: Double
    private let wn: Double
    
    init(amplitude: Double, phase: Double, frequency: Double, noiseAmplitude: Double) {
        self.amplitude = amplitude
        self.phase = phase
        self.noiseAmplitude = noiseAmplitude
        self.wn = 2 * Double.pi * frequency
    }
    
    func getYValueForIndex(_ index: Int) -> Int32 {
        return Int32(amplitude * sin(Double(index) * wn + phase) + (Double.random(in: 0 ..< 1) - 0.5) * noiseAmplitude)
    }
}
Back to iOS & macOS charts Examples