diff --git a/.gitignore b/.gitignore index bf339a6..9896dfb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ sonnum.prj node_modules/ dist/ *.pyc +*.snm +*.snmb err*.txt log*.txt err.txt diff --git a/pysonnum/activity.py b/pysonnum/activity.py new file mode 100644 index 0000000..f6c50d0 --- /dev/null +++ b/pysonnum/activity.py @@ -0,0 +1,59 @@ +import struct + +class Activity: + + OPCODES = { + + 'create': 0, + 'wire': 1, + 'air': 2, + 'endtick': 3, + 'relay': 4, + 'reset': 5, + 'setfreq': 6, + 'setpos': 7, + + } + + def __repr__(self): + + return f'{self.name.rjust(20)} | {self.OPCODES[self.name]} {self.tick_start} {self.tick_end} {self.src_node.order if self.src_node else 0} {self.trg_node.order if self.trg_node else 0} '+' '.join(str(op) for op in self.operands) + + def __init__(self, sonnum, name, tick_start, tick_end, src_node, trg_node, operands): + + self.sonnum = sonnum + + self.name = name + self.tick_start = tick_start + self.tick_end = tick_end + self.src_node = src_node + self.trg_node = trg_node + self.operands = operands + + def to_bytes(self): + + if self.name in self.OPCODES: + + b_opcode = self.OPCODES[self.name].to_bytes(2, byteorder="big") + b_tick_start = int(self.tick_start).to_bytes(4, byteorder="big") + b_tick_end = int(self.tick_end).to_bytes(4, byteorder="big") + + if not self.src_node: + b_src_node = b'\x00' * 2 + else: + b_src_node = self.src_node.order.to_bytes(2, byteorder="big") + + if not self.trg_node: + b_trg_node = b'\x00' * 2 + else: + b_trg_node = self.trg_node.order.to_bytes(2, byteorder="big") + + b_operands = [struct.pack('>d', 0.0)] * 6 + + for i in range(0, 6): + if len(self.operands) > i: b_operands[i] = struct.pack('>d', self.operands[i]) + + return b_opcode + b_tick_start + b_tick_end + b_src_node + b_trg_node + b''.join(b_operands) + + else: + return b'' \ No newline at end of file diff --git a/pysonnum/compiler.py b/pysonnum/compiler.py new file mode 100644 index 0000000..ea10824 --- /dev/null +++ b/pysonnum/compiler.py @@ -0,0 +1,161 @@ +from sonnum import Sonnum +from activity import Activity +from instruction import * + + +class SonnumCompiler: + + def __init__(self): + + self.sonnum = Sonnum() # {order: name} + self.activities = [] + + def list_activities(self): + + print('ACTIVITY LISTING') + for activity in self.activities: + print(activity) + + def list_bytecode(self): + + print('BYTECODE LISTING') + for activity in self.activities: + print(list(activity.to_bytes())) + + def add_activity(self, name, tick_start, tick_end, src_node, trg_node, operands): + + a = Activity(self.sonnum, name, tick_start, tick_end, src_node, trg_node, operands) + self.activities.append(a) + + def sanitize_operand(self, operand): + + try: + return str(float(operand)) + except: + return str(operand) + + def transpile_snm_to_py(self, snm_src): + + py_src = [] + + for ln in snm_src.split('\n'): + + tablevel = 0 + + while ln.startswith('\t'): + + tablevel += 1 + ln = ln[1:] + + if ln.startswith(';'): + + if ln.endswith('*'): + + name = ln[1:-1] + ln = f"i_create_simple(self, self.sonnum, '{name}')" + + elif ln.endswith('!'): + + ticklen = int(ln[1:-1]) + ln = f"i_end_tick(self, self.sonnum, {ticklen})" + + elif ln.endswith('@'): + + name = ln[1:-1] + ln = f"i_create_relay(self, self.sonnum, '{name}')" + + elif '=>' in ln: + name_src, name_trg = ln[1:].split('=>') + ln = f"i_wire(self, self.sonnum, '{name_src}', '{name_trg}')" + + elif '->' in ln: + name_src, name_trg = ln[1:].split('->') + ln = f"i_air(self, self.sonnum, '{name_src}', '{name_trg}')" + + else: + print(ln) + lst = ln[1:].split(' ') + src_node_name = lst.pop(0) + instr = lst.pop(0) + operands = [self.sanitize_operand(op) for op in lst] + ln = f"i_{instr}(self, self.sonnum, '{src_node_name}', "+', '.join(operands)+")" + + ln = tablevel*'\t' + ln + py_src.append(ln) + + else: + ln = tablevel*'\t' + ln + py_src.append(ln) + + return '\n'.join(py_src) + + def run_transpiled_code(self, py_src): + + exec(py_src) + + def sort_activities(self): + + new = [] + + to_delete = [] + for activity in self.activities: + + if activity.name == 'endtick': + new.append(activity) + to_delete.append(activity) + + for activity in to_delete: + self.activities.remove(activity) + + to_delete = [] + for activity in self.activities: + + if activity.name == 'create': + new.append(activity) + to_delete.append(activity) + + for activity in to_delete: + self.activities.remove(activity) + + to_delete = [] + for activity in self.activities: + + if activity.name in ('wire','air'): + new.append(activity) + to_delete.append(activity) + + for activity in to_delete: + self.activities.remove(activity) + + self.activities.sort(key=lambda x: x.tick_start, reverse = False) + new.extend(self.activities) + self.activities = new + + def compile_to_smnb(self, snm_src, fn): + + py_src = self.transpile_snm_to_py(snm_src) + self.run_transpiled_code(py_src) + self.sort_activities() + self.list_activities() + + bytecode = [] + + for activity in self.activities: + bytecode.append(activity.to_bytes()) + + with open(fn, 'wb') as fl: + fl.write(b''.join(bytecode)) + + +TEST = """;44100! +;left_mic@ +;right_mic@ +;synth* + +;synth setfreq 0 440 0.9 + +;synth=>left_mic +;synth=>right_mic""" + +C = SonnumCompiler() +snm = C.compile_to_smnb(TEST, '../zigsonnum/test.snmb') \ No newline at end of file diff --git a/pysonnum/instruction.py b/pysonnum/instruction.py new file mode 100644 index 0000000..04d5bdd --- /dev/null +++ b/pysonnum/instruction.py @@ -0,0 +1,48 @@ +# Here, s stands for the sonnum object, +# c stands for compiler object + +def i_create_simple(c, s, name): + # ;name* + + node = s.add_node(name) + c.add_activity('create', 0, 0, node, None, []) + +def i_create_relay(c, s, name): + # ;name@ + + node = s.add_node(name) + c.add_activity('create', 0, 0, node, None, []) + c.add_activity('relay', 0, s.g('endtick'), node, None, []) + +def i_end_tick(c, s, endtick): + # ;endtick! + c.add_activity('endtick', 0, endtick, None, None, []) + s.s('endtick', endtick) + +def i_wire(c, s, src_name, trg_name): + # ;name=>name + + src_node = s.node_by_name(src_name) + trg_node = s.node_by_name(trg_name) + + if src_node and trg_node: + c.add_activity('wire', 0, 0, src_node, trg_node, []) + +def i_air(c, s, src_name, trg_name): + # ;name->name + + src_node = s.node_by_name(src_name) + trg_node = s.node_by_name(trg_name) + + if src_node and trg_node: + c.add_activity('air', 0, 0, src_node, trg_node, []) + +def i_pos(c, s, node_name, tick, x, y, z): + + node = s.node_by_name(node_name) + c.add_activity('setpos', tick, tick, node, None, [x, y, z]) + +def i_setfreq(c, s, node_name, tick, freq, r_amp): + + node = s.node_by_name(node_name) + c.add_activity('setfreq', tick, tick, node, None, [freq, r_amp]) \ No newline at end of file diff --git a/pysonnum/sonnum.py b/pysonnum/sonnum.py new file mode 100644 index 0000000..916464a --- /dev/null +++ b/pysonnum/sonnum.py @@ -0,0 +1,47 @@ +class SoundNode: + + def __init__(self, order, name): + + self.order = order + self.name = name + self.properties = dict() # {k: v} + + def __repr__(self): + return f'{self.order}:{self.name}' + + def g(self, k): + return self.properties.get(k) + + def s(self, k, v): + self.properties[k] = v + +class Sonnum: + + def __init__(self): + + self.nodes = dict() # {order: name} + self.properties = dict() # {k: v} + + def add_node(self, name): + + order = len(self.nodes) + self.nodes[name] = SoundNode(order, name) + + return self.nodes[name] + + def node_by_name(self, name): + + for order, node in self.nodes.items(): + + if node.name == name: + return node + + def node_by_order(self, order): + + return self.nodes.get(order) + + def g(self, k): + return self.properties.get(k) + + def s(self, k, v): + self.properties[k] = v \ No newline at end of file diff --git a/zigsonnum/activity.zig b/zigsonnum/activity.zig index d3b7465..d9a00db 100644 --- a/zigsonnum/activity.zig +++ b/zigsonnum/activity.zig @@ -1,5 +1,6 @@ const std = @import("std"); const print = std.debug.print; +const Allocator = std.mem.Allocator; const SoundNode = @import("soundnode.zig").SoundNode; const FreqAmpList = @import("freqamp.zig").FreqAmpList; @@ -11,12 +12,27 @@ pub const Activity = struct { soundnode: *SoundNode, operands: [6]f64 = std.mem.zeroes([6]f64), + pub fn create(allocator: Allocator, start_tick: u32, end_tick: u32, opcode: u16, soundnode: *SoundNode, operands: [6]f64) !*Activity { + + const a = try allocator.create(Activity); + a.* = .{ + .start_tick = start_tick, + .end_tick = end_tick, + .opcode = opcode, + .soundnode = soundnode, + .operands = operands, + }; + + return a; + + } + pub fn do(self: *Activity) !void { switch (self.opcode) { - 0 => { try self.reset(); }, - 1 => { try self.setfreq(); }, - 2 => { try self.relay(); }, - 3 => { try self.slide_freq(); }, + 4 => { try self.relay(); }, + 5 => { try self.reset(); }, + 6 => { try self.setfreq(); }, + 7 => { try self.slide_freq(); }, else => {}, } } @@ -53,7 +69,7 @@ pub const Activity = struct { } pub fn relay(self: *Activity) !void { - + try self.soundnode.fab.reset(); const current_tick: u32 = self.soundnode.fab.current_tick; const current_index: u32 = self.soundnode.fab.current_index; @@ -61,7 +77,7 @@ pub const Activity = struct { var current_fal: *FreqAmpList = undefined; for (self.soundnode.wire_in.items, 0..) |wired_sn, i| { - + current_fal = wired_sn.fab.fal_array[current_index]; for (current_fal.arraylist.items) |fa| { diff --git a/zigsonnum/sonnum.zig b/zigsonnum/sonnum.zig index 72f09d9..b359970 100644 --- a/zigsonnum/sonnum.zig +++ b/zigsonnum/sonnum.zig @@ -16,8 +16,264 @@ pub fn singleSineTick(st: *SoundSettings, freq: f64, t: u32, phase: f64) f64 { return math.sin(st.sine_multiplier * freq * ft - phase * utility.tau); } - pub fn main() !void { + + const allocator = std.heap.c_allocator; + + // Initializing sound settings + var settings: SoundSettings = SoundSettings{}; + + // Creating a list of soundnodes + var soundnodes = ArrayList(*SoundNode).init(allocator); + defer soundnodes.deinit(); + + // Determining length of resulting audio in ticks + const start_tick: u32 = 0; + var end_tick: u32 = 44100 * 10; + + // Loading the input binary code, preparing registers + const file = try std.fs.cwd().openFile("test.snmb", .{}); + defer file.close(); + + const stat = try file.stat(); + const buf: []u8 = try file.readToEndAlloc(allocator, stat.size); + + var cursor: u32 = 0; + + var opcode: u16 = 0; + var tick_start: u32 = 0; + var tick_end: u32 = 0; + var src_node: u16 = 0; + var trg_node: u16 = 0; + var op1: f64 = 0; + var op2: f64 = 0; + var op3: f64 = 0; + var op4: f64 = 0; + var op5: f64 = 0; + var op6: f64 = 0; + + // Preparing to write wav data to stdout + const stdout_file = std.io.getStdOut().writer(); + var bw = std.io.bufferedWriter(stdout_file); + const stdout = bw.writer(); + + // Writing WAV header + + try stdout.writeAll("RIFF"); + + try stdout.writeInt( + u32, + 36 + (end_tick * 2 * settings.sample_width), + Endian.little, + ); + + try stdout.writeAll("WAVE"); + try stdout.writeAll("fmt "); + + try stdout.writeInt(u32, 16, Endian.little); + try stdout.writeInt(u16, 1, Endian.little); + try stdout.writeInt(u16, 2, Endian.little); + try stdout.writeInt(u32, settings.sample_rate, Endian.little); + + const block_align: u16 = @intCast(2 * settings.sample_width); + + try stdout.writeInt(u32, @as(u32, block_align) * settings.sample_rate, Endian.little); + try stdout.writeInt(u16, block_align, Endian.little); + try stdout.writeInt(u16, settings.bit_depth, Endian.little); + + try stdout.writeAll("data"); + try stdout.writeInt( + u32, + end_tick * 2 * settings.sample_width, + Endian.little, + ); + + // Setting up tick iteration + var tick: u32 = start_tick; + var amp: f64 = 0; + var sample: i24 = 0.0; + var left: *SoundNode = undefined; + var right: *SoundNode = undefined; + + while (tick < end_tick) { + + while (cursor < buf.len) { + + opcode = std.mem.readVarInt(u16, buf[cursor..cursor+2], .big); + //print("----\nOPCODE {d} :: {any}\n", .{opcode, buf[cursor..cursor+2]}); + cursor += 2; + + tick_start = std.mem.readVarInt(u32, buf[cursor..cursor+4], .big); + //print("TICKSTART {d} :: {any}\n", .{tick_start, buf[cursor..cursor+4]}); + cursor += 4; + + if (tick_start > tick) { + cursor -= 6; + break; + + } else { + + tick_end = std.mem.readVarInt(u32, buf[cursor..cursor+4], .big); + //print("TICKEND {d} :: {any}\n", .{tick_end, buf[cursor..cursor+4]}); + cursor += 4; + + src_node = std.mem.readVarInt(u16, buf[cursor..cursor+2], .big); + //print("SRCNODE {d} :: {any}\n", .{src_node, buf[cursor..cursor+2]}); + cursor += 2; + + trg_node = std.mem.readVarInt(u16, buf[cursor..cursor+2], .big); + //print("TRGNODE {d} :: {any}\n", .{trg_node, buf[cursor..cursor+2]}); + cursor += 2; + + op1 = @bitCast(std.mem.readVarInt(u64, buf[cursor..cursor+8], .big)); + //print("OP1 {d} :: {any}\n", .{op1, buf[cursor..cursor+8]}); + cursor += 8; + + op2 = @bitCast(std.mem.readVarInt(u64, buf[cursor..cursor+8], .big)); + //print("OP2 {d} :: {any}\n", .{op2, buf[cursor..cursor+8]}); + cursor += 8; + + op3 = @bitCast(std.mem.readVarInt(u64, buf[cursor..cursor+8], .big)); + //print("OP3 {d} :: {any}\n", .{op3, buf[cursor..cursor+8]}); + cursor += 8; + + op4 = @bitCast(std.mem.readVarInt(u64, buf[cursor..cursor+8], .big)); + //print("OP4 {d} :: {any}\n", .{op4, buf[cursor..cursor+8]}); + cursor += 8; + + op5 = @bitCast(std.mem.readVarInt(u64, buf[cursor..cursor+8], .big)); + //print("OP5 {d} :: {any}\n", .{op5, buf[cursor..cursor+8]}); + cursor += 8; + + op6 = @bitCast(std.mem.readVarInt(u64, buf[cursor..cursor+8], .big)); + //print("OP6 {d} :: {any}\n", .{op6, buf[cursor..cursor+8]}); + cursor += 8; + + // Executing opcodes + + switch (opcode) { + + 0 => { + const nodename = try std.fmt.allocPrint(allocator, "{d}", .{src_node}); + + const sn = try SoundNode.create(allocator, nodename); + //print("Added node {s} at tick {d}\n", .{nodename, tick}); + try soundnodes.append(sn); + }, + + 1 => { + const src = soundnodes.items[src_node]; + const trg = soundnodes.items[trg_node]; + //print("Wired nodes at tick {d}\n", .{tick}); + + try trg.wire_in.append(src); + }, + + 2 => { + const src = soundnodes.items[src_node]; + const trg = soundnodes.items[trg_node]; + + //print("Aired nodes at tick {d}\n", .{tick}); + try trg.air_in.append(src); + }, + + 3 => { + end_tick = tick_end; + //print("End tick set to {d}\n", .{end_tick}); + }, + + else => { + + const src = soundnodes.items[src_node]; + const a = try Activity.create(allocator, tick_start, tick_end, opcode, src, [6]f64{op1, op2, op3, op4, op5, op6}); + + //print("Set activity {d} for node {d} s at tick {d}\n", .{opcode, src_node, tick}); + try src.activities.append(a); + + }, + + } + + } + + } + + //All but left and right + + for (soundnodes.items, 2..) |soundnode, i| { + + for (soundnode.activities.items, 0..) |activity, j| { + + if (tick <= activity.end_tick and tick >= activity.start_tick) { + try activity.do(); + } + + _ = j; + + } + + + soundnode.fab.increment_tick(); + _ = i; + } + + //Left and right + + for (soundnodes.items, 0..2) |soundnode, i| { + + for (soundnode.activities.items, 0..) |activity, j| { + + if (tick <= activity.end_tick and tick >= activity.start_tick) { + try activity.do(); + } + + _ = j; + + } + + + soundnode.fab.increment_tick(); + _ = i; + } + + //Calculating and writing output amps + + amp = 0; + + left = soundnodes.items[0]; + right = soundnodes.items[1]; + + const current_index = left.fab.current_index; + + const current_fal_left = left.fab.fal_array[current_index]; + + for (current_fal_left.arraylist.items) |fa| { + amp += fa.r_amp * singleSineTick(&settings, fa.freq, tick, fa.phase); + } + + sample = @intFromFloat(amp * @as(f64, @floatFromInt(settings.max_amp))); + try stdout.writeInt(i24, sample, Endian.little); + + amp = 0; + + const current_fal_right = right.fab.fal_array[current_index]; + + for (current_fal_right.arraylist.items) |fa| { + amp += fa.r_amp * singleSineTick(&settings, fa.freq, tick, fa.phase); + } + + sample = @intFromFloat(amp * @as(f64, @floatFromInt(settings.max_amp))); + try stdout.writeInt(i24, sample, Endian.little); + + tick += 1; + + } + + try bw.flush(); + +} + +pub fn nnnmain() !void { //var gpa = std.heap.GeneralPurposeAllocator(.{}){}; //const allocator = gpa.allocator(); diff --git a/zigsonnum/soundnode.zig b/zigsonnum/soundnode.zig index d43cf07..a8ffb21 100644 --- a/zigsonnum/soundnode.zig +++ b/zigsonnum/soundnode.zig @@ -30,6 +30,28 @@ pub const SoundNode = struct { fab: FreqAmpBuffer, + pub fn create(allocator: Allocator, name: []const u8) !*SoundNode { + + const air_in = ArrayList(*SoundNode).init(allocator); + const wire_in = ArrayList(*SoundNode).init(allocator); + const activities = ArrayList(*Activity).init(allocator); + const fab = try FreqAmpBuffer.init(allocator); + + const sn = try allocator.create(SoundNode); + + sn.* = .{ + .allocator = allocator, + .name = name, + .air_in = air_in, + .wire_in = wire_in, + .activities = activities, + .fab = fab, + }; + + return sn; + + } + pub fn init(allocator: Allocator, name: []const u8) !SoundNode { const air_in = ArrayList(*SoundNode).init(allocator);