c# midi播放器

  • Download source - 64.9 KB

    下载源64.9 KB

  • Download latest from GitHub

    从GitHub下载最新版本

介绍 (Introduction)

Consider visiting my followup article here. It has improved code and a more thorough explanation of MIDI and the Midi library API.

考虑在这里访问我的后续文章 。 它改进了代码,并对MIDI和Midi库API进行了更全面的说明。

Quite some time ago, I wrote a VST and FL Studio plugin which used looped MIDI streams to play audio. I wrote it in C++, but I prototyped many of the MIDI operations in C# first. Later, I expanded this C# prototype code into a MIDI file editing library, which I've provided here, along with an example utility that allows for simple editing of a MIDI file.

相当一段时间以前,我编写了一个VST和FL Studio插件,该插件使用循环的MIDI流播放音频。 我用C ++编写,但是我首先用C#制作了许多MIDI操作的原型。 后来,我将此C#原型代码扩展到了我在此处提供的MIDI文件编辑库中,并提供了一个示例实用程序,该实用程序允许简单地编辑MIDI文件。

Update: Added time signature support. Small bugfix in playback.

更新:添加了时间签名支持。 播放中的小错误修正。

Update 2: Added MIDI message classes for each type of MIDI operation, and several features to the MidiSlicer app

更新2:为每种MIDI操作类型添加了MIDI消息类,并为MidiSlicer应用添加了一些功能

Update 3: Added Normalize and Level scaling. Made the Offset and Length floating point

更新3:添加了标准化和级别缩放。 制作偏移和长度浮点

Update 4: Improvements to correctness of API created MIDI sequences and files, better preview and save.

更新4:改进API创建的MIDI序列和文件的正确性,更好地预览和保存。

Update 5: Added Transpose() option to API and to GUI

更新5:向API和GUI添加了Transpose()选项

Update 6: API enhancements and improvement to overall GUI behavior

更新6: API增强和整体GUI行为的改进

概念化这个混乱 (Conceptualizing this Mess)

MIDI stands for Musical Instrument Digital Interface. What it does is allow you to automate digital instruments similar to how one of those old player pianos work. MIDI contains all the note information - basically the "sheet music" for a song which it can then broadcast to up to 16 different digital audio devices like drum machines and synthesizers, or MIDI capable pianos and the like. Your Windows machine contains a default device that can play numerous synthesized sounds emulating various instruments like pianos and guitars. Your phone does too. Your systems can use this to play MIDI files out of your default audio device - usually your primary sound card or audio hardware. MIDI is often used by phones to store ring tones. That having been said, MIDI was originally designed for musicians, and its primary purpose is to allow musicians to record or "sequence" their performance and save it to a file for replay or editing.

MIDI代表乐器数字接口。 它的作用是让您自动化数字乐器,就像那些老式钢琴之一的工作方式一样。 MIDI包含所有音符信息-基本上是歌曲的“乐谱”,然后可以将其广播到多达16种不同的数字音频设备,例如鼓机和合成器,或具有MIDI功能的钢琴等。 您的Windows计算机包含一个默认设备,该设备可以播放模拟钢琴和吉他等各种乐器的大量合成声音。 您的手机也可以。 您的系统可以使用它来播放默认音频设备(通常是主声卡或音频硬件)中的MIDI文件。 手机通常使用MIDI来存储铃声。 话虽如此,MIDI最初是为音乐家而设计的,其主要目的是允许音乐家录制或“排序”他们的演奏并将其保存到文件中以进行重放或编辑。

MIDI协议 (The MIDI Protocol)

MIDI is first and foremost a wire protocol, and secondly a file format. The protocol consists of realtime MIDI "messages". All a MIDI file is in its essence is the protocol stream stored as a file along with a delta time for each "message" - the time in the song the "message" should be played. A delta time plus a message is called a MIDI event. MIDI events are stored as a stream in the file for replay later. Therefore, understanding the protocol is fundamental to understanding the file format.

MIDI首先是有线协议,其次是文件格式。 该协议包括实时MIDI“消息”。 实质上,所有MIDI文件都是作为文件存储的协议流以及每个“消息”的增量时间-“消息”中歌曲应播放的时间。 增量时间加上一条消息称为MIDI事件。 MIDI事件作为流存储在文件中,以供稍后重播。 因此,了解协议是了解文件格式的基础。

