monome / crow Goto Github PK
View Code? Open in Web Editor NEWCrow speaks and listens and remembers bits of text. A scriptable USB-CV-II machine
License: GNU General Public License v3.0
Crow speaks and listens and remembers bits of text. A scriptable USB-CV-II machine
License: GNU General Public License v3.0
since switching to coroutines approach, asl no longer supports the ability for a asl:bang(false) (ie low going trigger) to jump directly to the release phase.
at present the held construct just checks if it should skip the held brace when starting it.
Lua should be able to call into the bootloader directly.
the main program must set a flash register to tell the bootloader to stay in DFU mode until it successfully receives a program.
//
There is an edge case where the DFU fails upload, but tries to launch the applicaation anyway (unsetting the bootloader bit), even though the application fails. this means the user could not re-enter bootloader mode to fix the program, effectively bricking the module.
A solution would be that the flash bit is not unset, until after the bootloader finishes, and yields to the main application, after some sequence of successful startup, then unsetting the bootloader bit (having accepted that the program is sufficiently correct).
ie:
cv(5, value)
and cv command is passed via i2c to crow#2 (which has cv position 5)normal EVAL mode is just REPL. takes a string and then evaluates it upon completion.
functions to enable flash writing:
considering using a character which signifies that the preceding received bytes should be trashed.
ie, on connection say some other service tries to query the device. there will be garbage bytes in the buffer for example "23409j3sfd"
when a crow client connects, it'll try to id the crow by sending "_c.summon()" or whatever, but the REPL will try to execute "23409j3sfd_c.summon()" and throw an error. of course the client can retry. but perhaps it makes sense that whenever it seems ESC (code 27) it trashes the input buffer (resets position to 0?) whereupon the next command will get executed when a newline comes in.
i haven't looked at the crow code yet, just thinking out loud while working on the crow-command-line-client
the initial version requires the user to 'require' the table for their module to be remote-controlled:
require 'JF'
the design would mean a call to eg: ii.jf.transpose()
would fail if the above hadn't been required.
we fundamentally require this now because their is a hard RAM limit and loading all of the helper for all of the potential i2c clients would use far too much.
instead, we can hide this limitation from the user, making it appear as though all of the tables are always loaded. whenever there is a call to ii.jf.___ and the function is not found, we lookup if the jf table is nil (if it's not, then it suggests a typo), but if it is, we first require 'JF'
before executing the code.
this means that the RAM is only used after the user first uses a function from that subset even though it appears as though all the functions are always available.
//
note: this requires ii..listcommands() to be implemented in C, printing a c-string (from a header file) as the contents. or perhaps this string/array is passed back to lua for printing/matching.
currently just keeps printing debug .
s
hardware works.
needs to have sequential testing steps.
should be called during testing, and optionally called from lua at runtime (though should be unnecessary).
also possibility that we can directly upload scripts via dfu-util to the single 64kb page, which will likely be much faster than TTY plus has built-in data integrity checks.
fully functioning usb device REPL:
connect via screen/minicom
type directly at crow, receive evaluations
two types of messages received from crow: DEBUG and COMMAND. one or the other could simply have a preceding character, ie, debug starts with >
for example. a command would be something that could be directly executed by the host interpreter, ie adc(1,2)
investigate end character (newline?) to evaluate
note screen/minicom will need to turning local key echo on?
Currently make
can fail for a number of reasons, and can need to be run multiple times before success, or the user may need to run the lua util/ii_gen.lua
helper by hand. I've spent some time trying to configure pre-requisites but run into issues because of the way $(OBJS)
is a variable not a recipe.
The current build process in make-ese
is something like:
layer1: util/l2h.fnl
fennel --compile util/l2h.fnl -> util/l2h.lua
mkdir build
layer2: $(layer1) util/ii_gen.lua
lua util/ii_gen.lua # builds all the generated i2c files from $(II_SRC)
layer3: $(layer2) util/l2h.lua
lua util/l2h.lua $(LUA_SRC) # wraps all the lua files in c headers
$(EXECUTABLE): $(layer3) $(OBJS)
# everything works from here on
I'm guessing i have something subtly wrong with my understanding of how make handles prerequisites. Hoping someone more knowledgeable than me has an answer!
lua_name
match filenameWould help to rework the util/ii_gen.lua
script to load all the files first & generate copies of 'cmd' into 'getters' section, rather than special case on them constantly. then it would be easier to make custom iterators for each segment.
breaks the operator precedence basic rules, which were discovered through the currently broken precedence behaviour.
take the following asl:
loop{
loop{ toward(0)
, toward(1)
}
, toward(3,3)
}
the expected behaviour here is that the inner loop of toward(0), toward(1)
would never end. the toward(3,3)
would not be reached because the inner loop wouldn't exit.
conversely, if we change the outer loop{} to a thread{} the control flow is switched such that the inner loop is not able to take over the control flow. instead the sequence will be:
toward(0)
toward(3,3)
toward(1)
toward(3,3)
toward(0) --etc
this allows the creation of algorithmic cycles by threading multiple loops with different stage counts. eg:
thread{
loop{ toward(1,1), toward(2,2), toward(3,3 },
loop{ toward(4,4), toward(5,5), toward(6,6), toward(7,7), toward(8,8) } }
this program will create a sequence 35 stages long, but only requires describing 8 events. consider the possibility of threading a loop{}'d sequence of delay times, with a loop{}'d sequence of instant jumps, where the sequences are different lengths. this would have the effect of the cv sequence being cyclical, but the rhythm of the sequence cycling over a number of repetitions.
add functionality to append to an existing user-script, rather than requiring the whole thing to be uploaded at once. this way a user can extend their script on the fly without having to worry about the full script.
maybe this is not a good idea?
currently, the script is just loaded, and would require a hard reset to stop existing code.
not even a standard library idea, but just a general idea for a crow script:
IN1 takes a v8 pitch
IN2 takes a 'speed' setting
OUT1-4 provide a 'flock' of cvs that follow & move around the input signal. could be a nice way to make more interesting 'detune' sounds than with a simple static or random spread. also has an interesting time-constant effect.
furthermore, if this were then processed by a quantizer it could be an alternate approach to the ASR algorithm.
crow is able to be queried by another i2c leader device. teletype is the clear companion device who would lead crow, so the TT project must be updated with hooks for the actions.
This should be implemented for a single crow device first.
Afterwards we can consider how to handle multiple crow devices. This could be sequential input/output numbers (ie CROW.OUT 5
would refer to the first output of the second device, vs CROW.2.OUT 1
or CROW.OUT 2 1
etc).
Proposed initial functions are listed in lua form: https://github.com/trentgill/crow/blob/aa45d96a322f6c818e84a98bc2d69b58a189ed45/lua/ii.lua#L38
and TT form: https://github.com/trentgill/crow/blob/aa45d96a322f6c818e84a98bc2d69b58a189ed45/lua/ii.lua#L67
All the generic functions should send /receive int16 values.
run make tests
to see the errors that occur in the ASL library.
basically the coroutine handling doesn't work correctly and is causing nested structures to only execute one step per resume. they should be exhausted before proceeding.
see monome/norns#572
if crow is the only master on the bus, and no powered busboard provided, we need i2c pullup in order for things to work.
ii.pullup( enable )
need to choose VID/PID and device name so it's more easily recognizable to the end-user (currently looks like a generic stm32 dfu bootloader).
currently crowlib treats the modules themselves with first character uppercase (eg: Input, Output, Metro), while the user-space objects use lower case (eg: input, output, metro).
ed note: This whole process could likely be deprecated if some smart person figures out how we can upload lua bytecode to crow directly, rather than manually convert everything into C strings & load them at runtime. The key benefit here is that lua syntax errors will be caught by that process (using luac
i guess), and perhaps smaller RAM footprint (because the C strings aren't loaded into memory at all).
I'm unclear on the memory implications of the current approach - Some help profiling the memory usage would likely be a good use of time.
/////////////////////////////////////////////////////
nb: l2h
is written using 'fennel', which is a lisp syntax for the lua language. if you want to rewrite it in plain lua, that's ok, but you'll need to update the Makefile to stop it from overwriting your work!
no attempt is made to validate the crow standard libs before 'make' creates binaries. while it's not the role of l2h
to do full testing of these libraries, it is useful to have l2h validate the code as at least able to be parsed. all that should be necessary is to use loadfile() on the .lua file and return an error if it fails to parse.
currently the lua files are simply copied, line-by-line, into a c-header file. in particular this means that comments & whitespace are taking up space in flash, and must be parsed out by the lua interpreter at runtime. importantly, once the lua code is flashed onto the chip there is no way to retrieve it, so it doesn't need to stay human readable.
we can reduce flash usage, and RAM overhead at load time the following ways. top of the list is easiest & biggest impact, while end of list is either difficult or has only a small benefit:
luac
), so perhaps a more lucrative use of time is to find a way to upload precompiled lua bytecode, rather than raw lua strings. if taking this approach, we should make sure to compare binary sizes as bytecode can apparently be larger than textual representation.currently the crow standard library enforces using the single-quote '
character for indicating strings or chars, or the double-brak [[
and ]]
form (though this is rarely used). when these libraries are wrapped into c-headers with l2h
each line is wrapped in a "
so the c-compiler can parse them as strings. this means that if a user writes lua code that uses the "
to denote a string, the resulting c-header file will not be correctly formed. a big step in the right direction would simply be to parse for the "
character in the lua source, and add a leading \
to escape it in C.
"
characters in lua sourcecurrently there is a hard limit on the size of an immediate-execution lua chunk sent over TTY.
the limit is set by the reader[]
buffer size in caw.c, which is itself set by the USB_RX_BUFFER
define. as of this writing it's 1024 bytes.
Line 7 in 969b0a9
in order to extend this capability we either need to increase the buffer size, or dynamically allocate memory as the buffer reaches the limit.
please discuss.
stm32f7 has onboard random number generator.
create a LL driver with a single external function to get random number (float?)
export getter into lua env
redefine math.random to query that getter, rather than the pseudo-random generator requiring a seed.
//
note the RNG can't deliver values over DMA, only interrupt (i think), so keep a queue of 16(?) values & set a flag that requests a new value whenever one is used. this means the getter can be instant, and a script can call math.random() repeatedly without worrying about the same value being returned, or needing a pseudo-random alternative.
Leaders can ping the network requesting all devices to signal their presence. The response should include:
Main use-case is for multiple self-organizing crows (and W/s) to communicate without requiring any explicit setup in a user-script. See issue #5.
What are the other use-cases where this functionality is valuable?
This feature clearly suggests a large amount of additional work beyond crow as other modules must support the query on the global broadcast channel. That is a large number of devices now (>15?) so I hope the response-to-ping behaviour can be relatively simple, thus simple to implement.
design notes
get_adc(1)
returns adc(1,value)
c command to print contents of flash.
can be used by "maiden" to "download" file from crow to local file system
command to RESET crow
_G
to startup condition (pre flash execution = blank slate)https://github.com/trentgill/crow/blob/b2d5f22be6f6639a3cf1eaf6bebc0865c440a421/lua/ii.lua#L36 proposes a set of crow ii commands (as follower). it provides the basics of input / output, in as similar a way as possible to teletype (so it can be used as an expander with almost zero mental overhead).
then also the generic .CMD
functions create a generic interface to get data into/out of crow. the implementation is entirely up to the user. all that is predetermined is the number of args (with options up to 4 args), which the user can then deal with in their crow script. eg. CW.CMD2
could be used to execute set_lfo( channel, speed )
CW.CMD2
could alternatively be used in dispatch_ii( action, argument )
, meaning that the first arg actually chooses between a (potentially giant) list of actions.
obviously this allows the user to create utterly undecipherable TT scripts. this seems like a downside, but it also seems like a massive boon for the creative possibilities. the idea that TT can trigger 'some event' with an ii call to crow seems essential, and i'd prefer not to have to codify all the possibilities of what that event should be.
//
an aside: moving to a multi-leader i2c setup suggests a set of new TT decision questions not yet engaged with. specifically, "what functionality should TT provide as an i2c follower?". the immediate parallel to the above proposition is to allow calls to IN, OUT but also SCRIPT.
food for thought!
> midi.showevents() -- midi.help() -- m.help()
crow.m.note_on = function( note, velocity, channel )
— handle a note_on
end
…
> input.showevents() -- input.help()
crow.input.change = function( channel, direction )
— handle a detected rising/falling event
end
crow.input.stream = function( channel, value )
— handle a stream of values read from the CV input jack
end
...
the above could be called with m.help()
or input.help()
and also give a line or two of how to use it. this kind of locally stored documentation seems like a great way to minimize the need for being connected to the internet when you’re already somewhat familiar with how it works. i can implement those things in C so they don’t have RAM implications, just flash. obviously they need to be as short as possible so they don’t flood the repl and take ages to send over the pipe.
of course we could add a help()
global function which prints out how to use the via-repl-help functions and gives a list of the different modules that are available. this stuff seems like it could happen quickly once we have the readline app working, as i’m sure this ideas will need to be massaged into working in that context, vs working in maiden (is that how a user will talk to crow for norns?).
> help()
--- CAW! this is crow help.
-- crow has the following modules loaded:
_c -- crow standard library
input -- CV input
out -- CV output
m -- MIDI input
metro -- Timers & metronomes
asl -- 'a slope language'
asllib -- standard slopes
-- you can find out available functions by typing <module_name>.help(), like this:
input.help()
Requires hardware extension with an optocoupler. Breadboard this with the generic optocouplers, then deadbug onto the module (transistor as buffer).
currently the input 'stream' only works if sent as a lua chunk after boot. including it in default.lua causes the system not to boot.
hypotheses:
call something short,
print(crow.version)
returns the version number led by 'crow'
crow 0.0.0
useful for autodetection for when you are querying uarts and seeing if they come back with the right response (ie now i know this is a crow)
Need a solution to make sure the user can't create a script (saved in flash as the default) that makes the lua environment unresponsive, thus being unable to change the script.
Ideas:
These would include a small number of functions that would break script-updating (dostring(), usb2repl() etc).
see issue #10 for further thoughts.
stm32 has some kind of hardware peripheral checksum available.
Fixed architecture dsp functionality.
Likely needs to be a global setting to switch the module into acting as a full polysynth.
(Triangle (ramp/waveshape) -> Filter | Noise -> Amplifier). 4-voice goal. Making a 2op FM option would be maximum suggested spec.
Smoothing on the control-rate inputs (from ADCs, or MIDI, or via USB).
LFO and Envelopes should be provided, potentially by the ASL language if that's possible. Might require something far weirder under the hood, so just start with the basics here.
Use as much from JF as will fit on the flash / in ram / in cpu time.
last feature on the sales pitch hah.
due to concerns about using too much RAM with loaded lua code, there should be a functionality to check (in debug builds?) the amount of RAM currently free.
this could be called in the C component of dofile()
int s;
int tos = (int)&s;
int* h = malloc(sizeof(int));
int boh = (int)h;
U_Print("ram left "); U_PrintU32(tos - boh);
free(h);
Requires the user to call a global function activating midi mode. this could / should deactivate the ADC on channel one, and definitley deactivate any callback set for that channel.
Might be able to auto-detect midistate by leaving the UART activated, and asking the user to 'press any key' on their device, then detect this as uart data.
UART should be on it's own channel to be independent of the uart-debugger.
probably just want to have a lua callback for any midi message, and likely just copy the midi-input library directly from norns (or include it directly).
expose some helper functions to the user that simplify getting notes vs. ccs vs. sync.
ASL stands for 'a slope language', but our entire focus on slopes is their close correlation with the representation of musical expressions. Yes 'LFO' and 'ADSR' are of course covered, but ASL can also describe 'crescendo' or 'melodic contour'.
A number of extensions to ASL are already proposed to move toward these ideas:
A primary concern is thinking about how the syntax or lexical structure of ASL could be refined to speak more directly to melody and rhythm.
It would require some changes to the implementation (and perhaps introduce some limitations), but running ASL at audio-rates could be conceptually interesting. Perhaps this requires further changes to the syntax as well? The idea is that of 'algorithmic waveforms' where modulations are built-in to the waveform descriptor.
Consider a waveform that is a simple triangle, where the 'top' of the triangle is moved about within the period of the waveform. this results in sawtooth through ramp sounds. typically such a oscillator is custom built with this behaviour (see Just Friends etc), then that point can be modulated by control-voltage or some other algorithm. I propose it would be interesting to control the location of that point with an algorithm. Using ASL, that algorithm is effortlessly wrapped in a closure that calculates a new location upon each repetition.
Of course this above idea can then be generalized such that arbitrary waveforms can be created with an arbitrary number of modulation points. The real key is that the modulation is as much a part of the description as the frequency and amplitudes of points. Thus we can say that the modulation is a component of the waveform itself.
This is the key to 'algorithmic waveforms'. That is, waveforms where their behaviour changes over time according to some context. The logical extension of algo-waves is that of 'behavioural waveforms' or the category of 'behavioural synthesis'. In this case the aforementioned 'context' would be based on an 'environment' shared across the synthesis platform.
jason needs it.
after some pin flipping, appears that the time is spent enabling the interrupt (actually masking the bit in the SPI register!).
takes ~23uS to call HAL_SPI_Transmit_IT() or HAL_SPI_Transmit_DMA() (dma actually takes slightly longer)
SCLK is running at 7MHz. this seems to be a limitation in the HAL driver, as it can only operate at RCC/8 while using HAL for some reason.
//
there's no reason that the IRQ needs to be enable/disabled between each transmission, as the callback only occurs when data is being sent (or fails to send).
in order to enable the IRQ we would have to use the LL driver instead of HAL because the enable/disable happens inside the HAL handling which we don't want to change.
//
instead of figuring out the LL driver, it would likely be much faster to just call the SPI transfer in blocking mode using HAL_SPI_Transmit() and just wait for the transfer to complete.
this will block processing longer than a good interrupt solution, but because it's such a short packet, even at 7MHz the packet time is only 3.2uS! that's almost a x10 improvement over the standard HAL_IT() implementation.
worth a shot!
should get beyond the 40kHz rate for 4 channels which is all we're after to do decent audio. then a variable-samplerate implementation would allow this to dip if it needs to.
minicom / screen is current solution which is sufficient for proof-of-concept, but is a pretty bad interface for real usage (plus requires setup knowledge of those programs).
making a small lua script (or if not possible, c application), would enable:
then optimizations / ease of stm32 development
lua COM port interfaces (both very old): https://github.com/edartuz/lua-serial, http://lua-users.org/wiki/SerialCommunication
I arbitrarily chose the i2c addresses as 0x78 through 0x7B.
Couldn't find an exhaustive list of the occupied addresses (though i do remember seeing one). Thinking the choice of i2c address could double as the 'which crow' number when multiple units share a single usb connection.
Perhaps @scanner-darkly has ideas here?
Currently slopes last until they converge with their destination. This means 'wait for time x' is not possible.
Instead use an explicit timing mechanism which counts samples before a callback. Will make callback detection much simpler (and possible at control-rate).
Could also add sub-sample callback detection with an 'overflow' amount to be applied on the new callback.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.