Skip to content

Commit

Permalink
Merge pull request #1 from kardianos/main
Browse files Browse the repository at this point in the history
meltysynth: make main and tests more robust
  • Loading branch information
sinshu authored Jan 9, 2023
2 parents 3cf5ef4 + 3dcbf34 commit d1f8a11
Show file tree
Hide file tree
Showing 39 changed files with 225 additions and 316 deletions.
104 changes: 78 additions & 26 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package main

import (
"encoding/binary"
"flag"
"fmt"
"io"
"log"
"math"
"os"
"time"
Expand All @@ -10,21 +14,60 @@ import (
)

func main() {

simpleChord()
flourish()
err := run()
if err != nil {
log.Fatal(err)
}
}

func simpleChord() {
func run() error {
sf2Path := flag.String("sf2", "", "Sound font file location")
outPath := flag.String("o", "out.pcm", "file to write pcm file to")
midiPath := flag.String("midi", "", `midi file to synth, or "chord" for example`)
flag.Parse()

if len(*sf2Path) == 0 {
flag.PrintDefaults()
return fmt.Errorf("missing sf2 path")
}
if len(*outPath) == 0 {
flag.PrintDefaults()
return fmt.Errorf("missing output path")
}
if len(*midiPath) == 0 {
flag.PrintDefaults()
return fmt.Errorf("missing midi path")
}

// Load the SoundFont.
sf2, _ := os.Open("TimGM6mb.sf2")
soundFont, _ := meltysynth.NewSoundFont(sf2)
sf2, err := os.Open(*sf2Path)
if err != nil {
return err
}
soundFont, err := meltysynth.NewSoundFont(sf2)
sf2.Close()
if err != nil {
return err
}

switch *midiPath {
default:
err = midi(soundFont, *midiPath, *outPath)
case "chord":
err = simpleChord(soundFont, *outPath)
}
if err != nil {
return err
}
return nil
}

func simpleChord(soundFont *meltysynth.SoundFont, outputFile string) error {
// Create the synthesizer.
settings := meltysynth.NewSynthesizerSettings(44100)
synthesizer, _ := meltysynth.NewSynthesizer(soundFont, settings)
synthesizer, err := meltysynth.NewSynthesizer(soundFont, settings)
if err != nil {
return err
}

// Play some notes (middle C, E, G).
synthesizer.NoteOn(0, 60, 100)
Expand All @@ -39,24 +82,27 @@ func simpleChord() {
// Render the waveform.
synthesizer.Render(left, right)

writePcmInterleavedInt16(left, right, "simpleChord.pcm")
return writeFile(left, right, outputFile)
}

func flourish() {

// Load the SoundFont.
sf2, _ := os.Open("TimGM6mb.sf2")
soundFont, _ := meltysynth.NewSoundFont(sf2)
sf2.Close()

func midi(soundFont *meltysynth.SoundFont, midiFilePath string, outputFile string) error {
// Create the synthesizer.
settings := meltysynth.NewSynthesizerSettings(44100)
synthesizer, _ := meltysynth.NewSynthesizer(soundFont, settings)
synthesizer, err := meltysynth.NewSynthesizer(soundFont, settings)
if err != nil {
return err
}

// Load the MIDI file.
mid, _ := os.Open("C:\\Windows\\Media\\flourish.mid")
midiFile, _ := meltysynth.NewMidiFile(mid)
mid, err := os.Open(midiFilePath)
if err != nil {
return err
}
midiFile, err := meltysynth.NewMidiFile(mid)
mid.Close()
if err != nil {
return err
}

// Create the MIDI sequencer.
sequencer := meltysynth.NewMidiFileSequencer(synthesizer)
Expand All @@ -70,14 +116,22 @@ func flourish() {
// Render the waveform.
sequencer.Render(left, right)

writePcmInterleavedInt16(left, right, "flourish.pcm")
return writeFile(left, right, outputFile)
}

func writePcmInterleavedInt16(left []float32, right []float32, path string) {
func writeFile(left []float32, right []float32, filename string) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()

length := len(left)
return writePCMInterleavedInt16(left, right, f)
}

max := 0.0
func writePCMInterleavedInt16(left []float32, right []float32, pcm io.Writer) error {
length := len(left)
var max float64

for i := 0; i < length; i++ {
absLeft := math.Abs(float64(left[i]))
Expand All @@ -99,7 +153,5 @@ func writePcmInterleavedInt16(left []float32, right []float32, path string) {
data[2*i+1] = int16(a * right[i])
}

pcm, _ := os.Create(path)
binary.Write(pcm, binary.LittleEndian, data)
pcm.Close()
return binary.Write(pcm, binary.LittleEndian, data)
}
2 changes: 0 additions & 2 deletions meltysynth/array_math.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package meltysynth

func arrayMultiplyAdd(a float32, x []float32, dst []float32) {

dstLen := len(dst)
for i := 0; i < dstLen; i++ {
dst[i] += a * x[i]
}
}

func arrayMultiplyAddSlope(a float32, step float32, x []float32, dst []float32) {

dstLen := len(dst)
for i := 0; i < dstLen; i++ {
dst[i] += a * x[i]
Expand Down
5 changes: 1 addition & 4 deletions meltysynth/binaryreader_ex.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
)

func readFourCC(r io.Reader) (string, error) {

var data [4]byte
n, err := r.Read(data[:])
if err != nil {
Expand All @@ -28,7 +27,6 @@ func readFourCC(r io.Reader) (string, error) {
}

func readFixedLengthString(r io.Reader, length int32) (string, error) {

var data []byte = make([]byte, length)
n, err := r.Read(data[:])
if err != nil {
Expand All @@ -49,8 +47,7 @@ func readFixedLengthString(r io.Reader, length int32) (string, error) {
}

func readIntVariableLength(r io.Reader) (int32, error) {

acc := int32(0)
var acc int32
count := 0
for {
var value byte
Expand Down
45 changes: 18 additions & 27 deletions meltysynth/biquad_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,42 +34,35 @@ func (bf *biQuadFilter) clearBuffer() {
}

func (bf *biQuadFilter) setLowPassFilter(cutoffFrequency float32, resonance float32) {
if cutoffFrequency >= 0.499*float32(bf.synthesizer.SampleRate) {
bf.active = false
return
}
bf.active = true

if cutoffFrequency < 0.499*float32(bf.synthesizer.SampleRate) {

bf.active = true

// This equation gives the Q value which makes the desired resonance peak.
// The error of the resultant peak height is less than 3%.
q := resonance - resonancePeakOffset/(1+6*(resonance-1))

w := 2 * math.Pi * float64(cutoffFrequency) / float64(bf.synthesizer.SampleRate)
cosw := math.Cos(w)
alpha := math.Sin(w) / float64(2*q)

b0 := (1 - cosw) / 2
b1 := 1 - cosw
b2 := (1 - cosw) / 2
a0 := 1 + alpha
a1 := -2 * cosw
a2 := 1 - alpha
// This equation gives the Q value which makes the desired resonance peak.
// The error of the resultant peak height is less than 3%.
q := resonance - resonancePeakOffset/(1+6*(resonance-1))

bf.setCoefficients(float32(a0), float32(a1), float32(a2), float32(b0), float32(b1), float32(b2))
w := 2 * math.Pi * float64(cutoffFrequency) / float64(bf.synthesizer.SampleRate)
cosw := math.Cos(w)
alpha := math.Sin(w) / float64(2*q)

} else {
b0 := (1 - cosw) / 2
b1 := 1 - cosw
b2 := (1 - cosw) / 2
a0 := 1 + alpha
a1 := -2 * cosw
a2 := 1 - alpha

bf.active = false
}
bf.setCoefficients(float32(a0), float32(a1), float32(a2), float32(b0), float32(b1), float32(b2))
}

func (bf *biQuadFilter) process(block []float32) {

blockLength := len(block)

if bf.active {

for t := 0; t < blockLength; t++ {

input := block[t]
output := bf.a0*input + bf.a1*bf.x1 + bf.a2*bf.x2 - bf.a3*bf.y1 - bf.a4*bf.y2

Expand All @@ -80,9 +73,7 @@ func (bf *biQuadFilter) process(block []float32) {

block[t] = output
}

} else {

bf.x2 = block[blockLength-2]
bf.x1 = block[blockLength-1]
bf.y2 = bf.x2
Expand Down
4 changes: 0 additions & 4 deletions meltysynth/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ type channel struct {
}

func newChannel(s *Synthesizer, isPercussionChannel bool) *channel {

result := new(channel)

result.synthesizer = s
Expand All @@ -37,7 +36,6 @@ func newChannel(s *Synthesizer, isPercussionChannel bool) *channel {
}

func (ch *channel) reset() {

if ch.isPercussionChannel {
ch.bankNumber = 128
} else {
Expand All @@ -64,7 +62,6 @@ func (ch *channel) reset() {
}

func (ch *channel) resetAllControllers() {

ch.modulation = 0
ch.expression = 127 << 7
ch.holdPedal = false
Expand All @@ -75,7 +72,6 @@ func (ch *channel) resetAllControllers() {
}

func (ch *channel) setBank(value int32) {

ch.bankNumber = value

if ch.isPercussionChannel {
Expand Down
3 changes: 0 additions & 3 deletions meltysynth/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ type generator struct {
}

func readGeneratorsFromChunk(r io.Reader, size int32) ([]generator, error) {

var n int
var err error

Expand All @@ -21,11 +20,9 @@ func readGeneratorsFromChunk(r io.Reader, size int32) ([]generator, error) {
}

count := size/4 - 1

generators := make([]generator, count)

for i := int32(0); i < count; i++ {

var gen generator

var generatorType uint16
Expand Down
2 changes: 0 additions & 2 deletions meltysynth/instrument.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@ type Instrument struct {
}

func createInstrument(info *instrumentInfo, zones []*zone, samples []*SampleHeader) (*Instrument, error) {

var err error

result := new(Instrument)

result.Name = info.name

zoneCount := info.zoneEndIndex - info.zoneStartIndex + 1
Expand Down
3 changes: 0 additions & 3 deletions meltysynth/instrument_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,16 @@ type instrumentInfo struct {
}

func readInstrumentsFromChunk(r io.Reader, size int32) ([]*instrumentInfo, error) {

var err error

if size%22 != 0 {
return nil, errors.New("the instrument list is invalid")
}

count := size / 22

instruments := make([]*instrumentInfo, count)

for i := int32(0); i < count; i++ {

instrument := new(instrumentInfo)

instrument.name, err = readFixedLengthString(r, 20)
Expand Down
5 changes: 0 additions & 5 deletions meltysynth/instrument_region.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ type InstrumentRegion struct {
}

func createInstrumentRegion(inst *Instrument, global *zone, local *zone, samples []*SampleHeader) (*InstrumentRegion, error) {

result := new(InstrumentRegion)

result.gs[gen_InitialFilterCutoffFrequency] = 13500
Expand Down Expand Up @@ -52,7 +51,6 @@ func createInstrumentRegion(inst *Instrument, global *zone, local *zone, samples
}

func createInstrumentRegions(inst *Instrument, zones []*zone, samples []*SampleHeader) ([]*InstrumentRegion, error) {

var err error

// Is the first one the global zone?
Expand All @@ -71,9 +69,7 @@ func createInstrumentRegions(inst *Instrument, zones []*zone, samples []*SampleH
}
}
return regions, nil

} else {

// No global zone.
count := len(zones)
regions := make([]*InstrumentRegion, count)
Expand All @@ -88,7 +84,6 @@ func createInstrumentRegions(inst *Instrument, zones []*zone, samples []*SampleH
}

func (region *InstrumentRegion) setParameter(param generator) {

index := param.generatorType

// Unknown generators should be ignored.
Expand Down
Loading

0 comments on commit d1f8a11

Please sign in to comment.