A list of Unit Generators in Sporth
44 |Below is a list of Sporth unit generators that can be used in-browser. 45 | Some of these ugens have links with short simple examples.
46 | """ + out + '├── .gitignore ├── api.go ├── config.go ├── config ├── .gitignore └── masher.example.config ├── db.go ├── go.mod ├── go.sum ├── license.txt ├── main.go ├── readme.md ├── scripts ├── create-table-patches.json ├── create-table-users.json ├── generate_ref.py ├── not yet implemented.yml ├── ugen_reference.yml └── upstart │ ├── audiomasher.conf │ └── readme.md ├── static ├── codemirror │ ├── addon │ │ └── mode │ │ │ └── simple.js │ ├── codemirror.css │ └── codemirror.js ├── controls.svg ├── loading.gif ├── masher-about.png ├── masher.css ├── masher.js ├── sporthem.js ├── sporthem.wasm ├── sporthparams.js ├── ugen_reference.html └── ugen_reference.js ├── templates ├── 404.html ├── about.html ├── browse.html ├── layout.html ├── learn.html ├── patch.html ├── tutorial │ ├── about_cookbook.html │ ├── ftables.html │ ├── gates_triggers.html │ ├── hello.html │ ├── index.html │ ├── lots_of_patches.html │ ├── lsys.html │ ├── operations.html │ ├── polysporth.html │ ├── pregisters.html │ ├── prop.html │ ├── recipes_bones.html │ ├── recipes_chatting_robot.html │ ├── recipes_fm.html │ ├── recipes_in_seven.html │ ├── recipes_machines_at_work.html │ ├── recipes_midnight_crawl.html │ ├── recipes_oatmeal.html │ ├── recipes_playwithtoys.html │ ├── recipes_waiting_room.html │ ├── stack.html │ ├── ugens.html │ ├── ugens_abs.html │ ├── ugens_add.html │ ├── ugens_adsr.html │ ├── ugens_allpass.html │ ├── ugens_ampdb.html │ ├── ugens_and.html │ ├── ugens_atone.html │ ├── ugens_autowah.html │ ├── ugens_bal.html │ ├── ugens_biscale.html │ ├── ugens_bitcrush.html │ ├── ugens_blsaw.html │ ├── ugens_blsquare.html │ ├── ugens_bltriangle.html │ ├── ugens_bpm2dur.html │ ├── ugens_bpm2rate.html │ ├── ugens_branch.html │ ├── ugens_brown.html │ ├── ugens_butbp.html │ ├── ugens_butbr.html │ ├── ugens_buthp.html │ ├── ugens_butlp.html │ ├── ugens_changed.html │ ├── ugens_clip.html │ ├── ugens_clock.html │ ├── ugens_comb.html │ ├── ugens_count.html │ ├── ugens_crossfade.html │ ├── ugens_dcblk.html │ ├── ugens_delay.html │ ├── ugens_diode.html │ ├── ugens_diskin.html │ ├── ugens_dist.html │ ├── ugens_div.html │ ├── ugens_dmetro.html │ ├── ugens_drip.html │ ├── ugens_drop.html │ ├── ugens_dtrig.html │ ├── ugens_dup.html │ ├── ugens_dup2.html │ ├── ugens_leftshift.html │ ├── ugens_mod.html │ ├── ugens_mul.html │ ├── ugens_or.html │ ├── ugens_osc.html │ ├── ugens_oscmorph.html │ ├── ugens_posc3.html │ ├── ugens_rightshift.html │ ├── ugens_sub.html │ ├── ugens_switch.html │ ├── ugens_tabread.html │ ├── ugens_tblrec.html │ ├── ugens_tenv.html │ ├── ugens_tget.html │ ├── ugens_tone.html │ ├── ugens_tseq.html │ ├── ugens_tset.html │ ├── ugens_xor.html │ ├── variables.html │ └── what_is_sporth.html └── user.html └── view.go /.gitignore: -------------------------------------------------------------------------------- 1 | AudioMasher.exe 2 | /AudioMasher 3 | masher.db 4 | masher.db.lock -------------------------------------------------------------------------------- /api.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base32" 5 | "encoding/json" 6 | "errors" 7 | "golang.org/x/crypto/bcrypt" 8 | "math/rand" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | type ApiResult struct { 14 | Success bool `json:"success"` 15 | Message string `json:"message"` 16 | PatchId string `json:"patchId,omitempty"` 17 | } 18 | 19 | func CheckPatchValid(patch MasherPatch) error { 20 | if _, exists := patch.Files["main.sp"]; !exists { 21 | return errors.New("Patch must have a main.sp file") 22 | } 23 | if patch.Title == "" { 24 | return errors.New("Title is missing or empty.") 25 | } 26 | if len(patch.Files["main.sp"]) > 100000 { 27 | return errors.New("Script too long (> 100K characters).") 28 | } 29 | if len(patch.Files["readme.txt"]) > 100000 { 30 | return errors.New("Description too long (> 100K characters).") 31 | } 32 | if len(patch.Title) > 50 { 33 | return errors.New("Title too long (> 50 characters).") 34 | } 35 | return nil 36 | } 37 | 38 | func CheckPatchUnique(patch MasherPatch) error { 39 | patches, err := RetrievePatches(SearchFilter{}) 40 | if err != nil { 41 | return errors.New("Could not load database for duplicate checking.") 42 | } 43 | for _, oldpatch := range patches { 44 | if oldpatch.Title == patch.Title { 45 | return errors.New("A patch with the same title already exists.") 46 | } 47 | if oldpatch.Files["main.sp"] == patch.Files["main.sp"] { 48 | return errors.New("A patch with the same script content already exists.") 49 | } 50 | if oldpatch.Id == patch.Id { 51 | return errors.New("Internal laziness error, try again.") 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | func RandomId() string { 58 | data := make([]byte, 5) 59 | rand.Read(data) 60 | str2 := base32.StdEncoding.EncodeToString(data) 61 | return str2[:6] 62 | } 63 | 64 | func PostPatch(w http.ResponseWriter, r *http.Request) { 65 | result := ApiResult{Success: false, Message: "Error posting patch. \n"} 66 | title := r.FormValue("title") 67 | mainScript := r.FormValue("main_script") 68 | patchDesc := r.FormValue("desc") 69 | session, err := SessionStore.Get(r, "sess") 70 | if err != nil { 71 | panic(err) 72 | } 73 | if _, loggedIn := session.Values["name"].(string); !loggedIn { 74 | result.Message += "You are not logged in." 75 | json.NewEncoder(w).Encode(&result) 76 | return 77 | } 78 | 79 | patch := MasherPatch{ 80 | Id: RandomId(), 81 | DateCreated: time.Now().Unix(), 82 | DateModified: time.Now().Unix(), 83 | Title: title, 84 | Author: session.Values["name"].(string), 85 | Files: map[string]string{"main.sp": mainScript, "readme.txt": patchDesc}, 86 | } 87 | 88 | if err = CheckPatchValid(patch); err != nil { 89 | result.Message += err.Error() 90 | json.NewEncoder(w).Encode(&result) 91 | return 92 | } 93 | if err = CheckPatchUnique(patch); err != nil { 94 | result.Message += err.Error() 95 | json.NewEncoder(w).Encode(&result) 96 | return 97 | } 98 | err = PutPatch(patch) 99 | if err != nil { 100 | result.Message += err.Error() 101 | json.NewEncoder(w).Encode(&result) 102 | return 103 | } 104 | result = ApiResult{ 105 | Success: true, 106 | Message: "Patch added: " + patch.Title + " by " + patch.Author, 107 | PatchId: patch.Id, 108 | } 109 | json.NewEncoder(w).Encode(&result) 110 | } 111 | 112 | func UpdatePatch(w http.ResponseWriter, r *http.Request) { 113 | result := ApiResult{Success: false, Message: "Error updating patch. \n"} 114 | title := r.FormValue("title") 115 | mainScript := r.FormValue("main_script") 116 | patchDesc := r.FormValue("desc") 117 | updateId := r.FormValue("update_id") 118 | session, err := SessionStore.Get(r, "sess") 119 | if err != nil { 120 | panic(err) 121 | } 122 | 123 | patch, err2 := RetrievePatch(updateId) 124 | if err2 != nil { 125 | result.Message += "Could not find existing patch in database." 126 | json.NewEncoder(w).Encode(&result) 127 | return 128 | } 129 | 130 | if currentUser, _ := session.Values["name"].(string); currentUser != patch.Author { 131 | result.Message += "You are not the author of this patch." 132 | json.NewEncoder(w).Encode(&result) 133 | return 134 | } 135 | 136 | patch.DateModified = time.Now().Unix() 137 | patch.Title = title 138 | patch.Files = map[string]string{"main.sp": mainScript, "readme.txt": patchDesc} 139 | 140 | if err = CheckPatchValid(patch); err != nil { 141 | result.Message += err.Error() 142 | json.NewEncoder(w).Encode(&result) 143 | return 144 | } 145 | err = PutPatch(patch) 146 | if err != nil { 147 | result.Message += err.Error() 148 | json.NewEncoder(w).Encode(&result) 149 | return 150 | } 151 | result = ApiResult{ 152 | Success: true, 153 | Message: "Patch updated: " + patch.Title + " by " + patch.Author, 154 | PatchId: patch.Id, 155 | } 156 | json.NewEncoder(w).Encode(&result) 157 | } 158 | 159 | func CheckUserValid(user MasherUser) error { 160 | if len(user.Name) < 2 || len(user.Name) > 50 { 161 | return errors.New("Username must be between 2 and 50 characters.") 162 | } 163 | users, err := RetrieveUsers() 164 | if err != nil { 165 | return errors.New("Could not load database for duplicate checking.") 166 | } 167 | for _, olduser := range users { 168 | if olduser.Name == user.Name { 169 | return errors.New("A user with the same name already exists.") 170 | } 171 | } 172 | return nil 173 | } 174 | 175 | func Signup(w http.ResponseWriter, r *http.Request) { 176 | result := ApiResult{Success: false, Message: "Error creating user. \n"} 177 | username := r.FormValue("username") 178 | session, err := SessionStore.Get(r, "sess") 179 | if err != nil { 180 | panic(err) 181 | } 182 | if _, loggedIn := session.Values["name"].(string); loggedIn { 183 | result.Message += "You are already logged in." 184 | json.NewEncoder(w).Encode(&result) 185 | return 186 | } 187 | hash, err := bcrypt.GenerateFromPassword([]byte(r.FormValue("password")), 8) 188 | if err != nil { 189 | panic(err) 190 | } 191 | user := MasherUser{ 192 | Name: username, 193 | Password: string(hash), 194 | Email: r.FormValue("email"), 195 | DateCreated: time.Now().Unix(), 196 | } 197 | if err = CheckUserValid(user); err != nil { 198 | result.Message += err.Error() 199 | json.NewEncoder(w).Encode(&result) 200 | return 201 | } 202 | err = AddUser(user) 203 | if err != nil { 204 | result.Message += "Error adding user: " + err.Error() 205 | json.NewEncoder(w).Encode(&result) 206 | return 207 | } 208 | result = ApiResult{ 209 | Success: true, 210 | Message: "User created: " + username, 211 | } 212 | json.NewEncoder(w).Encode(&result) 213 | } 214 | 215 | func Login(w http.ResponseWriter, r *http.Request) { 216 | result := ApiResult{Success: false, Message: "Error logging in. \n"} 217 | username := r.FormValue("username") 218 | session, err := SessionStore.Get(r, "sess") 219 | if err != nil { 220 | panic(err) 221 | } 222 | if _, loggedIn := session.Values["name"].(string); loggedIn { 223 | result.Message += "You are already logged in." 224 | json.NewEncoder(w).Encode(&result) 225 | return 226 | } 227 | storedUser, err2 := RetrieveUser(username) 228 | if err2 != nil { 229 | result.Message += "User not found." 230 | json.NewEncoder(w).Encode(&result) 231 | return 232 | } 233 | err = bcrypt.CompareHashAndPassword([]byte(storedUser.Password), []byte(r.FormValue("password"))) 234 | if err != nil { 235 | result.Message += "Wrong password." 236 | json.NewEncoder(w).Encode(&result) 237 | return 238 | } 239 | 240 | session.Values["name"] = username 241 | session.Save(r, w) 242 | result = ApiResult{ 243 | Success: true, 244 | Message: "User logged in: " + username, 245 | } 246 | json.NewEncoder(w).Encode(&result) 247 | } 248 | 249 | func Logout(w http.ResponseWriter, r *http.Request) { 250 | session, _ := SessionStore.Get(r, "sess") 251 | for k := range session.Values { 252 | delete(session.Values, k) 253 | } 254 | session.Save(r, w) 255 | http.Redirect(w, r, "/", 302) 256 | } 257 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | 6 | "github.com/JeremyLoy/config" 7 | "github.com/gorilla/sessions" 8 | ) 9 | 10 | type ConfigData struct { 11 | CookieSecret string `config:"MASHER_COOKIE_SECRET"` 12 | DBPath string `config:"MASHER_DB_PATH"` 13 | StaticRoot string `config:"MASHER_STATIC_ROOT"` 14 | Port int `config:"MASHER_PORT"` 15 | } 16 | 17 | var MasherConfig ConfigData 18 | 19 | func LoadConfig(file string) { 20 | err := config.From("config/masher.config").FromEnv().To(&MasherConfig) 21 | if err != nil { 22 | panic(err) 23 | } 24 | } 25 | 26 | var SessionStore *sessions.CookieStore 27 | 28 | func InitSession() { 29 | secret, err := hex.DecodeString(MasherConfig.CookieSecret) 30 | if err != nil { 31 | panic(err) 32 | } 33 | SessionStore = sessions.NewCookieStore(secret) 34 | } 35 | -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- 1 | masher.config 2 | -------------------------------------------------------------------------------- /config/masher.example.config: -------------------------------------------------------------------------------- 1 | MASHER_COOKIE_SECRET=32 or 64 random hex characters (0-f) 2 | MASHER_DB_PATH=masher.db 3 | MASHER_STATIC_ROOT=http://localhost:80/AudioMasher/static/ 4 | MASHER_PORT=8000 -------------------------------------------------------------------------------- /db.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | "github.com/asdine/storm/v3" 8 | ) 9 | 10 | type MasherPatch struct { 11 | Id string `storm:"id"` 12 | DateCreated int64 13 | DateModified int64 14 | Title string 15 | Author string 16 | Files map[string]string 17 | } 18 | 19 | type MasherUser struct { 20 | Name string `storm:"id"` 21 | Password string 22 | Email string 23 | DateCreated int64 24 | } 25 | 26 | type SearchFilter struct { 27 | Author string 28 | } 29 | 30 | var db *storm.DB 31 | 32 | func InitDB() { 33 | var err error 34 | db, err = storm.Open(MasherConfig.DBPath) 35 | if err != nil { 36 | panic(err) 37 | } 38 | rand.Seed(time.Now().UnixNano()) 39 | } 40 | 41 | func RetrievePatch(id string) (MasherPatch, error) { 42 | var result MasherPatch 43 | err := db.One("Id", id, &result) 44 | return result, err 45 | } 46 | 47 | func RetrievePatches(filter SearchFilter) ([]MasherPatch, error) { 48 | var results []MasherPatch 49 | var err error 50 | if filter.Author != "" { 51 | err = db.Find("Author", filter.Author, &results) 52 | } else { 53 | err = db.All(&results) 54 | } 55 | return results, err 56 | } 57 | 58 | func PutPatch(patch MasherPatch) error { 59 | err := db.Save(&patch) 60 | return err 61 | } 62 | 63 | func RetrieveUser(name string) (MasherUser, error) { 64 | var result MasherUser 65 | err := db.One("Name", name, &result) 66 | return result, err 67 | } 68 | 69 | func RetrieveUsers() ([]MasherUser, error) { 70 | var results []MasherUser 71 | err := db.All(&results) 72 | return results, err 73 | } 74 | 75 | func AddUser(user MasherUser) error { 76 | err := db.Save(&user) 77 | return err 78 | } 79 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pac-dev/AudioMasher 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/JeremyLoy/config v1.2.1 7 | github.com/asdine/storm/v3 v3.0.0 8 | github.com/aws/aws-sdk-go v1.19.5 9 | github.com/gorilla/context v1.1.1 // indirect 10 | github.com/gorilla/mux v1.7.3 11 | github.com/gorilla/sessions v1.2.0 12 | github.com/guregu/dynamo v1.2.1 13 | go.etcd.io/bbolt v1.3.3 // indirect 14 | golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf 15 | golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/JeremyLoy/config v1.2.1 h1:DJfUgTESmtd4UA4bkgAD1kIMO0Qd605SKfuShi92WrI= 2 | github.com/JeremyLoy/config v1.2.1/go.mod h1:YcGKcrAs8JJ0AEjPLWL3mCpXWgtuguf9G8WE5H/pLe0= 3 | github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= 4 | github.com/asdine/storm v2.1.2+incompatible h1:dczuIkyqwY2LrtXPz8ixMrU/OFgZp71kbKTHGrXYt/Q= 5 | github.com/asdine/storm/v3 v3.0.0 h1:J/yRhUOSGY5XZp24Yv2cfyCtVtcLSAtJPOMJA7GSBjg= 6 | github.com/asdine/storm/v3 v3.0.0/go.mod h1:wncSIXIbR3lvJQhBpnwAeNPQneL5Vx2KUox2jARUdmw= 7 | github.com/aws/aws-sdk-go v1.18.5/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 8 | github.com/aws/aws-sdk-go v1.19.5 h1:14awKCxT61IWqnZojrj/XdNEeyWDuci/p6xqoeek130= 9 | github.com/aws/aws-sdk-go v1.19.5/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 10 | github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY= 11 | github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= 15 | github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 16 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 17 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 18 | github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= 19 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 20 | github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= 21 | github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 22 | github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= 23 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 24 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= 25 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 26 | github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= 27 | github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= 28 | github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= 29 | github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 30 | github.com/guregu/dynamo v1.2.1 h1:1jKHg3GSTo4/JpmnlaLawqhh8XoYCrTCD5IrWs4ONp8= 31 | github.com/guregu/dynamo v1.2.1/go.mod h1:ZS3tuE64ykQlCnuGfOnAi+ztGZlq0Wo/z5EVQA1fwFY= 32 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= 33 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 34 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 35 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 36 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 37 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 38 | github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 39 | go.etcd.io/bbolt v1.3.0 h1:oY10fI923Q5pVCVt1GBTZMn8LHo5M+RCInFpeMnV4QI= 40 | go.etcd.io/bbolt v1.3.0/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 41 | go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= 42 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 43 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 44 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= 45 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 46 | golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf h1:fnPsqIDRbCSgumaMCRpoIoF2s4qxv0xSSS0BVZUE/ss= 47 | golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 48 | golang.org/x/net v0.0.0-20190318221613-d196dffd7c2b h1:ZWpVMTsK0ey5WJCu+vVdfMldWq7/ezaOcjnKWIHWVkE= 49 | golang.org/x/net v0.0.0-20190318221613-d196dffd7c2b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 50 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 51 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 52 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 53 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 54 | golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c h1:S/FtSvpNLtFBgjTqcKsRpsa6aVsI6iztaz1bQd9BJwE= 55 | golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 56 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 57 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Unless otherwise noted, all files are released under the MIT license, 2 | exceptions contain licensing information in them. 3 | 4 | Copyright (c) 2014 Pierre Cusa 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/gorilla/mux" 9 | ) 10 | 11 | func main() { 12 | LoadConfig("config/masher_config.json") 13 | InitSession() 14 | InitDB() 15 | InitTemplates() 16 | router := mux.NewRouter() 17 | 18 | // pages 19 | router.HandleFunc("/", ViewAbout) 20 | router.HandleFunc("/new", ViewNew) 21 | router.HandleFunc("/patch/{id}", ViewPatch) 22 | router.HandleFunc("/user/{user}", ViewUser) 23 | router.HandleFunc("/continue", ViewContinue) 24 | router.HandleFunc("/browse", ViewBrowse) 25 | router.HandleFunc("/learn", ViewLearn) 26 | router.HandleFunc("/learn/{page}", ViewLearn) 27 | 28 | // api endpoints 29 | router.HandleFunc("/api/signup", Signup).Methods("POST") 30 | router.HandleFunc("/api/login", Login).Methods("POST") 31 | router.HandleFunc("/api/post", PostPatch).Methods("POST") 32 | router.HandleFunc("/api/update", UpdatePatch).Methods("POST") 33 | 34 | // redirects 35 | router.HandleFunc("/logout", Logout) 36 | router.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) { 37 | http.Redirect(w, r, "/", 302) 38 | }) 39 | 40 | router.NotFoundHandler = http.HandlerFunc(ViewNotFound) 41 | log.Fatal(http.ListenAndServe(":"+strconv.Itoa(MasherConfig.Port), router)) 42 | } 43 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # AudioMasher 2 | Generative Audio Playground - https://www.audiomasher.org 3 | 4 | - Edit and play [Sporth](https://audiomasher.org/learn) audio scripts in the browser 5 | - Browse and listen to existing scripts 6 | - Add your scripts to the collection 7 | 8 | ## Building 9 | To get the latest source and build the server using [Git](https://git-scm.com/downloads) and [Go](https://golang.org/dl/): 10 | 11 | git clone https://github.com/pac-dev/AudioMasher 12 | cd AudioMasher/ 13 | go build 14 | 15 | Sporth is already compiled to JS and included in the project, but if you want to compile it yourself, the code glue is here: https://github.com/pac-dev/SporthEm 16 | 17 | ## Running 18 | - Serve the `static` folder from a static host. 19 | - Create your own `config/masher.config` based on the included example config file. 20 | - Run the AudioMasher binary alongside the `templates` and `config` directories. 21 | 22 | ## Troubleshooting 23 | - Files served by the static host might require some HTTP headers: 24 | - access-control-allow-origin set to the server address 25 | - content-type: application/wasm for the wasm file 26 | 27 | ## Tools used 28 | - Sporth is compiled to WASM using [Emscripten](https://github.com/kripken/emscripten) 29 | - Scripts and users are stored using [Storm](https://github.com/asdine/storm) 30 | - This project is tested with [BrowserStack](https://www.browserstack.com/). 31 | -------------------------------------------------------------------------------- /scripts/create-table-patches.json: -------------------------------------------------------------------------------- 1 | { 2 | "TableName": "MasherPatches", 3 | "AttributeDefinitions": [ 4 | { "AttributeName": "Id", "AttributeType": "S" }, 5 | { "AttributeName": "DateCreated", "AttributeType": "N" } 6 | ], 7 | "KeySchema": [ 8 | { "AttributeName": "Id", "KeyType": "HASH" }, 9 | { "AttributeName": "DateCreated", "KeyType": "RANGE" } 10 | ], 11 | "ProvisionedThroughput": { 12 | "ReadCapacityUnits": 5, 13 | "WriteCapacityUnits": 5 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /scripts/create-table-users.json: -------------------------------------------------------------------------------- 1 | { 2 | "TableName": "MasherUsers", 3 | "AttributeDefinitions": [ 4 | { "AttributeName": "Name", "AttributeType": "S" } 5 | ], 6 | "KeySchema": [ 7 | { "AttributeName": "Name", "KeyType": "HASH" } 8 | ], 9 | "ProvisionedThroughput": { 10 | "ReadCapacityUnits": 5, 11 | "WriteCapacityUnits": 5 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scripts/generate_ref.py: -------------------------------------------------------------------------------- 1 | import yaml, cgi 2 | 3 | refFile = open('ugen_reference.yml') 4 | data = yaml.load(refFile) 5 | refFile.close() 6 | for ugen in data: data[ugen].update({'name': ugen}) 7 | data = [(data[k]) for k in sorted(data)] 8 | 9 | ###################################### 10 | # create static/ugen_reference.html 11 | 12 | out = '' 13 | for ugen in data: 14 | out += '' 15 | if 'Link' in ugen: 16 | out += '' + cgi.escape(ugen['name']) + '' 17 | else: 18 | out += cgi.escape(ugen['name']) 19 | out += '
Below is a list of Sporth unit generators that can be used in-browser. 45 | Some of these ugens have links with short simple examples.
46 | """ + out + '
39 | A license (or the URL to a license) can be placed in the patch
40 | description or in a comment in the code itself. If there is no
41 | license there, all patches and the resulting audio are by default
42 | protected under the folliwing license:
43 |
44 | Attribution-NonCommercial-ShareAlike 3.0 Unported
45 | .
46 |
65 | pierre@osar.fr 66 |
67 |Patch | 6 |Author | 7 |Created | 8 |
---|---|---|
{{.Title}} | 12 |{{.Author}} | 13 |{{HumanDate .DateCreated}} | 14 |
The 15 | Sporth Cookbook 16 | was written by Sporth creator Paul Batchelor. This is the Interactive version, 17 | with modifications and features by Pierre Cusa. In this version, every code 18 | sample can be easily run and edited in-browser. Some stuff has also been removed, 19 | mostly because the in-browser version of Sporth can't load external files.
20 |From the editor to the right, you can type ctrl+Enter to compile and play, 22 | and ctrl+Space to stop playing. When the editor's text cursor is over a ugen, 23 | a tooltip will appear with the ugen's arguments and description.
24 |If you are new to Sporth, read Part 2: How Sporth Works, to learn the 25 | basics.
26 |Play around with Sporth. Have some fun with it. Then, dig into some of the 27 | thoroughly documented Sporth patches in Part 3: Example Sporth Patches. 28 | Studying these will help you master Sporth and build up an intuition for 29 | sound design in modular synthesis environments.
30 |F-tables, or "function tables" are terms borrowed from the Csound 15 | language. Simply put, ftables are arrays of floating point values. They are 16 | often used by audio-rate signal generators like samplers and table-lookup 17 | oscillators. Gen routines are in charge of filling stuff into the F-tables.
18 |Our most basic gen routine is gen_sine, which computes a single 20 | cyle of a sine wave. The code below shows how can build a 300 Hz sine wave 21 | using a table lookup oscillator:
22 | "sine" 4096 gen_sine
23 | 330 0.5 0 "sine" osc
24 | A few things to take note of:
25 |Most gen routines are adopted from Csound, where arguments exist inside a 34 | string, separated by spaces. In Sporth/Soundpipe this convention is carried 35 | over. The following patch uses sawtooth wavetable oscillator, whose wavetable 36 | was generated using the gen_line gen routine. For variety, a random number 37 | sample and hold generator is feeding into the frequency of the saw, and is 38 | also being fed into a butterworth lowpass filter.
39 | "saw" 4096 "0 1 4096 -1" gen_line
40 | (300 800 10 randh) 0.3 0 "saw" osc
41 | (1000 butlp)
42 | Gen routines conventionally follow a similar argument structure:
43 |NAME SIZE ARGSTRING gen_routine
44 |Where:
45 |Currently, the most comprehensive description of existing gen routines is the Soundpipe reference guide, 51 | which describes the Sounpipe function underlying each Sporth gen routine. For instance, 52 | here is the entry on gen_line, the gen 53 | routine used in above example.
54 |To save keystrokes and make ftables look prettier, the underscore key can be 56 | used in place of strings without spaces. For instance:
57 | "line" 4096 "0 1 4096 -1" gen_line
58 | Is identical to:
59 | _line 4096 "0 1 4096 -1" gen_line
60 | This is the preferred convention for ftables and variables, which will be 61 | discussed in the next chapter.
62 |Here are some common unit generators that make use of f-tables: 64 |
Triggers are a very important part of the Sporth ecosystem. They are a convention 16 | inspired by supercollider as well as the eurorack modular synth community.
17 |A single trigger is exactly one sample that is a non-zero value (typically, 18 | this value is just a '1'). Since everything in Sporth is sample accurate, triggers 19 | are sample accurate and can work at audio rate.
20 |Trigger-based signals feed into trigger-based ugens to generate signals. 21 | Some of these ugens include:
22 |To generate trigger signals, a few ugens are available:
40 |A gate is a unipolar, steady state-based signal that is either 0 or 1. These 55 | aren't as commonly used as triggers, but they are still used in some ugens 56 | that utilize gates:
57 |Gates are signals that can be multipled directly with other signals. They are 63 | very useful for things like reverb throws. You can also make a decent makeshift 64 | envelope putting a gate through a portamento filter to smooth out the edges:
65 | 1 metro tog 0.01 port
66 | A 1Hz metronome object is being fed into a toggle generator, whose value 67 | is going between 1 and 0 every second. This creates a gate signal which is 68 | then fed into the portamento filter, whose half time value is 10ms. 69 | The portamento filter (a simple one pole smoothing filter), 70 | creates the ideal exponential curves for envelope, with a convex exponential 71 | slope on the attack, and a concave exponential slope on the release. The 72 | "adsr" and "tenvx" ugens have been meticulously built and rebuilt to show these 73 | curves using this method. 74 | (You would be surprised how many envelope implementations I've seen 75 | that mess this up. Csound gets it wrong, as do most of the Eurorack 76 | modules!)
77 |There are few ways to generate gate signals in Sporth:
78 |Gate signals come into play more with APIs and other software, as gates 84 | are easier to make than one-sample trigger signals. Using gate with a thresh 85 | hold generator is often a good approach.
86 |Hello, dear reader. The sine wave is the most fundamental sound of 15 | computer music. It seems 16 | fitting here that it should be the first bit of Sporth code. Not quite a "hello 17 | world", but a "hello sine".
18 |The line below generates an A440 sine wave. This patch will be explained and expanded 19 | in the chapters to come:
20 | 440 0.5 sine
21 | 15 | In this third part of the interactive cookbook, you'll find a number of well-documented 16 | Sporth patches that can be run in the browser. Note that there are a few other places 17 | where you can find patches: 18 |
19 |15 | LSYS is a tiny little language designed to produce L-systems.
16 |A grammar for a classic L-system could look like this:
18 | a|a:ab|b:a
19 | The code is split up into three slices, delimited by the '|'. 20 | The first slice dictates the initial axiom, 'a'. 21 | The second slice dictates the definition for 'a' to be 'ab'. 22 | The third slice dictates the definition for 'b' to be 'a'.
23 |Once the code has been parsed, it can be used to generate a 24 | list, whose length is determined by the order N:
25 | N | output
26 | ----------
27 | 1 | a
28 | 2 | ab
29 | 3 | aba
30 | 4 | abaab
31 | 5 | abaababa
32 | 6 | abaababaabaab
33 | 7 | abaababaabaababaababa
34 | And so on and so forth...
35 |LSYS is implemented as a Sporth UGen, which takes in 37 | 3 arguments. From left to right, they are:
38 |The signal output by the LSYS ugen a number in 44 | the range of 0-35, which correspond to the base-36 45 | numbering system:
46 | 0123456789abcdefghijklmnopqrstuvwxyz
47 | In the example above, the signal would be alternating between 48 | 10 and 11.
49 |Here is an example of the lys ugen in action:
50 |# L-systems Test
51 | # Paul Batchleor
52 | # August 2016
53 |
54 | _tick var
55 | 0.1 dmetro _tick set
56 |
57 | _tick get dup
58 | 8 "a|a:ab|b:a" lsys 10 eq *
59 | 0.01 0.01 0.01 tenvx 1000 0.4 sine *
60 |
61 | _tick get dup
62 | 4 "a|a:ab|b:a" lsys 10 eq *
63 | 0.01 0.01 0.01 tenvx 500 0.4 sine *
64 |
65 | _tick get dup
66 | 3 "a|a:ab|b:a" lsys 10 eq *
67 | 0.01 0.01 0.01 tenvx 1500 0.4 sine *
68 |
69 | mix
70 | Using the C code found in Sporth 74 | , 75 | LSYS can also be compiled as a standalone application:
76 | gcc lsys.c -DLSYS_STANDALONE -o lsys
77 | It can be fed in code and the order as command line arguments:
78 | ./lsys 5 "01|0:121|1:01|2:1
79 | Which will produce the following string:
80 | 01101121011210101101121011210101121010110112101
81 | Stack operations manipulate the stack in Sporth. At the moment, Sporth has 15 | a very small set of stack operations.
16 |In order of usefulness, they are as follows:
17 |dup: duplicates the item on the stack ( a dup -> a a)
18 |swap: swaps the last two items on the stack ( a b swap -> b a )
19 |rot: rotates the third-to-last item on the stack: ( a b c rot -> b c a )
20 |Polysporth is an engine for polyphony in Sporth. Bindings for polysporth are 15 | written in the scheme dialect tinyscheme, included inside of Sporth.
16 |Sporth does a few things really well: it's simple, it's fast to write, and it's 18 | capable of building very complex signal chains. Sporth also does a few things 19 | quite poorly: concepts like events and notes are non-existent in Sporth, and 20 | polyphony is quite limited. Thus, an engine was built to enable these concepts. 21 | Polyphonic + Sporth = Polysporth!
22 |Some of the features include:
23 |Polysporth is called inside of Sporth, where a scheme file is loaded. 31 | Inside the scheme file, different chunks of sporth code are defined called 32 | sporthlets. Sporthlets can be individually scheduled to be be turned on and off.
33 |This chapter is really a gist of Polysporth. It's still in a very 35 | early phase. To learn more, be sure to check out the dedicated 36 | Polysporth project page.
37 |P-registers were an early way to share information without using the stack. 15 | It is simply an array accessible to Sporth where values can be easily 16 | read/written to. There are 16 p-registers, though this is easy to change in the source.
17 |Here is a simple patch demonstrating this:
18 | 440 0 pset
19 | 0 p 0.3 sine
20 | In the example above, the value of p-register 0 is being set to 440 via 21 | pset. In the line below, the value of p-register 0 is being read with 22 | p.
23 |P-registers are mostly useful for intergating Sporth with other applications, as 24 | they are trivial to read/write from the API. Another useful property of 25 | p-registers is that they persist from one sample to the next, so they allow the 26 | creation of nested delay lines and filters, or nesting other effects within 27 | a delay line, for example:
28 |tick 1 0.3 0 dust +
29 | 500 (-200 500 30 randh)+ 0.3 110 pluck
30 | 1 p 0 0.2 delay 0.6 *
31 | -1 500 50 pshift
32 | dup dup 1 1 5000 zrev + 2 * +
33 | 50 buthp dup rot + 1 pset
34 | A little silly side note on P-registers: the "P" stands for parameter. The term 35 | itself is borrowed from Csound, where p-fields, or parameter fields, are 36 | optional values read from a Csound score.
37 |Prop is one of the microlanguages embedded inside of Sporth. It is a 15 | rhythmic notation system based on proportions.
16 |Prop can be used to create very complex drum machines and rhythmic profiles.
17 |In Sporth, there are two ugens for prop. prop is the original ugen that compiles 18 | a prop string and a BPM, and creates a set of ticks. 19 | The ugen called tprop is identical to prop, except that it has a trigger 20 | argument that resets prop to the beginning.
21 |_seq "60 62 64 67 71" gen_vals
22 |
23 | 110 "+2(++)-" prop
24 | 1 _seq tseq
25 | mtof 0.01 port 0.1 saw
26 | 1000 0.1 moogladder
27 | For a full explanation of prop, consult the 29 | prop project page.
30 | # Bones
40 | # By Paul Batchelor
41 | # September 2016
42 | Bones is a quick sporthlet I made to get back into the habbit of 43 | making sporthlets. It primarily utilizes modal synthesize and variable 44 | feedback delay lines to achieve something vaguely bone like (well, 45 | at one point it was).
46 |The first table created is a set of modal ratios approximating a 48 | uniform wooden bar ratios. They have been obtained from 49 | http://www.csounds.com/manual/html/MiscModalFreq.html.
50 | _rat "1 2.5752 4.4644 6.984" gen_vals
51 | Variables are a relatively new concept in Sporth, and have their own 52 | commands for setting and getting. tk is a variable that will hold 53 | the trigger tick, and frq will eventually hold the fundamental 54 | frequency of the instrument.
55 | _tk var
56 | _frq var
57 | The base trigger is a metronome, whose rate is randomized via 59 | a randh module.
60 | 1 10 1 randh metro
61 | A consistent metronome can annoying quickly, so it is sent through 62 | a maytrig to hush it up every now and then. The probability randomly 63 | swings between 0.1 and 0.8 via randi. To make the line a little less 64 | regular, the rate gets changed with randh.
65 | (0.1 0.8 (1 10 3 randh) randi) maytrig
66 | The maytrig reduces the density, but phrasing is also important. 67 | for this reason, the output of the maytrig is multiplied by a maygate. 68 | (Could this have been done with just one maytrig? Probably. But that 69 | I did not think of this in the moment of creating this)
70 | dup 0.65 maygate *
71 | tick is added onto the trigger signal, to ensure that there is a 72 | trigger at the start of the sporth drawing.
73 | tick +
74 | The entire signal is assigned to the variable tk via the set ugen.
75 | _tk set
76 | An entire explanation of modal resonators and modal synthesis are 78 | beyond the scope of this sporthlet, but a small one will be given. 79 | One can think of a modal filter as a filter that resonants at a particular 80 | Q and frequency. In modal synthesis, an excitation signal (in this case, 81 | a trigger signal), is fed through a bank of modal filters, configured 82 | in both series and in parallel. What comes out is the sound. This technique 83 | is largely used in physical modelling.
84 |The base frequency of the instrument is set via trand, who trigger 85 | is obtained from the previously set variable tk.
86 | _tk get 500 1500 trand _frq set
87 | The trigger signal tk is sent through two modal filters in series, whose 88 | frequences are obtained from the first two elements table rat, multiplied 89 | by the frequency frq. In the first modal filter, the Q is being randomized 90 | via randi.
91 | _tk get (_frq get 0 _rat tget *) 10 40 3 randi mode
92 | (_frq get 1 _rat tget *) 20 mode
93 | The process is repeated again, this time with the last two modal 94 | frequency ratios in rat, and different Q values (by the way, these 95 | Q values were chosen fairly randomly with a little experimentation)
96 | _tk get (_frq get 2 _rat tget *) 22 mode
97 | (_frq get 3 _rat tget *) 15 mode +
98 | The variable delay line is the crucial element that adds a strange crittery 100 | characteristic to the sound. This is done by randomizing the feedback 101 | and delay times with randi.
102 | dup 0.1 0.8 3 randi
103 | 0.001 0.2 (0.1 4 1 randi) randi
104 | 1.0 vdelay
105 | To add more texture to the delay line, a smoothdelay is applied 106 | to the variable delay signal. A smooth delay line is like a variable 107 | delay line, except there is no residual pitch modulation. Randi is used 108 | to control feedback, and sine is used to control delay time. 109 | To add space and variation, the level is modulated by yet another 110 | randi.
111 | dup 0.4 0.9 0.5 randi
112 | 0.2 1 sine 0.01 0.3 biscale
113 | 1.0 64 smoothdelay 0 1 0.2 randi *
114 | Finally, the delay lines and dry signal are summed together.
115 | + +
116 | The final effects used are a gentle chowning reverb fed through 118 | a hard limiter limit.
119 | dup jcrev -6 ampdb * + -1 1 limit
120 |
121 |
122 |
123 | "Chatting Robot", also known as "computer talking on the phone with his 54 | Mother", is a patch centered around a rudimentary vowel synthesis 55 | technique. This technique involves an approximate glottal excitation 56 | signal sent through three bandpass filters tuned at specific formant 57 | frequencies. 58 |
The tables are used to store parameters for the three bandpass filters. 60 | Tables are used to interpolate between three different states, each corresponding 61 | to a different vowel sound:
62 | 63 |The interpolation between the states allows the voice to smoothly morph 69 | between vowels.
70 |Tables f1, f2, and f3 correspond to the first, second, and third 71 | bandfilters, respectively.
72 | # o a e
73 | 'f1' '350 600 400' gen_vals
74 | 'f2' '600 1040 1620' gen_vals
75 | 'f3' '2400 2250 2400' gen_vals
76 | Tables g1, g2, and g3 correspond to the gains of the bandpass filters.
77 | 'g1' '1 1 1' gen_vals
78 | 'g2' '0.28184 0.4468 0.251' gen_vals
79 | 'g3' '0.0891 0.354 0.354' gen_vals
80 | Tables bw1, bw2, and bw3 correspond to the bandwidths of the bandpass 81 | filters.
82 | 'bw1' '40 60 40' gen_vals
83 | 'bw2' '80 70 80' gen_vals
84 | 'bw3' '100 110 100' gen_vals
85 | The jitter signal is in charge of morphing between the vowel sounds. The 87 | core of the jitter signal is randi, who range and between 0 and 2, 88 | and whose speed is modulated by a sine wave between 2 and 10.
89 |The output signal is then copied to p-register 0.
90 | 0 2
91 | 0.1 1 sine 2 10 biscale
92 | randi 0 pset
93 | To simulate the glottis, a bandlimited pulse oscillator is used with a 95 | now pulse width, or duty cycle.
96 |The frequency of the glottis is randomly modulated with another randi 97 | jitter signal in the range of 110 and 200 Hz. The rate of change is modulated 98 | with a sine wave in the range of 2 and 8 Hz.
99 | 110 200 0.8 1 sine 2 8 biscale randi
100 | Additional deviation is added using jitter.
101 | 3 20 30 jitter +
102 | Finally, the rest of square, with an amplitude of 0.2 and a pulse width of 103 | 0.1.
104 | 0.2 0.1 square
105 | dup
106 | The glottal signal is fed through three bandpass filters in series to generate 108 | vowel sounds.
109 |As mentioned in the previous section, parameters are linearly interpolated 110 | between three states in order to morph between vowel sounds. The 111 | interpolation is done using tabread.
112 | 0 p 0 0 0 'g1' tabread *
113 | 0 p 0 0 0 'f1' tabread
114 | 0 p 0 0 0 'bw1' tabread
115 | butbp
116 | and here are the other two filters...
117 | swap dup
118 | 0 p 0 0 0 'g2' tabread *
119 | 0 p 0 0 0 'f2' tabread
120 | 0 p 0 0 0 'bw2' tabread
121 | butbp
122 | swap
123 | 0 p 0 0 0 'g3' tabread *
124 | 0 p 0 0 0 'f3' tabread
125 | 0 p 0 0 0 'bw3' tabread
126 | butbp
127 | + +
128 | The signal generated up to this point is pretty good, but it is a 130 | endless babble with no breaks. 131 | It would be good to add some pockets of silence to add space and create 132 | words and phrases. 133 | This effecting is created using a makeshifte envelope generator, built 134 | from a maygate and a portamento filter. The maygate is being fed by 135 | a dmetro, sending off a tick every 400 milliseconds.
136 | 0.4 dmetro 0.5 maygate 0.01 port * 2.0 *
137 | Finally, the resulting signal is sent into zitareverb.
139 | dup
140 | 60 500 3.0 1.0 6000
141 | 315 6
142 | 2000 0
143 | 0.2 2 zitarev drop
144 |
145 |
146 |
147 | 440 440 sine
15 | 440 +
16 | 0.5 sine
17 | We are going to make an FM pair from scratch. 18 | First we start with a modulator signal, which controls (modulates) the 19 | frequency of the carrier oscillator.
20 | 440 440 sine
21 | This creates a sine wave that goes between -440 and 440. Next we bias it 22 | by 440 Hz so it centers 440 Hz, going down to 0 Hz and going up to 880 Hz:
23 | 440 +
24 | Finally, we send this signal to modulate the frequency carrier signal:
25 | 0.5 sine
26 | What we end up with is a basic FM pair at 440 Hz, 1 1:1 Carrier-Modulator 27 | ratio, with a modulation index of 1, and an ampitude of 0.5.
28 | 29 | # in seven
45 | # paul batchelor
46 | # january 2017
47 | The sporthling in seven is a recreation of an earlier csound live coding 48 | patch that I wrote. It can be summarized as being a sequenced FM synth 49 | with careful use of C:M ratio modulation and reverb throws. 50 |
To begin, a number of tables and variables are created: 52 |
_seq "0 4 5 7 -2" gen_vals
64 | _clk var
65 | _dry var
66 | _send var
67 | The main clock signal used in this patch is generated via prop, a 69 | micro-language for generating rhythms based on proportions. A full 70 | explanation of prop is beyond the scope of this document, but more 71 | information can be found here.
72 |The main gist of what is happening is that "prop" is generating a 73 | 7/8 rhythm with a 223 subdivision. The tempo is set to 210 BPM. T
74 |This signal is saved to the variable clk.
75 | 210 "++2(+-+)" prop _clk set
76 | The crux of this patch is driven by FM oscillator. The parameters of 78 | an FM oscillator are ampltiude, frequency, C:M ratio, and a modulation 79 | index. Each of these parameters are carefully modulated to produce 80 | the range of timbres that you hear. 81 |
The frequency is being modulated via tseq, a trigger sequencer. The 83 | clock generated by prop feeds this signal and causes it to cycle through 84 | the table seq in time. The sequence is then biased by 58 to put it 85 | in the key of B flat, then converted to frequency with mtof. To prevent 86 | clicks, a small portamento filter is added.
87 | 88 | _clk get 0 _seq tseq 58 + mtof 0.001 port
89 | The amplitude of this parameter is fixed, and is set to 0.3.
90 | 0.3
91 | The carrier component is part of the C:M ratio. It is typically a 93 | positive integer value denoting the frequency of the "carrier" oscillator, 94 | which is the oscillator that you actually hear.
95 |The clock signal in the variable clk is being used with the triggerable 96 | random number generator trand to produce a value between 1 and 3. 97 | This value is converted to a whole number integer via floor. It is 98 | important to keep this value as a whole number to prevent 99 | clangorous non-harmonic timbres. When the Carrier component is anything 100 | but 1, it makes the frequency appear to jump to Carrier * frequency.
101 | _clk get 1 3 trand floor
102 | The modulator component is the second part of the C:M ratio. This 104 | component determines the frequency of the modulator oscillator, which 105 | modulates the frequency of the carrier oscillator. Larger values will 106 | cause harmonics to be more sparsely spread out, and often will sound 107 | brighter.
108 |Similar to the carrier signal, it is being modulated via trand, this 109 | time using values between 1 and 7. The signal is also being floored to 110 | keep timbres "tonal".
111 | _clk get 1 7 trand floor
112 | The modulatation index determines how much frequency modulation happens. 114 | A modulation index of 0 creates a sinusoidal tone. The modulation index 115 | can be crudely thought of as a brightness control or something analogous 116 | to filter cutoff in a subtractive synthesis patch.
117 |The modulation index is controlled by a low-frequency sinusoidal oscillator 118 | moving at a period of 30 seconds, moving between 0.1 and 2 via biscale.
119 | 30 inv 1 sine 0.1 2 biscale
120 | This being the last argument, it is closed with the actual "fm" word.
121 | fm
122 | To add excitement, some of the signal is occasionally thrown into a 124 | reverb in what as known as a reverb throw. In analogue days, this was 125 | done using a fader sporadically moved up from time to time.
126 |To begin, a copy of the FM sound is created via dup. One of these 127 | signals will signal sent to the reverb.
128 | dup
129 | To simulate the sporadically on fader, the clock signal clk is fed into a 130 | maygate. When triggered, maygate will have a 20% probability of 131 | turning on. This allows tempo-synced throws to occur.
132 | _clk get 0.2 maygate
133 | The maygate signal is either on or off, which can cause very sharp 134 | jumps. To smooth this out, a portamento filter is used.
135 | 0.01 port
136 | This signal is then multiplied with one of the copies of FM signal, and 137 | stored into the variable send.
138 | * _send set
139 | Right now, the FM signal is constantly "on"; there is no sense of 141 | articulation (authors note: the throw signal has no articulation. This 142 | was by accident, by it will be left in). To create articulation, an 143 | envelope signal is created via tenv. It is clocked by the global 144 | clock clk. The signal is then multiplied with the dry signal.
145 | _clk get 0.001 0.01 0.4 tenv *
146 | This completes the dry signal, so it is set to the variable dry.
147 | _dry set
148 | For reverb, revsc is used. The input signal of the reverb module is 151 | a combination of the dry signal and the send signal boosted by a factor 152 | of 2. Since this reverb is stereo, it is duplicated.
153 | _dry get _send get 2 * + dup
154 | The reverb parameters are set to have a 0.97 decay rate (1.0 being an 155 | infinite hold reverb) with a 5000Hz cutoff.
156 | 0.97 5000 revsc
157 | Since revsc is a stereo signal, one of the values is droped. 158 | The remaining signal is attenuated by 27 dB, making it only really audible 159 | when dry signal is "thrown" into it.
160 | drop -27 ampdb *
161 | ReverbSC causes a lot of DC offset, so a dc blocker filter dcblk is 162 | used.
163 | dcblk
164 | In addition to reverb, a tempo-synced delay line is also processed with 166 | the signal in parallel.
167 |The input signal is only the throw signal from the variable send.
168 | _send get
169 | The feedback parameter is set to 0.8, or 80 percent loss.
170 | 0.8
171 | The delay time is set to be a dotted quarter note delay time. This is 172 | done by converting the BPM (210) to a duration, then multiplying that 173 | by 1.5.
174 | 210 bpm2dur 1.5 *
175 | The output of the delay signal is sent into a butter lowpass filter 176 | butlp with a cutoff of 800Hz. This is done so it does not interfere 177 | with the spectrum of the dry signal. After that, it is attenuated by 178 | 7 dB.
179 | delay 800 butlp -7 ampdb * +
180 | The dry signal is added back in, thus completing the patch.
181 | _dry get +
182 |
183 |
184 |
185 | Midnight crawl is a prickly warm little patch featuring string resonators, 48 | and warm FM and subtractive saw sounds. 49 |
_seq "0 2 7" gen_vals
60 | _prog "0 2 5 7 9" gen_vals
61 | _seq2 "0 2 7 11 14 19" gen_vals
62 | _clk var
63 | _send var
64 | _env var
65 | First, the clock signal is created. The clock is generated using 67 | dmetro. It is set to 125 BPM, then multiplied by 4 to get 68 | sixteenth note subdivisions. The clock is sent into a maytrig 69 | with an 80% probability rate. All of this is assigned to 70 | the variable clk.
71 | 125 4 * bpm2dur dmetro 0.8 maytrig _clk set
72 | The warm sound saw is a sequenced subtractive sawtooth patch. 74 | The sequence is created via tseq, using the values inside of seq, 75 | and the clock clk.
76 | _clk get 0 _seq tseq 58 +
77 | To add a sense of a chord progression, another "progression" sequence 78 | is added to the note as a bias. The same clk signal is used, but it 79 | is fed into clock divider tdiv so that the "chord" changes every 80 | 4 notes. To keep it from changing that frequently, the clock signal is 81 | fed into a maytrig.
82 | _clk get 4 0 tdiv 0.1 maytrig 1 _prog tseq +
83 | To add occasional octave leaps, maygate scaled by 12 is added into 84 | the sequence signal.
85 | 12 _clk get 0.2 maygate * +
86 | To simulate analogue "drift", jitter is added to the sequenced signal. 87 | This entire signal is converted to frequency via mtof.
88 | 0.1 0.1 5 jitter + mtof
89 | Portamento between frequencies is controlled via port. The portamento 90 | time is randomized via randi to pick a value between 1 and 20 milliseconds 91 | every 5 seconds (inv efficiently inverts a number).
92 | 0.001 0.02 (5 inv) randi port
93 | With the frequency set, the amplitude of the saw is set to 0.4.
94 | 0.4 saw
95 | The sawtooth signal is fed into butterworth lowpass filter butlp. 96 | The cutoff frequency of the filter is determined via a sinusoidal LFO 97 | sine, modulating between 200 and 800 Hz.
98 | 15 inv 1 sine 200 800 biscale butlp
99 | The amplitude of the filtered saw signal is modulated by envelope 100 | generator tenv. The envelope used is very long, and is meant to 101 | be the contour for many notes at a given time. Since the clock signal 102 | is way too fast for this, it is sent through maytrig.
103 | _clk get 0.05 maytrig 1.4 1.1 2.3 tenv *
104 | Now for the instrument I like to call "streson prickles". These are the 106 | most novel sounds of the patch, created using a series of string resonators. 107 | The start of this sound are short little noise bursts, created from 108 | clk, maytrig, tenvx, and noise.
109 | _clk get 0.7 maytrig 0.001 0.001 0.001 tenvx 1.9 noise *
110 | Streson, is a string resonator. When fed an impulse (like from the noise 111 | bursts ) it will produce a karplus strong pluck sound. In addition to the 112 | input signal, there are two arguments to streson: the filter frequency 113 | determines the frequency of the string, and the feedback amount determines 114 | how long the note stays on for. The first string resonator has both 115 | the frequency and feedback parameters being randomized by clk-synchronized 116 | random number generators trand.
117 | _clk get 100 1000 trand _clk get 0.8 0.9 trand streson
118 | This signal is fed through two other string resonators in series. 119 | The decay times are set to be constants at 0.9, and 0.8, respectively.
120 | _clk get 400 4000 trand 0.9 streson
121 | _clk get 100 1000 trand 0.8 streson
122 | To keep samples numerically reasonable, this signal is fed through a 123 | DC blocker dcblk and attenuated by 10 dB. Some of the high end is 124 | shaved off using a lowpass filter butlp.
125 | dcblk -10 ampdb * 2500 butlp
126 | This sound is complete, but copy of it is sent to the variable send 127 | to be used as a throw signal. The throw mechanism below is built out 128 | of a maygate, tdiv, and port filter.
129 | dup _clk get 4 0 tdiv 0.1 maygate 0.01 port * _send set +
130 | The final instrument in the mix is a pinging FM sound. It is a sound 132 | buried in the mix. As the trigger signal will show, it is very sparse, 133 | having clk being set through a very large clock divider and 134 | maytrig.
135 | _clk get 16 0 tdiv 0.5 maytrig
136 | A copy of the signal maytrig is made with dup, and it is fed into 137 | the envelope generator tenvx. This generator sets the envelope env.
138 | dup 0.005 0.01 0.3 tenvx _env set
139 | The clock signal that was dupd before feeds into a tseq, where it 140 | is then biased by 58. When I wrote this patch, I wanted to bump this synth 141 | up an octave, so I added 12 to it too.
142 | 1 _seq2 tseq 58 12 + + mtof
143 | The amplitude of the FM synth is is modulated by a slow moving LFO.
144 | 13 inv 1 sine 0.01 0.2 biscale
145 | The C:M ratio is set to a typically very bright 2:5.
146 | 2 5
147 | The modulation index is modulated by envelope stored in env. This 148 | basically controls "brightness" in the FM world. This is the final argument 149 | in fm, the sound generator.
150 | _env get 5 * fm
151 | In addition to manipulating the timbre, env also controls the amplitude. 152 | this signal is then fed into butlp.
153 | _env get * 1500 butlp
154 | The instrument signal generated is copied and added onto the send signal, 155 | which will be sent into some effects.
156 | dup 0.3 * _send get + _send set +
157 | The first effect used in this patch is a feedback delay, with a very 159 | large feedback parameter. The delay time is based on the BPM, and is set 160 | to approximately dotted quarters. I say "approximately" because I made it 161 | slightly longer to make it go out of phase with the dry signal.
162 | dup 0.9 125 bpm2dur 1.51 * delay 2000 butlp -8 ampdb * +
163 | Next, the send signals are processed. First, send is fed into 164 | revsc. It is fed into a dc blocker dcblk, and a high pass filter 165 | buthp.
166 | _send get dup 0.98 5550 revsc drop 0.9 * dcblk 200 buthp +
167 | The send signal is fed into a tempo-synced feedback delay line 168 | delay. It is attenuated and then added into the rest of the patch.
169 | _send get 0.3 125 bpm2dur delay -3 ampdb * +
170 |
171 |
172 |
173 | This was a quick patch I whipped up some morning while eating some oatmeal. 38 | It is part of my ongoing collection of sporthlings. 39 | By and large, this patch was derived by layering simple sounds together. 40 |
Tables generation is minimal. A sine lookup table is generated for some oscillators, 42 | A single variable d is used for the dust signal.
43 | 44 | _wav 8192 gen_sine
45 | _d var
46 | The dust ugen is a ugen that fires random impulses at a certain 48 | density. It is unique in that it works well as both a control signal as 49 | well as an audio signal. This patch uses this signal for both purposes.
50 |The first argument to dust is amplitude, which is set to a constant 2.
51 | 2
52 | The second argument is density, which is controlled by a low-frequency 53 | oscillator, set at 0.2 Hz. It is scaled via biscale to go between 54 | 5 and 20 impulses per second.
55 | 0.2 1 0 _wav osc 5 20 biscale
56 | The rest of dust is written out. The final argument to dust is 57 | the mode, which has been set to 1, bipolar mode. 58 | The signal is duplicated, and one of the values is set to teh variable d.
59 | 1 dust dup _d set
60 | The dust signal continues onward, being filtered by wpkorg35. It is set 61 | to a cutoff frequency of 1100 with a very high resonance (this particular 62 | filter has a range of 0-2, and the resonance is 1.9. The sonic output 63 | of putting impulses into a highly resonant filter little sinusoidal blip 64 | sounds. One of my favorite go-to effects.
65 | 1100 1.9 0 wpkorg35
66 | To add some body, the blips are fed into the chowning reverb, which I have 67 | found to be very favorable to sinusoidal signals for some reason.
68 | jcrev
69 | The next layer in our cake of sound are what I will call "blerps". These 71 | are rhythmic blips tuned at whole integer ratios from one another.
72 |The make up of a single "blerp" consists of a sinusoid and an envelope. 73 | The envelope is created gate signal (generated via tgate) 74 | being sent through a portamento filter. The gate is controlled via the 75 | dust signal stored in variable d. To make things less busy, the trigger 76 | is fed into a maytrig.
77 | _d get 0.5 maytrig 0.01 tgate 0.001 port
78 | The envelope generatored is then multipled with a sinusoidal oscillator 79 | tuned at 1000Hz. This signal is then added onto the current signal.
80 | 1000 0.3 0 _wav osc * +
81 | The same thing more or less is repeated 2 more times, with a few variations 82 | in parameters. The oscillators are tuned to 2000Hz and 500Hz.
83 | _d get 0.5 maytrig 0.004 tgate 0.001 port
84 | 2000 0.3 0 _wav osc * +
85 | _d get 0.2 maytrig 0.004 tgate 0.001 port
86 | 500 0.3 0 _wav osc * +
87 | Randh, like dust, can function equally well as a control signal and an 89 | audio signal. When the rate is set to audio-rate frequencies, you can get 90 | different "colors" of noise, an sound reminiscent to 8-bit game sounds.
91 |For starters, an envelope signal is generated in similar manner to the 92 | envelopes created in the previous section.
93 | _d get 0.3 maytrig 0.04 tgate 0.005 port
94 | The arguments for ugen randh can now begin. These two arguments tell 95 | randh to produce random values between (+/-)0.05.
96 | -0.05 0.05
97 | The third argument to randh is rate (or frequency), which we have 98 | delegated to a triggerable random number generator trand. The 99 | trigger signal is being fed by our dust signal, and is producing random 100 | values between 1102Hz and 11025Hz.
101 | _d get 1102 11025 trand
102 | Finally, randh is called and multiplied with the corresponding 103 | envelope. It is then added to the current signal.
104 | randh * +
105 | The effects processing is used in a very unusual way. One could argue that 107 | the processing is an instrument as well.
108 |To begin, the signal is duplicated so that a dry version of the signal 109 | is stored.
110 | dup
111 | The first thing created is a "throw" signal. The dust signal used from 112 | before feeds into a very sparse maytrig. This trigger signal feeds 113 | into a tenv, where a very gentle envelope signal is multipled with 114 | the signal to be processed. The results is we are occasionally "throwing" 115 | the signal into processor. This term is borrowed from what is known by 116 | mix engineers as a "reverb throw".
117 | _d get 0.1 maytrig 0.4 0.1 0.1 tenv *
118 | The thrown signal is fed into a feedback delay of 300ms with a feedback 119 | amount of 60 percent.
120 | 0.6 0.3 delay
121 | The feedback delay is fed into the pitch shifter pshift, where it is 122 | tuned up an octave.
123 | 12 1024 512 pshift
124 | The pitch shifted signal is put through a butterworth lowpass, and then 125 | attenuated by 6dB. The dry and wet signal are then added together.
126 | 1000 butlp -6 ampdb * +
127 |
128 |
129 |
130 | Play with toys is a fun little patch making use of sequencing and 36 | weighted random distributions. 37 |
The only table used in this patch is called seq, used as the main 39 | sequencer. The table is generated using gen_rand, which generates 40 | a weighted distribution for randomness.
41 | 42 | _seq 32 "0 0.4 7 0.4 12 0.1 5 0.1" gen_rand
43 | The patch uses a number of variables through out the patch: 44 |
_clk var
53 | _trig var
54 | _freq var
55 | _dry var
56 | _bpm 110 varset
57 | After the variables and tables have been declared, it is time to set 59 | up the main sequencer. Like many patches, this starts with a clock signal. 60 | This is done using clock.
61 | 0 _bpm get 4 clock _clk set
62 | From the clock signal, a trigger signal is derived by feeding it through 63 | maygate.
64 | _clk get 0.6 maytrig _trig set
65 | Next, the trigger signal drives tseq. tseq is set to shuffle mode, 66 | randomly sequencing through the distribution in seq. It is then 67 | biased by 60 (middle C) and assigned to freq.
68 | _trig get 1 _seq tseq 60 + _freq set
69 | The crux of this patch comes from the FM oscillator described below. 71 | Here, the frequency is obtained from freq and converted from MIDI 72 | to frequency using mtof. The next argument, amplitude, is set to 0.1.
73 | _freq get mtof 0.1
74 | The trigger signal trig is used to randomly modulate the C:M ratio 75 | of the FM pair via trand. To keep the spectra harmonically related 76 | to the fundamental and not clangorous, it is important to ensure that 77 | the C:M ratio uses whole integers, so the output of trand is 78 | floored using floor.
79 | _trig get 0 3 trand floor
80 | _trig get 0 7 trand floor
81 | A static modulation index of 1 is used, which completes the arguments for 82 | fm.
83 | 1 fm
84 | The amplitude envelope of the FM oscillator is generated via tenvx. 85 | The trigger signal trig used above also feeds into tenvx. The FM 86 | and envelope signals are then multiplied together.
87 | _trig get 0.005 0.01 0.04 tenvx *
88 | Acting as a bassline, a subtractive saw oscillator uses a bandlimited 90 | sawtooth oscillator fed through a moog filter. The frequency used 91 | is identical to the frequency used in the FM oscillator, but pitched 92 | down an octave to exist in the bass register.
93 | _freq get 12 - mtof 0.1 saw 1000 0.1 moogladder
94 | The envelope generator in the bass patch also uses the same trig trigger 95 | signal, except that it is fed into a maytrig to make it more sparse and 96 | less overwhelming in the mix.
97 | _trig get 0.3 maytrig 0.01 0.01 0.1 tenvx *
98 | The bass patch is then mixed into everything else so far, and assigned to 99 | the variable dry.
100 | + _dry set
101 | For effects processing, the dry signal dry is fed into the zita reverb 103 | module zrev.
104 | _dry get dup 10 10 10000 zrev drop -10 ampdb *
105 | To keep the mix from getting too muddy, the reverb is highpassed at 1000kHZ, 106 | leaving plenty of space for the bass instrument.
107 | 1000 buthp
108 | This signal is then added back into the dry mix.
109 | _dry get +
110 |
111 |
112 |
113 | A sine wave table is used for the many sinusoid LFOs used in the patch. 44 | A table of notes is created using gen_vals, which will be used for 45 | the main melody.
46 | _sine 8192 gen_sine
47 | _seq "62 69 72 71 74" gen_vals
48 | The pads provide the canvas for the patch. For a sound generator, a 50 | buzz generator is used, called gbuzz. gbuzz produces a set of 51 | harmonically related sinusoids, which approximately sounds like a 52 | sawtooth. The strength of each successive overtone is modulated with 53 | a sine LFO via osc*. This modulates the overall timbre of the sound.
54 | 62 mtof 0.3 8 3
55 | 15 inv 1 0.2 _sine osc
56 | 0.1 0.6 biscale gbuzz
57 | This sound gets repeated 2 more times to form a triad with notes 58 | C, D, and A, which outlines some sort of D7 chord. The phase and 59 | frequency of each LFO is tweaked to make the voices more individual.
60 | 60 mtof 0.3 8 3
61 | 10 inv 1 0 _sine osc
62 | 0.1 0.6 biscale gbuzz
63 |
64 | +
65 |
66 | 12 inv 1 0.75 _sine osc
67 | 0.1 0.6 biscale gbuzz
68 |
69 | +
70 | The summed signal is attenuated by 6dB and put through a butterworth 71 | highpass filter.
72 | -8 ampdb * 400 buthp
73 | The chorused pluck sound is the lead instrument sound that plays the 75 | melody. It begins with a clock signal generated via clock, and 76 | then put through a maytrig. This signal is duplicated. One will 77 | feed the sequencer, the other will be the trigger signal for pluck.
78 | 0 77 1 clock 0.4 maytrig dup
79 | The melody generated comes from the seq table generated above. tseq 80 | is set to shuffle mode, picking notes randomly. A small bit of portamento 81 | is added as a stylistic choice, but it is not necessary.
82 | 1 _seq tseq mtof 0.003 port
83 | The rest of the arguments for pluck, then a lowpass filter butlp. 84 | The last argument to pluck is the lowest intended frequency to be used with 85 | pluck. This is a static value that sets the buffer size. This parameter 86 | is worth experimenting with a bit, as it drastically changes the sound 87 | and decay of the sound.
88 | 0.5 50 pluck 3000 butlp
89 | The puck signal is fed through a string resonator, which is the filtered 90 | version of pluck. This string resonator is tuned to F#, a major third 91 | above "D". The idea is to roughly simulate nodes and antinodes of a string 92 | body. When the plucked string plays an F# it will ring louder. 93 | streson and pluck are known to build up a lot of DC, so a dc blocker 94 | is placed at the end.
95 | 66 mtof 0.91 streson dcblk
96 | This string sound is duplicated, and fed into a chorus effect. 97 | Sporth does not have a chorus effect, but a makeshift one can be made 98 | easily using a vdelay and slow, small modulations of the delay time. 99 | For good measure, a dc blocker is used here as well (I must have been 100 | getting a lot of DC when making this patch!)
101 | dup 0.4 0.1 1 0 _sine osc 0.002 0.008 biscale 0.2 vdelay + dcblk
102 | The chorused instrument is then fed into a delay line whose delay time 103 | is set to a dotted-eigth note duration. This delay is fed into a butterworth 104 | lowpass filter to keep it out of the way, then scaled to make it quieter.
105 | dup 0.7 77 bpm2dur 0.75 * delay 2000 butlp 0.5 * +
106 | This signal attenuated by 3dB, then added to the rest of the mix.
107 | -3 ampdb * +
108 | This instrument provides the bass sound, a constant pulsating drone. It 110 | is comprised of two detuned bandlimited sawtooth oscillators tuned to a 111 | D.
112 | 26 mtof 0.2 saw 38.1 mtof 0.3 saw +
113 | As stated in the title, this is a subtractive patch, and the lowpass 114 | filter used here is diode, a filter design based of the one used by 115 | the TB303. The filter cutoff is modulated by a slow-moving LFO whose period 116 | is 8 seconds.
117 | 8 inv 1 0 _sine osc 500 2000 biscale 0.1 diode
118 | This sound is added the rest of the patch. The entire patch is attenuated 119 | by 3 dB.
120 | + -3 ampdb *
121 |
122 |
123 |
124 | Sporth is a stack-based language, largely inspired by languages like Forth and 15 | Postscript. In fact, Sporth is a blending of the words "Soundpipe" and "Forth". 16 | Understanding how the stack works is key to effectively using Sporth.
17 |A stack in computer science is a simple data structure, typically implemented 18 | inside an array. Items can be pushed to the stack and popped off the stack. 19 | For this reason, a stack is also known as a Last In, First Out (LIFO) data type.
20 |In stack-based languages like Sporth, the stack is the means for how data is 21 | exchanged. For this reason, arguments precede function calls. This can be seen 22 | in the A440 sine seen in the previous chapter:
23 | 440 0.5 sine
24 | In this code, the numbers "440" and "0.5" are arguments to the sine generator, 25 | for both frequency and amplitude, respectively. This patch code be broken down 26 | into stack operations:
27 |Monophonic Sporth patches will always have a value left on the stack meant for 37 | the speaker. This value should be in the range (-1, 1). In this patch, 38 | the sine generator is responsible for producing this last value. This sine generator 39 | is known as a "unit generator" or "ugen". Ugens are the core means of producing 40 | sound and signal alike. Sporth has over 41 | 100 unit generators. These unit generators can be used together to build up 42 | very complex patches.
43 |The patch below now has two sine waves being summed together. The frequencies 44 | produce a North American dial tone on phones:
45 | 440 0.3 sine
46 | 350 0.3 sine
47 | +
48 | In this patch, a 440hz sine is created, followed by a 330hz. These signals 49 | are then summed together with the '+' operator.
50 |In stack operations, this works as follows: 51 |
Despite the fact that all unit generators are audio-rate, they 71 | don't necessarily need to be used to generate audio. They 72 | are perfectly content being used to generate signals. In fact, the differences 73 | between a control signal and an audio signal is rather arbitrary. This is important.
74 |This patch below uses 75 | a sine wave to control the frequency of another sine wave:
76 | 6 40 sine
77 | 440 + 0.3 sine
78 | The first sine wave is 6 Hz with an amplitude of 40, which means the signal 79 | has a range (-40, 40). This signal is then being 80 | added to the constant value of 440. After the addition, this yields a signal 81 | oscilliating between 400 and 480. This value feeds into the frequency 82 | argument of another sine wave with an amplitude of 0.3.
83 |Sporth is largely whitespace insensitive. This patch could all be written 84 | on one line as follows:
85 | 6 40 sine 440 + 0.3 sine
86 | Parentheses are ignored. They can be used as a means to group things:
87 | ((6 40 sine) 440 +) 0.3 sine
88 | Hopefully that looks a little bit clearer.
89 |Study this patch. The stack operations create an implied signal flow. Being 90 | able to see the signal path in this configuration is a good first step 91 | in developing an intution for modular sound design.
92 |Takes the absolute value of signal.
16 |The example below turns a bipolar noise signal and makes it strictly 17 | positive
18 | 0.5 noise abs
19 |
20 |
21 |
22 | Add adds two numbers together! 16 | It can be called via "add" or "+".
17 | 350 0.2 sine
18 | 440 0.2 sine
19 | 500 0.2 sine add +
20 |
21 |
22 |
23 | A gate-based ADSR envelope generator.
16 |ADSR takes the following arguments: 17 |
In the example below a toggle signal generated with the ugens dmetro 30 | and tog are fed into adsr. This signal is then multiplied with 31 | a 440hz sine wave.
32 | 2 dmetro tog 0.1 0.1 0.9 0.8 adsr 440 0.5 sine *
33 |
34 |
35 |
36 | This is an allpass filter implementation taken from the Csound opcode 16 | alpass (note: 17 | spelling intentionally incorrect here.). In general, allpass filters are 18 | unique in that they have unit-magnitude frequency response for the whole 19 | spectrum. This particular allpass filter design is used in reverberation 20 | design to provide colorless density. For this reason, units for this ugen 21 | are in seconds rather than in hertz.
22 |The arguments for allpass are as follows: 23 |
The example below shows a typical usecase for an allpass filter. Allpass
32 | filters are often used in series. In the example below, three sucessive
33 | instances of allpass are created. For an input signal, an impulse is being
34 | generated with a broadband noise signal with an exponential envelope.
35 | The resulting sound you hear is the "impulse response", which gives you
36 | the characteristics of the reverb.
1 metro 0.001 0.001 0.001 tenvx 0.5 noise *
38 | 5 0.010 allpass
39 | 5 0.013 allpass
40 | 5 0.008 allpass
41 |
42 |
43 |
44 | Ampdb converts decibels to amplitude.
16 |Below is pink noise at an amplitude of 6 dB below full scale
17 | -6 ampdb pinknoise
18 |
19 |
20 |
21 | Performs logical "and" operation.
16 | # 7 & 4 = 4
17 | 7 4 &
18 |
19 |
20 |
21 | Arguments: input, cutoff
16 |Where 17 |
Atone is a simple one pole high-pass filter, directly related to tone.
23 | 0.1 noise dup 2000 atone bal
24 |
25 |
26 |
27 | Original a guitarix effect written in Faust, autowah is an automatic 16 | wah pedal. These typically involve some sort of envelope follower on an 17 | hooked up to the cutoff frequency of the wah filter 18 |
96 "2(++)4(?+?+)" prop 0.5 maytrig dup
28 | 440 0.3 220 pluck dcblk swap 0.001 0.01 0.2 tenvx *
29 | 1 1 100 autowah
30 |
31 |
32 |
33 | Arguments: reference, input
16 |Bal comes from the csound opcode "balance". Bal will take an input signal, 17 | and match the amplitude of the signal with a reference signal.
18 |Typically bal is used as a tool with filters like moogladder whose topology 19 | often reduces the gain of the input signal. The example below shows 20 | this typical usecase, splitting the input value into a filtered input signal 21 | and a dry reference signal. These values go into bal to add makeup gain.
22 | 150 0.3 saw dup
23 | 0.1 1 sine 200 1000 biscale 0.3 moogladder bal
24 |
25 |
26 |
27 | Arguments: input, min, max
16 |Biscale will take an input signal in the range -1, 1, and map it to 17 | an arbitrary minimum and maximum. If the input signal exceeds these ranges, 18 | the scaled output will be linearly mapped beyond the min/max bounds.
19 |The example below takes a unit amplitude low-frequency sinusoid and maps 20 | it to the range (200, 500). This signal acts as a control signal for 21 | the frequency of another audio-rate sine object.
22 | 0.1 1 sine 200 500 biscale 0.3 sine
23 |
24 |
25 |
26 | Arguments: Bitdepth, samplerate
16 |Bitcrush is a a bitcrusher that adds artificial samplerate and bitdepth 17 | reduction. The bitdepth parameter is in bits, and the samplerate 18 | parameter is in Hertz.
19 |The example below feeds an enveloped FM oscillator into bitcrush. To 20 | hear the effects, both paramters are modulated using low-frequency 21 | oscillators
22 |
23 | 3 metro 0.001 0.01 0.2 tenv 300 0.4 1 1 1 fm *
24 |
25 | 5 inv 1 sine 1 16 biscale
26 | 8 inv 1 sine 100 10000 biscale
27 | bitcrush
28 |
29 |
30 |
31 |
32 | Arguments: freq, amp
16 |blsaw is a band-limited sawtooth wave generator. For parameters, 17 | it takes in a frequency, and an amplitude. blsaw also uses the alias 18 | saw.
19 |The argument below creates a simple 440hz sawtooth sound, put through 20 | a butterworth lowpass filter.
21 | 440 0.3 blsaw 1000 butlp
22 |
23 |
24 |
25 | Arguments: Frequency, Amplitude, Pulse Width
16 |The ugen blsquare is a band-limited square wave generator. In addition 17 | to having frequency and amplitude control, there is also a modulatable 18 | pulse width parameter. This parameter should be in range 0-1, not including 19 | 0 or 1 exactly. 20 | The example below shows blsquare with a modulated pulse width.
21 | 200 0.3
22 | # LFO modulating pulse width
23 | 0.1 1 sine 0.01 0.99 biscale
24 | blsquare
25 |
26 |
27 |
28 | Arguments: freq, amp
16 |bltriangle is a band-limited triangle wave generator. For paramters, 17 | it takes in a frequency, and an amplitude.
18 |bltriangle has two other aliases: tri, and triangle.
19 |The argument below creates a simple 440hz triangle sound.
20 | 440 0.3 bltriangle
21 |
22 |
23 |
24 | A function that efficiently converts bpm to duration (in seconds) with
16 | the equation
17 | 60/BPM
The example uses bpm2dur to create a 130BPM clock signal with dmetro, 19 | fed into trand, which is used to control the frequency of a sine wave.
20 | # Create 130bpm clock signal with dmetro
21 | 130 bpm2dur dmetro
22 | # feed clock into trand, generating frequency values between 200 and 400.
23 | 200 400 trand
24 | # the rest of sine
25 | 0.3 sine
26 |
27 |
28 |
29 | A function that efficiently converts bpm to rate (in Hz) with
16 | the equation
17 | BPM/60
The example uses bpm2dur to create a 130BPM clock signal with metro, 19 | fed into trand, which is used to control the frequency of a sine wave.
20 | # Create 130bpm clock signal with metro
21 | 130 bpm2rate metro
22 | # feed clock into trand, generating frequency values between 200 and 400.
23 | 200 400 trand
24 | # the rest of sine
25 | 0.3 sine
26 |
27 |
28 |
29 | Arguments: gate, sig1, sig0 branch
16 |Branch switches between two signals given a gate signal. A value of 17 | "1" will go to sig1 (first signal), and a value of 0 will go to 18 | sig0 (second signal). A similar ugen is 19 | switch, which uses a trigger instead of a gate. 20 |
21 |The example below uses branch to switch between a random and steady 22 | signal to be used as frequency for a sine wave. The switching mechanism 23 | is generated via metro and tog
24 | # generate gate signal with metro and tog
25 | 1 metro tog
26 | # signal 1: random signal
27 | 200 800 10 randh
28 | # signal 2: steady signal
29 | 200
30 | branch
31 | 0.3 sine
32 |
33 |
34 |
35 | Creates brownian noise.
16 | brown
17 |
18 |
19 |
20 | Arguments: input, cutoff, bandwidth
16 |butbp is a butterworth bandpass generator. In addition to taking an 17 | input audio signal, it takes in cutoff and bandwidth parameters.
18 |The example below shows butbp filtering some noise.
19 | 0.8 noise 1500 30 butbp
20 |
21 |
22 |
23 | Arguments: input, cutoff, bandwidth
16 |butbr is a butterworth band reject filter. In addition to taking an 17 | input audio signal, it takes in cutoff and bandwidth parameters.
18 |The example below shows butbp filtering some noise.
19 | 0.4 noise 1500 1000 butbr
20 |
21 |
22 |
23 | Arguments: input, cutoff
16 |butbr is a butterworth high pass filter. In addition to taking an 17 | input audio signal, it takes in a cutoff parameter.
18 |The example below shows buthp filtering some noise.
19 | 0.4 pinknoise 1500 buthp
20 |
21 |
22 |
23 | Arguments: input, cutoff
16 |butbr is a butterworth low pass filter. In addition to taking an 17 | input audio signal, it takes in a cutoff parameter.
18 |The example below shows butlp filtering some noise.
19 | 0.4 pinknoise 1500 butlp
20 |
21 |
22 |
23 | This ugen takes in a signal, and outputs a trigger if the signal has 16 | changed. Sometimes useful for realtime-controlelr based setups.
17 |The example is an enveloped sine wave whose frequency is modulated with 18 | sample and holds via randh. Anytime the frequency changes, it triggers 19 | the envelope via changed.
20 | _freq var
21 | 200 1000 1 4 1 randh randh _freq set
22 |
23 | _freq get 0.6 sine _freq get changed 0.001 0.005 0.001 tenvx *
24 |
25 |
26 |
27 | Arguments: input, threshold
16 |Performs clipping on an input signal that goes past a threshold 17 | (both postive and negative).
18 |The patch below clips a sine wave and makes it sound more like a square 19 | wave.
20 | 440 1 sine
21 | # threshold to 0.3
22 | 0.3 clip -5 ampdb *
23 |
24 |
25 |
26 | Arguments: 16 |
The unit generator clock produces a trigger signal that can be used by 24 | other trigger-based unit generators. The example below shows clock 25 | being fed into the envelope generator tenvx.
26 | # Generate 120 BPM clock signal
27 | 0 120 1 clock
28 | # Feed clock into exponential envelope generator
29 | 0.001 0.001 0.05 tenvx
30 | # Multiply envelope with sine wave
31 | 1000 0.5 sine *
32 |
33 |
34 |
35 | This effect is a delay line comb-filter, which is often used in the 16 | creation of artificial reverberators. It has the following arguments: 17 |
# use metro to produce an impulse signal
23 | 1 metro
24 | # the output of comb is the impulse response
25 | 1.5 0.01 comb
26 |
27 |
28 |
29 | A trigger-based counter.
16 |It takes in the following arguments: 17 |
Count will always start at 0, and count upwards towards. It is assumed that 26 | a trigger signal will occur at the very beginning of the patch. If it does 27 | not occur, count will produce a value of -1 until triggered.
28 |The patch below uses count to modulate the frequency of a sine wave to 29 | produce the overtone series.
30 | # Metro trigger signal
31 | 4 metro
32 | # count 0-7
33 | 8 1 count
34 | # add one to make range 1-8
35 | 1 +
36 | # multiply signal by 100 to produce harmonic series
37 | 100 *
38 | # the rest of the sine
39 | 0.3 sine
40 |
41 |
42 |
43 | Crossfade applies a linear crossfrade between two signals with a certain 16 | percentage alpha. When alpha is 0, it 100% the top signal. When alpha 17 | is 1, the signal is 100% the second signal.
18 |Crossfade also has the alias "cf"
19 | # Signal 1
20 | 440 0.3 sine
21 | # Signal 2
22 | 880 0.3 sine
23 | # LFO modulating crossfade. 0 = signal 1, 1 = signal 2
24 | 0.2 1 sine 0 1 biscale
25 | crossfade
26 |
27 |
28 |
29 | A DC blocking filter. This is used to correct any DC offset introduced 16 | into the signal path. Common culprits that introduce DC offset include 17 | reverberators, comb filters, and string resonators.
18 | # use an impulse as an excitation signal
19 | 1 metro
20 | # feed it into some string resonators
21 | 220 0.999 streson
22 | 300 0.8 streson
23 | # remove any dc offset with dcblk
24 | dcblk
25 |
26 |
27 |
28 | Args: feedback (range 0-1), delay time (in seconds)
16 |Static feedback delay line. This unit generator creates a delay line with 17 | a fixed delay time, and modulatable feedback amount in the range 0-1.
18 |The following patch produces an enveloped sine wave which is fed into 19 | a delay line.
20 | # create two identical trigger signals
21 | 1 metro dup
22 | # feed one trigger into tenvx
23 | 0.001 0.001 0.01 tenvx
24 | # get the other trigger, and feed into triggerable random number generator
25 | swap 300 1000 trand
26 | # the RNG feeds into the freq parameter of sine
27 | 0.5 sine
28 | # multiply sine with envelope
29 | *
30 | # split sine wave signal
31 | dup
32 | # feed one of the signals into delay line with 375ms delay and 80% feedback
33 | 0.8 0.375 delay
34 | # mix dry and wet signals back together
35 | +
36 |
37 |
38 |
39 | diode is a diode ladder lowpass filter, adapted from code by Will 16 | Pirkle. The filter design is very similar to the one used by the iconic 17 | TB303 bassline generator.
18 |It has the following argument structure: 19 |
The following example shows diode being used to create a subtractive 26 | bass sound.
27 | # Note Sequence
28 | _seq "0 2 3 7" gen_vals
29 | # Create clock, duplicate, and feed into sequencer
30 | 8 metro dup 0 _seq tseq
31 | # bias squence to be pitched at 2 octaves below middle C
32 | 36 +
33 | # portamento filter to add glissando, then convert to frequency
34 | 0.01 port mtof
35 | # feed frequency into bandlimited sawtooth generator
36 | 0.8 saw
37 | # feed clock into envelope, and multiply with sawtooth signal
38 | swap 0.001 0.01 0.2 tenvx *
39 | # use LFO to control cutoff frequency of diode
40 | 0.1 1 sine 1000 8000 biscale
41 | # the rest of diode, with resonance cranked up
42 | 0.9 diode
43 |
44 |
45 |
46 | Plays an audio file. Audio file should be: 16 |
"foo.wav" diskin
24 |
25 |
26 |
27 | Performs distortion using a hyperbolic tangent function. 16 | dist has the following arguments: 17 |
The following patch demonstrates how dist can be used with diode 28 | to create a squelchy bass sound.
29 | # generate a saw wave
30 | 100 1 saw
31 | # LFO scaled for cutoff frequency sweep
32 | 0.1 1 sine 300 3000 biscale
33 | # diode with high resonance
34 | 0.9 diode
35 | # distortion with dist
36 | 5 0.5 0.01 0.9 dist
37 |
38 |
39 |
40 | Divide two signals together.
16 | # 4 divided by 2 is 2!
17 | 4 2 /
18 |
19 |
20 |
21 | dmetro creates a trigger signal in terms of duration.
16 |The following patch uses dmetro to trigger an envelope generator
17 | # create trigger every 100 ms
18 | 0.1 dmetro
19 | # feed it into envelope generator
20 | 0.001 0.01 0.01 tenvx
21 | # multiply with sine wave
22 | 1000 0.5 sine *
23 |
24 |
25 |
26 | Physical model of a dripping water sound. 16 | This has the following arguments: 17 |
For sensible values, it is best to refer to the example below.
30 | 0.5 dmetro 0.5 maytrig
31 | # number of units/tubes
32 | 10
33 | # amplitude
34 | 0.3
35 | # damp
36 | 0.2
37 | # shake_max: amount of energy to add back into the system
38 | 0
39 | # main resonant frequency
40 | 458
41 | # first resonant frequency
42 | 600
43 | # second resonant frequency
44 | 750
45 | # period of time over which all sound is stopped
46 | 0.09
47 | drip
48 |
49 | # feed into some reverb
50 | dup dup 3 8 10000 zrev drop 0.1 * +
51 |
52 |
53 |
54 | drops a value from the stack
16 | # push 0 and 1.234 onto the stack
17 | 0 1.234
18 | # drop 1.234
19 |
20 |
21 |
22 | dtrig is a delta-time trigger, used for creating trigger signals 16 | in terms of delta times, or the amount of time between two trigger signals. 17 | Delta times are read from an f-table.
18 |dtrig takes in the following arguments: 19 |
The following example using dtrig to control an envelope generator.
30 | # delta times: 100ms and 500 ms
31 | _delta "0.1 0.5" gen_vals
32 |
33 | # dtrig. "tick" is used to start it looping
34 | tick 1 0 1 _delta dtrig
35 | # dtrig feeds into an envelope generator...
36 | 0.001 0.001 0.1 tenvx
37 | # and then that is multiplied by a sine wave
38 | 1000 0.5 sine *
39 |
40 |
41 |
42 | duplicates last item on the stack.
16 | # push 0 onto the stack
17 | 0
18 | # duplicate it
19 | dup
20 |
21 |
22 | duplicates last two items onto stack. Useful for handling stereo signals.
16 | # push 1 and 2 onto the stack
17 | 1 2
18 | # duplicate both items
19 | dup2
20 |
21 |
22 |
23 | Binary left shift operation.
16 | # 8 << 1 = 16
17 | 8 1 <<
18 |
19 |
20 |
21 | Performs modulus operation.
16 | # 8 % 3 = 2
17 | 8 3 %
18 |
19 |
20 |
21 | Multiply two signals together.
16 | # 2 times 3 is 6!
17 | 2 3 *
18 |
19 |
20 |
21 | Logical "or" operation.
16 | # 4 | 8 = 12
17 | 8 4 |
18 |
19 |
20 |
21 | osc is a table-lookup oscillator with linear interpolation.
16 |It takes in the following arguments: 17 |
The following patch creates a 440Hz sine wave using osc. It is 25 | analogous to the unit generator sine.
26 | _sine 8192 gen_sine
27 | 440 0.5 0 _sine osc
28 |
29 |
30 |
31 | The oscmorph unit generators provide a table-lookup oscillator with 16 | the ability to crossfade between wavetables. oscmorph2 morphs between 17 | two waveshapes. oscmorph4 morphs between 4 waveshapes.
18 |Both oscmorph4 and oscmorph2 have the following argument structure: 19 |
# some wave shapes
31 | _sine 4096 gen_sine
32 | _sinesum1 4096 "0 0 1 11" gen_sinesum
33 | _saw 4096 "0 1 4096 -1" gen_line
34 | _sinesum2 4096 "1 1 0 1" gen_sinesum
35 |
36 | # 440 hz 0.3 amp
37 | 440 0.3
38 | # LFO oscillator modulating wave position
39 | 1 1 sine 0 1 biscale
40 | # initial phase
41 | 0
42 | # 4 wave shapes
43 | _sine _saw _sinesum1 _sinesum2
44 | oscmorph4
45 |
46 |
47 |
48 | posc3 is a table-lookup high precision oscillator with 16 | cubic interpolation.
17 |It takes in the following arguments: 18 |
The following patch creates a 440Hz sine wave using posc3. It is 25 | analogous to the unit generator sine.
26 | _sine 8192 gen_sine
27 | 440 0.5 _sine posc3
28 |
29 |
30 |
31 | Binary right shift operation.
16 | # 8 >> 1 = 4
17 | 8 1 >>
18 |
19 |
20 |
21 | Subtract two signals together.
16 | # 2 minus 3 is -1!
17 | 2 3 -
18 |
19 |
20 |
21 | Arguments: trigger, sig1, sig0 switch
16 |Switch toggles between two signals given a trigger signal. A similar ugen is 17 | branch, which uses a gate instead of a trigger. 18 |
19 |The example below uses switch to toggle between a random and steady 20 | signal to be used as frequency for a sine wave. The switching trigger 21 | is generated by metro.
22 | # generate trigger signal with metro
23 | 1 metro
24 | # signal 1: random signal
25 | 200 800 10 randh
26 | # signal 2: steady signal
27 | 200
28 | switch
29 | 0.3 sine
30 |
31 |
32 |
33 | Table lookup with linear interpolation
16 |tabread has the following arguments: 17 |
The following patch below shows how tabread can be used to make a 28 | table lookup oscillator.
29 | # Generate sine wave lookup-table
30 | _sine 8192 gen_sine
31 | # create 440hz phasor signal (sawtooth moving between 0 and 1)
32 | 440 0 phasor
33 | # feed phasor into tabread
34 | 1 0 0 _sine tabread
35 | # scale signal
36 | 0.3 *
37 | ##--
38 |
39 |
40 | Tblrec has the following arguments: 16 |
tblrec will record an input signal to an f-table. In addition, there is 23 | also the ability to turn the recording off and on. When turned back on, the 24 | pointer will rewind to the beginning, overwriting any previous data. When 25 | the record pointer reaches the end of the table, it loops to the beginning 26 | and continues onwards.
27 |The patch below demonstrates one particular way tblrec can be used. 28 | A simple karplus-strong instrument is recorded into a 2-second long buffer. 29 | The buffer is then randomly shuffled through using the phase-vocoder 30 | mincer.
31 |tblrec pushes a copy of the dry signal back onto the stack.
32 | # create a 2-second long buffer called "in"
33 | _in sr 2 * zeros
34 |
35 | # a small plucked instrument patch
36 | 1 metro dup 300 800 trand 0.5 100 pluck
37 |
38 | # record the patch using tblrec. "tick" makes it start recording indefinitely
39 | tick _in tblrec
40 |
41 | # drop the dry signal
42 | drop
43 |
44 | # create a jitter signal between 0 and the position of the input buffer
45 | 0 _in tbldur (2 10 1 randh) randi
46 |
47 | # mince it up using mincer
48 | 1 1 2048 _in mincer
49 |
50 |
51 |
52 |
53 | tenv is a trigger-based linear Attack/Hold/Release envelope generator. 16 | It takes the following arguments: 17 |
The following patch uses tenv to shape the amplitude of a sine wave:
25 | # trigger: a 2Hz metronome
26 | 2 metro
27 | # feed it into envelope generator
28 | 0.001 0.01 0.1 tenv
29 | # multiply with sine wave
30 | 1000 0.5 sine *
31 |
32 |
33 |
34 | tget will retrieve values from a table. It takes in an index position 16 | and a table name, and pushes that value onto the stack.
17 |The following patch uses tget to retrieve parameter values for 18 | sine.
19 | _vals "440 0.5" gen_vals
20 | 0 _vals tget
21 | 1 _vals tget
22 | sine
23 |
24 |
25 |
26 | Arguments: input, cutoff
16 |Where 17 |
Tone is a simple one-pole low pass filter. In the example below, 23 | white noise is being filtered by tone.
24 | 25 |A sequencer that steps through values in a table.
16 |tseq takes the following arguments: 17 |
In the example below, tseq is used to step through a sequence of notes.
25 |# store melody in a table
26 | _seq "0 5 0 3 0 3 5 0 3" gen_vals
27 |
28 | # create beat and half-beat triggers
29 | _beat var
30 | 4 metro _beat set
31 |
32 | # play melody with a sine wave
33 | (_beat get 0 _seq tseq 75 +) mtof 0.1 sine
34 |
35 | # apply envelope
36 | _beat get 0.001 0.05 0.1 tenvx *
37 |
38 |
39 |
40 | tset will set values in a table. It takes in a value, 16 | an index position, and a table name, then writes that value into the 17 | table.
18 |The following patch uses tget to retrieve parameter values for 19 | sine, and tset to set the parameters
20 | _vals 2 zeros
21 | 350 0 _vals tset
22 | 0.5 1 _vals tset
23 | 0 _vals tget
24 | 1 _vals tget
25 | sine
26 |
27 |
28 |
29 | Logical "xor" operation.
16 | # 4 ^ 5 = 1
17 | 8 4 ^
18 |
19 |
20 |
21 | Variables in Sporth are a wonderful solution to mitigate 15 | hard to follow stack operations.
16 |Consider this Sporth patch, where the metronome signal is being split 17 | and fed into a trand and tenvx:
18 | 10 metro dup
19 | 300 1000 trand
20 | swap 0.001 0.005 0.001 tenvx
21 | swap 0.3 sine *
22 | It's not terribly complicated yet, but if this patch were to grow, and the clock 23 | source were needed by more ugens, things could become harder to follow.
24 |With variables, the same patch could be realized in the following way:
25 | _clk var
26 | 10 metro _clk set
27 | _clk get 300 1000 trand
28 | _clk get 0.001 0.005 0.001 tenvx
29 | swap 0.3 sine *
30 | In this patch, the variable called "clk" is declared in the first line with:
31 | _clk var
32 | The metronome signal is set to the variable using "set" in the following line: 33 |
10 metro _clk set
34 |
35 | With the variable set, the value inside the variable accessed using "get", 36 | as seen in the two lines following:
37 | _clk get 300 1000 trand
38 | _clk get 0.001 0.005 0.001 tenvx
39 | With this patch, it is much clearer to see what is being fed into trand and 40 | tenvx.
41 |At first glance, 15 | Sporth is a stack-based programing language 16 | designed for audio. However, Sporth is really more of a text based modular synthesis 17 | environment, that uses a stack paradigm to build up patches. In addition to 18 | the language, Sporth has a very flexible API. It is very easy to build 19 | tools and software on top of Sporth. Sporth has been built to run inside of 20 | programs like ChucK, PD, and LMMS. It is also one of the main synthesis engines 21 | used inside of the iOS/macOS framework AudioKit.
22 |Sporth was originally designed because writing music in C via 23 | Soundpipe 24 | was too tedious. The stack-based language design was chosen 25 | because it was easy to build a parser for it. After months of working with the language, 26 | it became very clear that Sporth could be more than just a prototyping tool, and that 27 | the stack-based paradigm lended itself quite well to audio DSP. Over the 28 | past years, Sporth has grown up into a very unique and rewarding composition 29 | environment.
30 |Patch | 6 |Author | 7 |Created | 8 |
---|---|---|
{{.Title}} | 12 |{{.Author}} | 13 |{{HumanDate .DateCreated}} | 14 |