Table of Contents
以前、白鍵のみのシンプルなキーボードアプリをAudioKitで作成しました。
今回はこのアプリを改良して、以下の機能を加えてみます。
- 黒鍵
- 音の種類(サイン波、矩形波、三角波、ノコギリ波)を変更
- 音量(波の振幅)の変更
アプリの完成例
以下のように、鍵盤に黒鍵を加え、さらに音の波形の種類を選択するためのピッカーと音の振幅を変更するためのスライダーが追加します。
これらのUIパーツの使い方は、以下の記事を御覧ください。
アプリのソースコード
アプリの全ソースコードです。
必要な外部フレームワークは AudioKit のみです。
ViewController.swift
import UIKit
import AudioKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
let statusBarHeight = UIApplication.shared.statusBarFrame.height
var oscillator: AKOscillator!
var waveformPlot: AKNodeOutputPlot!
var amplitudeLabel: UILabel!
var currentAmplitude: Double = 1.0
// 音の周波数
let whiteFrequencies:[Double] = [
523.23,// C
587.34,// D
659.25,// E
698.45,// F
783.98,// G
879.99,// A
987.75,// B
1046.50// C
]
let blackFrequencies:[Double] = [
554.36,// C#
622.25,// D#
739.98,// F#
830.60,// G#
932.32,// A#
]
// 波形の種類
let waveType = [
"sine": AKTable(.sine),
"sawtooth": AKTable(.sawtooth),
"square": AKTable(.square),
"triangle": AKTable(.triangle)
]
override func viewDidLoad() {
super.viewDidLoad()
// 波形の種類を設定
oscillator = AKOscillator(waveform: waveType["sine"]!)
AudioKit.output = oscillator
AudioKit.start()
self.createWaveViewer(oscillator)
self.createSoundTypeSelector()
self.createVolumeSlider()
self.createKeyboard()
}
func createWaveViewer(_ oscillator: AKOscillator) {
// 鳴っている音の波形ビューを作成
if waveformPlot != nil {
waveformPlot.removeFromSuperview()
}
waveformPlot = AKNodeOutputPlot(oscillator, frame: CGRect(
x: 0,
y: statusBarHeight,
width: self.view.frame.width,
height: 100)
)
waveformPlot.plotType = .buffer
self.view.addSubview(waveformPlot)
}
func createSoundTypeSelector() {
// 音色を選択するピッカー
let picker = UIPickerView(frame: CGRect(x: 0, y: statusBarHeight + 100, width: self.view.frame.width, height: 100))
picker.delegate = self
picker.dataSource = self
picker.selectRow(0, inComponent: 0, animated: true)
self.view.addSubview(picker)
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return waveType.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return [String](waveType.keys)[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let sound = [AKTable](waveType.values)[row];
self.updateWaveform(sound)
}
// 音色の更新
func updateWaveform(_ sound: AKTable) {
AudioKit.stop()
oscillator = AKOscillator(waveform: sound)
self.createWaveViewer(oscillator)
AudioKit.output = oscillator
AudioKit.start()
}
func createVolumeSlider() {
// 音量(振幅)を変更するスライダーを作成
let slider = UISlider()
slider.frame.size.width = 200
slider.center = self.view.center
slider.sizeToFit()
slider.minimumValue = 0
slider.maximumValue = 100
slider.value = 100
slider.addTarget(self, action: #selector(self.changeAmplitude), for: .valueChanged)
self.view.addSubview(slider)
amplitudeLabel = UILabel()
amplitudeLabel.text = "\(slider.value)%"
amplitudeLabel.font = UIFont(name: "Arial", size: 24)
amplitudeLabel.sizeToFit()
amplitudeLabel.center.x = self.view.center.x
amplitudeLabel.center.y = self.view.center.y - 24
self.view.addSubview(amplitudeLabel)
}
func changeAmplitude(_ sender: UISlider) {
let value = sender.value
currentAmplitude = value / 100
amplitudeLabel.text = "\(value.rounded())%"
}
func createKeyboard() {
// 鍵盤の作成
let maxWhiteKeyLength = whiteFrequencies.count
let maxBlackKeyLength = blackFrequencies.count
let keyWidth: CGFloat = self.view.frame.width / CGFloat(maxWhiteKeyLength)
let keyHeight: CGFloat = 160
let blackKeyWidth = keyWidth * 0.8
let blackKeyHeight = keyHeight * 0.5
// 白鍵の作成
for i in 0 ..< maxWhiteKeyLength {
let button = UIButton(frame: CGRect(
x: CGFloat(i) * keyWidth,
y: self.view.frame.height - keyHeight,
width: keyWidth,
height: keyHeight)
)
button.backgroundColor = UIColor(white: 1, alpha: 1)
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.black.cgColor
button.layer.cornerRadius = 4
button.setTitleColor(UIColor.blue, for: .normal)
button.addTarget(self, action: #selector(self.toggleSound), for: .touchDown)
button.addTarget(self, action: #selector(self.toggleSound), for: .touchUpInside)
button.layer.setValue(whiteFrequencies[i], forKey: "freq")
self.view.addSubview(button)
}
// 黒鍵の作成
for i in 0 ..< maxBlackKeyLength {
let button = UIButton(frame: CGRect(
x: keyWidth / 2 + CGFloat(i) * keyWidth + keyWidth * 0.1,
y: self.view.frame.height - keyHeight,
width: blackKeyWidth,
height: blackKeyHeight)
)
if i >= 2 {
button.center.x += keyWidth
}
button.backgroundColor = UIColor(white: 0, alpha: 1)
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.black.cgColor
button.layer.cornerRadius = 4
button.setTitleColor(UIColor.blue, for: .normal)
button.addTarget(self, action: #selector(self.toggleSound), for: .touchDown)
button.addTarget(self, action: #selector(self.toggleSound), for: .touchUpInside)
button.layer.setValue(blackFrequencies[i], forKey: "freq")
self.view.addSubview(button)
}
}
func toggleSound(_ sender: UIButton) {
if oscillator.isPlaying {
oscillator.stop()
} else {
oscillator.amplitude = currentAmplitude
oscillator.frequency = sender.layer.value(forKey: "freq") as! Double
oscillator.start()
}
}
}
まだ、鍵盤の同時押しに対応できていなかったり、細かい動作上の不具合がありますが、キーボードアプリの基本形が出来てきました。

