How to render and export audios in AudioKit? - ios

I'm building a project about music and I am using the AudioKit framework.
AudioKit is being very useful, but I need now to export sounds created in the app.
I'm using AKSequencer to play notes in sequence and I'm also applying some filters on the sound, like reverb.
I found a example code here that exports an audio from another audio file, but it is not what I need. Actually, I need to render the notes sequence, including the filters, and export it to the user.
My code:
class Sequencer {
let oscBank = AKOscillatorBank(waveform: AKTable(AKTableType.positiveReverseSawtooth))//1
let sequencer = AKSequencer()
init() {
setup()
}
func setup() {
// I instantiate a MIDI node with the oscillator bank to put it in the track output.
// It makes the oscillator's filters are recognized in the track output.
let midiNode = AKMIDINode(node: oscBank)
_ = sequencer.newTrack()
sequencer.tracks[0].setMIDIOutput(midiNode.midiIn)
generateSequence()
// Here I'm applying some filter, a reverb in this case.
let reverb = AKReverb(oscBank)
reverb.loadFactoryPreset(.plate)
// The Audiokit's output may be the last filter applyed. It is working to play.
AudioKit.output = AKMixer(reverb)
try? AudioKit.start()
}
func play() {
sequencer.play()
}
func stop() {
sequencer.stop()
}
/// Generates some melody (Sweet Child of Mine)
func generateSequence() {
for _ in 0..<2 {
sequencer.tracks[0].add(noteNumber: 62, velocity: 127, position: AKDuration(beats: 0), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 74, velocity: 127, position: AKDuration(beats: 0.5), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 69, velocity: 127, position: AKDuration(beats: 1), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 67, velocity: 127, position: AKDuration(beats: 1.5), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 79, velocity: 127, position: AKDuration(beats: 2), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 69, velocity: 127, position: AKDuration(beats: 2.5), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 78, velocity: 127, position: AKDuration(beats: 3), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 69, velocity: 127, position: AKDuration(beats: 3.5), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 62, velocity: 127, position: AKDuration(beats: 4), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 74, velocity: 127, position: AKDuration(beats: 4.5), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 69, velocity: 127, position: AKDuration(beats: 5), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 67, velocity: 127, position: AKDuration(beats: 5.5), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 79, velocity: 127, position: AKDuration(beats: 6), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 69, velocity: 127, position: AKDuration(beats: 6.5), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 78, velocity: 127, position: AKDuration(beats: 7), duration: AKDuration(beats: 0.5))
sequencer.tracks[0].add(noteNumber: 69, velocity: 127, position: AKDuration(beats: 7.5), duration: AKDuration(beats: 0.5))
}
}
// Here is my problem. I want to get the song and export it with the .wav extension exactly as it was played.
// This code is just one of my many attempts and it also does not works.
func saveFile() {
guard let auxiliarPlayer = try? AKAudioPlayer(file: AKAudioFile(readFileName: "bass.wav")) else { return }
guard let outputURL = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("audio_file.m4a") else { return }
guard let output = try? AKAudioFile(forWriting: outputURL, settings: auxiliarPlayer.audioFile.fileFormat.settings) else { return }
try? AudioKit.renderToFile(output, seconds: sequencer.tracks[0].length, prerender: {
self.sequencer.play()
})
}
}
Can someone please help?

As far as I know, offline rendering does not work with MIDI due to timing issues. Basically, only things that process audio can be rendered, not things that rely on MIDI. I believe it has to do with the MIDI clock remaining constant and not being able to be virtually "sped up" to match the audio rendering speed.

Related

AudioKit: MIDICallbackInstrument not triggering a callback when triggered from Sequencer

