feat: add multithreaded rendering to the tracker side

The compiled player does not support multithreading, but with this,
users can already start composing songs with slightly less powerful
machines, even when targeting high-end machines.

Related to #199
This commit is contained in:
5684185+vsariola@users.noreply.github.com
2025-10-31 19:40:02 +02:00
parent c583156d1b
commit 9b9dc3548f
16 changed files with 500 additions and 62 deletions

View File

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"math"
"math/bits"
"sort"
"strconv"
@ -19,8 +20,12 @@ type (
Name string `yaml:",omitempty"`
Comment string `yaml:",omitempty"`
NumVoices int
Units []Unit
Mute bool `yaml:",omitempty"` // Mute is only used in the tracker for soloing/muting instruments; the compiled player ignores this field
// ThreadMaskM1 is a bit mask of which threads are used, minus 1. Minus
// 1 is done so that the default value 0 means bit mask 0b0001 i.e. only
// thread 1 is rendering the instrument.
ThreadMaskM1 int `yaml:",omitempty"`
Units []Unit
}
// Unit is e.g. a filter, oscillator, envelope and its parameters
@ -347,13 +352,14 @@ func init() {
// Copy makes a deep copy of a unit.
func (u *Unit) Copy() Unit {
parameters := make(map[string]int)
ret := *u
ret.Parameters = make(map[string]int, len(u.Parameters))
for k, v := range u.Parameters {
parameters[k] = v
ret.Parameters[k] = v
}
varArgs := make([]int, len(u.VarArgs))
copy(varArgs, u.VarArgs)
return Unit{Type: u.Type, Parameters: parameters, VarArgs: varArgs, ID: u.ID, Disabled: u.Disabled, Comment: u.Comment}
ret.VarArgs = make([]int, len(u.VarArgs))
copy(ret.VarArgs, u.VarArgs)
return ret
}
var stackUseSource = [2]StackUse{
@ -473,11 +479,12 @@ func (u *Unit) StackNeed() int {
// Copy makes a deep copy of an Instrument
func (instr *Instrument) Copy() Instrument {
units := make([]Unit, len(instr.Units))
ret := *instr
ret.Units = make([]Unit, len(instr.Units))
for i, u := range instr.Units {
units[i] = u.Copy()
ret.Units[i] = u.Copy()
}
return Instrument{Name: instr.Name, Comment: instr.Comment, NumVoices: instr.NumVoices, Units: units, Mute: instr.Mute}
return ret
}
// Implement the counter interface
@ -536,6 +543,16 @@ func (p Patch) NumSyncs() int {
return total
}
func (p Patch) NumThreads() int {
numThreads := 1
for _, instr := range p {
if l := bits.Len((uint)(instr.ThreadMaskM1 + 1)); l > numThreads {
numThreads = l
}
}
return numThreads
}
// FirstVoiceForInstrument returns the index of the first voice of given
// instrument. For example, if the Patch has three instruments (0, 1 and 2),
// with 1, 3, 2 voices, respectively, then FirstVoiceForInstrument(0) returns 0,