Having been designed in the 1980s, MIDI defines an 8 bit wire protocol. Any strings are ASCII, and most values are 0-127 (7 bits plus a leading 0 bit) with some values being 0-255 (8 bit). There are occasionally multibyte values (larger than 8 bits) in the stream. These are always big-endian, so on Intel platforms you'll have to swap the byte order.

MIDI是1980年代设计的,它定义了8位线协议。 任何字符串都是ASCII,大多数值为0-127(7位加上前导0位),有些值为0-255(8位)。 流中偶尔会有多字节值(大于8位)。 这些始终是big-endian,因此在Intel平台上,您必须交换字节顺序。

A MIDI message at the very least contains an 8 bit "status byte." However, a message may contain additional fields/payload depending on the value of the status byte. The status byte tells us both the "type" of message (in the first 4 bits), plus the "channel" (midi "device") the message is intended for in the final 4 bits - remember the MIDI protocol allows control of up to 16 devices - from here on referred to as channels. Most MIDI messages have additional fields. For example, a "note on" message contains the note number, and the velocity of the note to be played. The following MIDI message is composed of 3 bytes. In the first byte, the 9 half is the code for note on, and 0 half is the code for channel 0. Next the node number for note C, octave 5 (48 hex below) is to be played at maximum velocity (127/7F) as indicated by the final byte, which must have the high bit set to zero, leaving you with a numeric range of 0-127:

MIDI消息至少包含一个8位的“状态字节”。 但是,一条消息可能包含其他字段/有效负载,具体取决于状态字节的值。 状态字节告诉我们消息的“类型”(在前4位中)以及消息在最后4位中用于“通道”(中部“设备”)-记住MIDI协议允许控制向上到16个设备-从此处称为通道。 大多数MIDI消息都有其他字段。 例如,“ note on”消息中包含音符编号和要播放的音符的速度。 以下MIDI信息由3个字节组成。 在第一个字节中,9的一半是音符打开的代码,0的一半是通道0的代码。接下来,音符C的节点号八度5(下面的48十六进制)将以最大速度(127 / 7F),如最后一个字节所示,该字节必须将高位设置为零,从而使您的数字范围为0-127:

90 48 7F

This will cause the device to strike the note and hold it until a note off message is found for that same note:

这将导致设备敲击该音符并按住它,直到找到该音符的音符关闭消息为止:

80 48 7F

The only difference between a note off and a note on is the first nibble is 8 hex instead of 9 hex. Most devices won't use the velocity byte for a note off message but send it anyway. I typically will use the same value I specified in note on, or zero in cases where I don't know the corresponding note on velocity. Either way should work with MIDI devices out there, but it's always possible that a device is weird. The thing about this protocol is you have to be kind of forgiving of dodgy devices, and that requires good, old fashioned testing.

音符关闭和音符打开之间的唯一区别是第一个半字节是8进制而不是9十六进制。 大多数设备不会将速度字节用于音符关闭消息,但无论如何都要发送。 通常,我将使用在音符上指定的相同值,或者在我不知道速度的相应音符的情况下使用零。 两种方法可以与MIDI设备一起使用,但是设备总是很奇怪。 关于该协议的问题是,您必须宽容狡猾的设备,这需要良好的老式测试。

Again, different messages are different lengths. The patch/program change message indicates which "sound" a channel will use. Often MIDI devices such as synthesizers can produce many different kinds of sounds. This message allows you to send a 7-bit (0-127/7F) code (encoded as a full byte with the high bit 0) that indicates the patch to use. Selecting patch 2 gives us:

同样,不同的消息具有不同的长度。 补丁/程序更改消息指示通道将使用哪个“声音”。 诸如合成器之类的MIDI设备通常可以产生多种不同的声音。 此消息使您可以发送7位(0-127 / 7F)代码(编码为全字节,高位为0),指示要使用的补丁。 选择补丁2将为我们提供:

C0 02

You may have noticed that aside from the status byte, all of our values are 7-bit encoded as 8-bit with the high bit of zero leaving us with an effective range of values from 0-127/7F. This is important because of an optimization called a running status byte which I'll cover briefly, and is covered as well at the links in the Further Reading section.