I'm trying to get a callback when a midi note is triggered so I can display a graphical representation of the current beat in the bar. Nothing I do seems to callback. This is the simplest version of the code that I have tried - audio plays back fine but the callback is never triggered. I have enabled Audio in Background Modes.
Here is the code:
class ViewMain: UIViewController {
let engine = AudioEngine()
let kicks = Sampler()
let claps = Sampler()
let sequencer = Sequencer()
let mixer = Mixer()
let midiCallbackInstrument = MIDICallbackInstrument()
override func viewDidLoad() {
super.viewDidLoad()
midiCallbackInstrument.callback = myCallBack
mixer.addInput(midiCallbackInstrument)
mixer.addInput(kicks)
mixer.addInput(claps)
engine.output = mixer
do {
try engine.start()
} catch {
Log("AudioKit did not start! \(error)")
}
kicks.unloadAllSamples()
claps.unloadAllSamples()
guard let url = Bundle.main.resourceURL?.appendingPathComponent("SamplesSFZ/sfz/909A.sfz") else { return }
kicks.loadSFZ(url: url)
guard let url = Bundle.main.resourceURL?.appendingPathComponent("SamplesSFZ/sfz/909B.sfz") else { return }
claps.loadSFZ(url: url)
sequencer.addTrack(for: midiCallbackInstrument)
sequencer.tracks[0].add(noteNumber: 36, velocity: 120, channel: 0, position: 0.0, duration: 1.0)
sequencer.tracks[0].add(noteNumber: 37, velocity: 120, channel: 1, position: 0.5, duration: 1.0)
sequencer.tracks[0].add(noteNumber: 38, velocity: 120, channel: 2, position: 1.0, duration: 1.0)
sequencer.tracks[0].add(noteNumber: 39, velocity: 120, channel: 3, position: 0.5, duration: 1.0)
sequencer.tracks[0].add(noteNumber: 40, velocity: 120, channel: 4, position: 2.0, duration: 1.0)
sequencer.tracks[0].add(noteNumber: 41, velocity: 120, channel: 5, position: 0.5, duration: 1.0)
sequencer.tracks[0].add(noteNumber: 42, velocity: 120, channel: 6, position: 3.0, duration: 1.0)
sequencer.tracks[0].add(noteNumber: 43, velocity: 120, channel: 7, position: 0.5, duration: 1.0)
sequencer.addTrack(for: kicks)
sequencer.tracks[1].add(noteNumber: 24, velocity: 120, channel: 0, position: 0.0, duration: 2.0)
sequencer.addTrack(for: claps)
sequencer.tracks[2].add(noteNumber: 25, velocity: 120, channel: 0, position: 1.0, duration: 0.1)
sequencer.tracks[2].add(noteNumber: 25, velocity: 120, channel: 0, position: 3.0, duration: 0.1)
sequencer.loopEnabled = true
sequencer.tempo = 120.0
sequencer.length = 4.0
sequencer.play()
}
func myCallBack(a: UInt8, b:MIDINoteNumber, c:MIDIVelocity) -> () {
// THIS IS NEVER CALLED
print("*** callback invoked")
}
}
Thanks in advance - I really appreciate any help here.
Use sequencer.setGlobalMIDIOutput(midiCallbackInstrument.midiIn) or sequencer.tracks[0].setMIDIOutput(midiCallbackInstrument.midiIn) to connect sequencer and midiCallbackInstrument.
I have had good results with the newer AudioKit CallbackInstrument, which is intended as a replacement for MIDICallbackInstrument.

To get CallbackInstrument working with your code, you only need to make two changes:
// replace let midiCallbackInstrument = MIDICallbackInstrument() with
var midiCallbackInstrument = CallbackInstrument()
// replace midiCallbackInstrument.callback = myCallBack with
midiCallbackInstrument = CallbackInstrument(midiCallback: { [self] (event, beat, velocity) in
myCallBack(a: event, b: beat, c: velocity) } )
CallbackInstrument is documented here:
https://github.com/AudioKit/AudioKit/wiki/CallbackInstrument
and the Shaker Metronome recipe in the AudioKit Cookbook is an example of how it is used.
https://github.com/AudioKit/Cookbook

SwiftUI rotate together with addArc doesn't work correctly in some cases

