Skip to content

Instantly share code, notes, and snippets.

@LingDong-
Last active May 19, 2023 08:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LingDong-/61e0a68fd77a35c5b77014c4f89de393 to your computer and use it in GitHub Desktop.
Save LingDong-/61e0a68fd77a35c5b77014c4f89de393 to your computer and use it in GitHub Desktop.
a barebones midi synthesizer calling mac's low level API (CoreMIDI and CoreAudio) directly
//
// core_midi_synth.mm
//
// a barebones midi synthesizer demo that calls
// mac's low level API (CoreMIDI and CoreAudio) directly
// (because I can't be bothered to compile other people's libraries)
//
// with code fragments adapted from
// - https://stackoverflow.com/a/7964300
// - https://github.com/thestk/rtmidi
//
// see also
// - https://developer.apple.com/documentation/coreaudiotypes/
// - https://developer.apple.com/documentation/coremidi
//
// this is a single-file software with no dependencies,
// other than mac system frameworks,
// to compile with gcc/clang:
//
// g++ core_midi_synth.mm -framework CoreMIDI -framework AudioUnit -framework Foundation -O3 -o core_midi_synth
//
// to use:
// - plug in your midi keyboard
// - run this software by typing: ./core_midi_synth
// - play your midi keyboard
//
// lingdong 2022, MIT License
//
#include <CoreMIDI/CoreMIDI.h>
#include <AudioUnit/AudioUnit.h>
#include <Foundation/Foundation.h>
#include <iostream>
#include <vector>
#define SAMPLE_RATE 44100
const int n_wave = 2048;
double waveform[n_wave];
double phase = 0;
double amp_targs[128] = {0};
double amps[128] = {0};
double frqs[128];
double phases[128] = {0};
static OSStatus playbackCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {
// cout << "?" << endl;
// for (int k = 0; k < 128; k++){
// printf("%c",((int)(amps[k]*26)+'a'));
// }
// printf("\n");
for(UInt32 i = 0; i < ioData->mNumberBuffers; ++i){
int numSamples = ioData->mBuffers[i].mDataByteSize / 4;
for (int j = 0; j < numSamples; j++){
((int*)ioData->mBuffers[i].mData)[j] = 0;
}
}
int nKeyOn = 0;
for (int k = 0; k < 128; k++){
if (amps[k] < 0.001){
phases[k] = 0;
}else{
nKeyOn ++;
}
}
// float eachAmp = 32000.0/(float)nKeyOn;
float eachAmp = fmin(10000.0, 32000.0/((float)nKeyOn+1));
// float eachAmp = 10000.0;
for (int k = 0; k < 128; k++){
amps[k] = amp_targs[k] * 0.08 + amps[k] * 0.92;
double frq = frqs[k] + ((double)rand()/(double)RAND_MAX)*4-2;
double amp = amps[k];
double phaseStep = frq / (double)SAMPLE_RATE;
for(UInt32 i = 0; i < ioData->mNumberBuffers; ++i){
int numSamples = ioData->mBuffers[i].mDataByteSize / 4;
for (int j = 0; j < numSamples; j++){
phases[k] += phaseStep;
int waveformIndex = (int)(phases[k] * n_wave) % n_wave;
// std::cout << amp << std::endl;
((int*)ioData->mBuffers[i].mData)[j] += (waveform[waveformIndex]*amp)*eachAmp;
}
}
}
return noErr;
}
void coreaudioinit(){
// adapted from https://stackoverflow.com/questions/1361148/how-do-i-synthesize-sounds-with-coreaudio-on-iphone-mac
std::cout << "initializing CoreAudio..." << std::endl;
const int kOutputBus = 0;
const int kInputBus = 1;
OSStatus status;
AudioComponentInstance audioUnit;
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
// desc.componentSubType = 'rioc';
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc);
status = AudioComponentInstanceNew(inputComponent, &audioUnit);
std::cout << "instanced? " << (status==noErr) << std::endl;
UInt32 flag = 1;
status = AudioUnitSetProperty(audioUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output,
kOutputBus,
&flag,
sizeof(flag));
std::cout << "flag set? " << (status==noErr) << std::endl;
AudioStreamBasicDescription audioFormat;
audioFormat.mSampleRate = SAMPLE_RATE;
audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
// audioFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 2;
audioFormat.mBitsPerChannel = 16;
audioFormat.mBytesPerPacket = 4;
audioFormat.mBytesPerFrame = 4;
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
kOutputBus,
&audioFormat,
sizeof(audioFormat));
std::cout << "format set? " << (status==noErr) << std::endl;
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = playbackCallback;
callbackStruct.inputProcRefCon = NULL;
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Global,
kOutputBus,
&callbackStruct,
sizeof(callbackStruct));
std::cout << "callback set? " << (status==noErr) << std::endl;
status = AudioUnitInitialize(audioUnit);
std::cout << "initialized? " << (status==noErr) << std::endl;
status = AudioOutputUnitStart(audioUnit);
std::cout << "started? " << (status==noErr) << std::endl;
}
void note_on(int pitch, int velocity){
std::cout << "note on: pitch=" << pitch << " velocity=" << velocity << std::endl;
amp_targs[pitch] = ((float)velocity/128.0) * 0.8 + 0.2;
}
void note_off(int pitch){
std::cout << "note off: pitch=" << pitch << std::endl;
amp_targs[pitch] = 0.0;
}
void midicallback( std::vector< unsigned char > *bytes ){
unsigned int nBytes = bytes->size();
if (nBytes < 1){
return;
}
int isNoteOn = (bytes->at(0)>>4)==0b1001;
int isNoteOff = (bytes->at(0)>>4)==0b1000;
if (!isNoteOn && !isNoteOff){
return;
}
int pitch = bytes->at(1);
int velocity = bytes->at(2);
if (velocity == 0 || isNoteOff){
note_off(pitch);
}else if (isNoteOn){
note_on(pitch,velocity);
}
}
static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ ){
// adapted from https://github.com/thestk/rtmidi
unsigned char status;
unsigned short nBytes, iByte, size;
unsigned long long time;
int ignoreFlags = 7;
bool continueSysex = false;
std::vector<unsigned char> bytes;
const MIDIPacket *packet = &list->packet[0];
for ( unsigned int i=0; i<list->numPackets; ++i ) {
nBytes = packet->length;
if ( nBytes == 0 ) {
packet = MIDIPacketNext( packet );
continue;
}
bool foundNonFiltered = false;
iByte = 0;
if ( continueSysex ) {
if ( !( ignoreFlags & 0x01 ) ) {
for ( unsigned int j=0; j<nBytes; ++j )
bytes.push_back( packet->data[j] );
}
continueSysex = packet->data[nBytes-1] != 0xF7;
if ( !( ignoreFlags & 0x01 ) && !continueSysex ) {
midicallback( &bytes );
bytes.clear();
}
}
else {
while ( iByte < nBytes ) {
size = 0;
status = packet->data[iByte];
if ( !(status & 0x80) ) break;
if ( status < 0xC0 ) size = 3;
else if ( status < 0xE0 ) size = 2;
else if ( status < 0xF0 ) size = 3;
else if ( status == 0xF0 ) {
if ( ignoreFlags & 0x01 ) {
size = 0;
iByte = nBytes;
}
else size = nBytes - iByte;
continueSysex = packet->data[nBytes-1] != 0xF7;
}
else if ( status == 0xF1 ) {
if ( ignoreFlags & 0x02 ) {
size = 0;
iByte += 2;
}
else size = 2;
}
else if ( status == 0xF2 ) size = 3;
else if ( status == 0xF3 ) size = 2;
else if ( status == 0xF8 && ( ignoreFlags & 0x02 ) ) {
size = 0;
iByte += 1;
}
else if ( status == 0xFE && ( ignoreFlags & 0x04 ) ) {
size = 0;
iByte += 1;
}
else size = 1;
if ( size ) {
foundNonFiltered = true;
bytes.assign( &packet->data[iByte], &packet->data[iByte+size] );
if ( !continueSysex ) {
midicallback( &bytes );
bytes.clear();
}
iByte += size;
}
}
}
packet = MIDIPacketNext(packet);
}
}
MIDIPortRef port;
MIDIEndpointRef endpoint;
void coremidiquit(){
MIDIEndpointDispose( endpoint );
MIDIPortDispose( port );
}
void coremidiinit(){
std::cout << "initializing CoreMIDI..." << std::endl;
OSStatus result;
MIDIClientRef client;
CFStringRef clientName = CFStringCreateWithCString( NULL, "client", kCFStringEncodingASCII );
result = MIDIClientCreate(clientName, NULL, NULL, &client );
std::cout << "client created? " << (result==noErr) << std::endl;
CFRelease( clientName );
int numPorts = MIDIGetNumberOfSources();
std::cout << "num ports? " << numPorts << std::endl;
if (numPorts <= 0){
std::cout << "exiting because no port." << std::endl;
exit(0);
}
char portName[128];
int portNumber;
for (int i = 0; i < numPorts; i++){
MIDIEndpointRef portRef = MIDIGetDestination(i);
portNumber = i;
CFStringRef nameRef = CFSTR("");
MIDIObjectGetStringProperty(portRef, kMIDIPropertyDisplayName, &nameRef);
CFStringGetCString( nameRef, portName, sizeof(portName), kCFStringEncodingUTF8 );
CFRelease( nameRef );
std::cout << "port #" << i << " : " << portName << std::endl;
}
std::cout << "using last port..." << std::endl;
CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName, kCFStringEncodingASCII );
result = MIDIInputPortCreate( client, portNameRef, midiInputCallback, NULL, &port );
CFRelease( portNameRef );
endpoint = MIDIGetSource( portNumber );
std::cout << "endpoint = " << endpoint << std::endl;
result = MIDIPortConnectSource( port, endpoint, NULL );
std::cout << "connected? " << (result==noErr) << std::endl;
// atexit(coremidiquit);
}
int main(){
for (int i = 0; i < 128; i++){
frqs[i] = 440.0 * pow(2, (float)(i-69)/12.0);
}
for(int i = 0; i < n_wave; i++) {
// SIN
// waveform[i] = sin(i * (M_PI * 2.) / (double) n_wave);
// SAWTOOTH
// waveform[i] = i/(double)n_wave;
// FRANKENSTEIN
waveform[i] = (i/(double)n_wave)*0.5 + sin(i * (M_PI * 2.) / (double) n_wave)*0.5;
}
coremidiinit();
coreaudioinit();
std::cout << "listening now: (press ctrl-C to quit)" << std::endl;
char input;
std::cin.get(input);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment