zeffii / csound_patches

GNU General Public License v3.0
2 stars 0 forks source link

why does this repo exist? #2

Open zeffii opened 4 years ago

zeffii commented 4 years ago

03 Oktober 2020 I've been wanting to write my own tracker software for about 20 years, maybe 25. Being introduced to ChucK opened my eyes to audio programming and demistified the kinds of thinking required. Over that period I learned openGL and python (+ other languages ), but found python expressive , supercollider not so much and puredata was a bit mouse heavy. I hate using the mouse, this originates from my ImpulseTracker days.

i'm 300+ pages into the Floss csound manual, things are gradually starting to make sense, it seems exactly sufficient for my needs - but i won't know until a few clips of music emerge.

  1. The idea is to first make a bunch of synths and presets and learn about sample loading / manipulated playing.
  2. then see if it can be sequenced non-realtime in a way that makes sense to me (preferably in realtime...eventually)
  3. write some kind of tracker UI (language may be python first...) , which lets me have a track connected to a codewindow inside which i can write opcodes / isntruments
  4. abstract the synth writing with timedomain graphs to show the envlopes
  5. abstract the synth as a graph / node system. (but this is definitely not essential... maybe nice later)
zeffii commented 4 years ago

05 Oktober 2020 got to about page 350,, there are useful introductions to some of the built-in ways to do synthesis (phsycial modelling, additive, subtractive, waveguide, fm ...) . That's all great for the muscle memory and briefly seeing all kinds of interesting code. It also offers to chance to hear csound 's capacity to produce noise. Especially perry cook's bar demo is one of the first complexer sounds (Even without the reverb... which also sounds neat )

i wrote my first UDO to convert tracker-notes to midi notes. It's a lot of code compared to the python version. for now i'll blame my noobness.

zeffii commented 4 years ago

07 Oktober 2020 around page 380 now, covering envelopes. made first simple bassdrum patch. Would love to have a graphical representation of certain lines of code, especially envelope opcodes.

zeffii commented 4 years ago

12 Oktober 2020 at around 500 pages, it's easier to skip through the chapters, and so I jumped really fast to the end. Skipping consciously (typing out) very nice examples of synthesis. At this point i'm more interested in figuring out how to sequence events. I'm glad to say i've arrived at a few implementations. One using k intervals, the other using metro (and this one might be the most csound approach).

my intention is to have a tracker based approach to sequencing, so array based integers is only a placeholder. This shows I think some seedlings of csound comprehension. My introduction to ChucK on coursera made this a lot easier.

zeffii commented 4 years ago

17 Oktober 2020 First beginnings of trackr code,, parsing a multiline string (representing a pattern) into rows, and parsing the rows individually. While this is supremely possible to do directly in csound language, i already know i don't want to write low level parsing stuff like this (which is not required to have low latency) in csound. So the next step is to learn how to use the python-execution implementation.

i'm happy to write instruments in csound language, but python is perfect for pattern parsing.

Because csound functions (opcodes) can't have variable outputs, each machine's patterns might require their own pattern / (row/line) parser. The shape of a pattern should maybe be static per machine (which is an acceptable limitation for now)

i'd like to introduce some learnings here. All trackers implement some kind of character-to-value parser. For example notes that describe a pitch are similar to 3-characters -> midinote -> frequency . Below is the csound code that converts a note to a midinote.

gS_notes[] fillarray "C-","C#","D-","D#","E-","F-","F#","G-","G#","A-","A#","B-"

opcode NoteToMidi, i, S
    Sstr xin
    ithru       strcmp  "...", Sstr
    iterm       strcmp  "===", Sstr
    imidinote   init    -5
    Snums       init    "0123456789"
    i_index     init    -1
    i_counter   init    0

    if ithru == 0 then 
        imidinote = -2
    elseif iterm == 0 then
        imidinote = -3
    else
        ; check if within supported range
        Sidx     strsub     Sstr, 2, -1
        ioct     strindex   Snums, Sidx

        if (ioct < 0) then
            imidinote = -4
        endif

        SNoteKind   strsub Sstr, 0, 2

        ; which element in gS_notes corresponds to SNoteKind? 
        ; csound lacks "is_item_in_array" or a "dict" to do this implicitly.
        until i_counter == lenarray(gS_notes) do
            if strcmp(SNoteKind, gS_notes[i_counter]) == 0 then 
                i_index = i_counter
            endif
            i_counter += 1
        od 

        if i_index < 0 then 
            imidinote = -5
        else
            imidinote = (i_index + ioct * 12) + 12
        endif 

    endif

    xout imidinote
endop 

