Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

output shapers with arbitrary scales #292

Merged
merged 2 commits into from Jun 20, 2020
Merged

output shapers with arbitrary scales #292

merged 2 commits into from Jun 20, 2020

Conversation

@trentgill
Copy link
Collaborator

@trentgill trentgill commented Mar 26, 2020

Fixes #68

The idea here is to implement quantization of the ASL outputs, so that slopes can be seen as sequences of notes. With this extension, the ASL language takes on a new role as describing the 'shape' of melodies (or harmonies), and the absolute-shaper acts as a dynamic quantizer. This is not the final version, but an initial POC to explore what it feels like to play with slopes as melody-shapes.

I'm posting this now because I'm not by my synthesizer very much during the lockdown, and I'd love to see how anyone feels about it in practice. The driving idea behind the functionality is to provide a new way of writing melodies - as chains of slopes - so it's probably a little strange & requires thinking outside traditional compositional workflows.

General usage:

 -- no quantizing / shaping (same as current behaviour)
output[1].scale( 'none' ) -- set by default

-- 12TET ionian
output[1].scale{ 0,2,4,5,7,9,11 }

-- 12TET chromatic
output[1].scale{ 0,1,2,3,4,5,6,7,8,9,10,11 }

-- 19TET pentatonic
output[1].scale( {0,3,6,11,14}, 19 ) -- 2nd argument is nTET

-- 12TET pentatonic with 1.2V / octave
output[1].scale( {0,2,4,7,9}, 12, 1.2 )

-- shortcut for 12TET chromatic
output[1].scale()
output[1].scale{} -- both work

-- easy to make tables (or borrow from norns) of different scales
lydian = {0,2,4,6,7,9,11}
output[1].scale( lydian ) -- defaults to 12TET, 1V/8ve

Limitations:

  • equal-temperament only (though can be n-TET!)
  • scale lists are limited to 24 selections
  • root-note is always 0V output
  • the 'scaling' parameter doesn't convert types, just changes what is expected (so 1.2v/8 requires input values at 1.2v/8ve).

There are a few areas that need design consideration:

  • naming (currently called output.scale in lua, though functionality isn't limited to 'scales'). Should it be called output.quantize?
  • How to shift the 'root note'? This is particularly important for just intonation where scales are not directly transposable without harmonic implications.

The library is pretty basically implemented, which means it can do some strange things that are not like 'quantizers', but more like creative melody manipulators:

  • scales are not ordered, so they can arbitrarily jumble the scale. eg: {11,9,7,5,4,2,0} would result in descending major scales transposed up/down by whole volts.
  • scales are not range-limited so it's also possible to map to notes 'outside' the input range. eg: {0,24,12,-12} maps the [0,1)V range to a sequence of 4 different octaves.

And some features forthcoming:

  • Just Intonation support: .scale( {table-of-ratios}, 'just' )
  • Considering a 'weighting' list (or note/weighting tuples) to lean on eg. chord tones see @whimsical-sam s shapers lib

some other usage examples than plain output quantizing

-- input->quantizer with harmony
function init()
  input[1].mode('stream', 0.002)
  output[1].slew = 0
  output[2].slew = 0
  output[1].scale{0,2,4,5,7,9,11}
  output[2].scale{0,2,4,7,9}
end

input[1].stream = function( val )
  output[1].volts = val
  output[2].volts = val - 1.0 -- sub-octave in pentatonic
end
-- continuous control of equal-temperament divisions
-- input[1]: dynamic temperament input
-- output[1]: LFO sweeping with dynamic temperament
function init()
  input[1].mode('stream', 0.02) -- update nTET every 20ms
  output[1].scale( {}, 12 )
  output[1]( lfo() )
end

input[1].stream = function( val )
  -- would be better with expo shaping but probably too CPU intensive?
  val = val * 20 -- 1 to 200 TET
  val = (val <= 0.001) and 0.001 or val -- limit negative vals
  output[1].scale( {}, val ) -- NOTE: temperament need not be an integer
end
-- changing scale inside of an ASL
-- NOTE: this uses an underdocumented feature of ASL that it can call arbitrary functions, and will immediately proceed to the next slope

-- note how the 3rd scale reorders the notes
my_scales = { {0,2,4,5,7,9,11}, {0,2,3,6,7}, {0,4,7,2} }
local current_scale = 0
function next_scale( channel )
  current_scale = (current_scale % 3) + 1
  output[channel].scale( my_scales[current_scale] )
end

-- NOTE: 
output[1].action =
loop{ to(0,0)
    , to(2,1)
    , to(1,0)
    , to(1.5,1)
    , next_scale(1) -- will execute immediately then proceed (hence loop)
    }
lib/ashapes.c Outdated Show resolved Hide resolved
@tehn
tehn approved these changes Apr 3, 2020
@dndrks
dndrks approved these changes Apr 4, 2020
Copy link
Collaborator

@dndrks dndrks left a comment

have loved using this -- no noticeable issues in testing.

@trentgill trentgill removed the do not merge label Apr 25, 2020
@trentgill trentgill changed the base branch from master to main Jun 18, 2020
@trentgill trentgill merged commit d1ff04f into main Jun 20, 2020
1 check passed
1 check passed
build
Details
@trentgill trentgill deleted the ashapers branch Jun 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

3 participants