├── .gitignore ├── AudioKit-Experiments ├── AudioKit-Experiments-Effects │ ├── AudioKit-Experiments-Effects.entitlements │ ├── AudioUnitViewController.swift │ ├── Info.plist │ ├── MainInterface.storyboard │ └── UIKitKnob.swift ├── AudioKit-Experiments-MIDI │ ├── AudioKit-Experiments-MIDI.entitlements │ ├── Common │ │ ├── Audio Unit │ │ │ ├── AudioKit_Experiments_MIDIAudioUnit.h │ │ │ └── AudioKit_Experiments_MIDIAudioUnit.mm │ │ ├── AudioKit_Experiments_MIDI-Bridging-Header.h │ │ ├── DSP │ │ │ └── AudioKit_Experiments_MIDIAUProcessHelper.hpp │ │ ├── Parameters │ │ │ └── ParameterSpecBase.swift │ │ ├── UI │ │ │ ├── AudioUnitViewController.swift │ │ │ ├── Base.lproj │ │ │ │ └── MainInterface.storyboard │ │ │ └── ObservableAUParameter.swift │ │ └── Utility │ │ │ ├── CrossPlatform.swift │ │ │ └── String+Utils.swift │ ├── DSP │ │ └── AudioKit_Experiments_MIDIDSPKernel.hpp │ ├── Info.plist │ ├── Parameters │ │ ├── AudioKit_Experiments_MIDIParameterAddresses.h │ │ └── Parameters.swift │ ├── README.md │ └── UI │ │ ├── AudioKit_Experiments_MIDIMainView.swift │ │ ├── MomentaryButton.swift │ │ └── ParameterSlider.swift ├── AudioKit-Experiments.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ │ └── Package.resolved │ │ └── xcuserdata │ │ │ └── nickculbertson.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ ├── xcshareddata │ │ └── xcschemes │ │ │ ├── AudioKit-Experiments-MIDI.xcscheme │ │ │ └── AudioKit-Experiments.xcscheme │ └── xcuserdata │ │ └── nickculbertson.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── AudioKit-Experiments-Effect.xcscheme │ │ └── xcschememanagement.plist ├── AudioKit-Experiments │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── SynthKnobLarge.imageset │ │ │ ├── Contents.json │ │ │ └── knob1.png │ │ ├── visualizerBG.imageset │ │ │ ├── Contents.json │ │ │ └── visualizerBG.png │ │ └── visualizerBG2.imageset │ │ │ ├── Contents.json │ │ │ └── visualizerBG2.png │ ├── AudioKit-Experiments.entitlements │ ├── AudioKit_ExperimentsApp.swift │ ├── ContentView.swift │ ├── CookbookKnob.swift │ ├── Examples │ │ ├── AUv3Effects.swift │ │ ├── Arpeggiator.swift │ │ ├── CircularVisualizer.swift │ │ ├── InstrumentAUPreset.swift │ │ ├── InstrumentSFZ.swift │ │ ├── LFOView.swift │ │ ├── LinearVisualizer.swift │ │ ├── PolyphonicSynth.swift │ │ ├── RandomMIDI.swift │ │ ├── RecorderView.swift │ │ ├── SoundFont+MIDI.swift │ │ ├── SoundFont.swift │ │ └── SpriteSound.swift │ ├── FFTView2.swift │ ├── Info.plist │ ├── NodeRecorder2.swift │ ├── ParameterRow.swift │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ └── SwiftUIElements.swift ├── AudioKit-ExperimentsTests │ └── AudioKit_ExperimentsTests.swift ├── AudioKit-ExperimentsUITests │ ├── AudioKit_ExperimentsUITests.swift │ └── AudioKit_ExperimentsUITestsLaunchTests.swift ├── DunneAudioKit │ ├── .github │ │ ├── CODEOWNERS │ │ └── workflows │ │ │ └── tests.yml │ ├── .gitignore │ ├── LICENSE │ ├── Package.swift │ ├── README.md │ ├── Sources │ │ ├── CDunneAudioKit │ │ │ ├── DunneCore │ │ │ │ ├── Common │ │ │ │ │ ├── ADSREnvelope.cpp │ │ │ │ │ ├── ADSREnvelope.h │ │ │ │ │ ├── AHDSHREnvelope.cpp │ │ │ │ │ ├── AHDSHREnvelope.h │ │ │ │ │ ├── AudioKitCore.h │ │ │ │ │ ├── CoreEnvelope.h │ │ │ │ │ ├── DrawbarsOscillator.h │ │ │ │ │ ├── EnsembleOscillator.h │ │ │ │ │ ├── EnvelopeGeneratorBase.cpp │ │ │ │ │ ├── EnvelopeGeneratorBase.h │ │ │ │ │ ├── FunctionTable.cpp │ │ │ │ │ ├── FunctionTable.h │ │ │ │ │ ├── LinearParameterRamp.h │ │ │ │ │ ├── LinearRamper.h │ │ │ │ │ ├── MultiStageFilter.h │ │ │ │ │ ├── ParameterRampBase.h │ │ │ │ │ ├── README.md │ │ │ │ │ ├── ResonantLowPassFilter.cpp │ │ │ │ │ ├── ResonantLowPassFilter.h │ │ │ │ │ ├── SustainPedalLogic.cpp │ │ │ │ │ ├── SustainPedalLogic.h │ │ │ │ │ ├── SynthVoice.h │ │ │ │ │ ├── WaveStack.h │ │ │ │ │ └── wavpack.h │ │ │ │ ├── Modulated Delay │ │ │ │ │ ├── AdjustableDelayLine.cpp │ │ │ │ │ ├── AdjustableDelayLine.h │ │ │ │ │ ├── ModulatedDelay.cpp │ │ │ │ │ ├── ModulatedDelay.h │ │ │ │ │ ├── ModulatedDelay_Defines.h │ │ │ │ │ ├── StereoDelay.cpp │ │ │ │ │ └── StereoDelay.h │ │ │ │ ├── README.md │ │ │ │ ├── Sampler │ │ │ │ │ ├── CoreSampler.cpp │ │ │ │ │ ├── CoreSampler.h │ │ │ │ │ ├── README.md │ │ │ │ │ ├── SampleBuffer.cpp │ │ │ │ │ ├── SampleBuffer.h │ │ │ │ │ ├── SampleOscillator.h │ │ │ │ │ ├── SamplerVoice.cpp │ │ │ │ │ ├── SamplerVoice.h │ │ │ │ │ └── Wavpack │ │ │ │ │ │ ├── common_utils.c │ │ │ │ │ │ ├── decorr_tables.h │ │ │ │ │ │ ├── decorr_utils.c │ │ │ │ │ │ ├── entropy_utils.c │ │ │ │ │ │ ├── extra1.c │ │ │ │ │ │ ├── extra2.c │ │ │ │ │ │ ├── license.txt │ │ │ │ │ │ ├── open_filename.c │ │ │ │ │ │ ├── open_legacy.c │ │ │ │ │ │ ├── open_raw.c │ │ │ │ │ │ ├── open_utils.c │ │ │ │ │ │ ├── pack.c │ │ │ │ │ │ ├── pack_dns.c │ │ │ │ │ │ ├── pack_dsd.c │ │ │ │ │ │ ├── pack_floats.c │ │ │ │ │ │ ├── pack_utils.c │ │ │ │ │ │ ├── read_words.c │ │ │ │ │ │ ├── tag_utils.c │ │ │ │ │ │ ├── tags.c │ │ │ │ │ │ ├── unpack.c │ │ │ │ │ │ ├── unpack3.c │ │ │ │ │ │ ├── unpack3.h │ │ │ │ │ │ ├── unpack3_open.c │ │ │ │ │ │ ├── unpack3_seek.c │ │ │ │ │ │ ├── unpack_dsd.c │ │ │ │ │ │ ├── unpack_floats.c │ │ │ │ │ │ ├── unpack_seek.c │ │ │ │ │ │ ├── unpack_utils.c │ │ │ │ │ │ ├── wavpack_local.h │ │ │ │ │ │ ├── wavpack_version.h │ │ │ │ │ │ └── write_words.c │ │ │ │ └── Synth │ │ │ │ │ ├── CoreSynth.cpp │ │ │ │ │ ├── CoreSynth.h │ │ │ │ │ ├── DrawbarsOscillator.cpp │ │ │ │ │ ├── EnsembleOscillator.cpp │ │ │ │ │ ├── Envelope.cpp │ │ │ │ │ ├── MultiStageFilter.cpp │ │ │ │ │ ├── SynthVoice.cpp │ │ │ │ │ └── WaveStack.cpp │ │ │ ├── ModulatedDelayDSP.mm │ │ │ ├── SamplerDSP.mm │ │ │ ├── StereoDelayDSP.mm │ │ │ ├── SynthDSP.mm │ │ │ ├── TransientShaperDSP.mm │ │ │ └── include │ │ │ │ ├── CDunneAudioKit.h │ │ │ │ ├── ModulatedDelayDSP.h │ │ │ │ ├── ModulatedDelay_Typedefs.h │ │ │ │ ├── SamplerDSP.h │ │ │ │ ├── Sampler_Typedefs.h │ │ │ │ └── SynthDSP.h │ │ └── DunneAudioKit │ │ │ ├── Chorus.swift │ │ │ ├── DunneAudioKit.docc │ │ │ ├── DunneAudioKit.md │ │ │ ├── ModulatedDelayEffects.md │ │ │ ├── ModulationEffects.md │ │ │ ├── Preparing-Sample-Sets.md │ │ │ ├── Resources │ │ │ │ └── ModDelay.svg │ │ │ ├── Sampler Audio Unit and Node.md │ │ │ ├── Sampler-SFZ-files.md │ │ │ ├── Sampler-descriptors.md │ │ │ └── Sampler.md │ │ │ ├── Flanger.swift │ │ │ ├── Sampler+SFZ.swift │ │ │ ├── Sampler.swift │ │ │ ├── StereoDelay.swift │ │ │ ├── Synth.swift │ │ │ └── TransientShaper.swift │ └── Tests │ │ └── DunneAudioKitTests │ │ ├── GenericNodeTests.swift │ │ ├── SamplerTests.swift │ │ ├── SynthTests.swift │ │ ├── TestResources │ │ └── 12345.wav │ │ ├── TransientShaperTests.swift │ │ └── ValidatedMD5s.swift └── Sounds │ ├── Instrument1.aupreset │ ├── Piano.mp3 │ ├── PianoMuted.sf2 │ ├── SFZSamples │ └── saw220.wav │ ├── mute.wav │ ├── saw1.wav │ └── sqr.SFZ ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | AudioKit-Experiments/Sounds/.DS_Store 3 | AudioKit-Experiments/.DS_Store 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-Effects/AudioKit-Experiments-Effects.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-Effects/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionAttributes 8 | 9 | AudioComponents 10 | 11 | 12 | description 13 | AudioKit_Experiments_Effects 14 | factoryFunction 15 | $(PRODUCT_MODULE_NAME).AudioUnitViewController 16 | manufacturer 17 | TEST 18 | name 19 | Moby Pixel: AudioKit_Experiments_Effects 20 | sandboxSafe 21 | 22 | subtype 23 | abc3 24 | tags 25 | 26 | Effect 27 | 28 | type 29 | aufx 30 | version 31 | 67072 32 | 33 | 34 | 35 | NSExtensionMainStoryboard 36 | MainInterface 37 | NSExtensionPointIdentifier 38 | com.apple.AudioUnit-UI 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-MIDI/AudioKit-Experiments-MIDI.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-MIDI/Common/Audio Unit/AudioKit_Experiments_MIDIAudioUnit.h: -------------------------------------------------------------------------------- 1 | // 2 | // AudioKit_Experiments_MIDIAudioUnit.h 3 | // AudioKit-Experiments-MIDI 4 | // 5 | // Created by Nick Culbertson on 11/20/23. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | @interface AudioKit_Experiments_MIDIAudioUnit : AUAudioUnit 12 | - (void)setupParameterTree:(AUParameterTree *)parameterTree; 13 | @end 14 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-MIDI/Common/AudioKit_Experiments_MIDI-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // AudioKit_Experiments_MIDI-Bridging-Header.h 3 | // AudioKit-Experiments-MIDI 4 | // 5 | // Created by Nick Culbertson on 11/20/23. 6 | // 7 | 8 | #import "AudioKit_Experiments_MIDIAudioUnit.h" 9 | #import "AudioKit_Experiments_MIDIParameterAddresses.h" 10 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-MIDI/Common/DSP/AudioKit_Experiments_MIDIAUProcessHelper.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // AudioKit_Experiments_MIDIAUProcessHelper.hpp 3 | // AudioKit-Experiments-MIDI 4 | // 5 | // Created by Nick Culbertson on 11/20/23. 6 | // 7 | 8 | #pragma once 9 | 10 | #import 11 | #import 12 | 13 | #include 14 | #include "AudioKit_Experiments_MIDIDSPKernel.hpp" 15 | 16 | //MARK:- AUProcessHelper Utility Class 17 | class AUProcessHelper 18 | { 19 | public: 20 | AUProcessHelper(AudioKit_Experiments_MIDIDSPKernel& kernel) 21 | : mKernel{kernel} {} 22 | 23 | /** 24 | This function handles the event list processing and rendering loop for you. 25 | Call it inside your internalRenderBlock. 26 | */ 27 | void processWithEvents(AudioTimeStamp const *timestamp, AUAudioFrameCount frameCount, AURenderEvent const *events) { 28 | 29 | AUEventSampleTime now = AUEventSampleTime(timestamp->mSampleTime); 30 | AUAudioFrameCount framesRemaining = frameCount; 31 | AURenderEvent const *nextEvent = events; // events is a linked list, at the beginning, the nextEvent is the first event 32 | 33 | while (framesRemaining > 0) { 34 | // If there are no more events, we can process the entire remaining segment and exit. 35 | if (nextEvent == nullptr) { 36 | mKernel.process(now, framesRemaining); 37 | return; 38 | } 39 | 40 | // **** start late events late. 41 | auto timeZero = AUEventSampleTime(0); 42 | auto headEventTime = nextEvent->head.eventSampleTime; 43 | AUAudioFrameCount framesThisSegment = AUAudioFrameCount(std::max(timeZero, headEventTime - now)); 44 | 45 | // Compute everything before the next event. 46 | if (framesThisSegment > 0) { 47 | mKernel.process(now, framesThisSegment); 48 | 49 | // Advance frames. 50 | framesRemaining -= framesThisSegment; 51 | 52 | // Advance time. 53 | now += AUEventSampleTime(framesThisSegment); 54 | } 55 | 56 | nextEvent = performAllSimultaneousEvents(now, nextEvent); 57 | } 58 | } 59 | 60 | AURenderEvent const * performAllSimultaneousEvents(AUEventSampleTime now, AURenderEvent const *event) { 61 | do { 62 | mKernel.handleOneEvent(now, event); 63 | 64 | // Go to next event. 65 | event = event->head.next; 66 | 67 | // While event is not null and is simultaneous (or late). 68 | } while (event && event->head.eventSampleTime <= now); 69 | return event; 70 | } 71 | private: 72 | AudioKit_Experiments_MIDIDSPKernel& mKernel; 73 | }; 74 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-MIDI/Common/UI/Base.lproj/MainInterface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-MIDI/Common/Utility/CrossPlatform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrossPlatform.swift 3 | // AudioKit-Experiments-MIDI 4 | // 5 | // Created by Nick Culbertson on 11/20/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | #if os(iOS) 12 | typealias HostingController = UIHostingController 13 | #elseif os(macOS) 14 | typealias HostingController = NSHostingController 15 | 16 | extension NSView { 17 | 18 | func bringSubviewToFront(_ view: NSView) { 19 | // This function is a no-opp for macOS 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-MIDI/Common/Utility/String+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Utils.swift 3 | // AudioKit-Experiments-MIDI 4 | // 5 | // Created by Nick Culbertson on 11/20/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String { 11 | var range: NSRange { 12 | NSRange(location: 0, length: count) 13 | } 14 | 15 | func isAlphanumeric() -> Bool { 16 | if self.isEmpty { return false } 17 | let regex = try! NSRegularExpression(pattern: "^[a-zA-Z0-9_-]*$", options: .caseInsensitive) 18 | guard regex.firstMatch(in: self, options: [], range: range) != nil else { 19 | return false 20 | } 21 | return true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-MIDI/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionAttributes 8 | 9 | AudioComponents 10 | 11 | 12 | description 13 | AudioKit_Experiments_MIDI 14 | factoryFunction 15 | $(PRODUCT_MODULE_NAME).AudioUnitViewController 16 | manufacturer 17 | TEST 18 | name 19 | Moby Pixel: AudioKit_Experiments_MIDI 20 | sandboxSafe 21 | 22 | subtype 23 | abc1 24 | tags 25 | 26 | MIDI Processor 27 | 28 | type 29 | aumi 30 | version 31 | 67072 32 | 33 | 34 | 35 | NSExtensionMainStoryboard 36 | MainInterface 37 | NSExtensionPointIdentifier 38 | com.apple.AudioUnit-UI 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-MIDI/Parameters/AudioKit_Experiments_MIDIParameterAddresses.h: -------------------------------------------------------------------------------- 1 | // 2 | // AudioKit_Experiments_MIDIParameterAddresses.h 3 | // AudioKit-Experiments-MIDI 4 | // 5 | // Created by Nick Culbertson on 11/20/23. 6 | // 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #ifdef __cplusplus 13 | namespace AudioKit_Experiments_MIDIParameterAddress { 14 | #endif 15 | 16 | typedef NS_ENUM(AUParameterAddress, AudioKit_Experiments_MIDIParameterAddress) { 17 | sendNote = 0, 18 | midiNoteNumber = 1 19 | }; 20 | 21 | #ifdef __cplusplus 22 | } 23 | #endif 24 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-MIDI/Parameters/Parameters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parameters.swift 3 | // AudioKit-Experiments-MIDI 4 | // 5 | // Created by Nick Culbertson on 11/20/23. 6 | // 7 | 8 | import Foundation 9 | import AudioToolbox 10 | 11 | let AudioKit_Experiments_MIDIParameterSpecs = ParameterTreeSpec { 12 | ParameterGroupSpec(identifier: "global", name: "Global") { 13 | ParameterSpec( 14 | address: .sendNote, 15 | identifier: "sendNote", 16 | name: "Send Note", 17 | units: .boolean, 18 | valueRange: 0...1, 19 | defaultValue: 0 20 | ) 21 | 22 | ParameterSpec( 23 | address: .midiNoteNumber, 24 | identifier: "midiNoteNumber", 25 | name: "MIDI Note Number", 26 | units: .midiNoteNumber, 27 | valueRange: 0...127, 28 | defaultValue: 60, 29 | flags: [.flag_IsWritable] // so that hosts like AUM expose this as automatable 30 | ) 31 | } 32 | } 33 | 34 | extension ParameterSpec { 35 | init( 36 | address: AudioKit_Experiments_MIDIParameterAddress, 37 | identifier: String, 38 | name: String, 39 | units: AudioUnitParameterUnit, 40 | valueRange: ClosedRange, 41 | defaultValue: AUValue, 42 | unitName: String? = nil, 43 | flags: AudioUnitParameterOptions = [AudioUnitParameterOptions.flag_IsWritable, AudioUnitParameterOptions.flag_IsReadable], 44 | valueStrings: [String]? = nil, 45 | dependentParameters: [NSNumber]? = nil 46 | ) { 47 | self.init(address: address.rawValue, 48 | identifier: identifier, 49 | name: name, 50 | units: units, 51 | valueRange: valueRange, 52 | defaultValue: defaultValue, 53 | unitName: unitName, 54 | flags: flags, 55 | valueStrings: valueStrings, 56 | dependentParameters: dependentParameters) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-MIDI/UI/AudioKit_Experiments_MIDIMainView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioKit_Experiments_MIDIMainView.swift 3 | // AudioKit-Experiments-MIDI 4 | // 5 | // Created by Nick Culbertson on 11/20/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AudioKit_Experiments_MIDIMainView: View { 11 | var parameterTree: ObservableAUParameterGroup 12 | 13 | var body: some View { 14 | VStack { 15 | Text("This is a basic AUv3 MIDI test. Add as a MIDI processor, hit play, and hear random notes play.").padding(5) 16 | // ParameterSlider(param: parameterTree.global.midiNoteNumber) 17 | // .padding() 18 | // MomentaryButton( 19 | // "Play note", 20 | // param: parameterTree.global.sendNote 21 | // ) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-MIDI/UI/MomentaryButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MomentaryButton.swift 3 | // AudioKit-Experiments-MIDI 4 | // 5 | // Created by Nick Culbertson on 11/20/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct MomentaryButton: View { 11 | let normalColor = Color(red: 51.0 / 255.0, green: 51.0 / 255.0, blue: 51.0 / 255.0) 12 | let activeColor = Color(red: 50.0 / 255.0, green: 103.0 / 255.0, blue: 222.0 / 255.0) 13 | 14 | init(_ text: String, param: ObservableAUParameter) { 15 | self.text = text 16 | self.param = param 17 | } 18 | 19 | var text: String 20 | 21 | /// The value that this button should bind to 22 | /// 23 | /// The paramter value is treated as a bool, with 0.0 and 1.0 mapping to false and true, respectively 24 | @ObservedObject var param: ObservableAUParameter 25 | 26 | var value: Bool { 27 | get { 28 | param.value != 0 29 | } 30 | nonmutating set { 31 | param.value = newValue ? 1.0 : 0.0 32 | } 33 | } 34 | 35 | var body: some View { 36 | Text("\(text)") 37 | .padding() 38 | .background { 39 | value ? activeColor : normalColor 40 | } 41 | .cornerRadius(9.0) 42 | .simultaneousGesture( 43 | DragGesture(minimumDistance: 0, coordinateSpace: .local) 44 | .onChanged({ _ in 45 | if !value { 46 | value = true 47 | } 48 | }) 49 | .onEnded({ _ in 50 | value = false 51 | }) 52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments-MIDI/UI/ParameterSlider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParameterSlider.swift 3 | // AudioKit-Experiments-MIDI 4 | // 5 | // Created by Nick Culbertson on 11/20/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | /// A SwiftUI Slider container which is bound to an ObservableAUParameter 11 | /// 12 | /// This view wraps a SwiftUI Slider, and provides it relevant data from the Parameter, like the minimum and maximum values. 13 | struct ParameterSlider: View { 14 | @ObservedObject var param: ObservableAUParameter 15 | 16 | var specifier: String { 17 | switch param.unit { 18 | case .midiNoteNumber: 19 | return "%.0f" 20 | default: 21 | return "%.2f" 22 | } 23 | } 24 | 25 | var body: some View { 26 | VStack { 27 | Slider( 28 | value: $param.value, 29 | in: param.min...param.max, 30 | onEditingChanged: param.onEditingChanged, 31 | minimumValueLabel: Text("\(param.min, specifier: specifier)"), 32 | maximumValueLabel: Text("\(param.max, specifier: specifier)") 33 | ) { 34 | EmptyView() 35 | } 36 | Text("\(param.displayName): \(param.value, specifier: specifier)") 37 | } 38 | .padding() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "AudioKit", 6 | "repositoryURL": "https://github.com/AudioKit/AudioKit", 7 | "state": { 8 | "branch": null, 9 | "revision": "82cab9d4c168b5b03b67e2ff099819f589b530f7", 10 | "version": "5.6.2" 11 | } 12 | }, 13 | { 14 | "package": "AudioKitEX", 15 | "repositoryURL": "https://github.com/AudioKit/AudioKitEX", 16 | "state": { 17 | "branch": null, 18 | "revision": "58fa4a002028a2addf96466dd5ad68182e7d387e", 19 | "version": "5.6.0" 20 | } 21 | }, 22 | { 23 | "package": "AudioKitUI", 24 | "repositoryURL": "https://github.com/AudioKit/AudioKitUI", 25 | "state": { 26 | "branch": null, 27 | "revision": "a5b6c27236f09a9591b95e9136b0b91ac77ba6f2", 28 | "version": "0.3.6" 29 | } 30 | }, 31 | { 32 | "package": "Controls", 33 | "repositoryURL": "https://github.com/AudioKit/Controls.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "6861a9380963378115856f0b9b54414a9ec5f1ad", 37 | "version": "1.1.2" 38 | } 39 | }, 40 | { 41 | "package": "Keyboard", 42 | "repositoryURL": "https://github.com/AudioKit/Keyboard", 43 | "state": { 44 | "branch": null, 45 | "revision": "fb589acbbb0854da0fa571ad83c4f1230c2cd280", 46 | "version": "1.3.6" 47 | } 48 | }, 49 | { 50 | "package": "KissFFT", 51 | "repositoryURL": "https://github.com/AudioKit/KissFFT", 52 | "state": { 53 | "branch": null, 54 | "revision": "dd0636e151724b8ba2e0908eba4d99a6ff24d00c", 55 | "version": "1.0.0" 56 | } 57 | }, 58 | { 59 | "package": "MIDIKit", 60 | "repositoryURL": "https://github.com/orchetect/MIDIKit", 61 | "state": { 62 | "branch": null, 63 | "revision": "be3342e9b0af495f571b8851bd2f3507b95304db", 64 | "version": "0.9.3" 65 | } 66 | }, 67 | { 68 | "package": "SoundpipeAudioKit", 69 | "repositoryURL": "https://github.com/AudioKit/SoundpipeAudioKit", 70 | "state": { 71 | "branch": null, 72 | "revision": "ee7d9542078ae48b24744a67c0350a12f9de354b", 73 | "version": "5.6.1" 74 | } 75 | }, 76 | { 77 | "package": "SporthAudioKit", 78 | "repositoryURL": "https://github.com/AudioKit/SporthAudioKit", 79 | "state": { 80 | "branch": null, 81 | "revision": "638c95036430ce23ba2810101de319ed31f481bb", 82 | "version": "5.5.1" 83 | } 84 | }, 85 | { 86 | "package": "TimecodeKit", 87 | "repositoryURL": "https://github.com/orchetect/TimecodeKit", 88 | "state": { 89 | "branch": null, 90 | "revision": "72aaacf95fd9200b64ce0d0281a783a5ef17793c", 91 | "version": "2.0.4" 92 | } 93 | }, 94 | { 95 | "package": "Tonic", 96 | "repositoryURL": "https://github.com/AudioKit/Tonic.git", 97 | "state": { 98 | "branch": null, 99 | "revision": "bd73003b0e6caa90fb1d71b57329b0d609b097ee", 100 | "version": "1.0.10" 101 | } 102 | }, 103 | { 104 | "package": "Waveform", 105 | "repositoryURL": "https://github.com/AudioKit/Waveform", 106 | "state": { 107 | "branch": null, 108 | "revision": "79103e766434eb32aff9f0b169a469fce0af9ebd", 109 | "version": "1.0.2" 110 | } 111 | } 112 | ] 113 | }, 114 | "version": 1 115 | } 116 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments.xcodeproj/project.xcworkspace/xcuserdata/nickculbertson.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NickCulbertson/AudioKit-Experiments/c2d1a94322f9cd5aa646f9b7873854ed2e3ae32d/AudioKit-Experiments/AudioKit-Experiments.xcodeproj/project.xcworkspace/xcuserdata/nickculbertson.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments.xcodeproj/xcshareddata/xcschemes/AudioKit-Experiments-MIDI.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 46 | 47 | 59 | 61 | 67 | 68 | 69 | 70 | 78 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments.xcodeproj/xcshareddata/xcschemes/AudioKit-Experiments.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 42 | 44 | 50 | 51 | 52 | 53 | 59 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments.xcodeproj/xcuserdata/nickculbertson.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments.xcodeproj/xcuserdata/nickculbertson.xcuserdatad/xcschemes/AudioKit-Experiments-Effect.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 46 | 47 | 59 | 61 | 67 | 68 | 69 | 70 | 78 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Assets.xcassets/SynthKnobLarge.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "knob1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Assets.xcassets/SynthKnobLarge.imageset/knob1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NickCulbertson/AudioKit-Experiments/c2d1a94322f9cd5aa646f9b7873854ed2e3ae32d/AudioKit-Experiments/AudioKit-Experiments/Assets.xcassets/SynthKnobLarge.imageset/knob1.png -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Assets.xcassets/visualizerBG.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "visualizerBG.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Assets.xcassets/visualizerBG.imageset/visualizerBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NickCulbertson/AudioKit-Experiments/c2d1a94322f9cd5aa646f9b7873854ed2e3ae32d/AudioKit-Experiments/AudioKit-Experiments/Assets.xcassets/visualizerBG.imageset/visualizerBG.png -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Assets.xcassets/visualizerBG2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "visualizerBG2.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Assets.xcassets/visualizerBG2.imageset/visualizerBG2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NickCulbertson/AudioKit-Experiments/c2d1a94322f9cd5aa646f9b7873854ed2e3ae32d/AudioKit-Experiments/AudioKit-Experiments/Assets.xcassets/visualizerBG2.imageset/visualizerBG2.png -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/AudioKit-Experiments.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.device.audio-input 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/AudioKit_ExperimentsApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import AVFoundation 3 | import AudioKit 4 | 5 | @main 6 | struct AudioKit_ExperimentsApp: App { 7 | init() { 8 | #if os(iOS) 9 | do { 10 | // Settings.sampleRate default is 44_100 11 | if #available(iOS 18.0, *) { 12 | if !ProcessInfo.processInfo.isMacCatalystApp && !ProcessInfo.processInfo.isiOSAppOnMac { 13 | // Set samplerRate for iOS 18 and newer (not on macOS) 14 | Settings.sampleRate = 48_000 15 | } 16 | } 17 | if #available(macOS 15.0, *) { 18 | // Set samplerRate for macOS 15 and newer (reverted back to 44_100) 19 | Settings.sampleRate = 44_100 20 | } 21 | 22 | Settings.bufferLength = .medium 23 | try AVAudioSession.sharedInstance().setPreferredIOBufferDuration(Settings.bufferLength.duration) 24 | try AVAudioSession.sharedInstance().setCategory(.playAndRecord, 25 | options: [.defaultToSpeaker, .mixWithOthers, .allowBluetoothA2DP]) 26 | try AVAudioSession.sharedInstance().setActive(true) 27 | } catch let err { 28 | print(err) 29 | } 30 | #endif 31 | } 32 | var body: some Scene { 33 | WindowGroup { 34 | ContentView() 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | struct ContentView: View { 3 | var body: some View { 4 | NavigationView { 5 | MasterView() 6 | }.navigationViewStyle(StackNavigationViewStyle()) 7 | } 8 | } 9 | struct MasterView: View { 10 | var body: some View { 11 | Form { 12 | Section(header: Text("Demos")) { 13 | Group { 14 | NavigationLink("1. SoundFont Player", destination: SoundFontView()) 15 | NavigationLink("2. Circular Visualizer", destination: CircularVisualizerView()) 16 | NavigationLink("3. Linear Visualizer", destination: LinearVisualizerView()) 17 | NavigationLink("4. SpriteKit Audio", destination: SpriteSoundView()) 18 | NavigationLink("5. Sampler Synth", destination: RecordView()) 19 | NavigationLink("6. LFO Timer", destination: LFOView()) 20 | NavigationLink("7. Instrument AUPreset", destination: InstrumentAUPresetView()) 21 | NavigationLink("8. Instrument SFZ", destination: InstrumentSFZView()) 22 | NavigationLink("9. Polyphonic Synth", destination: PolyphonicSynthView()) 23 | } 24 | Group { 25 | NavigationLink("10. Arpeggiator", destination: ArpeggiatorView()) 26 | NavigationLink("11. MIDI AUv3", destination: RandomMIDIView()) 27 | NavigationLink("12. Effects AUv3", destination: AUv3EffectsView()) 28 | } 29 | } 30 | }.navigationBarTitle("AudioKit Experiments") 31 | } 32 | } 33 | 34 | extension NSNotification.Name { 35 | static let keyNoteOn = Notification.Name("keyNoteOn") 36 | static let keyNoteOff = Notification.Name("keyNoteOff") 37 | static let MIDIKey = Notification.Name("MIDIKey") 38 | } 39 | 40 | #Preview { 41 | ContentView() 42 | } 43 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/CookbookKnob.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | import Controls 3 | import SwiftUI 4 | 5 | public struct CookbookKnob: View { 6 | var text: String 7 | @Binding var parameter: AUValue 8 | var range: ClosedRange 9 | var format: String = "%0.2f" 10 | var units: String = "" 11 | 12 | public init(text: String, 13 | parameter: Binding, 14 | range: ClosedRange, 15 | units: String = "") { 16 | _parameter = parameter 17 | self.text = text 18 | self.range = range 19 | self.units = units 20 | } 21 | 22 | public var body: some View { 23 | VStack { 24 | VStack { 25 | Text(text) 26 | .minimumScaleFactor(0.2) 27 | .lineLimit(2) 28 | .multilineTextAlignment(.center) 29 | if units == "" || units == "Generic" { 30 | Text("\(parameter, specifier: format)") 31 | .lineLimit(1) 32 | } else if units == "%" || units == "Percent" { 33 | Text("\(parameter * 100, specifier: "%0.f")%") 34 | .lineLimit(1) 35 | } else if units == "Percent-0-100" { // for audio units that use 0-100 instead of 0-1 36 | Text("\(parameter, specifier: "%0.f")%") 37 | .lineLimit(1) 38 | } else if units == "Hertz" { 39 | Text("\(parameter, specifier: "%0.2f") Hz") 40 | .lineLimit(1) 41 | } else { 42 | Text("\(parameter, specifier: format) \(units)") 43 | .lineLimit(1) 44 | } 45 | } 46 | .frame(height: 50) 47 | SmallKnob(value: $parameter, range: range) 48 | }.frame(maxWidth: 150, maxHeight: 200).frame(minHeight: 100) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Examples/AUv3Effects.swift: -------------------------------------------------------------------------------- 1 | import AudioKit 2 | import DunneAudioKit 3 | import AVFoundation 4 | import Keyboard 5 | import SwiftUI 6 | import Tonic 7 | import MIDIKit 8 | 9 | class AUv3EffectsConductor: ObservableObject, HasAudioEngine { 10 | let engine = AudioEngine() 11 | var player = AudioPlayer() 12 | let delay: StereoDelay 13 | 14 | init() { 15 | // Make engine connections 16 | // Engine started in HasAudioEngine 17 | delay = StereoDelay(player, time: 0.33, feedback: 0.33, pingPong: true) 18 | engine.output = delay 19 | 20 | let url = Bundle.main.resourceURL?.appendingPathComponent( 21 | "Sounds/Piano.mp3") 22 | loadFile(url: url!) 23 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 24 | self.player.play() 25 | } 26 | } 27 | 28 | // Player functions 29 | func loadFile(url: URL) { 30 | do { 31 | try player.load(url: url) 32 | } catch { 33 | Log(error.localizedDescription, type: .error) 34 | } 35 | } 36 | } 37 | 38 | struct AUv3EffectsView: View { 39 | @StateObject var conductor = AUv3EffectsConductor() 40 | @Environment(\.colorScheme) var colorScheme 41 | 42 | 43 | var body: some View { 44 | VStack{ 45 | ZStack { 46 | // Credit to the image creator, Jason Blackeye on Unsplash (https://unsplash.com/photos/silhouette-of-mountains-during-starry-night-FzURx0rFhUk) 47 | Image("visualizerBG2") 48 | .resizable() 49 | .scaledToFill() 50 | .opacity(0.5) 51 | HStack { 52 | ForEach(conductor.delay.parameters) { 53 | ParameterRow(param: $0) 54 | } 55 | } 56 | } 57 | }.onAppear { 58 | conductor.start() 59 | } 60 | .onDisappear { 61 | conductor.player.stop() 62 | conductor.stop() 63 | } 64 | .background(Color(red: 0.0, green: 0.0, blue: 0.0)) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Examples/CircularVisualizer.swift: -------------------------------------------------------------------------------- 1 | import AudioKit 2 | import AudioKitUI 3 | import AVFoundation 4 | import Keyboard 5 | import SwiftUI 6 | import Tonic 7 | import MIDIKit 8 | 9 | class CircularVisualizerConductor: ObservableObject, HasAudioEngine { 10 | let engine = AudioEngine() 11 | var player = AudioPlayer() 12 | let mixer: Mixer! 13 | 14 | init() { 15 | // Make engine connections 16 | // Engine started in HasAudioEngine 17 | mixer = Mixer(player) 18 | engine.output = mixer 19 | 20 | let url = Bundle.main.resourceURL?.appendingPathComponent( 21 | "Sounds/Piano.mp3") 22 | loadFile(url: url!) 23 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 24 | self.player.play() 25 | } 26 | } 27 | 28 | // Player functions 29 | func loadFile(url: URL) { 30 | do { 31 | try player.load(url: url) 32 | } catch { 33 | Log(error.localizedDescription, type: .error) 34 | } 35 | } 36 | } 37 | 38 | struct CircularVisualizerView: View { 39 | @StateObject var conductor = CircularVisualizerConductor() 40 | var body: some View { 41 | VStack{ 42 | ZStack { 43 | // Credit to the image creator, Jason Blackeye on Unsplash (https://unsplash.com/photos/silhouette-of-mountains-during-starry-night-FzURx0rFhUk) 44 | Image("visualizerBG") 45 | .resizable() 46 | .aspectRatio(contentMode: .fill) 47 | .opacity(0.4) 48 | FFTView2(conductor.engine.output!, barColor: .white.opacity(0.75), placeMiddle: false, barCount: 100, minAmplitude: -130).aspectRatio(contentMode: .fit) 49 | } 50 | } 51 | .onAppear { 52 | conductor.start() 53 | } 54 | .onDisappear { 55 | conductor.player.stop() 56 | conductor.stop() 57 | } 58 | .background(Color(red: 0.0, green: 0.0, blue: 0.0)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Examples/LFOView.swift: -------------------------------------------------------------------------------- 1 | import AudioKit 2 | import SporthAudioKit 3 | import AudioKitUI 4 | import AVFoundation 5 | import Keyboard 6 | import SwiftUI 7 | import Tonic 8 | import MIDIKit 9 | import Controls 10 | 11 | class LFOConductor: ObservableObject, HasAudioEngine { 12 | let engine = AudioEngine() 13 | var instrument = AppleSampler() 14 | 15 | var timer: Timer? 16 | var tickCount = 0.0 17 | var lfoAmount: Float = 1.0 18 | var lfoRate: Float = 0.15 19 | 20 | @Published var lfoValue = 0.0 { 21 | didSet{ 22 | instrument.tuning = AUValue(lfoValue) 23 | } 24 | } 25 | 26 | init() { 27 | timer = Timer.scheduledTimer(withTimeInterval: 0.02, repeats: true) { timer in 28 | self.tickCount += Double(self.lfoRate) 29 | self.lfoValue = sin(Double(self.tickCount))*Double(self.lfoAmount) 30 | print(self.lfoValue) 31 | } 32 | 33 | engine.output = instrument 34 | 35 | } 36 | } 37 | 38 | struct LFOView: View { 39 | @StateObject var conductor = LFOConductor() 40 | var body: some View { 41 | VStack{ 42 | HStack{ 43 | VStack{ 44 | Text("Amount: \(conductor.lfoAmount)") 45 | SmallKnob(value: $conductor.lfoAmount, range: 0...2) 46 | } 47 | VStack{ 48 | Text("Rate: \(conductor.lfoRate)") 49 | SmallKnob(value: $conductor.lfoRate, range: 0.01...1) 50 | } 51 | }.frame(maxHeight: 300) 52 | } 53 | .onAppear { 54 | conductor.start() 55 | conductor.instrument.play(noteNumber: MIDINoteNumber(60), velocity: 90, channel: 0) 56 | } 57 | .onDisappear { 58 | conductor.instrument.stop(noteNumber: MIDINoteNumber(60), channel: 0) 59 | conductor.stop() 60 | conductor.timer?.invalidate() 61 | } 62 | } 63 | 64 | } 65 | 66 | #Preview { 67 | LFOView() 68 | } 69 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Examples/LinearVisualizer.swift: -------------------------------------------------------------------------------- 1 | import AudioKit 2 | import AudioKitUI 3 | import AVFoundation 4 | import Keyboard 5 | import SwiftUI 6 | import Tonic 7 | import MIDIKit 8 | 9 | class LinearVisualizerConductor: ObservableObject, HasAudioEngine { 10 | let engine = AudioEngine() 11 | var player = AudioPlayer() 12 | let mixer: Mixer! 13 | 14 | init() { 15 | // Make engine connections 16 | // Engine started in HasAudioEngine 17 | mixer = Mixer(player) 18 | engine.output = mixer 19 | 20 | let url = Bundle.main.resourceURL?.appendingPathComponent( 21 | "Sounds/Piano.mp3") 22 | loadFile(url: url!) 23 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 24 | self.player.play() 25 | } 26 | } 27 | 28 | // Player functions 29 | func loadFile(url: URL) { 30 | do { 31 | try player.load(url: url) 32 | } catch { 33 | Log(error.localizedDescription, type: .error) 34 | } 35 | } 36 | } 37 | 38 | struct LinearVisualizerView: View { 39 | @StateObject var conductor = LinearVisualizerConductor() 40 | @Environment(\.colorScheme) var colorScheme 41 | @State private var hueVal = 0 42 | func hueValIncrease() { 43 | hueVal += 1 44 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 45 | self.hueValIncrease() 46 | } 47 | } 48 | 49 | var body: some View { 50 | VStack{ 51 | ZStack { 52 | // Credit to the image creator, Jason Blackeye on Unsplash (https://unsplash.com/photos/silhouette-of-mountains-during-starry-night-FzURx0rFhUk) 53 | Image("visualizerBG") 54 | .resizable() 55 | .scaledToFit() 56 | .opacity(0.4) 57 | FFTView2(conductor.mixer, barColor: .blue, placeMiddle: true, barCount: 40) 58 | } 59 | }.hueRotation(.degrees(Double(hueVal))) 60 | .onAppear { 61 | conductor.start() 62 | hueValIncrease() 63 | } 64 | .onDisappear { 65 | conductor.player.stop() 66 | conductor.stop() 67 | } 68 | .background(Color(red: 0.0, green: 0.0, blue: 0.0)) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Examples/SoundFont+MIDI.swift: -------------------------------------------------------------------------------- 1 | import AudioKit 2 | import AudioKitUI 3 | import AVFoundation 4 | import Keyboard 5 | import SwiftUI 6 | import Tonic 7 | import MIDIKit 8 | 9 | extension SoundFontConductor { 10 | // Connect MIDI on init 11 | func MIDIConnect() { 12 | do { 13 | print("Starting MIDI services.") 14 | try midiManager.start() 15 | } catch { 16 | print("Error starting MIDI services:", error.localizedDescription) 17 | } 18 | 19 | do { 20 | try midiManager.addInputConnection( 21 | to: .allOutputs, // no need to specify if we're using .allEndpoints 22 | tag: "Listener", 23 | filter: .owned(), // don't allow self-created virtual endpoints 24 | receiver: .events { [weak self] events in 25 | // Note: this handler will be called on a background thread 26 | // so call the next line on main if it may result in UI updates 27 | DispatchQueue.main.async { 28 | events.forEach { self?.received(midiEvent: $0) } 29 | } 30 | } 31 | ) 32 | } catch { 33 | print( 34 | "Error setting up managed MIDI all-listener connection:", 35 | error.localizedDescription 36 | ) 37 | } 38 | } 39 | 40 | // MIDI Events 41 | private func received(midiEvent: MIDIKit.MIDIEvent) { 42 | switch midiEvent { 43 | case .noteOn(let payload): 44 | print("Note On:", payload.note, payload.velocity, payload.channel) 45 | instrument.play(noteNumber: MIDINoteNumber(payload.note.number.uInt8Value), velocity: payload.velocity.midi1Value.uInt8Value, channel: 0) 46 | NotificationCenter.default.post(name: .MIDIKey, object: nil, userInfo: ["info": payload.note.number.uInt8Value, "bool": true]) 47 | case .noteOff(let payload): 48 | print("Note Off:", payload.note, payload.velocity, payload.channel) 49 | instrument.stop(noteNumber: MIDINoteNumber(payload.note.number.uInt8Value), channel: 0) 50 | NotificationCenter.default.post(name: .MIDIKey, object: nil, userInfo: ["info": payload.note.number.uInt8Value, "bool": false]) 51 | case .cc(let payload): 52 | print("CC:", payload.controller, payload.value, payload.channel) 53 | if payload.controller == 74 { 54 | instrument.samplerUnit.sendController(74, withValue: payload.value.midi1Value.uInt8Value, onChannel: 0) 55 | } 56 | case .programChange(let payload): 57 | print("Program Change:", payload.program, payload.channel) 58 | default: 59 | break 60 | } 61 | } 62 | } 63 | 64 | #if os(iOS) 65 | 66 | import CoreAudioKit 67 | 68 | struct BluetoothMIDIView: UIViewControllerRepresentable { 69 | func makeUIViewController(context: Context) -> BTMIDICentralViewController { 70 | BTMIDICentralViewController() 71 | } 72 | 73 | func updateUIViewController( 74 | _ uiViewController: BTMIDICentralViewController, 75 | context: Context 76 | ) { } 77 | 78 | typealias UIViewControllerType = BTMIDICentralViewController 79 | } 80 | 81 | class BTMIDICentralViewController: CABTMIDICentralViewController { 82 | var uiViewController: UIViewController? 83 | 84 | override public func viewDidLayoutSubviews() { 85 | super.viewDidLayoutSubviews() 86 | 87 | navigationItem.rightBarButtonItem = UIBarButtonItem( 88 | barButtonSystemItem: .done, 89 | target: self, 90 | action: #selector(doneAction) 91 | ) 92 | } 93 | 94 | @objc 95 | public func doneAction() { 96 | uiViewController?.dismiss(animated: true, completion: nil) 97 | } 98 | } 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Examples/SpriteSound.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SpriteKit 3 | import AudioKit 4 | import AVFoundation 5 | 6 | class GameScene: SKScene, SKPhysicsContactDelegate { 7 | var conductor: SpriteSoundViewConductor? 8 | override func didMove(to view: SKView) { 9 | physicsWorld.contactDelegate = self 10 | physicsBody = SKPhysicsBody(edgeLoopFrom: frame) 11 | self.backgroundColor = .white 12 | for i in 1...3 { 13 | let plat = SKShapeNode(rectOf: CGSize(width: 80, height: 10)) 14 | plat.fillColor = .lightGray 15 | plat.strokeColor = .lightGray 16 | if i == 2 { 17 | plat.zRotation = .pi / 8 18 | plat.position = CGPoint(x:590,y:700-75*i) 19 | } else { 20 | plat.zRotation = -.pi / 8 21 | plat.position = CGPoint(x:490,y:700-75*i) 22 | } 23 | plat.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 80, height: 10)) 24 | plat.physicsBody?.categoryBitMask = 2 25 | plat.physicsBody?.contactTestBitMask = 2 26 | plat.physicsBody?.affectedByGravity = false 27 | plat.physicsBody?.isDynamic = false 28 | plat.name = "platform\(i)" 29 | addChild(plat) 30 | } 31 | } 32 | 33 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 34 | guard let touch = touches.first else { return } 35 | let location = touch.location(in: self) 36 | print(location) 37 | let box = SKShapeNode(circleOfRadius: 5) 38 | box.fillColor = .gray 39 | box.strokeColor = .gray 40 | box.position = location 41 | box.physicsBody = SKPhysicsBody(circleOfRadius: 5) 42 | box.physicsBody?.restitution = 0.55 43 | box.physicsBody?.categoryBitMask = 2 44 | box.physicsBody?.contactTestBitMask = 2 45 | box.physicsBody?.affectedByGravity = true 46 | box.physicsBody?.isDynamic = true 47 | box.name = "ball" 48 | addChild(box) 49 | } 50 | 51 | func didBegin(_ contact: SKPhysicsContact) { 52 | if contact.bodyB.node?.name == "platform1" || contact.bodyA.node?.name == "platform1" { 53 | conductor!.instrument.play(noteNumber: MIDINoteNumber(60), velocity: 90, channel: 0) 54 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 55 | self.conductor!.instrument.stop(noteNumber: MIDINoteNumber(60), channel: 0) 56 | } 57 | } else if contact.bodyB.node?.name == "platform2" || contact.bodyA.node?.name == "platform2" { 58 | conductor!.instrument.play(noteNumber: MIDINoteNumber(64), velocity: 90, channel: 0) 59 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 60 | self.conductor!.instrument.stop(noteNumber: MIDINoteNumber(64), channel: 0) 61 | } 62 | } else if contact.bodyB.node?.name == "platform3" || contact.bodyA.node?.name == "platform3" { 63 | conductor!.instrument.play(noteNumber: MIDINoteNumber(67), velocity: 90, channel: 0) 64 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 65 | self.conductor!.instrument.stop(noteNumber: MIDINoteNumber(67), channel: 0) 66 | } 67 | } else if contact.bodyB.node?.name != "ball" || contact.bodyA.node?.name != "ball" { 68 | contact.bodyB.node?.removeFromParent() 69 | } 70 | } 71 | } 72 | 73 | class SpriteSoundViewConductor: ObservableObject, HasAudioEngine { 74 | let engine = AudioEngine() 75 | @Published var instrument = MIDISampler(name: "Instrument 1") 76 | init() { 77 | engine.output = Reverb(instrument) 78 | try! instrument.loadMelodicSoundFont(url: Bundle.main.url(forResource: "Sounds/PianoMuted", withExtension: "sf2")!, preset: 0) 79 | } 80 | } 81 | 82 | struct SpriteSoundView: View { 83 | @StateObject var conductor = SpriteSoundViewConductor() 84 | var scene: SKScene { 85 | let scene = GameScene() 86 | scene.size = CGSize(width: 1080, height: 1080) 87 | scene.scaleMode = .aspectFit 88 | scene.conductor = conductor 89 | return scene 90 | } 91 | var body: some View { 92 | VStack { 93 | SpriteView(scene: scene).frame(maxWidth: .infinity, maxHeight: .infinity).ignoresSafeArea() 94 | }.onAppear { 95 | conductor.start() 96 | }.onDisappear { 97 | conductor.stop() 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIBackgroundModes 6 | 7 | audio 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/ParameterRow.swift: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/AudioKitUI/ 2 | 3 | import AudioKit 4 | import Controls 5 | import SwiftUI 6 | import CoreMIDI 7 | 8 | /// Hack to get SwiftUI to poll and refresh our UI. 9 | class Refresher: ObservableObject { 10 | @Published var version = 0 11 | } 12 | 13 | public struct ParameterRow: View { 14 | var param: NodeParameter 15 | @StateObject var refresher = Refresher() 16 | 17 | public init(param: NodeParameter) { 18 | self.param = param 19 | } 20 | 21 | func floatToDoubleRange(_ floatRange: ClosedRange) -> ClosedRange { 22 | Double(floatRange.lowerBound) ... Double(floatRange.upperBound) 23 | } 24 | 25 | func getBinding() -> Binding { 26 | Binding( 27 | get: { param.value }, 28 | set: { param.value = $0; refresher.version += 1} 29 | ) 30 | } 31 | 32 | func getIntBinding() -> Binding { 33 | Binding(get: { Int(param.value) }, set: { param.value = AUValue($0); refresher.version += 1 }) 34 | } 35 | 36 | func intValues() -> [Int] { 37 | Array(Int(param.range.lowerBound) ... Int(param.range.upperBound)) 38 | } 39 | var format: String { 40 | if (param.range.upperBound - param.range.lowerBound) > 20 { 41 | return "%0.0f" 42 | } else { 43 | return "%0.2f" 44 | } 45 | } 46 | 47 | public var body: some View { 48 | VStack(alignment: .center) { 49 | VStack { 50 | Text(param.def.name) 51 | .minimumScaleFactor(0.2) 52 | .lineLimit(2) 53 | .multilineTextAlignment(.center) 54 | Text("\(String(format: format, param.value))").lineLimit(1) 55 | } 56 | .frame(height: 50) 57 | switch param.def.unit { 58 | case .boolean: 59 | Toggle(isOn: Binding(get: { param.value == 1.0 }, set: { 60 | param.value = $0 ? 1.0 : 0.0; refresher.version += 1 61 | }), label: { Text(param.def.name) }) 62 | case .indexed: 63 | if param.range.upperBound - param.range.lowerBound < 5 { 64 | Picker(param.def.name, selection: getIntBinding()) { 65 | ForEach(intValues(), id: \.self) { value in 66 | Text("\(value)").tag(value) 67 | } 68 | } 69 | .pickerStyle(.segmented) 70 | } else { 71 | SmallKnob(value: getBinding(), range: param.range) 72 | 73 | } 74 | default: 75 | SmallKnob(value: getBinding(), range: param.range) 76 | } 77 | }.frame(maxWidth: 150, maxHeight: 200).frame(minHeight: 100) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-Experiments/SwiftUIElements.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftUI 3 | import Keyboard 4 | import Tonic 5 | import AVFoundation 6 | struct SwiftUIKeyboard: View { 7 | var firstOctave: Int 8 | var octaveCount: Int 9 | var noteOn: (Pitch, CGPoint) -> Void = { _, _ in } 10 | var noteOff: (Pitch)->Void 11 | var color: Color = .pink 12 | 13 | var body: some View { 14 | Keyboard(layout: .piano(pitchRange: Pitch(intValue: firstOctave * 12 + 24)...Pitch(intValue: firstOctave * 12 + octaveCount * 12 + 24)), 15 | noteOn: noteOn, noteOff: noteOff){ pitch, isActivated in 16 | SwiftUIKeyboardKey(pitch: pitch, 17 | isActivated: isActivated, color: color) 18 | }.cornerRadius(5) 19 | } 20 | } 21 | 22 | struct SwiftUIKeyboardKey: View { 23 | @State var MIDIKeyPressed = [Bool](repeating: false, count: 128) 24 | var pitch : Pitch 25 | var isActivated : Bool 26 | var color: Color 27 | 28 | var body: some View { 29 | VStack{ 30 | KeyboardKey(pitch: pitch, 31 | isActivated: isActivated, 32 | text: "", 33 | whiteKeyColor: .white, 34 | blackKeyColor: .black, 35 | pressedColor: color, 36 | flatTop: true, 37 | isActivatedExternally: MIDIKeyPressed[pitch.intValue]) 38 | }.onReceive(NotificationCenter.default.publisher(for: .MIDIKey), perform: { obj in 39 | if let userInfo = obj.userInfo, let info = userInfo["info"] as? UInt8, let val = userInfo["bool"] as? Bool { 40 | self.MIDIKeyPressed[Int(info)] = val 41 | } 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-ExperimentsTests/AudioKit_ExperimentsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioKit_ExperimentsTests.swift 3 | // AudioKit-ExperimentsTests 4 | // 5 | // Created by Nick Culbertson on 10/31/23. 6 | // 7 | 8 | import XCTest 9 | @testable import AudioKit_Experiments 10 | 11 | final class AudioKit_ExperimentsTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | // Any test you write for XCTest can be annotated as throws and async. 25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 27 | } 28 | 29 | func testPerformanceExample() throws { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-ExperimentsUITests/AudioKit_ExperimentsUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioKit_ExperimentsUITests.swift 3 | // AudioKit-ExperimentsUITests 4 | // 5 | // Created by Nick Culbertson on 10/31/23. 6 | // 7 | 8 | import XCTest 9 | 10 | final class AudioKit_ExperimentsUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use XCTAssert and related functions to verify your tests produce the correct results. 31 | } 32 | 33 | func testLaunchPerformance() throws { 34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 35 | // This measures how long it takes to launch your application. 36 | measure(metrics: [XCTApplicationLaunchMetric()]) { 37 | XCUIApplication().launch() 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /AudioKit-Experiments/AudioKit-ExperimentsUITests/AudioKit_ExperimentsUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioKit_ExperimentsUITestsLaunchTests.swift 3 | // AudioKit-ExperimentsUITests 4 | // 5 | // Created by Nick Culbertson on 10/31/23. 6 | // 7 | 8 | import XCTest 9 | 10 | final class AudioKit_ExperimentsUITestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | func testLaunch() throws { 21 | let app = XCUIApplication() 22 | app.launch() 23 | 24 | // Insert steps here to perform after app launch but before taking a screenshot, 25 | // such as logging into a test account or navigating somewhere in the app 26 | 27 | let attachment = XCTAttachment(screenshot: app.screenshot()) 28 | attachment.name = "Launch Screen" 29 | attachment.lifetime = .keepAlways 30 | add(attachment) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # SoundpipeAudioKit Code Owners File 2 | # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners 3 | 4 | # Primary Owners 5 | * @aure @wtholliday 6 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | 10 | jobs: 11 | swift_test: 12 | name: Test 13 | runs-on: macos-latest 14 | steps: 15 | - name: Check out DunneAudioKit 16 | uses: actions/checkout@v4 17 | - name: Test DunneAudioKit 18 | run: swift test -c release 19 | 20 | # Send notification to Discord on failure. 21 | send_notification: 22 | name: Send Notification 23 | uses: AudioKit/ci/.github/workflows/send_notification.yml@main 24 | needs: [swift_test] 25 | if: ${{ failure() && github.ref == 'refs/heads/main' }} 26 | secrets: inherit 27 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | 9 | Package.resolved -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 AudioKit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "DunneAudioKit", 8 | platforms: [.macOS(.v12), .iOS(.v13), .tvOS(.v13)], 9 | products: [.library(name: "DunneAudioKit", targets: ["DunneAudioKit"])], 10 | dependencies: [ 11 | .package(url: "https://github.com/AudioKit/KissFFT", from: "1.0.0"), 12 | .package(url: "https://github.com/AudioKit/AudioKit", from: "5.6.0"), 13 | .package(url: "https://github.com/AudioKit/AudioKitEX", from: "5.6.0"), 14 | ], 15 | targets: [ 16 | .target(name: "DunneAudioKit", dependencies: ["AudioKit", "AudioKitEX", "CDunneAudioKit"]), 17 | .target( 18 | name: "CDunneAudioKit", 19 | dependencies: ["AudioKit", "AudioKitEX", "KissFFT"], 20 | exclude: [ 21 | "DunneCore/Sampler/Wavpack/license.txt", 22 | "DunneCore/Common/README.md", 23 | "DunneCore/Sampler/README.md", 24 | "DunneCore/README.md", 25 | ], 26 | cxxSettings: [.headerSearchPath("DunneCore/Common")] 27 | ), 28 | .testTarget(name: "DunneAudioKitTests", dependencies: ["DunneAudioKit"], resources: [.copy("TestResources/")]), 29 | ], 30 | cxxLanguageStandard: .cxx14 31 | ) 32 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | # Dunne AudioKit 5 | 6 | [![Build Status](https://github.com/AudioKit/DunneAudioKit/workflows/CI/badge.svg)](https://github.com/AudioKit/DunneAudioKit/actions?query=workflow%3ACI) 7 | [![License](https://img.shields.io/github/license/AudioKit/DunneAudioKit)](https://github.com/AudioKit/DunneAudioKit/blob/main/LICENSE) 8 | [![Swift Compatibility](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FAudioKit%2FDunneAudioKit%2Fbadge%3Ftype%3Dswift-versions&label=)](https://swiftpackageindex.com/AudioKit/DunneAudioKit) 9 | [![Platform Compatibility](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FAudioKit%2FDunneAudioKit%2Fbadge%3Ftype%3Dplatforms&label=)](https://swiftpackageindex.com/AudioKit/DunneAudioKit) 10 | [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) 11 | [![Twitter Follow](https://img.shields.io/twitter/follow/AudioKitPro.svg?style=social)](https://twitter.com/AudioKitPro) 12 | 13 |
14 | 15 | Chorus, Flanger, Sampler, Stereo Delay, and Synth for AudioKit, by Shane Dunne. 16 | 17 | ## Documentation 18 | 19 | Detailed documentation is provided on [AudioKit's Website](http://audiokit.io/Packages/DunneAudioKit/). 20 | 21 | ## API Reference 22 | 23 | * [Chorus](https://audiokit.io/DunneAudioKit/documentation/dunneaudiokit/chorus) 24 | * [Flanger](https://audiokit.io/DunneAudioKit/documentation/dunneaudiokit/flanger) 25 | * [Sampler](https://audiokit.io/DunneAudioKit/documentation/dunneaudiokit/sampler) 26 | * [Stereo Delay](https://audiokit.io/DunneAudioKit/documentation//dunneaudiokit/stereodelay) 27 | * [Synth](https://audiokit.io/DunneAudioKit/documentation/dunneaudiokit/synth) 28 | * [Transient Shaper](https://audiokit.io/DunneAudioKit/documentation/dunneaudiokit/transientshaper) 29 | 30 | ## Installation 31 | 32 | Install with Swift Package Manager. 33 | 34 | ## Examples 35 | 36 | See the [AudioKit Cookbook](https://github.com/AudioKit/Cookbook/) for examples. 37 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/ADSREnvelope.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #include "EnvelopeGeneratorBase.h" 4 | 5 | namespace DunneCore 6 | { 7 | 8 | struct ADSREnvelopeParameters 9 | { 10 | float sampleRateHz; 11 | float attackSamples, decaySamples, releaseSamples; 12 | float sustainFraction; // [0.0, 1.0] 13 | 14 | ADSREnvelopeParameters(); 15 | void init(float newSampleRateHz, float attackSeconds, float decaySeconds, float susFraction, float releaseSeconds); 16 | void init(float attackSeconds, float decaySeconds, float susFraction, float releaseSeconds); 17 | void updateSampleRate(float newSampleRateHz); 18 | 19 | void setAttackDurationSeconds(float attackSeconds) { attackSamples = attackSeconds * sampleRateHz; } 20 | float getAttackDurationSeconds() { return attackSamples / sampleRateHz; } 21 | void setDecayDurationSeconds(float decaySeconds) { decaySamples = decaySeconds * sampleRateHz; } 22 | float getDecayDurationSeconds() { return decaySamples / sampleRateHz; } 23 | void setReleaseDurationSeconds(float releaseSeconds) { releaseSamples = releaseSeconds * sampleRateHz; } 24 | float getReleaseDurationSeconds() { return releaseSamples / sampleRateHz; } 25 | }; 26 | 27 | struct ADSREnvelope 28 | { 29 | ADSREnvelopeParameters* pParameters; // many ADSREnvelopes can share a common set of parameters 30 | 31 | enum EG_Segment 32 | { 33 | kIdle = 0, 34 | kSilence, 35 | kAttack, 36 | kDecay, 37 | kSustain, 38 | kRelease 39 | }; 40 | 41 | enum CurvatureType 42 | { 43 | kLinear, // all segments linear 44 | kAnalogLike, // models CEM3310 integrated circuit 45 | kLinearInDb // decay and release are linear-in-dB 46 | }; 47 | 48 | void init(CurvatureType curvatureType = kAnalogLike); 49 | void updateParams(); 50 | 51 | void start(); // called for note-on 52 | void restart(); // quickly dampen note then start again 53 | void release(); // called for note-off 54 | void reset(); // reset to idle state 55 | bool isIdle() { return env.getCurrentSegmentIndex() == kIdle; } 56 | bool isPreStarting() { return env.getCurrentSegmentIndex() == kSilence; } 57 | bool isReleasing() { return env.getCurrentSegmentIndex() == kRelease; } 58 | 59 | inline float getValue() 60 | { 61 | return env.getValue(); 62 | } 63 | 64 | inline float getSample() 65 | { 66 | float sample; 67 | env.getSample(sample); 68 | return sample; 69 | } 70 | 71 | protected: 72 | MultiSegmentEnvelopeGenerator env; 73 | MultiSegmentEnvelopeGenerator::Descriptor envDesc; 74 | }; 75 | 76 | } 77 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/AHDSHREnvelope.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | // 3 | // Attack, Hold, Decay, Sustain, Release-hold, Release envelope 4 | // 1) Attack fades in from 0 for attackSamples 5 | // 2) Holds value at 1 for holdSamples 6 | // 3) Decays to sustainFraction for decaySamples 7 | // 4) Holds value at sustainFraction until release / noteOff 8 | // 5) Holds value at sustainFraction for releaseHoldSamples 9 | // 6) Fades to 0 for releaseSamples 10 | 11 | #include "EnvelopeGeneratorBase.h" 12 | 13 | namespace DunneCore 14 | { 15 | 16 | struct AHDSHREnvelopeParameters 17 | { 18 | float sampleRateHz; 19 | float attackSamples, holdSamples, decaySamples, releaseHoldSamples, releaseSamples; 20 | float sustainFraction; // [0.0, 1.0] 21 | 22 | AHDSHREnvelopeParameters(); 23 | void init(float newSampleRateHz, float attackSeconds, float holdSeconds, float decaySeconds, float susFraction, 24 | float releaseHoldSeconds, float releaseSeconds); 25 | void init(float attackSeconds, float holdSeconds, float decaySeconds, float susFraction, 26 | float releaseHoldSeconds, float releaseSeconds); 27 | void updateSampleRate(float newSampleRateHz); 28 | 29 | void setAttackDurationSeconds(float attackSeconds) { attackSamples = attackSeconds * sampleRateHz; } 30 | float getAttackDurationSeconds() { return attackSamples / sampleRateHz; } 31 | void setHoldDurationSeconds(float holdSeconds) { holdSamples = holdSeconds * sampleRateHz; } 32 | float getHoldDurationSeconds() { return holdSamples / sampleRateHz; } 33 | void setDecayDurationSeconds(float decaySeconds) { decaySamples = decaySeconds * sampleRateHz; } 34 | float getDecayDurationSeconds() { return decaySamples / sampleRateHz; } 35 | void setReleaseHoldDurationSeconds(float releaseHoldSeconds) { releaseHoldSamples = releaseHoldSeconds * sampleRateHz; } 36 | float getReleaseHoldDurationSeconds() { return releaseHoldSamples / sampleRateHz; } 37 | void setReleaseDurationSeconds(float releaseSeconds) { releaseSamples = releaseSeconds * sampleRateHz; } 38 | float getReleaseDurationSeconds() { return releaseSamples / sampleRateHz; } 39 | }; 40 | 41 | struct AHDSHREnvelope 42 | { 43 | AHDSHREnvelopeParameters* pParameters; // many ADSREnvelopes can share a common set of parameters 44 | 45 | enum EG_Segment 46 | { 47 | kIdle = 0, 48 | kSilence, 49 | kAttack, 50 | kHold, 51 | kDecay, 52 | kSustain, 53 | kReleaseHold, 54 | kRelease 55 | }; 56 | 57 | enum CurvatureType 58 | { 59 | kLinear, // all segments linear 60 | kAnalogLike, // models CEM3310 integrated circuit 61 | kLinearInDb // decay and release are linear-in-dB 62 | }; 63 | 64 | void init(CurvatureType curvatureType = kAnalogLike); 65 | void updateParams(); 66 | 67 | void start(); // called for note-on 68 | void restart(); // quickly dampen note then start again 69 | void release(); // called for note-off 70 | void reset(); // reset to idle state 71 | bool isIdle() { return env.getCurrentSegmentIndex() == kIdle; } 72 | bool isPreStarting() { return env.getCurrentSegmentIndex() == kSilence; } 73 | bool isReleasing() { 74 | return env.getCurrentSegmentIndex() == kReleaseHold || env.getCurrentSegmentIndex() == kRelease; 75 | } 76 | 77 | inline float getValue() 78 | { 79 | return env.getValue(); 80 | } 81 | 82 | inline float getSample() 83 | { 84 | float sample; 85 | env.getSample(sample); 86 | return sample; 87 | } 88 | 89 | protected: 90 | MultiSegmentEnvelopeGenerator env; 91 | MultiSegmentEnvelopeGenerator::Descriptor envDesc; 92 | }; 93 | 94 | } 95 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/AudioKitCore.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/CoreEnvelope.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | #include "LinearRamper.h" 5 | #include "FunctionTable.h" 6 | 7 | namespace DunneCore 8 | { 9 | 10 | struct EnvelopeSegmentParameters 11 | { 12 | // where this segment starts 13 | float initialLevel; 14 | 15 | // where it ends 16 | float finalLevel; 17 | 18 | // how long it takes to get there 19 | float seconds; 20 | }; 21 | 22 | struct EnvelopeParameters 23 | { 24 | float sampleRateHz; 25 | 26 | // number of segments 27 | int nSegments; 28 | 29 | // points to an array of nSegments elements 30 | EnvelopeSegmentParameters *pSeg; 31 | 32 | // start() begins at this segment 33 | int attackSegmentIndex; 34 | 35 | // index of first sustain segment (-1 if none) 36 | int sustainSegmentIndex; 37 | 38 | // release() jumps to this segment 39 | int releaseSegmentIndex; 40 | 41 | EnvelopeParameters(); 42 | void init(float newSampleRateHz, 43 | int nSegs, 44 | EnvelopeSegmentParameters *pSegParameters, 45 | int susSegIndex = -1, 46 | int attackSegIndex = 0, 47 | int releaseSegIndex = -1); 48 | void updateSampleRate(float newSampleRateHz); 49 | }; 50 | 51 | struct Envelope 52 | { 53 | EnvelopeParameters *pParameters; 54 | LinearRamper ramper; 55 | int currentSegmentIndex; 56 | 57 | void init(EnvelopeParameters *pParameters); 58 | 59 | // begin attack segment 60 | void start(); 61 | 62 | // go to segment 0 63 | void restart(); 64 | 65 | // go to release segment 66 | void release(); 67 | 68 | // reset to idle state 69 | void reset(); 70 | bool isIdle() { return currentSegmentIndex < 0; } 71 | bool isReleasing() { return currentSegmentIndex >= pParameters->releaseSegmentIndex; } 72 | 73 | float getSample(); 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/DrawbarsOscillator.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "FunctionTable.h" 6 | #include "WaveStack.h" 7 | 8 | namespace DunneCore 9 | { 10 | 11 | // DrawbarsOscillator is WaveStack-based oscillator which implements multiple simultaneous 12 | // waveform-readout phases, whose frequencies are related as a harmonic series, as in a 13 | // traditional "drawbar" organ. 14 | 15 | struct DrawbarsOscillator 16 | { 17 | // current output sample rate 18 | double sampleRateHz; 19 | 20 | // pointer to shared WaveStack 21 | WaveStack *pWaveStack; 22 | 23 | // per-phase variables 24 | static constexpr int phaseCount = 16; 25 | 26 | // WaveStack octave used by this phase 27 | int octave[phaseCount]; 28 | 29 | // Fraction of the way through waveform 30 | float phase[phaseCount]; 31 | 32 | // normalized frequency: cycles per sample 33 | float phaseDelta[phaseCount]; 34 | 35 | // relative level of each phase (fraction) 36 | float *level; 37 | float safetyLevels[phaseCount]; 38 | 39 | // performance variables 40 | 41 | // phaseDelta multiplier for pitchbend, vibrato 42 | float phaseDeltaMultiplier; 43 | 44 | void init(double sampleRate, WaveStack* pStack); 45 | void setFrequency(float frequency); 46 | 47 | float getSample(); 48 | void getSamples(float *pLeft, float *pRight, float gain); 49 | 50 | // 9 Hammond-like drawbars mapped to level[] indices 51 | static const int drawBarMap[9]; 52 | }; 53 | 54 | } 55 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/EnsembleOscillator.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "FunctionTable.h" 6 | #include "WaveStack.h" 7 | #include 8 | 9 | namespace DunneCore 10 | { 11 | 12 | /// An EnsembleOscillator is WaveStack-based oscillator which provides an "ensemble" effect 13 | /// based on up to 10 simultaneous waveform-readout "phases" differing slightly in frequency 14 | /// (pitch spread) and left/right balance (pan spread). 15 | /// If the phases variable is set to 0, the oscillator is disabled. If set to 1, the result 16 | /// is a conventional, single-phase oscillator. 17 | struct EnsembleOscillator 18 | { 19 | std::mt19937* gen; 20 | std::uniform_real_distribution dis{0.0f, 1.0f}; 21 | 22 | /// current output sample rate 23 | double sampleRateHz; 24 | 25 | /// pointer to shared WaveStack 26 | WaveStack *pWaveStack; 27 | 28 | /// number of unison/ensemble phases 29 | int phaseCount; 30 | /// frequency difference between phases, cents 31 | float frequencySpread; 32 | 33 | // per-phase variables 34 | 35 | /// maximum number of phases 36 | static constexpr int maxPhases = 10; 37 | 38 | /// WaveStack octave used by this phase 39 | int octave[maxPhases]; 40 | 41 | /// Fraction of the way through waveform 42 | float phase[maxPhases]; 43 | 44 | /// normalized frequency: cycles per sample 45 | float phaseDelta[maxPhases]; 46 | float leftGain[maxPhases]; 47 | float rightGain[maxPhases]; 48 | 49 | // performance variables 50 | 51 | /// phaseDelta multiplier for pitchbend, vibrato 52 | float phaseDeltaMultiplier; 53 | 54 | EnsembleOscillator(std::mt19937* gen) : phaseCount(1), frequencySpread(0.0f), gen(gen) {} 55 | void init(double sampleRate, WaveStack *pStack); 56 | void setPhases(int nPhases); 57 | void setFreqSpread(float fSpread) { frequencySpread = fSpread; } 58 | 59 | /// argument is a fraction: 0 = no spread, 1 = max spread 60 | void setPanSpread(float fSpread); 61 | void setFrequency(float frequency); 62 | 63 | float getSample(); 64 | void getSamples(float *pLeft, float *pRight, float gain); 65 | }; 66 | 67 | } 68 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/EnvelopeGeneratorBase.cpp: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #include "EnvelopeGeneratorBase.h" 4 | #include 5 | #include 6 | 7 | namespace DunneCore 8 | { 9 | 10 | void ExponentialSegmentGenerator::reset(double initialValue, double targetValue, double tco, int segmentLengthSamples) 11 | { 12 | output = segmentLengthSamples > 0 ? initialValue : targetValue; 13 | target = targetValue; 14 | isHorizontal = targetValue == initialValue; 15 | isLinear = tco <= 0.0; 16 | isRising = targetValue > initialValue; 17 | 18 | if (isHorizontal) 19 | { 20 | tcount = 0; 21 | segLength = segmentLengthSamples; 22 | } 23 | else if (isLinear) 24 | { 25 | if (segmentLengthSamples <= 0) 26 | coefficient = target - output; 27 | else 28 | coefficient = (targetValue - initialValue) / segmentLengthSamples; 29 | } 30 | else 31 | { 32 | if (segmentLengthSamples == 0) 33 | { 34 | coefficient = 0.0; 35 | offset = target; 36 | } 37 | else 38 | { 39 | // Correction to Pirkle (who uses delta = 1.0 always) 40 | // According to Redmon (who only discusses the delta = 1.0 case), delta should be defined thus 41 | double delta = abs(targetValue - initialValue); 42 | coefficient = exp(-log((delta + tco) / tco) / segmentLengthSamples); 43 | if (isRising) 44 | offset = (target + tco) * (1.0 - coefficient); 45 | else 46 | offset = (target - tco) * (1.0 - coefficient); 47 | } 48 | } 49 | } 50 | 51 | void MultiSegmentEnvelopeGenerator::setupCurSeg() 52 | { 53 | SegmentDescriptor seg = (*segments)[curSegIndex]; 54 | ExponentialSegmentGenerator::reset(seg.initialValue, seg.finalValue, seg.tco, seg.lengthSamples); 55 | } 56 | 57 | void MultiSegmentEnvelopeGenerator::setupCurSeg(double initValue) 58 | { 59 | SegmentDescriptor seg = (*segments)[curSegIndex]; 60 | double targetValue = seg.finalValue; 61 | bool isHorizontal = seg.initialValue == seg.finalValue; 62 | if (isHorizontal) { // if flat (hold) then use same value, prevents fades from currentVal to hold val 63 | targetValue = initValue; 64 | } 65 | ExponentialSegmentGenerator::reset(initValue, targetValue, seg.tco, seg.lengthSamples); 66 | } 67 | 68 | void MultiSegmentEnvelopeGenerator::reset(Descriptor* pDesc, int initialSegmentIndex) 69 | { 70 | segments = pDesc; 71 | curSegIndex = initialSegmentIndex; 72 | setupCurSeg(); 73 | } 74 | 75 | void MultiSegmentEnvelopeGenerator::startAtSegment(int segIndex) //puts the envelope in a 'fresh' state, allows sudden jumps to first segment 76 | { 77 | curSegIndex = segIndex; 78 | if (skipEmptySegments()) { 79 | SegmentDescriptor& seg = (*segments)[curSegIndex]; 80 | setupCurSeg(seg.initialValue); // we are restarting, not advancing, so always start from the first value we get to 81 | }; 82 | } 83 | 84 | void MultiSegmentEnvelopeGenerator::advanceToSegment(int segIndex) //advances w/ awareness of state, so as to not make sudden jumps 85 | { 86 | curSegIndex = segIndex; 87 | if (skipEmptySegments()) { 88 | setupCurSeg(output); //we are advancing, not restarting, so always start from the value we are currently at 89 | }; 90 | } 91 | 92 | bool MultiSegmentEnvelopeGenerator::skipEmptySegments() //skips over any segment w/ length 0, so as to not influence the state of the envelope 93 | { 94 | assert(segments); 95 | 96 | // skip any segments that are 0-length 97 | while(curSegIndex < segments->size() 98 | && (*segments)[curSegIndex].lengthSamples == 0) { 99 | curSegIndex++; 100 | } 101 | 102 | // if at end of the envelope, reset 103 | if(curSegIndex == segments->size()) { 104 | reset(segments); 105 | return false; 106 | } 107 | 108 | return true; 109 | 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/EnvelopeGeneratorBase.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | #include 5 | 6 | namespace DunneCore 7 | { 8 | 9 | // Iterative-exponential envelope generator, as described by Will Pirkle's synthesizer book 10 | // (Designing Software Synthesizer Plug-Ins in C++, Focal Press, 2014, ISBN 978-1-138-78707-0) 11 | // and Nigel Redmon (http://www.earlevel.com/main/2013/06/02/envelope-generators-adsr-part-2/). 12 | 13 | // Additions/adaptations by Shane Dunne to support 14 | // * arbitrary number of segments, not just "ADSR" 15 | // * horizontal segments, both fixed-length ("hold time") and indefinite length ("sustain") 16 | // * linear segments (e.g. for fast shutoff) 17 | 18 | class ExponentialSegmentGenerator 19 | { 20 | public: 21 | void reset(double initialValue, double targetValue, double tco, int segmentLengthSamples); 22 | 23 | inline float getValue() 24 | { 25 | return float(isHorizontal ? target : output); 26 | } 27 | 28 | inline bool getSample(float& out) 29 | { 30 | if (isHorizontal) 31 | { 32 | out = float(target); 33 | if (segLength < 0) return false; // non-timed "sustain"segment 34 | else return (++tcount >= segLength); // timed "hold" segment 35 | } 36 | else 37 | { 38 | if (isLinear) 39 | output += coefficient; 40 | else 41 | output = offset + coefficient * output; 42 | bool overshoot = isRising ? (output >= target) : (output <= target); 43 | if (overshoot) output = target; 44 | out = float(output); 45 | return overshoot; 46 | } 47 | } 48 | 49 | protected: 50 | double output, target, offset, coefficient; 51 | bool isRising; 52 | bool isHorizontal; 53 | int tcount, segLength; 54 | bool isLinear; 55 | }; 56 | 57 | class MultiSegmentEnvelopeGenerator : public ExponentialSegmentGenerator 58 | { 59 | public: 60 | struct SegmentDescriptor 61 | { 62 | double initialValue; 63 | double finalValue; 64 | double tco; 65 | int lengthSamples; 66 | }; 67 | typedef std::vector Descriptor; 68 | 69 | void reset(Descriptor* pDesc, int initialSegmentIndex = 0); 70 | void advanceToSegment(int segIndex); 71 | void startAtSegment(int segIndex); 72 | 73 | inline bool getSample(float& out) 74 | { 75 | if (ExponentialSegmentGenerator::getSample(out)) 76 | { 77 | if (++curSegIndex >= int(segments->size())) 78 | { 79 | reset(segments); 80 | return true; 81 | } 82 | else 83 | { 84 | setupCurSeg(); 85 | } 86 | } 87 | return false; 88 | } 89 | 90 | int getCurrentSegmentIndex() { return curSegIndex; } 91 | 92 | protected: 93 | Descriptor* segments = nullptr; 94 | int curSegIndex; 95 | 96 | void setupCurSeg(); 97 | void setupCurSeg(double initValue); 98 | bool skipEmptySegments(); 99 | }; 100 | 101 | } 102 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/LinearParameterRamp.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #import "ParameterRampBase.h" 6 | 7 | #ifdef __cplusplus 8 | 9 | // Currently Unused 10 | 11 | struct LinearParameterRamp : ParameterRampBase { 12 | 13 | float computeValueAt(int64_t atSample) override { 14 | float fract = (float)(atSample - _startSample) / _duration; 15 | return _value = _startValue + (_target - _startValue) * fract; 16 | } 17 | 18 | }; 19 | 20 | #endif 21 | 22 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/LinearRamper.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | namespace DunneCore 6 | { 7 | 8 | struct LinearRamper 9 | { 10 | static float constexpr flagValue = 100000.0f; 11 | 12 | float value; // current value 13 | float target; // where it is headed 14 | float increment; // per-sample increment 15 | int count; // counts down samples 16 | 17 | LinearRamper() : value(0.0f), target(0.0f), increment(0.0f), count(0) {} 18 | 19 | // initialize to a stable value 20 | void init(float v) 21 | { 22 | value = target = v; 23 | increment = 0.0f; 24 | count = 0; 25 | } 26 | 27 | // initialize all parameters 28 | void init(float startValue, float targetValue, int intervalSamples) 29 | { 30 | count = intervalSamples; // assume startValue != targetValue 31 | target = targetValue; 32 | if (count < 1) 33 | { 34 | // target has already been hit, we're already done 35 | value = target; 36 | increment = 0.0f; 37 | } 38 | else 39 | { 40 | // normal case: value ramps to target 41 | value = startValue; 42 | increment = (target - value) / count; 43 | } 44 | } 45 | 46 | // reset new target and new interval, retaining current value 47 | inline void reinit(float targetValue, int intervalSamples) 48 | { 49 | init(value, targetValue, intervalSamples); 50 | } 51 | 52 | inline float isRamping() 53 | { 54 | return count > 0; 55 | } 56 | 57 | inline float getNextValue() 58 | { 59 | if (count > 0) 60 | { 61 | value += increment; 62 | count--; 63 | } 64 | return value; 65 | } 66 | 67 | inline void getValues(int nValuesNeeded, float *pOut) 68 | { 69 | for (int i=0; i < nValuesNeeded; i++) *pOut++ = getNextValue(); 70 | } 71 | }; 72 | 73 | } 74 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/MultiStageFilter.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | // MultiStageFilter implements a simple digital low-pass filter with dynamically 4 | // adjustable cutoff frequency and resonance. 5 | // 6 | // Filter resonance is usually expressed in dB, but to avoid having to call expensive 7 | // math functions like pow(), we use a linear value between 10.0 (-20 dB) and 0.1 (+20 dB) 8 | 9 | #pragma once 10 | 11 | #include "ResonantLowPassFilter.h" 12 | 13 | namespace DunneCore 14 | { 15 | 16 | struct MultiStageFilter 17 | { 18 | static constexpr int maxStages = 4; 19 | int stages; 20 | ResonantLowPassFilter stage[maxStages]; 21 | 22 | MultiStageFilter(); 23 | 24 | void init(double sampleRateHz); 25 | void updateSampleRate(double sampleRateHz); 26 | 27 | void setStages(int nStages); 28 | void setParameters(double newCutoffHz, double newResLinear); 29 | void setCutoff(double newCutoffHz); 30 | void setResonance(double newResLinear); 31 | 32 | float process(float sample); 33 | }; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/ParameterRampBase.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #import 6 | #import "DSPBase.h" // have to put this here to get it included in umbrella header 7 | 8 | #ifdef __cplusplus 9 | 10 | class ParameterRampBase { 11 | protected: 12 | float _paramValue = 0; // set by UI thread 13 | float _target = 0; 14 | float _value = 0; 15 | float _startValue = 0; 16 | int64_t _duration = 0; // in samples 17 | int64_t _startSample = 0; 18 | int _rampType = 0; // see Settings.RampType 19 | 20 | void updateTarget(int64_t atSample) 21 | { 22 | _target = _paramValue; 23 | _startSample = atSample; 24 | _startValue = _value; 25 | } 26 | 27 | public: 28 | 29 | virtual float computeValueAt(int64_t atSample) = 0; 30 | 31 | float getStartValue() 32 | { 33 | return _startValue; 34 | } 35 | 36 | float getValue() 37 | { 38 | return _value; 39 | } 40 | 41 | void setTarget(float value, bool immediate = false) __attribute__((no_sanitize("thread"))) 42 | { 43 | if (immediate) { 44 | _startValue = _paramValue = _value = _target = value; 45 | } else { 46 | _target = _paramValue = value; 47 | } 48 | } 49 | 50 | float getTarget() __attribute__((no_sanitize("thread"))) 51 | { 52 | return _target; 53 | } 54 | 55 | void setRampType(int rampType) 56 | { 57 | _rampType = rampType; 58 | } 59 | 60 | int getRampType() 61 | { 62 | return _rampType; 63 | } 64 | 65 | void setDurationInSamples(int64_t duration) 66 | { 67 | if (duration >= 0) _duration = duration; 68 | } 69 | 70 | float getDurationInSamples() 71 | { 72 | return _duration; 73 | } 74 | 75 | void setRampDuration(float seconds, int64_t sampleRate) 76 | { 77 | _duration = seconds * sampleRate; 78 | } 79 | 80 | float getRampDuration(int64_t sampleRate) 81 | { 82 | return (sampleRate == 0) ? 0 : _duration / sampleRate; 83 | } 84 | 85 | float advanceTo(int64_t atSample) 86 | { 87 | if (_paramValue != _target) { 88 | updateTarget(atSample); 89 | } 90 | if (_value == _target) return _value; 91 | int64_t deltaSamples = atSample - _startSample; 92 | if (deltaSamples >= _duration || deltaSamples < 0) { 93 | _value = _target; 94 | _startSample = 0; // for good measure 95 | } else { 96 | computeValueAt(atSample); 97 | } 98 | return _value; 99 | } 100 | }; 101 | 102 | #endif 103 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/README.md: -------------------------------------------------------------------------------- 1 | # DunneCore/Common 2 | 3 | This directory contains basic building-block C++ classes used in several AudioKit DSP modules. 4 | 5 | ## ADSREnvelope 6 | Basic four-segment envelope generator with linear *Attack*, *Decay*, *Sustain* and *Release* segments, plus a special "silence" segment used to quickly (but not instantaneously) silence a note before re-triggering it. 7 | 8 | This is a stand-alone class at the moment, but it will eventually become one of several specialized subclasses of a more general multi-segment "Envelope" class. 9 | 10 | ## FunctionTable 11 | Basic one-dimensional *lookup table* for tabulated functions, with *linear interpolation* between adjacent values, and a choice of either *cyclical addressing* (for periodic functions; see **FunctionTableOscillator**) or *bounded addressing* (for non-periodic functions; see **WaveShaper**). 12 | 13 | Utility functions are provided to initialize the table data to triangle, sinusoid, and sawtooth waves (useful for LFOs) and exponential curves (useful for wave shaping). 14 | 15 | ## FunctionTableOscillator 16 | Simple oscillator based on samples of a periodic function stored in an **FunctionTable**. 17 | 18 | ## WaveShaper 19 | Wraps an **FunctionTable** and provides saved scale and offset parameters for both input (x) and output (y) values. 20 | 21 | ## LinearRamper 22 | Basic digital ramp generator, with a floating-point *value* member variable which advances by small increments toward a specified *target* value. A core building-block for envelope generators. 23 | 24 | ## ResonantLowPassFilter 25 | A simple digital low-pass filter with resonance, adapted from an Apple code sample. 26 | 27 | ## SustainPedalLogic 28 | Encapsulates the basic logic for tracking the up/down state of MIDI keys and a sustain pedal, to allow a multi-voice instrument to determine how to respond to *key-down*, *key-up*, *pedal-down*, and *pedal-up* events. 29 | 30 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/ResonantLowPassFilter.cpp: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | // ResonantLowPassFilter implements a simple digital low-pass filter with dynamically 4 | // adjustable cutoff frequency and resonance. 5 | 6 | #include "ResonantLowPassFilter.h" 7 | #include "FunctionTable.h" 8 | 9 | namespace DunneCore 10 | { 11 | // To avoid having to call sin() and cos() in setParameters() (whenever filter parameters 12 | // are changed), we maintain this static sine lookup table. 13 | static FunctionTable sineTable; 14 | static float Sine(float phase) { return sineTable.interp_cyclic(phase); } 15 | static float Cosine(float phase) { return sineTable.interp_cyclic(phase + 0.25f); } 16 | 17 | static const float kMinCutoffHz = 12.0f; 18 | static const float kMinResLinear = 0.1f; 19 | static const float kMaxResLinear = 10.0f; 20 | 21 | ResonantLowPassFilter::ResonantLowPassFilter() 22 | { 23 | init(44100.0); // sensible guess, will be overridden by init() call anyway 24 | 25 | if (sineTable.waveTable.empty()) // build sine table only once 26 | { 27 | sineTable.init(2048); 28 | sineTable.sinusoid(); 29 | } 30 | } 31 | 32 | void ResonantLowPassFilter::init(double sampleRateHz) 33 | { 34 | this->sampleRateHz = sampleRateHz; 35 | x1 = x2 = y1 = y2 = 0.0; 36 | mLastCutoffHz = mLastResLinear = -1.0; // force recalc of coefficients 37 | } 38 | 39 | void ResonantLowPassFilter::setParameters(double newCutoffHz, double newResLinear) 40 | { 41 | // only calculate the filter coefficients if the parameters have changed from last time 42 | if (newCutoffHz == mLastCutoffHz && newResLinear == mLastResLinear) return; 43 | 44 | if (newCutoffHz < kMinCutoffHz) newCutoffHz = kMinCutoffHz; 45 | if (newResLinear < kMinResLinear ) newResLinear = kMinResLinear; 46 | if (newResLinear > kMaxResLinear ) newResLinear = kMaxResLinear; 47 | 48 | // convert cutoff from Hz to 0->1 normalized frequency 49 | double cutoff = 2.0 * newCutoffHz / sampleRateHz; 50 | if (cutoff > 0.99) cutoff = 0.99; // clip 51 | 52 | mLastCutoffHz = newCutoffHz; 53 | mLastResLinear = newResLinear; 54 | 55 | double k = 0.5 * newResLinear * Sine(float(0.5 * cutoff)); 56 | double c1 = 0.5 * (1.0 - k) / (1.0 + k); 57 | double c2 = (0.5 + c1) * Cosine(float(0.5 * cutoff)); 58 | double c3 = (0.5 + c1 - c2) * 0.25; 59 | 60 | a0 = 2.0 * c3; 61 | a1 = 2.0 * 2.0 * c3; 62 | a2 = 2.0 * c3; 63 | b1 = 2.0 * -c2; 64 | b2 = 2.0 * c1; 65 | } 66 | 67 | void ResonantLowPassFilter::process(const float *sourceP, float *destP, int inFramesToProcess) 68 | { 69 | while (inFramesToProcess--) 70 | { 71 | float inputSample = *sourceP++; 72 | float outputSample = float(a0*inputSample + a1*x1 + a2*x2 - b1*y1 - b2*y2); 73 | 74 | x2 = x1; 75 | x1 = inputSample; 76 | y2 = y1; 77 | y1 = outputSample; 78 | 79 | *destP++ = outputSample; 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/ResonantLowPassFilter.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | // ResonantLowPassFilter implements a simple digital low-pass filter with dynamically 4 | // adjustable cutoff frequency and resonance. 5 | // 6 | // Filter resonance is usually expressed in dB, but to avoid having to call expensive 7 | // math functions like pow(), we use a linear value between 10.0 (-20 dB) and 0.1 (+20 dB) 8 | 9 | #pragma once 10 | 11 | namespace DunneCore 12 | { 13 | 14 | struct ResonantLowPassFilter 15 | { 16 | // coefficients 17 | double a0, a1, a2, b1, b2; 18 | 19 | // state 20 | double x1, x2, y1, y2; 21 | 22 | // misc 23 | double sampleRateHz, mLastCutoffHz, mLastResLinear; 24 | 25 | ResonantLowPassFilter(); 26 | 27 | void init(double sampleRateHz); 28 | void updateSampleRate(double sampleRate) { sampleRateHz = sampleRate; } 29 | 30 | void setParameters(double newCutoffHz, double newResLinear); 31 | void setCutoff(double newCutoffHz) { setParameters(newCutoffHz, mLastResLinear); } 32 | void setResonance(double newResLinear) { setParameters(mLastCutoffHz, newResLinear); } 33 | 34 | void process(const float *inSourceP, float *inDestP, int inFramesToProcess); 35 | 36 | inline float process(float inputSample) 37 | { 38 | float outputSample = (float)(a0*inputSample + a1*x1 + a2*x2 - b1*y1 - b2*y2); 39 | 40 | x2 = x1; 41 | x1 = inputSample; 42 | y2 = y1; 43 | y1 = outputSample; 44 | 45 | return outputSample; 46 | } 47 | 48 | }; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/SustainPedalLogic.cpp: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #include "SustainPedalLogic.h" 4 | 5 | namespace DunneCore 6 | { 7 | 8 | SustainPedalLogic::SustainPedalLogic() 9 | { 10 | for (int i=0; i < kMidiNoteNumbers; i++) keyDown[i] = isPlaying[i] = false; 11 | pedalIsDown = false; 12 | } 13 | 14 | bool SustainPedalLogic::keyDownAction(unsigned noteNumber) 15 | { 16 | bool noteShouldStopBeforePlayingAgain = false; 17 | 18 | if (pedalIsDown && keyDown[noteNumber]) 19 | noteShouldStopBeforePlayingAgain = true; 20 | else 21 | keyDown[noteNumber] = true; 22 | 23 | isPlaying[noteNumber] = true; 24 | return noteShouldStopBeforePlayingAgain; 25 | } 26 | 27 | bool SustainPedalLogic::keyUpAction(unsigned noteNumber) 28 | { 29 | bool noteShouldStop = false; 30 | 31 | if (!pedalIsDown) 32 | { 33 | noteShouldStop = true; 34 | isPlaying[noteNumber] = false; 35 | } 36 | keyDown[noteNumber] = false; 37 | return noteShouldStop; 38 | } 39 | 40 | void SustainPedalLogic::pedalDown() { pedalIsDown = true; } 41 | 42 | void SustainPedalLogic::pedalUp() { pedalIsDown = false; } 43 | 44 | bool SustainPedalLogic::isNoteSustaining(unsigned noteNumber) 45 | { 46 | return isPlaying[noteNumber] && !keyDown[noteNumber]; 47 | } 48 | 49 | bool SustainPedalLogic::isAnyKeyDown() 50 | { 51 | for (int i = 0; i < kMidiNoteNumbers; i++) if (keyDown[i]) return true; 52 | return false; 53 | } 54 | 55 | int SustainPedalLogic::firstKeyDown() 56 | { 57 | for (int i = 0; i < kMidiNoteNumbers; i++) if (keyDown[i]) return i; 58 | return -1; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/SustainPedalLogic.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | namespace DunneCore 6 | { 7 | static const int kMidiNoteNumbers = 128; 8 | 9 | class SustainPedalLogic 10 | { 11 | bool keyDown[kMidiNoteNumbers]; 12 | bool isPlaying[kMidiNoteNumbers]; 13 | bool pedalIsDown; 14 | 15 | public: 16 | SustainPedalLogic(); 17 | 18 | // return true if given note should stop playing 19 | bool keyDownAction(unsigned noteNumber); 20 | bool keyUpAction(unsigned noteNumber); 21 | 22 | void pedalDown(); 23 | bool isNoteSustaining(unsigned noteNumber); 24 | bool isAnyKeyDown(); 25 | int firstKeyDown(); 26 | void pedalUp(); 27 | }; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/SynthVoice.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | #include 5 | 6 | #include "EnsembleOscillator.h" 7 | #include "DrawbarsOscillator.h" 8 | #include "ADSREnvelope.h" 9 | #include "CoreEnvelope.h" 10 | #include "MultiStageFilter.h" 11 | 12 | namespace DunneCore 13 | { 14 | 15 | struct SynthOscParameters 16 | { 17 | int phases; // 1 to 10, or 0 to disable oscillator 18 | float frequencySpread; // cents 19 | float panSpread; // fraction 0 = no spread, 1 = max spread 20 | float pitchOffset; // semitones, relative to MIDI note 21 | float mixLevel; // fraction 22 | }; 23 | 24 | struct OrganParameters 25 | { 26 | float drawbars[DrawbarsOscillator::phaseCount]; 27 | float mixLevel; 28 | }; 29 | 30 | struct SynthVoiceParameters 31 | { 32 | SynthOscParameters osc1, osc2; 33 | OrganParameters osc3; 34 | /// 1 to 4, or 0 to disable filter 35 | int filterStages; 36 | }; 37 | 38 | struct SynthVoice 39 | { 40 | SynthVoiceParameters *pParameters; 41 | 42 | EnsembleOscillator osc1, osc2; 43 | DrawbarsOscillator osc3; 44 | MultiStageFilter leftFilter, rightFilter; // two filters (left/right) 45 | ADSREnvelope ampEG, filterEG; 46 | Envelope pumpEG; 47 | 48 | unsigned event = 0; // last "event number" associated with this voice 49 | int noteNumber = -1; // MIDI note number, or -1 if not playing any note 50 | float noteFrequency = 0; // note frequency in Hz 51 | float noteVolume = 0; // fraction 0.0 - 1.0, based on MIDI velocity 52 | 53 | // temporary holding variables 54 | int newNoteNumber; // holds new note number while damping note before restarting 55 | float newNoteVol; // holds new note volume while damping note before restarting 56 | float tempGain; // product of global volume, note volume, and amp EG 57 | 58 | SynthVoice(std::mt19937* gen) : noteNumber(-1), osc1(gen), osc2(gen) {} 59 | 60 | void init(double sampleRate, 61 | WaveStack *pOsc1Stack, 62 | WaveStack *pOsc2Stack, 63 | WaveStack *pOsc3Stack, 64 | SynthVoiceParameters *pParameters, 65 | EnvelopeParameters *pEnvParameters); 66 | 67 | void updateAmpAdsrParameters() { ampEG.updateParams(); } 68 | void updateFilterAdsrParameters() { filterEG.updateParams(); } 69 | 70 | void start(unsigned evt, unsigned noteNumber, float frequency, float volume); 71 | void restart(unsigned evt, float volume); 72 | void restart(unsigned evt, unsigned noteNumber, float frequency, float volume); 73 | void release(unsigned evt); 74 | void stop(unsigned evt); 75 | 76 | // return true if amp envelope is finished 77 | bool prepToGetSamples(float masterVol, 78 | float phaseDeltaMultiplier, 79 | float cutoffMultiple, 80 | float cutoffStrength, 81 | float resLinear); 82 | bool getSamples(int sampleCount, float *leftOuput, float *rightOutput); 83 | }; 84 | 85 | } 86 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Common/WaveStack.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace DunneCore 8 | { 9 | 10 | // WaveStack represents a series of progressively lower-resolution sampled versions of a 11 | // waveform. Client code supplies the initial waveform, at a resolution of 1024 samples, 12 | // equivalent to 43.6 Hz at 44.1K samples/sec (about 23.44 cents below F1, midi note 29), 13 | // and then calls initStack() to create the filtered higher-octave versions. 14 | // This provides a basis for anti-aliased oscillators; see class WaveStackOscillator. 15 | 16 | struct WaveStack 17 | { 18 | // Highest-resolution rep uses 2^maxBits samples 19 | static constexpr int maxBits = 10; // 1024 20 | 21 | // maxBits also defines the number of octave levels; highest level has just 2 samples 22 | float *pData[maxBits]; 23 | 24 | WaveStack(); 25 | ~WaveStack(); 26 | 27 | // Fill pWaveData with 1024 samples, then call this 28 | void initStack(const std::vector& waveData, int maxHarmonic=512); 29 | 30 | float interp(int octave, float phase); 31 | }; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Modulated Delay/AdjustableDelayLine.cpp: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #include "AdjustableDelayLine.h" 4 | #include 5 | 6 | namespace DunneCore 7 | { 8 | void AdjustableDelayLine::init(double sampleRate, double maxDelayMilliseconds) 9 | { 10 | sampleRateHz = sampleRate; 11 | maxDelayMs = maxDelayMilliseconds; 12 | 13 | buffer.resize(int(maxDelayMs * sampleRateHz / 1000.0)); 14 | clear(); 15 | writeIndex = 0; 16 | readIndex = (float)(buffer.size() - 1); 17 | fbFraction = 0.0f; 18 | output = 0.0f; 19 | } 20 | 21 | void AdjustableDelayLine::deinit() 22 | { 23 | buffer.clear(); 24 | } 25 | 26 | void AdjustableDelayLine::clear() 27 | { 28 | std::fill(buffer.begin(), buffer.end(), 0.0f); 29 | } 30 | 31 | void AdjustableDelayLine::setDelayMs(double delayMs) 32 | { 33 | if (delayMs > maxDelayMs) delayMs = maxDelayMs; 34 | if (delayMs < 0.0f) delayMs = 0.0f; 35 | 36 | size_t capacity = buffer.size(); 37 | 38 | float fReadWriteGap = float(delayMs * sampleRateHz / 1000.0); 39 | if (fReadWriteGap < 0.0f) fReadWriteGap = 0.0f; 40 | if (fReadWriteGap > capacity) fReadWriteGap = (float)capacity; 41 | readIndex = writeIndex - fReadWriteGap; 42 | while (readIndex < 0.0f) readIndex += capacity; 43 | while (readIndex >= capacity) readIndex -= capacity; 44 | } 45 | 46 | float AdjustableDelayLine::push(float sample) 47 | { 48 | if (buffer.empty()) return sample; 49 | 50 | size_t capacity = buffer.size(); 51 | 52 | int ri = int(readIndex); 53 | float f = readIndex - ri; 54 | int rj = ri + 1; if (rj >= capacity) rj -= capacity; 55 | readIndex += 1.0f; 56 | if (readIndex >= capacity) readIndex -= capacity; 57 | 58 | float si = buffer[ri]; 59 | float sj = buffer[rj]; 60 | float outSample = (1.0f - f) * si + f * sj; 61 | 62 | buffer[writeIndex++] = sample + fbFraction * outSample; 63 | if (writeIndex >= capacity) writeIndex = 0; 64 | 65 | return (output = outSample); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Modulated Delay/AdjustableDelayLine.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | #include 5 | 6 | namespace DunneCore 7 | { 8 | class AdjustableDelayLine { 9 | double sampleRateHz; 10 | double maxDelayMs; 11 | float fbFraction; 12 | std::vector buffer; 13 | int writeIndex; 14 | float readIndex; 15 | float output; 16 | 17 | public: 18 | ~AdjustableDelayLine() { deinit(); } 19 | 20 | void init(double sampleRate, double maxDelayMilliseconds); 21 | void deinit(); 22 | 23 | void clear(); 24 | 25 | double getMaxDelayMs() { return maxDelayMs; } 26 | 27 | void setDelayMs(double delayMs); 28 | void setFeedback(float feedback) { fbFraction = feedback; } 29 | 30 | float push(float sample); 31 | 32 | float getOutput() { return output; } 33 | }; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Modulated Delay/ModulatedDelay.cpp: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #include "ModulatedDelay.h" 4 | #include "ModulatedDelay_Defines.h" 5 | 6 | #include "AdjustableDelayLine.h" 7 | #include "FunctionTable.h" 8 | 9 | struct ModulatedDelay::InternalData 10 | { 11 | DunneCore::AdjustableDelayLine leftDelayLine, rightDelayLine; 12 | DunneCore::FunctionTableOscillator modOscillator; 13 | }; 14 | 15 | ModulatedDelay::ModulatedDelay(ModulatedDelayType type) 16 | : modFreqHz(1.0f) 17 | , modDepthFraction(0.0f) 18 | , effectType(type), data(new InternalData) 19 | { 20 | } 21 | 22 | ModulatedDelay::~ModulatedDelay() 23 | { 24 | deinit(); 25 | } 26 | 27 | void ModulatedDelay::init(int channelCount, double sampleRate) 28 | { 29 | minDelayMs = kChorusMinDelayMs; 30 | maxDelayMs = kChorusMaxDelayMs; 31 | data->modOscillator.init(sampleRate, modFreqHz); 32 | switch (effectType) { 33 | case kFlanger: 34 | minDelayMs = kFlangerMinDelayMs; 35 | maxDelayMs = kFlangerMaxDelayMs; 36 | data->modOscillator.waveTable.triangle(); 37 | break; 38 | case kChorus: 39 | default: 40 | data->modOscillator.waveTable.sinusoid(); 41 | break; 42 | } 43 | delayRangeMs = 0.5f * (maxDelayMs - minDelayMs); 44 | midDelayMs = 0.5f * (minDelayMs + maxDelayMs); 45 | data->leftDelayLine.init(sampleRate, maxDelayMs); 46 | data->rightDelayLine.init(sampleRate, maxDelayMs); 47 | data->leftDelayLine.setDelayMs(minDelayMs); 48 | data->rightDelayLine.setDelayMs(minDelayMs); 49 | } 50 | 51 | void ModulatedDelay::deinit() 52 | { 53 | data->leftDelayLine.deinit(); 54 | data->rightDelayLine.deinit(); 55 | data->modOscillator.deinit(); 56 | } 57 | 58 | void ModulatedDelay::setModFrequencyHz(float freq) 59 | { 60 | data->modOscillator.setFrequency(freq); 61 | } 62 | 63 | void ModulatedDelay::setLeftFeedback(float feedback) 64 | { 65 | data->leftDelayLine.setFeedback(feedback); 66 | } 67 | 68 | void ModulatedDelay::setRightFeedback(float feedback) 69 | { 70 | data->rightDelayLine.setFeedback(feedback); 71 | } 72 | 73 | void ModulatedDelay::Render(unsigned channelCount, unsigned sampleCount, 74 | float *inBuffers[], float *outBuffers[]) 75 | { 76 | float *pInLeft = inBuffers[0]; 77 | float *pInRight = inBuffers[1]; 78 | float *pOutLeft = outBuffers[0]; 79 | float *pOutRight = outBuffers[1]; 80 | 81 | for (int i=0; i < (int)sampleCount; i++) 82 | { 83 | float modLeft, modRight; 84 | data->modOscillator.getSamples(&modLeft, &modRight); 85 | 86 | float leftDelayMs = midDelayMs + delayRangeMs * modDepthFraction * modLeft; 87 | float rightDelayMs = midDelayMs + delayRangeMs * modDepthFraction * modRight; 88 | switch (effectType) { 89 | case kFlanger: 90 | leftDelayMs = minDelayMs + delayRangeMs * modDepthFraction * (1.0f + modLeft); 91 | rightDelayMs = minDelayMs + delayRangeMs * modDepthFraction * (1.0f + modRight); 92 | break; 93 | 94 | case kChorus: 95 | default: 96 | break; 97 | } 98 | data->leftDelayLine.setDelayMs(leftDelayMs); 99 | data->rightDelayLine.setDelayMs(rightDelayMs); 100 | 101 | float dryFraction = 1.0f - dryWetMix; 102 | *pOutLeft++ = dryFraction * (*pInLeft) + dryWetMix * data->leftDelayLine.push(*pInLeft); 103 | pInLeft++; 104 | if (channelCount > 1) 105 | { 106 | *pOutRight++ = dryFraction * (*pInRight) + dryWetMix * data->rightDelayLine.push(*pInRight); 107 | pInRight++; 108 | } 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Modulated Delay/ModulatedDelay.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #ifdef __cplusplus 4 | #pragma once 5 | 6 | #include "ModulatedDelay_Typedefs.h" 7 | 8 | #import 9 | 10 | class ModulatedDelay 11 | { 12 | public: 13 | ModulatedDelay(ModulatedDelayType type); 14 | ~ModulatedDelay(); 15 | 16 | void init(int channelCount, double sampleRate); 17 | void deinit(); 18 | 19 | void setModFrequencyHz(float freq); 20 | float getModFrequencyHz() { return modFreqHz; } 21 | 22 | void setModDepthFraction(float fraction) { modDepthFraction = fraction; } 23 | float getModDepthFraction() { return modDepthFraction; } 24 | 25 | void setLeftFeedback(float feedback); 26 | void setRightFeedback(float feedback); 27 | 28 | void setDryWetMix(float mix) { dryWetMix = mix; } 29 | 30 | void Render(unsigned channelCount, unsigned sampleCount, float *inBuffers[], float *outBuffers[]); 31 | 32 | protected: 33 | float minDelayMs, maxDelayMs, midDelayMs, delayRangeMs; 34 | float modFreqHz, modDepthFraction, dryWetMix; 35 | ModulatedDelayType effectType; 36 | 37 | struct InternalData; 38 | std::unique_ptr data; 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Modulated Delay/ModulatedDelay_Defines.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | // MARK: - Constants inherent to the definition of Chorus and Flanger effects 6 | 7 | // MARK: Chorus Inherent Constants 8 | #define kChorusMinDelayMs 4.00f 9 | #define kChorusMaxDelayMs 24.00f 10 | 11 | // MARK: Flanger Inherent Constants 12 | #define kFlangerMinDelayMs 0.01f 13 | #define kFlangerMaxDelayMs 10.00f 14 | #define kFlangerDefaultMix 0.50f // Conventionally, flanger uses a 50% mix 15 | 16 | // MARK: - Suggested default values 17 | 18 | // MARK: Chorus Defaults 19 | #define kChorusDefaultModFreqHz 1.00f 20 | #define kChorusDefaultDepth 0.25f 21 | #define kChorusDefaultFeedback 0.00f 22 | #define kChorusDefaultMix 0.25f 23 | 24 | // MARK: Flanger Defaults 25 | #define kFlangerDefaultModFreqHz 1.00f 26 | #define kFlangerMinModFreqHz 0.10f 27 | #define kFlangerMaxModFreqHz 10.00f 28 | #define kFlangerDefaultDepth 0.25f 29 | #define kFlangerDefaultFeedback 0.00f 30 | 31 | // MARK: - Suggested ranges 32 | 33 | // MARK: Chorus Ranges 34 | #define kChorusMinModFreqHz 0.10f 35 | #define kChorusMaxModFreqHz 10.00f 36 | #define kChorusMinDepth 0.00f 37 | #define kChorusMaxDepth 1.00f 38 | #define kChorusMinFeedback 0.00f 39 | #define kChorusMaxFeedback 0.95f 40 | #define kChorusMinDryWetMix 0.00f 41 | #define kChorusMaxDryWetMix 1.00f 42 | 43 | // MARK: Flanger Ranges 44 | #define kFlangerMinModFreqHz 0.10f 45 | #define kFlangerMaxModFreqHz 10.00f 46 | #define kFlangerMinDepth 0.00f 47 | #define kFlangerMaxDepth 1.00f 48 | #define kFlangerMinFeedback -0.95f 49 | #define kFlangerMaxFeedback 0.95f 50 | #define kFlangerMinDryWetMix 0.00f 51 | #define kFlangerMaxDryWetMix 1.00f 52 | 53 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Modulated Delay/StereoDelay.cpp: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #include "StereoDelay.h" 4 | 5 | namespace DunneCore 6 | { 7 | void StereoDelay::init(double sampleRate, double maxDelayMs) 8 | { 9 | delayLine1.init(sampleRate, maxDelayMs); 10 | delayLine2.init(sampleRate, maxDelayMs); 11 | } 12 | 13 | void StereoDelay::deinit() 14 | { 15 | delayLine1.deinit(); 16 | delayLine2.deinit(); 17 | } 18 | 19 | void StereoDelay::clear() 20 | { 21 | delayLine1.clear(); 22 | delayLine2.clear(); 23 | } 24 | 25 | void StereoDelay::setPingPongMode(bool pingPong) 26 | { 27 | pingPongMode = pingPong; 28 | setFeedback(feedbackFraction); 29 | } 30 | 31 | void StereoDelay::setDelayMs(double delayMs) 32 | { 33 | delayLine1.setDelayMs(fmax(1.0, delayMs)); 34 | delayLine2.setDelayMs(fmax(1.0, delayMs)); 35 | } 36 | 37 | void StereoDelay::setFeedback(float fraction) 38 | { 39 | feedbackFraction = fraction; 40 | delayLine1.setFeedback(pingPongMode ? 0.0f : fraction); 41 | delayLine2.setFeedback(pingPongMode ? 0.0f : fraction); 42 | } 43 | 44 | void StereoDelay::setDryWetMix(float fraction) 45 | { 46 | dryWetMixFraction = fraction; 47 | } 48 | 49 | void StereoDelay::render(int sampleCount, const float *inBuffers[], float *outBuffers[]) 50 | { 51 | if (pingPongMode) 52 | { 53 | for (int i = 0; i < sampleCount; i++) 54 | { 55 | float inputSample = 0.5f * (inBuffers[0][i] + inBuffers[1][i]); 56 | float leftSample = delayLine1.push(inputSample + feedbackFraction * delayLine2.getOutput()); 57 | float rightSample = delayLine2.push(leftSample); 58 | 59 | outBuffers[0][i] = (1.0f - dryWetMixFraction) * leftSample + dryWetMixFraction * inBuffers[0][i]; 60 | outBuffers[1][i] = (1.0f - dryWetMixFraction) * rightSample + dryWetMixFraction * inBuffers[1][i]; 61 | } 62 | } 63 | else 64 | { 65 | for (int i = 0; i < sampleCount; i++) 66 | { 67 | float leftSample = delayLine1.push(inBuffers[0][i]); 68 | float rightSample = delayLine2.push(inBuffers[1][i]); 69 | 70 | outBuffers[0][i] = (1.0f - dryWetMixFraction) * leftSample + dryWetMixFraction * inBuffers[0][i]; 71 | outBuffers[1][i] = (1.0f - dryWetMixFraction) * rightSample + dryWetMixFraction * inBuffers[1][i]; 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Modulated Delay/StereoDelay.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | #include "AdjustableDelayLine.h" 5 | 6 | namespace DunneCore 7 | { 8 | class StereoDelay { 9 | double sampleRateHz; 10 | float feedbackFraction; 11 | float dryWetMixFraction; 12 | bool pingPongMode; 13 | 14 | AdjustableDelayLine delayLine1, delayLine2; 15 | 16 | public: 17 | StereoDelay() : feedbackFraction(0.0f), dryWetMixFraction(0.5f), pingPongMode(false) {} 18 | ~StereoDelay() { deinit(); } 19 | 20 | void init(double sampleRate, double maxDelayMs); 21 | void deinit(); 22 | 23 | void clear(); 24 | 25 | void setPingPongMode(bool pingPong); 26 | void setDelayMs(double delayMs); 27 | void setFeedback(float fraction); 28 | void setDryWetMix(float fraction); 29 | 30 | bool getPingPongMode() { return pingPongMode; } 31 | 32 | void render(int sampleCount, const float *inBuffers[], float *outBuffers[]); 33 | }; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/README.md: -------------------------------------------------------------------------------- 1 | # AudioKit Core 2 | 3 | This directory stores the platform-independent, C++ building-block classes developed by the AudioKit group. All C++ code is defined in the **DunneCore** namespace. 4 | 5 | ## Common 6 | Code used in several other modules of AudioKit Core. 7 | 8 | ## Modulated Delay 9 | This stereo modulated delay-line is the basis of the **Chorus** and **Flanger** modules. 10 | 11 | ## Sampler 12 | A polyphonic sampler instrument to replace Apple's **AUSampler**. 13 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Sampler/README.md: -------------------------------------------------------------------------------- 1 | # DunneCore/Sampler 2 | 3 | Platform-independent C++ *DunneCore::Sampler* class and its specialized component classes. 4 | 5 | ## Sampler_Typedefs.h 6 | This file defines three C ``struct``s used in the API for **Sampler**, which can be bridged to Swift. Because this is (Objective-)C code, it does not use the *DunneCore* namespace, instead using the "AK" name prefix used at the Swift level. 7 | 8 | ## Sampler 9 | Class **Sampler** implements a complete multi-voice sample playback engine, roughly comparable to Apple's built-in **AUSampler**. It provides 10 | 11 | * A dynamic pool of in-memory *sample buffers* 12 | * A dynamic *key-map* defining how MIDI note-number, velocity pairs are used to select samples for playback 13 | * A bank of 64 *voices*, each *voice* comprising all resources required to play a note (see below) 14 | * A set of common *parameters* e.g. master volume, pitch bend, etc. 15 | * Member functions to trigger note playback and interpret real-time parameter changes (e.g. pitch bend) 16 | * Member functions to load and unload samples and build the key-map 17 | 18 | ## SamplerVoice 19 | Class **SamplerVoice** represents one of the 64 voices of an **Sampler**, and comprises: 20 | 21 | * pointer a *sample buffer* 22 | * a *sample oscillator* to scan and play samples from the buffer 23 | * two *resonant low-pass filters* (for Left and Right) channels 24 | * two *ADSR envelope generators*, one for amplitude, one for filter cutoff 25 | 26 | ## SampleOscillator 27 | Class **SamplerOscillator** is a very lightweight class for scanning through the samples of an **SampleBuffer** at a given speed, with *linear interpolation* between adjacent samples. 28 | 29 | ## SampleBuffer 30 | Class **SampleBuffer** represents a sample loaded in memory. Class **KeyMappedSampleBuffer** adds metadata about the range of MIDI note numbers and velocity values which should trigger this sample. 31 | 32 | Samples can be either mono or stereo, and have an associated MIDI note number (primarily for identification in a group of samples) and an associated pitch in Hz. 33 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Sampler/SampleBuffer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #include "SampleBuffer.h" 4 | 5 | namespace DunneCore 6 | { 7 | 8 | SampleBuffer::SampleBuffer() 9 | : samples(0) 10 | , channelCount(0) 11 | , sampleCount(0) 12 | , startPoint(0.0f) 13 | , endPoint(0.0f) 14 | , isLooping(false) 15 | , loopStartPoint(0.0f) 16 | , loopEndPoint(0.0f) 17 | { 18 | } 19 | 20 | SampleBuffer::~SampleBuffer() 21 | { 22 | deinit(); 23 | } 24 | 25 | void SampleBuffer::init(float sampleRate, int channelCount, int sampleCount) 26 | { 27 | this->sampleRate = sampleRate; 28 | this->sampleCount = sampleCount; 29 | this->channelCount = channelCount; 30 | if (samples) delete[] samples; 31 | samples = new float[channelCount * sampleCount]; 32 | loopStartPoint = startPoint = 0.0f; 33 | loopEndPoint = endPoint = (float)(sampleCount - 1); 34 | } 35 | 36 | void SampleBuffer::deinit() 37 | { 38 | if (samples) delete[] samples; 39 | samples = 0; 40 | } 41 | 42 | void SampleBuffer::setData(unsigned index, float data) 43 | { 44 | if ((int)index < channelCount * sampleCount) 45 | { 46 | samples[index] = data; 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Sampler/SampleBuffer.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | namespace DunneCore 5 | { 6 | 7 | // SampleBuffer represents an array of sample data, which can be addressed with a real-valued 8 | // "index" via linear interpolation. 9 | 10 | struct SampleBuffer 11 | { 12 | float *samples; 13 | float sampleRate; 14 | int channelCount; 15 | int sampleCount; 16 | float startPoint, endPoint; 17 | bool isLooping; 18 | float loopStartPoint, loopEndPoint; 19 | float noteFrequency; 20 | 21 | SampleBuffer(); 22 | ~SampleBuffer(); 23 | 24 | void init(float sampleRate, int channelCount, int sampleCount); 25 | void deinit(); 26 | 27 | void setData(unsigned index, float data); 28 | 29 | // Use double for the real-valued index, because oscillators will need the extra precision. 30 | inline float interp(double fIndex, float gain) 31 | { 32 | if (samples == 0 || sampleCount == 0) return 0.0f; 33 | 34 | int ri = int(fIndex); 35 | double f = fIndex - ri; 36 | int rj = ri + 1; 37 | 38 | float si = ri < sampleCount ? samples[ri] : 0.0f; 39 | float sj = rj < sampleCount ? samples[rj] : 0.0f; 40 | return (float)(gain * ((1.0 - f) * si + f * sj)); 41 | } 42 | 43 | inline void interp(double fIndex, float *leftOutput, float *rightOutput, float gain) 44 | { 45 | if (samples == 0 || sampleCount == 0) 46 | { 47 | *leftOutput = *rightOutput = 0.0f; 48 | return; 49 | } 50 | if (channelCount == 1) 51 | { 52 | *leftOutput = *rightOutput = interp(fIndex, gain); 53 | return; 54 | } 55 | 56 | int ri = int(fIndex); 57 | double f = fIndex - ri; 58 | int rj = ri + 1; 59 | 60 | float si = ri < sampleCount ? samples[ri] : 0.0f; 61 | float sj = rj < sampleCount ? samples[rj] : 0.0f; 62 | *leftOutput = (float)(gain * ((1.0 - f) * si + f * sj)); 63 | si = ri < sampleCount ? samples[sampleCount + ri] : 0.0f; 64 | sj = rj < sampleCount ? samples[sampleCount + rj] : 0.0f; 65 | *rightOutput = (float)(gain * ((1.0f - f) * si + f * sj)); 66 | } 67 | }; 68 | 69 | // KeyMappedSampleBuffer is a derived version with added MIDI note-number and velocity ranges 70 | struct KeyMappedSampleBuffer : public SampleBuffer 71 | { 72 | // Any of these members may be negative, meaning "no value assigned" 73 | int noteNumber; // closest MIDI note-number to this sample's frequency (noteFrequency) 74 | int minimumNoteNumber, maximumNoteNumber; // bounding note numbers for mapping 75 | int minimumVelocity, maximumVelocity; // min/max MIDI velocities for mapping 76 | }; 77 | 78 | } 79 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Sampler/SampleOscillator.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | #include 5 | 6 | #include "SampleBuffer.h" 7 | 8 | namespace DunneCore 9 | { 10 | 11 | struct SampleOscillator 12 | { 13 | bool isLooping; // true until note released 14 | double indexPoint; // use double so we don't lose precision when indexPoint becomes much larger than increment 15 | double increment; // 1.0 = play at original speed 16 | double multiplier; // multiplier applied to increment for pitch bend, vibrato 17 | 18 | void setPitchOffsetSemitones(double semitones) { multiplier = pow(2.0, semitones/12.0); } 19 | 20 | // return true if we run out of samples 21 | inline bool getSample(SampleBuffer *sampleBuffer, int sampleCount, float *output, float gain) 22 | { 23 | if (sampleBuffer == NULL || indexPoint > sampleBuffer->endPoint) return true; 24 | *output = sampleBuffer->interp(indexPoint, gain); 25 | 26 | indexPoint += multiplier * increment; 27 | if (sampleBuffer->isLooping && isLooping) 28 | { 29 | if (indexPoint > sampleBuffer->loopEndPoint) 30 | indexPoint = indexPoint - sampleBuffer->loopEndPoint + sampleBuffer->loopStartPoint; 31 | } 32 | return false; 33 | } 34 | 35 | // return true if we run out of samples 36 | inline bool getSamplePair(SampleBuffer *sampleBuffer, int sampleCount, float *leftOutput, float *rightOutput, float gain) 37 | { 38 | if (sampleBuffer == NULL || indexPoint > sampleBuffer->endPoint) return true; 39 | sampleBuffer->interp(indexPoint, leftOutput, rightOutput, gain); 40 | 41 | indexPoint += multiplier * increment; 42 | if (sampleBuffer->isLooping && isLooping) 43 | { 44 | if (indexPoint > sampleBuffer->loopEndPoint) 45 | indexPoint = indexPoint - sampleBuffer->loopEndPoint + sampleBuffer->loopStartPoint; 46 | } 47 | return false; 48 | } 49 | }; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Sampler/SamplerVoice.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | #include 5 | 6 | #include "SampleBuffer.h" 7 | #include "SampleOscillator.h" 8 | #include "ADSREnvelope.h" 9 | #include "AHDSHREnvelope.h" 10 | #include "FunctionTable.h" 11 | #include "ResonantLowPassFilter.h" 12 | #include "LinearRamper.h" 13 | 14 | // process samples in "chunks" this size 15 | #define CORESAMPLER_CHUNKSIZE 16 // should probably be set elsewhere - currently only use this for setting up lfo 16 | 17 | namespace DunneCore 18 | { 19 | 20 | struct SamplerVoice 21 | { 22 | float samplingRate; 23 | /// every voice has 1 oscillator 24 | SampleOscillator oscillator; 25 | 26 | /// a pointer to the sample buffer for that oscillator 27 | SampleBuffer *sampleBuffer; 28 | 29 | /// two filters (left/right) 30 | ResonantLowPassFilter leftFilter, rightFilter; 31 | AHDSHREnvelope ampEnvelope; 32 | ADSREnvelope filterEnvelope, pitchEnvelope; 33 | 34 | // per-voice vibrato LFO 35 | FunctionTableOscillator vibratoLFO; 36 | 37 | // restart phase of per-voice vibrato LFO 38 | bool restartVoiceLFO; 39 | 40 | /// common glide rate, seconds per octave 41 | float *glideSecPerOctave; 42 | 43 | /// MIDI note number, or -1 if not playing any note 44 | int noteNumber; 45 | 46 | /// (target) note frequency in Hz 47 | float noteFrequency; 48 | 49 | /// will reduce to zero during glide 50 | float glideSemitones; 51 | 52 | /// amount of semitone change via pitch envelope 53 | float pitchEnvelopeSemitones; 54 | 55 | /// amount of semitone change via voice lfo 56 | float voiceLFOSemitones; 57 | 58 | /// fraction 0.0 - 1.0, based on MIDI velocity 59 | float noteVolume; 60 | 61 | // temporary holding variables 62 | 63 | /// Previous note volume while damping note before restarting 64 | float tempNoteVolume; 65 | 66 | /// Next sample buffer to use at restart 67 | SampleBuffer *newSampleBuffer; 68 | 69 | /// product of global volume, note volume 70 | float tempGain; 71 | 72 | /// ramper to smooth subsampled output of adsrEnvelope 73 | LinearRamper volumeRamper; 74 | 75 | /// true if filter should be used 76 | bool isFilterEnabled; 77 | 78 | SamplerVoice() : noteNumber(-1) {} 79 | 80 | void init(double sampleRate); 81 | 82 | void updateAmpAdsrParameters() { ampEnvelope.updateParams(); } 83 | void updateFilterAdsrParameters() { filterEnvelope.updateParams(); } 84 | void updatePitchAdsrParameters() { pitchEnvelope.updateParams(); } 85 | 86 | void start(unsigned noteNumber, 87 | float sampleRate, 88 | float frequency, 89 | float volume, 90 | SampleBuffer *sampleBuffer); 91 | void restartNewNote(unsigned noteNumber, float sampleRate, float frequency, float volume, SampleBuffer *buffer); 92 | void restartNewNoteLegato(unsigned noteNumber, float sampleRate, float frequency); 93 | void restartSameNote(float volume, SampleBuffer *sampleBuffer); 94 | void release(bool loopThruRelease); 95 | void stop(); 96 | 97 | // return true if amp envelope is finished 98 | bool prepToGetSamples(int sampleCount, 99 | float masterVolume, 100 | float pitchOffset, 101 | float cutoffMultiple, 102 | float keyTracking, 103 | float cutoffEnvelopeStrength, 104 | float cutoffEnvelopeVelocityScaling, 105 | float resLinear, 106 | float pitchADSRSemitones, 107 | float voiceLFOFrequencyHz, 108 | float voiceLFODepthSemitones); 109 | 110 | bool getSamples(int sampleCount, float *leftOutput, float *rightOutput); 111 | 112 | private: 113 | bool hasStartedVoiceLFO; 114 | void restartVoiceLFOIfNeeded(); 115 | }; 116 | 117 | } 118 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Sampler/Wavpack/license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 1998 - 2017 David Bryant 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of Conifer Software nor the names of its contributors 13 | may be used to endorse or promote products derived from this software 14 | without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Sampler/Wavpack/open_legacy.c: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////// 2 | // **** WAVPACK **** // 3 | // Hybrid Lossless Wavefile Compressor // 4 | // Copyright (c) 1998 - 2016 David Bryant. // 5 | // All Rights Reserved. // 6 | // Distributed under the BSD Software License (see license.txt) // 7 | //////////////////////////////////////////////////////////////////////////// 8 | 9 | // open_legacy.c 10 | 11 | // This code provides an interface between the new reader callback mechanism that 12 | // WavPack uses internally and the old reader callback functions that did not 13 | // provide large file support. 14 | 15 | #include 16 | #include 17 | 18 | #include "wavpack_local.h" 19 | 20 | typedef struct { 21 | WavpackStreamReader *reader; 22 | void *id; 23 | } WavpackReaderTranslator; 24 | 25 | static int32_t trans_read_bytes (void *id, void *data, int32_t bcount) 26 | { 27 | WavpackReaderTranslator *trans = id; 28 | return trans->reader->read_bytes (trans->id, data, bcount); 29 | } 30 | 31 | static int32_t trans_write_bytes (void *id, void *data, int32_t bcount) 32 | { 33 | WavpackReaderTranslator *trans = id; 34 | return trans->reader->write_bytes (trans->id, data, bcount); 35 | } 36 | 37 | static int64_t trans_get_pos (void *id) 38 | { 39 | WavpackReaderTranslator *trans = id; 40 | return trans->reader->get_pos (trans->id); 41 | } 42 | 43 | static int trans_set_pos_abs (void *id, int64_t pos) 44 | { 45 | WavpackReaderTranslator *trans = id; 46 | return trans->reader->set_pos_abs (trans->id, (uint32_t) pos); 47 | } 48 | 49 | static int trans_set_pos_rel (void *id, int64_t delta, int mode) 50 | { 51 | WavpackReaderTranslator *trans = id; 52 | return trans->reader->set_pos_rel (trans->id, (int32_t) delta, mode); 53 | } 54 | 55 | static int trans_push_back_byte (void *id, int c) 56 | { 57 | WavpackReaderTranslator *trans = id; 58 | return trans->reader->push_back_byte (trans->id, c); 59 | } 60 | 61 | static int64_t trans_get_length (void *id) 62 | { 63 | WavpackReaderTranslator *trans = id; 64 | return trans->reader->get_length (trans->id); 65 | } 66 | 67 | static int trans_can_seek (void *id) 68 | { 69 | WavpackReaderTranslator *trans = id; 70 | return trans->reader->can_seek (trans->id); 71 | } 72 | 73 | static int trans_close_stream (void *id) 74 | { 75 | free (id); 76 | return 0; 77 | } 78 | 79 | static WavpackStreamReader64 trans_reader = { 80 | trans_read_bytes, trans_write_bytes, trans_get_pos, trans_set_pos_abs, trans_set_pos_rel, 81 | trans_push_back_byte, trans_get_length, trans_can_seek, NULL, trans_close_stream 82 | }; 83 | 84 | // This function is identical to WavpackOpenFileInput64() except that instead 85 | // of providing the new 64-bit reader callbacks, the old reader callbacks are 86 | // utilized and a translation layer is employed. It is provided as a compatibility 87 | // function for existing applications. To ensure that streaming applications using 88 | // this function continue to work, the OPEN_NO_CHECKSUM flag is forced on when 89 | // the OPEN_STREAMING flag is set. 90 | 91 | WavpackContext *WavpackOpenFileInputEx (WavpackStreamReader *reader, void *wv_id, void *wvc_id, char *error, int flags, int norm_offset) 92 | { 93 | WavpackReaderTranslator *trans_wv = NULL, *trans_wvc = NULL; 94 | 95 | // this prevents existing streaming applications from failing if they try to pass 96 | // in blocks that have been modified from the original (e.g., Matroska blocks) 97 | 98 | if (flags & OPEN_STREAMING) 99 | flags |= OPEN_NO_CHECKSUM; 100 | 101 | if (wv_id) { 102 | trans_wv = malloc (sizeof (WavpackReaderTranslator)); 103 | trans_wv->reader = reader; 104 | trans_wv->id = wv_id; 105 | } 106 | 107 | if (wvc_id) { 108 | trans_wvc = malloc (sizeof (WavpackReaderTranslator)); 109 | trans_wvc->reader = reader; 110 | trans_wvc->id = wvc_id; 111 | } 112 | 113 | return WavpackOpenFileInputEx64 (&trans_reader, trans_wv, trans_wvc, error, flags, norm_offset); 114 | } 115 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Sampler/Wavpack/wavpack_version.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////// 2 | // **** WAVPACK **** // 3 | // Hybrid Lossless Wavefile Compressor // 4 | // Copyright (c) 1998 - 2006 Conifer Software. // 5 | // All Rights Reserved. // 6 | // Distributed under the BSD Software License (see license.txt) // 7 | //////////////////////////////////////////////////////////////////////////// 8 | 9 | // wavpack_version.h 10 | 11 | #ifndef WAVPACK_VERSION_H 12 | #define WAVPACK_VERSION_H 13 | 14 | #define LIBWAVPACK_MAJOR 5 15 | #define LIBWAVPACK_MINOR 1 16 | #define LIBWAVPACK_MICRO 0 17 | #define LIBWAVPACK_VERSION_STRING "5.1.0" 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Synth/CoreSynth.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #ifdef __cplusplus 4 | #import 5 | 6 | #define SYNTH_CHUNKSIZE 16 // process samples in "chunks" this size 7 | 8 | namespace DunneCore 9 | { 10 | struct SynthVoice; 11 | } 12 | 13 | class CoreSynth 14 | { 15 | public: 16 | CoreSynth(); 17 | ~CoreSynth(); 18 | 19 | /// returns system error code, nonzero only if a problem occurs 20 | int init(double sampleRate); 21 | 22 | /// call this to un-load all samples and clear the keymap 23 | void deinit(); 24 | 25 | void playNote(unsigned noteNumber, unsigned velocity, float noteFrequency); 26 | void stopNote(unsigned noteNumber, bool immediate); 27 | void sustainPedal(bool down); 28 | 29 | void setAmpAttackDurationSeconds(float value); 30 | float getAmpAttackDurationSeconds(void); 31 | void setAmpDecayDurationSeconds(float value); 32 | float getAmpDecayDurationSeconds(void); 33 | void setAmpSustainFraction(float value); 34 | float getAmpSustainFraction(void); 35 | void setAmpReleaseDurationSeconds(float value); 36 | float getAmpReleaseDurationSeconds(void); 37 | 38 | void setFilterAttackDurationSeconds(float value); 39 | float getFilterAttackDurationSeconds(void); 40 | void setFilterDecayDurationSeconds(float value); 41 | float getFilterDecayDurationSeconds(void); 42 | void setFilterSustainFraction(float value); 43 | float getFilterSustainFraction(void); 44 | void setFilterReleaseDurationSeconds(float value); 45 | float getFilterReleaseDurationSeconds(void); 46 | 47 | void render(unsigned channelCount, unsigned sampleCount, float *outBuffers[]); 48 | 49 | protected: 50 | 51 | struct InternalData; 52 | std::unique_ptr data; 53 | 54 | /// "event" counter for voice-stealing (reallocation) 55 | unsigned eventCounter; 56 | 57 | // performance parameters 58 | float masterVolume, pitchOffset, vibratoDepth; 59 | 60 | // filter parameters 61 | 62 | /// multiple of note frequency - 1.0 means cutoff at fundamental 63 | float cutoffMultiple; 64 | 65 | /// how much filter EG adds on top of cutoffMultiple 66 | float cutoffEnvelopeStrength; 67 | 68 | /// resonance [-20 dB, +20 dB] becomes linear [10.0, 0.1] 69 | float linearResonance; 70 | 71 | void play(unsigned noteNumber, unsigned velocity, float noteFrequency); 72 | void stop(unsigned noteNumber, bool immediate); 73 | 74 | DunneCore::SynthVoice *voicePlayingNote(unsigned noteNumber); 75 | }; 76 | 77 | #endif 78 | 79 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Synth/DrawbarsOscillator.cpp: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #include "DrawbarsOscillator.h" 4 | #include 5 | #include 6 | 7 | namespace DunneCore 8 | { 9 | 10 | // 9 Hammond drawbars mapped to harmonic numbers, minus 1 for a 0-based array 11 | const int DrawbarsOscillator::drawBarMap[9] = { 0, 2, 1, 3, 5, 7, 9, 11, 15 }; 12 | 13 | void DrawbarsOscillator::init(double sampleRate, WaveStack *pStack) 14 | { 15 | sampleRateHz = sampleRate; 16 | pWaveStack = pStack; 17 | phaseDeltaMultiplier = 1.0f; 18 | for (int i=0; i < phaseCount; i++) 19 | { 20 | phase[i] = phaseDelta[i] = 0.0f; 21 | safetyLevels[i] = 0.0f; 22 | } 23 | level = safetyLevels; 24 | } 25 | 26 | void DrawbarsOscillator::setFrequency(float frequency) 27 | { 28 | // First, compute the normalized base frequency (all we need for one phase) 29 | double normalizedFrequency = double(frequency) / sampleRateHz; 30 | 31 | // set each phase's normalized frequency 32 | for (int i=0; i < phaseCount; i++) 33 | { 34 | octave[i] = 0; 35 | phaseDelta[i] = (i + 1) * (float)normalizedFrequency; 36 | int length = 1 << WaveStack::maxBits; 37 | while (phaseDelta[i] * length >= 1.0f) 38 | { 39 | octave[i]++; 40 | length >>= 1; 41 | } 42 | 43 | // frequency components beyond octave 9 must be suppressed 44 | if (octave[i] >= WaveStack::maxBits) 45 | { 46 | octave[i] = 0; 47 | level[i] = 0.0f; 48 | } 49 | } 50 | } 51 | 52 | float DrawbarsOscillator::getSample() 53 | { 54 | float sample = 0.0f; 55 | for (int i=0; i < phaseCount; i++) 56 | { 57 | if (level[i] == 0.0f) continue; 58 | sample += level[i] * pWaveStack->interp(octave[i], phase[i]); 59 | phase[i] += phaseDeltaMultiplier * phaseDelta[i]; 60 | if (phase[i] >= 1.0f) phase[i] -= 1.0f; 61 | } 62 | return sample; 63 | } 64 | 65 | void DrawbarsOscillator::getSamples(float *pLeft, float *pRight, float gain) 66 | { 67 | if (gain == 0.0f) return; 68 | float sample = gain * getSample(); 69 | *pLeft += sample; 70 | *pRight += sample; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Synth/MultiStageFilter.cpp: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | // MultiStageFilter implements a simple digital low-pass filter with dynamically 4 | // adjustable cutoff frequency and resonance. 5 | 6 | #include "MultiStageFilter.h" 7 | #include "FunctionTable.h" 8 | #include 9 | 10 | namespace DunneCore 11 | { 12 | MultiStageFilter::MultiStageFilter() 13 | { 14 | for (int i=0; i < maxStages; i++) stage[i].init(44100.0); 15 | stages = 1; 16 | } 17 | 18 | void MultiStageFilter::init(double sampleRateHz) 19 | { 20 | for (int i=0; i < maxStages; i++) stage[i].init(sampleRateHz); 21 | } 22 | 23 | void MultiStageFilter::setStages(int nStages) 24 | { 25 | if (nStages < 0) nStages = 0; 26 | if (nStages > maxStages) nStages = maxStages; 27 | stages = nStages; 28 | 29 | for (int i=1; i < stages; i++) 30 | stage[i].setParameters(stage[0].mLastCutoffHz, stage[0].mLastResLinear); 31 | } 32 | 33 | void MultiStageFilter::setParameters(double newCutoffHz, double newResLinear) 34 | { 35 | for (int i=0; i < stages; i++) stage[i].setParameters(newCutoffHz, newResLinear); 36 | } 37 | 38 | float MultiStageFilter::process(float sample) 39 | { 40 | for (int i=0; i < stages; i++) 41 | sample = stage[i].process(sample); 42 | return sample; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/DunneCore/Synth/WaveStack.cpp: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #include "WaveStack.h" 4 | #include "kiss_fftr.h" 5 | #include 6 | 7 | namespace DunneCore 8 | { 9 | 10 | WaveStack::WaveStack() 11 | { 12 | int length = 1 << maxBits; // length of level-0 data 13 | pData[0] = new float[2 * length]; // 2x is enough for all levels 14 | for (int i=1; i>= 1; 18 | } 19 | } 20 | 21 | WaveStack::~WaveStack() 22 | { 23 | delete[] pData[0]; 24 | } 25 | 26 | void WaveStack::initStack(const std::vector& waveData, int maxHarmonic) 27 | { 28 | // setup 29 | const int fftLength = 1 << maxBits; 30 | std::vector buf(fftLength); 31 | kiss_fftr_cfg fwd = kiss_fftr_alloc(fftLength, 0, 0, 0); 32 | kiss_fftr_cfg inv = kiss_fftr_alloc(fftLength, 1, 0, 0); 33 | 34 | assert(waveData.size() >= fftLength); 35 | 36 | // copy supplied wave data for octave 0 37 | for (int i=0; i < fftLength; i++) pData[0][i] = waveData[i]; 38 | 39 | // perform initial forward FFT to get spectrum 40 | kiss_fft_cpx spectrum[fftLength / 2 + 1]; 41 | kiss_fftr(fwd, pData[0], spectrum); 42 | 43 | float scaleFactor = 1.0f / (fftLength / 2); 44 | 45 | for (int octave = (maxHarmonic==512) ? 1 : 0; octave < maxBits; octave++) 46 | { 47 | // zero all harmonic coefficients above new Nyquist limit 48 | int maxHarm = 1 << (maxBits - octave - 1); 49 | if (maxHarm > maxHarmonic) maxHarm = maxHarmonic; 50 | for (int h=maxHarm; h <= fftLength/2; h++) 51 | { 52 | spectrum[h].r = 0.0f; 53 | spectrum[h].i = 0.0f; 54 | } 55 | 56 | // perform inverse FFT to get filtered waveform 57 | kiss_fftri(inv, spectrum, buf.data()); 58 | 59 | // resample filtered waveform 60 | int skip = 1 << octave; 61 | float *pOut = pData[octave]; 62 | for (int i=0; i < fftLength; i += skip) *pOut++ = scaleFactor * buf[i]; 63 | } 64 | 65 | // teardown 66 | kiss_fftr_free(inv); 67 | kiss_fftr_free(fwd); 68 | } 69 | 70 | float WaveStack::interp(int octave, float phase) 71 | { 72 | while (phase < 0) phase += 1.0; 73 | while (phase >= 1.0) phase -= 1.0f; 74 | 75 | int nTableSize = 1 << (maxBits - octave); 76 | float readIndex = phase * nTableSize; 77 | int ri = int(readIndex); 78 | float f = readIndex - ri; 79 | int rj = ri + 1; if (rj >= nTableSize) rj -= nTableSize; 80 | 81 | float *pWaveTable = pData[octave]; 82 | float si = pWaveTable[ri]; 83 | float sj = pWaveTable[rj]; 84 | return (float)((1.0 - f) * si + f * sj); 85 | } 86 | 87 | } 88 | 89 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/StereoDelayDSP.mm: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #include "DSPBase.h" 4 | #include "ParameterRamper.h" 5 | #include "DunneCore/Modulated Delay/StereoDelay.h" 6 | 7 | enum StereoDelayParameter : AUParameterAddress { 8 | StereoDelayParameterTime, 9 | StereoDelayParameterFeedback, 10 | StereoDelayParameterDryWetMix, 11 | StereoDelayParameterPingPong, 12 | }; 13 | 14 | class StereoDelayDSP : public DSPBase { 15 | private: 16 | DunneCore::StereoDelay delay; 17 | float timeUpperBound = 2.f; 18 | ParameterRamper timeRamp; 19 | ParameterRamper feedbackRamp; 20 | ParameterRamper dryWetMixRamp; 21 | 22 | public: 23 | StereoDelayDSP() : DSPBase(1, true) { 24 | parameters[StereoDelayParameterTime] = &timeRamp; 25 | parameters[StereoDelayParameterFeedback] = &feedbackRamp; 26 | parameters[StereoDelayParameterDryWetMix] = &dryWetMixRamp; 27 | } 28 | 29 | void setParameter(AUParameterAddress address, AUValue value, bool immediate) override { 30 | if (address == StereoDelayParameterPingPong) { 31 | delay.setPingPongMode(value > 0.5f); 32 | } 33 | else { 34 | DSPBase::setParameter(address, value, immediate); 35 | } 36 | } 37 | 38 | float getParameter(uint64_t address) override { 39 | if (address == StereoDelayParameterPingPong) { 40 | return delay.getPingPongMode() ? 1.0f : 0.0f; 41 | } 42 | else { 43 | return DSPBase::getParameter(address); 44 | } 45 | } 46 | 47 | void init(int channelCount, double sampleRate) override { 48 | DSPBase::init(channelCount, sampleRate); 49 | delay.init(sampleRate, timeUpperBound * 1000.0); 50 | } 51 | 52 | void deinit() override { 53 | DSPBase::deinit(); 54 | delay.deinit(); 55 | } 56 | 57 | void reset() override { 58 | DSPBase::reset(); 59 | delay.clear(); 60 | } 61 | 62 | #define CHUNKSIZE 8 // defines ramp interval 63 | 64 | void process(FrameRange range) override { 65 | const float *inBuffers[2]; 66 | float *outBuffers[2]; 67 | inBuffers[0] = (const float *)inputBufferLists[0]->mBuffers[0].mData + range.start; 68 | inBuffers[1] = (const float *)inputBufferLists[0]->mBuffers[1].mData + range.start; 69 | outBuffers[0] = (float *)outputBufferList->mBuffers[0].mData + range.start; 70 | outBuffers[1] = (float *)outputBufferList->mBuffers[1].mData + range.start; 71 | //unsigned inChannelCount = inputBufferLists[0]->mNumberBuffers; 72 | //unsigned outChannelCount = outputBufferList->mNumberBuffers; 73 | 74 | if (!isStarted) 75 | { 76 | // effect bypassed: just copy input to output 77 | memcpy(outBuffers[0], inBuffers[0], range.count * sizeof(float)); 78 | memcpy(outBuffers[1], inBuffers[1], range.count * sizeof(float)); 79 | return; 80 | } 81 | 82 | // process in chunks of maximum length CHUNKSIZE 83 | for (int frameIndex = 0; frameIndex < range.count; frameIndex += CHUNKSIZE) 84 | { 85 | int chunkSize = range.count - frameIndex; 86 | if (chunkSize > CHUNKSIZE) chunkSize = CHUNKSIZE; 87 | 88 | // ramp parameters 89 | timeRamp.stepBy(chunkSize); 90 | feedbackRamp.stepBy(chunkSize); 91 | dryWetMixRamp.stepBy(chunkSize); 92 | 93 | // apply changes 94 | delay.setDelayMs(1000.0 * timeRamp.get()); 95 | delay.setFeedback(feedbackRamp.get()); 96 | delay.setDryWetMix(dryWetMixRamp.get()); 97 | 98 | // process 99 | delay.render(chunkSize, inBuffers, outBuffers); 100 | 101 | // advance pointers 102 | inBuffers[0] += chunkSize; 103 | inBuffers[1] += chunkSize; 104 | outBuffers[0] += chunkSize; 105 | outBuffers[1] += chunkSize; 106 | } 107 | } 108 | }; 109 | 110 | AK_REGISTER_DSP(StereoDelayDSP, "sdly") 111 | AK_REGISTER_PARAMETER(StereoDelayParameterTime) 112 | AK_REGISTER_PARAMETER(StereoDelayParameterFeedback) 113 | AK_REGISTER_PARAMETER(StereoDelayParameterDryWetMix) 114 | AK_REGISTER_PARAMETER(StereoDelayParameterPingPong) 115 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/include/CDunneAudioKit.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #import "ModulatedDelay_Typedefs.h" 6 | #import "ModulatedDelayDSP.h" 7 | 8 | #import "SynthDSP.h" 9 | 10 | #import "Sampler_Typedefs.h" 11 | #import "SamplerDSP.h" 12 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/include/ModulatedDelayDSP.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #import 6 | #import "Interop.h" 7 | 8 | typedef NS_ENUM(AUParameterAddress, ModulatedDelayParameter) { 9 | ModulatedDelayParameterFrequency, 10 | ModulatedDelayParameterDepth, 11 | ModulatedDelayParameterFeedback, 12 | ModulatedDelayParameterDryWetMix, 13 | }; 14 | 15 | // constants 16 | extern const float kChorus_DefaultFrequency; 17 | extern const float kChorus_DefaultDepth; 18 | extern const float kChorus_DefaultFeedback; 19 | extern const float kChorus_DefaultDryWetMix; 20 | 21 | extern const float kChorus_MinFrequency; 22 | extern const float kChorus_MaxFrequency; 23 | extern const float kChorus_MinFeedback; 24 | extern const float kChorus_MaxFeedback; 25 | extern const float kChorus_MinDepth; 26 | extern const float kChorus_MaxDepth; 27 | extern const float kChorus_MinDryWetMix; 28 | extern const float kChorus_MaxDryWetMix; 29 | 30 | extern const float kFlanger_DefaultFrequency; 31 | extern const float kFlanger_MinFrequency; 32 | extern const float kFlanger_MaxFrequency; 33 | extern const float kFlanger_DefaultDepth; 34 | extern const float kFlanger_DefaultFeedback; 35 | extern const float kFlanger_DefaultDryWetMix; 36 | 37 | extern const float kFlanger_MinFrequency; 38 | extern const float kFlanger_MaxFrequency; 39 | extern const float kFlanger_MinFeedback; 40 | extern const float kFlanger_MaxFeedback; 41 | extern const float kFlanger_MinDepth; 42 | extern const float kFlanger_MaxDepth; 43 | extern const float kFlanger_MinDryWetMix; 44 | extern const float kFlanger_MaxDryWetMix; 45 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/include/ModulatedDelay_Typedefs.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | // This file is safe to include in either (Objective-)C or C++ contexts. 4 | 5 | #pragma once 6 | 7 | typedef enum { 8 | kChorus, 9 | kFlanger 10 | } ModulatedDelayType; 11 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/include/SamplerDSP.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #import 6 | #import "Interop.h" 7 | 8 | typedef NS_ENUM(AUParameterAddress, SamplerParameter) 9 | { 10 | // ramped parameters 11 | SamplerParameterMasterVolume, 12 | SamplerParameterPitchBend, 13 | SamplerParameterVibratoDepth, 14 | SamplerParameterVibratoFrequency, 15 | SamplerParameterVoiceVibratoDepth, 16 | SamplerParameterVoiceVibratoFrequency, 17 | SamplerParameterFilterCutoff, 18 | SamplerParameterFilterStrength, 19 | SamplerParameterFilterResonance, 20 | SamplerParameterGlideRate, 21 | 22 | // simple parameters 23 | SamplerParameterAttackDuration, 24 | SamplerParameterHoldDuration, 25 | SamplerParameterDecayDuration, 26 | SamplerParameterSustainLevel, 27 | SamplerParameterReleaseHoldDuration, 28 | SamplerParameterReleaseDuration, 29 | SamplerParameterFilterAttackDuration, 30 | SamplerParameterFilterDecayDuration, 31 | SamplerParameterFilterSustainLevel, 32 | SamplerParameterFilterReleaseDuration, 33 | SamplerParameterFilterEnable, 34 | SamplerParameterRestartVoiceLFO, 35 | SamplerParameterPitchAttackDuration, 36 | SamplerParameterPitchDecayDuration, 37 | SamplerParameterPitchSustainLevel, 38 | SamplerParameterPitchReleaseDuration, 39 | SamplerParameterPitchADSRSemitones, 40 | SamplerParameterLoopThruRelease, 41 | SamplerParameterMonophonic, 42 | SamplerParameterLegato, 43 | SamplerParameterKeyTrackingFraction, 44 | SamplerParameterFilterEnvelopeVelocityScaling, 45 | 46 | // ensure this is always last in the list, to simplify parameter addressing 47 | SamplerParameterRampDuration, 48 | }; 49 | 50 | #include "Sampler_Typedefs.h" 51 | 52 | CF_EXTERN_C_BEGIN 53 | typedef struct CoreSampler* CoreSamplerRef; 54 | 55 | DSPRef akSamplerCreateDSP(void); 56 | 57 | /// Takes ownership of the CoreSampler. 58 | void akSamplerUpdateCoreSampler(DSPRef pDSP, CoreSamplerRef pSampler); 59 | 60 | CoreSamplerRef akCoreSamplerCreate(void); 61 | void akCoreSamplerLoadData(CoreSamplerRef pSampler, SampleDataDescriptor *pSDD); 62 | void akCoreSamplerLoadCompressedFile(CoreSamplerRef pSampler, SampleFileDescriptor *pSFD); 63 | void akCoreSamplerSetNoteFrequency(CoreSamplerRef pSampler, int noteNumber, float noteFrequency); 64 | void akCoreSamplerBuildSimpleKeyMap(CoreSamplerRef pSampler); 65 | void akCoreSamplerBuildKeyMap(CoreSamplerRef pSampler); 66 | void akCoreSamplerSetLoopThruRelease(CoreSamplerRef pSampler, bool value); 67 | CF_EXTERN_C_END 68 | 69 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/include/Sampler_Typedefs.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | // This file is safe to include in either (Objective-)C or C++ contexts. 4 | 5 | #pragma once 6 | 7 | typedef struct 8 | { 9 | int noteNumber; 10 | float noteFrequency; 11 | 12 | int minimumNoteNumber, maximumNoteNumber; 13 | int minimumVelocity, maximumVelocity; 14 | 15 | bool isLooping; 16 | float loopStartPoint, loopEndPoint; 17 | float startPoint, endPoint; 18 | 19 | } SampleDescriptor; 20 | 21 | typedef struct 22 | { 23 | SampleDescriptor sampleDescriptor; 24 | 25 | float sampleRate; 26 | bool isInterleaved; 27 | int channelCount; 28 | int sampleCount; 29 | float *data; 30 | 31 | } SampleDataDescriptor; 32 | 33 | typedef struct 34 | { 35 | SampleDescriptor sampleDescriptor; 36 | 37 | const char *path; 38 | 39 | } SampleFileDescriptor; 40 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/CDunneAudioKit/include/SynthDSP.h: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #import 6 | #import "Interop.h" 7 | 8 | typedef NS_ENUM(AUParameterAddress, SynthParameter) 9 | { 10 | // ramped parameters 11 | 12 | SynthParameterMasterVolume, 13 | SynthParameterPitchBend, 14 | SynthParameterVibratoDepth, 15 | SynthParameterFilterCutoff, 16 | SynthParameterFilterStrength, 17 | SynthParameterFilterResonance, 18 | 19 | // simple parameters 20 | 21 | SynthParameterAttackDuration, 22 | SynthParameterDecayDuration, 23 | SynthParameterSustainLevel, 24 | SynthParameterReleaseDuration, 25 | SynthParameterFilterAttackDuration, 26 | SynthParameterFilterDecayDuration, 27 | SynthParameterFilterSustainLevel, 28 | SynthParameterFilterReleaseDuration, 29 | 30 | // ensure this is always last in the list, to simplify parameter addressing 31 | SynthParameterRampDuration, 32 | }; 33 | 34 | CF_EXTERN_C_BEGIN 35 | DSPRef akSynthCreateDSP(void); 36 | CF_EXTERN_C_END -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/DunneAudioKit/Chorus.swift: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | import AudioKit 4 | import AudioKitEX 5 | import AVFoundation 6 | import CDunneAudioKit 7 | 8 | /// Shane's Chorus 9 | /// 10 | public class Chorus: Node { 11 | let input: Node 12 | 13 | /// Connected nodes 14 | public var connections: [Node] { [input] } 15 | 16 | /// Underlying AVAudioNode 17 | public var avAudioNode = instantiate(effect: "chrs") 18 | 19 | // MARK: - Parameters 20 | 21 | /// Specification details for frequency 22 | public static let frequencyDef = NodeParameterDef( 23 | identifier: "frequency", 24 | name: "Frequency (Hz)", 25 | address: ModulatedDelayParameter.frequency.rawValue, 26 | defaultValue: kChorus_DefaultFrequency, 27 | range: kChorus_MinFrequency ... kChorus_MaxFrequency, 28 | unit: .hertz 29 | ) 30 | 31 | /// Modulation Frequency (Hz) 32 | @Parameter(frequencyDef) public var frequency: AUValue 33 | 34 | /// Specification details for depth 35 | public static let depthDef = NodeParameterDef( 36 | identifier: "depth", 37 | name: "Depth 0-1", 38 | address: ModulatedDelayParameter.depth.rawValue, 39 | defaultValue: kChorus_DefaultDepth, 40 | range: kChorus_MinDepth ... kChorus_MaxDepth, 41 | unit: .generic 42 | ) 43 | 44 | /// Modulation Depth (fraction) 45 | @Parameter(depthDef) public var depth: AUValue 46 | 47 | /// Specification details for feedback 48 | public static let feedbackDef = NodeParameterDef( 49 | identifier: "feedback", 50 | name: "Feedback 0-1", 51 | address: ModulatedDelayParameter.feedback.rawValue, 52 | defaultValue: kChorus_DefaultFeedback, 53 | range: kChorus_MinFeedback ... kChorus_MaxFeedback, 54 | unit: .generic 55 | ) 56 | 57 | /// Feedback (fraction) 58 | @Parameter(feedbackDef) public var feedback: AUValue 59 | 60 | /// Specification details for dry wet mix 61 | public static let dryWetMixDef = NodeParameterDef( 62 | identifier: "dryWetMix", 63 | name: "Dry Wet Mix 0-1", 64 | address: ModulatedDelayParameter.dryWetMix.rawValue, 65 | defaultValue: kChorus_DefaultDryWetMix, 66 | range: kChorus_MinDryWetMix ... kChorus_MaxDryWetMix, 67 | unit: .generic 68 | ) 69 | 70 | /// Dry Wet Mix (fraction) 71 | @Parameter(dryWetMixDef) public var dryWetMix: AUValue 72 | 73 | // MARK: - Initialization 74 | 75 | /// Initialize this chorus node 76 | /// 77 | /// - Parameters: 78 | /// - input: Node whose output will be processed 79 | /// - frequency: modulation frequency Hz 80 | /// - depth: depth of modulation (fraction) 81 | /// - feedback: feedback fraction 82 | /// - dryWetMix: fraction of wet signal in mix 83 | /// 84 | public init( 85 | _ input: Node, 86 | frequency: AUValue = frequencyDef.defaultValue, 87 | depth: AUValue = depthDef.defaultValue, 88 | feedback: AUValue = feedbackDef.defaultValue, 89 | dryWetMix: AUValue = dryWetMixDef.defaultValue 90 | ) { 91 | self.input = input 92 | 93 | setupParameters() 94 | 95 | self.frequency = frequency 96 | self.depth = depth 97 | self.feedback = feedback 98 | self.dryWetMix = dryWetMix 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/DunneAudioKit/DunneAudioKit.docc/DunneAudioKit.md: -------------------------------------------------------------------------------- 1 | # ``DunneAudioKit`` 2 | 3 | Chorus, Flanger, Sampler, Stereo Delay, and Synth for AudioKit, by Shane Dunne. 4 | 5 | ## Overview 6 | 7 | Github: [https://github.com/AudioKit/DunneAudioKit/](https://github.com/AudioKit/DunneAudioKit/) 8 | 9 | See the [AudioKit Cookbook](https://github.com/AudioKit/Cookbook/) for examples. 10 | 11 | There are two targets: 12 | 13 | | Name | Description | Language | 14 | |----------------|-------------|---------------| 15 | | DunneAudioKit | API Layer | Swift | 16 | | CDunneAudioKit | DSP Layer | Objective-C++ | 17 | 18 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/DunneAudioKit/DunneAudioKit.docc/ModulatedDelayEffects.md: -------------------------------------------------------------------------------- 1 | # Modulated Delay Effects 2 | 3 | Modulated-delay effects include **chorus**, **flanging**, and **vibrato**. These are all achieved by mixing an input signal with a delayed version of itself, and modulating the delay-time with a low-frequency oscillator (LFO). 4 | 5 | 6 | ## Overview 7 | 8 | ![ModDelay Circuit Diagram](ModDelay.svg) 9 | 10 | The LFO's output (typically a sinusoid or triangle wave) sets the instantaneous delay-time (i.e., the position of the output tap along the delay-line's length), which then varies cyclically between limits *min _delay* and *max_delay*, about a midpoint *mid_delay*. 11 | 12 | The balance between the "dry" (input) signal and the "wet" (delayed) signal is usually set based on a user-selected fraction *wf* applied as a scaling factor (*Wet Level* in the diagram) on the wet signal, with the corresponding *Dry Level* set as *1.0 − f*, so there is no net gain. 13 | 14 | A user-selected *Rate* parameter sets the LFO frequency, and a *Depth* parameter sets the difference between *max_delay* and *min_delay*. 15 | 16 | For some effects, a fraction (indicated as *Feedback* in the diagram) of the delayed signal is fed back into the delay line. 17 | 18 | ### Chorus 19 | For a chorus effect, the delay time varies about *mid_delay* which is fixed at the midpoint of the delay-line, whose total length is typically 20-40 ms. A user-selected *Depth* parameter controls how far *mid_delay* and *max_delay* deviate from this central value, from a minimum of zero (no change) to the point where *min_delay* becomes zero. No feedback is used. 20 | 21 | In the **Chorus** effect, *mid_delay* is fixed at 14 ms. Setting the *Depth* parameter to 1.0 (maximum) results in actual delay values of 14 ± 10 ms, i.e. *min_delay* = 4 ms, *max_delay* = 24 ms. The *Feedback* parameter will normally be left at the default value of 0, but can be set as high as 0.25. **Chorus** uses a sine-wave LFO, range 0.1 Hz to 10.0 Hz. 22 | 23 | ### Flanger 24 | For a flanging effect, *min_delay* is fixed at nearly zero, and the user-selected *Depth* parameter controls *max_delay*, from a minimum of zero to a maximum of about 7-10 ms. Typical settings include a 50/50 wet/dry mix, and at least some feedback. 25 | 26 | The *Feedback* parameter is a signed fraction in the range -1.0 to + 1.0, where negative values indicate that the signal is inverted before begin fed back. This is important because when the delay time gets very close to zero, the low-frequency parts of the wet and dry signals overlap almost perfectly, so positive feedback can result in a sudden increase in volume. Using negative feedback instead yields a momentary reduction in volume, which is less noticeable. 27 | 28 | In the **Flanger** effect, setting the *Depth* parameter to 1.0 results in *max_delay* = 10 ms. *Feedback* may vary from -0.95 to +0.95. LFO is a triangle wave, 0.1 - 10 Hz. 29 | 30 | ### Vibrato 31 | With a modulated-delay effect in either chorus (*mid_delay* fixed at midpoint of delay-line) or flanger (*min_delay* fixed at near-zero) configuration, setting the *Wet Level* to 100% will yield a vibrato effect. This is due to the effect of the LFO modulating the delay time. When the delay-time is decreasing, the short fragment of sound in the delay-line is effectively resampled at a rate faster than its original sample rate, so the pitch rises. When the delay-time is increasing, the sound is resampled at a rate slower than its original sampling rate, so the pitch drops. 32 | 33 | ### Stereo Chorus and Flanging 34 | Both **Chorus** and **Flanger** are actually stereo effects. The DSP structure shown in the above diagram is duplicated for each of the Left and Right channels. The two LFOs run in lock-step at the same frequency (set by the *Rate* parameter) and amplitude (set by *Depth*), but offset in phase by 90 degrees. This technique, called *quadrature modulation*, is quite common in stereo modulation effects. 35 | 36 | ### For more information 37 | Modulated-delay effects are described in detail in Chapter 10 of [Designing Audio Effect Plug-Ins in C++](https://www.amazon.com/Designing-Audio-Effect-Plug-Ins-Processing/dp/0240825152) by Will Pirkle. 38 | 39 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/DunneAudioKit/DunneAudioKit.docc/ModulationEffects.md: -------------------------------------------------------------------------------- 1 | # Modulation Effects 2 | 3 | Chorus and Flanger 4 | 5 | ## Overview 6 | 7 | As described by Will Pirkle in his excellent book "Designing Audio Effect Plug-Ins in C++", chorus and flanger are modulated-delay effects. A short delay line is used (up to 10 ms for flanger, or 24 ms for chorus), and the delay time is modulated using a low-frequency oscillator (LFO). Feedback is always used for flanging, typically not for chorus. There is also a wet/dry mix setting, which will normally be 50/50 for flanging. Setting the mix to 100% wet (for either effect) produces vibrato. 8 | 9 | The code here is all original; none of Pirkle's code has been used. 10 | 11 | These are both stereo effects (stereo-in, stereo-out). The modulator LFO signals used for the left and right channels are the same frequency, but differ in phase by 90 degrees. 12 | 13 | These effects all take up to four parameters as follows: 14 | 15 | ### `frequency` 16 | Frequency of the modulating LFO, Hz. Acceptable range 0.1 to 10.0 Hz. For chorus and flanger, you will usually use rates less than 2 Hz. For vibrato, 5 Hz sounds good. 17 | 18 | ### `depth` 19 | Depth of modulation, expressed as a fraction 0.0 - 1.0. The higher the number, the more pronounced the effect. 20 | 21 | ### `feedback` 22 | Another fractional scale factor which is the amount of delayed signal which is "fed back" into the input of the delay block. For flanger (which requires at least some feedback), the acceptable range is -0.95 - +0.95; negative values mean the feedback signal is inverted. For chorus (where feedback is usually not used), the acceptable range is 0.0 - 0.25. In both cases, numbers further from zero yield more pronounced effect. 23 | 24 | ### `dryWetMix` 25 | The effects' output is a mix of the input ("dry") signal and the delayed ("wet") signal. The *dryWetMix* value is the scale factor (always a fraction 0.0 - 1.0) for the wet signal. The scale factor for the dry signal is computed internally as 1.0 - *dryWetMix*, so they always sum to unity. The higher the *dryWetMix* value, the more pronounced the effect. 26 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/DunneAudioKit/DunneAudioKit.docc/Sampler Audio Unit and Node.md: -------------------------------------------------------------------------------- 1 | # Sampler Audio Unit and Node 2 | 3 | Implementation of the **Sampler** Swift class, which is built on top of the similarly-named C++ Core class. 4 | 5 | There are *four distinct layers of code* here, as follows. 6 | 7 | ## SamplerDSP 8 | **SamplerDSP** is a C++ class which inherits from the Core *Sampler* as well as **DSPBase**, one of the primary AudioKit base classes for DSP code. 9 | 10 | The implementation resides in a `.mm` file rather than a `.cpp` file, because it also contains several Objective-C accessor functions which facilitate bridging between Swift code above and C++ code below. 11 | 12 | Hence there are *two separate code layers* here: the **SamplerDSP** class below and the Objective-C accessor functions above. 13 | 14 | ## SamplerAudioUnit 15 | The Swift **SamplerAudioUnit** class is the next level above the **Sampler** class and its Objective-C accessor functions. It wraps the DSP code within a *version-3 Audio Unit* object which exposes several dynamic *parameters* and can be connected to other Audio Unit objects to process the audio stream it generates. 16 | 17 | ## Sampler and extensions 18 | The highest level **Sampler** Swift class wraps the Audio Unit code within an AudioKit **Node** object, which facilitates easy interconnection with other AudioKit nodes, and exposes the underlying Audio Unit parameters as Swift *properties*. 19 | 20 | The **Sampler** class also includes utility functions to assist with loading sample data into the underlying C++ `Sampler` object (using **AVAudioFile**). 21 | 22 | Additional utility functions are implemented in separate files as Swift *extensions*. `Sampler+SFZ.swift` adds a rudimentary facility to load whole sets of samples by interpreting a SFZ file; read more about those in . 23 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/DunneAudioKit/DunneAudioKit.docc/Sampler.md: -------------------------------------------------------------------------------- 1 | # ``DunneAudioKit/Sampler`` 2 | @Metadata { 3 | @DocumentationExtension(mergeBehavior:append) 4 | } 5 | 6 | **Sampler** is a polyphonic sample-playback engine built from scratch in C++. It is 64-voice polyphonic and features a per-voice, stereo low-pass filter with resonance and ADSR envelopes for both amplitude and filter cutoff. Samples must be loaded into memory and remain resident there; it does not do streaming. It reads standard audio files via **AVAudioFile**, as well as a more efficient Wavpack compressed format. 7 | 8 | ### Sampler vs AppleSampler 9 | 10 | **AppleSampler** and its companion class **MIDISampler** are wrappers for Apple's *AUSampler* Audio Unit, an exceptionally powerful polyphonic, multi-timbral sampler instrument which is built-in to both macOS and iOS. Unfortunately, *AUSampler* is far from perfect and not properly documented. This **Sampler** is an attempt to provide an open-source alternative. 11 | 12 | **Sampler** is nowhere near as powerful as *AUSampler*. If your app depends on **AppleSampler** or the **MIDISampler** wrapper class, you should continue to use it. 13 | 14 | ### Loading samples 15 | **Sampler** provides three distinct mechanisms for loading samples: 16 | 17 | 1. `loadRawSampleData()` allows use of sample data already in memory, e.g. data generated programmatically or read using custom file-reading code. 18 | 2. `loadSFZ()` loads entire sets of samples by interpreting a simplistic subset of the "SFZ" soundfont file format. 19 | 3. `loadRawSampleData()` and `loadCompressedSampleFile()` take a "descriptor" argument, whose many member variables define details like the sample's natural MIDI note-number and pitch (frequency), plus details about loop start and end points, if used. See more in . 20 | 21 | For `loadUsingSfzFile()` allows all this "metadata" to be encoded in a SFZ file, using a simple plain-text format which is easy to understand and edit manually. More information on . 22 | 23 | The mapping of MIDI (note number, velocity) pairs to samples is done using some internal lookup tables, which can be populated in one of two ways: 24 | 25 | 1. When your metadata includes min/max note-number and velocity values for all samples, call `buildKeyMap()` to build a full key/velocity map. 26 | 2. If you only have note-numbers for each sample, call `buildSimpleKeyMap()` to map each MIDI note-number (at any velocity) to the *nearest available* sample. 27 | 28 | **Important:** Before loading a new group of samples, you must call `unloadAllSamples()`. Otherwise, the new samples will be loaded *in addition* to the already-loaded ones. This wastes memory and worse, newly-loaded samples will usually not sound at all, because the sampler simply plays the first matching sample it finds. 29 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/DunneAudioKit/Flanger.swift: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | import AudioKit 4 | import AudioKitEX 5 | import AVFoundation 6 | import CDunneAudioKit 7 | 8 | /// Stereo Flanger 9 | /// 10 | public class Flanger: Node { 11 | let input: Node 12 | 13 | /// Connected nodes 14 | public var connections: [Node] { [input] } 15 | 16 | /// Underlying AVAudioNode 17 | public var avAudioNode = instantiate(effect: "flgr") 18 | 19 | // MARK: - Parameters 20 | 21 | /// Specification for the frequency 22 | public static let frequencyDef = NodeParameterDef( 23 | identifier: "frequency", 24 | name: "Frequency (Hz)", 25 | address: ModulatedDelayParameter.frequency.rawValue, 26 | defaultValue: kFlanger_DefaultFrequency, 27 | range: kFlanger_MinFrequency ... kFlanger_MaxFrequency, 28 | unit: .hertz 29 | ) 30 | 31 | /// Modulation Frequency (Hz) 32 | @Parameter(frequencyDef) public var frequency: AUValue 33 | 34 | /// Specification for the depth 35 | public static let depthDef = NodeParameterDef( 36 | identifier: "depth", 37 | name: "Depth 0-1", 38 | address: ModulatedDelayParameter.depth.rawValue, 39 | defaultValue: kFlanger_DefaultDepth, 40 | range: kFlanger_MinDepth ... kFlanger_MaxDepth, 41 | unit: .generic 42 | ) 43 | 44 | /// Modulation Depth (fraction) 45 | @Parameter(depthDef) public var depth: AUValue 46 | 47 | /// Specification for the feedback 48 | public static let feedbackDef = NodeParameterDef( 49 | identifier: "feedback", 50 | name: "Feedback 0-1", 51 | address: ModulatedDelayParameter.feedback.rawValue, 52 | defaultValue: kFlanger_DefaultFeedback, 53 | range: kFlanger_MinFeedback ... kFlanger_MaxFeedback, 54 | unit: .generic 55 | ) 56 | 57 | /// Feedback (fraction) 58 | @Parameter(feedbackDef) public var feedback: AUValue 59 | 60 | /// Specification for the dry wet mix 61 | public static let dryWetMixDef = NodeParameterDef( 62 | identifier: "dryWetMix", 63 | name: "Dry Wet Mix 0-1", 64 | address: ModulatedDelayParameter.dryWetMix.rawValue, 65 | defaultValue: kFlanger_DefaultDryWetMix, 66 | range: kFlanger_MinDryWetMix ... kFlanger_MaxDryWetMix, 67 | unit: .generic 68 | ) 69 | 70 | /// Dry Wet Mix (fraction) 71 | @Parameter(dryWetMixDef) public var dryWetMix: AUValue 72 | 73 | // MARK: - Initialization 74 | 75 | /// Initialize this flanger node 76 | /// 77 | /// - Parameters: 78 | /// - input: Node whose output will be processed 79 | /// - frequency: modulation frequency Hz 80 | /// - depth: depth of modulation (fraction) 81 | /// - feedback: feedback fraction 82 | /// - dryWetMix: fraction of wet signal in mix - traditionally 50%, avoid changing this value 83 | /// 84 | public init( 85 | _ input: Node, 86 | frequency: AUValue = frequencyDef.defaultValue, 87 | depth: AUValue = depthDef.defaultValue, 88 | feedback: AUValue = feedbackDef.defaultValue, 89 | dryWetMix: AUValue = dryWetMixDef.defaultValue 90 | ) { 91 | self.input = input 92 | 93 | setupParameters() 94 | 95 | self.frequency = frequency 96 | self.depth = depth 97 | self.feedback = feedback 98 | self.dryWetMix = dryWetMix 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/DunneAudioKit/StereoDelay.swift: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | import AudioKit 4 | import AudioKitEX 5 | import AVFoundation 6 | import CDunneAudioKit 7 | 8 | /// Stereo delay-line with stereo (linked dual mono) and ping-pong modes 9 | /// 10 | /// TODO: This node needs tests 11 | public class StereoDelay: Node { 12 | let input: Node 13 | 14 | /// Connected nodes 15 | public var connections: [Node] { [input] } 16 | 17 | /// Underlying AVAudioNode 18 | public var avAudioNode = instantiate(effect: "sdly") 19 | 20 | // MARK: - Parameters 21 | 22 | /// Specification details for time 23 | public static let timeDef = NodeParameterDef( 24 | identifier: "time", 25 | name: "Delay time (Seconds)", 26 | address: akGetParameterAddress("StereoDelayParameterTime"), 27 | defaultValue: 0, 28 | range: 0 ... 2.0, 29 | unit: .seconds 30 | ) 31 | 32 | /// Delay time (in seconds) This value must not exceed the maximum delay time. 33 | @Parameter(timeDef) public var time: AUValue 34 | 35 | /// Specification details for feedback 36 | public static let feedbackDef = NodeParameterDef( 37 | identifier: "feedback", 38 | name: "Feedback (%)", 39 | address: akGetParameterAddress("StereoDelayParameterFeedback"), 40 | defaultValue: 0, 41 | range: 0.0 ... 1.0, 42 | unit: .generic 43 | ) 44 | 45 | /// Feedback amount. Should be a value between 0-1. 46 | @Parameter(feedbackDef) public var feedback: AUValue 47 | 48 | /// Specification details for dry wet mix 49 | public static let dryWetMixDef = NodeParameterDef( 50 | identifier: "dryWetMix", 51 | name: "Dry-Wet Mix", 52 | address: akGetParameterAddress("StereoDelayParameterDryWetMix"), 53 | defaultValue: 0.5, 54 | range: 0.0 ... 1.0, 55 | unit: .generic 56 | ) 57 | 58 | /// Dry/wet mix. Should be a value between 0-1. 59 | @Parameter(dryWetMixDef) public var dryWetMix: AUValue 60 | 61 | /// Specification details for ping pong mode 62 | public static let pingPongDef = NodeParameterDef( 63 | identifier: "pingPong", 64 | name: "Ping-Pong Mode", 65 | address: akGetParameterAddress("StereoDelayParameterPingPong"), 66 | defaultValue: 0, 67 | range: 0.0 ... 1.0, 68 | unit: .boolean, 69 | flags: [.flag_IsReadable, .flag_IsWritable] 70 | ) 71 | 72 | /// Ping-pong mode: true or false (stereo mode) 73 | @Parameter(pingPongDef) public var pingPong: AUValue 74 | 75 | // MARK: - Initialization 76 | 77 | /// Initialize this delay node 78 | /// 79 | /// - Parameters: 80 | /// - input: Input node to process 81 | /// - time: Delay time (in seconds) This value must not exceed the maximum delay time. 82 | /// - feedback: Feedback amount. Should be a value between 0-1. 83 | /// - dryWetMix: Dry/wet mix. Should be a value between 0-1. 84 | /// - pingPong: true for ping-pong mode, false for stereo mode. 85 | /// - maximumDelayTime: The maximum delay time, in seconds. 86 | /// 87 | public init( 88 | _ input: Node, 89 | time: AUValue = timeDef.defaultValue, 90 | feedback: AUValue = feedbackDef.defaultValue, 91 | dryWetMix: AUValue = dryWetMixDef.defaultValue, 92 | pingPong: Bool = (dryWetMixDef.defaultValue == 1.0), 93 | maximumDelayTime _: AUValue = 2.0 94 | ) { 95 | self.input = input 96 | 97 | setupParameters() 98 | 99 | self.time = time 100 | self.feedback = feedback 101 | self.dryWetMix = dryWetMix 102 | self.pingPong = pingPong ? 1.0 : 0.0 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Sources/DunneAudioKit/TransientShaper.swift: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | import AudioKit 4 | import AudioKitEX 5 | import AVFoundation 6 | import CDunneAudioKit 7 | 8 | /// Transient shaper 9 | public class TransientShaper: Node { 10 | let input: Node 11 | 12 | /// Connected nodes 13 | public var connections: [Node] { [input] } 14 | 15 | /// Underlying AVAudioNode 16 | public var avAudioNode = instantiate(effect: "trsh") 17 | 18 | // MARK: - Parameters 19 | 20 | /// Specification details for input amount 21 | public static let inputAmountDef = NodeParameterDef( 22 | identifier: "inputAmount", 23 | name: "Input", 24 | address: akGetParameterAddress("TransientShaperParameterInputAmount"), 25 | defaultValue: 0.0, 26 | range: -60.0 ... 30.0, 27 | unit: .decibels 28 | ) 29 | 30 | /// Input Amount 31 | @Parameter(inputAmountDef) public var inputAmount: AUValue 32 | 33 | /// Specification details for attack amount 34 | public static let attackAmountDef = NodeParameterDef( 35 | identifier: "attackAmount", 36 | name: "Attack", 37 | address: akGetParameterAddress("TransientShaperParameterAttackAmount"), 38 | defaultValue: 0.0, 39 | range: -40.0 ... 40.0, 40 | unit: .decibels 41 | ) 42 | 43 | /// Attack Amount 44 | @Parameter(attackAmountDef) public var attackAmount: AUValue 45 | 46 | /// Specification details for release amount 47 | public static let releaseAmountDef = NodeParameterDef( 48 | identifier: "releaseAmount", 49 | name: "Release", 50 | address: akGetParameterAddress("TransientShaperParameterReleaseAmount"), 51 | defaultValue: 0.0, 52 | range: -40.0 ... 40.0, 53 | unit: .decibels 54 | ) 55 | 56 | /// Release Amount 57 | @Parameter(releaseAmountDef) public var releaseAmount: AUValue 58 | 59 | /// Specification details for output amount 60 | public static let outputAmountDef = NodeParameterDef( 61 | identifier: "outputAmount", 62 | name: "Output", 63 | address: akGetParameterAddress("TransientShaperParameterOutputAmount"), 64 | defaultValue: 0.0, 65 | range: -60.0 ... 30.0, 66 | unit: .decibels 67 | ) 68 | 69 | /// Output Amount 70 | @Parameter(outputAmountDef) public var outputAmount: AUValue 71 | 72 | // MARK: - Initialization 73 | 74 | /// Initialize this delay node 75 | /// 76 | /// - Parameters: 77 | /// - input: Input node to process 78 | /// - attackAmount 79 | /// - releaseAmount 80 | /// - output 81 | public init( 82 | _ input: Node, 83 | inputAmount: AUValue = inputAmountDef.defaultValue, 84 | attackAmount: AUValue = attackAmountDef.defaultValue, 85 | releaseAmount: AUValue = releaseAmountDef.defaultValue, 86 | outputAmount: AUValue = outputAmountDef.defaultValue 87 | ) { 88 | self.input = input 89 | 90 | setupParameters() 91 | 92 | self.inputAmount = inputAmount 93 | self.attackAmount = attackAmount 94 | self.releaseAmount = releaseAmount 95 | self.outputAmount = outputAmount 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Tests/DunneAudioKitTests/GenericNodeTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | import AudioKit 4 | import AVFoundation 5 | @testable import DunneAudioKit 6 | import XCTest 7 | 8 | class GenericNodeTests: XCTestCase { 9 | func nodeParameterTest(md5: String, factory: (Node) -> Node, m1MD5: String = "", audition: Bool = false) { 10 | let url = Bundle.module.url(forResource: "12345", withExtension: "wav", subdirectory: "TestResources")! 11 | let player = AudioPlayer(url: url)! 12 | let node = factory(player) 13 | 14 | let duration = node.parameters.count + 1 15 | 16 | let engine = AudioEngine() 17 | var bigBuffer: AVAudioPCMBuffer? 18 | 19 | engine.output = node 20 | 21 | /// Do the default parameters first 22 | if bigBuffer == nil { 23 | let audio = engine.startTest(totalDuration: 1.0) 24 | player.play() 25 | player.isLooping = true 26 | audio.append(engine.render(duration: 1.0)) 27 | bigBuffer = AVAudioPCMBuffer(pcmFormat: audio.format, frameCapacity: audio.frameLength * UInt32(duration)) 28 | 29 | bigBuffer?.append(audio) 30 | } 31 | 32 | for i in 0 ..< node.parameters.count { 33 | let node = factory(player) 34 | engine.output = node 35 | 36 | let param = node.parameters[i] 37 | 38 | node.start() 39 | 40 | param.value = param.def.range.lowerBound 41 | param.ramp(to: param.def.range.upperBound, duration: 1) 42 | 43 | let audio = engine.startTest(totalDuration: 1.0) 44 | audio.append(engine.render(duration: 1.0)) 45 | 46 | bigBuffer?.append(audio) 47 | } 48 | 49 | XCTAssertFalse(bigBuffer!.isSilent) 50 | 51 | if audition { 52 | bigBuffer!.audition() 53 | } 54 | XCTAssertTrue([md5, m1MD5].contains(bigBuffer!.md5), "\(node)\nFAILEDMD5 \(bigBuffer!.md5)") 55 | } 56 | 57 | func testEffects() { 58 | nodeParameterTest(md5: "b09c41cdef96fb4cfe89c34c5262e9d2", factory: { input in Flanger(input) }) 59 | nodeParameterTest(md5: "7006860de2e4a726fd53c7f583f44c75", factory: { input in StereoDelay(input) }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Tests/DunneAudioKitTests/SynthTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | #if !os(tvOS) 3 | 4 | import AudioKit 5 | import XCTest 6 | import DunneAudioKit 7 | 8 | class SynthTests: XCTestCase { 9 | 10 | func testChord() { 11 | let engine = AudioEngine() 12 | let synth = Synth() 13 | engine.output = synth 14 | let audio = engine.startTest(totalDuration: 1.0) 15 | synth.play(noteNumber: 64, velocity: 120) 16 | synth.play(noteNumber: 67, velocity: 120) 17 | synth.play(noteNumber: 71, velocity: 120) 18 | audio.append(engine.render(duration: 1.0)) 19 | testMD5(audio) 20 | } 21 | 22 | func testMonophonicPlayback() { 23 | let engine = AudioEngine() 24 | let synth = Synth() 25 | engine.output = synth 26 | let audio = engine.startTest(totalDuration: 2.0) 27 | synth.play(noteNumber: 64, velocity: 120) 28 | audio.append(engine.render(duration: 1.0)) 29 | synth.stop(noteNumber: 64) 30 | synth.play(noteNumber: 65, velocity: 120) 31 | audio.append(engine.render(duration: 1.0)) 32 | testMD5(audio) 33 | } 34 | 35 | func testParameterInitialization() { 36 | let engine = AudioEngine() 37 | let synth = Synth(masterVolume: 0.9, 38 | pitchBend: 0.1, 39 | vibratoDepth: 0.2, 40 | filterCutoff: 40, 41 | filterStrength: 19, 42 | filterResonance: 0.11, 43 | attackDuration: 0.05, 44 | decayDuration: 0.2, 45 | sustainLevel: 0.5, 46 | releaseDuration: 0.5, 47 | filterEnable: true, 48 | filterAttackDuration: 0.13, 49 | filterDecayDuration: 0.19, 50 | filterSustainLevel: 0.4, 51 | filterReleaseDuration: 0.43) 52 | engine.output = synth 53 | let audio = engine.startTest(totalDuration: 2.0) 54 | synth.play(noteNumber: 64, velocity: 120) 55 | audio.append(engine.render(duration: 1.0)) 56 | synth.stop(noteNumber: 64) 57 | audio.append(engine.render(duration: 1.0)) 58 | testMD5(audio) 59 | } 60 | 61 | } 62 | #endif 63 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Tests/DunneAudioKitTests/TestResources/12345.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NickCulbertson/AudioKit-Experiments/c2d1a94322f9cd5aa646f9b7873854ed2e3ae32d/AudioKit-Experiments/DunneAudioKit/Tests/DunneAudioKitTests/TestResources/12345.wav -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Tests/DunneAudioKitTests/TransientShaperTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright AudioKit. All Rights Reserved. 2 | 3 | import AudioKit 4 | import DunneAudioKit 5 | import XCTest 6 | 7 | class TransientShaperTests: XCTestCase { 8 | 9 | func testDefault() { 10 | let engine = AudioEngine() 11 | let url = Bundle.module.url(forResource: "12345", withExtension: "wav", subdirectory: "TestResources")! 12 | let player = AudioPlayer(url: url)! 13 | engine.output = TransientShaper(player) 14 | let audio = engine.startTest(totalDuration: 1.0) 15 | player.play() 16 | audio.append(engine.render(duration: 1.0)) 17 | testMD5(audio) 18 | } 19 | 20 | func testplayerAmount() { 21 | let engine = AudioEngine() 22 | let url = Bundle.module.url(forResource: "12345", withExtension: "wav", subdirectory: "TestResources")! 23 | let player = AudioPlayer(url: url)! 24 | engine.output = TransientShaper(player, inputAmount: -20.0) 25 | let audio = engine.startTest(totalDuration: 1.0) 26 | player.play() 27 | audio.append(engine.render(duration: 1.0)) 28 | testMD5(audio) 29 | } 30 | 31 | func testOutputAmount() { 32 | let engine = AudioEngine() 33 | let url = Bundle.module.url(forResource: "12345", withExtension: "wav", subdirectory: "TestResources")! 34 | let player = AudioPlayer(url: url)! 35 | engine.output = TransientShaper(player, outputAmount: -20.0) 36 | let audio = engine.startTest(totalDuration: 1.0) 37 | player.play() 38 | audio.append(engine.render(duration: 1.0)) 39 | testMD5(audio) 40 | } 41 | 42 | /* Intermittent Tests 43 | 44 | func testAttackAmount() { 45 | let engine = AudioEngine() 46 | let url = Bundle.module.url(forResource: "12345", withExtension: "wav", subdirectory: "TestResources")! 47 | let player = AudioPlayer(url: url)! 48 | engine.output = TransientShaper(player, attackAmount: 1.0) 49 | let audio = engine.startTest(totalDuration: 1.0) 50 | player.play() 51 | audio.append(engine.render(duration: 1.0)) 52 | testMD5(audio) 53 | } 54 | 55 | func testReleaseAmount() { 56 | let engine = AudioEngine() 57 | let url = Bundle.module.url(forResource: "12345", withExtension: "wav", subdirectory: "TestResources")! 58 | let player = AudioPlayer(url: url)! 59 | engine.output = TransientShaper(player, releaseAmount: 1.0) 60 | let audio = engine.startTest(totalDuration: 1.0) 61 | player.play() 62 | audio.append(engine.render(duration: 1.0)) 63 | testMD5(audio) 64 | } 65 | */ 66 | 67 | /* This produces different MD5s on local machines vs CI 68 | func testParameters() { 69 | let engine = AudioEngine() 70 | let url = Bundle.module.url(forResource: "12345", withExtension: "wav", subdirectory: "TestResources")! 71 | let player = AudioPlayer(url: url)! 72 | engine.output = TransientShaper(player, 73 | playerAmount: -1.0, 74 | attackAmount: -3.0, 75 | releaseAmount: 1.0, 76 | outputAmount: 0.0) 77 | player.play() 78 | let audio = engine.startTest(totalDuration: 1.0) 79 | audio.append(engine.render(duration: 1.0)) 80 | testMD5(audio) 81 | } 82 | */ 83 | 84 | } 85 | -------------------------------------------------------------------------------- /AudioKit-Experiments/DunneAudioKit/Tests/DunneAudioKitTests/ValidatedMD5s.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | import XCTest 3 | 4 | extension XCTestCase { 5 | func testMD5(_ buffer: AVAudioPCMBuffer) { 6 | let localMD5 = buffer.md5 7 | let name = description 8 | XCTAssertFalse(buffer.isSilent) 9 | XCTAssert(validatedMD5s[name] == buffer.md5, "\nFAILEDMD5 \"\(name)\": \"\(localMD5)\",") 10 | } 11 | } 12 | 13 | let validatedMD5s: [String: String] = [ 14 | "-[SamplerTests testSampler]": "8739229f6bc52fa5db3cc2afe85ee580", 15 | "-[SamplerTests testSamplerAttackVolumeEnvelope]": "bf00177ac48148fa4f780e5e364e84e2", 16 | "-[SynthTests testChord]": "670c95beba121ff85150eb12497f3652", 17 | "-[SynthTests testMonophonicPlayback]": "625554cfe7cc840083df9931d47490a6", 18 | "-[SynthTests testParameterInitialization]": "7bd35b742ceff0ba77238d7da2ef046d", 19 | "-[TransientShaperTests testAttackAmount]": "481068b77fc73b349315f2327fb84318", 20 | "-[TransientShaperTests testDefault]": "cea9fc1deb7a77fe47a071d7aaf411d3", 21 | "-[TransientShaperTests testOutputAmount]": "e84963aeedd6268dd648dd6a862fb76a", 22 | "-[TransientShaperTests testplayerAmount]": "f70c4ba579921129c86b9a6abb0cb52e", 23 | "-[TransientShaperTests testReleaseAmount]": "accb7a919f3c63e4dbec41c0e7ef88db", 24 | ] 25 | -------------------------------------------------------------------------------- /AudioKit-Experiments/Sounds/Piano.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NickCulbertson/AudioKit-Experiments/c2d1a94322f9cd5aa646f9b7873854ed2e3ae32d/AudioKit-Experiments/Sounds/Piano.mp3 -------------------------------------------------------------------------------- /AudioKit-Experiments/Sounds/PianoMuted.sf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NickCulbertson/AudioKit-Experiments/c2d1a94322f9cd5aa646f9b7873854ed2e3ae32d/AudioKit-Experiments/Sounds/PianoMuted.sf2 -------------------------------------------------------------------------------- /AudioKit-Experiments/Sounds/SFZSamples/saw220.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NickCulbertson/AudioKit-Experiments/c2d1a94322f9cd5aa646f9b7873854ed2e3ae32d/AudioKit-Experiments/Sounds/SFZSamples/saw220.wav -------------------------------------------------------------------------------- /AudioKit-Experiments/Sounds/mute.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NickCulbertson/AudioKit-Experiments/c2d1a94322f9cd5aa646f9b7873854ed2e3ae32d/AudioKit-Experiments/Sounds/mute.wav -------------------------------------------------------------------------------- /AudioKit-Experiments/Sounds/saw1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NickCulbertson/AudioKit-Experiments/c2d1a94322f9cd5aa646f9b7873854ed2e3ae32d/AudioKit-Experiments/Sounds/saw1.wav -------------------------------------------------------------------------------- /AudioKit-Experiments/Sounds/sqr.SFZ: -------------------------------------------------------------------------------- 1 | lokey=0 hikey=127 pitch_keycenter=57 pitch_keytrack=100 2 | lovel=000 hivel=127 amp_velcurve_127=1 loop_mode=loop_continuous loop_start=0 loop_end=220 sample=SFZSamples/saw220.wav 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Nick Culbertson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AudioKit Experiments 2 | This project is a place to post my various AudioKit app experiments. I'm starting this for Codevember 2023, and hopefully it'll continue into the future. Feel free to use these examples however you'd like! 3 | ## Current Examples 4 | (Click the image to watch AudioKit Experiments | Exploring 12 New Mini Projects) 5 | 6 | [![AudioKit Experiments | Exploring 12 New Mini Projects](https://img.youtube.com/vi/tCx_KHUsAoA/0.jpg)](https://youtu.be/tCx_KHUsAoA "AudioKit Experiments | Exploring 12 New Mini Projects") 7 | 1. SoundFont Player 8 | 2. Circular Visualizer 9 | 3. Linear Visualizer 10 | 4. SpriteKit + AudioKit 11 | 5. Recording Sampler 12 | 6. LFO Timer 13 | 7. AppleSampler ADSR Envelope 14 | 8. DunneAudioKit Sampler 15 | 9. DunneAudioKit Synth 16 | 10. Arpeggiator 17 | 11. AUv3 MIDI Generator 18 | 12. AUv3 Effect 19 | --------------------------------------------------------------------------------