Open zeffii opened 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.
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.
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.
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)
i can't get python to run from csoundQT, from sublime3,, or from cabbage.
NOOOOOOOOOOOOOOOOO!!
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.
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.
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
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.
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 .
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.
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.
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...)
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.