Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
matbouil committed Oct 3, 2022
0 parents commit 15316fd
Show file tree
Hide file tree
Showing 63 changed files with 6,142 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BassMidiAudioUnitHost/Soundfonts/Soundfont[[:space:]]-[[:space:]]FluidR3[[:space:]]GM[[:space:]](20011225).sf2 filter=lfs diff=lfs merge=lfs -text
95 changes: 95 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## User settings
xcuserdata/
*.pbxuser

# osx noise
.DS_Store
profile

## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout

## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3

## Obj-C/Swift specific
*.hmap

## App packaging
*.ipa
*.dSYM.zip
*.dSYM

## Playgrounds
timeline.xctimeline
playground.xcworkspace

# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm

.build/

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build/

# Accio dependency management
Dependencies/
.accio/

# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output

# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode

iOSInjectionProject/
25 changes: 25 additions & 0 deletions BassMidi/Audio Unit/BassMidiAudioUnit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// BassMidiAudioUnit.h
// BassMidi
//
// Created by Matthieu bouillaud on 17/03/2022.
//

#import <AudioToolbox/AudioToolbox.h>
#import <BassMidiAudioUnitFramework/BassMidiDSPKernelAdapter.h>
#import <AVFoundation/AVFoundation.h>

@interface BassMidiAudioUnit : AUAudioUnit

@property (nonatomic, readonly) BassMidiDSPKernelAdapter *kernelAdapter;
- (void)setupAudioBuses;
- (void)setupParameterTree;
- (void)setupParameterCallbacks;
+ (BOOL)initBassMidi:(UInt32)sampleRate;
- (BOOL)loadSoundFont:(NSString*)path;

@property (nonatomic) int32_t instrument;
@property (nonatomic) int32_t transposition;
@property (nonatomic) BOOL isMuted;

@end
182 changes: 182 additions & 0 deletions BassMidi/Audio Unit/BassMidiAudioUnit.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
//
// BassMidiAudioUnit.m
// BassMidi
//
// Created by Matthieu bouillaud on 17/03/2022.
//

#import "BassMidiAudioUnit.h"
#include <stdint.h>
#include "bassmidi.h"

@interface BassMidiAudioUnit () {
int32_t _instrument;
int32_t _transposition;
BOOL _isMuted;
}
@property (nonatomic, readwrite) AUParameterTree *parameterTree;
@property AUAudioUnitBusArray *inputBusArray;
@property AUAudioUnitBusArray *outputBusArray;
@end


@implementation BassMidiAudioUnit
@synthesize parameterTree = _parameterTree;

#define kInvalidInstrumentCode -1

- (instancetype)initWithComponentDescription:(AudioComponentDescription)componentDescription options:(AudioComponentInstantiationOptions)options error:(NSError **)outError {
self = [super initWithComponentDescription:componentDescription options:options error:outError];

if (self == nil) { return nil; }

_kernelAdapter = [[BassMidiDSPKernelAdapter alloc] init];
_instrument = kInvalidInstrumentCode;
_transposition = 0;
_isMuted = NO;
[self setupAudioBuses];
[self setupParameterTree];
[self setupParameterCallbacks];
return self;
}

#pragma mark - AUAudioUnit Setup

+ (BOOL)initBassMidi:(UInt32)sampleRate {
return BASS_Init(0, sampleRate, 0, NULL, NULL);
}

- (BOOL)loadSoundFont:(NSString*)filePath {
NSLog(@"loadSoundFont = %@", filePath);
return [_kernelAdapter prepareWithSoundFont:filePath];
}

- (int32_t)instrument {
return _instrument;
}

- (void)setInstrument:(int32_t)instrument {
_instrument = instrument;
[_kernelAdapter setInstrument: instrument];
}

- (int32_t)transposition {
return _transposition;
}

- (void)setTransposition:(int32_t)transposition {
_transposition = transposition;
[_kernelAdapter setTransposition:transposition];
}

- (BOOL)isMuted {
return _isMuted;
}

- (void)setIsMuted:(BOOL)isMuted {
_isMuted = isMuted;
[_kernelAdapter setIsMuted:isMuted];
}

- (void)setupAudioBuses {
// Create the input and output bus arrays.
_inputBusArray = [[AUAudioUnitBusArray alloc] initWithAudioUnit:self
busType:AUAudioUnitBusTypeInput
busses: @[_kernelAdapter.inputBus]];
_outputBusArray = [[AUAudioUnitBusArray alloc] initWithAudioUnit:self
busType:AUAudioUnitBusTypeOutput
busses: @[_kernelAdapter.outputBus]];
}

- (void)setupParameterTree {
// Create parameter objects.
}

- (void)setupParameterCallbacks {
// Make a local pointer to the kernel to avoid capturing self.
__block BassMidiDSPKernelAdapter * kernelAdapter = _kernelAdapter;

// implementorValueObserver is called when a parameter changes value.
_parameterTree.implementorValueObserver = ^(AUParameter *param, AUValue value) {
[kernelAdapter setParameter:param value:value];
};

// implementorValueProvider is called when the value needs to be refreshed.
_parameterTree.implementorValueProvider = ^(AUParameter *param) {
return [kernelAdapter valueForParameter:param];
};

// A function to provide string representations of parameter values.
_parameterTree.implementorStringFromValueCallback = ^(AUParameter *param, const AUValue *__nullable valuePtr) {
AUValue value = valuePtr == nil ? param.value : *valuePtr;

return [NSString stringWithFormat:@"%.f", value];
};
}

