Skip to content

Commit

Permalink
Merge pull request #17 from cachilders/feat-eng-env
Browse files Browse the repository at this point in the history
Feat eng env
  • Loading branch information
cachilders authored Mar 30, 2024
2 parents 56ca383 + 377b6f8 commit e9fb465
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 20 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ Every locked door has a matching key, and every floor has a final door that desc

While events are recorded in a breadth-based loop, playback is depth-first. At each step in the playback loop, which begins advancing when the splash screen is dismissed, all available modulation events within that loop step are played before advancing to the next step. 1:1, 1:2, 1:3, 2:1, 2:2, 3:1, and so on.

Adjust the `Drone Base` note, `Loop Length`, and `Max Step Depth`, along with various `Asterion (Engine)` characteristics in the params menu. Note that in the `Asterion (Engine)` params, only `amplitude`, `hz` and `noise amplitude` will stay where you put them due to the nature of the script. Also note that `attack`, `decay`, and `release` are both unused in the script and currently unimplemented in the engine. That's a big TODO, buddy.
Adjust the `Drone Base` note, `Loop Length`, and `Max Step Depth`, along with various `Asterion (Engine)` characteristics in the params menu. Note that in the `Asterion (Engine)` params, only `amplitude`, `hz` and `noise amplitude` will stay where you put them due to the nature of the script.

Parameter adjustments are not recorded to the loop. Only events from gameplay are sequenced.
In addition to the sequenced drone, programmed by exploring the labyrinth, you can play accent notes in scale from the drone base using your (typing) keyboard's number keys. These will play the ten notes in the `scale` of your selection from the `Asterion` parameter group. You may also adjust the fixed `velocity` of accent notes played. Note that `attack`, `decay`, `sustain` and `release` within the `Asterion (Engine)` params apply exclusively to the Accent feature in this script and have not effect on the drone.

Parameter adjustments and accent notes are not recorded to the loop. Only events from gameplay are sequenced.

A word of WARNING: Care has been take to tame the drone, but it has some intimidating qualities that should be explored at moderate volumes as you begin to wander the labyrinth. I moved from testing it on a regular stereo speaker to a bass amp and nearly had a heart attack.

Expand Down
7 changes: 6 additions & 1 deletion asterion.lua
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function init()
parameters = Parameters:new()
pouch = Pouch:new()
labyrinth:init()
parameters.init()
parameters:init()
redraw()
end

Expand All @@ -88,6 +88,11 @@ function keyboard.code(k, z)
else
commence()
end
else
local note_input = tonumber(k)
if note_input then
minstrel:accent_song(note_input, parameters:get('scale'))
end
end
end

Expand Down
66 changes: 56 additions & 10 deletions lib/engine/Engine_Asterion.sc
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@