您可能已经注意到,除了状态字节以外,我们所有的值都被7位编码为8位,高位为零,从而使我们的有效值范围为0-127 / 7F。 这一点很重要,因为有一个称为运行状态字节的优化,我将简要介绍它,在“ 进一步阅读”部分的链接中也将进行介绍。

A MIDI message may be a complete message with a status byte, or the status byte may be omitted in which case the previous status will be used. This allows for "runs" of multiple messages of the same type and sent to the same channel but with different parameters. Most of the time, this just causes extra complication for an optimization that often doesn't matter, so you don't really have to emit it but you have to be able to read it. That being said, MIDI is technically bandwidth limited so if you have a lot of events it might make sense to emit it as well.

MIDI消息可以是带有状态字节的完整消息,或者可以省略状态字节,在这种情况下,将使用先前的状态。 这允许“运行”相同类型的多个消息,并发送到相同通道但参数不同。 在大多数情况下,这只会给优化带来额外的复杂性,而这种优化通常并不重要,因此您实际上不必发出它,但必须能够阅读它。 话虽这么说,MIDI在技术上受带宽限制,所以如果您有很多事件,那么发出它也是很有意义的。

Occasionally in MIDI messages, such as a the pitch bend (status nibble E), you'll find 14-bit values are used in messages. These are encoded by the least significant 7-bits (in an 8-bit field with high bit 0) followed by the most significant 7-bits (in an 8-bit field with high bit 0)

有时在MIDI消息中,例如弯音(状态半字节E),您会发现消息中使用了14位值。 它们由最低有效7位(在高位0的8位字段中)编码,然后由最高有效7位(在高位0的8位字段中)编码

You can find a complete list of MIDI messages at the links in the Further Reading section.

您可以在“ 进一步阅读”部分的链接中找到MIDI消息的完整列表。

MIDI文件格式 (The MIDI File Format)

A MIDI file is laid out in "chunks." Each chunk is a fourCC ASCII value that indicates the "type" of the chunk. This is followed by a big-endian 4-byte integer that indicates the length of the data that follows, which is the actual data for the chunk. The first chunk type must be "MThd" and the only other common chunk type is "MTrk" Any chunk types not understood should be skipped.

MIDI文件以“块”布局。 每个块都是一个fourCC CC ASCII值,指示该块的“类型”。 这之后是一个大字节序的4字节整数,该整数指示后面的数据的长度,该数据是块的实际数据。 第一个块类型必须为“ MThd”,唯一的其他公共块类型为“ MTrk”。任何不了解的块类型都应跳过。

The "MThd" chunk contains the MIDI file type (usually 1), the track count, and the timebase (commonly 480), each encoded as big-endian 16-bit words.

“ MThd”块包含MIDI文件类型(通常为1),音轨计数和时基(通常为480),每个均编码为大端16位字。

The "MTrk" chunk contains a track which is a sequence of note on/off message events and other MIDI events. Each event is a delta time followed by a partial or complete MIDI message. The first MIDI track in a MIDI file is usually "special" in that it contains meta information about the MIDI file, including critical data like the tempo information, but also things like lyrics.

“ MTrk”块包含一个轨道,该轨道是音符开/关消息事件和其他MIDI事件的序列。 每个事件都是一个增量时间,然后是部分或完整的MIDI消息。 MIDI文件中的第一个MIDI轨道通常是“特殊”的,因为它包含有关MIDI文件的元信息,包括诸如节奏信息之类的关键数据,还包括歌词之类的东西。

The delta time is encoded as a "variable length integer" which I won't cover here, but is covered at the Standard MIDI File Format link in the Further Reading section. It indicates the number of MIDI "ticks" since the last event (hence delta.) I'll get into timing below.

增量时间被编码为“可变长度整数”,在此不做介绍,但在“进一步阅读”部分的“标准MIDI文件格式”链接中进行了介绍。 它指示自上次事件以来的MIDI“滴答声”的数量(因此发生变化)。

The message that follows can be a full MIDI message, or partial MIDI message with the status byte omitted as described previously.

后面的消息可以是完整的MIDI消息,也可以是部分MIDI消息,如前所述,其状态字节被省略了。