to go from a midinote to a frequency is done using a single inbuilt opcode mtof , so if you event is sending midinote on p5 then this will convert to frequency

iFreq   mtof p5

As a newcomer to csound the above midi_to_note opcode took me a while to write,, heavily punctuated by fits of frustration, and moments of zen. It is a necessary function and looking back at it now it shows conveniently some core features of the language. It also shows how csound is ultimately not convenient for string parsing :)

secondly i want to show another string parsing function which is has narrow usefulness, due to my laziness (and premonition regarding csound domain specific language strengths..). This code only splits a multiline string into substrings by a set line length determined by the first real newline.

opcode multiline_split, S[], S
    /*

    multiline_split  

    usage:   

            S_rows[] multiline_split gS_pattern_001

    info:

    :   accept -  single multiple string
    :   return -  an array of strings

    :   this function will test if the first character in the string is a newline
    :   and remove it before doing the main text split 

    : known limitations
    1. expects equal size splits
    2. this means patterns of over 99 lines long will need to do something like
       99 C-% ... 
       100C-5 ...
       effectively limiting this function to patterns of 999 lines, which is fine.
       512 is uncomfortable already, and I can use Hex instead if 1024+ was needed
    */

    S_multiline_str xin

    ipos strindex S_multiline_str, "\n"
    if ipos == 0 then
        ; prints "removing first newline if pos 0"
        S_multiline_str strsub S_multiline_str, 1
    endif

    iFirstNewLine strindex S_multiline_str, "\n"
    iSizeNewArray = int(strlen(S_multiline_str) / (iFirstNewLine + 1))

    S_new_array[] init iSizeNewArray
    i_counter = 0
    i_substart = 0
    i_subend = 0

    while (i_counter <= iSizeNewArray-1) do
        i_substart = int(i_counter * (iFirstNewLine+1))
        i_subend = int(i_substart + iFirstNewLine)
        S_new_array[i_counter] = strsub(S_multiline_str, i_substart, i_subend)
        i_counter += 1
    od

    xout S_new_array

endop

the docstring (comments at the head of the opcode) explains the limitations. Still interesting to implement this.

Now. if you want to import an opcode writen elsewhere you can do it directly in the body if there are no csd tags in the file.

#include ".\\opcodes\\opcode_tnote_to_midi.csd"

opcode parse_rows, i[], S[]

    S_rows[] xin

    iLenArray lenarray S_rows
    itriggers[] init iLenArray

    iCounter = 0
    while iCounter < iLenArray do

        S_temp_note strsub S_rows[iCounter], 3, 6
        S_temp_note_test = "..."

        if strcmp(S_temp_note, S_temp_note_test) != 0 then
            inote = NoteToMidi(S_temp_note)
            itriggers[iCounter] = inote
        else 
            itriggers[iCounter] = 0
        endif

        iCounter += 1
    od

    xout itriggers

endop

but if the .csd does have tags, then you might see something like..


<CsoundSynthesizer>
<CsOptions>
-odac  ; -nm0
</CsOptions>

<CsInstruments>
sr = 44100
nchnls = 2
ksmps = 32
0dbfs = 1

#include ".\\opcodes\\opcode_string_multiline_split.csd"
#include ".\\opcodes\\opcode_row_parser.csd"
#include ".\\opcodes\\opcode_tnote_to_midi.csd"

....

i did not figure out how to do relative paths (like dropping a dir, and pointing at a parallel dir ...) , it may be better to have a single opcode folder (system wide)

zeffii commented 4 years ago

i can't get python to run from csoundQT, from sublime3,, or from cabbage.

NOOOOOOOOOOOOOOOOO!!

zeffii commented 4 years ago

18 Oktober 2020

YES python works, on windows System variables must be set - it seems manually ...

PYTHONPATH=
C:\Python27\;C:\Python27\lib

19 Oktober 2020 As the amount of code starts to grow, it's necessary to break code out into files.

somewhere at the top of <CsInstruments> is where we stick the includes. Notice that it apparent'y makes a difference in what order these includes are written. From my brief introduction so far, it seems that instruments must be included first, then opcodes.

...
#include "your_instrument.csd"

#include ".\\opcodes\\opcode_string_multiline_split.csd"
#include ".\\opcodes\\opcode_row_parser.csd"
#include ".\\opcodes\\opcode_tnote_to_midi.csd"
...

i still don't know if it's possible to do more elaborate path syntax (down one folder, into a parallel folder )

I needed an opcode that converted arbitrary hex to base10 integers.

