From 353c4f8b2ec64c973d9d3b34e800efb5158930ba Mon Sep 17 00:00:00 2001 From: LiamvdM Date: Sat, 21 Jun 2025 21:55:14 +0800 Subject: [PATCH] added mac support for main1 --- CoreAudioWaveMaker.hpp | 102 +++++++++++++++++++++++++++++ main1_mac.cpp | 144 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 CoreAudioWaveMaker.hpp create mode 100644 main1_mac.cpp diff --git a/CoreAudioWaveMaker.hpp b/CoreAudioWaveMaker.hpp new file mode 100644 index 0000000..ce9277f --- /dev/null +++ b/CoreAudioWaveMaker.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +template +class CoreAudioWaveMaker { +public: + using AudioFunction = std::function; + + 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(inRefCon)->Render(ioData, inNumberFrames); + } + + OSStatus Render(AudioBufferList* ioData, UInt32 inNumberFrames) { + float* buffer = reinterpret_cast(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(sample); + m_globalTime += 1.0 / m_sampleRate; + } + + return noErr; + } + +private: + AudioUnit m_audioUnit = nullptr; + AudioFunction m_userFunc; + double m_sampleRate; + double m_globalTime; +}; \ No newline at end of file diff --git a/main1_mac.cpp b/main1_mac.cpp new file mode 100644 index 0000000..a430456 --- /dev/null +++ b/main1_mac.cpp @@ -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 +#include +#include +#include + +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 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 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; +}