MIDI文件中的定时 (Timing in MIDI Files)

MIDI timing is measured in ticks. The timing of a tick varies depending on the timebase of the MIDI file, which is measured in ticks-per-quarter-note. This is also known as pulses-per-quarter-note or PPQ. That gives you the length of one beat, at the default 4/4 time. The default tempo is 120 beats per minute. The tempo and time signature are set using MIDI events with special MIDI "meta" messages (status byte of FF) and can be set throughout the playback. These are typically in the first track, and are global to all tracks.

MIDI音调以滴答度数为单位。 滴答声的时序因MIDI文件的时基而异,该时基以每四分音符滴答声为单位。 这也称为每四分之一脉冲数或PPQ。 这样一来,您的拍子长度将达到默认的4/4时间。 默认速度为每分钟120次。 使用带有特殊MIDI“元”消息(FF的状态字节)的MIDI事件设置速度和拍号,并可在整个播放过程中进行设置。 这些通常位于第一轨道,并且对所有轨道都是全局的。

I've included a couple of MIDI files I downloaded in the project directory for testing. Any copyright information is available in the MIDI file itself. A-Warm-Place.mid uses features not fully supported by this library but it "mostly" plays. The reason I've included it is it's a useful test for extending the library in the future to support SMPTE timing and proper sysex transmission.

我包含了一些我下载到项目目录中的MIDI文件以进行测试。 MIDI文件本身中提供了所有版权信息。 A-Warm-Place.mid使用的功能未得到该库的完全支持,但“大多数”可以播放。 我包含它的原因是,这是将来扩展库以支持SMPTE计时和正确sysex传输的有用测试。

编码此混乱 (Coding this Mess)

The main class is MidiFile, suitably named because this class represents MIDI data in the MIDI file format. It's not necessarily backed by a physical file. It can be created and operated on entirely in memory. It provides access to common features that apply to all tracks, plus timing information, and access to the meta information in track 0. Read a MIDI file using MidiFile.ReadFrom() and write a MIDI file using WriteTo().

主类是MidiFile ,因此可以适当命名,因为此类表示MIDI文件格式的MIDI数据。 它不一定由物理文件支持。 它可以完全在内存中创建和操作。 它提供对适用于所有轨道的通用功能的访问,以及定时信息,以及对轨道0中的元信息的访问。使用MidiFile.ReadFrom()读取MIDI文件,并使用WriteTo()写入MIDI文件。

The other really important class here is MidiSequence which represents a single sequence or track in a MidiFile. This class allows you to access all of its MidiEvents either as relative delta based or absolute time, and provides access to any meta information stored in the sequence. Sequences can be merged with Merge() or concatenated with Concat(). They can be stretched or compressed with Stretch(). You can retrieve a range of events using GetRange(). Note that some of these operations appear on MidiFile as well and those will operate on each track/sequence in the file.

另一个真正重要的类是MidiSequence ,它表示MidiFile的单个序列或音轨。 此类允许您基于相对增量或绝对时间访问其所有MidiEvent ,并提供对序列中存储的任何元信息的访问。 序列可以与Merge()或与Concat()串联。 可以使用Stretch()拉伸或压缩它们。 您可以使用GetRange()检索一系列事件。 请注意,其中一些操作也会出现在MidiFile ,而这些操作将在文件中的每个音轨/序列上进行。

In the demo code, we process each track depending on the settings in the UI:

在演示代码中,我们根据UI中的设置处理每个轨道:

if (NormalizeCheckBox.Checked)trk.NormalizeVelocities();
if (1m != LevelsUpDown.Value)trk.ScaleVelocities((double)LevelsUpDown.Value);
var ofs = OffsetUpDown.Value;
var len = LengthUpDown.Value;
if (0 == UnitsCombo.SelectedIndex) // beats
{len = Math.Min(len * _file.TimeBase, _file.Length);ofs = Math.Min(ofs * _file.TimeBase, _file.Length);
}
...
if (1m != StretchUpDown.Value)trk = trk.Stretch((double)StretchUpDown.Value, AdjustTempoCheckBox.Checked);

First we handle velocity normalization and scaling. Next, we grab the offset and length of the selection. Then if it's specified in beats, we use the TimeBase to compute the beats. The rest of the Math calls just clamp the values to the maximum allowable length.