Swift 5, iOS 13
Using addArc to draw a semi-circle and then using rotation3DEffect to rotate it. On the y axis it works perfectly, but in the x axis is seems broken. Am I doing something wrong here.
I got this code to work. But it is a kludge.
I want to us the x axis on the MyArc2 and not have to use an offset+rotationEffect. The first image is what you see with this code as is.
However if I do use x axis it doesn't draw it correctly.
import SwiftUI
struct ContentView: View {
#State var turn:Double = 0
var body: some View {
return VStack {
Image(systemName: "circle")
.foregroundColor(Color.blue)
.onTapGesture {
withAnimation(.linear(duration: 36)) {
self.turn = 360
}
}
Globe2View(turn: $turn)
} // VStack
}
}
struct Globe2View: View {
struct MyArc : Shape {
func path(in rect: CGRect) -> Path {
var p = Path()
p.addArc(center: CGPoint(x: UIScreen.screenWidth/2, y:UIScreen.screenHeight/2), radius: 64, startAngle: .degrees(90), endAngle: .degrees(270), clockwise: true)
return p
}
}
struct MyArc2 : Shape {
func path(in rect: CGRect) -> Path {
var p = Path()
p.addArc(center: CGPoint(x: UIScreen.screenWidth/2, y:UIScreen.screenHeight/2), radius: 64, startAngle: .degrees(90), endAngle: .degrees(270), clockwise: true)
return p
}
}
#Binding var turn:Double
var body: some View {
ZStack {
ForEach(0..<12) { loop in
MyArc()
.stroke(Color.blue, lineWidth: 2)
.rotation3DEffect(.degrees(self.turn + Double((loop * 30))), axis: (x: 0, y: -1, z: 0), anchor: UnitPoint.center, anchorZ: 0, perspective: 0)
}
ForEach(0..<12) { loop in
MyArc2()
.stroke(Color.green, lineWidth: 2)
.rotation3DEffect(.degrees(self.turn + Double((loop * 30))), axis: (x: 0, y: -1, z: 0), anchor: UnitPoint.center, anchorZ: 0, perspective: 0)
.rotationEffect(.degrees(90))
.offset(x: 20, y: 20)
}
}
}
}
If I change the last few lines so they look like this...
struct MyArc2 : Shape {
func path(in rect: CGRect) -> Path {
var p = Path()
p.addArc(center: CGPoint(x: UIScreen.screenWidth/2, y:UIScreen.screenHeight/2), radius: 64, startAngle: .degrees(0), endAngle: .degrees(180), clockwise: true)
return p
}
}
ForEach(0..<12) { loop in
MyArc2()
.stroke(Color.green, lineWidth: 2)
.rotation3DEffect(.degrees(self.turn + Double((loop * 30))), axis: (x: 1, y: 0, z: 0), anchor: UnitPoint.center, anchorZ: 0, perspective: 0)
// .rotationEffect(.degrees(90))
// .offset(x: 20, y: 20)
}
It is not clear why it is needed MyArc2 as it is the same, but the following, with simple fix in shape, just works as expected (to my mind).
Tested with Xcode 11.4 / iOS 13.4
struct Globe2View: View {
struct MyArc : Shape {
func path(in rect: CGRect) -> Path {
var p = Path()
// used provided dynamic rect instead of hardcoded NSScreen
p.addArc(center: CGPoint(x: rect.width/2, y:rect.height/2), radius: 64, startAngle: .degrees(90), endAngle: .degrees(270), clockwise: true)
return p
}
}
#Binding var turn:Double
var body: some View {
ZStack {
ForEach(0..<12) { loop in
MyArc()
.stroke(Color.blue, lineWidth: 2)
.rotation3DEffect(.degrees(self.turn + Double((loop * 30))), axis: (x: 0, y: -1, z: 0), anchor: UnitPoint.center, anchorZ: 0, perspective: 0)
}
ForEach(0..<12) { loop in
MyArc()
.stroke(Color.green, lineWidth: 2)
.rotation3DEffect(.degrees(self.turn + Double((loop * 30))), axis: (x: 0, y: -1, z: 0), anchor: UnitPoint.center, anchorZ: 0, perspective: 0)
.rotationEffect(.degrees(90))
}
}
}
}

