ref: 6e5657413d5ed686c396b394440239fb8c512530
parent: d21fe7b22c8c906e749e4be4bb389efc2e761964
author: qwx <[email protected]>
date: Wed Mar 1 18:01:50 EST 2023
port + README
--- a/README.md
+++ b/README.md
@@ -1,37 +1,44 @@
-# TinySoundFont
-SoundFont2 synthesizer library in a single C/C++ file
-
-## Overview
-
-TinySoundFont is a software synthesizer using SoundFont2 sound bank files.
-
-The library is a single C header file so it is extremely simple to integrate in your C/C++ projects.
-
-```c++
-#define TSF_IMPLEMENTATION
-#include "tsf.h"
-
-...
-
-tsf* TinySoundFont = tsf_load_filename("soundfont.sf2");
-tsf_set_output(TinySoundFont, TSF_MONO, 44100, 0); //sample rate
-tsf_note_on(TinySoundFont, 0, 60, 1.0f); //preset 0, middle C
-short HalfSecond[22050]; //synthesize 0.5 seconds
-tsf_render_short(TinySoundFont, HalfSecond, 22050, 0);
-```
-
-The library code is based on [SFZero by Steve Folta](https://github.com/stevefolta/SFZero).
-
-## Documentation
-
-The API documentation can be found on [top of the library source code](https://github.com/schellingb/TinySoundFont/blob/master/tsf.h).
-
-There are also [examples available](https://github.com/schellingb/TinySoundFont/tree/master/examples) which come with a sample SoundFont file and build and play sound on Win32, Win64, Linux and MacOSX with no further dependencies.
-
-## Dependencies
-
-C standard libraries for fopen, math and malloc (can be removed by providing custom functions with #defines).
-
-## License
-
-TinySoundFont is available under the [MIT license](https://choosealicense.com/licenses/mit/).
+# sf2mid: TinySoundFont-based midi player using soundfont2 banks
+
+## Usage
+
+Recommended soundfont: Patch93's Roland SC55 font.
+
+Example usage: playing a doom midi file
+
+ ; games/wadfs /sys/games/lib/doom/doom1.wad
+ createfile SW18_7: file already exists
+ ; sf2mid /lib/midi/sf2/patch93.sc-55.sf2 d_messag.mid >/dev/audio
+ ; games/mus </mnt/wad/d_e1m3 \
+ | sf2mid /lib/midi/sf2/patch93.sc-55.sf2 >/dev/audio
+
+See: [mus(1)](http://man.9front.org/1/mus).
+
+Example usage: using sf2mid for playback in doom(1):
+
+ Edit dmus(1):
+ ; cat /bin/dmus
+ #!/bin/rc
+ #sf2=sc55.v3.7.sf2
+ sf2=patch93.sc-55.sf2
+ if(test -f /lib/midi/sf2/$sf2)
+ c=(sf2mid /lib/midi/sf2/$sf2)
+ if not if(test -f /tmp/genmidi.*)
+ c=(games/dmid -i /tmp/genmidi.* '|' games/opl3)
+ if not
+ c=(games/midi -c)
+ if(~ `{file -m $1} audio/mus)
+ c=(games/mus '<' $1 '|' $c)
+ if not
+ c=('<' $1 $c)
+ eval $c
+
+Current port-specific issues: performance is not great;
+doom will hang while sf2mid unscrews itself.
+
+## Library
+
+See [TinySoundFont library](https://github.com/schellingb/TinySoundFont)
+for more information.
+
+License: MIT
--- /dev/null
+++ b/sf2mid.c
@@ -1,0 +1,99 @@
+/* adapted from examples */
+#define TSF_IMPLEMENTATION
+#include "tsf.h"
+#define TML_IMPLEMENTATION
+#include "tml.h"
+
+#include <unistd.h>
+
+tsf *ttsf;
+double T; /* msec */
+tml_message* next;
+
+enum{
+ Sampsz = 2 * 2,
+ Delay = 1764, /* 40 ms */
+ Rate = 44100,
+};
+
+static int
+samp(void*, tsf_s16 *stream, int samp)
+{
+ int chunk;
+
+ samp *= 2;
+ for(chunk=2048; samp!=0; samp-=chunk){
+ if(chunk > samp)
+ chunk = samp;
+ T += (1000.0 / Rate) * chunk / 2;
+ for(; next && T >= next->time; next = next->next){
+ switch(next->type){
+ case TML_PROGRAM_CHANGE:
+ tsf_channel_set_presetnumber(ttsf, next->channel,
+ next->program, next->channel == 9);
+ break;
+ case TML_NOTE_ON:
+ tsf_channel_note_on(ttsf, next->channel,
+ next->key, next->velocity / 127.0);
+ break;
+ case TML_NOTE_OFF:
+ tsf_channel_note_off(ttsf, next->channel,
+ next->key);
+ break;
+ case TML_PITCH_BEND:
+ tsf_channel_set_pitchwheel(ttsf, next->channel,
+ next->pitch_bend);
+ break;
+ case TML_CONTROL_CHANGE:
+ tsf_channel_midi_control(ttsf, next->channel,
+ next->control, next->control_value);
+ break;
+ }
+ }
+ if(next == NULL)
+ return -1;
+ tsf_render_short(ttsf, stream, chunk, 0);
+ stream += chunk;
+ }
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ FILE *out;
+ char *sf, *mid;
+ tml_message* tml;
+ tsf_s16 obuf[Sampsz * Delay];
+
+ sf = NULL;
+ mid = "/fd/0";
+ out = stdout;
+ if(argc < 2){
+ fprintf(stderr, "usage: %s SF2BANK [FILE]\n", argv[0]);
+ return 1;
+ }
+ if(argc >= 2)
+ sf = argv[1];
+ if(argc >= 3)
+ mid = argv[2];
+ if((tml = tml_load_filename(mid)) == NULL){
+ fprintf(stderr, "error reading midi file\n");
+ return 1;
+ }
+ if((ttsf = tsf_load_filename(sf)) == NULL){
+ fprintf(stderr, "error loading soundfont\n");
+ return 2;
+ }
+ next = tml;
+ // arm 10th channel for percussion sound bank (128) if available
+ tsf_channel_set_bank_preset(ttsf, 9, 128, -3.0);
+ tsf_set_output(ttsf, TSF_STEREO_UNWEAVED, Rate, 0.0);
+ for(;;){
+ memset(obuf, 0, sizeof obuf);
+ if(samp(NULL, obuf, Delay) < 0)
+ break;
+ fwrite(obuf, Delay, 4, out);
+ }
+ return 0;
+}
--- a/tsf.h
+++ b/tsf.h
@@ -384,6 +384,7 @@
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 };
+#pragma pack on
struct tsf_hydra
{
struct tsf_hydra_phdr *phdrs; struct tsf_hydra_pbag *pbags; struct tsf_hydra_pmod *pmods;
@@ -471,6 +472,7 @@
int channelNum, activeChannel;
struct tsf_channel channels[1];
};
+#pragma pack off
static double tsf_timecents2Secsd(double timecents) { return TSF_POW(2.0, timecents / 1200.0); }
static float tsf_timecents2Secsf(float timecents) { return TSF_POWF(2.0f, timecents / 1200.0f); }
@@ -1170,7 +1172,7 @@
double adjustedPitch = v->region->pitch_keycenter + (note - v->region->pitch_keycenter) * (v->region->pitch_keytrack / 100.0);
if (pitchShift) adjustedPitch += pitchShift;
v->pitchInputTimecents = adjustedPitch * 100.0;
- v->pitchOutputFactor = v->region->sample_rate / (tsf_timecents2Secsd(v->region->pitch_keycenter * 100.0) * outSampleRate);
+ v->pitchOutputFactor = v->region->sample_rate / (tsf_timecents2Secsd(v->region->pitch_keycenter * 100.0) * outSampleRate * 2.0f);
}
static void tsf_voice_render(tsf* f, struct tsf_voice* v, float* outputBuffer, int numSamples)
@@ -1644,7 +1646,7 @@
while (samples > 0)
{
int channelSamples = (samples > maxChannelSamples ? maxChannelSamples : samples);
- short* bufferEnd = buffer + channelSamples * channels;
+ short* bufferEnd = buffer + channelSamples;// * channels;
float *floatSamples = outputSamples;
tsf_render_float(f, floatSamples, channelSamples, TSF_FALSE);
samples -= channelSamples;