首先,我们处理速度归一化和缩放。 接下来,我们获取选择的偏移量和长度。 然后,如果以拍子指定,则使用TimeBase来计算拍子。 其余的Math调用仅将值限制为最大允许长度。

Next, if our length and offsets are different than 0 and the length of the sequence we get the range of events within that time.

接下来,如果我们的长度和偏移量不同于0,并且序列的长度不同,那么我们将获得该时间范围内的事件范围。

I've ommitted a bunch of code in the middle from the listing above, but it handles the rest of the features in the UI by calling the appropriate MidiSequence API methods.

我在上面的清单中省略了很多代码,但是它通过调用适当的MidiSequence API方法来处理UI中的其余功能。

Finally, if we've specified a stretch value other than 1, we call Stretch() to stretch the track.

最后,如果我们指定的拉伸值不是1,则调用Stretch()拉伸轨道。

MidiEvent simply contains a Position in ticks, and a MidiMessage. Depending on whether this event was retrieved through Events or AbsoluteEvents, the Position will be a delta time or an absolute time, respectively.

MidiEvent只是包含一个以刻度为单位的Position和一个MidiMessage 。 根据该事件是通过Events还是AbsoluteEvents检索的, Position将分别为增量时间或绝对时间。

MidiMessage and its derivatives represent MIDI messages of various lengths such as MidiMessageByte and MidiMessageWord. There are also the special MidiMessageMeta and MidiMessageSysex classes which represent a MIDI meta message and a MIDI system exclusive message respectively. See the Standard MIDI File Format link in the Further Reading section for more about these messages. In addition, there are MIDI messages for each type of MIDI operation, such as MidiMessageNoteOn, MidiMessageCC, and MidiMessageChannelPitch.

MidiMessage及其派生词表示各种长度的MIDI消息,例如MidiMessageByteMidiMessageWord 。 还有特殊的MidiMessageMetaMidiMessageSysex类,分别表示MIDI元消息和MIDI系统专有消息。 有关这些消息的更多信息,请参见“进一步阅读”部分的“标准MIDI文件格式”链接。 此外,每种MIDI操作类型都有MIDI消息,例如MidiMessageNoteOnMidiMessageCCMidiMessageChannelPitch

MidiContext is a class that represents the current "state" of a MIDI sequence. When using GetContext(), one of these instances will be returned from the function and it will give you all of the current note velocities, control positions, pitch wheel position, aftertouch information and the rest. This allows you to know what is playing and how at any given moment within the sequence.

MidiContext是一个类,代表MIDI序列的当前“状态”。 使用GetContext() ,将从函数中返回这些实例之一,它将为您提供所有当前音符速度,控制位置,音高轮位置,触后信息以及其他信息。 这样一来,您就可以知道正在播放什么以及在序列中的任何给定时刻如何播放。

MidiUtility provides low level MIDI features and you shouldn't typically need to use it directly. It provides some low level IO methods, byte swapping, and conversion of tempo/microtempo.

MidiUtility提供了低级MIDI功能,您通常不需要直接使用它。 它提供了一些底层IO方法,字节交换和速度/微速度的转换。

Note that to do things like set the tempo and time signature, you must add MidiMessageMeta messages for each to the corresponding tempo or time signature change. In most files, these should be added to track #0.

请注意,要执行诸如设置速度和时间签名的操作,必须将每条消息的MidiMessageMeta消息添加到相应的速度或时间签名更改中。 在大多数文件中,这些文件应添加到轨道#0。

局限性 (Limitations)

  • This library currently only works with Windows even though most of it is portable.该库当前仅适用于Windows,即使大多数库都是可移植的。
  • While it can read sysex messages from a file, it won't transmit them correctly.虽然它可以从文件中读取sysex消息,但无法正确传输它们。
  • It does not support SMPTE timing.它不支持SMPTE计时。
  • The loading and saving could be more robust in terms of handling corrupt files.就处理损坏的文件而言,加载和保存可能更加健壮。
  • FL Studio import for Track #0 doesn't seem to get fully recognized by FL Studio. I have yet to track down why. WMP seems to handle it fine, and FL does respect the timing and patch info so it doesn't seem to be a show stopper.曲目#0的FL Studio导入似乎没有被FL Studio完全识别。 我还没有找到原因。 WMP似乎可以很好地处理它,而FL确实尊重时间和补丁信息,因此它似乎不是秀场停止者。