Add drop shadow to SwiftUI Path elements

I have a rudimentary ClockView build with SwiftUI. :) My question is if applying drop shadows to the clock-hands is easily possible with this approach or if i need a different layout and grouping of the elements? I've tried to find a way to add shadows to Path, but got stuck. Thank you.
Code
import SwiftUI
struct ClockView: View {
#Binding var time : TimeValue
let hoursHandWidth = 10
let minutesHandWidth = 10
let secondsHandWidth = 10
var body: some View {
return(
ZStack {
// clock-face
Path { path in
let seconds : Path = Path(CGRect(x: 100, y: -0.5, width: 10, height: 1))
for i in 0...60 {
…
}
}
.fill(Color(red: 0.3, green: 0.3, blue: 0.3, opacity: 1.0))
Path { path in
let hours : Path = Path(CGRect(x: 100, y: -1, width: 12, height: 2))
for i in 0...12 {
…
}
}
.fill(Color(red: 0.3, green: 0.3, blue: 0.3, opacity: 1.0))
Path { path in
let threehours : Path = Path(CGRect(x: 95, y: -2, width: 20, height: 4))
for i in 0...4 {
…
}
}
.fill(Color.red)
// clock-hands
Path { path in
let minutehand : Path = Path(CGRect(x: 0, y: -(minutesHandWidth/2), width: 95, height: minutesHandWidth))
path.addPath(minutehand, …)
}
.fill(Color.blue)
Path { path in
let hourhand : Path = Path(CGRect(x: 0, y: -(hoursHandWidth/2), width: 72, height: hoursHandWidth))
path.addPath(hourhand, …)
}
.fill(Color.blue)
Path { path in
let secondshand : Path = Path(CGRect(x: 0, y: -(secondsHandWidth/2), width: 97, height: secondsHandWidth))
path.addPath(secondshand, …)
}
.fill(Color.yellow)
.animation(.easeInOut)
Path { path in
path.addPath(Path(CGPath(ellipseIn: CGRect(x: -5, y: -5, width: 10, height: 10), transform: nil)))
}
.fill(Color.black)
}
.offset(CGSize(width: 150, height: 150))
.frame(width: 300, height: 300, alignment: .topLeading)
.scaleEffect(1.2)
.background(Color(red: 0.8, green: 0.8, blue: 0.8, opacity: 1.0))
)
}
}
Look
** Update **
I've applied .shadow(..) as user Asperi recommended:
Path { path in
let secondshand : Path = Path(CGRect(x: 0, y: -(secondsHandWidth/2), width: 97, height: secondsHandWidth))
path.addPath(secondshand, transform: .init(rotationAngle: 2.0 * CGFloat.pi * CGFloat(time.seconds-15)/60.0))
}
.fill(Color.yellow)
.shadow(color: .black, radius: 8, x: 1, y: 1)
But the result is not exactly what i expected :)
I've moved the transformation (rotation) from the subpath to the resulting path. Now it looks good.
Old:
Path { path in
let secondshand : Path = Path(CGRect(x: 0, y: -(secondsHandWidth/2), width: 97, height: secondsHandWidth))
path.addPath(secondshand, transform: .init(rotationAngle: 2.0 * CGFloat.pi * CGFloat(time.seconds-15)/60.0))
}
.fill(Color.yellow)
.shadow(color: .black, radius: 2)
New:
Path { path in
let secondshand : Path = Path(CGRect(x: 0, y: -(secondsHandWidth/2), width: 97, height: secondsHandWidth))
path.addPath(secondshand)
}
.fill(Color.yellow)
.shadow(color: .black, radius: 2)
.transformEffect(CGAffineTransform(rotationAngle: 2.0 * CGFloat.pi * CGFloat(time.seconds-15)/60.0))
Look (new):
Here is an example
Path { path in
let secondshand : Path = Path(CGRect(x: 0, y: -(secondsHandWidth/2), width: 97, height: secondsHandWidth))
path.addPath(secondshand)
}
.fill(Color.yellow)
.shadow(color: .black, radius: 8, x: 4, y: 1)

