原文:WRITING MUSIC IN JAVA: TWO APPROACHES

简介

音乐软件能够表达音乐思想,必须是人类可读和计算机可读的。 现代乐谱记谱法具有极强的表现力,能够在一个紧凑的空间内传达节奏、旋律、和声以及各种演奏指令。 不幸的是,作为一种图形化的、人类可读的记谱法,乐谱并不能很好地转化为计算机。 一个单独的记谱系统,即特定领域语言(DSL),对于计算机能够处理音乐是必要的。 此外,我们还需要能理解这种DSL的工具,并允许我们对音乐进行操作。

WRITING MUSIC IN JAVA: TWO APPROACHES介绍了两个开源的Java库,它们使用两种不同的符号,以计算机友好的ASCII格式表达音乐信息。 这两个库都可以通过计算机扬声器以MIDI序列的形式播放曲子,但在其他功能上有所不同。

本文只涉及JFugue,因为后面那个abc4j并不太完善的样子。文章的内容部分翻译自WRITING MUSIC IN JAVA: TWO APPROACHES,版权归于Lance Finney。

文章仅仅把JFugue更新到5.0版,编程采用Kotlin完成。

JFUGUE

JFugue是一个LGPL许可的开源库,用于 “为音乐编程而不需要复杂的MIDI”。 它有自己的符号,只用ASCII字符表示音乐,提供MIDI文件的输入/输出,并允许以编程方式操作音乐。

为了演示JFugue的功能,我们将使用童谣 "Itsy Bitsy Spider "的变体。

JFugue的第一个演示主要显示了歌曲旋律的基本记号。

