Created
October 8, 2015 20:27
-
-
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)))"`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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