// Inherit methods from CroneEngine
Engine_Asterion : CroneEngine {
var <synth, params;
var <drone, params, voice=0, voice_next, voice_on, voice_off, <voices;

*new { arg context, doneCallback;
^super.new(context, doneCallback);
}

alloc {
// TODO Expand with trigger and env
SynthDef(\Asterion, {
arg amp=0.0, attack=0.1, breadth=0.1, decay=0.1, depth=0.1, gloom=0.01, hz=65.41, noise_amp=0.5, release=0.3, shine=0.1, t_gate;
var band_hz, band_width, delay, delay_buffer, filtered, high, low, mid, mix, noise, noise_cutoff, verb;
arg amp=0.0, attack=0.1, breadth=0.1, decay=0.1, depth=0.1, gate=1, gloom=0.01, hz=65.41, noise_amp=0.5, release=0.3, shine=0.1, sustain=1;
var band_hz, band_width, delay, delay_buffer, env, filtered, high, low, mid, mix, noise, noise_cutoff, verb;
env = EnvGen.kr(Env.adsr(attack, decay, sustain, release), gate);
delay_buffer = Buffer.alloc(context.server, 360);
band_hz = hz - ((hz / 2) * depth);
band_width = 3 * breadth;
Expand All @@ -26,36 +26,82 @@ Engine_Asterion : CroneEngine {
verb = FreeVerb.ar({Mix.ar(high)}, shine, breadth, depth);
mix = LeakDC.ar(Splay.ar([low, mid, high, noise, delay, verb]));
filtered = BBandStop.ar(mix, band_hz, band_width);
Out.ar(0, Limiter.ar(filtered * amp));
Out.ar(0, Limiter.ar(filtered * amp * env));
}).add;

context.server.sync;

synth = Synth(\Asterion, target:context.server);
drone = Synth(\Asterion, target:context.server);

params = Dictionary.newFrom([
\amp: 0.5,
\attack: 0.1,
\breadth: 0.1,
\decay: 0.1,
\depth: 0.1,
\gate: 1,
\gloom: 0.1,
\hz: 130.813,
\noise_amp: 0.5,
\release: 0.3,
\shine: 0.5;
\shine: 0.5,
\sustain: 1;
]);

params.keysDo({ arg key;
this.addCommand(key, "f", { arg msg;
synth.set(key, msg[1])
drone.set(key, msg[1])
});
});

this.addCommand(\note, "i", { arg msg; synth.set(\hz, msg[1].midicps)});
// note commands select or cycle through five voices in addition to the drone,
// which are controlled with command arguments.
voices = Array.fill(5, Synth(\Asterion, [\gate, 0], target:context.server));

voice_next = ({voice = (voice+1).wrap(0, 4)});

voice_on = ({
arg id, note_num, velocity, attack, decay, sustain, release;
var hz=if(note_num.notNil, {note_num.midicps}, {params[\hz]});
var amp=if(velocity.notNil, {1/127 * velocity}, {params[\amp]});

id=if(id.notNil, {id}, {var v = voice; voice_next.(); v});
attack=if(attack.notNil, {attack}, {params[\attack]});
decay=if(decay.notNil, {decay}, {params[\decay]});
sustain=if(sustain.notNil, {sustain}, {params[\sustain]});
release=if(release.notNil, {release}, {params[\release]});

voice_off.(id);
voices[id].set(\amp, amp, \hz, hz, \attack, attack, \decay, decay, \sustain, sustain, \release, release, \gate, 1)
});

voice_off = ({|i| voices[i].set(\gate, 0)});

this.addCommand(\note, "i", { arg msg; drone.set(\hz, msg[1].midicps)});

this.addCommand(\note_on, "iiiffff", {
// id, note_num, velocity, attack, decay, sustain, release
arg msg;
voice_on.(msg[1], msg[2], msg[3], msg[4], msg[5], msg[6], msg[7]);
});

this.addCommand(\note_off, "i", {arg msg;
voice_off.(msg[1])
});

// One-shot with duration argument
this.addCommand(\play_note, "iifffff", {
// note_num, velocity, duration, attack, decay, sustain, release
arg msg;
var v = voice;
voice_next.();
voice_on.(v, msg[1], msg[2], msg[4], msg[5], msg[6], msg[7]);
SystemClock.sched(msg[3], {voice_off.(v); nil});
});
}

free {
synth.free;
voices.do({arg synth; synth.free});
drone.free;
}
}
6 changes: 4 additions & 2 deletions lib/engine/asterion_engine.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ local parameters = {
{id = 'depth', name = 'depth', type = 'control', min = 0.01, max = 1, default = 0.1, formatter = function(param) return quantize_and_format(param:get()*100, 0.1, '%') end},
{id = 'gloom', name = 'gloom', type = 'control', min = 0.01, max = 1, default = 0.1, formatter = function(param) return quantize_and_format(param:get()*100, 0.1, '%') end},
{id = 'shine', name = 'shine', type = 'control', min = 0.01, max = 1, default = 0.5, formatter = function(param) return quantize_and_format(param:get()*100, 0.1, '%') end},
{id = 'gate', name = 'drone gate', type = 'number', min = 0, max = 1, default = 1, formatter = function(param) return (param:get() == 1 and 'open' or 'closed') end},
{id = 'attack', name = 'attack', type = 'control', min = 0.001, max = 17, warp = 'exp', default = 0.01, formatter = function(param) return quantize_and_format(param:get(), 0.01, ' s') end},
{id = 'decay', name = 'decay', type = 'control', min = 0.001, max = 17, warp = 'exp', default = 0.1, formatter = function(param) return quantize_and_format(param:get(), 0.01, ' s') end},
{id = 'release', name = 'release', type = 'control', min = 0.001, max = 17, warp = 'exp', default = 0.3, formatter = function(param) return quantize_and_format(param:get(), 0.01, ' s') end}
{id = 'sustain', name = 'sustain', type = 'control', min = 0.01, max = 1, default = 1, formatter = function(param) return quantize_and_format(param:get()*100, 0.1, '%') end},
{id = 'release', name = 'release', type = 'control', min = 0.001, max = 17, warp = 'exp', default = 0.3, formatter = function(param) return quantize_and_format(param:get(), 0.01, ' s') end},
}

function Asterion:add_params()
params:add_group('asterion_engine', 'ASTERION (ENGINE)', 10)
params:add_group('asterion_drone', 'ASTERION (Engine)', 12)
for i = 1, #parameters do
local parameter = parameters[i]
if parameter.type == 'control' then
Expand Down
16 changes: 15 additions & 1 deletion lib/minstrel.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local Song = include('lib/minstrel/song')
local music_util = require('musicutil')

local Minstrel = {
clock = nil,
Expand Down Expand Up @@ -26,6 +27,19 @@ function Minstrel:init()
self.clock:start()
end

function Minstrel:accent_song(i, scale)
if i == 0 then i = 10 end
engine.play_note(
scale[i],
params:get('velocity'),
self:_get_time(),
params:get('attack'),
params:get('decay'),
params:get('sustain'),
params:get('release')
)
end

function Minstrel:adjust_song()
self.song:adjust()
end
Expand All @@ -52,7 +66,7 @@ function Minstrel:_check_time()
end

function Minstrel:_get_time()
return 60 / self.tempo
return 60 / (self.tempo or params:get('clock_tempo'))
end

return Minstrel
38 changes: 34 additions & 4 deletions lib/parameters.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
local music_util = require('musicutil')
local asterion_engine = include('/lib/engine/asterion_engine')

local Parameters = {}
local SCALE_LENGTH = 10

local Parameters = {
scale = nil,
scale_type = nil,
scale_types = nil
}

function Parameters:new(options)
local instance = options or {}
Expand All @@ -10,18 +16,42 @@ function Parameters:new(options)
return instance
end

function Parameters.init()
params:add_group('asterion', 'ASTERION', 3)
function Parameters:init()
self:_set_musicutil_scale_types()
params:add_group('asterion', 'ASTERION', 6)
params:add_number('base', 'Drone Base', 0, 127, 36, function(param) return music_util.note_num_to_name(param:get(), true) end)
params:set_action('base', function(n) params:set('hz', music_util.note_num_to_freq(n)) end)
params:set_action('base', function(n) params:set('hz', music_util.note_num_to_freq(n)); self:_generate_scale() end)
params:add_number('loop_length', 'Loop Length', 8, 2048, 16)
params:add_number('step_depth', 'Max Step Depth', 1, 256, 4)
params:add_separator('accent', 'ACCENTS')
params:add_option('scale_type', 'Scale', self.scale_types, 1)
params:set_action('scale_type', function(i) self.scale_type = self.scale_types[i]; self:_generate_scale() end)
params:add_number('velocity', 'Velocity', 0, 127, 100)
asterion_engine:add_params()
end

function Parameters:get(k)
return self[k]
end

function Parameters.set_song_update(update_song_params)
params:set_action('loop_length', update_song_params)
params:set_action('step_depth', update_song_params)
end

function Parameters:_generate_scale()
local note = music_util.freq_to_note_num(params:get('hz'))
self.scale = music_util.generate_scale_of_length(note, self.scale_type, SCALE_LENGTH)
end

function Parameters:_set_musicutil_scale_types()
local scales = {}
for i = 1, #music_util.SCALES do
scales[i] = music_util.SCALES[i].name
end

self.scale_types = scales
end


return Parameters

0 comments on commit e9fb465

Please sign in to comment.