From f08be7db824cbac0a1aeafaac1f603a29d743c38 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 28 Sep 2024 08:16:12 +0300
Subject: [PATCH 01/59] * Android: UI: popup suggestions where applicable
---
.../github/stsaz/phiola/ConvertActivity.java | 9 +++
.../com/github/stsaz/phiola/ExplorerMenu.java | 71 +++++++++++++++++++
.../github/stsaz/phiola/ListSaveActivity.java | 6 +-
.../github/stsaz/phiola/SettingsActivity.java | 22 +++++-
.../java/com/github/stsaz/phiola/VarMenu.java | 37 ++++++++++
5 files changed, 141 insertions(+), 4 deletions(-)
create mode 100644 android/phiola/src/main/java/com/github/stsaz/phiola/ExplorerMenu.java
create mode 100644 android/phiola/src/main/java/com/github/stsaz/phiola/VarMenu.java
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/ConvertActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/ConvertActivity.java
index 6cff157..1464762 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/ConvertActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/ConvertActivity.java
@@ -15,6 +15,8 @@
public class ConvertActivity extends AppCompatActivity {
private static final String TAG = "phiola.ConvertActivity";
Core core;
+ private VarMenu varmenu;
+ private ExplorerMenu explorer;
private long length_msec;
private ConvertBinding b;
@@ -24,6 +26,13 @@ protected void onCreate(Bundle savedInstanceState) {
b = ConvertBinding.inflate(getLayoutInflater());
setContentView(b.getRoot());
+ varmenu = new VarMenu(this);
+ explorer = new ExplorerMenu(this);
+ b.eOutDir.setOnClickListener(v -> explorer.show(b.eOutDir));
+ b.eOutName.setOnClickListener(v -> varmenu.show(b.eOutName, new String[]{
+ "@filename", "@album", "@artist", "@title", "@tracknumber",
+ }));
+
ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item
, CoreSettings.conv_format_display);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/ExplorerMenu.java b/android/phiola/src/main/java/com/github/stsaz/phiola/ExplorerMenu.java
new file mode 100644
index 0000000..daa731a
--- /dev/null
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/ExplorerMenu.java
@@ -0,0 +1,71 @@
+/** phiola/Android
+2024, Simon Zolin */
+
+package com.github.stsaz.phiola;
+
+import android.app.Activity;
+import android.view.MenuItem;
+import android.widget.EditText;
+import android.widget.PopupMenu;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class ExplorerMenu {
+ private Core core;
+ private Activity parent;
+ private EditText ctl;
+ private String path;
+ private String[] files;
+ private boolean up_dir;
+
+ ExplorerMenu(Activity parent) {
+ core = Core.getInstance();
+ this.parent = parent;
+ }
+
+ void show(EditText v) {
+ ctl = v;
+ String[] rows;
+
+ path = ctl.getText().toString();
+ if (path.isEmpty()) {
+ rows = core.storage_paths;
+ files = core.storage_paths;
+ up_dir = false;
+
+ } else {
+ UtilNative.Files f = core.util.dirList(path, 0);
+ ArrayList a = new ArrayList<>(Arrays.asList(f.display_rows).subList(0, f.n_directories));
+ rows = a.toArray(new String[0]);
+ files = f.file_names;
+ up_dir = true;
+ }
+
+ PopupMenu m = new PopupMenu(parent, v);
+ m.setOnMenuItemClickListener(this::menu_click);
+ int i = 0;
+
+ if (up_dir)
+ m.getMenu().add(0, i++, 0, parent.getString(R.string.explorer_up));
+
+ for (String s : rows) {
+ m.getMenu().add(0, i++, 0, s);
+ }
+ m.show();
+ }
+
+ private boolean menu_click(MenuItem item) {
+ int i = item.getItemId();
+ String s;
+ if (i == 0 && up_dir) {
+ s = Util.path_split2(path)[0];
+ } else {
+ if (up_dir)
+ i--;
+ s = files[i];
+ }
+ ctl.setText(s);
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/ListSaveActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/ListSaveActivity.java
index a8d39d4..aac2ca0 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/ListSaveActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/ListSaveActivity.java
@@ -13,7 +13,8 @@
public class ListSaveActivity extends AppCompatActivity {
private static final String TAG = "phiola.ListSaveActivity";
- Core core;
+ private Core core;
+ private ExplorerMenu explorer;
private ListSaveBinding b;
protected void onCreate(Bundle savedInstanceState) {
@@ -21,7 +22,10 @@ protected void onCreate(Bundle savedInstanceState) {
b = ListSaveBinding.inflate(getLayoutInflater());
setContentView(b.getRoot());
+ explorer = new ExplorerMenu(this);
+
b.bSave.setOnClickListener((v) -> save());
+ b.eDir.setOnClickListener(v -> explorer.show(b.eDir));
core = Core.getInstance();
load();
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java
index 9d43a3c..c5f3df2 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java
@@ -18,6 +18,7 @@ public class SettingsActivity extends AppCompatActivity {
private static final String TAG = "phiola.SettingsActivity";
private Core core;
private SettingsBinding b;
+ private ExplorerMenu explorer;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -29,6 +30,20 @@ protected void onCreate(Bundle savedInstanceState) {
if (actionBar != null)
actionBar.setDisplayHomeAsUpEnabled(true);
+ explorer = new ExplorerMenu(this);
+
+ play_init();
+
+ b.eDataDir.setOnClickListener(v -> explorer.show(b.eDataDir));
+ b.eQuickMoveDir.setOnClickListener(v -> explorer.show(b.eQuickMoveDir));
+
+ rec_init();
+
+ core = Core.getInstance();
+ load();
+ }
+
+ private void play_init() {
b.sbPlayAutoSkip.setMax(auto_skip_progress(200));
b.sbPlayAutoSkip.setOnSeekBarChangeListener(new SBOnSeekBarChangeListener() {
@Override
@@ -58,6 +73,10 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
}
});
+ }
+
+ private void rec_init() {
+ b.eRecDir.setOnClickListener(v -> explorer.show(b.eRecDir));
ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item
, new String[] {
@@ -131,9 +150,6 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
b.eRecGain.setEnabled(false);
b.swRecExclusive.setEnabled(false);
}
-
- core = Core.getInstance();
- load();
}
@Override
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/VarMenu.java b/android/phiola/src/main/java/com/github/stsaz/phiola/VarMenu.java
new file mode 100644
index 0000000..e249d1a
--- /dev/null
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/VarMenu.java
@@ -0,0 +1,37 @@
+/** phiola/Android
+2024, Simon Zolin */
+
+package com.github.stsaz.phiola;
+
+import android.app.Activity;
+import android.view.MenuItem;
+import android.widget.EditText;
+import android.widget.PopupMenu;
+
+public class VarMenu {
+ private Activity parent;
+ private EditText ctl;
+ private String[] rows;
+
+ VarMenu(Activity parent) {
+ this.parent = parent;
+ }
+
+ void show(EditText v, String[] rows) {
+ ctl = v;
+ this.rows = rows;
+
+ PopupMenu m = new PopupMenu(parent, v);
+ m.setOnMenuItemClickListener(this::menu_click);
+ int i = 0;
+ for (String s : rows) {
+ m.getMenu().add(0, i++, 0, s);
+ }
+ m.show();
+ }
+
+ private boolean menu_click(MenuItem item) {
+ ctl.getText().append(rows[item.getItemId()]);
+ return true;
+ }
+}
From e0e6db4d21908aee11f5bdf84f7fda960482758a Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 28 Sep 2024 08:16:12 +0300
Subject: [PATCH 02/59] * Android: seek forward on long pressing Next button
---
.../com/github/stsaz/phiola/MainActivity.java | 26 +++++++++++++++++--
.../com/github/stsaz/phiola/Settings.java | 7 +++++
2 files changed, 31 insertions(+), 2 deletions(-)
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
index 9259ec3..d29a8cc 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
@@ -331,9 +331,17 @@ private void init_ui() {
b.bplay.setOnClickListener((v) -> play_pause_click());
- b.bnext.setOnClickListener((v) -> trackctl.next());
-
b.bprev.setOnClickListener((v) -> trackctl.prev());
+ b.bprev.setOnLongClickListener((v) -> {
+ seek_back();
+ return true;
+ });
+
+ b.bnext.setOnClickListener((v) -> trackctl.next());
+ b.bnext.setOnLongClickListener((v) -> {
+ seek_fwd();
+ return true;
+ });
b.bexplorer.setOnClickListener((v) -> explorer_click());
@@ -877,6 +885,20 @@ private void seek(int percent) {
trackctl.seek((total_dur_msec / 1000) * percent / 100 * 1000);
}
+ private void seek_back() {
+ int percent = b.seekbar.getProgress() - core.setts.play_seek_back_percent;
+ if (percent < 0)
+ percent = 0;
+ seek(percent);
+ }
+
+ private void seek_fwd() {
+ int percent = b.seekbar.getProgress() + core.setts.play_seek_fwd_percent;
+ if (percent >= 100)
+ percent = 99;
+ seek(percent);
+ }
+
// [Playing]
// [PLA,STP,REC|RPA,CON]
private String state_flags(int st) {
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java
index 21eb38c..0cffa38 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java
@@ -67,6 +67,8 @@ void auto_skip_tail_set(String s) {
core.phiola.playCmd(Phiola.PC_AUTO_SKIP_TAIL, n);
}
+ int play_seek_fwd_percent, play_seek_back_percent;
+
String rec_path; // directory for recordings
String rec_name_template;
String rec_enc, rec_fmt;
@@ -284,6 +286,11 @@ void normalize() {
if (trash_dir.isEmpty())
trash_dir = "Trash";
+ if (play_seek_fwd_percent <= 0 || play_seek_fwd_percent > 50)
+ play_seek_fwd_percent = 5;
+ if (play_seek_back_percent <= 0 || play_seek_back_percent > 50)
+ play_seek_back_percent = 5;
+
normalize_rec();
normalize_convert();
}
From e6643f97b68a90a4e21403ac97abd02a4be63271 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 28 Sep 2024 08:16:12 +0300
Subject: [PATCH 03/59] + Android: Convert: new setting: `Sample Format`
---
.../com/github/stsaz/phiola/ConvertActivity.java | 7 +++++++
.../main/java/com/github/stsaz/phiola/Phiola.java | 1 +
.../java/com/github/stsaz/phiola/Settings.java | 14 ++++++++++++++
android/phiola/src/main/res/layout/convert.xml | 12 ++++++++++++
android/phiola/src/main/res/values/strings.xml | 1 +
src/jni/queue.h | 6 ++++++
6 files changed, 41 insertions(+)
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/ConvertActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/ConvertActivity.java
index 1464762..4429c0f 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/ConvertActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/ConvertActivity.java
@@ -61,6 +61,11 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
});
b.bUntilSetCur.setOnClickListener((v) -> pos_set_cur(false));
+ adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item
+ , CoreSettings.conv_sample_formats_str);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ b.spSampleFormat.setAdapter(adapter);
+
b.sbAacQ.setMax(aac_q_progress(5));
b.sbAacQ.setOnSeekBarChangeListener(new SBOnSeekBarChangeListener() {
@Override
@@ -89,6 +94,7 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
});
b.swCopy.setOnCheckedChangeListener((v, checked) -> {
+ b.spSampleFormat.setEnabled(!checked);
b.eSampleRate.setEnabled(!checked);
b.sbAacQ.setEnabled(!checked);
@@ -261,6 +267,7 @@ private void convert() {
Phiola.ConvertParams p = new Phiola.ConvertParams();
p.from_msec = b.eFrom.getText().toString();
p.to_msec = b.eUntil.getText().toString();
+ p.sample_format = CoreSettings.conv_sample_formats[b.spSampleFormat.getSelectedItemPosition()];
p.sample_rate = core.str_to_uint(b.eSampleRate.getText().toString(), 0);
p.aac_quality = core.setts.conv_aac_quality;
p.opus_quality = core.setts.conv_opus_quality;
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
index 824d405..9c57c8a 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
@@ -70,6 +70,7 @@ static class ConvertParams {
String out_name;
String from_msec, to_msec;
+ int sample_format;
int sample_rate;
int aac_quality;
int opus_quality;
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java
index 0cffa38..9327104 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java
@@ -99,6 +99,20 @@ void auto_skip_tail_set(String s) {
boolean conv_copy;
boolean conv_file_date_preserve;
boolean conv_new_add_list;
+ static final int[] conv_sample_formats = {
+ 0,
+ 8, // PHI_PCM_8
+ 16, // PHI_PCM_16
+ 24, // PHI_PCM_24
+ 32, // PHI_PCM_32
+ };
+ static final String[] conv_sample_formats_str = {
+ "As Source",
+ "int8",
+ "int16",
+ "int24",
+ "int32",
+ };
static final int conv_encoders[] = {
Phiola.AF_AAC_LC,
Phiola.AF_AAC_HE,
diff --git a/android/phiola/src/main/res/layout/convert.xml b/android/phiola/src/main/res/layout/convert.xml
index dae4b14..c3f2fe4 100644
--- a/android/phiola/src/main/res/layout/convert.xml
+++ b/android/phiola/src/main/res/layout/convert.xml
@@ -132,6 +132,18 @@
android:text="@string/conv_filters"
android:textColor="@color/sett_group" />
+
+
+
End Audio Position:
Filters
+ Sample Format
Sample rate (Hz):
Encoding
diff --git a/src/jni/queue.h b/src/jni/queue.h
index 28d3749..65f26bb 100644
--- a/src/jni/queue.h
+++ b/src/jni/queue.h
@@ -443,6 +443,7 @@ Java_com_github_stsaz_phiola_Phiola_quConvertBegin(JNIEnv *env, jobject thiz, jl
struct phi_track_conf conf = {
.ifile.preserve_date = !!(flags & F_DATE_PRESERVE),
.stream_copy = !!(flags & F_COPY),
+ .oaudio.format.format = jni_obj_int(jconf, jni_field_int(jc_conf, "sample_format")),
.oaudio.format.rate = jni_obj_int(jconf, jni_field_int(jc_conf, "sample_rate")),
.aac.quality = jni_obj_int(jconf, jni_field_int(jc_conf, "aac_quality")),
.vorbis.quality = jni_obj_int(jconf, jni_field_int(jc_conf, "vorbis_quality")),
@@ -470,14 +471,19 @@ Java_com_github_stsaz_phiola_Phiola_quConvertBegin(JNIEnv *env, jobject thiz, jl
struct phi_queue_entry *qe;
for (i = 0; !!(qe = x->queue.at(q, i)); i++) {
struct phi_track_conf *c = &qe->conf;
+
c->ifile.preserve_date = conf.ifile.preserve_date;
c->seek_msec = conf.seek_msec;
c->until_msec = conf.until_msec;
c->stream_copy = conf.stream_copy;
+
+ c->oaudio.format.format = conf.oaudio.format.format;
c->oaudio.format.rate = conf.oaudio.format.rate;
+
c->aac.quality = conf.aac.quality;
c->vorbis.quality = conf.vorbis.quality;
c->opus.bitrate = conf.opus.bitrate;
+
c->ofile.name = ffsz_dup(ofn);
c->ofile.name_tmp = 1;
c->ofile.overwrite = conf.ofile.overwrite;
From 80ee799cf25a07afb48f6d9fc89ac7fa76dc584c Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 28 Sep 2024 08:16:12 +0300
Subject: [PATCH 04/59] * .ogg read: support chained Opus stream
---
doc/arch/arch.md | 18 ++++++++
src/acodec/opus-dec.h | 25 +++++++----
src/format/ogg-read.h | 16 +++++--
src/format/opus-meta.h | 94 ++++++++++++++++++++++++++++--------------
src/track.h | 1 +
test.sh | 13 ++++++
6 files changed, 123 insertions(+), 44 deletions(-)
diff --git a/doc/arch/arch.md b/doc/arch/arch.md
index 7222af3..237ad5e 100644
--- a/doc/arch/arch.md
+++ b/doc/arch/arch.md
@@ -85,6 +85,24 @@ Key factor: 'fmt' and 'dec' know nothing about `abs_seek`;
`AS` - audio offset for the current .cue track (`abs_seek`).
+## .ogg chained stream reading
+
+```C
+ogg.wr opus-meta opus.dec
+=======================================
+...
+* detects new stream
+RST=1 -hdr-> ?RST
+ *hdr
+ -> *tag -hdr-> ?RST
+ RST=0 <- *skip
+ -> *data -tag-> *skip
+ -data-> *decode
+```
+
+`RST` flag is set when a new logical stream is detected; it signifies that the next 2 packets are opus-info and vorbis-tags.
+
+
## .ogg write
Case 1. Add packets normally with known audio position of every packet.
diff --git a/src/acodec/opus-dec.h b/src/acodec/opus-dec.h
index a448777..ed378a0 100644
--- a/src/acodec/opus-dec.h
+++ b/src/acodec/opus-dec.h
@@ -17,14 +17,6 @@ static void* opus_open(phi_track *t)
return PHI_OPEN_ERR;
struct opus_dec *o = phi_track_allocT(t, struct opus_dec);
-
- if (ffopus_open(&o->opus)) {
- errlog(t, "ffopus_open(): %s", ffopus_errstr(&o->opus));
- phi_track_free(t, o);
- return PHI_OPEN_ERR;
- }
-
- o->prev_page_pos = ~0ULL;
return o;
}
@@ -46,7 +38,7 @@ static const char* opus_pkt_mode(const char *d)
static int opus_in_decode(void *ctx, phi_track *t)
{
- enum { R_HDR, R_TAGS, R_DATA1, R_DATA };
+ enum { R_INIT, R_HDR, R_TAGS, R_DATA1, R_DATA };
struct opus_dec *o = ctx;
int r;
const char *opus_mode = NULL;
@@ -63,6 +55,15 @@ static int opus_in_decode(void *ctx, phi_track *t)
ffstr out;
for (;;) {
switch (o->state) {
+ case R_INIT:
+ if (ffopus_open(&o->opus)) {
+ errlog(t, "ffopus_open(): %s", ffopus_errstr(&o->opus));
+ return PHI_ERR;
+ }
+ o->prev_page_pos = ~0ULL;
+ o->state = R_HDR;
+ // fallthrough
+
case R_HDR:
case R_TAGS:
if (!(t->chain_flags & PHI_FFWD))
@@ -84,6 +85,12 @@ static int opus_in_decode(void *ctx, phi_track *t)
// fallthrough
case R_DATA:
+ if (t->audio.ogg_reset) {
+ ffopus_close(&o->opus);
+ ffmem_zero_obj(o);
+ continue;
+ }
+
if (t->audio.seek_req) {
// a new seek request is received, pass control to UI module
o->reset_decoder = 1;
diff --git a/src/format/ogg-read.h b/src/format/ogg-read.h
index 58c0b1c..f25134e 100644
--- a/src/format/ogg-read.h
+++ b/src/format/ogg-read.h
@@ -7,6 +7,7 @@ struct ogg_r {
oggread og;
ffstr in;
void *trk;
+ uint page_serial;
uint sample_rate;
uint state;
uint stmcopy :1;
@@ -133,15 +134,24 @@ static int ogg_read(void *ctx, phi_track *t)
}
return PHI_MORE;
- case OGGREAD_HEADER:
- case OGGREAD_DATA:
+ case OGGREAD_HEADER: {
+ const struct oggread_info *i = oggread_info(&o->og);
if (o->state == I_HDR) {
o->state = I_INFO;
- const struct oggread_info *i = oggread_info(&o->og);
t->audio.total = (i->total_samples) ? i->total_samples : ~0ULL;
if (0 != add_decoder(o, t, t->data_out))
return PHI_ERR;
+ } else {
+ if (o->page_serial != i->serial) {
+ dbglog(t, "new logical stream");
+ t->audio.ogg_reset = 1;
+ }
}
+ o->page_serial = i->serial;
+ goto data;
+ }
+
+ case OGGREAD_DATA:
goto data;
case OGGREAD_DONE:
diff --git a/src/format/opus-meta.h b/src/format/opus-meta.h
index 0aadcd8..61e5e64 100644
--- a/src/format/opus-meta.h
+++ b/src/format/opus-meta.h
@@ -4,6 +4,7 @@
#include
#include
+extern const phi_meta_if phi_metaif;
extern int vorbistag_read(phi_track *t, ffstr vc);
struct opus_hdr {
@@ -52,6 +53,7 @@ static int opuscomment_read(ffstr pkt, ffstr *body)
struct opusmeta {
uint state;
+ uint reset :1;
ffvec hdr;
ffstr tags;
};
@@ -72,43 +74,71 @@ static void opusmeta_close(void *ctx, phi_track *t)
static int opusmeta_read(void *ctx, phi_track *t)
{
struct opusmeta *o = ctx;
- switch (o->state) {
- case 0: {
- struct opus_info info;
- if (0 != opusinfo_read(&info, t->data_in)) {
- errlog(t, "opusinfo_read");
- return PHI_ERR;
+ for (;;) {
+ switch (o->state) {
+ case 0: {
+ struct opus_info info;
+ if (0 != opusinfo_read(&info, t->data_in)) {
+ errlog(t, "opusinfo_read");
+ return PHI_ERR;
+ }
+ t->audio.decoder = "Opus";
+
+ if (o->reset) {
+ if (!(info.channels == t->audio.format.channels
+ && info.rate == t->audio.format.rate)) {
+ errlog(t, "changing the audio format on-the-fly is not supported");
+ return PHI_ERR;
+ }
+ t->meta_changed = 1;
+ }
+
+ struct phi_af f = {
+ .format = PHI_PCM_FLOAT32,
+ .channels = info.channels,
+ .rate = info.rate,
+ };
+ t->audio.format = f;
+
+ ffvec_addstr(&o->hdr, &t->data_in);
+ o->state = 1;
+ return PHI_MORE;
}
- t->audio.decoder = "Opus";
- struct phi_af f = {
- .format = PHI_PCM_FLOAT32,
- .channels = info.channels,
- .rate = info.rate,
- };
- t->audio.format = f;
-
- ffvec_addstr(&o->hdr, &t->data_in);
- o->state = 1;
- return PHI_MORE;
- }
- case 1: {
- ffstr vc;
- if (0 != opuscomment_read(t->data_in, &vc)) {
- errlog(t, "opuscomment_read");
- return PHI_ERR;
+ case 1: {
+ ffstr vc;
+ if (0 != opuscomment_read(t->data_in, &vc)) {
+ errlog(t, "opuscomment_read");
+ return PHI_ERR;
+ }
+ vorbistag_read(t, vc);
+
+ o->tags = t->data_in;
+ t->data_out = *(ffstr*)&o->hdr;
+ o->state = 2;
+ return PHI_DATA;
}
- vorbistag_read(t, vc);
- o->tags = t->data_in;
- t->data_out = *(ffstr*)&o->hdr;
- o->state = 2;
- return PHI_DATA;
- }
+ case 2:
+ t->audio.ogg_reset = 0;
+ ffvec_free(&o->hdr);
+ o->state = 3;
+ t->data_out = o->tags;
+ return (t->conf.info_only) ? PHI_LASTOUT
+ : (t->chain_flags & PHI_FFIRST) ? PHI_DONE
+ : PHI_OK;
+
+ case 3:
+ if (t->audio.ogg_reset) {
+ phi_metaif.destroy(&t->meta);
+ o->reset = 1;
+ o->state = 0;
+ continue;
+ }
+ t->data_out = t->data_in;
+ return (t->chain_flags & PHI_FFIRST) ? PHI_DONE : PHI_OK;
+ }
}
-
- t->data_out = o->tags;
- return (t->conf.info_only) ? PHI_LASTOUT : PHI_DONE;
}
const phi_filter phi_opusmeta_read = {
diff --git a/src/track.h b/src/track.h
index 88209ff..ada2a57 100644
--- a/src/track.h
+++ b/src/track.h
@@ -165,6 +165,7 @@ struct phi_track {
uint64 pos, total; // samples; -1:unset/unknown
int64 seek; // >0:msec; -1:unset
uint seek_req :1; // New seek request is received (UI -> fmt.read)
+ uint ogg_reset :1; // ogg.read -> opus.meta
uint bitrate;
double gain_db; // Audio gain/attenuation
double maxpeak_db;
diff --git a/test.sh b/test.sh
index f282eb0..45540e4 100644
--- a/test.sh
+++ b/test.sh
@@ -208,6 +208,18 @@ test_seek() {
./phiola i -peaks -s 1 fm_wv.wv | grep '48,000 total'
}
+test_ogg() {
+ if ! test -f pl.wav ; then
+ ./phiola rec -rate 48000 -o pl.wav -f -u 2
+ fi
+
+ # Chained OGG(Opus) stream
+ ./phiola co pl.wav -o ogg1.opus -f
+ ./phiola co pl.wav -o ogg2.opus -f
+ cat ogg1.opus ogg2.opus >ogg3.opus
+ ./phiola pl ogg3.opus
+}
+
test_convert_af() {
O=co_wav_i24.wav ; ./phiola co co.wav -af int24 -f -o $O ; ./phiola i $O | grep 'int24' ; ./phiola pl $O
O=co_wav_mono.wav ; ./phiola co co.wav -ch 1 -f -o $O ; ./phiola i $O | grep 'mono' ; ./phiola pl $O
@@ -664,6 +676,7 @@ TESTS=(
info
until
seek
+ ogg
copy
meta
danorm
From 16cd4d7375a2b7e0db4bf43c43e9fe0d416c597e Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 28 Sep 2024 08:16:12 +0300
Subject: [PATCH 05/59] - couldn't convert from .ogg(Vorbis) to .ogg(Opus)
---
src/track.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/track.h b/src/track.h
index ada2a57..dd64c3d 100644
--- a/src/track.h
+++ b/src/track.h
@@ -97,7 +97,7 @@ struct phi_filter {
char name[16];
};
-#define MAX_FILTERS 20
+#define MAX_FILTERS 24
struct filter {
void *obj;
From d0648ddf78a2d4cf3bb94114552cea0cd09da89b Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 28 Sep 2024 08:16:12 +0300
Subject: [PATCH 06/59] minor
---
.../src/main/java/com/github/stsaz/phiola/Explorer.java | 4 ----
src/acodec/alib3-bridge/opus-dec-if.h | 6 ++++--
src/afilter/soxr-conv.h | 4 +++-
src/core/track.c | 7 ++++---
src/format/ogg-write.h | 2 +-
src/jni/convert.h | 2 ++
src/net/http.c | 1 -
src/track.h | 1 +
8 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Explorer.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Explorer.java
index 6f607e4..382c8aa 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Explorer.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Explorer.java
@@ -11,10 +11,6 @@
import androidx.core.app.ActivityCompat;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-
class Explorer {
private static final String TAG = "phiola.Explorer";
private final Core core;
diff --git a/src/acodec/alib3-bridge/opus-dec-if.h b/src/acodec/alib3-bridge/opus-dec-if.h
index bdb7b1b..859c941 100644
--- a/src/acodec/alib3-bridge/opus-dec-if.h
+++ b/src/acodec/alib3-bridge/opus-dec-if.h
@@ -155,8 +155,10 @@ int ffopus_decode(ffopus *o, ffstr *input, ffstr *output, uint64 *pos)
float *pcm = (void*)o->pcmbuf.ptr;
r = opus_decode_f(o->dec, input->ptr, input->len, pcm);
- if (r < 0)
- return ERR(o, r);
+ if (r < 0) {
+ o->err = r;
+ return FFOPUS_RWARN;
+ }
input->len = 0;
ffstr_set(output, pcm, r * o->info.channels * sizeof(float));
diff --git a/src/afilter/soxr-conv.h b/src/afilter/soxr-conv.h
index 8847c44..09f1096 100644
--- a/src/afilter/soxr-conv.h
+++ b/src/afilter/soxr-conv.h
@@ -61,8 +61,10 @@ static int soxr_conv(void *ctx, phi_track *t)
if (0 != (val = ffsoxr_create(&c->soxr, &inpcm, &outpcm))
|| (core->conf.log_level >= PHI_LOG_DEBUG)) {
log_pcmconv(val, &inpcm, &outpcm, t);
- if (val != 0)
+ if (val != 0) {
+ t->error = PHI_E_ACONV;
return PHI_ERR;
+ }
}
c->state = 3;
diff --git a/src/core/track.c b/src/core/track.c
index 4510610..1459d37 100644
--- a/src/core/track.c
+++ b/src/core/track.c
@@ -214,8 +214,9 @@ static int trk_filter_add(phi_track *t, const phi_filter *iface, uint pos)
t->conveyor.n_active++;
char buf[200];
- dbglog(t, "%s: added to chain [%s]"
- , f->iface->name, conveyor_print(v, f, buf, sizeof(buf)));
+ dbglog(t, "%s: added to chain [%u/%u] {%s}"
+ , f->iface->name, t->conveyor.n_active, v->i_fpool
+ , conveyor_print(v, f, buf, sizeof(buf)));
return pos;
}
@@ -358,7 +359,7 @@ static int trk_filter_handle_result(phi_track *t, struct filter *f, int r)
if (chain_modified) {
char buf[200];
- dbglog(t, "chain (%u): [%s]"
+ dbglog(t, "chain [%u] {%s}"
, t->conveyor.n_active, conveyor_print(&t->conveyor, NULL, buf, sizeof(buf)));
}
diff --git a/src/format/ogg-write.h b/src/format/ogg-write.h
index c542b1e..353370d 100644
--- a/src/format/ogg-write.h
+++ b/src/format/ogg-write.h
@@ -23,7 +23,7 @@ static void* ogg_w_open(phi_track *t)
seed = 1;
fftime t;
fftime_now(&t);
- ffrand_seed(fftime_sec(&t));
+ ffrand_seed(fftime_sec(&t) + fftime_nsec(&t));
}
return o;
diff --git a/src/jni/convert.h b/src/jni/convert.h
index 45ca3c7..3f88cf2 100644
--- a/src/jni/convert.h
+++ b/src/jni/convert.h
@@ -27,12 +27,14 @@ static const char* trk_errstr(uint e)
return fferr_strptr(e & ~PHI_E_SYS);
e--;
+ // enum PHI_E
static const char errstr[][30] = {
"Input file doesn't exist", // PHI_E_NOSRC
"Output file already exists", // PHI_E_DSTEXIST
"Unknown input file format", // PHI_E_UNKIFMT
"Input audio device problem", // PHI_E_AUDIO_INPUT
"Cancelled", // PHI_E_CANCELLED
+ "Sample conversion", // PHI_E_ACONV
};
const char *s = "Unknown";
if (e < FF_COUNT(errstr))
diff --git a/src/net/http.c b/src/net/http.c
index 141b459..f26e284 100644
--- a/src/net/http.c
+++ b/src/net/http.c
@@ -336,7 +336,6 @@ static void net_close()
#ifndef PHI_HTTP_NO_SSL
nml_ssl_uninit(phi_ssl_ctx);
if (phi_ssl_ctx) {
- ffmem_free(phi_ssl_ctx->ctx_conf->cert_file);
ffmem_free(phi_ssl_ctx->ctx_conf);
ffmem_free(phi_ssl_ctx);
}
diff --git a/src/track.h b/src/track.h
index dd64c3d..0c80475 100644
--- a/src/track.h
+++ b/src/track.h
@@ -128,6 +128,7 @@ enum PHI_E {
PHI_E_UNKIFMT, // unknown input format
PHI_E_AUDIO_INPUT,
PHI_E_CANCELLED,
+ PHI_E_ACONV, // audio conversion
PHI_E_OTHER = 255,
PHI_E_SYS = 0x80000000,
};
From ee11422cddda60023621f2ad170decd81bd18936 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 28 Sep 2024 08:16:12 +0300
Subject: [PATCH 07/59] 2.2.7
---
android/phiola/build.gradle | 4 ++--
src/phiola.h | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/android/phiola/build.gradle b/android/phiola/build.gradle
index b299c9b..930f75f 100644
--- a/android/phiola/build.gradle
+++ b/android/phiola/build.gradle
@@ -8,8 +8,8 @@ android {
applicationId "com.github.stsaz.phiola"
minSdkVersion 16
targetSdk 33
- versionCode 20206
- versionName '2.2.6'
+ versionCode 20207
+ versionName '2.2.7'
}
buildFeatures {
diff --git a/src/phiola.h b/src/phiola.h
index 747d273..2f4d622 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -6,7 +6,7 @@
#include
#include
-#define PHI_VERSION 20206
+#define PHI_VERSION 20207
/** Inter-module compatibility version.
It must be updated when incompatible changes are made to this file,
From 5c2bb2c55d1b97cfffab39bc1418a809ad7272ef Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 2 Nov 2024 10:19:44 +0300
Subject: [PATCH 08/59] - HTTP: query string was not passed to server
---
src/exe/cmd.h | 6 ++++--
src/net/http.c | 2 ++
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/exe/cmd.h b/src/exe/cmd.h
index 31d74b2..01d328d 100644
--- a/src/exe/cmd.h
+++ b/src/exe/cmd.h
@@ -144,8 +144,10 @@ static int cmd_input(ffvec *input, ffstr s)
return cmd_input_names(input, ffstdin);
#ifdef FF_WIN
- if (ffstr_findany(&s, "*?", 2) >= 0
- && !ffstr_matchz(&s, "\\\\?\\")) {
+ if (!(ffstr_matchz(&s, "http://")
+ || ffstr_matchz(&s, "https://")
+ || ffstr_matchz(&s, "\\\\?\\"))
+ && ffstr_findany(&s, "*?", 2) >= 0) {
if (!!wildcard_expand(input, s))
return 1;
} else
diff --git a/src/net/http.c b/src/net/http.c
index f26e284..9b2fc7e 100644
--- a/src/net/http.c
+++ b/src/net/http.c
@@ -225,6 +225,8 @@ static int conf_prepare(struct httpcl *h, struct nml_http_client_conf *c, phi_tr
c->host.len += p.port.len;
if (!p.port.len)
c->server_port = 80;
+ if (p.query.len)
+ p.path.len += p.query.len; // = "/path?query"
ffvec_alloc(&h->path, p.path.len * 3, 1);
h->path.len = httpurl_escape(h->path.ptr, h->path.cap, p.path);
c->path = *(ffstr*)&h->path;
From 980815c1d10352029b4cb8ce2bec56f444d3b013 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 2 Nov 2024 10:19:44 +0300
Subject: [PATCH 09/59] + new remote command: `volume`
---
src/exe/remote.h | 1 +
src/remote-ctl.c | 10 ++++++++++
src/tui/play.h | 2 +-
src/tui/tui.c | 5 ++++-
4 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/src/exe/remote.h b/src/exe/remote.h
index dd2c165..3166b2e 100644
--- a/src/exe/remote.h
+++ b/src/exe/remote.h
@@ -14,6 +14,7 @@ Commands:\n\
`next` Play next track\n\
`previous` Play previous track\n\
`stop` Stop all tracks\n\
+ `volume` NUMBER Set playback volume level: 0..100\n\
`quit` Exit\n\
");
x->exit_code = 0;
diff --git a/src/remote-ctl.c b/src/remote-ctl.c
index 8ed11b8..be37a4d 100644
--- a/src/remote-ctl.c
+++ b/src/remote-ctl.c
@@ -62,6 +62,15 @@ static int cmd_quit() {
return 0;
}
+static int cmd_volume(void *o, uint64 n) {
+ struct phi_ui_conf uc = {
+ .volume_percent = n,
+ };
+ const phi_ui_if *uif = g->core->mod("tui.if");
+ uif->conf(&uc);
+ return 0;
+}
+
static const struct ffarg args[] = {
{ "clear", '1', cmd_clear },
{ "next", '1', cmd_next },
@@ -70,6 +79,7 @@ static const struct ffarg args[] = {
{ "quit", '1', cmd_quit },
{ "start", 'S', cmd_start },
{ "stop", '1', cmd_stop },
+ { "volume", 'u', cmd_volume },
{}
};
diff --git a/src/tui/play.h b/src/tui/play.h
index c9beec7..93a59ae 100644
--- a/src/tui/play.h
+++ b/src/tui/play.h
@@ -151,7 +151,7 @@ static double tui_setvol(tui_track *u, uint vol)
static void tuiplay_vol(tui_track *u, uint cmd)
{
- uint vol = 0;
+ uint vol = mod->vol;
switch (cmd & ~FFKEY_MODMASK) {
case CMD_VOLUP:
diff --git a/src/tui/tui.c b/src/tui/tui.c
index ab714f5..0288c49 100644
--- a/src/tui/tui.c
+++ b/src/tui/tui.c
@@ -423,8 +423,11 @@ static void tui_destroy()
static void tui_conf(struct phi_ui_conf *c)
{
- if (c->volume_percent != 0xff)
+ if (c->volume_percent != 0xff) {
mod->vol = ffmin((uint)c->volume_percent, VOL_MAX);
+ if (mod->curtrk)
+ tuiplay_vol(mod->curtrk, ~0U);
+ }
}
static struct phi_ui_if phi_tui_if = {
From 0f8517a496227f12eece7c7379a1b2096c5ad19a Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 2 Nov 2024 10:19:44 +0300
Subject: [PATCH 10/59] Android: About: "Enable debug logging"
---
.../main/java/com/github/stsaz/phiola/AboutActivity.java | 9 +++++++++
.../src/main/java/com/github/stsaz/phiola/Phiola.java | 1 +
.../src/main/java/com/github/stsaz/phiola/Settings.java | 1 +
android/phiola/src/main/res/layout/about.xml | 7 +++++++
android/phiola/src/main/res/values/strings.xml | 1 +
src/jni/phiola-jni.c | 7 +++++++
6 files changed, 26 insertions(+)
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/AboutActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/AboutActivity.java
index a9abc46..e5b428c 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/AboutActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/AboutActivity.java
@@ -25,6 +25,10 @@ protected void onCreate(Bundle savedInstanceState) {
b.lAbout.setText(String.format("v%s\n\n%s",
core.phiola.version(),
"https://github.com/stsaz/phiola"));
+
+ b.bDebugLog.setChecked(core.setts.debug_logs);
+ b.bDebugLog.setOnClickListener((v) -> logs_debug());
+
b.bSaveLogs.setOnClickListener((v) -> logs_save_file());
}
@@ -33,6 +37,11 @@ protected void onDestroy() {
super.onDestroy();
}
+ private void logs_debug() {
+ core.setts.debug_logs = !core.setts.debug_logs;
+ core.phiola.setDebug(core.setts.debug_logs);
+ }
+
private void logs_save_file() {
String fn = String.format("%s/logs.txt", core.setts.pub_data_dir);
String[] args = new String[]{
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
index 9c57c8a..766bd43 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
@@ -17,6 +17,7 @@ private static boolean lib_load(String filename) {
native String version();
native void setCodepage(String codepage);
+ native void setDebug(boolean enable);
static class Meta {
int queue_pos;
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java
index 9327104..e56efd7 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java
@@ -43,6 +43,7 @@ static int parse(String s) {
class CoreSettings {
private Core core;
+ boolean debug_logs;
boolean svc_notification_disable;
String trash_dir;
boolean file_del;
diff --git a/android/phiola/src/main/res/layout/about.xml b/android/phiola/src/main/res/layout/about.xml
index 82601a0..2624e65 100644
--- a/android/phiola/src/main/res/layout/about.xml
+++ b/android/phiola/src/main/res/layout/about.xml
@@ -18,6 +18,13 @@
android:gravity="center_horizontal"
android:textAlignment="center" />
+
+
+ Enable debug logging
Save system logs to file
Saved logs to %s
diff --git a/src/jni/phiola-jni.c b/src/jni/phiola-jni.c
index 114fd04..25d9378 100644
--- a/src/jni/phiola-jni.c
+++ b/src/jni/phiola-jni.c
@@ -356,6 +356,13 @@ Java_com_github_stsaz_phiola_Phiola_setCodepage(JNIEnv *env, jobject thiz, jstri
jni_sz_free(sz, jcodepage);
}
+JNIEXPORT void JNICALL
+Java_com_github_stsaz_phiola_Phiola_setDebug(JNIEnv *env, jobject thiz, jboolean enable)
+{
+ x->debug = !!enable;
+ x->core->conf.log_level = (x->debug) ? PHI_LOG_EXTRA : PHI_LOG_VERBOSE;
+}
+
JNIEXPORT jint JNI_OnLoad(JavaVM *_jvm, void *reserved)
{
jvm = _jvm;
From 68bf97c497be5462211fa55bbcf1b57e59b3fc37 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 2 Nov 2024 10:19:44 +0300
Subject: [PATCH 11/59] minor
---
src/format/mp3-write.h | 4 +++-
src/jni/convert.h | 1 +
src/track.h | 1 +
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/format/mp3-write.h b/src/format/mp3-write.h
index 36b938e..8abd46d 100644
--- a/src/format/mp3-write.h
+++ b/src/format/mp3-write.h
@@ -66,8 +66,10 @@ int mpeg_out_process(void *ctx, phi_track *t)
return PHI_ERR;
}
- if (!core->track->filter(t, core->mod("mpeg.encode"), PHI_TF_PREV))
+ if (!core->track->filter(t, core->mod("mpeg.encode"), PHI_TF_PREV)) {
+ t->error = PHI_E_OUT_FMT;
return PHI_ERR;
+ }
return PHI_MORE;
case 2:
diff --git a/src/jni/convert.h b/src/jni/convert.h
index 3f88cf2..dc06820 100644
--- a/src/jni/convert.h
+++ b/src/jni/convert.h
@@ -35,6 +35,7 @@ static const char* trk_errstr(uint e)
"Input audio device problem", // PHI_E_AUDIO_INPUT
"Cancelled", // PHI_E_CANCELLED
"Sample conversion", // PHI_E_ACONV
+ "Output format is not supported", // PHI_E_OUT_FMT
};
const char *s = "Unknown";
if (e < FF_COUNT(errstr))
diff --git a/src/track.h b/src/track.h
index 0c80475..2e8c967 100644
--- a/src/track.h
+++ b/src/track.h
@@ -129,6 +129,7 @@ enum PHI_E {
PHI_E_AUDIO_INPUT,
PHI_E_CANCELLED,
PHI_E_ACONV, // audio conversion
+ PHI_E_OUT_FMT,
PHI_E_OTHER = 255,
PHI_E_SYS = 0x80000000,
};
From f1251a3f979e76249f21080bc0d74cf143873dd2 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 2 Nov 2024 10:19:44 +0300
Subject: [PATCH 12/59] 2.2.8
---
android/phiola/build.gradle | 4 ++--
src/phiola.h | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/android/phiola/build.gradle b/android/phiola/build.gradle
index 930f75f..11f6792 100644
--- a/android/phiola/build.gradle
+++ b/android/phiola/build.gradle
@@ -8,8 +8,8 @@ android {
applicationId "com.github.stsaz.phiola"
minSdkVersion 16
targetSdk 33
- versionCode 20207
- versionName '2.2.7'
+ versionCode 20208
+ versionName '2.2.8'
}
buildFeatures {
diff --git a/src/phiola.h b/src/phiola.h
index 2f4d622..d93e1fb 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -6,7 +6,7 @@
#include
#include
-#define PHI_VERSION 20207
+#define PHI_VERSION 20208
/** Inter-module compatibility version.
It must be updated when incompatible changes are made to this file,
From 5a67775c46fec6c302a946e86eb40b7a413721f3 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 16 Nov 2024 09:49:08 +0300
Subject: [PATCH 13/59] + playback HLS radio streams
---
src/core/queue-entry.h | 3 +
src/net/Makefile | 3 +
src/net/hls.h | 206 +++++++++++++++++++++++++++++++++++++++++
src/net/http-filters.c | 3 +
src/net/http.c | 167 +++++++++++++++++++++++++++++----
src/util/util.h | 13 +++
test.sh | 25 ++++-
7 files changed, 397 insertions(+), 23 deletions(-)
create mode 100644 src/net/hls.h
diff --git a/src/core/queue-entry.h b/src/core/queue-entry.h
index 955280b..1bcca1f 100644
--- a/src/core/queue-entry.h
+++ b/src/core/queue-entry.h
@@ -205,6 +205,9 @@ static int qe_expand(struct q_entry *e)
struct phi_track_conf *c = &e->pub.conf;
const phi_track_if *track = core->track;
+ if (url_checkz(c->ifile.name))
+ return -1;
+
ffbool dir = 0, decompress = 0;
fffileinfo fi;
if (!fffile_info_path(c->ifile.name, &fi)
diff --git a/src/net/Makefile b/src/net/Makefile
index acc35b8..e19041f 100644
--- a/src/net/Makefile
+++ b/src/net/Makefile
@@ -24,6 +24,7 @@ MODS += http.$(SO)
HTTP_OBJ := http.o \
icy.o \
+ netmill-core-cache.o \
netmill-http-client.o \
netmill-http-filters.o
@@ -69,5 +70,7 @@ netmill-http-filters.o: $(PHIOLA)/src/net/http-filters.c
$(C) $(CFLAGS_NETMILL) -I$(PHIOLA)/src -I$(FFSYS) $< -o $@
netmill-http-client.o: $(NETMILL)/src/http-client/client.c
$(C) $(CFLAGS_NETMILL) -I$(FFSYS) $< -o $@
+netmill-core-cache.o: $(NETMILL)/src/core/cache.c
+ $(C) $(CFLAGS_NETMILL) -I$(FFSYS) $< -o $@
ffssl.o: $(NETMILL)/src/util/ffssl.c
$(C) $(CFLAGS_NETMILL) -I$(FFSYS) $< -o $@
diff --git a/src/net/hls.h b/src/net/hls.h
new file mode 100644
index 0000000..02cf88e
--- /dev/null
+++ b/src/net/hls.h
@@ -0,0 +1,206 @@
+/** phiola: HLS reader
+2024, Simon Zolin */
+
+/*
+HLS client algorithm:
+. Repeat:
+ . Request .m3u8 by HTTP and receive its data
+ . Parse the data and get file names
+ . Determine which files are new from the last time (using #EXT-X-MEDIA-SEQUENCE value)
+ . If there are no new files, wait
+ . Add new files to the queue
+ . Repeat:
+ . Pop the first data file from the queue
+ . If the queue is empty, exit the loop
+ . Prepare a complete URL for the file (base URL for .m3u8 file + file name)
+ . Request the data file by HTTP
+ . Pass file data to the next filters
+*/
+
+/*
+Call map:
+ ... [phiola]
+ httpcl_process()
+ nml_http_client_run()
+ ... [.m3u] [netmill]
+ phi_hc_data()
+ on_complete()
+ hls_f_request()
+ nml_http_client_run()
+ ... [.ogg] [netmill]
+ phi_hc_data()
+ ... [phiola]
+ httpcl_process()
+ ... [.ogg] [netmill]
+ on_complete()
+ hls_f_request()
+ ...
+*/
+
+struct hlsread {
+ ffvec data, url;
+ ffvec q; // ffstr[]
+ ffstr base_url;
+ uint64 seq_last;
+ phi_timer wait;
+ phi_task task;
+ uint worker;
+ uint n_fail_tries;
+ uint data_file :1;
+};
+
+static struct hlsread* hls_new(struct httpcl *h)
+{
+ struct hlsread *l = ffmem_new(struct hlsread);
+ ffpath_splitpath_str(FFSTR_Z(h->trk->conf.ifile.name), &l->base_url, NULL);
+ l->worker = h->trk->worker;
+ return l;
+}
+
+static void hls_free(struct httpcl *h, struct hlsread *l)
+{
+ if (!l) return;
+
+ ffvec_free(&l->data);
+ ffstr *it;
+ FFSLICE_WALK(&l->q, it) {
+ ffstr_free(it);
+ }
+ ffvec_free(&l->q);
+ ffvec_free(&l->url);
+ core->timer(l->worker, &l->wait, 0, NULL, NULL);
+ ffmem_free(l);
+}
+
+/** Received new data chunk */
+static int hls_data(struct httpcl *h, ffstr data)
+{
+ struct hlsread *l = h->hls;
+ if (!l->data_file) {
+ ffvec_addstr(&l->data, &data);
+ return 1;
+ }
+ return 0;
+}
+
+/** Retrieve new data files from m3u list.
+Return 0 if something was added */
+static int hls_q_update(struct httpcl *h, ffstr d)
+{
+ struct hlsread *l = h->hls;
+ dbglog(h->trk, "m3u content: %S", &d);
+
+ int n = 0;
+ ffstr ln, s;
+ uint64 seq = 0;
+ while (d.len) {
+ ffstr_splitby(&d, '\n', &ln, &d);
+ ffstr_rskipchar1(&ln, '\r');
+
+ if (ffstr_matchz(&ln, "#")) {
+ if (ffstr_matchz(&ln, "#EXT-X-MEDIA-SEQUENCE:")) {
+ s = ln;
+ ffstr_shift(&s, FFS_LEN("#EXT-X-MEDIA-SEQUENCE:"));
+ if (!ffstr_to_uint64(&s, &seq))
+ warnlog(h->trk, "incorrect '#EXT-X-MEDIA-SEQUENCE' value: %S", &ln);
+ }
+ continue;
+ }
+
+ if (l->seq_last < seq) {
+ l->seq_last = seq;
+ ffstr *ps = ffvec_zpushT(&l->q, ffstr);
+ ffstr_dupstr(ps, &ln);
+ n++;
+ }
+ seq++;
+ }
+ return !n;
+}
+
+/** Get the first data file from the queue.
+name: User must free the value with ffstr_free() */
+static int hls_q_get(struct httpcl *h, ffstr *name)
+{
+ struct hlsread *l = h->hls;
+ if (!l->q.len)
+ return -1;
+
+ *name = *ffslice_itemT(&l->q, 0, ffstr);
+ ffslice_rmT((ffslice*)&l->q, 0, 1, ffstr);
+ dbglog(h->trk, "playing data file %S [%L]"
+ , name, l->q.len);
+ return 0;
+}
+
+/** Request file from server */
+static void hls_f_request(struct httpcl *h, ffstr name)
+{
+ struct hlsread *l = h->hls;
+ nml_http_client_free(h->cl);
+ ffstr_free(&h->conf.headers);
+ ffmem_zero_obj(&h->conf);
+
+ h->cl = nml_http_client_create();
+
+ struct nml_http_client_conf *c = &h->conf;
+ ffstr url = FFSTR_Z(h->trk->conf.ifile.name);
+ l->data_file = 0;
+ if (name.len) {
+ l->url.len = 0;
+ ffvec_addfmt(&l->url, "%S/%S", &l->base_url, &name);
+ ffstr_free(&name);
+ url = *(ffstr*)&l->url;
+ l->data_file = 1;
+ }
+
+ conf_prepare(h, c, h->trk, url);
+ dbglog(h->trk, "requesting %S", &url);
+ nml_http_client_conf(h->cl, c);
+ nml_http_client_run(h->cl);
+}
+
+static void hls_f_next(void *param)
+{
+ struct httpcl *h = param;
+ ffstr name = {};
+ hls_q_get(h, &name);
+
+ if (ffstr_matchz(&name, "http://")
+ || ffstr_matchz(&name, "https://")) {
+ errlog(h->trk, "expecting relative file name: '%S'", &name);
+ if (FF_SWAP(&h->state, ST_ERR) == ST_WAIT)
+ core->track->wake(h->trk);
+ return;
+ }
+
+ hls_f_request(h, name);
+}
+
+/** HTTP request is complete */
+static int hls_f_complete(struct httpcl *h)
+{
+ struct hlsread *l = h->hls;
+ if (!l->data_file) {
+ int r = hls_q_update(h, *(ffstr*)&l->data);
+ l->data.len = 0;
+ if (r) {
+ if (!l->seq_last
+ || l->n_fail_tries == 10) {
+ errlog(h->trk, "no more files in m3u");
+ return -1;
+ }
+
+ l->data_file = 0;
+ dbglog(h->trk, "no new files in m3u, waiting");
+ l->n_fail_tries++;
+ core->timer(l->worker, &l->wait, -2000, hls_f_next, h);
+ return 0;
+ }
+
+ l->n_fail_tries = 0;
+ }
+
+ core->task(l->worker, &l->task, hls_f_next, h);
+ return 0;
+}
diff --git a/src/net/http-filters.c b/src/net/http-filters.c
index 714fb1e..fb2c905 100644
--- a/src/net/http-filters.c
+++ b/src/net/http-filters.c
@@ -2,6 +2,7 @@
#include
#include
+#include
#include
#include
#include
@@ -47,6 +48,7 @@ static const nml_http_cl_component nml_http_cl_phi_bridge = {
const nml_http_cl_component *hc_chain[] = {
&nml_http_cl_resolve,
+ &nml_http_cl_connection_cache,
&nml_http_cl_connect,
&nml_http_cl_request,
&nml_http_cl_send,
@@ -67,6 +69,7 @@ const nml_http_cl_component *hc_chain[] = {
#include
const nml_http_cl_component *hc_ssl_chain[] = {
&nml_http_cl_resolve,
+ &nml_http_cl_connection_cache,
&nml_http_cl_connect,
&nml_htcl_ssl_recv,
&nml_htcl_ssl_handshake,
diff --git a/src/net/http.c b/src/net/http.c
index 9b2fc7e..b068adc 100644
--- a/src/net/http.c
+++ b/src/net/http.c
@@ -8,26 +8,35 @@
#include
#include
#include
+#include
const phi_core *core;
extern const phi_filter phi_icy;
#ifndef PHI_HTTP_NO_SSL
static struct nml_ssl_ctx *phi_ssl_ctx;
#endif
-#define errlog(t, ...) phi_errlog(core, "http-client", t, __VA_ARGS__)
-#define warnlog(t, ...) phi_warnlog(core, "http-client", t, __VA_ARGS__)
+#define MOD_NAME "http-client"
+#define errlog(t, ...) phi_errlog(core, MOD_NAME, t, __VA_ARGS__)
+#define warnlog(t, ...) phi_warnlog(core, MOD_NAME, t, __VA_ARGS__)
+#define dbglog(t, ...) phi_dbglog(core, MOD_NAME, t, __VA_ARGS__)
struct httpcl {
nml_http_client *cl;
struct nml_http_client_conf conf;
phi_track *trk;
ffvec path;
+ nml_cache_ctx *conn_cache;
+
+ ffring *buf;
+ ffring_head rhead;
ffstr data;
uint state; // enum ST
- uint fstate;
+ uint cl_state;
uint done :1;
uint icy :1;
+
+ struct hlsread *hls;
};
enum ST {
@@ -35,8 +44,13 @@ enum ST {
ST_WAIT,
ST_DATA,
ST_ERR,
+ ST_HLS_HAVE_DATA,
+ ST_HLS_PROCESSING,
};
+static int conf_prepare(struct httpcl *h, struct nml_http_client_conf *c, phi_track *t, ffstr url);
+#include
+
static void nml_log(void *log_obj, uint level, const char *ctx, const char *id, const char *format, ...)
{
struct httpcl *h = log_obj;
@@ -55,7 +69,7 @@ static void nml_log(void *log_obj, uint level, const char *ctx, const char *id,
va_list va;
va_start(va, format);
- core->conf.logv(core->conf.log_obj, level, NULL, h->trk, format, va);
+ core->conf.logv(core->conf.log_obj, level, MOD_NAME, h->trk, format, va);
va_end(va);
}
@@ -100,6 +114,7 @@ static fftime nmlcore_date(void *boss, ffstr *dts)
return t;
}
+/** Bridge between netmill and phiola Core */
static const struct nml_core nmlcore = {
.kev_new = nmlcore_kev_new,
.kev_free = nmlcore_kev_free,
@@ -109,6 +124,7 @@ static const struct nml_core nmlcore = {
.date = nmlcore_date,
};
+/** Received HTTP response headers */
int phi_hc_resp(void *ctx, struct phi_http_data *d)
{
struct httpcl *h = ctx;
@@ -118,39 +134,97 @@ int phi_hc_resp(void *ctx, struct phi_http_data *d)
return NMLR_ERR;
}
- static const struct map_sz_vptr ct_ext[] = {
+ static const struct map_sz24_vptr ct_ext[] = {
{ "application/ogg", "ogg" },
+ { "application/x-mpegURL", "m3u" },
{ "audio/aac", "aac" },
{ "audio/aacp", "aac" },
{ "audio/mpeg", "mp3" },
{ "audio/ogg", "ogg" },
};
- h->trk->data_type = map_sz_vptr_findstr(ct_ext, FF_COUNT(ct_ext), d->ct); // help format.detector in case it didn't detect format
+ h->trk->data_type = map_sz24_vptr_findstr(ct_ext, FF_COUNT(ct_ext), d->ct); // help format.detector in case it didn't detect format
+ if (!h->trk->data_type
+ && ffstr_eqz(&d->ct, "application/vnd.apple.mpegurl")
+ && !h->hls) {
+ h->hls = hls_new(h);
+ size_t cap = (h->trk->conf.ifile.buf_size) ? h->trk->conf.ifile.buf_size : 64*1024;
+ h->buf = ffring_alloc(cap, FFRING_1_READER | FFRING_1_WRITER);
+ }
- h->trk->icy_meta_interval = d->icy_meta_interval;
- h->icy = !!d->icy_meta_interval;
- h->trk->meta_changed = !d->icy_meta_interval;
+ if (!h->hls) {
+ h->trk->icy_meta_interval = d->icy_meta_interval;
+ h->icy = !!d->icy_meta_interval;
+ h->trk->meta_changed = !d->icy_meta_interval;
+ }
return NMLR_OPEN;
}
+enum {
+ CLST_RECEIVING,
+ CLST_PROCESSING,
+};
+
+static inline ffsize _ffring_writestr(ffring *b, ffstr data, ffsize *free)
+{
+ ffstr d;
+ ffring_head wh = ffring_write_begin(b, data.len, &d, free);
+ if (d.len == 0)
+ return 0;
+
+ ffmem_copy(d.ptr, data.ptr, d.len);
+ ffring_write_finish(b, wh, NULL);
+ return d.len;
+}
+
+/** New data chunk is available for reading */
int phi_hc_data(void *ctx, ffstr data, uint flags)
{
struct httpcl *h = ctx;
+ int r;
+
+ if (h->hls) {
+ switch (h->cl_state) {
+ case CLST_RECEIVING:
+ h->done = !!(flags & 1);
+ if (hls_data(h, data))
+ return (flags & 1) ? NMLR_FIN : NMLR_BACK;
+
+ h->data = data;
+ h->cl_state = CLST_PROCESSING;
+ // fallthrough
+
+ case CLST_PROCESSING: {
+ size_t nfree;
+ r = _ffring_writestr(h->buf, h->data, &nfree);
+ dbglog(h->trk, "buffer:%L", h->buf->cap - nfree);
+ ffstr_shift(&h->data, r);
+ if (FF_SWAP(&h->state, ST_HLS_HAVE_DATA) == ST_WAIT)
+ core->track->wake(h->trk);
+
+ if (h->data.len)
+ return NMLR_ASYNC;
+ h->cl_state = CLST_RECEIVING;
+ return (flags & 1) ? NMLR_FIN : NMLR_BACK;
+ }
+ }
- switch (h->fstate) {
- case 0:
+ return NMLR_ERR;
+ }
+
+ switch (h->cl_state) {
+ case CLST_RECEIVING:
h->data = data;
h->done = !!(flags & 1);
if (FF_SWAP(&h->state, ST_DATA) == ST_WAIT) {
core->track->wake(h->trk);
- h->fstate = 1;
+ h->cl_state = CLST_PROCESSING;
}
return NMLR_ASYNC;
- case 1:
- h->fstate = 0;
- return NMLR_BACK;
+ case CLST_PROCESSING:
+ h->cl_state = CLST_RECEIVING;
+ return (flags & 1) ? NMLR_FIN : NMLR_BACK;
}
return NMLR_ERR;
}
@@ -158,6 +232,11 @@ int phi_hc_data(void *ctx, ffstr data, uint flags)
static void on_complete(void *param)
{
struct httpcl *h = param;
+
+ if (h->hls
+ && !hls_f_complete(h))
+ return;
+
if (FF_SWAP(&h->state, ST_ERR) == ST_WAIT)
core->track->wake(h->trk);
}
@@ -165,6 +244,7 @@ static void on_complete(void *param)
extern const nml_http_cl_component
*hc_chain[],
*hc_ssl_chain[];
+extern const struct nml_cache_if nml_cache_interface;
#ifndef PHI_HTTP_NO_SSL
#include
@@ -202,7 +282,31 @@ static struct nml_ssl_ctx* ssl_prepare(struct nml_http_client_conf *c)
}
#endif
-static int conf_prepare(struct httpcl *h, struct nml_http_client_conf *c, phi_track *t)
+static nml_cache_ctx* conn_cache_new(struct httpcl *h)
+{
+ struct nml_cache_conf *cc = ffmem_new(struct nml_cache_conf);
+ nml_cache_interface.conf(NULL, cc);
+
+ if (core->conf.log_level >= PHI_LOG_EXTRA)
+ cc->log_level = NML_LOG_EXTRA;
+ else if (core->conf.log_level >= PHI_LOG_DEBUG)
+ cc->log_level = NML_LOG_DEBUG;
+ cc->log = nml_log;
+ cc->log_obj = h;
+
+ cc->max_items = 2;
+ cc->ttl_sec = 30;
+ cc->destroy = nml_http_cl_conn_cache_destroy;
+ cc->opaque = NULL;
+
+ nml_cache_ctx *cx = nml_cache_interface.create();
+ if (!cx)
+ return NULL;
+ nml_cache_interface.conf(cx, cc);
+ return cx;
+}
+
+static int conf_prepare(struct httpcl *h, struct nml_http_client_conf *c, phi_track *t, ffstr url)
{
nml_http_client_conf(NULL, c);
c->opaque = h;
@@ -219,8 +323,11 @@ static int conf_prepare(struct httpcl *h, struct nml_http_client_conf *c, phi_tr
c->core = nmlcore;
c->boss = h;
+ c->connect.cache = h->conn_cache;
+ c->connect.cif = &nml_cache_interface;
+
struct httpurl_parts p = {};
- httpurl_split(&p, FFSTR_Z(h->trk->conf.ifile.name));
+ httpurl_split(&p, url);
c->host = p.host;
c->host.len += p.port.len;
if (!p.port.len)
@@ -254,7 +361,7 @@ static int conf_prepare(struct httpcl *h, struct nml_http_client_conf *c, phi_tr
ffsize headers_cap = 0;
ffstr_growaddz(&c->headers, &headers_cap, "User-Agent: phiola/2\r\n");
- if (!t->conf.ifile.no_meta)
+ if (!t->conf.ifile.no_meta && !h->hls)
ffstr_growaddz(&c->headers, &headers_cap, "Icy-MetaData: 1\r\n");
if (t->conf.ifile.connect_timeout_sec)
@@ -269,10 +376,12 @@ static void* httpcl_open(phi_track *t)
{
struct httpcl *h = phi_track_allocT(t, struct httpcl);
h->trk = t;
+ h->conn_cache = conn_cache_new(h);
+
h->cl = nml_http_client_create();
struct nml_http_client_conf *c = &h->conf;
- if (conf_prepare(h, c, t))
+ if (conf_prepare(h, c, t, FFSTR_Z(t->conf.ifile.name)))
return PHI_OPEN_ERR;
nml_http_client_conf(h->cl, c);
h->state = ST_PROCESSING;
@@ -288,6 +397,9 @@ static void httpcl_close(struct httpcl *h, phi_track *t)
nml_http_client_free(h->cl);
ffvec_free(&h->path);
ffstr_free(&h->conf.headers);
+ hls_free(h, h->hls);
+ ffring_free(h->buf);
+ nml_cache_interface.destroy(h->conn_cache);
phi_track_free(t, h);
}
@@ -315,6 +427,23 @@ static int httpcl_process(struct httpcl *h, phi_track *t)
nml_http_client_run(h->cl);
return PHI_ASYNC;
+ case ST_HLS_HAVE_DATA:
+ h->rhead = ffring_read_begin(h->buf, h->buf->cap, &t->data_out, NULL);
+ h->state = ST_HLS_PROCESSING;
+ return PHI_DATA;
+
+ case ST_HLS_PROCESSING: {
+ h->state = ST_WAIT;
+
+ size_t used;
+ ffring_read_finish_status(h->buf, h->rhead, &used);
+ dbglog(h->trk, "buffer:%L", used - (h->rhead.nu - h->rhead.old));
+
+ if (h->cl_state == CLST_PROCESSING)
+ nml_http_client_run(h->cl);
+ return PHI_ASYNC;
+ }
+
case ST_ERR:
default:
return PHI_ERR;
diff --git a/src/util/util.h b/src/util/util.h
index bb38321..e9e69e0 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -121,6 +121,19 @@ static inline const void* map_sz_vptr_findstr(const struct map_sz_vptr *m, ffsiz
}
+struct map_sz24_vptr {
+ char key[24];
+ const void *val;
+};
+static inline const void* map_sz24_vptr_findstr(const struct map_sz24_vptr *m, ffsize n, ffstr name)
+{
+ ffssize i = ffcharr_findsorted_padding(m, n, sizeof(m->key), sizeof(m->val), name.ptr, name.len);
+ if (i < 0)
+ return NULL;
+ return m[i].val;
+}
+
+
static inline int url_checkz(const char *s)
{
return (ffsz_matchz(s, "http://")
diff --git a/test.sh b/test.sh
index 45540e4..49b7353 100644
--- a/test.sh
+++ b/test.sh
@@ -590,7 +590,9 @@ test_http() {
fi
./phiola pl "http://localhost:1/" || true # no connection
+ # echo 'application/vnd.apple.mpegurl m3u8' >> $(dirname $(which netmill))/content-types.conf
netmill http l 8080 w . &
+ local nml_pid=$!
sleep .5
./phiola pl "http://localhost:8080/404" || true # http error
@@ -602,16 +604,31 @@ test_http() {
./phiola pl "http://localhost:8080/http.m3u"
# -tee
- ./phiola pl "http://localhost:8080/http.m3u" -tee @stdout.ogg >http-tee-stdout.ogg ; ./phiola http-tee-stdout.ogg
- ./phiola pl "http://localhost:8080/http.m3u" -tee http-tee.ogg ; ./phiola http-tee.ogg
- ./phiola pl "http://localhost:8080/http.m3u" -tee http-tee.ogg # file already exists
- ./phiola pl "http://localhost:8080/http.m3u" -tee http-@title.ogg ; ./phiola http-mytrack.ogg
+ ./phiola pl "http://localhost:8080/http.ogg" -tee @stdout.ogg >http-tee-stdout.ogg ; ./phiola http-tee-stdout.ogg
+ ./phiola pl "http://localhost:8080/http.ogg" -tee http-tee.ogg ; ./phiola http-tee.ogg
+ ./phiola pl "http://localhost:8080/http.ogg" -tee http-tee.ogg # file already exists
+ # ./phiola pl "http://localhost:8080/http.ogg" -tee http-@title.ogg ; ./phiola http-mytrack.ogg
# -dup
./phiola pl "http://localhost:8080/http.mp3" -dup @stdout.wav >http-dup-stdout.wav ; ./phiola http-dup-stdout.wav
./phiola pl "http://localhost:8080/http.mp3" -dup http-dup-@title.wav ; ./phiola http-dup-mytrack.wav
+ # HLS
+ cp http.ogg hls1.ogg
+ cp http.ogg hls2.ogg
+ cp http.ogg hls3.ogg
+ cat <hls.m3u8
+#EXTM3U
+#EXT-X-MEDIA-SEQUENCE:1
+hls1.ogg
+hls2.ogg
+hls3.ogg
+EOF
+ ./phiola pl "http://localhost:8080/hls.m3u8" &
+ sleep 10
kill $!
+
+ kill $nml_pid
}
test_ofile_vars() {
From 0e57287748a205da4dab4e6f20c5363d8a417979 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 16 Nov 2024 09:49:08 +0300
Subject: [PATCH 14/59] - `-dup` option didn't work sometimes
---
src/tui/play.h | 6 ++++--
src/tui/tui.c | 1 +
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/tui/play.h b/src/tui/play.h
index 93a59ae..3cd3360 100644
--- a/src/tui/play.h
+++ b/src/tui/play.h
@@ -182,6 +182,7 @@ static int handle_seek(tui_track *u, phi_track *t)
return PHI_MORE; // new seek request
} else if (!(t->chain_flags & PHI_FFWD)) {
+ t->meta_changed = 0;
return PHI_MORE; // going back without seeking
} else if (t->data_in.len == 0 && !(t->chain_flags & PHI_FFIRST)) {
@@ -203,14 +204,15 @@ static int tuiplay_process(void *ctx, phi_track *t)
if (t->chain_flags & PHI_FSTOP)
return PHI_FIN;
- if (u->show_info || t->meta_changed) {
+ uint new_meta = (t->meta_changed && !u->meta_change_seen);
+ u->meta_change_seen = t->meta_changed;
+ if (u->show_info || new_meta) {
if (!t->audio.format.rate) {
errlog(t, "audio sample rate is not set");
return PHI_ERR;
}
u->show_info = 0;
- t->meta_changed = 0;
u->total_samples = t->audio.total;
u->played_samples = 0;
tui_info(u);
diff --git a/src/tui/tui.c b/src/tui/tui.c
index 0288c49..0718ba9 100644
--- a/src/tui/tui.c
+++ b/src/tui/tui.c
@@ -30,6 +30,7 @@ typedef struct tui_track {
ffvec buf;
uint nback;
uint show_info :1;
+ uint meta_change_seen :1;
uint paused :1;
} tui_track;
From 03ffd6f868d3057e23211f103f140e4f6103c660 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 16 Nov 2024 09:49:08 +0300
Subject: [PATCH 15/59] + read .ts(AAC) streams
---
README.md | 2 +-
src/format/Makefile | 1 +
src/format/mod-fmt.c | 3 +
src/format/ts-read.c | 127 +++++++++++++++++++++++++++++++++++++++++++
src/net/http.c | 1 +
5 files changed, 133 insertions(+), 1 deletion(-)
create mode 100644 src/format/ts-read.c
diff --git a/README.md b/README.md
index 9ba3058..7706b01 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ Contents:
## Features
-* Play audio: `.mp3`, `.ogg`(Vorbis/Opus), `.mp4`/`.mov`(AAC/ALAC/MPEG), `.mkv`/`.webm`(AAC/ALAC/MPEG/Vorbis/Opus/PCM), `.caf`(AAC/ALAC/PCM), `.avi`(AAC/MPEG/PCM), `.aac`, `.mpc`; `.flac`, `.ape`, `.wv`, `.wav`.
+* Play audio: `.mp3`, `.ogg`(Vorbis/Opus), `.mp4`/`.mov`(AAC/ALAC/MPEG), `.mkv`/`.webm`(AAC/ALAC/MPEG/Vorbis/Opus/PCM), `.caf`(AAC/ALAC/PCM), `.avi`(AAC/MPEG/PCM), `.ts`(AAC), `.aac`, `.mpc`; `.flac`, `.ape`, `.wv`, `.wav`.
* Record audio: `.m4a`(AAC), `.ogg`, `.opus`; `.flac`, `.wav`
* Convert audio
* List/search file meta tags; edit MP3 tags
diff --git a/src/format/Makefile b/src/format/Makefile
index 52be6a3..a55d839 100644
--- a/src/format/Makefile
+++ b/src/format/Makefile
@@ -33,6 +33,7 @@ format.$(SO): mod-fmt.o \
mp4.o \
mpc-read.o \
ogg.o \
+ ts-read.o \
wav.o \
wv.o \
\
diff --git a/src/format/mod-fmt.c b/src/format/mod-fmt.c
index 020f203..19b117c 100644
--- a/src/format/mod-fmt.c
+++ b/src/format/mod-fmt.c
@@ -33,6 +33,7 @@ extern const phi_filter
phi_mpc_read,
phi_ogg_read,
phi_opusmeta_read,
+ phi_ts_read,
phi_vorbismeta_read,
phi_wav_read,
phi_wv_read;
@@ -100,6 +101,7 @@ static const void* fmt_mod_iface_input(const char *name)
{ "ogg", &phi_ogg_read },
{ "opus", &phi_ogg_read },
{ "pls", &phi_pls_read },
+ { "ts", &phi_ts_read },
{ "wav", &phi_wav_read },
{ "wv", &phi_wv_read },
};
@@ -130,6 +132,7 @@ static const void* fmt_mod_iface(const char *name)
{ "opusmeta", &phi_opusmeta_read },
{ "pls", &phi_pls_read },
{ "tag", &phi_tag },
+ { "ts", &phi_ts_read },
{ "vorbismeta", &phi_vorbismeta_read },
{ "wav", &phi_wav_read },
{ "wav-write", &phi_wav_write },
diff --git a/src/format/ts-read.c b/src/format/ts-read.c
new file mode 100644
index 0000000..d583651
--- /dev/null
+++ b/src/format/ts-read.c
@@ -0,0 +1,127 @@
+/** phiola: .ts reader
+2024, Simon Zolin */
+
+#include
+#include
+#include
+#include
+
+extern const phi_core *core;
+#define errlog(t, ...) phi_errlog(core, NULL, t, __VA_ARGS__)
+#define dbglog(t, ...) phi_dbglog(core, NULL, t, __VA_ARGS__)
+
+struct ts_r {
+ tsread ts;
+ ffstr in;
+ uint track_id;
+ uint64 n_pkt;
+ uint64 pos_start_msec;
+ void *trk;
+};
+
+static void ts_log(void *udata, const char *fmt, va_list va)
+{
+ struct ts_r *c = udata;
+ phi_dbglogv(core, NULL, c->trk, fmt, va);
+}
+
+static void* ts_open(phi_track *t)
+{
+ struct ts_r *c = phi_track_allocT(t, struct ts_r);
+ c->trk = t;
+ c->pos_start_msec = ~0ULL;
+ uint64 tsize = (t->input.size != ~0ULL) ? t->input.size : 0;
+ tsread_open(&c->ts, tsize);
+ c->ts.log = ts_log;
+ c->ts.udata = c;
+ return c;
+}
+
+static void ts_close(void *ctx, phi_track *t)
+{
+ struct ts_r *c = ctx;
+ tsread_close(&c->ts);
+ phi_track_free(t, c);
+}
+
+static int ts_info(struct ts_r *c, phi_track *t, const struct _tsr_pm *info)
+{
+ const char *mod;
+ switch (info->stream_type) {
+ case TS_STREAM_AUDIO_AAC:
+ mod = "format.aac"; break;
+ default:
+ dbglog(t, "codec not supported: %u", info->stream_type);
+ return 1;
+ }
+ if (!core->track->filter(t, core->mod(mod), 0))
+ return -1;
+
+ c->track_id = info->pid;
+ return 0;
+}
+
+static int ts_process(void *ctx, phi_track *t)
+{
+ struct ts_r *c = ctx;
+ int r;
+ ffstr pkt;
+
+ if (t->chain_flags & PHI_FSTOP) {
+ return PHI_LASTOUT;
+ }
+
+ if (t->data_in.len) {
+ c->in = t->data_in;
+ t->data_in.len = 0;
+ }
+
+ for (;;) {
+ r = tsread_process(&c->ts, &c->in, &pkt);
+
+ switch (r) {
+
+ case TSREAD_DATA:
+ if (c->n_pkt == 0) {
+ // select the first audio track with a recognized codec
+ r = ts_info(c, t, tsread_info(&c->ts));
+ if (r < 0)
+ return PHI_ERR;
+ else if (r > 0)
+ continue;
+ }
+
+ if (c->track_id != tsread_info(&c->ts)->pid)
+ continue;
+
+ goto data;
+
+ case TSREAD_MORE:
+ return PHI_MORE;
+
+ // case TSREAD_DONE:
+ // return PHI_LASTOUT;
+
+ case TSREAD_ERROR:
+ errlog(t, "tsread_process(): %s. Offset: %U"
+ , tsread_error(&c->ts), tsread_offset(&c->ts));
+ return PHI_ERR;
+ }
+ }
+
+data:
+ if (c->pos_start_msec == ~0ULL)
+ c->pos_start_msec = tsread_pos_msec(&c->ts);
+ if (t->audio.format.rate)
+ t->audio.pos = msec_to_samples(tsread_pos_msec(&c->ts) - c->pos_start_msec, t->audio.format.rate);
+ dbglog(t, "frame#%U passing %L bytes @%U"
+ , c->n_pkt, pkt.len, t->audio.pos);
+ c->n_pkt++;
+ t->data_out = pkt;
+ return PHI_DATA;
+}
+
+const phi_filter phi_ts_read = {
+ ts_open, (void*)ts_close, (void*)ts_process,
+ "ts-read"
+};
diff --git a/src/net/http.c b/src/net/http.c
index b068adc..b3a5be8 100644
--- a/src/net/http.c
+++ b/src/net/http.c
@@ -141,6 +141,7 @@ int phi_hc_resp(void *ctx, struct phi_http_data *d)
{ "audio/aacp", "aac" },
{ "audio/mpeg", "mp3" },
{ "audio/ogg", "ogg" },
+ { "video/MP2T", "ts" },
};
h->trk->data_type = map_sz24_vptr_findstr(ct_ext, FF_COUNT(ct_ext), d->ct); // help format.detector in case it didn't detect format
if (!h->trk->data_type
From 0a9f793fa78f8f17ba088208df382e48f5ea9eca Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 16 Nov 2024 09:49:08 +0300
Subject: [PATCH 16/59] minor
---
Makefile | 3 ---
src/core/Makefile | 3 +++
src/exe/main.c | 11 +++++++++++
src/gui/windows.c | 11 +++++++++++
src/list/m3u-read.h | 5 ++++-
src/net/http.c | 11 +----------
6 files changed, 30 insertions(+), 14 deletions(-)
diff --git a/Makefile b/Makefile
index 4693c6a..3250bc3 100644
--- a/Makefile
+++ b/Makefile
@@ -26,9 +26,6 @@ ifeq "$(ASAN)" "1"
CFLAGS += -fsanitize=address
LINKFLAGS += -fsanitize=address
endif
-ifdef PHI_VERSION_STR
- CFLAGS += -DPHI_VERSION_STR=\"$(PHI_VERSION_STR)\"
-endif
CFLAGS += $(CFLAGS_USER)
CFLAGS_BASE := $(CFLAGS)
CFLAGS += -I$(PHIOLA)/src -I$(FFSYS)
diff --git a/src/core/Makefile b/src/core/Makefile
index d4c0b4a..66cf61a 100644
--- a/src/core/Makefile
+++ b/src/core/Makefile
@@ -35,6 +35,9 @@ libphiola.$(SO): $(CORE_O)
$(LINK) -shared $+ $(LINKFLAGS_CORE) $(LINK_PTHREAD) $(LINK_DL) -o $@
CFLAGS_CORE := $(CFLAGS) -DFFBASE_OPT_SIZE
+ifdef PHI_VERSION_STR
+ CFLAGS_CORE += -DPHI_VERSION_STR=\"$(PHI_VERSION_STR)\"
+endif
%.o: $(PHIOLA)/src/core/%.c
$(C) $(CFLAGS_CORE) $< -o $@
%.o: $(PHIOLA)/src/jni/%.c
diff --git a/src/exe/main.c b/src/exe/main.c
index da6ee25..aef841d 100644
--- a/src/exe/main.c
+++ b/src/exe/main.c
@@ -221,6 +221,16 @@ static char* mod_loading(ffstr name)
, &x->root_dir, FFPATH_SLASH, &name, FFDL_EXT);
}
+static ffstr resource_load(const char *name)
+{
+ char *fn = ffsz_allocfmt("%Smod%c%s"
+ , &x->root_dir, FFPATH_SLASH, name);
+ ffvec d = {};
+ fffile_readwhole(fn, &d, 100*1024*1024);
+ ffmem_free(fn);
+ return *(ffstr*)&d;
+}
+
static int core()
{
struct phi_core_conf conf = {
@@ -231,6 +241,7 @@ static int core()
.env_expand = env_expand,
.mod_loading = mod_loading,
+ .resource_load = resource_load,
.workers = x->workers,
.cpu_affinity = x->cpu_affinity,
diff --git a/src/gui/windows.c b/src/gui/windows.c
index 0f82f40..04b6a5f 100644
--- a/src/gui/windows.c
+++ b/src/gui/windows.c
@@ -155,6 +155,16 @@ static char* mod_loading(ffstr name)
, &x->root_dir, FFPATH_SLASH, &name, FFDL_EXT);
}
+static ffstr resource_load(const char *name)
+{
+ char *fn = ffsz_allocfmt("%Smod%c%s"
+ , &x->root_dir, FFPATH_SLASH, name);
+ ffvec d = {};
+ fffile_readwhole(fn, &d, 100*1024*1024);
+ ffmem_free(fn);
+ return *(ffstr*)&d;
+}
+
static int core()
{
struct phi_core_conf conf = {
@@ -165,6 +175,7 @@ static int core()
.env_expand = env_expand,
.mod_loading = mod_loading,
+ .resource_load = resource_load,
.workers = ~0U,
.io_workers = ~0U,
diff --git a/src/list/m3u-read.h b/src/list/m3u-read.h
index dd3095a..14909f0 100644
--- a/src/list/m3u-read.h
+++ b/src/list/m3u-read.h
@@ -102,7 +102,7 @@ static int m3u_process(void *ctx, phi_track *t)
r = m3uread_process(&m->m3u, &data, &val);
- switch (r) {
+ switch ((enum M3UREAD_R)r) {
case M3UREAD_MORE:
if (!(t->chain_flags & PHI_FFIRST)) {
_ffvec_copyself(&m->pls_ent.artist);
@@ -140,6 +140,9 @@ static int m3u_process(void *ctx, phi_track *t)
warnlog(t, "parse error: %s", m3uread_error(&m->m3u));
continue;
+ case M3UREAD_EXT:
+ continue;
+
default:
FF_ASSERT(0);
return PHI_ERR;
diff --git a/src/net/http.c b/src/net/http.c
index b3a5be8..5cedee2 100644
--- a/src/net/http.c
+++ b/src/net/http.c
@@ -255,17 +255,9 @@ static struct nml_ssl_ctx* ssl_prepare(struct nml_http_client_conf *c)
struct ffssl_ctx_conf *scc = ffmem_new(struct ffssl_ctx_conf);
sc->ctx_conf = scc;
- char *cert_file = NULL;
- ffstr cert_data = {};
-#ifdef FF_ANDROID
- cert_data = core->conf.resource_load("http-client.pem");
+ ffstr cert_data = core->conf.resource_load("http-client.pem");
scc->cert_data = cert_data;
scc->pkey_data = cert_data;
-#else
- cert_file = ffsz_allocfmt("%S/mod/http-client.pem", &core->conf.root);
- scc->cert_file = cert_file;
- scc->pkey_file = cert_file;
-#endif
sc->log_level = c->log_level;
sc->log_obj = c->log_obj;
@@ -278,7 +270,6 @@ static struct nml_ssl_ctx* ssl_prepare(struct nml_http_client_conf *c)
}
ffstr_free(&cert_data);
- ffmem_free(cert_file);
return sc;
}
#endif
From db29df02e10aedba8fc118c557e3bc681a48aae6 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 16 Nov 2024 09:49:08 +0300
Subject: [PATCH 17/59] rename modules
---
src/acodec/Makefile | 38 ++++++++++++++++++++------------------
src/format/aac-read.h | 2 +-
src/format/ape-read.c | 2 +-
src/format/avi.c | 2 +-
src/format/caf.c | 2 +-
src/format/flac-ogg.c | 2 +-
src/format/flac-read.h | 2 +-
src/format/flac-write.h | 2 +-
src/format/mkv.c | 2 +-
src/format/mp3-read.h | 2 +-
src/format/mp3-write.h | 2 +-
src/format/mp4-read.h | 6 +++---
src/format/mp4-write.h | 2 +-
src/format/mpc-read.c | 2 +-
src/format/ogg-read.h | 4 ++--
src/format/ogg-write.h | 4 ++--
src/format/wv.c | 2 +-
17 files changed, 40 insertions(+), 38 deletions(-)
diff --git a/src/acodec/Makefile b/src/acodec/Makefile
index be6b480..b99ddae 100644
--- a/src/acodec/Makefile
+++ b/src/acodec/Makefile
@@ -21,68 +21,70 @@ AVPACK := $(ROOT_DIR)/avpack
ALIB3 := $(PHIOLA)/alib3
ALIB3_BIN := $(ALIB3)/_$(SYS)-$(CPU)
+PFX := ac-
+
# LOSSY
%.o: $(PHIOLA)/src/acodec/%.c
$(C) $(CFLAGS) -I$(ALIB3) $< -o $@
-MODS += aac.$(SO)
+MODS += $(PFX)aac.$(SO)
LIBS3 += $(ALIB3_BIN)/libfdk-aac-phi.$(SO)
-aac.$(SO): aac.o
+$(PFX)aac.$(SO): aac.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lfdk-aac-phi -o $@
-MODS += mpeg.$(SO)
+MODS += $(PFX)mpeg.$(SO)
LIBS3 += $(ALIB3_BIN)/libmpg123-phi.$(SO)
-mpeg.$(SO): mpeg.o
+$(PFX)mpeg.$(SO): mpeg.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lmpg123-phi -o $@
-MODS += vorbis.$(SO)
+MODS += $(PFX)vorbis.$(SO)
LIBS3 += $(ALIB3_BIN)/libvorbis-phi.$(SO) $(ALIB3_BIN)/libvorbisenc-phi.$(SO)
vorbis.o: $(PHIOLA)/src/acodec/vorbis.c
$(C) $(CFLAGS) -I$(ALIB3) -I$(AVPACK) $< -o $@
-vorbis.$(SO): vorbis.o
+$(PFX)vorbis.$(SO): vorbis.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lvorbis-phi -lvorbisenc-phi -o $@
-MODS += opus.$(SO)
+MODS += $(PFX)opus.$(SO)
LIBS3 += $(ALIB3_BIN)/libopus-phi.$(SO)
opus.o: $(PHIOLA)/src/acodec/opus.c
$(C) $(CFLAGS) -I$(ALIB3) -I$(AVPACK) $< -o $@
-opus.$(SO): opus.o
+$(PFX)opus.$(SO): opus.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lopus-phi -o $@
ifneq "$(SYS)" "android"
-MODS += mpc.$(SO)
+MODS += $(PFX)mpc.$(SO)
LIBS3 += $(ALIB3_BIN)/libmusepack-phi.$(SO)
endif
-mpc.$(SO): mpc.o
+$(PFX)mpc.$(SO): mpc.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lmusepack-phi -o $@
# LOSSLESS
-MODS += alac.$(SO)
+MODS += $(PFX)alac.$(SO)
LIBS3 += $(ALIB3_BIN)/libALAC-phi.$(SO)
-alac.$(SO): alac.o
+$(PFX)alac.$(SO): alac.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lALAC-phi -o $@
ifneq "$(SYS)" "android"
-MODS += ape.$(SO)
+MODS += $(PFX)ape.$(SO)
LIBS3 += $(ALIB3_BIN)/libMAC-phi.$(SO)
endif
ape.o: $(PHIOLA)/src/acodec/ape.c
$(C) $(CFLAGS) -I$(ALIB3) -I$(AVPACK) $< -o $@
-ape.$(SO): ape.o
+$(PFX)ape.$(SO): ape.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lMAC-phi -o $@
-MODS += flac.$(SO)
+MODS += $(PFX)flac.$(SO)
LIBS3 += $(ALIB3_BIN)/libFLAC-phi.$(SO)
flac.o: $(PHIOLA)/src/acodec/flac.c
$(C) $(CFLAGS) -I$(ALIB3) -I$(AVPACK) $< -o $@
-flac.$(SO): flac.o
+$(PFX)flac.$(SO): flac.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lFLAC-phi -o $@
ifneq "$(SYS)" "android"
-MODS += wavpack.$(SO)
+MODS += $(PFX)wavpack.$(SO)
LIBS3 += $(ALIB3_BIN)/libwavpack-phi.$(SO)
endif
-wavpack.$(SO): wavpack.o
+$(PFX)wavpack.$(SO): wavpack.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lwavpack-phi -o $@
diff --git a/src/format/aac-read.h b/src/format/aac-read.h
index c314f7f..ca33467 100644
--- a/src/format/aac-read.h
+++ b/src/format/aac-read.h
@@ -72,7 +72,7 @@ static int aac_adts_process(void *ctx, phi_track *t)
a->sample_rate = t->audio.format.rate;
if (!t->conf.stream_copy) {
- if (!core->track->filter(t, core->mod("aac.decode"), 0))
+ if (!core->track->filter(t, core->mod("ac-aac.decode"), 0))
return PHI_ERR;
}
diff --git a/src/format/ape-read.c b/src/format/ape-read.c
index 6629da0..f68ea29 100644
--- a/src/format/ape-read.c
+++ b/src/format/ape-read.c
@@ -88,7 +88,7 @@ static int ape_in_process(void *ctx, phi_track *t)
break;
case APEREAD_HEADER:
- if (!core->track->filter(t, core->mod("ape.decode"), 0))
+ if (!core->track->filter(t, core->mod("ac-ape.decode"), 0))
return PHI_ERR;
a->state = I_HDR_PARSED;
return PHI_DATA;
diff --git a/src/format/avi.c b/src/format/avi.c
index 377da7e..5b48e87 100644
--- a/src/format/avi.c
+++ b/src/format/avi.c
@@ -55,7 +55,7 @@ static const ushort avi_codecs[] = {
AVI_A_AAC, AVI_A_MP3,
};
static const char* const avi_codecs_str[] = {
- "aac.decode", "mpeg.decode",
+ "ac-aac.decode", "ac-mpeg.decode",
};
static const struct avi_audio_info* get_first_audio_track(struct avi_r *a)
diff --git a/src/format/caf.c b/src/format/caf.c
index 236f00b..04b0947 100644
--- a/src/format/caf.c
+++ b/src/format/caf.c
@@ -43,7 +43,7 @@ static const ffbyte caf_codecs[] = {
CAF_AAC, CAF_ALAC, CAF_LPCM,
};
static const char* const caf_codecs_str[] = {
- "aac.decode", "alac.decode", "",
+ "ac-aac.decode", "ac-alac.decode", "",
};
static int caf_process(void *ctx, phi_track *t)
diff --git a/src/format/flac-ogg.c b/src/format/flac-ogg.c
index 7ecbe0f..b03fcdb 100644
--- a/src/format/flac-ogg.c
+++ b/src/format/flac-ogg.c
@@ -107,7 +107,7 @@ static int flacogg_in_read(void *ctx, phi_track *t)
f->fr_samples = t->audio.flac_minblock;
- if (!core->track->filter(t, core->mod("flac.decode"), 0))
+ if (!core->track->filter(t, core->mod("ac-flac.decode"), 0))
return PHI_ERR;
break;
diff --git a/src/format/flac-read.h b/src/format/flac-read.h
index 08079e8..4d90697 100644
--- a/src/format/flac-read.h
+++ b/src/format/flac-read.h
@@ -123,7 +123,7 @@ static int flac_in_read(void *ctx, phi_track *t)
if (t->conf.info_only)
return PHI_LASTOUT;
- if (!core->track->filter(t, core->mod("flac.decode"), 0))
+ if (!core->track->filter(t, core->mod("ac-flac.decode"), 0))
return PHI_ERR;
break;
diff --git a/src/format/flac-write.h b/src/format/flac-write.h
index 29d9b99..c06ffec 100644
--- a/src/format/flac-write.h
+++ b/src/format/flac-write.h
@@ -125,7 +125,7 @@ static int flac_out_encode(void *ctx, phi_track *t)
switch (f->state) {
case I_FIRST:
- if (!core->track->filter(t, core->mod("flac.encode"), PHI_TF_PREV))
+ if (!core->track->filter(t, core->mod("ac-flac.encode"), PHI_TF_PREV))
return PHI_ERR;
f->state = I_INIT;
return PHI_MORE;
diff --git a/src/format/mkv.c b/src/format/mkv.c
index 28a6248..6fab61a 100644
--- a/src/format/mkv.c
+++ b/src/format/mkv.c
@@ -59,7 +59,7 @@ static const ushort mkv_codecs[] = {
MKV_A_AAC, MKV_A_ALAC, MKV_A_MPEGL3, MKV_A_OPUS, MKV_A_VORBIS, MKV_A_PCM,
};
static const char* const mkv_codecs_str[] = {
- "aac.decode", "alac.decode", "mpeg.decode", "opus.decode", "vorbis.decode", "",
+ "ac-aac.decode", "ac-alac.decode", "ac-mpeg.decode", "ac-opus.decode", "ac-vorbis.decode", "",
};
static const ushort mkv_vcodecs[] = {
MKV_V_AVC, MKV_V_HEVC,
diff --git a/src/format/mp3-read.h b/src/format/mp3-read.h
index e67fbfd..e1f203a 100644
--- a/src/format/mp3-read.h
+++ b/src/format/mp3-read.h
@@ -122,7 +122,7 @@ static int mp3_process(struct mp3_r *m, phi_track *t)
return PHI_LASTOUT;
if (!t->conf.stream_copy
- && !core->track->filter(t, core->mod("mpeg.decode"), 0))
+ && !core->track->filter(t, core->mod("ac-mpeg.decode"), 0))
return PHI_ERR;
break;
diff --git a/src/format/mp3-write.h b/src/format/mp3-write.h
index 8abd46d..ab9a98a 100644
--- a/src/format/mp3-write.h
+++ b/src/format/mp3-write.h
@@ -66,7 +66,7 @@ int mpeg_out_process(void *ctx, phi_track *t)
return PHI_ERR;
}
- if (!core->track->filter(t, core->mod("mpeg.encode"), PHI_TF_PREV)) {
+ if (!core->track->filter(t, core->mod("ac-mpeg.encode"), PHI_TF_PREV)) {
t->error = PHI_E_OUT_FMT;
return PHI_ERR;
}
diff --git a/src/format/mp4-read.h b/src/format/mp4-read.h
index be636bc..9786c9b 100644
--- a/src/format/mp4-read.h
+++ b/src/format/mp4-read.h
@@ -109,12 +109,12 @@ static const char* mp4r_info(struct mp4_r *m, phi_track *t, const struct mp4read
const char *filter = NULL;
switch (ai->codec) {
case MP4_A_ALAC:
- filter = "alac.decode";
+ filter = "ac-alac.decode";
t->audio.bitrate = ai->real_bitrate;
break;
case MP4_A_AAC:
- filter = "aac.decode";
+ filter = "ac-aac.decode";
if (!t->conf.stream_copy) {
t->audio.start_delay = ai->enc_delay;
t->audio.end_padding = ai->end_padding;
@@ -123,7 +123,7 @@ static const char* mp4r_info(struct mp4_r *m, phi_track *t, const struct mp4read
break;
case MP4_A_MPEG1:
- filter = "mpeg.decode";
+ filter = "ac-mpeg.decode";
t->audio.bitrate = (ai->aac_bitrate != 0) ? ai->aac_bitrate : 0;
break;
}
diff --git a/src/format/mp4-write.h b/src/format/mp4-write.h
index e7cc5b7..06d3ab3 100644
--- a/src/format/mp4-write.h
+++ b/src/format/mp4-write.h
@@ -61,7 +61,7 @@ static int mp4w_write(struct mp4_w *m, phi_track *t)
m->state = 1;
} else if (ffsz_eq(t->data_type, "pcm")) {
- if (!core->track->filter(t, core->mod("aac.encode"), PHI_TF_PREV))
+ if (!core->track->filter(t, core->mod("ac-aac.encode"), PHI_TF_PREV))
return PHI_ERR;
m->state = 1;
return PHI_MORE;
diff --git a/src/format/mpc-read.c b/src/format/mpc-read.c
index 800384d..069c3e5 100644
--- a/src/format/mpc-read.c
+++ b/src/format/mpc-read.c
@@ -89,7 +89,7 @@ static int mpc_process(void *ctx, phi_track *t)
case MPCREAD_HEADER:
mpc_info(m, t, mpcread_info(&m->mpc));
- if (!core->track->filter(t, core->mod("mpc.decode"), 0))
+ if (!core->track->filter(t, core->mod("ac-mpc.decode"), 0))
return PHI_ERR;
m->sample_rate = t->audio.format.rate;
diff --git a/src/format/ogg-read.h b/src/format/ogg-read.h
index f25134e..05cae3e 100644
--- a/src/format/ogg-read.h
+++ b/src/format/ogg-read.h
@@ -53,12 +53,12 @@ static int add_decoder(struct ogg_r *o, phi_track *t, ffstr data)
if (ffstr_matchz(&data, VORBIS_HEAD_STR)) {
meta_filter_name = "format.vorbismeta";
if (!t->conf.stream_copy && !t->conf.info_only)
- dec = "vorbis.decode";
+ dec = "ac-vorbis.decode";
} else if (ffstr_matchz(&data, OPUS_HEAD_STR)) {
meta_filter_name = "format.opusmeta";
if (!t->conf.stream_copy && !t->conf.info_only)
- dec = "opus.decode";
+ dec = "ac-opus.decode";
} else if (ffstr_matchz(&data, FLAC_HEAD_STR)) {
dec = "format.flacogg";
diff --git a/src/format/ogg-write.h b/src/format/ogg-write.h
index 353370d..93225cc 100644
--- a/src/format/ogg-write.h
+++ b/src/format/ogg-write.h
@@ -43,8 +43,8 @@ static const char* ogg_enc_mod(const char *fn)
ffpath_splitpath(fn, ffsz_len(fn), NULL, &name);
ffstr_rsplitby(&name, '.', NULL, &ext);
if (ffstr_eqcz(&ext, "opus"))
- return "opus.encode";
- return "vorbis.encode";
+ return "ac-opus.encode";
+ return "ac-vorbis.encode";
}
static int pkt_write(struct ogg_w *o, phi_track *t, ffstr *in, ffstr *out, uint64 endpos, uint flags)
diff --git a/src/format/wv.c b/src/format/wv.c
index 74f65dc..3a966bb 100644
--- a/src/format/wv.c
+++ b/src/format/wv.c
@@ -103,7 +103,7 @@ static int wv_in_process(void *ctx, phi_track *t)
w->hdr_done = 1;
const struct wvread_info *info = wvread_info(&w->wv);
t->audio.total = info->total_samples;
- if (!core->track->filter(t, core->mod("wavpack.decode"), 0))
+ if (!core->track->filter(t, core->mod("ac-wavpack.decode"), 0))
return PHI_ERR;
w->state = I_HDR_PARSED;
}
From 30608ef973825f14c7fb636d0c792659a97e6dd5 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 16 Nov 2024 09:49:08 +0300
Subject: [PATCH 18/59] 2.3-beta0
---
android/phiola/build.gradle | 4 ++--
src/phiola.h | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/android/phiola/build.gradle b/android/phiola/build.gradle
index 11f6792..507625d 100644
--- a/android/phiola/build.gradle
+++ b/android/phiola/build.gradle
@@ -8,8 +8,8 @@ android {
applicationId "com.github.stsaz.phiola"
minSdkVersion 16
targetSdk 33
- versionCode 20208
- versionName '2.2.8'
+ versionCode 20300
+ versionName '2.3-beta0'
}
buildFeatures {
diff --git a/src/phiola.h b/src/phiola.h
index d93e1fb..d7cc824 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -6,13 +6,13 @@
#include
#include
-#define PHI_VERSION 20208
+#define PHI_VERSION 20300
/** Inter-module compatibility version.
It must be updated when incompatible changes are made to this file,
then all modules must be rebuilt.
The core will refuse to load modules built for any other core version. */
-#define PHI_VERSION_CORE 20206
+#define PHI_VERSION_CORE 20300
typedef long long int64;
typedef unsigned long long uint64;
From cc0eefd27d56eb3cb282b7983a641d52cafc2015 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 23 Nov 2024 09:06:35 +0300
Subject: [PATCH 19/59] * Android: select playlist via menu
---
.../com/github/stsaz/phiola/MainActivity.java | 39 ++++++++++++++++---
.../java/com/github/stsaz/phiola/Queue.java | 2 +
2 files changed, 35 insertions(+), 6 deletions(-)
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
index d29a8cc..a7efc50 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
@@ -346,6 +346,10 @@ private void init_ui() {
b.bexplorer.setOnClickListener((v) -> explorer_click());
b.bplaylist.setOnClickListener((v) -> plist_click());
+ b.bplaylist.setOnLongClickListener((v) -> {
+ playlist_menu_show();
+ return true;
+ });
b.bplaylist.setChecked(true);
bplaylist_text(queue.current_list_index());
@@ -503,6 +507,20 @@ private void plist_click() {
plist_show();
}
+ private void playlist_menu_show() {
+ PopupMenu m = new PopupMenu(this, b.bplaylist);
+ m.setOnMenuItemClickListener((item) -> {
+ list_switch_i(item.getItemId());
+ return true;
+ });
+ int n = queue.number();
+ for (int i = 0; i < n; i++) {
+ String s = String.format(getString(R.string.main_playlist_n), i + 1);
+ m.getMenu().add(0, i, 0, s);
+ }
+ m.show();
+ }
+
private void file_tags_show() { startActivity(new Intent(this, TagsActivity.class)); }
/** Delete file and update view */
@@ -731,18 +749,27 @@ private void list_save() {
startActivity(new Intent(this, ListSaveActivity.class));
}
- private void list_switch() {
- list_leave();
-
- int qi = queue.next_list_select();
+ private void list_switched(int i) {
list_update();
- bplaylist_text(qi);
+ bplaylist_text(i);
- int n = gui.list_scroll_pos(qi);
+ int n = gui.list_scroll_pos(i);
if (n != 0)
b.list.scrollToPosition(n);
}
+ private void list_switch() {
+ list_leave();
+ int qi = queue.next_list_select();
+ list_switched(qi);
+ }
+
+ private void list_switch_i(int i) {
+ list_leave();
+ queue.switch_list(i);
+ list_switched(i);
+ }
+
private void list_next_add_cur() {
int qi = queue.next_list_add_cur();
if (qi >= 0)
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
index ade5209..b2dd1cd 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
@@ -483,6 +483,8 @@ void rm_non_existing() {
queues.get(i_selected).remove_non_existing();
}
+ int number() { return queues.size(); }
+
/** Get tracks number in the currently selected (not filtered) list */
int count() { return queues.get(i_selected).count(); }
From 6925be292327b5d911f31bdcddd01edb7b7f5225 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 23 Nov 2024 09:06:35 +0300
Subject: [PATCH 20/59] * Android: load playlists on-demand
---
.../java/com/github/stsaz/phiola/Queue.java | 25 ++++++---
src/jni/phiola-jni.c | 5 +-
src/jni/queue.h | 53 +++++++++++++------
3 files changed, 61 insertions(+), 22 deletions(-)
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
index b2dd1cd..91ebb2c 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
@@ -13,6 +13,7 @@ class PhiolaQueue {
long q;
boolean modified;
boolean conversion;
+ String file_name;
PhiolaQueue(Phiola _phi, long _q) {
phi = _phi;
@@ -81,6 +82,13 @@ PhiolaQueue filter(String filter, int flags) {
return new PhiolaQueue(phi, phi.quFilter(q, filter, flags));
}
+ void load_once() {
+ if (file_name == null) return;
+
+ phi.quLoad(q, file_name);
+ file_name = null;
+ }
+
boolean save(String filepath) {
boolean b = phi.quSave(q, filepath);
if (b)
@@ -205,6 +213,9 @@ else if (pos < trk_idx)
case 'c':
nfy_all(QueueNotify.REMOVED, -1); break;
+ case 'a':
+ nfy_all(QueueNotify.ADDED, pos); break;
+
case 'r':
nfy_all(QueueNotify.REMOVED, pos); break;
}
@@ -243,6 +254,7 @@ else if (i_selected < i_conversion)
i_selected--;
if (i_selected < 0)
i_selected = 0;
+ queues.get(i_selected).load_once();
// As positions of all next lists have just been changed, we must rewrite the files on disk accordingly
int i = 0;
@@ -276,7 +288,7 @@ void load() {
break;
new_list();
- phi.quLoad(queues.get(i).q, fn);
+ queues.get(i).file_name = fn;
}
if (i == 0) {
@@ -288,6 +300,7 @@ void load() {
i_active = 0;
if (i_selected >= i)
i_selected = 0;
+ queues.get(i_selected).load_once();
if (flags_test(F_REPEAT))
phi.quCmd(-1, Phiola.QUCOM_REPEAT, 1);
@@ -333,12 +346,16 @@ private int index_next(int qi) {
int next_list_select() {
filter("");
i_selected = index_next(i_selected);
+ queues.get(i_selected).load_once();
return i_selected;
}
boolean conversion_list(int i) { return i == i_conversion; }
- void switch_list(int i) { i_selected = i; }
+ void switch_list(int i) {
+ i_selected = i;
+ queues.get(i_selected).load_once();
+ }
int current_list_index() { return i_selected; }
long current_list_id() { return queues.get(i_selected).q; }
@@ -494,22 +511,18 @@ void rm_non_existing() {
static final int ADD = 2;
void addmany(String[] urls, int flags) {
- int pos = count();
filter_close();
int f = 0;
if (flags == ADD_RECURSE)
f = Phiola.QUADD_RECURSE;
queues.get(i_selected).add_many(urls, f);
- nfy_all(QueueNotify.ADDED, pos);
}
/** Add an entry */
void add(String url) {
core.dbglog(TAG, "add: %s", url);
- int pos = count();
filter_close();
queues.get(i_selected).add(url, 0);
- nfy_all(QueueNotify.ADDED, pos);
}
String conf_write() {
diff --git a/src/jni/phiola-jni.c b/src/jni/phiola-jni.c
index 25d9378..3ed6445 100644
--- a/src/jni/phiola-jni.c
+++ b/src/jni/phiola-jni.c
@@ -148,7 +148,10 @@ do { \
struct core_data {
phi_task task;
uint cmd;
- int64 param_int;
+ union {
+ int64 param_int;
+ char *param_str;
+ };
phi_queue_id q;
};
diff --git a/src/jni/queue.h b/src/jni/queue.h
index 65f26bb..2b24fe2 100644
--- a/src/jni/queue.h
+++ b/src/jni/queue.h
@@ -8,6 +8,7 @@ static void qu_on_change(phi_queue_id q, uint flags, uint pos)
dbglog("%s: '%c' q:%p", __func__, flags, (size_t)q);
switch (flags) {
+ case 'a':
case 'r':
case 'c':
case 'u':
@@ -75,11 +76,21 @@ Java_com_github_stsaz_phiola_Phiola_quDestroy(JNIEnv *env, jobject thiz, jlong q
dbglog("%s: exit", __func__);
}
-JNIEXPORT void JNICALL
-Java_com_github_stsaz_phiola_Phiola_quDup(JNIEnv *env, jobject thiz, jlong jq, jlong q_src, jint pos)
+#define QUADD_RECURSE 1
+
+static void qu_cmd_add(struct core_data *d)
{
- dbglog("%s: enter", __func__);
- phi_queue_id q = (phi_queue_id)jq, iq = (phi_queue_id)q_src;
+ struct phi_queue_entry qe = {
+ .conf.ifile.name = d->param_str,
+ };
+ x->queue.add(d->q, &qe);
+ ffmem_free(d);
+}
+
+static void qu_cmd_dup(struct core_data *d)
+{
+ phi_queue_id iq = (phi_queue_id)d->param_int;
+ int pos = d->cmd;
const struct phi_queue_entry *iqe;
uint i = (pos >= 0) ? pos : 0;
for (; (iqe = x->queue.at(iq, i)); i++) {
@@ -87,14 +98,24 @@ Java_com_github_stsaz_phiola_Phiola_quDup(JNIEnv *env, jobject thiz, jlong jq, j
phi_track_conf_assign(&qe.conf, &iqe->conf);
qe.conf.ifile.name = ffsz_dup(iqe->conf.ifile.name);
x->metaif.copy(&qe.conf.meta, &iqe->conf.meta);
- x->queue.add(q, &qe);
+ x->queue.add(d->q, &qe);
if (pos >= 0)
break;
}
- dbglog("%s: exit", __func__);
+ ffmem_free(d);
}
-#define QUADD_RECURSE 1
+JNIEXPORT void JNICALL
+Java_com_github_stsaz_phiola_Phiola_quDup(JNIEnv *env, jobject thiz, jlong jq, jlong q_src, jint pos)
+{
+ dbglog("%s: enter", __func__);
+ struct core_data *d = ffmem_new(struct core_data);
+ d->q = (phi_queue_id)jq;
+ d->cmd = pos;
+ d->param_int = q_src;
+ core_task(d, qu_cmd_dup);
+ dbglog("%s: exit", __func__);
+}
JNIEXPORT void JNICALL
Java_com_github_stsaz_phiola_Phiola_quAdd(JNIEnv *env, jobject thiz, jlong q, jobjectArray jurls, jint flags)
@@ -108,10 +129,10 @@ Java_com_github_stsaz_phiola_Phiola_quAdd(JNIEnv *env, jobject thiz, jlong q, jo
js = jni_joa_i(jurls, i);
fn = jni_sz_js(js);
- struct phi_queue_entry qe = {
- .conf.ifile.name = ffsz_dup(fn),
- };
- x->queue.add((phi_queue_id)q, &qe);
+ struct core_data *d = ffmem_new(struct core_data);
+ d->q = (phi_queue_id)q;
+ d->param_str = ffsz_dup(fn);
+ core_task(d, qu_cmd_add);
}
jni_sz_free(fn, js);
@@ -564,10 +585,12 @@ Java_com_github_stsaz_phiola_Phiola_quLoad(JNIEnv *env, jobject thiz, jlong q, j
{
dbglog("%s: enter", __func__);
const char *fn = jni_sz_js(jfilepath);
- struct phi_queue_entry qe = {
- .conf.ifile.name = ffsz_dup(fn),
- };
- x->queue.add((phi_queue_id)q, &qe);
+
+ struct core_data *d = ffmem_new(struct core_data);
+ d->q = (phi_queue_id)q;
+ d->param_str = ffsz_dup(fn);
+ core_task(d, qu_cmd_add);
+
jni_sz_free(fn, jfilepath);
dbglog("%s: exit", __func__);
return 0;
From 723fdae75bf9dca374ef459caeb820d6a9123956 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 23 Nov 2024 09:06:35 +0300
Subject: [PATCH 21/59] + Android: start playback from file manager via "Open
With" list
---
android/phiola/src/main/AndroidManifest.xml | 15 +++++++++++++++
.../com/github/stsaz/phiola/MainActivity.java | 9 +++++++++
.../main/java/com/github/stsaz/phiola/Util.java | 17 +++++++++++++++++
3 files changed, 41 insertions(+)
diff --git a/android/phiola/src/main/AndroidManifest.xml b/android/phiola/src/main/AndroidManifest.xml
index c665247..f70e251 100644
--- a/android/phiola/src/main/AndroidManifest.xml
+++ b/android/phiola/src/main/AndroidManifest.xml
@@ -27,6 +27,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
"/storage/emulated/0/Music/1.mp3" */
+ static String path_real(String s, String[] storage_paths) {
+ int pos = s.indexOf(':');
+ if (pos < 0 || pos + 1 == s.length())
+ return null;
+
+ String path = s.substring(pos + 1);
+ for (String stg : storage_paths) {
+ s = stg + "/" + path;
+ if (new File(s).exists())
+ return s;
+ }
+
+ return null;
+ }
+
boolean file_delete(String path) {
try {
File f = new File(path);
From b4af76f53bf391ea00585d8ddf1511b791530b80 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 23 Nov 2024 09:06:35 +0300
Subject: [PATCH 22/59] * support .ts(MP3) streams
---
README.md | 2 +-
src/format/ts-read.c | 11 ++++++++++-
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 7706b01..85e0aae 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ Contents:
## Features
-* Play audio: `.mp3`, `.ogg`(Vorbis/Opus), `.mp4`/`.mov`(AAC/ALAC/MPEG), `.mkv`/`.webm`(AAC/ALAC/MPEG/Vorbis/Opus/PCM), `.caf`(AAC/ALAC/PCM), `.avi`(AAC/MPEG/PCM), `.ts`(AAC), `.aac`, `.mpc`; `.flac`, `.ape`, `.wv`, `.wav`.
+* Play audio: `.mp3`, `.ogg`(Vorbis/Opus), `.mp4`/`.mov`(AAC/ALAC/MP3), `.mkv`/`.webm`(AAC/ALAC/MP3/Vorbis/Opus/PCM), `.caf`(AAC/ALAC/PCM), `.avi`(AAC/MP3/PCM), `.ts`(AAC/MP3), `.aac`, `.mpc`; `.flac`, `.ape`, `.wv`, `.wav`.
* Record audio: `.m4a`(AAC), `.ogg`, `.opus`; `.flac`, `.wav`
* Convert audio
* List/search file meta tags; edit MP3 tags
diff --git a/src/format/ts-read.c b/src/format/ts-read.c
index d583651..56c609f 100644
--- a/src/format/ts-read.c
+++ b/src/format/ts-read.c
@@ -8,6 +8,7 @@
extern const phi_core *core;
#define errlog(t, ...) phi_errlog(core, NULL, t, __VA_ARGS__)
+#define warnlog(t, ...) phi_warnlog(core, NULL, t, __VA_ARGS__)
#define dbglog(t, ...) phi_dbglog(core, NULL, t, __VA_ARGS__)
struct ts_r {
@@ -32,6 +33,7 @@ static void* ts_open(phi_track *t)
c->pos_start_msec = ~0ULL;
uint64 tsize = (t->input.size != ~0ULL) ? t->input.size : 0;
tsread_open(&c->ts, tsize);
+ t->input.size = ~0ULL; // prevent file seek requests from the format filter
c->ts.log = ts_log;
c->ts.udata = c;
return c;
@@ -48,6 +50,8 @@ static int ts_info(struct ts_r *c, phi_track *t, const struct _tsr_pm *info)
{
const char *mod;
switch (info->stream_type) {
+ case TS_STREAM_AUDIO_MP3:
+ mod = "format.mp3"; break;
case TS_STREAM_AUDIO_AAC:
mod = "format.aac"; break;
default:
@@ -79,7 +83,7 @@ static int ts_process(void *ctx, phi_track *t)
for (;;) {
r = tsread_process(&c->ts, &c->in, &pkt);
- switch (r) {
+ switch ((enum TSREAD_R)r) {
case TSREAD_DATA:
if (c->n_pkt == 0) {
@@ -102,6 +106,11 @@ static int ts_process(void *ctx, phi_track *t)
// case TSREAD_DONE:
// return PHI_LASTOUT;
+ case TSREAD_WARN:
+ warnlog(t, "tsread_process(): %s. Offset: %U"
+ , tsread_error(&c->ts), tsread_offset(&c->ts));
+ continue;
+
case TSREAD_ERROR:
errlog(t, "tsread_process(): %s. Offset: %U"
, tsread_error(&c->ts), tsread_offset(&c->ts));
From bc8c8862e5ab371bdba5b0f5eb15d3437974c384 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 23 Nov 2024 09:06:35 +0300
Subject: [PATCH 23/59] * HLS: support m3u8 sublist
---
src/net/hls.h | 34 +++++++++++++++++++++++++++++++++-
1 file changed, 33 insertions(+), 1 deletion(-)
diff --git a/src/net/hls.h b/src/net/hls.h
index 02cf88e..d0688c4 100644
--- a/src/net/hls.h
+++ b/src/net/hls.h
@@ -46,7 +46,9 @@ struct hlsread {
phi_task task;
uint worker;
uint n_fail_tries;
+ uint n_redirect;
uint data_file :1;
+ uint m3u_file :1;
};
static struct hlsread* hls_new(struct httpcl *h)
@@ -107,6 +109,26 @@ static int hls_q_update(struct httpcl *h, ffstr d)
continue;
}
+ if (ffstr_irmatchcz(&ln, ".m3u8")) {
+ // m3u8 inside m3u8: redirect to the first sublist
+
+ if (l->seq_last) {
+ errlog(h->trk, "unexpected m3u file: %S", &ln);
+ return -1;
+ }
+
+ if (l->n_redirect++ == 10) {
+ errlog(h->trk, "couldn't reach the leaf m3u sublist. Last: %S", &ln);
+ return -1;
+ }
+
+ ffstr *ps = ffvec_zpushT(&l->q, ffstr);
+ ffstr_dupstr(ps, &ln);
+ l->m3u_file = 1;
+ n++;
+ break;
+ }
+
if (l->seq_last < seq) {
l->seq_last = seq;
ffstr *ps = ffvec_zpushT(&l->q, ffstr);
@@ -152,6 +174,14 @@ static void hls_f_request(struct httpcl *h, ffstr name)
ffstr_free(&name);
url = *(ffstr*)&l->url;
l->data_file = 1;
+
+ if (l->m3u_file) {
+ l->m3u_file = 0;
+ l->data_file = 0;
+ // set the m3u sublist as main URL
+ ffmem_free(h->trk->conf.ifile.name);
+ h->trk->conf.ifile.name = ffsz_dupstr(&url);
+ }
}
conf_prepare(h, c, h->trk, url);
@@ -184,7 +214,9 @@ static int hls_f_complete(struct httpcl *h)
if (!l->data_file) {
int r = hls_q_update(h, *(ffstr*)&l->data);
l->data.len = 0;
- if (r) {
+ if (r < 0) {
+ return -1;
+ } else if (r) {
if (!l->seq_last
|| l->n_fail_tries == 10) {
errlog(h->trk, "no more files in m3u");
From bb633a97cc5d391d7121057d15a3e7982a36baac Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 23 Nov 2024 09:06:35 +0300
Subject: [PATCH 24/59] + GUI: Convert: can specify output audio tags
---
src/core/queue-entry.h | 2 +-
src/gui/convert.hpp | 14 ++++++++++++--
src/gui/lang_en.conf | 1 +
src/gui/mod.c | 6 ++++++
src/gui/ui-gtk.conf | 6 ++++++
src/gui/ui-winapi.conf | 6 ++++++
6 files changed, 32 insertions(+), 3 deletions(-)
diff --git a/src/core/queue-entry.h b/src/core/queue-entry.h
index 1bcca1f..25be19f 100644
--- a/src/core/queue-entry.h
+++ b/src/core/queue-entry.h
@@ -180,7 +180,7 @@ static int qe_play(struct q_entry *e)
goto err;
}
- if (e->have_user_meta) {
+ if ((e->have_user_meta = (e->pub.conf.meta.len && !e->pub.conf.meta_transient))) {
phi_metaif->copy(&t->meta, &t->conf.meta);
}
diff --git a/src/gui/convert.hpp b/src/gui/convert.hpp
index eb9ef8c..78e274c 100644
--- a/src/gui/convert.hpp
+++ b/src/gui/convert.hpp
@@ -5,8 +5,8 @@
struct gui_wconvert {
ffui_windowxx wnd;
- ffui_labelxx ldir, lname, lext, lfrom, luntil, laacq, lvorbisq, lopusq;
- ffui_editxx edir, ename, efrom, euntil, eaacq, evorbisq, eopusq;
+ ffui_labelxx ldir, lname, lext, lfrom, luntil, ltags, laacq, lvorbisq, lopusq;
+ ffui_editxx edir, ename, efrom, euntil, etags, eaacq, evorbisq, eopusq;
ffui_comboboxxx cbext;
ffui_checkboxxx cbcopy;
ffui_buttonxx bbrowse, bstart;
@@ -27,6 +27,7 @@ FF_EXTERN const ffui_ldr_ctl wconvert_ctls[] = {
_(bbrowse),
_(lfrom), _(efrom),
_(luntil), _(euntil),
+ _(ltags), _(etags),
_(cbcopy),
_(laacq), _(eaacq),
_(lvorbisq),_(evorbisq),
@@ -188,6 +189,15 @@ static struct phi_track_conf* conv_conf_create()
tc->until_msec = time_value(xxvec(c->euntil.text()).str());
tc->stream_copy = c->conf_copy;
+ xxvec tags = c->etags.text();
+ xxstr s = tags.str(), name, val;
+ while (s.len) {
+ s.split_by(';', &name, &s);
+ name.split_by('=', &name, &val);
+ if (name.len)
+ gd->metaif->set(&tc->meta, name, val, 0);
+ }
+
tc->aac.quality = c->conf_aacq;
tc->vorbis.quality = c->conf_vorbisq;
tc->opus.bitrate = c->conf_opusq;
diff --git a/src/gui/lang_en.conf b/src/gui/lang_en.conf
index 6f10c7e..4681b23 100644
--- a/src/gui/lang_en.conf
+++ b/src/gui/lang_en.conf
@@ -77,6 +77,7 @@ MMConvert "_Convert"
CVOutChoose "Choose Output File..."
CVFrom "Start Audio Position:"
CVUntil "End Audio Position:"
+ CVTags "Tags (NAME=VALUE;...):"
CVCopy "[.mp3,.m4a,.ogg] Stream Copy"
CVAACQ "[.m4a] AAC Quality: 1..5 (VBR) or 8..800 (CBR, kbit/s)"
CVVorbisQ "[.ogg] Vorbis Quality: 0..10"
diff --git a/src/gui/mod.c b/src/gui/mod.c
index 894f771..a53109a 100644
--- a/src/gui/mod.c
+++ b/src/gui/mod.c
@@ -735,6 +735,12 @@ void convert_begin(void *param)
qe->conf.vorbis.quality = c->vorbis.quality;
qe->conf.opus.bitrate = c->opus.bitrate;
qe->conf.stream_copy = c->stream_copy;
+
+ if (c->meta.len) {
+ qe->conf.meta_transient = 0;
+ gd->metaif->destroy(&qe->conf.meta);
+ gd->metaif->copy(&qe->conf.meta, &c->meta);
+ }
}
if (i)
gd->queue->play(NULL, gd->queue->at(gd->q_convert, 0));
diff --git a/src/gui/ui-gtk.conf b/src/gui/ui-gtk.conf
index 9aa7ab6..cc12bac 100644
--- a/src/gui/ui-gtk.conf
+++ b/src/gui/ui-gtk.conf
@@ -486,6 +486,12 @@ window wconvert {
editbox euntil {
}
+ label ltags {
+ text $CVTags
+ }
+ editbox etags {
+ }
+
checkbox cbcopy {
text $CVCopy
}
diff --git a/src/gui/ui-winapi.conf b/src/gui/ui-winapi.conf
index 6342976..a3c0ef0 100644
--- a/src/gui/ui-winapi.conf
+++ b/src/gui/ui-winapi.conf
@@ -524,6 +524,12 @@ window wconvert {
editbox euntil {
}
+ label ltags {
+ text $CVTags
+ }
+ editbox etags {
+ }
+
checkbox cbcopy {
text $CVCopy
}
From 7cdc212bed33e828b9290f5a327c454b5ae3f8c6 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 23 Nov 2024 09:06:35 +0300
Subject: [PATCH 25/59] + Android: Convert: can specify output audio tags
---
.../github/stsaz/phiola/ConvertActivity.java | 1 +
.../java/com/github/stsaz/phiola/Phiola.java | 2 ++
android/phiola/src/main/res/layout/convert.xml | 13 +++++++++++++
android/phiola/src/main/res/values/strings.xml | 1 +
src/jni/queue.h | 17 +++++++++++++++++
5 files changed, 34 insertions(+)
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/ConvertActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/ConvertActivity.java
index 4429c0f..893bc89 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/ConvertActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/ConvertActivity.java
@@ -267,6 +267,7 @@ private void convert() {
Phiola.ConvertParams p = new Phiola.ConvertParams();
p.from_msec = b.eFrom.getText().toString();
p.to_msec = b.eUntil.getText().toString();
+ p.tags = b.eTags.getText().toString();
p.sample_format = CoreSettings.conv_sample_formats[b.spSampleFormat.getSelectedItemPosition()];
p.sample_rate = core.str_to_uint(b.eSampleRate.getText().toString(), 0);
p.aac_quality = core.setts.conv_aac_quality;
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
index 766bd43..5727b7b 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
@@ -57,6 +57,7 @@ static class ConvertParams {
out_name = "";
from_msec = "";
to_msec = "";
+ tags = "";
trash_dir_rel = "";
q_pos = -1;
}
@@ -71,6 +72,7 @@ static class ConvertParams {
String out_name;
String from_msec, to_msec;
+ String tags;
int sample_format;
int sample_rate;
int aac_quality;
diff --git a/android/phiola/src/main/res/layout/convert.xml b/android/phiola/src/main/res/layout/convert.xml
index c3f2fe4..a84140b 100644
--- a/android/phiola/src/main/res/layout/convert.xml
+++ b/android/phiola/src/main/res/layout/convert.xml
@@ -126,6 +126,19 @@
android:layout_marginHorizontal="@dimen/sett_margin"
android:text="@string/conv_from_set_cur" />
+
+
+
Start Audio Position:
Set To Current
End Audio Position:
+ Tags (NAME=VALUE;...):
Filters
Sample Format
diff --git a/src/jni/queue.h b/src/jni/queue.h
index 2b24fe2..ae9b619 100644
--- a/src/jni/queue.h
+++ b/src/jni/queue.h
@@ -454,11 +454,13 @@ Java_com_github_stsaz_phiola_Phiola_quConvertBegin(JNIEnv *env, jobject thiz, jl
jstring jout_name = jni_obj_jo(jconf, jni_field_str(jc_conf, "out_name"));
jstring jfrom = jni_obj_jo(jconf, jni_field_str(jc_conf, "from_msec"));
jstring jto = jni_obj_jo(jconf, jni_field_str(jc_conf, "to_msec"));
+ jstring jtags = jni_obj_jo(jconf, jni_field_str(jc_conf, "tags"));
jstring jtrash_dir_rel = jni_obj_jo(jconf, jni_field_str(jc_conf, "trash_dir_rel"));
uint flags = jni_obj_int(jconf, jni_field_int(jc_conf, "flags"));
const char *ofn = jni_sz_js(jout_name)
, *from = jni_sz_js(jfrom)
, *to = jni_sz_js(jto)
+ , *tags = jni_sz_js(jtags)
, *trash_dir_rel = jni_sz_js(jtrash_dir_rel);
struct phi_track_conf conf = {
@@ -481,6 +483,14 @@ Java_com_github_stsaz_phiola_Phiola_quConvertBegin(JNIEnv *env, jobject thiz, jl
goto end;
}
+ ffstr s = FFSTR_INITZ(tags), name, val;
+ while (s.len) {
+ ffstr_splitby(&s, ';', &name, &s);
+ ffstr_splitby(&name, '=', &name, &val);
+ if (name.len)
+ x->metaif.set(&conf.meta, name, val, 0);
+ }
+
ffmem_free(x->convert.trash_dir_rel);
x->convert.trash_dir_rel = (trash_dir_rel[0]) ? ffsz_dup(trash_dir_rel) : NULL;
@@ -508,6 +518,12 @@ Java_com_github_stsaz_phiola_Phiola_quConvertBegin(JNIEnv *env, jobject thiz, jl
c->ofile.name = ffsz_dup(ofn);
c->ofile.name_tmp = 1;
c->ofile.overwrite = conf.ofile.overwrite;
+
+ if (conf.meta.len) {
+ qe->conf.meta_transient = 0;
+ x->metaif.destroy(&c->meta);
+ x->metaif.copy(&c->meta, &conf.meta);
+ }
}
ffvec_free_align(&x->convert.tracks);
@@ -525,6 +541,7 @@ Java_com_github_stsaz_phiola_Phiola_quConvertBegin(JNIEnv *env, jobject thiz, jl
jni_sz_free(ofn, jout_name);
jni_sz_free(from, jfrom);
jni_sz_free(to, jto);
+ jni_sz_free(tags, jtags);
jstring js = jni_js_sz(error);
dbglog("%s: exit", __func__);
return js;
From 330715877da6ef9e9f1e4c9e7075f3ea7e15ef86 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 23 Nov 2024 09:06:35 +0300
Subject: [PATCH 26/59] * convert/record: default Vorbis encoding quality = 5
---
src/exe/convert.h | 2 +-
src/exe/record.h | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/exe/convert.h b/src/exe/convert.h
index 8c46726..1dd8b60 100644
--- a/src/exe/convert.h
+++ b/src/exe/convert.h
@@ -231,7 +231,7 @@ static void conv_qu_add(struct cmd_conv *v, ffstr *fn)
.quality = v->aac_q,
.bandwidth = (ushort)v->aac_bandwidth,
},
- .vorbis.quality = (v->vorbis_q + 1) * 10,
+ .vorbis.quality = (v->vorbis_q) ? (v->vorbis_q + 1) * 10 : 0,
.opus = {
.bitrate = v->opus_q,
.mode = v->opus_mode_n,
diff --git a/src/exe/record.h b/src/exe/record.h
index 1047f36..f8c554e 100644
--- a/src/exe/record.h
+++ b/src/exe/record.h
@@ -163,7 +163,7 @@ static int rec_action(struct cmd_rec *r)
.profile = r->aac_profile[0],
.quality = r->aac_q,
},
- .vorbis.quality = (r->vorbis_q + 1) * 10,
+ .vorbis.quality = (r->vorbis_q) ? (r->vorbis_q + 1) * 10 : 0,
.opus = {
.bitrate = r->opus_q,
.mode = r->opus_mode_n,
From d45ded34501feb1ab197760ef5f8488b08116064 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 23 Nov 2024 09:06:35 +0300
Subject: [PATCH 27/59] minor
---
.../com/github/stsaz/phiola/MainActivity.java | 24 +++++++++++--------
android/phiola/src/main/res/menu/list.xml | 9 -------
.../phiola/src/main/res/values/strings.xml | 3 ++-
src/format/detector.h | 11 ++++++++-
src/list/m3u-read.h | 5 ++++
src/list/m3u.c | 1 +
src/net/hls.h | 5 ++++
src/net/http.c | 11 +++++----
8 files changed, 43 insertions(+), 26 deletions(-)
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
index 5499b2f..516f206 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
@@ -216,16 +216,7 @@ private boolean list_menu_click(MenuItem item) {
list_next_add_cur(); break;
case R.id.action_list_sort:
- queue.sort(Phiola.QU_SORT_FILENAME); break;
-
- case R.id.action_list_sort_filesize:
- queue.sort(Phiola.QU_SORT_FILESIZE); break;
-
- case R.id.action_list_sort_filedate:
- queue.sort(Phiola.QU_SORT_FILEDATE); break;
-
- case R.id.action_list_shuffle:
- queue.sort(Phiola.QU_SORT_RANDOM); break;
+ sort_menu_show(); break;
case R.id.action_list_convert:
list_convert(); break;
@@ -239,6 +230,19 @@ private boolean list_menu_click(MenuItem item) {
return true;
}
+ private void sort_menu_show() {
+ PopupMenu m = new PopupMenu(this, b.list);
+ m.setOnMenuItemClickListener((item) -> {
+ queue.sort(item.getItemId());
+ return true;
+ });
+ m.getMenu().add(0, Phiola.QU_SORT_FILENAME, 0, getString(R.string.mlist_sort_filename));
+ m.getMenu().add(0, Phiola.QU_SORT_FILESIZE, 0, getString(R.string.mlist_sort_filesize));
+ m.getMenu().add(0, Phiola.QU_SORT_FILEDATE, 0, getString(R.string.mlist_sort_filedate));
+ m.getMenu().add(0, Phiola.QU_SORT_RANDOM, 0, getString(R.string.mlist_shuffle));
+ m.show();
+ }
+
private static final int
REQUEST_PERM_READ_STORAGE = 1,
REQUEST_PERM_RECORD = 2;
diff --git a/android/phiola/src/main/res/menu/list.xml b/android/phiola/src/main/res/menu/list.xml
index 4a1a5eb..27f4af0 100644
--- a/android/phiola/src/main/res/menu/list.xml
+++ b/android/phiola/src/main/res/menu/list.xml
@@ -32,15 +32,6 @@
-
-
-
-
-
-
diff --git a/android/phiola/src/main/res/values/strings.xml b/android/phiola/src/main/res/values/strings.xml
index 4a2f1db..6b4ea55 100644
--- a/android/phiola/src/main/res/values/strings.xml
+++ b/android/phiola/src/main/res/values/strings.xml
@@ -38,7 +38,8 @@
List: Show Current Track
List: Add Current to Next
Added track to Playlist %d
- List: Sort
+ List: Sort...
+ List: Sort by File Path
List: Sort by File Size
List: Sort by File Date
List: Shuffle
diff --git a/src/format/detector.h b/src/format/detector.h
index f1456ca..67024ee 100644
--- a/src/format/detector.h
+++ b/src/format/detector.h
@@ -14,6 +14,7 @@ enum FILE_FORMAT {
FILE_MP4,
FILE_OGG,
FILE_PLS,
+ FILE_TS,
FILE_WAV,
FILE_WV,
FILE_ID3,
@@ -29,6 +30,7 @@ const char file_ext[][5] = {
"mp4",
"ogg",
"pls",
+ "ts",
"wav",
"wv",
"",
@@ -44,11 +46,18 @@ const char* file_ext_str(uint i)
}
/** Detect file format by first several bytes
-len: >=12
Return enum FILE_FORMAT */
int file_format_detect(const void *data, ffsize len)
{
const ffbyte *d = data;
+
+ if (len >= 189) {
+ // byte sync // 0x47
+ if (d[0] == 0x47
+ && d[188] == 0x47)
+ return FILE_TS;
+ }
+
if (len >= 12) {
// byte id[4]; // "RIFF"
// byte size[4];
diff --git a/src/list/m3u-read.h b/src/list/m3u-read.h
index 14909f0..2e38da1 100644
--- a/src/list/m3u-read.h
+++ b/src/list/m3u-read.h
@@ -131,6 +131,11 @@ static int m3u_process(void *ctx, phi_track *t)
break;
case M3UREAD_URL:
+ if (!ffutf8_valid(val.ptr, val.len)) {
+ warnlog(t, "incorrect UTF-8 data in URL");
+ continue;
+ }
+
ffstr_set2(&m->pls_ent.url, &val);
if (0 != m3u_add(m, t))
return PHI_ERR;
diff --git a/src/list/m3u.c b/src/list/m3u.c
index c4e3f37..53889bf 100644
--- a/src/list/m3u.c
+++ b/src/list/m3u.c
@@ -5,6 +5,7 @@
extern const phi_core *core;
static const phi_queue_if *queue;
static const phi_meta_if *metaif;
+#define errlog(t, ...) phi_errlog(core, NULL, t, __VA_ARGS__)
#define warnlog(t, ...) phi_warnlog(core, NULL, t, __VA_ARGS__)
#include
diff --git a/src/net/hls.h b/src/net/hls.h
index d0688c4..3789fd6 100644
--- a/src/net/hls.h
+++ b/src/net/hls.h
@@ -109,6 +109,11 @@ static int hls_q_update(struct httpcl *h, ffstr d)
continue;
}
+ if (!ffutf8_valid(ln.ptr, ln.len)) {
+ errlog(h->trk, "bad UTF-8 URL");
+ return -1;
+ }
+
if (ffstr_irmatchcz(&ln, ".m3u8")) {
// m3u8 inside m3u8: redirect to the first sublist
diff --git a/src/net/http.c b/src/net/http.c
index 5cedee2..e3286d8 100644
--- a/src/net/http.c
+++ b/src/net/http.c
@@ -137,11 +137,12 @@ int phi_hc_resp(void *ctx, struct phi_http_data *d)
static const struct map_sz24_vptr ct_ext[] = {
{ "application/ogg", "ogg" },
{ "application/x-mpegURL", "m3u" },
- { "audio/aac", "aac" },
- { "audio/aacp", "aac" },
- { "audio/mpeg", "mp3" },
- { "audio/ogg", "ogg" },
- { "video/MP2T", "ts" },
+ { "audio/aac", "aac" },
+ { "audio/aacp", "aac" },
+ { "audio/mpeg", "mp3" },
+ { "audio/ogg", "ogg" },
+ { "audio/x-aac", "aac" },
+ { "video/MP2T", "ts" },
};
h->trk->data_type = map_sz24_vptr_findstr(ct_ext, FF_COUNT(ct_ext), d->ct); // help format.detector in case it didn't detect format
if (!h->trk->data_type
From 18ecfc4658b264e9178da3d36dcb076abc482d0a Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 23 Nov 2024 09:06:35 +0300
Subject: [PATCH 28/59] 2.3-beta1
---
android/phiola/build.gradle | 4 ++--
src/phiola.h | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/android/phiola/build.gradle b/android/phiola/build.gradle
index 507625d..59ff8d9 100644
--- a/android/phiola/build.gradle
+++ b/android/phiola/build.gradle
@@ -8,8 +8,8 @@ android {
applicationId "com.github.stsaz.phiola"
minSdkVersion 16
targetSdk 33
- versionCode 20300
- versionName '2.3-beta0'
+ versionCode 20301
+ versionName '2.3-beta1'
}
buildFeatures {
diff --git a/src/phiola.h b/src/phiola.h
index d7cc824..9cf2535 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -6,7 +6,7 @@
#include
#include
-#define PHI_VERSION 20300
+#define PHI_VERSION 20301
/** Inter-module compatibility version.
It must be updated when incompatible changes are made to this file,
From f25d648a36a75b90cee58a94bd9fdae1e626eace Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 30 Nov 2024 08:57:42 +0300
Subject: [PATCH 29/59] + TUI: Play first/last track by `Home`/`End` keys
+ TUI: Play track at index by pressing numeric keys
---
src/tui/help.txt | 3 +++
src/tui/play.h | 2 +-
src/tui/tui.c | 65 +++++++++++++++++++++++++++++++++++++++++-------
3 files changed, 60 insertions(+), 10 deletions(-)
diff --git a/src/tui/help.txt b/src/tui/help.txt
index 4e4f217..edc34fe 100644
--- a/src/tui/help.txt
+++ b/src/tui/help.txt
@@ -6,6 +6,9 @@ Space Play/Pause
s Stop
n Next track
p Previous track
+Home First track
+End Last track
+[0-9]... Play track at index
Right Seek forward
+Alt medium step
diff --git a/src/tui/play.h b/src/tui/play.h
index 3cd3360..8bf17cf 100644
--- a/src/tui/play.h
+++ b/src/tui/play.h
@@ -325,7 +325,7 @@ static void tuiplay_rm_playnext(tui_track *u)
if (!u->t) return;
tuiplay_rm(u);
- cmd_next();
+ cmd_activate(CMD_NEXT);
}
static const phi_filter phi_tuiplay = {
diff --git a/src/tui/tui.c b/src/tui/tui.c
index 0718ba9..6b99b5e 100644
--- a/src/tui/tui.c
+++ b/src/tui/tui.c
@@ -59,6 +59,9 @@ struct tui_mod {
uint vol;
uint progress_dots;
+ uint activating_track_n;
+ phi_timer activating_track_n_tmr;
+
struct {
const char *progress,
*filename,
@@ -95,6 +98,11 @@ enum CMDS {
CMD_VOLUP,
CMD_VOLDOWN,
CMD_MUTE,
+ CMD_NEXT,
+ CMD_PREV,
+ CMD_FIRST,
+ CMD_LAST,
+ CMD_ACTIVATE_N,
CMD_SHOWTAGS,
@@ -117,7 +125,7 @@ typedef void (*cmdfunc1)(uint cmd);
static void tui_cmd_read(void *param);
static void tui_help(uint cmd);
static void tui_op(uint cmd);
-static void cmd_next();
+static void cmd_activate(uint cmd);
static void tui_print(const void *d, ffsize n)
{
@@ -146,14 +154,31 @@ static void cmd_play()
tuiplay_pause_resume(mod->curtrk);
}
-static void cmd_next()
+static void cmd_activate(uint cmd)
{
- mod->queue->play_next(NULL);
-}
+ uint i = 0;
+ switch (cmd) {
+ case CMD_NEXT:
+ mod->queue->play_next(NULL); return;
-static void cmd_prev()
-{
- mod->queue->play_previous(NULL);
+ case CMD_PREV:
+ mod->queue->play_previous(NULL); return;
+
+ case CMD_FIRST:
+ break;
+
+ case CMD_LAST:
+ i = mod->queue->count(NULL) - 1;
+ break;
+
+ case CMD_ACTIVATE_N:
+ i = mod->activating_track_n - 1;
+ break;
+ }
+
+ void *qe = mod->queue->at(NULL, i);
+ if (qe)
+ mod->queue->play(NULL, qe);
}
static void cmd_random()
@@ -210,8 +235,8 @@ static const struct key hotkeys[] = {
{ 'h', _CMD_F1, tui_help },
{ 'i', CMD_SHOWTAGS | _CMD_CURTRK | _CMD_CORE, tui_op_trk },
{ 'm', CMD_MUTE | _CMD_CURTRK | _CMD_CORE, tuiplay_vol },
- { 'n', _CMD_PLAYBACK | _CMD_F1 | _CMD_CORE, cmd_next },
- { 'p', _CMD_PLAYBACK | _CMD_F1 | _CMD_CORE, cmd_prev },
+ { 'n', CMD_NEXT | _CMD_PLAYBACK | _CMD_F1 | _CMD_CORE, cmd_activate },
+ { 'p', CMD_PREV | _CMD_PLAYBACK | _CMD_F1 | _CMD_CORE, cmd_activate },
{ 'q', CMD_QUIT | _CMD_F1 | _CMD_CORE, tui_op },
{ 'r', _CMD_PLAYBACK | _CMD_F1 | _CMD_CORE, cmd_random },
{ 's', CMD_STOP | _CMD_F1 | _CMD_CORE, tui_op },
@@ -221,6 +246,8 @@ static const struct key hotkeys[] = {
{ FFKEY_DOWN, CMD_VOLDOWN | _CMD_CURTRK | _CMD_CORE, tuiplay_vol },
{ FFKEY_RIGHT, CMD_SEEKRIGHT | _CMD_CURTRK | _CMD_F3 | _CMD_CORE, tuiplay_seek },
{ FFKEY_LEFT, CMD_SEEKLEFT | _CMD_CURTRK | _CMD_F3 | _CMD_CORE, tuiplay_seek },
+ { FFKEY_HOME, CMD_FIRST | _CMD_PLAYBACK | _CMD_F1 | _CMD_CORE, cmd_activate },
+ { FFKEY_END, CMD_LAST | _CMD_PLAYBACK | _CMD_F1 | _CMD_CORE, cmd_activate },
};
static const struct key* key2cmd(int key)
@@ -329,6 +356,22 @@ static void tui_stdin_prepare(void *param)
tui_cmd_read(mod);
}
+static void act_trk_tmr(void *param)
+{
+ cmd_activate(CMD_ACTIVATE_N);
+ mod->activating_track_n = 0;
+}
+
+static int act_trk_key_process(uint k)
+{
+ if (k >= '0' && k <= '9') {
+ mod->activating_track_n = (mod->activating_track_n * 10) + (k - '0');
+ core->timer(0, &mod->activating_track_n_tmr, -800, act_trk_tmr, NULL);
+ return 0;
+ }
+ return -1;
+}
+
static void tui_cmd_read(void *param)
{
ffstd_ev ev = {};
@@ -352,10 +395,13 @@ static void tui_cmd_read(void *param)
const struct key *k = key2cmd(key);
if (k == NULL) {
+ if (!act_trk_key_process(key & ~FFKEY_MODMASK))
+ continue;
dbglog(NULL, "unknown key seq %*xb"
, (ffsize)keydata.len, keydata.ptr);
continue;
}
+
dbglog(NULL, "received command %u", k->cmd & CMD_MASK);
if (k->cmd & _CMD_CORE) {
@@ -419,6 +465,7 @@ static void tui_destroy()
uint attr = FFSTD_LINEINPUT;
ffstd_attr(ffstdin, attr, attr);
+ core->timer(0, &mod->activating_track_n_tmr, 0, NULL, NULL);
ffmem_free(mod);
}
From 1eaacea3ea3a40cfec9508ce0fb3e61adc136151 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 30 Nov 2024 08:57:42 +0300
Subject: [PATCH 30/59] + remote: new commands `seek forward` and `seek back`
---
src/exe/remote.h | 3 +++
src/phiola.h | 1 +
src/remote-ctl.c | 22 ++++++++++++++++++++--
src/tui/tui.c | 10 ++++++++++
4 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/src/exe/remote.h b/src/exe/remote.h
index 3166b2e..d6a4eba 100644
--- a/src/exe/remote.h
+++ b/src/exe/remote.h
@@ -14,6 +14,9 @@ Commands:\n\
`next` Play next track\n\
`previous` Play previous track\n\
`stop` Stop all tracks\n\
+ `seek` PARAM Seek:\n\
+ `forward`\n\
+ `back`\n\
`volume` NUMBER Set playback volume level: 0..100\n\
`quit` Exit\n\
");
diff --git a/src/phiola.h b/src/phiola.h
index 9cf2535..59401eb 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -576,4 +576,5 @@ typedef struct phi_ui_if phi_ui_if;
struct phi_ui_if {
void (*conf)(struct phi_ui_conf *c);
void (*log)(void *udata, ffstr s);
+ void (*seek)(uint val, uint flags);
};
diff --git a/src/remote-ctl.c b/src/remote-ctl.c
index be37a4d..1cee698 100644
--- a/src/remote-ctl.c
+++ b/src/remote-ctl.c
@@ -13,6 +13,7 @@
struct remote_ctl {
const phi_core* core;
const phi_queue_if* queue;
+ const phi_ui_if* ui;
phi_kevent server_kev;
fffd server_pipe;
@@ -62,12 +63,28 @@ static int cmd_quit() {
return 0;
}
+static int cmd_seek(void *o, ffstr s) {
+ uint f;
+ if (ffstr_eqz(&s, "forward"))
+ f = 0;
+ else if (ffstr_eqz(&s, "back"))
+ f = 1;
+ else
+ return 0;
+
+ if (!g->ui)
+ g->ui = g->core->mod("tui.if");
+ g->ui->seek(0, f);
+ return 0;
+}
+
static int cmd_volume(void *o, uint64 n) {
struct phi_ui_conf uc = {
.volume_percent = n,
};
- const phi_ui_if *uif = g->core->mod("tui.if");
- uif->conf(&uc);
+ if (!g->ui)
+ g->ui = g->core->mod("tui.if");
+ g->ui->conf(&uc);
return 0;
}
@@ -77,6 +94,7 @@ static const struct ffarg args[] = {
{ "play", '1', cmd_play },
{ "previous", '1', cmd_previous },
{ "quit", '1', cmd_quit },
+ { "seek", 'S', cmd_seek },
{ "start", 'S', cmd_start },
{ "stop", '1', cmd_stop },
{ "volume", 'u', cmd_volume },
diff --git a/src/tui/tui.c b/src/tui/tui.c
index 6b99b5e..664a598 100644
--- a/src/tui/tui.c
+++ b/src/tui/tui.c
@@ -478,8 +478,18 @@ static void tui_conf(struct phi_ui_conf *c)
}
}
+static void tui_play_seek(uint val, uint flags)
+{
+ if (!mod->curtrk) return;
+
+ uint cmd = CMD_SEEKRIGHT;
+ if (flags & 1) cmd = CMD_SEEKLEFT;
+ tuiplay_seek(mod->curtrk, cmd, (void*)0);
+}
+
static struct phi_ui_if phi_tui_if = {
.conf = tui_conf,
+ .seek = tui_play_seek,
};
static const void* tui_iface(const char *name)
From 75a0f557ba8992e1e948f495bcf3ee02da57aea6 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 30 Nov 2024 08:57:42 +0300
Subject: [PATCH 31/59] * support redirection between plain HTTP and HTTPS
---
src/net/http-bridge.h | 3 +++
src/net/http-filters.c | 9 +++++++++
src/net/http.c | 34 ++++++++++++++++++++++++++++++++++
3 files changed, 46 insertions(+)
diff --git a/src/net/http-bridge.h b/src/net/http-bridge.h
index 11a874e..09498ed 100644
--- a/src/net/http-bridge.h
+++ b/src/net/http-bridge.h
@@ -11,3 +11,6 @@ struct phi_http_data {
extern int phi_hc_resp(void *ctx, struct phi_http_data *d);
extern int phi_hc_data(void *ctx, ffstr data, uint flags);
+
+/** Get necessary info for handling HTTP<->HTTPS redirection */
+extern const char* http_e_redirect(nml_http_client *c);
diff --git a/src/net/http-filters.c b/src/net/http-filters.c
index fb2c905..3d01428 100644
--- a/src/net/http-filters.c
+++ b/src/net/http-filters.c
@@ -45,6 +45,15 @@ static const nml_http_cl_component nml_http_cl_phi_bridge = {
"phiola-output"
};
+const char* http_e_redirect(nml_http_client *c)
+{
+ if (c->error == NML_HC_E_REDIRECT) {
+ c->error = 0;
+ return c->redirect_location;
+ }
+ return NULL;
+}
+
const nml_http_cl_component *hc_chain[] = {
&nml_http_cl_resolve,
diff --git a/src/net/http.c b/src/net/http.c
index e3286d8..8c679eb 100644
--- a/src/net/http.c
+++ b/src/net/http.c
@@ -35,6 +35,7 @@ struct httpcl {
uint cl_state;
uint done :1;
uint icy :1;
+ uint n_redirect;
struct hlsread *hls;
};
@@ -231,10 +232,43 @@ int phi_hc_data(void *ctx, ffstr data, uint flags)
return NMLR_ERR;
}
+static void http_request(struct httpcl *h, ffstr url)
+{
+ nml_http_client_free(h->cl);
+ ffstr_free(&h->conf.headers);
+ ffmem_zero_obj(&h->conf);
+
+ dbglog(h->trk, "requesting %S", &url);
+ h->cl = nml_http_client_create();
+ conf_prepare(h, &h->conf, h->trk, url);
+ nml_http_client_conf(h->cl, &h->conf);
+ nml_http_client_run(h->cl);
+}
+
+static int http_redirect(struct httpcl *h, const char *location)
+{
+ if (h->n_redirect++ == 10) {
+ errlog(h->trk, "reached max. number of full redirects");
+ return -1;
+ }
+
+ ffmem_free(h->trk->conf.ifile.name);
+ h->trk->conf.ifile.name = ffsz_dup(location);
+
+ http_request(h, FFSTR_Z(h->trk->conf.ifile.name));
+ return 0;
+}
+
static void on_complete(void *param)
{
struct httpcl *h = param;
+ const char *redirect_location;
+ if (!h->hls && (redirect_location = http_e_redirect(h->cl))) {
+ if (!http_redirect(h, redirect_location))
+ return;
+ }
+
if (h->hls
&& !hls_f_complete(h))
return;
From 8ab4f9d35fa0b31ddff84e1d1a67f9fb7a0a9383 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 30 Nov 2024 08:57:42 +0300
Subject: [PATCH 32/59] minor
---
.../github/stsaz/phiola/AddURLActivity.java | 2 +-
.../java/com/github/stsaz/phiola/GUI.java | 44 +++++++-----
.../github/stsaz/phiola/ListSaveActivity.java | 2 +-
.../com/github/stsaz/phiola/MainActivity.java | 68 +++++++++---------
.../github/stsaz/phiola/PlaylistAdapter.java | 12 ++--
.../java/com/github/stsaz/phiola/Queue.java | 72 ++++++++++---------
.../java/com/github/stsaz/phiola/Svc.java | 4 +-
.../com/github/stsaz/phiola/TagsActivity.java | 2 +-
.../com/github/stsaz/phiola/UtilNative.java | 6 +-
src/format/ts-read.c | 8 +--
src/jni/android-utils.h | 4 +-
src/jni/phiola-jni.c | 3 +
src/jni/queue.h | 34 ++++++++-
src/net/hls.h | 12 +---
src/net/http.c | 1 +
src/tui/help.txt | 3 +
src/tui/tui.c | 2 +
17 files changed, 155 insertions(+), 124 deletions(-)
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/AddURLActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/AddURLActivity.java
index 1dd1eab..b24285f 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/AddURLActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/AddURLActivity.java
@@ -38,7 +38,7 @@ private void add() {
core.gui().msg_show(AddURLActivity.this, "Unsupported URL");
return;
}
- core.queue().add(fn);
+ core.queue().current_add(fn, 0);
finish();
}
}
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/GUI.java b/android/phiola/src/main/java/com/github/stsaz/phiola/GUI.java
index e43ed5f..304b6dd 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/GUI.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/GUI.java
@@ -8,6 +8,8 @@
import android.content.DialogInterface;
import android.widget.Toast;
+import java.util.ArrayList;
+
class GUI {
private static final String TAG = "phiola.GUI";
private Core core;
@@ -17,10 +19,11 @@ class GUI {
boolean record_hide;
boolean ainfo_in_title;
String cur_path = ""; // current explorer path
- private int[] list_pos; // list scroll position
+ private ArrayList list_pos; // list scroll position
- static final int THM_DEF = 0;
- static final int THM_DARK = 1;
+ static final int
+ THM_DEF = 0,
+ THM_DARK = 1;
int theme;
static final int
@@ -36,28 +39,29 @@ class GUI {
GUI(Core core) {
this.core = core;
- list_pos = new int[3];
+ list_pos = new ArrayList<>();
}
String conf_write() {
+ String list_pos_str = "";
+ for (Integer it : list_pos) {
+ list_pos_str += String.format("%d ", it);
+ }
+
return String.format(
"ui_curpath %s\n"
+ "ui_state_hide %d\n"
+ "ui_filter_hide %d\n"
+ "ui_record_hide %d\n"
+ "ui_info_in_title %d\n"
- + "ui_list_scroll_pos0 %d\n"
- + "ui_list_scroll_pos1 %d\n"
- + "ui_list_scroll_pos2 %d\n"
+ + "ui_list_scroll_pos %s\n"
+ "ui_theme %d\n"
, cur_path
, core.bool_to_int(state_hide)
, core.bool_to_int(filter_hide)
, core.bool_to_int(record_hide)
, core.bool_to_int(ainfo_in_title)
- , list_pos[0]
- , list_pos[1]
- , list_pos[2]
+ , list_pos_str
, theme
);
}
@@ -68,10 +72,14 @@ void conf_load(Conf.Entry[] kv) {
filter_hide = kv[Conf.UI_FILTER_HIDE].enabled;
record_hide = kv[Conf.UI_RECORD_HIDE].enabled;
ainfo_in_title = kv[Conf.UI_INFO_IN_TITLE].enabled;
- list_pos[0] = kv[Conf.UI_LIST_SCROLL_POS0].number;
- list_pos[1] = kv[Conf.UI_LIST_SCROLL_POS1].number;
- list_pos[2] = kv[Conf.UI_LIST_SCROLL_POS2].number;
+ String list_pos_str = kv[Conf.UI_LIST_SCROLL_POS].value;
theme = kv[Conf.UI_THEME].number;
+
+ String[] v = list_pos_str.split(" ");
+ for (String s : v) {
+ if (!s.isEmpty())
+ list_pos.add(Util.str_to_uint(s, 0));
+ }
}
boolean state_test(int mask) { return (state & mask) != 0; }
@@ -86,14 +94,16 @@ int state_update(int mask, int val) {
}
int list_scroll_pos(int i) {
- if (i >= list_pos.length)
+ if (i >= list_pos.size())
return 0;
- return list_pos[i];
+ return list_pos.get(i);
}
void list_scroll_pos_set(int i, int n) {
- if (i < list_pos.length)
- list_pos[i] = n;
+ for (int j = list_pos.size(); j <= i; j++) {
+ list_pos.add(0);
+ }
+ list_pos.set(i, n);
}
void on_error(String fmt, Object... args) {
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/ListSaveActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/ListSaveActivity.java
index aac2ca0..50e7de5 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/ListSaveActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/ListSaveActivity.java
@@ -49,7 +49,7 @@ private void save() {
core.errlog(TAG, "File already exists. Please specify a different name.");
return;
}
- if (!core.queue().save(fn)) {
+ if (!core.queue().current_save(fn)) {
core.errlog(TAG, "Error saving playlist file");
return;
}
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
index 516f206..9721bd8 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
@@ -195,7 +195,7 @@ private boolean list_menu_click(MenuItem item) {
list_rm(); break;
case R.id.action_list_rm_non_existing:
- queue.rm_non_existing(); break;
+ queue.current_remove_non_existing(); break;
case R.id.action_list_clear:
list_clear(); break;
@@ -206,7 +206,7 @@ private boolean list_menu_click(MenuItem item) {
case R.id.action_list_showcur: {
if (view_explorer)
plist_click();
- int pos = queue.active_track_pos();
+ int pos = queue.active_pos();
if (pos >= 0)
b.list.scrollToPosition(pos);
break;
@@ -216,7 +216,7 @@ private boolean list_menu_click(MenuItem item) {
list_next_add_cur(); break;
case R.id.action_list_sort:
- sort_menu_show(); break;
+ list_sort_menu_show(); break;
case R.id.action_list_convert:
list_convert(); break;
@@ -230,10 +230,10 @@ private boolean list_menu_click(MenuItem item) {
return true;
}
- private void sort_menu_show() {
+ private void list_sort_menu_show() {
PopupMenu m = new PopupMenu(this, b.list);
m.setOnMenuItemClickListener((item) -> {
- queue.sort(item.getItemId());
+ queue.current_sort(item.getItemId());
return true;
});
m.getMenu().add(0, Phiola.QU_SORT_FILENAME, 0, getString(R.string.mlist_sort_filename));
@@ -364,7 +364,7 @@ private void init_ui() {
return true;
});
b.bplaylist.setChecked(true);
- bplaylist_text(queue.current_list_index());
+ bplaylist_text(queue.current_index());
b.seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
int val; // last value
@@ -442,7 +442,7 @@ private void rec_fin(int code, String filename) {
stopService(new Intent(this, RecSvc.class));
if (code == 0) {
if (core.setts.rec_list_add)
- queue.add(filename);
+ queue.current_add(filename, 0);
gui.msg_show(this, getString(R.string.main_rec_fin));
}
@@ -550,15 +550,15 @@ private void file_del(int pos, String fn) {
return;
gui.msg_show(this, "Deleted file");
}
- queue.remove(pos);
+ queue.active_remove(pos);
}
/** Ask confirmation before deleting the currently playing file from storage */
private void file_del_cur() {
- int pos = queue.active_track_pos();
- if (pos < 0)
+ int pos = queue.active_pos();
+ String fn = queue.active_track_url();
+ if (fn == null)
return;
- String fn = queue.get(pos);
AlertDialog.Builder b = new AlertDialog.Builder(this);
b.setIcon(android.R.drawable.ic_dialog_alert);
@@ -591,10 +591,9 @@ private void file_move_cur() {
}
private void file_move_cur_confirmed() {
- int pos = queue.active_track_pos();
- if (pos < 0)
+ String fn = queue.active_track_url();
+ if (fn == null)
return;
- String fn = queue.get(pos);
String e = core.util.fileMove(fn, core.setts.quick_move_dir);
if (!e.isEmpty()) {
@@ -611,14 +610,11 @@ void explorer_event(String fn, int flags) {
return;
}
- int n = queue.count();
- String[] ents = new String[1];
- ents[0] = fn;
- queue.addmany(ents, flags);
- core.dbglog(TAG, "added %d items", ents.length);
- gui.msg_show(this, "Added %d items to playlist", ents.length);
+ int n = queue.current_items();
+ queue.current_add(fn, flags);
+ gui.msg_show(this, "Added %d items to playlist", 1);
if (flags == Queue.ADD)
- queue.play(n);
+ queue.current_play(n);
}
private void explorer_file_current_show() {
@@ -653,21 +649,21 @@ private void list_update() {
}
private void plist_show() {
- int n = gui.list_scroll_pos(queue.current_list_index());
+ int n = gui.list_scroll_pos(queue.current_index());
if (n != 0)
b.list.scrollToPosition(n);
}
/** Called when we're leaving the playlist tab */
void list_leave() {
- queue.filter("");
+ queue.current_filter("");
LinearLayoutManager llm = (LinearLayoutManager)b.list.getLayoutManager();
- gui.list_scroll_pos_set(queue.current_list_index(), llm.findLastCompletelyVisibleItemPosition());
+ gui.list_scroll_pos_set(queue.current_index(), llm.findLastCompletelyVisibleItemPosition());
}
private void plist_filter(String filter) {
core.dbglog(TAG, "list_filter: %s", filter);
- queue.filter(filter);
+ queue.current_filter(filter);
list_update();
}
@@ -713,7 +709,7 @@ private void list_new() {
private void list_close() {
if (view_explorer) return;
- if (0 == queue.count()) {
+ if (0 == queue.current_items()) {
list_close_confirmed();
return;
}
@@ -726,34 +722,34 @@ private void list_close() {
}
private void list_close_confirmed() {
- if (queue.close_current_list() != 0) {
+ if (queue.current_close() != 0) {
core.errlog(TAG, "Please wait until the conversion is complete");
return;
}
gui.msg_show(this, getString(R.string.mlist_closed));
list_update();
- bplaylist_text(queue.current_list_index());
+ bplaylist_text(queue.current_index());
}
private void list_clear() {
- if (0 == queue.count())
+ if (0 == queue.current_items())
return;
gui.dlg_question(this, "Clear playlist"
, "Remove all items from the current playlist?"
, "Clear", "Do nothing"
- , (dialog, which) -> { queue.clear(); }
+ , (dialog, which) -> { queue.current_clear(); }
);
}
/** Remove currently playing track from playlist */
private void list_rm() {
- int pos = queue.active_track_pos();
+ int pos = queue.active_pos();
if (pos < 0)
return;
- queue.remove(pos);
+ queue.active_remove(pos);
gui.msg_show(this, getString(R.string.mlist_trk_rm));
}
@@ -821,8 +817,8 @@ static String q_error(int e) {
}
private void list_convert() {
- long qi_old = queue.current_list_id();
- int trk_pos = queue.active_track_pos();
+ long qi_old = queue.current_id();
+ int trk_pos = queue.active_pos();
int qi = queue.convert_add(Queue.CONV_CUR_LIST);
if (qi < 0) {
@@ -848,8 +844,8 @@ private void list_convert() {
}
private void file_convert() {
- long qi_old = queue.current_list_id();
- int trk_pos = queue.active_track_pos();
+ long qi_old = queue.current_id();
+ int trk_pos = queue.active_pos();
int qi = queue.convert_add(Queue.CONV_CUR_FILE);
if (qi < 0) {
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/PlaylistAdapter.java b/android/phiola/src/main/java/com/github/stsaz/phiola/PlaylistAdapter.java
index 3c2970d..0d51520 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/PlaylistAdapter.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/PlaylistAdapter.java
@@ -1,7 +1,5 @@
-/**
- * phiola/Android
- * 2023, Simon Zolin
- */
+/** phiola/Android
+2023, Simon Zolin */
package com.github.stsaz.phiola;
@@ -41,6 +39,7 @@ public boolean onLongClick(View v) {
class PlaylistAdapter extends RecyclerView.Adapter {
+ private static final String TAG = "phiola.PlaylistAdapter";
private final Core core;
private final Queue queue;
private final LayoutInflater inflater;
@@ -73,7 +72,7 @@ public int getItemCount() {
if (view_explorer)
return explorer.count();
- return queue.visiblelist_itemcount();
+ return queue.visible_items();
}
static final int EV_LONGCLICK = 1;
@@ -87,10 +86,11 @@ void on_event(int ev, int i) {
if (ev == EV_LONGCLICK)
return;
- queue.visiblelist_play(i);
+ queue.visible_play(i);
}
void on_change(int how, int pos) {
+ core.dbglog(TAG, "on_change: %d %d", how, pos);
if (pos < 0)
notifyDataSetChanged();
else if (how == QueueNotify.UPDATE)
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
index 91ebb2c..63d5d68 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
@@ -28,7 +28,7 @@ class PhiolaQueue {
void destroy() { phi.quDestroy(q); }
- void dup(long q_src, int pos) {
+ void copy_entries(long q_src, int pos) {
phi.quDup(q, q_src, pos);
modified = true;
}
@@ -228,7 +228,7 @@ int new_list() {
return queues.size() - 1;
}
- int close_current_list() {
+ int current_close() {
if (i_selected == i_conversion && converting)
return E_BUSY; // can't close the conversion list while the conversion is in progress
@@ -236,7 +236,7 @@ int close_current_list() {
if (i_conversion >= 0)
n_playlists--;
if (n_playlists == 1 && i_selected != i_conversion) {
- clear();
+ current_clear();
return 0;
}
@@ -245,6 +245,8 @@ int close_current_list() {
queues.remove(i_selected);
if (i_active == i_selected)
i_active = 0;
+ else if (i_active > i_selected)
+ i_active--;
if (i_selected == i_conversion)
i_conversion = -1;
@@ -327,7 +329,7 @@ void saveconf() {
}
/** Save playlist to a file */
- boolean save(String fn) {
+ boolean current_save(String fn) {
if (!q_selected().save(fn))
return false;
core.dbglog(TAG, "saved %s", fn);
@@ -344,7 +346,7 @@ private int index_next(int qi) {
/** Change to next playlist */
int next_list_select() {
- filter("");
+ current_filter("");
i_selected = index_next(i_selected);
queues.get(i_selected).load_once();
return i_selected;
@@ -357,8 +359,8 @@ void switch_list(int i) {
queues.get(i_selected).load_once();
}
- int current_list_index() { return i_selected; }
- long current_list_id() { return queues.get(i_selected).q; }
+ int current_index() { return i_selected; }
+ long current_id() { return queues.get(i_selected).q; }
/** Add currently playing track to next list.
Return the modified list index. */
@@ -390,7 +392,7 @@ private void nfy_all(int how, int first_pos) {
}
/** Play track at cursor */
- void playcur() {
+ void active_play() {
int pos = curpos;
if (pos < 0)
pos = 0;
@@ -410,11 +412,11 @@ private void _play(int iq, int it) {
}
/** Play track at the specified position */
- void play(int index) {
+ void current_play(int index) {
_play(i_selected, index);
}
- void visiblelist_play(int i) {
+ void visible_play(int i) {
if (i_selected == i_conversion)
return; // ignore click on entry in Conversion list
@@ -471,7 +473,12 @@ private void play_on_close(TrackHandle t) {
auto_stop_active = false;
}
- String get(int i) { return phi.quEntry(queues.get(i_selected).q, i); }
+ String active_track_url() {
+ if (i_selected != i_active
+ || trk_idx < 0)
+ return null;
+ return phi.quEntry(queues.get(i_selected).q, trk_idx);
+ }
String display_line(int i) {
return phi.quDisplayLine(q_visible().q, i);
@@ -481,14 +488,13 @@ int move_all(String dst_dir) {
return phi.quMoveAll(q_visible().q, dst_dir);
}
- void remove(int pos) {
+ void active_remove(int pos) {
core.dbglog(TAG, "remove: %d:%d", i_active, pos);
filter_close();
queues.get(i_active).remove(pos);
}
- /** Clear playlist */
- void clear() {
+ void current_clear() {
core.dbglog(TAG, "clear");
filter_close();
queues.get(i_selected).clear();
@@ -496,22 +502,24 @@ void clear() {
trk_idx = -1;
}
- void rm_non_existing() {
+ void current_remove_non_existing() {
queues.get(i_selected).remove_non_existing();
}
int number() { return queues.size(); }
/** Get tracks number in the currently selected (not filtered) list */
- int count() { return queues.get(i_selected).count(); }
+ int current_items() { return queues.get(i_selected).count(); }
- int visiblelist_itemcount() { return q_visible().count(); }
+ int visible_items() { return q_visible().count(); }
- static final int ADD_RECURSE = 1;
- static final int ADD = 2;
+ static final int
+ ADD_RECURSE = 1,
+ ADD = 2;
- void addmany(String[] urls, int flags) {
+ void current_addmany(String[] urls, int flags) {
filter_close();
+ queues.get(i_selected).load_once();
int f = 0;
if (flags == ADD_RECURSE)
f = Phiola.QUADD_RECURSE;
@@ -519,10 +527,10 @@ void addmany(String[] urls, int flags) {
}
/** Add an entry */
- void add(String url) {
- core.dbglog(TAG, "add: %s", url);
- filter_close();
- queues.get(i_selected).add(url, 0);
+ void current_add(String url, int flags) {
+ String[] urls = new String[1];
+ urls[0] = url;
+ current_addmany(urls, flags);
}
String conf_write() {
@@ -568,29 +576,25 @@ void conf_normalize() {
}
/** Get currently playing track index */
- int active_track_pos() {
+ int active_pos() {
if (i_selected != i_active)
return -1;
return trk_idx;
}
- String[] meta(int i) {
- if (i == -1)
- i = trk_idx;
- Phiola.Meta m = phi.quMeta(queues.get(i_active).q, i);
+ String[] active_meta() {
+ Phiola.Meta m = phi.quMeta(queues.get(i_active).q, trk_idx);
if (m == null)
return null;
return m.meta;
}
- void sort(int flags) {
+ void current_sort(int flags) {
if (q_filtered != null) return;
q_selected().sort(flags);
}
- long q_active_id() { return queues.get(i_active).q; }
-
/** Currently selected (not filtered) list */
private PhiolaQueue q_selected() { return queues.get(i_selected); }
@@ -609,7 +613,7 @@ private void filter_close() {
}
/** Create filtered list */
- void filter(String filter) {
+ void current_filter(String filter) {
PhiolaQueue newqf = null;
if (!filter.isEmpty()) {
PhiolaQueue qcur = queues.get(i_selected);
@@ -658,7 +662,7 @@ int convert_add(int flags) {
queues.add(new PhiolaQueue(core.phiola, Phiola.QUNF_CONVERSION, 0));
i_conversion = queues.size() - 1;
- queues.get(i_conversion).dup(q_src, i);
+ queues.get(i_conversion).copy_entries(q_src, i);
i_selected = i_conversion;
return i_conversion;
}
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Svc.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Svc.java
index 0cae557..7ce6ca5 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Svc.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Svc.java
@@ -126,7 +126,7 @@ void sess_init() {
public void onPlay() {
core.dbglog(TAG, "MediaSessionCompat.onPlay");
if (track.state() == Track.STATE_NONE) {
- queue.playcur();
+ queue.active_play();
return;
}
track.unpause();
@@ -280,7 +280,7 @@ int new_track(TrackHandle t) {
startService(new Intent(this, Svc.class));
}
- pstate.setActiveQueueItemId(queue.active_track_pos());
+ pstate.setActiveQueueItemId(queue.active_pos());
String title = t.pmeta.title;
if (t.pmeta.title.isEmpty())
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/TagsActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/TagsActivity.java
index 9bf25d2..e355bef 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/TagsActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/TagsActivity.java
@@ -39,7 +39,7 @@ protected void onDestroy() {
}
private void show() {
- meta = core.queue().meta(-1);
+ meta = core.queue().active_meta();
if (meta == null)
meta = new String[0];
ArrayList tags = new ArrayList<>();
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java b/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java
index f4f69ca..1a44f7e 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java
@@ -53,10 +53,8 @@ static class Entry {
UI_CURPATH = REC_UNTIL + 1,
UI_FILTER_HIDE = UI_CURPATH + 1,
UI_INFO_IN_TITLE = UI_FILTER_HIDE + 1,
- UI_LIST_SCROLL_POS0 = UI_INFO_IN_TITLE + 1,
- UI_LIST_SCROLL_POS1 = UI_LIST_SCROLL_POS0 + 1,
- UI_LIST_SCROLL_POS2 = UI_LIST_SCROLL_POS1 + 1,
- UI_RECORD_HIDE = UI_LIST_SCROLL_POS2 + 1,
+ UI_LIST_SCROLL_POS = UI_INFO_IN_TITLE + 1,
+ UI_RECORD_HIDE = UI_LIST_SCROLL_POS + 1,
UI_STATE_HIDE = UI_RECORD_HIDE + 1,
UI_SVC_NOTFN_DISABLE = UI_STATE_HIDE + 1,
UI_THEME = UI_SVC_NOTFN_DISABLE+1
diff --git a/src/format/ts-read.c b/src/format/ts-read.c
index 56c609f..794e905 100644
--- a/src/format/ts-read.c
+++ b/src/format/ts-read.c
@@ -55,7 +55,7 @@ static int ts_info(struct ts_r *c, phi_track *t, const struct _tsr_pm *info)
case TS_STREAM_AUDIO_AAC:
mod = "format.aac"; break;
default:
- dbglog(t, "codec not supported: %u", info->stream_type);
+ errlog(t, "codec not supported: %u", info->stream_type);
return 1;
}
if (!core->track->filter(t, core->mod(mod), 0))
@@ -87,12 +87,8 @@ static int ts_process(void *ctx, phi_track *t)
case TSREAD_DATA:
if (c->n_pkt == 0) {
- // select the first audio track with a recognized codec
- r = ts_info(c, t, tsread_info(&c->ts));
- if (r < 0)
+ if (ts_info(c, t, tsread_info(&c->ts)))
return PHI_ERR;
- else if (r > 0)
- continue;
}
if (c->track_id != tsread_info(&c->ts)->pid)
diff --git a/src/jni/android-utils.h b/src/jni/android-utils.h
index b106394..89fb118 100644
--- a/src/jni/android-utils.h
+++ b/src/jni/android-utils.h
@@ -49,9 +49,7 @@ static const char setting_names[][20] = {
"ui_curpath",
"ui_filter_hide",
"ui_info_in_title",
- "ui_list_scroll_pos0",
- "ui_list_scroll_pos1",
- "ui_list_scroll_pos2",
+ "ui_list_scroll_pos",
"ui_record_hide",
"ui_state_hide",
"ui_svc_notfn_disable",
diff --git a/src/jni/phiola-jni.c b/src/jni/phiola-jni.c
index 3ed6445..90a1807 100644
--- a/src/jni/phiola-jni.c
+++ b/src/jni/phiola-jni.c
@@ -52,6 +52,9 @@ struct phiola_jni {
uint n_tracks_updated;
} convert;
+ phi_timer tmr_q_draw;
+ phi_queue_id q_adding;
+
jclass Phiola_class;
jmethodID Phiola_lib_load;
diff --git a/src/jni/queue.h b/src/jni/queue.h
index ae9b619..1a2bdd4 100644
--- a/src/jni/queue.h
+++ b/src/jni/queue.h
@@ -3,12 +3,42 @@
static void qu_conv_update(phi_queue_id q);
+static void list_redraw_delayed(void *param)
+{
+ JNIEnv *env;
+ int r = jni_vm_attach(jvm, &env);
+ if (r) {
+ errlog("jni_vm_attach: %d", r);
+ goto end;
+ }
+
+ jni_call_void(x->obj_QueueCallback, x->Phiola_QueueCallback_on_change, (jlong)x->q_adding, 'u', 0);
+
+end:
+ jni_vm_detach(jvm);
+ x->q_adding = 0;
+}
+
static void qu_on_change(phi_queue_id q, uint flags, uint pos)
{
- dbglog("%s: '%c' q:%p", __func__, flags, (size_t)q);
+ dbglog("%s: '%c' q:%p pos:%u", __func__, flags, (size_t)q, pos);
switch (flags) {
case 'a':
+ if (q == x->q_adding)
+ return; // redraw timer is already set
+ if (!x->q_adding) {
+ x->q_adding = q;
+ x->core->timer(0, &x->tmr_q_draw, -50, list_redraw_delayed, NULL);
+ return; // delay redrawing this list
+ }
+
+ // redraw the previously modified list
+ q = FF_SWAP(&x->q_adding, q);
+ flags = 'u';
+ pos = 0;
+ break;
+
case 'r':
case 'c':
case 'u':
@@ -409,7 +439,7 @@ static void display_name_prepare(ffstr *val, ffsize cap, struct phi_queue_entry
JNIEXPORT jstring JNICALL
Java_com_github_stsaz_phiola_Phiola_quDisplayLine(JNIEnv *env, jobject thiz, jlong jq, jint i)
{
- dbglog("%s: enter", __func__);
+ dbglog("%s: enter %p %d", __func__, jq, i);
phi_queue_id q = (phi_queue_id)jq;
char buf[256];
ffstr val = {};
diff --git a/src/net/hls.h b/src/net/hls.h
index 3789fd6..0ffd480 100644
--- a/src/net/hls.h
+++ b/src/net/hls.h
@@ -164,13 +164,6 @@ static int hls_q_get(struct httpcl *h, ffstr *name)
static void hls_f_request(struct httpcl *h, ffstr name)
{
struct hlsread *l = h->hls;
- nml_http_client_free(h->cl);
- ffstr_free(&h->conf.headers);
- ffmem_zero_obj(&h->conf);
-
- h->cl = nml_http_client_create();
-
- struct nml_http_client_conf *c = &h->conf;
ffstr url = FFSTR_Z(h->trk->conf.ifile.name);
l->data_file = 0;
if (name.len) {
@@ -189,10 +182,7 @@ static void hls_f_request(struct httpcl *h, ffstr name)
}
}
- conf_prepare(h, c, h->trk, url);
- dbglog(h->trk, "requesting %S", &url);
- nml_http_client_conf(h->cl, c);
- nml_http_client_run(h->cl);
+ http_request(h, url);
}
static void hls_f_next(void *param)
diff --git a/src/net/http.c b/src/net/http.c
index 8c679eb..f90684d 100644
--- a/src/net/http.c
+++ b/src/net/http.c
@@ -50,6 +50,7 @@ enum ST {
};
static int conf_prepare(struct httpcl *h, struct nml_http_client_conf *c, phi_track *t, ffstr url);
+static void http_request(struct httpcl *h, ffstr url);
#include
static void nml_log(void *log_obj, uint level, const char *ctx, const char *id, const char *format, ...)
diff --git a/src/tui/help.txt b/src/tui/help.txt
index edc34fe..89e7d97 100644
--- a/src/tui/help.txt
+++ b/src/tui/help.txt
@@ -20,6 +20,9 @@ Down Volume down
m Mute
i Show tags
+d Remove current track from playlist
+x Remove current track from playlist and play next track
r "Choose random track" setting: on/off
+Shift+l Save playlist to disk
h Show help
q Quit
diff --git a/src/tui/tui.c b/src/tui/tui.c
index 664a598..f32ea38 100644
--- a/src/tui/tui.c
+++ b/src/tui/tui.c
@@ -408,6 +408,8 @@ static void tui_cmd_read(void *param)
void *udata = NULL;
if (k->cmd & _CMD_F3)
udata = (void*)(ffsize)key;
+ else if (key & FFKEY_MODMASK)
+ continue;
tui_corecmd_add(k, udata);
} else if (k->cmd & _CMD_F1) {
From 7b4903a6c7a123442d8127f788eb078fa759b72f Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 30 Nov 2024 08:57:42 +0300
Subject: [PATCH 33/59] v2.3-beta2
---
android/phiola/build.gradle | 4 ++--
src/phiola.h | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/android/phiola/build.gradle b/android/phiola/build.gradle
index 59ff8d9..b125556 100644
--- a/android/phiola/build.gradle
+++ b/android/phiola/build.gradle
@@ -8,8 +8,8 @@ android {
applicationId "com.github.stsaz.phiola"
minSdkVersion 16
targetSdk 33
- versionCode 20301
- versionName '2.3-beta1'
+ versionCode 20302
+ versionName '2.3-beta2'
}
buildFeatures {
diff --git a/src/phiola.h b/src/phiola.h
index 59401eb..da593a0 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -6,7 +6,7 @@
#include
#include
-#define PHI_VERSION 20301
+#define PHI_VERSION 20302
/** Inter-module compatibility version.
It must be updated when incompatible changes are made to this file,
From f9cc5ba36fa014bc5e3fbbc3b6b855caffb7ecc5 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 21 Dec 2024 10:35:02 +0300
Subject: [PATCH 34/59] + Android: new command `List: Move Left`
---
.../java/com/github/stsaz/phiola/GUI.java | 7 +++++++
.../com/github/stsaz/phiola/MainActivity.java | 9 +++++++++
.../java/com/github/stsaz/phiola/Phiola.java | 6 ++++++
.../java/com/github/stsaz/phiola/Queue.java | 20 +++++++++++++++++++
android/phiola/src/main/res/menu/list.xml | 3 +++
.../phiola/src/main/res/values/strings.xml | 1 +
src/core/queue.c | 14 +++++++++++++
src/jni/queue.h | 17 ++++++++++++++++
src/phiola.h | 5 ++++-
9 files changed, 81 insertions(+), 1 deletion(-)
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/GUI.java b/android/phiola/src/main/java/com/github/stsaz/phiola/GUI.java
index 304b6dd..546ff26 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/GUI.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/GUI.java
@@ -106,6 +106,13 @@ void list_scroll_pos_set(int i, int n) {
list_pos.set(i, n);
}
+ void list_scroll_pos_swap(int a, int b) {
+ int aa = list_scroll_pos(a);
+ int bb = list_scroll_pos(b);
+ list_scroll_pos_set(a, bb);
+ list_scroll_pos_set(b, aa);
+ }
+
void on_error(String fmt, Object... args) {
if (cur_activity == null)
return;
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
index 9721bd8..ed35995 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
@@ -218,6 +218,15 @@ private boolean list_menu_click(MenuItem item) {
case R.id.action_list_sort:
list_sort_menu_show(); break;
+ case R.id.action_move_left: {
+ int i = queue.current_move_left();
+ if (i >= 0) {
+ gui.list_scroll_pos_swap(i, i + 1);
+ bplaylist_text(i);
+ }
+ break;
+ }
+
case R.id.action_list_convert:
list_convert(); break;
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
index 5727b7b..a3e23de 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
@@ -122,6 +122,12 @@ interface QueueCallback {
native long quNew(int flags);
native void quDestroy(long q);
+ /** Move the list to a new position. */
+ native void quMove(int from, int to);
+
+ /** Copy entries from another queue (asynchronous).
+ pos: Source entry index
+ -1: Copy all entries */
native void quDup(long q, long q_src, int pos);
static final int QUADD_RECURSE = 1;
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
index 63d5d68..c0b8da2 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
@@ -5,6 +5,7 @@
import java.io.File;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Timer;
import java.util.TimerTask;
@@ -533,6 +534,25 @@ void current_add(String url, int flags) {
current_addmany(urls, flags);
}
+ int current_move_left() {
+ if (i_selected == 0) return -1;
+
+ int nu = i_selected - 1;
+ phi.quMove(i_selected, nu);
+ queues.get(i_selected).modified = true;
+ queues.get(nu).load_once();
+ queues.get(nu).modified = true;
+ Collections.swap(queues, i_selected, nu);
+
+ if (i_active == i_selected)
+ i_active--;
+ else if (i_active == nu)
+ i_active++;
+
+ i_selected--;
+ return nu;
+ }
+
String conf_write() {
return String.format(
"list_curpos %d\n"
diff --git a/android/phiola/src/main/res/menu/list.xml b/android/phiola/src/main/res/menu/list.xml
index 27f4af0..3d42352 100644
--- a/android/phiola/src/main/res/menu/list.xml
+++ b/android/phiola/src/main/res/menu/list.xml
@@ -32,6 +32,9 @@
+
+
diff --git a/android/phiola/src/main/res/values/strings.xml b/android/phiola/src/main/res/values/strings.xml
index 6b4ea55..af71de5 100644
--- a/android/phiola/src/main/res/values/strings.xml
+++ b/android/phiola/src/main/res/values/strings.xml
@@ -43,6 +43,7 @@
List: Sort by File Size
List: Sort by File Date
List: Shuffle
+ List: Move Left
List: Convert All...
List: Quick Move All Files
diff --git a/src/core/queue.c b/src/core/queue.c
index af4a003..ecf1fc9 100644
--- a/src/core/queue.c
+++ b/src/core/queue.c
@@ -127,6 +127,19 @@ static void qm_set_on_change(on_change_t cb)
qm->on_change = cb;
}
+static void qm_move(uint from, uint to)
+{
+ if (ffmax(from, to) >= qm->lists.len
+ || !(from - 1 == to)) // move left
+ return;
+
+ struct phi_queue **l = qm->lists.ptr;
+ l[to] = FF_SWAP(&l[from], l[to]);
+ if (qm->selected == from)
+ qm->selected = to;
+ dbglog("move: %u -> %u", from, to);
+}
+
static void q_free(struct phi_queue *q)
{
@@ -683,6 +696,7 @@ const phi_queue_if phi_queueif = {
qm_select,
q_conf,
qm_qselect,
+ qm_move,
q_add,
q_count,
diff --git a/src/jni/queue.h b/src/jni/queue.h
index 1a2bdd4..21169ca 100644
--- a/src/jni/queue.h
+++ b/src/jni/queue.h
@@ -135,6 +135,23 @@ static void qu_cmd_dup(struct core_data *d)
ffmem_free(d);
}
+static void qu_cmd_move(struct core_data *d)
+{
+ x->queue.move(d->cmd, d->param_int);
+ ffmem_free(d);
+}
+
+JNIEXPORT void JNICALL
+Java_com_github_stsaz_phiola_Phiola_quMove(JNIEnv *env, jobject thiz, jint from, jint to)
+{
+ dbglog("%s: enter", __func__);
+ struct core_data *d = ffmem_new(struct core_data);
+ d->cmd = from;
+ d->param_int = to;
+ core_task(d, qu_cmd_move);
+ dbglog("%s: exit", __func__);
+}
+
JNIEXPORT void JNICALL
Java_com_github_stsaz_phiola_Phiola_quDup(JNIEnv *env, jobject thiz, jlong jq, jlong q_src, jint pos)
{
diff --git a/src/phiola.h b/src/phiola.h
index da593a0..c065b1f 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -12,7 +12,7 @@
It must be updated when incompatible changes are made to this file,
then all modules must be rebuilt.
The core will refuse to load modules built for any other core version. */
-#define PHI_VERSION_CORE 20300
+#define PHI_VERSION_CORE 20303
typedef long long int64;
typedef unsigned long long uint64;
@@ -483,6 +483,9 @@ struct phi_queue_if {
struct phi_queue_conf* (*conf)(phi_queue_id q);
void (*qselect)(phi_queue_id q);
+ /** Move list to a new position. */
+ void (*move)(uint from, uint to);
+
int (*add)(phi_queue_id q, struct phi_queue_entry *qe);
int (*count)(phi_queue_id q);
From 8cc86d64327eae8ebd89497d7024290af6b278ae Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 21 Dec 2024 10:35:02 +0300
Subject: [PATCH 35/59] + GUI: Convert: new setting "Preserve Original File
Date"
---
src/gui/convert.hpp | 4 +++-
src/gui/lang_en.conf | 1 +
src/gui/mod.c | 1 +
src/gui/ui-gtk.conf | 4 ++++
src/gui/ui-winapi.conf | 4 ++++
5 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/src/gui/convert.hpp b/src/gui/convert.hpp
index 78e274c..46e5657 100644
--- a/src/gui/convert.hpp
+++ b/src/gui/convert.hpp
@@ -8,7 +8,7 @@ struct gui_wconvert {
ffui_labelxx ldir, lname, lext, lfrom, luntil, ltags, laacq, lvorbisq, lopusq;
ffui_editxx edir, ename, efrom, euntil, etags, eaacq, evorbisq, eopusq;
ffui_comboboxxx cbext;
- ffui_checkboxxx cbcopy;
+ ffui_checkboxxx cbcopy, cbkeepdate;
ffui_buttonxx bbrowse, bstart;
xxstr conf_dir, conf_name, conf_ext;
@@ -32,6 +32,7 @@ FF_EXTERN const ffui_ldr_ctl wconvert_ctls[] = {
_(laacq), _(eaacq),
_(lvorbisq),_(evorbisq),
_(lopusq), _(eopusq),
+ _(cbkeepdate),
_(bstart),
FFUI_LDR_CTL_END
};
@@ -202,6 +203,7 @@ static struct phi_track_conf* conv_conf_create()
tc->vorbis.quality = c->conf_vorbisq;
tc->opus.bitrate = c->conf_opusq;
+ tc->ifile.preserve_date = c->cbkeepdate.checked();
tc->ofile.name = ffsz_allocfmt("%S/%S.%S", &c->conf_dir, &c->conf_name, &c->conf_ext);
return tc;
}
diff --git a/src/gui/lang_en.conf b/src/gui/lang_en.conf
index 4681b23..e69e0f0 100644
--- a/src/gui/lang_en.conf
+++ b/src/gui/lang_en.conf
@@ -82,6 +82,7 @@ MMConvert "_Convert"
CVAACQ "[.m4a] AAC Quality: 1..5 (VBR) or 8..800 (CBR, kbit/s)"
CVVorbisQ "[.ogg] Vorbis Quality: 0..10"
CVOpusQ "[.opus] Opus Bitrate (VBR): 6..510"
+ CVKeepDate "Preserve Original File Date"
CVStart "Begin Conversion"
MCSetStartPos "Set '_Start Position'"
MCSetEndPos "Set '_End Position'"
diff --git a/src/gui/mod.c b/src/gui/mod.c
index a53109a..c485d7b 100644
--- a/src/gui/mod.c
+++ b/src/gui/mod.c
@@ -729,6 +729,7 @@ void convert_begin(void *param)
struct phi_queue_entry *qe;
for (i = 0; !!(qe = gd->queue->at(gd->q_convert, i)); i++) {
qe->conf.ofile.name = ffsz_dup(c->ofile.name);
+ qe->conf.ifile.preserve_date = c->ifile.preserve_date;
qe->conf.seek_msec = c->seek_msec;
qe->conf.until_msec = c->until_msec;
qe->conf.aac.quality = c->aac.quality;
diff --git a/src/gui/ui-gtk.conf b/src/gui/ui-gtk.conf
index cc12bac..f256150 100644
--- a/src/gui/ui-gtk.conf
+++ b/src/gui/ui-gtk.conf
@@ -514,6 +514,10 @@ window wconvert {
editbox eopusq {
}
+ checkbox cbkeepdate {
+ text $CVKeepDate
+ }
+
button bstart {
text $CVStart
action A_CONVERT_START
diff --git a/src/gui/ui-winapi.conf b/src/gui/ui-winapi.conf
index a3c0ef0..c624080 100644
--- a/src/gui/ui-winapi.conf
+++ b/src/gui/ui-winapi.conf
@@ -552,6 +552,10 @@ window wconvert {
editbox eopusq {
}
+ checkbox cbkeepdate {
+ text $CVKeepDate
+ }
+
button bstart {
text $CVStart
action A_CONVERT_START
From c4da7629ecd80f7e5b82b7a3269bbdb3ba866038 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 21 Dec 2024 10:35:02 +0300
Subject: [PATCH 36/59] ref
---
src/exe/tag.h | 6 +++-
src/format/tag.c | 87 ++++++++++++++++++++++++++----------------------
src/phiola.h | 1 +
src/util/util.h | 26 +++++++++++++++
4 files changed, 79 insertions(+), 41 deletions(-)
diff --git a/src/exe/tag.h b/src/exe/tag.h
index 25616e6..0c0b332 100644
--- a/src/exe/tag.h
+++ b/src/exe/tag.h
@@ -4,7 +4,7 @@
static int tag_help()
{
help_info_write("\
-Edit .mp3 file tags:\n\
+Edit file tags:\n\
`phiola tag` OPTIONS FILE...\n\
\n\
Options:\n\
@@ -12,6 +12,7 @@ Options:\n\
`-meta` NAME=VALUE Meta data\n\
.mp3 supports: album, albumartist, artist, comment, date, genre, picture, publisher, title, tracknumber, tracktotal.\n\
`-preserve_date` Preserve file modification date\n\
+ `-fast` Fail if need to rewrite whole file\n\
");
x->exit_code = 0;
return 1;
@@ -22,6 +23,7 @@ struct cmd_tag {
ffvec meta;
u_char clear;
u_char preserve_date;
+ u_char fast;
};
static int tag_input(struct cmd_tag *t, ffstr s)
@@ -53,6 +55,7 @@ static int tag_action(struct cmd_tag *t)
.meta = t->meta,
.clear = t->clear,
.preserve_date = t->preserve_date,
+ .no_expand = t->fast,
};
r |= tag->edit(&conf);
}
@@ -72,6 +75,7 @@ static int tag_prepare(struct cmd_tag *t)
#define O(m) (void*)FF_OFF(struct cmd_tag, m)
static const struct ffarg cmd_tag_args[] = {
{ "-clear", '1', O(clear) },
+ { "-fast", '1', O(fast) },
{ "-help", 0, tag_help },
{ "-meta", '+S', tag_meta },
{ "-preserve_date", '1', O(preserve_date) },
diff --git a/src/format/tag.c b/src/format/tag.c
index dcc13af..9857b52 100644
--- a/src/format/tag.c
+++ b/src/format/tag.c
@@ -2,6 +2,7 @@
2022, Simon Zolin */
#include
+#include
#include
#include
#include
@@ -57,29 +58,6 @@ static void tag_edit_close(struct tag_edit *t)
ffvec_free(&t->meta2);
}
-static int file_copydata(fffd src, ffuint64 offsrc, fffd dst, ffuint64 offdst, ffuint64 size)
-{
- int rc = -1, r;
- ffvec v = {};
- ffvec_alloc(&v, 8*1024*1024, 1);
-
- while (size != 0) {
- if (0 > (r = fffile_readat(src, v.ptr, ffmin(size, v.cap), offsrc)))
- goto end;
- offsrc += r;
- if (0 > (r = fffile_writeat(dst, v.ptr, r, offdst)))
- goto end;
- offdst += r;
- size -= r;
- }
-
- rc = 0;
-
-end:
- ffvec_free(&v);
- return rc;
-}
-
static int user_meta_split(ffstr kv, ffstr *k, ffstr *v)
{
if (0 > ffstr_splitby(&kv, '=', k, v)) {
@@ -106,7 +84,45 @@ static int user_meta_find(const ffvec *m, uint tag, ffstr *k, ffstr *v)
return 0;
}
-static int mp3_id3v2(struct tag_edit *t)
+static int tag_file_write(struct tag_edit *t, ffstr head, ffstr tags, uint64 tail_off_src)
+{
+ uint64 src_tail_size = fffile_size(t->fd) - tail_off_src;
+
+ t->fnw = ffsz_allocfmt("%s.phiolatemp", t->conf.filename);
+ if (FFFILE_NULL == (t->fdw = fffile_open(t->fnw, FFFILE_CREATENEW | FFFILE_WRITEONLY))) {
+ syserrlog("file create: %s", t->fnw);
+ return -1;
+ }
+ fffile_trunc(t->fdw, head.len + tags.len + src_tail_size);
+ dbglog("created file: %s", t->fnw);
+
+ // Copy header
+ if (head.len) {
+ if (0 > fffile_writeat(t->fdw, head.ptr, head.len, 0)) {
+ syserrlog("file write: %s", t->fnw);
+ return -1;
+ }
+ dbglog("written %L bytes @%U", head.len, 0ULL);
+ }
+
+ // Write tags
+ if (0 > fffile_writeat(t->fdw, tags.ptr, tags.len, head.len)) {
+ syserrlog("file write: %s", t->fnw);
+ return -1;
+ }
+ dbglog("written %L bytes @%U", tags.len, (uint64)head.len);
+
+ // Copy tail
+ uint64 woff = head.len + tags.len;
+ if (file_copydata(t->fd, tail_off_src, t->fdw, woff, src_tail_size)) {
+ syserrlog("file read/write: %s -> %s", t->conf.filename, t->fnw);
+ return -1;
+ }
+ dbglog("written %U bytes @%U", src_tail_size, woff);
+ return 0;
+}
+
+static int tag_mp3_id3v2(struct tag_edit *t)
{
int rc = 'e', r;
uint id3v2_size = 0;
@@ -221,23 +237,14 @@ static int mp3_id3v2(struct tag_edit *t)
}
} else {
- t->fnw = ffsz_allocfmt("%s.phiolatemp", t->conf.filename);
- if (FFFILE_NULL == (t->fdw = fffile_open(t->fnw, FFFILE_CREATENEW | FFFILE_WRITEONLY))) {
- syserrlog("file create: %s", t->fnw);
+ if (t->conf.no_expand) {
+ errlog("File rewrite is disabled");
goto end;
}
- dbglog("created file: %s", t->fnw);
- if (0 > (r = fffile_writeat(t->fdw, t->buf.ptr, t->buf.len, 0))) {
- syserrlog("file write:%s", t->fnw);
+ ffstr head = {};
+ if (tag_file_write(t, head, *(ffstr*)&t->buf, id3v2_size))
goto end;
- }
-
- ffint64 sz = fffile_size(t->fd) - id3v2_size;
- if (0 != file_copydata(t->fd, id3v2_size, t->fdw, t->buf.len, sz)) {
- syserrlog("file read/write: %s -> %s", t->conf.filename, t->fnw);
- goto end;
- }
}
rc = 0;
@@ -248,7 +255,7 @@ static int mp3_id3v2(struct tag_edit *t)
return rc;
}
-static int mp3_id3v1(struct tag_edit *t)
+static int tag_mp3_id3v1(struct tag_edit *t)
{
int r, have_id3v1 = 0;
ffint64 sz = fffile_size(t->fd);
@@ -344,8 +351,8 @@ static int tag_edit_process(struct tag_edit *t)
const char *ext = file_ext_str(fmt);
if (ffsz_eq(ext, "mp3")) {
- if (0 == (r = mp3_id3v2(t)))
- r = mp3_id3v1(t);
+ if (0 == (r = tag_mp3_id3v2(t)))
+ r = tag_mp3_id3v1(t);
} else {
errlog("unsupported format");
r = -1;
diff --git a/src/phiola.h b/src/phiola.h
index c065b1f..44a56e8 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -559,6 +559,7 @@ struct phi_tag_conf {
ffvec meta; // ffstr[]
uint clear :1;
uint preserve_date :1;
+ uint no_expand :1;
};
typedef struct phi_tag_if phi_tag_if;
diff --git a/src/util/util.h b/src/util/util.h
index e9e69e0..6fdbb32 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -196,3 +196,29 @@ static inline void phi_af_update(struct phi_af *dst, const struct phi_af *src)
if (src->channels)
dst->channels = src->channels;
}
+
+
+#include
+
+static inline int file_copydata(fffd src, ffuint64 offsrc, fffd dst, ffuint64 offdst, ffuint64 size)
+{
+ int rc = -1, r;
+ ffvec v = {};
+ ffvec_alloc(&v, 8*1024*1024, 1);
+
+ while (size != 0) {
+ if (0 >= (r = fffile_readat(src, v.ptr, ffmin(size, v.cap), offsrc)))
+ goto end;
+ offsrc += r;
+ if (0 > (r = fffile_writeat(dst, v.ptr, r, offdst)))
+ goto end;
+ offdst += r;
+ size -= r;
+ }
+
+ rc = 0;
+
+end:
+ ffvec_free(&v);
+ return rc;
+}
From 50ffc7823240e6b451ed9b98b98cc9857f94cb58 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 21 Dec 2024 10:35:02 +0300
Subject: [PATCH 37/59] + `tag` command: support .ogg and .opus files
---
README.md | 2 +-
src/format/tag.c | 238 +++++++++++++++++++++++++++++++++++++++++++++++
test.sh | 32 ++++---
3 files changed, 257 insertions(+), 15 deletions(-)
diff --git a/README.md b/README.md
index 85e0aae..cab46ff 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@ Contents:
* Play audio: `.mp3`, `.ogg`(Vorbis/Opus), `.mp4`/`.mov`(AAC/ALAC/MP3), `.mkv`/`.webm`(AAC/ALAC/MP3/Vorbis/Opus/PCM), `.caf`(AAC/ALAC/PCM), `.avi`(AAC/MP3/PCM), `.ts`(AAC/MP3), `.aac`, `.mpc`; `.flac`, `.ape`, `.wv`, `.wav`.
* Record audio: `.m4a`(AAC), `.ogg`, `.opus`; `.flac`, `.wav`
* Convert audio
-* List/search file meta tags; edit MP3 tags
+* List/search file meta tags; edit file tags (.mp3, .ogg, .opus)
* List available audio devices
* Input: file, directory, HTTP/HTTPS URL, console (stdin), playlists: `.m3u`, `.pls`, `.cue`
* Command Line Interface for Desktop OS
diff --git a/src/format/tag.c b/src/format/tag.c
index 9857b52..6eccfb2 100644
--- a/src/format/tag.c
+++ b/src/format/tag.c
@@ -6,6 +6,11 @@
#include
#include
#include
+#include
+#include
+#include
+#include
+#include
#include
#include
@@ -319,6 +324,235 @@ static int tag_mp3_id3v1(struct tag_edit *t)
return 0;
}
+/** Read Vorbis tags region.
+Require Vorbis/Opus info packet on a separate page;
+ require tags packet on a separate page,
+ but allow tags packet on the same page with Vorbis Codebook.
+vtags: tags body (including framing bit for Vorbis)
+vcb: full Vorbis Codebook packet
+Return packet format: 'o', 'v' */
+static int tag_ogg_vtag_read(oggread *ogg, ffstr in, ffstr *page, uint *page_off, uint *page_num, ffstr *vtags, ffstr *vcb)
+{
+ enum {
+ I_HDR, I_OPUS_TAGS, I_VORBIS_TAGS, I_VORBIS_CB,
+ };
+ uint state = 0;
+ for (;;) {
+ ffstr pkt;
+ int r = oggread_process(ogg, &in, &pkt);
+ switch (r) {
+ case OGGREAD_HEADER:
+ switch (state) {
+ case I_HDR:
+ if (ogg->page_counter != 1)
+ goto err;
+ if (pkt.len >= 7
+ && !ffmem_cmp(pkt.ptr, "\x01vorbis", 7))
+ state = I_VORBIS_TAGS;
+ else if (pkt.len >= 8
+ && !ffmem_cmp(pkt.ptr, "OpusHead", 8))
+ state = I_OPUS_TAGS;
+ else
+ goto err;
+ continue;
+
+ case I_OPUS_TAGS:
+ if (ogg->page_counter != 2)
+ goto err;
+ if (!(r = opus_tags_read(pkt.ptr, pkt.len)))
+ goto err;
+ ffstr_shift(&pkt, r);
+ *vtags = pkt;
+ break;
+
+ case I_VORBIS_TAGS:
+ if (ogg->page_counter != 2)
+ goto err;
+ if (!(r = vorbis_tags_read(pkt.ptr, pkt.len)))
+ goto err;
+ ffstr_shift(&pkt, r);
+ *vtags = pkt;
+ if (oggread_pkt_last(ogg))
+ break;
+ state = I_VORBIS_CB;
+ continue;
+
+ case I_VORBIS_CB:
+ if (!(pkt.len >= 7
+ && !ffmem_cmp(pkt.ptr, "\x05vorbis", 7)))
+ goto err;
+ *vcb = pkt;
+ break;
+ }
+
+ if (!oggread_pkt_last(ogg))
+ goto err;
+ *page = ogg->chunk;
+ *page_off = ogg->off - _avp_stream_used(&ogg->stream);
+ *page_num = oggread_page_num(ogg);
+ return (state == I_OPUS_TAGS) ? 'o' : 'v';
+
+ case OGGREAD_MORE:
+ errlog("couldn't find Vorbis Tags");
+ break;
+
+ default:
+ errlog("ogg parser returned code %d", r);
+ }
+ break;
+ }
+
+err:
+ errlog("file format not supported");
+ return 0;
+}
+
+static int tag_vorbis(struct tag_edit *t)
+{
+ int r, rc = 'e', format;
+ uint tags_page_off, tags_page_num, vtags_len;
+ ffstr vtag, vorbis_codebook = {}, page, *kv, k, v, k2, v2;
+ oggwrite ogw = {};
+ oggread ogg = {};
+ vorbistagwrite vtw = {
+ .left_zone = 8,
+ };
+ vorbistagread vtr = {};
+ u_char tags_added[_MMTAG_N] = {};
+
+ ffvec_realloc(&t->buf, 64*1024, 1);
+ if (0 > (r = fffile_readat(t->fd, t->buf.ptr, t->buf.cap, 0))) {
+ syserrlog("file read");
+ return 'e';
+ }
+ t->buf.len = r;
+
+ oggread_open(&ogg, -1);
+ if (!(format = tag_ogg_vtag_read(&ogg, *(ffstr*)&t->buf, &page, &tags_page_off, &tags_page_num, &vtag, &vorbis_codebook)))
+ goto end;
+ vtags_len = vtag.len - (format == 'v') ? 1 : 0;
+
+ // Copy "Vendor" field
+ int tag = vorbistagread_process(&vtr, &vtag, &k, &v);
+ if (tag != MMTAG_VENDOR) {
+ errlog("parsing Vorbis tag");
+ goto end;
+ }
+ vorbistagwrite_create(&vtw);
+ if (!vorbistagwrite_add(&vtw, MMTAG_VENDOR, v))
+ dbglog("vorbistag: written vendor = %S", &v);
+
+ if (!t->conf.clear) {
+ // Replace tags, copy existing tags preserving the original order
+ for (;;) {
+ tag = vorbistagread_process(&vtr, &vtag, &k, &v);
+ if (tag == VORBISTAGREAD_DONE) {
+ break;
+ } else if (tag == VORBISTAGREAD_ERROR) {
+ errlog("parsing Vorbis tags");
+ goto end;
+ }
+
+ if (user_meta_find(&t->conf.meta, tag, &k2, &v2)) {
+ // Write user tag
+ if (!vorbistagwrite_add(&vtw, tag, v2))
+ dbglog("vorbistag: written %S = %S", &k2, &v2);
+
+ } else {
+ // Copy existing tag
+ if (tag != 0) {
+ vorbistagwrite_add(&vtw, tag, v);
+ } else {
+ // Unknown tag
+ vorbistagwrite_add_name(&vtw, k, v);
+ }
+ }
+
+ tags_added[tag] = 1;
+ }
+ }
+
+ // Add new tags
+ FFSLICE_WALK(&t->conf.meta, kv) {
+ if (!kv->len)
+ continue;
+ tag = user_meta_split(*kv, &k, &v);
+ if (tag < 0)
+ goto end;
+ if (!tag)
+ continue;
+ if (tags_added[tag])
+ continue;
+ if (!vorbistagwrite_add(&vtw, tag, v))
+ dbglog("vorbistag: written %S = %S", &k, &v);
+ }
+
+ // Prepare OGG packet with Opus/Vorbis header, tags data and padding
+ uint tags_len = vorbistagwrite_fin(&vtw).len;
+ int padding = vtags_len - tags_len;
+ if (padding < 0)
+ padding = 1000 - tags_len;
+ if (padding < 0)
+ padding = 0;
+ ffvec_grow(&vtw.out, 1 + padding, 1);
+ ffstr vt = *(ffstr*)&vtw.out;
+ if (format == 'o') {
+ vt.len = opus_tags_write(vt.ptr, -1, tags_len);
+ } else {
+ ffstr_shift(&vt, 1);
+ vt.len = vorbis_tags_write(vt.ptr, -1, tags_len);
+ }
+ ffmem_zero(vt.ptr + vt.len, padding);
+ vt.len += padding;
+
+ // Prepare OGG page with Vorbis Tags and maybe Vorbis Codebook
+ ogw.page.number = tags_page_num;
+ oggwrite_create(&ogw, ogg.info.serial, 0);
+ ffstr wpage;
+ uint f = (vorbis_codebook.len) ? 0 : OGGWRITE_FFLUSH;
+ r = oggwrite_process(&ogw, &vt, &wpage, 0, f);
+ if (vt.len) {
+ errlog("resulting OGG page is too large");
+ goto end;
+ }
+ if (vorbis_codebook.len) {
+ r = oggwrite_process(&ogw, &vorbis_codebook, &wpage, 0, OGGWRITE_FFLUSH);
+ if (vorbis_codebook.len) {
+ errlog("resulting OGG page is too large");
+ goto end;
+ }
+ }
+ FF_ASSERT(r == OGGWRITE_DATA);
+ FF_ASSERT(wpage.len >= page.len);
+
+ if (wpage.len == page.len) {
+ // Rewrite OGG page in-place
+ if (0 > (r = fffile_writeat(t->fdw, wpage.ptr, wpage.len, tags_page_off))) {
+ syserrlog("file write");
+ goto end;
+ }
+ dbglog("written %L bytes @%U", wpage.len, tags_page_off);
+
+ } else {
+ if (t->conf.no_expand) {
+ errlog("File rewrite is disabled");
+ goto end;
+ }
+
+ ffstr head = FFSTR_INITN(t->buf.ptr, tags_page_off);
+ if (tag_file_write(t, head, wpage, tags_page_off + page.len))
+ goto end;
+ }
+
+ rc = 0;
+
+end:
+ oggwrite_close(&ogw);
+ vorbistagwrite_destroy(&vtw);
+ oggread_close(&ogg);
+ return rc;
+}
+
static int tag_edit_process(struct tag_edit *t)
{
int r;
@@ -353,6 +587,10 @@ static int tag_edit_process(struct tag_edit *t)
if (ffsz_eq(ext, "mp3")) {
if (0 == (r = tag_mp3_id3v2(t)))
r = tag_mp3_id3v1(t);
+
+ } else if (ffsz_eq(ext, "ogg")) {
+ r = tag_vorbis(t);
+
} else {
errlog("unsupported format");
r = -1;
diff --git a/test.sh b/test.sh
index 49b7353..55e5104 100644
--- a/test.sh
+++ b/test.sh
@@ -658,22 +658,26 @@ test_tag() {
./phiola rec -o tag.wav -f -u 2
ffmpeg -i tag.wav -y -c:a libmp3lame tag.mp3 2>/dev/null
./phiola co tag.wav -o .ogg
+ ./phiola co tag.wav -o .opus
fi
- # unsupported format
- ./phiola tag -m 'artist=Great Artist' tag.ogg || true
-
- cp tag.mp3 tag_a.mp3
- ./phiola tag -m 'artist=Great Artist' tag_a.mp3 ; ./phiola i tag_a.mp3 | grep "Great Artist - "
-
- cp tag_a.mp3 tag_at.mp3
- ./phiola tag -m 'title=Cool Song' tag_at.mp3 ; ./phiola i tag_at.mp3 | grep "Great Artist - Cool Song"
-
- cp tag.mp3 tag_at2.mp3
- ./phiola tag -m 'artist=Great Artist' -m 'title=Cool Song' tag_at2.mp3 ; ./phiola i tag_at2.mp3 | grep "Great Artist - Cool Song"
-
- cp tag_at.mp3 tag_t.mp3
- ./phiola tag -clear -m 'title=T2' tag_t.mp3 ; ./phiola i tag_t.mp3 | grep " - T2"
+ # add new tags
+ ./phiola tag -m 'artist=Great Artist' -m 'title=Cool Song' tag.mp3 tag.ogg tag.opus
+ ./phiola i tag.mp3 | grep "Great Artist - Cool Song"
+ ./phiola i tag.ogg | grep "Great Artist - Cool Song"
+ ./phiola i tag.opus | grep "Great Artist - Cool Song"
+
+ # replace tag
+ ./phiola tag -m 'title=Very Cool Song' tag.mp3 tag.ogg tag.opus
+ ./phiola i tag.mp3 | grep "Great Artist - Very Cool Song"
+ ./phiola i tag.ogg | grep "Great Artist - Very Cool Song"
+ ./phiola i tag.opus | grep "Great Artist - Very Cool Song"
+
+ # set tag
+ ./phiola tag -clear -m 'title=Cool Song' tag.mp3 tag.ogg tag.opus
+ ./phiola i tag.mp3 | grep " - Cool Song"
+ ./phiola i tag.ogg | grep " - Cool Song"
+ ./phiola i tag.opus | grep " - Cool Song"
}
test_clean() {
From 9d19081bfe28c77c7b89dd691e5a64c73e8d4a68 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 21 Dec 2024 10:35:02 +0300
Subject: [PATCH 38/59] minor
---
README.md | 9 +--
.../com/github/stsaz/phiola/MainActivity.java | 20 +++---
.../java/com/github/stsaz/phiola/Queue.java | 22 +++---
.../java/com/github/stsaz/phiola/Track.java | 27 +-------
src/acodec/Makefile | 40 ++++++-----
src/afilter/Makefile | 9 +--
src/afilter/conv.c | 2 +-
src/afilter/gain.c | 1 -
src/core/core.c | 4 +-
src/core/queue-entry.h | 4 +-
src/core/queue.c | 2 +-
src/exe/record.h | 2 +-
src/format/opus-meta.h | 67 ++++--------------
src/format/vorbis-meta.h | 68 ++-----------------
src/jni/phiola-jni.c | 18 ++---
src/jni/record.h | 2 +-
...ld-debianbullseye.sh => xbuild-debianBW.sh | 8 +--
17 files changed, 86 insertions(+), 219 deletions(-)
rename xbuild-debianbullseye.sh => xbuild-debianBW.sh (91%)
diff --git a/README.md b/README.md
index cab46ff..185d639 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ Contents:
* Terminal/Console UI for interaction at runtime
* GUI for Windows, Linux, Android
* Instant startup time: very short initial delay until the audio starts playing (e.g. Linux/PulseAudio: TUI: `~25ms`, GUI: `~50ms`)
-* Fast (low footprint): keeps your CPU, memory & disk I/O at absolute minimum; spends 99% of time inside codec algorithms
+* Fast (small footprint, low overhead): keeps your CPU, memory & disk I/O at absolute minimum; spends 99% of time inside codec algorithms
**Bonus:** Convenient API with plugin support which allows using all the above features from any C/C++/Java app!
@@ -51,13 +51,10 @@ Features and notes by platform:
| Dark themed GUI | ✅ (GTK default) | incomplete | ✅ |
| Powerful CLI | ✅ | ✅ | ❌ |
| Simple TUI | ✅ | ✅ | ❌ |
-| Playback formats | ✅ all supported | ✅ all supported | all supported except `.mpc`, `.ape`, `.wv` |
-| Conversion formats | ✅ all supported | ✅ all supported | all supported except `.mpc`, `.ape`, `.wv` |
-| Batch conversion | ✅ | ✅ | ✅ |
-| Record from mic | ✅ | ✅ | ✅ |
+| File formats | ✅ all supported | ✅ all supported | all supported except `.mpc`, `.ape`, `.wv` |
| Record from Internet | ✅ | ✅ | ❌ |
| Record what you hear | ✅ (PulseAudio) | ✅ | ❌ |
-| Requirements | glibc-2.31(AMD64), glibc-2.36(ARM64) | Windows 7 | Android 8 (ARM64), Android 6 (ARM) |
+| Requirements | glibc-2.36 | Windows 7 | Android 8 (ARM64), Android 6 (ARM) |
| HW Requirements | AMD64, ARM64 | AMD64 | ARM64, ARM(incomplete) |
> Although not officially supported, phiola should build fine for **macOS**, **FreeBSD** and **Windows XP** after tweaking the build script.
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
index ed35995..c00635d 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/MainActivity.java
@@ -475,10 +475,8 @@ private void rec_start_stop() {
private void rec_pause_toggle() {
int r = track.record_pause_toggle();
- String s = null;
- if (r < 0) {
- s = "Long press to start recording";
- } else {
+ String s = "Long press to start recording";
+ if (r >= 0) {
s = "Paused Recording";
int st = GUI.STATE_REC_PAUSED;
if (r == 0) {
@@ -778,7 +776,7 @@ private void list_switched(int i) {
private void list_switch() {
list_leave();
- int qi = queue.next_list_select();
+ int qi = queue.switch_list_next();
list_switched(qi);
}
@@ -1037,13 +1035,13 @@ private void track_closed(TrackHandle t) {
private int track_update(TrackHandle t) {
core.dbglog(TAG, "track_update: state:%d pos:%d", t.state, t.pos_msec);
switch (t.state) {
- case Track.STATE_PAUSED:
- state(GUI.MASK_PLAYBACK, GUI.STATE_PAUSED);
- break;
+ case Track.STATE_PAUSED:
+ state(GUI.MASK_PLAYBACK, GUI.STATE_PAUSED);
+ break;
- case Track.STATE_PLAYING:
- state(GUI.MASK_PLAYBACK, GUI.STATE_PLAYING);
- break;
+ case Track.STATE_PLAYING:
+ state(GUI.MASK_PLAYBACK, GUI.STATE_PLAYING);
+ break;
}
String s;
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
index c0b8da2..89a723a 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
@@ -35,17 +35,15 @@ void copy_entries(long q_src, int pos) {
}
void add(String url, int flags) {
- if (conversion) return;
-
String[] urls = new String[1];
urls[0] = url;
- phi.quAdd(q, urls, flags);
- modified = true;
+ add_many(urls, flags);
}
void add_many(String[] urls, int flags) {
if (conversion) return;
+ load_once();
phi.quAdd(q, urls, flags);
modified = true;
}
@@ -346,20 +344,17 @@ private int index_next(int qi) {
}
/** Change to next playlist */
- int next_list_select() {
+ int switch_list_next() { return switch_list(index_next(i_selected)); }
+
+ int switch_list(int i) {
current_filter("");
- i_selected = index_next(i_selected);
- queues.get(i_selected).load_once();
- return i_selected;
+ queues.get(i).load_once();
+ i_selected = i;
+ return i;
}
boolean conversion_list(int i) { return i == i_conversion; }
- void switch_list(int i) {
- i_selected = i;
- queues.get(i_selected).load_once();
- }
-
int current_index() { return i_selected; }
long current_id() { return queues.get(i_selected).q; }
@@ -520,7 +515,6 @@ void current_remove_non_existing() {
void current_addmany(String[] urls, int flags) {
filter_close();
- queues.get(i_selected).load_once();
int f = 0;
if (flags == ADD_RECURSE)
f = Phiola.QUADD_RECURSE;
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Track.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Track.java
index ac569ee..956f0e6 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Track.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Track.java
@@ -5,9 +5,6 @@
import android.os.Build;
-import androidx.annotation.NonNull;
-import androidx.collection.SimpleArrayMap;
-
import java.util.ArrayList;
abstract class PlaybackObserver {
@@ -39,7 +36,6 @@ class Track {
private Core core;
private Phiola phi;
private ArrayList observers;
- private SimpleArrayMap supp_exts;
private TrackHandle tplay;
private TrackHandle trec;
@@ -77,12 +73,6 @@ public void on_update(long pos_msec) {
tplay = new TrackHandle();
observers = new ArrayList<>();
tplay.state = STATE_NONE;
-
- String[] exts = {"mp3", "ogg", "opus", "m4a", "wav", "flac", "mp4", "mkv", "avi"};
- supp_exts = new SimpleArrayMap<>(exts.length);
- for (String e : exts) {
- supp_exts.put(e, true);
- }
}
String cur_url() {
@@ -95,28 +85,13 @@ long curpos_msec() {
return tplay.pos_msec;
}
- boolean supported_url(@NonNull String name) {
+ boolean supported_url(String name) {
if (name.startsWith("/")
|| name.startsWith("http://") || name.startsWith("https://"))
return true;
return false;
}
- /**
- * Return TRUE if file name's extension is supported
- */
- boolean supported(@NonNull String name) {
- if (supported_url(name))
- return true;
-
- int dot = name.lastIndexOf('.');
- if (dot <= 0)
- return false;
- dot++;
- String ext = name.substring(dot);
- return supp_exts.containsKey(ext);
- }
-
void observer_add(PlaybackObserver f) {
observers.add(f);
}
diff --git a/src/acodec/Makefile b/src/acodec/Makefile
index b99ddae..b81bc40 100644
--- a/src/acodec/Makefile
+++ b/src/acodec/Makefile
@@ -17,74 +17,72 @@
# MODS
AVPACK := $(ROOT_DIR)/avpack
-
ALIB3 := $(PHIOLA)/alib3
ALIB3_BIN := $(ALIB3)/_$(SYS)-$(CPU)
-
-PFX := ac-
+ACPFX := ac-
# LOSSY
%.o: $(PHIOLA)/src/acodec/%.c
$(C) $(CFLAGS) -I$(ALIB3) $< -o $@
-MODS += $(PFX)aac.$(SO)
+MODS += $(ACPFX)aac.$(SO)
LIBS3 += $(ALIB3_BIN)/libfdk-aac-phi.$(SO)
-$(PFX)aac.$(SO): aac.o
+$(ACPFX)aac.$(SO): aac.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lfdk-aac-phi -o $@
-MODS += $(PFX)mpeg.$(SO)
+MODS += $(ACPFX)mpeg.$(SO)
LIBS3 += $(ALIB3_BIN)/libmpg123-phi.$(SO)
-$(PFX)mpeg.$(SO): mpeg.o
+$(ACPFX)mpeg.$(SO): mpeg.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lmpg123-phi -o $@
-MODS += $(PFX)vorbis.$(SO)
+MODS += $(ACPFX)vorbis.$(SO)
LIBS3 += $(ALIB3_BIN)/libvorbis-phi.$(SO) $(ALIB3_BIN)/libvorbisenc-phi.$(SO)
vorbis.o: $(PHIOLA)/src/acodec/vorbis.c
$(C) $(CFLAGS) -I$(ALIB3) -I$(AVPACK) $< -o $@
-$(PFX)vorbis.$(SO): vorbis.o
+$(ACPFX)vorbis.$(SO): vorbis.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lvorbis-phi -lvorbisenc-phi -o $@
-MODS += $(PFX)opus.$(SO)
+MODS += $(ACPFX)opus.$(SO)
LIBS3 += $(ALIB3_BIN)/libopus-phi.$(SO)
opus.o: $(PHIOLA)/src/acodec/opus.c
$(C) $(CFLAGS) -I$(ALIB3) -I$(AVPACK) $< -o $@
-$(PFX)opus.$(SO): opus.o
+$(ACPFX)opus.$(SO): opus.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lopus-phi -o $@
ifneq "$(SYS)" "android"
-MODS += $(PFX)mpc.$(SO)
+MODS += $(ACPFX)mpc.$(SO)
LIBS3 += $(ALIB3_BIN)/libmusepack-phi.$(SO)
endif
-$(PFX)mpc.$(SO): mpc.o
+$(ACPFX)mpc.$(SO): mpc.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lmusepack-phi -o $@
# LOSSLESS
-MODS += $(PFX)alac.$(SO)
+MODS += $(ACPFX)alac.$(SO)
LIBS3 += $(ALIB3_BIN)/libALAC-phi.$(SO)
-$(PFX)alac.$(SO): alac.o
+$(ACPFX)alac.$(SO): alac.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lALAC-phi -o $@
ifneq "$(SYS)" "android"
-MODS += $(PFX)ape.$(SO)
+MODS += $(ACPFX)ape.$(SO)
LIBS3 += $(ALIB3_BIN)/libMAC-phi.$(SO)
endif
ape.o: $(PHIOLA)/src/acodec/ape.c
$(C) $(CFLAGS) -I$(ALIB3) -I$(AVPACK) $< -o $@
-$(PFX)ape.$(SO): ape.o
+$(ACPFX)ape.$(SO): ape.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lMAC-phi -o $@
-MODS += $(PFX)flac.$(SO)
+MODS += $(ACPFX)flac.$(SO)
LIBS3 += $(ALIB3_BIN)/libFLAC-phi.$(SO)
flac.o: $(PHIOLA)/src/acodec/flac.c
$(C) $(CFLAGS) -I$(ALIB3) -I$(AVPACK) $< -o $@
-$(PFX)flac.$(SO): flac.o
+$(ACPFX)flac.$(SO): flac.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lFLAC-phi -o $@
ifneq "$(SYS)" "android"
-MODS += $(PFX)wavpack.$(SO)
+MODS += $(ACPFX)wavpack.$(SO)
LIBS3 += $(ALIB3_BIN)/libwavpack-phi.$(SO)
endif
-$(PFX)wavpack.$(SO): wavpack.o
+$(ACPFX)wavpack.$(SO): wavpack.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lwavpack-phi -o $@
diff --git a/src/afilter/Makefile b/src/afilter/Makefile
index 9760c84..875f11f 100644
--- a/src/afilter/Makefile
+++ b/src/afilter/Makefile
@@ -18,6 +18,7 @@
ALIB3 := $(PHIOLA)/alib3
ALIB3_BIN := $(ALIB3)/_$(SYS)-$(CPU)
+AFPFX := af-
MODS += afilter.$(SO)
%.o: $(PHIOLA)/src/afilter/%.c
@@ -34,16 +35,16 @@ afilter.$(SO): afilter.o \
str-format.o
$(LINK) -shared $+ $(LINKFLAGS) -lm -o $@
-MODS += soxr.$(SO)
+MODS += $(AFPFX)soxr.$(SO)
LIBS3 += $(ALIB3_BIN)/libsoxr-phi.$(SO)
soxr.o: $(PHIOLA)/src/afilter/soxr.c
$(C) $(CFLAGS) -I$(ALIB3) $< -o $@
-soxr.$(SO): soxr.o
+$(AFPFX)soxr.$(SO): soxr.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lsoxr-phi -o $@
-MODS += danorm.$(SO)
+MODS += $(AFPFX)danorm.$(SO)
LIBS3 += $(ALIB3_BIN)/libDynamicAudioNormalizer-phi.$(SO)
dynanorm.o: $(PHIOLA)/src/afilter/dynanorm.c
$(C) $(CFLAGS) -I$(ALIB3) $< -o $@
-danorm.$(SO): dynanorm.o
+$(AFPFX)danorm.$(SO): dynanorm.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lDynamicAudioNormalizer-phi -o $@
diff --git a/src/afilter/conv.c b/src/afilter/conv.c
index 941a13f..794df5c 100644
--- a/src/afilter/conv.c
+++ b/src/afilter/conv.c
@@ -67,7 +67,7 @@ static int aconv_prepare(struct aconv *c, phi_track *t)
c->fo = t->aconv.out;
if (c->fi.rate != c->fo.rate) {
- if (!core->track->filter(t, core->mod("soxr.conv"), 0))
+ if (!core->track->filter(t, core->mod("af-soxr.conv"), 0))
return PHI_ERR;
if (c->fi.channels == c->fo.channels
diff --git a/src/afilter/gain.c b/src/afilter/gain.c
index bdfd097..c7c75aa 100644
--- a/src/afilter/gain.c
+++ b/src/afilter/gain.c
@@ -5,7 +5,6 @@
#include
extern const phi_core *core;
-#define errlog(t, ...) phi_errlog(core, NULL, t, __VA_ARGS__)
#define dbglog(t, ...) phi_dbglog(core, NULL, t, __VA_ARGS__)
struct gain {
diff --git a/src/core/core.c b/src/core/core.c
index 7738d5b..652bedc 100644
--- a/src/core/core.c
+++ b/src/core/core.c
@@ -167,6 +167,8 @@ static int mod_load(struct core_mod *m, ffstr file)
goto end;
}
+ dbglog("loading module: %s", fn);
+
if (FFDL_NULL == (dl = ffdl_open(fn, FFDL_SELFDIR))) {
errlog("%s: ffdl_open: %s", fn, ffdl_errstr());
goto end;
@@ -179,8 +181,6 @@ static int mod_load(struct core_mod *m, ffstr file)
goto end;
}
- dbglog("%s: calling phi_mod_init()", fn);
-
const phi_mod *mod;
if (NULL == (mod = mod_init(core)))
goto end;
diff --git a/src/core/queue-entry.h b/src/core/queue-entry.h
index 25be19f..6881d12 100644
--- a/src/core/queue-entry.h
+++ b/src/core/queue-entry.h
@@ -142,7 +142,7 @@ static int qe_play(struct q_entry *e)
|| !track->filter(t, core->mod("format.detect"), 0)
|| !track->filter(t, core->mod("afilter.until"), 0)
|| (c->afilter.danorm
- && !track->filter(t, core->mod("danorm.f"), 0))
+ && !track->filter(t, core->mod("af-danorm.f"), 0))
|| !track->filter(t, ui_if, 0)
|| !track->filter(t, core->mod("afilter.gain"), 0)
|| !track->filter(t, core->mod("afilter.auto-conv"), 0)
@@ -170,7 +170,7 @@ static int qe_play(struct q_entry *e)
|| !track->filter(t, core->mod("format.detect"), 0)
|| !track->filter(t, core->mod("afilter.until"), 0)
|| (c->afilter.danorm
- && !track->filter(t, core->mod("danorm.f"), 0))
+ && !track->filter(t, core->mod("af-danorm.f"), 0))
|| !track->filter(t, ui_if, 0)
|| !track->filter(t, core->mod("afilter.gain"), 0)
|| !track->filter(t, core->mod("afilter.auto-conv"), 0)
diff --git a/src/core/queue.c b/src/core/queue.c
index ecf1fc9..414c52f 100644
--- a/src/core/queue.c
+++ b/src/core/queue.c
@@ -199,7 +199,7 @@ static void* q_insert(struct phi_queue *q, uint pos, struct phi_queue_entry *qe)
else
*ffslice_moveT((ffslice*)&q->index, pos, pos + 1, q->index.len - 1 - pos, void*) = e;
fflock_unlock(&q->lock);
- dbglog("added '%s' [%L]", qe->conf.ifile.name, q->index.len);
+ dbglog("added '%s' [%u/%L]", qe->conf.ifile.name, pos, q->index.len);
q_modified(q);
qm->on_change(q, 'a', pos);
return e;
diff --git a/src/exe/record.h b/src/exe/record.h
index f8c554e..f28ea39 100644
--- a/src/exe/record.h
+++ b/src/exe/record.h
@@ -190,7 +190,7 @@ static int rec_action(struct cmd_rec *r)
|| !track->filter(t, x->core->mod("afilter.rtpeak"), 0)
|| !track->filter(t, x->core->mod("tui.rec"), 0)
|| (r->danorm
- && !track->filter(t, x->core->mod("danorm.f"), 0))
+ && !track->filter(t, x->core->mod("af-danorm.f"), 0))
|| !track->filter(t, x->core->mod("afilter.gain"), 0)
|| !track->filter(t, x->core->mod("afilter.auto-conv"), 0)
|| (r->split
diff --git a/src/format/opus-meta.h b/src/format/opus-meta.h
index 61e5e64..d103462 100644
--- a/src/format/opus-meta.h
+++ b/src/format/opus-meta.h
@@ -3,54 +3,11 @@
#include
#include
+#include
extern const phi_meta_if phi_metaif;
extern int vorbistag_read(phi_track *t, ffstr vc);
-struct opus_hdr {
- char id[8]; // ="OpusHead"
- ffbyte ver;
- ffbyte channels;
- ffbyte preskip[2];
- ffbyte orig_sample_rate[4];
- //ffbyte unused[3];
-};
-
-struct opus_info {
- ffuint channels;
- ffuint rate;
- ffuint orig_rate;
- ffuint preskip;
-};
-
-/** Read OpusHead packet */
-static int opusinfo_read(struct opus_info *i, ffstr pkt)
-{
- const struct opus_hdr *h = (struct opus_hdr*)pkt.ptr;
- if (sizeof(struct opus_hdr) > pkt.len
- || !!ffmem_cmp(h->id, "OpusHead", 8)
- || h->ver != 1)
- return -1;
-
- i->channels = h->channels;
- i->rate = 48000;
- i->orig_rate = ffint_le_cpu32_ptr(h->orig_sample_rate);
- i->preskip = ffint_le_cpu16_ptr(h->preskip);
- return 0;
-}
-
-/** Check OpusTags packet.
-body: Vorbis-tag data */
-static int opuscomment_read(ffstr pkt, ffstr *body)
-{
- if (8 > pkt.len
- || ffmem_cmp(pkt.ptr, "OpusTags", 8))
- return -1;
- ffstr_set(body, pkt.ptr + 8, pkt.len - 8);
- return 0;
-}
-
-
struct opusmeta {
uint state;
uint reset :1;
@@ -77,16 +34,16 @@ static int opusmeta_read(void *ctx, phi_track *t)
for (;;) {
switch (o->state) {
case 0: {
- struct opus_info info;
- if (0 != opusinfo_read(&info, t->data_in)) {
- errlog(t, "opusinfo_read");
+ uint channels, preskip;
+ if (!opus_hdr_read(t->data_in.ptr, t->data_in.len, &channels, &preskip)) {
+ errlog(t, "opus_hdr_read");
return PHI_ERR;
}
t->audio.decoder = "Opus";
if (o->reset) {
- if (!(info.channels == t->audio.format.channels
- && info.rate == t->audio.format.rate)) {
+ if (!(channels == t->audio.format.channels
+ && 48000 == t->audio.format.rate)) {
errlog(t, "changing the audio format on-the-fly is not supported");
return PHI_ERR;
}
@@ -95,8 +52,8 @@ static int opusmeta_read(void *ctx, phi_track *t)
struct phi_af f = {
.format = PHI_PCM_FLOAT32,
- .channels = info.channels,
- .rate = info.rate,
+ .channels = channels,
+ .rate = 48000,
};
t->audio.format = f;
@@ -106,11 +63,13 @@ static int opusmeta_read(void *ctx, phi_track *t)
}
case 1: {
- ffstr vc;
- if (0 != opuscomment_read(t->data_in, &vc)) {
- errlog(t, "opuscomment_read");
+ int r;
+ if (!(r = opus_tags_read(t->data_in.ptr, t->data_in.len))) {
+ errlog(t, "opus_tags_read");
return PHI_ERR;
}
+ ffstr vc = t->data_in;
+ ffstr_shift(&vc, r);
vorbistag_read(t, vc);
o->tags = t->data_in;
diff --git a/src/format/vorbis-meta.h b/src/format/vorbis-meta.h
index d9dfb76..1375160 100644
--- a/src/format/vorbis-meta.h
+++ b/src/format/vorbis-meta.h
@@ -3,66 +3,10 @@
#include
#include
+#include
extern const phi_meta_if phi_metaif;
-enum VORBIS_HDR_T {
- VORBIS_HDR_INFO = 1,
- VORBIS_HDR_COMMENT = 3,
-};
-
-struct vorbis_hdr {
- ffbyte type; // enum VORBIS_HDR_T
- char vorbis[6]; // ="vorbis"
-};
-
-struct vorbis_info {
- ffbyte ver[4]; // =0
- ffbyte channels;
- ffbyte rate[4];
- ffbyte br_max[4];
- ffbyte br_nominal[4];
- ffbyte br_min[4];
- ffbyte blocksize;
- ffbyte framing_bit; // =1
-};
-
-/** Read Vorbis-info packet */
-static int vorbisinfo_read(ffstr pkt, ffuint *channels, ffuint *rate, ffuint *br_nominal)
-{
- const struct vorbis_hdr *h = (struct vorbis_hdr*)pkt.ptr;
- if (sizeof(struct vorbis_hdr) + sizeof(struct vorbis_info) > pkt.len
- || !(h->type == VORBIS_HDR_INFO && !ffmem_cmp(h->vorbis, "vorbis", 6)))
- return -1;
-
- const struct vorbis_info *vi = (struct vorbis_info*)(pkt.ptr + sizeof(struct vorbis_hdr));
- *channels = vi->channels;
- *rate = ffint_le_cpu32_ptr(vi->rate);
- *br_nominal = ffint_le_cpu32_ptr(vi->br_nominal);
- if (0 != ffint_le_cpu32_ptr(vi->ver)
- || *channels == 0
- || *rate == 0
- || vi->framing_bit != 1)
- return -2;
-
- return 0;
-}
-
-/** Check Vorbis-comment packet.
-body: Vorbis-tag data */
-static int vorbiscomment_read(ffstr pkt, ffstr *body)
-{
- const struct vorbis_hdr *h = (struct vorbis_hdr*)pkt.ptr;
- if (sizeof(struct vorbis_hdr) > pkt.len
- || !(h->type == VORBIS_HDR_COMMENT && !ffmem_cmp(h->vorbis, "vorbis", 6)))
- return -1;
-
- *body = pkt;
- ffstr_shift(body, sizeof(struct vorbis_hdr));
- return 0;
-}
-
-
struct vorbismeta {
uint state;
ffvec hdr;
@@ -109,8 +53,8 @@ static int vorbismeta_read(void *ctx, phi_track *t)
switch (v->state) {
case 0: {
uint chan, rate, br;
- if (0 != vorbisinfo_read(t->data_in, &chan, &rate, &br)) {
- errlog(t, "vorbisinfo_read");
+ if (!vorbis_info_read(t->data_in.ptr, t->data_in.len, &chan, &rate, &br)) {
+ errlog(t, "vorbis_info_read");
return PHI_ERR;
}
t->audio.decoder = "Vorbis";
@@ -128,11 +72,13 @@ static int vorbismeta_read(void *ctx, phi_track *t)
}
case 1: {
- ffstr vc;
- if (0 != vorbiscomment_read(t->data_in, &vc)) {
+ int r;
+ if (!(r = vorbis_tags_read(t->data_in.ptr, t->data_in.len))) {
errlog(t, "vorbiscomment_read");
return PHI_ERR;
}
+ ffstr vc = t->data_in;
+ ffstr_shift(&vc, r);
vorbistag_read(t, vc);
v->tags = t->data_in;
diff --git a/src/jni/phiola-jni.c b/src/jni/phiola-jni.c
index 90a1807..4e112fa 100644
--- a/src/jni/phiola-jni.c
+++ b/src/jni/phiola-jni.c
@@ -195,15 +195,15 @@ static char* mod_loading(ffstr name)
char* znames[3] = {};
static const struct map_sz_vptr mod_deps[] = {
- { "aac", "libfdk-aac-phi" },
- { "alac", "libALAC-phi" },
- { "danorm", "libDynamicAudioNormalizer-phi" },
- { "flac", "libFLAC-phi" },
- { "mpeg", "libmpg123-phi" },
- { "opus", "libopus-phi" },
- { "soxr", "libsoxr-phi" },
- { "vorbis", "libvorbis-phi" },
- { "zstd", "libzstd-ffpack" },
+ { "ac-aac", "libfdk-aac-phi" },
+ { "ac-alac", "libALAC-phi" },
+ { "ac-flac", "libFLAC-phi" },
+ { "ac-mpeg", "libmpg123-phi" },
+ { "ac-opus", "libopus-phi" },
+ { "ac-vorbis", "libvorbis-phi" },
+ { "af-danorm", "libDynamicAudioNormalizer-phi" },
+ { "af-soxr", "libsoxr-phi" },
+ { "zstd", "libzstd-ffpack" },
{}
};
const char *dep = map_sz_vptr_findstr(mod_deps, FF_COUNT(mod_deps), name);
diff --git a/src/jni/record.h b/src/jni/record.h
index ca37ec2..028d465 100644
--- a/src/jni/record.h
+++ b/src/jni/record.h
@@ -123,7 +123,7 @@ Java_com_github_stsaz_phiola_Phiola_recStart(JNIEnv *env, jobject thiz, jstring
|| !track->filter(t, x->core->mod("afilter.until"), 0)
|| !track->filter(t, &phi_android_rec_ctl, 0)
|| ((flags & RECF_DANORM)
- && !track->filter(t, x->core->mod("danorm.f"), 0))
+ && !track->filter(t, x->core->mod("af-danorm.f"), 0))
|| !track->filter(t, x->core->mod("afilter.gain"), 0)
|| !track->filter(t, x->core->mod("afilter.auto-conv"), 0)
|| !track->filter(t, x->core->mod("format.auto-write"), 0)
diff --git a/xbuild-debianbullseye.sh b/xbuild-debianBW.sh
similarity index 91%
rename from xbuild-debianbullseye.sh
rename to xbuild-debianBW.sh
index 87db4fb..b7e69ab 100644
--- a/xbuild-debianbullseye.sh
+++ b/xbuild-debianBW.sh
@@ -1,9 +1,9 @@
#!/bin/bash
-# phiola: cross-build on Linux for Debian-bullseye
+# phiola: cross-build on Linux for Debian-bookworm
-IMAGE_NAME=phiola-debianbullseye-builder
-CONTAINER_NAME=phiola_debianbullseye_build
+IMAGE_NAME=phiola-debianBW-builder
+CONTAINER_NAME=phiola_debianBW_build
ARGS=${@@Q}
set -xe
@@ -17,7 +17,7 @@ if ! podman container exists $CONTAINER_NAME ; then
# Create builder image
cat <
Date: Sat, 21 Dec 2024 10:35:02 +0300
Subject: [PATCH 39/59] 2.3-beta3
---
android/phiola/build.gradle | 4 ++--
src/phiola.h | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/android/phiola/build.gradle b/android/phiola/build.gradle
index b125556..83bcd85 100644
--- a/android/phiola/build.gradle
+++ b/android/phiola/build.gradle
@@ -8,8 +8,8 @@ android {
applicationId "com.github.stsaz.phiola"
minSdkVersion 16
targetSdk 33
- versionCode 20302
- versionName '2.3-beta2'
+ versionCode 20303
+ versionName '2.3-beta3'
}
buildFeatures {
diff --git a/src/phiola.h b/src/phiola.h
index 44a56e8..b5e0761 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -6,7 +6,7 @@
#include
#include
-#define PHI_VERSION 20302
+#define PHI_VERSION 20303
/** Inter-module compatibility version.
It must be updated when incompatible changes are made to this file,
From bea0abe8938e9d129707435f5501c849d6a16926 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 4 Jan 2025 10:04:59 +0300
Subject: [PATCH 40/59] + GUI: basic functionality for editing file tags
---
src/format/tag.c | 2 +-
src/gui/actions.h | 4 ++
src/gui/gui.c | 2 +
src/gui/gui.h | 4 +-
src/gui/lang_en.conf | 6 +-
src/gui/meta-info.hpp | 158 ++++++++++++++++++++++++++++++++++++-----
src/gui/ui-gtk.conf | 19 ++++-
src/gui/ui-winapi.conf | 19 ++++-
8 files changed, 190 insertions(+), 24 deletions(-)
diff --git a/src/format/tag.c b/src/format/tag.c
index 6eccfb2..f776b21 100644
--- a/src/format/tag.c
+++ b/src/format/tag.c
@@ -430,7 +430,7 @@ static int tag_vorbis(struct tag_edit *t)
oggread_open(&ogg, -1);
if (!(format = tag_ogg_vtag_read(&ogg, *(ffstr*)&t->buf, &page, &tags_page_off, &tags_page_num, &vtag, &vorbis_codebook)))
goto end;
- vtags_len = vtag.len - (format == 'v') ? 1 : 0;
+ vtags_len = vtag.len - ((format == 'v') ? 1 : 0);
// Copy "Vendor" field
int tag = vorbistagread_process(&vtr, &vtag, &k, &v);
diff --git a/src/gui/actions.h b/src/gui/actions.h
index b9bbe00..f7a5517 100644
--- a/src/gui/actions.h
+++ b/src/gui/actions.h
@@ -1,7 +1,11 @@
// File
X(A_FILE_INFO),
+ X(A_INFO_ADD_ARTIST),
+ X(A_INFO_ADD_TITLE),
X(A_INFO_EDIT),
+ X(A_INFO_EDIT_DONE),
+ X(A_INFO_WRITE),
X(A_FILE_SHOWDIR),
X(A_FILE_DEL),
X(A_SETTINGS_SHOW),
diff --git a/src/gui/gui.c b/src/gui/gui.c
index 07c524e..cc67e55 100644
--- a/src/gui/gui.c
+++ b/src/gui/gui.c
@@ -179,6 +179,7 @@ static void* gui_getctl(void *udata, const ffstr *name)
FFUI_LDR_CTL(struct gui, mhelp),
FFUI_LDR_CTL(struct gui, mpopup),
FFUI_LDR_CTL(struct gui, dlg),
+ FFUI_LDR_CTL(struct gui, mminfo_addtag),
FFUI_LDR_CTL3_PTR(struct gui, wmain, wmain_ctls),
FFUI_LDR_CTL3_PTR(struct gui, winfo, winfo_ctls),
FFUI_LDR_CTL3_PTR(struct gui, wsettings, wsettings_ctls),
@@ -325,6 +326,7 @@ int FFTHREAD_PROCCALL gui_worker(void *param)
void gui_quit()
{
gui_userconf_save();
+ winfo_fin();
wmain_fin();
ffui_post_quitloop();
}
diff --git a/src/gui/gui.h b/src/gui/gui.h
index 4dd08bb..7bcfd99 100644
--- a/src/gui/gui.h
+++ b/src/gui/gui.h
@@ -23,6 +23,7 @@ FF_EXTERN void wmain_fin();
struct gui_winfo;
FF_EXTERN void winfo_init();
+FF_EXTERN void winfo_fin();
FF_EXTERN void winfo_show(uint show, uint idx);
FF_EXTERN void winfo_userconf_write(ffconfw *cw);
FF_EXTERN const struct ffarg winfo_args[];
@@ -77,7 +78,8 @@ struct gui {
, mplay
, mrecord
, mconvert
- , mhelp;
+ , mhelp
+ , mminfo_addtag;
ffui_menu mpopup;
ffui_dialog dlg;
struct gui_wmain* wmain;
diff --git a/src/gui/lang_en.conf b/src/gui/lang_en.conf
index e69e0f0..f4ab0a7 100644
--- a/src/gui/lang_en.conf
+++ b/src/gui/lang_en.conf
@@ -1,9 +1,13 @@
-# phiola v2.0-beta18 GUI English
+# phiola v2.3-beta4 GUI English
MNPhiola "phiola"
MMFile "_File"
MFInfo "Show _Info"
+ FIMAddTag "Add Tag"
+ TAArtist "Artist"
+ TATitle "Title"
+ FIMWriteTagsToFile "Write Tags to File!"
FIName "Name"
FIValue "Value"
MFShowDir "Show Di_rectory"
diff --git a/src/gui/meta-info.hpp b/src/gui/meta-info.hpp
index ac13abc..ec026f1 100644
--- a/src/gui/meta-info.hpp
+++ b/src/gui/meta-info.hpp
@@ -5,15 +5,25 @@
#include
struct gui_winfo {
- ffui_windowxx wnd;
- ffui_viewxx vinfo;
+ ffui_windowxx wnd;
+ ffui_menu mm;
+ ffui_viewxx vinfo;
+
+ xxvec keys; // ffstr[]
+ uint changed;
+ struct phi_queue_entry *qe;
+
+ uint edit_idx;
char *wnd_pos;
uint initialized :1;
};
+#define META_N 4
+
FF_EXTERN const ffui_ldr_ctl winfo_ctls[] = {
FFUI_LDR_CTL(gui_winfo, wnd),
+ FFUI_LDR_CTL(gui_winfo, mm),
FFUI_LDR_CTL(gui_winfo, vinfo),
FFUI_LDR_CTL_END
};
@@ -49,6 +59,8 @@ static void winfo_display(struct phi_queue_entry *qe)
data.ptr = buf;
w->vinfo.clear();
+ w->keys.reset();
+ w->changed = 0;
winfo_addpair("File path", qe->conf.ifile.name);
@@ -79,28 +91,19 @@ static void winfo_display(struct phi_queue_entry *qe)
uint i = 0;
while (gd->metaif->list(meta, &i, &name, &val, 0)) {
winfo_addpair(name, val);
+ ffstr_dupstr(w->keys.push_z(), &name);
}
fflock_unlock((fflock*)&qe->lock);
}
-static void winfo_edit()
-{
-#ifdef FF_WIN
- gui_winfo *w = gg->winfo;
- int i, isub;
- ffui_point pt;
- ffui_cur_pos(&pt);
- if (-1 == (i = ffui_view_hittest(&w->vinfo, &pt, &isub))
- || isub != 1)
- return;
- ffui_view_edit(&w->vinfo, i, 1);
-#endif
-}
-
void winfo_show(uint show, uint idx)
{
gui_winfo *w = gg->winfo;
+ if (w->qe)
+ gd->queue->unref(w->qe);
+ w->qe = NULL;
+
if (!show) {
w->wnd.show(0);
return;
@@ -119,16 +122,124 @@ void winfo_show(uint show, uint idx)
w->wnd.title(qe->conf.ifile.name);
winfo_display(qe);
- gd->queue->unref(qe);
+ w->qe = qe;
+ // keep the entry locked
w->wnd.show(1);
}
+static void winfo_edit(uint idx, const char *new_text)
+{
+ gui_winfo *w = gg->winfo;
+ ffstr val = FFSTR_Z(new_text);
+ uint ki = idx - META_N;
+ if ((int)ki < 0)
+ return;
+ ffstr name = *w->keys.at(ki);
+ gd->metaif->set(&w->qe->conf.meta, name, val, PHI_META_REPLACE);
+ if (ki >= 32)
+ warnlog("can write only up to 32 tags");
+ ffbit_set32(&w->changed, ki);
+ w->vinfo.set(idx, 0, xxvec().add_f("%S (*)", &name).str());
+ w->vinfo.set(idx, 1, val);
+}
+
+static void winfo_tag_add(ffstr name)
+{
+ gui_winfo *w = gg->winfo;
+ if (!w->qe) return;
+
+ ffstr val;
+ if (!gd->metaif->find(&w->qe->conf.meta, name, &val, 0)) {
+ warnlog("tag already exists: %S", &name);
+ return;
+ }
+ val = FFSTR_Z("");
+ gd->metaif->set(&w->qe->conf.meta, name, val, 0);
+ winfo_addpair(name, val);
+ ffstr_dupstr(w->keys.push_z(), &name);
+ ffbit_set32(&w->changed, w->keys.len - 1);
+}
+
+/** Get the list of modified or newly added tags and write them to file */
+static void winfo_write()
+{
+ gui_winfo *w = gg->winfo;
+ if (!w->qe) return;
+
+ const phi_tag_if *tag = (phi_tag_if*)core->mod("format.tag");
+
+ xxvec m;
+ ffstr k, v;
+ uint bits = w->changed, i;
+ while (bits) {
+ i = ffbit_rfind32(bits) - 1;
+ ffbit_reset32(&bits, i);
+ k = *w->keys.at(i);
+ if (!gd->metaif->find(&w->qe->conf.meta, k, &v, 0)) {
+ ffvec s = {};
+ ffvec_addfmt(&s, "%S=%S", &k, &v);
+ *m.push() = *(ffstr*)&s;
+ }
+ }
+
+ if (!m.len)
+ return;
+
+ struct phi_tag_conf conf = {};
+ conf.filename = w->qe->conf.ifile.name;
+ conf.meta = m.slice();
+ if (!tag->edit(&conf)) {
+ gd->metaif->destroy(&w->qe->conf.meta);
+ w->keys.reset();
+ w->changed = 0;
+ w->vinfo.clear();
+ if (w->qe)
+ gd->queue->unref(w->qe);
+ w->qe = NULL;
+ }
+
+ FFSLICE_FOREACH_T(&m, ffstr_free, ffstr);
+}
+
static void winfo_action(ffui_window *wnd, int id)
{
+ gui_winfo *w = gg->winfo;
+ ffstr name;
+
switch (id) {
- case A_INFO_EDIT:
- winfo_edit(); break;
+ case A_INFO_EDIT: {
+#ifdef FF_WIN
+ int i, isub;
+ ffui_point pt;
+ ffui_cur_pos(&pt);
+ if (-1 == (i = ffui_view_hittest(&w->vinfo, &pt, &isub))
+ || isub != 1)
+ break;
+ ffui_view_edit(&w->vinfo, i, 1);
+ w->edit_idx = i;
+#endif
+ break;
+ }
+
+ case A_INFO_EDIT_DONE:
+#ifdef FF_WIN
+ winfo_edit(w->edit_idx, w->vinfo.text);
+#else
+ winfo_edit(w->vinfo.edited.idx, w->vinfo.edited.new_text);
+#endif
+ break;
+
+ case A_INFO_ADD_ARTIST:
+ ffstr_setz(&name, "artist"); goto tag_add;
+ case A_INFO_ADD_TITLE:
+ ffstr_setz(&name, "title"); goto tag_add;
+ tag_add:
+ winfo_tag_add(name);
+ break;
+
+ case A_INFO_WRITE:
+ gui_core_task(winfo_write); break;
}
}
@@ -137,5 +248,14 @@ void winfo_init()
gui_winfo *w = ffmem_new(gui_winfo);
w->wnd.hide_on_close = 1;
w->wnd.on_action = winfo_action;
+ w->vinfo.edit_id = A_INFO_EDIT_DONE;
gg->winfo = w;
}
+
+void winfo_fin()
+{
+ gui_winfo *w = gg->winfo;
+ FFSLICE_FOREACH_T(&w->keys, ffstr_free, ffstr);
+ if (w->qe)
+ gd->queue->unref(w->qe);
+}
diff --git a/src/gui/ui-gtk.conf b/src/gui/ui-gtk.conf
index f256150..9bf9e7a 100644
--- a/src/gui/ui-gtk.conf
+++ b/src/gui/ui-gtk.conf
@@ -1,4 +1,4 @@
-# phiola v2.1-beta3 GUI
+# phiola v2.3-beta4 GUI
include_language {
# "lang_.conf"
@@ -318,9 +318,26 @@ window wmain {
}
}
+menu mminfo_addtag {
+ item $TAArtist {
+ action A_INFO_ADD_ARTIST
+ }
+ item $TATitle {
+ action A_INFO_ADD_TITLE
+ }
+}
+
window winfo {
position 300 100 300 500
popupfor wmain
+ mainmenu mm {
+ item $FIMAddTag {
+ submenu mminfo_addtag
+ }
+ item $FIMWriteTagsToFile {
+ action A_INFO_WRITE
+ }
+ }
listview vinfo {
style editable
diff --git a/src/gui/ui-winapi.conf b/src/gui/ui-winapi.conf
index c624080..a440070 100644
--- a/src/gui/ui-winapi.conf
+++ b/src/gui/ui-winapi.conf
@@ -1,4 +1,4 @@
-# phiola v2.1-beta3 GUI
+# phiola v2.3-beta4 GUI
include_language {
# "lang_.conf"
@@ -352,9 +352,26 @@ window wmain {
}
}
+menu mminfo_addtag {
+ item $TAArtist {
+ action A_INFO_ADD_ARTIST
+ }
+ item $TATitle {
+ action A_INFO_ADD_TITLE
+ }
+}
+
window winfo {
position 300 100 300 500
popupfor wmain
+ mainmenu mm {
+ item $FIMAddTag {
+ submenu mminfo_addtag
+ }
+ item $FIMWriteTagsToFile {
+ action A_INFO_WRITE
+ }
+ }
listview vinfo {
size 100 100
From 798d99d037138a05ea14631c63411977d02adf36 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 4 Jan 2025 10:04:59 +0300
Subject: [PATCH 41/59] * .cue read: can specify range of tracks, e.g. `-tracks
1-5`
---
README.md | 2 +-
src/exe/cmd.h | 23 ++++++++++++++++++-----
test.sh | 1 +
3 files changed, 20 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index 185d639..31774a4 100644
--- a/README.md
+++ b/README.md
@@ -204,7 +204,7 @@ phiola convert "My Recordings" -include "*.wav" -o @filepath/@filename.flac -pre
phiola convert -copy -seek 1:0 -until 2:0 input.mp3 -o output.mp3
# Extract (100% accurately) several tracks from a .cue file
-phiola convert input.cue -tracks 3,7,13 -o "@tracknumber. @artist - @title.flac"
+phiola convert input.cue -tracks 3,5-7,13 -o "@tracknumber. @artist - @title.flac"
# Increase .wav file's volume by 6 dB
phiola convert file.wav -gain 6 -o file-louder.wav
diff --git a/src/exe/cmd.h b/src/exe/cmd.h
index 01d328d..86aad98 100644
--- a/src/exe/cmd.h
+++ b/src/exe/cmd.h
@@ -43,15 +43,28 @@ static int cmd_time_value(ffuint64 *msec, ffstr s)
static int cmd_tracks(ffvec *tracks, ffstr s)
{
+ ffstr it;
while (s.len) {
- ffstr it;
- ffstr_splitby(&s, ',', &it, &s);
- uint n;
+ ssize_t r = ffstr_splitbyany(&s, ",-", &it, &s);
+ uint n, n2;
if (!ffstr_to_uint32(&it, &n))
- return _ffargs_err(&x->cmd, 1, "incorrect track number '%S'", &it);
- *ffvec_pushT(tracks, uint) = n;
+ goto err;
+ if (r > 0 && it.ptr[it.len] == '-') {
+ ffstr_splitby(&s, ',', &it, &s);
+ if (!ffstr_to_uint32(&it, &n2))
+ goto err;
+ ffvec_growT(tracks, n2 - n + 1, uint);
+ while (n <= n2) {
+ *(uint*)_ffvec_push(tracks, 4) = n++;
+ }
+ } else {
+ *ffvec_pushT(tracks, uint) = n;
+ }
}
return 0;
+
+err:
+ return _ffargs_err(&x->cmd, 1, "incorrect track number '%S'", &it);
}
static void cmd_meta_set(ffvec *dst, const ffvec *src)
diff --git a/test.sh b/test.sh
index 55e5104..a411ab0 100644
--- a/test.sh
+++ b/test.sh
@@ -544,6 +544,7 @@ EOF
fi
./phiola i cue.cue -tracks 2,3 | grep 'Artist - T2'
./phiola i cue.cue -tracks 2,3 | grep 'Artist - T3'
+ ./phiola i cue.cue -tracks 1-3,4
cat <cue.cue
PERFORMER Artist
From 39d78755813fca37d9b67fb6ea235f0929a028a1 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 4 Jan 2025 10:04:59 +0300
Subject: [PATCH 42/59] + `info -loudness`: analyze audio loudness
! removed `info -peaks_crc` function
---
3pt/crc/crc.c | 94 -------
3pt/crc/crc32_table_be.h | 525 ------------------------------------
3pt/crc/crc32_table_le.h | 525 ------------------------------------
README.md | 6 +-
alib3/EBUR128/Makefile | 41 +++
alib3/EBUR128/ebur128-phi.c | 95 +++++++
alib3/EBUR128/ebur128-phi.h | 47 ++++
alib3/Makefile | 5 +
src/afilter/Makefile | 10 +-
src/afilter/afilter.c | 1 +
src/afilter/auto-conv.h | 55 +++-
src/afilter/loudness.c | 90 +++++++
src/afilter/peaks.c | 80 ++----
src/core/queue-entry.h | 8 +-
src/exe/cmd.h | 2 +-
src/exe/info.h | 13 +-
src/jni/phiola-jni.c | 1 +
src/phiola.h | 3 +-
src/track.h | 1 +
test.sh | 2 +-
20 files changed, 383 insertions(+), 1221 deletions(-)
delete mode 100644 3pt/crc/crc.c
delete mode 100644 3pt/crc/crc32_table_be.h
delete mode 100644 3pt/crc/crc32_table_le.h
create mode 100644 alib3/EBUR128/Makefile
create mode 100644 alib3/EBUR128/ebur128-phi.c
create mode 100644 alib3/EBUR128/ebur128-phi.h
create mode 100644 src/afilter/loudness.c
diff --git a/3pt/crc/crc.c b/3pt/crc/crc.c
deleted file mode 100644
index a893813..0000000
--- a/3pt/crc/crc.c
+++ /dev/null
@@ -1,94 +0,0 @@
-/** Fast CRC32 implementation using 8k table */
-
-#include
-
-#ifdef WORDS_BIGENDIAN
-# include "crc32_table_be.h"
-#else
-# include "crc32_table_le.h"
-#endif
-
-
-/* liblzma/check/crc_macros.h */
-
-#ifdef WORDS_BIGENDIAN
-# define A(x) ((x) >> 24)
-# define B(x) (((x) >> 16) & 0xFF)
-# define C(x) (((x) >> 8) & 0xFF)
-# define D(x) ((x) & 0xFF)
-
-# define S8(x) ((x) << 8)
-# define S32(x) ((x) << 32)
-
-#else
-# define A(x) ((x) & 0xFF)
-# define B(x) (((x) >> 8) & 0xFF)
-# define C(x) (((x) >> 16) & 0xFF)
-# define D(x) ((x) >> 24)
-
-# define S8(x) ((x) >> 8)
-# define S32(x) ((x) >> 32)
-#endif
-
-
-/* liblzma/check/crc32_fast.c by Lasse Collin */
-
-// If you make any changes, do some benchmarking! Seemingly unrelated
-// changes can very easily ruin the performance (and very probably is
-// very compiler dependent).
-unsigned int crc32(const unsigned char *buf, size_t size, unsigned int crc)
-{
- crc = ~crc;
-
-#ifdef WORDS_BIGENDIAN
- crc = bswap32(crc);
-#endif
-
- if (size > 8) {
- // Fix the alignment, if needed. The if statement above
- // ensures that this won't read past the end of buf[].
- while ((size_t)(buf) & 7) {
- crc = crc32_table[0][*buf++ ^ A(crc)] ^ S8(crc);
- --size;
- }
-
- // Calculate the position where to stop.
- const unsigned char *const limit = buf + (size & ~(size_t)(7));
-
- // Calculate how many bytes must be calculated separately
- // before returning the result.
- size &= (size_t)(7);
-
- // Calculate the CRC32 using the slice-by-eight algorithm.
- while (buf < limit) {
- crc ^= *(const unsigned int *)(buf);
- buf += 4;
-
- crc = crc32_table[7][A(crc)]
- ^ crc32_table[6][B(crc)]
- ^ crc32_table[5][C(crc)]
- ^ crc32_table[4][D(crc)];
-
- const unsigned int tmp = *(const unsigned int *)(buf);
- buf += 4;
-
- // At least with some compilers, it is critical for
- // performance, that the crc variable is XORed
- // between the two table-lookup pairs.
- crc = crc32_table[3][A(tmp)]
- ^ crc32_table[2][B(tmp)]
- ^ crc
- ^ crc32_table[1][C(tmp)]
- ^ crc32_table[0][D(tmp)];
- }
- }
-
- while (size-- != 0)
- crc = crc32_table[0][*buf++ ^ A(crc)] ^ S8(crc);
-
-#ifdef WORDS_BIGENDIAN
- crc = bswap32(crc);
-#endif
-
- return ~crc;
-}
diff --git a/3pt/crc/crc32_table_be.h b/3pt/crc/crc32_table_be.h
deleted file mode 100644
index eb347be..0000000
--- a/3pt/crc/crc32_table_be.h
+++ /dev/null
@@ -1,525 +0,0 @@
-/* This file has been automatically generated by crc32_tablegen.c. */
-
-const unsigned int crc32_table[8][256] = {
- {
- 0x00000000, 0x96300777, 0x2C610EEE, 0xBA510999,
- 0x19C46D07, 0x8FF46A70, 0x35A563E9, 0xA395649E,
- 0x3288DB0E, 0xA4B8DC79, 0x1EE9D5E0, 0x88D9D297,
- 0x2B4CB609, 0xBD7CB17E, 0x072DB8E7, 0x911DBF90,
- 0x6410B71D, 0xF220B06A, 0x4871B9F3, 0xDE41BE84,
- 0x7DD4DA1A, 0xEBE4DD6D, 0x51B5D4F4, 0xC785D383,
- 0x56986C13, 0xC0A86B64, 0x7AF962FD, 0xECC9658A,
- 0x4F5C0114, 0xD96C0663, 0x633D0FFA, 0xF50D088D,
- 0xC8206E3B, 0x5E10694C, 0xE44160D5, 0x727167A2,
- 0xD1E4033C, 0x47D4044B, 0xFD850DD2, 0x6BB50AA5,
- 0xFAA8B535, 0x6C98B242, 0xD6C9BBDB, 0x40F9BCAC,
- 0xE36CD832, 0x755CDF45, 0xCF0DD6DC, 0x593DD1AB,
- 0xAC30D926, 0x3A00DE51, 0x8051D7C8, 0x1661D0BF,
- 0xB5F4B421, 0x23C4B356, 0x9995BACF, 0x0FA5BDB8,
- 0x9EB80228, 0x0888055F, 0xB2D90CC6, 0x24E90BB1,
- 0x877C6F2F, 0x114C6858, 0xAB1D61C1, 0x3D2D66B6,
- 0x9041DC76, 0x0671DB01, 0xBC20D298, 0x2A10D5EF,
- 0x8985B171, 0x1FB5B606, 0xA5E4BF9F, 0x33D4B8E8,
- 0xA2C90778, 0x34F9000F, 0x8EA80996, 0x18980EE1,
- 0xBB0D6A7F, 0x2D3D6D08, 0x976C6491, 0x015C63E6,
- 0xF4516B6B, 0x62616C1C, 0xD8306585, 0x4E0062F2,
- 0xED95066C, 0x7BA5011B, 0xC1F40882, 0x57C40FF5,
- 0xC6D9B065, 0x50E9B712, 0xEAB8BE8B, 0x7C88B9FC,
- 0xDF1DDD62, 0x492DDA15, 0xF37CD38C, 0x654CD4FB,
- 0x5861B24D, 0xCE51B53A, 0x7400BCA3, 0xE230BBD4,
- 0x41A5DF4A, 0xD795D83D, 0x6DC4D1A4, 0xFBF4D6D3,
- 0x6AE96943, 0xFCD96E34, 0x468867AD, 0xD0B860DA,
- 0x732D0444, 0xE51D0333, 0x5F4C0AAA, 0xC97C0DDD,
- 0x3C710550, 0xAA410227, 0x10100BBE, 0x86200CC9,
- 0x25B56857, 0xB3856F20, 0x09D466B9, 0x9FE461CE,
- 0x0EF9DE5E, 0x98C9D929, 0x2298D0B0, 0xB4A8D7C7,
- 0x173DB359, 0x810DB42E, 0x3B5CBDB7, 0xAD6CBAC0,
- 0x2083B8ED, 0xB6B3BF9A, 0x0CE2B603, 0x9AD2B174,
- 0x3947D5EA, 0xAF77D29D, 0x1526DB04, 0x8316DC73,
- 0x120B63E3, 0x843B6494, 0x3E6A6D0D, 0xA85A6A7A,
- 0x0BCF0EE4, 0x9DFF0993, 0x27AE000A, 0xB19E077D,
- 0x44930FF0, 0xD2A30887, 0x68F2011E, 0xFEC20669,
- 0x5D5762F7, 0xCB676580, 0x71366C19, 0xE7066B6E,
- 0x761BD4FE, 0xE02BD389, 0x5A7ADA10, 0xCC4ADD67,
- 0x6FDFB9F9, 0xF9EFBE8E, 0x43BEB717, 0xD58EB060,
- 0xE8A3D6D6, 0x7E93D1A1, 0xC4C2D838, 0x52F2DF4F,
- 0xF167BBD1, 0x6757BCA6, 0xDD06B53F, 0x4B36B248,
- 0xDA2B0DD8, 0x4C1B0AAF, 0xF64A0336, 0x607A0441,
- 0xC3EF60DF, 0x55DF67A8, 0xEF8E6E31, 0x79BE6946,
- 0x8CB361CB, 0x1A8366BC, 0xA0D26F25, 0x36E26852,
- 0x95770CCC, 0x03470BBB, 0xB9160222, 0x2F260555,
- 0xBE3BBAC5, 0x280BBDB2, 0x925AB42B, 0x046AB35C,
- 0xA7FFD7C2, 0x31CFD0B5, 0x8B9ED92C, 0x1DAEDE5B,
- 0xB0C2649B, 0x26F263EC, 0x9CA36A75, 0x0A936D02,
- 0xA906099C, 0x3F360EEB, 0x85670772, 0x13570005,
- 0x824ABF95, 0x147AB8E2, 0xAE2BB17B, 0x381BB60C,
- 0x9B8ED292, 0x0DBED5E5, 0xB7EFDC7C, 0x21DFDB0B,
- 0xD4D2D386, 0x42E2D4F1, 0xF8B3DD68, 0x6E83DA1F,
- 0xCD16BE81, 0x5B26B9F6, 0xE177B06F, 0x7747B718,
- 0xE65A0888, 0x706A0FFF, 0xCA3B0666, 0x5C0B0111,
- 0xFF9E658F, 0x69AE62F8, 0xD3FF6B61, 0x45CF6C16,
- 0x78E20AA0, 0xEED20DD7, 0x5483044E, 0xC2B30339,
- 0x612667A7, 0xF71660D0, 0x4D476949, 0xDB776E3E,
- 0x4A6AD1AE, 0xDC5AD6D9, 0x660BDF40, 0xF03BD837,
- 0x53AEBCA9, 0xC59EBBDE, 0x7FCFB247, 0xE9FFB530,
- 0x1CF2BDBD, 0x8AC2BACA, 0x3093B353, 0xA6A3B424,
- 0x0536D0BA, 0x9306D7CD, 0x2957DE54, 0xBF67D923,
- 0x2E7A66B3, 0xB84A61C4, 0x021B685D, 0x942B6F2A,
- 0x37BE0BB4, 0xA18E0CC3, 0x1BDF055A, 0x8DEF022D
- }, {
- 0x00000000, 0x41311B19, 0x82623632, 0xC3532D2B,
- 0x04C56C64, 0x45F4777D, 0x86A75A56, 0xC796414F,
- 0x088AD9C8, 0x49BBC2D1, 0x8AE8EFFA, 0xCBD9F4E3,
- 0x0C4FB5AC, 0x4D7EAEB5, 0x8E2D839E, 0xCF1C9887,
- 0x5112C24A, 0x1023D953, 0xD370F478, 0x9241EF61,
- 0x55D7AE2E, 0x14E6B537, 0xD7B5981C, 0x96848305,
- 0x59981B82, 0x18A9009B, 0xDBFA2DB0, 0x9ACB36A9,
- 0x5D5D77E6, 0x1C6C6CFF, 0xDF3F41D4, 0x9E0E5ACD,
- 0xA2248495, 0xE3159F8C, 0x2046B2A7, 0x6177A9BE,
- 0xA6E1E8F1, 0xE7D0F3E8, 0x2483DEC3, 0x65B2C5DA,
- 0xAAAE5D5D, 0xEB9F4644, 0x28CC6B6F, 0x69FD7076,
- 0xAE6B3139, 0xEF5A2A20, 0x2C09070B, 0x6D381C12,
- 0xF33646DF, 0xB2075DC6, 0x715470ED, 0x30656BF4,
- 0xF7F32ABB, 0xB6C231A2, 0x75911C89, 0x34A00790,
- 0xFBBC9F17, 0xBA8D840E, 0x79DEA925, 0x38EFB23C,
- 0xFF79F373, 0xBE48E86A, 0x7D1BC541, 0x3C2ADE58,
- 0x054F79F0, 0x447E62E9, 0x872D4FC2, 0xC61C54DB,
- 0x018A1594, 0x40BB0E8D, 0x83E823A6, 0xC2D938BF,
- 0x0DC5A038, 0x4CF4BB21, 0x8FA7960A, 0xCE968D13,
- 0x0900CC5C, 0x4831D745, 0x8B62FA6E, 0xCA53E177,
- 0x545DBBBA, 0x156CA0A3, 0xD63F8D88, 0x970E9691,
- 0x5098D7DE, 0x11A9CCC7, 0xD2FAE1EC, 0x93CBFAF5,
- 0x5CD76272, 0x1DE6796B, 0xDEB55440, 0x9F844F59,
- 0x58120E16, 0x1923150F, 0xDA703824, 0x9B41233D,
- 0xA76BFD65, 0xE65AE67C, 0x2509CB57, 0x6438D04E,
- 0xA3AE9101, 0xE29F8A18, 0x21CCA733, 0x60FDBC2A,
- 0xAFE124AD, 0xEED03FB4, 0x2D83129F, 0x6CB20986,
- 0xAB2448C9, 0xEA1553D0, 0x29467EFB, 0x687765E2,
- 0xF6793F2F, 0xB7482436, 0x741B091D, 0x352A1204,
- 0xF2BC534B, 0xB38D4852, 0x70DE6579, 0x31EF7E60,
- 0xFEF3E6E7, 0xBFC2FDFE, 0x7C91D0D5, 0x3DA0CBCC,
- 0xFA368A83, 0xBB07919A, 0x7854BCB1, 0x3965A7A8,
- 0x4B98833B, 0x0AA99822, 0xC9FAB509, 0x88CBAE10,
- 0x4F5DEF5F, 0x0E6CF446, 0xCD3FD96D, 0x8C0EC274,
- 0x43125AF3, 0x022341EA, 0xC1706CC1, 0x804177D8,
- 0x47D73697, 0x06E62D8E, 0xC5B500A5, 0x84841BBC,
- 0x1A8A4171, 0x5BBB5A68, 0x98E87743, 0xD9D96C5A,
- 0x1E4F2D15, 0x5F7E360C, 0x9C2D1B27, 0xDD1C003E,
- 0x120098B9, 0x533183A0, 0x9062AE8B, 0xD153B592,
- 0x16C5F4DD, 0x57F4EFC4, 0x94A7C2EF, 0xD596D9F6,
- 0xE9BC07AE, 0xA88D1CB7, 0x6BDE319C, 0x2AEF2A85,
- 0xED796BCA, 0xAC4870D3, 0x6F1B5DF8, 0x2E2A46E1,
- 0xE136DE66, 0xA007C57F, 0x6354E854, 0x2265F34D,
- 0xE5F3B202, 0xA4C2A91B, 0x67918430, 0x26A09F29,
- 0xB8AEC5E4, 0xF99FDEFD, 0x3ACCF3D6, 0x7BFDE8CF,
- 0xBC6BA980, 0xFD5AB299, 0x3E099FB2, 0x7F3884AB,
- 0xB0241C2C, 0xF1150735, 0x32462A1E, 0x73773107,
- 0xB4E17048, 0xF5D06B51, 0x3683467A, 0x77B25D63,
- 0x4ED7FACB, 0x0FE6E1D2, 0xCCB5CCF9, 0x8D84D7E0,
- 0x4A1296AF, 0x0B238DB6, 0xC870A09D, 0x8941BB84,
- 0x465D2303, 0x076C381A, 0xC43F1531, 0x850E0E28,
- 0x42984F67, 0x03A9547E, 0xC0FA7955, 0x81CB624C,
- 0x1FC53881, 0x5EF42398, 0x9DA70EB3, 0xDC9615AA,
- 0x1B0054E5, 0x5A314FFC, 0x996262D7, 0xD85379CE,
- 0x174FE149, 0x567EFA50, 0x952DD77B, 0xD41CCC62,
- 0x138A8D2D, 0x52BB9634, 0x91E8BB1F, 0xD0D9A006,
- 0xECF37E5E, 0xADC26547, 0x6E91486C, 0x2FA05375,
- 0xE836123A, 0xA9070923, 0x6A542408, 0x2B653F11,
- 0xE479A796, 0xA548BC8F, 0x661B91A4, 0x272A8ABD,
- 0xE0BCCBF2, 0xA18DD0EB, 0x62DEFDC0, 0x23EFE6D9,
- 0xBDE1BC14, 0xFCD0A70D, 0x3F838A26, 0x7EB2913F,
- 0xB924D070, 0xF815CB69, 0x3B46E642, 0x7A77FD5B,
- 0xB56B65DC, 0xF45A7EC5, 0x370953EE, 0x763848F7,
- 0xB1AE09B8, 0xF09F12A1, 0x33CC3F8A, 0x72FD2493
- }, {
- 0x00000000, 0x376AC201, 0x6ED48403, 0x59BE4602,
- 0xDCA80907, 0xEBC2CB06, 0xB27C8D04, 0x85164F05,
- 0xB851130E, 0x8F3BD10F, 0xD685970D, 0xE1EF550C,
- 0x64F91A09, 0x5393D808, 0x0A2D9E0A, 0x3D475C0B,
- 0x70A3261C, 0x47C9E41D, 0x1E77A21F, 0x291D601E,
- 0xAC0B2F1B, 0x9B61ED1A, 0xC2DFAB18, 0xF5B56919,
- 0xC8F23512, 0xFF98F713, 0xA626B111, 0x914C7310,
- 0x145A3C15, 0x2330FE14, 0x7A8EB816, 0x4DE47A17,
- 0xE0464D38, 0xD72C8F39, 0x8E92C93B, 0xB9F80B3A,
- 0x3CEE443F, 0x0B84863E, 0x523AC03C, 0x6550023D,
- 0x58175E36, 0x6F7D9C37, 0x36C3DA35, 0x01A91834,
- 0x84BF5731, 0xB3D59530, 0xEA6BD332, 0xDD011133,
- 0x90E56B24, 0xA78FA925, 0xFE31EF27, 0xC95B2D26,
- 0x4C4D6223, 0x7B27A022, 0x2299E620, 0x15F32421,
- 0x28B4782A, 0x1FDEBA2B, 0x4660FC29, 0x710A3E28,
- 0xF41C712D, 0xC376B32C, 0x9AC8F52E, 0xADA2372F,
- 0xC08D9A70, 0xF7E75871, 0xAE591E73, 0x9933DC72,
- 0x1C259377, 0x2B4F5176, 0x72F11774, 0x459BD575,
- 0x78DC897E, 0x4FB64B7F, 0x16080D7D, 0x2162CF7C,
- 0xA4748079, 0x931E4278, 0xCAA0047A, 0xFDCAC67B,
- 0xB02EBC6C, 0x87447E6D, 0xDEFA386F, 0xE990FA6E,
- 0x6C86B56B, 0x5BEC776A, 0x02523168, 0x3538F369,
- 0x087FAF62, 0x3F156D63, 0x66AB2B61, 0x51C1E960,
- 0xD4D7A665, 0xE3BD6464, 0xBA032266, 0x8D69E067,
- 0x20CBD748, 0x17A11549, 0x4E1F534B, 0x7975914A,
- 0xFC63DE4F, 0xCB091C4E, 0x92B75A4C, 0xA5DD984D,
- 0x989AC446, 0xAFF00647, 0xF64E4045, 0xC1248244,
- 0x4432CD41, 0x73580F40, 0x2AE64942, 0x1D8C8B43,
- 0x5068F154, 0x67023355, 0x3EBC7557, 0x09D6B756,
- 0x8CC0F853, 0xBBAA3A52, 0xE2147C50, 0xD57EBE51,
- 0xE839E25A, 0xDF53205B, 0x86ED6659, 0xB187A458,
- 0x3491EB5D, 0x03FB295C, 0x5A456F5E, 0x6D2FAD5F,
- 0x801B35E1, 0xB771F7E0, 0xEECFB1E2, 0xD9A573E3,
- 0x5CB33CE6, 0x6BD9FEE7, 0x3267B8E5, 0x050D7AE4,
- 0x384A26EF, 0x0F20E4EE, 0x569EA2EC, 0x61F460ED,
- 0xE4E22FE8, 0xD388EDE9, 0x8A36ABEB, 0xBD5C69EA,
- 0xF0B813FD, 0xC7D2D1FC, 0x9E6C97FE, 0xA90655FF,
- 0x2C101AFA, 0x1B7AD8FB, 0x42C49EF9, 0x75AE5CF8,
- 0x48E900F3, 0x7F83C2F2, 0x263D84F0, 0x115746F1,
- 0x944109F4, 0xA32BCBF5, 0xFA958DF7, 0xCDFF4FF6,
- 0x605D78D9, 0x5737BAD8, 0x0E89FCDA, 0x39E33EDB,
- 0xBCF571DE, 0x8B9FB3DF, 0xD221F5DD, 0xE54B37DC,
- 0xD80C6BD7, 0xEF66A9D6, 0xB6D8EFD4, 0x81B22DD5,
- 0x04A462D0, 0x33CEA0D1, 0x6A70E6D3, 0x5D1A24D2,
- 0x10FE5EC5, 0x27949CC4, 0x7E2ADAC6, 0x494018C7,
- 0xCC5657C2, 0xFB3C95C3, 0xA282D3C1, 0x95E811C0,
- 0xA8AF4DCB, 0x9FC58FCA, 0xC67BC9C8, 0xF1110BC9,
- 0x740744CC, 0x436D86CD, 0x1AD3C0CF, 0x2DB902CE,
- 0x4096AF91, 0x77FC6D90, 0x2E422B92, 0x1928E993,
- 0x9C3EA696, 0xAB546497, 0xF2EA2295, 0xC580E094,
- 0xF8C7BC9F, 0xCFAD7E9E, 0x9613389C, 0xA179FA9D,
- 0x246FB598, 0x13057799, 0x4ABB319B, 0x7DD1F39A,
- 0x3035898D, 0x075F4B8C, 0x5EE10D8E, 0x698BCF8F,
- 0xEC9D808A, 0xDBF7428B, 0x82490489, 0xB523C688,
- 0x88649A83, 0xBF0E5882, 0xE6B01E80, 0xD1DADC81,
- 0x54CC9384, 0x63A65185, 0x3A181787, 0x0D72D586,
- 0xA0D0E2A9, 0x97BA20A8, 0xCE0466AA, 0xF96EA4AB,
- 0x7C78EBAE, 0x4B1229AF, 0x12AC6FAD, 0x25C6ADAC,
- 0x1881F1A7, 0x2FEB33A6, 0x765575A4, 0x413FB7A5,
- 0xC429F8A0, 0xF3433AA1, 0xAAFD7CA3, 0x9D97BEA2,
- 0xD073C4B5, 0xE71906B4, 0xBEA740B6, 0x89CD82B7,
- 0x0CDBCDB2, 0x3BB10FB3, 0x620F49B1, 0x55658BB0,
- 0x6822D7BB, 0x5F4815BA, 0x06F653B8, 0x319C91B9,
- 0xB48ADEBC, 0x83E01CBD, 0xDA5E5ABF, 0xED3498BE
- }, {
- 0x00000000, 0x6567BCB8, 0x8BC809AA, 0xEEAFB512,
- 0x5797628F, 0x32F0DE37, 0xDC5F6B25, 0xB938D79D,
- 0xEF28B4C5, 0x8A4F087D, 0x64E0BD6F, 0x018701D7,
- 0xB8BFD64A, 0xDDD86AF2, 0x3377DFE0, 0x56106358,
- 0x9F571950, 0xFA30A5E8, 0x149F10FA, 0x71F8AC42,
- 0xC8C07BDF, 0xADA7C767, 0x43087275, 0x266FCECD,
- 0x707FAD95, 0x1518112D, 0xFBB7A43F, 0x9ED01887,
- 0x27E8CF1A, 0x428F73A2, 0xAC20C6B0, 0xC9477A08,
- 0x3EAF32A0, 0x5BC88E18, 0xB5673B0A, 0xD00087B2,
- 0x6938502F, 0x0C5FEC97, 0xE2F05985, 0x8797E53D,
- 0xD1878665, 0xB4E03ADD, 0x5A4F8FCF, 0x3F283377,
- 0x8610E4EA, 0xE3775852, 0x0DD8ED40, 0x68BF51F8,
- 0xA1F82BF0, 0xC49F9748, 0x2A30225A, 0x4F579EE2,
- 0xF66F497F, 0x9308F5C7, 0x7DA740D5, 0x18C0FC6D,
- 0x4ED09F35, 0x2BB7238D, 0xC518969F, 0xA07F2A27,
- 0x1947FDBA, 0x7C204102, 0x928FF410, 0xF7E848A8,
- 0x3D58149B, 0x583FA823, 0xB6901D31, 0xD3F7A189,
- 0x6ACF7614, 0x0FA8CAAC, 0xE1077FBE, 0x8460C306,
- 0xD270A05E, 0xB7171CE6, 0x59B8A9F4, 0x3CDF154C,
- 0x85E7C2D1, 0xE0807E69, 0x0E2FCB7B, 0x6B4877C3,
- 0xA20F0DCB, 0xC768B173, 0x29C70461, 0x4CA0B8D9,
- 0xF5986F44, 0x90FFD3FC, 0x7E5066EE, 0x1B37DA56,
- 0x4D27B90E, 0x284005B6, 0xC6EFB0A4, 0xA3880C1C,
- 0x1AB0DB81, 0x7FD76739, 0x9178D22B, 0xF41F6E93,
- 0x03F7263B, 0x66909A83, 0x883F2F91, 0xED589329,
- 0x546044B4, 0x3107F80C, 0xDFA84D1E, 0xBACFF1A6,
- 0xECDF92FE, 0x89B82E46, 0x67179B54, 0x027027EC,
- 0xBB48F071, 0xDE2F4CC9, 0x3080F9DB, 0x55E74563,
- 0x9CA03F6B, 0xF9C783D3, 0x176836C1, 0x720F8A79,
- 0xCB375DE4, 0xAE50E15C, 0x40FF544E, 0x2598E8F6,
- 0x73888BAE, 0x16EF3716, 0xF8408204, 0x9D273EBC,
- 0x241FE921, 0x41785599, 0xAFD7E08B, 0xCAB05C33,
- 0x3BB659ED, 0x5ED1E555, 0xB07E5047, 0xD519ECFF,
- 0x6C213B62, 0x094687DA, 0xE7E932C8, 0x828E8E70,
- 0xD49EED28, 0xB1F95190, 0x5F56E482, 0x3A31583A,
- 0x83098FA7, 0xE66E331F, 0x08C1860D, 0x6DA63AB5,
- 0xA4E140BD, 0xC186FC05, 0x2F294917, 0x4A4EF5AF,
- 0xF3762232, 0x96119E8A, 0x78BE2B98, 0x1DD99720,
- 0x4BC9F478, 0x2EAE48C0, 0xC001FDD2, 0xA566416A,
- 0x1C5E96F7, 0x79392A4F, 0x97969F5D, 0xF2F123E5,
- 0x05196B4D, 0x607ED7F5, 0x8ED162E7, 0xEBB6DE5F,
- 0x528E09C2, 0x37E9B57A, 0xD9460068, 0xBC21BCD0,
- 0xEA31DF88, 0x8F566330, 0x61F9D622, 0x049E6A9A,
- 0xBDA6BD07, 0xD8C101BF, 0x366EB4AD, 0x53090815,
- 0x9A4E721D, 0xFF29CEA5, 0x11867BB7, 0x74E1C70F,
- 0xCDD91092, 0xA8BEAC2A, 0x46111938, 0x2376A580,
- 0x7566C6D8, 0x10017A60, 0xFEAECF72, 0x9BC973CA,
- 0x22F1A457, 0x479618EF, 0xA939ADFD, 0xCC5E1145,
- 0x06EE4D76, 0x6389F1CE, 0x8D2644DC, 0xE841F864,
- 0x51792FF9, 0x341E9341, 0xDAB12653, 0xBFD69AEB,
- 0xE9C6F9B3, 0x8CA1450B, 0x620EF019, 0x07694CA1,
- 0xBE519B3C, 0xDB362784, 0x35999296, 0x50FE2E2E,
- 0x99B95426, 0xFCDEE89E, 0x12715D8C, 0x7716E134,
- 0xCE2E36A9, 0xAB498A11, 0x45E63F03, 0x208183BB,
- 0x7691E0E3, 0x13F65C5B, 0xFD59E949, 0x983E55F1,
- 0x2106826C, 0x44613ED4, 0xAACE8BC6, 0xCFA9377E,
- 0x38417FD6, 0x5D26C36E, 0xB389767C, 0xD6EECAC4,
- 0x6FD61D59, 0x0AB1A1E1, 0xE41E14F3, 0x8179A84B,
- 0xD769CB13, 0xB20E77AB, 0x5CA1C2B9, 0x39C67E01,
- 0x80FEA99C, 0xE5991524, 0x0B36A036, 0x6E511C8E,
- 0xA7166686, 0xC271DA3E, 0x2CDE6F2C, 0x49B9D394,
- 0xF0810409, 0x95E6B8B1, 0x7B490DA3, 0x1E2EB11B,
- 0x483ED243, 0x2D596EFB, 0xC3F6DBE9, 0xA6916751,
- 0x1FA9B0CC, 0x7ACE0C74, 0x9461B966, 0xF10605DE
- }, {
- 0x00000000, 0xB029603D, 0x6053C07A, 0xD07AA047,
- 0xC0A680F5, 0x708FE0C8, 0xA0F5408F, 0x10DC20B2,
- 0xC14B7030, 0x7162100D, 0xA118B04A, 0x1131D077,
- 0x01EDF0C5, 0xB1C490F8, 0x61BE30BF, 0xD1975082,
- 0x8297E060, 0x32BE805D, 0xE2C4201A, 0x52ED4027,
- 0x42316095, 0xF21800A8, 0x2262A0EF, 0x924BC0D2,
- 0x43DC9050, 0xF3F5F06D, 0x238F502A, 0x93A63017,
- 0x837A10A5, 0x33537098, 0xE329D0DF, 0x5300B0E2,
- 0x042FC1C1, 0xB406A1FC, 0x647C01BB, 0xD4556186,
- 0xC4894134, 0x74A02109, 0xA4DA814E, 0x14F3E173,
- 0xC564B1F1, 0x754DD1CC, 0xA537718B, 0x151E11B6,
- 0x05C23104, 0xB5EB5139, 0x6591F17E, 0xD5B89143,
- 0x86B821A1, 0x3691419C, 0xE6EBE1DB, 0x56C281E6,
- 0x461EA154, 0xF637C169, 0x264D612E, 0x96640113,
- 0x47F35191, 0xF7DA31AC, 0x27A091EB, 0x9789F1D6,
- 0x8755D164, 0x377CB159, 0xE706111E, 0x572F7123,
- 0x4958F358, 0xF9719365, 0x290B3322, 0x9922531F,
- 0x89FE73AD, 0x39D71390, 0xE9ADB3D7, 0x5984D3EA,
- 0x88138368, 0x383AE355, 0xE8404312, 0x5869232F,
- 0x48B5039D, 0xF89C63A0, 0x28E6C3E7, 0x98CFA3DA,
- 0xCBCF1338, 0x7BE67305, 0xAB9CD342, 0x1BB5B37F,
- 0x0B6993CD, 0xBB40F3F0, 0x6B3A53B7, 0xDB13338A,
- 0x0A846308, 0xBAAD0335, 0x6AD7A372, 0xDAFEC34F,
- 0xCA22E3FD, 0x7A0B83C0, 0xAA712387, 0x1A5843BA,
- 0x4D773299, 0xFD5E52A4, 0x2D24F2E3, 0x9D0D92DE,
- 0x8DD1B26C, 0x3DF8D251, 0xED827216, 0x5DAB122B,
- 0x8C3C42A9, 0x3C152294, 0xEC6F82D3, 0x5C46E2EE,
- 0x4C9AC25C, 0xFCB3A261, 0x2CC90226, 0x9CE0621B,
- 0xCFE0D2F9, 0x7FC9B2C4, 0xAFB31283, 0x1F9A72BE,
- 0x0F46520C, 0xBF6F3231, 0x6F159276, 0xDF3CF24B,
- 0x0EABA2C9, 0xBE82C2F4, 0x6EF862B3, 0xDED1028E,
- 0xCE0D223C, 0x7E244201, 0xAE5EE246, 0x1E77827B,
- 0x92B0E6B1, 0x2299868C, 0xF2E326CB, 0x42CA46F6,
- 0x52166644, 0xE23F0679, 0x3245A63E, 0x826CC603,
- 0x53FB9681, 0xE3D2F6BC, 0x33A856FB, 0x838136C6,
- 0x935D1674, 0x23747649, 0xF30ED60E, 0x4327B633,
- 0x102706D1, 0xA00E66EC, 0x7074C6AB, 0xC05DA696,
- 0xD0818624, 0x60A8E619, 0xB0D2465E, 0x00FB2663,
- 0xD16C76E1, 0x614516DC, 0xB13FB69B, 0x0116D6A6,
- 0x11CAF614, 0xA1E39629, 0x7199366E, 0xC1B05653,
- 0x969F2770, 0x26B6474D, 0xF6CCE70A, 0x46E58737,
- 0x5639A785, 0xE610C7B8, 0x366A67FF, 0x864307C2,
- 0x57D45740, 0xE7FD377D, 0x3787973A, 0x87AEF707,
- 0x9772D7B5, 0x275BB788, 0xF72117CF, 0x470877F2,
- 0x1408C710, 0xA421A72D, 0x745B076A, 0xC4726757,
- 0xD4AE47E5, 0x648727D8, 0xB4FD879F, 0x04D4E7A2,
- 0xD543B720, 0x656AD71D, 0xB510775A, 0x05391767,
- 0x15E537D5, 0xA5CC57E8, 0x75B6F7AF, 0xC59F9792,
- 0xDBE815E9, 0x6BC175D4, 0xBBBBD593, 0x0B92B5AE,
- 0x1B4E951C, 0xAB67F521, 0x7B1D5566, 0xCB34355B,
- 0x1AA365D9, 0xAA8A05E4, 0x7AF0A5A3, 0xCAD9C59E,
- 0xDA05E52C, 0x6A2C8511, 0xBA562556, 0x0A7F456B,
- 0x597FF589, 0xE95695B4, 0x392C35F3, 0x890555CE,
- 0x99D9757C, 0x29F01541, 0xF98AB506, 0x49A3D53B,
- 0x983485B9, 0x281DE584, 0xF86745C3, 0x484E25FE,
- 0x5892054C, 0xE8BB6571, 0x38C1C536, 0x88E8A50B,
- 0xDFC7D428, 0x6FEEB415, 0xBF941452, 0x0FBD746F,
- 0x1F6154DD, 0xAF4834E0, 0x7F3294A7, 0xCF1BF49A,
- 0x1E8CA418, 0xAEA5C425, 0x7EDF6462, 0xCEF6045F,
- 0xDE2A24ED, 0x6E0344D0, 0xBE79E497, 0x0E5084AA,
- 0x5D503448, 0xED795475, 0x3D03F432, 0x8D2A940F,
- 0x9DF6B4BD, 0x2DDFD480, 0xFDA574C7, 0x4D8C14FA,
- 0x9C1B4478, 0x2C322445, 0xFC488402, 0x4C61E43F,
- 0x5CBDC48D, 0xEC94A4B0, 0x3CEE04F7, 0x8CC764CA
- }, {
- 0x00000000, 0xA5D35CCB, 0x0BA1C84D, 0xAE729486,
- 0x1642919B, 0xB391CD50, 0x1DE359D6, 0xB830051D,
- 0x6D8253EC, 0xC8510F27, 0x66239BA1, 0xC3F0C76A,
- 0x7BC0C277, 0xDE139EBC, 0x70610A3A, 0xD5B256F1,
- 0x9B02D603, 0x3ED18AC8, 0x90A31E4E, 0x35704285,
- 0x8D404798, 0x28931B53, 0x86E18FD5, 0x2332D31E,
- 0xF68085EF, 0x5353D924, 0xFD214DA2, 0x58F21169,
- 0xE0C21474, 0x451148BF, 0xEB63DC39, 0x4EB080F2,
- 0x3605AC07, 0x93D6F0CC, 0x3DA4644A, 0x98773881,
- 0x20473D9C, 0x85946157, 0x2BE6F5D1, 0x8E35A91A,
- 0x5B87FFEB, 0xFE54A320, 0x502637A6, 0xF5F56B6D,
- 0x4DC56E70, 0xE81632BB, 0x4664A63D, 0xE3B7FAF6,
- 0xAD077A04, 0x08D426CF, 0xA6A6B249, 0x0375EE82,
- 0xBB45EB9F, 0x1E96B754, 0xB0E423D2, 0x15377F19,
- 0xC08529E8, 0x65567523, 0xCB24E1A5, 0x6EF7BD6E,
- 0xD6C7B873, 0x7314E4B8, 0xDD66703E, 0x78B52CF5,
- 0x6C0A580F, 0xC9D904C4, 0x67AB9042, 0xC278CC89,
- 0x7A48C994, 0xDF9B955F, 0x71E901D9, 0xD43A5D12,
- 0x01880BE3, 0xA45B5728, 0x0A29C3AE, 0xAFFA9F65,
- 0x17CA9A78, 0xB219C6B3, 0x1C6B5235, 0xB9B80EFE,
- 0xF7088E0C, 0x52DBD2C7, 0xFCA94641, 0x597A1A8A,
- 0xE14A1F97, 0x4499435C, 0xEAEBD7DA, 0x4F388B11,
- 0x9A8ADDE0, 0x3F59812B, 0x912B15AD, 0x34F84966,
- 0x8CC84C7B, 0x291B10B0, 0x87698436, 0x22BAD8FD,
- 0x5A0FF408, 0xFFDCA8C3, 0x51AE3C45, 0xF47D608E,
- 0x4C4D6593, 0xE99E3958, 0x47ECADDE, 0xE23FF115,
- 0x378DA7E4, 0x925EFB2F, 0x3C2C6FA9, 0x99FF3362,
- 0x21CF367F, 0x841C6AB4, 0x2A6EFE32, 0x8FBDA2F9,
- 0xC10D220B, 0x64DE7EC0, 0xCAACEA46, 0x6F7FB68D,
- 0xD74FB390, 0x729CEF5B, 0xDCEE7BDD, 0x793D2716,
- 0xAC8F71E7, 0x095C2D2C, 0xA72EB9AA, 0x02FDE561,
- 0xBACDE07C, 0x1F1EBCB7, 0xB16C2831, 0x14BF74FA,
- 0xD814B01E, 0x7DC7ECD5, 0xD3B57853, 0x76662498,
- 0xCE562185, 0x6B857D4E, 0xC5F7E9C8, 0x6024B503,
- 0xB596E3F2, 0x1045BF39, 0xBE372BBF, 0x1BE47774,
- 0xA3D47269, 0x06072EA2, 0xA875BA24, 0x0DA6E6EF,
- 0x4316661D, 0xE6C53AD6, 0x48B7AE50, 0xED64F29B,
- 0x5554F786, 0xF087AB4D, 0x5EF53FCB, 0xFB266300,
- 0x2E9435F1, 0x8B47693A, 0x2535FDBC, 0x80E6A177,
- 0x38D6A46A, 0x9D05F8A1, 0x33776C27, 0x96A430EC,
- 0xEE111C19, 0x4BC240D2, 0xE5B0D454, 0x4063889F,
- 0xF8538D82, 0x5D80D149, 0xF3F245CF, 0x56211904,
- 0x83934FF5, 0x2640133E, 0x883287B8, 0x2DE1DB73,
- 0x95D1DE6E, 0x300282A5, 0x9E701623, 0x3BA34AE8,
- 0x7513CA1A, 0xD0C096D1, 0x7EB20257, 0xDB615E9C,
- 0x63515B81, 0xC682074A, 0x68F093CC, 0xCD23CF07,
- 0x189199F6, 0xBD42C53D, 0x133051BB, 0xB6E30D70,
- 0x0ED3086D, 0xAB0054A6, 0x0572C020, 0xA0A19CEB,
- 0xB41EE811, 0x11CDB4DA, 0xBFBF205C, 0x1A6C7C97,
- 0xA25C798A, 0x078F2541, 0xA9FDB1C7, 0x0C2EED0C,
- 0xD99CBBFD, 0x7C4FE736, 0xD23D73B0, 0x77EE2F7B,
- 0xCFDE2A66, 0x6A0D76AD, 0xC47FE22B, 0x61ACBEE0,
- 0x2F1C3E12, 0x8ACF62D9, 0x24BDF65F, 0x816EAA94,
- 0x395EAF89, 0x9C8DF342, 0x32FF67C4, 0x972C3B0F,
- 0x429E6DFE, 0xE74D3135, 0x493FA5B3, 0xECECF978,
- 0x54DCFC65, 0xF10FA0AE, 0x5F7D3428, 0xFAAE68E3,
- 0x821B4416, 0x27C818DD, 0x89BA8C5B, 0x2C69D090,
- 0x9459D58D, 0x318A8946, 0x9FF81DC0, 0x3A2B410B,
- 0xEF9917FA, 0x4A4A4B31, 0xE438DFB7, 0x41EB837C,
- 0xF9DB8661, 0x5C08DAAA, 0xF27A4E2C, 0x57A912E7,
- 0x19199215, 0xBCCACEDE, 0x12B85A58, 0xB76B0693,
- 0x0F5B038E, 0xAA885F45, 0x04FACBC3, 0xA1299708,
- 0x749BC1F9, 0xD1489D32, 0x7F3A09B4, 0xDAE9557F,
- 0x62D95062, 0xC70A0CA9, 0x6978982F, 0xCCABC4E4
- }, {
- 0x00000000, 0xB40B77A6, 0x29119F97, 0x9D1AE831,
- 0x13244FF4, 0xA72F3852, 0x3A35D063, 0x8E3EA7C5,
- 0x674EEF33, 0xD3459895, 0x4E5F70A4, 0xFA540702,
- 0x746AA0C7, 0xC061D761, 0x5D7B3F50, 0xE97048F6,
- 0xCE9CDE67, 0x7A97A9C1, 0xE78D41F0, 0x53863656,
- 0xDDB89193, 0x69B3E635, 0xF4A90E04, 0x40A279A2,
- 0xA9D23154, 0x1DD946F2, 0x80C3AEC3, 0x34C8D965,
- 0xBAF67EA0, 0x0EFD0906, 0x93E7E137, 0x27EC9691,
- 0x9C39BDCF, 0x2832CA69, 0xB5282258, 0x012355FE,
- 0x8F1DF23B, 0x3B16859D, 0xA60C6DAC, 0x12071A0A,
- 0xFB7752FC, 0x4F7C255A, 0xD266CD6B, 0x666DBACD,
- 0xE8531D08, 0x5C586AAE, 0xC142829F, 0x7549F539,
- 0x52A563A8, 0xE6AE140E, 0x7BB4FC3F, 0xCFBF8B99,
- 0x41812C5C, 0xF58A5BFA, 0x6890B3CB, 0xDC9BC46D,
- 0x35EB8C9B, 0x81E0FB3D, 0x1CFA130C, 0xA8F164AA,
- 0x26CFC36F, 0x92C4B4C9, 0x0FDE5CF8, 0xBBD52B5E,
- 0x79750B44, 0xCD7E7CE2, 0x506494D3, 0xE46FE375,
- 0x6A5144B0, 0xDE5A3316, 0x4340DB27, 0xF74BAC81,
- 0x1E3BE477, 0xAA3093D1, 0x372A7BE0, 0x83210C46,
- 0x0D1FAB83, 0xB914DC25, 0x240E3414, 0x900543B2,
- 0xB7E9D523, 0x03E2A285, 0x9EF84AB4, 0x2AF33D12,
- 0xA4CD9AD7, 0x10C6ED71, 0x8DDC0540, 0x39D772E6,
- 0xD0A73A10, 0x64AC4DB6, 0xF9B6A587, 0x4DBDD221,
- 0xC38375E4, 0x77880242, 0xEA92EA73, 0x5E999DD5,
- 0xE54CB68B, 0x5147C12D, 0xCC5D291C, 0x78565EBA,
- 0xF668F97F, 0x42638ED9, 0xDF7966E8, 0x6B72114E,
- 0x820259B8, 0x36092E1E, 0xAB13C62F, 0x1F18B189,
- 0x9126164C, 0x252D61EA, 0xB83789DB, 0x0C3CFE7D,
- 0x2BD068EC, 0x9FDB1F4A, 0x02C1F77B, 0xB6CA80DD,
- 0x38F42718, 0x8CFF50BE, 0x11E5B88F, 0xA5EECF29,
- 0x4C9E87DF, 0xF895F079, 0x658F1848, 0xD1846FEE,
- 0x5FBAC82B, 0xEBB1BF8D, 0x76AB57BC, 0xC2A0201A,
- 0xF2EA1688, 0x46E1612E, 0xDBFB891F, 0x6FF0FEB9,
- 0xE1CE597C, 0x55C52EDA, 0xC8DFC6EB, 0x7CD4B14D,
- 0x95A4F9BB, 0x21AF8E1D, 0xBCB5662C, 0x08BE118A,
- 0x8680B64F, 0x328BC1E9, 0xAF9129D8, 0x1B9A5E7E,
- 0x3C76C8EF, 0x887DBF49, 0x15675778, 0xA16C20DE,
- 0x2F52871B, 0x9B59F0BD, 0x0643188C, 0xB2486F2A,
- 0x5B3827DC, 0xEF33507A, 0x7229B84B, 0xC622CFED,
- 0x481C6828, 0xFC171F8E, 0x610DF7BF, 0xD5068019,
- 0x6ED3AB47, 0xDAD8DCE1, 0x47C234D0, 0xF3C94376,
- 0x7DF7E4B3, 0xC9FC9315, 0x54E67B24, 0xE0ED0C82,
- 0x099D4474, 0xBD9633D2, 0x208CDBE3, 0x9487AC45,
- 0x1AB90B80, 0xAEB27C26, 0x33A89417, 0x87A3E3B1,
- 0xA04F7520, 0x14440286, 0x895EEAB7, 0x3D559D11,
- 0xB36B3AD4, 0x07604D72, 0x9A7AA543, 0x2E71D2E5,
- 0xC7019A13, 0x730AEDB5, 0xEE100584, 0x5A1B7222,
- 0xD425D5E7, 0x602EA241, 0xFD344A70, 0x493F3DD6,
- 0x8B9F1DCC, 0x3F946A6A, 0xA28E825B, 0x1685F5FD,
- 0x98BB5238, 0x2CB0259E, 0xB1AACDAF, 0x05A1BA09,
- 0xECD1F2FF, 0x58DA8559, 0xC5C06D68, 0x71CB1ACE,
- 0xFFF5BD0B, 0x4BFECAAD, 0xD6E4229C, 0x62EF553A,
- 0x4503C3AB, 0xF108B40D, 0x6C125C3C, 0xD8192B9A,
- 0x56278C5F, 0xE22CFBF9, 0x7F3613C8, 0xCB3D646E,
- 0x224D2C98, 0x96465B3E, 0x0B5CB30F, 0xBF57C4A9,
- 0x3169636C, 0x856214CA, 0x1878FCFB, 0xAC738B5D,
- 0x17A6A003, 0xA3ADD7A5, 0x3EB73F94, 0x8ABC4832,
- 0x0482EFF7, 0xB0899851, 0x2D937060, 0x999807C6,
- 0x70E84F30, 0xC4E33896, 0x59F9D0A7, 0xEDF2A701,
- 0x63CC00C4, 0xD7C77762, 0x4ADD9F53, 0xFED6E8F5,
- 0xD93A7E64, 0x6D3109C2, 0xF02BE1F3, 0x44209655,
- 0xCA1E3190, 0x7E154636, 0xE30FAE07, 0x5704D9A1,
- 0xBE749157, 0x0A7FE6F1, 0x97650EC0, 0x236E7966,
- 0xAD50DEA3, 0x195BA905, 0x84414134, 0x304A3692
- }, {
- 0x00000000, 0x9E00AACC, 0x7D072542, 0xE3078F8E,
- 0xFA0E4A84, 0x640EE048, 0x87096FC6, 0x1909C50A,
- 0xB51BE5D3, 0x2B1B4F1F, 0xC81CC091, 0x561C6A5D,
- 0x4F15AF57, 0xD115059B, 0x32128A15, 0xAC1220D9,
- 0x2B31BB7C, 0xB53111B0, 0x56369E3E, 0xC83634F2,
- 0xD13FF1F8, 0x4F3F5B34, 0xAC38D4BA, 0x32387E76,
- 0x9E2A5EAF, 0x002AF463, 0xE32D7BED, 0x7D2DD121,
- 0x6424142B, 0xFA24BEE7, 0x19233169, 0x87239BA5,
- 0x566276F9, 0xC862DC35, 0x2B6553BB, 0xB565F977,
- 0xAC6C3C7D, 0x326C96B1, 0xD16B193F, 0x4F6BB3F3,
- 0xE379932A, 0x7D7939E6, 0x9E7EB668, 0x007E1CA4,
- 0x1977D9AE, 0x87777362, 0x6470FCEC, 0xFA705620,
- 0x7D53CD85, 0xE3536749, 0x0054E8C7, 0x9E54420B,
- 0x875D8701, 0x195D2DCD, 0xFA5AA243, 0x645A088F,
- 0xC8482856, 0x5648829A, 0xB54F0D14, 0x2B4FA7D8,
- 0x324662D2, 0xAC46C81E, 0x4F414790, 0xD141ED5C,
- 0xEDC29D29, 0x73C237E5, 0x90C5B86B, 0x0EC512A7,
- 0x17CCD7AD, 0x89CC7D61, 0x6ACBF2EF, 0xF4CB5823,
- 0x58D978FA, 0xC6D9D236, 0x25DE5DB8, 0xBBDEF774,
- 0xA2D7327E, 0x3CD798B2, 0xDFD0173C, 0x41D0BDF0,
- 0xC6F32655, 0x58F38C99, 0xBBF40317, 0x25F4A9DB,
- 0x3CFD6CD1, 0xA2FDC61D, 0x41FA4993, 0xDFFAE35F,
- 0x73E8C386, 0xEDE8694A, 0x0EEFE6C4, 0x90EF4C08,
- 0x89E68902, 0x17E623CE, 0xF4E1AC40, 0x6AE1068C,
- 0xBBA0EBD0, 0x25A0411C, 0xC6A7CE92, 0x58A7645E,
- 0x41AEA154, 0xDFAE0B98, 0x3CA98416, 0xA2A92EDA,
- 0x0EBB0E03, 0x90BBA4CF, 0x73BC2B41, 0xEDBC818D,
- 0xF4B54487, 0x6AB5EE4B, 0x89B261C5, 0x17B2CB09,
- 0x909150AC, 0x0E91FA60, 0xED9675EE, 0x7396DF22,
- 0x6A9F1A28, 0xF49FB0E4, 0x17983F6A, 0x899895A6,
- 0x258AB57F, 0xBB8A1FB3, 0x588D903D, 0xC68D3AF1,
- 0xDF84FFFB, 0x41845537, 0xA283DAB9, 0x3C837075,
- 0xDA853B53, 0x4485919F, 0xA7821E11, 0x3982B4DD,
- 0x208B71D7, 0xBE8BDB1B, 0x5D8C5495, 0xC38CFE59,
- 0x6F9EDE80, 0xF19E744C, 0x1299FBC2, 0x8C99510E,
- 0x95909404, 0x0B903EC8, 0xE897B146, 0x76971B8A,
- 0xF1B4802F, 0x6FB42AE3, 0x8CB3A56D, 0x12B30FA1,
- 0x0BBACAAB, 0x95BA6067, 0x76BDEFE9, 0xE8BD4525,
- 0x44AF65FC, 0xDAAFCF30, 0x39A840BE, 0xA7A8EA72,
- 0xBEA12F78, 0x20A185B4, 0xC3A60A3A, 0x5DA6A0F6,
- 0x8CE74DAA, 0x12E7E766, 0xF1E068E8, 0x6FE0C224,
- 0x76E9072E, 0xE8E9ADE2, 0x0BEE226C, 0x95EE88A0,
- 0x39FCA879, 0xA7FC02B5, 0x44FB8D3B, 0xDAFB27F7,
- 0xC3F2E2FD, 0x5DF24831, 0xBEF5C7BF, 0x20F56D73,
- 0xA7D6F6D6, 0x39D65C1A, 0xDAD1D394, 0x44D17958,
- 0x5DD8BC52, 0xC3D8169E, 0x20DF9910, 0xBEDF33DC,
- 0x12CD1305, 0x8CCDB9C9, 0x6FCA3647, 0xF1CA9C8B,
- 0xE8C35981, 0x76C3F34D, 0x95C47CC3, 0x0BC4D60F,
- 0x3747A67A, 0xA9470CB6, 0x4A408338, 0xD44029F4,
- 0xCD49ECFE, 0x53494632, 0xB04EC9BC, 0x2E4E6370,
- 0x825C43A9, 0x1C5CE965, 0xFF5B66EB, 0x615BCC27,
- 0x7852092D, 0xE652A3E1, 0x05552C6F, 0x9B5586A3,
- 0x1C761D06, 0x8276B7CA, 0x61713844, 0xFF719288,
- 0xE6785782, 0x7878FD4E, 0x9B7F72C0, 0x057FD80C,
- 0xA96DF8D5, 0x376D5219, 0xD46ADD97, 0x4A6A775B,
- 0x5363B251, 0xCD63189D, 0x2E649713, 0xB0643DDF,
- 0x6125D083, 0xFF257A4F, 0x1C22F5C1, 0x82225F0D,
- 0x9B2B9A07, 0x052B30CB, 0xE62CBF45, 0x782C1589,
- 0xD43E3550, 0x4A3E9F9C, 0xA9391012, 0x3739BADE,
- 0x2E307FD4, 0xB030D518, 0x53375A96, 0xCD37F05A,
- 0x4A146BFF, 0xD414C133, 0x37134EBD, 0xA913E471,
- 0xB01A217B, 0x2E1A8BB7, 0xCD1D0439, 0x531DAEF5,
- 0xFF0F8E2C, 0x610F24E0, 0x8208AB6E, 0x1C0801A2,
- 0x0501C4A8, 0x9B016E64, 0x7806E1EA, 0xE6064B26
- }
-};
diff --git a/3pt/crc/crc32_table_le.h b/3pt/crc/crc32_table_le.h
deleted file mode 100644
index ca8e53a..0000000
--- a/3pt/crc/crc32_table_le.h
+++ /dev/null
@@ -1,525 +0,0 @@
-/* This file has been automatically generated by crc32_tablegen.c. */
-
-const unsigned int crc32_table[8][256] = {
- {
- 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
- 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
- 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
- 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
- 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
- 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
- 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
- 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
- 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
- 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
- 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
- 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
- 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
- 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
- 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
- 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
- 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
- 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
- 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
- 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
- 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
- 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
- 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
- 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
- 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
- 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
- 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
- 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
- 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
- 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
- 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
- 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
- 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
- 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
- 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
- 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
- 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
- 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
- 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
- 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
- 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
- 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
- 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
- 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
- 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
- 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
- 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
- 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
- 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
- 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
- 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
- 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
- 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
- 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
- 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
- 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
- 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
- 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
- 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
- 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
- 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
- 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
- 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
- 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
- }, {
- 0x00000000, 0x191B3141, 0x32366282, 0x2B2D53C3,
- 0x646CC504, 0x7D77F445, 0x565AA786, 0x4F4196C7,
- 0xC8D98A08, 0xD1C2BB49, 0xFAEFE88A, 0xE3F4D9CB,
- 0xACB54F0C, 0xB5AE7E4D, 0x9E832D8E, 0x87981CCF,
- 0x4AC21251, 0x53D92310, 0x78F470D3, 0x61EF4192,
- 0x2EAED755, 0x37B5E614, 0x1C98B5D7, 0x05838496,
- 0x821B9859, 0x9B00A918, 0xB02DFADB, 0xA936CB9A,
- 0xE6775D5D, 0xFF6C6C1C, 0xD4413FDF, 0xCD5A0E9E,
- 0x958424A2, 0x8C9F15E3, 0xA7B24620, 0xBEA97761,
- 0xF1E8E1A6, 0xE8F3D0E7, 0xC3DE8324, 0xDAC5B265,
- 0x5D5DAEAA, 0x44469FEB, 0x6F6BCC28, 0x7670FD69,
- 0x39316BAE, 0x202A5AEF, 0x0B07092C, 0x121C386D,
- 0xDF4636F3, 0xC65D07B2, 0xED705471, 0xF46B6530,
- 0xBB2AF3F7, 0xA231C2B6, 0x891C9175, 0x9007A034,
- 0x179FBCFB, 0x0E848DBA, 0x25A9DE79, 0x3CB2EF38,
- 0x73F379FF, 0x6AE848BE, 0x41C51B7D, 0x58DE2A3C,
- 0xF0794F05, 0xE9627E44, 0xC24F2D87, 0xDB541CC6,
- 0x94158A01, 0x8D0EBB40, 0xA623E883, 0xBF38D9C2,
- 0x38A0C50D, 0x21BBF44C, 0x0A96A78F, 0x138D96CE,
- 0x5CCC0009, 0x45D73148, 0x6EFA628B, 0x77E153CA,
- 0xBABB5D54, 0xA3A06C15, 0x888D3FD6, 0x91960E97,
- 0xDED79850, 0xC7CCA911, 0xECE1FAD2, 0xF5FACB93,
- 0x7262D75C, 0x6B79E61D, 0x4054B5DE, 0x594F849F,
- 0x160E1258, 0x0F152319, 0x243870DA, 0x3D23419B,
- 0x65FD6BA7, 0x7CE65AE6, 0x57CB0925, 0x4ED03864,
- 0x0191AEA3, 0x188A9FE2, 0x33A7CC21, 0x2ABCFD60,
- 0xAD24E1AF, 0xB43FD0EE, 0x9F12832D, 0x8609B26C,
- 0xC94824AB, 0xD05315EA, 0xFB7E4629, 0xE2657768,
- 0x2F3F79F6, 0x362448B7, 0x1D091B74, 0x04122A35,
- 0x4B53BCF2, 0x52488DB3, 0x7965DE70, 0x607EEF31,
- 0xE7E6F3FE, 0xFEFDC2BF, 0xD5D0917C, 0xCCCBA03D,
- 0x838A36FA, 0x9A9107BB, 0xB1BC5478, 0xA8A76539,
- 0x3B83984B, 0x2298A90A, 0x09B5FAC9, 0x10AECB88,
- 0x5FEF5D4F, 0x46F46C0E, 0x6DD93FCD, 0x74C20E8C,
- 0xF35A1243, 0xEA412302, 0xC16C70C1, 0xD8774180,
- 0x9736D747, 0x8E2DE606, 0xA500B5C5, 0xBC1B8484,
- 0x71418A1A, 0x685ABB5B, 0x4377E898, 0x5A6CD9D9,
- 0x152D4F1E, 0x0C367E5F, 0x271B2D9C, 0x3E001CDD,
- 0xB9980012, 0xA0833153, 0x8BAE6290, 0x92B553D1,
- 0xDDF4C516, 0xC4EFF457, 0xEFC2A794, 0xF6D996D5,
- 0xAE07BCE9, 0xB71C8DA8, 0x9C31DE6B, 0x852AEF2A,
- 0xCA6B79ED, 0xD37048AC, 0xF85D1B6F, 0xE1462A2E,
- 0x66DE36E1, 0x7FC507A0, 0x54E85463, 0x4DF36522,
- 0x02B2F3E5, 0x1BA9C2A4, 0x30849167, 0x299FA026,
- 0xE4C5AEB8, 0xFDDE9FF9, 0xD6F3CC3A, 0xCFE8FD7B,
- 0x80A96BBC, 0x99B25AFD, 0xB29F093E, 0xAB84387F,
- 0x2C1C24B0, 0x350715F1, 0x1E2A4632, 0x07317773,
- 0x4870E1B4, 0x516BD0F5, 0x7A468336, 0x635DB277,
- 0xCBFAD74E, 0xD2E1E60F, 0xF9CCB5CC, 0xE0D7848D,
- 0xAF96124A, 0xB68D230B, 0x9DA070C8, 0x84BB4189,
- 0x03235D46, 0x1A386C07, 0x31153FC4, 0x280E0E85,
- 0x674F9842, 0x7E54A903, 0x5579FAC0, 0x4C62CB81,
- 0x8138C51F, 0x9823F45E, 0xB30EA79D, 0xAA1596DC,
- 0xE554001B, 0xFC4F315A, 0xD7626299, 0xCE7953D8,
- 0x49E14F17, 0x50FA7E56, 0x7BD72D95, 0x62CC1CD4,
- 0x2D8D8A13, 0x3496BB52, 0x1FBBE891, 0x06A0D9D0,
- 0x5E7EF3EC, 0x4765C2AD, 0x6C48916E, 0x7553A02F,
- 0x3A1236E8, 0x230907A9, 0x0824546A, 0x113F652B,
- 0x96A779E4, 0x8FBC48A5, 0xA4911B66, 0xBD8A2A27,
- 0xF2CBBCE0, 0xEBD08DA1, 0xC0FDDE62, 0xD9E6EF23,
- 0x14BCE1BD, 0x0DA7D0FC, 0x268A833F, 0x3F91B27E,
- 0x70D024B9, 0x69CB15F8, 0x42E6463B, 0x5BFD777A,
- 0xDC656BB5, 0xC57E5AF4, 0xEE530937, 0xF7483876,
- 0xB809AEB1, 0xA1129FF0, 0x8A3FCC33, 0x9324FD72
- }, {
- 0x00000000, 0x01C26A37, 0x0384D46E, 0x0246BE59,
- 0x0709A8DC, 0x06CBC2EB, 0x048D7CB2, 0x054F1685,
- 0x0E1351B8, 0x0FD13B8F, 0x0D9785D6, 0x0C55EFE1,
- 0x091AF964, 0x08D89353, 0x0A9E2D0A, 0x0B5C473D,
- 0x1C26A370, 0x1DE4C947, 0x1FA2771E, 0x1E601D29,
- 0x1B2F0BAC, 0x1AED619B, 0x18ABDFC2, 0x1969B5F5,
- 0x1235F2C8, 0x13F798FF, 0x11B126A6, 0x10734C91,
- 0x153C5A14, 0x14FE3023, 0x16B88E7A, 0x177AE44D,
- 0x384D46E0, 0x398F2CD7, 0x3BC9928E, 0x3A0BF8B9,
- 0x3F44EE3C, 0x3E86840B, 0x3CC03A52, 0x3D025065,
- 0x365E1758, 0x379C7D6F, 0x35DAC336, 0x3418A901,
- 0x3157BF84, 0x3095D5B3, 0x32D36BEA, 0x331101DD,
- 0x246BE590, 0x25A98FA7, 0x27EF31FE, 0x262D5BC9,
- 0x23624D4C, 0x22A0277B, 0x20E69922, 0x2124F315,
- 0x2A78B428, 0x2BBADE1F, 0x29FC6046, 0x283E0A71,
- 0x2D711CF4, 0x2CB376C3, 0x2EF5C89A, 0x2F37A2AD,
- 0x709A8DC0, 0x7158E7F7, 0x731E59AE, 0x72DC3399,
- 0x7793251C, 0x76514F2B, 0x7417F172, 0x75D59B45,
- 0x7E89DC78, 0x7F4BB64F, 0x7D0D0816, 0x7CCF6221,
- 0x798074A4, 0x78421E93, 0x7A04A0CA, 0x7BC6CAFD,
- 0x6CBC2EB0, 0x6D7E4487, 0x6F38FADE, 0x6EFA90E9,
- 0x6BB5866C, 0x6A77EC5B, 0x68315202, 0x69F33835,
- 0x62AF7F08, 0x636D153F, 0x612BAB66, 0x60E9C151,
- 0x65A6D7D4, 0x6464BDE3, 0x662203BA, 0x67E0698D,
- 0x48D7CB20, 0x4915A117, 0x4B531F4E, 0x4A917579,
- 0x4FDE63FC, 0x4E1C09CB, 0x4C5AB792, 0x4D98DDA5,
- 0x46C49A98, 0x4706F0AF, 0x45404EF6, 0x448224C1,
- 0x41CD3244, 0x400F5873, 0x4249E62A, 0x438B8C1D,
- 0x54F16850, 0x55330267, 0x5775BC3E, 0x56B7D609,
- 0x53F8C08C, 0x523AAABB, 0x507C14E2, 0x51BE7ED5,
- 0x5AE239E8, 0x5B2053DF, 0x5966ED86, 0x58A487B1,
- 0x5DEB9134, 0x5C29FB03, 0x5E6F455A, 0x5FAD2F6D,
- 0xE1351B80, 0xE0F771B7, 0xE2B1CFEE, 0xE373A5D9,
- 0xE63CB35C, 0xE7FED96B, 0xE5B86732, 0xE47A0D05,
- 0xEF264A38, 0xEEE4200F, 0xECA29E56, 0xED60F461,
- 0xE82FE2E4, 0xE9ED88D3, 0xEBAB368A, 0xEA695CBD,
- 0xFD13B8F0, 0xFCD1D2C7, 0xFE976C9E, 0xFF5506A9,
- 0xFA1A102C, 0xFBD87A1B, 0xF99EC442, 0xF85CAE75,
- 0xF300E948, 0xF2C2837F, 0xF0843D26, 0xF1465711,
- 0xF4094194, 0xF5CB2BA3, 0xF78D95FA, 0xF64FFFCD,
- 0xD9785D60, 0xD8BA3757, 0xDAFC890E, 0xDB3EE339,
- 0xDE71F5BC, 0xDFB39F8B, 0xDDF521D2, 0xDC374BE5,
- 0xD76B0CD8, 0xD6A966EF, 0xD4EFD8B6, 0xD52DB281,
- 0xD062A404, 0xD1A0CE33, 0xD3E6706A, 0xD2241A5D,
- 0xC55EFE10, 0xC49C9427, 0xC6DA2A7E, 0xC7184049,
- 0xC25756CC, 0xC3953CFB, 0xC1D382A2, 0xC011E895,
- 0xCB4DAFA8, 0xCA8FC59F, 0xC8C97BC6, 0xC90B11F1,
- 0xCC440774, 0xCD866D43, 0xCFC0D31A, 0xCE02B92D,
- 0x91AF9640, 0x906DFC77, 0x922B422E, 0x93E92819,
- 0x96A63E9C, 0x976454AB, 0x9522EAF2, 0x94E080C5,
- 0x9FBCC7F8, 0x9E7EADCF, 0x9C381396, 0x9DFA79A1,
- 0x98B56F24, 0x99770513, 0x9B31BB4A, 0x9AF3D17D,
- 0x8D893530, 0x8C4B5F07, 0x8E0DE15E, 0x8FCF8B69,
- 0x8A809DEC, 0x8B42F7DB, 0x89044982, 0x88C623B5,
- 0x839A6488, 0x82580EBF, 0x801EB0E6, 0x81DCDAD1,
- 0x8493CC54, 0x8551A663, 0x8717183A, 0x86D5720D,
- 0xA9E2D0A0, 0xA820BA97, 0xAA6604CE, 0xABA46EF9,
- 0xAEEB787C, 0xAF29124B, 0xAD6FAC12, 0xACADC625,
- 0xA7F18118, 0xA633EB2F, 0xA4755576, 0xA5B73F41,
- 0xA0F829C4, 0xA13A43F3, 0xA37CFDAA, 0xA2BE979D,
- 0xB5C473D0, 0xB40619E7, 0xB640A7BE, 0xB782CD89,
- 0xB2CDDB0C, 0xB30FB13B, 0xB1490F62, 0xB08B6555,
- 0xBBD72268, 0xBA15485F, 0xB853F606, 0xB9919C31,
- 0xBCDE8AB4, 0xBD1CE083, 0xBF5A5EDA, 0xBE9834ED
- }, {
- 0x00000000, 0xB8BC6765, 0xAA09C88B, 0x12B5AFEE,
- 0x8F629757, 0x37DEF032, 0x256B5FDC, 0x9DD738B9,
- 0xC5B428EF, 0x7D084F8A, 0x6FBDE064, 0xD7018701,
- 0x4AD6BFB8, 0xF26AD8DD, 0xE0DF7733, 0x58631056,
- 0x5019579F, 0xE8A530FA, 0xFA109F14, 0x42ACF871,
- 0xDF7BC0C8, 0x67C7A7AD, 0x75720843, 0xCDCE6F26,
- 0x95AD7F70, 0x2D111815, 0x3FA4B7FB, 0x8718D09E,
- 0x1ACFE827, 0xA2738F42, 0xB0C620AC, 0x087A47C9,
- 0xA032AF3E, 0x188EC85B, 0x0A3B67B5, 0xB28700D0,
- 0x2F503869, 0x97EC5F0C, 0x8559F0E2, 0x3DE59787,
- 0x658687D1, 0xDD3AE0B4, 0xCF8F4F5A, 0x7733283F,
- 0xEAE41086, 0x525877E3, 0x40EDD80D, 0xF851BF68,
- 0xF02BF8A1, 0x48979FC4, 0x5A22302A, 0xE29E574F,
- 0x7F496FF6, 0xC7F50893, 0xD540A77D, 0x6DFCC018,
- 0x359FD04E, 0x8D23B72B, 0x9F9618C5, 0x272A7FA0,
- 0xBAFD4719, 0x0241207C, 0x10F48F92, 0xA848E8F7,
- 0x9B14583D, 0x23A83F58, 0x311D90B6, 0x89A1F7D3,
- 0x1476CF6A, 0xACCAA80F, 0xBE7F07E1, 0x06C36084,
- 0x5EA070D2, 0xE61C17B7, 0xF4A9B859, 0x4C15DF3C,
- 0xD1C2E785, 0x697E80E0, 0x7BCB2F0E, 0xC377486B,
- 0xCB0D0FA2, 0x73B168C7, 0x6104C729, 0xD9B8A04C,
- 0x446F98F5, 0xFCD3FF90, 0xEE66507E, 0x56DA371B,
- 0x0EB9274D, 0xB6054028, 0xA4B0EFC6, 0x1C0C88A3,
- 0x81DBB01A, 0x3967D77F, 0x2BD27891, 0x936E1FF4,
- 0x3B26F703, 0x839A9066, 0x912F3F88, 0x299358ED,
- 0xB4446054, 0x0CF80731, 0x1E4DA8DF, 0xA6F1CFBA,
- 0xFE92DFEC, 0x462EB889, 0x549B1767, 0xEC277002,
- 0x71F048BB, 0xC94C2FDE, 0xDBF98030, 0x6345E755,
- 0x6B3FA09C, 0xD383C7F9, 0xC1366817, 0x798A0F72,
- 0xE45D37CB, 0x5CE150AE, 0x4E54FF40, 0xF6E89825,
- 0xAE8B8873, 0x1637EF16, 0x048240F8, 0xBC3E279D,
- 0x21E91F24, 0x99557841, 0x8BE0D7AF, 0x335CB0CA,
- 0xED59B63B, 0x55E5D15E, 0x47507EB0, 0xFFEC19D5,
- 0x623B216C, 0xDA874609, 0xC832E9E7, 0x708E8E82,
- 0x28ED9ED4, 0x9051F9B1, 0x82E4565F, 0x3A58313A,
- 0xA78F0983, 0x1F336EE6, 0x0D86C108, 0xB53AA66D,
- 0xBD40E1A4, 0x05FC86C1, 0x1749292F, 0xAFF54E4A,
- 0x322276F3, 0x8A9E1196, 0x982BBE78, 0x2097D91D,
- 0x78F4C94B, 0xC048AE2E, 0xD2FD01C0, 0x6A4166A5,
- 0xF7965E1C, 0x4F2A3979, 0x5D9F9697, 0xE523F1F2,
- 0x4D6B1905, 0xF5D77E60, 0xE762D18E, 0x5FDEB6EB,
- 0xC2098E52, 0x7AB5E937, 0x680046D9, 0xD0BC21BC,
- 0x88DF31EA, 0x3063568F, 0x22D6F961, 0x9A6A9E04,
- 0x07BDA6BD, 0xBF01C1D8, 0xADB46E36, 0x15080953,
- 0x1D724E9A, 0xA5CE29FF, 0xB77B8611, 0x0FC7E174,
- 0x9210D9CD, 0x2AACBEA8, 0x38191146, 0x80A57623,
- 0xD8C66675, 0x607A0110, 0x72CFAEFE, 0xCA73C99B,
- 0x57A4F122, 0xEF189647, 0xFDAD39A9, 0x45115ECC,
- 0x764DEE06, 0xCEF18963, 0xDC44268D, 0x64F841E8,
- 0xF92F7951, 0x41931E34, 0x5326B1DA, 0xEB9AD6BF,
- 0xB3F9C6E9, 0x0B45A18C, 0x19F00E62, 0xA14C6907,
- 0x3C9B51BE, 0x842736DB, 0x96929935, 0x2E2EFE50,
- 0x2654B999, 0x9EE8DEFC, 0x8C5D7112, 0x34E11677,
- 0xA9362ECE, 0x118A49AB, 0x033FE645, 0xBB838120,
- 0xE3E09176, 0x5B5CF613, 0x49E959FD, 0xF1553E98,
- 0x6C820621, 0xD43E6144, 0xC68BCEAA, 0x7E37A9CF,
- 0xD67F4138, 0x6EC3265D, 0x7C7689B3, 0xC4CAEED6,
- 0x591DD66F, 0xE1A1B10A, 0xF3141EE4, 0x4BA87981,
- 0x13CB69D7, 0xAB770EB2, 0xB9C2A15C, 0x017EC639,
- 0x9CA9FE80, 0x241599E5, 0x36A0360B, 0x8E1C516E,
- 0x866616A7, 0x3EDA71C2, 0x2C6FDE2C, 0x94D3B949,
- 0x090481F0, 0xB1B8E695, 0xA30D497B, 0x1BB12E1E,
- 0x43D23E48, 0xFB6E592D, 0xE9DBF6C3, 0x516791A6,
- 0xCCB0A91F, 0x740CCE7A, 0x66B96194, 0xDE0506F1
- }, {
- 0x00000000, 0x3D6029B0, 0x7AC05360, 0x47A07AD0,
- 0xF580A6C0, 0xC8E08F70, 0x8F40F5A0, 0xB220DC10,
- 0x30704BC1, 0x0D106271, 0x4AB018A1, 0x77D03111,
- 0xC5F0ED01, 0xF890C4B1, 0xBF30BE61, 0x825097D1,
- 0x60E09782, 0x5D80BE32, 0x1A20C4E2, 0x2740ED52,
- 0x95603142, 0xA80018F2, 0xEFA06222, 0xD2C04B92,
- 0x5090DC43, 0x6DF0F5F3, 0x2A508F23, 0x1730A693,
- 0xA5107A83, 0x98705333, 0xDFD029E3, 0xE2B00053,
- 0xC1C12F04, 0xFCA106B4, 0xBB017C64, 0x866155D4,
- 0x344189C4, 0x0921A074, 0x4E81DAA4, 0x73E1F314,
- 0xF1B164C5, 0xCCD14D75, 0x8B7137A5, 0xB6111E15,
- 0x0431C205, 0x3951EBB5, 0x7EF19165, 0x4391B8D5,
- 0xA121B886, 0x9C419136, 0xDBE1EBE6, 0xE681C256,
- 0x54A11E46, 0x69C137F6, 0x2E614D26, 0x13016496,
- 0x9151F347, 0xAC31DAF7, 0xEB91A027, 0xD6F18997,
- 0x64D15587, 0x59B17C37, 0x1E1106E7, 0x23712F57,
- 0x58F35849, 0x659371F9, 0x22330B29, 0x1F532299,
- 0xAD73FE89, 0x9013D739, 0xD7B3ADE9, 0xEAD38459,
- 0x68831388, 0x55E33A38, 0x124340E8, 0x2F236958,
- 0x9D03B548, 0xA0639CF8, 0xE7C3E628, 0xDAA3CF98,
- 0x3813CFCB, 0x0573E67B, 0x42D39CAB, 0x7FB3B51B,
- 0xCD93690B, 0xF0F340BB, 0xB7533A6B, 0x8A3313DB,
- 0x0863840A, 0x3503ADBA, 0x72A3D76A, 0x4FC3FEDA,
- 0xFDE322CA, 0xC0830B7A, 0x872371AA, 0xBA43581A,
- 0x9932774D, 0xA4525EFD, 0xE3F2242D, 0xDE920D9D,
- 0x6CB2D18D, 0x51D2F83D, 0x167282ED, 0x2B12AB5D,
- 0xA9423C8C, 0x9422153C, 0xD3826FEC, 0xEEE2465C,
- 0x5CC29A4C, 0x61A2B3FC, 0x2602C92C, 0x1B62E09C,
- 0xF9D2E0CF, 0xC4B2C97F, 0x8312B3AF, 0xBE729A1F,
- 0x0C52460F, 0x31326FBF, 0x7692156F, 0x4BF23CDF,
- 0xC9A2AB0E, 0xF4C282BE, 0xB362F86E, 0x8E02D1DE,
- 0x3C220DCE, 0x0142247E, 0x46E25EAE, 0x7B82771E,
- 0xB1E6B092, 0x8C869922, 0xCB26E3F2, 0xF646CA42,
- 0x44661652, 0x79063FE2, 0x3EA64532, 0x03C66C82,
- 0x8196FB53, 0xBCF6D2E3, 0xFB56A833, 0xC6368183,
- 0x74165D93, 0x49767423, 0x0ED60EF3, 0x33B62743,
- 0xD1062710, 0xEC660EA0, 0xABC67470, 0x96A65DC0,
- 0x248681D0, 0x19E6A860, 0x5E46D2B0, 0x6326FB00,
- 0xE1766CD1, 0xDC164561, 0x9BB63FB1, 0xA6D61601,
- 0x14F6CA11, 0x2996E3A1, 0x6E369971, 0x5356B0C1,
- 0x70279F96, 0x4D47B626, 0x0AE7CCF6, 0x3787E546,
- 0x85A73956, 0xB8C710E6, 0xFF676A36, 0xC2074386,
- 0x4057D457, 0x7D37FDE7, 0x3A978737, 0x07F7AE87,
- 0xB5D77297, 0x88B75B27, 0xCF1721F7, 0xF2770847,
- 0x10C70814, 0x2DA721A4, 0x6A075B74, 0x576772C4,
- 0xE547AED4, 0xD8278764, 0x9F87FDB4, 0xA2E7D404,
- 0x20B743D5, 0x1DD76A65, 0x5A7710B5, 0x67173905,
- 0xD537E515, 0xE857CCA5, 0xAFF7B675, 0x92979FC5,
- 0xE915E8DB, 0xD475C16B, 0x93D5BBBB, 0xAEB5920B,
- 0x1C954E1B, 0x21F567AB, 0x66551D7B, 0x5B3534CB,
- 0xD965A31A, 0xE4058AAA, 0xA3A5F07A, 0x9EC5D9CA,
- 0x2CE505DA, 0x11852C6A, 0x562556BA, 0x6B457F0A,
- 0x89F57F59, 0xB49556E9, 0xF3352C39, 0xCE550589,
- 0x7C75D999, 0x4115F029, 0x06B58AF9, 0x3BD5A349,
- 0xB9853498, 0x84E51D28, 0xC34567F8, 0xFE254E48,
- 0x4C059258, 0x7165BBE8, 0x36C5C138, 0x0BA5E888,
- 0x28D4C7DF, 0x15B4EE6F, 0x521494BF, 0x6F74BD0F,
- 0xDD54611F, 0xE03448AF, 0xA794327F, 0x9AF41BCF,
- 0x18A48C1E, 0x25C4A5AE, 0x6264DF7E, 0x5F04F6CE,
- 0xED242ADE, 0xD044036E, 0x97E479BE, 0xAA84500E,
- 0x4834505D, 0x755479ED, 0x32F4033D, 0x0F942A8D,
- 0xBDB4F69D, 0x80D4DF2D, 0xC774A5FD, 0xFA148C4D,
- 0x78441B9C, 0x4524322C, 0x028448FC, 0x3FE4614C,
- 0x8DC4BD5C, 0xB0A494EC, 0xF704EE3C, 0xCA64C78C
- }, {
- 0x00000000, 0xCB5CD3A5, 0x4DC8A10B, 0x869472AE,
- 0x9B914216, 0x50CD91B3, 0xD659E31D, 0x1D0530B8,
- 0xEC53826D, 0x270F51C8, 0xA19B2366, 0x6AC7F0C3,
- 0x77C2C07B, 0xBC9E13DE, 0x3A0A6170, 0xF156B2D5,
- 0x03D6029B, 0xC88AD13E, 0x4E1EA390, 0x85427035,
- 0x9847408D, 0x531B9328, 0xD58FE186, 0x1ED33223,
- 0xEF8580F6, 0x24D95353, 0xA24D21FD, 0x6911F258,
- 0x7414C2E0, 0xBF481145, 0x39DC63EB, 0xF280B04E,
- 0x07AC0536, 0xCCF0D693, 0x4A64A43D, 0x81387798,
- 0x9C3D4720, 0x57619485, 0xD1F5E62B, 0x1AA9358E,
- 0xEBFF875B, 0x20A354FE, 0xA6372650, 0x6D6BF5F5,
- 0x706EC54D, 0xBB3216E8, 0x3DA66446, 0xF6FAB7E3,
- 0x047A07AD, 0xCF26D408, 0x49B2A6A6, 0x82EE7503,
- 0x9FEB45BB, 0x54B7961E, 0xD223E4B0, 0x197F3715,
- 0xE82985C0, 0x23755665, 0xA5E124CB, 0x6EBDF76E,
- 0x73B8C7D6, 0xB8E41473, 0x3E7066DD, 0xF52CB578,
- 0x0F580A6C, 0xC404D9C9, 0x4290AB67, 0x89CC78C2,
- 0x94C9487A, 0x5F959BDF, 0xD901E971, 0x125D3AD4,
- 0xE30B8801, 0x28575BA4, 0xAEC3290A, 0x659FFAAF,
- 0x789ACA17, 0xB3C619B2, 0x35526B1C, 0xFE0EB8B9,
- 0x0C8E08F7, 0xC7D2DB52, 0x4146A9FC, 0x8A1A7A59,
- 0x971F4AE1, 0x5C439944, 0xDAD7EBEA, 0x118B384F,
- 0xE0DD8A9A, 0x2B81593F, 0xAD152B91, 0x6649F834,
- 0x7B4CC88C, 0xB0101B29, 0x36846987, 0xFDD8BA22,
- 0x08F40F5A, 0xC3A8DCFF, 0x453CAE51, 0x8E607DF4,
- 0x93654D4C, 0x58399EE9, 0xDEADEC47, 0x15F13FE2,
- 0xE4A78D37, 0x2FFB5E92, 0xA96F2C3C, 0x6233FF99,
- 0x7F36CF21, 0xB46A1C84, 0x32FE6E2A, 0xF9A2BD8F,
- 0x0B220DC1, 0xC07EDE64, 0x46EAACCA, 0x8DB67F6F,
- 0x90B34FD7, 0x5BEF9C72, 0xDD7BEEDC, 0x16273D79,
- 0xE7718FAC, 0x2C2D5C09, 0xAAB92EA7, 0x61E5FD02,
- 0x7CE0CDBA, 0xB7BC1E1F, 0x31286CB1, 0xFA74BF14,
- 0x1EB014D8, 0xD5ECC77D, 0x5378B5D3, 0x98246676,
- 0x852156CE, 0x4E7D856B, 0xC8E9F7C5, 0x03B52460,
- 0xF2E396B5, 0x39BF4510, 0xBF2B37BE, 0x7477E41B,
- 0x6972D4A3, 0xA22E0706, 0x24BA75A8, 0xEFE6A60D,
- 0x1D661643, 0xD63AC5E6, 0x50AEB748, 0x9BF264ED,
- 0x86F75455, 0x4DAB87F0, 0xCB3FF55E, 0x006326FB,
- 0xF135942E, 0x3A69478B, 0xBCFD3525, 0x77A1E680,
- 0x6AA4D638, 0xA1F8059D, 0x276C7733, 0xEC30A496,
- 0x191C11EE, 0xD240C24B, 0x54D4B0E5, 0x9F886340,
- 0x828D53F8, 0x49D1805D, 0xCF45F2F3, 0x04192156,
- 0xF54F9383, 0x3E134026, 0xB8873288, 0x73DBE12D,
- 0x6EDED195, 0xA5820230, 0x2316709E, 0xE84AA33B,
- 0x1ACA1375, 0xD196C0D0, 0x5702B27E, 0x9C5E61DB,
- 0x815B5163, 0x4A0782C6, 0xCC93F068, 0x07CF23CD,
- 0xF6999118, 0x3DC542BD, 0xBB513013, 0x700DE3B6,
- 0x6D08D30E, 0xA65400AB, 0x20C07205, 0xEB9CA1A0,
- 0x11E81EB4, 0xDAB4CD11, 0x5C20BFBF, 0x977C6C1A,
- 0x8A795CA2, 0x41258F07, 0xC7B1FDA9, 0x0CED2E0C,
- 0xFDBB9CD9, 0x36E74F7C, 0xB0733DD2, 0x7B2FEE77,
- 0x662ADECF, 0xAD760D6A, 0x2BE27FC4, 0xE0BEAC61,
- 0x123E1C2F, 0xD962CF8A, 0x5FF6BD24, 0x94AA6E81,
- 0x89AF5E39, 0x42F38D9C, 0xC467FF32, 0x0F3B2C97,
- 0xFE6D9E42, 0x35314DE7, 0xB3A53F49, 0x78F9ECEC,
- 0x65FCDC54, 0xAEA00FF1, 0x28347D5F, 0xE368AEFA,
- 0x16441B82, 0xDD18C827, 0x5B8CBA89, 0x90D0692C,
- 0x8DD55994, 0x46898A31, 0xC01DF89F, 0x0B412B3A,
- 0xFA1799EF, 0x314B4A4A, 0xB7DF38E4, 0x7C83EB41,
- 0x6186DBF9, 0xAADA085C, 0x2C4E7AF2, 0xE712A957,
- 0x15921919, 0xDECECABC, 0x585AB812, 0x93066BB7,
- 0x8E035B0F, 0x455F88AA, 0xC3CBFA04, 0x089729A1,
- 0xF9C19B74, 0x329D48D1, 0xB4093A7F, 0x7F55E9DA,
- 0x6250D962, 0xA90C0AC7, 0x2F987869, 0xE4C4ABCC
- }, {
- 0x00000000, 0xA6770BB4, 0x979F1129, 0x31E81A9D,
- 0xF44F2413, 0x52382FA7, 0x63D0353A, 0xC5A73E8E,
- 0x33EF4E67, 0x959845D3, 0xA4705F4E, 0x020754FA,
- 0xC7A06A74, 0x61D761C0, 0x503F7B5D, 0xF64870E9,
- 0x67DE9CCE, 0xC1A9977A, 0xF0418DE7, 0x56368653,
- 0x9391B8DD, 0x35E6B369, 0x040EA9F4, 0xA279A240,
- 0x5431D2A9, 0xF246D91D, 0xC3AEC380, 0x65D9C834,
- 0xA07EF6BA, 0x0609FD0E, 0x37E1E793, 0x9196EC27,
- 0xCFBD399C, 0x69CA3228, 0x582228B5, 0xFE552301,
- 0x3BF21D8F, 0x9D85163B, 0xAC6D0CA6, 0x0A1A0712,
- 0xFC5277FB, 0x5A257C4F, 0x6BCD66D2, 0xCDBA6D66,
- 0x081D53E8, 0xAE6A585C, 0x9F8242C1, 0x39F54975,
- 0xA863A552, 0x0E14AEE6, 0x3FFCB47B, 0x998BBFCF,
- 0x5C2C8141, 0xFA5B8AF5, 0xCBB39068, 0x6DC49BDC,
- 0x9B8CEB35, 0x3DFBE081, 0x0C13FA1C, 0xAA64F1A8,
- 0x6FC3CF26, 0xC9B4C492, 0xF85CDE0F, 0x5E2BD5BB,
- 0x440B7579, 0xE27C7ECD, 0xD3946450, 0x75E36FE4,
- 0xB044516A, 0x16335ADE, 0x27DB4043, 0x81AC4BF7,
- 0x77E43B1E, 0xD19330AA, 0xE07B2A37, 0x460C2183,
- 0x83AB1F0D, 0x25DC14B9, 0x14340E24, 0xB2430590,
- 0x23D5E9B7, 0x85A2E203, 0xB44AF89E, 0x123DF32A,
- 0xD79ACDA4, 0x71EDC610, 0x4005DC8D, 0xE672D739,
- 0x103AA7D0, 0xB64DAC64, 0x87A5B6F9, 0x21D2BD4D,
- 0xE47583C3, 0x42028877, 0x73EA92EA, 0xD59D995E,
- 0x8BB64CE5, 0x2DC14751, 0x1C295DCC, 0xBA5E5678,
- 0x7FF968F6, 0xD98E6342, 0xE86679DF, 0x4E11726B,
- 0xB8590282, 0x1E2E0936, 0x2FC613AB, 0x89B1181F,
- 0x4C162691, 0xEA612D25, 0xDB8937B8, 0x7DFE3C0C,
- 0xEC68D02B, 0x4A1FDB9F, 0x7BF7C102, 0xDD80CAB6,
- 0x1827F438, 0xBE50FF8C, 0x8FB8E511, 0x29CFEEA5,
- 0xDF879E4C, 0x79F095F8, 0x48188F65, 0xEE6F84D1,
- 0x2BC8BA5F, 0x8DBFB1EB, 0xBC57AB76, 0x1A20A0C2,
- 0x8816EAF2, 0x2E61E146, 0x1F89FBDB, 0xB9FEF06F,
- 0x7C59CEE1, 0xDA2EC555, 0xEBC6DFC8, 0x4DB1D47C,
- 0xBBF9A495, 0x1D8EAF21, 0x2C66B5BC, 0x8A11BE08,
- 0x4FB68086, 0xE9C18B32, 0xD82991AF, 0x7E5E9A1B,
- 0xEFC8763C, 0x49BF7D88, 0x78576715, 0xDE206CA1,
- 0x1B87522F, 0xBDF0599B, 0x8C184306, 0x2A6F48B2,
- 0xDC27385B, 0x7A5033EF, 0x4BB82972, 0xEDCF22C6,
- 0x28681C48, 0x8E1F17FC, 0xBFF70D61, 0x198006D5,
- 0x47ABD36E, 0xE1DCD8DA, 0xD034C247, 0x7643C9F3,
- 0xB3E4F77D, 0x1593FCC9, 0x247BE654, 0x820CEDE0,
- 0x74449D09, 0xD23396BD, 0xE3DB8C20, 0x45AC8794,
- 0x800BB91A, 0x267CB2AE, 0x1794A833, 0xB1E3A387,
- 0x20754FA0, 0x86024414, 0xB7EA5E89, 0x119D553D,
- 0xD43A6BB3, 0x724D6007, 0x43A57A9A, 0xE5D2712E,
- 0x139A01C7, 0xB5ED0A73, 0x840510EE, 0x22721B5A,
- 0xE7D525D4, 0x41A22E60, 0x704A34FD, 0xD63D3F49,
- 0xCC1D9F8B, 0x6A6A943F, 0x5B828EA2, 0xFDF58516,
- 0x3852BB98, 0x9E25B02C, 0xAFCDAAB1, 0x09BAA105,
- 0xFFF2D1EC, 0x5985DA58, 0x686DC0C5, 0xCE1ACB71,
- 0x0BBDF5FF, 0xADCAFE4B, 0x9C22E4D6, 0x3A55EF62,
- 0xABC30345, 0x0DB408F1, 0x3C5C126C, 0x9A2B19D8,
- 0x5F8C2756, 0xF9FB2CE2, 0xC813367F, 0x6E643DCB,
- 0x982C4D22, 0x3E5B4696, 0x0FB35C0B, 0xA9C457BF,
- 0x6C636931, 0xCA146285, 0xFBFC7818, 0x5D8B73AC,
- 0x03A0A617, 0xA5D7ADA3, 0x943FB73E, 0x3248BC8A,
- 0xF7EF8204, 0x519889B0, 0x6070932D, 0xC6079899,
- 0x304FE870, 0x9638E3C4, 0xA7D0F959, 0x01A7F2ED,
- 0xC400CC63, 0x6277C7D7, 0x539FDD4A, 0xF5E8D6FE,
- 0x647E3AD9, 0xC209316D, 0xF3E12BF0, 0x55962044,
- 0x90311ECA, 0x3646157E, 0x07AE0FE3, 0xA1D90457,
- 0x579174BE, 0xF1E67F0A, 0xC00E6597, 0x66796E23,
- 0xA3DE50AD, 0x05A95B19, 0x34414184, 0x92364A30
- }, {
- 0x00000000, 0xCCAA009E, 0x4225077D, 0x8E8F07E3,
- 0x844A0EFA, 0x48E00E64, 0xC66F0987, 0x0AC50919,
- 0xD3E51BB5, 0x1F4F1B2B, 0x91C01CC8, 0x5D6A1C56,
- 0x57AF154F, 0x9B0515D1, 0x158A1232, 0xD92012AC,
- 0x7CBB312B, 0xB01131B5, 0x3E9E3656, 0xF23436C8,
- 0xF8F13FD1, 0x345B3F4F, 0xBAD438AC, 0x767E3832,
- 0xAF5E2A9E, 0x63F42A00, 0xED7B2DE3, 0x21D12D7D,
- 0x2B142464, 0xE7BE24FA, 0x69312319, 0xA59B2387,
- 0xF9766256, 0x35DC62C8, 0xBB53652B, 0x77F965B5,
- 0x7D3C6CAC, 0xB1966C32, 0x3F196BD1, 0xF3B36B4F,
- 0x2A9379E3, 0xE639797D, 0x68B67E9E, 0xA41C7E00,
- 0xAED97719, 0x62737787, 0xECFC7064, 0x205670FA,
- 0x85CD537D, 0x496753E3, 0xC7E85400, 0x0B42549E,
- 0x01875D87, 0xCD2D5D19, 0x43A25AFA, 0x8F085A64,
- 0x562848C8, 0x9A824856, 0x140D4FB5, 0xD8A74F2B,
- 0xD2624632, 0x1EC846AC, 0x9047414F, 0x5CED41D1,
- 0x299DC2ED, 0xE537C273, 0x6BB8C590, 0xA712C50E,
- 0xADD7CC17, 0x617DCC89, 0xEFF2CB6A, 0x2358CBF4,
- 0xFA78D958, 0x36D2D9C6, 0xB85DDE25, 0x74F7DEBB,
- 0x7E32D7A2, 0xB298D73C, 0x3C17D0DF, 0xF0BDD041,
- 0x5526F3C6, 0x998CF358, 0x1703F4BB, 0xDBA9F425,
- 0xD16CFD3C, 0x1DC6FDA2, 0x9349FA41, 0x5FE3FADF,
- 0x86C3E873, 0x4A69E8ED, 0xC4E6EF0E, 0x084CEF90,
- 0x0289E689, 0xCE23E617, 0x40ACE1F4, 0x8C06E16A,
- 0xD0EBA0BB, 0x1C41A025, 0x92CEA7C6, 0x5E64A758,
- 0x54A1AE41, 0x980BAEDF, 0x1684A93C, 0xDA2EA9A2,
- 0x030EBB0E, 0xCFA4BB90, 0x412BBC73, 0x8D81BCED,
- 0x8744B5F4, 0x4BEEB56A, 0xC561B289, 0x09CBB217,
- 0xAC509190, 0x60FA910E, 0xEE7596ED, 0x22DF9673,
- 0x281A9F6A, 0xE4B09FF4, 0x6A3F9817, 0xA6959889,
- 0x7FB58A25, 0xB31F8ABB, 0x3D908D58, 0xF13A8DC6,
- 0xFBFF84DF, 0x37558441, 0xB9DA83A2, 0x7570833C,
- 0x533B85DA, 0x9F918544, 0x111E82A7, 0xDDB48239,
- 0xD7718B20, 0x1BDB8BBE, 0x95548C5D, 0x59FE8CC3,
- 0x80DE9E6F, 0x4C749EF1, 0xC2FB9912, 0x0E51998C,
- 0x04949095, 0xC83E900B, 0x46B197E8, 0x8A1B9776,
- 0x2F80B4F1, 0xE32AB46F, 0x6DA5B38C, 0xA10FB312,
- 0xABCABA0B, 0x6760BA95, 0xE9EFBD76, 0x2545BDE8,
- 0xFC65AF44, 0x30CFAFDA, 0xBE40A839, 0x72EAA8A7,
- 0x782FA1BE, 0xB485A120, 0x3A0AA6C3, 0xF6A0A65D,
- 0xAA4DE78C, 0x66E7E712, 0xE868E0F1, 0x24C2E06F,
- 0x2E07E976, 0xE2ADE9E8, 0x6C22EE0B, 0xA088EE95,
- 0x79A8FC39, 0xB502FCA7, 0x3B8DFB44, 0xF727FBDA,
- 0xFDE2F2C3, 0x3148F25D, 0xBFC7F5BE, 0x736DF520,
- 0xD6F6D6A7, 0x1A5CD639, 0x94D3D1DA, 0x5879D144,
- 0x52BCD85D, 0x9E16D8C3, 0x1099DF20, 0xDC33DFBE,
- 0x0513CD12, 0xC9B9CD8C, 0x4736CA6F, 0x8B9CCAF1,
- 0x8159C3E8, 0x4DF3C376, 0xC37CC495, 0x0FD6C40B,
- 0x7AA64737, 0xB60C47A9, 0x3883404A, 0xF42940D4,
- 0xFEEC49CD, 0x32464953, 0xBCC94EB0, 0x70634E2E,
- 0xA9435C82, 0x65E95C1C, 0xEB665BFF, 0x27CC5B61,
- 0x2D095278, 0xE1A352E6, 0x6F2C5505, 0xA386559B,
- 0x061D761C, 0xCAB77682, 0x44387161, 0x889271FF,
- 0x825778E6, 0x4EFD7878, 0xC0727F9B, 0x0CD87F05,
- 0xD5F86DA9, 0x19526D37, 0x97DD6AD4, 0x5B776A4A,
- 0x51B26353, 0x9D1863CD, 0x1397642E, 0xDF3D64B0,
- 0x83D02561, 0x4F7A25FF, 0xC1F5221C, 0x0D5F2282,
- 0x079A2B9B, 0xCB302B05, 0x45BF2CE6, 0x89152C78,
- 0x50353ED4, 0x9C9F3E4A, 0x121039A9, 0xDEBA3937,
- 0xD47F302E, 0x18D530B0, 0x965A3753, 0x5AF037CD,
- 0xFF6B144A, 0x33C114D4, 0xBD4E1337, 0x71E413A9,
- 0x7B211AB0, 0xB78B1A2E, 0x39041DCD, 0xF5AE1D53,
- 0x2C8E0FFF, 0xE0240F61, 0x6EAB0882, 0xA201081C,
- 0xA8C40105, 0x646E019B, 0xEAE10678, 0x264B06E6
- }
-};
diff --git a/README.md b/README.md
index 31774a4..92de632 100644
--- a/README.md
+++ b/README.md
@@ -226,6 +226,9 @@ phiola info -tags -inc "*.mp3" . | grep -B10 "Purple Haze"
# Analyze and show PCM info
phiola info -peaks file.mp3
+
+# Analyze audio loudness
+phiola info -loudness file.mp3
```
Other use-cases:
@@ -255,7 +258,7 @@ Currently supported commands:
| [convert](src/exe/convert.h) | Convert audio |
| [device](src/exe/device.h) | List audio devices |
| [gui](src/exe/gui.h) | Start graphical interface |
-| [info](src/exe/info.h) | Show file meta data |
+| [info](src/exe/info.h) | Analyze audio files |
| [list](src/exe/list.h) | Process playlist files |
| [play](src/exe/play.h) | Play audio |
| [record](src/exe/record.h) | Record audio |
@@ -332,6 +335,7 @@ phiola uses modified versions of these third party libraries:
[libALAC](https://github.com/macosforge/alac),
[libfdk-aac](https://github.com/mstorsjo/fdk-aac),
[libFLAC](https://github.com/xiph/flac),
+[libebur128](https://github.com/jiixyj/libebur128),
libMAC,
[libmpg123](https://mpg123.de),
libmpc,
diff --git a/alib3/EBUR128/Makefile b/alib3/EBUR128/Makefile
new file mode 100644
index 0000000..914292d
--- /dev/null
+++ b/alib3/EBUR128/Makefile
@@ -0,0 +1,41 @@
+# libEBUR128
+
+include ../config.mk
+
+VER := 1.2.6
+URL := https://github.com/jiixyj/libebur128/archive/refs/tags/v$(VER).zip
+MD5SUM := 0b6a89f6a68d2213035ef27b526485f5
+PKG := $(ALIB3)/EBUR128/$(notdir $(URL))
+DIR := libebur128-$(VER)
+LIB := libebur128-phi.$(SO)
+
+default: $(DIR)
+ $(SUBMAKE) $(LIB)
+
+# download
+$(PKG):
+ $(CURL) -o $(PKG) $(URL)
+
+# unpack
+$(DIR): $(PKG)
+ echo "$(MD5SUM) *$(PKG)" | md5sum -c -
+ $(UNZIP) $(PKG)
+
+# build
+CFLAGS += \
+ -I$(DIR)/ebur128 -I$(DIR)/ebur128/queue
+
+SRC := $(DIR)/ebur128/ebur128.c
+OBJ := ebur128-phi.o $(SRC:.c=.o)
+
+%.o: $(ALIB3)/EBUR128/%.c
+ $(C) $(CFLAGS) $< -o $@
+
+%.o: %.c
+ $(C) $(CFLAGS) $< -o $@
+
+$(LIB): $(OBJ)
+ $(LINK) -shared $+ $(LINKFLAGS) -o $@
+
+clean:
+ $(RM) $(OBJ) $(DIR)
diff --git a/alib3/EBUR128/ebur128-phi.c b/alib3/EBUR128/ebur128-phi.c
new file mode 100644
index 0000000..97bf95f
--- /dev/null
+++ b/alib3/EBUR128/ebur128-phi.c
@@ -0,0 +1,95 @@
+/** libEBUR128 wrapper */
+
+#include "ebur128-phi.h"
+#include
+
+struct ebur128_ctx {
+ ebur128_state *es;
+ unsigned channels;
+};
+
+int ebur128_open(ebur128_ctx **pc, struct ebur128_conf *conf)
+{
+ if (!conf->mode)
+ conf->mode = EBUR128_LOUDNESS_GLOBAL;
+ unsigned cm = conf->mode, mode = 0;
+ if (cm & EBUR128_LOUDNESS_MOMENTARY)
+ mode |= EBUR128_MODE_M;
+ if (cm & EBUR128_LOUDNESS_SHORTTERM)
+ mode |= EBUR128_MODE_S;
+ if (cm & EBUR128_LOUDNESS_GLOBAL)
+ mode |= EBUR128_MODE_I;
+ if (cm & EBUR128_LOUDNESS_RANGE)
+ mode |= EBUR128_MODE_LRA;
+ if (cm & EBUR128_SAMPLE_PEAK)
+ mode |= EBUR128_MODE_SAMPLE_PEAK;
+
+ ebur128_ctx *c;
+ if (!(c = calloc(1, sizeof(ebur128_ctx))))
+ return -1;
+ if (!(c->es = ebur128_init(conf->channels, conf->sample_rate, mode))) {
+ free(c);
+ return -1;
+ }
+ c->channels = conf->channels;
+ *pc = c;
+ return 0;
+}
+
+void ebur128_close(ebur128_ctx *c)
+{
+ if (!c) return;
+
+ ebur128_destroy(&c->es);
+ free(c);
+}
+
+void ebur128_process(ebur128_ctx *c, const double *data, size_t len)
+{
+ ebur128_add_frames_double(c->es, data, len / 8 / c->channels);
+}
+
+int ebur128_get(ebur128_ctx *c, unsigned what, void *buf, size_t cap)
+{
+ switch (what) {
+ case EBUR128_LOUDNESS_MOMENTARY:
+ if (8 > cap
+ || ebur128_loudness_momentary(c->es, buf))
+ break;
+ return 8;
+
+ case EBUR128_LOUDNESS_SHORTTERM:
+ if (8 > cap
+ || ebur128_loudness_shortterm(c->es, buf))
+ break;
+ return 8;
+
+ case EBUR128_LOUDNESS_GLOBAL:
+ if (8 > cap
+ || ebur128_loudness_global(c->es, buf))
+ break;
+ return 8;
+
+ case EBUR128_LOUDNESS_RANGE:
+ if (8 > cap
+ || ebur128_loudness_range(c->es, buf))
+ break;
+ return 8;
+
+ case EBUR128_SAMPLE_PEAK: {
+ if (8 > cap)
+ break;
+ double r = 0, d;
+ for (unsigned i = 0; i < c->channels; i++) {
+ if (ebur128_sample_peak(c->es, i, &d))
+ return 0;
+ if (d > r)
+ r = d;
+ }
+ *(double*)buf = r;
+ return 8;
+ }
+ }
+
+ return 0;
+}
diff --git a/alib3/EBUR128/ebur128-phi.h b/alib3/EBUR128/ebur128-phi.h
new file mode 100644
index 0000000..5ad202c
--- /dev/null
+++ b/alib3/EBUR128/ebur128-phi.h
@@ -0,0 +1,47 @@
+/** libEBUR128 wrapper */
+
+#pragma once
+#include
+
+#ifdef WIN32
+ #define _EXPORT __declspec(dllexport)
+#else
+ #define _EXPORT __attribute__((visibility("default")))
+ #include
+#endif
+
+typedef struct ebur128_ctx ebur128_ctx;
+
+enum EBUR128_PROPERTY {
+ EBUR128_LOUDNESS_MOMENTARY = 1,
+ EBUR128_LOUDNESS_SHORTTERM = 2,
+ EBUR128_LOUDNESS_GLOBAL = 4,
+ EBUR128_LOUDNESS_RANGE = 8,
+ EBUR128_SAMPLE_PEAK = 0x10,
+};
+
+struct ebur128_conf {
+ unsigned channels;
+ unsigned sample_rate;
+ unsigned mode; // enum EBUR128_PROPERTY
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+_EXPORT int ebur128_open(ebur128_ctx **c, struct ebur128_conf *conf);
+
+_EXPORT void ebur128_close(ebur128_ctx *c);
+
+_EXPORT void ebur128_process(ebur128_ctx *c, const double *data, size_t len);
+
+/**
+what: enum EBUR128_PROPERTY */
+_EXPORT int ebur128_get(ebur128_ctx *c, unsigned what, void *buf, size_t cap);
+
+#ifdef __cplusplus
+}
+#endif
+
+#undef _EXPORT
diff --git a/alib3/Makefile b/alib3/Makefile
index 11be19c..04688a0 100644
--- a/alib3/Makefile
+++ b/alib3/Makefile
@@ -9,6 +9,7 @@ LIBS := \
DynamicAudioNormalizer \
fdk-aac \
FLAC \
+ EBUR128 \
mpg123 \
opus \
soxr \
@@ -38,6 +39,10 @@ libFLAC-phi.$(SO):
$(MAKE) -f $(ALIB3)/FLAC/Makefile
FLAC: libFLAC-phi.$(SO)
+libebur128-phi.$(SO):
+ $(MAKE) -f $(ALIB3)/EBUR128/Makefile
+EBUR128: libebur128-phi.$(SO)
+
libMAC-phi.$(SO):
$(MAKE) -f $(ALIB3)/MAC/Makefile
MAC: libMAC-phi.$(SO)
diff --git a/src/afilter/Makefile b/src/afilter/Makefile
index 875f11f..f7e2e0e 100644
--- a/src/afilter/Makefile
+++ b/src/afilter/Makefile
@@ -23,10 +23,7 @@ AFPFX := af-
MODS += afilter.$(SO)
%.o: $(PHIOLA)/src/afilter/%.c
$(C) $(CFLAGS) -DFFBASE_OPT_SIZE $< -o $@
-crc.o: $(PHIOLA)/3pt/crc/crc.c
- $(C) $(CFLAGS) $< -o $@
afilter.$(SO): afilter.o \
- crc.o \
peaks.o \
gain.o \
rtpeak.o \
@@ -48,3 +45,10 @@ dynanorm.o: $(PHIOLA)/src/afilter/dynanorm.c
$(C) $(CFLAGS) -I$(ALIB3) $< -o $@
$(AFPFX)danorm.$(SO): dynanorm.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lDynamicAudioNormalizer-phi -o $@
+
+MODS += $(AFPFX)loudness.$(SO)
+LIBS3 += $(ALIB3_BIN)/libebur128-phi.$(SO)
+loudness.o: $(PHIOLA)/src/afilter/loudness.c
+ $(C) $(CFLAGS) -I$(ALIB3) $< -o $@
+$(AFPFX)loudness.$(SO): loudness.o
+ $(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lebur128-phi -lm -o $@
diff --git a/src/afilter/afilter.c b/src/afilter/afilter.c
index 651bba5..5b0e545 100644
--- a/src/afilter/afilter.c
+++ b/src/afilter/afilter.c
@@ -27,6 +27,7 @@ static const void* af_iface(const char *name)
{
static const struct map_sz_vptr mods[] = {
{ "auto-conv", &phi_autoconv },
+ { "auto-conv-f",&phi_autoconv_f },
{ "conv", &phi_aconv },
{ "gain", &phi_gain },
{ "peaks", &phi_peaks },
diff --git a/src/afilter/auto-conv.h b/src/afilter/auto-conv.h
index 921e98a..bc95211 100644
--- a/src/afilter/auto-conv.h
+++ b/src/afilter/auto-conv.h
@@ -7,15 +7,18 @@
struct autoconv {
uint state;
ffstr in;
+ struct phi_af iaf, oaf;
};
static void* autoconv_open(phi_track *t)
{
- t->oaudio.format = t->audio.format;
+ if (!t->oaudio.format.format)
+ t->oaudio.format = t->audio.format;
if (t->conf.stream_copy || !t->data_type || !ffsz_eq(t->data_type, "pcm"))
return PHI_OPEN_SKIP;
struct autoconv *c = phi_track_allocT(t, struct autoconv);
+ c->iaf = t->oaudio.format;
return c;
}
@@ -26,7 +29,6 @@ static void autoconv_close(void *ctx, phi_track *t)
static int autoconv_process(struct autoconv *c, phi_track *t)
{
- const struct phi_af *iaf = &t->audio.format;
struct phi_af *oaf = &t->oaudio.format;
if (c->state == 0) {
@@ -42,10 +44,10 @@ static int autoconv_process(struct autoconv *c, phi_track *t)
phi_af_update(oaf, &t->oaudio.conv_format); // apply settings from encoder
oaf->interleaved = t->oaudio.conv_format.interleaved;
- if (!ffmem_cmp(iaf, oaf, sizeof(*iaf)))
+ if (!ffmem_cmp(&c->iaf, oaf, sizeof(c->iaf)))
goto done; // no conversion is needed
- t->aconv.in = *iaf;
+ t->aconv.in = c->iaf;
t->aconv.out = *oaf;
if (!core->track->filter(t, core->mod("afilter.conv"), 0))
return PHI_ERR;
@@ -59,3 +61,48 @@ const phi_filter phi_autoconv = {
autoconv_open, autoconv_close, (void*)autoconv_process,
"auto-conv"
};
+
+
+static void* autoconv_f_open(phi_track *t)
+{
+ PHI_ASSERT(t->data_type && ffsz_eq(t->data_type, "pcm"));
+ struct autoconv *c = phi_track_allocT(t, struct autoconv);
+ return c;
+}
+
+static void autoconv_f_close(void *ctx, phi_track *t)
+{
+ phi_track_free(t, ctx);
+}
+
+static int autoconv_f_process(struct autoconv *c, phi_track *t)
+{
+ if (c->state == 0) {
+ c->iaf = t->audio.format;
+ c->oaf = c->iaf;
+ c->oaf.format = PHI_PCM_FLOAT64;
+ c->oaf.interleaved = 1;
+ FF_ASSERT(!t->oaudio.format.format);
+ t->oaudio.format = c->oaf;
+ char buf[100];
+ dbglog(t, "filtering audio format: %s", phi_af_print(&c->oaf, buf, 100));
+ c->in = t->data_in;
+ c->state = 1;
+ return PHI_DATA;
+ }
+
+ if (ffmem_cmp(&c->iaf, &c->oaf, sizeof(c->iaf))) {
+ t->aconv.in = c->iaf;
+ t->aconv.out = c->oaf;
+ if (!core->track->filter(t, core->mod("afilter.conv"), 0))
+ return PHI_ERR;
+ }
+
+ t->data_out = c->in;
+ return PHI_DONE;
+}
+
+const phi_filter phi_autoconv_f = {
+ autoconv_f_open, autoconv_f_close, (void*)autoconv_f_process,
+ "auto-conv-f"
+};
diff --git a/src/afilter/loudness.c b/src/afilter/loudness.c
new file mode 100644
index 0000000..badd6a3
--- /dev/null
+++ b/src/afilter/loudness.c
@@ -0,0 +1,90 @@
+/** phiola: loudness analyzer
+2024, Simon Zolin */
+
+#include
+#include
+#include
+#include
+#include
+
+static const phi_core *core;
+#define errlog(t, ...) phi_errlog(core, NULL, t, __VA_ARGS__)
+#define userlog(t, ...) phi_userlog(core, NULL, t, __VA_ARGS__)
+#define dbglog(t, ...) phi_dbglog(core, NULL, t, __VA_ARGS__)
+
+struct loudness {
+ ebur128_ctx *ebur128;
+};
+
+static void* loudness_open(phi_track *t)
+{
+ if (!(t->oaudio.format.interleaved
+ && t->oaudio.format.format == PHI_PCM_FLOAT64)) {
+ errlog(t, "input audio format not supported");
+ return PHI_OPEN_ERR;
+ }
+ struct loudness *c = phi_track_allocT(t, struct loudness);
+ struct ebur128_conf conf = {
+ .channels = t->oaudio.format.channels,
+ .sample_rate = t->oaudio.format.rate,
+ .mode = EBUR128_LOUDNESS_MOMENTARY | EBUR128_LOUDNESS_GLOBAL,
+ };
+ if (ebur128_open(&c->ebur128, &conf)) {
+ errlog(t, "ebur128_open()");
+ phi_track_free(t, c);
+ return PHI_OPEN_ERR;
+ }
+ return c;
+}
+
+static void loudness_close(void *f, phi_track *t)
+{
+ struct loudness *c = f;
+ ebur128_close(c->ebur128);
+ phi_track_free(t, c);
+}
+
+static int loudness_process(void *f, phi_track *t)
+{
+ struct loudness *c = f;
+ t->data_out = t->data_in;
+
+ ebur128_process(c->ebur128, (double*)t->data_in.ptr, t->data_in.len);
+
+ double global, momentary;
+ ebur128_get(c->ebur128, EBUR128_LOUDNESS_GLOBAL, &global, 8);
+ ebur128_get(c->ebur128, EBUR128_LOUDNESS_MOMENTARY, &momentary, 8);
+ t->oaudio.loudness = global;
+ t->oaudio.loudness_momentary = momentary;
+ dbglog(t, "loudness: %f %f", global, momentary);
+
+ if (t->chain_flags & PHI_FFIRST) {
+ if (t->conf.afilter.loudness_summary)
+ userlog(t, "Loudness: %f", global);
+ return PHI_DONE;
+ }
+ return PHI_OK;
+}
+
+static const phi_filter phi_loudness = {
+ loudness_open, loudness_close, loudness_process,
+ "loudness"
+};
+
+
+static const void* loudness_iface(const char *name)
+{
+ if (ffsz_eq(name, "analyze")) return &phi_loudness;
+ return NULL;
+}
+
+static const phi_mod phi_mod_loudness = {
+ .ver = PHI_VERSION, .ver_core = PHI_VERSION_CORE,
+ loudness_iface
+};
+
+FF_EXPORT const phi_mod* phi_mod_init(const phi_core *_core)
+{
+ core = _core;
+ return &phi_mod_loudness;
+}
diff --git a/src/afilter/peaks.c b/src/afilter/peaks.c
index 8828212..192ef99 100644
--- a/src/afilter/peaks.c
+++ b/src/afilter/peaks.c
@@ -8,34 +8,27 @@ extern const phi_core *core;
#define errlog(t, ...) phi_errlog(core, NULL, t, __VA_ARGS__)
#define userlog(t, ...) phi_userlog(core, NULL, t, __VA_ARGS__)
-/** Fast CRC32 implementation using 8k table. */
-extern uint crc32(const void *buf, ffsize size, uint crc);
-
struct peaks {
- uint state;
- uint nch;
+ uint channels;
uint64 total;
struct {
- uint crc;
- uint high;
- uint64 sum;
+ double max;
uint64 clipped;
} ch[8];
- uint do_crc :1;
};
static void* peaks_open(phi_track *t)
{
- struct peaks *p;
- if (t->oaudio.format.channels > FF_COUNT(p->ch)) {
- errlog(t, "invalid channels");
+ if (!(t->oaudio.format.interleaved
+ && t->oaudio.format.format == PHI_PCM_FLOAT64
+ && t->oaudio.format.channels <= 8)) {
+ errlog(t, "invalid input format");
return PHI_OPEN_ERR;
}
- p = phi_track_allocT(t, struct peaks);
- p->nch = t->oaudio.format.channels;
- p->do_crc = t->conf.afilter.peaks_crc;
+ struct peaks *p = phi_track_allocT(t, struct peaks);
+ p->channels = t->oaudio.format.channels;
return p;
}
@@ -46,47 +39,23 @@ static void peaks_close(struct peaks *p, phi_track *t)
static int peaks_process(struct peaks *p, phi_track *t)
{
- ffsize i, ich, samples;
-
- switch (p->state) {
- case 0:
- t->oaudio.conv_format.interleaved = 0;
- t->oaudio.conv_format.format = PHI_PCM_16;
- p->state = 1;
- return PHI_MORE;
-
- case 1:
- if (t->oaudio.format.interleaved
- || t->oaudio.format.format != PHI_PCM_16) {
- errlog(t, "input must be non-interleaved 16LE PCM");
- return PHI_ERR;
- }
- p->state = 2;
- break;
- }
+ ffsize i, c, samples;
- samples = t->data_in.len / (sizeof(short) * p->nch);
+ samples = t->data_in.len / 8 / p->channels;
p->total += samples;
- void **datani = (void**)t->data_in.ptr;
- for (ich = 0; ich != p->nch; ich++) {
+ const double *data = (void*)t->data_in.ptr;
+ for (c = 0; c != p->channels; c++) {
for (i = 0; i != samples; i++) {
- int sh = ((short**)t->data_in.ptr)[ich][i];
-
- if (sh == 0x7fff || sh == -0x8000)
- p->ch[ich].clipped++;
+ double d = data[i * p->channels + c];
- if (sh < 0)
- sh = -sh;
+ if (d >= 1 || d <= -1)
+ p->ch[c].clipped++;
- if (p->ch[ich].high < (uint)sh)
- p->ch[ich].high = sh;
-
- p->ch[ich].sum += sh;
+ d = fabs(d);
+ if (p->ch[c].max < d)
+ p->ch[c].max = d;
}
-
- if (samples != 0 && p->do_crc)
- p->ch[ich].crc = crc32(datani[ich], t->data_in.len / p->nch, p->ch[ich].crc);
}
t->data_out = t->data_in;
@@ -97,14 +66,11 @@ static int peaks_process(struct peaks *p, phi_track *t)
, p->total);
if (p->total != 0) {
- for (ich = 0; ich != p->nch; ich++) {
-
- double hi = gain_db(pcm_16le_flt(p->ch[ich].high));
- double avg = gain_db(pcm_16le_flt(p->ch[ich].sum / p->total));
- ffvec_addfmt(&buf, "Channel #%L: highest peak:%.2FdB, avg peak:%.2FdB. Clipped: %U (%.4F%%). CRC:%08xu\n"
- , ich + 1, hi, avg
- , p->ch[ich].clipped, ((double)p->ch[ich].clipped * 100 / p->total)
- , p->ch[ich].crc);
+ for (c = 0; c != p->channels; c++) {
+
+ double hi = gain_db(p->ch[c].max);
+ ffvec_addfmt(&buf, "Channel #%L: max peak: %.2FdB Clipped: %U\n"
+ , c + 1, hi, p->ch[c].clipped);
}
}
diff --git a/src/core/queue-entry.h b/src/core/queue-entry.h
index 6881d12..a44d3a2 100644
--- a/src/core/queue-entry.h
+++ b/src/core/queue-entry.h
@@ -151,15 +151,17 @@ static int qe_play(struct q_entry *e)
goto err;
t->output.allow_async = 1;
- } else if (c->info_only || c->afilter.peaks_info) {
+ } else if (e->q->conf.analyze) {
if (!track->filter(t, &phi_queue_guard, 0)
|| !track->filter(t, core->mod("core.auto-input"), 0)
|| !track->filter(t, core->mod("format.detect"), 0)
|| !track->filter(t, core->mod("afilter.until"), 0)
|| !track->filter(t, ui_if, 0)
- || !track->filter(t, core->mod("afilter.auto-conv"), 0)
+ || !track->filter(t, core->mod("afilter.auto-conv-f"), 0)
|| (c->afilter.peaks_info
- && !track->filter(t, core->mod("afilter.peaks"), 0)))
+ && !track->filter(t, core->mod("afilter.peaks"), 0))
+ || (c->afilter.loudness_summary
+ && !track->filter(t, core->mod("af-loudness.analyze"), 0)))
goto err;
} else {
diff --git a/src/exe/cmd.h b/src/exe/cmd.h
index 86aad98..0bc5a92 100644
--- a/src/exe/cmd.h
+++ b/src/exe/cmd.h
@@ -227,7 +227,7 @@ Commands:\n\
`convert` Convert audio\n\
`device` List audio devices\n\
`gui` Show graphical interface\n\
- `info` Show file meta data\n\
+ `info` Analyze audio files\n\
`list` Process playlist files\n\
`play` Play audio [Default command]\n\
`record` Record audio\n\
diff --git a/src/exe/info.h b/src/exe/info.h
index 6c92e53..8873761 100644
--- a/src/exe/info.h
+++ b/src/exe/info.h
@@ -4,7 +4,7 @@
static int info_help()
{
help_info_write("\
-Show file meta data:\n\
+Analyze audio files:\n\
`phiola info` [OPTIONS] INPUT...\n\
\n\
INPUT File name, directory or URL\n\
@@ -24,8 +24,8 @@ Options:\n\
[[HH:]MM:]SS[.MSC]\n\
`-until` TIME Stop at time\n\
\n\
+ `-loudness` Analyze audio loudness\n\
`-peaks` Analyze audio and print some details\n\
- `-peaks_crc` Print audio data CRC (use with `-peaks`)\n\
\n\
`-perf` Print performance counters\n\
");
@@ -35,7 +35,7 @@ Options:\n\
struct cmd_info {
u_char duration;
- u_char pcm_crc;
+ u_char loudness;
u_char pcm_peaks;
u_char perf;
u_char tags;
@@ -86,9 +86,9 @@ static void info_qu_add(struct cmd_info *p, ffstr *fn)
.until_msec = p->until,
.afilter = {
.peaks_info = p->pcm_peaks,
- .peaks_crc = p->pcm_crc,
+ .loudness_summary = p->loudness,
},
- .info_only = !p->pcm_peaks,
+ .info_only = !(p->pcm_peaks || p->loudness),
.print_tags = p->tags,
.print_time = p->perf,
};
@@ -105,6 +105,7 @@ static int info_action(struct cmd_info *p)
struct phi_queue_conf qc = {
.first_filter = &phi_guard,
.ui_module = "tui.play",
+ .analyze = 1,
};
x->queue->create(&qc);
ffstr *it;
@@ -131,8 +132,8 @@ static const struct ffarg cmd_info[] = {
{ "-exclude", 'S', info_exclude },
{ "-help", 0, info_help },
{ "-include", 'S', info_include },
+ { "-loudness", '1', O(loudness) },
{ "-peaks", '1', O(pcm_peaks) },
- { "-peaks_crc", '1', O(pcm_crc) },
{ "-perf", '1', O(perf) },
{ "-seek", 'S', info_seek },
{ "-tags", '1', O(tags) },
diff --git a/src/jni/phiola-jni.c b/src/jni/phiola-jni.c
index 4e112fa..e748084 100644
--- a/src/jni/phiola-jni.c
+++ b/src/jni/phiola-jni.c
@@ -202,6 +202,7 @@ static char* mod_loading(ffstr name)
{ "ac-opus", "libopus-phi" },
{ "ac-vorbis", "libvorbis-phi" },
{ "af-danorm", "libDynamicAudioNormalizer-phi" },
+ { "af-loudness","libebur128-phi" },
{ "af-soxr", "libsoxr-phi" },
{ "zstd", "libzstd-ffpack" },
{}
diff --git a/src/phiola.h b/src/phiola.h
index b5e0761..b556ebd 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -289,7 +289,7 @@ struct phi_track_conf {
struct {
double gain_db; // Audio gain/attenuation
uint peaks_info :1;
- uint peaks_crc :1;
+ uint loudness_summary :1;
const char *danorm;
} afilter;
@@ -436,6 +436,7 @@ struct phi_queue_conf {
};
fftime last_mod_time;
uint conversion :1;
+ uint analyze :1;
uint random :1;
uint repeat_all :1;
uint modified :1;
diff --git a/src/track.h b/src/track.h
index 2e8c967..843b7b9 100644
--- a/src/track.h
+++ b/src/track.h
@@ -209,6 +209,7 @@ struct phi_track {
struct {
struct phi_af format;
struct phi_af conv_format;
+ double loudness, loudness_momentary;
// ui -> audio.play
void *adev_ctx;
diff --git a/test.sh b/test.sh
index a411ab0..7a5d293 100644
--- a/test.sh
+++ b/test.sh
@@ -112,7 +112,7 @@ test_info() {
./phiola i pl.wav -tags
./phiola i pl.wav -peaks
- ./phiola i pl.wav -peaks -peaks_crc
+ ./phiola i pl.wav -loudness
if ! test -f fm_wv.wv ; then
ffmpeg_encode pl.wav
From fd02b801b10d70354493c6f7164b252ed3c2e902 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 4 Jan 2025 10:04:59 +0300
Subject: [PATCH 43/59] ref
---
src/core/queue-entry.h | 3 ---
src/core/queue.c | 8 --------
src/gui/gui.c | 2 --
src/gui/mod.c | 44 ++++++++++++++++++++++--------------------
src/gui/mod.h | 2 --
src/gui/settings.hpp | 16 ++++++++++-----
src/phiola.h | 3 ---
7 files changed, 34 insertions(+), 44 deletions(-)
diff --git a/src/core/queue-entry.h b/src/core/queue-entry.h
index a44d3a2..54be390 100644
--- a/src/core/queue-entry.h
+++ b/src/core/queue-entry.h
@@ -186,9 +186,6 @@ static int qe_play(struct q_entry *e)
phi_metaif->copy(&t->meta, &t->conf.meta);
}
- if (qm->dev_idx >= 0)
- t->conf.oaudio.device_index = qm->dev_idx;
-
e->trk = t;
e->used++;
e->q->active_n++;
diff --git a/src/core/queue.c b/src/core/queue.c
index 414c52f..7d7bb37 100644
--- a/src/core/queue.c
+++ b/src/core/queue.c
@@ -18,7 +18,6 @@ struct queue_mgr {
ffvec lists; // struct phi_queue*[]
uint selected;
uint errors;
- int dev_idx;
uint random_ready :1;
on_change_t on_change;
struct q_entry *cursor;
@@ -73,7 +72,6 @@ void qm_destroy()
void qm_init()
{
qm = ffmem_new(struct queue_mgr);
- qm->dev_idx = -1;
qm->on_change = q_on_change;
}
@@ -645,11 +643,6 @@ static void q_sort(phi_queue_id q, uint flags)
qm->on_change(q, 'u', 0);
}
-static void qm_device(uint device)
-{
- qm->dev_idx = device;
-}
-
static void q_remove_multi(phi_queue_id q, uint flags)
{
if (!q) q = qm_default();
@@ -689,7 +682,6 @@ static void q_remove_multi(phi_queue_id q, uint flags)
const phi_queue_if phi_queueif = {
qm_set_on_change,
- qm_device,
q_create,
q_destroy,
diff --git a/src/gui/gui.c b/src/gui/gui.c
index cc67e55..33cdff8 100644
--- a/src/gui/gui.c
+++ b/src/gui/gui.c
@@ -107,8 +107,6 @@ void gui_userconf_load()
int r = ffargs_process_conf(&a, args, NULL, 0, d);
if (r)
warnlog("%s:%s", gd->user_conf_name, a.error);
- else
- playback_device_set();
end:
ffvec_free(&buf);
diff --git a/src/gui/mod.c b/src/gui/mod.c
index c485d7b..71d5cc6 100644
--- a/src/gui/mod.c
+++ b/src/gui/mod.c
@@ -291,9 +291,11 @@ void ctl_play(uint i)
{
if (!gd->q_filtered && !gd->tab_conversion) {
gd->queue->qselect(gd->q_selected); // set the visible list as default
+ // Apply settings for the list that we're activating
struct phi_queue_conf *qc = gd->queue->conf(NULL);
qc->repeat_all = gd->conf.repeat;
qc->random = gd->conf.random;
+ qc->tconf.oaudio.device_index = gd->conf.odev;
}
phi_queue_id q = (gd->q_filtered) ? gd->q_filtered : NULL;
gd->queue->play(NULL, gd->queue->at(q, i));
@@ -348,24 +350,6 @@ static void ctl_seek(uint cmd)
gtrk_seek(gd->playing_track, seek);
}
-/** Toggle 'repeat all/none' setting for the default playlist */
-static void list_repeat_toggle()
-{
- struct phi_queue_conf *qc = gd->queue->conf(NULL);
- gd->conf.repeat = !gd->conf.repeat;
- qc->repeat_all = gd->conf.repeat;
- wmain_status("Repeat: %s", (gd->conf.repeat) ? "All" : "None");
-}
-
-/** Toggle 'random on/off' setting for the default playlist */
-static void list_random_toggle()
-{
- struct phi_queue_conf *qc = gd->queue->conf(NULL);
- gd->conf.random = !gd->conf.random;
- qc->random = gd->conf.random;
- wmain_status("Random: %s", (gd->conf.random) ? "On" : "Off");
-}
-
void ctl_action(uint cmd)
{
uint n;
@@ -445,10 +429,23 @@ void ctl_action(uint cmd)
break;
case A_REPEAT_TOGGLE:
- list_repeat_toggle(); break;
+ case A_RANDOM_TOGGLE: {
+ // Apply settings for the active playlist
+ struct phi_queue_conf *qc = gd->queue->conf(NULL);
+ int r;
+ switch (cmd) {
+ case A_REPEAT_TOGGLE:
+ r = qc->repeat_all = gd->conf.repeat = !gd->conf.repeat;
+ wmain_status("Repeat: %s", (r) ? "All" : "None");
+ break;
- case A_RANDOM_TOGGLE:
- list_random_toggle(); break;
+ case A_RANDOM_TOGGLE:
+ r = qc->random = gd->conf.random = !gd->conf.random;
+ wmain_status("Random: %s", (r) ? "On" : "Off");
+ break;
+ }
+ }
+ break;
}
}
@@ -871,9 +868,14 @@ static void gui_start(void *param)
return;
phi_queue_id q = gd->queue->select(0);
+
struct phi_queue_conf *qc = gd->queue->conf(q);
gd->playback_first_filter = qc->first_filter;
qc->name = ffsz_dup("Playlist 1");
+ qc->repeat_all = gd->conf.repeat;
+ qc->random = gd->conf.random;
+ qc->tconf.oaudio.device_index = gd->conf.odev;
+
struct list_info *li = ffvec_zpushT(&gd->lists, struct list_info);
li->q = q;
gd->q_selected = q;
diff --git a/src/gui/mod.h b/src/gui/mod.h
index 399cae6..3c9cffb 100644
--- a/src/gui/mod.h
+++ b/src/gui/mod.h
@@ -156,8 +156,6 @@ struct gui_data {
};
FF_EXTERN struct gui_data *gd;
-static inline void playback_device_set() { gd->queue->device(gd->conf.odev); }
-
#define syserrlog(...) \
core->conf.log(core->conf.log_obj, PHI_LOG_ERR | PHI_LOG_SYS, "gui", NULL, __VA_ARGS__)
diff --git a/src/gui/settings.hpp b/src/gui/settings.hpp
index 84ef8c2..040a564 100644
--- a/src/gui/settings.hpp
+++ b/src/gui/settings.hpp
@@ -57,10 +57,17 @@ static void wsettings_ui_to_conf()
if (w->cbdarktheme.h)
theme_switch(w->cbdarktheme.checked());
- uint odev = w->cbdev.get();
- if (gd->conf.odev != odev) {
- gd->conf.odev = odev;
- playback_device_set();
+ uint mod = 0, r;
+
+ if (gd->conf.odev != (r = w->cbdev.get())) {
+ gd->conf.odev = r;
+ mod = 1;
+ }
+
+ if (mod) {
+ // Apply settings for the active playlist
+ struct phi_queue_conf *qc = gd->queue->conf(NULL);
+ qc->tconf.oaudio.device_index = gd->conf.odev;
}
gd->conf.seek_step_delta = xxvec(w->eseek_by.text()).str().uint32(10);
@@ -78,7 +85,6 @@ static void wsettings_ui_from_conf()
uint odev = adevices_fill(PHI_ADEV_PLAYBACK, w->cbdev, gd->conf.odev);
if (gd->conf.odev != odev) {
gd->conf.odev = odev;
- playback_device_set();
}
xxstr_buf<100> s;
diff --git a/src/phiola.h b/src/phiola.h
index b556ebd..6633369 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -475,9 +475,6 @@ struct phi_queue_if {
*/
void (*on_change)(void (*cb)(phi_queue_id q, uint flags, uint pos));
- /** Override output device index for all tracks */
- void (*device)(uint device);
-
phi_queue_id (*create)(struct phi_queue_conf *conf);
void (*destroy)(phi_queue_id q);
phi_queue_id (*select)(uint pos);
From b2bdf962536a43ba4cdf191c898cc2c25e2c3893 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 4 Jan 2025 10:04:59 +0300
Subject: [PATCH 44/59] + `play -norm ""`: Auto Loudness Normalizer
+ Android: Settings->Playback: Auto Loudness Normalizer
! removed `play -danorm` function
---
.../java/com/github/stsaz/phiola/Phiola.java | 4 +-
.../java/com/github/stsaz/phiola/Queue.java | 11 +-
.../github/stsaz/phiola/SettingsActivity.java | 4 +-
.../com/github/stsaz/phiola/UtilNative.java | 3 +-
.../phiola/src/main/res/layout/settings.xml | 7 ++
.../phiola/src/main/res/values/strings.xml | 1 +
src/afilter/Makefile | 1 +
src/afilter/afilter.c | 2 +
src/afilter/auto-norm.c | 119 ++++++++++++++++++
src/core/queue-entry.h | 6 +-
src/exe/play.h | 17 ++-
src/gui/lang_en.conf | 1 +
src/gui/mod.c | 4 +
src/gui/mod.h | 1 +
src/gui/settings.hpp | 11 +-
src/gui/ui-gtk.conf | 4 +
src/gui/ui-winapi.conf | 4 +
src/jni/android-utils.h | 1 +
src/jni/phiola-jni.c | 1 +
src/jni/queue.h | 18 ++-
src/phiola.h | 1 +
test.sh | 8 ++
22 files changed, 210 insertions(+), 19 deletions(-)
create mode 100644 src/afilter/auto-norm.c
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
index a3e23de..1a94c00 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
@@ -158,7 +158,9 @@ to the index within its parent (not filtered) list */
/** Update current status of all entries.
Return 0 when conversion is complete. */
- QUCOM_CONV_UPDATE = 14;
+ QUCOM_CONV_UPDATE = 14,
+
+ QUCOM_AUTO_NORM = 15;
native int quCmd(long q, int cmd, int i);
native Meta quMeta(long q, int i);
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
index 89a723a..ea21a7e 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
@@ -130,7 +130,8 @@ class Queue {
F_RANDOM = 2,
F_MOVE_ON_NEXT = 4,
F_RM_ON_NEXT = 8,
- F_RM_ON_ERR = 0x10;
+ F_RM_ON_ERR = 0x10,
+ F_AUTO_NORM = 0x20;
private int flags;
void flags_set1(int mask, boolean val) {
int i = 0;
@@ -145,6 +146,9 @@ void flags_set(int mask, int val) {
if ((mask & F_RANDOM) != 0 && (val & F_RANDOM) != (this.flags & F_RANDOM))
phi.quCmd(-1, Phiola.QUCOM_RANDOM, val & F_RANDOM);
+ if ((mask & F_AUTO_NORM) != 0 && (val & F_AUTO_NORM) != (this.flags & F_AUTO_NORM))
+ phi.quCmd(-1, Phiola.QUCOM_AUTO_NORM, val & F_AUTO_NORM);
+
if ((mask & F_RM_ON_ERR) != 0 && (val & F_RM_ON_ERR) != (this.flags & F_RM_ON_ERR))
phi.quCmd(-1, Phiola.QUCOM_REMOVE_ON_ERROR, val & F_RM_ON_ERR);
@@ -309,6 +313,8 @@ void load() {
phi.quCmd(-1, Phiola.QUCOM_RANDOM, 1);
if (flags_test(F_RM_ON_ERR))
phi.quCmd(-1, Phiola.QUCOM_REMOVE_ON_ERROR, 1);
+ if (flags_test(F_AUTO_NORM))
+ phi.quCmd(-1, Phiola.QUCOM_AUTO_NORM, 1);
core.phiola.quSetCallback(this::on_change);
}
@@ -556,6 +562,7 @@ String conf_write() {
+ "list_add_rm_on_next %d\n"
+ "list_rm_on_next %d\n"
+ "list_rm_on_err %d\n"
+ + "play_auto_norm %d\n"
+ "play_auto_stop %d\n"
, curpos
, i_active
@@ -564,6 +571,7 @@ String conf_write() {
, core.bool_to_int(flags_test(F_MOVE_ON_NEXT))
, core.bool_to_int(flags_test(F_RM_ON_NEXT))
, core.bool_to_int(flags_test(F_RM_ON_ERR))
+ , core.bool_to_int(flags_test(F_AUTO_NORM))
, auto_stop_value_min
);
}
@@ -579,6 +587,7 @@ void conf_load(Conf.Entry[] kv) {
if (kv[Conf.LIST_ADD_RM_ON_NEXT].enabled) f |= F_MOVE_ON_NEXT;
if (kv[Conf.LIST_RM_ON_NEXT].enabled) f |= F_RM_ON_NEXT;
if (kv[Conf.LIST_RM_ON_ERR].enabled) f |= F_RM_ON_ERR;
+ if (kv[Conf.PLAY_AUTO_NORM].enabled) f |= F_AUTO_NORM;
this.flags = f;
auto_stop_value_min = kv[Conf.PLAY_AUTO_STOP].number;
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java
index c5f3df2..1b3724a 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java
@@ -190,6 +190,7 @@ private void load() {
b.swListAddRmOnNext.setChecked(queue.flags_test(Queue.F_MOVE_ON_NEXT));
b.swListRmOnNext.setChecked(queue.flags_test(Queue.F_RM_ON_NEXT));
b.swListRmOnErr.setChecked(queue.flags_test(Queue.F_RM_ON_ERR));
+ b.swAutoNorm.setChecked(queue.flags_test(Queue.F_AUTO_NORM));
b.sbPlayAutoStop.setProgress(auto_stop_progress(queue.auto_stop_value_min));
b.eAutoStop.setText(core.int_to_str(queue.auto_stop_value_min));
@@ -236,7 +237,8 @@ private void save() {
f |= flag(Queue.F_MOVE_ON_NEXT, b.swListAddRmOnNext.isChecked());
f |= flag(Queue.F_RM_ON_NEXT, b.swListRmOnNext.isChecked());
f |= flag(Queue.F_RM_ON_ERR, b.swListRmOnErr.isChecked());
- int mask = Queue.F_RANDOM | Queue.F_REPEAT | Queue.F_MOVE_ON_NEXT | Queue.F_RM_ON_NEXT | Queue.F_RM_ON_ERR;
+ f |= flag(Queue.F_AUTO_NORM, b.swAutoNorm.isChecked());
+ int mask = Queue.F_RANDOM | Queue.F_REPEAT | Queue.F_MOVE_ON_NEXT | Queue.F_RM_ON_NEXT | Queue.F_RM_ON_ERR | Queue.F_AUTO_NORM;
queue.flags_set(mask, f);
queue.auto_stop_value_min = core.str_to_uint(b.eAutoStop.getText().toString(), 0);
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java b/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java
index 1a44f7e..c66631e 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java
@@ -34,7 +34,8 @@ static class Entry {
OP_PLIST_SAVE_DIR = OP_FILE_DELETE + 1,
OP_QUICK_MOVE_DIR = OP_PLIST_SAVE_DIR + 1,
OP_TRASH_DIR_REL = OP_QUICK_MOVE_DIR + 1,
- PLAY_AUTO_SKIP = OP_TRASH_DIR_REL + 1,
+ PLAY_AUTO_NORM = OP_TRASH_DIR_REL + 1,
+ PLAY_AUTO_SKIP = PLAY_AUTO_NORM + 1,
PLAY_AUTO_SKIP_TAIL = PLAY_AUTO_SKIP + 1,
PLAY_AUTO_STOP = PLAY_AUTO_SKIP_TAIL + 1,
REC_BITRATE = PLAY_AUTO_STOP + 1,
diff --git a/android/phiola/src/main/res/layout/settings.xml b/android/phiola/src/main/res/layout/settings.xml
index 50ad2e7..b284d34 100644
--- a/android/phiola/src/main/res/layout/settings.xml
+++ b/android/phiola/src/main/res/layout/settings.xml
@@ -186,6 +186,13 @@
android:inputType="text"
android:textSize="@dimen/text_editbox" />
+
+
Codepage for Non-Unicode Tags
Auto-Skip the Beginning of Each Track (% or sec):
Auto-Skip the Ending of Each Track (% or sec):
+ Auto Loudness Normalizer
Operation
Don\'t Move Files to Trash
diff --git a/src/afilter/Makefile b/src/afilter/Makefile
index f7e2e0e..f6f74eb 100644
--- a/src/afilter/Makefile
+++ b/src/afilter/Makefile
@@ -27,6 +27,7 @@ afilter.$(SO): afilter.o \
peaks.o \
gain.o \
rtpeak.o \
+ auto-norm.o \
conv.o \
\
str-format.o
diff --git a/src/afilter/afilter.c b/src/afilter/afilter.c
index 5b0e545..1b3ce56 100644
--- a/src/afilter/afilter.c
+++ b/src/afilter/afilter.c
@@ -20,6 +20,7 @@ const phi_core *core;
extern const phi_filter
phi_aconv,
+ phi_auto_norm,
phi_gain,
phi_peaks,
phi_rtpeak;
@@ -28,6 +29,7 @@ static const void* af_iface(const char *name)
static const struct map_sz_vptr mods[] = {
{ "auto-conv", &phi_autoconv },
{ "auto-conv-f",&phi_autoconv_f },
+ { "auto-norm", &phi_auto_norm },
{ "conv", &phi_aconv },
{ "gain", &phi_gain },
{ "peaks", &phi_peaks },
diff --git a/src/afilter/auto-norm.c b/src/afilter/auto-norm.c
new file mode 100644
index 0000000..65be061
--- /dev/null
+++ b/src/afilter/auto-norm.c
@@ -0,0 +1,119 @@
+/** phiola: auto loudness normalizer
+2024, Simon Zolin */
+
+#include
+#include
+#include
+#include
+
+extern const phi_core *core;
+#define errlog(t, ...) phi_errlog(core, NULL, t, __VA_ARGS__)
+#define dbglog(t, ...) phi_dbglog(core, NULL, t, __VA_ARGS__)
+
+struct autonorm_conf {
+ int target;
+ int max_gain, max_attenuate;
+};
+
+#define O(m) (void*)(size_t)FF_OFF(struct autonorm_conf, m)
+static const struct ffarg autonorm_conf_args[] = {
+ { "attenuate", 'd', O(max_attenuate) },
+ { "gain", 'd', O(max_gain) },
+ { "target", 'd', O(target) },
+ {}
+};
+#undef O
+
+struct autonorm {
+ uint state;
+ struct phi_af af;
+ double gain_db, gain;
+ ffvec buf;
+ struct autonorm_conf conf;
+};
+
+static void* anorm_open(phi_track *t)
+{
+ struct autonorm *c = phi_track_allocT(t, struct autonorm);
+ c->af = t->oaudio.format;
+ struct autonorm_conf conf = {
+ .target = -14,
+ .max_gain = 6,
+ .max_attenuate = -6,
+ };
+ c->conf = conf;
+ struct ffargs a = {};
+ if (ffargs_process_line(&a, autonorm_conf_args, &c->conf, FFARGS_O_PARTIAL | FFARGS_O_DUPLICATES, t->conf.afilter.auto_normalizer)) {
+ errlog(t, "%s", a.error);
+ phi_track_free(t, c);
+ return PHI_OPEN_ERR;
+ }
+ return c;
+}
+
+static void anorm_close(void *ctx, phi_track *t)
+{
+ struct autonorm *c = ctx;
+ ffvec_free(&c->buf);
+ phi_track_free(t, c);
+}
+
+/**
+e.g. for Target=-14:
+Loudness LM Gain
+-inf (hold)
+-8 -6
+-14 0
+-20 +6
+< -10 -8 -6
+*/
+static int anorm_process(void *ctx, phi_track *t)
+{
+ struct autonorm *c = ctx;
+ t->data_out = t->data_in;
+
+ switch (c->state) {
+ case 0:
+ c->state = 1;
+ return PHI_OK;
+
+ case 1:
+ ffvec_addstr(&c->buf, &t->data_in);
+ t->data_out = *(ffstr*)&c->buf;
+ if (isinf(t->oaudio.loudness)) {
+ if (t->chain_flags & PHI_FFIRST)
+ return PHI_DONE;
+ return PHI_MORE;
+ }
+ c->state = 2;
+ break;
+
+ case 2:
+ ffvec_free(&c->buf);
+ c->state = 3;
+ }
+
+ double db = c->conf.target - t->oaudio.loudness;
+ if (t->oaudio.loudness_momentary - t->oaudio.loudness > 2)
+ db = c->conf.target - t->oaudio.loudness_momentary;
+ if (db > c->conf.max_gain)
+ db = c->conf.max_gain;
+ else if (db < c->conf.max_attenuate)
+ db = c->conf.max_attenuate;
+
+ if (db != c->gain_db) {
+ c->gain_db = db;
+ c->gain = db_gain(db);
+ dbglog(t, "gain: %.02FdB %.02F", c->gain_db, c->gain);
+ }
+
+ if (db != 0)
+ pcm_gain(&c->af, c->gain, t->data_out.ptr, t->data_out.ptr, t->data_out.len / pcm_size1(&c->af));
+
+ return (t->chain_flags & PHI_FFIRST) ? PHI_DONE : PHI_OK;
+}
+
+const phi_filter phi_auto_norm = {
+ anorm_open, anorm_close, anorm_process,
+ "auto-norm"
+};
diff --git a/src/core/queue-entry.h b/src/core/queue-entry.h
index 54be390..5473cd2 100644
--- a/src/core/queue-entry.h
+++ b/src/core/queue-entry.h
@@ -171,9 +171,11 @@ static int qe_play(struct q_entry *e)
&& !track->filter(t, core->mod("core.tee"), 0))
|| !track->filter(t, core->mod("format.detect"), 0)
|| !track->filter(t, core->mod("afilter.until"), 0)
- || (c->afilter.danorm
- && !track->filter(t, core->mod("af-danorm.f"), 0))
|| !track->filter(t, ui_if, 0)
+ || (c->afilter.auto_normalizer
+ && (!track->filter(t, core->mod("afilter.auto-conv-f"), 0)
+ || !track->filter(t, core->mod("af-loudness.analyze"), 0)
+ || !track->filter(t, core->mod("afilter.auto-norm"), 0)))
|| !track->filter(t, core->mod("afilter.gain"), 0)
|| !track->filter(t, core->mod("afilter.auto-conv"), 0)
|| (c->tee_output
diff --git a/src/exe/play.h b/src/exe/play.h
index 5ab9539..b7120fc 100644
--- a/src/exe/play.h
+++ b/src/exe/play.h
@@ -36,13 +36,10 @@ Options:\n\
[[HH:]MM:]SS[.MSC]\n\
`-until` TIME Stop at time\n\
\n\
- `-danorm` \"OPTIONS\" Apply Dynamic Audio Normalizer filter. Options:\n\
- `frame` Integer\n\
- `size` Integer\n\
- `peak` Float\n\
- `max-amp` Float\n\
- `target-rms` Float\n\
- `compress` Float\n\
+ `-norm` \"OPTIONS\" Auto loudness normalizer. Options:\n\
+ `target` Integer\n\
+ `attenuate` Integer\n\
+ `gain` Integer\n\
\n\
`-audio` STRING Audio library name (e.g. alsa)\n\
`-device` NUMBER Playback device number\n\
@@ -63,7 +60,7 @@ Options:\n\
struct cmd_play {
char* audio_module;
- const char* danorm;
+ const char* auto_norm;
const char* dup;
const char* tee;
ffstr audio;
@@ -131,7 +128,7 @@ static void play_qu_add(struct cmd_play *p, ffstr *fn)
.seek_msec = p->seek,
.until_msec = p->until,
.afilter = {
- .danorm = p->danorm,
+ .auto_normalizer = p->auto_norm,
},
.oaudio = {
.device_index = p->device,
@@ -209,7 +206,6 @@ static const struct ffarg cmd_play[] = {
{ "-audio", 'S', O(audio) },
{ "-buffer", 'u', O(buffer) },
{ "-connect_timeout", 'u', O(connect_timeout) },
- { "-danorm", 's', O(danorm) },
{ "-device", 'u', O(device) },
{ "-dup", 's', O(dup) },
{ "-exclude", 'S', play_exclude },
@@ -217,6 +213,7 @@ static const struct ffarg cmd_play[] = {
{ "-help", 0, play_help },
{ "-include", 'S', play_include },
{ "-no_meta", '1', O(no_meta) },
+ { "-norm", 's', O(auto_norm) },
{ "-perf", '1', O(perf) },
{ "-random", '1', O(random) },
{ "-rbuffer", 'u', O(rbuffer_kb) },
diff --git a/src/gui/lang_en.conf b/src/gui/lang_en.conf
index f4ab0a7..7edd846 100644
--- a/src/gui/lang_en.conf
+++ b/src/gui/lang_en.conf
@@ -19,6 +19,7 @@ MMFile "_File"
STSeekBy "Seek By (sec)"
STLeapBy "Leap By (sec)"
STAutoSkip "Auto Skip the Beginning of Each Track (% or sec)"
+ STAutoNorm "Auto Loudness Normalizer"
MFExit "E_xit"
MMList "_List"
diff --git a/src/gui/mod.c b/src/gui/mod.c
index 71d5cc6..721f225 100644
--- a/src/gui/mod.c
+++ b/src/gui/mod.c
@@ -32,6 +32,7 @@ static void gui_finish();
#define O(m) (void*)FF_OFF(struct gui_data, m)
const struct ffarg guimod_args[] = {
{ "list.auto_sel", 'u', O(auto_select) },
+ { "play.auto_norm", 'u', O(conf.auto_norm) },
{ "play.auto_skip", 'd', O(conf.auto_skip_sec_percent) },
{ "play.cursor", 'u', O(cursor) },
{ "play.dev", 'u', O(conf.odev) },
@@ -48,6 +49,7 @@ const struct ffarg guimod_args[] = {
void mod_userconf_write(ffconfw *cw)
{
ffconfw_add2u(cw, "list.auto_sel", gd->auto_select);
+ ffconfw_add2u(cw, "play.auto_norm", gd->conf.auto_norm);
ffconfw_add2u(cw, "play.auto_skip", (ffint64)gd->conf.auto_skip_sec_percent);
ffconfw_add2u(cw, "play.cursor", gd->cursor);
ffconfw_add2u(cw, "play.dev", gd->conf.odev);
@@ -295,6 +297,7 @@ void ctl_play(uint i)
struct phi_queue_conf *qc = gd->queue->conf(NULL);
qc->repeat_all = gd->conf.repeat;
qc->random = gd->conf.random;
+ qc->tconf.afilter.auto_normalizer = (gd->conf.auto_norm) ? "" : NULL;
qc->tconf.oaudio.device_index = gd->conf.odev;
}
phi_queue_id q = (gd->q_filtered) ? gd->q_filtered : NULL;
@@ -874,6 +877,7 @@ static void gui_start(void *param)
qc->name = ffsz_dup("Playlist 1");
qc->repeat_all = gd->conf.repeat;
qc->random = gd->conf.random;
+ qc->tconf.afilter.auto_normalizer = (gd->conf.auto_norm) ? "" : NULL;
qc->tconf.oaudio.device_index = gd->conf.odev;
struct list_info *li = ffvec_zpushT(&gd->lists, struct list_info);
diff --git a/src/gui/mod.h b/src/gui/mod.h
index 3c9cffb..dcaca38 100644
--- a/src/gui/mod.h
+++ b/src/gui/mod.h
@@ -149,6 +149,7 @@ struct gui_data {
uint odev;
uint repeat;
uint random;
+ uint auto_norm;
uint seek_step_delta;
uint seek_leap_delta;
int auto_skip_sec_percent;
diff --git a/src/gui/settings.hpp b/src/gui/settings.hpp
index 040a564..5476817 100644
--- a/src/gui/settings.hpp
+++ b/src/gui/settings.hpp
@@ -5,7 +5,7 @@ struct gui_wsettings {
ffui_windowxx wnd;
ffui_label ldev, lseek_by, lleap_by, lauto_skip;
ffui_editxx eseek_by, eleap_by, eauto_skip;
- ffui_checkboxxx cbdarktheme;
+ ffui_checkboxxx cbdarktheme, cbauto_norm;
ffui_comboboxxx cbdev;
char* wnd_pos;
@@ -21,6 +21,7 @@ FF_EXTERN const ffui_ldr_ctl wsettings_ctls[] = {
_(lseek_by), _(eseek_by),
_(lleap_by), _(eleap_by),
_(lauto_skip), _(eauto_skip),
+ _(cbauto_norm),
FFUI_LDR_CTL_END
};
#undef _
@@ -59,6 +60,11 @@ static void wsettings_ui_to_conf()
uint mod = 0, r;
+ if (gd->conf.auto_norm != (r = w->cbauto_norm.checked())) {
+ gd->conf.auto_norm = r;
+ mod = 1;
+ }
+
if (gd->conf.odev != (r = w->cbdev.get())) {
gd->conf.odev = r;
mod = 1;
@@ -67,6 +73,7 @@ static void wsettings_ui_to_conf()
if (mod) {
// Apply settings for the active playlist
struct phi_queue_conf *qc = gd->queue->conf(NULL);
+ qc->tconf.afilter.auto_normalizer = (gd->conf.auto_norm) ? "" : NULL;
qc->tconf.oaudio.device_index = gd->conf.odev;
}
@@ -82,6 +89,8 @@ static void wsettings_ui_from_conf()
if (w->cbdarktheme.h)
w->cbdarktheme.check(!!gd->conf.theme);
+ w->cbauto_norm.check(!!gd->conf.auto_norm);
+
uint odev = adevices_fill(PHI_ADEV_PLAYBACK, w->cbdev, gd->conf.odev);
if (gd->conf.odev != odev) {
gd->conf.odev = odev;
diff --git a/src/gui/ui-gtk.conf b/src/gui/ui-gtk.conf
index 9bf9e7a..22b3e18 100644
--- a/src/gui/ui-gtk.conf
+++ b/src/gui/ui-gtk.conf
@@ -594,4 +594,8 @@ window wsettings {
}
editbox eauto_skip {
}
+
+ checkbox cbauto_norm {
+ text $STAutoNorm
+ }
}
diff --git a/src/gui/ui-winapi.conf b/src/gui/ui-winapi.conf
index a440070..e6d0e2d 100644
--- a/src/gui/ui-winapi.conf
+++ b/src/gui/ui-winapi.conf
@@ -656,4 +656,8 @@ window wsettings {
}
editbox eauto_skip {
}
+
+ checkbox cbauto_norm {
+ text $STAutoNorm
+ }
}
diff --git a/src/jni/android-utils.h b/src/jni/android-utils.h
index 89fb118..0eb291e 100644
--- a/src/jni/android-utils.h
+++ b/src/jni/android-utils.h
@@ -30,6 +30,7 @@ static const char setting_names[][20] = {
"op_plist_save_dir",
"op_quick_move_dir",
"op_trash_dir_rel",
+ "play_auto_norm",
"play_auto_skip",
"play_auto_skip_tail",
"play_auto_stop",
diff --git a/src/jni/phiola-jni.c b/src/jni/phiola-jni.c
index e748084..6e0576b 100644
--- a/src/jni/phiola-jni.c
+++ b/src/jni/phiola-jni.c
@@ -41,6 +41,7 @@ struct phiola_jni {
uint auto_stop_timer_expired :1;
uint repeat_all :1;
uint random :1;
+ uint auto_normalizer :1;
} play;
struct {
diff --git a/src/jni/queue.h b/src/jni/queue.h
index 21169ca..46a7c82 100644
--- a/src/jni/queue.h
+++ b/src/jni/queue.h
@@ -253,6 +253,7 @@ enum {
QUCOM_REMOVE_ON_ERROR = 12,
QUCOM_CONV_CANCEL = 13,
QUCOM_CONV_UPDATE = 14,
+ QUCOM_AUTO_NORM = 15,
};
static void qu_cmd(struct core_data *d)
@@ -276,6 +277,7 @@ static void qu_cmd(struct core_data *d)
struct phi_queue_conf *qc = x->queue.conf(NULL);
qc->repeat_all = x->play.repeat_all;
qc->random = x->play.random;
+ qc->tconf.afilter.auto_normalizer = (x->play.auto_normalizer) ? "" : NULL;
x->queue.play(NULL, x->queue.at(q, d->param_int));
break;
}
@@ -315,10 +317,22 @@ Java_com_github_stsaz_phiola_Phiola_quCmd(JNIEnv *env, jobject thiz, jlong jq, j
x->play.remove_on_error = !!i; break;
case QUCOM_REPEAT:
- x->play.repeat_all = !!i; break;
+ x->play.repeat_all = !!i; goto qc_apply;
case QUCOM_RANDOM:
- x->play.random = !!i; break;
+ x->play.random = !!i; goto qc_apply;
+
+ case QUCOM_AUTO_NORM:
+ x->play.auto_normalizer = !!i;
+
+ qc_apply: {
+ // Apply settings for the active playlist
+ struct phi_queue_conf *qc = x->queue.conf(NULL);
+ qc->repeat_all = x->play.repeat_all;
+ qc->random = x->play.random;
+ qc->tconf.afilter.auto_normalizer = (x->play.auto_normalizer) ? "" : NULL;
+ }
+ break;
case QUCOM_CONV_CANCEL:
FFINT_WRITEONCE(x->convert.interrupt, 1); break;
diff --git a/src/phiola.h b/src/phiola.h
index 6633369..8c517b8 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -290,6 +290,7 @@ struct phi_track_conf {
double gain_db; // Audio gain/attenuation
uint peaks_info :1;
uint loudness_summary :1;
+ const char *auto_normalizer;
const char *danorm;
} afilter;
diff --git a/test.sh b/test.sh
index 7a5d293..a82b913 100644
--- a/test.sh
+++ b/test.sh
@@ -410,6 +410,13 @@ test_danorm() {
./phiola rec -danorm "" -f -o dan_rec96k.flac -u 10 -af int24 -rate 96000 ; ./phiola i dan_rec96k.flac | grep 'int24 96000Hz' ; ./phiola dan_rec96k.flac
}
+test_norm() {
+ if ! test -f pl.wav ; then
+ ./phiola rec -rate 48000 -o pl.wav -f -u 2
+ fi
+ ./phiola pl pl.wav -norm ""
+}
+
test_dir_read() {
./phiola i -inc '*.wav' .
./phiola i -inc '*.wav' -exc 'co*.wav' .
@@ -702,6 +709,7 @@ TESTS=(
copy
meta
danorm
+ norm
dir_read
list
list_heal
From d8e0aca0b39ebb5cb8f52ff9300ed36bc00f0db9 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 4 Jan 2025 10:04:59 +0300
Subject: [PATCH 45/59] ref: soxr
---
README.md | 2 +-
alib3/soxr/Makefile | 41 +++--
alib3/soxr/soxr-config.h | 27 ---
alib3/soxr/soxr-phi.c | 93 +++++++++++
alib3/soxr/soxr-phi.h | 55 +++++++
alib3/soxr/soxr.h | 348 ---------------------------------------
src/afilter/Makefile | 4 +-
src/afilter/soxr-conv.c | 153 +++++++++++++++++
src/afilter/soxr-conv.h | 98 -----------
src/afilter/soxr.c | 27 ---
src/afilter/soxr.h | 162 ------------------
11 files changed, 323 insertions(+), 687 deletions(-)
delete mode 100644 alib3/soxr/soxr-config.h
create mode 100644 alib3/soxr/soxr-phi.c
create mode 100644 alib3/soxr/soxr-phi.h
delete mode 100644 alib3/soxr/soxr.h
create mode 100644 src/afilter/soxr-conv.c
delete mode 100644 src/afilter/soxr-conv.h
delete mode 100644 src/afilter/soxr.c
delete mode 100644 src/afilter/soxr.h
diff --git a/README.md b/README.md
index 92de632..ab6a0cc 100644
--- a/README.md
+++ b/README.md
@@ -343,7 +343,7 @@ libmpc,
[libopus](https://github.com/xiph/opus),
[libvorbis](https://github.com/xiph/vorbis),
libwavpack,
-libsoxr,
+[libsoxr](https://github.com/dofuuz/soxr),
[libzstd](https://github.com/facebook/zstd),
[libDynamicAudioNormalizer](https://github.com/lordmulder/DynamicAudioNormalizer).
And these unmodified libraries:
diff --git a/alib3/soxr/Makefile b/alib3/soxr/Makefile
index 4409580..3236440 100644
--- a/alib3/soxr/Makefile
+++ b/alib3/soxr/Makefile
@@ -2,11 +2,11 @@
include ../config.mk
-VER := 0.1.3
-URL := https://github.com/stsaz/phiola/raw/alib3/alib3/soxr/soxr-0.1.3-Source.tar.xz
-MD5SUM := 3f16f4dcb35b471682d4321eda6f6c08
-PKG := $(ALIB3)/soxr/$(notdir $(URL))
-DIR := soxr-$(VER)-Source
+VER := edbdb4081db14eb21729b57c4f2c813514ad0259
+URL := https://github.com/dofuuz/soxr/archive/soxr-$(VER).zip
+MD5SUM := e5294f21a1d98860f4d94fe40ebe7f7c
+PKG := $(ALIB3)/soxr/soxr-$(VER).zip
+DIR := soxr-$(VER)
LIB := libsoxr-phi.$(SO)
default: $(DIR)/soxr-config.h
@@ -19,11 +19,11 @@ $(PKG):
# unpack
$(DIR): $(PKG)
echo "$(MD5SUM) *$(PKG)" | md5sum -c -
- $(UNTAR_XZ) $(PKG)
+ $(UNZIP) $(PKG)
$(DIR)/soxr-config.h: | $(DIR)
cd $(DIR) && cmake -Wno-dev -DCMAKE_BUILD_TYPE="Release" $(CMAKE_FLAGS) .
- cat $(ALIB3)/soxr/*.patch | patch -d $(DIR) -p1
+ # cat $(ALIB3)/soxr/*.patch | patch -d $(DIR) -p1
# cd $(DIR) && make VERBOSE=1
$(SED) 's/AVUTIL_FOUND 1/AVUTIL_FOUND 0/' $(DIR)/soxr-config.h
ifeq "$(CPU)" "amd64"
@@ -42,7 +42,7 @@ ifeq "$(CPU)" "arm"
# w/a for `cannot locate symbol "fetestexcept"`
CFLAGS += -DSOXR_NO_FETESTEXCEPT
endif
-ifeq ($(OS),windows)
+ifeq "$(OS)" "windows"
CFLAGS += -DSOXR_DLL -Dsoxr_EXPORTS -D_USE_MATH_DEFINES
else
CFLAGS += -DSOXR_VISIBILITY
@@ -65,28 +65,25 @@ SRC_SIMD32 := \
$(DIR)/src/pffft32s.c \
$(DIR)/src/util32s.c
+SRC_AVX := \
+ $(DIR)/src/cr64s.c \
+ $(DIR)/src/pffft64s.c \
+ $(DIR)/src/util64s.c
+
SRC += $(SRC_SIMD32)
ifeq "$(CPU)" "amd64"
- SRC += \
- $(DIR)/src/cr64s.c \
- $(DIR)/src/pffft64s.c \
- $(DIR)/src/util64s.c
+ SRC += $(SRC_AVX)
+$(SRC_AVX:.c=.o): CFLAGS += -mavx
endif
-OBJ := $(SRC:.c=.o)
+OBJ := soxr-phi.o $(SRC:.c=.o)
-%.o: %.c $(wildcard $(DIR)/src/*.h)
+%.o: $(ALIB3)/soxr/%.c $(wildcard $(DIR)/src/*.h)
$(C) $(CFLAGS) $< -o $@
-$(DIR)/src/cr64s.o: $(DIR)/src/cr64s.c $(wildcard $(DIR)/src/*.h)
- $(C) $(CFLAGS) -mavx $< -o $@
-
-$(DIR)/src/pffft64s.o: $(DIR)/src/pffft64s.c $(wildcard $(DIR)/src/*.h)
- $(C) $(CFLAGS) -mavx $< -o $@
-
-$(DIR)/src/util64s.o: $(DIR)/src/util64s.c $(wildcard $(DIR)/src/*.h)
- $(C) $(CFLAGS) -mavx $< -o $@
+%.o: %.c $(wildcard $(DIR)/src/*.h)
+ $(C) $(CFLAGS) $< -o $@
$(LIB): $(OBJ)
$(LINK) -shared $+ $(LINKFLAGS) -o $@
diff --git a/alib3/soxr/soxr-config.h b/alib3/soxr/soxr-config.h
deleted file mode 100644
index 6f8b2a6..0000000
--- a/alib3/soxr/soxr-config.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/* SoX Resampler Library Copyright (c) 2007-16 robs@users.sourceforge.net
- * Licence for this file: LGPL v2.1 See LICENCE for details. */
-
-#if !defined soxr_config_included
-#define soxr_config_included
-
-#define AVCODEC_FOUND 0
-#define AVUTIL_FOUND 0
-#define WITH_PFFFT 1
-
-#define HAVE_FENV_H 1
-#define HAVE_STDBOOL_H 1
-#define HAVE_STDINT_H 1
-#define HAVE_LRINT 1
-#define HAVE_BIGENDIAN 0
-
-#define WITH_CR32 1
-#define WITH_CR32S 1
-#define WITH_CR64 1
-#define WITH_CR64S 1
-#define WITH_VR32 1
-
-#define WITH_HI_PREC_CLOCK 1
-#define WITH_FLOAT_STD_PREC_CLOCK 0
-#define WITH_DEV_TRACE 1
-
-#endif
diff --git a/alib3/soxr/soxr-phi.c b/alib3/soxr/soxr-phi.c
new file mode 100644
index 0000000..aaeb54c
--- /dev/null
+++ b/alib3/soxr/soxr-phi.c
@@ -0,0 +1,93 @@
+/** libsoxr wrapper */
+
+#include "soxr-phi.h"
+#include
+#include
+
+struct soxr_ctx {
+ soxr_t soxr;
+ struct soxr_conf conf;
+ unsigned i_sample_size, o_sample_size;
+};
+
+void phi_soxr_destroy(soxr_ctx *c)
+{
+ if (!c) return;
+
+ soxr_delete(c->soxr);
+ free(c);
+}
+
+static int sx_format(int f, int interleaved)
+{
+ static const char formats[][2][4] = {
+ {
+ { SOXR_INT16_S, SOXR_INT32_S, 0, 0 },
+ { 0, SOXR_FLOAT32_S, 0, SOXR_FLOAT64_S },
+ },
+ {
+ { SOXR_INT16_I, SOXR_INT32_I, 0, 0 },
+ { 0, SOXR_FLOAT32_I, 0, SOXR_FLOAT64_I },
+ },
+ };
+ return formats[interleaved][!!(f & 0x0100)][(f & 0xff) / 16 - 1];
+}
+
+int phi_soxr_create(soxr_ctx **pc, struct soxr_conf *conf)
+{
+ if (conf->channels > 8) {
+ conf->error = "invalid channels";
+ return -1;
+ }
+
+ soxr_ctx *c;
+ if (!(c = calloc(1, sizeof(soxr_ctx))))
+ return -1;
+
+ soxr_io_spec_t io = {
+ .itype = sx_format(conf->i_format, conf->i_interleaved),
+ .otype = sx_format(conf->o_format, conf->o_interleaved),
+ .scale = 1,
+ .flags = (conf->flags & SOXR_F_NO_DITHER) ? SOXR_NO_DITHER : SOXR_TPDF,
+ };
+
+ if (!conf->quality)
+ conf->quality = 20; // SOXR_20_BITQ
+ soxr_quality_spec_t q = soxr_quality_spec(conf->quality / 4 - 1, SOXR_ROLLOFF_SMALL);
+
+ soxr_error_t err;
+ c->soxr = soxr_create(conf->i_rate, conf->o_rate, conf->channels, &err, &io, &q, NULL);
+ if (err) {
+ conf->error = soxr_strerror(err);
+ goto end;
+ }
+
+ c->i_sample_size = (conf->i_format & 0xff) / 8 * conf->channels;
+ c->o_sample_size = (conf->o_format & 0xff) / 8 * conf->channels;
+ c->conf = *conf;
+ *pc = c;
+ return 0;
+
+end:
+ phi_soxr_destroy(c);
+ return -1;
+}
+
+int phi_soxr_convert(soxr_ctx *c, const void *input, size_t len, size_t *off, void *output, size_t cap)
+{
+ void *in = (char*)input + *off;
+ const void *in_v[8];
+ if (input && !c->conf.i_interleaved) {
+ for (unsigned i = 0; i < c->conf.channels; i++) {
+ in_v[i] = (char*)((void**)input)[i] + *off;
+ }
+ in = in_v;
+ }
+
+ size_t idone, odone;
+ if (soxr_process(c->soxr, in, (len - *off) / c->i_sample_size, &idone, output, cap / c->o_sample_size, &odone))
+ return -1;
+
+ *off += idone * c->i_sample_size;
+ return odone * c->o_sample_size;
+}
diff --git a/alib3/soxr/soxr-phi.h b/alib3/soxr/soxr-phi.h
new file mode 100644
index 0000000..8a956a8
--- /dev/null
+++ b/alib3/soxr/soxr-phi.h
@@ -0,0 +1,55 @@
+/** libsoxr wrapper */
+
+#pragma once
+#include
+
+#ifdef WIN32
+ #define _EXPORT __declspec(dllexport)
+#else
+ #define _EXPORT __attribute__((visibility("default")))
+ #include
+#endif
+
+typedef struct soxr_ctx soxr_ctx;
+
+enum SOXR_FMT {
+ SOXR_I16 = 16,
+ SOXR_I32 = 32,
+ SOXR_F32 = 0x0100 | 32,
+ SOXR_F64 = 0x0100 | 64,
+};
+
+enum SOXR_F {
+ SOXR_F_NO_DITHER = 1,
+};
+
+struct soxr_conf {
+ unsigned i_rate, o_rate;
+ unsigned i_format, o_format; // enum SOXR_FMT
+ unsigned i_interleaved, o_interleaved;
+ unsigned channels;
+
+ unsigned quality;
+ unsigned flags; // enum SOXR_F
+ const char *error;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+_EXPORT int phi_soxr_create(soxr_ctx **c, struct soxr_conf *conf);
+
+_EXPORT void phi_soxr_destroy(soxr_ctx *c);
+
+/**
+Flush: input=NULL, len=0, *off=0
+Return N of bytes written;
+ <0: error. */
+_EXPORT int phi_soxr_convert(soxr_ctx *c, const void *input, size_t len, size_t *off, void *output, size_t cap);
+
+#ifdef __cplusplus
+}
+#endif
+
+#undef _EXPORT
diff --git a/alib3/soxr/soxr.h b/alib3/soxr/soxr.h
deleted file mode 100644
index 8d9622d..0000000
--- a/alib3/soxr/soxr.h
+++ /dev/null
@@ -1,348 +0,0 @@
-/* SoX Resampler Library Copyright (c) 2007-13 robs@users.sourceforge.net
- *
- * This library is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2.1 of the License, or (at
- * your option) any later version.
- *
- * This library is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
- * General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this library; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-
-
-/* -------------------------------- Gubbins --------------------------------- */
-
-#if !defined soxr_included
-#define soxr_included
-
-
-#if defined __cplusplus
- #include
- extern "C" {
-#else
- #include
-#endif
-
-#if defined SOXR_DLL
- #if defined soxr_EXPORTS
- #define SOXR __declspec(dllexport)
- #else
- #define SOXR __declspec(dllimport)
- #endif
-#elif defined SOXR_VISIBILITY && defined __GNUC__ && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 1)
- #define SOXR __attribute__ ((visibility("default")))
-#else
- #define SOXR
-#endif
-
-typedef struct soxr_io_spec soxr_io_spec_t;
-typedef struct soxr_quality_spec soxr_quality_spec_t;
-typedef struct soxr_runtime_spec soxr_runtime_spec_t;
-
-
-
-/* ---------------------------- API conventions --------------------------------
-
-Buffer lengths (and occupancies) are expressed as the number of contained
-samples per channel.
-
-Parameter names for buffer lengths have the suffix `len'.
-
-A single-character `i' or 'o' is often used in names to give context as
-input or output (e.g. ilen, olen). */
-
-
-
-/* --------------------------- Version management --------------------------- */
-
-/* E.g. #if SOXR_THIS_VERSION >= SOXR_VERSION(0,1,1) ... */
-
-#define SOXR_VERSION(x,y,z) (((x)<<16)|((y)<<8)|(z))
-#define SOXR_THIS_VERSION SOXR_VERSION(0,1,2)
-#define SOXR_THIS_VERSION_STR "0.1.2"
-
-
-
-/* --------------------------- Type declarations ---------------------------- */
-
-typedef struct soxr * soxr_t; /* A resampler for 1 or more channels. */
-typedef char const * soxr_error_t; /* 0:no-error; non-0:error. */
-
-typedef void * soxr_buf_t; /* 1 buffer of channel-interleaved samples. */
-typedef void const * soxr_cbuf_t; /* Ditto; read-only. */
-
-typedef soxr_buf_t const * soxr_bufs_t;/* Or, a separate buffer for each ch. */
-typedef soxr_cbuf_t const * soxr_cbufs_t; /* Ditto; read-only. */
-
-typedef void const * soxr_in_t; /* Either a soxr_cbuf_t or soxr_cbufs_t,
- depending on itype in soxr_io_spec_t. */
-typedef void * soxr_out_t; /* Either a soxr_buf_t or soxr_bufs_t,
- depending on otype in soxr_io_spec_t. */
-
-
-
-/* --------------------------- API main functions --------------------------- */
-
-SOXR char const * soxr_version(void); /* Query library version: "libsoxr-x.y.z" */
-
-#define soxr_strerror(e) /* Soxr counterpart to strerror. */ \
- ((e)?(e):"no error")
-
-
-/* Create a stream resampler: */
-
-SOXR soxr_t soxr_create(
- double input_rate, /* Input sample-rate. */
- double output_rate, /* Output sample-rate. */
- unsigned num_channels, /* Number of channels to be used. */
- /* All following arguments are optional (may be set to NULL). */
- soxr_error_t *, /* To report any error during creation. */
- soxr_io_spec_t const *, /* To specify non-default I/O formats. */
- soxr_quality_spec_t const *, /* To specify non-default resampling quality.*/
- soxr_runtime_spec_t const *);/* To specify non-default runtime resources.
-
- Default io_spec is per soxr_io_spec(SOXR_FLOAT32_I, SOXR_FLOAT32_I)
- Default quality_spec is per soxr_quality_spec(SOXR_HQ, 0)
- Default runtime_spec is per soxr_runtime_spec(1) */
-
-
-
-/* If not using an app-supplied input function, after creating a stream
- * resampler, repeatedly call: */
-
-SOXR soxr_error_t soxr_process(
- soxr_t resampler, /* As returned by soxr_create. */
- /* Input (to be resampled): */
- soxr_in_t in, /* Input buffer(s); may be NULL (see below). */
- size_t ilen, /* Input buf. length (samples per channel). */
- size_t * idone, /* To return actual # samples used (<= ilen). */
- /* Output (resampled): */
- soxr_out_t out, /* Output buffer(s).*/
- size_t olen, /* Output buf. length (samples per channel). */
- size_t * odone); /* To return actual # samples out (<= olen).
-
- Note that no special meaning is associated with ilen or olen equal to
- zero. End-of-input (i.e. no data is available nor shall be available)
- may be indicated by seting `in' to NULL. */
-
-
-
-/* If using an app-supplied input function, it must look and behave like this:*/
-
-typedef size_t /* data_len */
- (* soxr_input_fn_t)( /* Supply data to be resampled. */
- void * input_fn_state, /* As given to soxr_set_input_fn (below). */
- soxr_in_t * data, /* Returned data; see below. N.B. ptr to ptr(s)*/
- size_t requested_len); /* Samples per channel, >= returned data_len.
-
- data_len *data Indicates Meaning
- ------- ------- ------------ -------------------------
- !=0 !=0 Success *data contains data to be
- input to the resampler.
- 0 !=0 (or End-of-input No data is available nor
- not set) shall be available.
- 0 0 Failure An error occurred whilst trying to
- source data to be input to the resampler. */
-
-/* and be registered with a previously created stream resampler using: */
-
-SOXR soxr_error_t soxr_set_input_fn(/* Set (or reset) an input function.*/
- soxr_t resampler, /* As returned by soxr_create. */
- soxr_input_fn_t, /* Function to supply data to be resampled.*/
- void * input_fn_state, /* If needed by the input function. */
- size_t max_ilen); /* Maximum value for input fn. requested_len.*/
-
-/* then repeatedly call: */
-
-SOXR size_t /*odone*/ soxr_output(/* Resample and output a block of data.*/
- soxr_t resampler, /* As returned by soxr_create. */
- soxr_out_t data, /* App-supplied buffer(s) for resampled data.*/
- size_t olen); /* Amount of data to output; >= odone. */
-
-
-
-/* Common stream resampler operations: */
-
-SOXR soxr_error_t soxr_error(soxr_t); /* Query error status. */
-SOXR size_t * soxr_num_clips(soxr_t); /* Query int. clip counter (for R/W). */
-SOXR double soxr_delay(soxr_t); /* Query current delay in output samples.*/
-SOXR char const * soxr_engine(soxr_t p); /* Query resampling engine name. */
-
-SOXR soxr_error_t soxr_clear(soxr_t); /* Ready for fresh signal, same config. */
-SOXR void soxr_delete(soxr_t); /* Free resources. */
-
-
-
-/* `Short-cut', single call to resample a (probably short) signal held entirely
- * in memory. See soxr_create and soxr_process above for parameter details.
- * Note that unlike soxr_create however, the default quality spec. for
- * soxr_oneshot is per soxr_quality_spec(SOXR_LQ, 0). */
-
-SOXR soxr_error_t soxr_oneshot(
- double input_rate,
- double output_rate,
- unsigned num_channels,
- soxr_in_t in , size_t ilen, size_t * idone,
- soxr_out_t out, size_t olen, size_t * odone,
- soxr_io_spec_t const *,
- soxr_quality_spec_t const *,
- soxr_runtime_spec_t const *);
-
-
-
-/* For variable-rate resampling. See example # 5 for how to create a
- * variable-rate resampler and how to use this function. */
-
-SOXR soxr_error_t soxr_set_io_ratio(soxr_t, double io_ratio, size_t slew_len);
-
-
-
-/* -------------------------- API type definitions -------------------------- */
-
-typedef enum { /* Datatypes supported for I/O to/from the resampler: */
- /* Internal; do not use: */
- SOXR_FLOAT32, SOXR_FLOAT64, SOXR_INT32, SOXR_INT16, SOXR_SPLIT = 4,
-
- /* Use for interleaved channels: */
- SOXR_FLOAT32_I = SOXR_FLOAT32, SOXR_FLOAT64_I, SOXR_INT32_I, SOXR_INT16_I,
-
- /* Use for split channels: */
- SOXR_FLOAT32_S = SOXR_SPLIT , SOXR_FLOAT64_S, SOXR_INT32_S, SOXR_INT16_S
-
-} soxr_datatype_t;
-
-#define soxr_datatype_size(x) /* Returns `sizeof' a soxr_datatype_t sample. */\
- ((unsigned char *)"\4\10\4\2")[(x)&3]
-
-
-
-struct soxr_io_spec { /* Typically */
- soxr_datatype_t itype; /* Input datatype. SOXR_FLOAT32_I */
- soxr_datatype_t otype; /* Output datatype. SOXR_FLOAT32_I */
- double scale; /* Linear gain to apply during resampling. 1 */
- void * e; /* Reserved for internal use 0 */
- unsigned long flags; /* Per the following #defines. 0 */
-};
-
-#define SOXR_TPDF 0 /* Applicable only if otype is INT16. */
-#define SOXR_NO_DITHER 8u /* Disable the above. */
-
-
-
-struct soxr_quality_spec { /* Typically */
- double precision; /* Conversion precision (in bits). 20 */
- double phase_response; /* 0=minimum, ... 50=linear, ... 100=maximum 50 */
- double passband_end; /* 0dB pt. bandwidth to preserve; nyquist=1 0.913*/
- double stopband_begin; /* Aliasing/imaging control; > passband_end 1 */
- void * e; /* Reserved for internal use. 0 */
- unsigned long flags; /* Per the following #defines. 0 */
-};
-
-#define SOXR_ROLLOFF_SMALL 0u /* <= 0.01 dB */
-#define SOXR_ROLLOFF_MEDIUM 1u /* <= 0.35 dB */
-#define SOXR_ROLLOFF_NONE 2u /* For Chebyshev bandwidth. */
-
-#define SOXR_MAINTAIN_3DB_PT 4u /* Reserved for internal use. */
-#define SOXR_HI_PREC_CLOCK 8u /* Increase `irrational' ratio accuracy. */
-#define SOXR_DOUBLE_PRECISION 16u /* Use D.P. calcs even if precision <= 20. */
-#define SOXR_VR 32u /* Variable-rate resampling. */
-
-
-
-struct soxr_runtime_spec { /* Typically */
- unsigned log2_min_dft_size;/* For DFT efficiency. [8,15] 10 */
- unsigned log2_large_dft_size;/* For DFT efficiency. [16,20] 17 */
- unsigned coef_size_kbytes; /* For SOXR_COEF_INTERP_AUTO (below). 400 */
- unsigned num_threads; /* If built so. 0 means `automatic'. 1 */
- void * e; /* Reserved for internal use. 0 */
- unsigned long flags; /* Per the following #defines. 0 */
-};
- /* For `irrational' ratios only: */
-#define SOXR_COEF_INTERP_AUTO 0u /* Auto select coef. interpolation. */
-#define SOXR_COEF_INTERP_LOW 2u /* Man. select: less CPU, more memory. */
-#define SOXR_COEF_INTERP_HIGH 3u /* Man. select: more CPU, less memory. */
-
-#define SOXR_STRICT_BUFFERING 4u /* Reserved for future use. */
-#define SOXR_NOSMALLINTOPT 8u /* For test purposes only. */
-
-
-
-/* -------------------------- API type constructors ------------------------- */
-
-/* These functions allow setting of the most commonly-used structure
- * parameters, with other parameters being given default values. The default
- * values may then be overridden, directly in the structure, if needed. */
-
-SOXR soxr_quality_spec_t soxr_quality_spec(
- unsigned long recipe, /* Per the #defines immediately below. */
- unsigned long flags); /* As soxr_quality_spec_t.flags. */
-
- /* The 5 standard qualities found in SoX: */
-#define SOXR_QQ 0 /* 'Quick' cubic interpolation. */
-#define SOXR_LQ 1 /* 'Low' 16-bit with larger rolloff. */
-#define SOXR_MQ 2 /* 'Medium' 16-bit with medium rolloff. */
-#define SOXR_HQ SOXR_20_BITQ /* 'High quality'. */
-#define SOXR_VHQ SOXR_28_BITQ /* 'Very high quality'. */
-
-#define SOXR_16_BITQ 3
-#define SOXR_20_BITQ 4
-#define SOXR_24_BITQ 5
-#define SOXR_28_BITQ 6
-#define SOXR_32_BITQ 7
- /* Libsamplerate equivalent qualities: */
-#define SOXR_LSR0Q 8 /* 'Best sinc'. */
-#define SOXR_LSR1Q 9 /* 'Medium sinc'. */
-#define SOXR_LSR2Q 10 /* 'Fast sinc'. */
-
-#define SOXR_LINEAR_PHASE 0x00
-#define SOXR_INTERMEDIATE_PHASE 0x10
-#define SOXR_MINIMUM_PHASE 0x30
-#define SOXR_STEEP_FILTER 0x40
-#define SOXR_ALLOW_ALIASING 0x80 /* Reserved for future use. */
-
-
-
-SOXR soxr_runtime_spec_t soxr_runtime_spec(
- unsigned num_threads);
-
-
-
-SOXR soxr_io_spec_t soxr_io_spec(
- soxr_datatype_t itype,
- soxr_datatype_t otype);
-
-
-
-/* --------------------------- Advanced use only ---------------------------- */
-
-/* For new designs, the following functions/usage will probably not be needed.
- * They might be useful when adding soxr into an existing design where values
- * for the resampling-rate and/or number-of-channels parameters to soxr_create
- * are not available when that function will be called. In such cases, the
- * relevant soxr_create parameter(s) can be given as 0, then one or both of the
- * following (as appropriate) later invoked (but prior to calling soxr_process
- * or soxr_output):
- *
- * soxr_set_error(soxr, soxr_set_io_ratio(soxr, io_ratio, 0));
- * soxr_set_error(soxr, soxr_set_num_channels(soxr, num_channels));
- */
-
-SOXR soxr_error_t soxr_set_error(soxr_t, soxr_error_t);
-SOXR soxr_error_t soxr_set_num_channels(soxr_t, unsigned);
-
-
-
-#undef SOXR
-
-#if defined __cplusplus
-}
-#endif
-
-#endif
diff --git a/src/afilter/Makefile b/src/afilter/Makefile
index f6f74eb..57d8a28 100644
--- a/src/afilter/Makefile
+++ b/src/afilter/Makefile
@@ -35,9 +35,9 @@ afilter.$(SO): afilter.o \
MODS += $(AFPFX)soxr.$(SO)
LIBS3 += $(ALIB3_BIN)/libsoxr-phi.$(SO)
-soxr.o: $(PHIOLA)/src/afilter/soxr.c
+soxr-conv.o: $(PHIOLA)/src/afilter/soxr-conv.c
$(C) $(CFLAGS) -I$(ALIB3) $< -o $@
-$(AFPFX)soxr.$(SO): soxr.o
+$(AFPFX)soxr.$(SO): soxr-conv.o
$(LINK) -shared $+ $(LINKFLAGS) $(LINK_RPATH_ORIGIN) -L$(ALIB3_BIN) -lsoxr-phi -o $@
MODS += $(AFPFX)danorm.$(SO)
diff --git a/src/afilter/soxr-conv.c b/src/afilter/soxr-conv.c
new file mode 100644
index 0000000..2737238
--- /dev/null
+++ b/src/afilter/soxr-conv.c
@@ -0,0 +1,153 @@
+/** phiola: afilter: soxr sample rate convertor
+2023, Simon Zolin */
+
+#include
+#include
+#include
+#include
+
+static const phi_core *core;
+#define errlog(t, ...) phi_errlog(core, NULL, t, __VA_ARGS__)
+#define dbglog(t, ...) phi_dbglog(core, NULL, t, __VA_ARGS__)
+
+struct soxr {
+ soxr_ctx *soxr;
+ ffstr in;
+ size_t in_off;
+ size_t buf_cap;
+ void *buf;
+ void *buf_v[8];
+};
+
+static int soxr_fmt(int pf)
+{
+ switch (pf) {
+ case PHI_PCM_16: return SOXR_I16;
+ case PHI_PCM_32: return SOXR_I32;
+ case PHI_PCM_FLOAT32: return SOXR_F32;
+ case PHI_PCM_FLOAT64: return SOXR_F64;
+ }
+ return 0;
+}
+
+static void log_pcmconv(int r, const struct phi_af *in, const struct phi_af *out, phi_track *t)
+{
+ int level = PHI_LOG_DEBUG;
+ const char *unsupp = "";
+ if (r != 0) {
+ level = PHI_LOG_ERR;
+ unsupp = " not supported";
+ }
+
+ char bufi[100], bufo[100];
+ core->conf.log(core->conf.log_obj, level, NULL, t, "audio conversion%s: %s -> %s"
+ , unsupp
+ , phi_af_print(in, bufi, sizeof(bufi)), phi_af_print(out, bufo, sizeof(bufo)));
+}
+
+static void* soxr_open(phi_track *t)
+{
+ struct soxr *c = phi_track_allocT(t, struct soxr);
+ struct phi_af oaf = t->aconv.out;
+ int r;
+ struct soxr_conf conf = {
+ .i_rate = t->aconv.in.rate,
+ .i_format = soxr_fmt(t->aconv.in.format),
+ .i_interleaved = t->aconv.in.interleaved,
+ .o_rate = oaf.rate,
+ .o_format = soxr_fmt(oaf.format),
+ .o_interleaved = oaf.interleaved,
+ .channels = t->aconv.in.channels,
+ };
+ if ((r = phi_soxr_create(&c->soxr, &conf))
+ || (core->conf.log_level >= PHI_LOG_DEBUG)) {
+ log_pcmconv(r, &t->aconv.in, &t->aconv.out, t);
+ if (r) {
+ dbglog(t, "phi_soxr_create(): %s", conf.error);
+ goto end;
+ }
+ }
+
+ // Allow no more than 16MB per 1 second of 64-bit 7.1 audio: 0x00ffffff/(64/8*8)=262143
+ if (oaf.rate > 262143) {
+ goto end;
+ }
+ uint channel_len = oaf.rate * (oaf.format & 0xff) / 8;
+ c->buf_cap = channel_len * oaf.channels;
+ c->buf = ffmem_alloc(c->buf_cap);
+ if (!oaf.interleaved) {
+ for (uint i = 0; i < oaf.channels; i++) {
+ c->buf_v[i] = (char*)c->buf + channel_len * i;
+ }
+ }
+ return c;
+
+end:
+ t->error = PHI_E_ACONV;
+ phi_track_free(t, c);
+ return PHI_OPEN_ERR;
+}
+
+static void soxr_close(struct soxr *c, phi_track *t)
+{
+ phi_soxr_destroy(c->soxr);
+ ffmem_free(c->buf);
+ phi_track_free(t, c);
+}
+
+/*
+This filter converts both format and sample rate.
+Previous filter must deal with channel conversion.
+*/
+static int soxr_conv(struct soxr *c, phi_track *t)
+{
+ if (t->chain_flags & PHI_FFWD) {
+ c->in = t->data_in;
+ c->in_off = 0;
+ }
+ const void *in = c->in.ptr;
+ if (t->chain_flags & PHI_FFIRST) {
+ if (c->in.len == c->in_off) {
+ in = NULL;
+ c->in.len = 0;
+ c->in_off = 0;
+ }
+ } else if (!c->in.len) {
+ return PHI_MORE;
+ }
+ void *out = (!c->buf_v[0]) ? c->buf : c->buf_v;
+ int r;
+ if (0 > (r = phi_soxr_convert(c->soxr, in, c->in.len, &c->in_off, out, c->buf_cap))) {
+ errlog(t, "phi_soxr_convert()");
+ return PHI_ERR;
+ }
+
+ if (!r)
+ return (t->chain_flags & PHI_FFIRST) ? PHI_DONE : PHI_MORE;
+
+ ffstr_set(&t->data_out, out, r);
+ return PHI_DATA;
+}
+
+const phi_filter phi_soxr = {
+ soxr_open, (void*)soxr_close, (void*)soxr_conv,
+ "soxr-convert"
+};
+
+
+static const void* soxr_mod_iface(const char *name)
+{
+ if (ffsz_eq(name, "conv")) return &phi_soxr;
+ return NULL;
+}
+
+static const phi_mod soxr_mod = {
+ .ver = PHI_VERSION, .ver_core = PHI_VERSION_CORE,
+ soxr_mod_iface
+};
+
+FF_EXPORT const phi_mod* phi_mod_init(const phi_core *_core)
+{
+ core = _core;
+ return &soxr_mod;
+}
diff --git a/src/afilter/soxr-conv.h b/src/afilter/soxr-conv.h
deleted file mode 100644
index 09f1096..0000000
--- a/src/afilter/soxr-conv.h
+++ /dev/null
@@ -1,98 +0,0 @@
-/** phiola: afilter: soxr sample rate convertor
-2023, Simon Zolin */
-
-#include
-#include
-#include
-
-struct soxr {
- uint state;
- ffsoxr soxr;
- struct phi_af inpcm, outpcm;
-};
-
-static void* soxr_open(phi_track *t)
-{
- struct soxr *c = phi_track_allocT(t, struct soxr);
- c->inpcm = t->aconv.in;
- c->outpcm = t->aconv.out;
- ffsoxr_init(&c->soxr);
- return c;
-}
-
-static void soxr_close(void *ctx, phi_track *t)
-{
- struct soxr *c = ctx;
- ffsoxr_destroy(&c->soxr);
- phi_track_free(t, c);
-}
-
-static void log_pcmconv(int r, const struct phi_af *in, const struct phi_af *out, phi_track *t)
-{
- int level = PHI_LOG_DEBUG;
- const char *unsupp = "";
- if (r != 0) {
- level = PHI_LOG_ERR;
- unsupp = " not supported";
- }
-
- char bufi[100], bufo[100];
- core->conf.log(core->conf.log_obj, level, NULL, t, "audio conversion%s: %s -> %s"
- , unsupp
- , phi_af_print(in, bufi, sizeof(bufi)), phi_af_print(out, bufo, sizeof(bufo)));
-}
-
-/*
-This filter converts both format and sample rate.
-Previous filter must deal with channel conversion.
-*/
-static int soxr_conv(void *ctx, phi_track *t)
-{
- struct soxr *c = ctx;
- int val;
- struct phi_af inpcm, outpcm;
-
- switch (c->state) {
- case 0:
- inpcm = c->inpcm;
- outpcm = c->outpcm;
-
- // c->soxr.dither = 1;
- if (0 != (val = ffsoxr_create(&c->soxr, &inpcm, &outpcm))
- || (core->conf.log_level >= PHI_LOG_DEBUG)) {
- log_pcmconv(val, &inpcm, &outpcm, t);
- if (val != 0) {
- t->error = PHI_E_ACONV;
- return PHI_ERR;
- }
- }
-
- c->state = 3;
- break;
-
- case 3:
- break;
- }
-
- if (t->chain_flags & PHI_FFWD) {
- c->soxr.in_i = t->data_in.ptr;
- c->soxr.inlen = t->data_in.len;
- c->soxr.inoff = 0;
- }
- c->soxr.fin = !!(t->chain_flags & PHI_FFIRST);
- if (ffsoxr_convert(&c->soxr, &t->data_out)) {
- errlog(t, "ffsoxr_convert(): %s", ffsoxr_errstr(&c->soxr));
- return PHI_ERR;
- }
-
- if (t->data_out.len == 0) {
- return (t->chain_flags & PHI_FFIRST) ? PHI_DONE : PHI_MORE;
- }
-
- return PHI_DATA;
-}
-
-const phi_filter phi_soxr = {
- soxr_open, soxr_close, soxr_conv,
- "soxr-convert"
-};
diff --git a/src/afilter/soxr.c b/src/afilter/soxr.c
deleted file mode 100644
index 4ca2eda..0000000
--- a/src/afilter/soxr.c
+++ /dev/null
@@ -1,27 +0,0 @@
-/** phiola: SoXr filter
-2017, Simon Zolin */
-
-#include
-#include
-
-static const phi_core *core;
-#define errlog(t, ...) phi_errlog(core, NULL, t, __VA_ARGS__)
-
-#include
-
-static const void* soxr_mod_iface(const char *name)
-{
- if (ffsz_eq(name, "conv")) return &phi_soxr;
- return NULL;
-}
-
-static const phi_mod soxr_mod = {
- .ver = PHI_VERSION, .ver_core = PHI_VERSION_CORE,
- soxr_mod_iface
-};
-
-FF_EXPORT const phi_mod* phi_mod_init(const phi_core *_core)
-{
- core = _core;
- return &soxr_mod;
-}
diff --git a/src/afilter/soxr.h b/src/afilter/soxr.h
deleted file mode 100644
index cdbab98..0000000
--- a/src/afilter/soxr.h
+++ /dev/null
@@ -1,162 +0,0 @@
-/** phiola: libsoxr wrapper
-2015, Simon Zolin */
-
-#include
-#include
-
-typedef struct ffsoxr {
- soxr_t soxr;
- soxr_error_t err;
- uint isampsize
- , osampsize;
- uint nchannels;
-
- union {
- const void *in_i;
- const void **in;
- };
- uint inlen;
- uint inoff;
-
- union {
- void *out;
- void **outni;
- };
- uint outcap;
-
- uint quality; // 0..4. default:3 (High quality)
- uint in_ileaved :1
- , dither :1
- , fin :1; // the last block of input data
-} ffsoxr;
-
-static inline void ffsoxr_init(ffsoxr *soxr)
-{
- ffmem_zero_obj(soxr);
- soxr->quality = SOXR_HQ;
-}
-
-#define ffsoxr_errstr(soxr) soxr_strerror((soxr)->err)
-
-static const ffbyte soxr_fmts[2][3] = {
- { SOXR_INT16_S, SOXR_INT32_S, SOXR_FLOAT32_S },
- { SOXR_INT16_I, SOXR_INT32_I, SOXR_FLOAT32_I },
-};
-
-static int _ffsoxr_getfmt(int fmt, int interleaved)
-{
- switch (fmt) {
- case PHI_PCM_16:
- return soxr_fmts[interleaved][0];
- case PHI_PCM_32:
- return soxr_fmts[interleaved][1];
- case PHI_PCM_FLOAT32:
- return soxr_fmts[interleaved][2];
- }
-
- return -1;
-}
-
-/** Set array elements to point to consecutive regions of one buffer */
-static inline void arrp_setbuf(void **ar, ffsize n, const void *buf, ffsize region_len)
-{
- for (ffsize i = 0; i != n; i++) {
- ar[i] = (char*)buf + region_len * i;
- }
-}
-
-static inline int ffsoxr_create(ffsoxr *soxr, const struct phi_af *inpcm, const struct phi_af *outpcm)
-{
- soxr_io_spec_t io;
- soxr_quality_spec_t qual;
-
- if (inpcm->channels != outpcm->channels)
- return -1;
-
- // Allow no more than 16MB per 1 second of 64-bit 7.1 audio: 0x00ffffff/(64/8*8)=262143
- if (outpcm->rate > 262143)
- return -1;
-
- int itype = _ffsoxr_getfmt(inpcm->format, inpcm->interleaved);
- int otype = _ffsoxr_getfmt(outpcm->format, outpcm->interleaved);
- if (itype == -1 || otype == -1)
- return -1;
- io.itype = itype;
- io.otype = otype;
- io.scale = 1;
- io.e = NULL;
- io.flags = soxr->dither ? SOXR_TPDF : SOXR_NO_DITHER;
-
- qual = soxr_quality_spec(soxr->quality, SOXR_ROLLOFF_SMALL);
-
- soxr->soxr = soxr_create(inpcm->rate, outpcm->rate, inpcm->channels, &soxr->err
- , &io, &qual, NULL);
- if (soxr->err != NULL)
- return -1;
-
- soxr->isampsize = pcm_size1(inpcm);
- soxr->osampsize = pcm_size1(outpcm);
- soxr->outcap = outpcm->rate;
- if (NULL == (soxr->out = ffmem_alloc(sizeof(void*) * outpcm->channels + soxr->outcap * soxr->osampsize)))
- return -1;
- if (!outpcm->interleaved) {
- ffstr s;
- ffstr_set(&s, (char*)soxr->out + sizeof(void*) * outpcm->channels, soxr->outcap * pcm_bits(outpcm->format) / 8);
- // soxr->outni = soxr->out;
- arrp_setbuf(soxr->outni, outpcm->channels, s.ptr, s.len);
- }
- soxr->in_ileaved = inpcm->interleaved;
- soxr->nchannels = inpcm->channels;
- return 0;
-}
-
-static inline void ffsoxr_destroy(ffsoxr *soxr)
-{
- ffmem_free(soxr->out);
- soxr_delete(soxr->soxr);
-}
-
-static inline int ffsoxr_convert(ffsoxr *soxr, ffstr *out)
-{
- size_t idone, odone;
- uint i, fin = 0;
- void *in = soxr->in;
- const void *inarr[8];
-
- for (;;) {
-
- if (soxr->inlen == 0) {
- if (!soxr->fin) {
- out->len = 0;
- return 0;
- }
- in = NULL;
- fin = 1;
-
- } else if (soxr->in_ileaved)
- in = (char*)soxr->in_i + soxr->inoff * soxr->isampsize;
-
- else {
- for (i = 0; i != soxr->nchannels; i++) {
- inarr[i] = (char*)soxr->in[i] + soxr->inoff * soxr->isampsize / soxr->nchannels;
- }
- in = inarr;
- }
-
- soxr->err = soxr_process(soxr->soxr, in, soxr->inlen / soxr->isampsize, &idone
- , soxr->out, soxr->outcap, &odone);
- if (soxr->err != NULL)
- return -1;
-
- soxr->inoff += idone;
- soxr->inlen -= idone * soxr->isampsize;
- if (soxr->inlen == 0)
- soxr->inoff = 0;
-
- if (odone != 0 || fin) {
- ffstr_set(out, soxr->out, odone * soxr->osampsize);
- break;
- }
- }
- return 0;
-}
From e5476ada3ff5bf636191502a6b60a006cdd7fda8 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 4 Jan 2025 10:04:59 +0300
Subject: [PATCH 46/59] ! Android: disable insecure ALAC decoder by default
(`Settings->Enable Deprecated Modules`)
---
.../java/com/github/stsaz/phiola/Core.java | 2 +-
.../java/com/github/stsaz/phiola/Phiola.java | 2 +-
.../com/github/stsaz/phiola/Settings.java | 7 ++++++-
.../github/stsaz/phiola/SettingsActivity.java | 4 +++-
.../com/github/stsaz/phiola/UtilNative.java | 3 ++-
.../phiola/src/main/res/layout/settings.xml | 7 +++++++
.../phiola/src/main/res/values/strings.xml | 1 +
src/exe/main.c | 3 +++
src/gui/windows.c | 4 ++++
src/jni/android-utils.h | 1 +
src/jni/phiola-jni.c | 19 ++++++++++++++-----
11 files changed, 43 insertions(+), 10 deletions(-)
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Core.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Core.java
index 19bd0e8..a3349b1 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Core.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Core.java
@@ -133,7 +133,7 @@ private void loadconf() {
setts.normalize();
qu.conf_normalize();
- phiola.setCodepage(setts.codepage);
+ phiola.setConfig(setts.codepage, setts.deprecated_mods);
}
void clipboard_text_set(Context ctx, String s) {
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
index 1a94c00..b9ac16d 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
@@ -16,7 +16,7 @@ private static boolean lib_load(String filename) {
}
native String version();
- native void setCodepage(String codepage);
+ native void setConfig(String codepage, boolean deprecated_mods);
native void setDebug(boolean enable);
static class Meta {
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java
index e56efd7..c85cb3d 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Settings.java
@@ -46,7 +46,7 @@ class CoreSettings {
boolean debug_logs;
boolean svc_notification_disable;
String trash_dir;
- boolean file_del;
+ boolean file_del, deprecated_mods;
String codepage;
String pub_data_dir;
String plist_save_dir;
@@ -185,6 +185,8 @@ String conf_write() {
+ "op_plist_save_dir %s\n"
+ "op_quick_move_dir %s\n"
+ "op_trash_dir_rel %s\n"
+ + "op_deprecated_mods %d\n"
+
+ "play_auto_skip %s\n"
+ "play_auto_skip_tail %s\n"
+ "rec_path %s\n"
@@ -216,6 +218,8 @@ String conf_write() {
, plist_save_dir
, quick_move_dir
, trash_dir
+ , core.bool_to_int(deprecated_mods)
+
, auto_skip_head.str()
, auto_skip_tail.str()
, rec_path
@@ -317,6 +321,7 @@ void conf_load(Conf.Entry[] kv) {
plist_save_dir = kv[Conf.OP_PLIST_SAVE_DIR].value;
quick_move_dir = kv[Conf.OP_QUICK_MOVE_DIR].value;
trash_dir = kv[Conf.OP_TRASH_DIR_REL].value;
+ deprecated_mods = kv[Conf.OP_DEPRECATED_MODS].enabled;
set_codepage(kv[Conf.CODEPAGE].value);
auto_skip_head_set(kv[Conf.PLAY_AUTO_SKIP].value);
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java
index 1b3724a..3024372 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java
@@ -206,6 +206,7 @@ private void load() {
b.eTrashDir.setText(core.setts.trash_dir);
b.swFileDel.setChecked(core.setts.file_del);
b.eQuickMoveDir.setText(core.setts.quick_move_dir);
+ b.swDeprecatedMods.setChecked(core.setts.deprecated_mods);
rec_load();
}
@@ -245,7 +246,6 @@ private void save() {
// Playback
core.setts.set_codepage(b.eCodepage.getText().toString());
- core.phiola.setCodepage(core.setts.codepage);
core.setts.auto_skip_head_set(b.eAutoSkip.getText().toString());
core.setts.auto_skip_tail_set(b.eAutoSkipTail.getText().toString());
@@ -259,10 +259,12 @@ private void save() {
core.setts.trash_dir = b.eTrashDir.getText().toString();
core.setts.file_del = b.swFileDel.isChecked();
core.setts.quick_move_dir = b.eQuickMoveDir.getText().toString();
+ core.setts.deprecated_mods = b.swDeprecatedMods.isChecked();
rec_save();
core.queue().conf_normalize();
core.setts.normalize();
+ core.phiola.setConfig(core.setts.codepage, core.setts.deprecated_mods);
}
// 20%..1%; 0; 10sec..200sec by 10
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java b/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java
index c66631e..c9a792b 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java
@@ -30,7 +30,8 @@ static class Entry {
LIST_RM_ON_ERR = LIST_REPEAT + 1,
LIST_RM_ON_NEXT = LIST_RM_ON_ERR + 1,
OP_DATA_DIR = LIST_RM_ON_NEXT + 1,
- OP_FILE_DELETE = OP_DATA_DIR + 1,
+ OP_DEPRECATED_MODS = OP_DATA_DIR + 1,
+ OP_FILE_DELETE = OP_DEPRECATED_MODS + 1,
OP_PLIST_SAVE_DIR = OP_FILE_DELETE + 1,
OP_QUICK_MOVE_DIR = OP_PLIST_SAVE_DIR + 1,
OP_TRASH_DIR_REL = OP_QUICK_MOVE_DIR + 1,
diff --git a/android/phiola/src/main/res/layout/settings.xml b/android/phiola/src/main/res/layout/settings.xml
index b284d34..ed644ae 100644
--- a/android/phiola/src/main/res/layout/settings.xml
+++ b/android/phiola/src/main/res/layout/settings.xml
@@ -253,6 +253,13 @@
android:inputType="text"
android:textSize="@dimen/text_editbox" />
+
+
Trash Directory (relative to storage mount-point):
Quick-Move Directory:
App Data Directory:
+ Enable Deprecated Modules (ALAC)
Recording
Long-click for Start/Stop; Short-click for Pause/Resume
diff --git a/src/exe/main.c b/src/exe/main.c
index aef841d..5dbd52f 100644
--- a/src/exe/main.c
+++ b/src/exe/main.c
@@ -217,6 +217,9 @@ static char* env_expand(const char *s)
static char* mod_loading(ffstr name)
{
+ if (ffstr_eqz(&name, "ac-alac"))
+ warnlog("ALAC module is deprecated. Decoding the files from untrusted sources is NOT RECOMMENDED.");
+
return ffsz_allocfmt("%Smod%c%S.%s"
, &x->root_dir, FFPATH_SLASH, &name, FFDL_EXT);
}
diff --git a/src/gui/windows.c b/src/gui/windows.c
index 04b6a5f..ff01eba 100644
--- a/src/gui/windows.c
+++ b/src/gui/windows.c
@@ -64,6 +64,7 @@ static void exe_log(void *log_obj, uint flags, const char *module, phi_track *t,
}
#define errlog(...) exe_log(&x->log, PHI_LOG_ERR, NULL, NULL, __VA_ARGS__)
+#define warnlog(...) exe_log(&x->log, PHI_LOG_WARN, NULL, NULL, __VA_ARGS__)
static int ffu_coding(ffstr s)
{
@@ -151,6 +152,9 @@ static char* env_expand(const char *s)
static char* mod_loading(ffstr name)
{
+ if (ffstr_eqz(&name, "ac-alac"))
+ warnlog("ALAC module is deprecated. Decoding the files from untrusted sources is NOT RECOMMENDED.");
+
return ffsz_allocfmt("%Smod%c%S.%s"
, &x->root_dir, FFPATH_SLASH, &name, FFDL_EXT);
}
diff --git a/src/jni/android-utils.h b/src/jni/android-utils.h
index 0eb291e..f63a6e3 100644
--- a/src/jni/android-utils.h
+++ b/src/jni/android-utils.h
@@ -26,6 +26,7 @@ static const char setting_names[][20] = {
"list_rm_on_err",
"list_rm_on_next",
"op_data_dir",
+ "op_deprecated_mods",
"op_file_delete",
"op_plist_save_dir",
"op_quick_move_dir",
diff --git a/src/jni/phiola-jni.c b/src/jni/phiola-jni.c
index 6e0576b..9d07401 100644
--- a/src/jni/phiola-jni.c
+++ b/src/jni/phiola-jni.c
@@ -20,7 +20,8 @@ struct phiola_jni {
phi_meta_if metaif;
ffstr dir_libs;
- ffbyte debug;
+ u_char debug;
+ u_char deprecated_mods;
ffvec storage_paths; // char*[]
AAssetManager *am;
@@ -192,8 +193,9 @@ static int conf()
Some modules also have the dependencies - load them too. */
static char* mod_loading(ffstr name)
{
- int e = -1;
+ int e = -1, r, attached = 0;
char* znames[3] = {};
+ JNIEnv *env;
static const struct map_sz_vptr mod_deps[] = {
{ "ac-aac", "libfdk-aac-phi" },
@@ -211,13 +213,18 @@ static char* mod_loading(ffstr name)
const char *dep = map_sz_vptr_findstr(mod_deps, FF_COUNT(mod_deps), name);
znames[0] = ffsz_allocfmt("%S/lib%S.so", &x->dir_libs, &name);
if (dep) {
+
+ if (ffstr_eqz(&name, "ac-alac")
+ && !x->deprecated_mods) {
+ errlog("Loading deprecated libraries is restricted: %S", &name);
+ goto end;
+ }
+
znames[1] = ffsz_allocfmt("%S/%s.so", &x->dir_libs, dep);
if (ffstr_eqz(&name, "vorbis"))
znames[2] = ffsz_allocfmt("%S/libvorbisenc-phi.so", &x->dir_libs);
}
- JNIEnv *env;
- int r, attached;
if ((r = jni_vm_attach_once(jvm, &env, &attached, JNI_VERSION_1_6))) {
errlog("jni_vm_attach: %d", r);
goto end;
@@ -354,7 +361,7 @@ Java_com_github_stsaz_phiola_Phiola_version(JNIEnv *env, jobject thiz)
}
JNIEXPORT void JNICALL
-Java_com_github_stsaz_phiola_Phiola_setCodepage(JNIEnv *env, jobject thiz, jstring jcodepage)
+Java_com_github_stsaz_phiola_Phiola_setConfig(JNIEnv *env, jobject thiz, jstring jcodepage, jboolean deprecated_mods)
{
const char *sz = jni_sz_js(jcodepage);
if (ffsz_eq(sz, "cp1251"))
@@ -362,6 +369,8 @@ Java_com_github_stsaz_phiola_Phiola_setCodepage(JNIEnv *env, jobject thiz, jstri
else if (ffsz_eq(sz, "cp1252"))
x->core->conf.code_page = FFUNICODE_WIN1252;
jni_sz_free(sz, jcodepage);
+
+ x->deprecated_mods = deprecated_mods;
}
JNIEXPORT void JNICALL
From 7b868b7033e3d90869f754be382ec55f3e0abdd7 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 4 Jan 2025 10:04:59 +0300
Subject: [PATCH 47/59] * Opus encoder: use unconstrained VBR mode; use max.
complexity setting
---
alib3/opus/Makefile | 6 +++++-
alib3/opus/opus-phi.c | 3 +++
alib3/opus/opus-phi.h | 10 +++++++---
src/acodec/alib3-bridge/opus-enc-if.h | 15 ++++++++-------
src/acodec/opus-enc.h | 2 +-
5 files changed, 24 insertions(+), 12 deletions(-)
diff --git a/alib3/opus/Makefile b/alib3/opus/Makefile
index b3ba114..3b295a0 100644
--- a/alib3/opus/Makefile
+++ b/alib3/opus/Makefile
@@ -25,7 +25,11 @@ $(DIR): $(PKG)
# config
_:=
-ifeq "$(CPU)" "arm64"
+ifeq "$(CPU)" "amd64"
+ ifeq "$(SYS)" "android"
+ CONFIGURE_FLAGS := --host=x86_64
+ endif
+else ifeq "$(CPU)" "arm64"
ifeq "$(SYS)" "android"
CONFIGURE_FLAGS := --host=aarch64
else
diff --git a/alib3/opus/opus-phi.c b/alib3/opus/opus-phi.c
index 0182d8c..cef39da 100644
--- a/alib3/opus/opus-phi.c
+++ b/alib3/opus/opus-phi.c
@@ -80,6 +80,9 @@ int opus_encode_create(opus_ctx **pc, opus_encode_conf *conf)
&& OPUS_OK != (e = opus_encoder_ctl(c, OPUS_SET_BITRATE(conf->bitrate))))
goto fail;
+ if ((e = opus_encoder_ctl(c, OPUS_SET_VBR_CONSTRAINT(conf->vbr_mode == OPUS_VBR_CONSTRAINED))))
+ goto fail;
+
if (conf->complexity != 0
&& OPUS_OK != (e = opus_encoder_ctl(c, OPUS_SET_COMPLEXITY(conf->complexity - 1))))
goto fail;
diff --git a/alib3/opus/opus-phi.h b/alib3/opus/opus-phi.h
index 0652ab8..0e416e6 100644
--- a/alib3/opus/opus-phi.h
+++ b/alib3/opus/opus-phi.h
@@ -16,9 +16,7 @@ typedef struct opus_conf {
unsigned int channels;
} opus_conf;
-enum {
- OPUS_MAX_PKT = 4000,
-};
+#define OPUS_MAX_PKT (6*1275+12)
#ifdef __cplusplus
extern "C" {
@@ -55,6 +53,11 @@ enum OPUS_APP {
OPUS_VOIP,
};
+enum OPUS_VBR {
+ OPUS_VBR_UNCONSTRAINED,
+ OPUS_VBR_CONSTRAINED,
+};
+
typedef struct opus_encode_conf {
unsigned int channels;
unsigned int sample_rate;
@@ -62,6 +65,7 @@ typedef struct opus_encode_conf {
unsigned int complexity; //1..11
unsigned int application; //enum OPUS_APP
unsigned int bandwidth; //either 4, 6, 8, 12, 20kHz
+ unsigned int vbr_mode; // enum OPUS_VBR
unsigned int preskip; //encoder delay samples
} opus_encode_conf;
diff --git a/src/acodec/alib3-bridge/opus-enc-if.h b/src/acodec/alib3-bridge/opus-enc-if.h
index 4edf0fb..1014f72 100644
--- a/src/acodec/alib3-bridge/opus-enc-if.h
+++ b/src/acodec/alib3-bridge/opus-enc-if.h
@@ -49,13 +49,14 @@ static inline int ffopus_addtag(ffopus_enc *o, const char *name, const char *val
int ffopus_create(ffopus_enc *o, const struct phi_af *fmt, int bitrate)
{
int r;
- opus_encode_conf conf = {0};
- conf.channels = fmt->channels;
- conf.sample_rate = fmt->rate;
- conf.bitrate = bitrate;
- conf.complexity = o->complexity;
- conf.bandwidth = o->bandwidth;
- conf.application = o->mode;
+ opus_encode_conf conf = {
+ .channels = fmt->channels,
+ .sample_rate = fmt->rate,
+ .bitrate = bitrate,
+ .complexity = o->complexity,
+ .bandwidth = o->bandwidth,
+ .application = o->mode,
+ };
if (0 != (r = opus_encode_create(&o->enc, &conf)))
return ERR(o, r);
o->preskip = conf.preskip;
diff --git a/src/acodec/opus-enc.h b/src/acodec/opus-enc.h
index 5a25c8c..56b834a 100644
--- a/src/acodec/opus-enc.h
+++ b/src/acodec/opus-enc.h
@@ -72,7 +72,7 @@ static int opus_enc_encode(void *ctx, phi_track *t)
o->opus.bandwidth = t->conf.opus.bandwidth;
o->opus.mode = t->conf.opus.mode;
- o->opus.complexity = 0;
+ o->opus.complexity = 10 + 1;
o->opus.packet_dur = 40;
uint br = (t->conf.opus.bitrate) ? t->conf.opus.bitrate : 192;
if (0 != (r = ffopus_create(&o->opus, &o->fmt, br * 1000))) {
From 4804bab43aeee2bb83db838307f1f9ed41ae0ae2 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 4 Jan 2025 10:04:59 +0300
Subject: [PATCH 48/59] * update libmpg123 to 1.32.10
---
alib3/README.md | 2 +-
alib3/mpg123/Makefile | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/alib3/README.md b/alib3/README.md
index 15df464..14265c8 100644
--- a/alib3/README.md
+++ b/alib3/README.md
@@ -15,7 +15,7 @@ In order to be used by phiola all these libraries must be built in a specific wa
* fdk-aac-0.1.6
* flac-1.4.3
* MAC-433
-* mpg123-1.32.3
+* mpg123-1.32.10
* musepack-r475
* ogg-1.3.3
* opus-1.5.2
diff --git a/alib3/mpg123/Makefile b/alib3/mpg123/Makefile
index 17c96c9..efd31d3 100644
--- a/alib3/mpg123/Makefile
+++ b/alib3/mpg123/Makefile
@@ -2,9 +2,9 @@
include ../config.mk
-VER := 1.32.3
+VER := 1.32.10
URL := http://mpg123.de/download/mpg123-$(VER).tar.bz2
-MD5SUM := 5517c4c18d896da6f57a3c3b5cf317cc
+MD5SUM := f6d1a69dbf340c8d889b64772e8e0a61
PKG := $(ALIB3)/mpg123/$(notdir $(URL))
DIR := mpg123-$(VER)
LIB := libmpg123-phi.$(SO)
@@ -65,7 +65,7 @@ $(DIR)/src/config.h: | $(DIR)
# build
CFLAGS += \
- -I. -I$(DIR) -I$(DIR)/src -I$(DIR)/src/libmpg123 -I$(DIR)/src/compat \
+ -I. -I$(DIR) -I$(DIR)/src -I$(DIR)/src/include -I$(DIR)/src/libmpg123 -I$(DIR)/src/compat \
-DMPG123_EXPORT="" -DHAVE_CONFIG_H \
-DOPT_MULTI -DOPT_GENERIC -DOPT_GENERIC_DITHER -DREAL_IS_FLOAT \
-fomit-frame-pointer -funroll-all-loops -finline-functions -ffast-math
From 68cdd182b85a135566192a541014edb8b6ee920e Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 4 Jan 2025 10:04:59 +0300
Subject: [PATCH 49/59] ref: queue
---
src/afilter/split.h | 2 +-
src/core/dir-read.c | 13 ++---
src/core/file-tee.h | 2 +-
src/core/queue-entry.h | 100 ++++++++++++++++++++-------------------
src/core/queue.c | 14 ++++--
src/exe/cmd.h | 12 +++--
src/exe/convert.h | 21 ++++----
src/exe/gui.h | 5 +-
src/exe/info.h | 20 ++++----
src/exe/list-heal.h | 9 ++--
src/exe/list-sort.h | 2 +-
src/exe/list.h | 13 ++---
src/exe/play.h | 39 +++++++--------
src/exe/tag.h | 4 +-
src/format/meta.h | 60 +++++++++++++++--------
src/gui/main.cpp | 12 ++---
src/gui/meta-info.hpp | 20 ++++----
src/gui/mod.c | 65 ++++++++++++-------------
src/gui/track-playback.h | 11 ++---
src/gui/windows.c | 2 +-
src/jni/convert.h | 4 +-
src/jni/playback.h | 11 ++---
src/jni/queue.h | 73 ++++++++++++----------------
src/list/cue-read.c | 16 +++----
src/list/m3u-read.h | 24 ++++------
src/list/m3u-write.h | 6 +--
src/list/pls-read.c | 16 ++-----
src/phiola.h | 44 ++++++++++-------
src/remote-ctl.c | 3 +-
src/track.h | 11 +----
src/util/util.h | 24 ++++++++++
31 files changed, 326 insertions(+), 332 deletions(-)
diff --git a/src/afilter/split.h b/src/afilter/split.h
index ade5450..0db0f20 100644
--- a/src/afilter/split.h
+++ b/src/afilter/split.h
@@ -149,7 +149,7 @@ static void split_next(struct split *c, phi_track *t)
};
c->out_trk = track->create(&conf);
phi_track *ot = c->out_trk;
- meta_if->copy(&ot->meta, &t->meta);
+ meta_if->copy(&ot->meta, &t->meta, 0);
ot->data_type = "pcm";
ot->audio.format = t->audio.format;
diff --git a/src/core/dir-read.c b/src/core/dir-read.c
index 8210161..3a202f9 100644
--- a/src/core/dir-read.c
+++ b/src/core/dir-read.c
@@ -92,15 +92,12 @@ static int qu_add_dir_r(const char *fn, phi_track *t)
continue;
}
- struct phi_queue_entry qe = {};
- phi_track_conf_assign(&qe.conf, &t->conf);
- qe.conf.ifile.name = fpath;
- fpath = NULL;
- if (t->conf.ofile.name)
- qe.conf.ofile.name = ffsz_dup(t->conf.ofile.name);
- phi_metaif->copy(&qe.conf.meta, &t->conf.meta);
-
+ struct phi_queue_entry qe = {
+ .url = fpath,
+ };
qcur = phi_queueif.insert(qcur, &qe);
+ ffmem_free(fpath);
+ fpath = NULL;
if (!dir_removed) {
dir_removed = 1;
diff --git a/src/core/file-tee.h b/src/core/file-tee.h
index 224809c..06c5344 100644
--- a/src/core/file-tee.h
+++ b/src/core/file-tee.h
@@ -163,7 +163,7 @@ static int tee_process(void *f, phi_track *t)
.ofile.name = ffsz_dup(tee_name),
};
c->out_trk = core->track->create(&conf);
- meta_if->copy(&c->out_trk->meta, &t->meta);
+ meta_if->copy(&c->out_trk->meta, &t->meta, 0);
if (t->conf.tee_output) {
c->out_trk->data_type = "pcm";
diff --git a/src/core/queue-entry.h b/src/core/queue-entry.h
index 5473cd2..493d159 100644
--- a/src/core/queue-entry.h
+++ b/src/core/queue-entry.h
@@ -9,27 +9,20 @@ struct q_entry {
phi_track *trk;
uint index;
uint used;
- uint have_user_meta :1;
uint expand :1;
uint play_next_on_close :1;
+ char name[0];
};
-static void meta_destroy(ffvec *meta)
+static void meta_destroy(phi_meta *meta)
{
char **it;
FFSLICE_FOR(meta, it) {
ffmem_free(*it);
it += 2;
}
- ffvec_free(meta);
-}
-
-static inline void track_conf_destroy(struct phi_track_conf *c)
-{
- ffmem_free(c->ifile.name); c->ifile.name = NULL;
- ffmem_free(c->ofile.name); c->ofile.name = NULL;
- ffslice_free(&c->tracks);
- meta_destroy(&c->meta);
+ ffmem_free(meta->ptr);
+ phi_meta_null(meta);
}
static void qe_free(struct q_entry *e)
@@ -39,16 +32,21 @@ static void qe_free(struct q_entry *e)
if (e == qm->cursor)
qm->cursor = NULL;
- track_conf_destroy(&e->pub.conf);
+ meta_destroy(&e->pub.meta);
+ if (e->pub.url != e->name)
+ ffmem_free(e->pub.url);
ffmem_free(e);
}
static struct q_entry* qe_new(struct phi_queue_entry *qe)
{
- struct q_entry *e = ffmem_new(struct q_entry);
+ size_t n = ffsz_len(qe->url) + 1;
+ struct q_entry *e = ffmem_alloc(sizeof(struct q_entry) + n);
+ ffmem_zero_obj(e);
e->pub = *qe;
- e->have_user_meta = (e->pub.conf.meta.len && !e->pub.conf.meta_transient);
e->used = 1;
+ ffmem_copy(e->name, qe->url, n);
+ e->pub.url = e->name;
return e;
}
@@ -76,16 +74,16 @@ static void qe_close(void *f, phi_track *t)
if (e->trk == t) {
e->trk = NULL;
- if (e->q && !e->q->conf.conversion && !e->have_user_meta) {
- int mod = (e->pub.conf.meta.len || t->meta.len); // empty meta == not modified
+ if (e->q && !e->q->conf.conversion && !e->pub.meta_priority) {
+ int mod = (e->pub.meta.len || t->meta.len); // empty meta == not modified
if (t->meta.len) {
fflock_lock((fflock*)&e->pub.lock); // UI thread may read or write `conf.meta` at this moment
- ffvec meta_old = e->pub.conf.meta;
- e->pub.conf.meta = t->meta; // Remember the tags we read from file in this track
+ phi_meta meta_old = e->pub.meta;
+ e->pub.meta = t->meta; // Remember the tags we read from file in this track
fflock_unlock((fflock*)&e->pub.lock);
meta_destroy(&meta_old);
- ffvec_null(&t->meta);
+ phi_meta_null(&t->meta);
}
if (mod)
q_modified(e->q);
@@ -125,12 +123,15 @@ static int qe_play(struct q_entry *e)
return 1;
}
- struct phi_track_conf *c = &e->pub.conf;
- c->cross_worker_assign = e->q->conf.conversion;
+ struct phi_track_conf c = e->q->conf.tconf;
+ c.ifile.name = e->pub.url;
+ c.seek_cdframes = e->pub.seek_cdframes;
+ c.until_cdframes = e->pub.until_cdframes;
+ c.cross_worker_assign = e->q->conf.conversion;
const phi_filter *ui_if = (e->q->conf.ui_module_if_set) ? e->q->conf.ui_module_if : core->mod(e->q->conf.ui_module);
const phi_track_if *track = core->track;
- phi_track *t = track->create(c);
+ phi_track *t = track->create(&c);
if (e->q->conf.first_filter
&& !track->filter(t, e->q->conf.first_filter, 0))
@@ -141,7 +142,7 @@ static int qe_play(struct q_entry *e)
|| !track->filter(t, core->mod("core.auto-input"), 0)
|| !track->filter(t, core->mod("format.detect"), 0)
|| !track->filter(t, core->mod("afilter.until"), 0)
- || (c->afilter.danorm
+ || (c.afilter.danorm
&& !track->filter(t, core->mod("af-danorm.f"), 0))
|| !track->filter(t, ui_if, 0)
|| !track->filter(t, core->mod("afilter.gain"), 0)
@@ -157,36 +158,39 @@ static int qe_play(struct q_entry *e)
|| !track->filter(t, core->mod("format.detect"), 0)
|| !track->filter(t, core->mod("afilter.until"), 0)
|| !track->filter(t, ui_if, 0)
- || !track->filter(t, core->mod("afilter.auto-conv-f"), 0)
- || (c->afilter.peaks_info
+ || ((c.afilter.peaks_info
+ || c.afilter.loudness_summary)
+ && !track->filter(t, core->mod("afilter.auto-conv-f"), 0))
+ || (c.afilter.peaks_info
&& !track->filter(t, core->mod("afilter.peaks"), 0))
- || (c->afilter.loudness_summary
+ || (c.afilter.loudness_summary
&& !track->filter(t, core->mod("af-loudness.analyze"), 0)))
goto err;
} else {
if (!track->filter(t, &phi_queue_guard, 0)
|| !track->filter(t, core->mod("core.auto-input"), 0)
- || (c->tee
+ || (c.tee
&& !track->filter(t, core->mod("core.tee"), 0))
|| !track->filter(t, core->mod("format.detect"), 0)
|| !track->filter(t, core->mod("afilter.until"), 0)
|| !track->filter(t, ui_if, 0)
- || (c->afilter.auto_normalizer
+ || (c.afilter.auto_normalizer
&& (!track->filter(t, core->mod("afilter.auto-conv-f"), 0)
|| !track->filter(t, core->mod("af-loudness.analyze"), 0)
|| !track->filter(t, core->mod("afilter.auto-norm"), 0)))
|| !track->filter(t, core->mod("afilter.gain"), 0)
|| !track->filter(t, core->mod("afilter.auto-conv"), 0)
- || (c->tee_output
+ || (c.tee_output
&& !track->filter(t, core->mod("core.tee"), 0))
|| !track->filter(t, core->mod(e->q->conf.audio_module), 0))
goto err;
}
- if ((e->have_user_meta = (e->pub.conf.meta.len && !e->pub.conf.meta_transient))) {
- phi_metaif->copy(&t->meta, &t->conf.meta);
- }
+ if (e->q->conf.tconf.meta.len)
+ phi_metaif->copy(&t->meta, &e->q->conf.tconf.meta, 0); // from user
+ if (e->pub.meta.len && e->pub.meta_priority)
+ phi_metaif->copy(&t->meta, &e->pub.meta, (e->q->conf.tconf.meta.len) ? PHI_META_UNIQUE : 0); // from .cue
e->trk = t;
e->used++;
@@ -203,37 +207,37 @@ static int qe_play(struct q_entry *e)
static int qe_expand(struct q_entry *e)
{
- struct phi_track_conf *c = &e->pub.conf;
const phi_track_if *track = core->track;
-
- if (url_checkz(c->ifile.name))
+ if (url_checkz(e->pub.url))
return -1;
ffbool dir = 0, decompress = 0;
fffileinfo fi;
- if (!fffile_info_path(c->ifile.name, &fi)
+ if (!fffile_info_path(e->pub.url, &fi)
&& fffile_isdir(fffileinfo_attr(&fi))) {
dir = 1;
} else {
ffstr ext;
- ffpath_splitname_str(FFSTR_Z(c->ifile.name), NULL, &ext);
+ ffpath_splitname_str(FFSTR_Z(e->pub.url), NULL, &ext);
if (!(ffstr_eqz(&ext, "m3u8")
|| ffstr_eqz(&ext, "m3u")
|| (decompress = ffstr_eqz(&ext, "m3uz"))))
return -1;
}
- phi_track *t = track->create(c);
+ struct phi_track_conf c = e->q->conf.tconf;
+ c.ifile.name = e->pub.url;
+ phi_track *t = track->create(&c);
if (dir) {
- if (!core->track->filter(t, &phi_queue_guard, 0)
- || !core->track->filter(t, core->mod("core.dir-read"), 0))
+ if (!track->filter(t, &phi_queue_guard, 0)
+ || !track->filter(t, core->mod("core.dir-read"), 0))
goto err;
} else {
- if (!core->track->filter(t, &phi_queue_guard, 0)
- || !core->track->filter(t, core->mod("core.auto-input"), 0)
+ if (!track->filter(t, &phi_queue_guard, 0)
+ || !track->filter(t, core->mod("core.auto-input"), 0)
|| (decompress
- && !core->track->filter(t, core->mod("zstd.decompress"), 0))
- || !core->track->filter(t, core->mod("format.m3u"), 0))
+ && !track->filter(t, core->mod("zstd.decompress"), 0))
+ || !track->filter(t, core->mod("format.m3u"), 0))
goto err;
}
@@ -282,15 +286,15 @@ int qe_filter(struct q_entry *e, ffstr filter, uint flags)
{
ffstr name, val;
- if (flags & 1) {
- name = FFSTR_Z(e->pub.conf.ifile.name);
+ if (flags & PHI_QF_FILENAME) {
+ name = FFSTR_Z(e->pub.url);
if (ffstr_ifindstr(&name, &filter) >= 0)
return 1;
}
- if (flags & 2) {
+ if (flags & PHI_QF_META) {
uint i = 0;
- while (phi_metaif->list(&e->pub.conf.meta, &i, &name, &val, 0)) {
+ while (phi_metaif->list(&e->pub.meta, &i, &name, &val, 0)) {
if (ffstr_ifindstr(&val, &filter) >= 0)
return 1;
}
diff --git a/src/core/queue.c b/src/core/queue.c
index 7d7bb37..83b2357 100644
--- a/src/core/queue.c
+++ b/src/core/queue.c
@@ -152,6 +152,10 @@ static void q_free(struct phi_queue *q)
return;
}
+ ffmem_free(q->conf.tconf.ofile.name);
+ ffslice_free(&q->conf.tconf.tracks);
+ meta_destroy(&q->conf.tconf.meta);
+
ffmem_free(q->conf.name);
ffmem_free(q);
}
@@ -197,7 +201,7 @@ static void* q_insert(struct phi_queue *q, uint pos, struct phi_queue_entry *qe)
else
*ffslice_moveT((ffslice*)&q->index, pos, pos + 1, q->index.len - 1 - pos, void*) = e;
fflock_unlock(&q->lock);
- dbglog("added '%s' [%u/%L]", qe->conf.ifile.name, pos, q->index.len);
+ dbglog("added '%s' [%u/%L]", qe->url, pos, q->index.len);
q_modified(q);
qm->on_change(q, 'a', pos);
return e;
@@ -526,7 +530,7 @@ static int q_remove_at(struct phi_queue *q, uint pos, uint n)
if (!e)
return -1;
e->index = ~0;
- dbglog("removed '%s' @%u", e->pub.conf.ifile.name, pos);
+ dbglog("removed '%s' @%u", e->pub.url, pos);
fflock_lock(&q->lock); // after q_ref() has read the item @pos, but before 'used++', the item must not be destroyed
if (q->cursor_index > 0
@@ -601,7 +605,7 @@ static int q_sort_cmp(const void *_a, const void *_b, void *udata)
return 1;
}
- return ffsz_icmp(a->pub.conf.ifile.name, b->pub.conf.ifile.name);
+ return ffsz_icmp(a->pub.url, b->pub.url);
}
static void q_sort(phi_queue_id q, uint flags)
@@ -623,7 +627,7 @@ static void q_sort(phi_queue_id q, uint flags)
FFSLICE_WALK(&q->index, it) {
fffileinfo fi;
uint64 fs = 0;
- if (!fffile_info_path((*it)->pub.conf.ifile.name, &fi)) {
+ if (!fffile_info_path((*it)->pub.url, &fi)) {
fs = fffileinfo_size(&fi);
if (flags == PHI_Q_SORT_FILEDATE)
fs = fffileinfo_mtime(&fi).sec;
@@ -654,7 +658,7 @@ static void q_remove_multi(phi_queue_id q, uint flags)
FFSLICE_WALK(&q->index, it) {
struct q_entry *qe = *it;
if (flags & PHI_Q_RM_NONEXIST) {
- const char *fn = qe->pub.conf.ifile.name;
+ const char *fn = qe->pub.url;
if (fffile_exists(fn)) {
*ffvec_pushT(&new_index, void*) = qe;
continue;
diff --git a/src/exe/cmd.h b/src/exe/cmd.h
index 0bc5a92..6147298 100644
--- a/src/exe/cmd.h
+++ b/src/exe/cmd.h
@@ -67,7 +67,7 @@ static int cmd_tracks(ffvec *tracks, ffstr s)
return _ffargs_err(&x->cmd, 1, "incorrect track number '%S'", &it);
}
-static void cmd_meta_set(ffvec *dst, const ffvec *src)
+static void cmd_meta_set(phi_meta *dst, const ffvec *src)
{
if (!x->metaif)
x->metaif = x->core->mod("format.meta");
@@ -87,7 +87,7 @@ static int cmd_input_names(ffvec *input, fffd f)
ffvec buf = {};
for (;;) {
ffvec_grow(&buf, 8*1024, 1);
- ssize_t r = ffstdin_read(ffslice_end(&buf, 1), ffvec_unused(&buf));
+ ssize_t r = ffstdin_read(ffslice_end(&buf, 1), ffvec_unused(&buf) - 1);
if (r == 0) {
break;
} else if (r < 0) {
@@ -102,8 +102,10 @@ static int cmd_input_names(ffvec *input, fffd f)
while (view.len) {
ffstr_splitby(&view, '\n', &ln, &view);
ffstr_trimwhite(&ln);
- if (ln.len)
+ if (ln.len) {
+ ln.ptr[ln.len] = '\0';
*ffvec_pushT(input, ffstr) = ln;
+ }
}
rc = 0;
@@ -138,7 +140,8 @@ static int wildcard_expand(ffvec *input, ffstr s)
dbglog("wildcard expand: %s", fn);
ffstr *p = ffvec_zpushT(input, ffstr);
ffsize cap = 0;
- ffstr_growfmt(p, &cap, "%S\\%s", &dir, fn);
+ ffstr_growfmt(p, &cap, "%S\\%s%Z", &dir, fn);
+ p->len--;
}
rc = 0;
@@ -157,6 +160,7 @@ static int cmd_input(ffvec *input, ffstr s)
return cmd_input_names(input, ffstdin);
#ifdef FF_WIN
+ s.ptr[s.len] = '\0';
if (!(ffstr_matchz(&s, "http://")
|| ffstr_matchz(&s, "https://")
|| ffstr_matchz(&s, "\\\\?\\"))
diff --git a/src/exe/convert.h b/src/exe/convert.h
index 1dd8b60..01b3896 100644
--- a/src/exe/convert.h
+++ b/src/exe/convert.h
@@ -202,11 +202,12 @@ static int conv_cpu_affinity(struct cmd_conv *v, ffstr s)
return 0;
}
-static void conv_qu_add(struct cmd_conv *v, ffstr *fn)
+static int conv_action(struct cmd_conv *v)
{
+ x->queue->on_change(q_on_change);
+
struct phi_track_conf c = {
.ifile = {
- .name = ffsz_dupstr(fn),
.include = *(ffslice*)&v->include,
.exclude = *(ffslice*)&v->exclude,
.preserve_date = v->preserve_date,
@@ -243,28 +244,22 @@ static void conv_qu_add(struct cmd_conv *v, ffstr *fn)
.stream_copy = v->copy,
.print_time = v->perf,
};
-
cmd_meta_set(&c.meta, &v->meta);
ffvec_free(&v->meta);
- struct phi_queue_entry qe = {
- .conf = c,
- };
- x->queue->add(NULL, &qe);
-}
-
-static int conv_action(struct cmd_conv *v)
-{
- x->queue->on_change(q_on_change);
struct phi_queue_conf qc = {
.first_filter = &phi_guard,
.ui_module = "tui.play",
+ .tconf = c,
.conversion = 1,
};
x->queue->create(&qc);
ffstr *it;
FFSLICE_WALK(&v->input, it) {
- conv_qu_add(v, it);
+ struct phi_queue_entry qe = {
+ .url = it->ptr,
+ };
+ x->queue->add(NULL, &qe);
}
ffvec_free(&v->input);
diff --git a/src/exe/gui.h b/src/exe/gui.h
index 3e26f73..fb794db 100644
--- a/src/exe/gui.h
+++ b/src/exe/gui.h
@@ -19,8 +19,7 @@ struct cmd_gui {
static int gui_input(struct cmd_gui *g, ffstr s)
{
- *ffvec_pushT(&g->input, ffstr) = s;
- return 0;
+ return cmd_input(&g->input, s);
}
static void gui_log_ctl(uint flags)
@@ -42,7 +41,7 @@ static int gui_action(struct cmd_gui *g)
ffstr *it;
FFSLICE_WALK(&g->input, it) {
struct phi_queue_entry qe = {
- .conf.ifile.name = ffsz_dupstr(it),
+ .url = it->ptr,
};
x->queue->add(NULL, &qe);
}
diff --git a/src/exe/info.h b/src/exe/info.h
index 8873761..252d01f 100644
--- a/src/exe/info.h
+++ b/src/exe/info.h
@@ -73,11 +73,12 @@ static int info_input(struct cmd_info *p, ffstr s)
return cmd_input(&p->input, s);
}
-static void info_qu_add(struct cmd_info *p, ffstr *fn)
+static int info_action(struct cmd_info *p)
{
+ x->queue->on_change(q_on_change);
+
struct phi_track_conf c = {
.ifile = {
- .name = ffsz_dupstr(fn),
.include = *(ffslice*)&p->include,
.exclude = *(ffslice*)&p->exclude,
},
@@ -93,24 +94,19 @@ static void info_qu_add(struct cmd_info *p, ffstr *fn)
.print_time = p->perf,
};
- struct phi_queue_entry qe = {
- .conf = c,
- };
- x->queue->add(NULL, &qe);
-}
-
-static int info_action(struct cmd_info *p)
-{
- x->queue->on_change(q_on_change);
struct phi_queue_conf qc = {
.first_filter = &phi_guard,
.ui_module = "tui.play",
+ .tconf = c,
.analyze = 1,
};
x->queue->create(&qc);
ffstr *it;
FFSLICE_WALK(&p->input, it) {
- info_qu_add(p, it);
+ struct phi_queue_entry qe = {
+ .url = it->ptr,
+ };
+ x->queue->add(NULL, &qe);
}
ffvec_free(&p->input);
diff --git a/src/exe/list-heal.h b/src/exe/list-heal.h
index cbd61bc..9380ad8 100644
--- a/src/exe/list-heal.h
+++ b/src/exe/list-heal.h
@@ -217,6 +217,7 @@ static void lh_free_table(struct list_heal *lh)
continue;
struct lh_map_ent *me = it->val;
ffmem_free(me->path.ptr);
+ ffmem_free(me);
}
ffmap_free(&lh->map);
}
@@ -330,14 +331,14 @@ static void lh_ready(void *param)
struct phi_queue_entry *qe;
for (uint i = 0; (qe = x->queue->at(q, i)); i++) {
char *new_name;
- if ((new_name = lh_heal(lh, qe->conf.ifile.name))) {
- ffmem_free(qe->conf.ifile.name);
- qe->conf.ifile.name = new_name;
+ if ((new_name = lh_heal(lh, qe->url))) {
+ qe->url = new_name;
nfixed++;
}
ntotal++;
}
+ ffvec_free(&lh->tasks);
ffvec_free(&lh->buf);
ffvec_free(&lh->pl_dir);
ffvec_free(&lh->ds_path);
@@ -362,7 +363,7 @@ static int lh_action(struct list_heal *lh)
phi_queue_id q = x->queue->create(&qc);
struct phi_queue_entry qe = {
- .conf.ifile.name = ffsz_dup(*it),
+ .url = *it,
};
x->queue->add(q, &qe);
diff --git a/src/exe/list-sort.h b/src/exe/list-sort.h
index 8cadf3f..4867e93 100644
--- a/src/exe/list-sort.h
+++ b/src/exe/list-sort.h
@@ -56,7 +56,7 @@ static int ls_action(struct list_sort *ls)
phi_queue_id q = x->queue->create(&qc);
struct phi_queue_entry qe = {
- .conf.ifile.name = ffsz_dup(*it),
+ .url = *it,
};
x->queue->add(q, &qe);
diff --git a/src/exe/list.h b/src/exe/list.h
index 0733a55..5b962de 100644
--- a/src/exe/list.h
+++ b/src/exe/list.h
@@ -52,17 +52,18 @@ static void lc_done(void *lc, phi_track *t)
static int lc_action(struct list_create *lc)
{
- struct phi_queue_conf qc = {};
+ struct phi_queue_conf qc = {
+ .tconf = {
+ .ifile.include = *(ffslice*)&lc->include,
+ .ifile.exclude = *(ffslice*)&lc->exclude,
+ },
+ };
phi_queue_id q = x->queue->create(&qc);
char **it;
FFSLICE_WALK(&lc->input, it) {
struct phi_queue_entry qe = {
- .conf.ifile = {
- .name = ffsz_dup(*it),
- .include = *(ffslice*)&lc->include,
- .exclude = *(ffslice*)&lc->exclude,
- },
+ .url = *it,
};
x->queue->add(q, &qe);
}
diff --git a/src/exe/play.h b/src/exe/play.h
index b7120fc..31417fe 100644
--- a/src/exe/play.h
+++ b/src/exe/play.h
@@ -110,11 +110,21 @@ static int play_input(struct cmd_play *p, ffstr s)
return cmd_input(&p->input, s);
}
-static void play_qu_add(struct cmd_play *p, ffstr *fn)
+static int play_action(struct cmd_play *p)
{
+ if (p->volume != ~0U) {
+ struct phi_ui_conf uc = {
+ .volume_percent = p->volume,
+ };
+ const phi_ui_if *uif = x->core->mod("tui.if");
+ uif->conf(&uc);
+ }
+
+ if (p->audio.len)
+ p->audio_module = ffsz_allocfmt("%S.play", &p->audio);
+
struct phi_track_conf c = {
.ifile = {
- .name = ffsz_dupstr(fn),
.buf_size = p->rbuffer_kb * 1024,
.include = *(ffslice*)&p->include,
.exclude = *(ffslice*)&p->exclude,
@@ -138,37 +148,22 @@ static void play_qu_add(struct cmd_play *p, ffstr *fn)
.print_time = p->perf,
};
- struct phi_queue_entry qe = {
- .conf = c,
- };
- x->queue->add(NULL, &qe);
-}
-
-static int play_action(struct cmd_play *p)
-{
- if (p->volume != ~0U) {
- struct phi_ui_conf uc = {
- .volume_percent = p->volume,
- };
- const phi_ui_if *uif = x->core->mod("tui.if");
- uif->conf(&uc);
- }
-
- if (p->audio.len)
- p->audio_module = ffsz_allocfmt("%S.play", &p->audio);
-
x->queue->on_change(q_on_change);
struct phi_queue_conf qc = {
.first_filter = &phi_guard,
.audio_module = p->audio_module,
.ui_module = "tui.play",
+ .tconf = c,
.random = p->random,
.repeat_all = p->repeat_all,
};
x->queue->create(&qc);
ffstr *it;
FFSLICE_WALK(&p->input, it) {
- play_qu_add(p, it);
+ struct phi_queue_entry qe = {
+ .url = it->ptr,
+ };
+ x->queue->add(NULL, &qe);
}
ffvec_free(&p->input);
diff --git a/src/exe/tag.h b/src/exe/tag.h
index 0c0b332..8daf4b7 100644
--- a/src/exe/tag.h
+++ b/src/exe/tag.h
@@ -51,8 +51,8 @@ static int tag_action(struct cmd_tag *t)
ffstr *fn;
FFSLICE_WALK(&t->input, fn) {
struct phi_tag_conf conf = {
- .filename = ffsz_dupstr(fn),
- .meta = t->meta,
+ .filename = fn->ptr,
+ .meta = *(ffslice*)&t->meta,
.clear = t->clear,
.preserve_date = t->preserve_date,
.no_expand = t->fast,
diff --git a/src/format/meta.h b/src/format/meta.h
index 81db556..fa3501d 100644
--- a/src/format/meta.h
+++ b/src/format/meta.h
@@ -3,21 +3,31 @@
#include
-static void meta_destroy(ffvec *meta)
+static void meta_destroy(phi_meta *meta)
{
char **it;
FFSLICE_FOR(meta, it) {
ffmem_free(*it);
it += 2;
}
- ffvec_free(meta);
+ ffmem_free(meta->ptr);
+ phi_meta_null(meta);
}
-static void meta_set(ffvec *meta, ffstr name, ffstr val, uint flags)
+static void meta_set(phi_meta *meta, ffstr name, ffstr val, uint flags)
{
phi_dbglog(core, NULL, NULL, "meta: %S = %S", &name, &val);
if (!name.len) return;
+ if (flags & PHI_META_UNIQUE) {
+ char **it;
+ FFSLICE_FOR(meta, it) {
+ if (ffstr_ieqz(&name, it[0]))
+ return;
+ it += 2;
+ }
+ }
+
char *s = ffsz_allocfmt("%S%Z%S", &name, &val);
if (flags & PHI_META_REPLACE) {
@@ -33,54 +43,62 @@ static void meta_set(ffvec *meta, ffstr name, ffstr val, uint flags)
}
}
- *ffvec_pushT(meta, char*) = s;
- *ffvec_pushT(meta, char*) = s + name.len + 1;
+ if (meta->len + 2 > meta->cap) {
+ meta->cap = meta->len + ffmax(2, meta->len / 2);
+ meta->ptr = ffmem_realloc(meta->ptr, meta->cap * sizeof(char*));
+ }
+ char **m = meta->ptr;
+ m[meta->len] = s;
+ m[meta->len + 1] = s + name.len + 1;
+ meta->len += 2;
}
-static void meta_copy(ffvec *dst, const ffvec *src)
+static void meta_copy(phi_meta *dst, const phi_meta *src, uint flags)
{
char **it;
FFSLICE_FOR(src, it) {
- meta_set(dst, FFSTR_Z(it[0]), FFSTR_Z(it[1]), 0);
+ meta_set(dst, FFSTR_Z(it[0]), FFSTR_Z(it[1]), flags);
it += 2;
}
}
-static int meta_list(const ffvec *meta, uint *i, ffstr *name, ffstr *val, uint flags)
+static int meta_list(const phi_meta *meta, uint *index, ffstr *name, ffstr *val, uint flags)
{
+ int r = 1;
+ uint i = *index;
+ const char **m = meta->ptr;
for (;;) {
- if ((*i) * 2 == meta->len)
- return 0;
+ if (i * 2 == meta->len) {
+ r = 0;
+ break;
+ }
- const char *k = *ffslice_itemT(meta, (*i) * 2, char*);
- const char *v = *ffslice_itemT(meta, (*i) * 2 + 1, char*);
+ const char *k = m[i * 2], *v = m[i * 2 + 1];
ffstr_setz(name, k);
ffstr_setz(val, v);
- (*i)++;
+ i++;
if (!(flags & PHI_META_PRIVATE) && ffstr_matchz(name, "_phi_"))
continue;
int skip = 0;
if (flags & PHI_META_UNIQUE) {
- char **it;
- FFSLICE_FOR(meta, it) {
- if (k == *it)
- break;
+ for (const char **it = meta->ptr; *it != k; it += 2) {
if (ffstr_ieqz(name, *it)) {
skip = 1; // skip current k-v pair because same key is found before
break;
}
- it += 2;
}
}
-
if (!skip)
- return -1;
+ break;
}
+
+ *index = i;
+ return r;
}
-static int meta_find(const ffvec *meta, ffstr name, ffstr *val, uint flags)
+static int meta_find(const phi_meta *meta, ffstr name, ffstr *val, uint flags)
{
uint i = 0;
ffstr n, v;
diff --git a/src/gui/main.cpp b/src/gui/main.cpp
index 8e92bfe..ce93acb 100644
--- a/src/gui/main.cpp
+++ b/src/gui/main.cpp
@@ -218,7 +218,7 @@ static void conv_track_update(phi_track *t, const char *progress)
{
gui_wmain *m = gg->wmain;
struct phi_queue_entry *qe = (struct phi_queue_entry*)t->qent;
- gd->metaif->set(&qe->conf.meta, FFSTR_Z("_phi_dur"), FFSTR_Z(progress), PHI_META_REPLACE);
+ gd->metaif->set(&qe->meta, FFSTR_Z("_phi_dur"), FFSTR_Z(progress), PHI_META_REPLACE);
if (gd->tab_conversion) {
int idx = gd->queue->index(t->qent);
m->vlist.update(idx, 0);
@@ -260,7 +260,7 @@ static void list_display(ffui_view_disp *disp)
return;
fflock_lock((fflock*)&qe->lock); // core thread may read or write `conf.meta` at this moment
- ffvec *meta = &qe->conf.meta;
+ phi_meta *meta = &qe->meta;
switch (sub) {
case H_INDEX:
@@ -269,7 +269,7 @@ static void list_display(ffui_view_disp *disp)
break;
case H_FN:
- s = FFSTR_Z(qe->conf.ifile.name);
+ s = FFSTR_Z(qe->url);
val = &s;
break;
@@ -281,10 +281,10 @@ static void list_display(ffui_view_disp *disp)
switch (sub) {
case H_TITLE:
if (!val || !val->len) {
- if (url_checkz(qe->conf.ifile.name))
- ffstr_setz(&s, qe->conf.ifile.name); // use URL as a title
+ if (url_checkz(qe->url))
+ ffstr_setz(&s, qe->url); // use URL as a title
else
- ffpath_split3_str(FFSTR_Z(qe->conf.ifile.name), NULL, &s, NULL); // use filename as a title
+ ffpath_split3_str(FFSTR_Z(qe->url), NULL, &s, NULL); // use filename as a title
val = &s;
}
break;
diff --git a/src/gui/meta-info.hpp b/src/gui/meta-info.hpp
index ec026f1..5f07e9e 100644
--- a/src/gui/meta-info.hpp
+++ b/src/gui/meta-info.hpp
@@ -62,10 +62,10 @@ static void winfo_display(struct phi_queue_entry *qe)
w->keys.reset();
w->changed = 0;
- winfo_addpair("File path", qe->conf.ifile.name);
+ winfo_addpair("File path", qe->url);
fffileinfo fi = {};
- ffbool have_fi = (0 == fffile_info_path(qe->conf.ifile.name, &fi));
+ ffbool have_fi = (0 == fffile_info_path(qe->url, &fi));
data.len = 0;
if (have_fi)
@@ -83,7 +83,7 @@ static void winfo_display(struct phi_queue_entry *qe)
winfo_addpair("File date", data);
fflock_lock((fflock*)&qe->lock); // core thread may read or write `conf.meta` at this moment
- const ffvec *meta = &qe->conf.meta;
+ const phi_meta *meta = &qe->meta;
gd->metaif->find(meta, FFSTR_Z("_phi_info"), &val, PHI_META_PRIVATE);
winfo_addpair("Info", val);
@@ -120,7 +120,7 @@ void winfo_show(uint show, uint idx)
ffmem_free(w->wnd_pos);
}
- w->wnd.title(qe->conf.ifile.name);
+ w->wnd.title(qe->url);
winfo_display(qe);
w->qe = qe;
// keep the entry locked
@@ -136,7 +136,7 @@ static void winfo_edit(uint idx, const char *new_text)
if ((int)ki < 0)
return;
ffstr name = *w->keys.at(ki);
- gd->metaif->set(&w->qe->conf.meta, name, val, PHI_META_REPLACE);
+ gd->metaif->set(&w->qe->meta, name, val, PHI_META_REPLACE);
if (ki >= 32)
warnlog("can write only up to 32 tags");
ffbit_set32(&w->changed, ki);
@@ -150,12 +150,12 @@ static void winfo_tag_add(ffstr name)
if (!w->qe) return;
ffstr val;
- if (!gd->metaif->find(&w->qe->conf.meta, name, &val, 0)) {
+ if (!gd->metaif->find(&w->qe->meta, name, &val, 0)) {
warnlog("tag already exists: %S", &name);
return;
}
val = FFSTR_Z("");
- gd->metaif->set(&w->qe->conf.meta, name, val, 0);
+ gd->metaif->set(&w->qe->meta, name, val, 0);
winfo_addpair(name, val);
ffstr_dupstr(w->keys.push_z(), &name);
ffbit_set32(&w->changed, w->keys.len - 1);
@@ -176,7 +176,7 @@ static void winfo_write()
i = ffbit_rfind32(bits) - 1;
ffbit_reset32(&bits, i);
k = *w->keys.at(i);
- if (!gd->metaif->find(&w->qe->conf.meta, k, &v, 0)) {
+ if (!gd->metaif->find(&w->qe->meta, k, &v, 0)) {
ffvec s = {};
ffvec_addfmt(&s, "%S=%S", &k, &v);
*m.push() = *(ffstr*)&s;
@@ -187,10 +187,10 @@ static void winfo_write()
return;
struct phi_tag_conf conf = {};
- conf.filename = w->qe->conf.ifile.name;
+ conf.filename = w->qe->url;
conf.meta = m.slice();
if (!tag->edit(&conf)) {
- gd->metaif->destroy(&w->qe->conf.meta);
+ gd->metaif->destroy(&w->qe->meta);
w->keys.reset();
w->changed = 0;
w->vinfo.clear();
diff --git a/src/gui/mod.c b/src/gui/mod.c
index 721f225..1c98b1f 100644
--- a/src/gui/mod.c
+++ b/src/gui/mod.c
@@ -91,7 +91,7 @@ void file_del(ffslice indices)
FFSLICE_WALK(&indices, it) {
qe = gd->queue->at(gd->q_selected, *it);
- *ffvec_pushT(&names, char*) = qe->conf.ifile.name;
+ *ffvec_pushT(&names, char*) = qe->url;
}
#ifdef FF_WIN
@@ -138,12 +138,12 @@ void file_dir_show(ffslice indices)
const struct phi_queue_entry *qe = gd->queue->at(list_id_visible(), *it);
#ifdef FF_WIN
- const char *const names[] = { qe->conf.ifile.name };
+ const char *const names[] = { qe->url };
ffui_openfolder(names, 1);
#else
ffstr dir;
- ffpath_splitpath_str(FFSTR_Z(qe->conf.ifile.name), &dir, NULL);
+ ffpath_splitpath_str(FFSTR_Z(qe->url), &dir, NULL);
char *dirz = ffsz_dupstr(&dir);
dir_show(dirz);
ffmem_free(dirz);
@@ -528,7 +528,7 @@ void lists_load()
}
struct phi_queue_entry qe = {
- .conf.ifile.name = fn,
+ .url = fn,
};
fn = NULL;
gd->queue->add(q, &qe);
@@ -541,22 +541,25 @@ void list_add(ffstr fn)
{
if (gd->q_filtered) return;
- struct phi_queue_entry qe = {};
- qe.conf.ifile.name = ffsz_dupstr(&fn);
+ struct phi_queue_entry qe = {
+ .url = ffsz_dupstr(&fn),
+ };
gd->queue->add(gd->q_selected, &qe);
+ ffmem_free(qe.url);
}
void list_add_sz(void *sz)
{
- if (gd->q_filtered) {
- ffmem_free(sz);
- return;
- }
+ if (gd->q_filtered)
+ goto end;
struct phi_queue_entry qe = {
- .conf.ifile.name = sz,
+ .url = sz,
};
gd->queue->add(gd->q_selected, &qe);
+
+end:
+ ffmem_free(sz);
}
void list_add_multi(ffslice names)
@@ -605,7 +608,7 @@ void list_add_to_next(ffslice indices)
uint *it;
FFSLICE_WALK(&indices, it) {
struct phi_queue_entry *qe = gd->queue->at(q_src, *it), nqe = {};
- nqe.conf.ifile.name = ffsz_dup(qe->conf.ifile.name);
+ qe_copy(&nqe, qe, gd->metaif);
gd->queue->add(q_target, &nqe);
}
@@ -709,9 +712,7 @@ void convert_add(ffslice indices)
const struct phi_queue_entry *iqe = gd->queue->at(q, *it);
struct phi_queue_entry qe = {};
- phi_track_conf_assign(&qe.conf, &iqe->conf);
- qe.conf.ifile.name = ffsz_dup(iqe->conf.ifile.name);
- gd->metaif->copy(&qe.conf.meta, &iqe->conf.meta);
+ qe_copy(&qe, iqe, gd->metaif);
gd->queue->add(gd->q_convert, &qe);
}
@@ -722,34 +723,26 @@ void convert_add(ffslice indices)
void convert_begin(void *param)
{
struct phi_track_conf *c = param;
- uint i = 0;
+ struct phi_queue_entry *qe = NULL;
if (!gd->q_convert)
goto end;
- struct phi_queue_entry *qe;
- for (i = 0; !!(qe = gd->queue->at(gd->q_convert, i)); i++) {
- qe->conf.ofile.name = ffsz_dup(c->ofile.name);
- qe->conf.ifile.preserve_date = c->ifile.preserve_date;
- qe->conf.seek_msec = c->seek_msec;
- qe->conf.until_msec = c->until_msec;
- qe->conf.aac.quality = c->aac.quality;
- qe->conf.vorbis.quality = c->vorbis.quality;
- qe->conf.opus.bitrate = c->opus.bitrate;
- qe->conf.stream_copy = c->stream_copy;
-
- if (c->meta.len) {
- qe->conf.meta_transient = 0;
- gd->metaif->destroy(&qe->conf.meta);
- gd->metaif->copy(&qe->conf.meta, &c->meta);
- }
- }
- if (i)
+ if ((qe = gd->queue->at(gd->q_convert, 0))) {
+ struct phi_queue_conf *qc = gd->queue->conf(gd->q_convert);
+ gd->metaif->destroy(&qc->tconf.meta);
+ ffmem_free(qc->tconf.ofile.name);
+ qc->tconf = *c;
+ phi_meta_null(&c->meta);
+ c->ofile.name = NULL;
gd->queue->play(NULL, gd->queue->at(gd->q_convert, 0));
+ }
end:
- if (!i)
+ if (!qe) {
wconvert_done();
- ffmem_free(c->ofile.name);
+ ffmem_free(c->ofile.name);
+ gd->metaif->destroy(&c->meta);
+ }
ffmem_free(c);
}
diff --git a/src/gui/track-playback.h b/src/gui/track-playback.h
index fad5900..bd66e48 100644
--- a/src/gui/track-playback.h
+++ b/src/gui/track-playback.h
@@ -167,18 +167,13 @@ static int gtrk_process(void *ctx, phi_track *t)
gd->metaif->find(&t->meta, FFSTR_Z("artist"), &artist, 0);
gd->metaif->find(&t->meta, FFSTR_Z("title"), &title, 0);
if (!title.len)
- ffpath_split3_str(FFSTR_Z(qe->conf.ifile.name), NULL, &title, NULL); // use filename as a title
+ ffpath_split3_str(FFSTR_Z(t->conf.ifile.name), NULL, &title, NULL); // use filename as a title
ffsz_format(ti->buf, sizeof(ti->buf), "%S - %S - phiola"
, &artist, &title);
// We need to display the currently active track's meta data before `queue` does this on track close
- fflock_lock((fflock*)&qe->lock); // UI thread may read or write `conf.meta` at this moment
- ffvec meta_old = qe->conf.meta;
- qe->conf.meta = t->meta;
- fflock_unlock((fflock*)&qe->lock);
-
- gd->metaif->destroy(&meta_old);
- ffvec_null(&t->meta);
+ if (!qe->meta_priority)
+ qe_meta_update(qe, &t->meta, gd->metaif);
gui_task_ptr(wmain_track_new, ti);
diff --git a/src/gui/windows.c b/src/gui/windows.c
index ff01eba..6a72f01 100644
--- a/src/gui/windows.c
+++ b/src/gui/windows.c
@@ -255,7 +255,7 @@ static int action()
char **it;
FFSLICE_WALK(&x->input, it) {
struct phi_queue_entry qe = {
- .conf.ifile.name = ffsz_dup(*it),
+ .url = *it,
};
if (0 == x->queue->add(NULL, &qe))
x->queue->play(NULL, x->queue->at(NULL, 0));
diff --git a/src/jni/convert.h b/src/jni/convert.h
index dc06820..6f5cb00 100644
--- a/src/jni/convert.h
+++ b/src/jni/convert.h
@@ -74,7 +74,7 @@ static void conv_grd_close(void *ctx, phi_track *t)
if (x->convert.q_add_remove) {
struct phi_queue_entry qe = {
- .conf.ifile.name = ffsz_dup(oname),
+ .url = (char*)oname,
};
x->queue.add(x->convert.q_add_remove, &qe);
}
@@ -86,7 +86,7 @@ static void conv_grd_close(void *ctx, phi_track *t)
&& x->convert.q_pos >= 0) {
struct phi_queue_entry *qe = x->queue.ref(x->convert.q_add_remove, x->convert.q_pos);
if (qe) {
- if (ffsz_eq(qe->conf.ifile.name, t->conf.ifile.name))
+ if (ffsz_eq(qe->url, t->conf.ifile.name))
x->queue.remove(qe);
x->queue.unref(qe);
}
diff --git a/src/jni/playback.h b/src/jni/playback.h
index 00e2d69..eec2fbd 100644
--- a/src/jni/playback.h
+++ b/src/jni/playback.h
@@ -78,7 +78,7 @@ static void meta_fill(JNIEnv *env, jobject jmeta, const phi_track *t)
jni_obj_sz_set(env, jmeta, jni_field_str(x->Phiola_Meta, "url"), t->conf.ifile.name);
struct phi_queue_entry *qe = (struct phi_queue_entry*)t->qent;
- const ffvec *meta = &qe->conf.meta;
+ const phi_meta *meta = &qe->meta;
uint i = 0;
ffstr k, v;
@@ -174,13 +174,8 @@ static int play_ui_process(void *f, phi_track *t)
x->metaif.set(&t->meta, FFSTR_Z("_phi_info"), FFSTR_Z(buf), 0);
// We need to display the currently active track's meta data before `queue` does this on track close
- fflock_lock((fflock*)&qe->lock); // UI thread may read or write `conf.meta` at this moment
- ffvec meta_old = qe->conf.meta;
- qe->conf.meta = t->meta;
- fflock_unlock((fflock*)&qe->lock);
-
- x->metaif.destroy(&meta_old);
- ffvec_null(&t->meta);
+ if (!qe->meta_priority)
+ qe_meta_update(qe, &t->meta, &x->metaif);
JNIEnv *env;
int r = jni_vm_attach(jvm, &env);
diff --git a/src/jni/queue.h b/src/jni/queue.h
index 46a7c82..b8f39f3 100644
--- a/src/jni/queue.h
+++ b/src/jni/queue.h
@@ -111,9 +111,10 @@ Java_com_github_stsaz_phiola_Phiola_quDestroy(JNIEnv *env, jobject thiz, jlong q
static void qu_cmd_add(struct core_data *d)
{
struct phi_queue_entry qe = {
- .conf.ifile.name = d->param_str,
+ .url = d->param_str,
};
x->queue.add(d->q, &qe);
+ ffmem_free(d->param_str);
ffmem_free(d);
}
@@ -125,9 +126,7 @@ static void qu_cmd_dup(struct core_data *d)
uint i = (pos >= 0) ? pos : 0;
for (; (iqe = x->queue.at(iq, i)); i++) {
struct phi_queue_entry qe = {};
- phi_track_conf_assign(&qe.conf, &iqe->conf);
- qe.conf.ifile.name = ffsz_dup(iqe->conf.ifile.name);
- x->metaif.copy(&qe.conf.meta, &iqe->conf.meta);
+ qe_copy(&qe, iqe, &x->metaif);
x->queue.add(d->q, &qe);
if (pos >= 0)
break;
@@ -190,7 +189,7 @@ JNIEXPORT jstring JNICALL
Java_com_github_stsaz_phiola_Phiola_quEntry(JNIEnv *env, jobject thiz, jlong q, jint i)
{
struct phi_queue_entry *qe = x->queue.ref((phi_queue_id)q, i);
- const char *url = qe->conf.ifile.name;
+ const char *url = qe->url;
jstring s = jni_js_sz(url);
x->queue.unref(qe);
return s;
@@ -208,7 +207,7 @@ Java_com_github_stsaz_phiola_Phiola_quMoveAll(JNIEnv *env, jobject thiz, jlong q
struct phi_queue_entry *qe = x->queue.ref((phi_queue_id)q, i);
if (!qe)
break;
- const char *url = qe->conf.ifile.name;
+ const char *url = qe->url;
ffstr name;
ffpath_splitpath_str(FFSTR_Z(url), NULL, &name);
@@ -356,13 +355,13 @@ Java_com_github_stsaz_phiola_Phiola_quCmd(JNIEnv *env, jobject thiz, jlong jq, j
static ffvec info_prepare(const struct phi_queue_entry *qe)
{
- const ffvec *meta = &qe->conf.meta;
+ const phi_meta *meta = &qe->meta;
ffvec info = {};
ffvec_allocT(&info, 5*2 + meta->len, char*);
char **p = info.ptr;
*p++ = ffsz_dup("url");
- *p++ = ffsz_dup(qe->conf.ifile.name);
+ *p++ = ffsz_dup(qe->url);
*p++ = ffsz_dup("size");
*p++ = NULL;
@@ -444,14 +443,14 @@ Java_com_github_stsaz_phiola_Phiola_quMeta(JNIEnv *env, jobject thiz, jlong jq,
static void display_name_prepare(ffstr *val, ffsize cap, struct phi_queue_entry *qe, uint index, uint flags)
{
ffstr artist = {}, title = {}, name;
- x->metaif.find(&qe->conf.meta, FFSTR_Z("title"), &title, 0);
+ x->metaif.find(&qe->meta, FFSTR_Z("title"), &title, 0);
if (title.len) {
- x->metaif.find(&qe->conf.meta, FFSTR_Z("artist"), &artist, 0);
+ x->metaif.find(&qe->meta, FFSTR_Z("artist"), &artist, 0);
ffstr_addfmt(val, cap, "%u. %S - %S"
, index + 1, &artist, &title);
} else {
- ffstr_setz(&name, qe->conf.ifile.name);
- if (!url_checkz(qe->conf.ifile.name))
+ ffstr_setz(&name, qe->url);
+ if (!url_checkz(qe->url))
ffpath_splitpath_str(name, NULL, &name);
ffstr_addfmt(val, cap, "%u. %S"
, index + 1, &name);
@@ -476,11 +475,11 @@ Java_com_github_stsaz_phiola_Phiola_quDisplayLine(JNIEnv *env, jobject thiz, jlo
ffstr val = {};
struct phi_queue_entry *qe = x->queue.ref(q, i);
fflock_lock((fflock*)&qe->lock); // core thread may read or write `conf.meta` at this moment
- if (x->metaif.find(&qe->conf.meta, FFSTR_Z("_phi_display"), &val, PHI_META_PRIVATE)) {
+ if (x->metaif.find(&qe->meta, FFSTR_Z("_phi_display"), &val, PHI_META_PRIVATE)) {
val.ptr = buf;
uint flags = x->queue.conf(q)->conversion;
display_name_prepare(&val, sizeof(buf) - 1, qe, i, flags);
- x->metaif.set(&qe->conf.meta, FFSTR_Z("_phi_display"), val, 0);
+ x->metaif.set(&qe->meta, FFSTR_Z("_phi_display"), val, 0);
val.ptr[val.len] = '\0';
}
jstring js = jni_js_sz(val.ptr);
@@ -529,9 +528,13 @@ Java_com_github_stsaz_phiola_Phiola_quConvertBegin(JNIEnv *env, jobject thiz, jl
.stream_copy = !!(flags & F_COPY),
.oaudio.format.format = jni_obj_int(jconf, jni_field_int(jc_conf, "sample_format")),
.oaudio.format.rate = jni_obj_int(jconf, jni_field_int(jc_conf, "sample_rate")),
+
.aac.quality = jni_obj_int(jconf, jni_field_int(jc_conf, "aac_quality")),
.vorbis.quality = jni_obj_int(jconf, jni_field_int(jc_conf, "vorbis_quality")),
.opus.bitrate = jni_obj_int(jconf, jni_field_int(jc_conf, "opus_quality")),
+
+ .ofile.name = ffsz_dup(ofn),
+ .ofile.name_tmp = 1,
.ofile.overwrite = !!(flags & F_OVERWRITE),
};
@@ -559,42 +562,26 @@ Java_com_github_stsaz_phiola_Phiola_quConvertBegin(JNIEnv *env, jobject thiz, jl
x->convert.q_pos = jni_obj_int(jconf, jni_field_int(jc_conf, "q_pos"));
phi_queue_id q = (phi_queue_id)jq;
- uint i;
struct phi_queue_entry *qe;
- for (i = 0; !!(qe = x->queue.at(q, i)); i++) {
- struct phi_track_conf *c = &qe->conf;
-
- c->ifile.preserve_date = conf.ifile.preserve_date;
- c->seek_msec = conf.seek_msec;
- c->until_msec = conf.until_msec;
- c->stream_copy = conf.stream_copy;
-
- c->oaudio.format.format = conf.oaudio.format.format;
- c->oaudio.format.rate = conf.oaudio.format.rate;
-
- c->aac.quality = conf.aac.quality;
- c->vorbis.quality = conf.vorbis.quality;
- c->opus.bitrate = conf.opus.bitrate;
-
- c->ofile.name = ffsz_dup(ofn);
- c->ofile.name_tmp = 1;
- c->ofile.overwrite = conf.ofile.overwrite;
-
- if (conf.meta.len) {
- qe->conf.meta_transient = 0;
- x->metaif.destroy(&c->meta);
- x->metaif.copy(&c->meta, &conf.meta);
- }
+ if ((qe = x->queue.at(q, 0))) {
+ struct phi_queue_conf *qc = x->queue.conf(q);
+ ffmem_free(qc->tconf.ofile.name);
+ x->metaif.destroy(&qc->tconf.meta);
+ qc->tconf = conf;
+ qc->tconf.meta = conf.meta;
+ phi_meta_null(&conf.meta);
+ conf.ofile.name = NULL;
}
ffvec_free_align(&x->convert.tracks);
- ffvec_alloc_alignT(&x->convert.tracks, i, 64, struct conv_track_info);
- ffmem_zero(x->convert.tracks.ptr, i * sizeof(struct conv_track_info));
+ uint n = x->queue.count(q);
+ ffvec_alloc_alignT(&x->convert.tracks, n, 64, struct conv_track_info);
+ ffmem_zero(x->convert.tracks.ptr, n * sizeof(struct conv_track_info));
x->convert.q = q;
x->convert.interrupt = 0;
x->convert.n_tracks_updated = ~0U;
- if (i)
+ if (qe)
x->queue.play(NULL, x->queue.at(q, 0));
end:
@@ -652,7 +639,7 @@ static void qu_conv_update(phi_queue_id q)
}
fflock_lock((fflock*)&qe->lock); // UI thread may read or write `conf.meta` at this moment
- x->metaif.set(&qe->conf.meta, FFSTR_Z("_phi_display"), val, PHI_META_REPLACE);
+ x->metaif.set(&qe->meta, FFSTR_Z("_phi_display"), val, PHI_META_REPLACE);
fflock_unlock((fflock*)&qe->lock);
}
FFINT_WRITEONCE(x->convert.n_tracks_updated, n);
diff --git a/src/list/cue-read.c b/src/list/cue-read.c
index dc442dd..a1123ad 100644
--- a/src/list/cue-read.c
+++ b/src/list/cue-read.c
@@ -274,16 +274,12 @@ static void add_track(struct cue *c, struct ffcuetrk *ctrk, phi_track *t)
{
dbglog(t, "add '%S' %d..%d", &c->url, ctrk->from, ctrk->to);
struct phi_queue_entry qe = {
+ .url = c->url.ptr,
+ .seek_cdframes = ctrk->from,
+ .until_cdframes = ctrk->to,
.length_msec = (ctrk->to) ? (ctrk->to - ctrk->from) * 1000 / 75 : 0,
+ .meta_priority = 1,
};
- phi_track_conf_assign(&qe.conf, &t->conf);
- qe.conf.ifile.name = ffsz_dupstr(&c->url);
- if (t->conf.ofile.name)
- qe.conf.ofile.name = ffsz_dup(t->conf.ofile.name);
- metaif->copy(&qe.conf.meta, &t->conf.meta);
- ffslice_null(&qe.conf.tracks);
- qe.conf.seek_cdframes = ctrk->from;
- qe.conf.until_cdframes = ctrk->to;
// add global meta that isn't set in TRACK context
ffvec *m = (void*)c->gmetas.ptr;
@@ -292,13 +288,13 @@ static void add_track(struct cue *c, struct ffcuetrk *ctrk, phi_track *t)
if (cue_meta_find(&c->metas, c->nmeta, &m[i]) >= 0)
continue;
- metaif->set(&qe.conf.meta, *(ffstr*)&m[i], *(ffstr*)&m[i + 1], 0);
+ metaif->set(&qe.meta, *(ffstr*)&m[i], *(ffstr*)&m[i + 1], 0);
}
// add TRACK meta
m = (void*)c->metas.ptr;
for (uint i = 0; i != c->nmeta; i += 2) {
- metaif->set(&qe.conf.meta, *(ffstr*)&m[i], *(ffstr*)&m[i + 1], 0);
+ metaif->set(&qe.meta, *(ffstr*)&m[i], *(ffstr*)&m[i + 1], 0);
}
c->qu_cur = queue->insert(c->qu_cur, &qe);
diff --git a/src/list/m3u-read.h b/src/list/m3u-read.h
index 2e38da1..09a8bb9 100644
--- a/src/list/m3u-read.h
+++ b/src/list/m3u-read.h
@@ -53,26 +53,18 @@ static int m3u_add(struct m3u *m, phi_track *t)
dur = m->pls_ent.duration * 1000;
struct phi_queue_entry qe = {
+ .url = url.ptr,
.length_msec = dur,
};
- phi_track_conf_assign(&qe.conf, &t->conf);
- qe.conf.ifile.name = url.ptr;
- ffstr_null(&url);
- if (t->conf.ofile.name)
- qe.conf.ofile.name = ffsz_dup(t->conf.ofile.name);
- metaif->copy(&qe.conf.meta, &t->conf.meta);
- ffslice_null(&qe.conf.tracks);
-
- if (!qe.conf.meta.len) { // must not mix user-meta with transient-meta
- if (m->pls_ent.artist.len)
- metaif->set(&qe.conf.meta, FFSTR_Z("artist"), *(ffstr*)&m->pls_ent.artist, 0);
-
- if (m->pls_ent.title.len)
- metaif->set(&qe.conf.meta, FFSTR_Z("title"), *(ffstr*)&m->pls_ent.title, 0);
- qe.conf.meta_transient = 1;
- }
+
+ if (m->pls_ent.artist.len)
+ metaif->set(&qe.meta, FFSTR_Z("artist"), *(ffstr*)&m->pls_ent.artist, 0);
+
+ if (m->pls_ent.title.len)
+ metaif->set(&qe.meta, FFSTR_Z("title"), *(ffstr*)&m->pls_ent.title, 0);
m->qu_cur = queue->insert(m->qu_cur, &qe);
+ ffstr_free(&url);
if (!m->m3u_removed) {
m->m3u_removed = 1;
diff --git a/src/list/m3u-write.h b/src/list/m3u-write.h
index 7fe474b..c2efad4 100644
--- a/src/list/m3u-write.h
+++ b/src/list/m3u-write.h
@@ -40,11 +40,11 @@ static int m3uw_process(void *ctx, phi_track *t)
while (NULL != (qe = queue->at(m->q, m->pos++))) {
m3uwrite_entry m3e = {
- .url = FFSTR_Z(qe->conf.ifile.name),
+ .url = FFSTR_Z(qe->url),
.duration_sec = (qe->length_msec) ? (int)qe->length_msec / 1000 : -1,
};
- metaif->find(&qe->conf.meta, FFSTR_Z("artist"), &m3e.artist, 0);
- metaif->find(&qe->conf.meta, FFSTR_Z("title"), &m3e.title, 0);
+ metaif->find(&qe->meta, FFSTR_Z("artist"), &m3e.artist, 0);
+ metaif->find(&qe->meta, FFSTR_Z("title"), &m3e.title, 0);
m3uwrite_process(&m->m3, &m3e);
}
diff --git a/src/list/pls-read.c b/src/list/pls-read.c
index 715b5f8..b855a84 100644
--- a/src/list/pls-read.c
+++ b/src/list/pls-read.c
@@ -46,22 +46,16 @@ static int pls_add(struct pls_r *p, phi_track *t)
return 1;
struct phi_queue_entry qe = {
+ .url = url.ptr,
.length_msec = (p->pls_ent.duration != -1) ? p->pls_ent.duration * 1000 : 0,
};
- phi_track_conf_assign(&qe.conf, &t->conf);
- qe.conf.ifile.name = url.ptr;
- ffstr_null(&url);
- if (t->conf.ofile.name)
- qe.conf.ofile.name = ffsz_dup(t->conf.ofile.name);
- metaif->copy(&qe.conf.meta, &t->conf.meta);
- ffslice_null(&qe.conf.tracks);
-
- if (!qe.conf.meta.len && p->pls_ent.title.len) {
- metaif->set(&qe.conf.meta, FFSTR_Z("title"), *(ffstr*)&p->pls_ent.title, 0);
- qe.conf.meta_transient = 1;
+
+ if (p->pls_ent.title.len) {
+ metaif->set(&qe.meta, FFSTR_Z("title"), *(ffstr*)&p->pls_ent.title, 0);
}
p->qu_cur = queue->insert(p->qu_cur, &qe);
+ ffstr_free(&url);
if (!p->removed) {
p->removed = 1;
diff --git a/src/phiola.h b/src/phiola.h
index 8c517b8..775a251 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -29,6 +29,7 @@ struct _phi_fftask { size_t a[4]; };
typedef struct _phi_fftask phi_task;
struct _phi_fftimerqueue_node { size_t a[8]; };
typedef struct _phi_fftimerqueue_node phi_timer;
+typedef struct { uint len, cap; void *ptr; } phi_meta;
enum PHI_LOG {
PHI_LOG_ERR,
@@ -267,12 +268,10 @@ struct phi_track_conf {
ffslice tracks; // uint[]
uint split_msec;
- uint64 seek_msec;
- uint64 until_msec;
- uint64 seek_cdframes;
- uint64 until_cdframes;
+ uint64 seek_msec, until_msec;
+ uint seek_cdframes, until_cdframes;
- ffvec meta; // char*[]
+ phi_meta meta;
const char* tee;
const char* tee_output;
@@ -334,7 +333,6 @@ struct phi_track_conf {
uint info_only :1;
uint print_tags :1;
uint stream_copy :1;
- uint meta_transient :1;
uint cross_worker_assign :1;
};
@@ -386,23 +384,27 @@ struct phi_meta_if {
/** Set meta data.
flags: enum PHI_META_SET */
- void (*set)(ffvec *meta, ffstr name, ffstr val, uint flags);
+ void (*set)(phi_meta *meta, ffstr name, ffstr val, uint flags);
- void (*copy)(ffvec *dst, const ffvec *src);
+ void (*copy)(phi_meta *dst, const phi_meta *src, uint flags);
/**
Return 0 on success */
- int (*find)(const ffvec *meta, ffstr name, ffstr *val, uint flags);
+ int (*find)(const phi_meta *meta, ffstr name, ffstr *val, uint flags);
/**
idx: must be initialized to 0
flags: enum PHI_META_LIST
Return 0 on complete */
- int (*list)(const ffvec *meta, uint *idx, ffstr *name, ffstr *val, uint flags);
+ int (*list)(const phi_meta *meta, uint *idx, ffstr *name, ffstr *val, uint flags);
- void (*destroy)(ffvec *meta);
+ void (*destroy)(phi_meta *meta);
};
+static inline void phi_meta_null(phi_meta *m) {
+ m->ptr = NULL;
+ m->len = m->cap = 0;
+}
enum PHI_ADEV_F {
PHI_ADEV_PLAYBACK,
@@ -435,6 +437,7 @@ struct phi_queue_conf {
const char *ui_module;
const phi_filter *ui_module_if;
};
+ struct phi_track_conf tconf;
fftime last_mod_time;
uint conversion :1;
uint analyze :1;
@@ -445,9 +448,12 @@ struct phi_queue_conf {
};
struct phi_queue_entry {
- struct phi_track_conf conf;
- uint length_msec;
- uint lock; // For synchronizing access to `conf.meta`
+ char* url;
+ phi_meta meta;
+ uint length_msec;
+ uint seek_cdframes, until_cdframes;
+ uint lock; // For synchronizing access to `meta`
+ uint meta_priority :1; // Supplied `meta` has higher priority than meta from input file (e.g. for .cue track)
};
enum PHI_Q_SORT {
@@ -461,6 +467,11 @@ enum PHI_Q_REMOVE {
PHI_Q_RM_NONEXIST = 1,
};
+enum PHI_QUEUE_FILTER {
+ PHI_QF_FILENAME = 1,
+ PHI_QF_META = 2,
+};
+
typedef struct phi_queue* phi_queue_id;
typedef struct phi_queue_if phi_queue_if;
struct phi_queue_if {
@@ -488,7 +499,8 @@ struct phi_queue_if {
int (*add)(phi_queue_id q, struct phi_queue_entry *qe);
int (*count)(phi_queue_id q);
- /** Create a new virtual queue with the items matching a filter */
+ /** Create a new virtual queue with the items matching a filter
+ flags: enum PHI_QUEUE_FILTER */
phi_queue_id (*filter)(phi_queue_id q, ffstr filter, uint flags);
int (*play)(phi_queue_id q, void *e);
@@ -555,7 +567,7 @@ struct phi_remote_cl_if {
struct phi_tag_conf {
const char *filename;
- ffvec meta; // ffstr[]
+ ffslice meta; // ffstr[]
uint clear :1;
uint preserve_date :1;
uint no_expand :1;
diff --git a/src/remote-ctl.c b/src/remote-ctl.c
index 1cee698..c8982d4 100644
--- a/src/remote-ctl.c
+++ b/src/remote-ctl.c
@@ -26,9 +26,10 @@ static struct remote_ctl *g;
static int cmd_start(void *o, ffstr s)
{
struct phi_queue_entry qe = {
- .conf.ifile.name = ffsz_dupstr(&s),
+ .url = ffsz_dupstr(&s),
};
int i = g->queue->add(NULL, &qe);
+ ffmem_free(qe.url);
g->queue->play(NULL, g->queue->at(NULL, i));
return 0;
}
diff --git a/src/track.h b/src/track.h
index 843b7b9..7117113 100644
--- a/src/track.h
+++ b/src/track.h
@@ -156,7 +156,7 @@ struct phi_track {
fftime mtime; // Modification date/time
} input;
- ffvec meta;
+ phi_meta meta;
void *qent;
void *udata;
uint icy_meta_interval; // Upon receiving HTTP response, 'http' filter sets ICY meta interval for 'icy' filter
@@ -280,12 +280,3 @@ static inline void phi_track_free(phi_track *t, void *ptr)
}
ffmem_free(ptr);
}
-
-static inline void phi_track_conf_assign(struct phi_track_conf *dst, const struct phi_track_conf *src)
-{
- *dst = *src;
- dst->ifile.name = NULL;
- dst->ofile.name = NULL;
- ffvec_null(&dst->meta);
- ffslice_null(&dst->tracks);
-}
diff --git a/src/util/util.h b/src/util/util.h
index 6fdbb32..41f807a 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -198,6 +198,30 @@ static inline void phi_af_update(struct phi_af *dst, const struct phi_af *src)
}
+#include
+
+static inline void qe_meta_update(struct phi_queue_entry *qe, phi_meta *src, const phi_meta_if *metaif)
+{
+ fflock_lock((fflock*)&qe->lock); // UI thread may read or write `conf.meta` at this moment
+ phi_meta meta_old = qe->meta;
+ qe->meta = *src;
+ fflock_unlock((fflock*)&qe->lock);
+
+ metaif->destroy(&meta_old);
+ phi_meta_null(src);
+}
+
+static inline void qe_copy(struct phi_queue_entry *dst, const struct phi_queue_entry *src, const phi_meta_if *metaif)
+{
+ dst->url = src->url;
+ dst->length_msec = src->length_msec;
+ dst->seek_cdframes = src->seek_cdframes;
+ dst->until_cdframes = src->until_cdframes;
+ dst->meta_priority = src->meta_priority;
+ metaif->copy(&dst->meta, &src->meta, 0);
+}
+
+
#include
static inline int file_copydata(fffd src, ffuint64 offsrc, fffd dst, ffuint64 offdst, ffuint64 size)
From 9097c3536c4e13b0f98539810731677891ede430 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 4 Jan 2025 10:04:59 +0300
Subject: [PATCH 50/59] minor
---
README.md | 24 ++++++++---
alib3/README.md | 8 ++++
.../java/com/github/stsaz/phiola/Core.java | 6 ---
doc/arch/arch.md | 43 +++++++++++++++++++
src/acodec/mpeg-dec.h | 2 +-
src/afilter/dynanorm.c | 4 +-
src/afilter/gain.c | 16 +++----
src/afilter/pcm_convert.h | 8 ++++
src/afilter/split.h | 1 +
src/format/detector.h | 20 ++++++---
src/format/mp3-read.h | 2 +-
src/format/ogg-read.h | 2 +
src/format/opus-meta.h | 4 +-
src/format/tag.c | 21 +++++++--
src/format/vorbis-meta.h | 4 +-
src/gui/actions.h | 2 +-
src/gui/gui.c | 42 ++++++++++--------
src/gui/mod.h | 2 +-
src/gui/res/phiola.desktop | 2 +-
src/gui/track-playback.h | 2 +-
src/phiola.h | 8 ++++
src/remote-ctl.c | 4 +-
src/track.h | 2 +-
src/tui/play.h | 2 +-
src/tui/tui.c | 2 +-
src/util/util.hpp | 2 +-
...kworm-arm64.sh => xbuild-debianBW-arm64.sh | 4 +-
xbuild-debianBW.sh | 2 +-
28 files changed, 169 insertions(+), 72 deletions(-)
rename xbuild-debianbookworm-arm64.sh => xbuild-debianBW-arm64.sh (94%)
diff --git a/README.md b/README.md
index ab6a0cc..bfd3e53 100644
--- a/README.md
+++ b/README.md
@@ -160,7 +160,7 @@ phiola record -aformat int16 -rate 48000 -channels 2 -o audio.flac
# * WASAPI:
phiola record -loopback -o audio.flac
# * PulseAudio:
-phiola record -dev `phiola dev list -f Monitor -n` -o audio.flac
+phiola record -dev $(phiola dev list -f Monitor -n) -o audio.flac
# Start recording in background, then stop recording from another process:
# Step 1: record while listening for system-wide commands
@@ -231,16 +231,20 @@ phiola info -peaks file.mp3
phiola info -loudness file.mp3
```
-Other use-cases:
+Edit file tags:
```sh
-# List all available audio playback and recording devices
-phiola device list
-
# Replace/add MP3 tags in-place
# WARNING: please test first before actually using on real files (or at least make backups)!
phiola tag -m "artist=Great Artist" -m "title=Cool Song" file.mp3
+# Remove all existing tags; add new tags
+phiola tag -clear -m "artist=Great Artist" -m "title=Cool Song" file.mp3
+```
+
+Create/edit playlist files:
+
+```sh
# Create a playlist from all .mp3 files in the directory
phiola list create "My Music" -include "*.mp3" -o my-music.m3u
@@ -251,6 +255,13 @@ phiola list sort *.m3u
phiola list heal Music/playlist.m3u
```
+Other use-cases:
+
+```sh
+# List all available audio playback and recording devices
+phiola device list
+```
+
Currently supported commands:
| Command | Description |
@@ -263,7 +274,7 @@ Currently supported commands:
| [play](src/exe/play.h) | Play audio |
| [record](src/exe/record.h) | Record audio |
| [remote](src/exe/remote.h) | Send remote command |
-| [tag](src/exe/tag.h) | Edit .mp3 file tags |
+| [tag](src/exe/tag.h) | Edit file tags |
> For the details on each command you can click on the links above or execute `phiola COMMAND -h` on your PC.
@@ -339,7 +350,6 @@ phiola uses modified versions of these third party libraries:
libMAC,
[libmpg123](https://mpg123.de),
libmpc,
-[libogg](https://github.com/xiph/ogg),
[libopus](https://github.com/xiph/opus),
[libvorbis](https://github.com/xiph/vorbis),
libwavpack,
diff --git a/alib3/README.md b/alib3/README.md
index 14265c8..b16e0c1 100644
--- a/alib3/README.md
+++ b/alib3/README.md
@@ -11,6 +11,8 @@ In order to be used by phiola all these libraries must be built in a specific wa
## Libs
+Codecs:
+
* alac-rev2
* fdk-aac-0.1.6
* flac-1.4.3
@@ -22,6 +24,12 @@ In order to be used by phiola all these libraries must be built in a specific wa
* vorbis-1.3.7
* wavpack-4.75.0
+Filters:
+
+* DynamicAudioNormalizer-2.10
+* libebur128-1.2.6
+* soxr-0.1.3
+
## Requirements
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Core.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Core.java
index a3349b1..614ef94 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Core.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Core.java
@@ -103,9 +103,6 @@ GUI gui() {
return gui;
}
- /**
- * Save configuration
- */
void saveconf() {
String fn = work_dir + "/" + CONF_FN;
StringBuilder sb = new StringBuilder();
@@ -119,9 +116,6 @@ void saveconf() {
dbglog(TAG, "saveconf ok: %s", fn);
}
- /**
- * Load configuration
- */
private void loadconf() {
String fn = work_dir + "/" + CONF_FN;
Conf.Entry[] kv = conf.confRead(fn);
diff --git a/doc/arch/arch.md b/doc/arch/arch.md
index 237ad5e..0eaf1d5 100644
--- a/doc/arch/arch.md
+++ b/doc/arch/arch.md
@@ -23,6 +23,49 @@ Example of a track chain for audio conversion:
3. `aconv` applies the conversion format specified by output filter. The resulting conversion will be `float32/stereo => int16/mono`. Adds `conv` filter to the chain.
4. `conv` performs the conversion and supplies data to output filter.
+Playback with filtering (single converter):
+
+```
+ oa.f:
+ a.f: oa.f: oa.cf:
+ i24 i24 f64
+ 48k 48k
+ ni ni i
+dec -> aconv -> af -> adev.play
+
+ oa.cf:
+ i16
+ 96k
+ * <------- *
+
+ oa.f:
+ i16
+ 96k
+ i
+ * -> *
+```
+
+Playback with filtering (double converter):
+
+```
+ a.f: oa.f:
+ i24 f64
+ 48k
+ ni i
+dec -> aconv-f -> af -> aconv -> adev.play
+
+ oa.cf:
+ i16
+ 96k
+ * <------- * <-- *
+
+ oa.f: oa.f:
+ f64 i16
+ 48k 96k
+ i
+ * -> * -> * -> *
+```
+
## Seeking
diff --git a/src/acodec/mpeg-dec.h b/src/acodec/mpeg-dec.h
index 3fb7202..5a9c142 100644
--- a/src/acodec/mpeg-dec.h
+++ b/src/acodec/mpeg-dec.h
@@ -34,7 +34,7 @@ static void* mpeg_dec_open(phi_track *t)
t->audio.format.format = PHI_PCM_FLOAT32;
t->audio.format.interleaved = 1;
- t->audio.decoder = "MPEG1-L3";
+ t->audio.decoder = "MP3";
m->fr_size = phi_af_size(&t->audio.format);
t->data_type = "pcm";
return m;
diff --git a/src/afilter/dynanorm.c b/src/afilter/dynanorm.c
index b65df3c..f99fd3a 100644
--- a/src/afilter/dynanorm.c
+++ b/src/afilter/dynanorm.c
@@ -101,7 +101,7 @@ static int danorm_f_process(void *ctx, phi_track *t)
t->aconv.out.interleaved = 0;
if (!t->conf.oaudio.format.format)
t->conf.oaudio.format.format = t->audio.format.format;
- t->audio.format = t->aconv.out;
+ t->oaudio.format = t->aconv.out;
t->data_out = t->data_in;
c->state = 1;
return PHI_BACK;
@@ -121,7 +121,7 @@ static int danorm_f_process(void *ctx, phi_track *t)
return PHI_ERR;
c->buf.len = cap;
arrp_setbuf((void**)c->buf.ptr, ch, c->buf.ptr + sizeof(void*) * ch, cap * sizeof(double));
- c->fmt = t->audio.format;
+ c->fmt = t->oaudio.format;
c->state = 2;
break;
}
diff --git a/src/afilter/gain.c b/src/afilter/gain.c
index c7c75aa..54c9086 100644
--- a/src/afilter/gain.c
+++ b/src/afilter/gain.c
@@ -8,18 +8,18 @@ extern const phi_core *core;
#define dbglog(t, ...) phi_dbglog(core, NULL, t, __VA_ARGS__)
struct gain {
- struct phi_af pcm;
- uint samp_size;
+ struct phi_af af;
+ uint sample_size;
double db, gain;
};
static void* gain_open(phi_track *t)
{
struct gain *c = phi_track_allocT(t, struct gain);
- c->pcm = t->audio.format;
- c->samp_size = pcm_size1(&c->pcm);
- t->audio.gain_db = (t->audio.gain_db) ? t->audio.gain_db : t->conf.afilter.gain_db;
- c->db = -t->audio.gain_db;
+ c->af = (t->oaudio.format.format) ? t->oaudio.format : t->audio.format;
+ c->sample_size = pcm_size1(&c->af);
+ t->oaudio.gain_db = (t->oaudio.gain_db) ? t->oaudio.gain_db : t->conf.afilter.gain_db;
+ c->db = -t->oaudio.gain_db;
return c;
}
@@ -32,14 +32,14 @@ static void gain_close(void *ctx, phi_track *t)
static int gain_process(void *ctx, phi_track *t)
{
struct gain *c = ctx;
- double db = t->audio.gain_db;
+ double db = t->oaudio.gain_db;
if (db != 0) {
if (db != c->db) {
c->db = db;
c->gain = db_gain(db);
dbglog(t, "gain: %.02FdB %.02F", db, c->gain);
}
- pcm_gain(&c->pcm, c->gain, t->data_in.ptr, (void*)t->data_in.ptr, t->data_in.len / c->samp_size);
+ pcm_gain(&c->af, c->gain, t->data_in.ptr, (void*)t->data_in.ptr, t->data_in.len / c->sample_size);
}
t->data_out = t->data_in;
diff --git a/src/afilter/pcm_convert.h b/src/afilter/pcm_convert.h
index afa1ce0..9c5a440 100644
--- a/src/afilter/pcm_convert.h
+++ b/src/afilter/pcm_convert.h
@@ -432,6 +432,14 @@ static inline int pcm_convert(const struct phi_af *outpcm, void *out, const stru
}
break;
+ case X(PHI_PCM_U8, PHI_PCM_FLOAT64):
+ for (ich = 0; ich != nch; ich++) {
+ for (i = 0; i != samples; i++) {
+ to.pd[ich][i * ostep] = pcm_s8_flt(((int)from.pub[ich][i * istep]) - 127);
+ }
+ }
+ break;
+
// int8
case X(PHI_PCM_8, PHI_PCM_8):
for (ich = 0; ich != nch; ich++) {
diff --git a/src/afilter/split.h b/src/afilter/split.h
index 0db0f20..0101046 100644
--- a/src/afilter/split.h
+++ b/src/afilter/split.h
@@ -65,6 +65,7 @@ static void split_brg_close(void *f, phi_track *t)
g->state = S_NONE;
split_brg_unref(g);
meta_if->destroy(&t->meta);
+ ffmem_free(t->conf.ofile.name); t->conf.ofile.name = NULL;
}
static int split_brg_process(void *f, phi_track *t)
diff --git a/src/format/detector.h b/src/format/detector.h
index 67024ee..bd7bdaa 100644
--- a/src/format/detector.h
+++ b/src/format/detector.h
@@ -2,9 +2,12 @@
2020, Simon Zolin */
#include
+#include
+#include
enum FILE_FORMAT {
FILE_UNK,
+ FILE_AAC,
FILE_AVI,
FILE_CAF,
FILE_FLAC,
@@ -21,6 +24,7 @@ enum FILE_FORMAT {
};
const char file_ext[][5] = {
+ "aac",
"avi",
"caf",
"flac",
@@ -113,6 +117,12 @@ int file_format_detect(const void *data, ffsize len)
return FILE_M3U;
}
+ if (len >= 7) {
+ struct aacadts_hdr h;
+ if (!aacadts_parse(&h, d))
+ return FILE_AAC;
+ }
+
if (len >= 5) {
// byte sync[4]; // "OggS"
// byte ver; // 0x0
@@ -145,12 +155,8 @@ int file_format_detect(const void *data, ffsize len)
return FILE_MKV;
}
- if (len >= 2) {
- // byte sync1; // 0xff
- // byte sync2_ver_layer_noprotect; // [3]=0x7 [2]=0x3 [2]=0x1 [1]
- if (d[0] == 0xff && (d[1] & 0xe0) == 0xe0
- && (d[1] & 0x18) == 0x18
- && (d[1] & 0x06) == 2)
+ if (len >= 4) {
+ if (mpeg1_valid(d))
return FILE_MP3;
}
@@ -193,7 +199,7 @@ static void* fdetcr_open(phi_track *t)
if (t->conf.seek_cdframes) {
if (t->audio.seek == -1)
t->audio.seek = 0;
- t->audio.seek += t->conf.seek_cdframes * 1000 / 75;
+ t->audio.seek += (uint64)t->conf.seek_cdframes * 1000 / 75;
t->audio.seek_req = 1;
}
diff --git a/src/format/mp3-read.h b/src/format/mp3-read.h
index e1f203a..b81c5d4 100644
--- a/src/format/mp3-read.h
+++ b/src/format/mp3-read.h
@@ -67,7 +67,7 @@ static void mp3_info(struct mp3_r *m, phi_track *t, const struct mpeg1read_info
t->audio.format = f;
t->audio.bitrate = info->bitrate;
t->audio.total = info->total_samples;
- t->audio.decoder = "MPEG1-L3";
+ t->audio.decoder = "MP3";
t->data_type = "mpeg";
t->audio.start_delay = info->delay;
t->audio.end_padding = info->padding;
diff --git a/src/format/ogg-read.h b/src/format/ogg-read.h
index 05cae3e..24ba117 100644
--- a/src/format/ogg-read.h
+++ b/src/format/ogg-read.h
@@ -152,6 +152,8 @@ static int ogg_read(void *ctx, phi_track *t)
}
case OGGREAD_DATA:
+ if (t->conf.info_only)
+ return PHI_LASTOUT;
goto data;
case OGGREAD_DONE:
diff --git a/src/format/opus-meta.h b/src/format/opus-meta.h
index d103462..cdd462d 100644
--- a/src/format/opus-meta.h
+++ b/src/format/opus-meta.h
@@ -83,9 +83,7 @@ static int opusmeta_read(void *ctx, phi_track *t)
ffvec_free(&o->hdr);
o->state = 3;
t->data_out = o->tags;
- return (t->conf.info_only) ? PHI_LASTOUT
- : (t->chain_flags & PHI_FFIRST) ? PHI_DONE
- : PHI_OK;
+ return (t->conf.info_only || (t->chain_flags & PHI_FFIRST)) ? PHI_DONE : PHI_OK;
case 3:
if (t->audio.ogg_reset) {
diff --git a/src/format/tag.c b/src/format/tag.c
index f776b21..7639b07 100644
--- a/src/format/tag.c
+++ b/src/format/tag.c
@@ -40,6 +40,7 @@ struct tag_edit {
fftime mtime;
char* fnw;
int done;
+ uint written;
};
static void tag_edit_close(struct tag_edit *t)
@@ -56,7 +57,8 @@ static void tag_edit_close(struct tag_edit *t)
}
}
if (t->done) {
- infolog("saved file %s", t->conf.filename);
+ infolog("%uKB written to file %s"
+ , t->written / 1024, t->conf.filename);
}
ffmem_free(t->fnw);
ffvec_free(&t->buf);
@@ -76,8 +78,11 @@ static int user_meta_split(ffstr kv, ffstr *k, ffstr *v)
return tag;
}
-static int user_meta_find(const ffvec *m, uint tag, ffstr *k, ffstr *v)
+static int user_meta_find(const ffslice *m, uint tag, ffstr *k, ffstr *v)
{
+ if (tag == MMTAG_UNKNOWN)
+ return 0;
+
ffstr *kv;
FFSLICE_WALK(m, kv) {
if (kv->len) {
@@ -124,6 +129,8 @@ static int tag_file_write(struct tag_edit *t, ffstr head, ffstr tags, uint64 tai
return -1;
}
dbglog("written %U bytes @%U", src_tail_size, woff);
+
+ t->written += head.len + tags.len + src_tail_size;
return 0;
}
@@ -214,6 +221,10 @@ static int tag_mp3_id3v2(struct tag_edit *t)
tag = user_meta_split(*kv, &k, &v);
if (tag < 0)
goto end;
+ if (tag == 0) {
+ warnlog("skipping unknown tag: %S", &k);
+ continue;
+ }
if (tags_added[tag])
continue; // already added
dbglog("id3v2: writing %S = %S", &k, &v);
@@ -240,6 +251,7 @@ static int tag_mp3_id3v2(struct tag_edit *t)
syserrlog("file write");
goto end;
}
+ t->written += r;
} else {
if (t->conf.no_expand) {
@@ -321,6 +333,7 @@ static int tag_mp3_id3v1(struct tag_edit *t)
syserrlog("file write");
return 'e';
}
+ t->written += r;
return 0;
}
@@ -411,7 +424,7 @@ static int tag_vorbis(struct tag_edit *t)
{
int r, rc = 'e', format;
uint tags_page_off, tags_page_num, vtags_len;
- ffstr vtag, vorbis_codebook = {}, page, *kv, k, v, k2, v2;
+ ffstr vtag = {}, vorbis_codebook = {}, page, *kv, k, v, k2, v2;
oggwrite ogw = {};
oggread ogg = {};
vorbistagwrite vtw = {
@@ -441,6 +454,7 @@ static int tag_vorbis(struct tag_edit *t)
vorbistagwrite_create(&vtw);
if (!vorbistagwrite_add(&vtw, MMTAG_VENDOR, v))
dbglog("vorbistag: written vendor = %S", &v);
+ tags_added[MMTAG_VENDOR] = 1;
if (!t->conf.clear) {
// Replace tags, copy existing tags preserving the original order
@@ -531,6 +545,7 @@ static int tag_vorbis(struct tag_edit *t)
syserrlog("file write");
goto end;
}
+ t->written += r;
dbglog("written %L bytes @%U", wpage.len, tags_page_off);
} else {
diff --git a/src/format/vorbis-meta.h b/src/format/vorbis-meta.h
index 1375160..5d3a567 100644
--- a/src/format/vorbis-meta.h
+++ b/src/format/vorbis-meta.h
@@ -89,10 +89,10 @@ static int vorbismeta_read(void *ctx, phi_track *t)
}
t->data_out = v->tags;
- return (t->conf.info_only) ? PHI_LASTOUT : PHI_DONE;
+ return PHI_DONE;
}
const phi_filter phi_vorbismeta_read = {
vorbismeta_open, vorbismeta_close, vorbismeta_read,
- "vorbis-meta-read"
+ "vorbismeta-read"
};
diff --git a/src/gui/actions.h b/src/gui/actions.h
index f7a5517..ff18bd7 100644
--- a/src/gui/actions.h
+++ b/src/gui/actions.h
@@ -1,4 +1,5 @@
+ X(A_PLAY),
// File
X(A_FILE_INFO),
X(A_INFO_ADD_ARTIST),
@@ -35,7 +36,6 @@
X(A_LIST_AUTOSELECT),
// Playback
- X(A_PLAY),
X(A_PLAYPAUSE),
X(A_STOP),
X(A_NEXT),
diff --git a/src/gui/gui.c b/src/gui/gui.c
index 33cdff8..208a525 100644
--- a/src/gui/gui.c
+++ b/src/gui/gui.c
@@ -168,28 +168,32 @@ extern const ffui_ldr_ctl
static void* gui_getctl(void *udata, const ffstr *name)
{
+ #define _(m) FFUI_LDR_CTL(struct gui, m)
+ #define _w(w, ctls) FFUI_LDR_CTL3_PTR(struct gui, w, ctls)
static const ffui_ldr_ctl top_ctls[] = {
- FFUI_LDR_CTL(struct gui, mfile),
- FFUI_LDR_CTL(struct gui, mlist),
- FFUI_LDR_CTL(struct gui, mplay),
- FFUI_LDR_CTL(struct gui, mrecord),
- FFUI_LDR_CTL(struct gui, mconvert),
- FFUI_LDR_CTL(struct gui, mhelp),
- FFUI_LDR_CTL(struct gui, mpopup),
- FFUI_LDR_CTL(struct gui, dlg),
- FFUI_LDR_CTL(struct gui, mminfo_addtag),
- FFUI_LDR_CTL3_PTR(struct gui, wmain, wmain_ctls),
- FFUI_LDR_CTL3_PTR(struct gui, winfo, winfo_ctls),
- FFUI_LDR_CTL3_PTR(struct gui, wsettings, wsettings_ctls),
- FFUI_LDR_CTL3_PTR(struct gui, wgoto, wgoto_ctls),
- FFUI_LDR_CTL3_PTR(struct gui, wlistfilter, wlistfilter_ctls),
- FFUI_LDR_CTL3_PTR(struct gui, wlistadd, wlistadd_ctls),
- FFUI_LDR_CTL3_PTR(struct gui, wrecord, wrecord_ctls),
- FFUI_LDR_CTL3_PTR(struct gui, wconvert, wconvert_ctls),
- FFUI_LDR_CTL3_PTR(struct gui, wabout, wabout_ctls),
- FFUI_LDR_CTL3_PTR(struct gui, wlog, wlog_ctls),
+ _(mfile),
+ _(mlist),
+ _(mplay),
+ _(mrecord),
+ _(mconvert),
+ _(mhelp),
+ _(mpopup),
+ _(dlg),
+ _(mminfo_addtag),
+ _w(wmain, wmain_ctls),
+ _w(winfo, winfo_ctls),
+ _w(wsettings, wsettings_ctls),
+ _w(wgoto, wgoto_ctls),
+ _w(wlistfilter, wlistfilter_ctls),
+ _w(wlistadd, wlistadd_ctls),
+ _w(wrecord, wrecord_ctls),
+ _w(wconvert, wconvert_ctls),
+ _w(wabout, wabout_ctls),
+ _w(wlog, wlog_ctls),
FFUI_LDR_CTL_END
};
+ #undef _
+ #undef _w
struct gui *g = udata;
return ffui_ldr_findctl(top_ctls, g, name);
diff --git a/src/gui/mod.h b/src/gui/mod.h
index dcaca38..3b60b78 100644
--- a/src/gui/mod.h
+++ b/src/gui/mod.h
@@ -164,7 +164,7 @@ FF_EXTERN struct gui_data *gd;
core->conf.log(core->conf.log_obj, PHI_LOG_ERR, "gui", NULL, __VA_ARGS__)
#define warnlog(...) \
- core->conf.log(core->conf.log_obj, PHI_LOG_ERR, "gui", NULL, __VA_ARGS__)
+ core->conf.log(core->conf.log_obj, PHI_LOG_WARN, "gui", NULL, __VA_ARGS__)
#define dbglog(...) \
do { \
diff --git a/src/gui/res/phiola.desktop b/src/gui/res/phiola.desktop
index 5eca77a..c11cabd 100644
--- a/src/gui/res/phiola.desktop
+++ b/src/gui/res/phiola.desktop
@@ -1,6 +1,6 @@
[Desktop Entry]
Comment=Audio player/recorder/converter
-Exec=phiola gui
+Exec=phiola gui %F
GenericName=Audio player/recorder/converter
Icon=~/bin/phiola-2/mod/gui/phiola.ico
MimeType=audio/x-wav;audio/x-opus+ogg;audio/ogg;audio/mpeg;audio/mp4;audio/flac;
diff --git a/src/gui/track-playback.h b/src/gui/track-playback.h
index bd66e48..ba69b2f 100644
--- a/src/gui/track-playback.h
+++ b/src/gui/track-playback.h
@@ -24,7 +24,7 @@ struct gtrk {
/** Set volume */
void gtrk_vol_apply(struct gtrk *gt, double db)
{
- gt->t->audio.gain_db = db;
+ gt->t->oaudio.gain_db = db;
}
/** Set/reset 'pause' flag */
diff --git a/src/phiola.h b/src/phiola.h
index 775a251..d3059fe 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -587,9 +587,17 @@ struct phi_ui_conf {
phi_log_ctl log_ctl;
};
+enum PHI_UI_SEEK {
+ PHI_UI_SEEK_FWD,
+ PHI_UI_SEEK_BACK,
+};
+
typedef struct phi_ui_if phi_ui_if;
struct phi_ui_if {
void (*conf)(struct phi_ui_conf *c);
void (*log)(void *udata, ffstr s);
+
+ /**
+ flags: enum PHI_UI_SEEK */
void (*seek)(uint val, uint flags);
};
diff --git a/src/remote-ctl.c b/src/remote-ctl.c
index c8982d4..18f3ab1 100644
--- a/src/remote-ctl.c
+++ b/src/remote-ctl.c
@@ -67,9 +67,9 @@ static int cmd_quit() {
static int cmd_seek(void *o, ffstr s) {
uint f;
if (ffstr_eqz(&s, "forward"))
- f = 0;
+ f = PHI_UI_SEEK_FWD;
else if (ffstr_eqz(&s, "back"))
- f = 1;
+ f = PHI_UI_SEEK_BACK;
else
return 0;
diff --git a/src/track.h b/src/track.h
index 7117113..271a149 100644
--- a/src/track.h
+++ b/src/track.h
@@ -169,7 +169,6 @@ struct phi_track {
uint seek_req :1; // New seek request is received (UI -> fmt.read)
uint ogg_reset :1; // ogg.read -> opus.meta
uint bitrate;
- double gain_db; // Audio gain/attenuation
double maxpeak_db;
const char *decoder;
@@ -209,6 +208,7 @@ struct phi_track {
struct {
struct phi_af format;
struct phi_af conv_format;
+ double gain_db;
double loudness, loudness_momentary;
// ui -> audio.play
diff --git a/src/tui/play.h b/src/tui/play.h
index 8bf17cf..4e6f4c7 100644
--- a/src/tui/play.h
+++ b/src/tui/play.h
@@ -145,7 +145,7 @@ static double tui_setvol(tui_track *u, uint vol)
db = vol2db(vol, VOL_LO);
else
db = vol2db_inc(vol - 100, VOL_MAX - 100, VOL_HI);
- u->t->audio.gain_db = db;
+ u->t->oaudio.gain_db = db;
return db;
}
diff --git a/src/tui/tui.c b/src/tui/tui.c
index f32ea38..b295af3 100644
--- a/src/tui/tui.c
+++ b/src/tui/tui.c
@@ -485,7 +485,7 @@ static void tui_play_seek(uint val, uint flags)
if (!mod->curtrk) return;
uint cmd = CMD_SEEKRIGHT;
- if (flags & 1) cmd = CMD_SEEKLEFT;
+ if (flags & PHI_UI_SEEK_BACK) cmd = CMD_SEEKLEFT;
tuiplay_seek(mod->curtrk, cmd, (void*)0);
}
diff --git a/src/util/util.hpp b/src/util/util.hpp
index d237294..e8a7e4b 100644
--- a/src/util/util.hpp
+++ b/src/util/util.hpp
@@ -129,7 +129,7 @@ struct xxvec : ffvec {
va_end(va);
return *this;
}
- template T* at(ffsize i) { return ffslice_itemT(this, i, T); }
+ template T* at(ffsize i) { FF_ASSERT(i < len); return ffslice_itemT(this, i, T); }
template T* alloc(ffsize n) { return ffvec_allocT(this, n, T); }
template T* push() { return ffvec_pushT(this, T); }
template T* push_z() { return ffvec_zpushT(this, T); }
diff --git a/xbuild-debianbookworm-arm64.sh b/xbuild-debianBW-arm64.sh
similarity index 94%
rename from xbuild-debianbookworm-arm64.sh
rename to xbuild-debianBW-arm64.sh
index e0ca300..e0805fe 100644
--- a/xbuild-debianbookworm-arm64.sh
+++ b/xbuild-debianBW-arm64.sh
@@ -2,8 +2,8 @@
# phiola: cross-build on Linux for Debian-bookworm ARM64
-IMAGE_NAME=phiola-debianbookworm-arm64-builder
-CONTAINER_NAME=phiola_debianbookworm_arm64_build
+IMAGE_NAME=phiola-debianbw-arm64-builder
+CONTAINER_NAME=phiola_debianBW_arm64_build
ARGS=${@@Q}
set -xe
diff --git a/xbuild-debianBW.sh b/xbuild-debianBW.sh
index b7e69ab..1eec9ee 100644
--- a/xbuild-debianBW.sh
+++ b/xbuild-debianBW.sh
@@ -2,7 +2,7 @@
# phiola: cross-build on Linux for Debian-bookworm
-IMAGE_NAME=phiola-debianBW-builder
+IMAGE_NAME=phiola-debianbw-builder
CONTAINER_NAME=phiola_debianBW_build
ARGS=${@@Q}
From ce8c633a84b240ef88f91bbf52092790776b351b Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 4 Jan 2025 10:04:59 +0300
Subject: [PATCH 51/59] 2.3-beta4
---
android/phiola/build.gradle | 4 ++--
src/phiola.h | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/android/phiola/build.gradle b/android/phiola/build.gradle
index 83bcd85..8394b72 100644
--- a/android/phiola/build.gradle
+++ b/android/phiola/build.gradle
@@ -8,8 +8,8 @@ android {
applicationId "com.github.stsaz.phiola"
minSdkVersion 16
targetSdk 33
- versionCode 20303
- versionName '2.3-beta3'
+ versionCode 20304
+ versionName '2.3-beta4'
}
buildFeatures {
diff --git a/src/phiola.h b/src/phiola.h
index d3059fe..923e96b 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -6,13 +6,13 @@
#include
#include
-#define PHI_VERSION 20303
+#define PHI_VERSION 20304
/** Inter-module compatibility version.
It must be updated when incompatible changes are made to this file,
then all modules must be rebuilt.
The core will refuse to load modules built for any other core version. */
-#define PHI_VERSION_CORE 20303
+#define PHI_VERSION_CORE 20304
typedef long long int64;
typedef unsigned long long uint64;
From 9008346c2dea49d87899c1e2610d4a17c1a0899b Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 11 Jan 2025 11:27:01 +0300
Subject: [PATCH 52/59] + GUI: Convert: add setting `Overwrite Output File`
---
src/gui/convert.hpp | 5 +++--
src/gui/lang_en.conf | 1 +
src/gui/ui-gtk.conf | 4 ++++
src/gui/ui-winapi.conf | 4 ++++
4 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/src/gui/convert.hpp b/src/gui/convert.hpp
index 46e5657..f885504 100644
--- a/src/gui/convert.hpp
+++ b/src/gui/convert.hpp
@@ -8,7 +8,7 @@ struct gui_wconvert {
ffui_labelxx ldir, lname, lext, lfrom, luntil, ltags, laacq, lvorbisq, lopusq;
ffui_editxx edir, ename, efrom, euntil, etags, eaacq, evorbisq, eopusq;
ffui_comboboxxx cbext;
- ffui_checkboxxx cbcopy, cbkeepdate;
+ ffui_checkboxxx cbcopy, cbkeepdate, cboverwrite;
ffui_buttonxx bbrowse, bstart;
xxstr conf_dir, conf_name, conf_ext;
@@ -32,7 +32,7 @@ FF_EXTERN const ffui_ldr_ctl wconvert_ctls[] = {
_(laacq), _(eaacq),
_(lvorbisq),_(evorbisq),
_(lopusq), _(eopusq),
- _(cbkeepdate),
+ _(cbkeepdate), _(cboverwrite),
_(bstart),
FFUI_LDR_CTL_END
};
@@ -204,6 +204,7 @@ static struct phi_track_conf* conv_conf_create()
tc->opus.bitrate = c->conf_opusq;
tc->ifile.preserve_date = c->cbkeepdate.checked();
+ tc->ofile.overwrite = c->cboverwrite.checked();
tc->ofile.name = ffsz_allocfmt("%S/%S.%S", &c->conf_dir, &c->conf_name, &c->conf_ext);
return tc;
}
diff --git a/src/gui/lang_en.conf b/src/gui/lang_en.conf
index 7edd846..589d4f9 100644
--- a/src/gui/lang_en.conf
+++ b/src/gui/lang_en.conf
@@ -88,6 +88,7 @@ MMConvert "_Convert"
CVVorbisQ "[.ogg] Vorbis Quality: 0..10"
CVOpusQ "[.opus] Opus Bitrate (VBR): 6..510"
CVKeepDate "Preserve Original File Date"
+ CVOverwriteFile "Overwrite Output File"
CVStart "Begin Conversion"
MCSetStartPos "Set '_Start Position'"
MCSetEndPos "Set '_End Position'"
diff --git a/src/gui/ui-gtk.conf b/src/gui/ui-gtk.conf
index 22b3e18..f02e88b 100644
--- a/src/gui/ui-gtk.conf
+++ b/src/gui/ui-gtk.conf
@@ -535,6 +535,10 @@ window wconvert {
text $CVKeepDate
}
+ checkbox cboverwrite {
+ text $CVOverwriteFile
+ }
+
button bstart {
text $CVStart
action A_CONVERT_START
diff --git a/src/gui/ui-winapi.conf b/src/gui/ui-winapi.conf
index e6d0e2d..c25e994 100644
--- a/src/gui/ui-winapi.conf
+++ b/src/gui/ui-winapi.conf
@@ -573,6 +573,10 @@ window wconvert {
text $CVKeepDate
}
+ checkbox cboverwrite {
+ text $CVOverwriteFile
+ }
+
button bstart {
text $CVStart
action A_CONVERT_START
From 351650cdcf74a49588c1f76606802ca7a0712132 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 11 Jan 2025 11:27:01 +0300
Subject: [PATCH 53/59] + `play -rgnorm`: ReplayGain Normalizer
+ Android: `Settings->ReplayGain Normalizer`
---
.../java/com/github/stsaz/phiola/Phiola.java | 15 ++--
.../java/com/github/stsaz/phiola/Queue.java | 61 ++++++++++-----
.../github/stsaz/phiola/SettingsActivity.java | 13 +++-
.../com/github/stsaz/phiola/UtilNative.java | 3 +-
.../phiola/src/main/res/layout/settings.xml | 7 ++
.../phiola/src/main/res/values/strings.xml | 1 +
src/afilter/afilter.c | 4 +
src/afilter/gain.c | 2 +-
src/afilter/pcm.h | 19 +++++
src/afilter/rgnorm.h | 41 ++++++++++
src/afilter/split.h | 2 -
src/core/queue-entry.h | 2 +
src/exe/play.h | 4 +
src/format/mmtag.h | 1 +
src/gui/lang_en.conf | 1 +
src/gui/mod.c | 22 +++---
src/gui/mod.h | 2 +-
src/gui/settings.hpp | 9 ++-
src/gui/ui-gtk.conf | 3 +
src/gui/ui-winapi.conf | 3 +
src/jni/android-utils.h | 1 +
src/jni/phiola-jni.c | 1 +
src/jni/queue.h | 74 +++++++++++--------
src/phiola.h | 1 +
src/track.h | 1 +
25 files changed, 225 insertions(+), 68 deletions(-)
create mode 100644 src/afilter/rgnorm.h
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
index b9ac16d..d5895da 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Phiola.java
@@ -151,18 +151,21 @@ to the index within its parent (not filtered) list */
QUCOM_PLAY = 7,
QUCOM_PLAY_NEXT = 8,
QUCOM_PLAY_PREV = 9,
- QUCOM_REPEAT = 10,
- QUCOM_RANDOM = 11,
- QUCOM_REMOVE_ON_ERROR = 12,
QUCOM_CONV_CANCEL = 13,
/** Update current status of all entries.
Return 0 when conversion is complete. */
- QUCOM_CONV_UPDATE = 14,
-
- QUCOM_AUTO_NORM = 15;
+ QUCOM_CONV_UPDATE = 14;
native int quCmd(long q, int cmd, int i);
+ static final int
+ QC_REPEAT = 1,
+ QC_RANDOM = 2,
+ QC_REMOVE_ON_ERROR = 4,
+ QC_AUTO_NORM = 0x10,
+ QC_RG_NORM = 0x20;
+ native void quConf(int mask, int val);
+
native Meta quMeta(long q, int i);
/** Move all files in the list to the specified directory.
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java b/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
index ea21a7e..ea2665d 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/Queue.java
@@ -131,7 +131,8 @@ class Queue {
F_MOVE_ON_NEXT = 4,
F_RM_ON_NEXT = 8,
F_RM_ON_ERR = 0x10,
- F_AUTO_NORM = 0x20;
+ F_AUTO_NORM = 0x20,
+ F_RG_NORM = 0x40;
private int flags;
void flags_set1(int mask, boolean val) {
int i = 0;
@@ -140,17 +141,40 @@ void flags_set1(int mask, boolean val) {
flags_set(mask, i);
}
void flags_set(int mask, int val) {
- if ((mask & F_REPEAT) != 0 && (val & F_REPEAT) != (this.flags & F_REPEAT))
- phi.quCmd(-1, Phiola.QUCOM_REPEAT, val & F_REPEAT);
+ int m = 0, v = 0;
- if ((mask & F_RANDOM) != 0 && (val & F_RANDOM) != (this.flags & F_RANDOM))
- phi.quCmd(-1, Phiola.QUCOM_RANDOM, val & F_RANDOM);
+ if ((mask & F_REPEAT) != 0 && (val & F_REPEAT) != (this.flags & F_REPEAT)) {
+ m |= Phiola.QC_REPEAT;
+ if ((val & F_REPEAT) != 0)
+ v |= Phiola.QC_REPEAT;
+ }
+
+ if ((mask & F_RANDOM) != 0 && (val & F_RANDOM) != (this.flags & F_RANDOM)) {
+ m |= Phiola.QC_RANDOM;
+ if ((val & F_RANDOM) != 0)
+ v |= Phiola.QC_RANDOM;
+ }
+
+ if ((mask & F_RG_NORM) != 0 && (val & F_RG_NORM) != (this.flags & F_RG_NORM)) {
+ m |= Phiola.QC_RG_NORM;
+ if ((val & F_RG_NORM) != 0)
+ v |= Phiola.QC_RG_NORM;
+ }
+
+ if ((mask & F_AUTO_NORM) != 0 && (val & F_AUTO_NORM) != (this.flags & F_AUTO_NORM)) {
+ m |= Phiola.QC_AUTO_NORM;
+ if ((val & F_AUTO_NORM) != 0)
+ v |= Phiola.QC_AUTO_NORM;
+ }
- if ((mask & F_AUTO_NORM) != 0 && (val & F_AUTO_NORM) != (this.flags & F_AUTO_NORM))
- phi.quCmd(-1, Phiola.QUCOM_AUTO_NORM, val & F_AUTO_NORM);
+ if ((mask & F_RM_ON_ERR) != 0 && (val & F_RM_ON_ERR) != (this.flags & F_RM_ON_ERR)) {
+ m |= Phiola.QC_REMOVE_ON_ERROR;
+ if ((val & F_RM_ON_ERR) != 0)
+ v |= Phiola.QC_REMOVE_ON_ERROR;
+ }
- if ((mask & F_RM_ON_ERR) != 0 && (val & F_RM_ON_ERR) != (this.flags & F_RM_ON_ERR))
- phi.quCmd(-1, Phiola.QUCOM_REMOVE_ON_ERROR, val & F_RM_ON_ERR);
+ if (m != 0)
+ phi.quConf(m, v);
this.flags &= ~mask;
this.flags |= val;
@@ -307,14 +331,14 @@ void load() {
i_selected = 0;
queues.get(i_selected).load_once();
- if (flags_test(F_REPEAT))
- phi.quCmd(-1, Phiola.QUCOM_REPEAT, 1);
- if (flags_test(F_RANDOM))
- phi.quCmd(-1, Phiola.QUCOM_RANDOM, 1);
- if (flags_test(F_RM_ON_ERR))
- phi.quCmd(-1, Phiola.QUCOM_REMOVE_ON_ERROR, 1);
- if (flags_test(F_AUTO_NORM))
- phi.quCmd(-1, Phiola.QUCOM_AUTO_NORM, 1);
+ int v = 0;
+ if (flags_test(F_REPEAT)) v |= Phiola.QC_REPEAT;
+ if (flags_test(F_RANDOM)) v |= Phiola.QC_RANDOM;
+ if (flags_test(F_RM_ON_ERR)) v |= Phiola.QC_REMOVE_ON_ERROR;
+ if (flags_test(F_RG_NORM)) v |= Phiola.QC_RG_NORM;
+ if (flags_test(F_AUTO_NORM)) v |= Phiola.QC_AUTO_NORM;
+ if (v != 0)
+ phi.quConf(v, v);
core.phiola.quSetCallback(this::on_change);
}
@@ -562,6 +586,7 @@ String conf_write() {
+ "list_add_rm_on_next %d\n"
+ "list_rm_on_next %d\n"
+ "list_rm_on_err %d\n"
+ + "play_rg_norm %d\n"
+ "play_auto_norm %d\n"
+ "play_auto_stop %d\n"
, curpos
@@ -571,6 +596,7 @@ String conf_write() {
, core.bool_to_int(flags_test(F_MOVE_ON_NEXT))
, core.bool_to_int(flags_test(F_RM_ON_NEXT))
, core.bool_to_int(flags_test(F_RM_ON_ERR))
+ , core.bool_to_int(flags_test(F_RG_NORM))
, core.bool_to_int(flags_test(F_AUTO_NORM))
, auto_stop_value_min
);
@@ -587,6 +613,7 @@ void conf_load(Conf.Entry[] kv) {
if (kv[Conf.LIST_ADD_RM_ON_NEXT].enabled) f |= F_MOVE_ON_NEXT;
if (kv[Conf.LIST_RM_ON_NEXT].enabled) f |= F_RM_ON_NEXT;
if (kv[Conf.LIST_RM_ON_ERR].enabled) f |= F_RM_ON_ERR;
+ if (kv[Conf.PLAY_RG_NORM].enabled) f |= F_RG_NORM;
if (kv[Conf.PLAY_AUTO_NORM].enabled) f |= F_AUTO_NORM;
this.flags = f;
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java b/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java
index 3024372..180ecd6 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/SettingsActivity.java
@@ -73,6 +73,15 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
}
});
+
+ b.swRgNorm.setOnCheckedChangeListener((v, checked) -> {
+ if (checked)
+ b.swAutoNorm.setChecked(false);
+ });
+ b.swAutoNorm.setOnCheckedChangeListener((v, checked) -> {
+ if (checked)
+ b.swRgNorm.setChecked(false);
+ });
}
private void rec_init() {
@@ -190,6 +199,7 @@ private void load() {
b.swListAddRmOnNext.setChecked(queue.flags_test(Queue.F_MOVE_ON_NEXT));
b.swListRmOnNext.setChecked(queue.flags_test(Queue.F_RM_ON_NEXT));
b.swListRmOnErr.setChecked(queue.flags_test(Queue.F_RM_ON_ERR));
+ b.swRgNorm.setChecked(queue.flags_test(Queue.F_RG_NORM));
b.swAutoNorm.setChecked(queue.flags_test(Queue.F_AUTO_NORM));
b.sbPlayAutoStop.setProgress(auto_stop_progress(queue.auto_stop_value_min));
b.eAutoStop.setText(core.int_to_str(queue.auto_stop_value_min));
@@ -238,8 +248,9 @@ private void save() {
f |= flag(Queue.F_MOVE_ON_NEXT, b.swListAddRmOnNext.isChecked());
f |= flag(Queue.F_RM_ON_NEXT, b.swListRmOnNext.isChecked());
f |= flag(Queue.F_RM_ON_ERR, b.swListRmOnErr.isChecked());
+ f |= flag(Queue.F_RG_NORM, b.swRgNorm.isChecked());
f |= flag(Queue.F_AUTO_NORM, b.swAutoNorm.isChecked());
- int mask = Queue.F_RANDOM | Queue.F_REPEAT | Queue.F_MOVE_ON_NEXT | Queue.F_RM_ON_NEXT | Queue.F_RM_ON_ERR | Queue.F_AUTO_NORM;
+ int mask = Queue.F_RANDOM | Queue.F_REPEAT | Queue.F_MOVE_ON_NEXT | Queue.F_RM_ON_NEXT | Queue.F_RM_ON_ERR | Queue.F_RG_NORM | Queue.F_AUTO_NORM;
queue.flags_set(mask, f);
queue.auto_stop_value_min = core.str_to_uint(b.eAutoStop.getText().toString(), 0);
diff --git a/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java b/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java
index c9a792b..a464799 100644
--- a/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java
+++ b/android/phiola/src/main/java/com/github/stsaz/phiola/UtilNative.java
@@ -39,7 +39,8 @@ static class Entry {
PLAY_AUTO_SKIP = PLAY_AUTO_NORM + 1,
PLAY_AUTO_SKIP_TAIL = PLAY_AUTO_SKIP + 1,
PLAY_AUTO_STOP = PLAY_AUTO_SKIP_TAIL + 1,
- REC_BITRATE = PLAY_AUTO_STOP + 1,
+ PLAY_RG_NORM = PLAY_AUTO_STOP + 1,
+ REC_BITRATE = PLAY_RG_NORM + 1,
REC_BUF_LEN = REC_BITRATE + 1,
REC_CHANNELS = REC_BUF_LEN + 1,
REC_DANORM = REC_CHANNELS + 1,
diff --git a/android/phiola/src/main/res/layout/settings.xml b/android/phiola/src/main/res/layout/settings.xml
index ed644ae..b71d1c7 100644
--- a/android/phiola/src/main/res/layout/settings.xml
+++ b/android/phiola/src/main/res/layout/settings.xml
@@ -186,6 +186,13 @@
android:inputType="text"
android:textSize="@dimen/text_editbox" />
+
+
Codepage for Non-Unicode Tags
Auto-Skip the Beginning of Each Track (% or sec):
Auto-Skip the Ending of Each Track (% or sec):
+ ReplayGain Normalizer
Auto Loudness Normalizer
Operation
diff --git a/src/afilter/afilter.c b/src/afilter/afilter.c
index 1b3ce56..cd8d061 100644
--- a/src/afilter/afilter.c
+++ b/src/afilter/afilter.c
@@ -6,6 +6,7 @@
#include
const phi_core *core;
+const phi_meta_if *meta_if;
#define syserrlog(t, ...) phi_syserrlog(core, NULL, t, __VA_ARGS__)
#define errlog(t, ...) phi_errlog(core, NULL, t, __VA_ARGS__)
#define warnlog(t, ...) phi_warnlog(core, NULL, t, __VA_ARGS__)
@@ -17,9 +18,11 @@ const phi_core *core;
#include
#include
#include
+#include
extern const phi_filter
phi_aconv,
+ phi_rg_norm,
phi_auto_norm,
phi_gain,
phi_peaks,
@@ -33,6 +36,7 @@ static const void* af_iface(const char *name)
{ "conv", &phi_aconv },
{ "gain", &phi_gain },
{ "peaks", &phi_peaks },
+ { "rg-norm", &phi_rg_norm },
{ "rtpeak", &phi_rtpeak },
{ "silence-gen",&phi_sil_gen },
{ "skip", &phi_pcm_skip },
diff --git a/src/afilter/gain.c b/src/afilter/gain.c
index 54c9086..174150c 100644
--- a/src/afilter/gain.c
+++ b/src/afilter/gain.c
@@ -32,7 +32,7 @@ static void gain_close(void *ctx, phi_track *t)
static int gain_process(void *ctx, phi_track *t)
{
struct gain *c = ctx;
- double db = t->oaudio.gain_db;
+ double db = t->oaudio.replay_gain_db + t->oaudio.gain_db;
if (db != 0) {
if (db != c->db) {
c->db = db;
diff --git a/src/afilter/pcm.h b/src/afilter/pcm.h
index 682638a..14775c9 100644
--- a/src/afilter/pcm.h
+++ b/src/afilter/pcm.h
@@ -183,3 +183,22 @@ static inline char** pcm_setni(void **ni, void *b, uint fmt, uint nch)
}
return (char**)ni;
}
+
+/** Q7.8 number <-> float (max. value = 127.99999999 ~= 128) */
+static inline int Q78_float(int n, double *dst) {
+ if (n < -0x8000 || n > 0x8000)
+ return -1;
+ *dst = (double)n / 256; // 256 = 2^8
+ return 0;
+}
+static inline int Q78_from_float(int *dst, double d) {
+ *dst = round(d * 256);
+ return (*dst < -0x8000 || *dst > 0x8000);
+}
+
+// -18: RG target
+// -23: R128 target
+// -18 -> -23 = -5
+// -23 -> -18 = +5
+#define RG_R128(rg2) ((rg2) - 5)
+#define RG_from_R128(r128) ((r128) + 5)
diff --git a/src/afilter/rgnorm.h b/src/afilter/rgnorm.h
new file mode 100644
index 0000000..ecb4de9
--- /dev/null
+++ b/src/afilter/rgnorm.h
@@ -0,0 +1,41 @@
+/** phiola: replay-gain normalizer
+2025, Simon Zolin */
+
+#include
+#include
+
+static void* rgnorm_open(phi_track *t)
+{
+ if (!meta_if)
+ meta_if = core->mod("format.meta");
+
+ double db;
+ ffstr val;
+ if (!meta_if->find(&t->meta, FFSTR_Z("r128_track_gain"), &val, 0)) {
+ int n;
+ if (!ffstr_to_int32(&val, &n)
+ || Q78_float(n, &db))
+ goto end;
+ db = RG_from_R128(db);
+
+ } else if (!meta_if->find(&t->meta, FFSTR_Z("replaygain_track_gain"), &val, 0)) {
+ ffstr_trimwhite(&val);
+ ffstr_splitby(&val, ' ', &val, NULL);
+ if (!ffstr_to_float(&val, &db))
+ goto end;
+
+ } else {
+ goto end;
+ }
+
+ dbglog(t, "replay gain: %f", db);
+ t->oaudio.replay_gain_db = db;
+
+end:
+ return PHI_OPEN_SKIP;
+}
+
+static const phi_filter phi_rg_norm = {
+ rgnorm_open, NULL, NULL,
+ "replaygain"
+};
diff --git a/src/afilter/split.h b/src/afilter/split.h
index 0101046..5db2d0c 100644
--- a/src/afilter/split.h
+++ b/src/afilter/split.h
@@ -11,8 +11,6 @@ iaudio -> split (#1)
#include
#include
-static const phi_meta_if *meta_if;
-
struct split_brg {
uint users;
uint state;
diff --git a/src/core/queue-entry.h b/src/core/queue-entry.h
index 493d159..e6b0ed0 100644
--- a/src/core/queue-entry.h
+++ b/src/core/queue-entry.h
@@ -175,6 +175,8 @@ static int qe_play(struct q_entry *e)
|| !track->filter(t, core->mod("format.detect"), 0)
|| !track->filter(t, core->mod("afilter.until"), 0)
|| !track->filter(t, ui_if, 0)
+ || (c.afilter.rg_normalizer
+ && !track->filter(t, core->mod("afilter.rg-norm"), 0))
|| (c.afilter.auto_normalizer
&& (!track->filter(t, core->mod("afilter.auto-conv-f"), 0)
|| !track->filter(t, core->mod("af-loudness.analyze"), 0)
diff --git a/src/exe/play.h b/src/exe/play.h
index 31417fe..ec9c129 100644
--- a/src/exe/play.h
+++ b/src/exe/play.h
@@ -36,6 +36,7 @@ Options:\n\
[[HH:]MM:]SS[.MSC]\n\
`-until` TIME Stop at time\n\
\n\
+ `-rgnorm` ReplayGain normalizer\n\
`-norm` \"OPTIONS\" Auto loudness normalizer. Options:\n\
`target` Integer\n\
`attenuate` Integer\n\
@@ -73,6 +74,7 @@ struct cmd_play {
u_char random;
u_char remote;
u_char repeat_all;
+ u_char rg_norm;
uint buffer;
uint connect_timeout;
uint device;
@@ -138,6 +140,7 @@ static int play_action(struct cmd_play *p)
.seek_msec = p->seek,
.until_msec = p->until,
.afilter = {
+ .rg_normalizer = (p->rg_norm && !p->auto_norm),
.auto_normalizer = p->auto_norm,
},
.oaudio = {
@@ -215,6 +218,7 @@ static const struct ffarg cmd_play[] = {
{ "-recv_timeout", 'u', O(recv_timeout) },
{ "-remote", '1', O(remote) },
{ "-repeat_all",'1', O(repeat_all) },
+ { "-rgnorm", '1', O(rg_norm) },
{ "-seek", 'S', play_seek },
{ "-tee", 's', O(tee) },
{ "-tracks", 'S', play_tracks },
diff --git a/src/format/mmtag.h b/src/format/mmtag.h
index 0b1f5cb..258978a 100644
--- a/src/format/mmtag.h
+++ b/src/format/mmtag.h
@@ -13,6 +13,7 @@ static const char *const ffmmtag_str[] = {
"lyrics", // MMTAG_LYRICS
"picture", // MMTAG_PICTURE
"publisher", // MMTAG_PUBLISHER
+ "replaygain_track_gain", // MMTAG_REPLAYGAIN_TRACK_GAIN
"title", // MMTAG_TITLE
"tracknumber", // MMTAG_TRACKNO
"tracktotal", // MMTAG_TRACKTOTAL
diff --git a/src/gui/lang_en.conf b/src/gui/lang_en.conf
index 589d4f9..77a6f9b 100644
--- a/src/gui/lang_en.conf
+++ b/src/gui/lang_en.conf
@@ -19,6 +19,7 @@ MMFile "_File"
STSeekBy "Seek By (sec)"
STLeapBy "Leap By (sec)"
STAutoSkip "Auto Skip the Beginning of Each Track (% or sec)"
+ STRGNorm "ReplayGain Normalizer"
STAutoNorm "Auto Loudness Normalizer"
MFExit "E_xit"
diff --git a/src/gui/mod.c b/src/gui/mod.c
index 1c98b1f..4c27c36 100644
--- a/src/gui/mod.c
+++ b/src/gui/mod.c
@@ -38,6 +38,7 @@ const struct ffarg guimod_args[] = {
{ "play.dev", 'u', O(conf.odev) },
{ "play.random", 'u', O(conf.random) },
{ "play.repeat", 'u', O(conf.repeat) },
+ { "play.rg_norm", 'u', O(conf.rg_norm) },
{ "play.seek_leap", 'u', O(conf.seek_leap_delta) },
{ "play.seek_step", 'u', O(conf.seek_step_delta) },
{ "play.volume", 'u', O(volume) },
@@ -55,6 +56,7 @@ void mod_userconf_write(ffconfw *cw)
ffconfw_add2u(cw, "play.dev", gd->conf.odev);
ffconfw_add2u(cw, "play.random", gd->conf.random);
ffconfw_add2u(cw, "play.repeat", gd->conf.repeat);
+ ffconfw_add2u(cw, "play.rg_norm", gd->conf.rg_norm);
ffconfw_add2u(cw, "play.seek_leap", gd->conf.seek_leap_delta);
ffconfw_add2u(cw, "play.seek_step", gd->conf.seek_step_delta);
ffconfw_add2u(cw, "play.volume", gd->volume);
@@ -289,16 +291,21 @@ void list_filter(ffstr filter)
ffstr_free(&filter);
}
+static void qc_apply(struct phi_queue_conf *qc)
+{
+ qc->repeat_all = gd->conf.repeat;
+ qc->random = gd->conf.random;
+ qc->tconf.afilter.rg_normalizer = gd->conf.rg_norm;
+ qc->tconf.afilter.auto_normalizer = (gd->conf.auto_norm) ? "" : NULL;
+ qc->tconf.oaudio.device_index = gd->conf.odev;
+}
+
void ctl_play(uint i)
{
if (!gd->q_filtered && !gd->tab_conversion) {
gd->queue->qselect(gd->q_selected); // set the visible list as default
// Apply settings for the list that we're activating
- struct phi_queue_conf *qc = gd->queue->conf(NULL);
- qc->repeat_all = gd->conf.repeat;
- qc->random = gd->conf.random;
- qc->tconf.afilter.auto_normalizer = (gd->conf.auto_norm) ? "" : NULL;
- qc->tconf.oaudio.device_index = gd->conf.odev;
+ qc_apply(gd->queue->conf(NULL));
}
phi_queue_id q = (gd->q_filtered) ? gd->q_filtered : NULL;
gd->queue->play(NULL, gd->queue->at(q, i));
@@ -868,10 +875,7 @@ static void gui_start(void *param)
struct phi_queue_conf *qc = gd->queue->conf(q);
gd->playback_first_filter = qc->first_filter;
qc->name = ffsz_dup("Playlist 1");
- qc->repeat_all = gd->conf.repeat;
- qc->random = gd->conf.random;
- qc->tconf.afilter.auto_normalizer = (gd->conf.auto_norm) ? "" : NULL;
- qc->tconf.oaudio.device_index = gd->conf.odev;
+ qc_apply(qc);
struct list_info *li = ffvec_zpushT(&gd->lists, struct list_info);
li->q = q;
diff --git a/src/gui/mod.h b/src/gui/mod.h
index 3b60b78..2a668ed 100644
--- a/src/gui/mod.h
+++ b/src/gui/mod.h
@@ -149,7 +149,7 @@ struct gui_data {
uint odev;
uint repeat;
uint random;
- uint auto_norm;
+ uint rg_norm, auto_norm;
uint seek_step_delta;
uint seek_leap_delta;
int auto_skip_sec_percent;
diff --git a/src/gui/settings.hpp b/src/gui/settings.hpp
index 5476817..defdc46 100644
--- a/src/gui/settings.hpp
+++ b/src/gui/settings.hpp
@@ -5,7 +5,7 @@ struct gui_wsettings {
ffui_windowxx wnd;
ffui_label ldev, lseek_by, lleap_by, lauto_skip;
ffui_editxx eseek_by, eleap_by, eauto_skip;
- ffui_checkboxxx cbdarktheme, cbauto_norm;
+ ffui_checkboxxx cbdarktheme, cbrg_norm, cbauto_norm;
ffui_comboboxxx cbdev;
char* wnd_pos;
@@ -21,6 +21,7 @@ FF_EXTERN const ffui_ldr_ctl wsettings_ctls[] = {
_(lseek_by), _(eseek_by),
_(lleap_by), _(eleap_by),
_(lauto_skip), _(eauto_skip),
+ _(cbrg_norm),
_(cbauto_norm),
FFUI_LDR_CTL_END
};
@@ -64,6 +65,10 @@ static void wsettings_ui_to_conf()
gd->conf.auto_norm = r;
mod = 1;
}
+ if (gd->conf.rg_norm != (r = w->cbrg_norm.checked())) {
+ gd->conf.rg_norm = (r && !gd->conf.auto_norm);
+ mod = 1;
+ }
if (gd->conf.odev != (r = w->cbdev.get())) {
gd->conf.odev = r;
@@ -73,6 +78,7 @@ static void wsettings_ui_to_conf()
if (mod) {
// Apply settings for the active playlist
struct phi_queue_conf *qc = gd->queue->conf(NULL);
+ qc->tconf.afilter.rg_normalizer = gd->conf.rg_norm;
qc->tconf.afilter.auto_normalizer = (gd->conf.auto_norm) ? "" : NULL;
qc->tconf.oaudio.device_index = gd->conf.odev;
}
@@ -89,6 +95,7 @@ static void wsettings_ui_from_conf()
if (w->cbdarktheme.h)
w->cbdarktheme.check(!!gd->conf.theme);
+ w->cbrg_norm.check(!!gd->conf.rg_norm);
w->cbauto_norm.check(!!gd->conf.auto_norm);
uint odev = adevices_fill(PHI_ADEV_PLAYBACK, w->cbdev, gd->conf.odev);
diff --git a/src/gui/ui-gtk.conf b/src/gui/ui-gtk.conf
index f02e88b..09b0398 100644
--- a/src/gui/ui-gtk.conf
+++ b/src/gui/ui-gtk.conf
@@ -599,6 +599,9 @@ window wsettings {
editbox eauto_skip {
}
+ checkbox cbrg_norm {
+ text $STRGNorm
+ }
checkbox cbauto_norm {
text $STAutoNorm
}
diff --git a/src/gui/ui-winapi.conf b/src/gui/ui-winapi.conf
index c25e994..36ef622 100644
--- a/src/gui/ui-winapi.conf
+++ b/src/gui/ui-winapi.conf
@@ -661,6 +661,9 @@ window wsettings {
editbox eauto_skip {
}
+ checkbox cbrg_norm {
+ text $STRGNorm
+ }
checkbox cbauto_norm {
text $STAutoNorm
}
diff --git a/src/jni/android-utils.h b/src/jni/android-utils.h
index f63a6e3..f235390 100644
--- a/src/jni/android-utils.h
+++ b/src/jni/android-utils.h
@@ -35,6 +35,7 @@ static const char setting_names[][20] = {
"play_auto_skip",
"play_auto_skip_tail",
"play_auto_stop",
+ "play_rg_norm",
"rec_bitrate",
"rec_buf_len",
"rec_channels",
diff --git a/src/jni/phiola-jni.c b/src/jni/phiola-jni.c
index 9d07401..1ea4f9e 100644
--- a/src/jni/phiola-jni.c
+++ b/src/jni/phiola-jni.c
@@ -42,6 +42,7 @@ struct phiola_jni {
uint auto_stop_timer_expired :1;
uint repeat_all :1;
uint random :1;
+ uint rg_normalizer :1;
uint auto_normalizer :1;
} play;
diff --git a/src/jni/queue.h b/src/jni/queue.h
index b8f39f3..33e2929 100644
--- a/src/jni/queue.h
+++ b/src/jni/queue.h
@@ -247,14 +247,18 @@ enum {
QUCOM_PLAY = 7,
QUCOM_PLAY_NEXT = 8,
QUCOM_PLAY_PREV = 9,
- QUCOM_REPEAT = 10,
- QUCOM_RANDOM = 11,
- QUCOM_REMOVE_ON_ERROR = 12,
QUCOM_CONV_CANCEL = 13,
QUCOM_CONV_UPDATE = 14,
- QUCOM_AUTO_NORM = 15,
};
+static void qc_apply(struct phi_queue_conf *qc)
+{
+ qc->repeat_all = x->play.repeat_all;
+ qc->random = x->play.random;
+ qc->tconf.afilter.rg_normalizer = (x->play.rg_normalizer && !x->play.auto_normalizer);
+ qc->tconf.afilter.auto_normalizer = (x->play.auto_normalizer) ? "" : NULL;
+}
+
static void qu_cmd(struct core_data *d)
{
phi_queue_id q = d->q;
@@ -273,10 +277,7 @@ static void qu_cmd(struct core_data *d)
case QUCOM_PLAY: {
x->queue.qselect(q);
- struct phi_queue_conf *qc = x->queue.conf(NULL);
- qc->repeat_all = x->play.repeat_all;
- qc->random = x->play.random;
- qc->tconf.afilter.auto_normalizer = (x->play.auto_normalizer) ? "" : NULL;
+ qc_apply(x->queue.conf(NULL));
x->queue.play(NULL, x->queue.at(q, d->param_int));
break;
}
@@ -312,27 +313,6 @@ Java_com_github_stsaz_phiola_Phiola_quCmd(JNIEnv *env, jobject thiz, jlong jq, j
break;
}
- case QUCOM_REMOVE_ON_ERROR:
- x->play.remove_on_error = !!i; break;
-
- case QUCOM_REPEAT:
- x->play.repeat_all = !!i; goto qc_apply;
-
- case QUCOM_RANDOM:
- x->play.random = !!i; goto qc_apply;
-
- case QUCOM_AUTO_NORM:
- x->play.auto_normalizer = !!i;
-
- qc_apply: {
- // Apply settings for the active playlist
- struct phi_queue_conf *qc = x->queue.conf(NULL);
- qc->repeat_all = x->play.repeat_all;
- qc->random = x->play.random;
- qc->tconf.afilter.auto_normalizer = (x->play.auto_normalizer) ? "" : NULL;
- }
- break;
-
case QUCOM_CONV_CANCEL:
FFINT_WRITEONCE(x->convert.interrupt, 1); break;
@@ -353,6 +333,42 @@ Java_com_github_stsaz_phiola_Phiola_quCmd(JNIEnv *env, jobject thiz, jlong jq, j
return rc;
}
+enum {
+ QC_REPEAT = 1,
+ QC_RANDOM = 2,
+ QC_REMOVE_ON_ERROR = 4,
+ QC_AUTO_NORM = 0x10,
+ QC_RG_NORM = 0x20,
+};
+
+JNIEXPORT void JNICALL
+Java_com_github_stsaz_phiola_Phiola_quConf(JNIEnv *env, jobject thiz, jint mask, jint val)
+{
+ dbglog("%s: enter mask:%u val:%u", __func__, mask, val);
+
+ if (mask & QC_REMOVE_ON_ERROR)
+ x->play.remove_on_error = !!(val & QC_REMOVE_ON_ERROR);
+
+ if (mask & QC_REPEAT)
+ x->play.repeat_all = !!(val & QC_REPEAT);
+
+ if (mask & QC_RANDOM)
+ x->play.random = !!(val & QC_RANDOM);
+
+ if (mask & QC_RG_NORM)
+ x->play.rg_normalizer = !!(val & QC_RG_NORM);
+
+ if (mask & QC_AUTO_NORM)
+ x->play.auto_normalizer = !!(val & QC_AUTO_NORM);
+
+ if (mask & (QC_REPEAT | QC_RANDOM | QC_RG_NORM | QC_AUTO_NORM)) {
+ // Apply settings for the active playlist
+ qc_apply(x->queue.conf(NULL));
+ }
+
+ dbglog("%s: exit", __func__);
+}
+
static ffvec info_prepare(const struct phi_queue_entry *qe)
{
const phi_meta *meta = &qe->meta;
diff --git a/src/phiola.h b/src/phiola.h
index 923e96b..03b58c2 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -287,6 +287,7 @@ struct phi_track_conf {
struct {
double gain_db; // Audio gain/attenuation
+ uint rg_normalizer :1;
uint peaks_info :1;
uint loudness_summary :1;
const char *auto_normalizer;
diff --git a/src/track.h b/src/track.h
index 271a149..a8baaac 100644
--- a/src/track.h
+++ b/src/track.h
@@ -209,6 +209,7 @@ struct phi_track {
struct phi_af format;
struct phi_af conv_format;
double gain_db;
+ double replay_gain_db;
double loudness, loudness_momentary;
// ui -> audio.play
From b2fd63c25ec4aefbdba9f847e324aedcad120376 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 11 Jan 2025 11:27:01 +0300
Subject: [PATCH 54/59] + `tag -rg track_gain`: write ReplayGain tags
---
README.md | 5 +-
src/exe/tag.h | 134 +++++++++++++++++++++++++++++++++++++++++++-
src/format/Makefile | 2 +-
src/format/tag.c | 30 ++++++++++
4 files changed, 167 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index bfd3e53..d6d6dbb 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@ Contents:
* Play audio: `.mp3`, `.ogg`(Vorbis/Opus), `.mp4`/`.mov`(AAC/ALAC/MP3), `.mkv`/`.webm`(AAC/ALAC/MP3/Vorbis/Opus/PCM), `.caf`(AAC/ALAC/PCM), `.avi`(AAC/MP3/PCM), `.ts`(AAC/MP3), `.aac`, `.mpc`; `.flac`, `.ape`, `.wv`, `.wav`.
* Record audio: `.m4a`(AAC), `.ogg`, `.opus`; `.flac`, `.wav`
* Convert audio
-* List/search file meta tags; edit file tags (.mp3, .ogg, .opus)
+* List/search file meta tags; edit file tags, write ReplayGain tags (.mp3, .ogg, .opus)
* List available audio devices
* Input: file, directory, HTTP/HTTPS URL, console (stdin), playlists: `.m3u`, `.pls`, `.cue`
* Command Line Interface for Desktop OS
@@ -240,6 +240,9 @@ phiola tag -m "artist=Great Artist" -m "title=Cool Song" file.mp3
# Remove all existing tags; add new tags
phiola tag -clear -m "artist=Great Artist" -m "title=Cool Song" file.mp3
+
+# Write ReplayGain tag for all .mp3 & .flac files in the directory
+phiola tag -rg track_gain "My Music" -include "*.mp3" -include "*.flac"
```
Create/edit playlist files:
diff --git a/src/exe/tag.h b/src/exe/tag.h
index 8daf4b7..a1e08d0 100644
--- a/src/exe/tag.h
+++ b/src/exe/tag.h
@@ -8,9 +8,14 @@ Edit file tags:\n\
`phiola tag` OPTIONS FILE...\n\
\n\
Options:\n\
+ `-include` WILDCARD `-rg`: Only include files matching a wildcard (case-insensitive)\n\
+ `-exclude` WILDCARD `-rg`: Exclude files & directories matching a wildcard (case-insensitive)\n\
+\n\
`-clear` Remove all existing tags. By default all original tags are preserved.\n\
`-meta` NAME=VALUE Meta data\n\
.mp3 supports: album, albumartist, artist, comment, date, genre, picture, publisher, title, tracknumber, tracktotal.\n\
+ `-rg` \"OPTIONS\" Write ReplayGain tags. Options:\n\
+ `track_gain` (default)\n\
`-preserve_date` Preserve file modification date\n\
`-fast` Fail if need to rewrite whole file\n\
");
@@ -24,6 +29,10 @@ struct cmd_tag {
u_char clear;
u_char preserve_date;
u_char fast;
+ uint replay_gain;
+ ffvec include, exclude; // ffstr[]
+
+ const phi_tag_if *tag;
};
static int tag_input(struct cmd_tag *t, ffstr s)
@@ -34,6 +43,18 @@ static int tag_input(struct cmd_tag *t, ffstr s)
return cmd_input(&t->input, s);
}
+static int tag_include(struct cmd_tag *t, ffstr s)
+{
+ *ffvec_pushT(&t->include, ffstr) = s;
+ return 0;
+}
+
+static int tag_exclude(struct cmd_tag *t, ffstr s)
+{
+ *ffvec_pushT(&t->exclude, ffstr) = s;
+ return 0;
+}
+
static int tag_meta(struct cmd_tag *t, ffstr s)
{
ffstr name, val;
@@ -43,11 +64,117 @@ static int tag_meta(struct cmd_tag *t, ffstr s)
return 0;
}
+static int tag_replay_gain(struct cmd_tag *t, const char *s)
+{
+ struct rg {
+ u_char track_gain;
+ } rg;
+
+ #define O(m) (void*)(size_t)FF_OFF(struct rg, m)
+ static const struct ffarg rg_args[] = {
+ { "track_gain", '1', O(track_gain) },
+ {}
+ };
+ #undef O
+
+ struct ffargs a = {};
+ if (ffargs_process_line(&a, rg_args, &rg, FFARGS_O_PARTIAL | FFARGS_O_DUPLICATES, s))
+ return _ffargs_err(&x->cmd, 1, "%s", a.error);
+
+ if (rg.track_gain || !*s) {
+ t->replay_gain |= 1;
+ }
+ return 0;
+}
+
+static void tag_grd_close(void *f, phi_track *t)
+{
+ struct cmd_tag *tt = x->subcmd.obj;
+ ffvec tags = {};
+ ffstr s = {};
+ int rc = -1;
+
+ x->core->track->stop(t);
+
+ if (t->error)
+ goto end;
+
+ ffvec_add2T(&tags, &t->meta, ffstr);
+ ffstr_dupz(&s, "replaygain_track_gain=-xx.xx");
+ *ffvec_pushT(&tags, ffstr) = s;
+
+ // -18: ReplayGain target
+ // -1: EBU R 128: "The Maximum True-Peak Level in production shall not exceed −1 dBTP"
+ // e.g. -10 loudness -> -19 target = -9 gain
+ double rg = -18-1 - t->oaudio.loudness;
+
+ uint n = ffs_fromfloat(rg, s.ptr + 22, 6, FFS_FLTKEEPSIGN | FFS_FLTWIDTH(3) | FFS_FLTZERO | 2);
+ if (n != 6) {
+ phi_errlog(x->core, NULL, t, "ffs_fromfloat");
+ goto end;
+ }
+
+ struct phi_tag_conf conf = {
+ .filename = t->conf.ifile.name,
+ .meta = *(ffslice*)&tags,
+ .clear = tt->clear,
+ .preserve_date = tt->preserve_date,
+ .no_expand = tt->fast,
+ };
+ if (tt->tag->edit(&conf))
+ goto end;
+
+ rc = 0;
+
+end:
+ ffstr_free(&s);
+ ffvec_free(&tags);
+ if (rc)
+ x->exit_code = 1;
+}
+
+static const phi_filter tag_guard = {
+ NULL, tag_grd_close, phi_grd_process,
+ "tag-guard"
+};
+
static int tag_action(struct cmd_tag *t)
{
- const phi_tag_if *tag = x->core->mod("format.tag");
+ t->tag = x->core->mod("format.tag");
int r = 0;
+ if (t->replay_gain) {
+ x->queue->on_change(q_on_change);
+
+ struct phi_track_conf c = {
+ .ifile = {
+ .include = *(ffslice*)&t->include,
+ .exclude = *(ffslice*)&t->exclude,
+ },
+ .afilter.loudness_summary = 1,
+ };
+
+ struct phi_queue_conf qc = {
+ .first_filter = &tag_guard,
+ .ui_module = "tui.play",
+ .tconf = c,
+ .analyze = 1,
+ };
+ x->queue->create(&qc);
+
+ ffstr *it;
+ FFSLICE_WALK(&t->input, it) {
+ struct phi_queue_entry qe = {
+ .url = it->ptr,
+ };
+ x->queue->add(NULL, &qe);
+ }
+ ffvec_free(&t->input);
+
+ x->queue->play(NULL, NULL);
+ return 0;
+ }
+
ffstr *fn;
FFSLICE_WALK(&t->input, fn) {
struct phi_tag_conf conf = {
@@ -57,7 +184,7 @@ static int tag_action(struct cmd_tag *t)
.preserve_date = t->preserve_date,
.no_expand = t->fast,
};
- r |= tag->edit(&conf);
+ r |= t->tag->edit(&conf);
}
x->core->sig(PHI_CORE_STOP);
@@ -75,10 +202,13 @@ static int tag_prepare(struct cmd_tag *t)
#define O(m) (void*)FF_OFF(struct cmd_tag, m)
static const struct ffarg cmd_tag_args[] = {
{ "-clear", '1', O(clear) },
+ { "-exclude", 'S', tag_exclude },
{ "-fast", '1', O(fast) },
{ "-help", 0, tag_help },
+ { "-include", 'S', tag_include },
{ "-meta", '+S', tag_meta },
{ "-preserve_date", '1', O(preserve_date) },
+ { "-rg", 's', tag_replay_gain },
{ "\0\1", 'S', tag_input },
{ "", 0, tag_prepare },
};
diff --git a/src/format/Makefile b/src/format/Makefile
index a55d839..3af2d67 100644
--- a/src/format/Makefile
+++ b/src/format/Makefile
@@ -45,4 +45,4 @@ format.$(SO): mod-fmt.o \
\
sort.o \
str-format.o
- $(LINK) -shared $+ $(LINKFLAGS) -o $@
+ $(LINK) -shared $+ $(LINKFLAGS) -lm -o $@
diff --git a/src/format/tag.c b/src/format/tag.c
index 7639b07..5485359 100644
--- a/src/format/tag.c
+++ b/src/format/tag.c
@@ -3,6 +3,7 @@
#include
#include
+#include
#include
#include
#include
@@ -420,6 +421,25 @@ static int tag_ogg_vtag_read(oggread *ogg, ffstr in, ffstr *page, uint *page_off
return 0;
}
+static int tag_opus_r128_track_gain(vorbistagwrite *vtw, ffstr v)
+{
+ int r;
+ double d;
+ if (!ffstr_to_float(&v, &d)
+ || Q78_from_float(&r, RG_R128(d))) {
+ warnlog("incorrect value: %S", &v);
+ return -1;
+ }
+
+ char val[8];
+ v.ptr = val;
+ v.len = ffs_fromint(r, val, sizeof(val), FFS_INTSIGN);
+ ffstr k = FFSTR_Z("R128_TRACK_GAIN");
+ if (!vorbistagwrite_add_name(vtw, k, v))
+ dbglog("vorbistag: written %S = %S", &k, &v);
+ return 0;
+}
+
static int tag_vorbis(struct tag_edit *t)
{
int r, rc = 'e', format;
@@ -468,6 +488,9 @@ static int tag_vorbis(struct tag_edit *t)
}
if (user_meta_find(&t->conf.meta, tag, &k2, &v2)) {
+ if (tag == MMTAG_REPLAYGAIN_TRACK_GAIN && format == 'o')
+ continue; // Skip existing REPLAYGAIN_TRACK_GAIN tag
+
// Write user tag
if (!vorbistagwrite_add(&vtw, tag, v2))
dbglog("vorbistag: written %S = %S", &k2, &v2);
@@ -497,6 +520,13 @@ static int tag_vorbis(struct tag_edit *t)
continue;
if (tags_added[tag])
continue;
+
+ if (tag == MMTAG_REPLAYGAIN_TRACK_GAIN && format == 'o') {
+ // Write R128_TRACK_GAIN tag instead of REPLAYGAIN_TRACK_GAIN
+ tag_opus_r128_track_gain(&vtw, v);
+ continue;
+ }
+
if (!vorbistagwrite_add(&vtw, tag, v))
dbglog("vorbistag: written %S = %S", &k, &v);
}
From 0ac5c24c7044b92e68f64907c197dd1ffed12296 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 11 Jan 2025 11:27:01 +0300
Subject: [PATCH 55/59] ref
- `tag`: .mp3: safer approach for preserving existing tags
---
src/format/flac-read.h | 34 +++---
src/format/tag.c | 228 ++++++++++++++++++++++++++---------------
2 files changed, 169 insertions(+), 93 deletions(-)
diff --git a/src/format/flac-read.h b/src/format/flac-read.h
index 4d90697..9e45865 100644
--- a/src/format/flac-read.h
+++ b/src/format/flac-read.h
@@ -2,6 +2,7 @@
2021, Simon Zolin */
#include
+#include
struct flac_r {
flacread fl;
@@ -36,16 +37,25 @@ static void flac_in_free(void *ctx, phi_track *t)
}
extern const phi_meta_if phi_metaif;
-static void flac_meta(struct flac_r *f, phi_track *t)
+static void flac_tags(struct flac_r *f, phi_track *t, ffstr vtag)
{
- ffstr name, val;
- int tag = flacread_tag(&f->fl, &name, &val);
- dbglog(t, "%S: %S", &name, &val);
- if (tag == MMTAG_PICTURE)
- return;
- if (tag > 0)
- ffstr_setz(&name, ffmmtag_str[tag]);
- phi_metaif.set(&t->meta, name, val, 0);
+ vorbistagread vtr = {};
+ for (;;) {
+ ffstr name, val;
+ int tag = vorbistagread_process(&vtr, &vtag, &name, &val);
+ switch (tag) {
+ case VORBISTAGREAD_DONE:
+ return;
+ case VORBISTAGREAD_ERROR:
+ errlog(t, "bad Vorbis tags");
+ return;
+ }
+
+ dbglog(t, "tags: %S: %S", &name, &val);
+ if (tag > 0)
+ ffstr_setz(&name, ffmmtag_str[tag]);
+ phi_metaif.set(&t->meta, name, val, 0);
+ }
}
static void flac_info(struct flac_r *f, phi_track *t, const struct flac_info *i, int done)
@@ -113,8 +123,9 @@ static int flac_in_read(void *ctx, phi_track *t)
f->sample_rate = t->audio.format.rate;
break;
- case FLACREAD_TAG:
- flac_meta(f, t);
+ case FLACREAD_META_BLOCK:
+ if (flacread_meta_type(&f->fl) == FLAC_TTAGS)
+ flac_tags(f, t, out);
break;
case FLACREAD_HEADER_FIN:
@@ -135,7 +146,6 @@ static int flac_in_read(void *ctx, phi_track *t)
return PHI_MORE;
case FLACREAD_DONE:
-
return PHI_DONE;
case FLACREAD_ERROR:
diff --git a/src/format/tag.c b/src/format/tag.c
index 5485359..82c74a6 100644
--- a/src/format/tag.c
+++ b/src/format/tag.c
@@ -135,86 +135,111 @@ static int tag_file_write(struct tag_edit *t, ffstr head, ffstr tags, uint64 tai
return 0;
}
-static int tag_mp3_id3v2(struct tag_edit *t)
+/**
+Return whole ID3v2 region size */
+static int tag_mp3_id3v2_read(struct tag_edit *t, ffstr hdr)
{
- int rc = 'e', r;
- uint id3v2_size = 0;
- struct id3v2write w = {};
- id3v2write_create(&w);
- w.as_is = 1;
+ int rc = -1, r;
+ uint n = 0;
+ ffstr k, v;
struct id3v2read id3v2 = {};
id3v2read_open(&id3v2);
id3v2.as_is = 1;
-
- if (0 > (r = fffile_readat(t->fd, t->buf.ptr, t->buf.cap, 0))) {
- syserrlog("file read: %s", t->conf.filename);
- goto end;
- }
- t->buf.len = r;
-
- ffstr *kv, in, k, v, ik, iv;
-
- ffstr_setstr(&in, &t->buf);
- int tag = id3v2read_process(&id3v2, &in, &ik, &iv);
- if (tag != ID3V2READ_NO) {
- id3v2_size = id3v2read_size(&id3v2);
- if (id3v2_size > t->buf.cap) {
+ r = id3v2read_process(&id3v2, &hdr, &k, &v);
+ if (r != ID3V2READ_NO) {
+ n = id3v2read_size(&id3v2);
+ if (n > t->buf.cap) {
// we need full tag contents in memory
- if (id3v2_size > 100*1024*1024) {
- errlog("id3v2: %s: huge tag size %u", t->conf.filename, id3v2_size);
+ if (n > 100*1024*1024) {
+ errlog("id3v2: %s: huge tag size %u", t->conf.filename, n);
goto end;
}
- ffvec_realloc(&t->buf, id3v2_size, 1);
+ ffvec_realloc(&t->buf, n, 1);
if (0 > (r = fffile_readat(t->fd, t->buf.ptr, t->buf.cap, 0))) {
syserrlog("file read: %s", t->conf.filename);
goto end;
}
t->buf.len = r;
-
- id3v2read_close(&id3v2);
- ffmem_zero_obj(&id3v2);
- id3v2read_open(&id3v2);
- id3v2.as_is = 1;
- ffstr_setstr(&in, &t->buf);
- tag = id3v2read_process(&id3v2, &in, &ik, &iv);
}
}
+ rc = n;
+
+end:
+ id3v2read_close(&id3v2);
+ return rc;
+}
+
+static int tag_mp3_id3v2_process(struct tag_edit *t, struct id3v2write *w, ffstr in)
+{
+ int rc = -1, tag;
+ ffstr *kv, k, v, ik, iv;
+ struct id3v2read id3v2 = {
+ .as_is = 1,
+ };
+ id3v2read_open(&id3v2);
u_char tags_added[_MMTAG_N] = {};
if (!t->conf.clear) {
// replace tags, copy existing tags preserving the original order
- while (tag <= 0) {
+ for (;;) {
+ tag = id3v2read_process(&id3v2, &in, &ik, &iv);
+ dbglog("id3v2: %d", tag);
+ switch (tag) {
+ case ID3V2READ_NO:
+ case ID3V2READ_DONE:
+ goto add;
+
+ case ID3V2READ_WARN:
+ warnlog("%s", id3v2read_error(&id3v2));
+ continue;
+
+ case ID3V2READ_ERROR:
+ errlog("%s", id3v2read_error(&id3v2));
+ goto end;
+
+ default:
+ if (tag > 0)
+ goto end;
+ }
+
tag = -tag;
if (user_meta_find(&t->conf.meta, tag, &k, &v)) {
// write user tag
dbglog("id3v2: writing %S = %S", &k, &v);
- id3v2write_add(&w, tag, v);
+ if (id3v2write_add(w, tag, v)) {
+ errlog("id3v2write_add()");
+ goto end;
+ }
} else {
// copy existing tag
- dbglog("id3v2: writing %S = %S %d", &ik, &iv, tag);
- if (tag != 0) {
- id3v2write_add(&w, tag, iv);
+ dbglog("id3v2: writing (%d) %S = %S ", tag, &ik, &iv);
+ if (id3v2read_version(&id3v2) == 2) {
+ if (tag == 0) {
+ errlog("Can't copy ID3v2.2 tag %S", &ik);
+ goto end;
+ }
+ if (id3v2write_add(w, tag, iv)) {
+ errlog("id3v2write_add()");
+ goto end;
+ }
+
} else {
- // unknown tag
- char key[4] = {};
- ffmem_copy(key, ik.ptr, ffmax(ik.len, 4));
- _id3v2write_addframe(&w, key, FFSTR_Z(""), iv, 1);
+ // Copy v3/v4 tag data as-is
+ if (_id3v2write_addframe(w, ik.ptr, FFSTR_Z(""), iv, -1)) {
+ errlog("_id3v2write_addframe()");
+ goto end;
+ }
}
}
- tags_added[tag] = 1;
- tag = id3v2read_process(&id3v2, &in, &ik, &iv);
- dbglog("id3v2: %d", tag);
- switch (tag) {
- case ID3V2READ_WARN:
- case ID3V2READ_ERROR:
- warnlog("%s", id3v2read_error(&id3v2));
- }
+ tags_added[tag] = 1;
}
}
+add:
+
// add new tags
FFSLICE_WALK(&t->conf.meta, kv) {
if (!kv->len)
@@ -228,10 +253,41 @@ static int tag_mp3_id3v2(struct tag_edit *t)
}
if (tags_added[tag])
continue; // already added
+
dbglog("id3v2: writing %S = %S", &k, &v);
- id3v2write_add(&w, tag, v);
+ id3v2write_add(w, tag, v);
}
+ rc = 0;
+
+end:
+ id3v2read_close(&id3v2);
+ return rc;
+}
+
+static int tag_mp3_id3v2(struct tag_edit *t)
+{
+ int rc = 'e', r;
+ ffstr in;
+ uint id3v2_size;
+ struct id3v2write w = {};
+ id3v2write_create(&w);
+ w.as_is = 1;
+
+ if (0 > (r = fffile_readat(t->fd, t->buf.ptr, t->buf.cap, 0))) {
+ syserrlog("file read: %s", t->conf.filename);
+ goto end;
+ }
+ t->buf.len = r;
+ in = *(ffstr*)&t->buf;
+
+ if ((int)(id3v2_size = tag_mp3_id3v2_read(t, in)) < 0)
+ goto end;
+ in = *(ffstr*)&t->buf;
+
+ if (tag_mp3_id3v2_process(t, &w, in))
+ goto end;
+
int padding = id3v2_size - w.buf.len;
if (padding < 0)
padding = 1000 - w.buf.len;
@@ -268,7 +324,6 @@ static int tag_mp3_id3v2(struct tag_edit *t)
rc = 0;
end:
- id3v2read_close(&id3v2);
id3v2write_close(&w);
return rc;
}
@@ -440,39 +495,19 @@ static int tag_opus_r128_track_gain(vorbistagwrite *vtw, ffstr v)
return 0;
}
-static int tag_vorbis(struct tag_edit *t)
+static int tag_ogg_process(struct tag_edit *t, vorbistagwrite *vtw, ffstr vtag, uint format)
{
- int r, rc = 'e', format;
- uint tags_page_off, tags_page_num, vtags_len;
- ffstr vtag = {}, vorbis_codebook = {}, page, *kv, k, v, k2, v2;
- oggwrite ogw = {};
- oggread ogg = {};
- vorbistagwrite vtw = {
- .left_zone = 8,
- };
+ ffstr *kv, k, v, k2, v2;
vorbistagread vtr = {};
u_char tags_added[_MMTAG_N] = {};
- ffvec_realloc(&t->buf, 64*1024, 1);
- if (0 > (r = fffile_readat(t->fd, t->buf.ptr, t->buf.cap, 0))) {
- syserrlog("file read");
- return 'e';
- }
- t->buf.len = r;
-
- oggread_open(&ogg, -1);
- if (!(format = tag_ogg_vtag_read(&ogg, *(ffstr*)&t->buf, &page, &tags_page_off, &tags_page_num, &vtag, &vorbis_codebook)))
- goto end;
- vtags_len = vtag.len - ((format == 'v') ? 1 : 0);
-
// Copy "Vendor" field
int tag = vorbistagread_process(&vtr, &vtag, &k, &v);
if (tag != MMTAG_VENDOR) {
errlog("parsing Vorbis tag");
- goto end;
+ return -1;
}
- vorbistagwrite_create(&vtw);
- if (!vorbistagwrite_add(&vtw, MMTAG_VENDOR, v))
+ if (!vorbistagwrite_add(vtw, MMTAG_VENDOR, v))
dbglog("vorbistag: written vendor = %S", &v);
tags_added[MMTAG_VENDOR] = 1;
@@ -484,7 +519,7 @@ static int tag_vorbis(struct tag_edit *t)
break;
} else if (tag == VORBISTAGREAD_ERROR) {
errlog("parsing Vorbis tags");
- goto end;
+ return -1;
}
if (user_meta_find(&t->conf.meta, tag, &k2, &v2)) {
@@ -492,16 +527,16 @@ static int tag_vorbis(struct tag_edit *t)
continue; // Skip existing REPLAYGAIN_TRACK_GAIN tag
// Write user tag
- if (!vorbistagwrite_add(&vtw, tag, v2))
+ if (!vorbistagwrite_add(vtw, tag, v2))
dbglog("vorbistag: written %S = %S", &k2, &v2);
} else {
// Copy existing tag
if (tag != 0) {
- vorbistagwrite_add(&vtw, tag, v);
+ vorbistagwrite_add(vtw, tag, v);
} else {
// Unknown tag
- vorbistagwrite_add_name(&vtw, k, v);
+ vorbistagwrite_add_name(vtw, k, v);
}
}
@@ -515,7 +550,7 @@ static int tag_vorbis(struct tag_edit *t)
continue;
tag = user_meta_split(*kv, &k, &v);
if (tag < 0)
- goto end;
+ return -1;
if (!tag)
continue;
if (tags_added[tag])
@@ -523,14 +558,44 @@ static int tag_vorbis(struct tag_edit *t)
if (tag == MMTAG_REPLAYGAIN_TRACK_GAIN && format == 'o') {
// Write R128_TRACK_GAIN tag instead of REPLAYGAIN_TRACK_GAIN
- tag_opus_r128_track_gain(&vtw, v);
+ tag_opus_r128_track_gain(vtw, v);
continue;
}
- if (!vorbistagwrite_add(&vtw, tag, v))
+ if (!vorbistagwrite_add(vtw, tag, v))
dbglog("vorbistag: written %S = %S", &k, &v);
}
+ return 0;
+}
+
+static int tag_ogg(struct tag_edit *t)
+{
+ int r, rc = 'e', format;
+ uint tags_page_off, tags_page_num, vtags_len;
+ ffstr vtag = {}, vorbis_codebook = {}, page;
+ oggwrite ogw = {};
+ oggread ogg = {};
+ vorbistagwrite vtw = {
+ .left_zone = 8,
+ };
+ vorbistagwrite_create(&vtw);
+
+ ffvec_realloc(&t->buf, 64*1024, 1);
+ if (0 > (r = fffile_readat(t->fd, t->buf.ptr, t->buf.cap, 0))) {
+ syserrlog("file read");
+ return 'e';
+ }
+ t->buf.len = r;
+
+ oggread_open(&ogg, -1);
+ if (!(format = tag_ogg_vtag_read(&ogg, *(ffstr*)&t->buf, &page, &tags_page_off, &tags_page_num, &vtag, &vorbis_codebook)))
+ goto end;
+ vtags_len = vtag.len - ((format == 'v') ? 1 : 0);
+
+ if (tag_ogg_process(t, &vtw, vtag, format))
+ goto end;
+
// Prepare OGG packet with Opus/Vorbis header, tags data and padding
uint tags_len = vorbistagwrite_fin(&vtw).len;
int padding = vtags_len - tags_len;
@@ -629,12 +694,13 @@ static int tag_edit_process(struct tag_edit *t)
}
const char *ext = file_ext_str(fmt);
+ dbglog("%s: %s", t->conf.filename, ext);
if (ffsz_eq(ext, "mp3")) {
if (0 == (r = tag_mp3_id3v2(t)))
r = tag_mp3_id3v1(t);
} else if (ffsz_eq(ext, "ogg")) {
- r = tag_vorbis(t);
+ r = tag_ogg(t);
} else {
errlog("unsupported format");
From 623f325d314d06bc93442af494a0d2fd03da794a Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 11 Jan 2025 11:27:01 +0300
Subject: [PATCH 56/59] + `tag`: support .flac files
---
src/format/tag.c | 183 +++++++++++++++++++++++++++++++++++++++++++++++
test.sh | 10 ++-
2 files changed, 190 insertions(+), 3 deletions(-)
diff --git a/src/format/tag.c b/src/format/tag.c
index 82c74a6..8cb5a30 100644
--- a/src/format/tag.c
+++ b/src/format/tag.c
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
#include
#include
@@ -663,6 +664,185 @@ static int tag_ogg(struct tag_edit *t)
return rc;
}
+static int tag_flac_process(struct tag_edit *t, vorbistagwrite *vtw, ffstr vtags)
+{
+ ffstr *kv, k, v, k2, v2;
+ vorbistagread vtr = {};
+ u_char tags_added[_MMTAG_N] = {};
+
+ // Copy "Vendor" field
+ int tag = vorbistagread_process(&vtr, &vtags, &k, &v);
+ if (tag != MMTAG_VENDOR) {
+ errlog("parsing Vorbis tag");
+ return -1;
+ }
+ if (!vorbistagwrite_add(vtw, MMTAG_VENDOR, v))
+ dbglog("vorbistag: written vendor = %S", &v);
+ tags_added[MMTAG_VENDOR] = 1;
+
+ if (!t->conf.clear) {
+ // Replace tags, copy existing tags preserving the original order
+ for (;;) {
+ tag = vorbistagread_process(&vtr, &vtags, &k, &v);
+ if (tag == VORBISTAGREAD_DONE) {
+ break;
+ } else if (tag == VORBISTAGREAD_ERROR) {
+ errlog("parsing Vorbis tags");
+ return -1;
+ }
+
+ if (user_meta_find(&t->conf.meta, tag, &k2, &v2)) {
+ // Write user tag
+ if (!vorbistagwrite_add(vtw, tag, v2))
+ dbglog("vorbistag: written %S = %S", &k2, &v2);
+
+ } else {
+ // Copy existing tag
+ if (tag != 0) {
+ vorbistagwrite_add(vtw, tag, v);
+ } else {
+ // Unknown tag
+ vorbistagwrite_add_name(vtw, k, v);
+ }
+ }
+
+ tags_added[tag] = 1;
+ }
+ }
+
+ // Add new tags
+ FFSLICE_WALK(&t->conf.meta, kv) {
+ if (!kv->len)
+ continue;
+ tag = user_meta_split(*kv, &k, &v);
+ if (tag < 0)
+ return -1;
+ if (!tag)
+ continue;
+ if (tags_added[tag])
+ continue;
+
+ if (!vorbistagwrite_add(vtw, tag, v))
+ dbglog("vorbistag: written %S = %S", &k, &v);
+ }
+
+ return 0;
+}
+
+static int tag_flac(struct tag_edit *t)
+{
+ int rc = 'e', r;
+ ffstr input = {}, output;
+ uint64 tags_hdr_off = 0, padding_hdr_off = 0;
+ uint vtags_len = 0, padding_len, padding_last;
+ vorbistagwrite vtw = {
+ .left_zone = sizeof(struct flac_hdr),
+ };
+ vorbistagwrite_create(&vtw);
+ flacread fr = {};
+ flacread_open(&fr, 0);
+
+ ffvec_realloc(&t->buf, 64*1024, 1);
+ input = *(ffstr*)&t->buf;
+
+ for (;;) {
+ r = flacread_process(&fr, &input, &output);
+ switch (r) {
+ case FLACREAD_MORE:
+ if (0 >= (r = fffile_read(t->fd, t->buf.ptr, t->buf.cap))) {
+ if (r == 0)
+ errlog("bad FLAC file");
+ else
+ syserrlog("file read");
+ goto end;
+ }
+ t->buf.len = r;
+ input = *(ffstr*)&t->buf;
+ break;
+
+ case FLACREAD_HEADER:
+ break;
+
+ case FLACREAD_META_BLOCK:
+ dbglog("meta block %u", flacread_meta_type(&fr));
+ switch (flacread_meta_type(&fr)) {
+ case FLAC_TTAGS:
+ vtags_len = output.len;
+ tags_hdr_off = flacread_meta_offset(&fr);
+ if (tag_flac_process(t, &vtw, output))
+ goto end;
+ break;
+
+ case FLAC_TPADDING:
+ padding_last = fr.last_hdr_block;
+ padding_len = output.len;
+ padding_hdr_off = flacread_meta_offset(&fr);
+ goto fin;
+ }
+ break;
+
+ case FLACREAD_HEADER_FIN:
+ goto fin;
+
+ default:
+ errlog("flacread_process(): %u", r);
+ goto end;
+ }
+ }
+
+fin:
+ dbglog("tags @%U padding @%U", tags_hdr_off, padding_hdr_off);
+ if (!tags_hdr_off || !padding_hdr_off) {
+ errlog("Tags and padding regions must already exist");
+ goto end;
+ }
+ if (tags_hdr_off + sizeof(struct flac_hdr) + vtags_len != padding_hdr_off) {
+ errlog("Tags region must preceed padding region");
+ goto end;
+ }
+
+ uint new_tags_len = vorbistagwrite_fin(&vtw).len;
+ dbglog("old tags size:%u new size:%u", vtags_len, new_tags_len);
+
+ int padding = vtags_len + padding_len - new_tags_len;
+ if (padding < 0)
+ padding = 0;
+ ffvec_grow(&vtw.out, sizeof(struct flac_hdr) + padding, 1);
+ ffstr vt = *(ffstr*)&vtw.out;
+
+ flac_hdr_write(vt.ptr, FLAC_TTAGS, 0, new_tags_len);
+
+ vt.len += flac_hdr_write(vt.ptr + vt.len, FLAC_TPADDING, padding_last, padding);
+ ffmem_zero(vt.ptr + vt.len, padding);
+ vt.len += padding;
+
+ if (vt.len == sizeof(struct flac_hdr) * 2 + vtags_len + padding_len) {
+ // Rewrite tags and padding regions in-place
+ if (0 > (r = fffile_writeat(t->fdw, vt.ptr, vt.len, tags_hdr_off))) {
+ syserrlog("file write");
+ goto end;
+ }
+ t->written += r;
+ dbglog("written %L bytes @%U", vt.len, tags_hdr_off);
+
+ } else {
+ if (t->conf.no_expand) {
+ errlog("File rewrite is disabled");
+ goto end;
+ }
+
+ errlog("Expanding FLAC files is not implemented");
+ goto end;
+ }
+
+ rc = 0;
+
+end:
+ vorbistagwrite_destroy(&vtw);
+ flacread_close(&fr);
+ return rc;
+}
+
static int tag_edit_process(struct tag_edit *t)
{
int r;
@@ -702,6 +882,9 @@ static int tag_edit_process(struct tag_edit *t)
} else if (ffsz_eq(ext, "ogg")) {
r = tag_ogg(t);
+ } else if (ffsz_eq(ext, "flac")) {
+ r = tag_flac(t);
+
} else {
errlog("unsupported format");
r = -1;
diff --git a/test.sh b/test.sh
index a82b913..7186452 100644
--- a/test.sh
+++ b/test.sh
@@ -667,25 +667,29 @@ test_tag() {
ffmpeg -i tag.wav -y -c:a libmp3lame tag.mp3 2>/dev/null
./phiola co tag.wav -o .ogg
./phiola co tag.wav -o .opus
+ ./phiola co tag.wav -o .flac
fi
# add new tags
- ./phiola tag -m 'artist=Great Artist' -m 'title=Cool Song' tag.mp3 tag.ogg tag.opus
+ ./phiola tag -m 'artist=Great Artist' -m 'title=Cool Song' tag.mp3 tag.ogg tag.opus tag.flac
./phiola i tag.mp3 | grep "Great Artist - Cool Song"
./phiola i tag.ogg | grep "Great Artist - Cool Song"
./phiola i tag.opus | grep "Great Artist - Cool Song"
+ ./phiola i tag.flac | grep "Great Artist - Cool Song"
# replace tag
- ./phiola tag -m 'title=Very Cool Song' tag.mp3 tag.ogg tag.opus
+ ./phiola tag -m 'title=Very Cool Song' tag.mp3 tag.ogg tag.opus tag.flac
./phiola i tag.mp3 | grep "Great Artist - Very Cool Song"
./phiola i tag.ogg | grep "Great Artist - Very Cool Song"
./phiola i tag.opus | grep "Great Artist - Very Cool Song"
+ ./phiola i tag.flac | grep "Great Artist - Very Cool Song"
# set tag
- ./phiola tag -clear -m 'title=Cool Song' tag.mp3 tag.ogg tag.opus
+ ./phiola tag -clear -m 'title=Cool Song' tag.mp3 tag.ogg tag.opus tag.flac
./phiola i tag.mp3 | grep " - Cool Song"
./phiola i tag.ogg | grep " - Cool Song"
./phiola i tag.opus | grep " - Cool Song"
+ ./phiola i tag.flac | grep " - Cool Song"
}
test_clean() {
From 492705213c0bd48fe30d47ca310f610663fe59fb Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 11 Jan 2025 11:27:01 +0300
Subject: [PATCH 57/59] * CLI: all `-include/-exclude` options can be specified
multiple times
---
src/exe/convert.h | 4 ++--
src/exe/info.h | 4 ++--
src/exe/list.h | 4 ++--
src/exe/play.h | 4 ++--
src/exe/tag.h | 4 ++--
5 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/exe/convert.h b/src/exe/convert.h
index 01b3896..bf19471 100644
--- a/src/exe/convert.h
+++ b/src/exe/convert.h
@@ -298,11 +298,11 @@ static const struct ffarg cmd_conv[] = {
{ "-cpu_affinity", 'S', conv_cpu_affinity },
{ "-cue_gaps", 'S', conv_cue_gaps },
{ "-danorm", 's', O(danorm) },
- { "-exclude", 'S', conv_exclude },
+ { "-exclude", '+S', conv_exclude },
{ "-force", '1', O(force) },
{ "-gain", 'd', O(gain) },
{ "-help", 0, conv_help },
- { "-include", 'S', conv_include },
+ { "-include", '+S', conv_include },
{ "-meta", '+S', conv_meta },
{ "-o", 's', O(output) },
{ "-opus_mode", 's', O(opus_mode) },
diff --git a/src/exe/info.h b/src/exe/info.h
index 252d01f..b74e05c 100644
--- a/src/exe/info.h
+++ b/src/exe/info.h
@@ -125,9 +125,9 @@ static int info_check(struct cmd_info *p)
#define O(m) (void*)FF_OFF(struct cmd_info, m)
static const struct ffarg cmd_info[] = {
{ "-duration", '1', O(duration) },
- { "-exclude", 'S', info_exclude },
+ { "-exclude", '+S', info_exclude },
{ "-help", 0, info_help },
- { "-include", 'S', info_include },
+ { "-include", '+S', info_include },
{ "-loudness", '1', O(loudness) },
{ "-peaks", '1', O(pcm_peaks) },
{ "-perf", '1', O(perf) },
diff --git a/src/exe/list.h b/src/exe/list.h
index 5b962de..d8d2254 100644
--- a/src/exe/list.h
+++ b/src/exe/list.h
@@ -82,9 +82,9 @@ static int lc_fin(struct list_create *lc)
#define O(m) (void*)FF_OFF(struct list_create, m)
static const struct ffarg list_create_args[] = {
- { "-exclude", 'S', lc_exclude },
+ { "-exclude", '+S', lc_exclude },
{ "-help", '1', lc_help },
- { "-include", 'S', lc_include },
+ { "-include", '+S', lc_include },
{ "-out", 's', O(output) },
{ "\0\1", 's', lc_input },
{ "", '1', lc_fin }
diff --git a/src/exe/play.h b/src/exe/play.h
index ec9c129..aecde65 100644
--- a/src/exe/play.h
+++ b/src/exe/play.h
@@ -206,10 +206,10 @@ static const struct ffarg cmd_play[] = {
{ "-connect_timeout", 'u', O(connect_timeout) },
{ "-device", 'u', O(device) },
{ "-dup", 's', O(dup) },
- { "-exclude", 'S', play_exclude },
+ { "-exclude", '+S', play_exclude },
{ "-exclusive", '1', O(exclusive) },
{ "-help", 0, play_help },
- { "-include", 'S', play_include },
+ { "-include", '+S', play_include },
{ "-no_meta", '1', O(no_meta) },
{ "-norm", 's', O(auto_norm) },
{ "-perf", '1', O(perf) },
diff --git a/src/exe/tag.h b/src/exe/tag.h
index a1e08d0..0601663 100644
--- a/src/exe/tag.h
+++ b/src/exe/tag.h
@@ -202,10 +202,10 @@ static int tag_prepare(struct cmd_tag *t)
#define O(m) (void*)FF_OFF(struct cmd_tag, m)
static const struct ffarg cmd_tag_args[] = {
{ "-clear", '1', O(clear) },
- { "-exclude", 'S', tag_exclude },
+ { "-exclude", '+S', tag_exclude },
{ "-fast", '1', O(fast) },
{ "-help", 0, tag_help },
- { "-include", 'S', tag_include },
+ { "-include", '+S', tag_include },
{ "-meta", '+S', tag_meta },
{ "-preserve_date", '1', O(preserve_date) },
{ "-rg", 's', tag_replay_gain },
From db48be14d1046f68a2349d596010dcb0e7991dfe Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 11 Jan 2025 11:27:01 +0300
Subject: [PATCH 58/59] minor
---
README.md | 4 ++--
src/afilter/pcm.h | 1 +
src/afilter/pcm_convert.h | 23 +++++++++++++++++++++++
src/core/queue-entry.h | 1 -
src/core/queue.c | 2 +-
src/exe/convert.h | 1 +
src/exe/info.h | 1 +
src/exe/tag.h | 1 +
src/format/meta.h | 2 +-
src/gui/convert.hpp | 2 ++
src/jni/queue.h | 2 ++
src/phiola.h | 5 ++++-
12 files changed, 39 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index d6d6dbb..e40bd56 100644
--- a/README.md
+++ b/README.md
@@ -32,9 +32,9 @@ Contents:
* Play audio: `.mp3`, `.ogg`(Vorbis/Opus), `.mp4`/`.mov`(AAC/ALAC/MP3), `.mkv`/`.webm`(AAC/ALAC/MP3/Vorbis/Opus/PCM), `.caf`(AAC/ALAC/PCM), `.avi`(AAC/MP3/PCM), `.ts`(AAC/MP3), `.aac`, `.mpc`; `.flac`, `.ape`, `.wv`, `.wav`.
* Record audio: `.m4a`(AAC), `.ogg`, `.opus`; `.flac`, `.wav`
* Convert audio
-* List/search file meta tags; edit file tags, write ReplayGain tags (.mp3, .ogg, .opus)
+* List/search file meta tags; edit file tags, write ReplayGain tags (.mp3, .ogg, .opus, .flac)
* List available audio devices
-* Input: file, directory, HTTP/HTTPS URL, console (stdin), playlists: `.m3u`, `.pls`, `.cue`
+* Input: file, directory, ICY/HLS/HTTP/HTTPS URL, console (stdin), playlists: `.m3u`, `.pls`, `.cue`
* Command Line Interface for Desktop OS
* Terminal/Console UI for interaction at runtime
* GUI for Windows, Linux, Android
diff --git a/src/afilter/pcm.h b/src/afilter/pcm.h
index 14775c9..ce2b34e 100644
--- a/src/afilter/pcm.h
+++ b/src/afilter/pcm.h
@@ -9,6 +9,7 @@ union pcmdata {
short *sh;
int *in;
float *f;
+ double *d;
u_char **pub;
char **pb;
diff --git a/src/afilter/pcm_convert.h b/src/afilter/pcm_convert.h
index 9c5a440..f54129e 100644
--- a/src/afilter/pcm_convert.h
+++ b/src/afilter/pcm_convert.h
@@ -283,6 +283,29 @@ static int _pcm_chan_mix(uint ochan, void *odata, const struct phi_af *inpcm, co
}
break;
+ case PHI_PCM_FLOAT64:
+ for (oc = 0; oc != 8; oc++) {
+
+ if (!ffbit_test32(&omask, oc))
+ continue;
+
+ for (i = 0; i != samples; i++) {
+ double sum = 0;
+ uint icstm = 0;
+ for (ic = 0; ic != 8; ic++) {
+ if (!ffbit_test32(&imask, ic))
+ continue;
+ sum += in.pd[icstm][i * istep] * level[oc][ic];
+ icstm++;
+ }
+ out.d[ocstm + i * ostep] = pcm_limf(sum);
+ }
+
+ if (++ocstm == ochan)
+ break;
+ }
+ break;
+
default:
return -1;
}
diff --git a/src/core/queue-entry.h b/src/core/queue-entry.h
index e6b0ed0..499f9dd 100644
--- a/src/core/queue-entry.h
+++ b/src/core/queue-entry.h
@@ -127,7 +127,6 @@ static int qe_play(struct q_entry *e)
c.ifile.name = e->pub.url;
c.seek_cdframes = e->pub.seek_cdframes;
c.until_cdframes = e->pub.until_cdframes;
- c.cross_worker_assign = e->q->conf.conversion;
const phi_filter *ui_if = (e->q->conf.ui_module_if_set) ? e->q->conf.ui_module_if : core->mod(e->q->conf.ui_module);
const phi_track_if *track = core->track;
diff --git a/src/core/queue.c b/src/core/queue.c
index 83b2357..ec4c1f9 100644
--- a/src/core/queue.c
+++ b/src/core/queue.c
@@ -309,7 +309,7 @@ static int q_play(struct phi_queue *q, void *_e)
q = e->q;
}
- if (q->conf.conversion) {
+ if (q->conf.tconf.cross_worker_assign) {
for (;;) {
q->cursor = e;
q->cursor_index = qe_index(e);
diff --git a/src/exe/convert.h b/src/exe/convert.h
index bf19471..4ea8c11 100644
--- a/src/exe/convert.h
+++ b/src/exe/convert.h
@@ -243,6 +243,7 @@ static int conv_action(struct cmd_conv *v)
},
.stream_copy = v->copy,
.print_time = v->perf,
+ .cross_worker_assign = 1,
};
cmd_meta_set(&c.meta, &v->meta);
ffvec_free(&v->meta);
diff --git a/src/exe/info.h b/src/exe/info.h
index b74e05c..560d51c 100644
--- a/src/exe/info.h
+++ b/src/exe/info.h
@@ -101,6 +101,7 @@ static int info_action(struct cmd_info *p)
.analyze = 1,
};
x->queue->create(&qc);
+
ffstr *it;
FFSLICE_WALK(&p->input, it) {
struct phi_queue_entry qe = {
diff --git a/src/exe/tag.h b/src/exe/tag.h
index 0601663..4484d96 100644
--- a/src/exe/tag.h
+++ b/src/exe/tag.h
@@ -152,6 +152,7 @@ static int tag_action(struct cmd_tag *t)
.exclude = *(ffslice*)&t->exclude,
},
.afilter.loudness_summary = 1,
+ .cross_worker_assign = 1,
};
struct phi_queue_conf qc = {
diff --git a/src/format/meta.h b/src/format/meta.h
index fa3501d..94d6845 100644
--- a/src/format/meta.h
+++ b/src/format/meta.h
@@ -103,7 +103,7 @@ static int meta_find(const phi_meta *meta, ffstr name, ffstr *val, uint flags)
uint i = 0;
ffstr n, v;
while (meta_list(meta, &i, &n, &v, flags)) {
- if (ffstr_eq2(&n, &name)) {
+ if (ffstr_ieq2(&n, &name)) {
phi_dbglog(core, NULL, NULL, "meta requested: %S = %S", &n, &v);
*val = v;
return 0;
diff --git a/src/gui/convert.hpp b/src/gui/convert.hpp
index f885504..aad6be7 100644
--- a/src/gui/convert.hpp
+++ b/src/gui/convert.hpp
@@ -206,6 +206,8 @@ static struct phi_track_conf* conv_conf_create()
tc->ifile.preserve_date = c->cbkeepdate.checked();
tc->ofile.overwrite = c->cboverwrite.checked();
tc->ofile.name = ffsz_allocfmt("%S/%S.%S", &c->conf_dir, &c->conf_name, &c->conf_ext);
+
+ tc->cross_worker_assign = 1;
return tc;
}
diff --git a/src/jni/queue.h b/src/jni/queue.h
index 33e2929..78db53e 100644
--- a/src/jni/queue.h
+++ b/src/jni/queue.h
@@ -552,6 +552,8 @@ Java_com_github_stsaz_phiola_Phiola_quConvertBegin(JNIEnv *env, jobject thiz, jl
.ofile.name = ffsz_dup(ofn),
.ofile.name_tmp = 1,
.ofile.overwrite = !!(flags & F_OVERWRITE),
+
+ .cross_worker_assign = 1,
};
if (msec_apos(from, (int64*)&conf.seek_msec)) {
diff --git a/src/phiola.h b/src/phiola.h
index 03b58c2..36042b2 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -377,7 +377,8 @@ enum PHI_META_LIST {
};
enum PHI_META_SET {
- PHI_META_REPLACE = 1, // replace existing key-value pair
+ // PHI_META_UNIQUE
+ PHI_META_REPLACE = 4, // replace existing key-value pair
};
typedef struct phi_meta_if phi_meta_if;
@@ -387,6 +388,8 @@ struct phi_meta_if {
flags: enum PHI_META_SET */
void (*set)(phi_meta *meta, ffstr name, ffstr val, uint flags);
+ /**
+ flags: enum PHI_META_SET */
void (*copy)(phi_meta *dst, const phi_meta *src, uint flags);
/**
From f3569add8deb76e6e4820743ce61e7424a417e33 Mon Sep 17 00:00:00 2001
From: Simon Zolin <4729655+stsaz@users.noreply.github.com>
Date: Sat, 11 Jan 2025 11:27:01 +0300
Subject: [PATCH 59/59] 2.3-beta5
---
android/phiola/build.gradle | 4 ++--
src/phiola.h | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/android/phiola/build.gradle b/android/phiola/build.gradle
index 8394b72..122474f 100644
--- a/android/phiola/build.gradle
+++ b/android/phiola/build.gradle
@@ -8,8 +8,8 @@ android {
applicationId "com.github.stsaz.phiola"
minSdkVersion 16
targetSdk 33
- versionCode 20304
- versionName '2.3-beta4'
+ versionCode 20305
+ versionName '2.3-beta5'
}
buildFeatures {
diff --git a/src/phiola.h b/src/phiola.h
index 36042b2..6b8353b 100644
--- a/src/phiola.h
+++ b/src/phiola.h
@@ -6,13 +6,13 @@
#include
#include
-#define PHI_VERSION 20304
+#define PHI_VERSION 20305
/** Inter-module compatibility version.
It must be updated when incompatible changes are made to this file,
then all modules must be rebuilt.
The core will refuse to load modules built for any other core version. */
-#define PHI_VERSION_CORE 20304
+#define PHI_VERSION_CORE 20305
typedef long long int64;
typedef unsigned long long uint64;