123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- /*
- * ModDoc.h
- * --------
- * Purpose: Converting between various module formats.
- * Notes : (currently none)
- * Authors: OpenMPT Devs
- * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
- */
- #pragma once
- #include "openmpt/all/BuildSettings.hpp"
- #include "Sndfile.h"
- #include "../common/misc_util.h"
- #include "Undo.h"
- #include "Notification.h"
- #include "UpdateHints.h"
- #include <time.h>
- OPENMPT_NAMESPACE_BEGIN
- class EncoderFactoryBase;
- class CChildFrame;
- /////////////////////////////////////////////////////////////////////////
- // Split Keyboard Settings (pattern editor)
- struct SplitKeyboardSettings
- {
- enum
- {
- splitOctaveRange = 9,
- };
- bool IsSplitActive() const { return (octaveLink && (octaveModifier != 0)) || (splitInstrument != 0) || (splitVolume != 0); }
- int octaveModifier = 0; // determines by how many octaves the notes should be transposed up or down
- ModCommand::NOTE splitNote = NOTE_MIDDLEC - 1;
- ModCommand::INSTR splitInstrument = 0;
- ModCommand::VOL splitVolume = 0;
- bool octaveLink = false; // apply octaveModifier
- };
- enum InputTargetContext : int8;
- struct LogEntry
- {
- LogLevel level;
- mpt::ustring message;
- LogEntry() : level(LogInformation) {}
- LogEntry(LogLevel l, const mpt::ustring &m) : level(l), message(m) {}
- };
- enum LogMode
- {
- LogModeInstantReporting,
- LogModeGather,
- };
- class ScopedLogCapturer
- {
- private:
- CModDoc &m_modDoc;
- LogMode m_oldLogMode;
- CString m_title;
- CWnd *m_pParent;
- bool m_showLog;
- public:
- ScopedLogCapturer(CModDoc &modDoc, const CString &title = {}, CWnd *parent = nullptr, bool showLog = true);
- ~ScopedLogCapturer();
- void ShowLog(bool force = false);
- void ShowLog(const CString &preamble, bool force = false);
- [[deprecated]] void ShowLog(const std::string &preamble, bool force = false);
- void ShowLog(const mpt::ustring &preamble, bool force = false);
- };
- struct PlayNoteParam
- {
- std::bitset<128> *m_notesPlaying = nullptr;
- SmpLength m_loopStart = 0, m_loopEnd = 0, m_sampleOffset = 0;
- int32 m_volume = -1;
- SAMPLEINDEX m_sample = 0;
- INSTRUMENTINDEX m_instr = 0;
- CHANNELINDEX m_currentChannel = CHANNELINDEX_INVALID;
- ModCommand::NOTE m_note;
- PlayNoteParam(ModCommand::NOTE note) : m_note(note) { }
- PlayNoteParam& LoopStart(SmpLength loopStart) { m_loopStart = loopStart; return *this; }
- PlayNoteParam& LoopEnd(SmpLength loopEnd) { m_loopEnd = loopEnd; return *this; }
- PlayNoteParam& Offset(SmpLength sampleOffset) { m_sampleOffset = sampleOffset; return *this; }
- PlayNoteParam& Volume(int32 volume) { m_volume = volume; return *this; }
- PlayNoteParam& Sample(SAMPLEINDEX sample) { m_sample = sample; return *this; }
- PlayNoteParam& Instrument(INSTRUMENTINDEX instr) { m_instr = instr; return *this; }
- PlayNoteParam& Channel(CHANNELINDEX channel) { m_currentChannel = channel; return *this; }
- PlayNoteParam& CheckNNA(std::bitset<128> ¬esPlaying) { m_notesPlaying = ¬esPlaying; return *this; }
- };
- enum class RecordGroup : uint8
- {
- NoGroup = 0,
- Group1 = 1,
- Group2 = 2,
- };
- class CModDoc final : public CDocument
- {
- protected:
- friend ScopedLogCapturer;
- mutable std::vector<LogEntry> m_Log;
- LogMode m_LogMode = LogModeInstantReporting;
- CSoundFile m_SndFile;
- HWND m_hWndFollow = nullptr;
- FlagSet<Notification::Type, uint16> m_notifyType;
- Notification::Item m_notifyItem = 0;
- CSize m_szOldPatternScrollbarsPos = { -10, -10 };
- CPatternUndo m_PatternUndo;
- CSampleUndo m_SampleUndo;
- CInstrumentUndo m_InstrumentUndo;
- SplitKeyboardSettings m_SplitKeyboardSettings; // this is maybe not the best place to keep them, but it should do the job
- time_t m_creationTime;
- std::atomic<bool> m_modifiedAutosave = false; // Modified since last autosave?
- public:
- class NoteToChannelMap : public std::array<CHANNELINDEX, NOTE_MAX - NOTE_MIN + 1>
- {
- public:
- NoteToChannelMap() { fill(CHANNELINDEX_INVALID); }
- };
- NoteToChannelMap m_noteChannel; // Note -> Preview channel assignment
- bool m_ShowSavedialog = false;
- bool m_bHasValidPath = false; //becomes true if document is loaded or saved.
- protected:
- // Note-off event buffer for MIDI sustain pedal
- std::array<std::vector<uint32>, 16> m_midiSustainBuffer;
- std::array<std::bitset<128>, 16> m_midiPlayingNotes;
- std::bitset<16> m_midiSustainActive;
- std::bitset<MAX_BASECHANNELS> m_bsMultiRecordMask;
- std::bitset<MAX_BASECHANNELS> m_bsMultiSplitRecordMask;
- protected: // create from serialization only
- CModDoc();
- DECLARE_DYNCREATE(CModDoc)
- // public members
- public:
- CSoundFile &GetSoundFile() { return m_SndFile; }
- const CSoundFile &GetSoundFile() const { return m_SndFile; }
- #if MPT_COMPILER_CLANG
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Woverloaded-virtual"
- #endif // MPT_COMPILER_CLANG
- bool IsModified() const { return m_bModified != FALSE; } // Work-around: CDocument::IsModified() is not const...
- #if MPT_COMPILER_CLANG
- #pragma clang diagnostic pop
- #endif // MPT_COMPILER_CLANG
- void SetModified(bool modified = true);
- bool ModifiedSinceLastAutosave();
- void SetShowSaveDialog(bool b) { m_ShowSavedialog = b; }
- void PostMessageToAllViews(UINT uMsg, WPARAM wParam = 0, LPARAM lParam = 0);
- void SendNotifyMessageToAllViews(UINT uMsg, WPARAM wParam = 0, LPARAM lParam = 0);
- void SendMessageToActiveView(UINT uMsg, WPARAM wParam = 0, LPARAM lParam = 0);
- MODTYPE GetModType() const { return m_SndFile.m_nType; }
- INSTRUMENTINDEX GetNumInstruments() const { return m_SndFile.m_nInstruments; }
- SAMPLEINDEX GetNumSamples() const { return m_SndFile.m_nSamples; }
- // Logging for general progress and error events.
- void AddToLog(LogLevel level, const mpt::ustring &text) const;
- /*[[deprecated]]*/ void AddToLog(const CString &text) const { AddToLog(LogInformation, mpt::ToUnicode(text)); }
- /*[[deprecated]]*/ void AddToLog(const std::string &text) const { AddToLog(LogInformation, mpt::ToUnicode(mpt::Charset::Locale, text)); }
- /*[[deprecated]]*/ void AddToLog(const char *text) const { AddToLog(LogInformation, mpt::ToUnicode(mpt::Charset::Locale, text ? text : "")); }
- const std::vector<LogEntry> & GetLog() const { return m_Log; }
- mpt::ustring GetLogString() const;
- LogLevel GetMaxLogLevel() const;
- protected:
- LogMode GetLogMode() const { return m_LogMode; }
- void SetLogMode(LogMode mode) { m_LogMode = mode; }
- void ClearLog();
- UINT ShowLog(const CString &preamble, const CString &title = {}, CWnd *parent = nullptr);
- UINT ShowLog(const CString &title = {}, CWnd *parent = nullptr) { return ShowLog(_T(""), title, parent); }
- public:
- void ClearFilePath() { m_strPathName.Empty(); }
- void ViewPattern(UINT nPat, UINT nOrd);
- void ViewSample(UINT nSmp);
- void ViewInstrument(UINT nIns);
- HWND GetFollowWnd() const { return m_hWndFollow; }
- void SetFollowWnd(HWND hwnd);
- void SetNotifications(FlagSet<Notification::Type> type, Notification::Item item = 0) { m_notifyType = type; m_notifyItem = item; }
- FlagSet<Notification::Type, uint16> GetNotificationType() const { return m_notifyType; }
- Notification::Item GetNotificationItem() const { return m_notifyItem; }
- void ActivateWindow();
- void OnSongProperties();
- void PrepareUndoForAllPatterns(bool storeChannelInfo = false, const char *description = "");
- CPatternUndo &GetPatternUndo() { return m_PatternUndo; }
- CSampleUndo &GetSampleUndo() { return m_SampleUndo; }
- CInstrumentUndo &GetInstrumentUndo() { return m_InstrumentUndo; }
- SplitKeyboardSettings &GetSplitKeyboardSettings() { return m_SplitKeyboardSettings; }
- time_t GetCreationTime() const { return m_creationTime; }
- // operations
- public:
- bool ChangeModType(MODTYPE wType);
- bool ChangeNumChannels(CHANNELINDEX nNewChannels, const bool showCancelInRemoveDlg = true);
- bool RemoveChannels(const std::vector<bool> &keepMask, bool verbose = false);
- void CheckUsedChannels(std::vector<bool> &usedMask, CHANNELINDEX maxRemoveCount = MAX_BASECHANNELS) const;
- CHANNELINDEX ReArrangeChannels(const std::vector<CHANNELINDEX> &fromToArray, const bool createUndoPoint = true);
- SAMPLEINDEX ReArrangeSamples(const std::vector<SAMPLEINDEX> &newOrder);
- INSTRUMENTINDEX ReArrangeInstruments(const std::vector<INSTRUMENTINDEX> &newOrder, deleteInstrumentSamples removeSamples = doNoDeleteAssociatedSamples);
- SEQUENCEINDEX ReArrangeSequences(const std::vector<SEQUENCEINDEX> &newOrder);
- bool ConvertInstrumentsToSamples();
- bool ConvertSamplesToInstruments();
- PLUGINDEX RemovePlugs(const std::vector<bool> &keepMask);
- bool RemovePlugin(PLUGINDEX plugin);
- void ClonePlugin(SNDMIXPLUGIN &target, const SNDMIXPLUGIN &source);
- void AppendModule(const CSoundFile &source);
- // Create a new pattern and, if order position is specified, inserts it into the order list.
- PATTERNINDEX InsertPattern(ROWINDEX rows, ORDERINDEX ord = ORDERINDEX_INVALID);
- SAMPLEINDEX InsertSample();
- INSTRUMENTINDEX InsertInstrument(SAMPLEINDEX sample = SAMPLEINDEX_INVALID, INSTRUMENTINDEX duplicateSource = INSTRUMENTINDEX_INVALID, bool silent = false);
- INSTRUMENTINDEX InsertInstrumentForPlugin(PLUGINDEX plug);
- INSTRUMENTINDEX HasInstrumentForPlugin(PLUGINDEX plug) const;
- void InitializeInstrument(ModInstrument *pIns);
- bool RemoveOrder(SEQUENCEINDEX nSeq, ORDERINDEX nOrd);
- bool RemovePattern(PATTERNINDEX nPat);
- bool RemoveSample(SAMPLEINDEX nSmp);
- bool RemoveInstrument(INSTRUMENTINDEX nIns);
- void ProcessMIDI(uint32 midiData, INSTRUMENTINDEX ins, IMixPlugin *plugin, InputTargetContext ctx);
- CHANNELINDEX PlayNote(PlayNoteParam ¶ms, NoteToChannelMap *noteChannel = nullptr);
- bool NoteOff(UINT note, bool fade = false, INSTRUMENTINDEX ins = INSTRUMENTINDEX_INVALID, CHANNELINDEX currentChn = CHANNELINDEX_INVALID);
- void CheckNNA(ModCommand::NOTE note, INSTRUMENTINDEX ins, std::bitset<128> &playingNotes);
- void UpdateOPLInstrument(SAMPLEINDEX smp);
- bool IsNotePlaying(UINT note, SAMPLEINDEX nsmp = 0, INSTRUMENTINDEX nins = 0);
- bool MuteChannel(CHANNELINDEX nChn, bool bMute);
- bool UpdateChannelMuteStatus(CHANNELINDEX nChn);
- bool MuteSample(SAMPLEINDEX nSample, bool bMute);
- bool MuteInstrument(INSTRUMENTINDEX nInstr, bool bMute);
- // Returns true if toggling the mute status of a channel should set the document as modified given the current module format and settings.
- bool MuteToggleModifiesDocument() const;
- bool SoloChannel(CHANNELINDEX nChn, bool bSolo);
- bool IsChannelSolo(CHANNELINDEX nChn) const;
- bool SurroundChannel(CHANNELINDEX nChn, bool bSurround);
- bool SetChannelGlobalVolume(CHANNELINDEX nChn, uint16 nVolume);
- bool SetChannelDefaultPan(CHANNELINDEX nChn, uint16 nPan);
- bool IsChannelMuted(CHANNELINDEX nChn) const;
- bool IsSampleMuted(SAMPLEINDEX nSample) const;
- bool IsInstrumentMuted(INSTRUMENTINDEX nInstr) const;
- bool NoFxChannel(CHANNELINDEX nChn, bool bNoFx, bool updateMix = true);
- bool IsChannelNoFx(CHANNELINDEX nChn) const;
- RecordGroup GetChannelRecordGroup(CHANNELINDEX channel) const;
- void SetChannelRecordGroup(CHANNELINDEX channel, RecordGroup recordGroup);
- void ToggleChannelRecordGroup(CHANNELINDEX channel, RecordGroup recordGroup);
- void ReinitRecordState(bool unselect = true);
- CHANNELINDEX GetNumChannels() const { return m_SndFile.m_nChannels; }
- UINT GetPatternSize(PATTERNINDEX nPat) const;
- bool IsChildSample(INSTRUMENTINDEX nIns, SAMPLEINDEX nSmp) const;
- INSTRUMENTINDEX FindSampleParent(SAMPLEINDEX sample) const;
- SAMPLEINDEX FindInstrumentChild(INSTRUMENTINDEX nIns) const;
- bool MoveOrder(ORDERINDEX nSourceNdx, ORDERINDEX nDestNdx, bool bUpdate = true, bool bCopy = false, SEQUENCEINDEX nSourceSeq = SEQUENCEINDEX_INVALID, SEQUENCEINDEX nDestSeq = SEQUENCEINDEX_INVALID);
- BOOL ExpandPattern(PATTERNINDEX nPattern);
- BOOL ShrinkPattern(PATTERNINDEX nPattern);
- bool SetDefaultChannelColors() { return SetDefaultChannelColors(0, GetNumChannels()); }
- bool SetDefaultChannelColors(CHANNELINDEX channel) { return SetDefaultChannelColors(channel, channel + 1u); }
- bool SetDefaultChannelColors(CHANNELINDEX minChannel, CHANNELINDEX maxChannel);
- bool SupportsChannelColors() const { return GetModType() & (MOD_TYPE_XM | MOD_TYPE_IT | MOD_TYPE_MPT); }
- bool CopyEnvelope(INSTRUMENTINDEX nIns, EnvelopeType nEnv);
- bool SaveEnvelope(INSTRUMENTINDEX nIns, EnvelopeType nEnv, const mpt::PathString &fileName);
- bool PasteEnvelope(INSTRUMENTINDEX nIns, EnvelopeType nEnv);
- bool LoadEnvelope(INSTRUMENTINDEX nIns, EnvelopeType nEnv, const mpt::PathString &fileName);
- LRESULT ActivateView(UINT nIdView, DWORD dwParam);
- // Notify all views of document updates (GUI thread only)
- void UpdateAllViews(CView *pSender, UpdateHint hint, CObject *pHint=NULL);
- // Notify all views of document updates (for non-GUI threads)
- void UpdateAllViews(UpdateHint hint);
- void GetEditPosition(ROWINDEX &row, PATTERNINDEX &pat, ORDERINDEX &ord);
- LRESULT OnCustomKeyMsg(WPARAM, LPARAM);
- void TogglePluginEditor(UINT m_nCurrentPlugin, bool onlyThisEditor = false);
- void RecordParamChange(PLUGINDEX slot, PlugParamIndex param);
- void LearnMacro(int macro, PlugParamIndex param);
- void SetElapsedTime(ORDERINDEX nOrd, ROWINDEX nRow, bool setSamplePos);
- void SetLoopSong(bool loop);
- // Global settings to pattern effect conversion
- bool GlobalVolumeToPattern();
- bool HasMPTHacks(const bool autofix = false);
- void FixNullStrings();
- // Fix: save pattern scrollbar position when switching to other tab
- CSize GetOldPatternScrollbarsPos() const { return m_szOldPatternScrollbarsPos; };
- void SetOldPatternScrollbarsPos( CSize s ){ m_szOldPatternScrollbarsPos = s; };
- void OnFileWaveConvert(ORDERINDEX nMinOrder, ORDERINDEX nMaxOrder);
- void OnFileWaveConvert(ORDERINDEX nMinOrder, ORDERINDEX nMaxOrder, const std::vector<EncoderFactoryBase*> &encFactories);
- // Returns formatted ModInstrument name.
- // [in] bEmptyInsteadOfNoName: In case of unnamed instrument string, "(no name)" is returned unless this
- // parameter is true is case which an empty name is returned.
- // [in] bIncludeIndex: True to include instrument index in front of the instrument name, false otherwise.
- CString GetPatternViewInstrumentName(INSTRUMENTINDEX nInstr, bool bEmptyInsteadOfNoName = false, bool bIncludeIndex = true) const;
- // Check if a given channel contains data.
- bool IsChannelUnused(CHANNELINDEX nChn) const;
- // Check whether a sample is used.
- // In sample mode, the sample numbers in all patterns are checked.
- // In instrument mode, it is only checked if a sample is referenced by an instrument (but not if the sample is actually played anywhere)
- bool IsSampleUsed(SAMPLEINDEX sample, bool searchInMutedChannels = true) const;
- // Check whether an instrument is used (only for instrument mode).
- bool IsInstrumentUsed(INSTRUMENTINDEX instr, bool searchInMutedChannels = true) const;
- // protected members
- protected:
- void InitializeMod();
- CChildFrame *GetChildFrame(); //rewbs.customKeys
- // Overrides
- // ClassWizard generated virtual function overrides
- //{{AFX_VIRTUAL(CModDoc)
- public:
- BOOL OnNewDocument() override;
- BOOL OnOpenDocument(LPCTSTR lpszPathName) override;
- BOOL OnSaveDocument(LPCTSTR lpszPathName) override
- {
- return OnSaveDocument(lpszPathName ? mpt::PathString::FromCString(lpszPathName) : mpt::PathString()) ? TRUE : FALSE;
- }
- void OnCloseDocument() override;
- void SafeFileClose();
- bool OnSaveDocument(const mpt::PathString &filename, const bool setPath = true);
- #if MPT_COMPILER_CLANG
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Woverloaded-virtual"
- #endif // MPT_COMPILER_CLANG
- void SetPathName(const mpt::PathString &filename, BOOL bAddToMRU = TRUE)
- {
- CDocument::SetPathName(filename.ToCString(), bAddToMRU);
- }
- #if MPT_COMPILER_CLANG
- #pragma clang diagnostic pop
- #endif // MPT_COMPILER_CLANG
- mpt::PathString GetPathNameMpt() const
- {
- return mpt::PathString::FromCString(GetPathName());
- }
- BOOL SaveModified() override;
- bool SaveAllSamples(bool showPrompt = true);
- bool SaveSample(SAMPLEINDEX smp);
- BOOL DoSave(LPCTSTR lpszPathName, BOOL /*bSaveAs*/ = TRUE) override
- {
- return DoSave(lpszPathName ? mpt::PathString::FromCString(lpszPathName) : mpt::PathString());
- }
- BOOL DoSave(const mpt::PathString &filename, bool setPath = true);
- void DeleteContents() override;
- //}}AFX_VIRTUAL
- // Get the sample index for the current pattern cell (resolves instrument note maps, etc)
- SAMPLEINDEX GetSampleIndex(const ModCommand &m, ModCommand::INSTR lastInstr = 0) const;
- // Get group (octave) size from given instrument (or sample in sample mode)
- int GetInstrumentGroupSize(INSTRUMENTINDEX instr) const;
- int GetBaseNote(INSTRUMENTINDEX instr) const;
- ModCommand::NOTE GetNoteWithBaseOctave(int noteOffset, INSTRUMENTINDEX instr) const;
- // Convert a linear volume property to decibels
- static CString LinearToDecibels(double value, double valueAtZeroDB);
- // Convert a panning value to a more readable string
- static CString PanningToString(int32 value, int32 valueAtCenter);
- void SerializeViews() const;
- void DeserializeViews();
- // View MIDI Mapping dialog for given plugin and parameter combination.
- void ViewMIDIMapping(PLUGINDEX plugin = PLUGINDEX_INVALID, PlugParamIndex param = 0);
- // Implementation
- public:
- virtual ~CModDoc();
- // Generated message map functions
- public:
- //{{AFX_MSG(CModDoc)
- afx_msg void OnFileWaveConvert();
- afx_msg void OnFileMidiConvert();
- afx_msg void OnFileOPLExport();
- afx_msg void OnFileCompatibilitySave();
- afx_msg void OnPlayerPlay();
- afx_msg void OnPlayerStop();
- afx_msg void OnPlayerPause();
- afx_msg void OnPlayerPlayFromStart();
- afx_msg void OnPanic();
- afx_msg void OnEditGlobals();
- afx_msg void OnEditPatterns();
- afx_msg void OnEditSamples();
- afx_msg void OnEditInstruments();
- afx_msg void OnEditComments();
- afx_msg void OnShowCleanup();
- afx_msg void OnShowSampleTrimmer();
- afx_msg void OnSetupZxxMacros();
- afx_msg void OnEstimateSongLength();
- afx_msg void OnApproximateBPM();
- afx_msg void OnUpdateXMITMPTOnly(CCmdUI *p);
- afx_msg void OnUpdateHasEditHistory(CCmdUI *p);
- afx_msg void OnUpdateHasMIDIMappings(CCmdUI *p);
- afx_msg void OnUpdateCompatExportableOnly(CCmdUI *p);
- afx_msg void OnPatternRestart() { OnPatternRestart(true); } //rewbs.customKeys
- afx_msg void OnPatternRestart(bool loop); //rewbs.customKeys
- afx_msg void OnPatternPlay(); //rewbs.customKeys
- afx_msg void OnPatternPlayNoLoop(); //rewbs.customKeys
- afx_msg void OnViewEditHistory();
- afx_msg void OnViewMPTHacks();
- afx_msg void OnViewTempoSwingSettings();
- afx_msg void OnSaveCopy();
- afx_msg void OnSaveTemplateModule();
- afx_msg void OnAppendModule();
- afx_msg void OnViewMIDIMapping() { ViewMIDIMapping(); }
- afx_msg void OnChannelManager();
- //}}AFX_MSG
- DECLARE_MESSAGE_MAP()
- private:
- void ChangeFileExtension(MODTYPE nNewType);
- CHANNELINDEX FindAvailableChannel() const;
- };
- /////////////////////////////////////////////////////////////////////////////
- //{{AFX_INSERT_LOCATION}}
- // Microsoft Developer Studio will insert additional declarations immediately before the previous line.
- OPENMPT_NAMESPACE_END
|