duangsuse::Echo
719 subscribers
4.28K photos
130 videos
583 files
6.49K links
import this:
美而不丑、明而不暗、短而不凡、长而不乱,扁平不宽,读而后码,行之天下,勿托地上天国。
异常勿吞,难过勿过,叹一真理。效率是很重要,盲目最是低效。
简明是可靠的先验,不是可靠的祭品。
知其变,守其恒,为天下式;穷其变,知不穷,得地上势。知变守恒却穷变知新,我认真理,我不认真。

技术相干订阅~
另外有 throws 闲杂频道 @dsuset
转载频道 @dsusep
极小可能会有批评zf的消息 如有不适可退出
suse小站(面向运气编程): https://WOJS.org/#/
Download Telegram
duangsuse::Echo
最后就是更新不知道是什么的数据和发通知了(都是 service 里喽) if (Build.SDK_INT >= 29) { ContentValues values = new ContentValues(); values.put(MediaStore.Images.ImageColumns.IS_PENDING, false); cr.update(uri, values, null, null); } 🤔 [img]: false 是啥意思... 难不成对应 true 还有别的行为 …
讲完了。总结一下梗概:虽然是一个小应用 (onHandleIntent, onSave, doSave),但 notification 部分还是很多代码的……
如果可能的话,我觉得这一部分应该抽象成 ScheduleNotificationIntentService 毕竟 onCreate 里几乎没有无关代码

Intent, Bundle, Uri; Message, Handler, Looper; app.Notification, text.Html 
ContentSolver, ContentValues, database.Cursor
os.Build; os.Environment.DIR_DOWNLOADS

content.ParcelFileDescriptor
provider.MediaStore , provider.OpenableColumns


