package bridge // #cgo CFLAGS: -I"${SRCDIR}/../build/" // #cgo LDFLAGS: "${SRCDIR}/../build/libsointu.a" // #include import "C" import ( "errors" "fmt" "strings" "github.com/vsariola/sointu" "github.com/vsariola/sointu/compiler" ) func Synth(patch sointu.Patch) (*C.Synth, error) { s := new(C.Synth) comPatch, err := compiler.Encode(&patch, compiler.AllFeatures{}) if err != nil { return nil, fmt.Errorf("error compiling patch: %v", err) } if len(comPatch.Commands) > 2048 { // TODO: 2048 could probably be pulled automatically from cgo return nil, errors.New("bridge supports at most 2048 commands; the compiled patch has more") } if len(comPatch.Values) > 16384 { // TODO: 16384 could probably be pulled automatically from cgo return nil, errors.New("bridge supports at most 16384 values; the compiled patch has more") } for i, v := range comPatch.Commands { s.Commands[i] = (C.uchar)(v) } for i, v := range comPatch.Values { s.Values[i] = (C.uchar)(v) } for i, v := range comPatch.DelayTimes { s.DelayTimes[i] = (C.ushort)(v) } for i, v := range comPatch.SampleOffsets { s.SampleOffsets[i].Start = (C.uint)(v.Start) s.SampleOffsets[i].LoopStart = (C.ushort)(v.LoopStart) s.SampleOffsets[i].LoopLength = (C.ushort)(v.LoopLength) } s.NumVoices = C.uint(comPatch.NumVoices) s.Polyphony = C.uint(comPatch.PolyphonyBitmask) s.RandSeed = 1 return s, nil } // Render renders until the buffer is full or the modulated time is reached, whichever // happens first. // Parameters: // buffer float32 slice to fill with rendered samples. Stereo signal, so // should have even length. // maxtime how long nominal time to render in samples. Speed unit might modulate time // so the actual number of samples rendered depends on the modulation and if // buffer is full before maxtime is reached. // Returns a tuple (int, int, error), consisting of: // samples number of samples rendered in the buffer // time how much the time advanced // error potential error // In practice, if nsamples = len(buffer)/2, then time <= maxtime. If maxtime was reached // first, then nsamples <= len(buffer)/2 and time >= maxtime. Note that it could happen that // time > maxtime, as it is modulated and the time could advance by 2 or more, so the loop // exit condition would fire when the time is already past maxtime. // Under no conditions, nsamples >= len(buffer)/2 i.e. guaranteed to never overwrite the buffer. func (synth *C.Synth) Render(buffer []float32, maxtime int) (int, int, error) { if len(buffer)%1 == 1 { return -1, -1, errors.New("RenderTime writes stereo signals, so buffer should have even length") } samples := C.int(len(buffer) / 2) time := C.int(maxtime) errcode := int(C.su_render(synth, (*C.float)(&buffer[0]), &samples, &time)) if errcode > 0 { return int(samples), int(time), &RenderError{errcode: errcode} } return int(samples), int(time), nil } // Trigger is part of C.Synths' implementation of sointu.Synth interface func (s *C.Synth) Trigger(voice int, note byte) { s.SynthWrk.Voices[voice] = C.Voice{} s.SynthWrk.Voices[voice].Note = C.int(note) } // Release is part of C.Synths' implementation of sointu.Synth interface func (s *C.Synth) Release(voice int) { s.SynthWrk.Voices[voice].Release = 1 } // Render error stores the exact errorcode, which is actually just the x87 FPU flags, // with only the critical failure flags masked. Useful if you are interested exactly // what went wrong with the patch. type RenderError struct { errcode int } func (e *RenderError) Error() string { var reasons []string if e.errcode&0x40 != 0 { reasons = append(reasons, "FPU stack over/underflow") } if e.errcode&0x04 != 0 { reasons = append(reasons, "FPU divide by zero") } if e.errcode&0x01 != 0 { reasons = append(reasons, "FPU invalid operation") } if e.errcode&0x3800 != 0 { reasons = append(reasons, "FPU stack push/pops are not balanced") } return "RenderError: " + strings.Join(reasons, ", ") }