diff options
Diffstat (limited to 'autoutils')
-rw-r--r-- | autoutils/audio.go | 83 | ||||
-rw-r--r-- | autoutils/batches.go | 56 | ||||
-rw-r--r-- | autoutils/colorconversion.go | 57 | ||||
-rw-r--r-- | autoutils/functiongenerator.go | 227 |
4 files changed, 423 insertions, 0 deletions
diff --git a/autoutils/audio.go b/autoutils/audio.go new file mode 100644 index 0000000..19ae5f3 --- /dev/null +++ b/autoutils/audio.go @@ -0,0 +1,83 @@ +/* +Copyright (C) 2019 Leo Tenenbaum + +This file is part of AutoArt. + +AutoArt is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +AutoArt is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with AutoArt. If not, see <https://www.gnu.org/licenses/>. +*/ + +package autoutils + +import ( + "io" + "encoding/binary" +) + +// Write a header to writer. You need to decide ahead of time how many samples +// you want. +func WriteAudioHeader(writer io.Writer, nSamples int64, channels, sampleRate int32) error { + w := func (data interface{}) error { + return binary.Write(writer, binary.LittleEndian, data) + } + var err error + if err = w([]byte("RIFF")); err != nil { return err } + var chunkSize1 uint32 = 36 + uint32(nSamples) + if err = w(chunkSize1); err != nil { return err } + if err = w([]byte("WAVEfmt ")); err != nil { return err } + var subchunk1size uint32 = 16 + if err = w(subchunk1size); err != nil { return err } + var audioFormat uint16 = 1 + if err = w(audioFormat); err != nil { return err } + var nChannels uint16 = uint16(channels) + if err = w(nChannels); err != nil { return err } + var srate uint32 = uint32(sampleRate) + if err = w(srate); err != nil { return err } + var byteRate uint32 = srate * uint32(nChannels) + if err = w(byteRate); err != nil { return err } + var blockAlign uint16 = nChannels + if err = w(blockAlign); err != nil { return err } + var bitsPerSample uint16 = 8 + if err = w(bitsPerSample); err != nil { return err } + if err = w([]byte("data")); err != nil { return err } + var chunkSize2 uint32 = uint32(nSamples) * uint32(nChannels) + if err = w(chunkSize2); err != nil { return err } + return nil +} + + +// Writes some samples to the writer. You will need to write a header before +// any samples. +func WriteAudioSamples(writer io.Writer, samples []uint8) error { + return binary.Write(writer, binary.LittleEndian, samples) +} + +/* +Writes audio data in WAV format to the writer. There is only support for 8-bit +audio. If there are multiple channels, audio[0] should refer to the first sample +for the first channel, audio[1] should refer to the first sample for the second +channel, etc. +WAV does not support more then 65535 channels (but what are you doing if +you're using that many?!) +*/ +func WriteAudio(writer io.Writer, audio []uint8, channels, sampleRate int32) error { + + err := WriteAudioHeader(writer, int64(len(audio)), channels, sampleRate) + if err != nil { + return err + } + if err = WriteAudioSamples(writer, audio); err != nil { + return err + } + return nil +} diff --git a/autoutils/batches.go b/autoutils/batches.go new file mode 100644 index 0000000..fb39fc1 --- /dev/null +++ b/autoutils/batches.go @@ -0,0 +1,56 @@ +/* +Copyright (C) 2019 Leo Tenenbaum + +This file is part of AutoArt. + +AutoArt is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +AutoArt is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with AutoArt. If not, see <https://www.gnu.org/licenses/>. +*/ + +package autoutils + +import ( + "fmt" +) + +/* +This function runs f in batches of batchSize. n will be the number in the +sequence. f should send an error to errs if it wants this function to return +that error, and otherwise should send nil when it is done. Before each batch, +a message will be printed, starting with progress, and showing how many batches +have been completed so far out of the total number of batches. +*/ +const batchSize = 32 +func RunInBatches(number int64, progress string, f func (n int64, errs chan<- error)) error { + nBatches := number / batchSize + errs := make(chan error) + for batch := int64(0); batch <= nBatches; batch++ { + fmt.Println(progress, batch+1, "/", nBatches+1) + thisBatchSize := batchSize + if batch == nBatches { + // Deal with case of last batch + thisBatchSize = int(number - nBatches * batchSize) + } + for task := 0; task < thisBatchSize; task++ { + go f(int64(task) + batchSize * batch, errs) + } + + for completed := 0; completed < thisBatchSize; completed++ { + err := <-errs + if err != nil { + return err + } + } + } + return nil +}
\ No newline at end of file diff --git a/autoutils/colorconversion.go b/autoutils/colorconversion.go new file mode 100644 index 0000000..af21668 --- /dev/null +++ b/autoutils/colorconversion.go @@ -0,0 +1,57 @@ +/* +Copyright (C) 2019 Leo Tenenbaum + +This file is part of AutoArt. + +AutoArt is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +AutoArt is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with AutoArt. If not, see <https://www.gnu.org/licenses/>. +*/ + +package autoutils +import ( + "math" +) + +func HSVToRGB(h uint8, s uint8, v uint8) (uint8, uint8, uint8) { + // https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB + V := float64(v) / 256 + S := float64(s) / 256 + C := V * S + H := float64(h) / (256/6) + X := float64(C) * (1 - math.Abs(math.Mod(H, 2) - 1)) + var r, g, b float64 + switch true { + case s == 0: + r, g, b = 0, 0, 0 + case H <= 1: + r, g, b = C, X, 0 + case H <= 2: + r, g, b = X, C, 0 + case H <= 3: + r, g, b = 0, C, X + case H <= 4: + r, g, b = 0, X, C + case H <= 5: + r, g, b = X, 0, C + default: + r, g, b = C, 0, X + } + m := V - C + r += m + g += m + b += m + r *= 255 + g *= 255 + b *= 255 + return uint8(r), uint8(g), uint8(b) +} diff --git a/autoutils/functiongenerator.go b/autoutils/functiongenerator.go new file mode 100644 index 0000000..7acb680 --- /dev/null +++ b/autoutils/functiongenerator.go @@ -0,0 +1,227 @@ +/* +Copyright (C) 2019 Leo Tenenbaum + +This file is part of AutoArt. + +AutoArt is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +AutoArt is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with AutoArt. If not, see <https://www.gnu.org/licenses/>. +*/ + +package autoutils + +import ( + "math" + "math/rand" + "fmt" +) + +// Operators +const ( + CONST = iota + ADD + SUB + MUL + DIV + MIN + MAX + SQRT + SIN + COS + TAN + LOG + EXP + OPERATOR_COUNT +) +const FIRST_BINARY = ADD +const FIRST_UNARY = SQRT +const BINARY_COUNT = FIRST_UNARY - 1 // -1 for CONST +const UNARY_COUNT = OPERATOR_COUNT - FIRST_UNARY +const FIRST_VAR = OPERATOR_COUNT + +type Operator struct { + op int // Operator number. If operator is a variable, v, it is equal to FIRST_VAR + v + constant float64 // Constant (if op = CONST) +} + +type Function struct { + nvars int + operators []Operator +} + +// Generate a random function f with the given length (i.e. len(f.operators)) +// and the given number of variables +func (f *Function) Generate(nvars int, length int) { + f.nvars = nvars + f.operators = make([]Operator, length) + nsOnStack := 0 + i := 0 + for nsOnStack + i < length { + var operator Operator + var optype int + if nsOnStack == 0 { + // Pick a random variable + optype = 0 + } else if nsOnStack == 1 { + // Pick a constant/variable/unary operator + optype = rand.Intn(3) + } else { + // Pick a constant/variable/unary/binary operator + optype = rand.Intn(4) + } + switch optype { + case 0: + // variable + operator.op = FIRST_VAR + rand.Intn(nvars) + nsOnStack++ + case 1: + // Constant + operator.op = CONST + operator.constant = rand.Float64() + nsOnStack++ + case 2: + // unary + operator.op = rand.Intn(UNARY_COUNT) + FIRST_UNARY + case 3: + // binary + operator.op = rand.Intn(BINARY_COUNT) + FIRST_BINARY + nsOnStack-- + } + f.operators[i] = operator + i++ + } + + if nsOnStack + i == length { + // Add a unary operator + f.operators[i].op = rand.Intn(UNARY_COUNT) + FIRST_UNARY + i++ + } + + // Keep adding binary operators until nsOnStack == 1 + for nsOnStack > 1 { + f.operators[i].op = rand.Intn(BINARY_COUNT) + FIRST_BINARY + nsOnStack-- + i++ + } + +} + +func (f *Function) Evaluate(vars []float64) float64 { + var stack []float64 + for _, op := range f.operators { + l := len(stack) + switch (op.op) { + case CONST: + stack = append(stack, op.constant) + case ADD: + stack[l-2] += stack[l-1] + stack = stack[:l-1] + case SUB: + stack[l-2] -= stack[l-1] + stack = stack[:l-1] + case MUL: + stack[l-2] *= stack[l-1] + stack = stack[:l-1] + case DIV: + if stack[l-1] == 0 { // Check for division by 0 + stack[l-1] = 0.01 + } + stack[l-2] /= stack[l-1] + stack = stack[:l-1] + case MIN: + stack[l-2] = math.Min(stack[l-2], stack[l-1]) + stack = stack[:l-1] + case MAX: + stack[l-2] = math.Max(stack[l-2], stack[l-1]) + stack = stack[:l-1] + case SQRT: + stack[l-1] = math.Sqrt(math.Abs(stack[l-1])) + case SIN: + stack[l-1] = math.Sin(stack[l-1]) + case COS: + stack[l-1] = math.Cos(stack[l-1]) + case TAN: + stack[l-1] = math.Tan(stack[l-1]) + case LOG: + stack[l-1] = math.Log(math.Abs(stack[l-1])) + case EXP: + stack[l-1] = math.Exp(stack[l-1]) + default: + stack = append(stack, vars[op.op - FIRST_VAR]) + } + } + return stack[0] +} + +func (f *Function) String() string { + var str string + for _, op := range f.operators { + switch (op.op) { + case CONST: + str += fmt.Sprintf("%v",op.constant) + case ADD: + str += "+" + case SUB: + str += "-" + case MUL: + str += "*" + case DIV: + str += "/" + case SQRT: + str += "sqrt" + case SIN: + str += "sin" + case COS: + str += "cos" + case TAN: + str += "tan" + default: + str += fmt.Sprintf("v%v", op.op - FIRST_VAR) + } + str += " " + } + return str +} + +const mutationRate = 0.01 + +func (f *Function) Mutate() { + for i, op := range f.operators { + if op.op == CONST && rand.Float64() < mutationRate { + f.operators[i].constant += rand.NormFloat64() / 5 // Nudge constant + } + } +} + +func (f *Function) Breed(f1* Function, f2 *Function) { + // f(x) = (f1(x) + f2(x)) / 2 + f.operators = make([]Operator, len(f1.operators) + len(f2.operators) + 3) + for i, o := range f1.operators { + f.operators[i] = o + } + for i, o := range f2.operators { + f.operators[i + len(f1.operators)] = o + } + i := len(f1.operators) + len(f2.operators) + f.operators[i].op = ADD + f.operators[i+1].op = CONST + f.operators[i+1].constant = 2 + f.operators[i+2].op = DIV +} + +func (f *Function) CopyFrom(other *Function) { + f.nvars = other.nvars + f.operators = make([]Operator, len(other.operators)) + for i, op := range other.operators { + f.operators[i] = op + } +} |