AKOscillator in 4.1 sounding VERY different than 4.0.4 - swift4

Has something radically changed in AKOscillator or AKAmplitudeEnvelope 4.1? My code for a simple chime sound worked like a charm in 4.0 but is now some kind of weird alien thing in 4.1. I am not sure how to proceed without going back to 4.0.
Here is my code for setting and playing a note:
var oscillators = [AKOscillator]()
var envelopes = [AKAmplitudeEnvelope]()
var mix: AKMixer?
let chime1Partials: [PartialAttributes] =
[
PartialAttributes(partial: 0.5, amp: 0.25, attack: 0.25, decay: 0.35, sustain: 0.0, release: 1.0),
PartialAttributes(partial: 1.0, amp: 0.75, attack: 0.025, decay: 0.135, sustain: 0.0, release: 1.0),
PartialAttributes(partial: 2.69, amp: 0.35, attack: 0.025, decay: 0.25, sustain: 0.0, release: 1.0),
PartialAttributes(partial: 5.15, amp: 0.15, attack: 0.05, decay: 0.25, sustain: 0.0, release: 0.5)
]
class ChimeOscillator: NSObject, PlayerProtocol
{
var fundamental: Double = 0.0
var oscillators = [AKOscillator]()
var envelopes = [AKAmplitudeEnvelope]()
var mix: AKMixer?
init(_ fundamental: Double, partials: [PartialAttributes])
{
oscillators.removeAll()
envelopes.removeAll()
self.fundamental = fundamental
for attrib in partials
{
let osc = AKOscillator(waveform: AKTable(.sine))
osc.frequency = fundamental * attrib.partial
osc.amplitude = attrib.amp
osc.start()
oscillators.append(osc)
let env = AKAmplitudeEnvelope(osc)
env.attackDuration = attrib.attack
env.decayDuration = attrib.decay
env.sustainLevel = attrib.sustain
env.releaseDuration = attrib.release
envelopes.append(env)
}
mix = AKMixer(envelopes)
super.init()
}
func play(velocity: MIDIVelocity)
{
guard let mix = mix else{ return }
let amp = Double(velocity) / 127.0
// Mixer Output Volume (Default 1.0)
mix.volume = amp * amp
_ = envelopes.map{$0.start()}
}
func stop()
{
_ = envelopes.map{$0.stop()}
}
}

Related

Align a node with its neighbor in SceneKit

Using Swift 5.5, iOS 14
Trying to create a simple 3D bar chart and I immediately find myself in trouble.
I wrote this code...
var virgin = true
for i in stride(from: 0, to: 6, by: 0.5) {
let rnd = CGFloat.random(in: 1.0...4.0)
let targetGeometry = SCNBox(width: 0.5,
height: rnd,
length: 0.5,
chamferRadius: 0.2)
targetGeometry.firstMaterial?.fillMode = .lines
targetGeometry.firstMaterial?.diffuse.contents = UIColor.blue
let box = SCNNode(geometry: targetGeometry)
box.simdPosition = SIMD3(x: 0, y: 0, z: 0)
coreNode.addChildNode(box)
}
This works well, but all the bars a centred around their centre. But how can I ask SceneKit to change the alignment?
Almost got this working with this code...
box.simdPosition = SIMD3(x: Float(i) - 3,
y: Float(rnd * 0.5),
z: 0)
But the result isn't right... I want/need the bar to grow from the base.
https://youtu.be/KJgvdBFBfyc
How can I make this grow from the base?
--Updated with working solution--
Tried Andy Jazz suggestion replacing simdposition with the following formulae.
box.simdPosition = SIMD3(x:box.simdPosition.x, y:0, z:0)
box.simdPivot.columns.3.y = box.boundingBox.max.y - Float(rnd * 0.5)
Worked well, to which I added some animation! Thanks Andy.
changer = changeling.sink(receiveValue: { [self] _ in
var rnds:[CGFloat] = []
for i in 0..<12 {
let rnd = CGFloat.random(in: 1.0...2.0)
let targetGeometry = SCNBox(width: 0.45, height: rnd, length: 0.45, chamferRadius: 0.01)
let newNode = SCNNode(geometry: targetGeometry)
sourceNodes[i].simdPivot.columns.3.y = newNode.boundingBox.min.y
sourceNodes[i].simdPosition = SIMD3(x: sourceNodes[i].simdPosition.x, y: 0, z: 0)
rnds.append(rnd)
}
for k in 0..<12 {
if virgin {
coreNode.addChildNode(sourceNodes[k])
}
let targetGeometry = SCNBox(width: 0.45, height: rnds[k], length: 0.45, chamferRadius: 0.01)
targetGeometry.firstMaterial?.fillMode = .lines
targetGeometry.firstMaterial?.diffuse.contents = UIColor.blue
let morpher = SCNMorpher()
morpher.targets = [targetGeometry]
sourceNodes[k].morpher = morpher
let animation = CABasicAnimation(keyPath: "morpher.weights[0]")
animation.toValue = 1.0
animation.repeatCount = 0.0
animation.duration = 1.0
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
sourceNodes[k].addAnimation(animation, forKey: nil)
}
virgin = false
})
You have to position a pivot point of each bar to its base:
boxNode.simdPivot.columns.3.y = someFloatNumber
To reposition a pivot to bar's base use bounding box property:
boxNode.simdPivot.columns.3.y += (boxNode.boundingBox.min.y as? simd_float1)!
After pivot's offset, reposition boxNode towards negative direction of Y-axis.
boxNode.position.y = 0

