Skip to content

Commit

Permalink
add euclidean rythm
Browse files Browse the repository at this point in the history
  • Loading branch information
emicklei committed Sep 3, 2024
1 parent 5393ec9 commit 9822492
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 42 deletions.
104 changes: 104 additions & 0 deletions core/euclidean.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package core

import (
"bytes"
"fmt"
"strings"
"time"
)

type Euclidean struct {
Steps HasValue
Beats HasValue
Rotation HasValue
Playback HasValue
}

// Play is part of Playable
func (e *Euclidean) Play(ctx Context, at time.Time) error {
steps := getInt(e.Steps, false)
beats := getInt(e.Beats, false)
rotation := getInt(e.Rotation, false)
playback, ok := e.Playback.Value().(Sequenceable)
if !ok {
return fmt.Errorf("playback is not a sequenceable")
}
toggles := euclideanRhythm(steps, beats, rotation)
bpm := ctx.Control().BPM()
moment := at
dt := WholeNoteDuration(bpm) / time.Duration(steps)
for _, each := range toggles {
if each {
ctx.Device().Play(NoCondition, playback, bpm, moment)
}
moment = moment.Add(dt)
}
return nil
}

// Handle is part of TimelineEvent
func (e *Euclidean) Handle(tim *Timeline, when time.Time) {}

func (e *Euclidean) Storex() string {
var b bytes.Buffer
fmt.Fprintf(&b, "euclidean(%s,%s,%s,%s)", Storex(e.Steps), Storex(e.Beats), Storex(e.Rotation), Storex(e.Playback))
return b.String()
}

func (e *Euclidean) Evaluate(ctx Context) error {
// copied from Loop
e.Play(ctx, time.Now())
return nil
}

// Inspect is part of Inspectable
func (e *Euclidean) Inspect(i Inspection) {
steps := getInt(e.Steps, false)
beats := getInt(e.Beats, false)
rotation := getInt(e.Rotation, false)
toggles := euclideanRhythm(steps, beats, rotation)
b := new(strings.Builder)
for _, each := range toggles {
if each {
b.WriteString("!")
} else {
b.WriteString(".")
}
}
i.Properties["pattern"] = b.String()
i.Properties["steps"] = e.Steps
i.Properties["beats"] = e.Beats
i.Properties["rotation"] = e.Rotation
i.Properties["playback"] = e.Playback
}

// https://github.com/computermusicdesign/euclidean-rhythm/blob/master/max-example/euclidSimple.js
func euclideanRhythm(steps, pulses, rotation int) []bool {
rhythm := make([]bool, 0, steps)
bucket := 0

for i := 0; i < steps; i++ {
bucket += pulses
if bucket >= steps {
bucket -= steps
rhythm = append(rhythm, true)
} else {
rhythm = append(rhythm, false)
}
}

return rotatedRhythm(rhythm, rotation+1)
}

func rotatedRhythm(input []bool, rotate int) []bool {
output := make([]bool, len(input))
val := len(input) - rotate
for i := 0; i < len(input); i++ {
j := (i + val) % len(input)
if j < 0 {
j *= -1
}
output[i] = input[j]
}
return output
}
10 changes: 5 additions & 5 deletions dsl/eval_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/emicklei/melrose/control"
"github.com/emicklei/melrose/core"
"github.com/emicklei/melrose/midi"
"github.com/emicklei/melrose/mpg"

"github.com/emicklei/melrose/midi/file"

Expand Down Expand Up @@ -1279,13 +1278,14 @@ onkey('c4',onoff('e')) // uses default input and default output MIDI device`,
registerFunction(eval, "euclidean", Function{
Title: "Music Pattern Generator: Euclidean",
Description: "",
Template: "euclidean(${1:steps},${2:pulses},${3:rotation})",
Template: "euclidean(${1:steps},${2:beats},${3:rotation},${4:noteOrVariable})",
Samples: "",
Func: func(steps, pulses, rotation any) any {
return &mpg.Euclidean{
Func: func(steps, beats, rotation, noteOrVariable any) any {
return &core.Euclidean{
Steps: core.On(steps),
Pulses: core.On(pulses),
Beats: core.On(beats),
Rotation: core.On(rotation),
Playback: core.On(noteOrVariable),
}
},
})
Expand Down
9 changes: 9 additions & 0 deletions dsl/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,12 @@ func TestKeyOnChannelNote(t *testing.T) {
t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want)
}
}
func TestEuclidean(t *testing.T) {
e := newTestEvaluator()
r, err := e.EvaluateProgram(
`e = euclidean(12,4,0,note('c'))`)
checkError(t, err)
if got, want := r.(*core.Euclidean).Storex(), "euclidean(12,4,0,note('C'))"; got != want {
t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want)
}
}
21 changes: 0 additions & 21 deletions dsl/var_mpg.go

This file was deleted.

1 change: 0 additions & 1 deletion mpg/README.md

This file was deleted.

15 changes: 0 additions & 15 deletions mpg/euclidean.go

This file was deleted.

0 comments on commit 9822492

Please sign in to comment.