opcode char_to_num, i, S
    /*
    input any single hex value, as a string
    output the index equivalent  ( 0=0,...... 9=9, A=10,.. D=13, ..F=15 )

    limitations, expects only the following characters "0123456789ABCDEF",
                         ----
    - no lowercase
    - no newline, 
    - etc.,

    */
    S_in xin

    S_chars[] fillarray "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"
    i_num_items lenarray S_chars

    i_counter = 0
    until strcmp(S_in, S_chars[i_counter]) == 0 do 
        i_counter += 1
    od 

    xout i_counter

endop

opcode hex_to_decimal, i, S
    /*
    input:  a string representation of a hexvalue, of arbitrary length
    output:  the integer value

    "FAAB -> %d\n", hex_to_decimal("FAAB")
    FAAB -> 64171

    limitations, expects only the following characters "0123456789ABCDEF",
                         ----
    - no lowercase
    - no newline, 
    - etc.,

    */

    S_input xin
    i_num_chars strlen S_input
    i_running_sum = 0

    i_counter = 0
    while i_counter < i_num_chars do
        S_char strsub S_input, i_counter, i_counter+1
        i_power = i_num_chars - i_counter - 1

        if i_power == 0 then
            i_running_sum += char_to_num(S_char)
        elseif i_power = 1 then
            i_running_sum += char_to_num(S_char) * 16
        else
            i_running_sum += char_to_num(S_char) * (16 ^ i_power)
        endif

        i_counter += 1
    od

    xout i_running_sum

endop

/*
instr test_hex

    prints "FAAB -> %d\n", hex_to_decimal("FAAB")

endin

and i stumbled upon hexplay by @kunstmusik , which is so genius it was invented already (explained here) , hexplay is something i didn't know i was missing in my life - it looks like a neat way to construct longer trigger-phrases.

zeffii commented 4 years ago

20 Oktober 2020

When including instruments using #include "your_file.ext" , there may also be an order of precedence. You know this when you try to break out instruments into their own files, and suddenly get warnings that don't make much sense. These warnings may include the character # which i have taken to mean that the include order is not right. I don't know what else to remark here, only that i was mighty confused about precedence (this is the first bad smell that csound has given me - but i will yield to my ignorance of its subtleties for a while longer).

I will avoid dwelling on this confusion for too long, my interest in csound is primarily the power of it's synthesis language, it's ecosystem i will tolerate.

zeffii commented 4 years ago

23 Oktober 2020

I have a the faintest of faint tracker pattern parsing working, and it can pass more than notes and volumes of the instrument. I wonder if chnget / chnset would be more appropriate for passing params (synth parameters, Group Parameters) that aren't being sent while a note is being sent.

i'm also wondering if the instrument should take an array of notes, and volumes and an array of parameters. But - the main issue now is to construct a cool pad/stab synth

zeffii commented 4 years ago

25 Oktober 2020

i've pulled out the percussion segment into an opcode, opcodes can trigger event "i" no problem. I recall reading somwhere that instruments might be able to accept array properties too, but can't find it. I don't really want to setup a global value to provide instruments with all values, when a pattern-row contains only sparse parameters.

zeffii commented 4 years ago

26 Oktober 2020

i've achieved about the extent of what it possible as an utter noob. Have come to understand why some things don't work, and (like any other language) learned to read the error log. Now back to reading, because there are still many things that baffle me.. still feeling good about csound!

You know when you get that idea that there's an approach that you don't really want to take,, but you think it will probably work.

and then you try various alternatives, and after 3 or 4 days still end up implementing the way that first occurred to you, but seemed wrong for one reason. (not because it was too much work.. but because a more elegant solution might have presented itself after enough trying).

well. that happened. 4 days aren't a total waste of time , i've seen a bit of scenery in the csound landscape .

zeffii commented 4 years ago

27 Oktober 2020

i replaced global params with the global channel bus, this collapsed much of the uglier code. Now really really i need to learn how to write instruments.

zeffii commented 4 years ago

30 Oktober 2020

i have most of the rudimentary csound stuff figured out, and i'm getting tired of listening to the same pattern over and over. This means moving on to the UserInterface, which means i will be brushing away the cobwebs from a previous life of dabling in C++, and checking out SDL2. I'm willing to do a lot of work to get a nice tracker interface, if could use python but it's time to move on from that.

zeffii commented 4 years ago

08 November 2020

C++ is not yet something i've internalized, progress is slow but i don't know how slow yet. Any spare time i've had since he last entry has been pumped into breaching the surface of C++ / SDL .

Here's a start with SDL and text rendering https://github.com/zeffii/air_tracker/issues/1 , classes are cool but duplicating everything in headerfiles seems like such a duplication effort...weird language design. (but what do i know...)