Why are the shades of black in a custom SCNGeometry lighter than in an SCNSphere

In this scene
the column of spheres on the left are created using SCNSphere()
the column of 'circles' on the right are created using SCNGeometry() using .point primitives
there is only an ambient light source
all geometries are using a .constant lighting model
each pair of spheres uses the same RGB values to define the colour.
Why are the shades of black for the last two pairs lighter for the circles than their equivalient sphere?
Full playground code to reproduce this images:
Creating the scene
import UIKit
import SceneKit
import PlaygroundSupport
// Constants I'm using for the darkest grey colour
let B_RED: CGFloat = 0.05
let B_GREEN: CGFloat = 0.05
let B_BLUE: CGFloat = 0.05
let B_ALPHA: CGFloat = 1.0
let BLACK_COLOUR = UIColor(red: B_RED, green: B_GREEN, blue: B_BLUE, alpha: B_ALPHA)
let scene: SCNScene = {
let s = SCNScene()
return s
}()
let sceneView: SCNView = {
let v = SCNView(frame: CGRect(x: 0, y: 0, width: 600, height: 800))
v.scene = scene
v.backgroundColor = UIColor(white: 0.66, alpha: 1.0)
v.allowsCameraControl = true
v.debugOptions = [SCNDebugOptions.showLightInfluences]
v.backgroundColor
return v
}()
let ambientLigntNode: SCNNode = {
let n = SCNNode()
n.light = SCNLight()
n.light!.type = SCNLight.LightType.ambient
n.light!.color = UIColor(white: 1, alpha: 1.0)
return n
}()
PlaygroundPage.current.liveView = sceneView
// a camera
var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.simdPosition = simd_float3(0,0,8)
scene.rootNode.addChildNode(cameraNode)
scene.rootNode.addChildNode(ambientLigntNode)
The column of spheres
// ----------------------------------------------------
// White Sphere
let whiteSphere = SCNSphere(radius: 0.3)
let whiteMaterial = SCNMaterial()
whiteMaterial.diffuse.contents = simd_float3(1,1,1)
whiteMaterial.lightingModel = .constant
whiteSphere.materials = [whiteMaterial]
let whiteSphereNode = SCNNode(geometry: whiteSphere)
whiteSphereNode.simdPosition = simd_float3(-1,2,0)
scene.rootNode.addChildNode(whiteSphereNode)
// ----------------------------------------------------
// Black Sphere
let blackSphere = SCNSphere(radius: 0.3)
let blackMaterial = SCNMaterial()
blackMaterial.diffuse.contents = BLACK_COLOUR
blackMaterial.lightingModel = .constant
blackSphere.materials = [blackMaterial]
let blackSphereNode = SCNNode(geometry: blackSphere)
blackSphereNode.simdPosition = simd_float3(-1,-2,0)
scene.rootNode.addChildNode(blackSphereNode)
// ----------------------------------------------------
// Red Sphere
let redSphere = SCNSphere(radius: 0.3)
let redMaterial = SCNMaterial()
redMaterial.diffuse.contents = UIColor(
red: 1.0,
green: 0.0,
blue: 0.0,
alpha: 1.0
)
redMaterial.lightingModel = .constant
redSphere.materials = [redMaterial]
let redSphereNode = SCNNode(geometry: redSphere)
redSphereNode.simdPosition = simd_float3(-1, 1, 0)
scene.rootNode.addChildNode(redSphereNode)
// ----------------------------------------------------
// Green Sphere
let greenSphere = SCNSphere(radius: 0.3)
let greenMaterial = SCNMaterial()
greenMaterial.diffuse.contents = UIColor(
red: 0.0,
green: 1.0,
blue: 0.0,
alpha: 1.0
)
greenMaterial.lightingModel = .constant
greenSphere.materials = [greenMaterial]
let greenSphereNode = SCNNode(geometry: greenSphere)
greenSphereNode.simdPosition = simd_float3(-1, 0, 0)
scene.rootNode.addChildNode(greenSphereNode)
// ----------------------------------------------------
// Blue Sphere
let blueSphere = SCNSphere(radius: 0.3)
let blueMaterial = SCNMaterial()
blueMaterial.diffuse.contents = UIColor(
red: 0.0,
green: 0.0,
blue: 1.0,
alpha: 1.0
)
blueMaterial.lightingModel = .constant
blueSphere.materials = [blueMaterial]
let blueSphereNode = SCNNode(geometry: blueSphere)
blueSphereNode.simdPosition = simd_float3(-1, -1, 0)
scene.rootNode.addChildNode(blueSphereNode)
// ----------------------------------------------------
// Grey Sphere
let greySphere = SCNSphere(radius: 0.3)
let greyMaterial = SCNMaterial()
greyMaterial.diffuse.contents = UIColor(
red: 0.5,
green: 0.5,
blue: 0.5,
alpha: 1.0
)
greyMaterial.lightingModel = .constant
greySphere.materials = [greyMaterial]
let greySphereNode = SCNNode(geometry: greySphere)
greySphereNode.simdPosition = simd_float3(-1, -3, 0)
scene.rootNode.addChildNode(greySphereNode)
Column of circles
// ----------------------------------------------------
// Custom SCNGeometry using vertex data
struct Vertex {
let x: Float
let y: Float
let z: Float
let r: Float
let g: Float
let b: Float
let a: Float
}
let vertices: [Vertex] = [
Vertex(x: 0.0, y: 2.0, z: 0.0, r: 1.0, g: 1.0, b: 1.0, a: 1.0), // white
Vertex(x: 0.0, y: 1.0, z: 0.0, r: 1.0, g: 0.0, b: 0.0, a: 1.0), // red
Vertex(x: 0.0, y: 0.0, z: 0.0, r: 0.0, g: 1.0, b: 0.0, a: 1.0), // green
Vertex(x: 0.0, y: -1.0, z: 0.0, r: 0.0, g: 0.0, b: 1.0, a: 1.0), // blue
Vertex(x: 0.0, y: -3.0, z: 0.0, r: 0.5, g: 0.5, b: 0.5, a: 1.0), // rgb
Vertex(
x: 0.0, y: -2.0, z: 0.0,
r: Float(B_RED), g: Float(B_GREEN), b: Float(B_BLUE), a: Float(B_ALPHA)
)
]
let vertexData = Data(
bytes: vertices,
count: MemoryLayout<Vertex>.size * vertices.count
)
let positionSource = SCNGeometrySource(
data: vertexData,
semantic: SCNGeometrySource.Semantic.vertex,
vectorCount: vertices.count,
usesFloatComponents: true,
componentsPerVector: 3,
bytesPerComponent: MemoryLayout<Float>.size,
dataOffset: 0,
dataStride: MemoryLayout<Vertex>.size
)
let colourSource = SCNGeometrySource(
data: vertexData,
semantic: SCNGeometrySource.Semantic.color,
vectorCount: vertices.count,
usesFloatComponents: true,
componentsPerVector: 4,
bytesPerComponent: MemoryLayout<Float>.size,
dataOffset: MemoryLayout<Float>.size * 3,
dataStride: MemoryLayout<Vertex>.size
)
let elements = SCNGeometryElement(
data: nil,
primitiveType: .point,
primitiveCount: vertices.count,
bytesPerIndex: MemoryLayout<Int>.size
)
elements.pointSize = 100
elements.minimumPointScreenSpaceRadius = 100
elements.maximumPointScreenSpaceRadius = 100
let spheres = SCNGeometry(
sources: [positionSource, colourSource],
elements: [elements]
)
let sphereNode = SCNNode(geometry: spheres)
let sphereMaterial = SCNMaterial()
sphereMaterial.lightingModel = .constant
spheres.materials = [sphereMaterial]
sphereNode.simdPosition = simd_float3(0,0,0)
scene.rootNode.addChildNode(sphereNode)
Updated based on accepted answer. Converting from sRGB to LinearRGB gives the same result
func srgbToLinear(x: Float) -> Float {
if x <= 0.04045 {
return x / 12.92;
}
return powf((x + 0.055) / 1.055, 2.4)
}
let vertices: [Vertex] = [
Vertex(x: 0.0, y: 2.0, z: 0.0, r: 1.0, g: 1.0, b: 1.0, a: 1.0), // white
Vertex(x: 0.0, y: 1.0, z: 0.0, r: 1.0, g: 0.0, b: 0.0, a: 1.0), // red
Vertex(x: 0.0, y: 0.0, z: 0.0, r: 0.0, g: 1.0, b: 0.0, a: 1.0), // green
Vertex(x: 0.0, y: -1.0, z: 0.0, r: 0.0, g: 0.0, b: 1.0, a: 1.0), // blue
Vertex(x: 0.0, y: -3.0, z: 0.0, r: srgbToLinear(x: 0.5), g: srgbToLinear(x: 0.5), b: srgbToLinear(x: 0.5), a: 1.0), // rgb
Vertex(
x: 0.0, y: -2.0, z: 0.0,
r: srgbToLinear(x: Float(B_RED)),
g: srgbToLinear(x: Float(B_GREEN)),
b: srgbToLinear(x: Float(B_GREEN)),
a: 1.0
)
]
This would be the case if the two types of graphic are generated in different color spaces. For example, if gray for the spheres is interpreted as being in the sRGB as the color space, and the circle are interpreted as generic RGB space then you would see a different in color values.
Consider the following playground code:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
import CoreGraphics
class MyViewController : UIViewController {
override func loadView() {
let view = CustomView()
view.backgroundColor = UIColor.darkGray
self.view = view
}
}
class CustomView : UIView {
override func draw(_ rect: CGRect) {
if let cgContext = UIGraphicsGetCurrentContext() {
cgContext.saveGState()
let gray : [CGFloat] = [0.5, 0.5, 0.5, 1.0]
let srgbGray = CGColor(
colorSpace: CGColorSpace(name: CGColorSpace.sRGB)!, components: gray)!
cgContext.setFillColor(srgbGray)
cgContext.fillEllipse(in: CGRect(x:10, y:20, width:72, height: 72))
let genericGray = CGColor(colorSpace: CGColorSpace(name: CGColorSpace.genericRGBLinear)!, components: gray)!
cgContext.setFillColor(genericGray)
cgContext.fillEllipse(in: CGRect(x:110, y:20, width:72, height: 72))
cgContext.restoreGState()
}
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
It produces an effect almost identical to the one you are seeing. I suspect that the circles are drawn assuming a generic color space and the spheres are drawn assuming sRGB.

How to solve SpriteKit SKTexture's CGImage "Context leak"?

Xcode 11-12, MacOS Catalina, Swift 5 project...
This code is produced "Context leak detected, msgtracer returned -1" error:
import Foundation
import SpriteKit
import GameKit
func getTextureImage(_ index: Int, isSky: Bool = true) -> CGImage {
let perlinSource = GKPerlinNoiseSource(
frequency: 1,
octaveCount: 7,
persistence: 0.5,
lacunarity: 2.0,
seed: 123
)
let noise = GKNoise(perlinSource)
noise.gradientColors = [
-1.0: NSColor(red: 0.0, green: 0.15, blue: 0.85, alpha: 1.0),
1.0 : NSColor(red: 1, green: 1, blue: 1, alpha: 1.0),
]
noise.move(by: vector_double3(Double(index), 0, 0))
let noiseMap = GKNoiseMap(noise,
size: SIMD2(repeating: 1),
origin: SIMD2(repeating: 0.0),
sampleCount: SIMD2(repeating: 16),
seamless: false)
let t = SKTexture(noiseMap: noiseMap)
let i = t.cgImage()
return i
}
for x in 0...128 {
let texture = getTextureImage(x)
}
How to solve that error at line with .cgImage() method call?
I need CGImage data from colored GKNoiseMap, then I will use it with MetalKit to load data into MTLTexture with
MTKTextureLoader.newTexture(cgImage: texture, options: textureLoaderOptions)
Yeahh, of course I tried
autoreleasepool {
let t = getTextureImage(x)
}
and solved problem :) So, you have to use autoreleasepool with NSImage and CGImage in Swift API

How to calculate color composite in Swift?

I'm trying to create eraser tool for my photo editor app like in Photoshop (or that can remove defined color from the image), for example user will draw lines with desired color on image and my function will process the image after touch ends immediatly.
I created a method and it working well with some colors like white, black, yellow etc. but when I choose color with 0.5 alpha it's not working as desired!
I think my composite calculation is wrong so some one can help me with this please?
And here is my method for calculation :
extension UIImage {
func getImageRemovedDefinedColor(choosedColor: UIColor) -> UIImage? {
let image = UIImage(data: self.jpegData(compressionQuality: 1.0)!)!
let rawImageRef: CGImage = image.cgImage!
let components = choosedColor.cgColor.components!
var colorMasking: [CGFloat] = []
var red: CGFloat = 0.0, green: CGFloat = 0.0, blue: CGFloat = 0.0, alpha: CGFloat = 0.0
if choosedColor.isEqual(UIColor.white) {
colorMasking = [222, 255, 222, 255, 222, 255]
} else if choosedColor.isEqual(UIColor.black) {
colorMasking = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
} else {
if components.count > 3 {
red = components[0]
green = components[1]
blue = components[2]
alpha = components[3]
red = red * 255.0
green = green * 255.0
blue = blue * 255.0
alpha = alpha * 255.0
let redRange: [CGFloat] = {
return [max(red - (alpha / 2.0), 0.0), min(red + (alpha / 2.0), 255.0)]
}()
let greenRange: [CGFloat] = {
return [max(green - (alpha / 2.0), 0.0), min(green + (alpha / 2.0), 255.0)]
}()
let blueRange: [CGFloat] = {
return [max(blue - (alpha / 2.0), 0.0), min(blue + (alpha / 2.0), 255.0)]
}()
colorMasking = [redRange[0], redRange[1], greenRange[0], greenRange[1], blueRange[0], blueRange[1]]
}
}
print(colorMasking)
UIGraphicsBeginImageContext(image.size);
let maskedImageRef = rawImageRef.copy(maskingColorComponents: colorMasking)
UIGraphicsGetCurrentContext()?.translateBy(x: 0.0,y: image.size.height)
UIGraphicsGetCurrentContext()?.scaleBy(x: 1.0, y: -1.0)
UIGraphicsGetCurrentContext()?.draw(maskedImageRef!, in: CGRect.init(x: 0, y: 0, width: image.size.width, height: image.size.height))
let result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result
}
Note: Maybe my way completely wrong for doing this tool, if you have another idea about "how can we can create eraser tool with Swift" please share it.
All ideas acceptable, thanks in advance.

SceneKit - How to Add Animations to Change SCNNode's Color?

I would like to know how I can animate an SCNNode's color using Swift.
For example: I would like the node to constantly be changing color or I would like the node to fade from black to blue.
Do I use the SCNAction fadeIn or fadeOut?
You can create a custom action.
If you have a red sphere in your scene
let sphereNode = scene.rootNode.childNode(withName: "sphere", recursively: false)!
sphereNode.geometry!.firstMaterial!.diffuse.contents = UIColor.red
This is how you build the custom action
let changeColor = SCNAction.customAction(duration: 10) { (node, elapsedTime) -> () in
let percentage = elapsedTime / 5
let color = UIColor(red: 1 - percentage, green: percentage, blue: 0, alpha: 1)
node.geometry!.firstMaterial!.diffuse.contents = color
}
Finally you just need to run the action on the sphere
sphereNode.runAction(changeColor)
Result
Got idea from #Luca Angeletti, I write the code so we can animate between any colors, include their alphas:
func aniColor(from: UIColor, to: UIColor, percentage: CGFloat) -> UIColor {
let fromComponents = from.cgColor.components!
let toComponents = to.cgColor.components!
let color = UIColor(red: fromComponents[0] + (toComponents[0] - fromComponents[0]) * percentage,
green: fromComponents[1] + (toComponents[1] - fromComponents[1]) * percentage,
blue: fromComponents[2] + (toComponents[2] - fromComponents[2]) * percentage,
alpha: fromComponents[3] + (toComponents[3] - fromComponents[3]) * percentage)
return color
}
Use:
let oldColor = UIColor.red
let newColor = UIColor(colorLiteralRed: 0.0, green: 0.0, blue: 1.0, alpha: 0.5)
let duration: TimeInterval = 1
let act0 = SCNAction.customAction(duration: duration, action: { (node, elapsedTime) in
let percentage = elapsedTime / CGFloat(duration)
node.geometry?.firstMaterial?.diffuse.contents = self.aniColor(from: newColor, to: oldColor, percentage: percentage)
})
let act1 = SCNAction.customAction(duration: duration, action: { (node, elapsedTime) in
let percentage = elapsedTime / CGFloat(duration)
node.geometry?.firstMaterial?.diffuse.contents = self.aniColor(from: oldColor, to: newColor, percentage: percentage)
})
let act = SCNAction.repeatForever(SCNAction.sequence([act0, act1]))
node.runAction(act)
I am using this function;
You just give name of material, how long animation will take and current color and
the which color will be reached.
You give color as arrays (red, green, blue and alpha);
let startColor: [CGFloat] = [0.5, 0.5, 1, 1]
let targetColor: [CGFloat] = [1, 1, 1, 1]
This is how you call function;
changeColorWithAnimation(duration : 5,
materialName: myCubeMaterial,
start : startColor,
end : targetColor)
func chageColorWithAnimation(duration: CGFloat,
materialName: SCNMaterial,
start: [CGFloat],
end: [CGFloat]){
let rs = (end[0] - start[0]) / duration
let gs = (end[1] - start[1]) / duration
let bs = (end[2] - start[2]) / duration
let alphas = (end[3] - start[3]) / duration
let changeColor = SCNAction.customAction(duration: TimeInterval(duration)) { (node, elapsedTime) -> () in
let red = start[0] + rs * elapsedTime
let green = start[1] + gs * elapsedTime
let blue = start[2] + bs * elapsedTime
let alpha = start[3] + alphas * elapsedTime
materialName.diffuse.contents = UIColor(displayP3Red:red,
green: green,
blue: blue,
alpha: alpha)
}
rootNode.runAction(changeColor)
}