#pragma mark - AUAudioUnit Overrides

- (AUAudioFrameCount)maximumFramesToRender {
return _kernelAdapter.maximumFramesToRender;
}

- (void)setMaximumFramesToRender:(AUAudioFrameCount)maximumFramesToRender {
_kernelAdapter.maximumFramesToRender = maximumFramesToRender;
}

// If an audio unit has input, an audio unit's audio input connection points.
// Subclassers must override this property getter and should return the same object every time.
// See sample code.
- (AUAudioUnitBusArray *)inputBusses {
return _inputBusArray;
}

// An audio unit's audio output connection points.
// Subclassers must override this property getter and should return the same object every time.
// See sample code.
- (AUAudioUnitBusArray *)outputBusses {
return _outputBusArray;
}

// Allocate resources required to render.
// Subclassers should call the superclass implementation.
- (BOOL)allocateRenderResourcesAndReturnError:(NSError **)outError {
if (_kernelAdapter.outputBus.format.channelCount != _kernelAdapter.inputBus.format.channelCount) {
if (outError) {
*outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:kAudioUnitErr_FailedInitialization userInfo:nil];
}
// Notify superclass that initialization was not successful
self.renderResourcesAllocated = NO;

return NO;
}

[super allocateRenderResourcesAndReturnError:outError];
_kernelAdapter.musicalContextBlock = self.musicalContextBlock;
_kernelAdapter.MIDIOutBlock = self.MIDIOutputEventBlock;
if (_instrument != kInvalidInstrumentCode) {
[_kernelAdapter setInstrument:_instrument];
}
[_kernelAdapter setTransposition:_transposition];
[_kernelAdapter setIsMuted:_isMuted];
[_kernelAdapter allocateRenderResources];
return YES;
}

// Deallocate resources allocated in allocateRenderResourcesAndReturnError:
// Subclassers should call the superclass implementation.
- (void)deallocateRenderResources {
[_kernelAdapter deallocateRenderResources];

// Deallocate your resources.
[super deallocateRenderResources];
}

#pragma mark - AUAudioUnit (AUAudioUnitImplementation)

// Block which subclassers must provide to implement rendering.
- (AUInternalRenderBlock)internalRenderBlock {
return _kernelAdapter.internalRenderBlock;
}

@end
63 changes: 63 additions & 0 deletions BassMidi/Audio Unit/BassMidiAudioUnit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import AVFoundation

public struct BassMidiAudioUnitContainer {
public let avAudioUnit: AVAudioUnit
public let audioUnit: BassMidiAudioUnit
}

extension BassMidiAudioUnit {

enum BassMidiAudioUnitError: Error {
case unknown
case soundFontLoading
}

private static let subtype: FourCharCode = "bass"
private static let manufacturer: FourCharCode = "nwzk"
private static let componentName = "nwzk: BassMidi"
private static let version: UInt32 = UInt32.max
private static var registered = false

private static let componentDescription: AudioComponentDescription = {
// Ensure that AudioUnit type, subtype, and manufacturer match the extension's Info.plist values.
var componentDescription = AudioComponentDescription()
componentDescription.componentType = kAudioUnitType_MusicDevice
componentDescription.componentSubType = subtype
componentDescription.componentManufacturer = manufacturer
componentDescription.componentFlags = 0
componentDescription.componentFlagsMask = 0
return componentDescription
}()

private static func register() {
guard !registered else { return }
AUAudioUnit.registerSubclass(BassMidiAudioUnit.self,
as: componentDescription,
name: componentName,
version: version)
registered = true
}

public static func setup(sampleRate: UInt32) {
initBassMidi(sampleRate)
}

public static func instantiate(soundFontPath: String, _ completion: @escaping (Result<BassMidiAudioUnitContainer, Error>) -> Void) {
BassMidiAudioUnit.register()
AVAudioUnit.instantiate(with: componentDescription, options: []) { unit, error in
if let unit = unit, let bassMidiAudioUnit = unit.auAudioUnit as? BassMidiAudioUnit {
let container = BassMidiAudioUnitContainer(avAudioUnit: unit, audioUnit: bassMidiAudioUnit)
guard bassMidiAudioUnit.loadSoundFont(soundFontPath) else {
completion(.failure(BassMidiAudioUnitError.soundFontLoading))
return
}
completion(.success(container))
} else if let error = error {
completion(.failure(error))
} else {
completion(.failure(BassMidiAudioUnitError.unknown))
}
}
}
}

14 changes: 14 additions & 0 deletions BassMidi/Audio Unit/BassMidiParameters.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation
import AVFoundation

extension BassMidiAudioUnit {

private func paramValue(address: AUParameterAddress) -> AUValue? {
parameterTree?.parameter(withAddress: address)?.value
}

private func setParam(value: AUValue, address: AUParameterAddress) {
parameterTree?.parameter(withAddress: address)?.value = value
}

}
Loading

0 comments on commit 15316fd

Please sign in to comment.