├── .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 += '
' 20 | if 'Args' in ugen: 21 | out += ', '.join(ugen['Args']) + '
' 22 | out += '' + ugen['Description'] + '
\n' 23 | 24 | outFile = open('../static/ugen_reference.html', 'w') 25 | outFile.write('\n' + out + '\n') 26 | outFile.close() 27 | 28 | ###################################### 29 | # create tutorial/ugens.html 30 | 31 | out = '' 32 | for ugen in data: 33 | out += '' 34 | if 'Link' in ugen: 35 | out += '' + cgi.escape(ugen['name']) + '' 36 | else: 37 | out += cgi.escape(ugen['name']) 38 | out += '
' + ugen['Description'] + '

\n' 39 | 40 | outFile = open('../templates/tutorial/ugens.html', 'w') 41 | outFile.write("""
42 | 43 |

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 + '
') 47 | outFile.close() 48 | 49 | ###################################### 50 | # create static/ugen_reference.js 51 | 52 | jsUgens = [] 53 | for ugen in data: 54 | if set(ugen['name']).intersection(set('.\+*?[^]$(){}=!<>|:-')): 55 | continue 56 | jsUgen = "'" + ugen['name'] + "': { 'Args': [ " 57 | if 'Args' in ugen: 58 | jsUgen += ', '.join("'{0}'".format(a) for a in ugen['Args']) 59 | jsUgen += " ], 'Outputs': " + str(ugen['Outputs']) 60 | jsUgen += " , 'Description': '" + ugen['Description'].replace("'","\\'") + "'}" 61 | jsUgens.append(jsUgen) 62 | 63 | out = 'ugen_ref = {\n' + ',\n'.join(jsUgens) + '}\n' 64 | outFile = open('../static/ugen_reference.js', 'w') 65 | outFile.write(out) 66 | outFile.close() -------------------------------------------------------------------------------- /scripts/not yet implemented.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | diskin: 5 | Args: 6 | - filename 7 | Outputs: 1 8 | Description: Plays an audio file 9 | 10 | gen_sporth: 11 | Args: 12 | - tblname 13 | - size 14 | - filename 15 | Outputs: 1 16 | Description: render a (mono) sporth file to a table 17 | 18 | nsmp: 19 | Args: 20 | - trig 21 | - index 22 | - sr 23 | - ini 24 | - wav 25 | Outputs: 1 26 | Description: Nanosamp sampler. See soundpipe docs. 27 | 28 | render: 29 | Args: 30 | - filename 31 | Outputs: 1 32 | Description: renders a sporth file (private variables/tables) 33 | 34 | load: 35 | Args: 36 | - filename 37 | Outputs: 1 38 | Description: loads a sporth file 39 | 40 | writecode: 41 | Args: 42 | - filename 43 | Outputs: 1 44 | Description: writes sporth code to file 45 | 46 | slist: 47 | Args: 48 | - ftname 49 | - size 50 | - slist 51 | Outputs: 1 52 | Description: loads a text file into a string list 53 | 54 | load: 55 | Args: 56 | - filename 57 | Outputs: 1 58 | Description: loads a sporth file (global variables/tables) 59 | 60 | cdbtab: 61 | Args: 62 | - ftname 63 | - key(s) 64 | - db(s) 65 | - filename 66 | Outputs: 1 67 | Description: loads raw floating point data from a cdb file 68 | 69 | print: 70 | Args: 71 | - val (f/s) label 72 | Outputs: 1 73 | Description: prints values to terminal 74 | 75 | cdbload: 76 | Args: 77 | - ftname 78 | - filename 79 | Outputs: 1 80 | Description: loads cdb file 81 | 82 | cdb: 83 | Args: 84 | - key 85 | - ftname 86 | Outputs: 1 87 | Description: loads string from cdb file 88 | 89 | metatable_create: 90 | Args: 91 | - name 92 | - length 93 | Outputs: 1 94 | Description: create metatable that holds a number of tables 95 | 96 | metatable_add: 97 | Args: 98 | - name 99 | - metatable 100 | - position 101 | Outputs: 1 102 | Description: add ftable to position in metatable 103 | 104 | metatable_copy: 105 | Args: 106 | - name 107 | - metatable 108 | - position 109 | Outputs: 1 110 | Description: create a new ftable which shares the data at a metatable position 111 | 112 | ftsum: 113 | Args: 114 | - start 115 | - end 116 | - tbl 117 | Outputs: 1 118 | Description: sum a range of ftable values (inclusive). built for polysporth. 119 | 120 | fl: 121 | Args: 122 | - filename 123 | - tbl 124 | Outputs: 1 125 | Description: dynamically load a ugen 126 | 127 | fe: 128 | Args: 129 | - tbl 130 | Outputs: 1 131 | Description: execute a dynamically loaded ugen 132 | 133 | fc: 134 | Args: 135 | - tbl 136 | Outputs: 1 137 | Description: close a dynamically loaded ugen (call this once at the end) 138 | 139 | loadfile: 140 | Args: 141 | - table name 142 | - file name 143 | Outputs: 1 144 | Description: load a (mono) audio file into a table 145 | 146 | paulstretch: 147 | Args: 148 | - stretch 149 | - window size 150 | - tbl 151 | Outputs: 1 152 | Description: Paulstretch algorithm 153 | 154 | 155 | 156 | 157 | #=============================================== poly 158 | ps: 159 | Args: 160 | - max voices 161 | - out 162 | - file 163 | Outputs: 1 164 | Description: polysporth 165 | 166 | poly: 167 | Args: 168 | - max voices 169 | - max params 170 | - polyfile 171 | - ftable 172 | Outputs: 1 173 | Description: Parse a poly file and write to table ftable 174 | 175 | polyget: 176 | Args: 177 | - voice number 178 | - param number 179 | - tbl 180 | Outputs: 1 181 | Description: "get a value from a poly table. p0 = tick p1 = dur p2,3,etc: other" 182 | 183 | tpoly: 184 | Args: 185 | - trig 186 | - max voices 187 | - max params 188 | - arg_ft 189 | - poly_ft 190 | Outputs: 1 191 | Description: triggered polyphony 192 | 193 | psh: 194 | Args: 195 | - ftname 196 | - ninstances 197 | - filename 198 | Outputs: 1 199 | Description: Create polysporth handle 200 | 201 | pst: 202 | Args: 203 | - tog 204 | - id 205 | - ftname 206 | Outputs: 1 207 | Description: toggle a polysporth sporthlet 208 | 209 | ling: 210 | Args: 211 | - trig 212 | - N 213 | - mode 214 | - code 215 | Outputs: 1 216 | Description: "Evaluates a string of ling code. (0 = int mode, 1 = binary mode)" 217 | 218 | 219 | #=============================================== spa 220 | spa: 221 | Args: 222 | - filename 223 | Outputs: 1 224 | Description: Streams a spa file 225 | 226 | sparec: 227 | Args: 228 | - input 229 | - filename 230 | Outputs: 1 231 | Description: Writes a sparec file. -------------------------------------------------------------------------------- /scripts/upstart/audiomasher.conf: -------------------------------------------------------------------------------- 1 | description "AudioMasher" 2 | author "Pierre Cusa" 3 | 4 | start on (local-filesystems and net-device-up) 5 | stop on runlevel [!2345] 6 | 7 | respawn 8 | chdir /srv/audiomasher/ 9 | exec /srv/audiomasher/AudioMasher -------------------------------------------------------------------------------- /scripts/upstart/readme.md: -------------------------------------------------------------------------------- 1 | ## Upstart service for AudioMasher 2 | The script assumes that the AudioMasher binary as well as the `config` and `templates` directories are in `/srv/audiomasher/`. Copy audiomasher.conf to your server, then: 3 | 4 | sudo mv audiomasher.conf /etc/init/ 5 | sudo chown --reference=/etc/init/tty.conf /etc/init/audiomasher.conf 6 | sudo chmod --reference=/etc/init/tty.conf /etc/init/audiomasher.conf 7 | sudo initctl reload-configuration 8 | sudo start audiomasher 9 | 10 | -------------------------------------------------------------------------------- /static/codemirror/addon/mode/simple.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | CodeMirror.defineSimpleMode = function(name, states) { 15 | CodeMirror.defineMode(name, function(config) { 16 | return CodeMirror.simpleMode(config, states); 17 | }); 18 | }; 19 | 20 | CodeMirror.simpleMode = function(config, states) { 21 | ensureState(states, "start"); 22 | var states_ = {}, meta = states.meta || {}, hasIndentation = false; 23 | for (var state in states) if (state != meta && states.hasOwnProperty(state)) { 24 | var list = states_[state] = [], orig = states[state]; 25 | for (var i = 0; i < orig.length; i++) { 26 | var data = orig[i]; 27 | list.push(new Rule(data, states)); 28 | if (data.indent || data.dedent) hasIndentation = true; 29 | } 30 | } 31 | var mode = { 32 | startState: function() { 33 | return {state: "start", pending: null, 34 | local: null, localState: null, 35 | indent: hasIndentation ? [] : null}; 36 | }, 37 | copyState: function(state) { 38 | var s = {state: state.state, pending: state.pending, 39 | local: state.local, localState: null, 40 | indent: state.indent && state.indent.slice(0)}; 41 | if (state.localState) 42 | s.localState = CodeMirror.copyState(state.local.mode, state.localState); 43 | if (state.stack) 44 | s.stack = state.stack.slice(0); 45 | for (var pers = state.persistentStates; pers; pers = pers.next) 46 | s.persistentStates = {mode: pers.mode, 47 | spec: pers.spec, 48 | state: pers.state == state.localState ? s.localState : CodeMirror.copyState(pers.mode, pers.state), 49 | next: s.persistentStates}; 50 | return s; 51 | }, 52 | token: tokenFunction(states_, config), 53 | innerMode: function(state) { return state.local && {mode: state.local.mode, state: state.localState}; }, 54 | indent: indentFunction(states_, meta) 55 | }; 56 | if (meta) for (var prop in meta) if (meta.hasOwnProperty(prop)) 57 | mode[prop] = meta[prop]; 58 | return mode; 59 | }; 60 | 61 | function ensureState(states, name) { 62 | if (!states.hasOwnProperty(name)) 63 | throw new Error("Undefined state " + name + " in simple mode"); 64 | } 65 | 66 | function toRegex(val, caret) { 67 | if (!val) return /(?:)/; 68 | var flags = ""; 69 | if (val instanceof RegExp) { 70 | if (val.ignoreCase) flags = "i"; 71 | val = val.source; 72 | } else { 73 | val = String(val); 74 | } 75 | return new RegExp((caret === false ? "" : "^") + "(?:" + val + ")", flags); 76 | } 77 | 78 | function asToken(val) { 79 | if (!val) return null; 80 | if (val.apply) return val 81 | if (typeof val == "string") return val.replace(/\./g, " "); 82 | var result = []; 83 | for (var i = 0; i < val.length; i++) 84 | result.push(val[i] && val[i].replace(/\./g, " ")); 85 | return result; 86 | } 87 | 88 | function Rule(data, states) { 89 | if (data.next || data.push) ensureState(states, data.next || data.push); 90 | this.regex = toRegex(data.regex); 91 | this.token = asToken(data.token); 92 | this.data = data; 93 | } 94 | 95 | function tokenFunction(states, config) { 96 | return function(stream, state) { 97 | if (state.pending) { 98 | var pend = state.pending.shift(); 99 | if (state.pending.length == 0) state.pending = null; 100 | stream.pos += pend.text.length; 101 | return pend.token; 102 | } 103 | 104 | if (state.local) { 105 | if (state.local.end && stream.match(state.local.end)) { 106 | var tok = state.local.endToken || null; 107 | state.local = state.localState = null; 108 | return tok; 109 | } else { 110 | var tok = state.local.mode.token(stream, state.localState), m; 111 | if (state.local.endScan && (m = state.local.endScan.exec(stream.current()))) 112 | stream.pos = stream.start + m.index; 113 | return tok; 114 | } 115 | } 116 | 117 | var curState = states[state.state]; 118 | for (var i = 0; i < curState.length; i++) { 119 | var rule = curState[i]; 120 | var matches = (!rule.data.sol || stream.sol()) && stream.match(rule.regex); 121 | if (matches) { 122 | if (rule.data.next) { 123 | state.state = rule.data.next; 124 | } else if (rule.data.push) { 125 | (state.stack || (state.stack = [])).push(state.state); 126 | state.state = rule.data.push; 127 | } else if (rule.data.pop && state.stack && state.stack.length) { 128 | state.state = state.stack.pop(); 129 | } 130 | 131 | if (rule.data.mode) 132 | enterLocalMode(config, state, rule.data.mode, rule.token); 133 | if (rule.data.indent) 134 | state.indent.push(stream.indentation() + config.indentUnit); 135 | if (rule.data.dedent) 136 | state.indent.pop(); 137 | var token = rule.token 138 | if (token && token.apply) token = token(matches) 139 | if (matches.length > 2 && rule.token && typeof rule.token != "string") { 140 | state.pending = []; 141 | for (var j = 2; j < matches.length; j++) 142 | if (matches[j]) 143 | state.pending.push({text: matches[j], token: rule.token[j - 1]}); 144 | stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0)); 145 | return token[0]; 146 | } else if (token && token.join) { 147 | return token[0]; 148 | } else { 149 | return token; 150 | } 151 | } 152 | } 153 | stream.next(); 154 | return null; 155 | }; 156 | } 157 | 158 | function cmp(a, b) { 159 | if (a === b) return true; 160 | if (!a || typeof a != "object" || !b || typeof b != "object") return false; 161 | var props = 0; 162 | for (var prop in a) if (a.hasOwnProperty(prop)) { 163 | if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) return false; 164 | props++; 165 | } 166 | for (var prop in b) if (b.hasOwnProperty(prop)) props--; 167 | return props == 0; 168 | } 169 | 170 | function enterLocalMode(config, state, spec, token) { 171 | var pers; 172 | if (spec.persistent) for (var p = state.persistentStates; p && !pers; p = p.next) 173 | if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode == p.mode) pers = p; 174 | var mode = pers ? pers.mode : spec.mode || CodeMirror.getMode(config, spec.spec); 175 | var lState = pers ? pers.state : CodeMirror.startState(mode); 176 | if (spec.persistent && !pers) 177 | state.persistentStates = {mode: mode, spec: spec.spec, state: lState, next: state.persistentStates}; 178 | 179 | state.localState = lState; 180 | state.local = {mode: mode, 181 | end: spec.end && toRegex(spec.end), 182 | endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false), 183 | endToken: token && token.join ? token[token.length - 1] : token}; 184 | } 185 | 186 | function indexOf(val, arr) { 187 | for (var i = 0; i < arr.length; i++) if (arr[i] === val) return true; 188 | } 189 | 190 | function indentFunction(states, meta) { 191 | return function(state, textAfter, line) { 192 | if (state.local && state.local.mode.indent) 193 | return state.local.mode.indent(state.localState, textAfter, line); 194 | if (state.indent == null || state.local || meta.dontIndentStates && indexOf(state.state, meta.dontIndentStates) > -1) 195 | return CodeMirror.Pass; 196 | 197 | var pos = state.indent.length - 1, rules = states[state.state]; 198 | scan: for (;;) { 199 | for (var i = 0; i < rules.length; i++) { 200 | var rule = rules[i]; 201 | if (rule.data.dedent && rule.data.dedentIfLineStart !== false) { 202 | var m = rule.regex.exec(textAfter); 203 | if (m && m[0]) { 204 | pos--; 205 | if (rule.next || rule.push) rules = states[rule.next || rule.push]; 206 | textAfter = textAfter.slice(m[0].length); 207 | continue scan; 208 | } 209 | } 210 | } 211 | break; 212 | } 213 | return pos < 0 ? 0 : state.indent[pos]; 214 | }; 215 | } 216 | }); 217 | -------------------------------------------------------------------------------- /static/codemirror/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | color: black; 8 | direction: ltr; 9 | } 10 | 11 | /* PADDING */ 12 | 13 | .CodeMirror-lines { 14 | padding: 4px 0; /* Vertical padding around content */ 15 | } 16 | .CodeMirror pre { 17 | padding: 0 4px; /* Horizontal padding of content */ 18 | } 19 | 20 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 21 | background-color: white; /* The little square between H and V scrollbars */ 22 | } 23 | 24 | /* GUTTER */ 25 | 26 | .CodeMirror-gutters { 27 | border-right: 1px solid #ddd; 28 | background-color: #f7f7f7; 29 | white-space: nowrap; 30 | } 31 | .CodeMirror-linenumbers {} 32 | .CodeMirror-linenumber { 33 | padding: 0 3px 0 5px; 34 | min-width: 20px; 35 | text-align: right; 36 | color: #999; 37 | white-space: nowrap; 38 | } 39 | 40 | .CodeMirror-guttermarker { color: black; } 41 | .CodeMirror-guttermarker-subtle { color: #999; } 42 | 43 | /* CURSOR */ 44 | 45 | .CodeMirror-cursor { 46 | border-left: 1px solid black; 47 | border-right: none; 48 | width: 0; 49 | } 50 | /* Shown when moving in bi-directional text */ 51 | .CodeMirror div.CodeMirror-secondarycursor { 52 | border-left: 1px solid silver; 53 | } 54 | .cm-fat-cursor .CodeMirror-cursor { 55 | width: auto; 56 | border: 0 !important; 57 | background: #7e7; 58 | } 59 | .cm-fat-cursor div.CodeMirror-cursors { 60 | z-index: 1; 61 | } 62 | .cm-fat-cursor-mark { 63 | background-color: rgba(20, 255, 20, 0.5); 64 | -webkit-animation: blink 1.06s steps(1) infinite; 65 | -moz-animation: blink 1.06s steps(1) infinite; 66 | animation: blink 1.06s steps(1) infinite; 67 | } 68 | .cm-animate-fat-cursor { 69 | width: auto; 70 | border: 0; 71 | -webkit-animation: blink 1.06s steps(1) infinite; 72 | -moz-animation: blink 1.06s steps(1) infinite; 73 | animation: blink 1.06s steps(1) infinite; 74 | background-color: #7e7; 75 | } 76 | @-moz-keyframes blink { 77 | 0% {} 78 | 50% { background-color: transparent; } 79 | 100% {} 80 | } 81 | @-webkit-keyframes blink { 82 | 0% {} 83 | 50% { background-color: transparent; } 84 | 100% {} 85 | } 86 | @keyframes blink { 87 | 0% {} 88 | 50% { background-color: transparent; } 89 | 100% {} 90 | } 91 | 92 | /* Can style cursor different in overwrite (non-insert) mode */ 93 | .CodeMirror-overwrite .CodeMirror-cursor {} 94 | 95 | .cm-tab { display: inline-block; text-decoration: inherit; } 96 | 97 | .CodeMirror-rulers { 98 | position: absolute; 99 | left: 0; right: 0; top: -50px; bottom: -20px; 100 | overflow: hidden; 101 | } 102 | .CodeMirror-ruler { 103 | border-left: 1px solid #ccc; 104 | top: 0; bottom: 0; 105 | position: absolute; 106 | } 107 | 108 | /* DEFAULT THEME */ 109 | 110 | .cm-s-default .cm-header {color: blue;} 111 | .cm-s-default .cm-quote {color: #090;} 112 | .cm-negative {color: #d44;} 113 | .cm-positive {color: #292;} 114 | .cm-header, .cm-strong {font-weight: bold;} 115 | .cm-em {font-style: italic;} 116 | .cm-link {text-decoration: underline;} 117 | .cm-strikethrough {text-decoration: line-through;} 118 | 119 | .cm-s-default .cm-keyword {color: #708;} 120 | .cm-s-default .cm-atom {color: #219;} 121 | .cm-s-default .cm-number {color: #164;} 122 | .cm-s-default .cm-def {color: #00f;} 123 | .cm-s-default .cm-variable, 124 | .cm-s-default .cm-punctuation, 125 | .cm-s-default .cm-property, 126 | .cm-s-default .cm-operator {} 127 | .cm-s-default .cm-variable-2 {color: #05a;} 128 | .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;} 129 | .cm-s-default .cm-comment {color: #a50;} 130 | .cm-s-default .cm-string {color: #a11;} 131 | .cm-s-default .cm-string-2 {color: #f50;} 132 | .cm-s-default .cm-meta {color: #555;} 133 | .cm-s-default .cm-qualifier {color: #555;} 134 | .cm-s-default .cm-builtin {color: #30a;} 135 | .cm-s-default .cm-bracket {color: #997;} 136 | .cm-s-default .cm-tag {color: #170;} 137 | .cm-s-default .cm-attribute {color: #00c;} 138 | .cm-s-default .cm-hr {color: #999;} 139 | .cm-s-default .cm-link {color: #00c;} 140 | 141 | .cm-s-default .cm-error {color: #f00;} 142 | .cm-invalidchar {color: #f00;} 143 | 144 | .CodeMirror-composing { border-bottom: 2px solid; } 145 | 146 | /* Default styles for common addons */ 147 | 148 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 149 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 150 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 151 | .CodeMirror-activeline-background {background: #e8f2ff;} 152 | 153 | /* STOP */ 154 | 155 | /* The rest of this file contains styles related to the mechanics of 156 | the editor. You probably shouldn't touch them. */ 157 | 158 | .CodeMirror { 159 | position: relative; 160 | overflow: hidden; 161 | background: white; 162 | } 163 | 164 | .CodeMirror-scroll { 165 | overflow: scroll !important; /* Things will break if this is overridden */ 166 | /* 30px is the magic margin used to hide the element's real scrollbars */ 167 | /* See overflow: hidden in .CodeMirror */ 168 | margin-bottom: -30px; margin-right: -30px; 169 | padding-bottom: 30px; 170 | height: 100%; 171 | outline: none; /* Prevent dragging from highlighting the element */ 172 | position: relative; 173 | } 174 | .CodeMirror-sizer { 175 | position: relative; 176 | border-right: 30px solid transparent; 177 | } 178 | 179 | /* The fake, visible scrollbars. Used to force redraw during scrolling 180 | before actual scrolling happens, thus preventing shaking and 181 | flickering artifacts. */ 182 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 183 | position: absolute; 184 | z-index: 6; 185 | display: none; 186 | } 187 | .CodeMirror-vscrollbar { 188 | right: 0; top: 0; 189 | overflow-x: hidden; 190 | overflow-y: scroll; 191 | } 192 | .CodeMirror-hscrollbar { 193 | bottom: 0; left: 0; 194 | overflow-y: hidden; 195 | overflow-x: scroll; 196 | } 197 | .CodeMirror-scrollbar-filler { 198 | right: 0; bottom: 0; 199 | } 200 | .CodeMirror-gutter-filler { 201 | left: 0; bottom: 0; 202 | } 203 | 204 | .CodeMirror-gutters { 205 | position: absolute; left: 0; top: 0; 206 | min-height: 100%; 207 | z-index: 3; 208 | } 209 | .CodeMirror-gutter { 210 | white-space: normal; 211 | height: 100%; 212 | display: inline-block; 213 | vertical-align: top; 214 | margin-bottom: -30px; 215 | } 216 | .CodeMirror-gutter-wrapper { 217 | position: absolute; 218 | z-index: 4; 219 | background: none !important; 220 | border: none !important; 221 | } 222 | .CodeMirror-gutter-background { 223 | position: absolute; 224 | top: 0; bottom: 0; 225 | z-index: 4; 226 | } 227 | .CodeMirror-gutter-elt { 228 | position: absolute; 229 | cursor: default; 230 | z-index: 4; 231 | } 232 | .CodeMirror-gutter-wrapper ::selection { background-color: transparent } 233 | .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent } 234 | 235 | .CodeMirror-lines { 236 | cursor: text; 237 | min-height: 1px; /* prevents collapsing before first draw */ 238 | } 239 | .CodeMirror pre { 240 | /* Reset some styles that the rest of the page might have set */ 241 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 242 | border-width: 0; 243 | background: transparent; 244 | font-family: inherit; 245 | font-size: inherit; 246 | margin: 0; 247 | white-space: pre; 248 | word-wrap: normal; 249 | line-height: inherit; 250 | color: inherit; 251 | z-index: 2; 252 | position: relative; 253 | overflow: visible; 254 | -webkit-tap-highlight-color: transparent; 255 | -webkit-font-variant-ligatures: contextual; 256 | font-variant-ligatures: contextual; 257 | } 258 | .CodeMirror-wrap pre { 259 | word-wrap: break-word; 260 | white-space: pre-wrap; 261 | word-break: normal; 262 | } 263 | 264 | .CodeMirror-linebackground { 265 | position: absolute; 266 | left: 0; right: 0; top: 0; bottom: 0; 267 | z-index: 0; 268 | } 269 | 270 | .CodeMirror-linewidget { 271 | position: relative; 272 | z-index: 2; 273 | overflow: auto; 274 | } 275 | 276 | .CodeMirror-widget {} 277 | 278 | .CodeMirror-rtl pre { direction: rtl; } 279 | 280 | .CodeMirror-code { 281 | outline: none; 282 | } 283 | 284 | /* Force content-box sizing for the elements where we expect it */ 285 | .CodeMirror-scroll, 286 | .CodeMirror-sizer, 287 | .CodeMirror-gutter, 288 | .CodeMirror-gutters, 289 | .CodeMirror-linenumber { 290 | -moz-box-sizing: content-box; 291 | box-sizing: content-box; 292 | } 293 | 294 | .CodeMirror-measure { 295 | position: absolute; 296 | width: 100%; 297 | height: 0; 298 | overflow: hidden; 299 | visibility: hidden; 300 | } 301 | 302 | .CodeMirror-cursor { 303 | position: absolute; 304 | pointer-events: none; 305 | } 306 | .CodeMirror-measure pre { position: static; } 307 | 308 | div.CodeMirror-cursors { 309 | visibility: hidden; 310 | position: relative; 311 | z-index: 3; 312 | } 313 | div.CodeMirror-dragcursors { 314 | visibility: visible; 315 | } 316 | 317 | .CodeMirror-focused div.CodeMirror-cursors { 318 | visibility: visible; 319 | } 320 | 321 | .CodeMirror-selected { background: #d9d9d9; } 322 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 323 | .CodeMirror-crosshair { cursor: crosshair; } 324 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 325 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 326 | 327 | .cm-searching { 328 | background-color: #ffa; 329 | background-color: rgba(255, 255, 0, .4); 330 | } 331 | 332 | /* Used to force a border model for a node */ 333 | .cm-force-border { padding-right: .1px; } 334 | 335 | @media print { 336 | /* Hide the cursor when printing */ 337 | .CodeMirror div.CodeMirror-cursors { 338 | visibility: hidden; 339 | } 340 | } 341 | 342 | /* See issue #2901 */ 343 | .cm-tab-wrap-hack:after { content: ''; } 344 | 345 | /* Help users use markselection to safely style text background */ 346 | span.CodeMirror-selectedtext { background: none; } 347 | -------------------------------------------------------------------------------- /static/controls.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /static/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pac-dev/AudioMasher/1aefedca6174477f02c196efb0322b58a6eaa624/static/loading.gif -------------------------------------------------------------------------------- /static/masher-about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pac-dev/AudioMasher/1aefedca6174477f02c196efb0322b58a6eaa624/static/masher-about.png -------------------------------------------------------------------------------- /static/masher.css: -------------------------------------------------------------------------------- 1 | /* common styles */ 2 | html { 3 | height: 100%; 4 | } 5 | body { 6 | font-size: 15px; 7 | font-family: sans-serif; 8 | position: relative; 9 | min-width: 760px; 10 | height: 100%; 11 | padding: 0; 12 | margin: 0; 13 | } 14 | a, a:link, a:visited, a:active { 15 | color: inherit; 16 | text-decoration: none; 17 | cursor: pointer; 18 | } 19 | .top_bar { 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | right: 0; 24 | height: 28px; 25 | white-space: nowrap; 26 | overflow: hidden; 27 | } 28 | .left_pane { 29 | position: absolute; 30 | top: 29px; 31 | bottom: 0; 32 | left: 0; 33 | right: 0; 34 | } 35 | .help_pane, .params_pane, .learn_pane { 36 | display: none; 37 | position: absolute; 38 | top: 29px; 39 | bottom: 0; 40 | left: 58%; 41 | right: 0; 42 | overflow: hidden; 43 | } 44 | .helpon .left_pane, .paramson .left_pane { right: 42%; } 45 | .helpon .help_pane { display: block; } 46 | .paramson .params_pane { display: block; } 47 | .middle_pane { 48 | position: relative; 49 | top: 29px; 50 | margin: 0 auto; 51 | padding: 0 30px; 52 | width: calc(425px + 34%); 53 | min-height: calc(100% - 40px); 54 | } 55 | 56 | .top_bar>* { 57 | padding: 5px 12px; 58 | height: 18px; 59 | min-width: 73px; 60 | text-align: center; 61 | } 62 | .top_bar>.L { float: left; } 63 | .top_bar>.R { float: right; padding: 5px; } 64 | .top_bar .headline { 65 | max-width: calc(100% - 560px); 66 | } 67 | .top_bar .headline div { 68 | overflow: hidden; 69 | text-overflow: ellipsis; 70 | } 71 | .about h3 { margin-top: 40px; } 72 | 73 | a.tab_thumb { 74 | display: inline-block; 75 | padding: 6px 14px; 76 | margin: 0; 77 | cursor: pointer; 78 | } 79 | .tab_content { 80 | display: none; 81 | padding: 15px 4px; 82 | position: absolute; 83 | top: 25px; 84 | bottom: 0; 85 | left: 0; 86 | right: 0; 87 | overflow: auto; 88 | word-wrap: break-word; 89 | box-sizing: border-box; 90 | } 91 | .active_content { 92 | display:block; 93 | } 94 | .tab_content li { 95 | margin-bottom: 12px; 96 | } 97 | #no_params.nodisplay { 98 | display: none; 99 | } 100 | 101 | #sliders { 102 | position: relative; 103 | } 104 | 105 | .sliderOut { 106 | display: flex; 107 | align-items: center; 108 | height: 45px; 109 | } 110 | 111 | .sliderOut > * { 112 | flex-grow: 1; 113 | padding: 9px; 114 | } 115 | 116 | .sliderLabel { width: 23.2%; text-align: right; } 117 | .sliderRange { width: 53.2%; } 118 | .sliderDispl { width: 23.2%; text-align: left; } 119 | 120 | #controls { 121 | position: absolute; 122 | top: 16px; 123 | right: 0; 124 | z-index: 12; 125 | } 126 | #controls.learn { 127 | position: static; 128 | top: 0; 129 | right: 0; 130 | text-align: center; 131 | padding: 12px; 132 | } 133 | #controls>a { 134 | display: block; 135 | margin: 4px 0; 136 | padding: 7px; 137 | width: 36px; 138 | height: 36px; 139 | cursor: pointer; 140 | } 141 | #controls>a::after { 142 | content: " "; 143 | display: block; 144 | margin: 0 auto; 145 | width: 100%; 146 | height: 100%; 147 | background-size: 1000%; 148 | background-repeat: no-repeat; 149 | } 150 | #controls.learn>a { 151 | display: inline-block; 152 | border: 1px solid #949ac7; 153 | width: auto; 154 | position: relative; 155 | padding: 8px 11px 7px 41px; 156 | height: 21px; 157 | } 158 | 159 | #controls.learn>a::after { 160 | position: absolute; 161 | top: 0; 162 | width: 34px; 163 | left: 2px; 164 | height: 34px; 165 | } 166 | 167 | #c_play::after {background-position: 33.33% 0;} 168 | #c_stop::after {background-position: 55.55% 0;} 169 | #c_post::after {background-position: 77.77% 0;} 170 | #c_help::after {background-position: 11.11% 50%;} 171 | #c_params::after {background-position: 11.11% 0;} 172 | #c_output::after {background-position: 55.55% 50%;} 173 | #c_play.playing::after {background-position: 33.33% 50%;} 174 | 175 | #play_loading { 176 | display: block; 177 | margin: 4px 0; 178 | padding: 7px; 179 | width: 36px; 180 | height: 36px; 181 | background-repeat: no-repeat; 182 | background-position: 50% 50%; 183 | } 184 | #play_loading.learn{ 185 | display: inline-block; 186 | } 187 | #controls>*.nodisplay, #controls.learn>*.nodisplay {display: none;} 188 | 189 | #errors { 190 | position: absolute; 191 | bottom: 0; 192 | right: 70px; 193 | max-width: 50%; 194 | } 195 | #errors>div { 196 | margin: 8px; 197 | padding: 8px; 198 | z-index: 12; 199 | } 200 | #tooltip { 201 | display: none; 202 | padding: 8px; 203 | position: absolute; 204 | bottom: 12px; 205 | right: 78px; 206 | max-width: 50%; 207 | z-index: 12; 208 | } 209 | #tooltip.on { 210 | display: block; 211 | } 212 | #tooltip.top { 213 | top: 10px; 214 | bottom: auto; 215 | } 216 | .learn #tooltip, .learn #errors { 217 | bottom: 81px; 218 | } 219 | .learn #tooltip.top { 220 | bottom: auto; 221 | } 222 | 223 | .middle_pane>table { 224 | width: 100%; 225 | } 226 | .middle_pane>table a { 227 | display: block; 228 | padding: 2px 3px; 229 | } 230 | .middle_pane>table td:last-child { 231 | padding: 3px 5px; 232 | } 233 | .middle_pane img { 234 | width: 200px; 235 | } 236 | a.restore_link { 237 | display: none; 238 | } 239 | a.restore_link.on { 240 | display: inline; 241 | } 242 | #modal_back { 243 | display: none; 244 | position: fixed; 245 | top: 0; 246 | bottom: 0; 247 | right: 0; 248 | left: 0; 249 | cursor: pointer; 250 | z-index: 13; 251 | } 252 | #modal_front { 253 | display: none; 254 | position: fixed; 255 | top: 15px; 256 | left: 0; 257 | right: 0; 258 | margin-left: auto; 259 | margin-right: auto; 260 | width: 520px; 261 | padding: 18px 9px; 262 | max-height: calc(100% - 70px); 263 | z-index: 14; /* CodeMirror being an ass */ 264 | overflow-y: auto; 265 | 266 | } 267 | #modal_back.on, #modal_front.on { 268 | display: block; 269 | } 270 | #modal_front input, #modal_front textarea { 271 | margin: 8px 0; 272 | padding: 2px 8px; 273 | } 274 | #modal_front textarea { 275 | width: 96%; 276 | resize: vertical; 277 | } 278 | #modal_front input[type=text], 279 | #modal_front input[type=password] { width: 300px; } 280 | #modal_front input[type=button] { padding: 4px 20px; } 281 | #modal_front p { margin: 30px 0; } 282 | #modal_front i { opacity: 0.82; } 283 | #modal_front .label { 284 | display: inline-block; 285 | width: 140px; 286 | } 287 | #close_modal { 288 | position: absolute; 289 | top: 45px; 290 | right: 30px; 291 | } 292 | #quick_ref div { 293 | margin: 0 0 12px 12px; 294 | } 295 | 296 | #editor_container { 297 | width: calc(100% - 51px); 298 | height: 100%; 299 | } 300 | #editor_container.learn { 301 | width: 100%; 302 | height: calc(100% - 77px); 303 | } 304 | 305 | 306 | 307 | /********** color theme **********/ 308 | 309 | 310 | body.drk, .drk textarea { 311 | color: #484848; 312 | background-color: #fff; 313 | } 314 | .drk .top_bar { 315 | border-bottom: 1px solid #949ac7; 316 | color: #6b6b6b; 317 | } 318 | .drk .headline { 319 | border-bottom: 1px solid #949ac7; 320 | } 321 | .drk .top_bar>* { 322 | border-left: 1px solid #949ac7; 323 | } 324 | .drk .top_bar>a:hover { 325 | background-color: #f0f1fb; 326 | } 327 | .drk .help_pane, .drk .params_pane, .drk .learn_pane { 328 | border-left: 1px solid #949ac7; 329 | } 330 | .drk .middle_pane { 331 | background: #f2f2f2; 332 | } 333 | .drk #modal_front a:hover {color: #3913ff; } 334 | .drk #modal_front h2 {color: #777; } 335 | .drk .about a { color: blue; } 336 | .drk #controls { 337 | /*background-color: #eaeaea*/ 338 | } 339 | .drk #controls>a:hover { 340 | background-color: #f0f1fb; 341 | } 342 | .drk.helpon #c_help, .drk.helpon #c_help:hover { 343 | background-color: #949ac7; 344 | } 345 | .drk.helpon #c_help::after { 346 | background-position: 55.55% 50%; 347 | } 348 | .drk.paramson #c_params, .drk.paramson #c_params:hover { 349 | background-color: #949ac7; 350 | } 351 | .drk.paramson #c_params::after { 352 | background-position: 77.77% 50%; 353 | } 354 | .drk .middle_pane>table tr:nth-child(odd) { 355 | background-color: #dcdcdc; 356 | } 357 | .drk .middle_pane>table th { 358 | background-color: #949ac7; 359 | color: white; 360 | } 361 | .drk .middle_pane>table a:hover { 362 | background-color: #f0f1fb; 363 | outline: 1px solid #949ac7; 364 | } 365 | .drk #output .message { 366 | color: #3d3; 367 | } 368 | .drk #output .error { 369 | color: #f11; 370 | } 371 | .drk .tab_content a { 372 | text-decoration: underline; 373 | } 374 | .drk .tab_content a:hover { 375 | color: #3913ff; 376 | } 377 | .drk a.tab_thumb:hover { 378 | background-color: #f0f1fb; 379 | } 380 | .drk a.active_thumb, .drk a.active_thumb:hover { 381 | background-color: #949ac7; 382 | color: white; 383 | } 384 | .drk #errors>div { 385 | border: 1px solid #e66; 386 | background-color: #ffe6e6; 387 | } 388 | .drk #tooltip { 389 | border: 1px solid #777; 390 | background-color: white; 391 | } 392 | .drk #modal_back { 393 | background-color: rgba(0, 0, 0, 0.44); 394 | } 395 | .drk #modal_front { 396 | border: 1px solid #999; 397 | background-color: white; 398 | } 399 | .drk #modal_front input, .drk #modal_front textarea { 400 | background-color: #f1f1f1; 401 | border: 1px solid #8e8e8e; 402 | color: black; 403 | } 404 | .drk #modal_front a { 405 | text-decoration: underline; 406 | } 407 | .drk #modal_front hr { border: 1px solid #b5b5b5; } 408 | .drk #quick_ref b { 409 | color: #708; 410 | } 411 | .drk #metadata em { 412 | color: #909090; 413 | } 414 | 415 | 416 | /* codemirror theme */ 417 | .cm-s-masher-light.CodeMirror { background: #f2f2f2; border-right: 1px solid #949ac7; } 418 | .cm-s-masher-light .CodeMirror-gutters { background: white; border-right: 1px solid #949ac7; } 419 | 420 | .learn .cm-s-masher-light.CodeMirror { border-bottom: 1px solid #949ac7; border-right:none; } 421 | .learn .cm-s-masher-light .CodeMirror-gutters { background: transparent; } 422 | 423 | /* (everything below is copied from the default theme) */ 424 | .cm-s-masher-light .cm-header {color: blue;} 425 | .cm-s-masher-light .cm-quote {color: #090;} 426 | .cm-s-masher-light .cm-keyword {color: #708;} 427 | .cm-s-masher-light .cm-atom {color: #219;} 428 | .cm-s-masher-light .cm-number {color: #164;} 429 | .cm-s-masher-light .cm-def {color: #00f;} 430 | .cm-s-masher-light .cm-variable, 431 | .cm-s-masher-light .cm-punctuation, 432 | .cm-s-masher-light .cm-property, 433 | .cm-s-masher-light .cm-operator {} 434 | .cm-s-masher-light .cm-variable-2 {color: #05a;} 435 | .cm-s-masher-light .cm-variable-3, .cm-s-default .cm-type {color: #085;} 436 | .cm-s-masher-light .cm-comment {color: #a50;} 437 | .cm-s-masher-light .cm-string {color: #a11;} 438 | .cm-s-masher-light .cm-string-2 {color: #f50;} 439 | .cm-s-masher-light .cm-meta {color: #555;} 440 | .cm-s-masher-light .cm-qualifier {color: #555;} 441 | .cm-s-masher-light .cm-builtin {color: #30a;} 442 | .cm-s-masher-light .cm-bracket {color: #997;} 443 | .cm-s-masher-light .cm-tag {color: #170;} 444 | .cm-s-masher-light .cm-attribute {color: #00c;} 445 | .cm-s-masher-light .cm-hr {color: #999;} 446 | .cm-s-masher-light .cm-link {color: #00c;} 447 | .cm-s-masher-light .cm-error {color: #f00;} 448 | -------------------------------------------------------------------------------- /static/sporthem.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pac-dev/AudioMasher/1aefedca6174477f02c196efb0322b58a6eaa624/static/sporthem.wasm -------------------------------------------------------------------------------- /static/sporthparams.js: -------------------------------------------------------------------------------- 1 | 2 | var num = String.raw`(-?\d+(?:\.\d+)?)`; 3 | var re = new RegExp(String.raw`_([\w]+) (\d+) palias ?# ?${num} - ${num},? ?${num}? ?\(?([\w]+)?\)?`, 'g'); 4 | var setP = function(i, p) 5 | { 6 | if (typeof sporthem_setp !== 'undefined') sporthem_setp(i, p); 7 | } 8 | 9 | var createSlider = function(container, param, values) 10 | { 11 | var paramDiv = document.createElement("div"); 12 | paramDiv.className = "sliderOut"; 13 | 14 | var label = document.createElement("div"); 15 | label.innerHTML = param.name + ":"; 16 | label.className = "sliderLabel"; 17 | paramDiv.appendChild(label); 18 | 19 | var slider = document.createElement("input"); 20 | slider.type = "range"; 21 | slider.min = param.min; 22 | slider.max = param.max; 23 | slider.step = (param.max - param.min) / 1000; 24 | slider.className = "sliderRange"; 25 | paramDiv.appendChild(slider); 26 | 27 | var displ = document.createElement("div"); 28 | displ.innerHTML = param.value + ' ' + param.units; 29 | displ.className = "sliderDispl"; 30 | paramDiv.appendChild(displ); 31 | 32 | container.appendChild(paramDiv); 33 | 34 | slider.addEventListener('input', function(event) { 35 | values[param.name] = slider.value; 36 | setP(param.index, slider.value); 37 | displ.innerHTML = slider.value + ' ' + param.units; 38 | }); 39 | 40 | slider.value = param.value; 41 | values[param.name] = slider.value; 42 | } 43 | 44 | function sporthParam_createSliders(container, script, values = {}) 45 | { 46 | container.innerHTML = ''; 47 | while(true) { 48 | var match = re.exec(script); 49 | if (!match) break; 50 | var param = { 51 | name: match[1], 52 | index: match[2], 53 | min: match[3], 54 | max: match[4], 55 | }; 56 | if (values[param.name] !== undefined) 57 | param.value = values[param.name]; 58 | else if (match[5] !== undefined) 59 | param.value = match[5]; 60 | else 61 | param.value = param.min; 62 | param.units = (match[6] !== undefined) ? match[6] : ''; 63 | createSlider(container, param, values); 64 | } 65 | return values; 66 | } 67 | 68 | function sporthParam_setPvalues(script, values) 69 | { 70 | while(true) { 71 | var match = re.exec(script); 72 | if (!match) break; 73 | var param = { 74 | name: match[1], 75 | index: match[2], 76 | min: match[3], 77 | max: match[4], 78 | }; 79 | if (values[param.name] !== undefined) 80 | param.value = values[param.name]; 81 | else if (match[5] !== undefined) 82 | param.value = match[5]; 83 | else 84 | param.value = param.min; 85 | setP(param.index, param.value); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /templates/404.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |

404

7 |
8 | Page not found. 9 |
10 |
11 |
12 |
-------------------------------------------------------------------------------- /templates/about.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |

What is AudioMasher?

7 | 26 | 27 |

Where do I start?

28 | 36 | 37 |

What is the license on scripts posted to AudioMasher?

38 |

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 |

47 | 48 |

What other tools were used to create AudioMasher?

49 | 63 |

Contact

64 |

65 | pierre@osar.fr 66 |

67 |
68 | 69 |
70 |
71 |
-------------------------------------------------------------------------------- /templates/browse.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | {{range .Patches}} 10 | 11 | 12 | 13 | 14 | 15 | {{end}} 16 |
PatchAuthorCreated
{{.Title}}{{.Author}}{{HumanDate .DateCreated}}
17 |
18 |
19 |
-------------------------------------------------------------------------------- /templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | AudioMasher - {{.HeadlinePrefix}}{{.Headline}} 4 | 5 | 6 | 7 | 8 | 9 | 13 | 16 | 17 | 18 |
19 | AudioMasher 20 |
21 | [continue autosave] 22 | {{if eq .CurrentView "Learn"}} 23 | Learn Sporth 24 | {{else}} 25 | {{.HeadlinePrefix}}{{.Headline}} 26 | {{end}} 27 |
28 | 29 | {{if .LoggedIn}} 30 | logout ({{.UserName}}) 31 | {{else}} 32 | log in 33 | {{end}} 34 | learn 35 | new 36 | browse 37 |
38 | {{if eq .CurrentView "Patch"}} 39 | {{template "patch.html" .}} 40 | {{else if eq .CurrentView "User"}} 41 | {{template "user.html" .}} 42 | {{else if eq .CurrentView "Browse"}} 43 | {{template "browse.html" .}} 44 | {{else if eq .CurrentView "About"}} 45 | {{template "about.html" .}} 46 | {{else if eq .CurrentView "Learn"}} 47 | {{template "learn.html" .}} 48 | {{else if eq .CurrentView "404"}} 49 | {{template "404.html" .}} 50 | {{end}} 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /templates/learn.html: -------------------------------------------------------------------------------- 1 | 2 | 107 |
108 | {{.Tutorial}} 109 |

110 |
111 |
112 |
113 | 130 |
131 |
132 |
133 | Compile & play 134 | Stop audio 135 |
136 |
137 |
138 |
139 | -------------------------------------------------------------------------------- /templates/patch.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |
7 |
8 | 9 | 10 | 11 | 12 | {{if .CanUpdatePatch}} 13 | 14 | {{else if .LoggedIn}} 15 | 16 | {{else}} 17 | 18 | {{end}} 19 |
20 |
21 |
22 |
23 |
24 |
25 | No parameters.
26 |
27 |
28 |

29 |
30 |
31 | {{if eq .Patch.Title ""}} 32 | 33 | quick ref 34 | 35 | {{else}} 36 | 37 | meta 38 | 39 | 40 | quick ref 41 | 42 | {{end}} 43 | 44 | help 45 | 46 | {{if eq .Patch.Title ""}} 47 |
48 | loading... 49 |
50 | {{else}} 51 |
52 | Title:
53 | {{.Patch.Title}}

54 | Author:
55 | {{.Patch.Author}}

56 | Description:
57 | {{index .Patch.Files "readme.txt"}}

58 | Created: {{HumanDate .Patch.DateCreated }} 59 |

60 | Modified: {{HumanDate .Patch.DateModified }} 61 |

62 | License 63 |

64 | 65 |
66 |
67 | loading... 68 |
69 | {{end}} 70 |
71 | Here are some Sporth learning resources (most of them by Paul Batchelor): 72 |
73 | 92 |
93 | And some information about AudioMasher: 94 |
95 | 100 |

101 |
102 |
-------------------------------------------------------------------------------- /templates/tutorial/about_cookbook.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | About this Cookbook 7 | 8 | 9 | 10 | 11 | 12 |
13 |

About this Cookbook

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 |

How to read this book

21 |

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 |
31 | Previous: What is Sporth? | Back | Next: Hello, Sine 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /templates/tutorial/ftables.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | F-Tables 7 | 8 | 9 | 10 | 11 | 12 |
13 |

F-Tables

14 |

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 |

A simple example

19 |

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 | 32 |

Gen routines with arguments

33 |

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 | 50 |

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 |

The underscore (_) shortcut

55 |

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 |

Unit generators that use f-tables

63 |

Here are some common unit generators that make use of f-tables: 64 |

75 |

76 |
77 | Previous: Gates and Triggers | Back | Next: Variables 78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /templates/tutorial/gates_triggers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Gates and Triggers 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Gates and Triggers

14 |

All about the trigger

15 |

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 | 39 |

To generate trigger signals, a few ugens are available:

40 | 53 |

All about the gate

54 |

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 | 62 |

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 | 83 |

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 |
87 | Previous: Stack Operations | Back | Next: F-Tables 88 |
89 | 90 | 91 | -------------------------------------------------------------------------------- /templates/tutorial/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Hello, Sine 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Hello, Sine

14 |

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 |
22 | Previous: About this Cookbook | Back | Next: The Stack 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /templates/tutorial/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Learn Sporth 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Learn Sporth: An Interactive Cookbook

14 |

Part 1: Introduction

15 |
    16 |
  1. What is Sporth?
  2. 17 |
  3. About this Cookbook
  4. 18 |
19 |

Part 2: How Sporth Works ("The Kitchen")

20 |
    21 |
  1. Hello, Sine
  2. 22 |
  3. The Stack
  4. 23 |
  5. Stack Operations
  6. 24 |
  7. Gates and Triggers
  8. 25 |
  9. F-tables and Gen Routines
  10. 26 |
  11. Variables
  12. 27 |
  13. p registers
  14. 28 |
  15. prop
  16. 29 |
  17. lsys
  18. 30 |
  19. List of Sporth Ugens
  20. 31 |
32 |

Part 3: Example Sporth Patches ("The Recipes")

33 |
    34 |
  1. Lots of Patches
  2. 35 |
  3. FM
  4. 36 |
  5. Chatting Robot
  6. 37 |
  7. Bones
  8. 38 |
  9. Oatmeal
  10. 39 |
  11. Machines at Work
  12. 40 |
  13. Midnight Crawl
  14. 41 |
  15. Play With Toys
  16. 42 |
  17. The Waiting Room
  18. 43 |
  19. In Seven
  20. 44 |
45 | 46 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /templates/tutorial/lots_of_patches.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lots of Patches 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Lots of Patches

14 |

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 | 31 |
32 | Back 33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /templates/tutorial/lsys.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lsys 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Lsys

14 |

15 | LSYS is a tiny little language designed to produce L-systems.

16 |

Some LSYS code

17 |

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 in Sporth

36 |

LSYS is implemented as a Sporth UGen, which takes in 37 | 3 arguments. From left to right, they are:

38 |
    39 |
  1. trigger signal, which iterates through the L-system
  2. 40 |
  3. The order N of the L-system (init-time only)
  4. 41 |
  5. The code itself.
  6. 42 |
43 |

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 |

LSYS as a standalone

71 |

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 |
82 | Previous: Prop | Back | Next: A list of Unit Generators in Sporth 83 |
84 | 85 | 86 | -------------------------------------------------------------------------------- /templates/tutorial/operations.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stack Operations 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Stack Operations

14 |

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 |
21 | Previous: The Stack | Back | Next: Gates and Triggers 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /templates/tutorial/polysporth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Polysporth 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Polysporth

14 |

Polysporth is an engine for polyphony in Sporth. Bindings for polysporth are 15 | written in the scheme dialect tinyscheme, included inside of Sporth.

16 |

Why Polysporth?

17 |

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 | 29 |

How it works

30 |

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 |

More information

34 |

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 |
38 | Back 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /templates/tutorial/pregisters.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | P Registers 7 | 8 | 9 | 10 | 11 | 12 |
13 |

P Registers

14 |

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 |
38 | Previous: Variables | Back | Next: Prop 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /templates/tutorial/prop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Prop 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Prop

14 |

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 |

More

28 |

For a full explanation of prop, consult the 29 | prop project page.

30 |
31 | Previous: P Registers | Back | Next: Lsys 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /templates/tutorial/recipes_bones.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Bones
by Paul Batchelor

14 |
# Bones 15 | # By Paul Batchelor 16 | # September 2016 17 | _rat "1 2.5752 4.4644 6.984" gen_vals 18 | _tk var 19 | _frq var 20 | 1 10 1 randh metro 21 | (0.1 0.8 (1 10 3 randh) randi) maytrig 22 | dup 0.65 maygate * 23 | tick + 24 | _tk set 25 | _tk get 500 1500 trand _frq set 26 | _tk get (_frq get 0 _rat tget *) 10 40 3 randi mode 27 | (_frq get 1 _rat tget *) 20 mode 28 | _tk get (_frq get 2 _rat tget *) 22 mode 29 | (_frq get 3 _rat tget *) 15 mode + 30 | dup 0.1 0.8 3 randi 31 | 0.001 0.2 (0.1 4 1 randi) randi 32 | 1.0 vdelay 33 | dup 0.4 0.9 0.5 randi 34 | 0.2 1 sine 0.01 0.3 biscale 35 | 1.0 64 smoothdelay 0 1 0.2 randi * 36 | + + 37 | dup jcrev -6 ampdb * + -1 1 limit 38 |
show code >>
39 |
 # 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 |

Tables and variables

47 |

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 |

Trigger generation

58 |

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 |

Modal Resonators

77 |

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 |

Delay lines

99 |

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 |

Post-effects

117 |

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 |
124 | 125 | 126 | -------------------------------------------------------------------------------- /templates/tutorial/recipes_chatting_robot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Chatting Robot
by Paul Batchelor

14 |
# o a e 15 | 'f1' '350 600 400' gen_vals 16 | 'f2' '600 1040 1620' gen_vals 17 | 'f3' '2400 2250 2400' gen_vals 18 | 'g1' '1 1 1' gen_vals 19 | 'g2' '0.28184 0.4468 0.251' gen_vals 20 | 'g3' '0.0891 0.354 0.354' gen_vals 21 | 'bw1' '40 60 40' gen_vals 22 | 'bw2' '80 70 80' gen_vals 23 | 'bw3' '100 110 100' gen_vals 24 | 0 2 25 | 0.1 1 sine 2 10 biscale 26 | randi 0 pset 27 | 110 200 0.8 1 sine 2 8 biscale randi 28 | 3 20 30 jitter + 29 | 0.2 0.1 square 30 | dup 31 | 0 p 0 0 0 'g1' tabread * 32 | 0 p 0 0 0 'f1' tabread 33 | 0 p 0 0 0 'bw1' tabread 34 | butbp 35 | swap dup 36 | 0 p 0 0 0 'g2' tabread * 37 | 0 p 0 0 0 'f2' tabread 38 | 0 p 0 0 0 'bw2' tabread 39 | butbp 40 | swap 41 | 0 p 0 0 0 'g3' tabread * 42 | 0 p 0 0 0 'f3' tabread 43 | 0 p 0 0 0 'bw3' tabread 44 | butbp 45 | + + 46 | 0.4 dmetro 0.5 maygate 0.01 port * 2.0 * 47 | dup 48 | 60 500 3.0 1.0 6000 49 | 315 6 50 | 2000 0 51 | 0.2 2 zitarev drop 52 |
show code >>
53 |

"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 |

Table generation

59 |

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 | 68 |

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 |

Jitter

86 |

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 |

Glottis

94 |

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 |

Resonant Cavities

107 |

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 |

Words

129 |

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 |

Effects

138 |

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 |
148 | 149 | 150 | -------------------------------------------------------------------------------- /templates/tutorial/recipes_fm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

FM
by Paul Batchelor

14 |
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 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /templates/tutorial/recipes_in_seven.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

In Seven
by Paul Batchelor

14 |
# in seven 15 | # paul batchelor 16 | # january 2017 17 | _seq "0 4 5 7 -2" gen_vals 18 | _clk var 19 | _dry var 20 | _send var 21 | 210 "++2(+-+)" prop _clk set 22 | _clk get 0 _seq tseq 58 + mtof 0.001 port 23 | 0.3 24 | _clk get 1 3 trand floor 25 | _clk get 1 7 trand floor 26 | 30 inv 1 sine 0.1 2 biscale 27 | fm 28 | dup 29 | _clk get 0.2 maygate 30 | 0.01 port 31 | * _send set 32 | _clk get 0.001 0.01 0.4 tenv * 33 | _dry set 34 | _dry get _send get 2 * + dup 35 | 0.97 5000 revsc 36 | drop -27 ampdb * 37 | dcblk 38 | _send get 39 | 0.8 40 | 210 bpm2dur 1.5 * 41 | delay 800 butlp -7 ampdb * + 42 | _dry get + 43 |
show code >>
44 |
 # 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 |

Tables and Variables

51 |

To begin, a number of tables and variables are created: 52 |

61 |

62 |

63 |
 _seq "0 4 5 7 -2" gen_vals
 64 |  _clk var 
 65 |  _dry var 
 66 |  _send var 
67 |

Prop Clock

68 |

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 |

FM patch

77 |

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 |

Frequency and Amplitude

82 |

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 |

Carrier

92 |

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 |

Modulator

103 |

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 |

Modulation Index

113 |

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 |

Reverb Throw

123 |

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 |

Envelopes for articulation

140 |

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 |

Effect Processing

149 |

Reverb

150 |

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 |

Feedback Delay

165 |

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 |
186 | 187 | 188 | -------------------------------------------------------------------------------- /templates/tutorial/recipes_midnight_crawl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Midnight Crawl
by Paul Batchelor

14 |
_seq "0 2 7" gen_vals 15 | _prog "0 2 5 7 9" gen_vals 16 | _seq2 "0 2 7 11 14 19" gen_vals 17 | _clk var 18 | _send var 19 | _env var 20 | 125 4 * bpm2dur dmetro 0.8 maytrig _clk set 21 | _clk get 0 _seq tseq 58 + 22 | _clk get 4 0 tdiv 0.1 maytrig 1 _prog tseq + 23 | 12 _clk get 0.2 maygate * + 24 | 0.1 0.1 5 jitter + mtof 25 | 0.001 0.02 (5 inv) randi port 26 | 0.4 saw 27 | 15 inv 1 sine 200 800 biscale butlp 28 | _clk get 0.05 maytrig 1.4 1.1 2.3 tenv * 29 | _clk get 0.7 maytrig 0.001 0.001 0.001 tenvx 1.9 noise * 30 | _clk get 100 1000 trand _clk get 0.8 0.9 trand streson 31 | _clk get 400 4000 trand 0.9 streson 32 | _clk get 100 1000 trand 0.8 streson 33 | dcblk -10 ampdb * 2500 butlp 34 | dup _clk get 4 0 tdiv 0.1 maygate 0.01 port * _send set + 35 | _clk get 16 0 tdiv 0.5 maytrig 36 | dup 0.005 0.01 0.3 tenvx _env set 37 | 1 _seq2 tseq 58 12 + + mtof 38 | 13 inv 1 sine 0.01 0.2 biscale 39 | 2 5 40 | _env get 5 * fm 41 | _env get * 1500 butlp 42 | dup 0.3 * _send get + _send set + 43 | dup 0.9 125 bpm2dur 1.51 * delay 2000 butlp -8 ampdb * + 44 | _send get dup 0.98 5550 revsc drop 0.9 * dcblk 200 buthp + 45 | _send get 0.3 125 bpm2dur delay -3 ampdb * + 46 |
show code >>
47 |

Midnight crawl is a prickly warm little patch featuring string resonators, 48 | and warm FM and subtractive saw sounds. 49 |

Tables and variables

50 | 58 |

59 |
 _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 |

The Clock

66 |

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 |

Warm Saw Sound

73 |

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 |

Streson Prickles

105 |

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 |

Pinging FM Sound

131 |

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 |

Effects

158 |

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 |
174 | 175 | 176 | -------------------------------------------------------------------------------- /templates/tutorial/recipes_oatmeal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Oatmeal
by Paul Batchelor

14 |
_wav 8192 gen_sine 15 | _d var 16 | 2 17 | 0.2 1 0 _wav osc 5 20 biscale 18 | 1 dust dup _d set 19 | 1100 1.9 0 wpkorg35 20 | jcrev 21 | _d get 0.5 maytrig 0.01 tgate 0.001 port 22 | 1000 0.3 0 _wav osc * + 23 | _d get 0.5 maytrig 0.004 tgate 0.001 port 24 | 2000 0.3 0 _wav osc * + 25 | _d get 0.2 maytrig 0.004 tgate 0.001 port 26 | 500 0.3 0 _wav osc * + 27 | _d get 0.3 maytrig 0.04 tgate 0.005 port 28 | -0.05 0.05 29 | _d get 1102 11025 trand 30 | randh * + 31 | dup 32 | _d get 0.1 maytrig 0.4 0.1 0.1 tenv * 33 | 0.6 0.3 delay 34 | 12 1024 512 pshift 35 | 1000 butlp -6 ampdb * + 36 |
show code >>
37 |

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 and Variables

41 |

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 |

Dust

47 |

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 |

Blerps

70 |

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 |

Noise

88 |

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 |

Processing

106 |

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 |
131 | 132 | 133 | -------------------------------------------------------------------------------- /templates/tutorial/recipes_playwithtoys.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Play With Toys
by Paul Batchelor

14 |
_seq 32 "0 0.4 7 0.4 12 0.1 5 0.1" gen_rand 15 | _clk var 16 | _trig var 17 | _freq var 18 | _dry var 19 | _bpm 110 varset 20 | 0 _bpm get 4 clock _clk set 21 | _clk get 0.6 maytrig _trig set 22 | _trig get 1 _seq tseq 60 + _freq set 23 | _freq get mtof 0.1 24 | _trig get 0 3 trand floor 25 | _trig get 0 7 trand floor 26 | 1 fm 27 | _trig get 0.005 0.01 0.04 tenvx * 28 | _freq get 12 - mtof 0.1 saw 1000 0.1 moogladder 29 | _trig get 0.3 maytrig 0.01 0.01 0.1 tenvx * 30 | + _dry set 31 | _dry get dup 10 10 10000 zrev drop -10 ampdb * 32 | 1000 buthp 33 | _dry get + 34 |
show code >>
35 |

Play with toys is a fun little patch making use of sequencing and 36 | weighted random distributions. 37 |

Tables and Variables

38 |

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 |

51 |

52 |
 _clk var 
 53 |  _trig var
 54 |  _freq var
 55 |  _dry var
 56 |  _bpm 110 varset
57 |

Clocks, Triggers, and Sequencing

58 |

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 |

FM Oscillator

70 |

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 |

Moog Saw

89 |

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 |

Effects

102 |

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 |
114 | 115 | 116 | -------------------------------------------------------------------------------- /templates/tutorial/recipes_waiting_room.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

The Waiting Room
by Paul Batchelor

14 |
_sine 8192 gen_sine 15 | _seq "62 69 72 71 74" gen_vals 16 | 62 mtof 0.3 8 3 17 | 15 inv 1 0.2 _sine osc 18 | 0.1 0.6 biscale gbuzz 19 | 60 mtof 0.3 8 3 20 | 10 inv 1 0 _sine osc 21 | 0.1 0.6 biscale gbuzz 22 | 23 | + 24 | 25 | 67 mtof 0.3 8 3 26 | 12 inv 1 0.75 _sine osc 27 | 0.1 0.6 biscale gbuzz 28 | 29 | + 30 | -8 ampdb * 400 buthp 31 | 0 77 1 clock 0.4 maytrig dup 32 | 1 _seq tseq mtof 0.003 port 33 | 0.5 50 pluck 3000 butlp 34 | 66 mtof 0.91 streson dcblk 35 | dup 0.4 0.1 1 0 _sine osc 0.002 0.008 biscale 0.2 vdelay + dcblk 36 | dup 0.7 77 bpm2dur 0.75 * delay 2000 butlp 0.5 * + 37 | -3 ampdb * + 38 | 26 mtof 0.2 saw 38.1 mtof 0.3 saw + 39 | 8 inv 1 0 _sine osc 500 2000 biscale 0.1 diode 40 | + -3 ampdb * 41 |
show code >>
42 |

Tables

43 |

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 |

Gbuzz Pads

49 |

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 |

Chorused Pluck

74 |

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 |

Subtractive Bass

109 |

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 |
125 | 126 | 127 | -------------------------------------------------------------------------------- /templates/tutorial/stack.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | The Stack 7 | 8 | 9 | 10 | 11 | 12 |
13 |

The Stack

14 |

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 | 36 |

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 |

69 |

70 |

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 |
93 | Previous: Hello, Sine | Back | Next: Stack Operations 94 |
95 | 96 | 97 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_abs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

abs

14 | 15 |

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 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_add.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

add

14 | 15 |

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 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_adsr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

ADSR

14 | 15 |

A gate-based ADSR envelope generator.

16 |

ADSR takes the following arguments: 17 |

27 |

28 |

Example

29 |

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 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_allpass.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Allpass

14 | 15 |

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 |

29 |

30 |

Example

31 |

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.

37 |
 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 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_ampdb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Ampdb

14 | 15 |

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 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_and.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

and

14 | 15 |

Performs logical "and" operation.

16 |
 # 7 & 4 = 4
17 |  7 4 & 
18 | 
19 | 
20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_atone.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Atone

14 | 15 |

Arguments: input, cutoff

16 |

Where 17 |

21 |

22 |

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 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_autowah.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Autowah

14 | 15 |

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 |

Parameters

19 | 26 |

27 |
 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 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_bal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Bal

14 | 15 |

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 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_biscale.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Biscale

14 | 15 |

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 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_bitcrush.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Bitcrush

14 | 15 |

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 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_blsaw.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

blsaw

14 | 15 |

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 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_blsquare.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

blsquare

14 | 15 |

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 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_bltriangle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

bltriangle

14 | 15 |

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 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_bpm2dur.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

bpm2dur

14 | 15 |

A function that efficiently converts bpm to duration (in seconds) with 16 | the equation
17 | 60/BPM

18 |

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 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_bpm2rate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

bpm2rate

14 | 15 |

A function that efficiently converts bpm to rate (in Hz) with 16 | the equation
17 | BPM/60

18 |

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 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_branch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

branch

14 | 15 |

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 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_brown.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Brown

14 | 15 |

Creates brownian noise.

16 |
 brown
17 | 
18 | 
19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_butbp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Butbp

14 | 15 |

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 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_butbr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Butbr

14 | 15 |

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 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_buthp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Buthp

14 | 15 |

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 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_butlp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Butlp

14 | 15 |

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 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_changed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Changed

14 | 15 |

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 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_clip.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

clip

14 | 15 |

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 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_clock.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

clock

14 | 15 |

Arguments: 16 |

22 |

23 |

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 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_comb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

comb

14 | 15 |

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 |

21 |

22 |
 # 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 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_count.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

count

14 | 15 |

A trigger-based counter.

16 |

It takes in the following arguments: 17 |

24 |

25 |

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 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_crossfade.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

crossfade

14 | 15 |

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 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_dcblk.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

dcblk

14 | 15 |

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 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_delay.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

delay

14 | 15 |

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 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_diode.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

diode

14 | 15 |

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 |

24 |

25 |

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 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_diskin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

diskin

14 | 15 |

Plays an audio file. Audio file should be: 16 |

22 |

23 |
 "foo.wav" diskin
24 | 
25 | 
26 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_dist.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

dist

14 | 15 |

Performs distortion using a hyperbolic tangent function. 16 | dist has the following arguments: 17 |

26 |

27 |

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 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_div.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

div

14 | 15 |

Divide two signals together.

16 |
 # 4 divided by 2 is 2!
17 |  4 2 /
18 | 
19 | 
20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_dmetro.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

dmetro

14 | 15 |

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 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_drip.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

drip

14 | 15 |

Physical model of a dripping water sound. 16 | This has the following arguments: 17 |

28 |

29 |

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 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_drop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

drop

14 | 15 |

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 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_dtrig.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

dtrig

14 | 15 |

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 |

28 |

29 |

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 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_dup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

dup

14 | 15 |

duplicates last item on the stack.

16 |
 # push 0 onto the stack
17 |  0 
18 |  # duplicate it
19 |  dup
20 | 
21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_dup2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

dup2

14 | 15 |

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 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_leftshift.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

leftshift

14 | 15 |

Binary left shift operation.

16 |
 # 8 << 1 = 16
17 |  8 1 << 
18 | 
19 | 
20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_mod.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

mod

14 | 15 |

Performs modulus operation.

16 |
 # 8 % 3 = 2
17 |  8 3 % 
18 | 
19 | 
20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_mul.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

mul

14 | 15 |

Multiply two signals together.

16 |
 # 2 times 3 is 6!
17 |  2 3 *
18 | 
19 | 
20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_or.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

or

14 | 15 |

Logical "or" operation.

16 |
 # 4 | 8 = 12
17 |  8 4 | 
18 | 
19 | 
20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_osc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

osc

14 | 15 |

osc is a table-lookup oscillator with linear interpolation.

16 |

It takes in the following arguments: 17 |

23 |

24 |

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 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_oscmorph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

oscmorph

14 | 15 |

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 |

29 |

30 |
 # 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 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_posc3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

posc3

14 | 15 |

posc3 is a table-lookup high precision oscillator with 16 | cubic interpolation.

17 |

It takes in the following arguments: 18 |

23 |

24 |

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 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_rightshift.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

rightshift

14 | 15 |

Binary right shift operation.

16 |
 # 8 >> 1 = 4
17 |  8 1 >> 
18 | 
19 | 
20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_sub.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

sub

14 | 15 |

Subtract two signals together.

16 |
 # 2 minus 3 is -1!
17 |  2 3 -
18 | 
19 | 
20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_switch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

switch

14 | 15 |

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 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_tabread.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

tabread

14 | 15 |

Table lookup with linear interpolation

16 |

tabread has the following arguments: 17 |

26 |

27 |

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 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_tblrec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

tblrec

14 | 15 |

Tblrec has the following arguments: 16 |

21 |

22 |

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 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_tenv.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

tenv

14 | 15 |

tenv is a trigger-based linear Attack/Hold/Release envelope generator. 16 | It takes the following arguments: 17 |

23 |

24 |

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 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_tget.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

tget

14 | 15 |

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 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_tone.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Tone

14 | 15 |

Arguments: input, cutoff

16 |

Where 17 |

21 |

22 |

Tone is a simple one-pole low pass filter. In the example below, 23 | white noise is being filtered by tone.

24 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_tseq.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

tseq

14 | 15 |

A sequencer that steps through values in a table.

16 |

tseq takes the following arguments: 17 |

22 |

23 |

Example

24 |

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 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_tset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

tset

14 | 15 |

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 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /templates/tutorial/ugens_xor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

xor

14 | 15 |

Logical "xor" operation.

16 |
 # 4 ^ 5 = 1
17 |  8 4 ^ 
18 | 
19 | 
20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/tutorial/variables.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Variables 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Variables

14 |

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 |
42 | Previous: F-Tables | Back | Next: P Registers 43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /templates/tutorial/what_is_sporth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | What is Sporth? 7 | 8 | 9 | 10 | 11 | 12 |
13 |

What is Sporth?

14 |

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 |
31 | Back | Next: About this Cookbook 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /templates/user.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | {{range .Patches}} 10 | 11 | 12 | 13 | 14 | 15 | {{end}} 16 |
PatchAuthorCreated
{{.Title}}{{.Author}}{{HumanDate .DateCreated}}
17 |
18 |
19 |
-------------------------------------------------------------------------------- /view.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "html/template" 6 | "net/http" 7 | "sort" 8 | "strings" 9 | "time" 10 | 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | type TemplateData struct { 15 | CurrentView string 16 | LoggedIn bool 17 | CanUpdatePatch bool 18 | UserName string 19 | StaticRoot string 20 | Headline string 21 | HeadlinePrefix string 22 | FinalJS template.JS 23 | Patch MasherPatch 24 | Patches []MasherPatch 25 | Referer string 26 | ViewingUser MasherUser 27 | Tutorial template.HTML 28 | } 29 | 30 | var masherTemplates *template.Template 31 | var tutorialTemplates *template.Template 32 | 33 | func InitTemplates() { 34 | var err error 35 | funcs := template.FuncMap{ 36 | "HumanDate": func(d int64) string { 37 | return time.Unix(d, 0).Format("2006/01/02") 38 | }, 39 | } 40 | masherTemplates, err = template.New("masher").Funcs(funcs).ParseFiles( 41 | "templates/layout.html", 42 | "templates/patch.html", 43 | "templates/user.html", 44 | "templates/browse.html", 45 | "templates/about.html", 46 | "templates/learn.html", 47 | "templates/404.html") 48 | if err != nil { 49 | panic(err) 50 | } 51 | tutorialTemplates, err = template.New("masher").Funcs(funcs).ParseGlob("templates/tutorial/*.html") 52 | if err != nil { 53 | panic(err) 54 | } 55 | } 56 | 57 | func BaseTemplateData(w http.ResponseWriter, r *http.Request) TemplateData { 58 | ret := TemplateData{ 59 | StaticRoot: MasherConfig.StaticRoot} 60 | session, err := SessionStore.Get(r, "sess") 61 | if err != nil { 62 | panic(err) 63 | } 64 | if ret.UserName, ret.LoggedIn = session.Values["name"].(string); !ret.LoggedIn { 65 | ret.UserName = "" 66 | } 67 | return ret 68 | } 69 | 70 | func ViewPatch(w http.ResponseWriter, r *http.Request) { 71 | var err error 72 | params := mux.Vars(r) 73 | data := BaseTemplateData(w, r) 74 | data.Patch, err = RetrievePatch(params["id"]) 75 | if err != nil { 76 | ViewNotFound(w, r) 77 | return 78 | } 79 | if data.Patch.Author == data.UserName { 80 | data.CanUpdatePatch = true 81 | } 82 | data.CurrentView = "Patch" 83 | data.Headline = data.Patch.Title + " by " + data.Patch.Author 84 | err = masherTemplates.ExecuteTemplate(w, "layout.html", data) 85 | if err != nil { 86 | panic(err) 87 | } 88 | } 89 | 90 | func ViewUser(w http.ResponseWriter, r *http.Request) { 91 | var err error 92 | params := mux.Vars(r) 93 | data := BaseTemplateData(w, r) 94 | data.ViewingUser, err = RetrieveUser(params["user"]) 95 | if err != nil { 96 | ViewNotFound(w, r) 97 | return 98 | } 99 | data.Patches, err = RetrievePatches(SearchFilter{Author: data.ViewingUser.Name}) 100 | if err != nil { 101 | ViewNotFound(w, r) 102 | return 103 | } 104 | sort.Slice(data.Patches, func(i, j int) bool { 105 | return data.Patches[i].DateCreated > data.Patches[j].DateCreated 106 | }) 107 | data.HeadlinePrefix = "Viewing: " 108 | data.Headline = "patches by " + data.ViewingUser.Name 109 | data.CurrentView = "User" 110 | err = masherTemplates.ExecuteTemplate(w, "layout.html", data) 111 | if err != nil { 112 | panic(err) 113 | } 114 | } 115 | 116 | func ViewContinue(w http.ResponseWriter, r *http.Request) { 117 | data := BaseTemplateData(w, r) 118 | data.CurrentView = "Patch" 119 | data.HeadlinePrefix = "Editing: " 120 | data.Headline = "new patch" 121 | data.Patch.Files = map[string]string{"main.sp": "# You have just one Sporth. Make something.\n\n"} 122 | data.FinalJS = " restoreAutosave(); " 123 | err := masherTemplates.ExecuteTemplate(w, "layout.html", data) 124 | if err != nil { 125 | panic(err) 126 | } 127 | } 128 | 129 | func ViewNew(w http.ResponseWriter, r *http.Request) { 130 | data := BaseTemplateData(w, r) 131 | data.CurrentView = "Patch" 132 | data.HeadlinePrefix = "Editing: " 133 | data.Headline = "new patch" 134 | data.Patch.Files = map[string]string{"main.sp": "# You have just one Sporth. Make something.\n\n"} 135 | err := masherTemplates.ExecuteTemplate(w, "layout.html", data) 136 | if err != nil { 137 | panic(err) 138 | } 139 | } 140 | 141 | func ViewBrowse(w http.ResponseWriter, r *http.Request) { 142 | var err error 143 | data := BaseTemplateData(w, r) 144 | data.Patches, err = RetrievePatches(SearchFilter{}) 145 | if err != nil { 146 | ViewNotFound(w, r) 147 | return 148 | } 149 | sort.Slice(data.Patches, func(i, j int) bool { 150 | return data.Patches[i].DateCreated > data.Patches[j].DateCreated 151 | }) 152 | data.HeadlinePrefix = "Viewing: " 153 | data.Headline = "all patches" 154 | data.CurrentView = "Browse" 155 | err = masherTemplates.ExecuteTemplate(w, "layout.html", data) 156 | if err != nil { 157 | panic(err) 158 | } 159 | } 160 | 161 | func ViewAbout(w http.ResponseWriter, r *http.Request) { 162 | data := BaseTemplateData(w, r) 163 | data.Headline = "About" 164 | data.CurrentView = "About" 165 | err := masherTemplates.ExecuteTemplate(w, "layout.html", data) 166 | if err != nil { 167 | panic(err) 168 | } 169 | } 170 | 171 | func ViewLearn(w http.ResponseWriter, r *http.Request) { 172 | params := mux.Vars(r) 173 | tutPage := params["page"] 174 | if tutPage == "" { 175 | tutPage = "index" 176 | } 177 | tutPage = tutPage + ".html" 178 | tutorialBuf := &bytes.Buffer{} 179 | data := BaseTemplateData(w, r) 180 | 181 | data.CurrentView = "Learn" 182 | err := tutorialTemplates.ExecuteTemplate(tutorialBuf, tutPage, data) 183 | if err != nil { 184 | ViewNotFound(w, r) 185 | return 186 | } 187 | var title = strings.Split(strings.SplitAfter(tutorialBuf.String(), "")[1], "")[0] 188 | if title == "" { 189 | title = strings.Split(strings.SplitAfter(tutorialBuf.String(), "

")[1], "

")[0] 190 | } 191 | data.Headline = title 192 | data.Tutorial = template.HTML(strings.Split(strings.SplitAfter(tutorialBuf.String(), "")[1], "")[0]) 193 | data.Tutorial = "" + data.Tutorial + "" 194 | 195 | err = masherTemplates.ExecuteTemplate(w, "layout.html", data) 196 | if err != nil { 197 | panic(err) 198 | } 199 | } 200 | 201 | func ViewNotFound(w http.ResponseWriter, r *http.Request) { 202 | data := BaseTemplateData(w, r) 203 | data.Headline = "404 - Not Found" 204 | data.CurrentView = "404" 205 | w.WriteHeader(http.StatusNotFound) 206 | err := masherTemplates.ExecuteTemplate(w, "layout.html", data) 207 | if err != nil { 208 | panic(err) 209 | } 210 | } 211 | --------------------------------------------------------------------------------