import org.jfugue.player.Playerfun main() {val player = Player()player.play( // "Itsy, bitsy spider, climbed up the water spout.""F5q F5i F5q G5i A5q. A5q A5i G5q F5i G5q A5i F5q. Rq. " +// "Down came the rain and washed the spider out.""A5q. A5q Bb5i C6q. C6q. Bb5q A5i Bb5q C6i A5q. Rq. " +// "Out came the sun and dried up all the rain, so the""F5q. F5q G5i A5q. A5q. G5q F5i G5q A5i F5q. C5q C5i " +// "itsy, bitsy spider went up the spout again.""F5q F5i F5q G5i A5q. A5q A5i G5q F5i G5q A5i F5q. Rq.")
}

这里的主要课程是用于定义歌曲的记号。 这个简单的例子包括音符名称、八度音、休止符、变音和持续时间。

NOTES, OCTAVES, AND RESTS音符,八度音,休止符

音符是根据简单的A-G音阶指定的,接下来是八度音的数字。 例如,中C是C5,高一个八度的C是C6,而正下方的音符是B5。 这是一些乐器(如手摇铃)中常用的编号系统。

如果没有给定八度音,音符的默认八度是五度音,在中C以上。

JFugue还允许将音符指定为0到127的数字,甚至可以在音符之间定义音高(对于某些非西方音乐传统和某些类型的现代音乐),但这一高级细节不在本文的范围之内。

休止符是用R来定义的,而不是用音符-八度的组合。

ACCIDENTALS变音记号

要指定升调或降调,在音符和八度之间加一个#或b。我们的例子 "Itsy Bitsy Spider "是F大调,但JFugue默认为C大调(像标准音乐符号),所以我们需要指定第二行的B实际上是B调。稍后,我们将看到我们可以指定歌曲的调号,所以我们不需要指定调号内的升号和降号。此外,自然音可以用n来表示,以取消C大调以外的意外音(调号将在后面介绍)。不支持双升和双降。

DURATIONS持续时间

这里使用的是最简单的表达音符时长的方法,以美国系统为基础。

代码 持续时间
W 整个音符
H 半音符
Q 四分之一音符
I 八分之一音符
S 十六分之一音符
T 三十二分之一音符
X 六十四分之一音符
N 一百二十八分之一音符

在上面的例子中,我们使用四分音符(q)、八分音符(i)和带点四分音符(q.)。 在标准的音乐记谱法中,在一个音符后加一个点会使其长度延长50%。

其他时长,如三连音和其他小音符,可以用基于全音符的另一种数字符号来定义。 例如,要定义一个四分音符的C5,用C5/0.25。 要定义一个长度为四分之一音符的三分之一的C5音符,就用C5/0.083333。 我们将在后面看到,这种记谱法在将MIDI文件导入JFugue记谱法时也会用到。

播放MIDI

除了展示音乐记号外,这个例子还展示了JFugue的播放API。 具体来说,对player.play()的调用将定义的歌曲转换为MIDI序列,并通过计算机的扬声器播放它。

ADDING MEASURES, PATTERNS, AND VOICES增加小节/模式和声部

现在,音乐符号的基本要素已经确定,我们可以开始改进这首歌。 首先,从DRY的角度来看,我们应该去掉第一行和最后一行之间的重复,它们在音乐上是相同的。 幸运的是,JFugue提供了一个使用复合设计模式的Pattern类,允许我们重复使用音乐片段。 在下面的版本中,我们为每一个独特的线条创建一个Pattern实例,然后将它们依次添加到另一个代表整首歌的Pattern实例中。

import org.jfugue.pattern.Pattern
import org.jfugue.player.Playerfun main(args: Array<String>) {// "Itsy, bitsy spider, climbed up the water spout."// and "itsy, bitsy spider went up the spout again."val pattern1 = Pattern("F5q F5i F5q G5i | A5q. A5q A5i | G5q F5i G5q A5i | F5q. Rq. | ")// "Down came the rain and washed the spider out."val pattern2 = Pattern("A5q. A5q Bb5i | C6q. C6q. | Bb5q A5i Bb5q C6i | A5q. Rq. | ")// "Out came the sun and dried up all the rain, so the"val pattern3 = Pattern("F5q. F5q G5i | A5q. A5q. | G5q F5i G5q A5i | F5q. C5q C5i | ")// Put the whole song togetherval song = Pattern()song.add(pattern1)song.add(pattern2)song.add(pattern3)song.add(pattern1)// Play the songval player = Player()player.play(song)
}

这里的另一个变化是,我们为小节(Measure)之间的界限增加了标记("|"字符)。 有趣的是,这对程序对歌曲的解释没有任何影响—这只是为了方便用户和提高清晰度。 JFugue没有时间符号的概念,所以小节可以包含你想要的多少个拍子—它们只是为了阅读方便。

接下来,让我们使用声部(Voice)功能来创建一个二重唱(Round)。

import org.jfugue.pattern.Pattern
import org.jfugue.player.Playerfun main(args: Array<String>) {// "Itsy, bitsy spider, climbed up the water spout."// and "itsy, bitsy spider went up the spout again."val pattern1 = Pattern("F5q F5i F5q G5i | A5q. A5q A5i | G5q F5i G5q A5i | F5q. Rq. | ")// "Down came the rain and washed the spider out."val pattern2 = Pattern("A5q. A5q Bb5i | C6q. C6q. | Bb5q A5i Bb5q C6i | A5q. Rq. | ")// "Out came the sun and dried up all the rain, so the"val pattern3 = Pattern("F5q. F5q G5i | A5q. A5q. | G5q F5i G5q A5i | F5q. C5q C5i | ")// Put the whole song togetherval song = Pattern()song.add(pattern1)song.add(pattern2)song.add(pattern3)song.add(pattern1)val lineRest = Pattern("Rh. | Rh. | Rh. | Rh. | ")// Create the first voiceval round1 = Pattern("V0")round1.add(song)// Create the second voiceval round2 = Pattern("V1")round2.add(lineRest)round2.add(song)// Create the third voiceval round3 = Pattern("V2")round3.add(lineRest, 2)round3.add(song)// Put the voices togetherval roundSong = Pattern()roundSong.add(round1)roundSong.add(round2)roundSong.add(round3)// Play the songval player = Player()player.play(roundSong)
}

在这个例子中,我们通过组合三个类似的声音(类似于其他音乐背景下的音轨或通道)来创建一个重唱。 在这种情况下,每个声部都有一个单独的模式Pattern实例。 每个模式都收到与上一个例子相同的序列,但有些模式的前缀是一整行或多整行的休止符(lineRest),这样它们就有了交错的开始。

独立的声部是通过在模式中加入V0V1V2定义的。 语音声明后的所有内容都与该声部相关,直到指定另一个声部。 在这个例子中,每个声部的所有信息都集中在一起,但只要每次都重新指定声部,定义就可以穿插在一起,如下面的版本,使用前面介绍的Pattern实例。 这个例子在音乐上与前面的例子是相同的。

import org.jfugue.pattern.Pattern
import org.jfugue.player.Playerfun main(args: Array<String>) {// "Itsy, bitsy spider, climbed up the water spout."// and "itsy, bitsy spider went up the spout again."val pattern1 = Pattern("F5q F5i F5q G5i | A5q. A5q A5i | G5q F5i G5q A5i | F5q. Rq. | ")// "Down came the rain and washed the spider out."val pattern2 = Pattern("A5q. A5q Bb5i | C6q. C6q. | Bb5q A5i Bb5q C6i | A5q. Rq. | ")// "Out came the sun and dried up all the rain, so the"val pattern3 = Pattern("F5q. F5q G5i | A5q. A5q. | G5q F5i G5q A5i | F5q. C5q C5i | ")val lineRest = Pattern("Rh. | Rh. | Rh. | Rh. | ")// Put the whole song togetherval song = Pattern()song.add("V0 $pattern1")song.add("V1 $lineRest")song.add("V2 $lineRest")song.add("V0 $pattern2")song.add("V1 $pattern1")song.add("V2 $lineRest")song.add("V0 $pattern3")song.add("V1 $pattern2")song.add("V2 $pattern1")song.add("V0 $pattern1")song.add("V1 $pattern3")song.add("V2 $pattern2")song.add("V1 $pattern1")song.add("V2 $pattern3")song.add("V2 $pattern1")// Play the songval player = Player()player.play(song)
}

增加CHORDS, INSTRUMENTS, KEY SIGNATURES, AND TEMPO

声部的另一个用途是添加和声或和弦伴奏。 在下面的例子中,V0被用作前面介绍的旋律,而V1被用来提供低音和弦。 请注意,只需要指定和弦的简称(本例中是FmajBbmaj,但对于更复杂的和弦还有许多其他选项),默认的八度是3号(中C以下两个八度)。 和弦也可以通过指定和弦中的每个音符来定义,用+连接。 在这个例子中,我们对其中一个和弦使用了这种方法,以使用该和弦的第一转位,它没有一个简短的名字。

这个版本的另一个新增内容是乐器。 所有声音的默认乐器是钢琴,所以以前的例子听起来像是用钢琴演奏的。 在这个例子中,旋律是小号(指定为I[Trumpet]或备选为I56),和弦是教堂管风琴(指定为I[CHURCH_ORGAN]或备选为I19)。 这些乐器是在标题中定义的,但它们也可以在歌曲中的任何时候改变。

这里的ID号和选项来自MIDI规范;128种乐器的完整列表可在JFugue的文档中找到。

import org.jfugue.pattern.Pattern
import org.jfugue.player.Playerfun main(args: Array<String>) {val voice1 = Pattern("V0 I[Trumpet] ")// "Itsy, bitsy spider, climbed up the water spout."// and "itsy, bitsy spider went up the spout again."val pattern1 = Pattern("V0 F5q F5i F5q G5i | A5q. A5q A5i | G5q F5i G5q A5i | F5q. Rq. | ")// "Down came the rain and washed the spider out."val pattern2 = Pattern("V0 A5q. A5q Bb5i | C6q. C6q. | Bb5q A5i Bb5q C6i | A5q. Rq. | ")// "Out came the sun and dried up all the rain, so the"val pattern3 = Pattern("V0 F5q. F5q G5i | A5q. A5q. | G5q F5i G5q A5i | F5q. C5q C5i | ")val voice2 = Pattern("V1 I[CHURCH_ORGAN] ")//1st, 3rd, and 4th lines (third chord specified as notes)val chord1 = Pattern("V1 Fmajh. | Fmajh. | E3h.+G3h.+C4h. | Fmajh. | ")//2nd lineval chord2 = Pattern("V1 Fmajh. | Fmajh. | Bbmajh. | Fmajh. | ")// Put the whole song togetherval song = Pattern()//melodysong.add(voice1)song.add(pattern1)song.add(pattern2)song.add(pattern3)song.add(pattern1)//chordssong.add(voice2)song.add(chord1)song.add(chord2)song.add(chord1, 2)// Play the songval player = Player()player.play(song)
}

"Itsy Bitsy Spider "的最后一个JFugue版本在标题中增加了两个元素:调号和速度。 这些元素可以在歌曲过程中的任何地方定义,以改变调性或速度(例如,表达一个ritardano),但我们的例子只在初始阶段设置它们。 如果我们要在歌曲的过程中改变速度,我们就必须为每个声部分别改变它。

调号被指定为F大调(KFmaj),这意味着可以从B音中去掉平声记号,就像在标准音乐记号中一样。 如前所述,默认的调号是C大调。

节奏被指定为100个 “每四分音符的脉冲”,也就是给一个四分音符多少个 “脉冲”。 JFugue的文档在速度方面实际上是相当混乱的,因为默认值是120,文档同时将其定义为每四分音符120个 "脉冲 "和每分钟120次。 这两个标度的作用方向不同,每四分音符的脉冲数越多,速度就越慢,但每分钟的节拍数越多,速度就越快。 事实上,这里使用的是第一个定义,T100比默认速度快。

import org.jfugue.midi.MidiFileManager.savePatternToMidi
import org.jfugue.pattern.Pattern
import org.jfugue.player.Player
import java.io.Filefun main(args: Array<String>) {val header = Pattern("KFmaj T100 V0 I[Trumpet] V1 I[CHURCH_ORGAN] ")// "Itsy, bitsy spider, climbed up the water spout."// and "itsy, bitsy spider went up the spout again."val pattern1 = Pattern("V0 F5q F5i F5q G5i | A5q. A5q A5i | G5q F5i G5q A5i | F5q. Rq. | ")// "Down came the rain and washed the spider out."val pattern2 = Pattern("V0 A5q. A5q B5i | C6q. C6q. | B5q A5i B5q C6i | A5q. Rq. | ")// "Out came the sun and dried up all the rain, so the"val pattern3 = Pattern("V0 F5q. F5q G5i | A5q. A5q. | G5q F5i G5q A5i | F5q. C5q C5i | ")//1st, 3rd, and 4th lines (third chord specified as notes)val chord1 = Pattern("V1 Fmajh. | Fmajh. | E3h.+G3h.+C4h. | Fmajh. | ")//2nd lineval chord2 = Pattern("V1 Fmajh. | Fmajh. | Bmajh. | Fmajh. | ")// Put the whole song togetherval song = Pattern()song.add(header)//melodysong.add(pattern1)song.add(pattern2)song.add(pattern3)song.add(pattern1)//chordssong.add(chord1)song.add(chord2)song.add(chord1, 2)// Play the songval player = Player()player.play(song)// save as a midi file for use in the next examplesavePatternToMidi(song, File("spider.midi"))
}

MIDI文件的导入/存储和操作

除了像前面的例子那样将歌曲以MIDI序列的形式播放给扬声器之外,我们还可以将MIDI文件加载到JFugue库中,和/或将JFugue歌曲导出为MIDI文件。 此外,JFugue还提供了一个应用于Pattern的音乐转换的API,库中实现了一些转换。

在下面的例子中,使用歌曲的最终版本创建的MIDI文件被加载进来,解析后的JFugue表示法被打印到命令行。 然后,用ParserListenerAdapter将整首歌曲调高一个全音符并重新打印出来。 接下来,使用ParserListenerAdapter将整首歌曲放慢20%,并再次重印。 最后,新修改的歌曲被导出到新的MIDI文件。

这里原作者的JFugure 4.0代码,被更改为为5.0.9对应的代码,利用Pattern.transfrom函数来遍历歌曲的各元素,这里比较麻烦的是有两个音轨,需要分别处理。

import org.jfugue.midi.MidiDictionary.INSTRUMENT_BYTE_TO_STRING
import org.jfugue.midi.MidiFileManager.loadPatternFromMidi
import org.jfugue.midi.MidiFileManager.savePatternToMidi
import org.jfugue.parser.ParserListenerAdapter
import org.jfugue.pattern.Pattern
import org.jfugue.player.Player
import org.jfugue.theory.Note
import java.io.File/*** This program demonstrates MIDI I/O and musical transformations.*/fun main(args: Array<String>) {val player = Player()// load a midi filevar pattern: Pattern = loadPatternFromMidi(File("Spider.midi"))// print the song to the console with JFugue notationSystem.out.println("Original: $pattern")// high Key, Note value increased by 2val highKey = object : ParserListenerAdapter() {val song: Patternget() =_song.apply {tracks.forEach {add(it.value)}}private val _song: Pattern = Pattern()val tracks = mutableMapOf<Byte, Pattern>()var currentTrack: Byte = 0override fun onInstrumentParsed(instrument: Byte) {tracks[currentTrack]?.setInstrument(instrument.toInt())}override fun onTempoChanged(tempoBPM: Int) {_song.setTempo(tempoBPM)}override fun onTrackBeatTimeRequested(time: Double) {tracks[currentTrack]?.add("@$time")}override fun onTrackChanged(track: Byte) {currentTrack = tracktracks.getOrPut(track) {Pattern().apply { setVoice(track.toInt()) }}}override fun onNoteParsed(note: Note) {tracks[currentTrack]?.add(Note(note.value+2, note.duration))}}pattern.transform(highKey)println("Transposed: ${highKey.song}")// slow duration,val slow = object : ParserListenerAdapter() {val scale = 1.2val song: Patternget() =_song.apply {tracks.forEach {add(it.value)}}private val _song: Pattern = Pattern()val tracks = mutableMapOf<Byte, Pattern>()var currentTrack: Byte = 0override fun onInstrumentParsed(instrument: Byte) {tracks[currentTrack]?.setInstrument(instrument.toInt())}override fun onTempoChanged(tempoBPM: Int) {_song.setTempo(tempoBPM)}override fun onTrackBeatTimeRequested(time: Double) {tracks[currentTrack]?.add("@${time*scale}")}override fun onTrackChanged(track: Byte) {currentTrack = tracktracks.getOrPut(track) {Pattern().apply { setVoice(track.toInt()) }}}override fun onNoteParsed(note: Note) {tracks[currentTrack]?.add(Note(note.value, note.duration*scale))}}pattern.transform(slow)println("Slowed: ${slow.song}")// save as a midi filesavePatternToMidi(highKey.song, File("highKey.midi"))savePatternToMidi(slow.song, File("slow.midi"))}

提高整整一拍倒是比较简单。受限于我的乐理知识,我不清楚,把每个音符的时间长度增加20%之后,由@1.25指定的节拍时间是否也应该增加20%

程序输出:

Original: T100 V0 I56 F5q F5i F5q G5i A5q. A5q A5i G5q F5i G5q A5i F5q. Rq. A5q. A5q B5i C6q. C6q. B5q A5i B5q C6i A5q. Rq. F5q. F5q G5i A5q. A5q. G5q F5i G5q A5i F5q. C5q C5i F5q F5i F5q G5i A5q. A5q A5i G5q F5i G5q A5i F5q. V1 I19 F4h. @0.0 A4h. @0.0 C5h. F4h. @0.75 A4h. @0.75 C5h. E3h. @1.5 G3h. @1.5 C4h. F4h. @2.25 A4h. @2.25 C5h. F4h. @3.0 A4h. @3.0 C5h. F4h. @3.75 A4h. @3.75 C5h. B4h. @4.5 Eb5h. @4.5 F#5h. F4h. @5.25 A4h. @5.25 C5h. F4h. @6.0 A4h. @6.0 C5h. F4h. @6.75 A4h. @6.75 C5h. E3h. @7.5 G3h. @7.5 C4h. F4h. @8.25 A4h. @8.25 C5h. F4h. @9.0 A4h. @9.0 C5h. F4h. @9.75 A4h. @9.75 C5h. E3h. @10.5 G3h. @10.5 C4h. F4h. @11.25 A4h. @11.25 C5h.
Transposed: T100 V0 I[Trumpet] G5q G5i G5q A5i B5q. B5q B5i A5q G5i A5q B5i G5q. D0q. B5q. B5q C#6i D6q. D6q. C#6q B5i C#6q D6i B5q. D0q. G5q. G5q A5i B5q. B5q. A5q G5i A5q B5i G5q. D5q D5i G5q G5i G5q A5i B5q. B5q B5i A5q G5i A5q B5i G5q. V1 I[Church_Organ] G4h. @0.0 B4h. @0.0 D5h. G4h. @0.75 B4h. @0.75 D5h. F#3h. @1.5 A3h. @1.5 D4h. G4h. @2.25 B4h. @2.25 D5h. G4h. @3.0 B4h. @3.0 D5h. G4h. @3.75 B4h. @3.75 D5h. C#5h. @4.5 F5h. @4.5 G#5h. G4h. @5.25 B4h. @5.25 D5h. G4h. @6.0 B4h. @6.0 D5h. G4h. @6.75 B4h. @6.75 D5h. F#3h. @7.5 A3h. @7.5 D4h. G4h. @8.25 B4h. @8.25 D5h. G4h. @9.0 B4h. @9.0 D5h. G4h. @9.75 B4h. @9.75 D5h. F#3h. @10.5 A3h. @10.5 D4h. G4h. @11.25 B4h. @11.25 D5h.
Slowed: T100 V0 I[Trumpet] F5/0.3 F5/0.15 F5/0.3 G5/0.15 A5/0.44999999999999996 A5/0.3 A5/0.15 G5/0.3 F5/0.15 G5/0.3 A5/0.15 F5/0.44999999999999996 C0/0.44999999999999996 A5/0.44999999999999996 A5/0.3 B5/0.15 C6/0.44999999999999996 C6/0.44999999999999996 B5/0.3 A5/0.15 B5/0.3 C6/0.15 A5/0.44999999999999996 C0/0.44999999999999996 F5/0.44999999999999996 F5/0.3 G5/0.15 A5/0.44999999999999996 A5/0.44999999999999996 G5/0.3 F5/0.15 G5/0.3 A5/0.15 F5/0.44999999999999996 C5/0.3 C5/0.15 F5/0.3 F5/0.15 F5/0.3 G5/0.15 A5/0.44999999999999996 A5/0.3 A5/0.15 G5/0.3 F5/0.15 G5/0.3 A5/0.15 F5/0.44999999999999996 V1 I[Church_Organ] F4/0.8999999999999999 @0.0 A4/0.8999999999999999 @0.0 C5/0.8999999999999999 F4/0.8999999999999999 @0.8999999999999999 A4/0.8999999999999999 @0.8999999999999999 C5/0.8999999999999999 E3/0.8999999999999999 @1.7999999999999998 G3/0.8999999999999999 @1.7999999999999998 C4/0.8999999999999999 F4/0.8999999999999999 @2.6999999999999997 A4/0.8999999999999999 @2.6999999999999997 C5/0.8999999999999999 F4/0.8999999999999999 @3.5999999999999996 A4/0.8999999999999999 @3.5999999999999996 C5/0.8999999999999999 F4/0.8999999999999999 @4.5 A4/0.8999999999999999 @4.5 C5/0.8999999999999999 B4/0.8999999999999999 @5.3999999999999995 Eb5/0.8999999999999999 @5.3999999999999995 F#5/0.8999999999999999 F4/0.8999999999999999 @6.3 A4/0.8999999999999999 @6.3 C5/0.8999999999999999 F4/0.8999999999999999 @7.199999999999999 A4/0.8999999999999999 @7.199999999999999 C5/0.8999999999999999 F4/0.8999999999999999 @8.1 A4/0.8999999999999999 @8.1 C5/0.8999999999999999 E3/0.8999999999999999 @9.0 G3/0.8999999999999999 @9.0 C4/0.8999999999999999 F4/0.8999999999999999 @9.9 A4/0.8999999999999999 @9.9 C5/0.8999999999999999 F4/0.8999999999999999 @10.799999999999999 A4/0.8999999999999999 @10.799999999999999 C5/0.8999999999999999 F4/0.8999999999999999 @11.7 A4/0.8999999999999999 @11.7 C5/0.8999999999999999 E3/0.8999999999999999 @12.6 G3/0.8999999999999999 @12.6 C4/0.8999999999999999 F4/0.8999999999999999 @13.5 A4/0.8999999999999999 @13.5 C5/0.8999999999999999

结束语

JFugue看起还是很不错的,有空的时候来整点音乐!

使用Java/Kotlin编写音乐:JFugue相关推荐

  1. javascript扩展插件alook_使用 Kotlin 编写你的第一个 Firefox WebExtension 扩展

    Kotlin 是我最喜爱的编程语言.我们已经知道 Kotlin 编译成 Java 字节码可以快速被安卓和服务端采用.事实上,Kotlin 还支持编译成 JavaScript,因此该语言也开始在 Web ...

  2. kotlin编写后台_在Kotlin编写图书馆的提示

    kotlin编写后台 by Adam Arold 亚当·阿罗德(Adam Arold) 在Kotlin编写图书馆的提示 (Tips for Writing a Library in Kotlin) W ...

  3. java爱听音乐音乐播放器

    java 爱听音乐音乐播放器是我为自己编写的音乐播放器,我自己现在还在用,实现本地音乐播放,功能全面 下载地址:https://github.com/myopenresources/MiniMusic ...

  4. 使用Java实现MP3音乐播放

    使用Java实现MP3音乐播放 Java SE自带的API中缺少对MP3格式音频文件的支持,想要使用Java代码播放MP3需要第三方库.JLayer-MP3 library是一款Java语言编写的开源 ...

  5. kotlin android 开源,一款纯Kotlin编写的开源安卓应用 Smile

    原标题:一款纯Kotlin编写的开源安卓应用 "Smile" 本文作者 作者:王英豪 链接: http://blog.csdn.net/yhaolpz/article/detail ...

  6. 如果用java swing编写一个五子棋(人人对战)

    2020博客之星年度总评选进行中:请为74号的狗子投上宝贵的一票! 我的投票地址:点击为我投票 写在前面: 大家好,我是 花狗Fdog ,来自内蒙古的一个小城市,目前在泰州读书. 很感谢能有这样一个平 ...

  7. 安卓开发的两种语言比较——Java Kotlin

    首先介绍一下安卓历史和现状. 2005年8月,Google低调收购了成立仅22个月的高科技企业Android及其团队 2007年11月,Google以Apache免费开源许可证的授权方式,发布了And ...

  8. 2021年大数据Kafka(五):❤️Kafka的java API编写❤️

    全网最详细的大数据Kafka文章系列,强烈建议收藏加关注! 新文章都已经列出历史文章目录,帮助大家回顾前面的知识重点. 目录 系列历史文章 Kafka的java API编写 一.生产者代码 第一步: ...

  9. 编写音乐播放器的一些感想

    编写音乐播放器的一些感想 当初是想着学习C#,就动手开始实现一个简单的播放器.在实现的工程中发现自己能够学到很多东西,就有了把播放器用c++重写的想法,在实现过程中,发现c++想实现c#同样的功能,真 ...

最新文章

  1. MapReduce Java API实例-排序
  2. 系统学习Linux11点建议
  3. 浏览器linux版本,Opera浏览器电脑版|Opera浏览器 V60.0.3255.70 Linux版 下载_当下软件园_软件下载...
  4. java 字符串子串_java实现字符串匹配求两个字符串的最大公共子串
  5. [BZOJ4182]Shopping
  6. 为什么一个字节定义成8位?
  7. 东南大学王萌 | “神经+符号”学习与多模态知识发现
  8. java 对list增删_List 中正确的增删操作
  9. FreeEIM 与飞鸽传书的区别
  10. Python os.walk() 方法--输出在目录中的文件名
  11. 【Elasticsearch】Elasticsearch-Hadoop打通Elasticsearch和Hadoop
  12. ScrollReveal-元素随页面滚动产生动画的js插件
  13. Java try和catch的使用介绍
  14. 记第一次在程序中埋彩蛋
  15. android .9横向拉伸,神奇的问题!android .9图片拉伸不是不会变形吗?但是这里变形了...
  16. PLC控制系统设计的基本内容
  17. python爬虫_网易音乐歌单
  18. 数字图像处理 击中击不中变换
  19. 深入理解Java虚拟机——运行时栈帧结构(局部变量表)
  20. 初中教资计算机考试知识点,教资考试初中物理电学知识点总结来了

热门文章

  1. MiniUI 在线示例
  2. 如何学习并通过计算机一级论文,全国计算机等级考试一级MS Office 2010学习辅导与上机实验指导...
  3. Zynq Linux 使用 SPI ADC (ADS8332)
  4. Win8.1 中 配置Tomcat7
  5. 一些具非常有用源代码分享(百度指数破解(最新版),NDIS实现类似P2P终结者功能代码,GOOGLE在线翻译等等)
  6. 逃出你的肖申克(一):一定要亲身经历了之后才能明白?
  7. tensor.view()函数--自己的理解《pytorch学习》
  8. 【GLSL教程】(六)逐顶点的光照
  9. VB通过以太网接口使用winsock的永宏PLC上位机通讯系统设计
  10. Windows清理助手运行不起来