diff --git a/include/lgi/common/AudioView.h b/include/lgi/common/AudioView.h --- a/include/lgi/common/AudioView.h +++ b/include/lgi/common/AudioView.h @@ -1,1916 +1,1916 @@ /// Audio waveform display control #pragma once #include "lgi/common/Layout.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Menu.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Thread.h" #include "lgi/common/ThreadEvent.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/DropFiles.h" #include "lgi/common/PopupNotification.h" #include "ao.h" #define CC(code) LgiSwap32(code) -#define ERROR_ZERO_SAMPLE_COUNT 10 +#define ERROR_ZERO_SAMPLE_COUNT 5 struct int24_t { int32_t s : 24; }; union sample_t { int8_t *i8; int16_t *i16; int24_t *i24; int32_t *i32; float *f; sample_t(void *ptr = NULL) { *this = ptr; } sample_t &operator=(void *ptr) { i8 = (int8_t*)ptr; return *this; } }; template struct AvRect { T x1 = 0, y1 = 0, x2 = 0, y2 = 0; AvRect &operator =(const LRect &r) { x1 = r.x1; y1 = r.y1; x2 = r.x2; y2 = r.y2; return *this; } T X() { return x2 - x1 + 1; } T Y() { return y2 - y1 + 1; } bool Valid() { return x2 >= x1 && y2 >= y1; } operator LRect() { LRect r((int)x1, (int)y1, (int)x2, (int)y2); return r; } void ZOff(T sx, T sy) { x1 = y1 = 0; x2 = sx; y2 = sy; } const char *GetStr() { static char s[96]; sprintf_s(s, sizeof(s), LPrintfInt64 "," LPrintfInt64 "," LPrintfInt64 "," LPrintfInt64, x1, y1, x2, y2); return s; } AvRect &Offset(T x, T y) { x1 += x; x2 += x; y1 += y; y2 += y; return *this; } }; typedef int32_t (*ConvertFn)(sample_t &ptr); class LAudioView : public LLayout, public LThread, public LCancel, public LDragDropTarget { public: constexpr static int DefaultBorder = 10; // px enum LFileType { AudioUnknown, AudioRaw, AudioWav, AudioAif, }; enum LSampleType { AudioS8, AudioS16LE, AudioS16BE, AudioS24LE, AudioS24BE, AudioS32LE, AudioS32BE, AudioFloat32, }; enum Messages { M_WAVE_FORMS_FINISHED = M_USER + 100, M_SAVE_FINISHED, }; struct LBookmark { int x = 0; bool error = false; int64_t sample; LColour colour; }; protected: enum LCmds { IDC_COPY_CURSOR, IDC_LOAD_WAV, IDC_LOAD_RAW, IDC_SAVE_WAV, IDC_SAVE_RAW, IDC_DRAW_AUTO, IDC_DRAW_LINE, IDC_SCAN_SAMPLES, IDC_SCAN_GROUPS, IDC_44K, IDC_48K, IDC_88K, IDC_96K, IDC_MONO, IDC_STEREO, IDC_2pt1_CHANNELS, IDC_5pt1_CHANNELS, }; enum LDrawMode { DrawAutoSelect, DrawSamples, ScanSamples, ScanGroups, }; enum CtrlState { CtrlNormal, CtrlLoading, CtrlBuildingWaveforms, CtrlSaving, } State = CtrlNormal; LArray Audio; LFileType Type = AudioUnknown; LSampleType SampleType = AudioS16LE; uint32_t SampleBits = 0; uint32_t SampleRate = 0; uint32_t Channels = 0; size_t DataStart = 0; int64_t CursorSample = 0; AvRect Data; LArray> ChData; double XZoom = 1.0; LString Msg, ErrorMsg; LDrawMode DrawMode = DrawAutoSelect; LArray> Bookmarks; LString FilePath; template struct Grp { T Min = 0, Max = 0; }; LArray>> IntGrps; LArray>> FloatGrps; // Libao stuff int AoDriver = -1; bool AoPlaying = false; ao_device *AoDev = NULL; LThreadEvent AoEvent; void MouseToCursor(LMouse &m) { auto idx = ViewToSample(m.x); if (idx != CursorSample) { CursorSample = idx; UpdateMsg(); Invalidate(); } } template void SetData(T &r) { Data = r; ChData.Length(Channels); auto Dy = (int)Data.Y() - 1; for (uint32_t i=0; iMsg()) { case M_WAVE_FORMS_FINISHED: OnWaveFormsFinished(); break; } return LView::OnEvent(m); } int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override { Formats.SupportsFileDrops(); return DROPEFFECT_COPY; } int OnDrop(LArray &Data, LPoint Pt, int KeyState) override { for (auto &d : Data) { if (d.IsFileDrop()) { LDropFiles df(d); auto w = GetWindow(); if (w) { w->OnReceiveFiles(df); return DROPEFFECT_COPY; } } } return DROPEFFECT_NONE; } bool Empty() { Audio.Free(); Type = AudioUnknown; SampleType = AudioS16LE; SampleRate = 0; SampleBits = 0; DataStart = 0; Channels = 0; CursorSample = 0; Data.ZOff(-1, -1); ChData.Empty(); Msg.Empty(); FilePath.Empty(); IntGrps.Empty(); Bookmarks.Empty(); if (AoPlaying) { AoPlaying = false; LSleep(50); } if (AoDev) { ao_close(AoDev); AoDev = NULL; } Invalidate(); return false; } union AifType { uint32_t id; char str[4]; AifType(uint32_t init) { id = init; } bool Is(uint32_t atom) { return LgiSwap32(id) == atom; } }; bool ParseAif() { LPointer p; p.s8 = Audio.AddressOf(); auto end = p.s8 + Audio.Length(); while (p.s8 < end) { AifType Id = *p.u32++; auto Sz = *p.u32++; Sz = LgiSwap32(Sz); // printf("Id='%4.4s' Sz=%i\n", &Id, Sz); if (Id.Is('FORM')) { Id = *p.u32++; if (!Id.Is('AIFF')) return false; } else if (Id.Is('COMT')) { p.u8 += Sz + (Sz % 2); } else if (Id.Is('CHAN')) { p.u8 += Sz + (Sz % 2); } else if (Id.Is('COMM')) { auto channels = *p.u16++; Channels = LgiSwap16(channels); p.u32++; // long numSampleFrames uint16_t bits = *p.u16++; SampleBits = LgiSwap16(bits); uint32_t exp = ((int)p.u8[0]<<8) + p.u8[1]; //first 16 bits exp = exp - 0x3fff; uint32_t man = ((int)p.u8[2] << 24) + ((int)p.u8[3] << 16) + ((int)p.u8[4] << 8) + p.u8[5]; //bits 16..47 SampleRate = (uint32_t) (man >> (0x1f - exp)); p.s8 += 10; if (SampleBits == 16) SampleType = AudioS16BE; else if (SampleBits == 24) SampleType = AudioS24BE; else if (SampleBits == 32) SampleType = AudioS32BE; printf("Channels=%i, Bits=%i, Rate=%i\n", Channels, SampleBits, SampleRate); } else if (Id.Is('SSND')) { p.u32 += 2; DataStart = p.s8 - Audio.AddressOf(); return true; } else { printf("%s:%i - Unexpected atom '%4.4s'\n", _FL, Id.str); return false; } } return false; } bool ParseWav() { // Parse the wave file... LPointer p; p.s8 = Audio.AddressOf(); auto end = p.s8 + Audio.Length(); if (*p.u32++ != CC('RIFF')) return Empty(); auto ChunkSz = *p.u32++; auto Fmt = *p.u32++; while (p.s8 < end) { auto SubChunkId = *p.u32++; auto SubChunkSz = *p.u32++; auto NextChunk = p.u8 + SubChunkSz; if (SubChunkId == CC('fmt ')) { auto AudioFmt = *p.u16++; Channels = *p.u16++; SampleRate = *p.u32++; auto ByteRate = *p.u32++; auto BlockAlign = *p.u16++; SampleBits = *p.u16++; if (SampleBits == 16) SampleType = AudioS16LE; else if (SampleBits == 24) SampleType = AudioS24LE; else if (SampleBits == 32) SampleType = AudioS32LE; printf("Channels=%i\n", Channels); printf("SampleRate=%i\n", SampleRate); printf("SampleBits=%i\n", SampleBits); } else if (SubChunkId == CC('data')) { DataStart = p.s8 - Audio.AddressOf(); printf("DataStart=" LPrintfSizeT "\n", DataStart); break; } p.u8 = NextChunk; } if (!DataStart) { ErrorMsg = "No 'data' element found."; LPopupNotification::Message(GetWindow(), ErrorMsg); return Empty(); } return true; } bool Load(const char *FileName, int rate = 0, int bitDepth = 0, int channels = 0) { LFile f(FileName, O_READ); if (!f.IsOpen()) { ErrorMsg.Printf("Can't open '%s' for reading.", FileName); return false; } Empty(); Msg.Printf("Loading '%s'...", FileName); LPopupNotification::Message(GetWindow(), Msg); FilePath = FileName; if (!Audio.Length(f.GetSize())) { ErrorMsg.Printf("Can't allocate %s.", LFormatSize(f.GetSize()).Get()); return Empty(); } auto rd = f.Read(Audio.AddressOf(), f.GetSize()); if (rd > 0 && rd != f.GetSize()) Audio.Length(rd); auto Ext = LGetExtension(FileName); if (!Stricmp(Ext, "wav")) Type = AudioWav; else if (!Stricmp(Ext, "aif")) Type = AudioAif; else if (!Stricmp(Ext, "raw")) Type = AudioRaw; else { ErrorMsg.Printf("Unknown format: %s", Ext); LPopupNotification::Message(GetWindow(), ErrorMsg); return Empty(); } if (rate) SampleRate = rate; DataStart = 0; SampleBits = bitDepth; Channels = channels; if (bitDepth == 16) { SampleType = AudioS16LE; } else if (bitDepth == 32) { SampleType = AudioS32LE; } else if (Type == AudioWav) { if (!ParseWav()) return false; } else if (Type == AudioAif) { if (!ParseAif()) return false; } else if (Type == AudioRaw) { // Assume 32bit SampleType = AudioS32LE; SampleBits = 32; } if (!SampleRate) SampleRate = 44100; if (!Channels) Channels = 2; Invalidate(); LPopupNotification::Message(GetWindow(), "Loaded ok..."); return true; } protected: struct SaveThread : public LThread { LAudioView *view; LString ErrorMsg; LString FileName; SaveThread(LAudioView *v, const char *filename) : LThread("SaveThread", v->AddDispatch()) { view = v; FileName = filename; LString Msg; Msg.Printf("Saving '%s'...", FileName.Get()); LPopupNotification::Message(view->GetWindow(), Msg); Run(); } ~SaveThread() { WaitForExit(); } void OnComplete() { if (ErrorMsg) LPopupNotification::Message(view->GetWindow(), ErrorMsg); else LPopupNotification::Message(view->GetWindow(), "Saved ok."); view->State = CtrlNormal; view->Saving.Release(); // it doesn't own the ptr anymore DeleteOnExit = true; } int Main() { LFile f; if (!f.Open(FileName, O_WRITE)) { ErrorMsg.Printf("Can't open '%s' for writing.", FileName.Get()); return -1; } f.SetSize(0); auto wr = f.Write(view->Audio.AddressOf(0), view->Audio.Length()); if (wr != view->Audio.Length()) { ErrorMsg.Printf("Write error: only wrote " LPrintfSizeT " of " LPrintfSizeT " bytes", wr, view->Audio.Length()); return -1; } return 0; } }; LAutoPtr Saving; void Save(const char *FileName = NULL) { if (!FileName) FileName = FilePath; if (!FileName) return; Saving.Reset(new SaveThread(this, FileName)); } LRect DefaultPos() { auto c = GetClient(); c.y1 += LSysFont->GetHeight(); c.Inset(DefaultBorder, DefaultBorder); return c; } size_t GetSamples() { if (Audio.Length() == 0 || SampleBits == 0 || Channels == 0) return 0; return (Audio.Length() - DataStart) / (SampleBits >> 3) / Channels; } int SampleToView(size_t idx) { return (int) (Data.x1 + ((idx * Data.X()) / GetSamples())); } size_t ViewToSample(int x /*px*/) { int64_t offset = (int64_t)x - (int64_t)Data.x1; int64_t samples = GetSamples(); int64_t dx = (int64_t)Data.x2 - (int64_t)Data.x1 + 1; double pos = (double) offset / dx; int64_t idx = (int64_t)(samples * pos); #if 0 printf("ViewToSample(%i) data=%s offset=" LPrintfInt64 " samples=" LPrintfInt64 " idx=" LPrintfInt64 " pos=%f\n", x, Data.GetStr(), offset, samples, idx, pos); #endif if (idx < 0) idx = 0; else if (idx >= samples) idx = samples - 1; return idx; } void OnPosChange() override { auto def = DefaultPos(); LRect d = Data; d.y1 = def.y1; d.y2 = def.y2; SetData(d); } #define CheckItem(Sub, Name, Id, Chk) \ { \ auto item = Sub->AppendItem(Name, Id); \ if (item && Chk) item->Checked(true); \ } void OnMouseClick(LMouse &m) override { if (m.IsContextMenu()) { LSubMenu s; s.AppendItem("Copy Cursor Address", IDC_COPY_CURSOR); s.AppendSeparator(); auto File = s.AppendSub("File"); File->AppendItem("Load Wav", IDC_LOAD_WAV); File->AppendItem("Load Raw", IDC_LOAD_RAW); File->AppendItem("Save Wav", IDC_SAVE_WAV); File->AppendItem("Save Raw", IDC_SAVE_RAW); auto Draw = s.AppendSub("Draw Mode"); CheckItem(Draw, "Auto", IDC_DRAW_AUTO, DrawMode == DrawAutoSelect); CheckItem(Draw, "Line", IDC_DRAW_LINE, DrawMode == DrawSamples); CheckItem(Draw, "Scan Samples", IDC_SCAN_SAMPLES, DrawMode == ScanSamples); CheckItem(Draw, "Scan Groups", IDC_SCAN_GROUPS, DrawMode == ScanGroups); auto Rate = s.AppendSub("Sample Rate"); CheckItem(Rate, "44.1k", IDC_44K, SampleRate == 44100); CheckItem(Rate, "48k", IDC_48K, SampleRate == 4800); CheckItem(Rate, "88.2k", IDC_88K, SampleRate == 44100*2); CheckItem(Rate, "96k", IDC_96K, SampleRate == 48000*2); auto Ch = s.AppendSub("Channels"); CheckItem(Ch, "Mono", IDC_MONO, Channels == 1); CheckItem(Ch, "Stereo", IDC_STEREO, Channels == 2); CheckItem(Ch, "2.1", IDC_2pt1_CHANNELS, Channels == 3); CheckItem(Ch, "5.1", IDC_5pt1_CHANNELS, Channels == 6); bool Update = false; switch (s.Float(this, m)) { case IDC_COPY_CURSOR: { LClipBoard clip(this); size_t addrVal = DataStart + (CursorSample * Channels * SampleBits / 8); LString addr; addr.Printf(LPrintfSizeT, addrVal); clip.Text(addr); break; } case IDC_SAVE_RAW: { auto s = new LFileSelect; s->Parent(this); s->Save([this](auto s, auto ok) { if (ok) { LFile out(s->Name(), O_WRITE); if (out) { out.SetSize(0); auto ptr = Audio.AddressOf(DataStart); out.Write(ptr, Audio.Length() - DataStart); } else LgiMsg(this, "Can't open '%s' for writing.", "Error", MB_OK, s->Name()); } delete s; }); break; } case IDC_DRAW_AUTO: DrawMode = DrawAutoSelect; Update = true; break; case IDC_DRAW_LINE: DrawMode = DrawSamples; Update = true; break; case IDC_SCAN_SAMPLES: DrawMode = ScanSamples; Update = true; break; case IDC_SCAN_GROUPS: DrawMode = ScanGroups; Update = true; break; case IDC_44K: SampleRate = 44100; Update = true; break; case IDC_48K: SampleRate = 48000; Update = true; break; case IDC_88K: SampleRate = 44100*2; Update = true; break; case IDC_96K: SampleRate = 48000*2; Update = true; break; case IDC_MONO: Channels = 1; Update = true; break; case IDC_STEREO: Channels = 2; Update = true; break; case IDC_2pt1_CHANNELS: Channels = 3; Update = true; break; case IDC_5pt1_CHANNELS: Channels = 6; Update = true; break; default: break; } if (Update) { IntGrps.Length(0); FloatGrps.Length(0); SetData(Data); Invalidate(); } } else if (m.Left()) { Focus(true); MouseToCursor(m); } } void OnMouseMove(LMouse &m) override { if (m.Down()) MouseToCursor(m); } bool OnKey(LKey &k) override { // k.Trace("key"); if (k.IsChar) { switch (k.c16) { case ' ': { if (k.Down()) TogglePlay(); return true; } } } else if (k.Ctrl()) { if (k.c16 == 'S') { if (k.Down()) Save(); return true; } else if (k.c16 == 'W') { if (k.Down()) Empty(); return true; } } return false; } sample_t AddressOf(size_t Sample) { int SampleBytes = Channels * (SampleBits >> 3); return sample_t(Audio.AddressOf(DataStart + (Sample * SampleBytes))); } int64_t PtrToSample(sample_t &ptr) { int sampleBytes = SampleBits >> 3; auto start = Audio.AddressOf(DataStart); return (ptr.i8 - start) / (sampleBytes * Channels); } void UpdateMsg() { ssize_t Samples = GetSamples(); auto Addr = AddressOf(CursorSample); LString::Array Val; size_t GraphLen = IntGrps.Length() ? IntGrps[0].Length() : 0; auto Convert = GetConvert(); // Work out the time stamp of the cursor: auto Seconds = CursorSample / SampleRate; auto Hours = Seconds / 3600; Seconds -= Hours * 3600; auto Minutes = Seconds / 60; Seconds -= Minutes * 60; auto Ms = (CursorSample % SampleRate) * 1000 / SampleRate; LString Time; Time.Printf("%i:%02.2i:%02.2i.%i", (int)Hours, (int)Minutes, (int)Seconds, (int)Ms); for (uint32_t ch=0; ch cli.x1) d.Offset(cli.x1 - d.x1, 0); else if (d.x2 < cli.x2) d.Offset(cli.x2 - d.x2, 0); SetData(d); Invalidate(); } else { auto Client = GetClient(); int change = (int)(Lines * -6); auto d = Data; d.x1 += change; d.x2 += change; if (d.x2 < Client.x1) d.Offset(-d.x2, 0); else if (d.x1 >= Client.x2) d.Offset(-(d.x2 - Client.x2), 0); SetData(d); Invalidate(); } UpdateMsg(); return true; } #define PROFILE_PAINT_SAMPLES 0 #if PROFILE_PAINT_SAMPLES #define PROF(name) Prof.Add(name) #else #define PROF(name) #endif ConvertFn GetConvert() { switch (SampleType) { case AudioS8: return [](sample_t &s) -> int32_t { return *s.i8++; }; case AudioS16LE: return [](sample_t &s) -> int32_t { return *s.i16++; }; case AudioS16BE: return [](sample_t &s) -> int32_t { int16_t i = LgiSwap16(*s.i16); s.i16++; return i; }; case AudioS24LE: return [](sample_t &s) -> int32_t { int32_t i = s.i24->s; s.i8 += 3; return i; }; case AudioS24BE: return [](sample_t &s) -> int32_t { int24_t i; int8_t *p = (int8_t*)&i; p[0] = s.i8[2]; p[1] = s.i8[1]; p[2] = s.i8[0]; s.i8 += 3; return i.s; }; case AudioS32LE: return [](sample_t &s) -> int32_t { return *s.i32++; }; case AudioS32BE: return [](sample_t &s) -> int32_t { int32_t i = *s.i32++; return LgiSwap32(i); }; default: LAssert(!"Not impl."); break; } return NULL; } private: constexpr static int WaveformBlockSize = 256; struct WaveFormWork { ConvertFn convert; sample_t start; int8_t *end = NULL; int stepBytes = 0; int blkBytes = 0; size_t blocks = 0; Grp *out = NULL; }; LArray WaveFormTodo; LArray WaveFormThreads; struct WaveFormThread : public LThread, public LCancel { LAudioView *view; WaveFormThread(LAudioView *v) : LThread("WaveFormThread", v->AddDispatch()) { view = v; Run(); } ~WaveFormThread() { Cancel(); } void OnComplete() { // Clean up thread array... LThread *t = this; LAssert(view->WaveFormThreads.HasItem(t)); view->WaveFormThreads.Delete(t); DeleteOnExit = true; // Last one finished? if (view->WaveFormThreads.Length() == 0) view->PostEvent(M_WAVE_FORMS_FINISHED); } int Main() { while (!IsCancelled()) { WaveFormWork *w = NULL; if (view->Lock(_FL, 500)) { // Is there work? if (view->WaveFormTodo.Length() > 0) { w = view->WaveFormTodo[0]; view->WaveFormTodo.DeleteAt(0); } view->Unlock(); if (!w) return 0; } auto s = w->start; auto block = w->out; auto convert = w->convert; // For all blocks we're assigned... for (int b = 0; b < w->blocks && !IsCancelled(); b++, s.i8 += w->blkBytes, block++) { auto ptr = s; auto blkEnd = s.i8 + w->blkBytes; auto stepBytes = w->stepBytes; // For all samples in the block... block->Min = block->Max = convert(ptr); // init min/max with first sample while (ptr.i8 < blkEnd) { auto n = convert(ptr); if (n < block->Min) block->Min = n; if (n > block->Max) block->Max = n; ptr.i8 += stepBytes; } } } return 0; } }; public: void BuildWaveForms(LArray>> *Grps) { LPopupNotification::Message(GetWindow(), "Building waveform data..."); State = CtrlBuildingWaveforms; auto SampleBytes = SampleBits / 8; auto start = Audio.AddressOf(DataStart); size_t samples = GetSamples(); // auto end = start + (SampleBytes * samples * Channels); auto BlkBytes = WaveformBlockSize * Channels * SampleBytes; auto Blocks = ((Audio.Length() - DataStart) + BlkBytes - 1) / BlkBytes; int Threads = LAppInst->GetCpuCount(); // Initialize the graph Grps->Length(Channels); for (uint32_t ch = 0; ch < Channels; ch++) { auto &GraphArr = (*Grps)[ch]; GraphArr.Length(Blocks); for (int Thread = 0; Thread < Threads; Thread++) { auto from = Thread * Blocks / Threads; auto to = (Thread + 1) * Blocks / Threads; WaveFormWork *work = new WaveFormWork; work->convert = GetConvert(); work->start.i8 = start + (from * BlkBytes) + (ch * SampleBytes); work->end = start + (to * BlkBytes) + (ch * SampleBytes); work->stepBytes = (Channels - 1) * SampleBytes; work->blkBytes = BlkBytes; work->blocks = to - from; work->out = GraphArr.AddressOf(from); WaveFormTodo.Add(work); } } for (int Thread = 0; Thread < Threads; Thread++) WaveFormThreads.Add(new WaveFormThread(this)); } void OnWaveFormsFinished() { State = CtrlNormal; LPopupNotification::Message(GetWindow(), "Done."); Invalidate(); } void PaintSamples(LSurface *pDC, int ChannelIdx, LArray>> *Grps) { #if PROFILE_PAINT_SAMPLES LProfile Prof("PaintSamples", 10); #endif int32_t MaxT = 0; ConvertFn Convert = GetConvert(); if (SampleBits == 8) MaxT = 0x7f; else if (SampleBits == 16) MaxT = 0x7fff; else if (SampleBits == 24) MaxT = 0x7fffff; else MaxT = 0x7fffffff; auto SampleBytes = SampleBits / 8; auto Client = GetClient(); auto &c = ChData[ChannelIdx]; auto start = Audio.AddressOf(DataStart); size_t samples = GetSamples(); // printf("samples=" LPrintfSizeT "\n", samples); // auto end = start + (SampleBytes * samples * Channels); auto cy = c.y1 + (c.Y() / 2); pDC->Colour(cGrid); LRect cr = c; pDC->Box(&cr); if (Grps->Length() == 0) { BuildWaveForms(Grps); return; } else if (State != CtrlNormal) { return; } PROF("PrePaint"); auto YScale = c.Y() / 2; int CursorX = SampleToView(CursorSample); double blkScale = Grps->Length() > 0 ? (double) (*Grps)[0].Length() / c.X() : 1.0; // printf("blkScale=%f blks/px\n", blkScale); LDrawMode EffectiveMode = DrawMode; auto StartSample = ViewToSample(Client.x1); auto EndSample = ViewToSample(Client.x2); auto SampleRange = EndSample - StartSample; if (EffectiveMode == DrawAutoSelect) { if (SampleRange < Client.X() * 32) EffectiveMode = DrawSamples; else if (blkScale < 1.0) EffectiveMode = ScanSamples; else EffectiveMode = ScanGroups; } // This is the bytes in channels we're not looking at int byteInc = (Channels - 1) * SampleBytes; pDC->Colour(cGrid); pDC->HLine((int)MAX(0,c.x1), (int)MIN(pDC->X(),c.x2), (int)cy); // Setup for drawing bookmarks.. auto &BookmarkArr = Bookmarks[ChannelIdx]; for (auto &bm: BookmarkArr) bm.x = SampleToView(bm.sample); auto nextBookmark = BookmarkArr.Length() ? BookmarkArr.AddressOf(0) : NULL; auto endBookmark = BookmarkArr.Length() ? nextBookmark + BookmarkArr.Length() : NULL; if (EffectiveMode == DrawSamples) { sample_t pSample(start + (((StartSample * Channels) + ChannelIdx) * SampleBytes)); bool DrawDots = SampleRange < (Client.X() >> 2); if (CursorX >= Client.x1 && CursorX <= Client.x2) { pDC->Colour(L_BLACK); pDC->VLine(CursorX, (int)c.y1, (int)c.y2); } while ( nextBookmark && nextBookmark < endBookmark) { pDC->Colour(nextBookmark->colour); pDC->VLine(nextBookmark->x, (int)c.y1, (int)c.y2); nextBookmark++; } // For all samples in the view space... pDC->Colour(cMax); LPoint Prev(-1, -1); for (auto idx = StartSample; idx <= EndSample + 1; idx++, pSample.i8 += byteInc) { auto n = Convert(pSample); auto x = SampleToView(idx); auto y = (cy - ((int64_t)n * YScale / MaxT)); if (idx != StartSample) pDC->Line(Prev.x, Prev.y, (int)x, (int)y); Prev.Set((int)x, (int)y); if (DrawDots) pDC->Rectangle((int)x-1, (int)y-1, (int)x+1, (int)y+1); } } else { // For all x coordinates in the view space.. for (int Vx=Client.x1; Vx<=Client.x2; Vx++) { if (Vx % 100 == 0) { PROF("Pixels"); } if (Vx < c.x1 || Vx > c.x2) continue; if (nextBookmark && nextBookmark->x <= Vx) { while ( nextBookmark < endBookmark && nextBookmark->x <= Vx) { pDC->Colour(nextBookmark->colour); pDC->VLine(nextBookmark->x, (int)c.y1, (int)c.y2); nextBookmark++; } } auto isCursor = CursorX == Vx; if (isCursor) { pDC->Colour(L_BLACK); pDC->VLine(Vx, (int)c.y1, (int)c.y2); } Grp Min, Max; StartSample = ViewToSample(Vx); EndSample = ViewToSample(Vx+1); // printf("SampleIdx: " LPrintfSizeT "-" LPrintfSizeT "\n", StartSample, EndSample); if (EffectiveMode == ScanSamples) { // Scan individual samples sample_t pStart(start + ((StartSample * Channels + ChannelIdx) * SampleBytes)); auto pEnd = start + ((EndSample * Channels + ChannelIdx) * SampleBytes); #if 0 if (Vx==Client.x1) { auto PosX = SampleToView((pStart - ChannelIdx - start) / Channels); LgiTrace(" ptr=%i " LPrintfSizeT "-" LPrintfSizeT " x=%i pos=%i\n", (int)((char*)pStart-(char*)start), StartSample, EndSample, Vx, PosX); } #endif for (auto i = pStart; i.i8 < pEnd; i.i8 += byteInc) { auto n = Convert(i); Max.Min = MIN(Max.Min, n); Max.Max = MAX(Max.Max, n); } } else { // Scan the buckets auto &Graph = (*Grps)[ChannelIdx]; double blkStart = (double)StartSample * Graph.Length() / samples; double blkEnd = (double)EndSample * Graph.Length() / samples; /* if (Vx % 10 == 0) printf("BlkRange[%i]: %g-%g of:%i\n", Vx, blkStart, blkEnd, (int)Graph.Length()); */ #if 0 if (isCursor) LgiTrace("Blk %g-%g of " LPrintfSizeT "\n", blkStart, blkEnd, Graph.Length()); #endif auto iEnd = (int)ceil(blkEnd); int count = 0; for (int i=(int)floor(blkStart); iColour(isCursor ? cCursorMax : cMax); pDC->VLine(Vx, (int)y1, (int)y2); if (EffectiveMode == ScanGroups) { y1 = (cy - (Min.Max * YScale / MaxT)); y2 = (cy - (Min.Min * YScale / MaxT)); pDC->Colour(isCursor ? cCursorMin : cMin); pDC->VLine(Vx, (int)y1, (int)y2); } } } } void OnPaint(LSurface *pDC) override { #ifdef WINDOWS LDoubleBuffer DblBuf(pDC); #endif pDC->Colour(L_WORKSPACE); pDC->Rectangle(); auto Fnt = GetFont(); Fnt->Transparent(true); if (!FilePath) { LDisplayString ds(Fnt, Msg); Fnt->Fore(L_LOW); auto c = GetClient().Center(); ds.Draw(pDC, c.x - (ds.X()/2), c.y - (ds.Y()/2)); return; } if (!Data.Valid()) { auto r = DefaultPos(); SetData(r); } LDisplayString ds(Fnt, ErrorMsg ? ErrorMsg : Msg); Fnt->Fore(ErrorMsg ? LColour::Red : LColour(L_LOW)); ds.Draw(pDC, 4, 4); if (ErrorMsg) return; switch (SampleType) { case AudioS16LE: case AudioS16BE: { for (uint32_t ch = 0; ch < Channels; ch++) PaintSamples(pDC, ch, &IntGrps); break; } case AudioS24LE: case AudioS24BE: { for (uint32_t ch = 0; ch < Channels; ch++) PaintSamples(pDC, ch, &IntGrps); break; } case AudioS32LE: case AudioS32BE: { for (uint32_t ch = 0; ch < Channels; ch++) PaintSamples(pDC, ch, &IntGrps); break; } default: { LAssert(!"Not impl."); break; } } } void TogglePlay() { if (AoPlaying) { LgiTrace("stopping...\n"); AoPlaying = false; } else // Start playback { if (AoDriver < 0) { AoDriver = ao_default_driver_id(); if (AoDriver < 0) { int count = 0; auto info = ao_driver_info_list(&count); for (int i=0; iname); LPopupNotification::Message(GetWindow(), s); } } } if (!AoDev) { ao_sample_format fmt; fmt.bits = SampleBits; fmt.rate = SampleRate; fmt.channels = Channels; fmt.byte_format = AO_FMT_LITTLE; fmt.matrix = NULL; AoDev = ao_open_live(AoDriver, &fmt, NULL); } if (AoDev) { AoPlaying = true; AoEvent.Signal(); } else { LString s; s.Printf("Couldn't open audio device: %i", AoDriver); LPopupNotification::Message(GetWindow(), s); } } } int Main() override { int TimeSlice = 50; // ms int sliceBytes = 0; int sliceSamples = 0; int64_t playStart = 0; int64_t playSample = 0; bool playedSamples = false; while (!IsCancelled()) { if (AoPlaying) { // Play one timeslice of audio auto addr = AddressOf(CursorSample); /* auto startTs = LMicroTime(); auto elapsedSamples = CursorSample - playSample; while ((LMicroTime() - playStart) < elapsedSamples) ; auto waitTs = LMicroTime() - startTs; */ ao_play(AoDev, (char*)addr.i8, sliceBytes); CursorSample += sliceSamples; playedSamples = true; /* #if 1 LgiTrace("wait %.3fms\n", (double)waitTs / 1000.0); #else auto now = LMicroTime(); auto elapsedTime = now - playStart; double ms = (double)elapsedTime/1000.0; elapsedSamples = CursorSample - playSample; double sms = (double)elapsedSamples * 1000.0 / SampleRate; LgiTrace("play ms=%.3f sms=%.3f\n", ms, sms); #endif */ } else { if (playedSamples) { playedSamples = false; LgiTrace("stopped.\n"); } AoEvent.Wait(100); if (AoPlaying) { sliceSamples = SampleRate * TimeSlice / 1000; sliceBytes = Channels * sliceSamples * (SampleBits / 8); playStart = LMicroTime(); playSample = CursorSample; } } } return 0; } bool UnitTests() { SampleType = AudioS16LE; auto c = GetConvert(); int16_t le16 = (2 << 8) | (1); sample_t ptr(&le16); auto out = c(ptr); if (out != 0x201) { LAssert(0); return false; } SampleType = AudioS16BE; c = GetConvert(); int16_t be16 = (1 << 8) | (2); ptr = &be16; out = c(ptr); if (out != 0x201) { LAssert(0); return false; } SampleType = AudioS24LE; c = GetConvert(); uint8_t le24[] = {1, 2, 3}; ptr = &le24; out = c(ptr); if (out != 0x30201) { LAssert(0); return false; } SampleType = AudioS24BE; c = GetConvert(); uint8_t be24[] = {3, 2, 1}; ptr = &be24; out = c(ptr); if (out != 0x30201) { LAssert(0); return false; } SampleType = AudioS32LE; c = GetConvert(); uint8_t le32[] = {1, 2, 3, 4}; ptr = &le32; out = c(ptr); if (out != 0x4030201) { LAssert(0); return false; } SampleType = AudioS32BE; c = GetConvert(); uint8_t be32[] = {4, 3, 2, 1}; ptr = &be32; out = c(ptr); if (out != 0x4030201) { LAssert(0); return false; } return true; } }; class LAudioRepairView : public LAudioView { LArray> &GetBookMarks() { return Bookmarks; } void RepairAll() { for (int ch = 0; ch < Bookmarks.Length(); ch++) for (auto &bm: Bookmarks[ch]) RepairBookmark(ch, bm); Invalidate(); } void RepairBookmark(int Channel, LBookmark &bm) { if (!bm.error) return; auto Convert = GetConvert(); auto end = Audio.AddressOf(Audio.Length()-1) + 1; int sampleBytes = SampleBits / 8; int skipBytes = (Channels - 1) * sampleBytes; int channelOffset = Channel * sampleBytes; // Look at previous sample... auto repairSample = bm.sample; auto p = AddressOf(repairSample - 1); p.i8 += channelOffset; auto lastGood = Convert(p); p.i8 += skipBytes; // Seek over and find the end... auto nextGood = Convert(p); p.i8 += skipBytes; while (nextGood) { nextGood = Convert(p); p.i8 += skipBytes; repairSample++; } auto endRepair = repairSample; while (nextGood == 0) { nextGood = Convert(p); p.i8 += skipBytes; endRepair++; } int64_t sampleDiff = nextGood - lastGood; int64_t repairSamples = endRepair - repairSample + 1; // Write the new interpolated samples for (int64_t i = repairSample; i < endRepair; i++) { p = AddressOf(i); p.i8 += channelOffset; if (p.i8 >= end) break; switch (SampleType) { case AudioS24LE: { auto off = i - repairSample + 1; p.i24->s = lastGood + (off * sampleDiff / repairSamples); break; } default: { LAssert(!"Impl me."); break; } } } bm.colour = LColour::Green; bm.error = false; } void RepairNext() { for (uint32_t ch = 0; ch < Channels; ch++) { int64_t prev = 0; LBookmark *Bookmark = NULL; for (auto &bm: Bookmarks[ch]) { if (bm.sample >= CursorSample && prev < CursorSample && bm.error) { Bookmark = &bm; break; } } if (Bookmark) RepairBookmark(ch, *Bookmark); } Invalidate(); } struct ErrorThread : public LThread { LAudioRepairView *view; int64_t start, end; int channel; LArray bookmarks; ErrorThread(LAudioRepairView *v, int64_t startSample, int64_t endSample, int ch) : LThread("ErrorThread", v->AddDispatch()) { view = v; start = startSample; end = endSample; channel = ch; Run(); } void OnComplete() { - auto BookMarks = view->GetBookMarks(); + auto &BookMarks = view->GetBookMarks(); BookMarks[channel].Add(bookmarks); BookMarks[channel].Sort([](auto a, auto b) { return (int)(a->sample - b->sample); }); view->Invalidate(); LAssert(view->ErrorThreads.HasItem(this)); view->ErrorThreads.Delete(this); if (view->ErrorThreads.Length() == 0) LPopupNotification::Message(view->GetWindow(), "Done."); DeleteOnExit = true; } int Main() { auto Convert = view->GetConvert(); int32_t prev = 0; int sampleBytes = view->SampleBits / 8; int byteInc = (view->Channels - 1) * sampleBytes; int64_t zeroRunStart = -1; auto ptr = view->AddressOf(0); auto pStart = ptr.i8 + (start * view->Channels * sampleBytes) + (channel * sampleBytes); auto pEnd = ptr.i8 + (end * view->Channels * sampleBytes) + (channel * sampleBytes); ptr.i8 = pStart; while (ptr.i8 < pEnd) { auto s = Convert(ptr); ptr.i8 += byteInc; auto diff = s - prev; if (diff < 0) diff = -diff; if (s == 0) { if (zeroRunStart < 0) zeroRunStart = view->PtrToSample(ptr) - 1; } else if (zeroRunStart >= 0) { auto cur = view->PtrToSample(ptr) - 1; auto len = cur - zeroRunStart; if (len >= ERROR_ZERO_SAMPLE_COUNT) { // emit book mark auto &bm = bookmarks.New(); bm.sample = zeroRunStart; bm.colour = LColour::Red; bm.error = true; #if 0 if (Bookmarks.Length() >= 10000) break; #endif } zeroRunStart = -1; } prev = s; } return 0; } }; LArray ErrorThreads; public: void FindErrors() { if (ErrorThreads.Length() > 0) { LPopupNotification::Message(GetWindow(), "Already finding errors."); return; } LPopupNotification::Message(GetWindow(), "Finding errors..."); Bookmarks.Length(Channels); auto samples = GetSamples(); auto threads = LAppInst->GetCpuCount(); auto threadsPerCh = threads / Channels; for (uint32_t ch=0; ch 360.0) fHue -= 360.0; else if (fHue < 0.0) fHue += 360.0; if (fHue < 60.0) return (int) ((fN1 + (fN2 - fN1) * fHue / 60.0) * 255.0 + 0.5); else if (fHue < 180.0) return (int) ((fN2 * 255.0) + 0.5); else if (fHue < 240.0) return (int) ((fN1 + (fN2 - fN1) * (240.0 - fHue) / 60.0) * 255.0 + 0.5); return (int) ((fN1 * 255.0) + 0.5); } LColourSpace LColour::GetColourSpace() { return space; } bool LColour::SetColourSpace(LColourSpace cs) { if (space == CsNone) { space = cs; rgb.a = 255; return true; } if (space == cs) return true; LAssert(!"Impl conversion."); return false; } bool LColour::IsValid() const { return space != CsNone; } void LColour::Empty() { space = CsNone; } bool LColour::IsTransparent() { if (space == System32BitColourSpace) return rgb.a == 0; else if (space == CsIndex8) return !pal || index >= pal->GetSize(); return space == CsNone; } void LColour::Rgb(int r, int g, int b, int a) { c32(Rgba32(r, g, b, a)); } void LColour::Set(LSystemColour c) { *this = LColour(c); } void LColour::Set(uint32_t c, int bits, LPalette *palette) { pal = 0; switch (bits) { case 8: { index = c; space = CsIndex8; pal = palette; break; } case 15: { space = System32BitColourSpace; rgb.r = Rc15(c); rgb.g = Gc15(c); rgb.b = Bc15(c); rgb.a = 255; break; } case 16: { space = System32BitColourSpace; rgb.r = Rc16(c); rgb.g = Gc16(c); rgb.b = Bc16(c); rgb.a = 255; break; } case 24: case 48: { space = System32BitColourSpace; rgb.r = R24(c); rgb.g = G24(c); rgb.b = B24(c); rgb.a = 255; break; } case 32: case 64: { space = System32BitColourSpace; rgb.r = R32(c); rgb.g = G32(c); rgb.b = B32(c); rgb.a = A32(c); break; } default: { space = System32BitColourSpace; flat = 0; LgiTrace("Error: Unable to set colour %x, %i\n", c, bits); LAssert(!"Not a known colour depth."); } } } uint32_t LColour::Get(int bits) { switch (bits) { case 8: if (space == CsIndex8) return index; LAssert(!"Not supported."); break; case 24: return c24(); case 32: return c32(); } return 0; } uint8_t LColour::r() const { return R32(c32()); } void LColour::r(uint8_t i) { if (SetColourSpace(System32BitColourSpace)) rgb.r = i; else LAssert(0); } uint8_t LColour::g() const { return G32(c32()); } void LColour::g(uint8_t i) { if (SetColourSpace(System32BitColourSpace)) rgb.g = i; else LAssert(0); } uint8_t LColour::b() const { return B32(c32()); } void LColour::b(uint8_t i) { if (SetColourSpace(System32BitColourSpace)) rgb.b = i; else LAssert(0); } uint8_t LColour::a() const { return A32(c32()); } void LColour::a(uint8_t i) { if (SetColourSpace(System32BitColourSpace)) rgb.a = i; else LAssert(0); } uint8_t LColour::c8() const { return index; } void LColour::c8(uint8_t c, LPalette *p) { space = CsIndex8; pal = p; index = c; } uint32_t LColour::c24() const { if (space == System32BitColourSpace) { return Rgb24(rgb.r, rgb.g, rgb.b); } else if (space == CsIndex8) { if (pal) { // colour palette lookup if (index < pal->GetSize()) { GdcRGB *c = (*pal)[index]; if (c) { return Rgb24(c->r, c->g, c->b); } } return 0; } return Rgb24(index, index, index); // monochome } else if (space == CsHls32) { return Rgb32To24(c32()); } // Black... return 0; } void LColour::c24(uint32_t c) { space = System32BitColourSpace; rgb.r = R24(c); rgb.g = G24(c); rgb.b = B24(c); rgb.a = 255; pal = NULL; } uint32_t LColour::c32() const { if (space == System32BitColourSpace) { return Rgba32(rgb.r, rgb.g, rgb.b, rgb.a); } else if (space == CsIndex8) { if (pal) { // colour palette lookup if (index < pal->GetSize()) { GdcRGB *c = (*pal)[index]; if (c) { return Rgb32(c->r, c->g, c->b); } } return 0; } return Rgb32(index, index, index); // monochome } else if (space == CsHls32) { // Convert from HLS back to RGB LColour tmp = *this; tmp.ToRGB(); return tmp.c32(); } // Transparent? return 0; } void LColour::c32(uint32_t c) { space = System32BitColourSpace; pal = NULL; rgb.r = R32(c); rgb.g = G32(c); rgb.b = B32(c); rgb.a = A32(c); } LColour LColour::Invert() { LColour i(255-r(), 255-g(), 255-b()); return i; } LColour LColour::Mix(LColour Tint, float RatioOfTint) const { COLOUR c1 = c32(); COLOUR c2 = Tint.c32(); float RatioThis = 1.0f - RatioOfTint; int r = (int) ((RatioThis * R32(c1)) + (RatioOfTint * R32(c2)) + 0.5f); int g = (int) ((RatioThis * G32(c1)) + (RatioOfTint * G32(c2)) + 0.5f); int b = (int) ((RatioThis * B32(c1)) + (RatioOfTint * B32(c2)) + 0.5f); int a = (int) ((RatioThis * A32(c1)) + (RatioOfTint * A32(c2)) + 0.5f); return LColour(r, g, b, a); } uint32_t LColour::GetH() { ToHLS(); return hls.h; } bool LColour::HueIsUndefined() { ToHLS(); return hls.h == HUE_UNDEFINED; } uint32_t LColour::GetL() { ToHLS(); return hls.l; } uint32_t LColour::GetS() { ToHLS(); return hls.s; } bool LColour::ToHLS() { if (space == CsHls32) return true; uint32_t nMax, nMin, nDelta, c = c32(); int R = R32(c), G = G32(c), B = B32(c); double fHue; nMax = MAX(R, MAX(G, B)); nMin = MIN(R, MIN(G, B)); if (nMax == nMin) return false; hls.l = (nMax + nMin) / 2; if (hls.l < 128) hls.s = (uchar) ((255.0 * ((double)(nMax - nMin)) / (double)(nMax + nMin)) + 0.5); else hls.s = (uchar) ((255.0 * ((double)(nMax - nMin)) / (double)(511 - nMax - nMin)) + 0.5); nDelta = nMax - nMin; if (R == nMax) fHue = ((double) (G - B)) / (double) nDelta; else if (G == nMax) fHue = 2.0 + ((double) (B - R)) / (double) nDelta; else fHue = 4.0 + ((double) (R - G)) / (double) nDelta; fHue *= 60; if (fHue < 0.0) fHue += 360.0; hls.h = (uint16) (fHue + 0.5); space = CsHls32; pal = NULL; return true; } void LColour::SetHLS(uint16 h, uint8_t l, uint8_t s) { space = CsHls32; hls.h = h; hls.l = l; hls.s = s; } void LColour::ToRGB() { LHls32 Hls = hls; if (Hls.s == 0) { Rgb(0, 0, 0); } else { while (Hls.h >= 360) Hls.h -= 360; while (hls.h < 0) Hls.h += 360; double fHue = (double) Hls.h, fM1, fM2; double fLightness = ((double) Hls.l) / 255.0; double fSaturation = ((double) Hls.s) / 255.0; if (Hls.l < 128) fM2 = fLightness * (1 + fSaturation); else fM2 = fLightness + fSaturation - (fLightness * fSaturation); fM1 = 2.0 * fLightness - fM2; Rgb(HlsValue(fM1, fM2, fHue + 120.0), HlsValue(fM1, fM2, fHue), HlsValue(fM1, fM2, fHue - 120.0)); } } int LColour::GetGray(int BitDepth) const { if (BitDepth == 8) { int R = r() * 77; int G = g() * 151; int B = b() * 28; return (R + G + B) >> 8; } double Scale = 1 << BitDepth; int R = (int) ((double) r() * (0.3 * Scale) + 0.5); int G = (int) ((double) g() * (0.59 * Scale) + 0.5); int B = (int) ((double) b() * (0.11 * Scale) + 0.5); return (R + G + B) >> BitDepth; } uint32_t LColour::GetNative() { #ifdef WIN32 if (space == CsIndex8) { if (pal && index < pal->GetSize()) { GdcRGB *c = (*pal)[index]; if (c) { return RGB(c->r, c->g, c->b); } } return RGB(index, index, index); } else if (space == System32BitColourSpace) { return RGB(rgb.r, rgb.g, rgb.b); } else if (space == CsHls32) { LColour c(*this); c.ToRGB(); return RGB(c.r(), c.g(), c.b()); } else { LAssert(0); } #else LAssert(0); #endif return c32(); } char *LColour::GetStr() { static char Buf[4][32]; static int Idx = 0; int b = Idx++; if (Idx >= 4) Idx = 0; switch (space) { case System32BitColourSpace: if (rgb.a == 0xff) { sprintf_s( Buf[b], 32, "rgb(%i,%i,%i)", rgb.r, rgb.g, rgb.b); } else { sprintf_s( Buf[b], 32, "rgba(%i,%i,%i,%i)", rgb.r, rgb.g, rgb.b, rgb.a); } break; case CsIndex8: sprintf_s( Buf[b], 32, "index(%i)", index); break; case CsHls32: sprintf_s( Buf[b], 32, "hls(%i,%i,%i)", hls.h, hls.l, hls.s); break; default: sprintf_s( Buf[b], 32, "unknown(%i)", space); break; } return Buf[b]; } bool LColour::SetStr(const char *str) { if (!str) return false; LString Str = str; if (*str == '#') { // Web colour Str = Str.Strip("# \t\r\n"); if (Str.Length() == 3) { auto h = htoi(Str.Get()); uint8_t r = (h >> 8) & 0xf; uint8_t g = (h >> 4) & 0xf; uint8_t b = (h) & 0xf; Rgb(r | (r << 4), g | (g << 4), b | (b << 4)); } else if (Str.Length() == 6) { auto h = htoi(Str.Get()); uint8_t r = (h >> 16) & 0xff; uint8_t g = (h >> 8) & 0xff; uint8_t b = (h) & 0xff; Rgb(r, g, b); } else return false; return true; } char *s = strchr(Str, '('); if (!s) return false; char *e = strchr(s + 1, ')'); if (!s) return false; *s = 0; *e = 0; LString::Array Comp = LString(s+1).Split(","); if (!Stricmp(Str.Get(), "rgb")) { if (Comp.Length() == 3) Rgb((int)Comp[0].Int(), (int)Comp[1].Int(), (int)Comp[2].Int()); else if (Comp.Length() == 4) Rgb((int)Comp[0].Int(), (int)Comp[1].Int(), (int)Comp[2].Int(), (int)Comp[3].Int()); else return false; } else if (!Stricmp(Str.Get(), "hls")) { if (Comp.Length() == 3) SetHLS((uint16) Comp[0].Int(), (uint8_t) Comp[1].Int(), (uint8_t) Comp[2].Int()); else return false; } else if (!Stricmp(Str.Get(), "index")) { if (Comp.Length() == 1) { index = (uint8_t) Comp[0].Int(); space = CsIndex8; } else return false; } else return false; return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static LColour _LgiColours[L_MAXIMUM]; #define ReadColourConfig(def) LColour::GetConfigColour("Colour."#def, _LgiColours[def]) bool LColour::GetConfigColour(const char *Tag, LColour &c) { #ifdef LGI_STATIC return false; #else if (!Tag) return false; auto Col = LAppInst->GetConfig(Tag); if (!Col) return false; auto n = (int)Col.Strip("#").Int(16); c.Rgb( n>>16, n>>8, n ); return true; #endif } //////////////////////////////////////////////////////////////////////////// #ifdef __GTK_H__ COLOUR ColTo24(Gtk::GdkColor &c) { return Rgb24(c.red >> 8, c.green >> 8, c.blue >> 8); } #endif #if defined(WINDOWS) static LColour ConvertWinColour(uint32_t c) { return LColour(GetRValue(c), GetGValue(c), GetBValue(c)); } #endif void LColour::OnChange() { // Basic colours _LgiColours[L_BLACK].Rgb(0, 0, 0); // LC_BLACK _LgiColours[L_DKGREY].Rgb(0x40, 0x40, 0x40); // LC_DKGREY _LgiColours[L_MIDGREY].Rgb(0x80, 0x80, 0x80); // LC_MIDGREY _LgiColours[L_LTGREY].Rgb(0xc0, 0xc0, 0xc0); // LC_LTGREY _LgiColours[L_WHITE].Rgb(0xff, 0xff, 0xff); // LC_WHITE // Variable colours #if defined _XP_CTRLS _LgiColours[L_SHADOW] = Rgb24(0x42, 0x27, 0x63); // LC_SHADOW _LgiColours[L_LOW] = Rgb24(0x7a, 0x54, 0xa9); // LC_LOW _LgiColours[L_MED] = Rgb24(0xbc, 0xa9, 0xd4); // LC_MED _LgiColours[L_HIGH] = Rgb24(0xdd, 0xd4, 0xe9); // LC_HIGH _LgiColours[L_LIGHT] = Rgb24(0xff, 0xff, 0xff); // LC_LIGHT _LgiColours[L_DIALOG] = Rgb24(0xbc, 0xa9, 0xd4); // LC_DIALOG _LgiColours[L_WORKSPACE] = Rgb24(0xeb, 0xe6, 0xf2); // LC_WORKSPACE _LgiColours[L_TEXT] = Rgb24(0x35, 0x1f, 0x4f); // LC_TEXT _LgiColours[L_FOCUS_SEL_BACK] = Rgb24(0xbf, 0x67, 0x93); // LC_FOCUS_SEL_BACK _LgiColours[L_FOCUS_SEL_FORE] = Rgb24(0xff, 0xff, 0xff); // LC_FOCUS_SEL_FORE _LgiColours[L_ACTIVE_TITLE] = Rgb24(0x70, 0x3a, 0xec); // LC_ACTIVE_TITLE _LgiColours[L_ACTIVE_TITLE_TEXT] = Rgb24(0xff, 0xff, 0xff); // LC_ACTIVE_TITLE_TEXT _LgiColours[L_INACTIVE_TITLE] = Rgb24(0x80, 0x80, 0x80); // LC_INACTIVE_TITLE _LgiColours[L_INACTIVE_TITLE_TEXT] = Rgb24(0x40, 0x40, 0x40); // LC_INACTIVE_TITLE_TEXT _LgiColours[L_MENU_BACKGROUND] = Rgb24(0xbc, 0xa9, 0xd4); // LC_MENU_BACKGROUND _LgiColours[L_MENU_TEXT] = Rgb24(0x35, 0x1f, 0x4f); // LC_MENU_TEXT _LgiColours[L_NON_FOCUS_SEL_BACK] = Rgb24(0xbc, 0xa9, 0xd4); // LC_NON_FOCUS_SEL_BACK _LgiColours[L_NON_FOCUS_SEL_FORE] = Rgb24(0x35, 0x1f, 0x4f); // LC_NON_FOCUS_SEL_FORE LAssert(i == LC_MAXIMUM); #elif defined __GTK_H__ Gtk::GtkSettings *set = Gtk::gtk_settings_get_default(); if (!set) { printf("%s:%i - gtk_settings_get_for_screen failed.\n", _FL); return; } char PropName[] = "gtk-color-scheme"; Gtk::gchararray Value = 0; Gtk::g_object_get(set, PropName, &Value, NULL); LString::Array Lines = LString(Value).SplitDelimit("\n"); Gtk::g_free(Value); g_object_unref(set); LHashTbl, int> Colours(0, -1); auto ScreenBits = GdcD->GetBits(); for (int i=0; i> 8) & 0xff) | ((c >> 16) & 0xff00) | ((c >> 24) & 0xff0000); } Colours.Add(var, c24); // printf("Color %s = %x\n", var, c24); } } #define LookupColour(name, default) ((Colours.Find(name) >= 0) ? LColour(Colours.Find(name),24) : default) LColour Med = LookupColour("bg_color", LColour(0xe8, 0xe8, 0xe8)); LColour White(255, 255, 255); LColour Black(0, 0, 0); LColour Sel(0x33, 0x99, 0xff); _LgiColours[L_SHADOW] = GdcMixColour(Med, Black, 0.25); // LC_SHADOW _LgiColours[L_LOW] = GdcMixColour(Med, Black, 0.5); // LC_LOW _LgiColours[L_MED] = Med; // LC_MED _LgiColours[L_HIGH] = GdcMixColour(Med, White, 0.5); // LC_HIGH _LgiColours[L_LIGHT] = GdcMixColour(Med, White, 0.25); // LC_LIGHT _LgiColours[L_DIALOG] = Med; // LC_DIALOG _LgiColours[L_WORKSPACE] = LookupColour("base_color", White); // LC_WORKSPACE _LgiColours[L_TEXT] = LookupColour("text_color", Black); // LC_TEXT _LgiColours[L_FOCUS_SEL_BACK] = LookupColour("selected_bg_color", Sel); // LC_FOCUS_SEL_BACK _LgiColours[L_FOCUS_SEL_FORE] = LookupColour("selected_fg_color", White); // LC_FOCUS_SEL_FORE _LgiColours[L_ACTIVE_TITLE] = LookupColour("selected_bg_color", Sel); // LC_ACTIVE_TITLE _LgiColours[L_ACTIVE_TITLE_TEXT] = LookupColour("selected_fg_color", White); // LC_ACTIVE_TITLE_TEXT _LgiColours[L_INACTIVE_TITLE].Rgb(0xc0, 0xc0, 0xc0); // LC_INACTIVE_TITLE _LgiColours[L_INACTIVE_TITLE_TEXT].Rgb(0x80, 0x80, 0x80); // LC_INACTIVE_TITLE_TEXT _LgiColours[L_MENU_BACKGROUND] = LookupColour("bg_color", White); // LC_MENU_BACKGROUND _LgiColours[L_MENU_TEXT] = _LgiColours[L_TEXT]; // LC_MENU_TEXT _LgiColours[L_NON_FOCUS_SEL_BACK] = _LgiColours[L_FOCUS_SEL_BACK].Mix(_LgiColours[L_WORKSPACE]); // LC_NON_FOCUS_SEL_BACK _LgiColours[L_NON_FOCUS_SEL_FORE] = _LgiColours[L_TEXT]; // LC_NON_FOCUS_SEL_FORE // printf("_LgiColours[L_FOCUS_SEL_BACK]=%s\n", _LgiColours[L_FOCUS_SEL_BACK].GetStr()); // printf("_LgiColours[L_NON_FOCUS_SEL_BACK]=%s\n", _LgiColours[L_NON_FOCUS_SEL_BACK].GetStr()); #elif defined(WINDOWS) /* for (int i=0; i<30; i++) { auto c = GetSysColor(i); auto r = GetRValue(c); auto g = GetGValue(c); auto b = GetBValue(c); LgiTrace("[%i]=%i,%i,%i %x,%x,%x\n", i, r, g, b, r, g, b); } */ _LgiColours[L_SHADOW] = ConvertWinColour(GetSysColor(COLOR_3DDKSHADOW)); // LC_SHADOW _LgiColours[L_LOW] = ConvertWinColour(GetSysColor(COLOR_3DSHADOW)); // LC_LOW _LgiColours[L_MED] = ConvertWinColour(GetSysColor(COLOR_3DFACE)); // LC_MED _LgiColours[L_HIGH] = ConvertWinColour(GetSysColor(COLOR_3DLIGHT)); // LC_HIGH _LgiColours[L_LIGHT] = ConvertWinColour(GetSysColor(COLOR_3DHIGHLIGHT)); // LC_LIGHT _LgiColours[L_DIALOG] = ConvertWinColour(GetSysColor(COLOR_3DFACE)); // LC_DIALOG _LgiColours[L_WORKSPACE] = ConvertWinColour(GetSysColor(COLOR_WINDOW)); // LC_WORKSPACE _LgiColours[L_TEXT] = ConvertWinColour(GetSysColor(COLOR_WINDOWTEXT)); // LC_TEXT _LgiColours[L_FOCUS_SEL_BACK] = ConvertWinColour(GetSysColor(COLOR_HIGHLIGHT)); // LC_FOCUS_SEL_BACK _LgiColours[L_FOCUS_SEL_FORE] = ConvertWinColour(GetSysColor(COLOR_HIGHLIGHTTEXT)); // LC_FOCUS_SEL_FORE _LgiColours[L_ACTIVE_TITLE] = ConvertWinColour(GetSysColor(COLOR_ACTIVECAPTION)); // LC_ACTIVE_TITLE _LgiColours[L_ACTIVE_TITLE_TEXT] = ConvertWinColour(GetSysColor(COLOR_CAPTIONTEXT)); // LC_ACTIVE_TITLE_TEXT _LgiColours[L_INACTIVE_TITLE] = ConvertWinColour(GetSysColor(COLOR_INACTIVECAPTION)); // LC_INACTIVE_TITLE _LgiColours[L_INACTIVE_TITLE_TEXT] = ConvertWinColour(GetSysColor(COLOR_INACTIVECAPTIONTEXT)); // LC_INACTIVE_TITLE_TEXT _LgiColours[L_MENU_BACKGROUND] = ConvertWinColour(GetSysColor(COLOR_MENU)); // LC_MENU_BACKGROUND _LgiColours[L_MENU_TEXT] = ConvertWinColour(GetSysColor(COLOR_MENUTEXT)); // LC_MENU_TEXT _LgiColours[L_NON_FOCUS_SEL_BACK] = ConvertWinColour(GetSysColor(COLOR_3DLIGHT)); // LC_NON_FOCUS_SEL_BACK _LgiColours[L_NON_FOCUS_SEL_FORE] = ConvertWinColour(GetSysColor(COLOR_BTNTEXT)); // LC_NON_FOCUS_SEL_FORE #else // defaults for non-windows, plain grays #if defined(LINUX) && !defined(LGI_SDL) WmColour c; Proc_LgiWmGetColour WmGetColour = 0; LLibrary *WmLib = LAppInst->GetWindowManagerLib(); if (WmLib) { WmGetColour = (Proc_LgiWmGetColour) WmLib->GetAddress("LgiWmGetColour"); } #define SetCol(def) \ if (WmGetColour && WmGetColour(i, &c)) \ _LgiColours[i++] = Rgb24(c.r, c.g, c.b); \ else \ _LgiColours[i++] = def; #else // MAC #define SetCol(def) \ _LgiColours[i++] = def; #endif _LgiColours[L_SHADOW].Rgb(96, 96, 96); // LC_SHADOW _LgiColours[L_LOW].Rgb(150, 150, 150); // LC_LOW #ifdef HAIKU _LgiColours[L_MED].Rgb(216, 216, 216); // LC_MED #else _LgiColours[L_MED].Rgb(230, 230, 230); // LC_MED #endif _LgiColours[L_HIGH].Rgb(240, 240, 240); // LC_HIGH _LgiColours[L_LIGHT].Rgb(255, 255, 255); // LC_LIGHT _LgiColours[L_DIALOG].Rgb(216, 216, 216); // LC_DIALOG _LgiColours[L_WORKSPACE].Rgb(0xff, 0xff, 0xff); // LC_WORKSPACE _LgiColours[L_TEXT].Rgb(0, 0, 0); // LC_TEXT _LgiColours[L_FOCUS_SEL_BACK].Rgb(0x4a, 0x59, 0xa5); // LC_FOCUS_SEL_BACK _LgiColours[L_FOCUS_SEL_FORE].Rgb(0xff, 0xff, 0xff); // LC_FOCUS_SEL_FORE _LgiColours[L_ACTIVE_TITLE].Rgb(0, 0, 0x80); // LC_ACTIVE_TITLE _LgiColours[L_ACTIVE_TITLE_TEXT].Rgb(0xff, 0xff, 0xff); // LC_ACTIVE_TITLE_TEXT _LgiColours[L_INACTIVE_TITLE].Rgb(0x80, 0x80, 0x80); // LC_INACTIVE_TITLE _LgiColours[L_INACTIVE_TITLE_TEXT].Rgb(0x40, 0x40, 0x40); // LC_INACTIVE_TITLE_TEXT _LgiColours[L_MENU_BACKGROUND].Rgb(222, 222, 222); // LC_MENU_BACKGROUND _LgiColours[L_MENU_TEXT].Rgb(0, 0, 0); // LC_MENU_TEXT _LgiColours[L_NON_FOCUS_SEL_BACK].Rgb(222, 222, 222); // LC_NON_FOCUS_SEL_BACK _LgiColours[L_NON_FOCUS_SEL_FORE].Rgb(0, 0, 0); // LC_NON_FOCUS_SEL_FORE #endif // Tweak if (LGetOs() == LGI_OS_WIN32 || LGetOs() == LGI_OS_WIN64) { // Win32 doesn't seem to get this right, so we just tweak it here _LgiColours[L_LIGHT] = _LgiColours[L_MED].Mix(_LgiColours[L_LIGHT]); } _LgiColours[L_DEBUG_CURRENT_LINE].Rgb(0xff, 0xe0, 0x00); #ifdef MAC _LgiColours[L_TOOL_TIP].Rgb(0xef, 0xef, 0xef); #else - _LgiColours[L_TOOL_TIP].Rgb(255, 255, 231); + _LgiColours[L_TOOL_TIP].Rgb(0xf7, 0xf0, 0xd5); #endif } LColour::LColour(LSystemColour sc) { if (sc < L_MAXIMUM) *this = _LgiColours[sc]; } LColour LSysColour(LSystemColour Colour) { return Colour < L_MAXIMUM ? _LgiColours[Colour] : LColour(); } bool LColourLoad(const char *Json) { #ifdef LGI_STATIC // Not supported due to no CSS in static build return false; #else if (!Json) return false; LHashTbl,LSystemColour> Map; #define _(name) Map.Add("L_" #name, L_##name); _SystemColour(); #undef _ LFile f(Json, O_READ); if (!f.IsOpen()) return false; LJson j(f.Read()); for (auto i: j.GetArray("colors")) { auto p = i.Get(); auto c = Map.Find(p.key); if (c > L_TRANSPARENT && c < L_MAXIMUM) { LCss::ColorDef cd(p.value); if (cd.Type == LCss::ColorRgb) _LgiColours[c].Set(cd.Rgb32, 32); else LAssert(!"Invalid colour type."); } } return true; #endif } diff --git a/src/common/Lgi/AppCommon.cpp b/src/common/Lgi/AppCommon.cpp --- a/src/common/Lgi/AppCommon.cpp +++ b/src/common/Lgi/AppCommon.cpp @@ -1,304 +1,306 @@ #include "lgi/common/Lgi.h" #include "lgi/common/PopupNotification.h" #include "AppPriv.h" #ifdef LINUX namespace Gtk { #include "LgiWidget.h" } #endif class LPopupNotificationFactory { public: LHashTbl, LPopupNotification*> map; void Empty() { while (map.Length()) { auto p = map.begin(); if (p != map.end()) { delete (*p).value; } } } } PopupNotificationFactory; void LApp::CommonCleanup() { PopupNotificationFactory.Empty(); } LString LApp::GetConfigPath() { #if defined(LINUX) ::LFile::Path p(LSP_HOME); p += ".config"; #else ::LFile::Path p(LSP_USER_APP_DATA); p += "MemecodeLgi"; if (!p.Exists()) FileDev->CreateFolder(p); #endif if (p.Exists()) { p += "lgi.json"; return p.GetFull(); } p--; p += ".lgi.json"; return p.GetFull(); } ::LString LApp::GetConfig(const char *Variable) { auto c = d->GetConfig(); return c ? c->Get(Variable) : NULL; } void LApp::SetConfig(const char *Variable, const char *Value) { auto c = d->GetConfig(); if (c && c->Set(Variable, Value)) d->SaveConfig(); } /////////////////////////////////////////////////////////////////////////////////// LJson *LAppPrivate::GetConfig() { if (!Config) { auto Path = Owner->GetConfigPath(); if (Config.Reset(new LJson())) { ::LFile f; if (f.Open(Path, O_READ)) Config->SetJson(f.Read()); bool Dirty = false; #define DEFAULT(var, val) \ if (Config->Get(var).Length() == 0) \ Dirty |= Config->Set(var, val); #ifdef LINUX DEFAULT(LApp::CfgLinuxKeysShift, "GDK_SHIFT_MASK"); DEFAULT(LApp::CfgLinuxKeysCtrl, "GDK_CONTROL_MASK"); DEFAULT(LApp::CfgLinuxKeysAlt, "GDK_MOD1_MASK"); auto Sys = #if defined(MAC) "GDK_MOD2_MASK"; #else "GDK_SUPER_MASK"; #endif DEFAULT(LApp::CfgLinuxKeysSystem, Sys); DEFAULT("Linux.Keys.Debug", "0"); auto str = [](Gtk::GDK_MouseButtons btn) { LString s; s.Printf("%i", btn); return s; }; DEFAULT(LApp::CfgLinuxMouseLeft, str(Gtk::GDK_LEFT_BTN)); DEFAULT(LApp::CfgLinuxMouseMiddle, str(Gtk::GDK_MIDDLE_BTN)); DEFAULT(LApp::CfgLinuxMouseRight, str(Gtk::GDK_RIGHT_BTN)); DEFAULT(LApp::CfgLinuxMouseBack, str(Gtk::GDK_BACK_BTN)); DEFAULT(LApp::CfgLinuxMouseForward, str(Gtk::GDK_FORWARD_BTN)); #endif DEFAULT("Language", "-"); DEFAULT("Fonts.Comment", "Fonts are specified in the : format."); DEFAULT(LApp::CfgFontsGlyphSub, "1"); DEFAULT(LApp::CfgFontsPointSizeOffset, "0"); DEFAULT(LApp::CfgFontsSystemFont, "-"); DEFAULT(LApp::CfgFontsBoldFont, "-"); DEFAULT(LApp::CfgFontsMonoFont, "-"); DEFAULT(LApp::CfgFontsSmallFont, "-"); DEFAULT(LApp::CfgFontsCaptionFont, "-"); DEFAULT(LApp::CfgFontsMenuFont, "-"); DEFAULT("Colours.Comment", "Use CSS hex definitions here, ie #RRGGBB in hex."); #define _(name) \ if (L_##name < L_MAXIMUM) \ DEFAULT("Colours.L_" #name, "-"); _SystemColour(); #undef _ if (Dirty) SaveConfig(); LgiTrace("Using LGI config: '%s'\n", Path.Get()); } else printf("%s:%i - Alloc failed.\n", _FL); } return Config; } bool LAppPrivate::SaveConfig() { auto Path = Owner->GetConfigPath(); if (!Path || !Config) return false; ::LFile f; if (!f.Open(Path, O_WRITE)) return false; f.SetSize(0); return f.Write(Config->GetJson()); } //////////////////////////////////////////////////////////////////////////////////////////////// LPopupNotification *LPopupNotification::Message(LWindow *ref, LString msg) { if (!ref) return NULL; auto w = PopupNotificationFactory.map.Find(ref); if (w) { w->Add(ref, msg); } else { w = new LPopupNotification(ref, msg); if (w) PopupNotificationFactory.map.Add(ref, w); } return w; } LPopupNotification::LPopupNotification(LWindow *ref, LString msg) { + #if 1 cFore = cBack = cBorder = L_TOOL_TIP; if (cFore.ToHLS()) { cBorder.SetHLS(cBorder.GetH(), (int)(cBorder.GetL()*0.6), cBorder.GetS()); cFore.SetHLS (cFore.GetH(), (int)(cFore.GetL()*0.4), cFore.GetS()); } else { cBorder = cBorder.Mix(LColour::Black, 0.05f); cFore = cFore.Mix(LColour::Black, 0.5f); } - - // Fore = LColour(0xd4, 0xb8, 0x62); - // Back = LColour(0xf7, 0xf0, 0xd5); + #else + cBorder = cFore = LColour(0xd4, 0xb8, 0x62); + cBack = LColour(0xf7, 0xf0, 0xd5); + #endif SetTitleBar(false); SetWillFocus(false); // Don't take focus Name("Notification"); if (ref) RefWnd = ref; if (ref && msg) Add(ref, msg); } LPopupNotification::~LPopupNotification() { LAssert(PopupNotificationFactory.map.Find(RefWnd) == this); PopupNotificationFactory.map.Delete(RefWnd); } LPoint LPopupNotification::CalcSize() { LPoint p; for (auto ds: Msgs) { p.x = MAX(p.x, ds->X()); p.y += ds->Y(); } p.x += Border * 2 + Decor.x; p.y += Border * 2 + Decor.y; return p; } void LPopupNotification::Init() { if (!RefWnd) { LAssert(!"No reference window."); return; } auto r = RefWnd->GetPos(); auto Sz = CalcSize(); LRect pos; pos.ZOff(Sz.x - 1, Sz.y - 1); pos.Offset(r.x2 - pos.X(), r.y2 - pos.Y()); SetPos(pos); SetAlwaysOnTop(true); SetPulse(500); if (!IsAttached()) Attach(0); Visible(true); } void LPopupNotification::Add(LWindow *ref, LString msg) { if (msg) { HideTs = LCurrentTime(); Msgs.Add(new LDisplayString(GetFont(), msg)); } if (ref && RefWnd != ref) RefWnd = ref; if (RefWnd && Msgs.Length() > 0) Init(); } void LPopupNotification::OnPaint(LSurface *pDC) { pDC->Colour(cBorder); auto c = GetClient(); pDC->Box(&c); c.Inset(1, 1); pDC->Colour(cBack); pDC->Rectangle(&c); auto f = GetFont(); f->Fore(cFore); f->Back(cBack); f->Transparent(true); int x = c.x1 + Border; int y = c.y1 + Border; for (auto ds: Msgs) { ds->Draw(pDC, x, y); y += ds->Y(); } } void LPopupNotification::OnPulse() { if (HideTs && LCurrentTime() >= HideTs + ShowMs) { HideTs = 0; Visible(false); SetPulse(); Msgs.DeleteObjects(); } }