UIBezier Path, Node Jump

I'm working on a Mario Clone for Mac and would like to make the player jump on a curved line when the right and up arrow are pressed.I've heard about UIBezierPaths being the solution to this, but sadly I have no clue how to use them.Can someone please explain what code I would write in order to make a UIBezierPath as well as make Mario follow the path in a jump?Thank you.
Somewhat of the way I would like my player to jump:
!http://projects.haskell.org/diagrams/doc/images/d8527a0188182ef5.png
My Player Movements So far:
func jump(){
// 250 , 120
let jumpUp = SKAction.moveTo(y: 250, duration: 0.25)
let jumpDown = SKAction.moveTo(y: 120, duration: 0.25)
let jumpSequence = SKAction.sequence([jumpUp,jumpDown])
player.run(jumpSequence)
}
func userMoveRight(){
player.xScale = 0.23
level.run(SKAction.sequence([SKAction.moveBy(x: -20, y: 0, duration: 0.2)]))
background.run(SKAction.sequence([SKAction.moveBy(x: -20, y: 0, duration: 0.2)]))
question.run(SKAction.sequence([SKAction.moveBy(x: -20, y: 0, duration: 0.2)]))
}
func userMoveLeft(){
player.xScale = -0.23
background.run(SKAction.sequence([SKAction.moveBy(x: 20, y: 0, duration: 0.2)]))
level.run(SKAction.sequence([SKAction.moveBy(x: 20, y: 0, duration: 0.2)]))
question.run(SKAction.sequence([SKAction.moveBy(x: 20, y: 0, duration: 0.2)]))
}
override func keyDown(with theEvent: NSEvent) {
let keyCode = theEvent.keyCode
//Moving Right
if keyCode == 124 {
userMoveRight()
}
//Moving Left
if player.position.x <= 490, background.position.x <= 1513, keyCode == 123 {
userMoveLeft()
}
//Jump
if keyCode == 126 , player.position.y == 120 {
jump()
}
}

Circular path for sprite?

I'm trying to build a game using xcode 6.1 and swift and one of the key elements is having a ball go around in circles (not rotating around own axis but following circular path). How can I accomplish this the most efficiently? Here is the code I tried.
func rotate(){
newBall = balls(size: self.size, positionX: -36, positionY: 250)
var path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, 0, 0)
CGPathAddArc(path, nil, 0, 15, 15, CGFloat(M_PI_2), CGFloat(M_PI_2), true)
newBall.node.path = path
self.addChild(newBall.node)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
rotate()
}
I seem to remember that creating apparently zero-length paths doesn't always have the expected results. Try splitting the path up:
CGPathAddArc(path, nil, 0, 15, 15, CGFloat(M_PI_2), -CGFloat(M_PI_2), true)
CGPathAddArc(path, nil, 0, 15, 15, -CGFloat(M_PI_2), CGFloat(M_PI_2), true)
there are a ton of ways to do this. I'll just show you one way
you can copy paste this code and modify to your needs
// add sprite to scene
let mysprite = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 10, height: 10))
mysprite.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
self.addChild(mysprite)
// the circle path's diameter
let circleDiameter = CGFloat(100)
// center our path based on our sprites initial position
let pathCenterPoint = CGPoint(
x: mysprite.position.x - circleDiameter/2,
y: mysprite.position.y - circleDiameter/2
)
// create the path our sprite will travel along
let circlePath = CGPathCreateWithEllipseInRect(CGRect(origin: pathCenterPoint, size: CGSize(width: circleDiameter, height: circleDiameter)), nil)
// create a followPath action for our sprite
let followCirclePath = SKAction.followPath(circlePath, asOffset: false, orientToPath: true, duration: 2)
// make our sprite run this action forever
mysprite.runAction(SKAction.repeatActionForever(followCirclePath))

Resources