├── tests
├── config.nims
├── test2.nim
├── dueling_banjos.nim
├── dueling_banjos_json.nim
└── test1.nim
├── .gitattributes
├── src
├── paramidi
│ ├── tsf.c
│ ├── constants.nim
│ ├── tsf.nim
│ └── tsf.h
└── paramidi.nim
├── examples
├── config.nims
├── README.md
├── examples.nimble
└── src
│ ├── dueling_banjos.nim
│ ├── common.nim
│ ├── undone_in_sorrow.nim
│ └── aeriths_theme.nim
├── paramidi.nimble
├── UNLICENSE
└── README.md
/tests/config.nims:
--------------------------------------------------------------------------------
1 | switch("path", "$projectDir/../src")
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | **/*.c linguist-vendored
2 | **/*.h linguist-vendored
3 |
--------------------------------------------------------------------------------
/src/paramidi/tsf.c:
--------------------------------------------------------------------------------
1 | #define TSF_IMPLEMENTATION
2 | #include "tsf.h"
3 |
--------------------------------------------------------------------------------
/examples/config.nims:
--------------------------------------------------------------------------------
1 | when defined(linux):
2 | switch("passL", "-ldl -lm -lpthread")
3 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | To run each example:
2 |
3 | ```
4 | nimble run dueling_banjos
5 | ```
6 |
7 | ```
8 | nimble run aeriths_theme
9 | ```
10 |
11 | ```
12 | nimble run undone_in_sorrow
13 | ```
14 |
--------------------------------------------------------------------------------
/paramidi.nimble:
--------------------------------------------------------------------------------
1 | # Package
2 |
3 | version = "0.7.0"
4 | author = "oakes"
5 | description = "A library for making MIDI music"
6 | license = "Public Domain"
7 | srcDir = "src"
8 |
9 |
10 |
11 | # Dependencies
12 |
13 | requires "nim >= 1.2.6"
14 |
--------------------------------------------------------------------------------
/examples/examples.nimble:
--------------------------------------------------------------------------------
1 | # Package
2 |
3 | version = "0.1.0"
4 | author = "oakes"
5 | description = "Paramidi examples"
6 | license = "Public Domain"
7 | srcDir = "src"
8 | bin = @["dueling_banjos", "aeriths_theme", "undone_in_sorrow"]
9 |
10 |
11 | # Dependencies
12 |
13 | requires "nim >= 1.2.6"
14 | requires "paramidi >= 0.7.0"
15 | requires "paramidi_soundfonts >= 0.2.0"
16 | requires "parasound >= 1.0.0"
17 |
--------------------------------------------------------------------------------
/tests/test2.nim:
--------------------------------------------------------------------------------
1 | #? replace(sub="d♭", by="cx") | replace(sub="e♭", by="dx") | replace(sub="g♭", by="fx") | replace(sub="a♭", by="gx") | replace(sub="b♭", by="ax")
2 |
3 | import unittest
4 | import paramidi
5 |
6 | test "sharps and flats":
7 | const
8 | score1 = compile((piano, 1/4, cx, dx, fx, gx, ax, cx+1, dx+1, fx+1, gx+1, ax+1))
9 | score2 = compile((piano, 1/4, d♭, e♭, g♭, a♭, b♭, d♭+1, e♭+1, g♭+1, a♭+1, b♭+1))
10 | check score1 == score2
11 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/tests/dueling_banjos.nim:
--------------------------------------------------------------------------------
1 | import paramidi
2 |
3 | const
4 | measure1 = (1/16, b, +c, 1/8, +d, b, +c, a, b, g, a)
5 | measure2 = (1/16, g, g, 1/8, g, a, b, +c, +d, +c, 1/2, b)
6 | measure3 = (1/16, {d, -b, -g}, {d, -b, -g},
7 | 1/8, {d, -b, -g}, {e, c, -g}, {d, -b, -g})
8 | measure4 = (1/16, r, r, 1/8, g, r, d, r, g, g, d)
9 | measure5 = (1/4, g, 1/8, a, b, 1/4, g, 1/8, a, d)
10 | measure6 = (1/4, g, 1/8, a, b, 1/4, g, 1/8, {f, -a}, b, 1/4, c)
11 | score* =
12 | ((tempo: 80, octave: 3),
13 | (guitar, measure1),
14 | (banjo, measure1),
15 | (guitar, measure1),
16 | (guitar, 1/2, d, 1/8, g, g, a, b, g, b, 1/2, a),
17 | (banjo, 1/8, g, g, a, b, 1/2, g),
18 | (guitar, (octave: 2), measure2),
19 | (banjo, measure2),
20 | (guitar, (octave: 2), measure2),
21 | (banjo, measure2),
22 | (guitar, measure2),
23 | (banjo, measure2),
24 | (octave: 4),
25 | (guitar, measure3),
26 | (banjo, measure3),
27 | (guitar, measure3),
28 | (banjo, measure3),
29 | (guitar, (octave: 2), measure1),
30 | (banjo, (octave: 3), measure1),
31 | (tempo: 120, octave: 3),
32 | ((mode: concurrent),
33 | (banjo, measure1),
34 | (guitar, measure4)),
35 | ((mode: concurrent),
36 | (banjo, measure1),
37 | (guitar, measure4)),
38 | ((mode: concurrent),
39 | (banjo, measure1),
40 | (guitar, measure5)),
41 | ((mode: concurrent),
42 | (banjo, measure1),
43 | (guitar, measure6)))
44 |
--------------------------------------------------------------------------------
/tests/dueling_banjos_json.nim:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | let
4 | measure1 = %*[1/16, "b", "c+", 1/8, "d+", "b", "c+", "a", "b", "g", "a"]
5 | measure2 = %*[1/16, "g", "g", 1/8, "g", "a", "b", "c+", "d+", "c+", 1/2, "b"]
6 | measure3 = %*[1/16, [{"mode": "concurrent"}, "d", "b-", "g-"], [{"mode": "concurrent"}, "d", "b-", "g-"],
7 | 1/8, [{"mode": "concurrent"}, "d", "b-", "g-"], [{"mode": "concurrent"}, "e", "c", "g-"],
8 | [{"mode": "concurrent"}, "d", "b-", "g-"]]
9 | measure4 = %*[1/16, "r", "r", 1/8, "g", "r", "d", "r", "g", "g", "d"]
10 | measure5 = %*[1/4, "g", 1/8, "a", "b", 1/4, "g", 1/8, "a", "d"]
11 | measure6 = %*[1/4, "g", 1/8, "a", "b", 1/4, "g", 1/8, [{"mode": "concurrent"}, "f", "a-"], "b", 1/4, "c"]
12 | score* =
13 | %*[{"tempo": 80, "octave": 3},
14 | ["guitar", measure1],
15 | ["banjo", measure1],
16 | ["guitar", measure1],
17 | ["guitar", 1/2, "d", 1/8, "g", "g", "a", "b", "g", "b", 1/2, "a"],
18 | ["banjo", 1/8, "g", "g", "a", "b", 1/2, "g"],
19 | ["guitar", {"octave": 2}, measure2],
20 | ["banjo", measure2],
21 | ["guitar", {"octave": 2}, measure2],
22 | ["banjo", measure2],
23 | ["guitar", measure2],
24 | ["banjo", measure2],
25 | {"octave": 4},
26 | ["guitar", measure3],
27 | ["banjo", measure3],
28 | ["guitar", measure3],
29 | ["banjo", measure3],
30 | ["guitar", {"octave": 2}, measure1],
31 | ["banjo", {"octave": 3}, measure1],
32 | {"tempo": 120, "octave": 3},
33 | [{"mode": "concurrent"},
34 | ["banjo", measure1],
35 | ["guitar", measure4]],
36 | [{"mode": "concurrent"},
37 | ["banjo", measure1],
38 | ["guitar", measure4]],
39 | [{"mode": "concurrent"},
40 | ["banjo", measure1],
41 | ["guitar", measure5]],
42 | [{"mode": "concurrent"},
43 | ["banjo", measure1],
44 | ["guitar", measure6]]]
45 |
--------------------------------------------------------------------------------
/tests/test1.nim:
--------------------------------------------------------------------------------
1 | import unittest
2 | import paramidi, paramidi/tsf
3 | from os import nil
4 |
5 | test "load soundfont":
6 | const sfPath = "../paramidi_soundfonts/src/paramidi_soundfonts/aspirin.sf2"
7 | static: assert os.fileExists(sfPath)
8 | var sf = tsf_load_filename(sfPath)
9 | tsf_set_output(sf, TSF_MONO, 44100, 0) #sample rate
10 | tsf_note_on(sf, 0, 60, 1.0f) #preset 0, middle C
11 | var halfSecond: array[22050, cshort] # synthesize 0.5 seconds
12 | tsf_render_short(sf, cast[ptr cshort](halfSecond.addr), halfSecond.len.cint, 0)
13 |
14 | from algorithm import nil
15 |
16 | proc sortEvents(events: var seq[Event]) =
17 | # since the order of notes in a chord can be unpredictable,
18 | # the only way we can compare the outputs below is by
19 | # sorting them so the result is deterministic
20 | algorithm.sort(events, proc (x, y: Event): int =
21 | if x.time < y.time:
22 | -1
23 | elif x.time > y.time:
24 | 1
25 | elif x.note.ord < y.note.ord:
26 | -1
27 | elif x.note.ord > y.note.ord:
28 | 1
29 | else:
30 | 0
31 | )
32 |
33 | import json
34 |
35 | test "JSON simple":
36 | var
37 | score1 = compile((piano, 1/4, c, 2, d))
38 | score2 = compile(%*["piano", 1/4, "c", 2, "d"])
39 | score1.sortEvents
40 | score2.sortEvents
41 | check score1 == score2
42 |
43 | from dueling_banjos import nil
44 | from dueling_banjos_json import nil
45 |
46 | test "JSON dueling banjos":
47 | var
48 | score1 = compile(dueling_banjos.score)
49 | score2 = compile(dueling_banjos_json.score)
50 | score1.sortEvents
51 | score2.sortEvents
52 | check score1 == score2
53 |
54 | test "relative octaves":
55 | var
56 | score1 = compile((piano, c, (octave: 3), d, (octave: 5), e))
57 | score2 = compile((piano, c, (octave: `-1`), d, (octave: `+2`), e))
58 | score3 = compile(%*["piano", "c", {"octave": "-1"}, "d", {"octave": "+2"}, "e"])
59 | score1.sortEvents
60 | score2.sortEvents
61 | score3.sortEvents
62 | check score1 == score2
63 | check score1 == score3
64 |
--------------------------------------------------------------------------------
/examples/src/dueling_banjos.nim:
--------------------------------------------------------------------------------
1 | from common import nil
2 | import paramidi
3 | import paramidi/tsf
4 | import paramidi_soundfonts
5 |
6 | const
7 | measure1 = (1/16, b, +c, 1/8, +d, b, +c, a, b, g, a)
8 | measure2 = (1/16, g, g, 1/8, g, a, b, +c, +d, +c, 1/2, b)
9 | measure3 = (1/16, {d, -b, -g}, {d, -b, -g},
10 | 1/8, {d, -b, -g}, {e, c, -g}, {d, -b, -g})
11 | measure4 = (1/16, r, r, 1/8, g, r, d, r, g, g, d)
12 | measure5 = (1/4, g, 1/8, a, b, 1/4, g, 1/8, a, d)
13 | measure6 = (1/4, g, 1/8, a, b, 1/4, g, 1/8, {f, -a}, b, 1/4, c)
14 | score =
15 | ((tempo: 80, octave: 3),
16 | (guitar, measure1),
17 | (banjo, measure1),
18 | (guitar, measure1),
19 | (guitar, 1/2, d, 1/8, g, g, a, b, g, b, 1/2, a),
20 | (banjo, 1/8, g, g, a, b, 1/2, g),
21 | (guitar, (octave: 2), measure2),
22 | (banjo, measure2),
23 | (guitar, (octave: 2), measure2),
24 | (banjo, measure2),
25 | (guitar, measure2),
26 | (banjo, measure2),
27 | (octave: 4),
28 | (guitar, measure3),
29 | (banjo, measure3),
30 | (guitar, measure3),
31 | (banjo, measure3),
32 | (guitar, (octave: 2), measure1),
33 | (banjo, (octave: 3), measure1),
34 | (tempo: 120, octave: 3),
35 | ((mode: concurrent),
36 | (banjo, measure1),
37 | (guitar, measure4)),
38 | ((mode: concurrent),
39 | (banjo, measure1),
40 | (guitar, measure4)),
41 | ((mode: concurrent),
42 | (banjo, measure1),
43 | (guitar, measure5)),
44 | ((mode: concurrent),
45 | (banjo, measure1),
46 | (guitar, measure6)))
47 |
48 | when isMainModule:
49 | # get the sound font
50 | # in a release build, embed it in the binary.
51 | when defined(release):
52 | const soundfont = staticRead("paramidi_soundfonts/generaluser.sf2")
53 | var sf = tsf_load_memory(soundfont.cstring, soundfont.len.cint)
54 | # during dev, read it from the disk
55 | else:
56 | var sf = tsf_load_filename(paramidi_soundfonts.getSoundFontPath("generaluser.sf2"))
57 | # render the score
58 | const sampleRate = 44100
59 | tsf_set_output(sf, TSF_MONO, sampleRate, 0)
60 | var res = render[cshort](compile(score), sf, sampleRate)
61 | tsf_close(sf)
62 | # create the wav file and play it
63 | const
64 | writeToDisk = true # if false, the wav file will only exist in memory
65 | padding = 500f # add a half second so it doesn't cut off abruptly
66 | when writeToDisk:
67 | common.writeFile("output.wav", res.data, res.data.len.uint32, sampleRate)
68 | discard common.play("output.wav", int(res.seconds * 1000f + padding))
69 | else:
70 | let wav = common.writeMemory(res.data, res.data.len.uint32, sampleRate)
71 | discard common.play(wav, int(res.seconds * 1000f + padding))
72 |
--------------------------------------------------------------------------------
/examples/src/common.nim:
--------------------------------------------------------------------------------
1 | import parasound/dr_wav
2 | import parasound/miniaudio
3 | import os
4 |
5 | proc play*(data: string | seq[uint8], sleepMsecs: int): bool =
6 | ## if `data` is a string, it is interpreted as a filename.
7 | ## if `data` is a byte sequence, it is interpreted as an in-memory buffer.
8 | var engine = newSeq[uint8](ma_engine_size())
9 | if MA_SUCCESS != ma_engine_init(nil, engine[0].addr):
10 | return false
11 | when data is string:
12 | if MA_SUCCESS != ma_engine_play_sound(engine[0].addr, data, nil):
13 | ma_engine_uninit(engine[0].addr)
14 | return false
15 | os.sleep(sleepMsecs)
16 | ma_engine_uninit(engine[0].addr)
17 | elif data is seq[uint8]:
18 | var decoder = newSeq[uint8](ma_decoder_size())
19 | if MA_SUCCESS != ma_decoder_init_memory(data[0].unsafeAddr, data.len.csize_t, nil, decoder[0].addr):
20 | ma_engine_uninit(engine[0].addr)
21 | return false
22 | var sound = newSeq[uint8](ma_sound_size())
23 | if MA_SUCCESS != ma_sound_init_from_data_source(engine[0].addr, decoder[0].addr, 0, nil, sound[0].addr):
24 | discard ma_decoder_uninit(decoder[0].addr)
25 | ma_engine_uninit(engine[0].addr)
26 | return false
27 | if MA_SUCCESS != ma_sound_start(sound[0].addr):
28 | ma_sound_uninit(sound[0].addr)
29 | discard ma_decoder_uninit(decoder[0].addr)
30 | ma_engine_uninit(engine[0].addr)
31 | return false
32 | os.sleep(sleepMsecs)
33 | ma_sound_uninit(sound[0].addr)
34 | discard ma_decoder_uninit(decoder[0].addr)
35 | ma_engine_uninit(engine[0].addr)
36 | true
37 |
38 | proc writeFile*(filename: string, data: var openArray[cshort], numSamples: uint32, sampleRate: uint32) =
39 | var
40 | wav = newSeq[uint8](drwav_size())
41 | format: drwav_data_format
42 | format.container = drwav_container_riff
43 | format.format = DR_WAVE_FORMAT_PCM
44 | format.channels = 1
45 | format.sampleRate = sampleRate
46 | format.bitsPerSample = 16
47 | doAssert drwav_init_file_write(wav[0].addr, filename, addr(format), nil)
48 | doAssert numSamples == drwav_write_pcm_frames(wav[0].addr, numSamples, data.addr)
49 | discard drwav_uninit(wav[0].addr)
50 |
51 | proc writeMemory*(data: var openArray[cshort], numSamples: uint32, sampleRate: uint32): seq[uint8] =
52 | var
53 | wav = newSeq[uint8](drwav_size())
54 | format: drwav_data_format
55 | format.container = drwav_container_riff
56 | format.format = DR_WAVE_FORMAT_PCM
57 | format.channels = 1
58 | format.sampleRate = sampleRate
59 | format.bitsPerSample = 16
60 | var
61 | outputRaw: pointer
62 | outputSize: csize_t
63 | doAssert drwav_init_memory_write_sequential(wav[0].addr, outputRaw.addr, outputSize.addr, format.addr, numSamples, nil)
64 | doAssert numSamples == drwav_write_pcm_frames(wav[0].addr, numSamples, data[0].addr)
65 | doAssert outputSize > 0
66 | result = newSeq[uint8](outputSize)
67 | copyMem(result[0].addr, outputRaw, outputSize)
68 | drwav_free(outputRaw, nil)
69 | discard drwav_uninit(wav[0].addr)
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | A Nim library for making MIDI music. It uses [TinySoundFont](https://github.com/schellingb/TinySoundFont) underneath. A musical score is modeled as a simple hierarchy of tuples. There are probably bugs...or maybe your music just sounds bad. It could be that. Think about it.
2 |
3 | ## Quick start
4 |
5 | The fastest way to get started is by cloning the [starter project](https://github.com/paranim/paramidi_starter).
6 | Or you can use [paramidib](https://pietroppeter.github.io/paramidib/).
7 |
8 | ## Documentation
9 |
10 | * Check out the `examples` dir
11 | * Watch the screencast: https://www.youtube.com/watch?v=k3B5mhHbcK0
12 | * Watch the NimConf talk: https://www.youtube.com/watch?v=cBqBfPRWla8
13 | * Look at this commented example:
14 |
15 | ```nim
16 | # first hit middle c on the piano
17 | (piano, c)
18 |
19 | # hit all twelve notes
20 | (piano, c, cx, d, dx, e, f, fx, g, gx, a, ax, b)
21 |
22 | # by default you're on the 4th octave, but you can change it with an attribute tuple
23 | (piano, (octave: 3), c, d, e, f)
24 |
25 | # attribute tuples let you change attributes for anything that comes after it
26 | # (the `+1` is a relative change, so e and f will be played at the 4th octave)
27 | (piano, (octave: 3), c, d, (octave: `+1`), e, f)
28 |
29 | # notes are 1/4 length by default, but you can change that too
30 | (piano, (octave: 3), c, d, (octave: `+1`, length: 1/2), e, f)
31 |
32 | # you have to change note lengths often so here's a shorthand
33 | (piano, (octave: 3), c, d, (octave: `+1`), 1/2, e, f)
34 |
35 | # you can change individual notes' relative octave with + or - before
36 | (piano, (octave: 3), c, d, 1/2, +e, +f)
37 |
38 | # a number following the + or - changes it by that many octaves
39 | (piano, (octave: 3), c, d, 1/2, e+2, f+2)
40 |
41 | # if there is no + or - before the number, it sets the note's absolute octave
42 | (piano, (octave: 3), c, d, 1/2, e2, f2)
43 |
44 | # with all that, we can write the first line of dueling banjos
45 | (guitar, (octave: 3), 1/8, b, +c, 1/4, +d, b, +c, a, b, g, a)
46 |
47 | # you can also set volume, which is helpful for dynamics
48 | (piano, (volume: 120), c, d, e, f)
49 |
50 | # chords are just notes in a set
51 | (piano, {c, e})
52 |
53 | # you can change the length of chords just like single notes
54 | (guitar, (octave: 4),
55 | 1/8, {d, -b, -g}, {d, -b, -g},
56 | 1/4, {d, -b, -g}, {e, c, -g}, {d, -b, -g})
57 |
58 | # to play two instruments concurrently, set the "mode" attribute
59 | ((mode: concurrent),
60 | (banjo, (octave: 3), 1/16, b, +c, 1/8, +d, b, +c, a, b, g, a),
61 | (guitar, (octave: 3), 1/16, r, r, 1/8, g, r, d, r, g, g, d)) # the r means rest
62 | ```
63 |
64 | Sharp notes are represented with an `x`, so `cx` is C sharp. Using Nim's [source code filters](https://nim-lang.org/docs/filters.html) you can easily support other symbols. Source code filters can even be used to support flat notes by automatically converting them to their sharp equivalent, as you can see in [this example](tests/test2.nim).
65 |
66 | You can also represent scores using Nim's json module, which allows you to manipulate them more dynamically. For example see [dueling_banjos_json.nim](https://github.com/paranim/paramidi/blob/master/tests/dueling_banjos_json.nim) (for comparison, see [dueling_banjos.nim](https://github.com/paranim/paramidi/blob/master/tests/dueling_banjos.nim) for the same score with the tuple-based syntax).
67 |
--------------------------------------------------------------------------------
/examples/src/undone_in_sorrow.nim:
--------------------------------------------------------------------------------
1 | from common import nil
2 | import paramidi
3 | import paramidi/tsf
4 | import paramidi_soundfonts
5 |
6 | func longChord(chord: set[Note], otherNotes: tuple): auto =
7 | # play the given chord for a half note
8 | # while the other notes are played
9 | ((mode: concurrent), (1/2, chord), (1/8, r, otherNotes))
10 |
11 | const
12 | over_yonder_in_the_graveyard =
13 | (1/8, g, a, 1/4, b, +d, 1/8, +d, +e, longChord({d, g, b, +e}, (1/8, +d, 1/4, b, +d)),
14 | 1/4, +e, 1/8, +e, +d, 1/4, b, 1/8, g, a, 1/4, b,
15 | longChord({d, g, b, +d}, (1/8, +g, 1/4, +d)), longChord({d, g, b, +d}, (1/8, +g, 1/4, b)),)
16 | where_the_wild_wildflowers_grow =
17 | (1/8, g, a, 1/4, b, +d, 1/8, +d, +e, longChord({d, g, b, +e}, (1/8, +d, 1/4, b, +d)),
18 | 1/4, +e, +fx, 1/8, +e, +d, b, a, 1/4, b,
19 | longChord({d, g, b, +d}, (1/8, +g, 1/4, +d)), longChord({d, g, b, +d}, (1/8, +g, 1/4, b)),)
20 | oh_there_they_laid_my_own_true_lover =
21 | (1/8, b, +d, a, +d, 1/4, g,
22 | 1/8, d, e, 1/4, {+g, +d}, 1/8, g, +d, +g, +d, 1/4, d,
23 | 1/8, g, a, a, g, g,
24 | 1/8, d, e, 1/4, {+g, +d}, 1/8, g, +d, +g, +d, 1/4, d,)
25 | shes_gone_from_me_forevermore =
26 | (1/8, b, +d, a, +d, 1/4, g,
27 | 1/8, d, e, 1/4, {+g, +d}, 1/8, g, +d, +g, +d, 1/4, d,
28 | 1/8, a, b, a, g, g,
29 | 1/8, d, e, 1/4, {+g, +d}, 1/8, g, +d, +g, +d, 1/4, d,)
30 |
31 | she_was_fairier_than_the_sweetest_flower = over_yonder_in_the_graveyard
32 | restless_as_the_wildest_wind = where_the_wild_wildflowers_grow
33 | oh_born_with_love_deep_as_the_ocean = oh_there_they_laid_my_own_true_lover
34 | that_was_the_girl_i_did_win = shes_gone_from_me_forevermore
35 |
36 | i_left_her_there_back_in_the_mountains = over_yonder_in_the_graveyard
37 | to_see_the_world_riches_to_gain = where_the_wild_wildflowers_grow
38 | when_i_returned_no_earthly_treasure = oh_there_they_laid_my_own_true_lover
39 | could_ease_this_heart_so_full_of_pain = shes_gone_from_me_forevermore
40 |
41 | from_way_up_high_upon_that_mountain = over_yonder_in_the_graveyard
42 | beneath_a_little_mound_of_clay = where_the_wild_wildflowers_grow
43 | the_girl_that_i_returned_to_marry = oh_there_they_laid_my_own_true_lover
44 | so_still_among_the_flowers_did_lay = shes_gone_from_me_forevermore
45 |
46 | ill_go_away_and_i_will_wander = over_yonder_in_the_graveyard
47 | lay_aside_my_earthly_gains = where_the_wild_wildflowers_grow
48 | and_ill_not_end_as_a_man_with_riches = oh_there_they_laid_my_own_true_lover
49 | undone_in_sorrow_ill_remain = shes_gone_from_me_forevermore
50 |
51 | score =
52 | ((octave: 3), banjo,
53 |
54 | over_yonder_in_the_graveyard,
55 | where_the_wild_wildflowers_grow,
56 | oh_there_they_laid_my_own_true_lover,
57 | shes_gone_from_me_forevermore,
58 |
59 | she_was_fairier_than_the_sweetest_flower,
60 | restless_as_the_wildest_wind,
61 | oh_born_with_love_deep_as_the_ocean,
62 | that_was_the_girl_i_did_win,
63 |
64 | i_left_her_there_back_in_the_mountains,
65 | to_see_the_world_riches_to_gain,
66 | when_i_returned_no_earthly_treasure,
67 | could_ease_this_heart_so_full_of_pain,
68 |
69 | from_way_up_high_upon_that_mountain,
70 | beneath_a_little_mound_of_clay,
71 | the_girl_that_i_returned_to_marry,
72 | so_still_among_the_flowers_did_lay,
73 |
74 | ill_go_away_and_i_will_wander,
75 | lay_aside_my_earthly_gains,
76 | and_ill_not_end_as_a_man_with_riches,
77 | undone_in_sorrow_ill_remain,
78 | undone_in_sorrow_ill_remain,
79 | )
80 |
81 | when isMainModule:
82 | # get the sound font
83 | # in a release build, embed it in the binary.
84 | when defined(release):
85 | const soundfont = staticRead("paramidi_soundfonts/generaluser.sf2")
86 | var sf = tsf_load_memory(soundfont.cstring, soundfont.len.cint)
87 | # during dev, read it from the disk
88 | else:
89 | var sf = tsf_load_filename(paramidi_soundfonts.getSoundFontPath("generaluser.sf2"))
90 | # render the score
91 | const sampleRate = 44100
92 | tsf_set_output(sf, TSF_MONO, sampleRate, 0)
93 | var res = render[cshort](compile(score), sf, sampleRate)
94 | tsf_close(sf)
95 | # create the wav file and play it
96 | const
97 | writeToDisk = true # if false, the wav file will only exist in memory
98 | padding = 500f # add a half second so it doesn't cut off abruptly
99 | when writeToDisk:
100 | common.writeFile("output.wav", res.data, res.data.len.uint32, sampleRate)
101 | discard common.play("output.wav", int(res.seconds * 1000f + padding))
102 | else:
103 | let wav = common.writeMemory(res.data, res.data.len.uint32, sampleRate)
104 | discard common.play(wav, int(res.seconds * 1000f + padding))
105 |
--------------------------------------------------------------------------------
/examples/src/aeriths_theme.nim:
--------------------------------------------------------------------------------
1 | from common import nil
2 | import paramidi
3 | import paramidi/tsf
4 | import paramidi_soundfonts
5 |
6 | const
7 | score =
8 | (piano,
9 | (tempo: 74),
10 | # 1
11 | (1/8, {-d, -a, e, fx}, a,
12 | 1/2, {fx, +d},
13 | 1/8, {-e, e, +c}, a,
14 | 1/2, {c, e},
15 | 1/8, {-d, -a, e, fx}, a, +d, +cx, +e, +d, b, +cx,
16 | 1/2, {-e, c, a}, 1/2, {c, e}),
17 | # 5
18 | (1/8, {-d, -a, e, fx}, a,
19 | 1/4, {fx, +d}, e, d,
20 | (tempo: 140),
21 | 1/8, {a-2, c, +c}, a,
22 | 1/4, {c, e}, -b, -a),
23 | # 7
24 | (1/8, d, e, {-d, d, fx, +d}, -a, d, -a, fx, -a,
25 | d, -a, -d, -a, d,
26 | 1/4, {d, fx},
27 | 1/8, {-d, d}, {-cx, e}, {b-2, fx}, -fx, d, -b,
28 | 1/4, d,
29 | 1/8, {-fx, -a, d, fx}, g, {a-2, a}, -fx, cx, -a,
30 | 1/4, {cx, fx},
31 | 1/8, {-g, d, g, b}, +cx, {b-2, +d}, -d,
32 | {-b, g, b}, -g,
33 | 1/4, -b, {-g, e, g},
34 | 1/16, {-d, fx, a}, b,
35 | 1/8, {-a, a}, e, -a,
36 | 1/4, fx),
37 | # 15
38 | (1/8, {-d, a}, fx, {a-2, cx}, -fx, -a, cx, {a-2, +cx},
39 | -fx, -a, cx, {a-2, b}, -fx, -a, fx, {a-2, a}, -fx),
40 | # 17
41 | (1/8, {-a, b}, a, {b-2, d}, -d, -a, -b, {-d, b}, -a,
42 | -b, d, {-d, a}, -a, -b, d,
43 | {g, -d}, -a),
44 | # 19
45 | (1/8, {a, -b}, fx, {a-2, cx}, -gx, -a, cx, {a-2, +cx},
46 | -gx, -a, cx, {b, a-2}, -gx, -a, cx,
47 | {-fx, a}, -gx),
48 | # 21
49 | (1/8, {-a, b}, +cx, {-g, +d}, d, g, a, b, +d,
50 | 1/4, {fx, b, +d},
51 | 1/8, -d, -g,
52 | 1/4, {-b, g, +d}, {-g, a, +cx}),
53 | # 23
54 | (1/4, {-g, g, b}, {-d, d, fx, a},
55 | 1/8, -d, -a, {e, fx}, -a, {e, fx}, -a,
56 | -d, -g, {b, +d}, -d, {a, +cx}, -d,
57 | {g, b}, -d, {-a, +cx}),
58 | # 25
59 | (1/8, -d,
60 | 1/4, {-a, +cx},
61 | 1/8, a-2, +fx, {a-2, +fx}, cx,
62 | 1/4, fx,
63 | 1/8, b-2, -d, {a, +cx}, -d, {g, b}, -d,
64 | 1/4, {-a, +cx},
65 | 1/2, {-e, d, g, a}, {-e, d, e}, {a-2, -g, d, e},
66 | 1/4, {a-2, -a, cx},
67 | 1/8, {e, d}),
68 | # 27
69 | (1/8, {fx, a}, {-g, g, b}, {fx, a}, {e, g},
70 | 1/4, {d, fx},
71 | 1/8, -d, -g, -b, d, g,
72 | 1/4, b,
73 | 1/8, {-g, g, b}, {fx, a}, {d, fx}),
74 | # 31
75 | (1/8, {e, g}, {-d, fx}, -a, d, fx,
76 | 1/2, +d,
77 | 1/4, +d, +cx, b),
78 | # 33
79 | (1/2, {-e, d, g, a}, {-fx, -a, cx, e},
80 | 1/16, e, fx,
81 | 1/8, {-g, g}, fx, e,
82 | 1/2, d),
83 | # 35
84 | (1/8, -g, +d, b, a, +fx, +cx, b, g,
85 | {-d, -a, e, fx}, a,
86 | 1/2, {fx, +d}),
87 | # 37
88 | ((tempo: 156),
89 | 1/8, {a-2, c, +c}, a,
90 | 1/4, {c, e}, -b, -a),
91 | # 38
92 | (tempo: 80),
93 | (1/8, d, e,
94 | 1/4, {d, ax-2}, -g,
95 | 1/8, {f, -ax}, e,
96 | d, e, {d, ax-2}, -d, -g, (1/4, -a), -g,
97 | -ax, d, {+d, fx, d, -d}, -a, d, fx, d),
98 | # 41
99 | (1/8, d, -a, -d, -a, d, -a, fx, -a,
100 | 1/4, d, 1/2, -a),
101 | # 43
102 | (1/16, +d, d, fx, +cx, d, fx,
103 | 1/8, b, 1/2, {a, g, d, -e},
104 | {e, cx, -a, -fx}, 1/16, e, fx,
105 | 1/4, {g, -g}, 1/8, {+f, cx}, {+e, +c}),
106 | # 45
107 | (tempo: 72),
108 | (1/8, {+d, b}, 1/2, {g, -b, -g}, 1/4, {b, d, -a}, a-2),
109 | # 46
110 | (tempo: 66),
111 | (1/4, {+d, fx, d, -d}, 1/8, -a, 1, {fx, e, -a, -d}))
112 |
113 | when isMainModule:
114 | # get the sound font
115 | # in a release build, embed it in the binary.
116 | when defined(release):
117 | const soundfont = staticRead("paramidi_soundfonts/generaluser.sf2")
118 | var sf = tsf_load_memory(soundfont.cstring, soundfont.len.cint)
119 | # during dev, read it from the disk
120 | else:
121 | var sf = tsf_load_filename(paramidi_soundfonts.getSoundFontPath("generaluser.sf2"))
122 | # render the score
123 | const sampleRate = 44100
124 | tsf_set_output(sf, TSF_MONO, sampleRate, 0)
125 | var res = render[cshort](compile(score), sf, sampleRate)
126 | tsf_close(sf)
127 | # create the wav file and play it
128 | const
129 | writeToDisk = true # if false, the wav file will only exist in memory
130 | padding = 500f # add a half second so it doesn't cut off abruptly
131 | when writeToDisk:
132 | common.writeFile("output.wav", res.data, res.data.len.uint32, sampleRate)
133 | discard common.play("output.wav", int(res.seconds * 1000f + padding))
134 | else:
135 | let wav = common.writeMemory(res.data, res.data.len.uint32, sampleRate)
136 | discard common.play(wav, int(res.seconds * 1000f + padding))
137 |
--------------------------------------------------------------------------------
/src/paramidi/constants.nim:
--------------------------------------------------------------------------------
1 | import sets
2 |
3 | const
4 | notes* = [
5 | "c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b",
6 | "r",
7 | "c+", "c+1", "c+2", "c+3", "c+4", "c+5", "c+6",
8 | "c-", "c-1", "c-2", "c-3", "c-4", "c-5", "c-6",
9 | "c1", "c2", "c3", "c4", "c5", "c6", "c7",
10 | "c#+", "c#+1", "c#+2", "c#+3", "c#+4", "c#+5", "c#+6",
11 | "c#-", "c#-1", "c#-2", "c#-3", "c#-4", "c#-5", "c#-6",
12 | "c#1", "c#2", "c#3", "c#4", "c#5", "c#6", "c#7",
13 | "d+", "d+1", "d+2", "d+3", "d+4", "d+5", "d+6",
14 | "d-", "d-1", "d-2", "d-3", "d-4", "d-5", "d-6",
15 | "d1", "d2", "d3", "d4", "d5", "d6", "d7",
16 | "d#+", "d#+1", "d#+2", "d#+3", "d#+4", "d#+5", "d#+6",
17 | "d#-", "d#-1", "d#-2", "d#-3", "d#-4", "d#-5", "d#-6",
18 | "d#1", "d#2", "d#3", "d#4", "d#5", "d#6", "d#7",
19 | "e+", "e+1", "e+2", "e+3", "e+4", "e+5", "e+6",
20 | "e-", "e-1", "e-2", "e-3", "e-4", "e-5", "e-6",
21 | "e1", "e2", "e3", "e4", "e5", "e6", "e7",
22 | "f+", "f+1", "f+2", "f+3", "f+4", "f+5", "f+6",
23 | "f-", "f-1", "f-2", "f-3", "f-4", "f-5", "f-6",
24 | "f1", "f2", "f3", "f4", "f5", "f6", "f7",
25 | "f#+", "f#+1", "f#+2", "f#+3", "f#+4", "f#+5", "f#+6",
26 | "f#-", "f#-1", "f#-2", "f#-3", "f#-4", "f#-5", "f#-6",
27 | "f#1", "f#2", "f#3", "f#4", "f#5", "f#6", "f#7",
28 | "g+", "g+1", "g+2", "g+3", "g+4", "g+5", "g+6",
29 | "g-", "g-1", "g-2", "g-3", "g-4", "g-5", "g-6",
30 | "g1", "g2", "g3", "g4", "g5", "g6", "g7",
31 | "g#+", "g#+1", "g#+2", "g#+3", "g#+4", "g#+5", "g#+6",
32 | "g#-", "g#-1", "g#-2", "g#-3", "g#-4", "g#-5", "g#-6",
33 | "g#1", "g#2", "g#3", "g#4", "g#5", "g#6", "g#7",
34 | "a+", "a+1", "a+2", "a+3", "a+4", "a+5", "a+6",
35 | "a-", "a-1", "a-2", "a-3", "a-4", "a-5", "a-6",
36 | "a1", "a2", "a3", "a4", "a5", "a6", "a7",
37 | "a#+", "a#+1", "a#+2", "a#+3", "a#+4", "a#+5", "a#+6",
38 | "a#-", "a#-1", "a#-2", "a#-3", "a#-4", "a#-5", "a#-6",
39 | "a#1", "a#2", "a#3", "a#4", "a#5", "a#6", "a#7",
40 | "b+", "b+1", "b+2", "b+3", "b+4", "b+5", "b+6",
41 | "b-", "b-1", "b-2", "b-3", "b-4", "b-5", "b-6",
42 | "b1", "b2", "b3", "b4", "b5", "b6", "b7",
43 | ]
44 | instruments* = [
45 | # Piano
46 | "piano", # acoustic-grand-piano
47 | "bright-acoustic-piano",
48 | "electric-grand-piano",
49 | "honky-tonk-piano",
50 | "electric-piano-1",
51 | "electric-piano-2",
52 | "harpsichord",
53 | "clavinet",
54 | # Chromatic Percussion
55 | "celesta",
56 | "glockenspiel",
57 | "music-box",
58 | "vibraphone",
59 | "marimba",
60 | "xylophone",
61 | "tubular-bells",
62 | "dulcimer",
63 | # Organ
64 | "drawbar-organ",
65 | "percussive-organ",
66 | "rock-organ",
67 | "organ", # church-organ
68 | "reed-organ",
69 | "accordion",
70 | "harmonica",
71 | "tango-accordion",
72 | # Guitar
73 | "guitar", # acoustic-guitar-nylon
74 | "acoustic-guitar-steel",
75 | "electric-guitar-jazz",
76 | "electric-guitar-clean",
77 | "electric-guitar-palm-muted",
78 | "electric-guitar-overdrive",
79 | "electric-guitar-distorted",
80 | "electric-guitar-harmonics",
81 | # Bass
82 | "acoustic-bass",
83 | "electric-bass", # electric-bass-finger
84 | "electric-bass-pick",
85 | "fretless-bass",
86 | "bass-slap",
87 | "bass-pop",
88 | "synth-bass-1",
89 | "synth-bass-2",
90 | # Strings
91 | "violin",
92 | "viola",
93 | "cello",
94 | "contrabass",
95 | "tremolo-strings",
96 | "pizzicato-strings",
97 | "harp", # orchestral-harp
98 | "timpani",
99 | # Ensemble
100 | "string-ensemble-1",
101 | "string-ensemble-2",
102 | "synth-strings-1",
103 | "synth-strings-2",
104 | "choir-aahs",
105 | "voice-oohs",
106 | "synth-voice",
107 | "orchestra-hit",
108 | # Brass
109 | "trumpet",
110 | "trombone",
111 | "tuba",
112 | "muted-trumpet",
113 | "french-horn",
114 | "brass-section",
115 | "synth-brass-1",
116 | "synth-brass-2",
117 | # Reed
118 | "soprano-sax",
119 | "alto-sax",
120 | "tenor-sax",
121 | "baritone-sax",
122 | "oboe",
123 | "english-horn",
124 | "bassoon",
125 | "clarinet",
126 | # Pipe
127 | "piccolo",
128 | "flute",
129 | "recorder",
130 | "pan-flute",
131 | "bottle",
132 | "shakuhachi",
133 | "whistle",
134 | "ocarina",
135 | # Synth Lead
136 | "square-lead",
137 | "saw-wave",
138 | "calliope-lead",
139 | "chiffer-lead",
140 | "charang",
141 | "solo-vox",
142 | "fifths",
143 | "bass-and-lead",
144 | # Synth Pad
145 | "synth-pad-new-age",
146 | "synth-pad-warm",
147 | "synth-pad-polysynth",
148 | "synth-pad-choir",
149 | "synth-pad-bowed",
150 | "synth-pad-metallic",
151 | "synth-pad-halo",
152 | "synth-pad-sweep",
153 | # Synth Effects
154 | "fx-rain",
155 | "fx-soundtrack",
156 | "fx-crystal",
157 | "fx-atmosphere",
158 | "fx-brightness",
159 | "fx-goblins",
160 | "fx-echoes",
161 | "fx-sci-fi",
162 | # Ethnic
163 | "sitar",
164 | "banjo",
165 | "shamisen",
166 | "koto",
167 | "kalimba",
168 | "bagpipes",
169 | "fiddle",
170 | "shehnai",
171 | # Percussive
172 | "tinkle-bell",
173 | "agogo",
174 | "steel-drums",
175 | "woodblock",
176 | "taiko-drum",
177 | "melodic-tom",
178 | "synth-drum",
179 | "reverse-cymbal",
180 | # Sound Effects
181 | "guitar-fret-noise",
182 | "breath-noise",
183 | "seashore",
184 | "bird-tweet",
185 | "telephone-ring",
186 | "helicopter",
187 | "applause",
188 | "gun-shot",
189 | ]
190 | relativeOctaves* = [
191 | "+1", "+2", "+3", "+4", "+5", "+6",
192 | "-1", "-2", "-3", "-4", "-5", "-6",
193 | ]
194 | modes* = ["sequential", "concurrent"]
195 | noteSet* = notes.toHashSet
196 | instrumentSet* = instruments.toHashSet
197 | modeSet* = modes.toHashSet
198 | relativeOctaveSet* = relativeOctaves.toHashSet
199 |
--------------------------------------------------------------------------------
/src/paramidi/tsf.nim:
--------------------------------------------------------------------------------
1 | {.compile: "tsf.c".}
2 |
3 | type tsf* = object
4 |
5 | proc tsf_load_filename*(filename: cstring): ptr tsf {.cdecl, importc.}
6 | ## Load a SoundFont from a block of memory
7 |
8 | proc tsf_load_memory*(buffer: pointer; size: cint): ptr tsf {.cdecl, importc.}
9 | ## Stream structure for the generic loading
10 |
11 | type
12 | tsf_stream* {.bycopy.} = object
13 | data*: pointer ## Custom data given to the functions as the first parameter
14 | ## Function pointer will be called to read 'size' bytes into ptr (returns number of read bytes)
15 | read*: proc (data: pointer; `ptr`: pointer; size: cuint): cint ## Function pointer will be called to skip ahead over 'count' bytes (returns 1 on success, 0 on error)
16 | skip*: proc (data: pointer; count: cuint): cint
17 |
18 |
19 | ## Generic SoundFont loading method using the stream structure above
20 |
21 | proc tsf_load*(stream: ptr tsf_stream): ptr tsf {.cdecl, importc.}
22 | ## Free the memory related to this tsf instance
23 |
24 | proc tsf_close*(f: ptr tsf) {.cdecl, importc.}
25 | ## Stop all playing notes immediatly and reset all channel parameters
26 |
27 | proc tsf_reset*(f: ptr tsf) {.cdecl, importc.}
28 | ## Returns the preset index from a bank and preset number, or -1 if it does not exist in the loaded SoundFont
29 |
30 | proc tsf_get_presetindex*(f: ptr tsf; bank: cint; preset_number: cint): cint {.cdecl, importc.}
31 | ## Returns the number of presets in the loaded SoundFont
32 |
33 | proc tsf_get_presetcount*(f: ptr tsf): cint {.cdecl, importc.}
34 | ## Returns the name of a preset index >= 0 and < tsf_get_presetcount()
35 |
36 | proc tsf_get_presetname*(f: ptr tsf; preset_index: cint): cstring {.cdecl, importc.}
37 | ## Returns the name of a preset by bank and preset number
38 |
39 | proc tsf_bank_get_presetname*(f: ptr tsf; bank: cint; preset_number: cint): cstring {.cdecl, importc.}
40 | ## Supported output modes by the render methods
41 |
42 | type
43 | TSFOutputMode* = enum ## Two channels with single left/right samples one after another
44 | TSF_STEREO_INTERLEAVED, ## Two channels with all samples for the left channel first then right
45 | TSF_STEREO_UNWEAVED, ## A single channel (stereo instruments are mixed into center)
46 | TSF_MONO
47 |
48 |
49 | ## Thread safety:
50 | ## Your audio output which calls the tsf_render* functions will most likely
51 | ## run on a different thread than where the playback tsf_note* functions
52 | ## are called. In which case some sort of concurrency control like a
53 | ## mutex needs to be used so they are not called at the same time.
54 | ## Alternatively, you can pre-allocate a maximum number of voices that can
55 | ## play simultaneously by calling tsf_set_max_voices after loading.
56 | ## That way memory re-allocation will not happen during tsf_note_on and
57 | ## TSF should become mostly thread safe.
58 | ## There is a theoretical chance that ending notes would negatively influence
59 | ## a voice that is rendering at the time but it is hard to say.
60 | ## Also be aware, this has not been tested much.
61 | ## Setup the parameters for the voice render methods
62 | ## outputmode: if mono or stereo and how stereo channel data is ordered
63 | ## samplerate: the number of samples per second (output frequency)
64 | ## global_gain_db: volume gain in decibels (>0 means higher, <0 means lower)
65 |
66 | proc tsf_set_output*(f: ptr tsf; outputmode: TSFOutputMode; samplerate: cint;
67 | global_gain_db: cfloat) {.cdecl, importc.}
68 | ## Set the global gain as a volume factor
69 | ## global_gain: the desired volume where 1.0 is 100%
70 |
71 | proc tsf_set_volume*(f: ptr tsf; global_gain: cfloat) {.cdecl, importc.}
72 | ## Set the maximum number of voices to play simultaneously
73 | ## Depending on the soundfond, one note can cause many new voices to be started,
74 | ## so don't keep this number too low or otherwise sounds may not play.
75 | ## max_voices: maximum number to pre-allocate and set the limit to
76 |
77 | proc tsf_set_max_voices*(f: ptr tsf; max_voices: cint) {.cdecl, importc.}
78 | ## Start playing a note
79 | ## preset_index: preset index >= 0 and < tsf_get_presetcount()
80 | ## key: note value between 0 and 127 (60 being middle C)
81 | ## vel: velocity as a float between 0.0 (equal to note off) and 1.0 (full)
82 | ## bank: instrument bank number (alternative to preset_index)
83 | ## preset_number: preset number (alternative to preset_index)
84 | ## (bank_note_on returns 0 if preset does not exist, otherwise 1)
85 |
86 | proc tsf_note_on*(f: ptr tsf; preset_index: cint; key: cint; vel: cfloat) {.cdecl, importc.}
87 | proc tsf_bank_note_on*(f: ptr tsf; bank: cint; preset_number: cint; key: cint; vel: cfloat): cint {.cdecl, importc.}
88 | ## Stop playing a note
89 | ## (bank_note_off returns 0 if preset does not exist, otherwise 1)
90 |
91 | proc tsf_note_off*(f: ptr tsf; preset_index: cint; key: cint) {.cdecl, importc.}
92 | proc tsf_bank_note_off*(f: ptr tsf; bank: cint; preset_number: cint; key: cint): cint {.cdecl, importc.}
93 | ## Stop playing all notes (end with sustain and release)
94 |
95 | proc tsf_note_off_all*(f: ptr tsf) {.cdecl, importc.}
96 | ## Returns the number of active voices
97 |
98 | proc tsf_active_voice_count*(f: ptr tsf): cint {.cdecl, importc.}
99 | ## Render output samples into a buffer
100 | ## You can either render as signed 16-bit values (tsf_render_short) or
101 | ## as 32-bit float values (tsf_render_float)
102 | ## buffer: target buffer of size samples * output_channels * sizeof(type)
103 | ## samples: number of samples to render
104 | ## flag_mixing: if 0 clear the buffer first, otherwise mix into existing data
105 |
106 | proc tsf_render_short*(f: ptr tsf; buffer: ptr cshort; samples: cint; flag_mixing: cint) {.cdecl, importc.}
107 | proc tsf_render_float*(f: ptr tsf; buffer: ptr cfloat; samples: cint; flag_mixing: cint) {.cdecl, importc.}
108 | ## Higher level channel based functions, set up channel parameters
109 | ## channel: channel number
110 | ## preset_index: preset index >= 0 and < tsf_get_presetcount()
111 | ## preset_number: preset number (alternative to preset_index)
112 | ## flag_mididrums: 0 for normal channels, otherwise apply MIDI drum channel rules
113 | ## bank: instrument bank number (alternative to preset_index)
114 | ## pan: stereo panning value from 0.0 (left) to 1.0 (right) (default 0.5 center)
115 | ## volume: linear volume scale factor (default 1.0 full)
116 | ## pitch_wheel: pitch wheel position 0 to 16383 (default 8192 unpitched)
117 | ## pitch_range: range of the pitch wheel in semitones (default 2.0, total +/- 2 semitones)
118 | ## tuning: tuning of all playing voices in semitones (default 0.0, standard (A440) tuning)
119 | ## (set_preset_number and set_bank_preset return 0 if preset does not exist, otherwise 1)
120 |
121 | proc tsf_channel_set_presetindex*(f: ptr tsf; channel: cint; preset_index: cint) {.cdecl, importc.}
122 | proc tsf_channel_set_presetnumber*(f: ptr tsf; channel: cint; preset_number: cint;
123 | flag_mididrums: cint): cint {.cdecl, importc.}
124 | proc tsf_channel_set_bank*(f: ptr tsf; channel: cint; bank: cint) {.cdecl, importc.}
125 | proc tsf_channel_set_bank_preset*(f: ptr tsf; channel: cint; bank: cint;
126 | preset_number: cint): cint {.cdecl, importc.}
127 | proc tsf_channel_set_pan*(f: ptr tsf; channel: cint; pan: cfloat) {.cdecl, importc.}
128 | proc tsf_channel_set_volume*(f: ptr tsf; channel: cint; volume: cfloat) {.cdecl, importc.}
129 | proc tsf_channel_set_pitchwheel*(f: ptr tsf; channel: cint; pitch_wheel: cint) {.cdecl, importc.}
130 | proc tsf_channel_set_pitchrange*(f: ptr tsf; channel: cint; pitch_range: cfloat) {.cdecl, importc.}
131 | proc tsf_channel_set_tuning*(f: ptr tsf; channel: cint; tuning: cfloat) {.cdecl, importc.}
132 | ## Start or stop playing notes on a channel (needs channel preset to be set)
133 | ## channel: channel number
134 | ## key: note value between 0 and 127 (60 being middle C)
135 | ## vel: velocity as a float between 0.0 (equal to note off) and 1.0 (full)
136 |
137 | proc tsf_channel_note_on*(f: ptr tsf; channel: cint; key: cint; vel: cfloat) {.cdecl, importc.}
138 | proc tsf_channel_note_off*(f: ptr tsf; channel: cint; key: cint) {.cdecl, importc.}
139 | proc tsf_channel_note_off_all*(f: ptr tsf; channel: cint) {.cdecl, importc.}
140 | ## end with sustain and release
141 |
142 | proc tsf_channel_sounds_off_all*(f: ptr tsf; channel: cint) {.cdecl, importc.}
143 | ## end immediatly
144 | ## Apply a MIDI control change to the channel (not all controllers are supported!)
145 |
146 | proc tsf_channel_midi_control*(f: ptr tsf; channel: cint; controller: cint;
147 | control_value: cint) {.cdecl, importc.}
148 | ## Get current values set on the channels
149 |
150 | proc tsf_channel_get_preset_index*(f: ptr tsf; channel: cint): cint {.cdecl, importc.}
151 | proc tsf_channel_get_preset_bank*(f: ptr tsf; channel: cint): cint {.cdecl, importc.}
152 | proc tsf_channel_get_preset_number*(f: ptr tsf; channel: cint): cint {.cdecl, importc.}
153 | proc tsf_channel_get_pan*(f: ptr tsf; channel: cint): cfloat {.cdecl, importc.}
154 | proc tsf_channel_get_volume*(f: ptr tsf; channel: cint): cfloat {.cdecl, importc.}
155 | proc tsf_channel_get_pitchwheel*(f: ptr tsf; channel: cint): cint {.cdecl, importc.}
156 | proc tsf_channel_get_pitchrange*(f: ptr tsf; channel: cint): cfloat {.cdecl, importc.}
157 | proc tsf_channel_get_tuning*(f: ptr tsf; channel: cint): cfloat {.cdecl, importc.}
158 |
--------------------------------------------------------------------------------
/src/paramidi.nim:
--------------------------------------------------------------------------------
1 | from sequtils import nil
2 | from algorithm import nil
3 | import paramidi/tsf
4 | from paramidi/constants import nil
5 | import json, sets
6 |
7 | type
8 | Note* = enum
9 | c, cx, d, dx, e, f, fx, g, gx, a, ax, b,
10 | r, # rest
11 | `c+`, `c+1`, `c+2`, `c+3`, `c+4`, `c+5`, `c+6`,
12 | `c-`, `c-1`, `c-2`, `c-3`, `c-4`, `c-5`, `c-6`,
13 | c1, c2, c3, c4, c5, c6, c7,
14 | `cx+`, `cx+1`, `cx+2`, `cx+3`, `cx+4`, `cx+5`, `cx+6`,
15 | `cx-`, `cx-1`, `cx-2`, `cx-3`, `cx-4`, `cx-5`, `cx-6`,
16 | cx1, cx2, cx3, cx4, cx5, cx6, cx7,
17 | `d+`, `d+1`, `d+2`, `d+3`, `d+4`, `d+5`, `d+6`,
18 | `d-`, `d-1`, `d-2`, `d-3`, `d-4`, `d-5`, `d-6`,
19 | d1, d2, d3, d4, d5, d6, d7,
20 | `dx+`, `dx+1`, `dx+2`, `dx+3`, `dx+4`, `dx+5`, `dx+6`,
21 | `dx-`, `dx-1`, `dx-2`, `dx-3`, `dx-4`, `dx-5`, `dx-6`,
22 | dx1, dx2, dx3, dx4, dx5, dx6, dx7,
23 | `e+`, `e+1`, `e+2`, `e+3`, `e+4`, `e+5`, `e+6`,
24 | `e-`, `e-1`, `e-2`, `e-3`, `e-4`, `e-5`, `e-6`,
25 | e1, e2, e3, e4, e5, e6, e7,
26 | `f+`, `f+1`, `f+2`, `f+3`, `f+4`, `f+5`, `f+6`,
27 | `f-`, `f-1`, `f-2`, `f-3`, `f-4`, `f-5`, `f-6`,
28 | f1, f2, f3, f4, f5, f6, f7,
29 | `fx+`, `fx+1`, `fx+2`, `fx+3`, `fx+4`, `fx+5`, `fx+6`,
30 | `fx-`, `fx-1`, `fx-2`, `fx-3`, `fx-4`, `fx-5`, `fx-6`,
31 | fx1, fx2, fx3, fx4, fx5, fx6, fx7,
32 | `g+`, `g+1`, `g+2`, `g+3`, `g+4`, `g+5`, `g+6`,
33 | `g-`, `g-1`, `g-2`, `g-3`, `g-4`, `g-5`, `g-6`,
34 | g1, g2, g3, g4, g5, g6, g7,
35 | `gx+`, `gx+1`, `gx+2`, `gx+3`, `gx+4`, `gx+5`, `gx+6`,
36 | `gx-`, `gx-1`, `gx-2`, `gx-3`, `gx-4`, `gx-5`, `gx-6`,
37 | gx1, gx2, gx3, gx4, gx5, gx6, gx7,
38 | `a+`, `a+1`, `a+2`, `a+3`, `a+4`, `a+5`, `a+6`,
39 | `a-`, `a-1`, `a-2`, `a-3`, `a-4`, `a-5`, `a-6`,
40 | a1, a2, a3, a4, a5, a6, a7,
41 | `ax+`, `ax+1`, `ax+2`, `ax+3`, `ax+4`, `ax+5`, `ax+6`,
42 | `ax-`, `ax-1`, `ax-2`, `ax-3`, `ax-4`, `ax-5`, `ax-6`,
43 | ax1, ax2, ax3, ax4, ax5, ax6, ax7,
44 | `b+`, `b+1`, `b+2`, `b+3`, `b+4`, `b+5`, `b+6`,
45 | `b-`, `b-1`, `b-2`, `b-3`, `b-4`, `b-5`, `b-6`,
46 | b1, b2, b3, b4, b5, b6, b7,
47 | Instrument* = enum
48 | none = -1,
49 | # Piano
50 | piano, # acoustic_grand_piano
51 | bright_acoustic_piano,
52 | electric_grand_piano,
53 | honky_tonk_piano,
54 | electric_piano_1,
55 | electric_piano_2,
56 | harpsichord,
57 | clavinet,
58 | # Chromatic Percussion
59 | celesta,
60 | glockenspiel,
61 | music_box,
62 | vibraphone,
63 | marimba,
64 | xylophone,
65 | tubular_bells,
66 | dulcimer,
67 | # Organ
68 | drawbar_organ,
69 | percussive_organ,
70 | rock_organ,
71 | organ, # church_organ
72 | reed_organ,
73 | accordion,
74 | harmonica,
75 | tango_accordion,
76 | # Guitar
77 | guitar, # acoustic_guitar_nylon
78 | acoustic_guitar_steel,
79 | electric_guitar_jazz,
80 | electric_guitar_clean,
81 | electric_guitar_palm_muted,
82 | electric_guitar_overdrive,
83 | electric_guitar_distorted,
84 | electric_guitar_harmonics,
85 | # Bass
86 | acoustic_bass,
87 | electric_bass, # electric_bass_finger
88 | electric_bass_pick,
89 | fretless_bass,
90 | bass_slap,
91 | bass_pop,
92 | synth_bass_1,
93 | synth_bass_2,
94 | # Strings
95 | violin,
96 | viola,
97 | cello,
98 | contrabass,
99 | tremolo_strings,
100 | pizzicato_strings,
101 | harp, # orchestral_harp
102 | timpani,
103 | # Ensemble
104 | string_ensemble_1,
105 | string_ensemble_2,
106 | synth_strings_1,
107 | synth_strings_2,
108 | choir_aahs,
109 | voice_oohs,
110 | synth_voice,
111 | orchestra_hit,
112 | # Brass
113 | trumpet,
114 | trombone,
115 | tuba,
116 | muted_trumpet,
117 | french_horn,
118 | brass_section,
119 | synth_brass_1,
120 | synth_brass_2,
121 | # Reed
122 | soprano_sax,
123 | alto_sax,
124 | tenor_sax,
125 | baritone_sax,
126 | oboe,
127 | english_horn,
128 | bassoon,
129 | clarinet,
130 | # Pipe
131 | piccolo,
132 | flute,
133 | recorder,
134 | pan_flute,
135 | bottle,
136 | shakuhachi,
137 | whistle,
138 | ocarina,
139 | # Synth Lead
140 | square_lead,
141 | saw_wave,
142 | calliope_lead,
143 | chiffer_lead,
144 | charang,
145 | solo_vox,
146 | fifths,
147 | bass_and_lead,
148 | # Synth Pad
149 | synth_pad_new_age,
150 | synth_pad_warm,
151 | synth_pad_polysynth,
152 | synth_pad_choir,
153 | synth_pad_bowed,
154 | synth_pad_metallic,
155 | synth_pad_halo,
156 | synth_pad_sweep,
157 | # Synth Effects
158 | fx_rain,
159 | fx_soundtrack,
160 | fx_crystal,
161 | fx_atmosphere,
162 | fx_brightness,
163 | fx_goblins,
164 | fx_echoes,
165 | fx_sci_fi,
166 | # Ethnic
167 | sitar,
168 | banjo,
169 | shamisen,
170 | koto,
171 | kalimba,
172 | bagpipes,
173 | fiddle,
174 | shehnai,
175 | # Percussive
176 | tinkle_bell,
177 | agogo,
178 | steel_drums,
179 | woodblock,
180 | taiko_drum,
181 | melodic_tom,
182 | synth_drum,
183 | reverse_cymbal,
184 | # Sound Effects
185 | guitar_fret_noise,
186 | breath_noise,
187 | seashore,
188 | bird_tweet,
189 | telephone_ring,
190 | helicopter,
191 | applause,
192 | gun_shot,
193 | RelativeOctave* = enum
194 | `+1`, `+2`, `+3`, `+4`, `+5`, `+6`,
195 | `-1`, `-2`, `-3`, `-4`, `-5`, `-6`,
196 | EventKind* = enum
197 | On, Off,
198 | Event* = object
199 | kind*: EventKind
200 | note*: Note
201 | time*: float
202 | instrument*: Instrument
203 | octave*: range[1..7]
204 | tempo*: int
205 | volume: int
206 | Mode* = enum
207 | sequential, concurrent,
208 | Context* = object
209 | events*: ref seq[Event]
210 | time*: float # total length of all notes
211 | seconds*: float # total number of seconds
212 | instrument*: Instrument
213 | octave*: range[1..7]
214 | tempo*: int
215 | length*: float
216 | play*: bool
217 | mode*: Mode
218 | volume: int
219 | RenderResult*[T] = object
220 | data*: seq[T]
221 | seconds*: float
222 |
223 | proc getNote(note: Note): Note =
224 | case note:
225 | of c, cx, d, dx, e, f, fx, g, gx, a, ax, b, r:
226 | note
227 | of `c+`, `c+1`, `c+2`, `c+3`, `c+4`, `c+5`, `c+6`,
228 | `c-`, `c-1`, `c-2`, `c-3`, `c-4`, `c-5`, `c-6`,
229 | c1, c2, c3, c4, c5, c6, c7:
230 | c
231 | of `cx+`, `cx+1`, `cx+2`, `cx+3`, `cx+4`, `cx+5`, `cx+6`,
232 | `cx-`, `cx-1`, `cx-2`, `cx-3`, `cx-4`, `cx-5`, `cx-6`,
233 | cx1, cx2, cx3, cx4, cx5, cx6, cx7:
234 | cx
235 | of `d+`, `d+1`, `d+2`, `d+3`, `d+4`, `d+5`, `d+6`,
236 | `d-`, `d-1`, `d-2`, `d-3`, `d-4`, `d-5`, `d-6`,
237 | d1, d2, d3, d4, d5, d6, d7:
238 | d
239 | of `dx+`, `dx+1`, `dx+2`, `dx+3`, `dx+4`, `dx+5`, `dx+6`,
240 | `dx-`, `dx-1`, `dx-2`, `dx-3`, `dx-4`, `dx-5`, `dx-6`,
241 | dx1, dx2, dx3, dx4, dx5, dx6, dx7:
242 | dx
243 | of `e+`, `e+1`, `e+2`, `e+3`, `e+4`, `e+5`, `e+6`,
244 | `e-`, `e-1`, `e-2`, `e-3`, `e-4`, `e-5`, `e-6`,
245 | e1, e2, e3, e4, e5, e6, e7:
246 | e
247 | of `f+`, `f+1`, `f+2`, `f+3`, `f+4`, `f+5`, `f+6`,
248 | `f-`, `f-1`, `f-2`, `f-3`, `f-4`, `f-5`, `f-6`,
249 | f1, f2, f3, f4, f5, f6, f7:
250 | f
251 | of `fx+`, `fx+1`, `fx+2`, `fx+3`, `fx+4`, `fx+5`, `fx+6`,
252 | `fx-`, `fx-1`, `fx-2`, `fx-3`, `fx-4`, `fx-5`, `fx-6`,
253 | fx1, fx2, fx3, fx4, fx5, fx6, fx7:
254 | fx
255 | of `g+`, `g+1`, `g+2`, `g+3`, `g+4`, `g+5`, `g+6`,
256 | `g-`, `g-1`, `g-2`, `g-3`, `g-4`, `g-5`, `g-6`,
257 | g1, g2, g3, g4, g5, g6, g7:
258 | g
259 | of `gx+`, `gx+1`, `gx+2`, `gx+3`, `gx+4`, `gx+5`, `gx+6`,
260 | `gx-`, `gx-1`, `gx-2`, `gx-3`, `gx-4`, `gx-5`, `gx-6`,
261 | gx1, gx2, gx3, gx4, gx5, gx6, gx7:
262 | gx
263 | of `a+`, `a+1`, `a+2`, `a+3`, `a+4`, `a+5`, `a+6`,
264 | `a-`, `a-1`, `a-2`, `a-3`, `a-4`, `a-5`, `a-6`,
265 | a1, a2, a3, a4, a5, a6, a7:
266 | a
267 | of `ax+`, `ax+1`, `ax+2`, `ax+3`, `ax+4`, `ax+5`, `ax+6`,
268 | `ax-`, `ax-1`, `ax-2`, `ax-3`, `ax-4`, `ax-5`, `ax-6`,
269 | ax1, ax2, ax3, ax4, ax5, ax6, ax7:
270 | ax
271 | of `b+`, `b+1`, `b+2`, `b+3`, `b+4`, `b+5`, `b+6`,
272 | `b-`, `b-1`, `b-2`, `b-3`, `b-4`, `b-5`, `b-6`,
273 | b1, b2, b3, b4, b5, b6, b7:
274 | b
275 |
276 | proc getOctave(ctx: Context, note: Note): range[1..7] =
277 | case note:
278 | of c, cx, d, dx, e, f, fx, g, gx, a, ax, b, r:
279 | ctx.octave
280 | of `c+`, `cx+`, `d+`, `dx+`, `e+`, `f+`, `fx+`, `g+`, `gx+`, `a+`, `ax+`, `b+`,
281 | `c+1`, `cx+1`, `d+1`, `dx+1`, `e+1`, `f+1`, `fx+1`, `g+1`, `gx+1`, `a+1`, `ax+1`, `b+1`:
282 | ctx.octave + 1
283 | of `c+2`, `cx+2`, `d+2`, `dx+2`, `e+2`, `f+2`, `fx+2`, `g+2`, `gx+2`, `a+2`, `ax+2`, `b+2`:
284 | ctx.octave + 2
285 | of `c+3`, `cx+3`, `d+3`, `dx+3`, `e+3`, `f+3`, `fx+3`, `g+3`, `gx+3`, `a+3`, `ax+3`, `b+3`:
286 | ctx.octave + 3
287 | of `c+4`, `cx+4`, `d+4`, `dx+4`, `e+4`, `f+4`, `fx+4`, `g+4`, `gx+4`, `a+4`, `ax+4`, `b+4`:
288 | ctx.octave + 4
289 | of `c+5`, `cx+5`, `d+5`, `dx+5`, `e+5`, `f+5`, `fx+5`, `g+5`, `gx+5`, `a+5`, `ax+5`, `b+5`:
290 | ctx.octave + 5
291 | of `c+6`, `cx+6`, `d+6`, `dx+6`, `e+6`, `f+6`, `fx+6`, `g+6`, `gx+6`, `a+6`, `ax+6`, `b+6`:
292 | ctx.octave + 6
293 | of `c-`, `cx-`, `d-`, `dx-`, `e-`, `f-`, `fx-`, `g-`, `gx-`, `a-`, `ax-`, `b-`,
294 | `c-1`, `cx-1`, `d-1`, `dx-1`, `e-1`, `f-1`, `fx-1`, `g-1`, `gx-1`, `a-1`, `ax-1`, `b-1`:
295 | ctx.octave - 1
296 | of `c-2`, `cx-2`, `d-2`, `dx-2`, `e-2`, `f-2`, `fx-2`, `g-2`, `gx-2`, `a-2`, `ax-2`, `b-2`:
297 | ctx.octave - 2
298 | of `c-3`, `cx-3`, `d-3`, `dx-3`, `e-3`, `f-3`, `fx-3`, `g-3`, `gx-3`, `a-3`, `ax-3`, `b-3`:
299 | ctx.octave - 3
300 | of `c-4`, `cx-4`, `d-4`, `dx-4`, `e-4`, `f-4`, `fx-4`, `g-4`, `gx-4`, `a-4`, `ax-4`, `b-4`:
301 | ctx.octave - 4
302 | of `c-5`, `cx-5`, `d-5`, `dx-5`, `e-5`, `f-5`, `fx-5`, `g-5`, `gx-5`, `a-5`, `ax-5`, `b-5`:
303 | ctx.octave - 5
304 | of `c-6`, `cx-6`, `d-6`, `dx-6`, `e-6`, `f-6`, `fx-6`, `g-6`, `gx-6`, `a-6`, `ax-6`, `b-6`:
305 | ctx.octave - 6
306 | of c1, cx1, d1, dx1, e1, f1, fx1, g1, gx1, a1, ax1, b1:
307 | 1
308 | of c2, cx2, d2, dx2, e2, f2, fx2, g2, gx2, a2, ax2, b2:
309 | 2
310 | of c3, cx3, d3, dx3, e3, f3, fx3, g3, gx3, a3, ax3, b3:
311 | 3
312 | of c4, cx4, d4, dx4, e4, f4, fx4, g4, gx4, a4, ax4, b4:
313 | 4
314 | of c5, cx5, d5, dx5, e5, f5, fx5, g5, gx5, a5, ax5, b5:
315 | 5
316 | of c6, cx6, d6, dx6, e6, f6, fx6, g6, gx6, a6, ax6, b6:
317 | 6
318 | of c7, cx7, d7, dx7, e7, f7, fx7, g7, gx7, a7, ax7, b7:
319 | 7
320 |
321 | proc getOctave(ctx: Context, relOctave: RelativeOctave): range[1..7] =
322 | case relOctave:
323 | of `+1`: ctx.octave + 1
324 | of `+2`: ctx.octave + 2
325 | of `+3`: ctx.octave + 3
326 | of `+4`: ctx.octave + 4
327 | of `+5`: ctx.octave + 5
328 | of `+6`: ctx.octave + 6
329 | of `-1`: ctx.octave - 1
330 | of `-2`: ctx.octave - 2
331 | of `-3`: ctx.octave - 3
332 | of `-4`: ctx.octave - 4
333 | of `-5`: ctx.octave - 5
334 | of `-6`: ctx.octave - 6
335 |
336 | proc `+`*(note: Note): Note =
337 | case note:
338 | of c: `c+`
339 | of cx: `cx+`
340 | of d: `d+`
341 | of dx: `dx+`
342 | of e: `e+`
343 | of f: `f+`
344 | of fx: `fx+`
345 | of g: `g+`
346 | of gx: `gx+`
347 | of a: `a+`
348 | of ax: `ax+`
349 | of b: `b+`
350 | else:
351 | raise newException(Exception, $ note & " note cannot use +")
352 |
353 | proc `-`*(note: Note): Note =
354 | case note:
355 | of c: `c-`
356 | of cx: `cx-`
357 | of d: `d-`
358 | of dx: `dx-`
359 | of e: `e-`
360 | of f: `f-`
361 | of fx: `fx-`
362 | of g: `g-`
363 | of gx: `gx-`
364 | of a: `a-`
365 | of ax: `ax-`
366 | of b: `b-`
367 | else:
368 | raise newException(Exception, $ note & " note cannot use -")
369 |
370 | proc `+`*(note: Note, octave: range[1..6]): Note =
371 | case note:
372 | of c:
373 | case octave:
374 | of 1: `c+1`
375 | of 2: `c+2`
376 | of 3: `c+3`
377 | of 4: `c+4`
378 | of 5: `c+5`
379 | of 6: `c+6`
380 | of cx:
381 | case octave:
382 | of 1: `cx+1`
383 | of 2: `cx+2`
384 | of 3: `cx+3`
385 | of 4: `cx+4`
386 | of 5: `cx+5`
387 | of 6: `cx+6`
388 | of d:
389 | case octave:
390 | of 1: `d+1`
391 | of 2: `d+2`
392 | of 3: `d+3`
393 | of 4: `d+4`
394 | of 5: `d+5`
395 | of 6: `d+6`
396 | of dx:
397 | case octave:
398 | of 1: `dx+1`
399 | of 2: `dx+2`
400 | of 3: `dx+3`
401 | of 4: `dx+4`
402 | of 5: `dx+5`
403 | of 6: `dx+6`
404 | of e:
405 | case octave:
406 | of 1: `e+1`
407 | of 2: `e+2`
408 | of 3: `e+3`
409 | of 4: `e+4`
410 | of 5: `e+5`
411 | of 6: `e+6`
412 | of f:
413 | case octave:
414 | of 1: `f+1`
415 | of 2: `f+2`
416 | of 3: `f+3`
417 | of 4: `f+4`
418 | of 5: `f+5`
419 | of 6: `f+6`
420 | of fx:
421 | case octave:
422 | of 1: `fx+1`
423 | of 2: `fx+2`
424 | of 3: `fx+3`
425 | of 4: `fx+4`
426 | of 5: `fx+5`
427 | of 6: `fx+6`
428 | of g:
429 | case octave:
430 | of 1: `g+1`
431 | of 2: `g+2`
432 | of 3: `g+3`
433 | of 4: `g+4`
434 | of 5: `g+5`
435 | of 6: `g+6`
436 | of gx:
437 | case octave:
438 | of 1: `gx+1`
439 | of 2: `gx+2`
440 | of 3: `gx+3`
441 | of 4: `gx+4`
442 | of 5: `gx+5`
443 | of 6: `gx+6`
444 | of a:
445 | case octave:
446 | of 1: `a+1`
447 | of 2: `a+2`
448 | of 3: `a+3`
449 | of 4: `a+4`
450 | of 5: `a+5`
451 | of 6: `a+6`
452 | of ax:
453 | case octave:
454 | of 1: `ax+1`
455 | of 2: `ax+2`
456 | of 3: `ax+3`
457 | of 4: `ax+4`
458 | of 5: `ax+5`
459 | of 6: `ax+6`
460 | of b:
461 | case octave:
462 | of 1: `b+1`
463 | of 2: `b+2`
464 | of 3: `b+3`
465 | of 4: `b+4`
466 | of 5: `b+5`
467 | of 6: `b+6`
468 | else:
469 | raise newException(Exception, $ note & " note cannot use +")
470 |
471 | proc `-`*(note: Note, octave: range[1..6]): Note =
472 | case note:
473 | of c:
474 | case octave:
475 | of 1: `c-1`
476 | of 2: `c-2`
477 | of 3: `c-3`
478 | of 4: `c-4`
479 | of 5: `c-5`
480 | of 6: `c-6`
481 | of cx:
482 | case octave:
483 | of 1: `cx-1`
484 | of 2: `cx-2`
485 | of 3: `cx-3`
486 | of 4: `cx-4`
487 | of 5: `cx-5`
488 | of 6: `cx-6`
489 | of d:
490 | case octave:
491 | of 1: `d-1`
492 | of 2: `d-2`
493 | of 3: `d-3`
494 | of 4: `d-4`
495 | of 5: `d-5`
496 | of 6: `d-6`
497 | of dx:
498 | case octave:
499 | of 1: `dx-1`
500 | of 2: `dx-2`
501 | of 3: `dx-3`
502 | of 4: `dx-4`
503 | of 5: `dx-5`
504 | of 6: `dx-6`
505 | of e:
506 | case octave:
507 | of 1: `e-1`
508 | of 2: `e-2`
509 | of 3: `e-3`
510 | of 4: `e-4`
511 | of 5: `e-5`
512 | of 6: `e-6`
513 | of f:
514 | case octave:
515 | of 1: `f-1`
516 | of 2: `f-2`
517 | of 3: `f-3`
518 | of 4: `f-4`
519 | of 5: `f-5`
520 | of 6: `f-6`
521 | of fx:
522 | case octave:
523 | of 1: `fx-1`
524 | of 2: `fx-2`
525 | of 3: `fx-3`
526 | of 4: `fx-4`
527 | of 5: `fx-5`
528 | of 6: `fx-6`
529 | of g:
530 | case octave:
531 | of 1: `g-1`
532 | of 2: `g-2`
533 | of 3: `g-3`
534 | of 4: `g-4`
535 | of 5: `g-5`
536 | of 6: `g-6`
537 | of gx:
538 | case octave:
539 | of 1: `gx-1`
540 | of 2: `gx-2`
541 | of 3: `gx-3`
542 | of 4: `gx-4`
543 | of 5: `gx-5`
544 | of 6: `gx-6`
545 | of a:
546 | case octave:
547 | of 1: `a-1`
548 | of 2: `a-2`
549 | of 3: `a-3`
550 | of 4: `a-4`
551 | of 5: `a-5`
552 | of 6: `a-6`
553 | of ax:
554 | case octave:
555 | of 1: `ax-1`
556 | of 2: `ax-2`
557 | of 3: `ax-3`
558 | of 4: `ax-4`
559 | of 5: `ax-5`
560 | of 6: `ax-6`
561 | of b:
562 | case octave:
563 | of 1: `b-1`
564 | of 2: `b-2`
565 | of 3: `b-3`
566 | of 4: `b-4`
567 | of 5: `b-5`
568 | of 6: `b-6`
569 | else:
570 | raise newException(Exception, $ note & " note cannot use -")
571 |
572 | proc compileContent(ctx: var Context, note: Note) =
573 | if not ctx.play:
574 | return
575 | if ctx.instrument == none and note != r:
576 | raise newException(Exception, $ note & " note cannot be played without an instrument")
577 | let
578 | realNote = getNote(note)
579 | octave = getOctave(ctx, note)
580 | ctx.events[].add(Event(
581 | kind: On,
582 | note: realNote,
583 | time: ctx.time,
584 | instrument: ctx.instrument,
585 | octave: octave,
586 | tempo: ctx.tempo,
587 | volume: ctx.volume
588 | ))
589 | ctx.events[].add(Event(
590 | kind: Off,
591 | note: realNote,
592 | time: ctx.time + ctx.length,
593 | instrument: ctx.instrument,
594 | octave: octave,
595 | tempo: ctx.tempo,
596 | volume: ctx.volume
597 | ))
598 | ctx.time += ctx.length
599 |
600 | proc compileContent(ctx: var Context, chord: set[Note]) =
601 | if not ctx.play:
602 | return
603 | let time = ctx.time
604 | for note in chord:
605 | compileContent(ctx, note)
606 | ctx.time = time
607 | ctx.time += ctx.length
608 |
609 | proc compileContent(ctx: var Context, instrument: Instrument) =
610 | ctx.instrument = instrument
611 |
612 | proc setLength(ctx: var Context, length: float) =
613 | ctx.length = length
614 |
615 | proc setLength(ctx: var Context, length: int) =
616 | ctx.length = length.float
617 |
618 | proc setLength(ctx: var Context, length: BiggestInt) =
619 | ctx.length = length.float
620 |
621 | proc compileContent(ctx: var Context, length: float | int | BiggestInt) =
622 | setLength(ctx, length)
623 |
624 | proc compileContent(ctx: var Context, content: tuple) =
625 | var
626 | temp = ctx
627 | concurrent = false
628 | longestTime = ctx.time
629 | for k, v in content.fieldPairs:
630 | when k == "length":
631 | setLength(ctx, v)
632 | elif k == "octave":
633 | when v is RelativeOctave:
634 | ctx.octave = getOctave(ctx, v)
635 | else:
636 | ctx.octave = v
637 | elif k == "play":
638 | ctx.play = v
639 | elif k == "mode":
640 | ctx.mode = v
641 | elif k == "tempo":
642 | ctx.tempo = v
643 | elif k == "volume":
644 | ctx.volume = v
645 | else:
646 | let mode = temp.mode
647 | compileContent(temp, v)
648 | if mode != temp.mode and temp.mode == Mode.concurrent:
649 | concurrent = true
650 | temp.mode = Mode.sequential
651 | if concurrent:
652 | if temp.time > longestTime:
653 | longestTime = temp.time
654 | temp.time = ctx.time
655 | if concurrent:
656 | ctx.time = longestTime
657 | else:
658 | ctx.time = temp.time
659 |
660 | proc compileContent(ctx: var Context, node: JsonNode) =
661 | case node.kind:
662 | of JString:
663 | if constants.noteSet.contains(node.str):
664 | compileContent(ctx, Note(constants.notes.find(node.str)))
665 | elif constants.instruments.contains(node.str):
666 | compileContent(ctx, Instrument(constants.instruments.find(node.str)))
667 | else:
668 | raise newException(Exception, "Invalid value: " & $node & " (expected a note or instrument)")
669 | of JInt:
670 | compileContent(ctx, node.num)
671 | of JFloat:
672 | compileContent(ctx, node.fnum)
673 | of JBool, JNull:
674 | raise newException(Exception, "Invalid value: " & $node)
675 | of JObject:
676 | for k, v in node:
677 | if k == "length":
678 | case v.kind:
679 | of JInt:
680 | setLength(ctx, v.num)
681 | of JFloat:
682 | setLength(ctx, v.fnum)
683 | else:
684 | raise newException(Exception, "Invalid length: " & $v)
685 | elif k == "octave":
686 | if v.kind == JInt:
687 | ctx.octave = v.num
688 | elif v.kind == JString:
689 | if constants.relativeOctaveSet.contains(v.str):
690 | ctx.octave = getOctave(ctx, RelativeOctave(constants.relativeOctaves.find(v.str)))
691 | else:
692 | raise newException(Exception, "Invalid relative octave: " & $v)
693 | else:
694 | raise newException(Exception, "Invalid octave: " & $v)
695 | elif k == "play":
696 | if v.kind == JBool:
697 | ctx.play = v.bval
698 | else:
699 | raise newException(Exception, "Invalid play: " & $v)
700 | elif k == "mode":
701 | if v.kind == JString and constants.modeSet.contains(v.str):
702 | ctx.mode = Mode(constants.modes.find(v.str))
703 | else:
704 | raise newException(Exception, "Invalid mode: " & $v)
705 | elif k == "tempo":
706 | if v.kind == JInt:
707 | ctx.tempo = v.num.int
708 | else:
709 | raise newException(Exception, "Invalid tempo: " & $v)
710 | elif k == "volume":
711 | if v.kind == JInt:
712 | ctx.volume = v.num.int
713 | else:
714 | raise newException(Exception, "Invalid volume: " & $v)
715 | else:
716 | raise newException(Exception, "Invalid attribute: " & k)
717 | of JArray:
718 | var
719 | temp = ctx
720 | concurrent = false
721 | longestTime = ctx.time
722 | for item in node:
723 | let mode = temp.mode
724 | compileContent(temp, item)
725 | if mode != temp.mode and temp.mode == Mode.concurrent:
726 | concurrent = true
727 | temp.mode = Mode.sequential
728 | if concurrent:
729 | if temp.time > longestTime:
730 | longestTime = temp.time
731 | temp.time = ctx.time
732 | if concurrent:
733 | ctx.time = longestTime
734 | else:
735 | ctx.time = temp.time
736 |
737 | proc initContext*(): Context =
738 | result = Context(
739 | time: 0,
740 | instrument: none,
741 | octave: 4,
742 | length: 1/4,
743 | play: true,
744 | mode: sequential,
745 | tempo: 120,
746 | volume: 100
747 | )
748 | new result.events
749 |
750 | proc compile*(ctx: var Context, content: tuple | JsonNode): seq[Event] =
751 | compileContent(ctx, content)
752 | result = ctx.events[]
753 | algorithm.sort(result, proc (x, y: Event): int =
754 | if x.time < y.time:
755 | -1
756 | elif x.time > y.time:
757 | 1
758 | else:
759 | 0
760 | )
761 | # calculate the total time in seconds
762 | var lastTime = 0.0
763 | ctx.seconds = 0
764 | const
765 | minuteSecs = 60
766 | quarterNote = 1/4
767 | for event in result:
768 | case event.kind:
769 | of On:
770 | discard
771 | of Off:
772 | let
773 | noteLength = event.time - lastTime
774 | noteLengthSeconds = (minuteSecs / event.tempo) * (noteLength / quarterNote)
775 | lastTime = event.time
776 | ctx.seconds += noteLengthSeconds
777 |
778 | proc compile*(content: tuple | JsonNode): seq[Event] =
779 | var ctx = initContext()
780 | compile(ctx, content)
781 |
782 | proc render*[T: cshort](events: seq[Event], soundFont: ptr tsf, sampleRate: int): RenderResult[T] =
783 | var lastTime = 0.0
784 | const
785 | scaleCount = 12
786 | minuteSecs = 60
787 | quarterNote = 1/4
788 | for event in events:
789 | let note = cint(event.note.ord + event.octave.ord * scaleCount + scaleCount)
790 | tsf_set_volume(soundFont, cfloat event.volume / 100)
791 | case event.kind:
792 | of On:
793 | if event.note != r:
794 | tsf_note_on(soundFont, event.instrument.ord.cint, note, 1.0f)
795 | of Off:
796 | let
797 | currentSize = result.data.len
798 | noteLength = event.time - lastTime
799 | noteLengthSeconds = (minuteSecs / event.tempo) * (noteLength / quarterNote)
800 | numSamples = sampleRate.float * noteLengthSeconds
801 | newSize = currentSize + numSamples.int
802 | if noteLength > 0:
803 | result.data.setLen(newSize)
804 | when T is cshort:
805 | tsf_render_short(soundFont, result.data[currentSize].addr, numSamples.cint, 0)
806 | if event.note != r:
807 | tsf_note_off(soundFont, event.instrument.ord.cint, note)
808 | lastTime = event.time
809 | result.seconds += noteLengthSeconds
810 |
--------------------------------------------------------------------------------
/src/paramidi/tsf.h:
--------------------------------------------------------------------------------
1 | /* TinySoundFont - v0.9 - SoundFont2 synthesizer - https://github.com/schellingb/TinySoundFont
2 | no warranty implied; use at your own risk
3 | Do this:
4 | #define TSF_IMPLEMENTATION
5 | before you include this file in *one* C or C++ file to create the implementation.
6 | // i.e. it should look like this:
7 | #include ...
8 | #include ...
9 | #define TSF_IMPLEMENTATION
10 | #include "tsf.h"
11 |
12 | [OPTIONAL] #define TSF_NO_STDIO to remove stdio dependency
13 | [OPTIONAL] #define TSF_MALLOC, TSF_REALLOC, and TSF_FREE to avoid stdlib.h
14 | [OPTIONAL] #define TSF_MEMCPY, TSF_MEMSET to avoid string.h
15 | [OPTIONAL] #define TSF_POW, TSF_POWF, TSF_EXPF, TSF_LOG, TSF_TAN, TSF_LOG10, TSF_SQRT to avoid math.h
16 |
17 | NOT YET IMPLEMENTED
18 | - Support for ChorusEffectsSend and ReverbEffectsSend generators
19 | - Better low-pass filter without lowering performance too much
20 | - Support for modulators
21 |
22 | LICENSE (MIT)
23 |
24 | Copyright (C) 2017, 2018 Bernhard Schelling
25 | Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero)
26 |
27 | Permission is hereby granted, free of charge, to any person obtaining a copy of this
28 | software and associated documentation files (the "Software"), to deal in the Software
29 | without restriction, including without limitation the rights to use, copy, modify, merge,
30 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
31 | to whom the Software is furnished to do so, subject to the following conditions:
32 |
33 | The above copyright notice and this permission notice shall be included in all
34 | copies or substantial portions of the Software.
35 |
36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
37 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
38 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
39 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
40 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
41 | USE OR OTHER DEALINGS IN THE SOFTWARE.
42 |
43 | */
44 |
45 | #ifndef TSF_INCLUDE_TSF_INL
46 | #define TSF_INCLUDE_TSF_INL
47 |
48 | #ifdef __cplusplus
49 | extern "C" {
50 | # define CPP_DEFAULT0 = 0
51 | #else
52 | # define CPP_DEFAULT0
53 | #endif
54 |
55 | //define this if you want the API functions to be static
56 | #ifdef TSF_STATIC
57 | #define TSFDEF static
58 | #else
59 | #define TSFDEF extern
60 | #endif
61 |
62 | // The load functions will return a pointer to a struct tsf which all functions
63 | // thereafter take as the first parameter.
64 | // On error the tsf_load* functions will return NULL most likely due to invalid
65 | // data (or if the file did not exist in tsf_load_filename).
66 | typedef struct tsf tsf;
67 |
68 | #ifndef TSF_NO_STDIO
69 | // Directly load a SoundFont from a .sf2 file path
70 | TSFDEF tsf* tsf_load_filename(const char* filename);
71 | #endif
72 |
73 | // Load a SoundFont from a block of memory
74 | TSFDEF tsf* tsf_load_memory(const void* buffer, int size);
75 |
76 | // Stream structure for the generic loading
77 | struct tsf_stream
78 | {
79 | // Custom data given to the functions as the first parameter
80 | void* data;
81 |
82 | // Function pointer will be called to read 'size' bytes into ptr (returns number of read bytes)
83 | int (*read)(void* data, void* ptr, unsigned int size);
84 |
85 | // Function pointer will be called to skip ahead over 'count' bytes (returns 1 on success, 0 on error)
86 | int (*skip)(void* data, unsigned int count);
87 | };
88 |
89 | // Generic SoundFont loading method using the stream structure above
90 | TSFDEF tsf* tsf_load(struct tsf_stream* stream);
91 |
92 | // Free the memory related to this tsf instance
93 | TSFDEF void tsf_close(tsf* f);
94 |
95 | // Stop all playing notes immediatly and reset all channel parameters
96 | TSFDEF void tsf_reset(tsf* f);
97 |
98 | // Returns the preset index from a bank and preset number, or -1 if it does not exist in the loaded SoundFont
99 | TSFDEF int tsf_get_presetindex(const tsf* f, int bank, int preset_number);
100 |
101 | // Returns the number of presets in the loaded SoundFont
102 | TSFDEF int tsf_get_presetcount(const tsf* f);
103 |
104 | // Returns the name of a preset index >= 0 and < tsf_get_presetcount()
105 | TSFDEF const char* tsf_get_presetname(const tsf* f, int preset_index);
106 |
107 | // Returns the name of a preset by bank and preset number
108 | TSFDEF const char* tsf_bank_get_presetname(const tsf* f, int bank, int preset_number);
109 |
110 | // Supported output modes by the render methods
111 | enum TSFOutputMode
112 | {
113 | // Two channels with single left/right samples one after another
114 | TSF_STEREO_INTERLEAVED,
115 | // Two channels with all samples for the left channel first then right
116 | TSF_STEREO_UNWEAVED,
117 | // A single channel (stereo instruments are mixed into center)
118 | TSF_MONO,
119 | };
120 |
121 | // Thread safety:
122 | // Your audio output which calls the tsf_render* functions will most likely
123 | // run on a different thread than where the playback tsf_note* functions
124 | // are called. In which case some sort of concurrency control like a
125 | // mutex needs to be used so they are not called at the same time.
126 | // Alternatively, you can pre-allocate a maximum number of voices that can
127 | // play simultaneously by calling tsf_set_max_voices after loading.
128 | // That way memory re-allocation will not happen during tsf_note_on and
129 | // TSF should become mostly thread safe.
130 | // There is a theoretical chance that ending notes would negatively influence
131 | // a voice that is rendering at the time but it is hard to say.
132 | // Also be aware, this has not been tested much.
133 |
134 | // Setup the parameters for the voice render methods
135 | // outputmode: if mono or stereo and how stereo channel data is ordered
136 | // samplerate: the number of samples per second (output frequency)
137 | // global_gain_db: volume gain in decibels (>0 means higher, <0 means lower)
138 | TSFDEF void tsf_set_output(tsf* f, enum TSFOutputMode outputmode, int samplerate, float global_gain_db CPP_DEFAULT0);
139 |
140 | // Set the global gain as a volume factor
141 | // global_gain: the desired volume where 1.0 is 100%
142 | TSFDEF void tsf_set_volume(tsf* f, float global_gain);
143 |
144 | // Set the maximum number of voices to play simultaneously
145 | // Depending on the soundfond, one note can cause many new voices to be started,
146 | // so don't keep this number too low or otherwise sounds may not play.
147 | // max_voices: maximum number to pre-allocate and set the limit to
148 | TSFDEF void tsf_set_max_voices(tsf* f, int max_voices);
149 |
150 | // Start playing a note
151 | // preset_index: preset index >= 0 and < tsf_get_presetcount()
152 | // key: note value between 0 and 127 (60 being middle C)
153 | // vel: velocity as a float between 0.0 (equal to note off) and 1.0 (full)
154 | // bank: instrument bank number (alternative to preset_index)
155 | // preset_number: preset number (alternative to preset_index)
156 | // (bank_note_on returns 0 if preset does not exist, otherwise 1)
157 | TSFDEF void tsf_note_on(tsf* f, int preset_index, int key, float vel);
158 | TSFDEF int tsf_bank_note_on(tsf* f, int bank, int preset_number, int key, float vel);
159 |
160 | // Stop playing a note
161 | // (bank_note_off returns 0 if preset does not exist, otherwise 1)
162 | TSFDEF void tsf_note_off(tsf* f, int preset_index, int key);
163 | TSFDEF int tsf_bank_note_off(tsf* f, int bank, int preset_number, int key);
164 |
165 | // Stop playing all notes (end with sustain and release)
166 | TSFDEF void tsf_note_off_all(tsf* f);
167 |
168 | // Returns the number of active voices
169 | TSFDEF int tsf_active_voice_count(tsf* f);
170 |
171 | // Render output samples into a buffer
172 | // You can either render as signed 16-bit values (tsf_render_short) or
173 | // as 32-bit float values (tsf_render_float)
174 | // buffer: target buffer of size samples * output_channels * sizeof(type)
175 | // samples: number of samples to render
176 | // flag_mixing: if 0 clear the buffer first, otherwise mix into existing data
177 | TSFDEF void tsf_render_short(tsf* f, short* buffer, int samples, int flag_mixing CPP_DEFAULT0);
178 | TSFDEF void tsf_render_float(tsf* f, float* buffer, int samples, int flag_mixing CPP_DEFAULT0);
179 |
180 | // Higher level channel based functions, set up channel parameters
181 | // channel: channel number
182 | // preset_index: preset index >= 0 and < tsf_get_presetcount()
183 | // preset_number: preset number (alternative to preset_index)
184 | // flag_mididrums: 0 for normal channels, otherwise apply MIDI drum channel rules
185 | // bank: instrument bank number (alternative to preset_index)
186 | // pan: stereo panning value from 0.0 (left) to 1.0 (right) (default 0.5 center)
187 | // volume: linear volume scale factor (default 1.0 full)
188 | // pitch_wheel: pitch wheel position 0 to 16383 (default 8192 unpitched)
189 | // pitch_range: range of the pitch wheel in semitones (default 2.0, total +/- 2 semitones)
190 | // tuning: tuning of all playing voices in semitones (default 0.0, standard (A440) tuning)
191 | // (set_preset_number and set_bank_preset return 0 if preset does not exist, otherwise 1)
192 | TSFDEF void tsf_channel_set_presetindex(tsf* f, int channel, int preset_index);
193 | TSFDEF int tsf_channel_set_presetnumber(tsf* f, int channel, int preset_number, int flag_mididrums CPP_DEFAULT0);
194 | TSFDEF void tsf_channel_set_bank(tsf* f, int channel, int bank);
195 | TSFDEF int tsf_channel_set_bank_preset(tsf* f, int channel, int bank, int preset_number);
196 | TSFDEF void tsf_channel_set_pan(tsf* f, int channel, float pan);
197 | TSFDEF void tsf_channel_set_volume(tsf* f, int channel, float volume);
198 | TSFDEF void tsf_channel_set_pitchwheel(tsf* f, int channel, int pitch_wheel);
199 | TSFDEF void tsf_channel_set_pitchrange(tsf* f, int channel, float pitch_range);
200 | TSFDEF void tsf_channel_set_tuning(tsf* f, int channel, float tuning);
201 |
202 | // Start or stop playing notes on a channel (needs channel preset to be set)
203 | // channel: channel number
204 | // key: note value between 0 and 127 (60 being middle C)
205 | // vel: velocity as a float between 0.0 (equal to note off) and 1.0 (full)
206 | TSFDEF void tsf_channel_note_on(tsf* f, int channel, int key, float vel);
207 | TSFDEF void tsf_channel_note_off(tsf* f, int channel, int key);
208 | TSFDEF void tsf_channel_note_off_all(tsf* f, int channel); //end with sustain and release
209 | TSFDEF void tsf_channel_sounds_off_all(tsf* f, int channel); //end immediatly
210 |
211 | // Apply a MIDI control change to the channel (not all controllers are supported!)
212 | TSFDEF void tsf_channel_midi_control(tsf* f, int channel, int controller, int control_value);
213 |
214 | // Get current values set on the channels
215 | TSFDEF int tsf_channel_get_preset_index(tsf* f, int channel);
216 | TSFDEF int tsf_channel_get_preset_bank(tsf* f, int channel);
217 | TSFDEF int tsf_channel_get_preset_number(tsf* f, int channel);
218 | TSFDEF float tsf_channel_get_pan(tsf* f, int channel);
219 | TSFDEF float tsf_channel_get_volume(tsf* f, int channel);
220 | TSFDEF int tsf_channel_get_pitchwheel(tsf* f, int channel);
221 | TSFDEF float tsf_channel_get_pitchrange(tsf* f, int channel);
222 | TSFDEF float tsf_channel_get_tuning(tsf* f, int channel);
223 |
224 | #ifdef __cplusplus
225 | # undef CPP_DEFAULT0
226 | }
227 | #endif
228 |
229 | // end header
230 | // ---------------------------------------------------------------------------------------------------------
231 | #endif //TSF_INCLUDE_TSF_INL
232 |
233 | #ifdef TSF_IMPLEMENTATION
234 | #undef TSF_IMPLEMENTATION
235 |
236 | // The lower this block size is the more accurate the effects are.
237 | // Increasing the value significantly lowers the CPU usage of the voice rendering.
238 | // If LFO affects the low-pass filter it can be hearable even as low as 8.
239 | #ifndef TSF_RENDER_EFFECTSAMPLEBLOCK
240 | #define TSF_RENDER_EFFECTSAMPLEBLOCK 64
241 | #endif
242 |
243 | // Grace release time for quick voice off (avoid clicking noise)
244 | #define TSF_FASTRELEASETIME 0.01f
245 |
246 | #if !defined(TSF_MALLOC) || !defined(TSF_FREE) || !defined(TSF_REALLOC)
247 | # include
248 | # define TSF_MALLOC malloc
249 | # define TSF_FREE free
250 | # define TSF_REALLOC realloc
251 | #endif
252 |
253 | #if !defined(TSF_MEMCPY) || !defined(TSF_MEMSET)
254 | # include
255 | # define TSF_MEMCPY memcpy
256 | # define TSF_MEMSET memset
257 | #endif
258 |
259 | #if !defined(TSF_POW) || !defined(TSF_POWF) || !defined(TSF_EXPF) || !defined(TSF_LOG) || !defined(TSF_TAN) || !defined(TSF_LOG10) || !defined(TSF_SQRT)
260 | # include
261 | # if !defined(__cplusplus) && !defined(NAN) && !defined(powf) && !defined(expf) && !defined(sqrtf)
262 | # define powf (float)pow // deal with old math.h
263 | # define expf (float)exp // files that come without
264 | # define sqrtf (float)sqrt // powf, expf and sqrtf
265 | # endif
266 | # define TSF_POW pow
267 | # define TSF_POWF powf
268 | # define TSF_EXPF expf
269 | # define TSF_LOG log
270 | # define TSF_TAN tan
271 | # define TSF_LOG10 log10
272 | # define TSF_SQRTF sqrtf
273 | #endif
274 |
275 | #ifndef TSF_NO_STDIO
276 | # include
277 | #endif
278 |
279 | #define TSF_TRUE 1
280 | #define TSF_FALSE 0
281 | #define TSF_BOOL char
282 | #define TSF_PI 3.14159265358979323846264338327950288
283 | #define TSF_NULL 0
284 |
285 | #ifdef __cplusplus
286 | extern "C" {
287 | #endif
288 |
289 | typedef char tsf_fourcc[4];
290 | typedef signed char tsf_s8;
291 | typedef unsigned char tsf_u8;
292 | typedef unsigned short tsf_u16;
293 | typedef signed short tsf_s16;
294 | typedef unsigned int tsf_u32;
295 | typedef char tsf_char20[20];
296 |
297 | #define TSF_FourCCEquals(value1, value2) (value1[0] == value2[0] && value1[1] == value2[1] && value1[2] == value2[2] && value1[3] == value2[3])
298 |
299 | struct tsf
300 | {
301 | struct tsf_preset* presets;
302 | float* fontSamples;
303 | struct tsf_voice* voices;
304 | struct tsf_channels* channels;
305 | float* outputSamples;
306 |
307 | int presetNum;
308 | int voiceNum;
309 | int maxVoiceNum;
310 | int outputSampleSize;
311 | unsigned int voicePlayIndex;
312 |
313 | enum TSFOutputMode outputmode;
314 | float outSampleRate;
315 | float globalGainDB;
316 | };
317 |
318 | #ifndef TSF_NO_STDIO
319 | static int tsf_stream_stdio_read(FILE* f, void* ptr, unsigned int size) { return (int)fread(ptr, 1, size, f); }
320 | static int tsf_stream_stdio_skip(FILE* f, unsigned int count) { return !fseek(f, count, SEEK_CUR); }
321 | TSFDEF tsf* tsf_load_filename(const char* filename)
322 | {
323 | tsf* res;
324 | struct tsf_stream stream = { TSF_NULL, (int(*)(void*,void*,unsigned int))&tsf_stream_stdio_read, (int(*)(void*,unsigned int))&tsf_stream_stdio_skip };
325 | #if __STDC_WANT_SECURE_LIB__
326 | FILE* f = TSF_NULL; fopen_s(&f, filename, "rb");
327 | #else
328 | FILE* f = fopen(filename, "rb");
329 | #endif
330 | if (!f)
331 | {
332 | //if (e) *e = TSF_FILENOTFOUND;
333 | return TSF_NULL;
334 | }
335 | stream.data = f;
336 | res = tsf_load(&stream);
337 | fclose(f);
338 | return res;
339 | }
340 | #endif
341 |
342 | struct tsf_stream_memory { const char* buffer; unsigned int total, pos; };
343 | static int tsf_stream_memory_read(struct tsf_stream_memory* m, void* ptr, unsigned int size) { if (size > m->total - m->pos) size = m->total - m->pos; TSF_MEMCPY(ptr, m->buffer+m->pos, size); m->pos += size; return size; }
344 | static int tsf_stream_memory_skip(struct tsf_stream_memory* m, unsigned int count) { if (m->pos + count > m->total) return 0; m->pos += count; return 1; }
345 | TSFDEF tsf* tsf_load_memory(const void* buffer, int size)
346 | {
347 | struct tsf_stream stream = { TSF_NULL, (int(*)(void*,void*,unsigned int))&tsf_stream_memory_read, (int(*)(void*,unsigned int))&tsf_stream_memory_skip };
348 | struct tsf_stream_memory f = { 0, 0, 0 };
349 | f.buffer = (const char*)buffer;
350 | f.total = size;
351 | stream.data = &f;
352 | return tsf_load(&stream);
353 | }
354 |
355 | enum { TSF_LOOPMODE_NONE, TSF_LOOPMODE_CONTINUOUS, TSF_LOOPMODE_SUSTAIN };
356 |
357 | enum { TSF_SEGMENT_NONE, TSF_SEGMENT_DELAY, TSF_SEGMENT_ATTACK, TSF_SEGMENT_HOLD, TSF_SEGMENT_DECAY, TSF_SEGMENT_SUSTAIN, TSF_SEGMENT_RELEASE, TSF_SEGMENT_DONE };
358 |
359 | struct tsf_hydra
360 | {
361 | struct tsf_hydra_phdr *phdrs; struct tsf_hydra_pbag *pbags; struct tsf_hydra_pmod *pmods;
362 | struct tsf_hydra_pgen *pgens; struct tsf_hydra_inst *insts; struct tsf_hydra_ibag *ibags;
363 | struct tsf_hydra_imod *imods; struct tsf_hydra_igen *igens; struct tsf_hydra_shdr *shdrs;
364 | int phdrNum, pbagNum, pmodNum, pgenNum, instNum, ibagNum, imodNum, igenNum, shdrNum;
365 | };
366 |
367 | union tsf_hydra_genamount { struct { tsf_u8 lo, hi; } range; tsf_s16 shortAmount; tsf_u16 wordAmount; };
368 | struct tsf_hydra_phdr { tsf_char20 presetName; tsf_u16 preset, bank, presetBagNdx; tsf_u32 library, genre, morphology; };
369 | struct tsf_hydra_pbag { tsf_u16 genNdx, modNdx; };
370 | struct tsf_hydra_pmod { tsf_u16 modSrcOper, modDestOper; tsf_s16 modAmount; tsf_u16 modAmtSrcOper, modTransOper; };
371 | struct tsf_hydra_pgen { tsf_u16 genOper; union tsf_hydra_genamount genAmount; };
372 | struct tsf_hydra_inst { tsf_char20 instName; tsf_u16 instBagNdx; };
373 | struct tsf_hydra_ibag { tsf_u16 instGenNdx, instModNdx; };
374 | struct tsf_hydra_imod { tsf_u16 modSrcOper, modDestOper; tsf_s16 modAmount; tsf_u16 modAmtSrcOper, modTransOper; };
375 | struct tsf_hydra_igen { tsf_u16 genOper; union tsf_hydra_genamount genAmount; };
376 | struct tsf_hydra_shdr { tsf_char20 sampleName; tsf_u32 start, end, startLoop, endLoop, sampleRate; tsf_u8 originalPitch; tsf_s8 pitchCorrection; tsf_u16 sampleLink, sampleType; };
377 |
378 | #define TSFR(FIELD) stream->read(stream->data, &i->FIELD, sizeof(i->FIELD));
379 | static void tsf_hydra_read_phdr(struct tsf_hydra_phdr* i, struct tsf_stream* stream) { TSFR(presetName) TSFR(preset) TSFR(bank) TSFR(presetBagNdx) TSFR(library) TSFR(genre) TSFR(morphology) }
380 | static void tsf_hydra_read_pbag(struct tsf_hydra_pbag* i, struct tsf_stream* stream) { TSFR(genNdx) TSFR(modNdx) }
381 | static void tsf_hydra_read_pmod(struct tsf_hydra_pmod* i, struct tsf_stream* stream) { TSFR(modSrcOper) TSFR(modDestOper) TSFR(modAmount) TSFR(modAmtSrcOper) TSFR(modTransOper) }
382 | static void tsf_hydra_read_pgen(struct tsf_hydra_pgen* i, struct tsf_stream* stream) { TSFR(genOper) TSFR(genAmount) }
383 | static void tsf_hydra_read_inst(struct tsf_hydra_inst* i, struct tsf_stream* stream) { TSFR(instName) TSFR(instBagNdx) }
384 | static void tsf_hydra_read_ibag(struct tsf_hydra_ibag* i, struct tsf_stream* stream) { TSFR(instGenNdx) TSFR(instModNdx) }
385 | static void tsf_hydra_read_imod(struct tsf_hydra_imod* i, struct tsf_stream* stream) { TSFR(modSrcOper) TSFR(modDestOper) TSFR(modAmount) TSFR(modAmtSrcOper) TSFR(modTransOper) }
386 | static void tsf_hydra_read_igen(struct tsf_hydra_igen* i, struct tsf_stream* stream) { TSFR(genOper) TSFR(genAmount) }
387 | static void tsf_hydra_read_shdr(struct tsf_hydra_shdr* i, struct tsf_stream* stream) { TSFR(sampleName) TSFR(start) TSFR(end) TSFR(startLoop) TSFR(endLoop) TSFR(sampleRate) TSFR(originalPitch) TSFR(pitchCorrection) TSFR(sampleLink) TSFR(sampleType) }
388 | #undef TSFR
389 |
390 | struct tsf_riffchunk { tsf_fourcc id; tsf_u32 size; };
391 | struct tsf_envelope { float delay, attack, hold, decay, sustain, release, keynumToHold, keynumToDecay; };
392 | struct tsf_voice_envelope { float level, slope; int samplesUntilNextSegment; short segment, midiVelocity; struct tsf_envelope parameters; TSF_BOOL segmentIsExponential, isAmpEnv; };
393 | struct tsf_voice_lowpass { double QInv, a0, a1, b1, b2, z1, z2; TSF_BOOL active; };
394 | struct tsf_voice_lfo { int samplesUntil; float level, delta; };
395 |
396 | struct tsf_region
397 | {
398 | int loop_mode;
399 | unsigned int sample_rate;
400 | unsigned char lokey, hikey, lovel, hivel;
401 | unsigned int group, offset, end, loop_start, loop_end;
402 | int transpose, tune, pitch_keycenter, pitch_keytrack;
403 | float attenuation, pan;
404 | struct tsf_envelope ampenv, modenv;
405 | int initialFilterQ, initialFilterFc;
406 | int modEnvToPitch, modEnvToFilterFc, modLfoToFilterFc, modLfoToVolume;
407 | float delayModLFO;
408 | int freqModLFO, modLfoToPitch;
409 | float delayVibLFO;
410 | int freqVibLFO, vibLfoToPitch;
411 | };
412 |
413 | struct tsf_preset
414 | {
415 | tsf_char20 presetName;
416 | tsf_u16 preset, bank;
417 | struct tsf_region* regions;
418 | int regionNum;
419 | };
420 |
421 | struct tsf_voice
422 | {
423 | int playingPreset, playingKey, playingChannel;
424 | struct tsf_region* region;
425 | double pitchInputTimecents, pitchOutputFactor;
426 | double sourceSamplePosition;
427 | float noteGainDB, panFactorLeft, panFactorRight;
428 | unsigned int playIndex, loopStart, loopEnd;
429 | struct tsf_voice_envelope ampenv, modenv;
430 | struct tsf_voice_lowpass lowpass;
431 | struct tsf_voice_lfo modlfo, viblfo;
432 | };
433 |
434 | struct tsf_channel
435 | {
436 | unsigned short presetIndex, bank, pitchWheel, midiPan, midiVolume, midiExpression, midiRPN, midiData;
437 | float panOffset, gainDB, pitchRange, tuning;
438 | };
439 |
440 | struct tsf_channels
441 | {
442 | void (*setupVoice)(tsf* f, struct tsf_voice* voice);
443 | struct tsf_channel* channels;
444 | int channelNum, activeChannel;
445 | };
446 |
447 | static double tsf_timecents2Secsd(double timecents) { return TSF_POW(2.0, timecents / 1200.0); }
448 | static float tsf_timecents2Secsf(float timecents) { return TSF_POWF(2.0f, timecents / 1200.0f); }
449 | static float tsf_cents2Hertz(float cents) { return 8.176f * TSF_POWF(2.0f, cents / 1200.0f); }
450 | static float tsf_decibelsToGain(float db) { return (db > -100.f ? TSF_POWF(10.0f, db * 0.05f) : 0); }
451 | static float tsf_gainToDecibels(float gain) { return (gain <= .00001f ? -100.f : (float)(20.0 * TSF_LOG10(gain))); }
452 |
453 | static TSF_BOOL tsf_riffchunk_read(struct tsf_riffchunk* parent, struct tsf_riffchunk* chunk, struct tsf_stream* stream)
454 | {
455 | TSF_BOOL IsRiff, IsList;
456 | if (parent && sizeof(tsf_fourcc) + sizeof(tsf_u32) > parent->size) return TSF_FALSE;
457 | if (!stream->read(stream->data, &chunk->id, sizeof(tsf_fourcc)) || *chunk->id <= ' ' || *chunk->id >= 'z') return TSF_FALSE;
458 | if (!stream->read(stream->data, &chunk->size, sizeof(tsf_u32))) return TSF_FALSE;
459 | if (parent && sizeof(tsf_fourcc) + sizeof(tsf_u32) + chunk->size > parent->size) return TSF_FALSE;
460 | if (parent) parent->size -= sizeof(tsf_fourcc) + sizeof(tsf_u32) + chunk->size;
461 | IsRiff = TSF_FourCCEquals(chunk->id, "RIFF"), IsList = TSF_FourCCEquals(chunk->id, "LIST");
462 | if (IsRiff && parent) return TSF_FALSE; //not allowed
463 | if (!IsRiff && !IsList) return TSF_TRUE; //custom type without sub type
464 | if (!stream->read(stream->data, &chunk->id, sizeof(tsf_fourcc)) || *chunk->id <= ' ' || *chunk->id >= 'z') return TSF_FALSE;
465 | chunk->size -= sizeof(tsf_fourcc);
466 | return TSF_TRUE;
467 | }
468 |
469 | static void tsf_region_clear(struct tsf_region* i, TSF_BOOL for_relative)
470 | {
471 | TSF_MEMSET(i, 0, sizeof(struct tsf_region));
472 | i->hikey = i->hivel = 127;
473 | i->pitch_keycenter = 60; // C4
474 | if (for_relative) return;
475 |
476 | i->pitch_keytrack = 100;
477 |
478 | i->pitch_keycenter = -1;
479 |
480 | // SF2 defaults in timecents.
481 | i->ampenv.delay = i->ampenv.attack = i->ampenv.hold = i->ampenv.decay = i->ampenv.release = -12000.0f;
482 | i->modenv.delay = i->modenv.attack = i->modenv.hold = i->modenv.decay = i->modenv.release = -12000.0f;
483 |
484 | i->initialFilterFc = 13500;
485 |
486 | i->delayModLFO = -12000.0f;
487 | i->delayVibLFO = -12000.0f;
488 | }
489 |
490 | static void tsf_region_operator(struct tsf_region* region, tsf_u16 genOper, union tsf_hydra_genamount* amount, struct tsf_region* merge_region)
491 | {
492 | enum
493 | {
494 | _GEN_TYPE_MASK = 0x0F,
495 | GEN_FLOAT = 0x01,
496 | GEN_INT = 0x02,
497 | GEN_UINT_ADD = 0x03,
498 | GEN_UINT_ADD15 = 0x04,
499 | GEN_KEYRANGE = 0x05,
500 | GEN_VELRANGE = 0x06,
501 | GEN_LOOPMODE = 0x07,
502 | GEN_GROUP = 0x08,
503 | GEN_KEYCENTER = 0x09,
504 |
505 | _GEN_LIMIT_MASK = 0xF0,
506 | GEN_INT_LIMIT12K = 0x10, //min -12000, max 12000
507 | GEN_INT_LIMITFC = 0x20, //min 1500, max 13500
508 | GEN_INT_LIMITQ = 0x30, //min 0, max 960
509 | GEN_INT_LIMIT960 = 0x40, //min -960, max 960
510 | GEN_INT_LIMIT16K4500 = 0x50, //min -16000, max 4500
511 | GEN_FLOAT_LIMIT12K5K = 0x60, //min -12000, max 5000
512 | GEN_FLOAT_LIMIT12K8K = 0x70, //min -12000, max 8000
513 | GEN_FLOAT_LIMIT1200 = 0x80, //min -1200, max 1200
514 | GEN_FLOAT_LIMITPAN = 0x90, //* .001f, min -.5f, max .5f,
515 | GEN_FLOAT_LIMITATTN = 0xA0, //* .1f, min 0, max 144.0
516 | GEN_FLOAT_MAX1000 = 0xB0, //min 0, max 1000
517 | GEN_FLOAT_MAX1440 = 0xC0, //min 0, max 1440
518 |
519 | _GEN_MAX = 59,
520 | };
521 | #define _TSFREGIONOFFSET(TYPE, FIELD) (unsigned char)(((TYPE*)&((struct tsf_region*)0)->FIELD) - (TYPE*)0)
522 | #define _TSFREGIONENVOFFSET(TYPE, ENV, FIELD) (unsigned char)(((TYPE*)&((&(((struct tsf_region*)0)->ENV))->FIELD)) - (TYPE*)0)
523 | static const struct { unsigned char mode, offset; } genMetas[_GEN_MAX] =
524 | {
525 | { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, offset ) }, // 0 StartAddrsOffset
526 | { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, end ) }, // 1 EndAddrsOffset
527 | { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, loop_start ) }, // 2 StartloopAddrsOffset
528 | { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, loop_end ) }, // 3 EndloopAddrsOffset
529 | { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, offset ) }, // 4 StartAddrsCoarseOffset
530 | { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modLfoToPitch ) }, // 5 ModLfoToPitch
531 | { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, vibLfoToPitch ) }, // 6 VibLfoToPitch
532 | { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modEnvToPitch ) }, // 7 ModEnvToPitch
533 | { GEN_INT | GEN_INT_LIMITFC , _TSFREGIONOFFSET( int, initialFilterFc ) }, // 8 InitialFilterFc
534 | { GEN_INT | GEN_INT_LIMITQ , _TSFREGIONOFFSET( int, initialFilterQ ) }, // 9 InitialFilterQ
535 | { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modLfoToFilterFc ) }, //10 ModLfoToFilterFc
536 | { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modEnvToFilterFc ) }, //11 ModEnvToFilterFc
537 | { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, end ) }, //12 EndAddrsCoarseOffset
538 | { GEN_INT | GEN_INT_LIMIT960 , _TSFREGIONOFFSET( int, modLfoToVolume ) }, //13 ModLfoToVolume
539 | { 0 , (0 ) }, // Unused
540 | { 0 , (0 ) }, //15 ChorusEffectsSend (unsupported)
541 | { 0 , (0 ) }, //16 ReverbEffectsSend (unsupported)
542 | { GEN_FLOAT | GEN_FLOAT_LIMITPAN , _TSFREGIONOFFSET( float, pan ) }, //17 Pan
543 | { 0 , (0 ) }, // Unused
544 | { 0 , (0 ) }, // Unused
545 | { 0 , (0 ) }, // Unused
546 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONOFFSET( float, delayModLFO ) }, //21 DelayModLFO
547 | { GEN_INT | GEN_INT_LIMIT16K4500 , _TSFREGIONOFFSET( int, freqModLFO ) }, //22 FreqModLFO
548 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONOFFSET( float, delayVibLFO ) }, //23 DelayVibLFO
549 | { GEN_INT | GEN_INT_LIMIT16K4500 , _TSFREGIONOFFSET( int, freqVibLFO ) }, //24 FreqVibLFO
550 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, modenv, delay ) }, //25 DelayModEnv
551 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, modenv, attack ) }, //26 AttackModEnv
552 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, modenv, hold ) }, //27 HoldModEnv
553 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, modenv, decay ) }, //28 DecayModEnv
554 | { GEN_FLOAT | GEN_FLOAT_MAX1000 , _TSFREGIONENVOFFSET( float, modenv, sustain ) }, //29 SustainModEnv
555 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, modenv, release ) }, //30 ReleaseModEnv
556 | { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, modenv, keynumToHold ) }, //31 KeynumToModEnvHold
557 | { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, modenv, keynumToDecay) }, //32 KeynumToModEnvDecay
558 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, ampenv, delay ) }, //33 DelayVolEnv
559 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, ampenv, attack ) }, //34 AttackVolEnv
560 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, ampenv, hold ) }, //35 HoldVolEnv
561 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, ampenv, decay ) }, //36 DecayVolEnv
562 | { GEN_FLOAT | GEN_FLOAT_MAX1440 , _TSFREGIONENVOFFSET( float, ampenv, sustain ) }, //37 SustainVolEnv
563 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, ampenv, release ) }, //38 ReleaseVolEnv
564 | { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, ampenv, keynumToHold ) }, //39 KeynumToVolEnvHold
565 | { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, ampenv, keynumToDecay) }, //40 KeynumToVolEnvDecay
566 | { 0 , (0 ) }, // Instrument (special)
567 | { 0 , (0 ) }, // Reserved
568 | { GEN_KEYRANGE , (0 ) }, //43 KeyRange
569 | { GEN_VELRANGE , (0 ) }, //44 VelRange
570 | { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, loop_start ) }, //45 StartloopAddrsCoarseOffset
571 | { 0 , (0 ) }, //46 Keynum (special)
572 | { 0 , (0 ) }, //47 Velocity (special)
573 | { GEN_FLOAT | GEN_FLOAT_LIMITATTN , _TSFREGIONOFFSET( float, attenuation ) }, //48 InitialAttenuation
574 | { 0 , (0 ) }, // Reserved
575 | { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, loop_end ) }, //50 EndloopAddrsCoarseOffset
576 | { GEN_INT , _TSFREGIONOFFSET( int, transpose ) }, //51 CoarseTune
577 | { GEN_INT , _TSFREGIONOFFSET( int, tune ) }, //52 FineTune
578 | { 0 , (0 ) }, // SampleID (special)
579 | { GEN_LOOPMODE , _TSFREGIONOFFSET( int, loop_mode ) }, //54 SampleModes
580 | { 0 , (0 ) }, // Reserved
581 | { GEN_INT , _TSFREGIONOFFSET( int, pitch_keytrack ) }, //56 ScaleTuning
582 | { GEN_GROUP , _TSFREGIONOFFSET(unsigned int, group ) }, //57 ExclusiveClass
583 | { GEN_KEYCENTER , _TSFREGIONOFFSET( int, pitch_keycenter ) }, //58 OverridingRootKey
584 | };
585 | #undef _TSFREGIONOFFSET
586 | #undef _TSFREGIONENVOFFSET
587 | if (amount)
588 | {
589 | int offset;
590 | if (genOper >= _GEN_MAX) return;
591 | offset = genMetas[genOper].offset;
592 | switch (genMetas[genOper].mode & _GEN_TYPE_MASK)
593 | {
594 | case GEN_FLOAT: (( float*)region)[offset] = amount->shortAmount; return;
595 | case GEN_INT: (( int*)region)[offset] = amount->shortAmount; return;
596 | case GEN_UINT_ADD: ((unsigned int*)region)[offset] += amount->shortAmount; return;
597 | case GEN_UINT_ADD15: ((unsigned int*)region)[offset] += amount->shortAmount<<15; return;
598 | case GEN_KEYRANGE: region->lokey = amount->range.lo; region->hikey = amount->range.hi; return;
599 | case GEN_VELRANGE: region->lovel = amount->range.lo; region->hivel = amount->range.hi; return;
600 | case GEN_LOOPMODE: region->loop_mode = ((amount->wordAmount&3) == 3 ? TSF_LOOPMODE_SUSTAIN : ((amount->wordAmount&3) == 1 ? TSF_LOOPMODE_CONTINUOUS : TSF_LOOPMODE_NONE)); return;
601 | case GEN_GROUP: region->group = amount->wordAmount; return;
602 | case GEN_KEYCENTER: region->pitch_keycenter = amount->shortAmount; return;
603 | }
604 | }
605 | else //merge regions and clamp values
606 | {
607 | for (genOper = 0; genOper != _GEN_MAX; genOper++)
608 | {
609 | int offset = genMetas[genOper].offset;
610 | switch (genMetas[genOper].mode & _GEN_TYPE_MASK)
611 | {
612 | case GEN_FLOAT:
613 | {
614 | float *val = &((float*)region)[offset], vfactor, vmin, vmax;
615 | *val += ((float*)merge_region)[offset];
616 | switch (genMetas[genOper].mode & _GEN_LIMIT_MASK)
617 | {
618 | case GEN_FLOAT_LIMIT12K5K: vfactor = 1.0f; vmin = -12000.0f; vmax = 5000.0f; break;
619 | case GEN_FLOAT_LIMIT12K8K: vfactor = 1.0f; vmin = -12000.0f; vmax = 8000.0f; break;
620 | case GEN_FLOAT_LIMIT1200: vfactor = 1.0f; vmin = -1200.0f; vmax = 1200.0f; break;
621 | case GEN_FLOAT_LIMITPAN: vfactor = 0.001f; vmin = -0.5f; vmax = 0.5f; break;
622 | case GEN_FLOAT_LIMITATTN: vfactor = 0.1f; vmin = 0.0f; vmax = 144.0f; break;
623 | case GEN_FLOAT_MAX1000: vfactor = 1.0f; vmin = 0.0f; vmax = 1000.0f; break;
624 | case GEN_FLOAT_MAX1440: vfactor = 1.0f; vmin = 0.0f; vmax = 1440.0f; break;
625 | default: continue;
626 | }
627 | *val *= vfactor;
628 | if (*val < vmin) *val = vmin;
629 | else if (*val > vmax) *val = vmax;
630 | continue;
631 | }
632 | case GEN_INT:
633 | {
634 | int *val = &((int*)region)[offset], vmin, vmax;
635 | *val += ((int*)merge_region)[offset];
636 | switch (genMetas[genOper].mode & _GEN_LIMIT_MASK)
637 | {
638 | case GEN_INT_LIMIT12K: vmin = -12000; vmax = 12000; break;
639 | case GEN_INT_LIMITFC: vmin = 1500; vmax = 13500; break;
640 | case GEN_INT_LIMITQ: vmin = 0; vmax = 960; break;
641 | case GEN_INT_LIMIT960: vmin = -960; vmax = 960; break;
642 | case GEN_INT_LIMIT16K4500: vmin = -16000; vmax = 4500; break;
643 | default: continue;
644 | }
645 | if (*val < vmin) *val = vmin;
646 | else if (*val > vmax) *val = vmax;
647 | continue;
648 | }
649 | case GEN_UINT_ADD:
650 | {
651 | ((unsigned int*)region)[offset] += ((unsigned int*)merge_region)[offset];
652 | continue;
653 | }
654 | }
655 | }
656 | }
657 | }
658 |
659 | static void tsf_region_envtosecs(struct tsf_envelope* p, TSF_BOOL sustainIsGain)
660 | {
661 | // EG times need to be converted from timecents to seconds.
662 | // Pin very short EG segments. Timecents don't get to zero, and our EG is
663 | // happier with zero values.
664 | p->delay = (p->delay < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->delay));
665 | p->attack = (p->attack < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->attack));
666 | p->release = (p->release < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->release));
667 |
668 | // If we have dynamic hold or decay times depending on key number we need
669 | // to keep the values in timecents so we can calculate it during startNote
670 | if (!p->keynumToHold) p->hold = (p->hold < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->hold));
671 | if (!p->keynumToDecay) p->decay = (p->decay < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->decay));
672 |
673 | if (p->sustain < 0.0f) p->sustain = 0.0f;
674 | else if (sustainIsGain) p->sustain = tsf_decibelsToGain(-p->sustain / 10.0f);
675 | else p->sustain = 1.0f - (p->sustain / 1000.0f);
676 | }
677 |
678 | static void tsf_load_presets(tsf* res, struct tsf_hydra *hydra, unsigned int fontSampleCount)
679 | {
680 | enum { GenInstrument = 41, GenKeyRange = 43, GenVelRange = 44, GenSampleID = 53 };
681 | // Read each preset.
682 | struct tsf_hydra_phdr *pphdr, *pphdrMax;
683 | for (pphdr = hydra->phdrs, pphdrMax = pphdr + hydra->phdrNum - 1; pphdr != pphdrMax; pphdr++)
684 | {
685 | int sortedIndex = 0, region_index = 0;
686 | struct tsf_hydra_phdr *otherphdr;
687 | struct tsf_preset* preset;
688 | struct tsf_hydra_pbag *ppbag, *ppbagEnd;
689 | struct tsf_region globalRegion;
690 | for (otherphdr = hydra->phdrs; otherphdr != pphdrMax; otherphdr++)
691 | {
692 | if (otherphdr == pphdr || otherphdr->bank > pphdr->bank) continue;
693 | else if (otherphdr->bank < pphdr->bank) sortedIndex++;
694 | else if (otherphdr->preset > pphdr->preset) continue;
695 | else if (otherphdr->preset < pphdr->preset) sortedIndex++;
696 | else if (otherphdr < pphdr) sortedIndex++;
697 | }
698 |
699 | preset = &res->presets[sortedIndex];
700 | TSF_MEMCPY(preset->presetName, pphdr->presetName, sizeof(preset->presetName));
701 | preset->presetName[sizeof(preset->presetName)-1] = '\0'; //should be zero terminated in source file but make sure
702 | preset->bank = pphdr->bank;
703 | preset->preset = pphdr->preset;
704 | preset->regionNum = 0;
705 |
706 | //count regions covered by this preset
707 | for (ppbag = hydra->pbags + pphdr->presetBagNdx, ppbagEnd = hydra->pbags + pphdr[1].presetBagNdx; ppbag != ppbagEnd; ppbag++)
708 | {
709 | unsigned char plokey = 0, phikey = 127, plovel = 0, phivel = 127;
710 | struct tsf_hydra_pgen *ppgen, *ppgenEnd; struct tsf_hydra_inst *pinst; struct tsf_hydra_ibag *pibag, *pibagEnd; struct tsf_hydra_igen *pigen, *pigenEnd;
711 | for (ppgen = hydra->pgens + ppbag->genNdx, ppgenEnd = hydra->pgens + ppbag[1].genNdx; ppgen != ppgenEnd; ppgen++)
712 | {
713 | if (ppgen->genOper == GenKeyRange) { plokey = ppgen->genAmount.range.lo; phikey = ppgen->genAmount.range.hi; continue; }
714 | if (ppgen->genOper == GenVelRange) { plovel = ppgen->genAmount.range.lo; phivel = ppgen->genAmount.range.hi; continue; }
715 | if (ppgen->genOper != GenInstrument) continue;
716 | if (ppgen->genAmount.wordAmount >= hydra->instNum) continue;
717 | pinst = hydra->insts + ppgen->genAmount.wordAmount;
718 | for (pibag = hydra->ibags + pinst->instBagNdx, pibagEnd = hydra->ibags + pinst[1].instBagNdx; pibag != pibagEnd; pibag++)
719 | {
720 | unsigned char ilokey = 0, ihikey = 127, ilovel = 0, ihivel = 127;
721 | for (pigen = hydra->igens + pibag->instGenNdx, pigenEnd = hydra->igens + pibag[1].instGenNdx; pigen != pigenEnd; pigen++)
722 | {
723 | if (pigen->genOper == GenKeyRange) { ilokey = pigen->genAmount.range.lo; ihikey = pigen->genAmount.range.hi; continue; }
724 | if (pigen->genOper == GenVelRange) { ilovel = pigen->genAmount.range.lo; ihivel = pigen->genAmount.range.hi; continue; }
725 | if (pigen->genOper == GenSampleID && ihikey >= plokey && ilokey <= phikey && ihivel >= plovel && ilovel <= phivel) preset->regionNum++;
726 | }
727 | }
728 | }
729 | }
730 |
731 | preset->regions = (struct tsf_region*)TSF_MALLOC(preset->regionNum * sizeof(struct tsf_region));
732 | tsf_region_clear(&globalRegion, TSF_TRUE);
733 |
734 | // Zones.
735 | for (ppbag = hydra->pbags + pphdr->presetBagNdx, ppbagEnd = hydra->pbags + pphdr[1].presetBagNdx; ppbag != ppbagEnd; ppbag++)
736 | {
737 | struct tsf_hydra_pgen *ppgen, *ppgenEnd; struct tsf_hydra_inst *pinst; struct tsf_hydra_ibag *pibag, *pibagEnd; struct tsf_hydra_igen *pigen, *pigenEnd;
738 | struct tsf_region presetRegion = globalRegion;
739 | int hadGenInstrument = 0;
740 |
741 | // Generators.
742 | for (ppgen = hydra->pgens + ppbag->genNdx, ppgenEnd = hydra->pgens + ppbag[1].genNdx; ppgen != ppgenEnd; ppgen++)
743 | {
744 | // Instrument.
745 | if (ppgen->genOper == GenInstrument)
746 | {
747 | struct tsf_region instRegion;
748 | tsf_u16 whichInst = ppgen->genAmount.wordAmount;
749 | if (whichInst >= hydra->instNum) continue;
750 |
751 | tsf_region_clear(&instRegion, TSF_FALSE);
752 | pinst = &hydra->insts[whichInst];
753 | for (pibag = hydra->ibags + pinst->instBagNdx, pibagEnd = hydra->ibags + pinst[1].instBagNdx; pibag != pibagEnd; pibag++)
754 | {
755 | // Generators.
756 | struct tsf_region zoneRegion = instRegion;
757 | int hadSampleID = 0;
758 | for (pigen = hydra->igens + pibag->instGenNdx, pigenEnd = hydra->igens + pibag[1].instGenNdx; pigen != pigenEnd; pigen++)
759 | {
760 | if (pigen->genOper == GenSampleID)
761 | {
762 | struct tsf_hydra_shdr* pshdr;
763 |
764 | //preset region key and vel ranges are a filter for the zone regions
765 | if (zoneRegion.hikey < presetRegion.lokey || zoneRegion.lokey > presetRegion.hikey) continue;
766 | if (zoneRegion.hivel < presetRegion.lovel || zoneRegion.lovel > presetRegion.hivel) continue;
767 | if (presetRegion.lokey > zoneRegion.lokey) zoneRegion.lokey = presetRegion.lokey;
768 | if (presetRegion.hikey < zoneRegion.hikey) zoneRegion.hikey = presetRegion.hikey;
769 | if (presetRegion.lovel > zoneRegion.lovel) zoneRegion.lovel = presetRegion.lovel;
770 | if (presetRegion.hivel < zoneRegion.hivel) zoneRegion.hivel = presetRegion.hivel;
771 |
772 | //sum regions
773 | tsf_region_operator(&zoneRegion, 0, TSF_NULL, &presetRegion);
774 |
775 | // EG times need to be converted from timecents to seconds.
776 | tsf_region_envtosecs(&zoneRegion.ampenv, TSF_TRUE);
777 | tsf_region_envtosecs(&zoneRegion.modenv, TSF_FALSE);
778 |
779 | // LFO times need to be converted from timecents to seconds.
780 | zoneRegion.delayModLFO = (zoneRegion.delayModLFO < -11950.0f ? 0.0f : tsf_timecents2Secsf(zoneRegion.delayModLFO));
781 | zoneRegion.delayVibLFO = (zoneRegion.delayVibLFO < -11950.0f ? 0.0f : tsf_timecents2Secsf(zoneRegion.delayVibLFO));
782 |
783 | // Fixup sample positions
784 | pshdr = &hydra->shdrs[pigen->genAmount.wordAmount];
785 | zoneRegion.offset += pshdr->start;
786 | zoneRegion.end += pshdr->end;
787 | zoneRegion.loop_start += pshdr->startLoop;
788 | zoneRegion.loop_end += pshdr->endLoop;
789 | if (pshdr->endLoop > 0) zoneRegion.loop_end -= 1;
790 | if (zoneRegion.pitch_keycenter == -1) zoneRegion.pitch_keycenter = pshdr->originalPitch;
791 | zoneRegion.tune += pshdr->pitchCorrection;
792 | zoneRegion.sample_rate = pshdr->sampleRate;
793 | if (zoneRegion.end && zoneRegion.end < fontSampleCount) zoneRegion.end++;
794 | else zoneRegion.end = fontSampleCount;
795 |
796 | preset->regions[region_index] = zoneRegion;
797 | region_index++;
798 | hadSampleID = 1;
799 | }
800 | else tsf_region_operator(&zoneRegion, pigen->genOper, &pigen->genAmount, TSF_NULL);
801 | }
802 |
803 | // Handle instrument's global zone.
804 | if (pibag == hydra->ibags + pinst->instBagNdx && !hadSampleID)
805 | instRegion = zoneRegion;
806 |
807 | // Modulators (TODO)
808 | //if (ibag->instModNdx < ibag[1].instModNdx) addUnsupportedOpcode("any modulator");
809 | }
810 | hadGenInstrument = 1;
811 | }
812 | else tsf_region_operator(&presetRegion, ppgen->genOper, &ppgen->genAmount, TSF_NULL);
813 | }
814 |
815 | // Modulators (TODO)
816 | //if (pbag->modNdx < pbag[1].modNdx) addUnsupportedOpcode("any modulator");
817 |
818 | // Handle preset's global zone.
819 | if (ppbag == hydra->pbags + pphdr->presetBagNdx && !hadGenInstrument)
820 | globalRegion = presetRegion;
821 | }
822 | }
823 | }
824 |
825 | static void tsf_load_samples(float** fontSamples, unsigned int* fontSampleCount, struct tsf_riffchunk *chunkSmpl, struct tsf_stream* stream)
826 | {
827 | // Read sample data into float format buffer.
828 | float* out; unsigned int samplesLeft, samplesToRead, samplesToConvert;
829 | samplesLeft = *fontSampleCount = chunkSmpl->size / sizeof(short);
830 | out = *fontSamples = (float*)TSF_MALLOC(samplesLeft * sizeof(float));
831 | for (; samplesLeft; samplesLeft -= samplesToRead)
832 | {
833 | short sampleBuffer[1024], *in = sampleBuffer;;
834 | samplesToRead = (samplesLeft > 1024 ? 1024 : samplesLeft);
835 | stream->read(stream->data, sampleBuffer, samplesToRead * sizeof(short));
836 |
837 | // Convert from signed 16-bit to float.
838 | for (samplesToConvert = samplesToRead; samplesToConvert > 0; --samplesToConvert)
839 | // If we ever need to compile for big-endian platforms, we'll need to byte-swap here.
840 | *out++ = (float)(*in++ / 32767.0);
841 | }
842 | }
843 |
844 | static void tsf_voice_envelope_nextsegment(struct tsf_voice_envelope* e, short active_segment, float outSampleRate)
845 | {
846 | switch (active_segment)
847 | {
848 | case TSF_SEGMENT_NONE:
849 | e->samplesUntilNextSegment = (int)(e->parameters.delay * outSampleRate);
850 | if (e->samplesUntilNextSegment > 0)
851 | {
852 | e->segment = TSF_SEGMENT_DELAY;
853 | e->segmentIsExponential = TSF_FALSE;
854 | e->level = 0.0;
855 | e->slope = 0.0;
856 | return;
857 | }
858 | /* fall through */
859 | case TSF_SEGMENT_DELAY:
860 | e->samplesUntilNextSegment = (int)(e->parameters.attack * outSampleRate);
861 | if (e->samplesUntilNextSegment > 0)
862 | {
863 | if (!e->isAmpEnv)
864 | {
865 | //mod env attack duration scales with velocity (velocity of 1 is full duration, max velocity is 0.125 times duration)
866 | e->samplesUntilNextSegment = (int)(e->parameters.attack * ((145 - e->midiVelocity) / 144.0f) * outSampleRate);
867 | }
868 | e->segment = TSF_SEGMENT_ATTACK;
869 | e->segmentIsExponential = TSF_FALSE;
870 | e->level = 0.0f;
871 | e->slope = 1.0f / e->samplesUntilNextSegment;
872 | return;
873 | }
874 | /* fall through */
875 | case TSF_SEGMENT_ATTACK:
876 | e->samplesUntilNextSegment = (int)(e->parameters.hold * outSampleRate);
877 | if (e->samplesUntilNextSegment > 0)
878 | {
879 | e->segment = TSF_SEGMENT_HOLD;
880 | e->segmentIsExponential = TSF_FALSE;
881 | e->level = 1.0f;
882 | e->slope = 0.0f;
883 | return;
884 | }
885 | /* fall through */
886 | case TSF_SEGMENT_HOLD:
887 | e->samplesUntilNextSegment = (int)(e->parameters.decay * outSampleRate);
888 | if (e->samplesUntilNextSegment > 0)
889 | {
890 | e->segment = TSF_SEGMENT_DECAY;
891 | e->level = 1.0f;
892 | if (e->isAmpEnv)
893 | {
894 | // I don't truly understand this; just following what LinuxSampler does.
895 | float mysterySlope = -9.226f / e->samplesUntilNextSegment;
896 | e->slope = TSF_EXPF(mysterySlope);
897 | e->segmentIsExponential = TSF_TRUE;
898 | if (e->parameters.sustain > 0.0f)
899 | {
900 | // Again, this is following LinuxSampler's example, which is similar to
901 | // SF2-style decay, where "decay" specifies the time it would take to
902 | // get to zero, not to the sustain level. The SFZ spec is not that
903 | // specific about what "decay" means, so perhaps it's really supposed
904 | // to specify the time to reach the sustain level.
905 | e->samplesUntilNextSegment = (int)(TSF_LOG(e->parameters.sustain) / mysterySlope);
906 | }
907 | }
908 | else
909 | {
910 | e->slope = -1.0f / e->samplesUntilNextSegment;
911 | e->samplesUntilNextSegment = (int)(e->parameters.decay * (1.0f - e->parameters.sustain) * outSampleRate);
912 | e->segmentIsExponential = TSF_FALSE;
913 | }
914 | return;
915 | }
916 | /* fall through */
917 | case TSF_SEGMENT_DECAY:
918 | e->segment = TSF_SEGMENT_SUSTAIN;
919 | e->level = e->parameters.sustain;
920 | e->slope = 0.0f;
921 | e->samplesUntilNextSegment = 0x7FFFFFFF;
922 | e->segmentIsExponential = TSF_FALSE;
923 | return;
924 | case TSF_SEGMENT_SUSTAIN:
925 | e->segment = TSF_SEGMENT_RELEASE;
926 | e->samplesUntilNextSegment = (int)((e->parameters.release <= 0 ? TSF_FASTRELEASETIME : e->parameters.release) * outSampleRate);
927 | if (e->isAmpEnv)
928 | {
929 | // I don't truly understand this; just following what LinuxSampler does.
930 | float mysterySlope = -9.226f / e->samplesUntilNextSegment;
931 | e->slope = TSF_EXPF(mysterySlope);
932 | e->segmentIsExponential = TSF_TRUE;
933 | }
934 | else
935 | {
936 | e->slope = -e->level / e->samplesUntilNextSegment;
937 | e->segmentIsExponential = TSF_FALSE;
938 | }
939 | return;
940 | case TSF_SEGMENT_RELEASE:
941 | default:
942 | e->segment = TSF_SEGMENT_DONE;
943 | e->segmentIsExponential = TSF_FALSE;
944 | e->level = e->slope = 0.0f;
945 | e->samplesUntilNextSegment = 0x7FFFFFF;
946 | }
947 | }
948 |
949 | static void tsf_voice_envelope_setup(struct tsf_voice_envelope* e, struct tsf_envelope* new_parameters, int midiNoteNumber, short midiVelocity, TSF_BOOL isAmpEnv, float outSampleRate)
950 | {
951 | e->parameters = *new_parameters;
952 | if (e->parameters.keynumToHold)
953 | {
954 | e->parameters.hold += e->parameters.keynumToHold * (60.0f - midiNoteNumber);
955 | e->parameters.hold = (e->parameters.hold < -10000.0f ? 0.0f : tsf_timecents2Secsf(e->parameters.hold));
956 | }
957 | if (e->parameters.keynumToDecay)
958 | {
959 | e->parameters.decay += e->parameters.keynumToDecay * (60.0f - midiNoteNumber);
960 | e->parameters.decay = (e->parameters.decay < -10000.0f ? 0.0f : tsf_timecents2Secsf(e->parameters.decay));
961 | }
962 | e->midiVelocity = midiVelocity;
963 | e->isAmpEnv = isAmpEnv;
964 | tsf_voice_envelope_nextsegment(e, TSF_SEGMENT_NONE, outSampleRate);
965 | }
966 |
967 | static void tsf_voice_envelope_process(struct tsf_voice_envelope* e, int numSamples, float outSampleRate)
968 | {
969 | if (e->slope)
970 | {
971 | if (e->segmentIsExponential) e->level *= TSF_POWF(e->slope, (float)numSamples);
972 | else e->level += (e->slope * numSamples);
973 | }
974 | if ((e->samplesUntilNextSegment -= numSamples) <= 0)
975 | tsf_voice_envelope_nextsegment(e, e->segment, outSampleRate);
976 | }
977 |
978 | static void tsf_voice_lowpass_setup(struct tsf_voice_lowpass* e, float Fc)
979 | {
980 | // Lowpass filter from http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/
981 | double K = TSF_TAN(TSF_PI * Fc), KK = K * K;
982 | double norm = 1 / (1 + K * e->QInv + KK);
983 | e->a0 = KK * norm;
984 | e->a1 = 2 * e->a0;
985 | e->b1 = 2 * (KK - 1) * norm;
986 | e->b2 = (1 - K * e->QInv + KK) * norm;
987 | }
988 |
989 | static float tsf_voice_lowpass_process(struct tsf_voice_lowpass* e, double In)
990 | {
991 | double Out = In * e->a0 + e->z1; e->z1 = In * e->a1 + e->z2 - e->b1 * Out; e->z2 = In * e->a0 - e->b2 * Out; return (float)Out;
992 | }
993 |
994 | static void tsf_voice_lfo_setup(struct tsf_voice_lfo* e, float delay, int freqCents, float outSampleRate)
995 | {
996 | e->samplesUntil = (int)(delay * outSampleRate);
997 | e->delta = (4.0f * tsf_cents2Hertz((float)freqCents) / outSampleRate);
998 | e->level = 0;
999 | }
1000 |
1001 | static void tsf_voice_lfo_process(struct tsf_voice_lfo* e, int blockSamples)
1002 | {
1003 | if (e->samplesUntil > blockSamples) { e->samplesUntil -= blockSamples; return; }
1004 | e->level += e->delta * blockSamples;
1005 | if (e->level > 1.0f) { e->delta = -e->delta; e->level = 2.0f - e->level; }
1006 | else if (e->level < -1.0f) { e->delta = -e->delta; e->level = -2.0f - e->level; }
1007 | }
1008 |
1009 | static void tsf_voice_kill(struct tsf_voice* v)
1010 | {
1011 | v->playingPreset = -1;
1012 | }
1013 |
1014 | static void tsf_voice_end(tsf* f, struct tsf_voice* v)
1015 | {
1016 | // if maxVoiceNum is set, assume that voice rendering and note queuing are on sparate threads
1017 | // so to minimize the chance that voice rendering would advance the segment at the same time
1018 | // we just do it twice here and hope that it sticks
1019 | int repeats = (f->maxVoiceNum ? 2 : 1);
1020 | while (repeats--)
1021 | {
1022 | tsf_voice_envelope_nextsegment(&v->ampenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate);
1023 | tsf_voice_envelope_nextsegment(&v->modenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate);
1024 | if (v->region->loop_mode == TSF_LOOPMODE_SUSTAIN)
1025 | {
1026 | // Continue playing, but stop looping.
1027 | v->loopEnd = v->loopStart;
1028 | }
1029 | }
1030 | }
1031 |
1032 | static void tsf_voice_endquick(tsf* f, struct tsf_voice* v)
1033 | {
1034 | // if maxVoiceNum is set, assume that voice rendering and note queuing are on sparate threads
1035 | // so to minimize the chance that voice rendering would advance the segment at the same time
1036 | // we just do it twice here and hope that it sticks
1037 | int repeats = (f->maxVoiceNum ? 2 : 1);
1038 | while (repeats--)
1039 | {
1040 | v->ampenv.parameters.release = 0.0f; tsf_voice_envelope_nextsegment(&v->ampenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate);
1041 | v->modenv.parameters.release = 0.0f; tsf_voice_envelope_nextsegment(&v->modenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate);
1042 | }
1043 | }
1044 |
1045 | static void tsf_voice_calcpitchratio(struct tsf_voice* v, float pitchShift, float outSampleRate)
1046 | {
1047 | double note = v->playingKey + v->region->transpose + v->region->tune / 100.0;
1048 | double adjustedPitch = v->region->pitch_keycenter + (note - v->region->pitch_keycenter) * (v->region->pitch_keytrack / 100.0);
1049 | if (pitchShift) adjustedPitch += pitchShift;
1050 | v->pitchInputTimecents = adjustedPitch * 100.0;
1051 | v->pitchOutputFactor = v->region->sample_rate / (tsf_timecents2Secsd(v->region->pitch_keycenter * 100.0) * outSampleRate);
1052 | }
1053 |
1054 | static void tsf_voice_render(tsf* f, struct tsf_voice* v, float* outputBuffer, int numSamples)
1055 | {
1056 | struct tsf_region* region = v->region;
1057 | float* input = f->fontSamples;
1058 | float* outL = outputBuffer;
1059 | float* outR = (f->outputmode == TSF_STEREO_UNWEAVED ? outL + numSamples : TSF_NULL);
1060 |
1061 | // Cache some values, to give them at least some chance of ending up in registers.
1062 | TSF_BOOL updateModEnv = (region->modEnvToPitch || region->modEnvToFilterFc);
1063 | TSF_BOOL updateModLFO = (v->modlfo.delta && (region->modLfoToPitch || region->modLfoToFilterFc || region->modLfoToVolume));
1064 | TSF_BOOL updateVibLFO = (v->viblfo.delta && (region->vibLfoToPitch));
1065 | TSF_BOOL isLooping = (v->loopStart < v->loopEnd);
1066 | unsigned int tmpLoopStart = v->loopStart, tmpLoopEnd = v->loopEnd;
1067 | double tmpSampleEndDbl = (double)region->end, tmpLoopEndDbl = (double)tmpLoopEnd + 1.0;
1068 | double tmpSourceSamplePosition = v->sourceSamplePosition;
1069 | struct tsf_voice_lowpass tmpLowpass = v->lowpass;
1070 |
1071 | TSF_BOOL dynamicLowpass = (region->modLfoToFilterFc || region->modEnvToFilterFc);
1072 | float tmpSampleRate = f->outSampleRate, tmpInitialFilterFc, tmpModLfoToFilterFc, tmpModEnvToFilterFc;
1073 |
1074 | TSF_BOOL dynamicPitchRatio = (region->modLfoToPitch || region->modEnvToPitch || region->vibLfoToPitch);
1075 | double pitchRatio;
1076 | float tmpModLfoToPitch, tmpVibLfoToPitch, tmpModEnvToPitch;
1077 |
1078 | TSF_BOOL dynamicGain = (region->modLfoToVolume != 0);
1079 | float noteGain = 0, tmpModLfoToVolume;
1080 |
1081 | if (dynamicLowpass) tmpInitialFilterFc = (float)region->initialFilterFc, tmpModLfoToFilterFc = (float)region->modLfoToFilterFc, tmpModEnvToFilterFc = (float)region->modEnvToFilterFc;
1082 | else tmpInitialFilterFc = 0, tmpModLfoToFilterFc = 0, tmpModEnvToFilterFc = 0;
1083 |
1084 | if (dynamicPitchRatio) pitchRatio = 0, tmpModLfoToPitch = (float)region->modLfoToPitch, tmpVibLfoToPitch = (float)region->vibLfoToPitch, tmpModEnvToPitch = (float)region->modEnvToPitch;
1085 | else pitchRatio = tsf_timecents2Secsd(v->pitchInputTimecents) * v->pitchOutputFactor, tmpModLfoToPitch = 0, tmpVibLfoToPitch = 0, tmpModEnvToPitch = 0;
1086 |
1087 | if (dynamicGain) tmpModLfoToVolume = (float)region->modLfoToVolume * 0.1f;
1088 | else noteGain = tsf_decibelsToGain(v->noteGainDB), tmpModLfoToVolume = 0;
1089 |
1090 | while (numSamples)
1091 | {
1092 | float gainMono, gainLeft, gainRight;
1093 | int blockSamples = (numSamples > TSF_RENDER_EFFECTSAMPLEBLOCK ? TSF_RENDER_EFFECTSAMPLEBLOCK : numSamples);
1094 | numSamples -= blockSamples;
1095 |
1096 | if (dynamicLowpass)
1097 | {
1098 | float fres = tmpInitialFilterFc + v->modlfo.level * tmpModLfoToFilterFc + v->modenv.level * tmpModEnvToFilterFc;
1099 | float lowpassFc = (fres <= 13500 ? tsf_cents2Hertz(fres) / tmpSampleRate : 1.0f);
1100 | tmpLowpass.active = (lowpassFc < 0.499f);
1101 | if (tmpLowpass.active) tsf_voice_lowpass_setup(&tmpLowpass, lowpassFc);
1102 | }
1103 |
1104 | if (dynamicPitchRatio)
1105 | pitchRatio = tsf_timecents2Secsd(v->pitchInputTimecents + (v->modlfo.level * tmpModLfoToPitch + v->viblfo.level * tmpVibLfoToPitch + v->modenv.level * tmpModEnvToPitch)) * v->pitchOutputFactor;
1106 |
1107 | if (dynamicGain)
1108 | noteGain = tsf_decibelsToGain(v->noteGainDB + (v->modlfo.level * tmpModLfoToVolume));
1109 |
1110 | gainMono = noteGain * v->ampenv.level;
1111 |
1112 | // Update EG.
1113 | tsf_voice_envelope_process(&v->ampenv, blockSamples, tmpSampleRate);
1114 | if (updateModEnv) tsf_voice_envelope_process(&v->modenv, blockSamples, tmpSampleRate);
1115 |
1116 | // Update LFOs.
1117 | if (updateModLFO) tsf_voice_lfo_process(&v->modlfo, blockSamples);
1118 | if (updateVibLFO) tsf_voice_lfo_process(&v->viblfo, blockSamples);
1119 |
1120 | switch (f->outputmode)
1121 | {
1122 | case TSF_STEREO_INTERLEAVED:
1123 | gainLeft = gainMono * v->panFactorLeft, gainRight = gainMono * v->panFactorRight;
1124 | while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl)
1125 | {
1126 | unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1);
1127 |
1128 | // Simple linear interpolation.
1129 | float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha);
1130 |
1131 | // Low-pass filter.
1132 | if (tmpLowpass.active) val = tsf_voice_lowpass_process(&tmpLowpass, val);
1133 |
1134 | *outL++ += val * gainLeft;
1135 | *outL++ += val * gainRight;
1136 |
1137 | // Next sample.
1138 | tmpSourceSamplePosition += pitchRatio;
1139 | if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0);
1140 | }
1141 | break;
1142 |
1143 | case TSF_STEREO_UNWEAVED:
1144 | gainLeft = gainMono * v->panFactorLeft, gainRight = gainMono * v->panFactorRight;
1145 | while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl)
1146 | {
1147 | unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1);
1148 |
1149 | // Simple linear interpolation.
1150 | float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha);
1151 |
1152 | // Low-pass filter.
1153 | if (tmpLowpass.active) val = tsf_voice_lowpass_process(&tmpLowpass, val);
1154 |
1155 | *outL++ += val * gainLeft;
1156 | *outR++ += val * gainRight;
1157 |
1158 | // Next sample.
1159 | tmpSourceSamplePosition += pitchRatio;
1160 | if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0);
1161 | }
1162 | break;
1163 |
1164 | case TSF_MONO:
1165 | while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl)
1166 | {
1167 | unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1);
1168 |
1169 | // Simple linear interpolation.
1170 | float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha);
1171 |
1172 | // Low-pass filter.
1173 | if (tmpLowpass.active) val = tsf_voice_lowpass_process(&tmpLowpass, val);
1174 |
1175 | *outL++ += val * gainMono;
1176 |
1177 | // Next sample.
1178 | tmpSourceSamplePosition += pitchRatio;
1179 | if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0);
1180 | }
1181 | break;
1182 | }
1183 |
1184 | if (tmpSourceSamplePosition >= tmpSampleEndDbl || v->ampenv.segment == TSF_SEGMENT_DONE)
1185 | {
1186 | tsf_voice_kill(v);
1187 | return;
1188 | }
1189 | }
1190 |
1191 | v->sourceSamplePosition = tmpSourceSamplePosition;
1192 | if (tmpLowpass.active || dynamicLowpass) v->lowpass = tmpLowpass;
1193 | }
1194 |
1195 | TSFDEF tsf* tsf_load(struct tsf_stream* stream)
1196 | {
1197 | tsf* res = TSF_NULL;
1198 | struct tsf_riffchunk chunkHead;
1199 | struct tsf_riffchunk chunkList;
1200 | struct tsf_hydra hydra;
1201 | float* fontSamples = TSF_NULL;
1202 | unsigned int fontSampleCount = 0;
1203 |
1204 | if (!tsf_riffchunk_read(TSF_NULL, &chunkHead, stream) || !TSF_FourCCEquals(chunkHead.id, "sfbk"))
1205 | {
1206 | //if (e) *e = TSF_INVALID_NOSF2HEADER;
1207 | return res;
1208 | }
1209 |
1210 | // Read hydra and locate sample data.
1211 | TSF_MEMSET(&hydra, 0, sizeof(hydra));
1212 | while (tsf_riffchunk_read(&chunkHead, &chunkList, stream))
1213 | {
1214 | struct tsf_riffchunk chunk;
1215 | if (TSF_FourCCEquals(chunkList.id, "pdta"))
1216 | {
1217 | while (tsf_riffchunk_read(&chunkList, &chunk, stream))
1218 | {
1219 | #define HandleChunk(chunkName) (TSF_FourCCEquals(chunk.id, #chunkName) && !(chunk.size % chunkName##SizeInFile)) \
1220 | { \
1221 | int num = chunk.size / chunkName##SizeInFile, i; \
1222 | hydra.chunkName##Num = num; \
1223 | hydra.chunkName##s = (struct tsf_hydra_##chunkName*)TSF_MALLOC(num * sizeof(struct tsf_hydra_##chunkName)); \
1224 | for (i = 0; i < num; ++i) tsf_hydra_read_##chunkName(&hydra.chunkName##s[i], stream); \
1225 | }
1226 | enum
1227 | {
1228 | phdrSizeInFile = 38, pbagSizeInFile = 4, pmodSizeInFile = 10,
1229 | pgenSizeInFile = 4, instSizeInFile = 22, ibagSizeInFile = 4,
1230 | imodSizeInFile = 10, igenSizeInFile = 4, shdrSizeInFile = 46
1231 | };
1232 | if HandleChunk(phdr) else if HandleChunk(pbag) else if HandleChunk(pmod)
1233 | else if HandleChunk(pgen) else if HandleChunk(inst) else if HandleChunk(ibag)
1234 | else if HandleChunk(imod) else if HandleChunk(igen) else if HandleChunk(shdr)
1235 | else stream->skip(stream->data, chunk.size);
1236 | #undef HandleChunk
1237 | }
1238 | }
1239 | else if (TSF_FourCCEquals(chunkList.id, "sdta"))
1240 | {
1241 | while (tsf_riffchunk_read(&chunkList, &chunk, stream))
1242 | {
1243 | if (TSF_FourCCEquals(chunk.id, "smpl"))
1244 | {
1245 | tsf_load_samples(&fontSamples, &fontSampleCount, &chunk, stream);
1246 | }
1247 | else stream->skip(stream->data, chunk.size);
1248 | }
1249 | }
1250 | else stream->skip(stream->data, chunkList.size);
1251 | }
1252 | if (!hydra.phdrs || !hydra.pbags || !hydra.pmods || !hydra.pgens || !hydra.insts || !hydra.ibags || !hydra.imods || !hydra.igens || !hydra.shdrs)
1253 | {
1254 | //if (e) *e = TSF_INVALID_INCOMPLETE;
1255 | }
1256 | else if (fontSamples == TSF_NULL)
1257 | {
1258 | //if (e) *e = TSF_INVALID_NOSAMPLEDATA;
1259 | }
1260 | else
1261 | {
1262 | res = (tsf*)TSF_MALLOC(sizeof(tsf));
1263 | TSF_MEMSET(res, 0, sizeof(tsf));
1264 | res->presetNum = hydra.phdrNum - 1;
1265 | res->presets = (struct tsf_preset*)TSF_MALLOC(res->presetNum * sizeof(struct tsf_preset));
1266 | res->fontSamples = fontSamples;
1267 | res->outSampleRate = 44100.0f;
1268 | fontSamples = TSF_NULL; //don't free below
1269 | tsf_load_presets(res, &hydra, fontSampleCount);
1270 | }
1271 | TSF_FREE(hydra.phdrs); TSF_FREE(hydra.pbags); TSF_FREE(hydra.pmods);
1272 | TSF_FREE(hydra.pgens); TSF_FREE(hydra.insts); TSF_FREE(hydra.ibags);
1273 | TSF_FREE(hydra.imods); TSF_FREE(hydra.igens); TSF_FREE(hydra.shdrs);
1274 | TSF_FREE(fontSamples);
1275 | return res;
1276 | }
1277 |
1278 | TSFDEF void tsf_close(tsf* f)
1279 | {
1280 | struct tsf_preset *preset, *presetEnd;
1281 | if (!f) return;
1282 | for (preset = f->presets, presetEnd = preset + f->presetNum; preset != presetEnd; preset++)
1283 | TSF_FREE(preset->regions);
1284 | TSF_FREE(f->presets);
1285 | TSF_FREE(f->fontSamples);
1286 | TSF_FREE(f->voices);
1287 | if (f->channels) { TSF_FREE(f->channels->channels); TSF_FREE(f->channels); }
1288 | TSF_FREE(f->outputSamples);
1289 | TSF_FREE(f);
1290 | }
1291 |
1292 | TSFDEF void tsf_reset(tsf* f)
1293 | {
1294 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum;
1295 | for (; v != vEnd; v++)
1296 | if (v->playingPreset != -1 && (v->ampenv.segment < TSF_SEGMENT_RELEASE || v->ampenv.parameters.release))
1297 | tsf_voice_endquick(f, v);
1298 | if (f->channels) { TSF_FREE(f->channels->channels); TSF_FREE(f->channels); f->channels = TSF_NULL; }
1299 | }
1300 |
1301 | TSFDEF int tsf_get_presetindex(const tsf* f, int bank, int preset_number)
1302 | {
1303 | const struct tsf_preset *presets;
1304 | int i, iMax;
1305 | for (presets = f->presets, i = 0, iMax = f->presetNum; i < iMax; i++)
1306 | if (presets[i].preset == preset_number && presets[i].bank == bank)
1307 | return i;
1308 | return -1;
1309 | }
1310 |
1311 | TSFDEF int tsf_get_presetcount(const tsf* f)
1312 | {
1313 | return f->presetNum;
1314 | }
1315 |
1316 | TSFDEF const char* tsf_get_presetname(const tsf* f, int preset)
1317 | {
1318 | return (preset < 0 || preset >= f->presetNum ? TSF_NULL : f->presets[preset].presetName);
1319 | }
1320 |
1321 | TSFDEF const char* tsf_bank_get_presetname(const tsf* f, int bank, int preset_number)
1322 | {
1323 | return tsf_get_presetname(f, tsf_get_presetindex(f, bank, preset_number));
1324 | }
1325 |
1326 | TSFDEF void tsf_set_output(tsf* f, enum TSFOutputMode outputmode, int samplerate, float global_gain_db)
1327 | {
1328 | f->outputmode = outputmode;
1329 | f->outSampleRate = (float)(samplerate >= 1 ? samplerate : 44100.0f);
1330 | f->globalGainDB = global_gain_db;
1331 | }
1332 |
1333 | TSFDEF void tsf_set_volume(tsf* f, float global_volume)
1334 | {
1335 | f->globalGainDB = (global_volume == 1.0f ? 0 : -tsf_gainToDecibels(1.0f / global_volume));
1336 | }
1337 |
1338 | TSFDEF void tsf_set_max_voices(tsf* f, int max_voices)
1339 | {
1340 | int i = f->voiceNum;
1341 | f->voiceNum = f->maxVoiceNum = (f->voiceNum > max_voices ? f->voiceNum : max_voices);
1342 | f->voices = (struct tsf_voice*)TSF_REALLOC(f->voices, f->voiceNum * sizeof(struct tsf_voice));
1343 | for (; i != max_voices; i++)
1344 | f->voices[i].playingPreset = -1;
1345 | }
1346 |
1347 | TSFDEF void tsf_note_on(tsf* f, int preset_index, int key, float vel)
1348 | {
1349 | short midiVelocity = (short)(vel * 127);
1350 | int voicePlayIndex;
1351 | struct tsf_region *region, *regionEnd;
1352 |
1353 | if (preset_index < 0 || preset_index >= f->presetNum) return;
1354 | if (vel <= 0.0f) { tsf_note_off(f, preset_index, key); return; }
1355 |
1356 | // Play all matching regions.
1357 | voicePlayIndex = f->voicePlayIndex++;
1358 | for (region = f->presets[preset_index].regions, regionEnd = region + f->presets[preset_index].regionNum; region != regionEnd; region++)
1359 | {
1360 | struct tsf_voice *voice, *v, *vEnd; TSF_BOOL doLoop; float lowpassFilterQDB, lowpassFc;
1361 | if (key < region->lokey || key > region->hikey || midiVelocity < region->lovel || midiVelocity > region->hivel) continue;
1362 |
1363 | voice = TSF_NULL, v = f->voices, vEnd = v + f->voiceNum;
1364 | if (region->group)
1365 | {
1366 | for (; v != vEnd; v++)
1367 | if (v->playingPreset == preset_index && v->region->group == region->group) tsf_voice_endquick(f, v);
1368 | else if (v->playingPreset == -1 && !voice) voice = v;
1369 | }
1370 | else for (; v != vEnd; v++) if (v->playingPreset == -1) { voice = v; break; }
1371 |
1372 | if (!voice)
1373 | {
1374 | if (f->maxVoiceNum)
1375 | {
1376 | // voices have been pre-allocated and limited to a maximum, unable to start playing this voice
1377 | continue;
1378 | }
1379 | f->voiceNum += 4;
1380 | f->voices = (struct tsf_voice*)TSF_REALLOC(f->voices, f->voiceNum * sizeof(struct tsf_voice));
1381 | voice = &f->voices[f->voiceNum - 4];
1382 | voice[1].playingPreset = voice[2].playingPreset = voice[3].playingPreset = -1;
1383 | }
1384 |
1385 | voice->region = region;
1386 | voice->playingPreset = preset_index;
1387 | voice->playingKey = key;
1388 | voice->playIndex = voicePlayIndex;
1389 | voice->noteGainDB = f->globalGainDB - region->attenuation - tsf_gainToDecibels(1.0f / vel);
1390 |
1391 | if (f->channels)
1392 | {
1393 | f->channels->setupVoice(f, voice);
1394 | }
1395 | else
1396 | {
1397 | tsf_voice_calcpitchratio(voice, 0, f->outSampleRate);
1398 | // The SFZ spec is silent about the pan curve, but a 3dB pan law seems common. This sqrt() curve matches what Dimension LE does; Alchemy Free seems closer to sin(adjustedPan * pi/2).
1399 | voice->panFactorLeft = TSF_SQRTF(0.5f - region->pan);
1400 | voice->panFactorRight = TSF_SQRTF(0.5f + region->pan);
1401 | }
1402 |
1403 | // Offset/end.
1404 | voice->sourceSamplePosition = region->offset;
1405 |
1406 | // Loop.
1407 | doLoop = (region->loop_mode != TSF_LOOPMODE_NONE && region->loop_start < region->loop_end);
1408 | voice->loopStart = (doLoop ? region->loop_start : 0);
1409 | voice->loopEnd = (doLoop ? region->loop_end : 0);
1410 |
1411 | // Setup envelopes.
1412 | tsf_voice_envelope_setup(&voice->ampenv, ®ion->ampenv, key, midiVelocity, TSF_TRUE, f->outSampleRate);
1413 | tsf_voice_envelope_setup(&voice->modenv, ®ion->modenv, key, midiVelocity, TSF_FALSE, f->outSampleRate);
1414 |
1415 | // Setup lowpass filter.
1416 | lowpassFc = (region->initialFilterFc <= 13500 ? tsf_cents2Hertz((float)region->initialFilterFc) / f->outSampleRate : 1.0f);
1417 | lowpassFilterQDB = region->initialFilterQ / 10.0f;
1418 | voice->lowpass.QInv = 1.0 / TSF_POW(10.0, (lowpassFilterQDB / 20.0));
1419 | voice->lowpass.z1 = voice->lowpass.z2 = 0;
1420 | voice->lowpass.active = (lowpassFc < 0.499f);
1421 | if (voice->lowpass.active) tsf_voice_lowpass_setup(&voice->lowpass, lowpassFc);
1422 |
1423 | // Setup LFO filters.
1424 | tsf_voice_lfo_setup(&voice->modlfo, region->delayModLFO, region->freqModLFO, f->outSampleRate);
1425 | tsf_voice_lfo_setup(&voice->viblfo, region->delayVibLFO, region->freqVibLFO, f->outSampleRate);
1426 | }
1427 | }
1428 |
1429 | TSFDEF int tsf_bank_note_on(tsf* f, int bank, int preset_number, int key, float vel)
1430 | {
1431 | int preset_index = tsf_get_presetindex(f, bank, preset_number);
1432 | if (preset_index == -1) return 0;
1433 | tsf_note_on(f, preset_index, key, vel);
1434 | return 1;
1435 | }
1436 |
1437 | TSFDEF void tsf_note_off(tsf* f, int preset_index, int key)
1438 | {
1439 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum, *vMatchFirst = TSF_NULL, *vMatchLast = TSF_NULL;
1440 | for (; v != vEnd; v++)
1441 | {
1442 | //Find the first and last entry in the voices list with matching preset, key and look up the smallest play index
1443 | if (v->playingPreset != preset_index || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE) continue;
1444 | else if (!vMatchFirst || v->playIndex < vMatchFirst->playIndex) vMatchFirst = vMatchLast = v;
1445 | else if (v->playIndex == vMatchFirst->playIndex) vMatchLast = v;
1446 | }
1447 | if (!vMatchFirst) return;
1448 | for (v = vMatchFirst; v <= vMatchLast; v++)
1449 | {
1450 | //Stop all voices with matching preset, key and the smallest play index which was enumerated above
1451 | if (v != vMatchFirst && v != vMatchLast &&
1452 | (v->playIndex != vMatchFirst->playIndex || v->playingPreset != preset_index || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE)) continue;
1453 | tsf_voice_end(f, v);
1454 | }
1455 | }
1456 |
1457 | TSFDEF int tsf_bank_note_off(tsf* f, int bank, int preset_number, int key)
1458 | {
1459 | int preset_index = tsf_get_presetindex(f, bank, preset_number);
1460 | if (preset_index == -1) return 0;
1461 | tsf_note_off(f, preset_index, key);
1462 | return 1;
1463 | }
1464 |
1465 | TSFDEF void tsf_note_off_all(tsf* f)
1466 | {
1467 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum;
1468 | for (; v != vEnd; v++) if (v->playingPreset != -1 && v->ampenv.segment < TSF_SEGMENT_RELEASE)
1469 | tsf_voice_end(f, v);
1470 | }
1471 |
1472 | TSFDEF int tsf_active_voice_count(tsf* f)
1473 | {
1474 | int count = 0;
1475 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum;
1476 | for (; v != vEnd; v++) if (v->playingPreset != -1) count++;
1477 | return count;
1478 | }
1479 |
1480 | TSFDEF void tsf_render_short(tsf* f, short* buffer, int samples, int flag_mixing)
1481 | {
1482 | float *floatSamples;
1483 | int channelSamples = (f->outputmode == TSF_MONO ? 1 : 2) * samples, floatBufferSize = channelSamples * sizeof(float);
1484 | short* bufferEnd = buffer + channelSamples;
1485 | if (floatBufferSize > f->outputSampleSize)
1486 | {
1487 | TSF_FREE(f->outputSamples);
1488 | f->outputSamples = (float*)TSF_MALLOC(floatBufferSize);
1489 | f->outputSampleSize = floatBufferSize;
1490 | }
1491 |
1492 | tsf_render_float(f, f->outputSamples, samples, TSF_FALSE);
1493 |
1494 | floatSamples = f->outputSamples;
1495 | if (flag_mixing)
1496 | while (buffer != bufferEnd)
1497 | {
1498 | float v = *floatSamples++;
1499 | int vi = *buffer + (v < -1.00004566f ? (int)-32768 : (v > 1.00001514f ? (int)32767 : (int)(v * 32767.5f)));
1500 | *buffer++ = (vi < -32768 ? (short)-32768 : (vi > 32767 ? (short)32767 : (short)vi));
1501 | }
1502 | else
1503 | while (buffer != bufferEnd)
1504 | {
1505 | float v = *floatSamples++;
1506 | *buffer++ = (v < -1.00004566f ? (short)-32768 : (v > 1.00001514f ? (short)32767 : (short)(v * 32767.5f)));
1507 | }
1508 | }
1509 |
1510 | TSFDEF void tsf_render_float(tsf* f, float* buffer, int samples, int flag_mixing)
1511 | {
1512 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum;
1513 | if (!flag_mixing) TSF_MEMSET(buffer, 0, (f->outputmode == TSF_MONO ? 1 : 2) * sizeof(float) * samples);
1514 | for (; v != vEnd; v++)
1515 | if (v->playingPreset != -1)
1516 | tsf_voice_render(f, v, buffer, samples);
1517 | }
1518 |
1519 | static void tsf_channel_setup_voice(tsf* f, struct tsf_voice* v)
1520 | {
1521 | struct tsf_channel* c = &f->channels->channels[f->channels->activeChannel];
1522 | float newpan = v->region->pan + c->panOffset;
1523 | v->playingChannel = f->channels->activeChannel;
1524 | v->noteGainDB += c->gainDB;
1525 | tsf_voice_calcpitchratio(v, (c->pitchWheel == 8192 ? c->tuning : ((c->pitchWheel / 16383.0f * c->pitchRange * 2.0f) - c->pitchRange + c->tuning)), f->outSampleRate);
1526 | if (newpan <= -0.5f) { v->panFactorLeft = 1.0f; v->panFactorRight = 0.0f; }
1527 | else if (newpan >= 0.5f) { v->panFactorLeft = 0.0f; v->panFactorRight = 1.0f; }
1528 | else { v->panFactorLeft = TSF_SQRTF(0.5f - newpan); v->panFactorRight = TSF_SQRTF(0.5f + newpan); }
1529 | }
1530 |
1531 | static struct tsf_channel* tsf_channel_init(tsf* f, int channel)
1532 | {
1533 | int i;
1534 | if (f->channels && channel < f->channels->channelNum) return &f->channels->channels[channel];
1535 | if (!f->channels)
1536 | {
1537 | f->channels = (struct tsf_channels*)TSF_MALLOC(sizeof(struct tsf_channels));
1538 | f->channels->setupVoice = &tsf_channel_setup_voice;
1539 | f->channels->channels = NULL;
1540 | f->channels->channelNum = 0;
1541 | f->channels->activeChannel = 0;
1542 | }
1543 | i = f->channels->channelNum;
1544 | f->channels->channelNum = channel + 1;
1545 | f->channels->channels = (struct tsf_channel*)TSF_REALLOC(f->channels->channels, f->channels->channelNum * sizeof(struct tsf_channel));
1546 | for (; i <= channel; i++)
1547 | {
1548 | struct tsf_channel* c = &f->channels->channels[i];
1549 | c->presetIndex = c->bank = 0;
1550 | c->pitchWheel = c->midiPan = 8192;
1551 | c->midiVolume = c->midiExpression = 16383;
1552 | c->midiRPN = 0xFFFF;
1553 | c->midiData = 0;
1554 | c->panOffset = 0.0f;
1555 | c->gainDB = 0.0f;
1556 | c->pitchRange = 2.0f;
1557 | c->tuning = 0.0f;
1558 | }
1559 | return &f->channels->channels[channel];
1560 | }
1561 |
1562 | static void tsf_channel_applypitch(tsf* f, int channel, struct tsf_channel* c)
1563 | {
1564 | struct tsf_voice *v, *vEnd;
1565 | float pitchShift = (c->pitchWheel == 8192 ? c->tuning : ((c->pitchWheel / 16383.0f * c->pitchRange * 2.0f) - c->pitchRange + c->tuning));
1566 | for (v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++)
1567 | if (v->playingChannel == channel && v->playingPreset != -1)
1568 | tsf_voice_calcpitchratio(v, pitchShift, f->outSampleRate);
1569 | }
1570 |
1571 | TSFDEF void tsf_channel_set_presetindex(tsf* f, int channel, int preset_index)
1572 | {
1573 | tsf_channel_init(f, channel)->presetIndex = (unsigned short)preset_index;
1574 | }
1575 |
1576 | TSFDEF int tsf_channel_set_presetnumber(tsf* f, int channel, int preset_number, int flag_mididrums)
1577 | {
1578 | struct tsf_channel *c = tsf_channel_init(f, channel);
1579 | int preset_index;
1580 | if (flag_mididrums)
1581 | {
1582 | preset_index = tsf_get_presetindex(f, 128 | (c->bank & 0x7FFF), preset_number);
1583 | if (preset_index == -1) preset_index = tsf_get_presetindex(f, 128, preset_number);
1584 | if (preset_index == -1) preset_index = tsf_get_presetindex(f, 128, 0);
1585 | if (preset_index == -1) preset_index = tsf_get_presetindex(f, (c->bank & 0x7FFF), preset_number);
1586 | }
1587 | else preset_index = tsf_get_presetindex(f, (c->bank & 0x7FFF), preset_number);
1588 | if (preset_index == -1) preset_index = tsf_get_presetindex(f, 0, preset_number);
1589 | if (preset_index != -1)
1590 | {
1591 | c->presetIndex = (unsigned short)preset_index;
1592 | return 1;
1593 | }
1594 | return 0;
1595 | }
1596 |
1597 | TSFDEF void tsf_channel_set_bank(tsf* f, int channel, int bank)
1598 | {
1599 | tsf_channel_init(f, channel)->bank = (unsigned short)bank;
1600 | }
1601 |
1602 | TSFDEF int tsf_channel_set_bank_preset(tsf* f, int channel, int bank, int preset_number)
1603 | {
1604 | struct tsf_channel *c = tsf_channel_init(f, channel);
1605 | int preset_index = tsf_get_presetindex(f, bank, preset_number);
1606 | if (preset_index == -1) return 0;
1607 | c->presetIndex = (unsigned short)preset_index;
1608 | c->bank = (unsigned short)bank;
1609 | return 1;
1610 | }
1611 |
1612 | TSFDEF void tsf_channel_set_pan(tsf* f, int channel, float pan)
1613 | {
1614 | struct tsf_voice *v, *vEnd;
1615 | for (v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++)
1616 | if (v->playingChannel == channel && v->playingPreset != -1)
1617 | {
1618 | float newpan = v->region->pan + pan - 0.5f;
1619 | if (newpan <= -0.5f) { v->panFactorLeft = 1.0f; v->panFactorRight = 0.0f; }
1620 | else if (newpan >= 0.5f) { v->panFactorLeft = 0.0f; v->panFactorRight = 1.0f; }
1621 | else { v->panFactorLeft = TSF_SQRTF(0.5f - newpan); v->panFactorRight = TSF_SQRTF(0.5f + newpan); }
1622 | }
1623 | tsf_channel_init(f, channel)->panOffset = pan - 0.5f;
1624 | }
1625 |
1626 | TSFDEF void tsf_channel_set_volume(tsf* f, int channel, float volume)
1627 | {
1628 | struct tsf_channel *c = tsf_channel_init(f, channel);
1629 | float gainDB = tsf_gainToDecibels(volume), gainDBChange = gainDB - c->gainDB;
1630 | struct tsf_voice *v, *vEnd;
1631 | if (gainDBChange == 0) return;
1632 | for (v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++)
1633 | if (v->playingChannel == channel && v->playingPreset != -1)
1634 | v->noteGainDB += gainDBChange;
1635 | c->gainDB = gainDB;
1636 | }
1637 |
1638 | TSFDEF void tsf_channel_set_pitchwheel(tsf* f, int channel, int pitch_wheel)
1639 | {
1640 | struct tsf_channel *c = tsf_channel_init(f, channel);
1641 | if (c->pitchWheel == pitch_wheel) return;
1642 | c->pitchWheel = (unsigned short)pitch_wheel;
1643 | tsf_channel_applypitch(f, channel, c);
1644 | }
1645 |
1646 | TSFDEF void tsf_channel_set_pitchrange(tsf* f, int channel, float pitch_range)
1647 | {
1648 | struct tsf_channel *c = tsf_channel_init(f, channel);
1649 | if (c->pitchRange == pitch_range) return;
1650 | c->pitchRange = pitch_range;
1651 | if (c->pitchWheel != 8192) tsf_channel_applypitch(f, channel, c);
1652 | }
1653 |
1654 | TSFDEF void tsf_channel_set_tuning(tsf* f, int channel, float tuning)
1655 | {
1656 | struct tsf_channel *c = tsf_channel_init(f, channel);
1657 | if (c->tuning == tuning) return;
1658 | c->tuning = tuning;
1659 | tsf_channel_applypitch(f, channel, c);
1660 | }
1661 |
1662 | TSFDEF void tsf_channel_note_on(tsf* f, int channel, int key, float vel)
1663 | {
1664 | if (!f->channels || channel >= f->channels->channelNum) return;
1665 | f->channels->activeChannel = channel;
1666 | tsf_note_on(f, f->channels->channels[channel].presetIndex, key, vel);
1667 | }
1668 |
1669 | TSFDEF void tsf_channel_note_off(tsf* f, int channel, int key)
1670 | {
1671 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum, *vMatchFirst = TSF_NULL, *vMatchLast = TSF_NULL;
1672 | for (; v != vEnd; v++)
1673 | {
1674 | //Find the first and last entry in the voices list with matching channel, key and look up the smallest play index
1675 | if (v->playingPreset == -1 || v->playingChannel != channel || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE) continue;
1676 | else if (!vMatchFirst || v->playIndex < vMatchFirst->playIndex) vMatchFirst = vMatchLast = v;
1677 | else if (v->playIndex == vMatchFirst->playIndex) vMatchLast = v;
1678 | }
1679 | if (!vMatchFirst) return;
1680 | for (v = vMatchFirst; v <= vMatchLast; v++)
1681 | {
1682 | //Stop all voices with matching channel, key and the smallest play index which was enumerated above
1683 | if (v != vMatchFirst && v != vMatchLast &&
1684 | (v->playIndex != vMatchFirst->playIndex || v->playingPreset == -1 || v->playingChannel != channel || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE)) continue;
1685 | tsf_voice_end(f, v);
1686 | }
1687 | }
1688 |
1689 | TSFDEF void tsf_channel_note_off_all(tsf* f, int channel)
1690 | {
1691 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum;
1692 | for (; v != vEnd; v++)
1693 | if (v->playingPreset != -1 && v->playingChannel == channel && v->ampenv.segment < TSF_SEGMENT_RELEASE)
1694 | tsf_voice_end(f, v);
1695 | }
1696 |
1697 | TSFDEF void tsf_channel_sounds_off_all(tsf* f, int channel)
1698 | {
1699 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum;
1700 | for (; v != vEnd; v++)
1701 | if (v->playingPreset != -1 && v->playingChannel == channel && (v->ampenv.segment < TSF_SEGMENT_RELEASE || v->ampenv.parameters.release))
1702 | tsf_voice_endquick(f, v);
1703 | }
1704 |
1705 | TSFDEF void tsf_channel_midi_control(tsf* f, int channel, int controller, int control_value)
1706 | {
1707 | struct tsf_channel* c = tsf_channel_init(f, channel);
1708 | switch (controller)
1709 | {
1710 | case 7 /*VOLUME_MSB*/ : c->midiVolume = (unsigned short)((c->midiVolume & 0x7F ) | (control_value << 7)); goto TCMC_SET_VOLUME;
1711 | case 39 /*VOLUME_LSB*/ : c->midiVolume = (unsigned short)((c->midiVolume & 0x3F80) | control_value); goto TCMC_SET_VOLUME;
1712 | case 11 /*EXPRESSION_MSB*/ : c->midiExpression = (unsigned short)((c->midiExpression & 0x7F ) | (control_value << 7)); goto TCMC_SET_VOLUME;
1713 | case 43 /*EXPRESSION_LSB*/ : c->midiExpression = (unsigned short)((c->midiExpression & 0x3F80) | control_value); goto TCMC_SET_VOLUME;
1714 | case 10 /*PAN_MSB*/ : c->midiPan = (unsigned short)((c->midiPan & 0x7F ) | (control_value << 7)); goto TCMC_SET_PAN;
1715 | case 42 /*PAN_LSB*/ : c->midiPan = (unsigned short)((c->midiPan & 0x3F80) | control_value); goto TCMC_SET_PAN;
1716 | case 6 /*DATA_ENTRY_MSB*/ : c->midiData = (unsigned short)((c->midiData & 0x7F) | (control_value << 7)); goto TCMC_SET_DATA;
1717 | case 38 /*DATA_ENTRY_LSB*/ : c->midiData = (unsigned short)((c->midiData & 0x3F80) | control_value); goto TCMC_SET_DATA;
1718 | case 0 /*BANK_SELECT_MSB*/ : c->bank = (unsigned short)(0x8000 | control_value); return; //bank select MSB alone acts like LSB
1719 | case 32 /*BANK_SELECT_LSB*/ : c->bank = (unsigned short)((c->bank & 0x8000 ? ((c->bank & 0x7F) << 7) : 0) | control_value); return;
1720 | case 101 /*RPN_MSB*/ : c->midiRPN = (unsigned short)(((c->midiRPN == 0xFFFF ? 0 : c->midiRPN) & 0x7F ) | (control_value << 7)); return;
1721 | case 100 /*RPN_LSB*/ : c->midiRPN = (unsigned short)(((c->midiRPN == 0xFFFF ? 0 : c->midiRPN) & 0x3F80) | control_value); return;
1722 | case 98 /*NRPN_LSB*/ : c->midiRPN = 0xFFFF; return;
1723 | case 99 /*NRPN_MSB*/ : c->midiRPN = 0xFFFF; return;
1724 | case 120 /*ALL_SOUND_OFF*/ : tsf_channel_sounds_off_all(f, channel); return;
1725 | case 123 /*ALL_NOTES_OFF*/ : tsf_channel_note_off_all(f, channel); return;
1726 | case 121 /*ALL_CTRL_OFF*/ :
1727 | c->midiVolume = c->midiExpression = 16383;
1728 | c->midiPan = 8192;
1729 | c->bank = 0;
1730 | tsf_channel_set_volume(f, channel, 1.0f);
1731 | tsf_channel_set_pan(f, channel, 0.5f);
1732 | tsf_channel_set_pitchrange(f, channel, 2.0f);
1733 | return;
1734 | }
1735 | return;
1736 | TCMC_SET_VOLUME:
1737 | //Raising to the power of 3 seems to result in a decent sounding volume curve for MIDI
1738 | tsf_channel_set_volume(f, channel, TSF_POWF((c->midiVolume / 16383.0f) * (c->midiExpression / 16383.0f), 3.0f));
1739 | return;
1740 | TCMC_SET_PAN:
1741 | tsf_channel_set_pan(f, channel, c->midiPan / 16383.0f);
1742 | return;
1743 | TCMC_SET_DATA:
1744 | if (c->midiRPN == 0) tsf_channel_set_pitchrange(f, channel, (c->midiData >> 7) + 0.01f * (c->midiData & 0x7F));
1745 | else if (c->midiRPN == 1) tsf_channel_set_tuning(f, channel, (int)c->tuning + ((float)c->midiData - 8192.0f) / 8192.0f); //fine tune
1746 | else if (c->midiRPN == 2 && controller == 6) tsf_channel_set_tuning(f, channel, ((float)control_value - 64.0f) + (c->tuning - (int)c->tuning)); //coarse tune
1747 | return;
1748 | }
1749 |
1750 | TSFDEF int tsf_channel_get_preset_index(tsf* f, int channel)
1751 | {
1752 | return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].presetIndex : 0);
1753 | }
1754 |
1755 | TSFDEF int tsf_channel_get_preset_bank(tsf* f, int channel)
1756 | {
1757 | return (f->channels && channel < f->channels->channelNum ? (f->channels->channels[channel].bank & 0x7FFF) : 0);
1758 | }
1759 |
1760 | TSFDEF int tsf_channel_get_preset_number(tsf* f, int channel)
1761 | {
1762 | return (f->channels && channel < f->channels->channelNum ? f->presets[f->channels->channels[channel].presetIndex].preset : 0);
1763 | }
1764 |
1765 | TSFDEF float tsf_channel_get_pan(tsf* f, int channel)
1766 | {
1767 | return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].panOffset - 0.5f : 0.5f);
1768 | }
1769 |
1770 | TSFDEF float tsf_channel_get_volume(tsf* f, int channel)
1771 | {
1772 | return (f->channels && channel < f->channels->channelNum ? tsf_decibelsToGain(f->channels->channels[channel].gainDB) : 1.0f);
1773 | }
1774 |
1775 | TSFDEF int tsf_channel_get_pitchwheel(tsf* f, int channel)
1776 | {
1777 | return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].pitchWheel : 8192);
1778 | }
1779 |
1780 | TSFDEF float tsf_channel_get_pitchrange(tsf* f, int channel)
1781 | {
1782 | return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].pitchRange : 2.0f);
1783 | }
1784 |
1785 | TSFDEF float tsf_channel_get_tuning(tsf* f, int channel)
1786 | {
1787 | return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].tuning : 0.0f);
1788 | }
1789 |
1790 | #ifdef __cplusplus
1791 | }
1792 | #endif
1793 |
1794 | #endif //TSF_IMPLEMENTATION
1795 |
--------------------------------------------------------------------------------