Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions CoreAudioWaveMaker.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#pragma once

#include <AudioToolbox/AudioToolbox.h>
#include <thread>
#include <atomic>
#include <functional>
#include <cmath>
#include <iostream>

template<typename T>
class CoreAudioWaveMaker {
public:
using AudioFunction = std::function<double(double)>;

CoreAudioWaveMaker(AudioFunction userFunc, double sampleRate = 44100.0)
: m_userFunc(userFunc), m_sampleRate(sampleRate), m_globalTime(0.0) {}

bool Start() {
// Describe output unit
AudioComponentDescription desc = {};
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;

AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
if (!comp) return false;
if (AudioComponentInstanceNew(comp, &m_audioUnit) != noErr) return false;

// Set stream format
AudioStreamBasicDescription streamDesc = {};
streamDesc.mSampleRate = m_sampleRate;
streamDesc.mFormatID = kAudioFormatLinearPCM;
streamDesc.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
streamDesc.mFramesPerPacket = 1;
streamDesc.mChannelsPerFrame = 1;
streamDesc.mBitsPerChannel = sizeof(float) * 8;
streamDesc.mBytesPerPacket = sizeof(float);
streamDesc.mBytesPerFrame = sizeof(float);

AudioUnitSetProperty(m_audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&streamDesc,
sizeof(streamDesc));

// Set render callback
AURenderCallbackStruct callback = {};
callback.inputProc = &RenderStatic;
callback.inputProcRefCon = this;
AudioUnitSetProperty(m_audioUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
0,
&callback,
sizeof(callback));

// Start audio
AudioUnitInitialize(m_audioUnit);
AudioOutputUnitStart(m_audioUnit);
return true;
}

void Stop() {
if (m_audioUnit) {
AudioOutputUnitStop(m_audioUnit);
AudioUnitUninitialize(m_audioUnit);
AudioComponentInstanceDispose(m_audioUnit);
}
}

double GetTime() const { return m_globalTime; }

private:
static OSStatus RenderStatic(void* inRefCon,
AudioUnitRenderActionFlags* ioActionFlags,
const AudioTimeStamp* inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList* ioData) {
return static_cast<CoreAudioWaveMaker*>(inRefCon)->Render(ioData, inNumberFrames);
}

OSStatus Render(AudioBufferList* ioData, UInt32 inNumberFrames) {
float* buffer = reinterpret_cast<float*>(ioData->mBuffers[0].mData);

for (UInt32 i = 0; i < inNumberFrames; i++) {
double sample = m_userFunc ? m_userFunc(m_globalTime) : 0.0;
sample = std::clamp(sample, -1.0, 1.0);
buffer[i] = static_cast<float>(sample);
m_globalTime += 1.0 / m_sampleRate;
}

return noErr;
}

private:
AudioUnit m_audioUnit = nullptr;
AudioFunction m_userFunc;
double m_sampleRate;
double m_globalTime;
};
144 changes: 144 additions & 0 deletions main1_mac.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
IMPORTANT! Must be compiled with 'g++ -std=c++17 main1_mac.cpp -o synth -framework AudioToolbox -framework ApplicationServices'

OneLoneCoder.com - Simple Audio Noisy Thing
"Allows you to simply listen to that waveform!" - @Javidx9

License
~~~~~~~
Copyright (C) 2018 Javidx9
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; See license for details.
Original works located at:
https://www.github.com/onelonecoder
https://www.onelonecoder.com
https://www.youtube.com/javidx9

GNU GPLv3
https://github.com/OneLoneCoder/videos/blob/master/LICENSE

From Javidx9 :)
~~~~~~~~~~~~~~~
Hello! Ultimately I don't care what you use this for. It's intended to be
educational, and perhaps to the oddly minded - a little bit of fun.
Please hack this, change it and use it in any way you see fit. You acknowledge
that I am not responsible for anything bad that happens as a result of
your actions. However this code is protected by GNU GPLv3, see the license in the
github repo. This means you must attribute me if you use it. You can view this
license here: https://github.com/OneLoneCoder/videos/blob/master/LICENSE
Cheers!

Author
~~~~~~

Twitter: @javidx9
Blog: www.onelonecoder.com

Versions
~~~~~~~~

This is the first version of the software. It presents a simple keyboard and a sine
wave oscillator.

See video: https://youtu.be/tgamhuQnOkM

*/

#include <iostream>
#include <ApplicationServices/ApplicationServices.h>
#include <chrono>
#include <thread>

using namespace std;

#include "CoreAudioWaveMaker.hpp"

CGKeyCode keyCodes[18] = {
0, // A
13, // W
1, // S
14, // E
2, // D
3, // F
17, // T
5, // G
16, // Y
4, // H
32, // U
38, // J
40, // K
31, // O
37, // L
35, // P
41, // ;
39 // '
};

// Global synthesizer variables
atomic<double> dFrequencyOutput = 0.0; // dominant output frequency of instrument, i.e. the note
double dOctaveBaseFrequency = 110.0; // A2 // frequency of octave represented by keyboard
double d12thRootOf2 = pow(2.0, 1.0 / 12.0); // assuming western 12 notes per ocatve

// Function used by CoreAudioWaveMaker to generate sound waves
// Returns amplitude (-1.0 to +1.0) as a function of time
double MakeNoise(double dTime)
{
double dOutput = sin(dFrequencyOutput * 2.0 * 3.14159 * dTime);
return dOutput * 0.5; // Master Volume
}

bool isKeyPressed(CGKeyCode keycode) {
return CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, keycode);
}

int main()
{
// Shameless self-promotion
wcout << "www.OneLoneCoder.com - Synthesizer Part 1" << endl << "Single Sine Wave Oscillator, No Polyphony" << endl << endl;

// Display a keyboard
wcout << endl <<
"| | | | | | | | | | | | | | | | | | |" << endl <<
"| | W | | E | | | T | | Y | | U | | | O | | P | | |" << endl <<
"| |___| |___| | |___| |___| |___| | |___| |___| | |" << endl <<
"| | | | | | | | | | | |" << endl <<
"| A | S | D | F | G | H | J | K | L | ; | ' |" << endl <<
"|_____|_____|_____|_____|_____|_____|_____|_____|_____|_____|_____|" << endl << endl;

// Create sound machine!!
CoreAudioWaveMaker<float> sound(MakeNoise);

// Link noise function with sound machine
sound.Start();

// Sit in loop, capturing keyboard state changes and modify
// synthesizer output accordingly
int nCurrentKey = -1;
bool bKeyPressed = false;
while (true) {
bKeyPressed = false;

for (int k = 0; k < 18; k++) {
if (isKeyPressed(keyCodes[k])) {
if (nCurrentKey != k) {
dFrequencyOutput = dOctaveBaseFrequency * pow(d12thRootOf2, k);
wcout << L"\rNote On : " << sound.GetTime() << "s " << dFrequencyOutput << "Hz" << flush;
nCurrentKey = k;
}
bKeyPressed = true;
break; // Only play one note at a time
}
}

if (!bKeyPressed) {
if (nCurrentKey != -1) {
wcout << L"\rNote Off: " << sound.GetTime() << "s " << flush;
nCurrentKey = -1;
}
dFrequencyOutput = 0;
}
}

return 0;
}