Skip to content

Instantly share code, notes, and snippets.

@dgellow
Created October 8, 2015 20:27
Show Gist options
  • Save dgellow/8dad4c3262d992cc0778 to your computer and use it in GitHub Desktop.
Save dgellow/8dad4c3262d992cc0778 to your computer and use it in GitHub Desktop.
Some fun with a scheme interpreter and PCM. Call it with `swift AudioFun.playground/Contents.swift "(* t (& (b t 12) 63 (b t 4)))"`
import AVFoundation
var engine = AVAudioEngine()
var player = AVAudioPlayerNode()
var mixer = engine.mainMixerNode
//
//func sineWave(buffer: AVAudioPCMBuffer) -> AVAudioPCMBuffer {
// let sr = Float(mixer.outputFormatForBus(0).sampleRate)
// let n_channels = mixer.outputFormatForBus(0).channelCount
//
// for var i = 0; i < Int(buffer.frameLength); i+=Int(n_channels) {
// let val = sinf(441.0*Float(i)*2*Float(M_PI)/sr)
// buffer.floatChannelData.memory[i] = val * 0.5
// }
// return buffer
//}
engine.attachNode(player)
engine.connect(player, to: mixer, format: player.outputFormatForBus(0))
// Scheme
class Expression {
func toString() -> String { return "#E" }
}
class Nil : Expression {
override func toString() -> String {
return "#N"
}
}
class Symbol : Expression {
let name: String
init(name: String){
self.name = name
}
override func toString() -> String {
return "symbol(" + self.name + ")"
}
}
class Boolean : Expression {
let value: Bool
init(value: Bool) {
self.value = value
}
override func toString() -> String {
return self.value ? "#T" : "#F"
}
}
class Number : Expression {
let value: Int
init(value: Int) {
self.value = value
}
override func toString() -> String {
return String(self.value)
}
}
class List : Expression {
var es: [Expression] = [] // es stands for ElementS
init(es: [Expression]) {
self.es = es
}
override func toString() -> String {
return "(" + es.map({ $0.toString() }).joinWithSeparator(" ") + ")"
}
}
class Procedure : Expression {
var params: List
var body: List
var lexicalEnv: Environment
init(params: List, body: List, lexicalEnv: Environment){
self.params = params
self.body = body
self.lexicalEnv = lexicalEnv
}
override func toString() -> String {
return "\\(\(params.toString()) \(body.toString()))"
}
}
class Environment {
let outer: Environment?
var vars: [String : Expression] = [:]
init(outer: Environment?) { self.outer = outer }
func add(symbol: Symbol, expression: Expression) { vars[symbol.name] = expression }
func lookup(symbol: Symbol)
-> Expression! { return (vars[symbol.name] != nil) ? vars[symbol.name] : outer?.lookup(symbol) }
}
class Evaluator {
var rootEnv = Environment(outer: nil)
func tokenize(input: String) -> [String] {
var tokens: [String] = []
var nextIndex = input.startIndex
while nextIndex != input.endIndex {
let read = readNextToken(input, startIndex: nextIndex)
tokens += [read.token!]
nextIndex = read.nextIndex
}
return tokens
}
func getNextChar(input: String, nextIndex: String.Index) -> String {
return input.substringWithRange(nextIndex..<nextIndex.advancedBy(1))
}
func a(input: String, index: String.Index, char: String) -> Bool {
return input.substringFromIndex(index).hasPrefix(char)
}
func readNextToken(input :String, startIndex : String.Index) -> (token : String?, nextIndex : String.Index) {
var nextIndex = startIndex
while nextIndex != input.endIndex {
if a(input, index: nextIndex, char: " ") || a(input, index: nextIndex, char: "\n") {
nextIndex++
} else {
break
}
}
if nextIndex == input.endIndex {
return (nil, nextIndex)
}
let nextChar = getNextChar(input, nextIndex: nextIndex)
switch nextChar {
case "(", ")", "+", "*", "-", "/", "%", "<", ">", "=", "\\":
return (nextChar, ++nextIndex)
case "1", "2", "3", "4", "5", "6", "7", "8", "9", "0":
return readNumber(input, startIndex: nextIndex)
default:
return readSymbol(input, startIndex: nextIndex)
}
}
func readNumber(input: String, startIndex: String.Index) -> (token: String?, nextIndex:String.Index) {
var value = ""
var nextIndex = startIndex
while nextIndex != input.endIndex {
let nextChar = getNextChar(input, nextIndex: nextIndex)
switch nextChar {
case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
value += nextChar
default:
return (value, nextIndex)
}
nextIndex++
}
return (value, nextIndex)
}
func readSymbol(input: String, startIndex: String.Index) -> (token: String?, nextIndex: String.Index) {
var token = ""
var nextIndex = startIndex
while nextIndex != input.endIndex {
let nextChar = getNextChar(input, nextIndex: nextIndex)
switch nextChar {
case " ", ")":
return (token, nextIndex)
default :
token += nextChar
}
nextIndex++
}
return (token, nextIndex)
}
func parse(input: String) -> Expression? {
let tokens = tokenize(input)
return parseTokens(tokens, startIndex: tokens.startIndex, endIndex: tokens.endIndex).expression
}
func parseTokens(tokens: [String], startIndex: Int, endIndex: Int) -> (expression: Expression, lastIndex: Int) {
switch tokens[startIndex] {
case "(":
return readTillListEnd(tokens, startIndex: startIndex + 1, endIndex: endIndex)
case "+", "*", "-", "/", "%", "<", ">", "=", "\\":
return (Symbol(name: tokens[startIndex]), startIndex)
default:
let token = tokens[startIndex]
switch token.substringToIndex(token.startIndex.successor()) {
case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
return (Number(value: Int(tokens[startIndex])!), startIndex)
default:
return (Symbol(name: tokens[startIndex]), startIndex)
}
}
}
func readTillListEnd(tokens: [String], startIndex: Int, endIndex: Int) -> (expression: Expression, lastIndex: Int) {
var elements: [Expression] = []
var nextIndex = startIndex
while nextIndex < endIndex && tokens[nextIndex] != ")" {
let parsed = parseTokens(tokens, startIndex: nextIndex, endIndex: endIndex)
elements += [parsed.expression]
nextIndex = parsed.lastIndex + 1
}
return (List(es: elements), nextIndex)
}
func eval(expression: Expression) -> Expression {
return evalr(expression, env: rootEnv)
}
func nv /* number value */ (v: Expression) -> Int {
return (v as! Number).value
}
func nvs /* number values */ (es: [Expression], env: Environment) -> [Int] {
return es[1..<es.count].map({self.nv(self.evalr($0, env: env))})
}
func narithmetic(ns: [Int], op: (Int, Int) -> Int)
-> Number { return Number(value: ns[1..<ns.count].reduce(ns[0], combine: op))
}
func ncompare(ns: [Int], op: (Int, Int) -> Bool) -> Boolean {
return Boolean(value: op(ns[0], ns[1]))
}
func bindParam(params: List, args: [Expression], outer: Environment) -> Environment {
var env = Environment(outer: outer)
for (idx, param) in params.es.enumerate() {
env.add(param as! Symbol, expression: evalr(args[idx], env: env))
}
return env
}
func evalr(expression: Expression, env: Environment) -> Expression {
switch expression {
case let x where x is Procedure:
return evalr((x as! Procedure).body, env:env)
case let x where x is Symbol:
return (env.lookup(x as! Symbol) != nil) ? env.lookup(x as! Symbol)! : expression
case let x where x is List:
let l = x as! List
switch (l.es[0] as! Symbol).name {
case "b":
return narithmetic(nvs(l.es, env: env)){ $0 >> $1 }
case "|":
return narithmetic(nvs(l.es, env: env)){ $0 | $1 }
case "&":
return narithmetic(nvs(l.es, env: env)){ $0 & $1 }
case "+":
return narithmetic(nvs(l.es, env: env)){ $0 + $1 }
case "-":
return narithmetic(nvs(l.es, env: env)){ $0 - $1 }
case "*":
return narithmetic(nvs(l.es, env: env)){ $0 * $1 }
case "/":
return narithmetic(nvs(l.es, env: env)){ $0 / $1 }
case "%":
return narithmetic(nvs(l.es, env: env)){ $0 % $1 }
case "<":
return ncompare(nvs(l.es, env: env)){ $0 < $1 }
case ">":
return ncompare(nvs(l.es, env: env)){ $0 > $1 }
case "=":
return ncompare(nvs(l.es, env: env)){ $0 == $1 }
case "let":
var localEnv = Environment(outer: env)
var isName = true
var name: Symbol? = nil
for name_or_value in (l.es[1] as! List).es {
if isName {
name = (name_or_value as! Symbol)
} else {
localEnv.add(name!, expression: evalr(name_or_value, env: env))
}
isName = !isName
}
return evalr(l.es[2], env: localEnv)
case "map":
let proc = evalr(l.es[2], env: env) as! Procedure
let targets = evalr(l.es[1] as! List, env: env) as! List
return List(es: targets.es.map {
self.evalr(proc, env: self.bindParam(proc.params, args: [$0], outer: env))
})
case "defun":
env.add(l.es[1] as! Symbol, expression: Procedure(params: l.es[2] as! List, body: l.es[3] as! List, lexicalEnv: env))
return Nil()
case "if":
return evalr(l.es[(evalr(l.es[1], env: env) as! Boolean).value ? 2 : 3], env: env)
case "\\":
return Procedure(params: l.es[1] as! List, body: l.es[2] as! List, lexicalEnv: env)
case "quote":
return List(es: (l.es[1] as! List).es)
case "cons":
let car = evalr(l.es[1], env: env)
let cdr = (evalr(l.es[2], env: env) as! List).es
return List(es: [car] + cdr)
case "list":
return List(es: l.es[1..<l.es.count].map{ $0 }) // map is used to cnvert Slice to Array
case "first":
return (l.es[1] as! List).es[0]
case "rest":
var src = l.es[1] as! List
return List(es: src.es[1..<src.es.count].map{ $0 }) // map is used to cnvert Slice to Array
case let function where (env.lookup(l.es[0] as! Symbol) != nil):
let proc = env.lookup(l.es[0] as! Symbol) as! Procedure
return evalr(proc, env: bindParam(proc.params, args: l.es[1..<l.es.count].map{ $0 }, outer: env))
default: return Nil()
}
default: return expression
}
}
}
//func sample1(t: Int) -> Float {
// return Float(t * ((t>>12|t>>8)&63&t>>4))
//}
//
//func generateBuffer(frequency: Int, duration: Int, fn: (Int) -> Float) -> AVAudioPCMBuffer {
// let bufferSize = AVAudioFrameCount(frequency * duration)
// var buffer = AVAudioPCMBuffer(PCMFormat: player.outputFormatForBus(0), frameCapacity: bufferSize)
// buffer.frameLength = bufferSize
//
// for var i = 0; i < Int(bufferSize); i += Int(mixer.outputFormatForBus(0).channelCount) {
// buffer.floatChannelData.memory[i] = fn(i)
// }
//
// return buffer
//}
let evaluator = Evaluator()
//let sample1 = "t * ((t>>12|t>>8)&63&t>>4))"
//let sample1 = "(* t (& (| (b t 12) (b t 8)) 63 (b t 4)))"
let sample1 = Process.arguments[1]
func generateBuffer(frequency: Int, duration: Int, code: String) -> AVAudioPCMBuffer {
let bufferSize = AVAudioFrameCount(frequency * duration)
var buffer = AVAudioPCMBuffer(PCMFormat: player.outputFormatForBus(0), frameCapacity: bufferSize)
buffer.frameLength = bufferSize
for var i = 0; i < Int(bufferSize); i += Int(mixer.outputFormatForBus(0).channelCount) {
let input = "(let (t \(i)) \(code))"
let expression = evaluator.parse(input)
let evaluated = evaluator.eval(expression!)
buffer.floatChannelData.memory[i] = Float(evaluated.toString())!
}
return buffer
}
do {
try engine.start()
} catch {
// what do I have to put here?
}
print("Play")
player.play()
//player.scheduleBuffer(generateBuffer(8000, duration: 10, fn: sample1), atTime: nil, options: .Loops, completionHandler: {print("Done")})
player.scheduleBuffer(generateBuffer(8000, duration: 10, code: sample1), atTime: nil, options: .Loops, completionHandler: {print("Done")})
while true {
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment