workable smnb compilation

This commit is contained in:
aprilnightk 2025-09-10 21:00:37 +03:00
parent 04690d9df8
commit 0d33e1e022
8 changed files with 618 additions and 7 deletions

2
.gitignore vendored
View file

@ -9,6 +9,8 @@ sonnum.prj
node_modules/
dist/
*.pyc
*.snm
*.snmb
err*.txt
log*.txt
err.txt

59
pysonnum/activity.py Normal file
View file

@ -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''

161
pysonnum/compiler.py Normal file
View file

@ -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')

48
pysonnum/instruction.py Normal file
View file

@ -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])

47
pysonnum/sonnum.py Normal file
View file

@ -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

View file

@ -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| {

View file

@ -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();

View file

@ -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);