diff --git a/bridge/bridge.go b/bridge/bridge.go index fbf5249..b30f1ee 100644 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -82,25 +82,55 @@ func (o Opcode) Mono() Opcode { } // Render tries to fill the buffer with samples rendered by Sointu. +// Use this version if you are not interested in time modulation. Will always +// fill the buffer. // Parameters: // buffer float32 slice to fill with rendered samples. Stereo signal, so // should have even length. -// Returns a tuple (int, bool, error), consisting of the number samples -// rendered (len(buffer)/2 in the case where buffer was filled, less or equal -// if row end was reached before buffer was full), and bool indicating if row -// has ended -func (s *SynthState) Render(buffer []float32) (int, bool, error) { +// Returns an error if something went wrong. +func (s *SynthState) Render(buffer []float32) error { if len(buffer)%1 == 1 { - return -1, false, errors.New("Render writes stereo signals, so buffer should have even length") + return errors.New("Render writes stereo signals, so buffer should have even length") } maxSamples := len(buffer) / 2 + cs := (*C.SynthState)(s) + cs.SamplesPerRow = C.uint(math.MaxInt32) + cs.RowTick = 0 + C.su_render_samples((*C.SynthState)(s), C.int(maxSamples), (*C.float)(&buffer[0])) + return nil +} + +// RenderTime 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 is not the +// 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 (s *SynthState) RenderTime(buffer []float32, maxtime int) (int, int, error) { + if len(buffer)%1 == 1 { + return -1, -1, errors.New("Render writes stereo signals, so buffer should have even length") + } + maxSamples := len(buffer) / 2 + cs := (*C.SynthState)(s) + cs.SamplesPerRow = C.uint(maxtime) // these two lines are here just because the C-API is not + cs.RowTick = 0 // updated. SamplesPerRow should be "maxtime" and passed as a parameter retval := int(C.su_render_samples((*C.SynthState)(s), C.int(maxSamples), (*C.float)(&buffer[0]))) - if retval < 0 { - return maxSamples, false, nil + if retval < 0 { // this ugliness is just because the C-API is not updated yet + return maxSamples, int(cs.RowTick), nil } else if retval == 0 { - return maxSamples, true, nil + return maxSamples, int(cs.RowTick), nil } else { - return maxSamples - retval, true, nil + return maxSamples - retval, int(cs.RowTick), nil } } @@ -159,10 +189,6 @@ func (s *SynthState) Release(voice int) { cs.Synth.Voices[voice].Release = 1 } -func (s *SynthState) SetSamplesPerRow(spr int) { - s.SamplesPerRow = C.uint(spr) -} - func NewSynthState() *SynthState { s := new(SynthState) s.RandSeed = 1 diff --git a/bridge/bridge_test.go b/bridge/bridge_test.go index 1cae482..216b1da 100644 --- a/bridge/bridge_test.go +++ b/bridge/bridge_test.go @@ -30,22 +30,15 @@ func TestBridge(t *testing.T) { }}, }) s.Trigger(0, 64) - s.SamplesPerRow = SAMPLES_PER_ROW * 8 // this song is two blocks of 8 rows, release before second block start buffer := make([]float32, 2*su_max_samples) - n, rowend, err := s.Render(buffer) - if n < su_max_samples/2 { - t.Fatalf("render should have filled half of the buffer on first call, %v samples rendered, %v expected", n, su_max_samples/2) - } - if rowend != true { - t.Fatalf("Row end should have been hit (rowend should have been true) on the first call to Render") + err := s.Render(buffer[:len(buffer)/2]) + if err != nil { + t.Fatalf("first render gave an error") } s.Release(0) - n, rowend, err = s.Render(buffer[(n * 2):]) - if n < su_max_samples/2 { - t.Fatalf("render should have filled second half of the buffer on the second call, %v samples rendered, %v expected", n, su_max_samples/2) - } - if rowend != true { - t.Fatalf("Row end should have been hit (rowend should have been true) on the second call to Render") + err = s.Render(buffer[len(buffer)/2:]) + if err != nil { + t.Fatalf("first render gave an error") } _, filename, _, _ := runtime.Caller(0) expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", "test_render_samples.raw")) diff --git a/song/song.go b/song/song.go index 1932ec3..67ffb1a 100644 --- a/song/song.go +++ b/song/song.go @@ -93,7 +93,6 @@ func (s *Song) Render() ([]float32, error) { } synth := bridge.NewSynthState() synth.SetPatch(s.Patch) - synth.SetSamplesPerRow(44100 * 60 / (s.BPM * 4)) curVoices := make([]int, len(s.Tracks)) for i := range curVoices { curVoices[i] = s.FirstTrackVoice(i) @@ -104,6 +103,7 @@ func (s *Song) Render() ([]float32, error) { } buffer := make([]float32, samples*2) totaln := 0 + rowtime := 44100 * 60 / (s.BPM * 4) for row := 0; row < s.TotalRows(); row++ { patternRow := row % s.PatternRows() pattern := row / s.PatternRows() @@ -123,8 +123,8 @@ func (s *Song) Render() ([]float32, error) { synth.Trigger(curVoices[t], note) } } - n, _, _ := synth.Render(buffer[2*totaln:]) - totaln += n + samples, _, _ := synth.RenderTime(buffer[2*totaln:], rowtime) + totaln += samples } return buffer, nil }