mirror of
				https://github.com/vsariola/sointu.git
				synced 2025-11-03 16:35:57 -05:00 
			
		
		
		
	The old "native" compiler bridged version is now started with cmd/sointu-nativetrack, while the new pure-Go bytecode implemented bytecode interpreter is started with cmd/sointu-track Thus, you do not need any of the CMake / cgo stuff to run cmd/sointu-track
		
			
				
	
	
		
			163 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package sointu
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"math"
 | 
						|
)
 | 
						|
 | 
						|
// Patch is simply a list of instruments used in a song
 | 
						|
type Patch []Instrument
 | 
						|
 | 
						|
func (p Patch) Copy() Patch {
 | 
						|
	instruments := make([]Instrument, len(p))
 | 
						|
	for i, instr := range p {
 | 
						|
		instruments[i] = instr.Copy()
 | 
						|
	}
 | 
						|
	return instruments
 | 
						|
}
 | 
						|
 | 
						|
func (p Patch) NumVoices() int {
 | 
						|
	ret := 0
 | 
						|
	for _, i := range p {
 | 
						|
		ret += i.NumVoices
 | 
						|
	}
 | 
						|
	return ret
 | 
						|
}
 | 
						|
 | 
						|
func (p Patch) NumDelayLines() int {
 | 
						|
	total := 0
 | 
						|
	for _, instr := range p {
 | 
						|
		for _, unit := range instr.Units {
 | 
						|
			if unit.Type == "delay" {
 | 
						|
				total += len(unit.VarArgs)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return total
 | 
						|
}
 | 
						|
 | 
						|
func (p Patch) FirstVoiceForInstrument(instrIndex int) int {
 | 
						|
	ret := 0
 | 
						|
	for _, t := range p[:instrIndex] {
 | 
						|
		ret += t.NumVoices
 | 
						|
	}
 | 
						|
	return ret
 | 
						|
}
 | 
						|
 | 
						|
func (p Patch) InstrumentForVoice(voice int) (int, error) {
 | 
						|
	if voice < 0 {
 | 
						|
		return 0, errors.New("voice cannot be negative")
 | 
						|
	}
 | 
						|
	for i, instr := range p {
 | 
						|
		if voice < instr.NumVoices {
 | 
						|
			return i, nil
 | 
						|
		}
 | 
						|
		voice -= instr.NumVoices
 | 
						|
	}
 | 
						|
	return 0, errors.New("voice number is beyond the total voices of an instrument")
 | 
						|
}
 | 
						|
 | 
						|
func (p Patch) FindSendTarget(id int) (int, int, error) {
 | 
						|
	if id == 0 {
 | 
						|
		return 0, 0, errors.New("send targets unit id 0")
 | 
						|
	}
 | 
						|
	for i, instr := range p {
 | 
						|
		for u, unit := range instr.Units {
 | 
						|
			if unit.ID == id {
 | 
						|
				return i, u, nil
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return 0, 0, fmt.Errorf("send targets an unit with id %v, could not find a unit with such an ID in the patch", id)
 | 
						|
}
 | 
						|
 | 
						|
func (p Patch) ParamHintString(instrIndex, unitIndex int, param string) string {
 | 
						|
	if instrIndex < 0 || instrIndex >= len(p) {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	instr := p[instrIndex]
 | 
						|
	if unitIndex < 0 || unitIndex >= len(instr.Units) {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	unit := instr.Units[unitIndex]
 | 
						|
	value := unit.Parameters[param]
 | 
						|
	switch unit.Type {
 | 
						|
	case "envelope":
 | 
						|
		switch param {
 | 
						|
		case "attack":
 | 
						|
			return engineeringTime(math.Pow(2, 24*float64(value)/128) / 44100)
 | 
						|
		case "decay":
 | 
						|
			return engineeringTime(math.Pow(2, 24*float64(value)/128) / 44100 * (1 - float64(unit.Parameters["sustain"])/128))
 | 
						|
		case "release":
 | 
						|
			return engineeringTime(math.Pow(2, 24*float64(value)/128) / 44100 * float64(unit.Parameters["sustain"]) / 128)
 | 
						|
		}
 | 
						|
	case "oscillator":
 | 
						|
		switch param {
 | 
						|
		case "type":
 | 
						|
			switch value {
 | 
						|
			case Sine:
 | 
						|
				return "Sine"
 | 
						|
			case Trisaw:
 | 
						|
				return "Trisaw"
 | 
						|
			case Pulse:
 | 
						|
				return "Pulse"
 | 
						|
			case Gate:
 | 
						|
				return "Gate"
 | 
						|
			case Sample:
 | 
						|
				return "Sample"
 | 
						|
			default:
 | 
						|
				return "Unknown"
 | 
						|
			}
 | 
						|
		case "transpose":
 | 
						|
			relvalue := value - 64
 | 
						|
			octaves := relvalue / 12
 | 
						|
			semitones := relvalue % 12
 | 
						|
			if octaves != 0 {
 | 
						|
				return fmt.Sprintf("%v oct, %v st", octaves, semitones)
 | 
						|
			}
 | 
						|
			return fmt.Sprintf("%v st", semitones)
 | 
						|
		case "detune":
 | 
						|
			return fmt.Sprintf("%v st", float32(value-64)/64.0)
 | 
						|
		}
 | 
						|
	case "compressor":
 | 
						|
		switch param {
 | 
						|
		case "attack":
 | 
						|
			fallthrough
 | 
						|
		case "release":
 | 
						|
			alpha := math.Pow(2, -24*float64(value)/128) // alpha is the "smoothing factor" of first order low pass iir
 | 
						|
			sec := -1 / (44100 * math.Log(1-alpha))      // from smoothing factor to time constant, https://en.wikipedia.org/wiki/Exponential_smoothing
 | 
						|
			return engineeringTime(sec)
 | 
						|
		case "ratio":
 | 
						|
			return fmt.Sprintf("1 : %.3f", 1-float64(value)/128)
 | 
						|
		}
 | 
						|
	case "send":
 | 
						|
		switch param {
 | 
						|
		case "voice":
 | 
						|
			if value == 0 {
 | 
						|
				return "auto"
 | 
						|
			}
 | 
						|
			return fmt.Sprintf("%v", value)
 | 
						|
		case "target":
 | 
						|
			instrIndex, unitIndex, err := p.FindSendTarget(unit.Parameters["target"])
 | 
						|
			if err != nil {
 | 
						|
				return "invalid target"
 | 
						|
			}
 | 
						|
			instr := p[instrIndex]
 | 
						|
			unit := instr.Units[unitIndex]
 | 
						|
			return fmt.Sprintf("%v / %v%v", instr.Name, unit.Type, unitIndex)
 | 
						|
		case "port":
 | 
						|
			instrIndex, unitIndex, err := p.FindSendTarget(unit.Parameters["target"])
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Sprintf("%v ???", value)
 | 
						|
			}
 | 
						|
			portList := Ports[p[instrIndex].Units[unitIndex].Type]
 | 
						|
			if value < 0 || value >= len(portList) {
 | 
						|
				return fmt.Sprintf("%v ???", value)
 | 
						|
			}
 | 
						|
			return fmt.Sprintf(portList[value])
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return ""
 | 
						|
}
 |