兴趣点 (Points of Interest)

The Preview() methods do not use Win32 MCI "play" to play the file or sequence. Instead, the sequence is played on the calling thread using C# and calls to the MIDI device out Win32 API directly. Getting the timing right in that method was a total bear. You may want to dispatch it on a separate thread because it is CPU intensive. It's possible (maybe) to do this in a more CPU efficient manner by replacing the hard loop with a timer callback but that's not trivial to implement. See the demo code for the Preview thread handling.

Preview()方法不使用Win32 MCI“播放”来播放文件或序列。 而是使用C#在调用线程上播放序列,并直接通过Win32 API调用MIDI设备。 用这种方法正确地把握时机完全是徒劳。 您可能希望将其分派到单独的线程上,因为它占用大量CPU。 可以(也许)通过将硬循环替换为计时器回调以更有效的CPU方式执行此操作,但这并非易事。 有关预览线程处理的信息,请参见演示代码。

进一步阅读 (Further Reading)

  • Standard Midi File Format - describes the layout and structure of MIDI files in detail

    标准Midi文件格式 -详细描述MIDI文件的布局和结构

  • Essentials of the MIDI Protocol - describes the MIDI message protocol in detail

    MIDI协议要点 -详细描述MIDI消息协议

历史 (History)

  • 18th March, 2020 - Initial submission

    2020年3月18 -初次提交

  • 18th March, 2020 - Update 1: Added time signature support, bugfix in playback

    2020年3月18 -更新1:添加了拍号支持,并修复了播放中的错误

  • 18th March, 2020 - Update 2: Added several features to MidiSlicer and to the MIDI API

    2020年3月18 -更新2:为MidiSlicer和MIDI API添加了一些功能

  • 18th March, 2020 - Preview now loops.

    2020年3月18 -预览现在循环播放。

  • 20th March, 2020 - Added Normalize and Level scaling. Made the Offset and Length floating point

    2020年3月20 -添加了归一化和水平缩放。 制作偏移和长度浮点

  • 20th March, 2020 - Bugfix and improvement to Preview/SaveAs implementations

    2020年3月20 -修正和改进Preview / SaveAs实施

  • 21st March, 2020 - Improved timing in UI and of rendered tracks

    2020年3月21 -改进了UI和渲染轨道的时序

  • 22nd March, 2020 - Improved API, save, and preview

    3月22日2020年-改进的API,保存和预览

  • 22nd March, 2020 - Added Transpose() and Transpose GUI option

    3月22日2020年-增加移调()和移调GUI选项

  • 22nd March, 2020 - Added copyTimingAndPatchInfo option to GetRange() API methods

    3月22日2020年-增加copyTimingAndPatchInfo选项GetRange()API方法

  • 23rd March, 2020 - Improvements to API, and overal GUI functionality

    3月23日2020年-改进的API,并全部测试GUI功能

翻译自: https://www.codeproject.com/Articles/5262538/A-MIDI-File-Slicer-and-MIDI-Library-in-Csharp

c# midi播放器