File, InputStream, OutputStream, IOException
List, Map/*unused*/, ArrayList
#task 我来总结一下被鸽子的小项目:
LiterateKt (怎么连 class Project(File, Config) 的子项目树模型都总结不出来…… 还总是在 Markdown 语法上纠缠,用 java.util.regex.Pattern 不就可以了,项目都可 # Include,root项目可以有 # LiterateKt )
Bank Editor (稍后会有重写之前的 Qt 周期执行小应用,就是 QSettingsQTimer ,顺便包括 rectangle(0,0,p*width,height) 的自定义绘制,signal/slot 模型的基础使用嘛…… 不懂得怎么抽提出子程序)
(稍后也会想复制那个 sin wave 用到 QAudioDevice 的小程序)
(稍后还会有一个简单的 GTK# TreeView 示例 )
audio_output.pro
TARGET = audio_output
QT += widgets multimedia

SOURCES += main.cpp audio_output.coo
HEADERS += primitive_data.hpp helper.hpp audio_output.hpp

target.path = /bin/
INSTALLS += target

那么,架构器和预逻辑
AudioGenerator::AudioGenerator(QAudioFormat format, usec durationUs, cnt sampleRate): format(format), durationUs(durationUs), sampleRate(sampleRate) {
require(format.isValid(), "invalid format");
generateData();
}

void AudioGenerator::start() { open(QIODevice::ReadOnly); }
void AudioGenerator::stop() { position = 0; close(); }

然后我们看看生成波形数据的。
void AudioGenerator::generateData()
{
int bytePerSample = format.sampleSize() / BYTE_BITS;
int bytePerSec = format.sampleRate() * bytePerSample;
// [sample]: (rate * size) * n_channel
cnt n_byte = format.channelCount() * (this->durationUs / SEC_US * bytePerSec);
Q_ASSERT(n_byte % bytePerSec == 0);

audioBuffer.resize(n_byte);

byte* ys = reinterpret_cast<byte*>(audioBuffer.data());
for (idx i=0, x=0; i != n_byte; i += bytePerSample, x += 1) {
const qreal y = waveY(x);
for (int _t=0; _t<format.channelCount(); _t++) { writeSample(ys, i, y); }
}
}
qreal AudioGenerator::waveY(unsigned x) {
return qCos(2 * M_PI * sampleRate * qreal(x % format.sampleRate()) / format.sampleRate());
}


那么整个类余下的定义
class AudioGenerator: public QIODevice
{ Q_OBJECT
private:
idx position = 0;
QByteArray audioBuffer;
void generateData();
qreal waveY(int x);
void writeUInt16(byte* dst, quint16 v);
void writeSample(byte* ys, idx i, qreal y);

private:
QAudioFormat format;
long durationUs;
int sampleRate;


还有 overrides:
public:
longcnt readData(byte* dst, longcnt max_len) override;
longcnt writeData(const byte* src, longcnt len) override;
longcnt bytesAvailable() const override;
它们(readData, writeData, bytesAvailable)的实现则是
longcnt AudioGenerator::readData(char* data, longcnt len)
{
if (audioBuffer.isEmpty()) return 0;
cnt pos = 0;
for (cnt nChunk; pos < len; pos += nChunk) {
cnt nBufferRest = audioBuffer.size() - position;
nChunk = qMin(cnt(len) - pos, nBufferRest);
memcpy(data+pos, audioBuffer.constData()+position, nChunk);
position = coerceCycle(position + nChunk, cnt(audioBuffer.size()));
}
return pos; //count read
}

下面两个不重要, write 直接返回; bytesAvailable 直接 return super() + audioBuffer.length
qint64 AudioGenerator::writeData(const char *data, qint64 len)
{
Q_UNUSED(data); Q_UNUSED(len);
return 0;
}

qint64 AudioGenerator::bytesAvailable() const
{
return audioBuffer.size() + QIODevice::bytesAvailable();
}
这样是不是就很好理解了? n_chunk = qMin(lenv - pos, waveLen - wavePos)
auto AudioGenerator::writeUInt16(byte* dst, quint16 v) -> void {
if (this->format.byteOrder() == QAudioFormat::LittleEndian) qToLittleEndian(v, dst);
else qToBigEndian(v, dst);
};

auto AudioGenerator::writeSample(byte* ys, idx i, qreal y) -> void {
auto sampleType = format.sampleType();
switch (format.sampleSize()) {
case BYTE_BITS:
if (sampleType == QAudioFormat::UnSignedInt) {
ys[i] = static_cast<u8>((1.0 + y) / 2 * QUINT8_MAX);
} else if (format.sampleType() == QAudioFormat::SignedInt) {
ys[i] = static_cast<i8>(y * QINT8_MAX);
} else unsupported();
break;
case SHORT_BITS:
if (sampleType == QAudioFormat::UnSignedInt) {
writeUInt16(&ys[i], static_cast<u16>((1.0 + y) / 2 * QUINT16_MAX));
} else if (format.sampleType() == QAudioFormat::SignedInt) {
writeUInt16(&ys[i], static_cast<i16>(y * QINT16_MAX));
} else unsupported();
break;
default:
unsupported();
break;
}
}
最后发完这两个,就算是说完了……欸 其实我什么也没说,这个有啥意思…… 不该发
void writeUInt16(byte*dst, i16 v);
void writeSample(byte* ys, idx i, qreal y);

说实话这根本不应该放在一个应用类里吧……
不对,这人写的什么代码, if (a != 1) op(a); if (a != 0) break; ,什么鬼啊这是
#Qt 🤔 啊,我才知道 QTimer 默认就是 scheduleAtFixedRate ,而不需要为非 singleShot 的情况额外编程的,只需 timer->start(rate); timer->remainingTime() 就好了,真方便
template <typename T>
void prints(T obj) { cout << obj << endl; }
template <typename... Arg>
void prints(T obj, Arg... args) { cout << obj; prints(args); }

void prints(std::initializer_list texts) {
for (auto text : texts) cout << text;
cout << endl;
}
PREETY_FUNCTION — StackOverflow
大概就是这样,改完了但是有 bug 没法修,毕竟也就是个学习。
audio_output
68.9 KB
#Linux #qt #signal #math 公式是 qCos(2*M_PI* qreal(x) / format.sampleRate() * pitch) 其中 x 是 (i % format.sampleRate())
不写了不写了…… 不就是个 QSettings 和 QTimer 还有 Gtk# 的 TreeView 使用么…… 模型都是简单的,再复杂不过一个 table grid 而已

来看看别的

from re import compile
PAT_LRC_ENTRY = compile(r"[\[<](\d{2}):(\d{2}).(\d{2})[>\]] ?([^<\n]*)")
def readLrc(text):
def readEntry(g): return (int(g[0])*60 + int(g[1]) + int(g[2]) / 100, g[3]) # [mm:ss.xx] content
return [readEntry(e) for e in PAT_LRC_ENTRY.findall(text)]


def dumpLrc(lrc_lines):
def header(t, surr="[]"): return "%s%02i:%02i.%02i%s" %(surr[0], t/60, t%60, t%1.0 * 100, surr[1])
return "\n".join([header(lrcs[0][0]) + lrcs[0][1] + " ".join([header(t, "<>") + s for (t, s) in lrcs[1:]]) for lrcs in lrc_lines])


嗯……然后我们想弄一个能组合歌词的程序,比如这里有一个示例数据
[00:32.96]怎<00:33.12>么<00:33.36>大<00:33.76>风<00:34.16>越<00:34.56>狠<00:36.16>我<00:36.56>心<00:36.96>越<00:37.36>荡<00:39.36>幻<00:39.52>如<00:39.68>一<00:40.08>丝<00:40.32>尘<00:40.72>土<00:41.12>随<00:41.28>风<00:41.52>自<00:41.92>由<00:42.16>的<00:42.56>在<00:43.76>狂<00:44.16>舞<00:45.76>我<00:45.92>要<00:46.08>握<00:46.32>紧<00:46.72>手<00:47.12>中<00:47.36>坚<00:47.52>定<00:48.96>却<00:49.12>又<00:49.36>飘<00:49.76>散<00:50.16>的<00:50.56>勇<00:50.96>气<00:52.24>我<00:52.40>会<00:52.56>变<00:52.96>成<00:53.36>巨<00:53.52>人<00:54.16>踏<00:54.40>着<00:54.56>力<00:54.72>气<00:54.96>踩<00:55.36>着<00:55.76>梦

然后我们有楼上的 zipWithNext 和 zipTakeWhile 实现
等我弄完
写好啦,那个 zipTakeWhile 居然还浪费了我好长时间调试…… 居然没一遍写对,当然也不能怪我,这个不是流操作的流操作 和 zip 操作本身对定义初始情况的要求经常容易忘记
lrc_merge.py
1.2 KB
#Python #code #music #tools 这个工具可以用来合并简板的 lyrics ,如果你的 lyrics 是自动生成的,没有带好行号,它可以自动从两个字的间距算出歌词行号。
duangsuse::Echo
🤤 Sticker
这个小工具也写了我一个小时…… 不过还好,有了它就可以避免没歌词的麻烦了
……这个该死的 Python regex 怎么连 findall 都有限制,我只好分行来读取了,emmm
This media is not supported in your browser
VIEW IN TELEGRAM
list 又没有 flatten 又没有 flatMap ,这种烂代码让人怎么活啊……
itertools.starmap 也根本不可以用,我不是要给 transform 函数传 vararg…… 我是要给 result flatten
准备加入 Hachiko 豪华套餐(迫真)
duangsuse::Echo
准备加入 Hachiko 豪华套餐(迫真)
因为 smf-to-lrc 项目是 Kotlin 写的,不太容易要求用户有 Kotlin 开发环境也不好直接发布 jar 包,公开作为私用脚本算了…… 有心人自然会发现该依赖啥项目,然后具体怎么用
pypi 里都没有一个好的 lrc 解析器,唉,可怜