c# midi播放器_C#中的MIDI文件切片器和MIDI库相关推荐

  1. 怎样进服务器手机版视频文件,手机播放云服务器中的视频文件

    手机播放云服务器中的视频文件 内容精选 换一换 Cloud-Init工具安装完成后,请参考本节操作配置Cloud-Init工具.已安装Cloud-Init工具.已为云服务器绑定弹性公网IP.已登录云服 ...

  2. win10文件管理器计算机,找出win10中应用版文件资源管理器

    多少年来我们习惯使用桌面程序版的windows文件资源管理器,绝大多数人启动文件资源管理器都是通过桌面双击"我的电脑"来实现,而现如今微软对windows界面的发展方向越来也趋向于 ...

  3. python装饰器 property_python中property和setter装饰器用法

    作用:调用方法改为调用对象, 比如 : p.set_name() 改为 p.set_name 区别:前者改变get方法,后者改变set方法 效果图: 代码: class Person: def __i ...

  4. sharepoint 2013 文档库 资源管理器打开报错 在文件资源管理器中打开此位置时遇到问题,将此网站添加到受信任站点列表,然后重试。

    我们在使用sharepoint 2013的文档库或者资源库的时候,经常会需要用到使用"资源管理器"来管理文档,但是有时候,点击"使用资源管理器打开",会提示如下 ...

  5. python数独解题器,Python中最短的数独求解器 – 它是如何工作的?

    那么,通过修正语法,可以使事情变得更容易: def r(a): i = a.find('0') ~i or exit(a) [m in[(ij)%9*(i/9^j/9)*(i/27^j/27|i%9/ ...

  6. ppt复制切片器_切片器化繁为简,盘它 !

    与Power BI 相伴多年的时光里(不知牺牲了我多少游戏娱乐的时间),Power BI 越来越丰富的功能渐渐改变了我们使用Power BI报告的方式,如书签.按钮和选择等功能~~现在,大多数业务用户 ...

  7. excel切片器_excel中的超级好用的筛选神器——切片器

    对于excel中的切片器,很多朋友多多少少了解一些,但是日常工作中仍然习惯用筛选功能,很少用到切片器,那么就看下这篇文章的切片器功能介绍,小编带你认识不一样的筛选神器. 一.创建智能表. excel中 ...

  8. 切片器可以设置日期格式?_Power BI 中的切片器

    何时使用切片器 在要完成以下操作时,切片器非常有用: 在报表画布上显示常用或重要的筛选器,用以简化访问. 更轻松地查看当前筛选的状态,而无需打开下拉列表. 按数据表中不需要的和隐藏的列进行筛选. 通过 ...

  9. python资源管理器选择文件_Python:在资源管理器中获取选定文件的列表(windows7)...

    我知道现在在这里发布答案有点晚了,但我几个月前尝试过Olav的解决方案,但它没有完全起作用:工作目录是脚本的工作目录,所以我不得不删除if条件才能使其工作,但它选择了所有Windows资源管理器窗口中 ...

  10. excel切片器显示错误_Office 2016中报表用户的新Excel切片器功能

    excel切片器显示错误 Whilst researching for the article Report filtering: Excel slicers vs SQL Server Report ...

最新文章

  1. iis websocket同时连线人数_【NBA云专访】沈洋连线76人CEO 疫情期间如何管理球队?...
  2. 排序算法-01冒泡排序(Python实现)
  3. 来自女朋友的灵魂拷问!| 今日最佳
  4. html:(13):ol-li和div作用
  5. java 私有成员方法_Java Reflection 教程(7):类私有成员变量和方法
  6. 一名7年总监的6点离职忠告
  7. Java方法重载解析
  8. Linux系统-Ubuntu的下载和安装
  9. Python 机器学习经典实例
  10. 行政界线类型代码_行政区划代码的代码表
  11. php中的列表属性,php类中的长属性列表 – 我可以缩短它吗?
  12. 2015美国大学计算机科学专业排名,美国大学研究生计算机科学专业排名|2015年计算机科学专业排行榜(1/2)- 各国学校排名网...
  13. Android UI 绘制流程及原理
  14. Python基础之告警定义与告警抑制
  15. 模态对话框和非模态对话框的区别1
  16. 仿淘宝购买详情页购买缩小动画
  17. Workbook corruption: seen[2] == 4
  18. 张坤(帮别人名字作诗)
  19. xxx is not in the sudoers file.This incident will be reported错误
  20. 推荐一款Linux平台下的BT下载工具

热门文章

  1. 什么是hash,什么是hash表,为什么hash表查询快
  2. 怎么提高文公写作水平?公文写作请示类模板
  3. 【博弈论】翻硬币游戏8种模型
  4. 朗文3000词汇表带音标_朗文少儿英语2A-Unit3知识归纳(单词含音标版
  5. 智图—源于QQ空间图片WebP化的思考
  6. 刘毅5000词汇_不熟词汇整理_lesson_14 and part_4
  7. matlab $r$n$m,维纳滤波器推导以及MATLAB代码(Wiener Filter)
  8. ENVI遥感处理(9):遥感影像镶嵌和图像裁剪
  9. python之父去面试-Django面试题
  10. 摄动法在计算机中的应用,摄动法及其在电力系统中的应用