mirror of https://github.com/miguelmota/cointop
Merge branch 'master' into maciejaszek-docker
commit
91c01cd2dc
File diff suppressed because one or more lines are too long
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Michal Štrba
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -1,44 +0,0 @@
|
||||
# Beep [![GoDoc](https://godoc.org/github.com/faiface/beep?status.svg)](https://godoc.org/github.com/faiface/beep) [![Go Report Card](https://goreportcard.com/badge/github.com/faiface/beep)](https://goreportcard.com/report/github.com/faiface/beep)
|
||||
|
||||
A little package that brings sound to any Go application. Suitable for playback and audio-processing.
|
||||
|
||||
```
|
||||
go get -u github.com/faiface/beep
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
Beep is built on top of its [Streamer](https://godoc.org/github.com/faiface/beep#Streamer) interface, which is like [io.Reader](https://golang.org/pkg/io/#Reader), but for audio. It was one of the best design decisions I've ever made and it enabled all the rest of the features to naturally come together with not much code.
|
||||
|
||||
- **Decode and play WAV, MP3, OGG, and FLAC.**
|
||||
- **Encode and save WAV.**
|
||||
- **Very simple API.** Limiting the support to stereo (two channel) audio made it possible to simplify the architecture and the API.
|
||||
- **Rich library of compositors and effects.** Loop, pause/resume, change volume, mix, sequence, change playback speed, and more.
|
||||
- **Easily create new effects.** With the `Streamer` interface, creating new effects is very easy.
|
||||
- **Generate completely own artificial sounds.** Again, the `Streamer` interface enables easy sound generation.
|
||||
- **Very small codebase.** The core is just ~1K LOC.
|
||||
|
||||
## Tutorial
|
||||
|
||||
The [Wiki](https://github.com/faiface/beep/wiki) contains a handful of tutorials for you to get started. They teach the fundamentals and advanced topics alike. **Read them especially if you call `speaker.Init` every time you play something.**
|
||||
|
||||
- [Hello, Beep!](https://github.com/faiface/beep/wiki/Hello,-Beep!)
|
||||
- [Composing and controlling](https://github.com/faiface/beep/wiki/Composing-and-controlling)
|
||||
- [To buffer, or not to buffer, that is the question](https://github.com/faiface/beep/wiki/To-buffer,-or-not-to-buffer,-that-is-the-question)
|
||||
- [Making own streamers](https://github.com/faiface/beep/wiki/Making-own-streamers)
|
||||
|
||||
## Examples
|
||||
|
||||
| [Speedy Player](https://github.com/faiface/beep/tree/master/examples/speedy-player) | [Doppler Stereo Room](https://github.com/faiface/beep/tree/master/examples/doppler-stereo-room) |
|
||||
| --- | --- |
|
||||
| ![Speedy Player](https://github.com/faiface/beep/blob/master/examples/speedy-player/screenshot.png) | ![Doppler Stereo Room](https://github.com/faiface/beep/blob/master/examples/doppler-stereo-room/screenshot.png) |
|
||||
|
||||
## Dependencies
|
||||
|
||||
For playback, Beep uses [Oto](https://github.com/hajimehoshi/oto) under the hood. Check its requirements to see what you need to install for building your application.
|
||||
|
||||
Running an already built application should work with no extra dependencies.
|
||||
|
||||
## Licence
|
||||
|
||||
[MIT](https://github.com/faiface/beep/blob/master/LICENSE)
|
@ -1,262 +0,0 @@
|
||||
package beep
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SampleRate is the number of samples per second.
|
||||
type SampleRate int
|
||||
|
||||
// D returns the duration of n samples.
|
||||
func (sr SampleRate) D(n int) time.Duration {
|
||||
return time.Second * time.Duration(n) / time.Duration(sr)
|
||||
}
|
||||
|
||||
// N returns the number of samples that last for d duration.
|
||||
func (sr SampleRate) N(d time.Duration) int {
|
||||
return int(d * time.Duration(sr) / time.Second)
|
||||
}
|
||||
|
||||
// Format is the format of a Buffer or another audio source.
|
||||
type Format struct {
|
||||
// SampleRate is the number of samples per second.
|
||||
SampleRate SampleRate
|
||||
|
||||
// NumChannels is the number of channels. The value of 1 is mono, the value of 2 is stereo.
|
||||
// The samples should always be interleaved.
|
||||
NumChannels int
|
||||
|
||||
// Precision is the number of bytes used to encode a single sample. Only values up to 6 work
|
||||
// well, higher values loose precision due to floating point numbers.
|
||||
Precision int
|
||||
}
|
||||
|
||||
// Width returns the number of bytes per one frame (samples in all channels).
|
||||
//
|
||||
// This is equal to f.NumChannels * f.Precision.
|
||||
func (f Format) Width() int {
|
||||
return f.NumChannels * f.Precision
|
||||
}
|
||||
|
||||
// EncodeSigned encodes a single sample in f.Width() bytes to p in signed format.
|
||||
func (f Format) EncodeSigned(p []byte, sample [2]float64) (n int) {
|
||||
return f.encode(true, p, sample)
|
||||
}
|
||||
|
||||
// EncodeUnsigned encodes a single sample in f.Width() bytes to p in unsigned format.
|
||||
func (f Format) EncodeUnsigned(p []byte, sample [2]float64) (n int) {
|
||||
return f.encode(false, p, sample)
|
||||
}
|
||||
|
||||
// DecodeSigned decodes a single sample encoded in f.Width() bytes from p in signed format.
|
||||
func (f Format) DecodeSigned(p []byte) (sample [2]float64, n int) {
|
||||
return f.decode(true, p)
|
||||
}
|
||||
|
||||
// DecodeUnsigned decodes a single sample encoded in f.Width() bytes from p in unsigned format.
|
||||
func (f Format) DecodeUnsigned(p []byte) (sample [2]float64, n int) {
|
||||
return f.decode(false, p)
|
||||
}
|
||||
|
||||
func (f Format) encode(signed bool, p []byte, sample [2]float64) (n int) {
|
||||
switch {
|
||||
case f.NumChannels == 1:
|
||||
x := norm((sample[0] + sample[1]) / 2)
|
||||
p = p[encodeFloat(signed, f.Precision, p, x):]
|
||||
case f.NumChannels >= 2:
|
||||
for c := range sample {
|
||||
x := norm(sample[c])
|
||||
p = p[encodeFloat(signed, f.Precision, p, x):]
|
||||
}
|
||||
for c := len(sample); c < f.NumChannels; c++ {
|
||||
p = p[encodeFloat(signed, f.Precision, p, 0):]
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("format: encode: invalid number of channels: %d", f.NumChannels))
|
||||
}
|
||||
return f.Width()
|
||||
}
|
||||
|
||||
func (f Format) decode(signed bool, p []byte) (sample [2]float64, n int) {
|
||||
switch {
|
||||
case f.NumChannels == 1:
|
||||
x, _ := decodeFloat(signed, f.Precision, p)
|
||||
return [2]float64{x, x}, f.Width()
|
||||
case f.NumChannels >= 2:
|
||||
for c := range sample {
|
||||
x, n := decodeFloat(signed, f.Precision, p)
|
||||
sample[c] = x
|
||||
p = p[n:]
|
||||
}
|
||||
for c := len(sample); c < f.NumChannels; c++ {
|
||||
_, n := decodeFloat(signed, f.Precision, p)
|
||||
p = p[n:]
|
||||
}
|
||||
return sample, f.Width()
|
||||
default:
|
||||
panic(fmt.Errorf("format: decode: invalid number of channels: %d", f.NumChannels))
|
||||
}
|
||||
}
|
||||
|
||||
func encodeFloat(signed bool, precision int, p []byte, x float64) (n int) {
|
||||
var xUint64 uint64
|
||||
if signed {
|
||||
xUint64 = floatToSigned(precision, x)
|
||||
} else {
|
||||
xUint64 = floatToUnsigned(precision, x)
|
||||
}
|
||||
for i := 0; i < precision; i++ {
|
||||
p[i] = byte(xUint64)
|
||||
xUint64 >>= 8
|
||||
}
|
||||
return precision
|
||||
}
|
||||
|
||||
func decodeFloat(signed bool, precision int, p []byte) (x float64, n int) {
|
||||
var xUint64 uint64
|
||||
for i := precision - 1; i >= 0; i-- {
|
||||
xUint64 <<= 8
|
||||
xUint64 += uint64(p[i])
|
||||
}
|
||||
if signed {
|
||||
return signedToFloat(precision, xUint64), precision
|
||||
}
|
||||
return unsignedToFloat(precision, xUint64), precision
|
||||
}
|
||||
|
||||
func floatToSigned(precision int, x float64) uint64 {
|
||||
if x < 0 {
|
||||
compl := uint64(-x * (math.Exp2(float64(precision)*8-1) - 1))
|
||||
return uint64(1<<uint(precision*8)) - compl
|
||||
}
|
||||
return uint64(x * (math.Exp2(float64(precision)*8-1) - 1))
|
||||
}
|
||||
|
||||
func floatToUnsigned(precision int, x float64) uint64 {
|
||||
return uint64((x + 1) / 2 * (math.Exp2(float64(precision)*8) - 1))
|
||||
}
|
||||
|
||||
func signedToFloat(precision int, xUint64 uint64) float64 {
|
||||
if xUint64 >= 1<<uint(precision*8-1) {
|
||||
compl := 1<<uint(precision*8) - xUint64
|
||||
return -float64(int64(compl)) / (math.Exp2(float64(precision)*8-1) - 1)
|
||||
}
|
||||
return float64(int64(xUint64)) / (math.Exp2(float64(precision)*8-1) - 1)
|
||||
}
|
||||
|
||||
func unsignedToFloat(precision int, xUint64 uint64) float64 {
|
||||
return float64(xUint64)/(math.Exp2(float64(precision)*8)-1)*2 - 1
|
||||
}
|
||||
|
||||
func norm(x float64) float64 {
|
||||
if x < -1 {
|
||||
return -1
|
||||
}
|
||||
if x > +1 {
|
||||
return +1
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Buffer is a storage for audio data. You can think of it as a bytes.Buffer for audio samples.
|
||||
type Buffer struct {
|
||||
f Format
|
||||
data []byte
|
||||
tmp []byte
|
||||
}
|
||||
|
||||
// NewBuffer creates a new empty Buffer which stores samples in the provided format.
|
||||
func NewBuffer(f Format) *Buffer {
|
||||
return &Buffer{f: f, tmp: make([]byte, f.Width())}
|
||||
}
|
||||
|
||||
// Format returns the format of the Buffer.
|
||||
func (b *Buffer) Format() Format {
|
||||
return b.f
|
||||
}
|
||||
|
||||
// Len returns the number of samples currently in the Buffer.
|
||||
func (b *Buffer) Len() int {
|
||||
return len(b.data) / b.f.Width()
|
||||
}
|
||||
|
||||
// Pop removes n samples from the beginning of the Buffer.
|
||||
//
|
||||
// Existing Streamers are not affected.
|
||||
func (b *Buffer) Pop(n int) {
|
||||
b.data = b.data[n*b.f.Width():]
|
||||
}
|
||||
|
||||
// Append adds all audio data from the given Streamer to the end of the Buffer.
|
||||
//
|
||||
// The Streamer will be drained when this method finishes.
|
||||
func (b *Buffer) Append(s Streamer) {
|
||||
var samples [512][2]float64
|
||||
for {
|
||||
n, ok := s.Stream(samples[:])
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
for _, sample := range samples[:n] {
|
||||
b.f.EncodeSigned(b.tmp, sample)
|
||||
b.data = append(b.data, b.tmp...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Streamer returns a StreamSeeker which streams samples in the given interval (including from,
|
||||
// excluding to). If from<0 or to>b.Len() or to<from, this method panics.
|
||||
//
|
||||
// When using multiple goroutines, synchronization of Streamers with the Buffer is not required,
|
||||
// as Buffer is persistent (but efficient and garbage collected).
|
||||
func (b *Buffer) Streamer(from, to int) StreamSeeker {
|
||||
return &bufferStreamer{
|
||||
f: b.f,
|
||||
data: b.data[from*b.f.Width() : to*b.f.Width()],
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
type bufferStreamer struct {
|
||||
f Format
|
||||
data []byte
|
||||
pos int
|
||||
}
|
||||
|
||||
func (bs *bufferStreamer) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
if bs.pos >= len(bs.data) {
|
||||
return 0, false
|
||||
}
|
||||
for i := range samples {
|
||||
if bs.pos >= len(bs.data) {
|
||||
break
|
||||
}
|
||||
sample, advance := bs.f.DecodeSigned(bs.data[bs.pos:])
|
||||
samples[i] = sample
|
||||
bs.pos += advance
|
||||
n++
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
func (bs *bufferStreamer) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bs *bufferStreamer) Len() int {
|
||||
return len(bs.data) / bs.f.Width()
|
||||
}
|
||||
|
||||
func (bs *bufferStreamer) Position() int {
|
||||
return bs.pos / bs.f.Width()
|
||||
}
|
||||
|
||||
func (bs *bufferStreamer) Seek(p int) error {
|
||||
if p < 0 || bs.Len() < p {
|
||||
return fmt.Errorf("buffer: seek position %v out of range [%v, %v]", p, 0, bs.Len())
|
||||
}
|
||||
bs.pos = p * bs.f.Width()
|
||||
return nil
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
package beep
|
||||
|
||||
// Take returns a Streamer which streams at most num samples from s.
|
||||
//
|
||||
// The returned Streamer propagates s's errors through Err.
|
||||
func Take(num int, s Streamer) Streamer {
|
||||
return &take{
|
||||
s: s,
|
||||
remains: num,
|
||||
}
|
||||
}
|
||||
|
||||
type take struct {
|
||||
s Streamer
|
||||
remains int
|
||||
}
|
||||
|
||||
func (t *take) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
if t.remains <= 0 {
|
||||
return 0, false
|
||||
}
|
||||
toStream := t.remains
|
||||
if len(samples) < toStream {
|
||||
toStream = len(samples)
|
||||
}
|
||||
n, ok = t.s.Stream(samples[:toStream])
|
||||
t.remains -= n
|
||||
return n, ok
|
||||
}
|
||||
|
||||
func (t *take) Err() error {
|
||||
return t.s.Err()
|
||||
}
|
||||
|
||||
// Loop takes a StreamSeeker and plays it count times. If count is negative, s is looped infinitely.
|
||||
//
|
||||
// The returned Streamer propagates s's errors.
|
||||
func Loop(count int, s StreamSeeker) Streamer {
|
||||
return &loop{
|
||||
s: s,
|
||||
remains: count,
|
||||
}
|
||||
}
|
||||
|
||||
type loop struct {
|
||||
s StreamSeeker
|
||||
remains int
|
||||
}
|
||||
|
||||
func (l *loop) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
if l.remains == 0 || l.s.Err() != nil {
|
||||
return 0, false
|
||||
}
|
||||
for len(samples) > 0 {
|
||||
sn, sok := l.s.Stream(samples)
|
||||
if !sok {
|
||||
if l.remains > 0 {
|
||||
l.remains--
|
||||
}
|
||||
if l.remains == 0 {
|
||||
break
|
||||
}
|
||||
err := l.s.Seek(0)
|
||||
if err != nil {
|
||||
return n, true
|
||||
}
|
||||
continue
|
||||
}
|
||||
samples = samples[sn:]
|
||||
n += sn
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
func (l *loop) Err() error {
|
||||
return l.s.Err()
|
||||
}
|
||||
|
||||
// Seq takes zero or more Streamers and returns a Streamer which streams them one by one without pauses.
|
||||
//
|
||||
// Seq does not propagate errors from the Streamers.
|
||||
func Seq(s ...Streamer) Streamer {
|
||||
i := 0
|
||||
return StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
|
||||
for i < len(s) && len(samples) > 0 {
|
||||
sn, sok := s[i].Stream(samples)
|
||||
samples = samples[sn:]
|
||||
n, ok = n+sn, ok || sok
|
||||
if !sok {
|
||||
i++
|
||||
}
|
||||
}
|
||||
return n, ok
|
||||
})
|
||||
}
|
||||
|
||||
// Mix takes zero or more Streamers and returns a Streamer which streams them mixed together.
|
||||
//
|
||||
// Mix does not propagate errors from the Streamers.
|
||||
func Mix(s ...Streamer) Streamer {
|
||||
return StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
|
||||
var tmp [512][2]float64
|
||||
|
||||
for len(samples) > 0 {
|
||||
toStream := len(tmp)
|
||||
if toStream > len(samples) {
|
||||
toStream = len(samples)
|
||||
}
|
||||
|
||||
// clear the samples
|
||||
for i := range samples[:toStream] {
|
||||
samples[i] = [2]float64{}
|
||||
}
|
||||
|
||||
snMax := 0 // max number of streamed samples in this iteration
|
||||
for _, st := range s {
|
||||
// mix the stream
|
||||
sn, sok := st.Stream(tmp[:toStream])
|
||||
if sn > snMax {
|
||||
snMax = sn
|
||||
}
|
||||
ok = ok || sok
|
||||
|
||||
for i := range tmp[:sn] {
|
||||
samples[i][0] += tmp[i][0]
|
||||
samples[i][1] += tmp[i][1]
|
||||
}
|
||||
}
|
||||
|
||||
n += snMax
|
||||
if snMax < len(tmp) {
|
||||
break
|
||||
}
|
||||
samples = samples[snMax:]
|
||||
}
|
||||
|
||||
return n, ok
|
||||
})
|
||||
}
|
||||
|
||||
// Dup returns two Streamers which both stream the same data as the original s. The two Streamers
|
||||
// can't be used concurrently without synchronization.
|
||||
func Dup(s Streamer) (t, u Streamer) {
|
||||
var tBuf, uBuf [][2]float64
|
||||
return &dup{&tBuf, &uBuf, s}, &dup{&uBuf, &tBuf, s}
|
||||
}
|
||||
|
||||
type dup struct {
|
||||
myBuf, itsBuf *[][2]float64
|
||||
s Streamer
|
||||
}
|
||||
|
||||
func (d *dup) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
buf := *d.myBuf
|
||||
n = copy(samples, buf)
|
||||
ok = len(buf) > 0
|
||||
buf = buf[n:]
|
||||
samples = samples[n:]
|
||||
*d.myBuf = buf
|
||||
|
||||
if len(samples) > 0 {
|
||||
sn, sok := d.s.Stream(samples)
|
||||
n += sn
|
||||
ok = ok || sok
|
||||
*d.itsBuf = append(*d.itsBuf, samples[:sn]...)
|
||||
}
|
||||
|
||||
return n, ok
|
||||
}
|
||||
|
||||
func (d *dup) Err() error {
|
||||
return d.s.Err()
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package beep
|
||||
|
||||
// Ctrl allows for pausing a Streamer.
|
||||
//
|
||||
// Wrap a Streamer in a Ctrl.
|
||||
//
|
||||
// ctrl := &beep.Ctrl{Streamer: s}
|
||||
//
|
||||
// Then, we can pause the streaming (this will cause Ctrl to stream silence).
|
||||
//
|
||||
// ctrl.Paused = true
|
||||
//
|
||||
// To completely stop a Ctrl before the wrapped Streamer is drained, just set the wrapped Streamer
|
||||
// to nil.
|
||||
//
|
||||
// ctrl.Streamer = nil
|
||||
//
|
||||
// If you're playing a Streamer wrapped in a Ctrl through the speaker, you need to lock and unlock
|
||||
// the speaker when modifying the Ctrl to avoid race conditions.
|
||||
//
|
||||
// speaker.Play(ctrl)
|
||||
// // ...
|
||||
// speaker.Lock()
|
||||
// ctrl.Paused = true
|
||||
// speaker.Unlock()
|
||||
type Ctrl struct {
|
||||
Streamer Streamer
|
||||
Paused bool
|
||||
}
|
||||
|
||||
// Stream streams the wrapped Streamer, if not nil. If the Streamer is nil, Ctrl acts as drained.
|
||||
// When paused, Ctrl streams silence.
|
||||
func (c *Ctrl) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
if c.Streamer == nil {
|
||||
return 0, false
|
||||
}
|
||||
if c.Paused {
|
||||
for i := range samples {
|
||||
samples[i] = [2]float64{}
|
||||
}
|
||||
return len(samples), true
|
||||
}
|
||||
return c.Streamer.Stream(samples)
|
||||
}
|
||||
|
||||
// Err returns the error of the wrapped Streamer, if not nil.
|
||||
func (c *Ctrl) Err() error {
|
||||
if c.Streamer == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Streamer.Err()
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
module github.com/faiface/beep
|
||||
|
||||
require (
|
||||
github.com/gdamore/tcell v1.1.1
|
||||
github.com/hajimehoshi/go-mp3 v0.1.1
|
||||
github.com/hajimehoshi/oto v0.3.1
|
||||
github.com/jfreymuth/oggvorbis v1.0.0
|
||||
github.com/jfreymuth/vorbis v1.0.0 // indirect
|
||||
github.com/mewkiz/flac v1.0.5
|
||||
github.com/pkg/errors v0.8.1
|
||||
)
|
@ -1,39 +0,0 @@
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell v1.1.1 h1:U73YL+jMem2XfhvaIUfPO6MpJawaG92B2funXVb9qLs=
|
||||
github.com/gdamore/tcell v1.1.1/go.mod h1:K1udHkiR3cOtlpKG5tZPD5XxrF7v2y7lDq7Whcj+xkQ=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m256AsaDPQ+/4=
|
||||
github.com/gopherjs/gopherwasm v1.0.0 h1:32nge/RlujS1Im4HNCJPp0NbBOAeBXFuT1KonUuLl+Y=
|
||||
github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
|
||||
github.com/hajimehoshi/go-mp3 v0.1.1 h1:Y33fAdTma70fkrxnc9u50Uq0lV6eZ+bkAlssdMmCwUc=
|
||||
github.com/hajimehoshi/go-mp3 v0.1.1/go.mod h1:4i+c5pDNKDrxl1iu9iG90/+fhP37lio6gNhjCx9WBJw=
|
||||
github.com/hajimehoshi/oto v0.1.1/go.mod h1:hUiLWeBQnbDu4pZsAhOnGqMI1ZGibS6e2qhQdfpwz04=
|
||||
github.com/hajimehoshi/oto v0.3.1 h1:cpf/uIv4Q0oc5uf9loQn7PIehv+mZerh+0KKma6gzMk=
|
||||
github.com/hajimehoshi/oto v0.3.1/go.mod h1:e9eTLBB9iZto045HLbzfHJIc+jP3xaKrjZTghvb6fdM=
|
||||
github.com/jfreymuth/oggvorbis v1.0.0 h1:aOpiihGrFLXpsh2osOlEvTcg5/aluzGQeC7m3uYWOZ0=
|
||||
github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM=
|
||||
github.com/jfreymuth/vorbis v1.0.0 h1:SmDf783s82lIjGZi8EGUUaS7YxPHgRj4ZXW/h7rUi7U=
|
||||
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 h1:5MnxBC15uMxFv5FY/J/8vzyaBiArCOkMdFT9Jsw78iY=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mewkiz/flac v1.0.5 h1:dHGW/2kf+/KZ2GGqSVayNEhL9pluKn/rr/h/QqD9Ogc=
|
||||
github.com/mewkiz/flac v1.0.5/go.mod h1:EHZNU32dMF6alpurYyKHDLYpW1lYpBZ5WrXi/VuNIGs=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd h1:nLIcFw7GiqKXUS7HiChg6OAYWgASB2H97dZKd1GhDSs=
|
||||
golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/mobile v0.0.0-20180806140643-507816974b79 h1:t2JRgCWkY7Qaa1J2jal+wqC9OjbyHCHwIA9rVlRUSMo=
|
||||
golang.org/x/mobile v0.0.0-20180806140643-507816974b79/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb h1:pf3XwC90UUdNPYWZdFjhGBE7DUFuK3Ct1zWmZ65QN30=
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY=
|
||||
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
|
@ -1,106 +0,0 @@
|
||||
package beep
|
||||
|
||||
// Streamer is able to stream a finite or infinite sequence of audio samples.
|
||||
type Streamer interface {
|
||||
// Stream copies at most len(samples) next audio samples to the samples slice.
|
||||
//
|
||||
// The sample rate of the samples is unspecified in general, but should be specified for
|
||||
// each concrete Streamer.
|
||||
//
|
||||
// The value at samples[i][0] is the value of the left channel of the i-th sample.
|
||||
// Similarly, samples[i][1] is the value of the right channel of the i-th sample.
|
||||
//
|
||||
// Stream returns the number of streamed samples. If the Streamer is drained and no more
|
||||
// samples will be produced, it returns 0 and false. Stream must not touch any samples
|
||||
// outside samples[:n].
|
||||
//
|
||||
// There are 3 valid return pattterns of the Stream method:
|
||||
//
|
||||
// 1. n == len(samples) && ok
|
||||
//
|
||||
// Stream streamed all of the requested samples. Cases 1, 2 and 3 may occur in the following
|
||||
// calls.
|
||||
//
|
||||
// 2. 0 < n && n < len(samples) && ok
|
||||
//
|
||||
// Stream streamed n samples and drained the Streamer. Only case 3 may occur in the
|
||||
// following calls.
|
||||
//
|
||||
// 3. n == 0 && !ok
|
||||
//
|
||||
// The Streamer is drained and no more samples will come. If Err returns a non-nil error, only
|
||||
// this case is valid. Only this case may occur in the following calls.
|
||||
Stream(samples [][2]float64) (n int, ok bool)
|
||||
|
||||
// Err returns an error which occurred during streaming. If no error occurred, nil is
|
||||
// returned.
|
||||
//
|
||||
// When an error occurs, Streamer must become drained and Stream must return 0, false
|
||||
// forever.
|
||||
//
|
||||
// The reason why Stream doesn't return an error is that it dramatically simplifies
|
||||
// programming with Streamer. It's not very important to catch the error right when it
|
||||
// happens.
|
||||
Err() error
|
||||
}
|
||||
|
||||
// StreamSeeker is a finite duration Streamer which supports seeking to an arbitrary position.
|
||||
type StreamSeeker interface {
|
||||
Streamer
|
||||
|
||||
// Duration returns the total number of samples of the Streamer.
|
||||
Len() int
|
||||
|
||||
// Position returns the current position of the Streamer. This value is between 0 and the
|
||||
// total length.
|
||||
Position() int
|
||||
|
||||
// Seek sets the position of the Streamer to the provided value.
|
||||
//
|
||||
// If an error occurs during seeking, the position remains unchanged. This error will not be
|
||||
// returned through the Streamer's Err method.
|
||||
Seek(p int) error
|
||||
}
|
||||
|
||||
// StreamCloser is a Streamer streaming from a resource which needs to be released, such as a file
|
||||
// or a network connection.
|
||||
type StreamCloser interface {
|
||||
Streamer
|
||||
|
||||
// Close closes the Streamer and releases it's resources. Streamer will no longer stream any
|
||||
// samples.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// StreamSeekCloser is a union of StreamSeeker and StreamCloser.
|
||||
type StreamSeekCloser interface {
|
||||
Streamer
|
||||
Len() int
|
||||
Position() int
|
||||
Seek(p int) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
// StreamerFunc is a Streamer created by simply wrapping a streaming function (usually a closure,
|
||||
// which encloses a time tracking variable). This sometimes simplifies creating new streamers.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// noise := StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
|
||||
// for i := range samples {
|
||||
// samples[i][0] = rand.Float64()*2 - 1
|
||||
// samples[i][1] = rand.Float64()*2 - 1
|
||||
// }
|
||||
// return len(samples), true
|
||||
// })
|
||||
type StreamerFunc func(samples [][2]float64) (n int, ok bool)
|
||||
|
||||
// Stream calls the wrapped streaming function.
|
||||
func (sf StreamerFunc) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
return sf(samples)
|
||||
}
|
||||
|
||||
// Err always returns nil.
|
||||
func (sf StreamerFunc) Err() error {
|
||||
return nil
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package beep
|
||||
|
||||
// Mixer allows for dynamic mixing of arbitrary number of Streamers. Mixer automatically removes
|
||||
// drained Streamers. Mixer's stream never drains, when empty, Mixer streams silence.
|
||||
type Mixer struct {
|
||||
streamers []Streamer
|
||||
}
|
||||
|
||||
// Len returns the number of Streamers currently playing in the Mixer.
|
||||
func (m *Mixer) Len() int {
|
||||
return len(m.streamers)
|
||||
}
|
||||
|
||||
// Add adds Streamers to the Mixer.
|
||||
func (m *Mixer) Add(s ...Streamer) {
|
||||
m.streamers = append(m.streamers, s...)
|
||||
}
|
||||
|
||||
// Clear removes all Streamers from the mixer.
|
||||
func (m *Mixer) Clear() {
|
||||
m.streamers = m.streamers[:0]
|
||||
}
|
||||
|
||||
// Stream streams all Streamers currently in the Mixer mixed together. This method always returns
|
||||
// len(samples), true. If there are no Streamers available, this methods streams silence.
|
||||
func (m *Mixer) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
var tmp [512][2]float64
|
||||
|
||||
for len(samples) > 0 {
|
||||
toStream := len(tmp)
|
||||
if toStream > len(samples) {
|
||||
toStream = len(samples)
|
||||
}
|
||||
|
||||
// clear the samples
|
||||
for i := range samples[:toStream] {
|
||||
samples[i] = [2]float64{}
|
||||
}
|
||||
|
||||
for si := 0; si < len(m.streamers); si++ {
|
||||
// mix the stream
|
||||
sn, sok := m.streamers[si].Stream(tmp[:toStream])
|
||||
for i := range tmp[:sn] {
|
||||
samples[i][0] += tmp[i][0]
|
||||
samples[i][1] += tmp[i][1]
|
||||
}
|
||||
if !sok {
|
||||
// remove drained streamer
|
||||
sj := len(m.streamers) - 1
|
||||
m.streamers[si], m.streamers[sj] = m.streamers[sj], m.streamers[si]
|
||||
m.streamers = m.streamers[:sj]
|
||||
si--
|
||||
}
|
||||
}
|
||||
|
||||
samples = samples[toStream:]
|
||||
n += toStream
|
||||
}
|
||||
|
||||
return n, true
|
||||
}
|
||||
|
||||
// Err always returns nil for Mixer.
|
||||
//
|
||||
// There are two reasons. The first one is that erroring Streamers are immediately drained and
|
||||
// removed from the Mixer. The second one is that one Streamer shouldn't break the whole Mixer and
|
||||
// you should handle the errors right where they can happen.
|
||||
func (m *Mixer) Err() error {
|
||||
return nil
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
// Package mp3 implements audio data decoding in MP3 format.
|
||||
package mp3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/faiface/beep"
|
||||
gomp3 "github.com/hajimehoshi/go-mp3"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
gomp3NumChannels = 2
|
||||
gomp3Precision = 2
|
||||
gomp3BytesPerFrame = gomp3NumChannels * gomp3Precision
|
||||
)
|
||||
|
||||
// Decode takes a ReadCloser containing audio data in MP3 format and returns a StreamSeekCloser,
|
||||
// which streams that audio. The Seek method will panic if rc is not io.Seeker.
|
||||
//
|
||||
// Do not close the supplied ReadSeekCloser, instead, use the Close method of the returned
|
||||
// StreamSeekCloser when you want to release the resources.
|
||||
func Decode(rc io.ReadCloser) (s beep.StreamSeekCloser, format beep.Format, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "mp3")
|
||||
}
|
||||
}()
|
||||
d, err := gomp3.NewDecoder(rc)
|
||||
if err != nil {
|
||||
return nil, beep.Format{}, err
|
||||
}
|
||||
format = beep.Format{
|
||||
SampleRate: beep.SampleRate(d.SampleRate()),
|
||||
NumChannels: gomp3NumChannels,
|
||||
Precision: gomp3Precision,
|
||||
}
|
||||
return &decoder{rc, d, format, 0, nil}, format, nil
|
||||
}
|
||||
|
||||
type decoder struct {
|
||||
closer io.Closer
|
||||
d *gomp3.Decoder
|
||||
f beep.Format
|
||||
pos int
|
||||
err error
|
||||
}
|
||||
|
||||
func (d *decoder) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
if d.err != nil {
|
||||
return 0, false
|
||||
}
|
||||
var tmp [gomp3BytesPerFrame]byte
|
||||
for i := range samples {
|
||||
dn, err := d.d.Read(tmp[:])
|
||||
if dn == len(tmp) {
|
||||
samples[i], _ = d.f.DecodeSigned(tmp[:])
|
||||
d.pos += dn
|
||||
n++
|
||||
ok = true
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
d.err = errors.Wrap(err, "mp3")
|
||||
break
|
||||
}
|
||||
}
|
||||
return n, ok
|
||||
}
|
||||
|
||||
func (d *decoder) Err() error {
|
||||
return d.err
|
||||
}
|
||||
|
||||
func (d *decoder) Len() int {
|
||||
return int(d.d.Length()) / gomp3BytesPerFrame
|
||||
}
|
||||
|
||||
func (d *decoder) Position() int {
|
||||
return d.pos / gomp3BytesPerFrame
|
||||
}
|
||||
|
||||
func (d *decoder) Seek(p int) error {
|
||||
if p < 0 || d.Len() < p {
|
||||
return fmt.Errorf("mp3: seek position %v out of range [%v, %v]", p, 0, d.Len())
|
||||
}
|
||||
_, err := d.d.Seek(int64(p)*gomp3BytesPerFrame, io.SeekStart)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "mp3")
|
||||
}
|
||||
d.pos = p * gomp3BytesPerFrame
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *decoder) Close() error {
|
||||
err := d.closer.Close()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "mp3")
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
package beep
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Resample takes a Streamer which is assumed to stream at the old sample rate and returns a
|
||||
// Streamer, which streams the data from the original Streamer resampled to the new sample rate.
|
||||
//
|
||||
// This is, for example, useful when mixing multiple Streamer with different sample rates, either
|
||||
// through a beep.Mixer, or through a speaker. Speaker has a constant sample rate. Thus, playing
|
||||
// Streamer which stream at a different sample rate will lead to a changed speed and pitch of the
|
||||
// playback.
|
||||
//
|
||||
// sr := beep.SampleRate(48000)
|
||||
// speaker.Init(sr, sr.N(time.Second/2))
|
||||
// speaker.Play(beep.Resample(3, format.SampleRate, sr, s))
|
||||
//
|
||||
// In the example, the original sample rate of the source if format.SampleRate. We want to play it
|
||||
// at the speaker's native sample rate and thus we need to resample.
|
||||
//
|
||||
// The quality argument specifies the quality of the resampling process. Higher quality implies
|
||||
// worse performance. Values below 1 or above 64 are invalid and Resample will panic. Here's a table
|
||||
// for deciding which quality to pick.
|
||||
//
|
||||
// quality | use case
|
||||
// --------|---------
|
||||
// 1 | very high performance, on-the-fly resampling, low quality
|
||||
// 3-4 | good performance, on-the-fly resampling, good quality
|
||||
// 6 | higher CPU usage, usually not suitable for on-the-fly resampling, very good quality
|
||||
// >6 | even higher CPU usage, for offline resampling, very good quality
|
||||
//
|
||||
// Sane quality values are usually below 16. Higher values will consume too much CPU, giving
|
||||
// negligible quality improvements.
|
||||
//
|
||||
// Resample propagates errors from s.
|
||||
func Resample(quality int, old, new SampleRate, s Streamer) *Resampler {
|
||||
return ResampleRatio(quality, float64(old)/float64(new), s)
|
||||
}
|
||||
|
||||
// ResampleRatio is same as Resample, except it takes the ratio of the old and the new sample rate,
|
||||
// specifically, the old sample rate divided by the new sample rate. Aside from correcting the
|
||||
// sample rate, this can be used to change the speed of the audio. For example, resampling at the
|
||||
// ratio of 2 and playing at the original sample rate will cause doubled speed in playback.
|
||||
func ResampleRatio(quality int, ratio float64, s Streamer) *Resampler {
|
||||
if quality < 1 || 64 < quality {
|
||||
panic(fmt.Errorf("resample: invalid quality: %d", quality))
|
||||
}
|
||||
return &Resampler{
|
||||
s: s,
|
||||
ratio: ratio,
|
||||
first: true,
|
||||
buf1: make([][2]float64, 512),
|
||||
buf2: make([][2]float64, 512),
|
||||
pts: make([]point, quality*2),
|
||||
off: 0,
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Resampler is a Streamer created by Resample and ResampleRatio functions. It allows dynamic
|
||||
// changing of the resampling ratio, which can be useful for dynamically changing the speed of
|
||||
// streaming.
|
||||
type Resampler struct {
|
||||
s Streamer // the orignal streamer
|
||||
ratio float64 // old sample rate / new sample rate
|
||||
first bool // true when Stream was not called before
|
||||
buf1, buf2 [][2]float64 // buf1 contains previous buf2, new data goes into buf2, buf1 is because interpolation might require old samples
|
||||
pts []point // pts is for points used for interpolation
|
||||
off int // off is the position of the start of buf2 in the original data
|
||||
pos int // pos is the current position in the resampled data
|
||||
}
|
||||
|
||||
// Stream streams the original audio resampled according to the current ratio.
|
||||
func (r *Resampler) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
// if it's the first time, we need to fill buf2 with initial data, buf1 remains zeroed
|
||||
if r.first {
|
||||
sn, _ := r.s.Stream(r.buf2)
|
||||
r.buf2 = r.buf2[:sn]
|
||||
r.first = false
|
||||
}
|
||||
// we start resampling, sample by sample
|
||||
for len(samples) > 0 {
|
||||
again:
|
||||
for c := range samples[0] {
|
||||
// calculate the current position in the original data
|
||||
j := float64(r.pos) * r.ratio
|
||||
|
||||
// find quality*2 closest samples to j and translate them to points for interpolation
|
||||
for pi := range r.pts {
|
||||
// calculate the index of one of the closest samples
|
||||
k := int(j) + pi - len(r.pts)/2 + 1
|
||||
|
||||
var y float64
|
||||
switch {
|
||||
// the sample is in buf1
|
||||
case k < r.off:
|
||||
y = r.buf1[len(r.buf1)+k-r.off][c]
|
||||
// the sample is in buf2
|
||||
case k < r.off+len(r.buf2):
|
||||
y = r.buf2[k-r.off][c]
|
||||
// the sample is beyond buf2, so we need to load new data
|
||||
case k >= r.off+len(r.buf2):
|
||||
// we load into buf1
|
||||
sn, _ := r.s.Stream(r.buf1)
|
||||
// this condition happens when the original Streamer got
|
||||
// drained and j is after the end of the
|
||||
// original data
|
||||
if int(j) >= r.off+len(r.buf2)+sn {
|
||||
return n, n > 0
|
||||
}
|
||||
// this condition happens when the original Streamer got
|
||||
// drained and this one of the closest samples is after the
|
||||
// end of the original data
|
||||
if k >= r.off+len(r.buf2)+sn {
|
||||
y = 0
|
||||
break
|
||||
}
|
||||
// otherwise everything is fine, we swap buffers and start
|
||||
// calculating the sample again
|
||||
r.off += len(r.buf2)
|
||||
r.buf1 = r.buf1[:sn]
|
||||
r.buf1, r.buf2 = r.buf2, r.buf1
|
||||
goto again
|
||||
}
|
||||
|
||||
r.pts[pi] = point{float64(k), y}
|
||||
}
|
||||
|
||||
// calculate the resampled sample using polynomial interpolation from the
|
||||
// quality*2 closest samples
|
||||
samples[0][c] = lagrange(r.pts, j)
|
||||
}
|
||||
samples = samples[1:]
|
||||
n++
|
||||
r.pos++
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
// Err propagates the original Streamer's errors.
|
||||
func (r *Resampler) Err() error {
|
||||
return r.s.Err()
|
||||
}
|
||||
|
||||
// Ratio returns the current resampling ratio.
|
||||
func (r *Resampler) Ratio() float64 {
|
||||
return r.ratio
|
||||
}
|
||||
|
||||
// SetRatio sets the resampling ratio. This does not cause any glitches in the stream.
|
||||
func (r *Resampler) SetRatio(ratio float64) {
|
||||
r.pos = int(float64(r.pos) * r.ratio / ratio)
|
||||
r.ratio = ratio
|
||||
}
|
||||
|
||||
// lagrange calculates the value at x of a polynomial of order len(pts)+1 which goes through all
|
||||
// points in pts
|
||||
func lagrange(pts []point, x float64) (y float64) {
|
||||
y = 0.0
|
||||
for j := range pts {
|
||||
l := 1.0
|
||||
for m := range pts {
|
||||
if j == m {
|
||||
continue
|
||||
}
|
||||
l *= (x - pts[m].X) / (pts[j].X - pts[m].X)
|
||||
}
|
||||
y += pts[j].Y * l
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
type point struct {
|
||||
X, Y float64
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
// Package speaker implements playback of beep.Streamer values through physical speakers.
|
||||
package speaker
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/faiface/beep"
|
||||
"github.com/hajimehoshi/oto"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
mixer beep.Mixer
|
||||
samples [][2]float64
|
||||
buf []byte
|
||||
context *oto.Context
|
||||
player *oto.Player
|
||||
done chan struct{}
|
||||
)
|
||||
|
||||
// Init initializes audio playback through speaker. Must be called before using this package.
|
||||
//
|
||||
// The bufferSize argument specifies the number of samples of the speaker's buffer. Bigger
|
||||
// bufferSize means lower CPU usage and more reliable playback. Lower bufferSize means better
|
||||
// responsiveness and less delay.
|
||||
func Init(sampleRate beep.SampleRate, bufferSize int) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
Close()
|
||||
|
||||
mixer = beep.Mixer{}
|
||||
|
||||
numBytes := bufferSize * 4
|
||||
samples = make([][2]float64, bufferSize)
|
||||
buf = make([]byte, numBytes)
|
||||
|
||||
var err error
|
||||
context, err = oto.NewContext(int(sampleRate), 2, 2, numBytes)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize speaker")
|
||||
}
|
||||
player = context.NewPlayer()
|
||||
|
||||
done = make(chan struct{})
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
default:
|
||||
update()
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the playback and the driver. In most cases, there is certainly no need to call Close
|
||||
// even when the program doesn't play anymore, because in properly set systems, the default mixer
|
||||
// handles multiple concurrent processes. It's only when the default device is not a virtual but hardware
|
||||
// device, that you'll probably want to manually manage the device from your application.
|
||||
func Close() {
|
||||
if player != nil {
|
||||
if done != nil {
|
||||
done <- struct{}{}
|
||||
done = nil
|
||||
}
|
||||
player.Close()
|
||||
context.Close()
|
||||
player = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Lock locks the speaker. While locked, speaker won't pull new data from the playing Stramers. Lock
|
||||
// if you want to modify any currently playing Streamers to avoid race conditions.
|
||||
//
|
||||
// Always lock speaker for as little time as possible, to avoid playback glitches.
|
||||
func Lock() {
|
||||
mu.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks the speaker. Call after modifying any currently playing Streamer.
|
||||
func Unlock() {
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
// Play starts playing all provided Streamers through the speaker.
|
||||
func Play(s ...beep.Streamer) {
|
||||
mu.Lock()
|
||||
mixer.Add(s...)
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
// Clear removes all currently playing Streamers from the speaker.
|
||||
func Clear() {
|
||||
mu.Lock()
|
||||
mixer.Clear()
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
// update pulls new data from the playing Streamers and sends it to the speaker. Blocks until the
|
||||
// data is sent and started playing.
|
||||
func update() {
|
||||
mu.Lock()
|
||||
mixer.Stream(samples)
|
||||
mu.Unlock()
|
||||
|
||||
for i := range samples {
|
||||
for c := range samples[i] {
|
||||
val := samples[i][c]
|
||||
if val < -1 {
|
||||
val = -1
|
||||
}
|
||||
if val > +1 {
|
||||
val = +1
|
||||
}
|
||||
valInt16 := int16(val * (1<<15 - 1))
|
||||
low := byte(valInt16)
|
||||
high := byte(valInt16 >> 8)
|
||||
buf[i*4+c*2+0] = low
|
||||
buf[i*4+c*2+1] = high
|
||||
}
|
||||
}
|
||||
|
||||
player.Write(buf)
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package beep
|
||||
|
||||
// Silence returns a Streamer which streams num samples of silence. If num is negative, silence is
|
||||
// streamed forever.
|
||||
func Silence(num int) Streamer {
|
||||
return StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
|
||||
if num == 0 {
|
||||
return 0, false
|
||||
}
|
||||
if 0 < num && num < len(samples) {
|
||||
samples = samples[:num]
|
||||
}
|
||||
for i := range samples {
|
||||
samples[i] = [2]float64{}
|
||||
}
|
||||
if num > 0 {
|
||||
num -= len(samples)
|
||||
}
|
||||
return len(samples), true
|
||||
})
|
||||
}
|
||||
|
||||
// Callback returns a Streamer, which does not stream any samples, but instead calls f the first
|
||||
// time its Stream method is called.
|
||||
func Callback(f func()) Streamer {
|
||||
return StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
|
||||
if f != nil {
|
||||
f()
|
||||
f = nil
|
||||
}
|
||||
return 0, false
|
||||
})
|
||||
}
|
||||
|
||||
// Iterate returns a Streamer which successively streams Streamers obtains by calling the provided g
|
||||
// function. The streaming stops when g returns nil.
|
||||
//
|
||||
// Iterate does not propagate errors from the generated Streamers.
|
||||
func Iterate(g func() Streamer) Streamer {
|
||||
var (
|
||||
s Streamer
|
||||
first = true
|
||||
)
|
||||
return StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
|
||||
if first {
|
||||
s = g()
|
||||
first = false
|
||||
}
|
||||
if s == nil {
|
||||
return 0, false
|
||||
}
|
||||
for len(samples) > 0 {
|
||||
if s == nil {
|
||||
break
|
||||
}
|
||||
sn, sok := s.Stream(samples)
|
||||
if !sok {
|
||||
s = g()
|
||||
}
|
||||
samples = samples[sn:]
|
||||
n += sn
|
||||
}
|
||||
return n, true
|
||||
})
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
*~
|
||||
.DS_Store
|
@ -1,2 +0,0 @@
|
||||
Christopher Cooper <chris@getfreebird.com>
|
||||
Hajime Hoshi <hajimehoshi@gmail.com>
|
@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -1,7 +0,0 @@
|
||||
# go-mp3
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/hajimehoshi/go-mp3?status.svg)](http://godoc.org/github.com/hajimehoshi/go-mp3)
|
||||
|
||||
An MP3 decoder in pure Go based on [PDMP3](https://github.com/technosaurus/PDMP3).
|
||||
|
||||
[Slide at golang.tokyo #11](https://docs.google.com/presentation/d/e/2PACX-1vTTXf-LWNRvMVGQ7GI4Wh8EKohot_9CMtlF4dswpYGpuYKOek5NeNP-_QZnNcRFZp9Cwm0pCcykjqDN/pub?start=false&loop=false&delayms=3000)
|
@ -1,217 +0,0 @@
|
||||
// Copyright 2017 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mp3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3/internal/consts"
|
||||
"github.com/hajimehoshi/go-mp3/internal/frame"
|
||||
"github.com/hajimehoshi/go-mp3/internal/frameheader"
|
||||
)
|
||||
|
||||
// A Decoder is a MP3-decoded stream.
|
||||
//
|
||||
// Decoder decodes its underlying source on the fly.
|
||||
type Decoder struct {
|
||||
source *source
|
||||
sampleRate int
|
||||
length int64
|
||||
frameStarts []int64
|
||||
buf []byte
|
||||
frame *frame.Frame
|
||||
pos int64
|
||||
}
|
||||
|
||||
func (d *Decoder) readFrame() error {
|
||||
var err error
|
||||
d.frame, _, err = frame.Read(d.source, d.source.pos, d.frame)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return io.EOF
|
||||
}
|
||||
if _, ok := err.(*consts.UnexpectedEOF); ok {
|
||||
// TODO: Log here?
|
||||
return io.EOF
|
||||
}
|
||||
return err
|
||||
}
|
||||
d.buf = append(d.buf, d.frame.Decode()...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read is io.Reader's Read.
|
||||
func (d *Decoder) Read(buf []byte) (int, error) {
|
||||
for len(d.buf) == 0 {
|
||||
if err := d.readFrame(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
n := copy(buf, d.buf)
|
||||
d.buf = d.buf[n:]
|
||||
d.pos += int64(n)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Seek is io.Seeker's Seek.
|
||||
//
|
||||
// Seek panics when the underlying source is not io.Seeker.
|
||||
func (d *Decoder) Seek(offset int64, whence int) (int64, error) {
|
||||
npos := int64(0)
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
npos = offset
|
||||
case io.SeekCurrent:
|
||||
npos = d.pos + offset
|
||||
case io.SeekEnd:
|
||||
npos = d.Length() + offset
|
||||
default:
|
||||
panic(fmt.Sprintf("mp3: invalid whence: %v", whence))
|
||||
}
|
||||
d.pos = npos
|
||||
d.buf = nil
|
||||
d.frame = nil
|
||||
f := d.pos / consts.BytesPerFrame
|
||||
// If the frame is not first, read the previous ahead of reading that
|
||||
// because the previous frame can affect the targeted frame.
|
||||
if f > 0 {
|
||||
f--
|
||||
if _, err := d.source.Seek(d.frameStarts[f], 0); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := d.readFrame(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := d.readFrame(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
d.buf = d.buf[consts.BytesPerFrame+(d.pos%consts.BytesPerFrame):]
|
||||
} else {
|
||||
if _, err := d.source.Seek(d.frameStarts[f], 0); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := d.readFrame(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
d.buf = d.buf[d.pos:]
|
||||
}
|
||||
return npos, nil
|
||||
}
|
||||
|
||||
// Close is io.Closer's Close.
|
||||
func (d *Decoder) Close() error {
|
||||
return d.source.Close()
|
||||
}
|
||||
|
||||
// SampleRate returns the sample rate like 44100.
|
||||
//
|
||||
// Note that the sample rate is retrieved from the first frame.
|
||||
func (d *Decoder) SampleRate() int {
|
||||
return d.sampleRate
|
||||
}
|
||||
|
||||
func (d *Decoder) ensureFrameStartsAndLength() error {
|
||||
if d.length != invalidLength {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := d.source.reader.(io.Seeker); !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Keep the current position.
|
||||
pos, err := d.source.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.source.rewind(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.source.skipTags(); err != nil {
|
||||
return err
|
||||
}
|
||||
l := int64(0)
|
||||
for {
|
||||
h, pos, err := frameheader.Read(d.source, d.source.pos)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if _, ok := err.(*consts.UnexpectedEOF); ok {
|
||||
// TODO: Log here?
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
d.frameStarts = append(d.frameStarts, pos)
|
||||
l += consts.BytesPerFrame
|
||||
|
||||
buf := make([]byte, h.FrameSize()-4)
|
||||
if _, err := d.source.ReadFull(buf); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
d.length = l
|
||||
|
||||
if _, err := d.source.Seek(pos, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const invalidLength = -1
|
||||
|
||||
// Length returns the total size in bytes.
|
||||
//
|
||||
// Length returns -1 when the total size is not available
|
||||
// e.g. when the given source is not io.Seeker.
|
||||
func (d *Decoder) Length() int64 {
|
||||
return d.length
|
||||
}
|
||||
|
||||
// NewDecoder decodes the given io.ReadCloser and returns a decoded stream.
|
||||
//
|
||||
// The stream is always formatted as 16bit (little endian) 2 channels
|
||||
// even if the source is single channel MP3.
|
||||
// Thus, a sample always consists of 4 bytes.
|
||||
func NewDecoder(r io.ReadCloser) (*Decoder, error) {
|
||||
s := &source{
|
||||
reader: r,
|
||||
}
|
||||
d := &Decoder{
|
||||
source: s,
|
||||
length: invalidLength,
|
||||
}
|
||||
|
||||
if err := s.skipTags(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: Is readFrame here really needed?
|
||||
if err := d.readFrame(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.sampleRate = d.frame.SamplingFrequency()
|
||||
|
||||
if err := d.ensureFrameStartsAndLength(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
module github.com/hajimehoshi/go-mp3
|
||||
|
||||
require github.com/hajimehoshi/oto v0.1.1
|
@ -1,6 +0,0 @@
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f h1:FDM3EtwZLyhW48YRiyqjivNlNZjAObv4xt4NnJaU+NQ=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherwasm v0.1.1 h1:R/3+SfgCFStiql6ICfyfke1WtpglfjIvTEBux8R1euc=
|
||||
github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m256AsaDPQ+/4=
|
||||
github.com/hajimehoshi/oto v0.1.1 h1:EG+WxxeAfde1mI0adhLYvGbKgDCxm7bCTd6g+JIA6vI=
|
||||
github.com/hajimehoshi/oto v0.1.1/go.mod h1:hUiLWeBQnbDu4pZsAhOnGqMI1ZGibS6e2qhQdfpwz04=
|
@ -1,78 +0,0 @@
|
||||
// Copyright 2017 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package bits
|
||||
|
||||
type Bits struct {
|
||||
vec []byte
|
||||
bitPos int
|
||||
bytePos int
|
||||
}
|
||||
|
||||
func New(vec []byte) *Bits {
|
||||
return &Bits{
|
||||
vec: vec,
|
||||
}
|
||||
}
|
||||
|
||||
func Append(bits *Bits, buf []byte) *Bits {
|
||||
return New(append(bits.vec, buf...))
|
||||
}
|
||||
|
||||
func (b *Bits) Bit() int {
|
||||
if len(b.vec) <= b.bytePos {
|
||||
// TODO: Should this return error?
|
||||
return 0
|
||||
}
|
||||
tmp := uint(b.vec[b.bytePos]) >> (7 - uint(b.bitPos))
|
||||
tmp &= 0x01
|
||||
b.bytePos += (b.bitPos + 1) >> 3
|
||||
b.bitPos = (b.bitPos + 1) & 0x07
|
||||
return int(tmp)
|
||||
}
|
||||
|
||||
func (b *Bits) Bits(num int) int {
|
||||
if num == 0 {
|
||||
return 0
|
||||
}
|
||||
if len(b.vec) <= b.bytePos {
|
||||
// TODO: Should this return error?
|
||||
return 0
|
||||
}
|
||||
bb := make([]byte, 4)
|
||||
copy(bb, b.vec[b.bytePos:])
|
||||
tmp := (uint32(bb[0]) << 24) | (uint32(bb[1]) << 16) | (uint32(bb[2]) << 8) | (uint32(bb[3]))
|
||||
tmp <<= uint(b.bitPos)
|
||||
tmp >>= (32 - uint(num))
|
||||
b.bytePos += (b.bitPos + num) >> 3
|
||||
b.bitPos = (b.bitPos + num) & 0x07
|
||||
return int(tmp)
|
||||
}
|
||||
|
||||
func (b *Bits) BitPos() int {
|
||||
return b.bytePos<<3 + b.bitPos
|
||||
}
|
||||
|
||||
func (b *Bits) SetPos(pos int) {
|
||||
b.bytePos = pos >> 3
|
||||
b.bitPos = pos & 0x7
|
||||
}
|
||||
|
||||
func (b *Bits) LenInBytes() int {
|
||||
return len(b.vec)
|
||||
}
|
||||
|
||||
func (b *Bits) Tail(offset int) []byte {
|
||||
return b.vec[len(b.vec)-offset:]
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
// Copyright 2017 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package consts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type UnexpectedEOF struct {
|
||||
At string
|
||||
}
|
||||
|
||||
func (u *UnexpectedEOF) Error() string {
|
||||
return fmt.Sprintf("mp3: unexpected EOF at %s", u.At)
|
||||
}
|
||||
|
||||
type Version int
|
||||
|
||||
const (
|
||||
Version2_5 Version = 0
|
||||
VersionReserved Version = 1
|
||||
Version2 Version = 2
|
||||
Version1 Version = 3
|
||||
)
|
||||
|
||||
type Layer int
|
||||
|
||||
const (
|
||||
LayerReserved Layer = 0
|
||||
Layer3 Layer = 1
|
||||
Layer2 Layer = 2
|
||||
Layer1 Layer = 3
|
||||
)
|
||||
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
ModeStereo Mode = 0
|
||||
ModeJointStereo Mode = 1
|
||||
ModeDualChannel Mode = 2
|
||||
ModeSingleChannel Mode = 3
|
||||
)
|
||||
|
||||
const (
|
||||
SamplesPerGr = 576
|
||||
BytesPerFrame = SamplesPerGr * 2 * 4
|
||||
)
|
||||
|
||||
type SamplingFrequency int
|
||||
|
||||
const (
|
||||
SamplingFrequency44100 SamplingFrequency = 0
|
||||
SamplingFrequency48000 SamplingFrequency = 1
|
||||
SamplingFrequency32000 SamplingFrequency = 2
|
||||
SamplingFrequencyReserved SamplingFrequency = 3
|
||||
)
|
||||
|
||||
func (s SamplingFrequency) Int() int {
|
||||
switch s {
|
||||
case SamplingFrequency44100:
|
||||
return 44100
|
||||
case SamplingFrequency48000:
|
||||
return 48000
|
||||
case SamplingFrequency32000:
|
||||
return 32000
|
||||
}
|
||||
panic("not reahed")
|
||||
}
|
||||
|
||||
type SfBandIndices struct {
|
||||
L []int
|
||||
S []int
|
||||
}
|
||||
|
||||
var (
|
||||
SfBandIndicesSet = map[SamplingFrequency]SfBandIndices{
|
||||
SamplingFrequency44100: {
|
||||
L: []int{0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 52, 62, 74, 90, 110, 134, 162, 196, 238, 288, 342, 418, 576},
|
||||
S: []int{0, 4, 8, 12, 16, 22, 30, 40, 52, 66, 84, 106, 136, 192},
|
||||
},
|
||||
SamplingFrequency48000: {
|
||||
L: []int{0, 4, 8, 12, 16, 20, 24, 30, 36, 42, 50, 60, 72, 88, 106, 128, 156, 190, 230, 276, 330, 384, 576},
|
||||
S: []int{0, 4, 8, 12, 16, 22, 28, 38, 50, 64, 80, 100, 126, 192},
|
||||
},
|
||||
SamplingFrequency32000: {
|
||||
L: []int{0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 54, 66, 82, 102, 126, 156, 194, 240, 296, 364, 448, 550, 576},
|
||||
S: []int{0, 4, 8, 12, 16, 22, 30, 42, 58, 78, 104, 138, 180, 192},
|
||||
},
|
||||
}
|
||||
)
|
@ -1,676 +0,0 @@
|
||||
// Copyright 2017 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package frame
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3/internal/bits"
|
||||
"github.com/hajimehoshi/go-mp3/internal/consts"
|
||||
"github.com/hajimehoshi/go-mp3/internal/frameheader"
|
||||
"github.com/hajimehoshi/go-mp3/internal/imdct"
|
||||
"github.com/hajimehoshi/go-mp3/internal/maindata"
|
||||
"github.com/hajimehoshi/go-mp3/internal/sideinfo"
|
||||
)
|
||||
|
||||
var (
|
||||
powtab34 = make([]float64, 8207)
|
||||
pretab = []float64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 2, 0}
|
||||
)
|
||||
|
||||
func init() {
|
||||
for i := range powtab34 {
|
||||
powtab34[i] = math.Pow(float64(i), 4.0/3.0)
|
||||
}
|
||||
}
|
||||
|
||||
type Frame struct {
|
||||
header frameheader.FrameHeader
|
||||
sideInfo *sideinfo.SideInfo
|
||||
mainData *maindata.MainData
|
||||
|
||||
mainDataBits *bits.Bits
|
||||
store [2][32][18]float32
|
||||
v_vec [2][1024]float32
|
||||
}
|
||||
|
||||
type FullReader interface {
|
||||
ReadFull([]byte) (int, error)
|
||||
}
|
||||
|
||||
func readCRC(source FullReader) error {
|
||||
buf := make([]byte, 2)
|
||||
if n, err := source.ReadFull(buf); n < 2 {
|
||||
if err == io.EOF {
|
||||
return &consts.UnexpectedEOF{"readCRC"}
|
||||
}
|
||||
return fmt.Errorf("mp3: error at readCRC: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Read(source FullReader, position int64, prev *Frame) (frame *Frame, startPosition int64, err error) {
|
||||
h, pos, err := frameheader.Read(source, position)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if h.ProtectionBit() == 0 {
|
||||
if err := readCRC(source); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if h.ID() != consts.Version1 {
|
||||
return nil, 0, fmt.Errorf("mp3: only MPEG version 1 (want %d; got %d) is supported", consts.Version1, h.ID())
|
||||
}
|
||||
if h.Layer() != consts.Layer3 {
|
||||
return nil, 0, fmt.Errorf("mp3: only layer3 (want %d; got %d) is supported", consts.Layer3, h.Layer())
|
||||
}
|
||||
|
||||
si, err := sideinfo.Read(source, h)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// If there's not enough main data in the bit reservoir,
|
||||
// signal to calling function so that decoding isn't done!
|
||||
// Get main data (scalefactors and Huffman coded frequency data)
|
||||
var prevM *bits.Bits
|
||||
if prev != nil {
|
||||
prevM = prev.mainDataBits
|
||||
}
|
||||
md, mdb, err := maindata.Read(source, prevM, h, si)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
nf := &Frame{
|
||||
header: h,
|
||||
sideInfo: si,
|
||||
mainData: md,
|
||||
mainDataBits: mdb,
|
||||
}
|
||||
if prev != nil {
|
||||
nf.store = prev.store
|
||||
nf.v_vec = prev.v_vec
|
||||
}
|
||||
return nf, pos, nil
|
||||
}
|
||||
|
||||
func (f *Frame) SamplingFrequency() int {
|
||||
return f.header.SamplingFrequency().Int()
|
||||
}
|
||||
|
||||
func (f *Frame) Decode() []byte {
|
||||
out := make([]byte, consts.BytesPerFrame)
|
||||
nch := f.header.NumberOfChannels()
|
||||
for gr := 0; gr < 2; gr++ {
|
||||
for ch := 0; ch < nch; ch++ {
|
||||
f.requantize(gr, ch)
|
||||
f.reorder(gr, ch)
|
||||
}
|
||||
f.stereo(gr)
|
||||
for ch := 0; ch < nch; ch++ {
|
||||
f.antialias(gr, ch)
|
||||
f.hybridSynthesis(gr, ch)
|
||||
f.frequencyInversion(gr, ch)
|
||||
f.subbandSynthesis(gr, ch, out[consts.SamplesPerGr*4*gr:])
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (f *Frame) requantizeProcessLong(gr, ch, is_pos, sfb int) {
|
||||
sf_mult := 0.5
|
||||
if f.sideInfo.ScalefacScale[gr][ch] != 0 {
|
||||
sf_mult = 1.0
|
||||
}
|
||||
pf_x_pt := float64(f.sideInfo.Preflag[gr][ch]) * pretab[sfb]
|
||||
idx := -(sf_mult * (float64(f.mainData.ScalefacL[gr][ch][sfb]) + pf_x_pt)) +
|
||||
0.25*(float64(f.sideInfo.GlobalGain[gr][ch])-210)
|
||||
tmp1 := math.Pow(2.0, idx)
|
||||
tmp2 := 0.0
|
||||
if f.mainData.Is[gr][ch][is_pos] < 0.0 {
|
||||
tmp2 = -powtab34[int(-f.mainData.Is[gr][ch][is_pos])]
|
||||
} else {
|
||||
tmp2 = powtab34[int(f.mainData.Is[gr][ch][is_pos])]
|
||||
}
|
||||
f.mainData.Is[gr][ch][is_pos] = float32(tmp1 * tmp2)
|
||||
}
|
||||
|
||||
func (f *Frame) requantizeProcessShort(gr, ch, is_pos, sfb, win int) {
|
||||
sf_mult := 0.5
|
||||
if f.sideInfo.ScalefacScale[gr][ch] != 0 {
|
||||
sf_mult = 1.0
|
||||
}
|
||||
idx := -(sf_mult * float64(f.mainData.ScalefacS[gr][ch][sfb][win])) +
|
||||
0.25*(float64(f.sideInfo.GlobalGain[gr][ch])-210.0-
|
||||
8.0*float64(f.sideInfo.SubblockGain[gr][ch][win]))
|
||||
tmp1 := math.Pow(2.0, idx)
|
||||
tmp2 := 0.0
|
||||
if f.mainData.Is[gr][ch][is_pos] < 0 {
|
||||
tmp2 = -powtab34[int(-f.mainData.Is[gr][ch][is_pos])]
|
||||
} else {
|
||||
tmp2 = powtab34[int(f.mainData.Is[gr][ch][is_pos])]
|
||||
}
|
||||
f.mainData.Is[gr][ch][is_pos] = float32(tmp1 * tmp2)
|
||||
}
|
||||
|
||||
func (f *Frame) requantize(gr int, ch int) {
|
||||
// Setup sampling frequency index
|
||||
sfreq := f.header.SamplingFrequency()
|
||||
// Determine type of block to process
|
||||
if f.sideInfo.WinSwitchFlag[gr][ch] == 1 && f.sideInfo.BlockType[gr][ch] == 2 { // Short blocks
|
||||
// Check if the first two subbands
|
||||
// (=2*18 samples = 8 long or 3 short sfb's) uses long blocks
|
||||
if f.sideInfo.MixedBlockFlag[gr][ch] != 0 { // 2 longbl. sb first
|
||||
// First process the 2 long block subbands at the start
|
||||
sfb := 0
|
||||
next_sfb := consts.SfBandIndicesSet[sfreq].L[sfb+1]
|
||||
for i := 0; i < 36; i++ {
|
||||
if i == next_sfb {
|
||||
sfb++
|
||||
next_sfb = consts.SfBandIndicesSet[sfreq].L[sfb+1]
|
||||
}
|
||||
f.requantizeProcessLong(gr, ch, i, sfb)
|
||||
}
|
||||
// And next the remaining,non-zero,bands which uses short blocks
|
||||
sfb = 3
|
||||
next_sfb = consts.SfBandIndicesSet[sfreq].S[sfb+1] * 3
|
||||
win_len := consts.SfBandIndicesSet[sfreq].S[sfb+1] -
|
||||
consts.SfBandIndicesSet[sfreq].S[sfb]
|
||||
|
||||
for i := 36; i < int(f.sideInfo.Count1[gr][ch]); /* i++ done below! */ {
|
||||
// Check if we're into the next scalefac band
|
||||
if i == next_sfb {
|
||||
sfb++
|
||||
next_sfb = consts.SfBandIndicesSet[sfreq].S[sfb+1] * 3
|
||||
win_len = consts.SfBandIndicesSet[sfreq].S[sfb+1] -
|
||||
consts.SfBandIndicesSet[sfreq].S[sfb]
|
||||
}
|
||||
for win := 0; win < 3; win++ {
|
||||
for j := 0; j < win_len; j++ {
|
||||
f.requantizeProcessShort(gr, ch, i, sfb, win)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else { // Only short blocks
|
||||
sfb := 0
|
||||
next_sfb := consts.SfBandIndicesSet[sfreq].S[sfb+1] * 3
|
||||
win_len := consts.SfBandIndicesSet[sfreq].S[sfb+1] -
|
||||
consts.SfBandIndicesSet[sfreq].S[sfb]
|
||||
for i := 0; i < int(f.sideInfo.Count1[gr][ch]); /* i++ done below! */ {
|
||||
// Check if we're into the next scalefac band
|
||||
if i == next_sfb {
|
||||
sfb++
|
||||
next_sfb = consts.SfBandIndicesSet[sfreq].S[sfb+1] * 3
|
||||
win_len = consts.SfBandIndicesSet[sfreq].S[sfb+1] -
|
||||
consts.SfBandIndicesSet[sfreq].S[sfb]
|
||||
}
|
||||
for win := 0; win < 3; win++ {
|
||||
for j := 0; j < win_len; j++ {
|
||||
f.requantizeProcessShort(gr, ch, i, sfb, win)
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // Only long blocks
|
||||
sfb := 0
|
||||
next_sfb := consts.SfBandIndicesSet[sfreq].L[sfb+1]
|
||||
for i := 0; i < int(f.sideInfo.Count1[gr][ch]); i++ {
|
||||
if i == next_sfb {
|
||||
sfb++
|
||||
next_sfb = consts.SfBandIndicesSet[sfreq].L[sfb+1]
|
||||
}
|
||||
f.requantizeProcessLong(gr, ch, i, sfb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frame) reorder(gr int, ch int) {
|
||||
re := make([]float32, consts.SamplesPerGr)
|
||||
|
||||
sfreq := f.header.SamplingFrequency() // Setup sampling freq index
|
||||
// Only reorder short blocks
|
||||
if (f.sideInfo.WinSwitchFlag[gr][ch] == 1) && (f.sideInfo.BlockType[gr][ch] == 2) { // Short blocks
|
||||
// Check if the first two subbands
|
||||
// (=2*18 samples = 8 long or 3 short sfb's) uses long blocks
|
||||
sfb := 0
|
||||
// 2 longbl. sb first
|
||||
if f.sideInfo.MixedBlockFlag[gr][ch] != 0 {
|
||||
sfb = 3
|
||||
}
|
||||
next_sfb := consts.SfBandIndicesSet[sfreq].S[sfb+1] * 3
|
||||
win_len := consts.SfBandIndicesSet[sfreq].S[sfb+1] - consts.SfBandIndicesSet[sfreq].S[sfb]
|
||||
i := 36
|
||||
if sfb == 0 {
|
||||
i = 0
|
||||
}
|
||||
for i < consts.SamplesPerGr {
|
||||
// Check if we're into the next scalefac band
|
||||
if i == next_sfb {
|
||||
// Copy reordered data back to the original vector
|
||||
j := 3 * consts.SfBandIndicesSet[sfreq].S[sfb]
|
||||
copy(f.mainData.Is[gr][ch][j:j+3*win_len], re[0:3*win_len])
|
||||
// Check if this band is above the rzero region,if so we're done
|
||||
if i >= f.sideInfo.Count1[gr][ch] {
|
||||
return
|
||||
}
|
||||
sfb++
|
||||
next_sfb = consts.SfBandIndicesSet[sfreq].S[sfb+1] * 3
|
||||
win_len = consts.SfBandIndicesSet[sfreq].S[sfb+1] - consts.SfBandIndicesSet[sfreq].S[sfb]
|
||||
}
|
||||
for win := 0; win < 3; win++ { // Do the actual reordering
|
||||
for j := 0; j < win_len; j++ {
|
||||
re[j*3+win] = f.mainData.Is[gr][ch][i]
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copy reordered data of last band back to original vector
|
||||
j := 3 * consts.SfBandIndicesSet[sfreq].S[12]
|
||||
copy(f.mainData.Is[gr][ch][j:j+3*win_len], re[0:3*win_len])
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
isRatios = []float32{0.000000, 0.267949, 0.577350, 1.000000, 1.732051, 3.732051}
|
||||
)
|
||||
|
||||
func (f *Frame) stereoProcessIntensityLong(gr int, sfb int) {
|
||||
is_ratio_l := float32(0)
|
||||
is_ratio_r := float32(0)
|
||||
// Check that((is_pos[sfb]=scalefac) < 7) => no intensity stereo
|
||||
if is_pos := f.mainData.ScalefacL[gr][0][sfb]; is_pos < 7 {
|
||||
sfreq := f.header.SamplingFrequency() // Setup sampling freq index
|
||||
sfb_start := consts.SfBandIndicesSet[sfreq].L[sfb]
|
||||
sfb_stop := consts.SfBandIndicesSet[sfreq].L[sfb+1]
|
||||
if is_pos == 6 { // tan((6*PI)/12 = PI/2) needs special treatment!
|
||||
is_ratio_l = 1.0
|
||||
is_ratio_r = 0.0
|
||||
} else {
|
||||
is_ratio_l = isRatios[is_pos] / (1.0 + isRatios[is_pos])
|
||||
is_ratio_r = 1.0 / (1.0 + isRatios[is_pos])
|
||||
}
|
||||
// Now decode all samples in this scale factor band
|
||||
for i := sfb_start; i < sfb_stop; i++ {
|
||||
f.mainData.Is[gr][0][i] *= is_ratio_l
|
||||
f.mainData.Is[gr][1][i] *= is_ratio_r
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frame) stereoProcessIntensityShort(gr int, sfb int) {
|
||||
is_ratio_l := float32(0)
|
||||
is_ratio_r := float32(0)
|
||||
sfreq := f.header.SamplingFrequency() // Setup sampling freq index
|
||||
// The window length
|
||||
win_len := consts.SfBandIndicesSet[sfreq].S[sfb+1] - consts.SfBandIndicesSet[sfreq].S[sfb]
|
||||
// The three windows within the band has different scalefactors
|
||||
for win := 0; win < 3; win++ {
|
||||
// Check that((is_pos[sfb]=scalefac) < 7) => no intensity stereo
|
||||
is_pos := f.mainData.ScalefacS[gr][0][sfb][win]
|
||||
if is_pos < 7 {
|
||||
sfb_start := consts.SfBandIndicesSet[sfreq].S[sfb]*3 + win_len*win
|
||||
sfb_stop := sfb_start + win_len
|
||||
if is_pos == 6 { // tan((6*PI)/12 = PI/2) needs special treatment!
|
||||
is_ratio_l = 1.0
|
||||
is_ratio_r = 0.0
|
||||
} else {
|
||||
is_ratio_l = isRatios[is_pos] / (1.0 + isRatios[is_pos])
|
||||
is_ratio_r = 1.0 / (1.0 + isRatios[is_pos])
|
||||
}
|
||||
// Now decode all samples in this scale factor band
|
||||
for i := sfb_start; i < sfb_stop; i++ {
|
||||
// https://github.com/technosaurus/PDMP3/issues/3
|
||||
f.mainData.Is[gr][0][i] *= is_ratio_l
|
||||
f.mainData.Is[gr][1][i] *= is_ratio_r
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frame) stereo(gr int) {
|
||||
if f.header.UseMSStereo() {
|
||||
// Determine how many frequency lines to transform
|
||||
i := 1
|
||||
if f.sideInfo.Count1[gr][0] > f.sideInfo.Count1[gr][1] {
|
||||
i = 0
|
||||
}
|
||||
max_pos := int(f.sideInfo.Count1[gr][i])
|
||||
// Do the actual processing
|
||||
const invSqrt2 = math.Sqrt2 / 2
|
||||
for i := 0; i < max_pos; i++ {
|
||||
left := (f.mainData.Is[gr][0][i] + f.mainData.Is[gr][1][i]) * invSqrt2
|
||||
right := (f.mainData.Is[gr][0][i] - f.mainData.Is[gr][1][i]) * invSqrt2
|
||||
f.mainData.Is[gr][0][i] = left
|
||||
f.mainData.Is[gr][1][i] = right
|
||||
}
|
||||
}
|
||||
|
||||
if f.header.UseIntensityStereo() {
|
||||
// Setup sampling frequency index
|
||||
sfreq := f.header.SamplingFrequency()
|
||||
|
||||
// First band that is intensity stereo encoded is first band scale factor
|
||||
// band on or above count1 frequency line. N.B.: Intensity stereo coding is
|
||||
// only done for higher subbands, but logic is here for lower subbands.
|
||||
// Determine type of block to process
|
||||
if (f.sideInfo.WinSwitchFlag[gr][0] == 1) &&
|
||||
(f.sideInfo.BlockType[gr][0] == 2) { // Short blocks
|
||||
// Check if the first two subbands
|
||||
// (=2*18 samples = 8 long or 3 short sfb's) uses long blocks
|
||||
if f.sideInfo.MixedBlockFlag[gr][0] != 0 { // 2 longbl. sb first
|
||||
for sfb := 0; sfb < 8; sfb++ { // First process 8 sfb's at start
|
||||
// Is this scale factor band above count1 for the right channel?
|
||||
if consts.SfBandIndicesSet[sfreq].L[sfb] >= f.sideInfo.Count1[gr][1] {
|
||||
f.stereoProcessIntensityLong(gr, sfb)
|
||||
}
|
||||
}
|
||||
// And next the remaining bands which uses short blocks
|
||||
for sfb := 3; sfb < 12; sfb++ {
|
||||
// Is this scale factor band above count1 for the right channel?
|
||||
if consts.SfBandIndicesSet[sfreq].S[sfb]*3 >= f.sideInfo.Count1[gr][1] {
|
||||
f.stereoProcessIntensityShort(gr, sfb)
|
||||
}
|
||||
}
|
||||
} else { // Only short blocks
|
||||
for sfb := 0; sfb < 12; sfb++ {
|
||||
// Is this scale factor band above count1 for the right channel?
|
||||
if consts.SfBandIndicesSet[sfreq].S[sfb]*3 >= f.sideInfo.Count1[gr][1] {
|
||||
f.stereoProcessIntensityShort(gr, sfb)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // Only long blocks
|
||||
for sfb := 0; sfb < 21; sfb++ {
|
||||
// Is this scale factor band above count1 for the right channel?
|
||||
if consts.SfBandIndicesSet[sfreq].L[sfb] >= f.sideInfo.Count1[gr][1] {
|
||||
f.stereoProcessIntensityLong(gr, sfb)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
cs = []float32{0.857493, 0.881742, 0.949629, 0.983315, 0.995518, 0.999161, 0.999899, 0.999993}
|
||||
ca = []float32{-0.514496, -0.471732, -0.313377, -0.181913, -0.094574, -0.040966, -0.014199, -0.003700}
|
||||
)
|
||||
|
||||
func (f *Frame) antialias(gr int, ch int) {
|
||||
// No antialiasing is done for short blocks
|
||||
if (f.sideInfo.WinSwitchFlag[gr][ch] == 1) &&
|
||||
(f.sideInfo.BlockType[gr][ch] == 2) &&
|
||||
(f.sideInfo.MixedBlockFlag[gr][ch]) == 0 {
|
||||
return
|
||||
}
|
||||
// Setup the limit for how many subbands to transform
|
||||
sblim := 32
|
||||
if (f.sideInfo.WinSwitchFlag[gr][ch] == 1) &&
|
||||
(f.sideInfo.BlockType[gr][ch] == 2) &&
|
||||
(f.sideInfo.MixedBlockFlag[gr][ch] == 1) {
|
||||
sblim = 2
|
||||
}
|
||||
// Do the actual antialiasing
|
||||
for sb := 1; sb < sblim; sb++ {
|
||||
for i := 0; i < 8; i++ {
|
||||
li := 18*sb - 1 - i
|
||||
ui := 18*sb + i
|
||||
lb := f.mainData.Is[gr][ch][li]*cs[i] - f.mainData.Is[gr][ch][ui]*ca[i]
|
||||
ub := f.mainData.Is[gr][ch][ui]*cs[i] + f.mainData.Is[gr][ch][li]*ca[i]
|
||||
f.mainData.Is[gr][ch][li] = lb
|
||||
f.mainData.Is[gr][ch][ui] = ub
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frame) hybridSynthesis(gr int, ch int) {
|
||||
// Loop through all 32 subbands
|
||||
for sb := 0; sb < 32; sb++ {
|
||||
// Determine blocktype for this subband
|
||||
bt := int(f.sideInfo.BlockType[gr][ch])
|
||||
if (f.sideInfo.WinSwitchFlag[gr][ch] == 1) &&
|
||||
(f.sideInfo.MixedBlockFlag[gr][ch] == 1) && (sb < 2) {
|
||||
bt = 0
|
||||
}
|
||||
// Do the inverse modified DCT and windowing
|
||||
in := make([]float32, 18)
|
||||
for i := range in {
|
||||
in[i] = f.mainData.Is[gr][ch][sb*18+i]
|
||||
}
|
||||
rawout := imdct.Win(in, bt)
|
||||
// Overlapp add with stored vector into main_data vector
|
||||
for i := 0; i < 18; i++ {
|
||||
f.mainData.Is[gr][ch][sb*18+i] = rawout[i] + f.store[ch][sb][i]
|
||||
f.store[ch][sb][i] = rawout[i+18]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frame) frequencyInversion(gr int, ch int) {
|
||||
for sb := 1; sb < 32; sb += 2 {
|
||||
for i := 1; i < 18; i += 2 {
|
||||
f.mainData.Is[gr][ch][sb*18+i] = -f.mainData.Is[gr][ch][sb*18+i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var synthNWin = [64][32]float32{}
|
||||
|
||||
func init() {
|
||||
for i := 0; i < 64; i++ {
|
||||
for j := 0; j < 32; j++ {
|
||||
synthNWin[i][j] =
|
||||
float32(math.Cos(float64((16+i)*(2*j+1)) * (math.Pi / 64.0)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var synthDtbl = [512]float32{
|
||||
0.000000000, -0.000015259, -0.000015259, -0.000015259,
|
||||
-0.000015259, -0.000015259, -0.000015259, -0.000030518,
|
||||
-0.000030518, -0.000030518, -0.000030518, -0.000045776,
|
||||
-0.000045776, -0.000061035, -0.000061035, -0.000076294,
|
||||
-0.000076294, -0.000091553, -0.000106812, -0.000106812,
|
||||
-0.000122070, -0.000137329, -0.000152588, -0.000167847,
|
||||
-0.000198364, -0.000213623, -0.000244141, -0.000259399,
|
||||
-0.000289917, -0.000320435, -0.000366211, -0.000396729,
|
||||
-0.000442505, -0.000473022, -0.000534058, -0.000579834,
|
||||
-0.000625610, -0.000686646, -0.000747681, -0.000808716,
|
||||
-0.000885010, -0.000961304, -0.001037598, -0.001113892,
|
||||
-0.001205444, -0.001296997, -0.001388550, -0.001480103,
|
||||
-0.001586914, -0.001693726, -0.001785278, -0.001907349,
|
||||
-0.002014160, -0.002120972, -0.002243042, -0.002349854,
|
||||
-0.002456665, -0.002578735, -0.002685547, -0.002792358,
|
||||
-0.002899170, -0.002990723, -0.003082275, -0.003173828,
|
||||
0.003250122, 0.003326416, 0.003387451, 0.003433228,
|
||||
0.003463745, 0.003479004, 0.003479004, 0.003463745,
|
||||
0.003417969, 0.003372192, 0.003280640, 0.003173828,
|
||||
0.003051758, 0.002883911, 0.002700806, 0.002487183,
|
||||
0.002227783, 0.001937866, 0.001617432, 0.001266479,
|
||||
0.000869751, 0.000442505, -0.000030518, -0.000549316,
|
||||
-0.001098633, -0.001693726, -0.002334595, -0.003005981,
|
||||
-0.003723145, -0.004486084, -0.005294800, -0.006118774,
|
||||
-0.007003784, -0.007919312, -0.008865356, -0.009841919,
|
||||
-0.010848999, -0.011886597, -0.012939453, -0.014022827,
|
||||
-0.015121460, -0.016235352, -0.017349243, -0.018463135,
|
||||
-0.019577026, -0.020690918, -0.021789551, -0.022857666,
|
||||
-0.023910522, -0.024932861, -0.025909424, -0.026840210,
|
||||
-0.027725220, -0.028533936, -0.029281616, -0.029937744,
|
||||
-0.030532837, -0.031005859, -0.031387329, -0.031661987,
|
||||
-0.031814575, -0.031845093, -0.031738281, -0.031478882,
|
||||
0.031082153, 0.030517578, 0.029785156, 0.028884888,
|
||||
0.027801514, 0.026535034, 0.025085449, 0.023422241,
|
||||
0.021575928, 0.019531250, 0.017257690, 0.014801025,
|
||||
0.012115479, 0.009231567, 0.006134033, 0.002822876,
|
||||
-0.000686646, -0.004394531, -0.008316040, -0.012420654,
|
||||
-0.016708374, -0.021179199, -0.025817871, -0.030609131,
|
||||
-0.035552979, -0.040634155, -0.045837402, -0.051132202,
|
||||
-0.056533813, -0.061996460, -0.067520142, -0.073059082,
|
||||
-0.078628540, -0.084182739, -0.089706421, -0.095169067,
|
||||
-0.100540161, -0.105819702, -0.110946655, -0.115921021,
|
||||
-0.120697021, -0.125259399, -0.129562378, -0.133590698,
|
||||
-0.137298584, -0.140670776, -0.143676758, -0.146255493,
|
||||
-0.148422241, -0.150115967, -0.151306152, -0.151962280,
|
||||
-0.152069092, -0.151596069, -0.150497437, -0.148773193,
|
||||
-0.146362305, -0.143264771, -0.139450073, -0.134887695,
|
||||
-0.129577637, -0.123474121, -0.116577148, -0.108856201,
|
||||
0.100311279, 0.090927124, 0.080688477, 0.069595337,
|
||||
0.057617188, 0.044784546, 0.031082153, 0.016510010,
|
||||
0.001068115, -0.015228271, -0.032379150, -0.050354004,
|
||||
-0.069168091, -0.088775635, -0.109161377, -0.130310059,
|
||||
-0.152206421, -0.174789429, -0.198059082, -0.221984863,
|
||||
-0.246505737, -0.271591187, -0.297210693, -0.323318481,
|
||||
-0.349868774, -0.376800537, -0.404083252, -0.431655884,
|
||||
-0.459472656, -0.487472534, -0.515609741, -0.543823242,
|
||||
-0.572036743, -0.600219727, -0.628295898, -0.656219482,
|
||||
-0.683914185, -0.711318970, -0.738372803, -0.765029907,
|
||||
-0.791213989, -0.816864014, -0.841949463, -0.866363525,
|
||||
-0.890090942, -0.913055420, -0.935195923, -0.956481934,
|
||||
-0.976852417, -0.996246338, -1.014617920, -1.031936646,
|
||||
-1.048156738, -1.063217163, -1.077117920, -1.089782715,
|
||||
-1.101211548, -1.111373901, -1.120223999, -1.127746582,
|
||||
-1.133926392, -1.138763428, -1.142211914, -1.144287109,
|
||||
1.144989014, 1.144287109, 1.142211914, 1.138763428,
|
||||
1.133926392, 1.127746582, 1.120223999, 1.111373901,
|
||||
1.101211548, 1.089782715, 1.077117920, 1.063217163,
|
||||
1.048156738, 1.031936646, 1.014617920, 0.996246338,
|
||||
0.976852417, 0.956481934, 0.935195923, 0.913055420,
|
||||
0.890090942, 0.866363525, 0.841949463, 0.816864014,
|
||||
0.791213989, 0.765029907, 0.738372803, 0.711318970,
|
||||
0.683914185, 0.656219482, 0.628295898, 0.600219727,
|
||||
0.572036743, 0.543823242, 0.515609741, 0.487472534,
|
||||
0.459472656, 0.431655884, 0.404083252, 0.376800537,
|
||||
0.349868774, 0.323318481, 0.297210693, 0.271591187,
|
||||
0.246505737, 0.221984863, 0.198059082, 0.174789429,
|
||||
0.152206421, 0.130310059, 0.109161377, 0.088775635,
|
||||
0.069168091, 0.050354004, 0.032379150, 0.015228271,
|
||||
-0.001068115, -0.016510010, -0.031082153, -0.044784546,
|
||||
-0.057617188, -0.069595337, -0.080688477, -0.090927124,
|
||||
0.100311279, 0.108856201, 0.116577148, 0.123474121,
|
||||
0.129577637, 0.134887695, 0.139450073, 0.143264771,
|
||||
0.146362305, 0.148773193, 0.150497437, 0.151596069,
|
||||
0.152069092, 0.151962280, 0.151306152, 0.150115967,
|
||||
0.148422241, 0.146255493, 0.143676758, 0.140670776,
|
||||
0.137298584, 0.133590698, 0.129562378, 0.125259399,
|
||||
0.120697021, 0.115921021, 0.110946655, 0.105819702,
|
||||
0.100540161, 0.095169067, 0.089706421, 0.084182739,
|
||||
0.078628540, 0.073059082, 0.067520142, 0.061996460,
|
||||
0.056533813, 0.051132202, 0.045837402, 0.040634155,
|
||||
0.035552979, 0.030609131, 0.025817871, 0.021179199,
|
||||
0.016708374, 0.012420654, 0.008316040, 0.004394531,
|
||||
0.000686646, -0.002822876, -0.006134033, -0.009231567,
|
||||
-0.012115479, -0.014801025, -0.017257690, -0.019531250,
|
||||
-0.021575928, -0.023422241, -0.025085449, -0.026535034,
|
||||
-0.027801514, -0.028884888, -0.029785156, -0.030517578,
|
||||
0.031082153, 0.031478882, 0.031738281, 0.031845093,
|
||||
0.031814575, 0.031661987, 0.031387329, 0.031005859,
|
||||
0.030532837, 0.029937744, 0.029281616, 0.028533936,
|
||||
0.027725220, 0.026840210, 0.025909424, 0.024932861,
|
||||
0.023910522, 0.022857666, 0.021789551, 0.020690918,
|
||||
0.019577026, 0.018463135, 0.017349243, 0.016235352,
|
||||
0.015121460, 0.014022827, 0.012939453, 0.011886597,
|
||||
0.010848999, 0.009841919, 0.008865356, 0.007919312,
|
||||
0.007003784, 0.006118774, 0.005294800, 0.004486084,
|
||||
0.003723145, 0.003005981, 0.002334595, 0.001693726,
|
||||
0.001098633, 0.000549316, 0.000030518, -0.000442505,
|
||||
-0.000869751, -0.001266479, -0.001617432, -0.001937866,
|
||||
-0.002227783, -0.002487183, -0.002700806, -0.002883911,
|
||||
-0.003051758, -0.003173828, -0.003280640, -0.003372192,
|
||||
-0.003417969, -0.003463745, -0.003479004, -0.003479004,
|
||||
-0.003463745, -0.003433228, -0.003387451, -0.003326416,
|
||||
0.003250122, 0.003173828, 0.003082275, 0.002990723,
|
||||
0.002899170, 0.002792358, 0.002685547, 0.002578735,
|
||||
0.002456665, 0.002349854, 0.002243042, 0.002120972,
|
||||
0.002014160, 0.001907349, 0.001785278, 0.001693726,
|
||||
0.001586914, 0.001480103, 0.001388550, 0.001296997,
|
||||
0.001205444, 0.001113892, 0.001037598, 0.000961304,
|
||||
0.000885010, 0.000808716, 0.000747681, 0.000686646,
|
||||
0.000625610, 0.000579834, 0.000534058, 0.000473022,
|
||||
0.000442505, 0.000396729, 0.000366211, 0.000320435,
|
||||
0.000289917, 0.000259399, 0.000244141, 0.000213623,
|
||||
0.000198364, 0.000167847, 0.000152588, 0.000137329,
|
||||
0.000122070, 0.000106812, 0.000106812, 0.000091553,
|
||||
0.000076294, 0.000076294, 0.000061035, 0.000061035,
|
||||
0.000045776, 0.000045776, 0.000030518, 0.000030518,
|
||||
0.000030518, 0.000030518, 0.000015259, 0.000015259,
|
||||
0.000015259, 0.000015259, 0.000015259, 0.000015259,
|
||||
}
|
||||
|
||||
func (f *Frame) subbandSynthesis(gr int, ch int, out []byte) {
|
||||
u_vec := make([]float32, 512)
|
||||
s_vec := make([]float32, 32)
|
||||
|
||||
nch := f.header.NumberOfChannels()
|
||||
// Setup the n_win windowing vector and the v_vec intermediate vector
|
||||
for ss := 0; ss < 18; ss++ { // Loop through 18 samples in 32 subbands
|
||||
copy(f.v_vec[ch][64:1024], f.v_vec[ch][0:1024-64])
|
||||
d := f.mainData.Is[gr][ch]
|
||||
for i := 0; i < 32; i++ { // Copy next 32 time samples to a temp vector
|
||||
s_vec[i] = d[i*18+ss]
|
||||
}
|
||||
for i := 0; i < 64; i++ { // Matrix multiply input with n_win[][] matrix
|
||||
sum := float32(0)
|
||||
for j := 0; j < 32; j++ {
|
||||
sum += synthNWin[i][j] * s_vec[j]
|
||||
}
|
||||
f.v_vec[ch][i] = sum
|
||||
}
|
||||
v := f.v_vec[ch]
|
||||
for i := 0; i < 512; i += 64 { // Build the U vector
|
||||
copy(u_vec[i:i+32], v[(i<<1):(i<<1)+32])
|
||||
copy(u_vec[i+32:i+64], v[(i<<1)+96:(i<<1)+128])
|
||||
}
|
||||
for i := 0; i < 512; i++ { // Window by u_vec[i] with synthDtbl[i]
|
||||
u_vec[i] *= synthDtbl[i]
|
||||
}
|
||||
for i := 0; i < 32; i++ { // Calc 32 samples,store in outdata vector
|
||||
sum := float32(0)
|
||||
for j := 0; j < 512; j += 32 {
|
||||
sum += u_vec[j+i]
|
||||
}
|
||||
// sum now contains time sample 32*ss+i. Convert to 16-bit signed int
|
||||
samp := int(sum * 32767)
|
||||
if samp > 32767 {
|
||||
samp = 32767
|
||||
} else if samp < -32767 {
|
||||
samp = -32767
|
||||
}
|
||||
s := int16(samp)
|
||||
idx := 4 * (32*ss + i)
|
||||
if nch == 1 {
|
||||
// We always run in stereo mode and duplicate channels here for mono.
|
||||
out[idx] = byte(s)
|
||||
out[idx+1] = byte(s >> 8)
|
||||
out[idx+2] = byte(s)
|
||||
out[idx+3] = byte(s >> 8)
|
||||
continue
|
||||
}
|
||||
if ch == 0 {
|
||||
out[idx] = byte(s)
|
||||
out[idx+1] = byte(s >> 8)
|
||||
} else {
|
||||
out[idx+2] = byte(s)
|
||||
out[idx+3] = byte(s >> 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,206 +0,0 @@
|
||||
// Copyright 2017 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package frameheader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3/internal/consts"
|
||||
)
|
||||
|
||||
// A mepg1FrameHeader is MPEG1 Layer 1-3 frame header
|
||||
type FrameHeader uint32
|
||||
|
||||
// ID returns this header's ID stored in position 20,19
|
||||
func (f FrameHeader) ID() consts.Version {
|
||||
return consts.Version((f & 0x00180000) >> 19)
|
||||
}
|
||||
|
||||
// Layer returns the mpeg layer of this frame stored in position 18,17
|
||||
func (f FrameHeader) Layer() consts.Layer {
|
||||
return consts.Layer((f & 0x00060000) >> 17)
|
||||
}
|
||||
|
||||
// ProtectionBit returns the protection bit stored in position 16
|
||||
func (f FrameHeader) ProtectionBit() int {
|
||||
return int(f&0x00010000) >> 16
|
||||
}
|
||||
|
||||
// BirateIndex returns the bitrate index stored in position 15,12
|
||||
func (f FrameHeader) BitrateIndex() int {
|
||||
return int(f&0x0000f000) >> 12
|
||||
}
|
||||
|
||||
// SamplingFrequency returns the SamplingFrequency in Hz stored in position 11,10
|
||||
func (f FrameHeader) SamplingFrequency() consts.SamplingFrequency {
|
||||
return consts.SamplingFrequency(int(f&0x00000c00) >> 10)
|
||||
}
|
||||
|
||||
// PaddingBit returns the padding bit stored in position 9
|
||||
func (f FrameHeader) PaddingBit() int {
|
||||
return int(f&0x00000200) >> 9
|
||||
}
|
||||
|
||||
// PrivateBit returns the private bit stored in position 8 - this bit may be used to store arbitrary data to be used
|
||||
// by an application
|
||||
func (f FrameHeader) PrivateBit() int {
|
||||
return int(f&0x00000100) >> 8
|
||||
}
|
||||
|
||||
// Mode returns the channel mode, stored in position 7,6
|
||||
func (f FrameHeader) Mode() consts.Mode {
|
||||
return consts.Mode((f & 0x000000c0) >> 6)
|
||||
}
|
||||
|
||||
// modeExtension returns the mode_extension - for use with Joint Stereo - stored in position 4,5
|
||||
func (f FrameHeader) modeExtension() int {
|
||||
return int(f&0x00000030) >> 4
|
||||
}
|
||||
|
||||
// UseMSStereo returns a boolean value indicating whether the frame uses middle/side stereo.
|
||||
func (f FrameHeader) UseMSStereo() bool {
|
||||
if f.Mode() != consts.ModeJointStereo {
|
||||
return false
|
||||
}
|
||||
return f.modeExtension()&0x2 != 0
|
||||
}
|
||||
|
||||
// UseIntensityStereo returns a boolean value indicating whether the frame uses intensity stereo.
|
||||
func (f FrameHeader) UseIntensityStereo() bool {
|
||||
if f.Mode() != consts.ModeJointStereo {
|
||||
return false
|
||||
}
|
||||
return f.modeExtension()&0x1 != 0
|
||||
}
|
||||
|
||||
// Copyright returns whether or not this recording is copywritten - stored in position 3
|
||||
func (f FrameHeader) Copyright() int {
|
||||
return int(f&0x00000008) >> 3
|
||||
}
|
||||
|
||||
// OriginalOrCopy returns whether or not this is an Original recording or a copy of one - stored in position 2
|
||||
func (f FrameHeader) OriginalOrCopy() int {
|
||||
return int(f&0x00000004) >> 2
|
||||
}
|
||||
|
||||
// Emphasis returns emphasis - the emphasis indication is here to tell the decoder that the file must be de-emphasized - stored in position 0,1
|
||||
func (f FrameHeader) Emphasis() int {
|
||||
return int(f&0x00000003) >> 0
|
||||
}
|
||||
|
||||
// IsValid returns a boolean value indicating whether the header is valid or not.
|
||||
func (f FrameHeader) IsValid() bool {
|
||||
const sync = 0xffe00000
|
||||
if (f & sync) != sync {
|
||||
return false
|
||||
}
|
||||
if f.ID() == consts.VersionReserved {
|
||||
return false
|
||||
}
|
||||
if f.BitrateIndex() == 15 {
|
||||
return false
|
||||
}
|
||||
if f.SamplingFrequency() == consts.SamplingFrequencyReserved {
|
||||
return false
|
||||
}
|
||||
if f.Layer() == consts.LayerReserved {
|
||||
return false
|
||||
}
|
||||
if f.Emphasis() == 2 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func bitrate(layer consts.Layer, index int) int {
|
||||
switch layer {
|
||||
case consts.Layer1:
|
||||
return []int{
|
||||
0, 32000, 64000, 96000, 128000, 160000, 192000, 224000,
|
||||
256000, 288000, 320000, 352000, 384000, 416000, 448000}[index]
|
||||
case consts.Layer2:
|
||||
return []int{
|
||||
0, 32000, 48000, 56000, 64000, 80000, 96000, 112000,
|
||||
128000, 160000, 192000, 224000, 256000, 320000, 384000}[index]
|
||||
case consts.Layer3:
|
||||
return []int{
|
||||
0, 32000, 40000, 48000, 56000, 64000, 80000, 96000,
|
||||
112000, 128000, 160000, 192000, 224000, 256000, 320000}[index]
|
||||
}
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (f FrameHeader) FrameSize() int {
|
||||
return (144*bitrate(f.Layer(), f.BitrateIndex()))/
|
||||
f.SamplingFrequency().Int() +
|
||||
int(f.PaddingBit())
|
||||
}
|
||||
|
||||
func (f FrameHeader) NumberOfChannels() int {
|
||||
if f.Mode() == consts.ModeSingleChannel {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
}
|
||||
|
||||
type FullReader interface {
|
||||
ReadFull([]byte) (int, error)
|
||||
}
|
||||
|
||||
func Read(source FullReader, position int64) (h FrameHeader, startPosition int64, err error) {
|
||||
buf := make([]byte, 4)
|
||||
if n, err := source.ReadFull(buf); n < 4 {
|
||||
if err == io.EOF {
|
||||
if n == 0 {
|
||||
// Expected EOF
|
||||
return 0, 0, io.EOF
|
||||
}
|
||||
return 0, 0, &consts.UnexpectedEOF{"readHeader (1)"}
|
||||
}
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
b1 := uint32(buf[0])
|
||||
b2 := uint32(buf[1])
|
||||
b3 := uint32(buf[2])
|
||||
b4 := uint32(buf[3])
|
||||
header := FrameHeader((b1 << 24) | (b2 << 16) | (b3 << 8) | (b4 << 0))
|
||||
for !header.IsValid() {
|
||||
b1 = b2
|
||||
b2 = b3
|
||||
b3 = b4
|
||||
|
||||
buf := make([]byte, 1)
|
||||
if _, err := source.ReadFull(buf); err != nil {
|
||||
if err == io.EOF {
|
||||
return 0, 0, &consts.UnexpectedEOF{"readHeader (2)"}
|
||||
}
|
||||
return 0, 0, err
|
||||
}
|
||||
b4 = uint32(buf[0])
|
||||
header = FrameHeader((b1 << 24) | (b2 << 16) | (b3 << 8) | (b4 << 0))
|
||||
position++
|
||||
}
|
||||
|
||||
// If we get here we've found the sync word, and can decode the header
|
||||
// which is in the low 20 bits of the 32-bit sync+header word.
|
||||
|
||||
if header.BitrateIndex() == 0 {
|
||||
return 0, 0, fmt.Errorf("mp3: free bitrate format is not supported. Header word is 0x%08x at position %d",
|
||||
header, position)
|
||||
}
|
||||
return header, position, nil
|
||||
}
|
@ -1,419 +0,0 @@
|
||||
// Copyright 2017 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package huffman
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3/internal/bits"
|
||||
)
|
||||
|
||||
var huffmanTable = []uint16{
|
||||
// 1
|
||||
0x0201, 0x0000, 0x0201, 0x0010, 0x0201, 0x0001, 0x0011,
|
||||
// 2
|
||||
0x0201, 0x0000, 0x0401, 0x0201, 0x0010, 0x0001, 0x0201, 0x0011, 0x0401, 0x0201, 0x0020,
|
||||
0x0021, 0x0201, 0x0012, 0x0201, 0x0002, 0x0022,
|
||||
// 3
|
||||
0x0401, 0x0201, 0x0000, 0x0001, 0x0201, 0x0011, 0x0201, 0x0010, 0x0401, 0x0201, 0x0020,
|
||||
0x0021, 0x0201, 0x0012, 0x0201, 0x0002, 0x0022,
|
||||
// 5
|
||||
0x0201, 0x0000, 0x0401, 0x0201, 0x0010, 0x0001, 0x0201, 0x0011, 0x0801, 0x0401, 0x0201,
|
||||
0x0020, 0x0002, 0x0201, 0x0021, 0x0012, 0x0801, 0x0401, 0x0201, 0x0022, 0x0030, 0x0201,
|
||||
0x0003, 0x0013, 0x0201, 0x0031, 0x0201, 0x0032, 0x0201, 0x0023, 0x0033,
|
||||
// 6
|
||||
0x0601, 0x0401, 0x0201, 0x0000, 0x0010, 0x0011, 0x0601, 0x0201, 0x0001, 0x0201, 0x0020,
|
||||
0x0021, 0x0601, 0x0201, 0x0012, 0x0201, 0x0002, 0x0022, 0x0401, 0x0201, 0x0031, 0x0013,
|
||||
0x0401, 0x0201, 0x0030, 0x0032, 0x0201, 0x0023, 0x0201, 0x0003, 0x0033,
|
||||
// 7
|
||||
0x0201, 0x0000, 0x0401, 0x0201, 0x0010, 0x0001, 0x0801, 0x0201, 0x0011, 0x0401, 0x0201,
|
||||
0x0020, 0x0002, 0x0021, 0x1201, 0x0601, 0x0201, 0x0012, 0x0201, 0x0022, 0x0030, 0x0401,
|
||||
0x0201, 0x0031, 0x0013, 0x0401, 0x0201, 0x0003, 0x0032, 0x0201, 0x0023, 0x0004, 0x0a01,
|
||||
0x0401, 0x0201, 0x0040, 0x0041, 0x0201, 0x0014, 0x0201, 0x0042, 0x0024, 0x0c01, 0x0601,
|
||||
0x0401, 0x0201, 0x0033, 0x0043, 0x0050, 0x0401, 0x0201, 0x0034, 0x0005, 0x0051, 0x0601,
|
||||
0x0201, 0x0015, 0x0201, 0x0052, 0x0025, 0x0401, 0x0201, 0x0044, 0x0035, 0x0401, 0x0201,
|
||||
0x0053, 0x0054, 0x0201, 0x0045, 0x0055,
|
||||
// 8
|
||||
0x0601, 0x0201, 0x0000, 0x0201, 0x0010, 0x0001, 0x0201, 0x0011, 0x0401, 0x0201, 0x0021,
|
||||
0x0012, 0x0e01, 0x0401, 0x0201, 0x0020, 0x0002, 0x0201, 0x0022, 0x0401, 0x0201, 0x0030,
|
||||
0x0003, 0x0201, 0x0031, 0x0013, 0x0e01, 0x0801, 0x0401, 0x0201, 0x0032, 0x0023, 0x0201,
|
||||
0x0040, 0x0004, 0x0201, 0x0041, 0x0201, 0x0014, 0x0042, 0x0c01, 0x0601, 0x0201, 0x0024,
|
||||
0x0201, 0x0033, 0x0050, 0x0401, 0x0201, 0x0043, 0x0034, 0x0051, 0x0601, 0x0201, 0x0015,
|
||||
0x0201, 0x0005, 0x0052, 0x0601, 0x0201, 0x0025, 0x0201, 0x0044, 0x0035, 0x0201, 0x0053,
|
||||
0x0201, 0x0045, 0x0201, 0x0054, 0x0055,
|
||||
// 9
|
||||
0x0801, 0x0401, 0x0201, 0x0000, 0x0010, 0x0201, 0x0001, 0x0011, 0x0a01, 0x0401, 0x0201,
|
||||
0x0020, 0x0021, 0x0201, 0x0012, 0x0201, 0x0002, 0x0022, 0x0c01, 0x0601, 0x0401, 0x0201,
|
||||
0x0030, 0x0003, 0x0031, 0x0201, 0x0013, 0x0201, 0x0032, 0x0023, 0x0c01, 0x0401, 0x0201,
|
||||
0x0041, 0x0014, 0x0401, 0x0201, 0x0040, 0x0033, 0x0201, 0x0042, 0x0024, 0x0a01, 0x0601,
|
||||
0x0401, 0x0201, 0x0004, 0x0050, 0x0043, 0x0201, 0x0034, 0x0051, 0x0801, 0x0401, 0x0201,
|
||||
0x0015, 0x0052, 0x0201, 0x0025, 0x0044, 0x0601, 0x0401, 0x0201, 0x0005, 0x0054, 0x0053,
|
||||
0x0201, 0x0035, 0x0201, 0x0045, 0x0055,
|
||||
// 10
|
||||
0x0201, 0x0000, 0x0401, 0x0201, 0x0010, 0x0001, 0x0a01, 0x0201, 0x0011, 0x0401, 0x0201,
|
||||
0x0020, 0x0002, 0x0201, 0x0021, 0x0012, 0x1c01, 0x0801, 0x0401, 0x0201, 0x0022, 0x0030,
|
||||
0x0201, 0x0031, 0x0013, 0x0801, 0x0401, 0x0201, 0x0003, 0x0032, 0x0201, 0x0023, 0x0040,
|
||||
0x0401, 0x0201, 0x0041, 0x0014, 0x0401, 0x0201, 0x0004, 0x0033, 0x0201, 0x0042, 0x0024,
|
||||
0x1c01, 0x0a01, 0x0601, 0x0401, 0x0201, 0x0050, 0x0005, 0x0060, 0x0201, 0x0061, 0x0016,
|
||||
0x0c01, 0x0601, 0x0401, 0x0201, 0x0043, 0x0034, 0x0051, 0x0201, 0x0015, 0x0201, 0x0052,
|
||||
0x0025, 0x0401, 0x0201, 0x0026, 0x0036, 0x0071, 0x1401, 0x0801, 0x0201, 0x0017, 0x0401,
|
||||
0x0201, 0x0044, 0x0053, 0x0006, 0x0601, 0x0401, 0x0201, 0x0035, 0x0045, 0x0062, 0x0201,
|
||||
0x0070, 0x0201, 0x0007, 0x0064, 0x0e01, 0x0401, 0x0201, 0x0072, 0x0027, 0x0601, 0x0201,
|
||||
0x0063, 0x0201, 0x0054, 0x0055, 0x0201, 0x0046, 0x0073, 0x0801, 0x0401, 0x0201, 0x0037,
|
||||
0x0065, 0x0201, 0x0056, 0x0074, 0x0601, 0x0201, 0x0047, 0x0201, 0x0066, 0x0075, 0x0401,
|
||||
0x0201, 0x0057, 0x0076, 0x0201, 0x0067, 0x0077,
|
||||
// 11
|
||||
0x0601, 0x0201, 0x0000, 0x0201, 0x0010, 0x0001, 0x0801, 0x0201, 0x0011, 0x0401, 0x0201,
|
||||
0x0020, 0x0002, 0x0012, 0x1801, 0x0801, 0x0201, 0x0021, 0x0201, 0x0022, 0x0201, 0x0030,
|
||||
0x0003, 0x0401, 0x0201, 0x0031, 0x0013, 0x0401, 0x0201, 0x0032, 0x0023, 0x0401, 0x0201,
|
||||
0x0040, 0x0004, 0x0201, 0x0041, 0x0014, 0x1e01, 0x1001, 0x0a01, 0x0401, 0x0201, 0x0042,
|
||||
0x0024, 0x0401, 0x0201, 0x0033, 0x0043, 0x0050, 0x0401, 0x0201, 0x0034, 0x0051, 0x0061,
|
||||
0x0601, 0x0201, 0x0016, 0x0201, 0x0006, 0x0026, 0x0201, 0x0062, 0x0201, 0x0015, 0x0201,
|
||||
0x0005, 0x0052, 0x1001, 0x0a01, 0x0601, 0x0401, 0x0201, 0x0025, 0x0044, 0x0060, 0x0201,
|
||||
0x0063, 0x0036, 0x0401, 0x0201, 0x0070, 0x0017, 0x0071, 0x1001, 0x0601, 0x0401, 0x0201,
|
||||
0x0007, 0x0064, 0x0072, 0x0201, 0x0027, 0x0401, 0x0201, 0x0053, 0x0035, 0x0201, 0x0054,
|
||||
0x0045, 0x0a01, 0x0401, 0x0201, 0x0046, 0x0073, 0x0201, 0x0037, 0x0201, 0x0065, 0x0056,
|
||||
0x0a01, 0x0601, 0x0401, 0x0201, 0x0055, 0x0057, 0x0074, 0x0201, 0x0047, 0x0066, 0x0401,
|
||||
0x0201, 0x0075, 0x0076, 0x0201, 0x0067, 0x0077,
|
||||
// 12
|
||||
0x0c01, 0x0401, 0x0201, 0x0010, 0x0001, 0x0201, 0x0011, 0x0201, 0x0000, 0x0201, 0x0020,
|
||||
0x0002, 0x1001, 0x0401, 0x0201, 0x0021, 0x0012, 0x0401, 0x0201, 0x0022, 0x0031, 0x0201,
|
||||
0x0013, 0x0201, 0x0030, 0x0201, 0x0003, 0x0040, 0x1a01, 0x0801, 0x0401, 0x0201, 0x0032,
|
||||
0x0023, 0x0201, 0x0041, 0x0033, 0x0a01, 0x0401, 0x0201, 0x0014, 0x0042, 0x0201, 0x0024,
|
||||
0x0201, 0x0004, 0x0050, 0x0401, 0x0201, 0x0043, 0x0034, 0x0201, 0x0051, 0x0015, 0x1c01,
|
||||
0x0e01, 0x0801, 0x0401, 0x0201, 0x0052, 0x0025, 0x0201, 0x0053, 0x0035, 0x0401, 0x0201,
|
||||
0x0060, 0x0016, 0x0061, 0x0401, 0x0201, 0x0062, 0x0026, 0x0601, 0x0401, 0x0201, 0x0005,
|
||||
0x0006, 0x0044, 0x0201, 0x0054, 0x0045, 0x1201, 0x0a01, 0x0401, 0x0201, 0x0063, 0x0036,
|
||||
0x0401, 0x0201, 0x0070, 0x0007, 0x0071, 0x0401, 0x0201, 0x0017, 0x0064, 0x0201, 0x0046,
|
||||
0x0072, 0x0a01, 0x0601, 0x0201, 0x0027, 0x0201, 0x0055, 0x0073, 0x0201, 0x0037, 0x0056,
|
||||
0x0801, 0x0401, 0x0201, 0x0065, 0x0074, 0x0201, 0x0047, 0x0066, 0x0401, 0x0201, 0x0075,
|
||||
0x0057, 0x0201, 0x0076, 0x0201, 0x0067, 0x0077,
|
||||
// 13
|
||||
0x0201, 0x0000, 0x0601, 0x0201, 0x0010, 0x0201, 0x0001, 0x0011, 0x1c01, 0x0801, 0x0401,
|
||||
0x0201, 0x0020, 0x0002, 0x0201, 0x0021, 0x0012, 0x0801, 0x0401, 0x0201, 0x0022, 0x0030,
|
||||
0x0201, 0x0003, 0x0031, 0x0601, 0x0201, 0x0013, 0x0201, 0x0032, 0x0023, 0x0401, 0x0201,
|
||||
0x0040, 0x0004, 0x0041, 0x4601, 0x1c01, 0x0e01, 0x0601, 0x0201, 0x0014, 0x0201, 0x0033,
|
||||
0x0042, 0x0401, 0x0201, 0x0024, 0x0050, 0x0201, 0x0043, 0x0034, 0x0401, 0x0201, 0x0051,
|
||||
0x0015, 0x0401, 0x0201, 0x0005, 0x0052, 0x0201, 0x0025, 0x0201, 0x0044, 0x0053, 0x0e01,
|
||||
0x0801, 0x0401, 0x0201, 0x0060, 0x0006, 0x0201, 0x0061, 0x0016, 0x0401, 0x0201, 0x0080,
|
||||
0x0008, 0x0081, 0x1001, 0x0801, 0x0401, 0x0201, 0x0035, 0x0062, 0x0201, 0x0026, 0x0054,
|
||||
0x0401, 0x0201, 0x0045, 0x0063, 0x0201, 0x0036, 0x0070, 0x0601, 0x0401, 0x0201, 0x0007,
|
||||
0x0055, 0x0071, 0x0201, 0x0017, 0x0201, 0x0027, 0x0037, 0x4801, 0x1801, 0x0c01, 0x0401,
|
||||
0x0201, 0x0018, 0x0082, 0x0201, 0x0028, 0x0401, 0x0201, 0x0064, 0x0046, 0x0072, 0x0801,
|
||||
0x0401, 0x0201, 0x0084, 0x0048, 0x0201, 0x0090, 0x0009, 0x0201, 0x0091, 0x0019, 0x1801,
|
||||
0x0e01, 0x0801, 0x0401, 0x0201, 0x0073, 0x0065, 0x0201, 0x0056, 0x0074, 0x0401, 0x0201,
|
||||
0x0047, 0x0066, 0x0083, 0x0601, 0x0201, 0x0038, 0x0201, 0x0075, 0x0057, 0x0201, 0x0092,
|
||||
0x0029, 0x0e01, 0x0801, 0x0401, 0x0201, 0x0067, 0x0085, 0x0201, 0x0058, 0x0039, 0x0201,
|
||||
0x0093, 0x0201, 0x0049, 0x0086, 0x0601, 0x0201, 0x00a0, 0x0201, 0x0068, 0x000a, 0x0201,
|
||||
0x00a1, 0x001a, 0x4401, 0x1801, 0x0c01, 0x0401, 0x0201, 0x00a2, 0x002a, 0x0401, 0x0201,
|
||||
0x0095, 0x0059, 0x0201, 0x00a3, 0x003a, 0x0801, 0x0401, 0x0201, 0x004a, 0x0096, 0x0201,
|
||||
0x00b0, 0x000b, 0x0201, 0x00b1, 0x001b, 0x1401, 0x0801, 0x0201, 0x00b2, 0x0401, 0x0201,
|
||||
0x0076, 0x0077, 0x0094, 0x0601, 0x0401, 0x0201, 0x0087, 0x0078, 0x00a4, 0x0401, 0x0201,
|
||||
0x0069, 0x00a5, 0x002b, 0x0c01, 0x0601, 0x0401, 0x0201, 0x005a, 0x0088, 0x00b3, 0x0201,
|
||||
0x003b, 0x0201, 0x0079, 0x00a6, 0x0601, 0x0401, 0x0201, 0x006a, 0x00b4, 0x00c0, 0x0401,
|
||||
0x0201, 0x000c, 0x0098, 0x00c1, 0x3c01, 0x1601, 0x0a01, 0x0601, 0x0201, 0x001c, 0x0201,
|
||||
0x0089, 0x00b5, 0x0201, 0x005b, 0x00c2, 0x0401, 0x0201, 0x002c, 0x003c, 0x0401, 0x0201,
|
||||
0x00b6, 0x006b, 0x0201, 0x00c4, 0x004c, 0x1001, 0x0801, 0x0401, 0x0201, 0x00a8, 0x008a,
|
||||
0x0201, 0x00d0, 0x000d, 0x0201, 0x00d1, 0x0201, 0x004b, 0x0201, 0x0097, 0x00a7, 0x0c01,
|
||||
0x0601, 0x0201, 0x00c3, 0x0201, 0x007a, 0x0099, 0x0401, 0x0201, 0x00c5, 0x005c, 0x00b7,
|
||||
0x0401, 0x0201, 0x001d, 0x00d2, 0x0201, 0x002d, 0x0201, 0x007b, 0x00d3, 0x3401, 0x1c01,
|
||||
0x0c01, 0x0401, 0x0201, 0x003d, 0x00c6, 0x0401, 0x0201, 0x006c, 0x00a9, 0x0201, 0x009a,
|
||||
0x00d4, 0x0801, 0x0401, 0x0201, 0x00b8, 0x008b, 0x0201, 0x004d, 0x00c7, 0x0401, 0x0201,
|
||||
0x007c, 0x00d5, 0x0201, 0x005d, 0x00e0, 0x0a01, 0x0401, 0x0201, 0x00e1, 0x001e, 0x0401,
|
||||
0x0201, 0x000e, 0x002e, 0x00e2, 0x0801, 0x0401, 0x0201, 0x00e3, 0x006d, 0x0201, 0x008c,
|
||||
0x00e4, 0x0401, 0x0201, 0x00e5, 0x00ba, 0x00f0, 0x2601, 0x1001, 0x0401, 0x0201, 0x00f1,
|
||||
0x001f, 0x0601, 0x0401, 0x0201, 0x00aa, 0x009b, 0x00b9, 0x0201, 0x003e, 0x0201, 0x00d6,
|
||||
0x00c8, 0x0c01, 0x0601, 0x0201, 0x004e, 0x0201, 0x00d7, 0x007d, 0x0201, 0x00ab, 0x0201,
|
||||
0x005e, 0x00c9, 0x0601, 0x0201, 0x000f, 0x0201, 0x009c, 0x006e, 0x0201, 0x00f2, 0x002f,
|
||||
0x2001, 0x1001, 0x0601, 0x0401, 0x0201, 0x00d8, 0x008d, 0x003f, 0x0601, 0x0201, 0x00f3,
|
||||
0x0201, 0x00e6, 0x00ca, 0x0201, 0x00f4, 0x004f, 0x0801, 0x0401, 0x0201, 0x00bb, 0x00ac,
|
||||
0x0201, 0x00e7, 0x00f5, 0x0401, 0x0201, 0x00d9, 0x009d, 0x0201, 0x005f, 0x00e8, 0x1e01,
|
||||
0x0c01, 0x0601, 0x0201, 0x006f, 0x0201, 0x00f6, 0x00cb, 0x0401, 0x0201, 0x00bc, 0x00ad,
|
||||
0x00da, 0x0801, 0x0201, 0x00f7, 0x0401, 0x0201, 0x007e, 0x007f, 0x008e, 0x0601, 0x0401,
|
||||
0x0201, 0x009e, 0x00ae, 0x00cc, 0x0201, 0x00f8, 0x008f, 0x1201, 0x0801, 0x0401, 0x0201,
|
||||
0x00db, 0x00bd, 0x0201, 0x00ea, 0x00f9, 0x0401, 0x0201, 0x009f, 0x00eb, 0x0201, 0x00be,
|
||||
0x0201, 0x00cd, 0x00fa, 0x0e01, 0x0401, 0x0201, 0x00dd, 0x00ec, 0x0601, 0x0401, 0x0201,
|
||||
0x00e9, 0x00af, 0x00dc, 0x0201, 0x00ce, 0x00fb, 0x0801, 0x0401, 0x0201, 0x00bf, 0x00de,
|
||||
0x0201, 0x00cf, 0x00ee, 0x0401, 0x0201, 0x00df, 0x00ef, 0x0201, 0x00ff, 0x0201, 0x00ed,
|
||||
0x0201, 0x00fd, 0x0201, 0x00fc, 0x00fe,
|
||||
// 15
|
||||
0x1001, 0x0601, 0x0201, 0x0000, 0x0201, 0x0010, 0x0001, 0x0201, 0x0011, 0x0401, 0x0201,
|
||||
0x0020, 0x0002, 0x0201, 0x0021, 0x0012, 0x3201, 0x1001, 0x0601, 0x0201, 0x0022, 0x0201,
|
||||
0x0030, 0x0031, 0x0601, 0x0201, 0x0013, 0x0201, 0x0003, 0x0040, 0x0201, 0x0032, 0x0023,
|
||||
0x0e01, 0x0601, 0x0401, 0x0201, 0x0004, 0x0014, 0x0041, 0x0401, 0x0201, 0x0033, 0x0042,
|
||||
0x0201, 0x0024, 0x0043, 0x0a01, 0x0601, 0x0201, 0x0034, 0x0201, 0x0050, 0x0005, 0x0201,
|
||||
0x0051, 0x0015, 0x0401, 0x0201, 0x0052, 0x0025, 0x0401, 0x0201, 0x0044, 0x0053, 0x0061,
|
||||
0x5a01, 0x2401, 0x1201, 0x0a01, 0x0601, 0x0201, 0x0035, 0x0201, 0x0060, 0x0006, 0x0201,
|
||||
0x0016, 0x0062, 0x0401, 0x0201, 0x0026, 0x0054, 0x0201, 0x0045, 0x0063, 0x0a01, 0x0601,
|
||||
0x0201, 0x0036, 0x0201, 0x0070, 0x0007, 0x0201, 0x0071, 0x0055, 0x0401, 0x0201, 0x0017,
|
||||
0x0064, 0x0201, 0x0072, 0x0027, 0x1801, 0x1001, 0x0801, 0x0401, 0x0201, 0x0046, 0x0073,
|
||||
0x0201, 0x0037, 0x0065, 0x0401, 0x0201, 0x0056, 0x0080, 0x0201, 0x0008, 0x0074, 0x0401,
|
||||
0x0201, 0x0081, 0x0018, 0x0201, 0x0082, 0x0028, 0x1001, 0x0801, 0x0401, 0x0201, 0x0047,
|
||||
0x0066, 0x0201, 0x0083, 0x0038, 0x0401, 0x0201, 0x0075, 0x0057, 0x0201, 0x0084, 0x0048,
|
||||
0x0601, 0x0401, 0x0201, 0x0090, 0x0019, 0x0091, 0x0401, 0x0201, 0x0092, 0x0076, 0x0201,
|
||||
0x0067, 0x0029, 0x5c01, 0x2401, 0x1201, 0x0a01, 0x0401, 0x0201, 0x0085, 0x0058, 0x0401,
|
||||
0x0201, 0x0009, 0x0077, 0x0093, 0x0401, 0x0201, 0x0039, 0x0094, 0x0201, 0x0049, 0x0086,
|
||||
0x0a01, 0x0601, 0x0201, 0x0068, 0x0201, 0x00a0, 0x000a, 0x0201, 0x00a1, 0x001a, 0x0401,
|
||||
0x0201, 0x00a2, 0x002a, 0x0201, 0x0095, 0x0059, 0x1a01, 0x0e01, 0x0601, 0x0201, 0x00a3,
|
||||
0x0201, 0x003a, 0x0087, 0x0401, 0x0201, 0x0078, 0x00a4, 0x0201, 0x004a, 0x0096, 0x0601,
|
||||
0x0401, 0x0201, 0x0069, 0x00b0, 0x00b1, 0x0401, 0x0201, 0x001b, 0x00a5, 0x00b2, 0x0e01,
|
||||
0x0801, 0x0401, 0x0201, 0x005a, 0x002b, 0x0201, 0x0088, 0x0097, 0x0201, 0x00b3, 0x0201,
|
||||
0x0079, 0x003b, 0x0801, 0x0401, 0x0201, 0x006a, 0x00b4, 0x0201, 0x004b, 0x00c1, 0x0401,
|
||||
0x0201, 0x0098, 0x0089, 0x0201, 0x001c, 0x00b5, 0x5001, 0x2201, 0x1001, 0x0601, 0x0401,
|
||||
0x0201, 0x005b, 0x002c, 0x00c2, 0x0601, 0x0401, 0x0201, 0x000b, 0x00c0, 0x00a6, 0x0201,
|
||||
0x00a7, 0x007a, 0x0a01, 0x0401, 0x0201, 0x00c3, 0x003c, 0x0401, 0x0201, 0x000c, 0x0099,
|
||||
0x00b6, 0x0401, 0x0201, 0x006b, 0x00c4, 0x0201, 0x004c, 0x00a8, 0x1401, 0x0a01, 0x0401,
|
||||
0x0201, 0x008a, 0x00c5, 0x0401, 0x0201, 0x00d0, 0x005c, 0x00d1, 0x0401, 0x0201, 0x00b7,
|
||||
0x007b, 0x0201, 0x001d, 0x0201, 0x000d, 0x002d, 0x0c01, 0x0401, 0x0201, 0x00d2, 0x00d3,
|
||||
0x0401, 0x0201, 0x003d, 0x00c6, 0x0201, 0x006c, 0x00a9, 0x0601, 0x0401, 0x0201, 0x009a,
|
||||
0x00b8, 0x00d4, 0x0401, 0x0201, 0x008b, 0x004d, 0x0201, 0x00c7, 0x007c, 0x4401, 0x2201,
|
||||
0x1201, 0x0a01, 0x0401, 0x0201, 0x00d5, 0x005d, 0x0401, 0x0201, 0x00e0, 0x000e, 0x00e1,
|
||||
0x0401, 0x0201, 0x001e, 0x00e2, 0x0201, 0x00aa, 0x002e, 0x0801, 0x0401, 0x0201, 0x00b9,
|
||||
0x009b, 0x0201, 0x00e3, 0x00d6, 0x0401, 0x0201, 0x006d, 0x003e, 0x0201, 0x00c8, 0x008c,
|
||||
0x1001, 0x0801, 0x0401, 0x0201, 0x00e4, 0x004e, 0x0201, 0x00d7, 0x007d, 0x0401, 0x0201,
|
||||
0x00e5, 0x00ba, 0x0201, 0x00ab, 0x005e, 0x0801, 0x0401, 0x0201, 0x00c9, 0x009c, 0x0201,
|
||||
0x00f1, 0x001f, 0x0601, 0x0401, 0x0201, 0x00f0, 0x006e, 0x00f2, 0x0201, 0x002f, 0x00e6,
|
||||
0x2601, 0x1201, 0x0801, 0x0401, 0x0201, 0x00d8, 0x00f3, 0x0201, 0x003f, 0x00f4, 0x0601,
|
||||
0x0201, 0x004f, 0x0201, 0x008d, 0x00d9, 0x0201, 0x00bb, 0x00ca, 0x0801, 0x0401, 0x0201,
|
||||
0x00ac, 0x00e7, 0x0201, 0x007e, 0x00f5, 0x0801, 0x0401, 0x0201, 0x009d, 0x005f, 0x0201,
|
||||
0x00e8, 0x008e, 0x0201, 0x00f6, 0x00cb, 0x2201, 0x1201, 0x0a01, 0x0601, 0x0401, 0x0201,
|
||||
0x000f, 0x00ae, 0x006f, 0x0201, 0x00bc, 0x00da, 0x0401, 0x0201, 0x00ad, 0x00f7, 0x0201,
|
||||
0x007f, 0x00e9, 0x0801, 0x0401, 0x0201, 0x009e, 0x00cc, 0x0201, 0x00f8, 0x008f, 0x0401,
|
||||
0x0201, 0x00db, 0x00bd, 0x0201, 0x00ea, 0x00f9, 0x1001, 0x0801, 0x0401, 0x0201, 0x009f,
|
||||
0x00dc, 0x0201, 0x00cd, 0x00eb, 0x0401, 0x0201, 0x00be, 0x00fa, 0x0201, 0x00af, 0x00dd,
|
||||
0x0e01, 0x0601, 0x0401, 0x0201, 0x00ec, 0x00ce, 0x00fb, 0x0401, 0x0201, 0x00bf, 0x00ed,
|
||||
0x0201, 0x00de, 0x00fc, 0x0601, 0x0401, 0x0201, 0x00cf, 0x00fd, 0x00ee, 0x0401, 0x0201,
|
||||
0x00df, 0x00fe, 0x0201, 0x00ef, 0x00ff,
|
||||
// 16
|
||||
0x0201, 0x0000, 0x0601, 0x0201, 0x0010, 0x0201, 0x0001, 0x0011, 0x2a01, 0x0801, 0x0401,
|
||||
0x0201, 0x0020, 0x0002, 0x0201, 0x0021, 0x0012, 0x0a01, 0x0601, 0x0201, 0x0022, 0x0201,
|
||||
0x0030, 0x0003, 0x0201, 0x0031, 0x0013, 0x0a01, 0x0401, 0x0201, 0x0032, 0x0023, 0x0401,
|
||||
0x0201, 0x0040, 0x0004, 0x0041, 0x0601, 0x0201, 0x0014, 0x0201, 0x0033, 0x0042, 0x0401,
|
||||
0x0201, 0x0024, 0x0050, 0x0201, 0x0043, 0x0034, 0x8a01, 0x2801, 0x1001, 0x0601, 0x0401,
|
||||
0x0201, 0x0005, 0x0015, 0x0051, 0x0401, 0x0201, 0x0052, 0x0025, 0x0401, 0x0201, 0x0044,
|
||||
0x0035, 0x0053, 0x0a01, 0x0601, 0x0401, 0x0201, 0x0060, 0x0006, 0x0061, 0x0201, 0x0016,
|
||||
0x0062, 0x0801, 0x0401, 0x0201, 0x0026, 0x0054, 0x0201, 0x0045, 0x0063, 0x0401, 0x0201,
|
||||
0x0036, 0x0070, 0x0071, 0x2801, 0x1201, 0x0801, 0x0201, 0x0017, 0x0201, 0x0007, 0x0201,
|
||||
0x0055, 0x0064, 0x0401, 0x0201, 0x0072, 0x0027, 0x0401, 0x0201, 0x0046, 0x0065, 0x0073,
|
||||
0x0a01, 0x0601, 0x0201, 0x0037, 0x0201, 0x0056, 0x0008, 0x0201, 0x0080, 0x0081, 0x0601,
|
||||
0x0201, 0x0018, 0x0201, 0x0074, 0x0047, 0x0201, 0x0082, 0x0201, 0x0028, 0x0066, 0x1801,
|
||||
0x0e01, 0x0801, 0x0401, 0x0201, 0x0083, 0x0038, 0x0201, 0x0075, 0x0084, 0x0401, 0x0201,
|
||||
0x0048, 0x0090, 0x0091, 0x0601, 0x0201, 0x0019, 0x0201, 0x0009, 0x0076, 0x0201, 0x0092,
|
||||
0x0029, 0x0e01, 0x0801, 0x0401, 0x0201, 0x0085, 0x0058, 0x0201, 0x0093, 0x0039, 0x0401,
|
||||
0x0201, 0x00a0, 0x000a, 0x001a, 0x0801, 0x0201, 0x00a2, 0x0201, 0x0067, 0x0201, 0x0057,
|
||||
0x0049, 0x0601, 0x0201, 0x0094, 0x0201, 0x0077, 0x0086, 0x0201, 0x00a1, 0x0201, 0x0068,
|
||||
0x0095, 0xdc01, 0x7e01, 0x3201, 0x1a01, 0x0c01, 0x0601, 0x0201, 0x002a, 0x0201, 0x0059,
|
||||
0x003a, 0x0201, 0x00a3, 0x0201, 0x0087, 0x0078, 0x0801, 0x0401, 0x0201, 0x00a4, 0x004a,
|
||||
0x0201, 0x0096, 0x0069, 0x0401, 0x0201, 0x00b0, 0x000b, 0x00b1, 0x0a01, 0x0401, 0x0201,
|
||||
0x001b, 0x00b2, 0x0201, 0x002b, 0x0201, 0x00a5, 0x005a, 0x0601, 0x0201, 0x00b3, 0x0201,
|
||||
0x00a6, 0x006a, 0x0401, 0x0201, 0x00b4, 0x004b, 0x0201, 0x000c, 0x00c1, 0x1e01, 0x0e01,
|
||||
0x0601, 0x0401, 0x0201, 0x00b5, 0x00c2, 0x002c, 0x0401, 0x0201, 0x00a7, 0x00c3, 0x0201,
|
||||
0x006b, 0x00c4, 0x0801, 0x0201, 0x001d, 0x0401, 0x0201, 0x0088, 0x0097, 0x003b, 0x0401,
|
||||
0x0201, 0x00d1, 0x00d2, 0x0201, 0x002d, 0x00d3, 0x1201, 0x0601, 0x0401, 0x0201, 0x001e,
|
||||
0x002e, 0x00e2, 0x0601, 0x0401, 0x0201, 0x0079, 0x0098, 0x00c0, 0x0201, 0x001c, 0x0201,
|
||||
0x0089, 0x005b, 0x0e01, 0x0601, 0x0201, 0x003c, 0x0201, 0x007a, 0x00b6, 0x0401, 0x0201,
|
||||
0x004c, 0x0099, 0x0201, 0x00a8, 0x008a, 0x0601, 0x0201, 0x000d, 0x0201, 0x00c5, 0x005c,
|
||||
0x0401, 0x0201, 0x003d, 0x00c6, 0x0201, 0x006c, 0x009a, 0x5801, 0x5601, 0x2401, 0x1001,
|
||||
0x0801, 0x0401, 0x0201, 0x008b, 0x004d, 0x0201, 0x00c7, 0x007c, 0x0401, 0x0201, 0x00d5,
|
||||
0x005d, 0x0201, 0x00e0, 0x000e, 0x0801, 0x0201, 0x00e3, 0x0401, 0x0201, 0x00d0, 0x00b7,
|
||||
0x007b, 0x0601, 0x0401, 0x0201, 0x00a9, 0x00b8, 0x00d4, 0x0201, 0x00e1, 0x0201, 0x00aa,
|
||||
0x00b9, 0x1801, 0x0a01, 0x0601, 0x0401, 0x0201, 0x009b, 0x00d6, 0x006d, 0x0201, 0x003e,
|
||||
0x00c8, 0x0601, 0x0401, 0x0201, 0x008c, 0x00e4, 0x004e, 0x0401, 0x0201, 0x00d7, 0x00e5,
|
||||
0x0201, 0x00ba, 0x00ab, 0x0c01, 0x0401, 0x0201, 0x009c, 0x00e6, 0x0401, 0x0201, 0x006e,
|
||||
0x00d8, 0x0201, 0x008d, 0x00bb, 0x0801, 0x0401, 0x0201, 0x00e7, 0x009d, 0x0201, 0x00e8,
|
||||
0x008e, 0x0401, 0x0201, 0x00cb, 0x00bc, 0x009e, 0x00f1, 0x0201, 0x001f, 0x0201, 0x000f,
|
||||
0x002f, 0x4201, 0x3801, 0x0201, 0x00f2, 0x3401, 0x3201, 0x1401, 0x0801, 0x0201, 0x00bd,
|
||||
0x0201, 0x005e, 0x0201, 0x007d, 0x00c9, 0x0601, 0x0201, 0x00ca, 0x0201, 0x00ac, 0x007e,
|
||||
0x0401, 0x0201, 0x00da, 0x00ad, 0x00cc, 0x0a01, 0x0601, 0x0201, 0x00ae, 0x0201, 0x00db,
|
||||
0x00dc, 0x0201, 0x00cd, 0x00be, 0x0601, 0x0401, 0x0201, 0x00eb, 0x00ed, 0x00ee, 0x0601,
|
||||
0x0401, 0x0201, 0x00d9, 0x00ea, 0x00e9, 0x0201, 0x00de, 0x0401, 0x0201, 0x00dd, 0x00ec,
|
||||
0x00ce, 0x003f, 0x00f0, 0x0401, 0x0201, 0x00f3, 0x00f4, 0x0201, 0x004f, 0x0201, 0x00f5,
|
||||
0x005f, 0x0a01, 0x0201, 0x00ff, 0x0401, 0x0201, 0x00f6, 0x006f, 0x0201, 0x00f7, 0x007f,
|
||||
0x0c01, 0x0601, 0x0201, 0x008f, 0x0201, 0x00f8, 0x00f9, 0x0401, 0x0201, 0x009f, 0x00fa,
|
||||
0x00af, 0x0801, 0x0401, 0x0201, 0x00fb, 0x00bf, 0x0201, 0x00fc, 0x00cf, 0x0401, 0x0201,
|
||||
0x00fd, 0x00df, 0x0201, 0x00fe, 0x00ef,
|
||||
// 24
|
||||
0x3c01, 0x0801, 0x0401, 0x0201, 0x0000, 0x0010, 0x0201, 0x0001, 0x0011, 0x0e01, 0x0601,
|
||||
0x0401, 0x0201, 0x0020, 0x0002, 0x0021, 0x0201, 0x0012, 0x0201, 0x0022, 0x0201, 0x0030,
|
||||
0x0003, 0x0e01, 0x0401, 0x0201, 0x0031, 0x0013, 0x0401, 0x0201, 0x0032, 0x0023, 0x0401,
|
||||
0x0201, 0x0040, 0x0004, 0x0041, 0x0801, 0x0401, 0x0201, 0x0014, 0x0033, 0x0201, 0x0042,
|
||||
0x0024, 0x0601, 0x0401, 0x0201, 0x0043, 0x0034, 0x0051, 0x0601, 0x0401, 0x0201, 0x0050,
|
||||
0x0005, 0x0015, 0x0201, 0x0052, 0x0025, 0xfa01, 0x6201, 0x2201, 0x1201, 0x0a01, 0x0401,
|
||||
0x0201, 0x0044, 0x0053, 0x0201, 0x0035, 0x0201, 0x0060, 0x0006, 0x0401, 0x0201, 0x0061,
|
||||
0x0016, 0x0201, 0x0062, 0x0026, 0x0801, 0x0401, 0x0201, 0x0054, 0x0045, 0x0201, 0x0063,
|
||||
0x0036, 0x0401, 0x0201, 0x0071, 0x0055, 0x0201, 0x0064, 0x0046, 0x2001, 0x0e01, 0x0601,
|
||||
0x0201, 0x0072, 0x0201, 0x0027, 0x0037, 0x0201, 0x0073, 0x0401, 0x0201, 0x0070, 0x0007,
|
||||
0x0017, 0x0a01, 0x0401, 0x0201, 0x0065, 0x0056, 0x0401, 0x0201, 0x0080, 0x0008, 0x0081,
|
||||
0x0401, 0x0201, 0x0074, 0x0047, 0x0201, 0x0018, 0x0082, 0x1001, 0x0801, 0x0401, 0x0201,
|
||||
0x0028, 0x0066, 0x0201, 0x0083, 0x0038, 0x0401, 0x0201, 0x0075, 0x0057, 0x0201, 0x0084,
|
||||
0x0048, 0x0801, 0x0401, 0x0201, 0x0091, 0x0019, 0x0201, 0x0092, 0x0076, 0x0401, 0x0201,
|
||||
0x0067, 0x0029, 0x0201, 0x0085, 0x0058, 0x5c01, 0x2201, 0x1001, 0x0801, 0x0401, 0x0201,
|
||||
0x0093, 0x0039, 0x0201, 0x0094, 0x0049, 0x0401, 0x0201, 0x0077, 0x0086, 0x0201, 0x0068,
|
||||
0x00a1, 0x0801, 0x0401, 0x0201, 0x00a2, 0x002a, 0x0201, 0x0095, 0x0059, 0x0401, 0x0201,
|
||||
0x00a3, 0x003a, 0x0201, 0x0087, 0x0201, 0x0078, 0x004a, 0x1601, 0x0c01, 0x0401, 0x0201,
|
||||
0x00a4, 0x0096, 0x0401, 0x0201, 0x0069, 0x00b1, 0x0201, 0x001b, 0x00a5, 0x0601, 0x0201,
|
||||
0x00b2, 0x0201, 0x005a, 0x002b, 0x0201, 0x0088, 0x00b3, 0x1001, 0x0a01, 0x0601, 0x0201,
|
||||
0x0090, 0x0201, 0x0009, 0x00a0, 0x0201, 0x0097, 0x0079, 0x0401, 0x0201, 0x00a6, 0x006a,
|
||||
0x00b4, 0x0c01, 0x0601, 0x0201, 0x001a, 0x0201, 0x000a, 0x00b0, 0x0201, 0x003b, 0x0201,
|
||||
0x000b, 0x00c0, 0x0401, 0x0201, 0x004b, 0x00c1, 0x0201, 0x0098, 0x0089, 0x4301, 0x2201,
|
||||
0x1001, 0x0801, 0x0401, 0x0201, 0x001c, 0x00b5, 0x0201, 0x005b, 0x00c2, 0x0401, 0x0201,
|
||||
0x002c, 0x00a7, 0x0201, 0x007a, 0x00c3, 0x0a01, 0x0601, 0x0201, 0x003c, 0x0201, 0x000c,
|
||||
0x00d0, 0x0201, 0x00b6, 0x006b, 0x0401, 0x0201, 0x00c4, 0x004c, 0x0201, 0x0099, 0x00a8,
|
||||
0x1001, 0x0801, 0x0401, 0x0201, 0x008a, 0x00c5, 0x0201, 0x005c, 0x00d1, 0x0401, 0x0201,
|
||||
0x00b7, 0x007b, 0x0201, 0x001d, 0x00d2, 0x0901, 0x0401, 0x0201, 0x002d, 0x00d3, 0x0201,
|
||||
0x003d, 0x00c6, 0x55fa, 0x0401, 0x0201, 0x006c, 0x00a9, 0x0201, 0x009a, 0x00d4, 0x2001,
|
||||
0x1001, 0x0801, 0x0401, 0x0201, 0x00b8, 0x008b, 0x0201, 0x004d, 0x00c7, 0x0401, 0x0201,
|
||||
0x007c, 0x00d5, 0x0201, 0x005d, 0x00e1, 0x0801, 0x0401, 0x0201, 0x001e, 0x00e2, 0x0201,
|
||||
0x00aa, 0x00b9, 0x0401, 0x0201, 0x009b, 0x00e3, 0x0201, 0x00d6, 0x006d, 0x1401, 0x0a01,
|
||||
0x0601, 0x0201, 0x003e, 0x0201, 0x002e, 0x004e, 0x0201, 0x00c8, 0x008c, 0x0401, 0x0201,
|
||||
0x00e4, 0x00d7, 0x0401, 0x0201, 0x007d, 0x00ab, 0x00e5, 0x0a01, 0x0401, 0x0201, 0x00ba,
|
||||
0x005e, 0x0201, 0x00c9, 0x0201, 0x009c, 0x006e, 0x0801, 0x0201, 0x00e6, 0x0201, 0x000d,
|
||||
0x0201, 0x00e0, 0x000e, 0x0401, 0x0201, 0x00d8, 0x008d, 0x0201, 0x00bb, 0x00ca, 0x4a01,
|
||||
0x0201, 0x00ff, 0x4001, 0x3a01, 0x2001, 0x1001, 0x0801, 0x0401, 0x0201, 0x00ac, 0x00e7,
|
||||
0x0201, 0x007e, 0x00d9, 0x0401, 0x0201, 0x009d, 0x00e8, 0x0201, 0x008e, 0x00cb, 0x0801,
|
||||
0x0401, 0x0201, 0x00bc, 0x00da, 0x0201, 0x00ad, 0x00e9, 0x0401, 0x0201, 0x009e, 0x00cc,
|
||||
0x0201, 0x00db, 0x00bd, 0x1001, 0x0801, 0x0401, 0x0201, 0x00ea, 0x00ae, 0x0201, 0x00dc,
|
||||
0x00cd, 0x0401, 0x0201, 0x00eb, 0x00be, 0x0201, 0x00dd, 0x00ec, 0x0801, 0x0401, 0x0201,
|
||||
0x00ce, 0x00ed, 0x0201, 0x00de, 0x00ee, 0x000f, 0x0401, 0x0201, 0x00f0, 0x001f, 0x00f1,
|
||||
0x0401, 0x0201, 0x00f2, 0x002f, 0x0201, 0x00f3, 0x003f, 0x1201, 0x0801, 0x0401, 0x0201,
|
||||
0x00f4, 0x004f, 0x0201, 0x00f5, 0x005f, 0x0401, 0x0201, 0x00f6, 0x006f, 0x0201, 0x00f7,
|
||||
0x0201, 0x007f, 0x008f, 0x0a01, 0x0401, 0x0201, 0x00f8, 0x00f9, 0x0401, 0x0201, 0x009f,
|
||||
0x00af, 0x00fa, 0x0801, 0x0401, 0x0201, 0x00fb, 0x00bf, 0x0201, 0x00fc, 0x00cf, 0x0401,
|
||||
0x0201, 0x00fd, 0x00df, 0x0201, 0x00fe, 0x00ef,
|
||||
// 32
|
||||
0x0201, 0x0000, 0x0801, 0x0401, 0x0201, 0x0008, 0x0004, 0x0201, 0x0001, 0x0002, 0x0801,
|
||||
0x0401, 0x0201, 0x000c, 0x000a, 0x0201, 0x0003, 0x0006, 0x0601, 0x0201, 0x0009, 0x0201,
|
||||
0x0005, 0x0007, 0x0401, 0x0201, 0x000e, 0x000d, 0x0201, 0x000f, 0x000b,
|
||||
// 33
|
||||
0x1001, 0x0801, 0x0401, 0x0201, 0x0000, 0x0001, 0x0201, 0x0002, 0x0003, 0x0401, 0x0201,
|
||||
0x0004, 0x0005, 0x0201, 0x0006, 0x0007, 0x0801, 0x0401, 0x0201, 0x0008, 0x0009, 0x0201,
|
||||
0x000a, 0x000b, 0x0401, 0x0201, 0x000c, 0x000d, 0x0201, 0x000e, 0x000f,
|
||||
}
|
||||
|
||||
type huffTables struct {
|
||||
hufftable []uint16
|
||||
treelen int
|
||||
linbits int
|
||||
}
|
||||
|
||||
var huffmanMain = [...]huffTables{
|
||||
{nil, 0, 0}, // Table 0
|
||||
{huffmanTable, 7, 0}, // Table 1
|
||||
{huffmanTable[7:], 17, 0}, // Table 2
|
||||
{huffmanTable[24:], 17, 0}, // Table 3
|
||||
{nil, 0, 0}, // Table 4
|
||||
{huffmanTable[41:], 31, 0}, // Table 5
|
||||
{huffmanTable[72:], 31, 0}, // Table 6
|
||||
{huffmanTable[103:], 71, 0}, // Table 7
|
||||
{huffmanTable[174:], 71, 0}, // Table 8
|
||||
{huffmanTable[245:], 71, 0}, // Table 9
|
||||
{huffmanTable[316:], 127, 0}, // Table 10
|
||||
{huffmanTable[443:], 127, 0}, // Table 11
|
||||
{huffmanTable[570:], 127, 0}, // Table 12
|
||||
{huffmanTable[697:], 511, 0}, // Table 13
|
||||
{nil, 0, 0}, // Table 14
|
||||
{huffmanTable[1208:], 511, 0}, // Table 15
|
||||
{huffmanTable[1719:], 511, 1}, // Table 16
|
||||
{huffmanTable[1719:], 511, 2}, // Table 17
|
||||
{huffmanTable[1719:], 511, 3}, // Table 18
|
||||
{huffmanTable[1719:], 511, 4}, // Table 19
|
||||
{huffmanTable[1719:], 511, 6}, // Table 20
|
||||
{huffmanTable[1719:], 511, 8}, // Table 21
|
||||
{huffmanTable[1719:], 511, 10}, // Table 22
|
||||
{huffmanTable[1719:], 511, 13}, // Table 23
|
||||
{huffmanTable[2230:], 512, 4}, // Table 24
|
||||
{huffmanTable[2230:], 512, 5}, // Table 25
|
||||
{huffmanTable[2230:], 512, 6}, // Table 26
|
||||
{huffmanTable[2230:], 512, 7}, // Table 27
|
||||
{huffmanTable[2230:], 512, 8}, // Table 28
|
||||
{huffmanTable[2230:], 512, 9}, // Table 29
|
||||
{huffmanTable[2230:], 512, 11}, // Table 30
|
||||
{huffmanTable[2230:], 512, 13}, // Table 31
|
||||
{huffmanTable[2742:], 31, 0}, // Table 32
|
||||
{huffmanTable[2773:], 31, 0}, // Table 33
|
||||
}
|
||||
|
||||
func Decode(m *bits.Bits, table_num int) (x, y, v, w int, err error) {
|
||||
point := 0
|
||||
error := 1
|
||||
bitsleft := 32
|
||||
treelen := huffmanMain[table_num].treelen
|
||||
linbits := huffmanMain[table_num].linbits
|
||||
if treelen == 0 { // Check for empty tables
|
||||
return 0, 0, 0, 0, nil
|
||||
}
|
||||
htptr := huffmanMain[table_num].hufftable
|
||||
for { // Start reading the Huffman code word,bit by bit
|
||||
// Check if we've matched a code word
|
||||
if (htptr[point] & 0xff00) == 0 {
|
||||
error = 0
|
||||
x = int((htptr[point] >> 4) & 0xf)
|
||||
y = int(htptr[point] & 0xf)
|
||||
break
|
||||
}
|
||||
if m.Bit() != 0 { // Go right in tree
|
||||
for (htptr[point] & 0xff) >= 250 {
|
||||
point += int(htptr[point]) & 0xff
|
||||
}
|
||||
point += int(htptr[point]) & 0xff
|
||||
} else { // Go left in tree
|
||||
for (htptr[point] >> 8) >= 250 {
|
||||
point += int(htptr[point]) >> 8
|
||||
}
|
||||
point += int(htptr[point]) >> 8
|
||||
}
|
||||
bitsleft--
|
||||
if bitsleft <= 0 || point >= treelen {
|
||||
break
|
||||
}
|
||||
}
|
||||
if error != 0 { // Check for error.
|
||||
err := fmt.Errorf("mp3: illegal Huff code in data. bleft = %d, point = %d. tab = %d.",
|
||||
bitsleft, point, table_num)
|
||||
return 0, 0, 0, 0, err
|
||||
}
|
||||
if table_num > 31 { // Process sign encodings for quadruples tables.
|
||||
v = (y >> 3) & 1
|
||||
w = (y >> 2) & 1
|
||||
x = (y >> 1) & 1
|
||||
y = y & 1
|
||||
if (v != 0) && (m.Bit() == 1) {
|
||||
v = -v
|
||||
}
|
||||
if (w != 0) && (m.Bit() == 1) {
|
||||
w = -w
|
||||
}
|
||||
if (x != 0) && (m.Bit() == 1) {
|
||||
x = -x
|
||||
}
|
||||
if (y != 0) && (m.Bit() == 1) {
|
||||
y = -y
|
||||
}
|
||||
} else {
|
||||
if (linbits != 0) && (x == 15) {
|
||||
x += m.Bits(linbits) // Get linbits
|
||||
}
|
||||
if (x != 0) && (m.Bit() == 1) {
|
||||
x = -x // Get sign bit
|
||||
}
|
||||
if (linbits != 0) && (y == 15) {
|
||||
y += m.Bits(linbits) // Get linbits
|
||||
}
|
||||
if (y != 0) && (m.Bit() == 1) {
|
||||
y = -y // Get sign bit
|
||||
}
|
||||
}
|
||||
return x, y, v, w, nil
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
// Copyright 2017 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package imdct
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
var imdctWinData = [4][36]float32{}
|
||||
|
||||
func init() {
|
||||
for i := 0; i < 36; i++ {
|
||||
imdctWinData[0][i] = float32(math.Sin(math.Pi / 36 * (float64(i) + 0.5)))
|
||||
}
|
||||
for i := 0; i < 18; i++ {
|
||||
imdctWinData[1][i] = float32(math.Sin(math.Pi / 36 * (float64(i) + 0.5)))
|
||||
}
|
||||
for i := 18; i < 24; i++ {
|
||||
imdctWinData[1][i] = 1.0
|
||||
}
|
||||
for i := 24; i < 30; i++ {
|
||||
imdctWinData[1][i] = float32(math.Sin(math.Pi / 12 * (float64(i) + 0.5 - 18.0)))
|
||||
}
|
||||
for i := 30; i < 36; i++ {
|
||||
imdctWinData[1][i] = 0.0
|
||||
}
|
||||
for i := 0; i < 12; i++ {
|
||||
imdctWinData[2][i] = float32(math.Sin(math.Pi / 12 * (float64(i) + 0.5)))
|
||||
}
|
||||
for i := 12; i < 36; i++ {
|
||||
imdctWinData[2][i] = 0.0
|
||||
}
|
||||
for i := 0; i < 6; i++ {
|
||||
imdctWinData[3][i] = 0.0
|
||||
}
|
||||
for i := 6; i < 12; i++ {
|
||||
imdctWinData[3][i] = float32(math.Sin(math.Pi / 12 * (float64(i) + 0.5 - 6.0)))
|
||||
}
|
||||
for i := 12; i < 18; i++ {
|
||||
imdctWinData[3][i] = 1.0
|
||||
}
|
||||
for i := 18; i < 36; i++ {
|
||||
imdctWinData[3][i] = float32(math.Sin(math.Pi / 36 * (float64(i) + 0.5)))
|
||||
}
|
||||
}
|
||||
|
||||
var cosN12 = [6][12]float32{}
|
||||
|
||||
func init() {
|
||||
const N = 12
|
||||
for i := 0; i < 6; i++ {
|
||||
for j := 0; j < 12; j++ {
|
||||
cosN12[i][j] = float32(math.Cos(math.Pi / (2 * N) * (2*float64(j) + 1 + N/2) * (2*float64(i) + 1)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cosN36 = [18][36]float32{}
|
||||
|
||||
func init() {
|
||||
const N = 36
|
||||
for i := 0; i < 18; i++ {
|
||||
for j := 0; j < 36; j++ {
|
||||
cosN36[i][j] = float32(math.Cos(math.Pi / (2 * N) * (2*float64(j) + 1 + N/2) * (2*float64(i) + 1)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Win(in []float32, blockType int) []float32 {
|
||||
out := make([]float32, 36)
|
||||
if blockType == 2 {
|
||||
iwd := imdctWinData[blockType]
|
||||
const N = 12
|
||||
for i := 0; i < 3; i++ {
|
||||
for p := 0; p < N; p++ {
|
||||
sum := float32(0.0)
|
||||
for m := 0; m < N/2; m++ {
|
||||
sum += in[i+3*m] * cosN12[m][p]
|
||||
}
|
||||
out[6*i+p+6] += sum * iwd[p]
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
const N = 36
|
||||
iwd := imdctWinData[blockType]
|
||||
for p := 0; p < N; p++ {
|
||||
sum := float32(0.0)
|
||||
for m := 0; m < N/2; m++ {
|
||||
sum += in[m] * cosN36[m][p]
|
||||
}
|
||||
out[p] = sum * iwd[p]
|
||||
}
|
||||
return out
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
// Copyright 2017 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maindata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3/internal/bits"
|
||||
"github.com/hajimehoshi/go-mp3/internal/consts"
|
||||
"github.com/hajimehoshi/go-mp3/internal/frameheader"
|
||||
"github.com/hajimehoshi/go-mp3/internal/huffman"
|
||||
"github.com/hajimehoshi/go-mp3/internal/sideinfo"
|
||||
)
|
||||
|
||||
func readHuffman(m *bits.Bits, header frameheader.FrameHeader, sideInfo *sideinfo.SideInfo, mainData *MainData, part_2_start, gr, ch int) error {
|
||||
// Check that there is any data to decode. If not, zero the array.
|
||||
if sideInfo.Part2_3Length[gr][ch] == 0 {
|
||||
for i := 0; i < consts.SamplesPerGr; i++ {
|
||||
mainData.Is[gr][ch][i] = 0.0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Calculate bit_pos_end which is the index of the last bit for this part.
|
||||
bit_pos_end := part_2_start + sideInfo.Part2_3Length[gr][ch] - 1
|
||||
// Determine region boundaries
|
||||
region_1_start := 0
|
||||
region_2_start := 0
|
||||
if (sideInfo.WinSwitchFlag[gr][ch] == 1) && (sideInfo.BlockType[gr][ch] == 2) {
|
||||
region_1_start = 36 // sfb[9/3]*3=36
|
||||
region_2_start = consts.SamplesPerGr // No Region2 for short block case.
|
||||
} else {
|
||||
sfreq := header.SamplingFrequency()
|
||||
l := consts.SfBandIndicesSet[sfreq].L
|
||||
i := sideInfo.Region0Count[gr][ch] + 1
|
||||
if i < 0 || len(l) <= i {
|
||||
// TODO: Better error messages (#3)
|
||||
return fmt.Errorf("mp3: readHuffman failed: invalid index i: %d", i)
|
||||
}
|
||||
region_1_start = l[i]
|
||||
j := sideInfo.Region0Count[gr][ch] + sideInfo.Region1Count[gr][ch] + 2
|
||||
if j < 0 || len(l) <= j {
|
||||
// TODO: Better error messages (#3)
|
||||
return fmt.Errorf("mp3: readHuffman failed: invalid index j: %d", j)
|
||||
}
|
||||
region_2_start = l[j]
|
||||
}
|
||||
// Read big_values using tables according to region_x_start
|
||||
for is_pos := 0; is_pos < sideInfo.BigValues[gr][ch]*2; is_pos++ {
|
||||
// #22
|
||||
if is_pos >= len(mainData.Is[gr][ch]) {
|
||||
return fmt.Errorf("mp3: is_pos was too big: %d", is_pos)
|
||||
}
|
||||
table_num := 0
|
||||
if is_pos < region_1_start {
|
||||
table_num = sideInfo.TableSelect[gr][ch][0]
|
||||
} else if is_pos < region_2_start {
|
||||
table_num = sideInfo.TableSelect[gr][ch][1]
|
||||
} else {
|
||||
table_num = sideInfo.TableSelect[gr][ch][2]
|
||||
}
|
||||
// Get next Huffman coded words
|
||||
x, y, _, _, err := huffman.Decode(m, table_num)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// In the big_values area there are two freq lines per Huffman word
|
||||
mainData.Is[gr][ch][is_pos] = float32(x)
|
||||
is_pos++
|
||||
mainData.Is[gr][ch][is_pos] = float32(y)
|
||||
}
|
||||
// Read small values until is_pos = 576 or we run out of huffman data
|
||||
// TODO: Is this comment wrong?
|
||||
table_num := sideInfo.Count1TableSelect[gr][ch] + 32
|
||||
is_pos := sideInfo.BigValues[gr][ch] * 2
|
||||
for is_pos <= 572 && m.BitPos() <= bit_pos_end {
|
||||
// Get next Huffman coded words
|
||||
x, y, v, w, err := huffman.Decode(m, table_num)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mainData.Is[gr][ch][is_pos] = float32(v)
|
||||
is_pos++
|
||||
if is_pos >= consts.SamplesPerGr {
|
||||
break
|
||||
}
|
||||
mainData.Is[gr][ch][is_pos] = float32(w)
|
||||
is_pos++
|
||||
if is_pos >= consts.SamplesPerGr {
|
||||
break
|
||||
}
|
||||
mainData.Is[gr][ch][is_pos] = float32(x)
|
||||
is_pos++
|
||||
if is_pos >= consts.SamplesPerGr {
|
||||
break
|
||||
}
|
||||
mainData.Is[gr][ch][is_pos] = float32(y)
|
||||
is_pos++
|
||||
}
|
||||
// Check that we didn't read past the end of this section
|
||||
if m.BitPos() > (bit_pos_end + 1) {
|
||||
// Remove last words read
|
||||
is_pos -= 4
|
||||
}
|
||||
|
||||
// Setup count1 which is the index of the first sample in the rzero reg.
|
||||
sideInfo.Count1[gr][ch] = is_pos
|
||||
|
||||
// Zero out the last part if necessary
|
||||
for is_pos < consts.SamplesPerGr {
|
||||
mainData.Is[gr][ch][is_pos] = 0.0
|
||||
is_pos++
|
||||
}
|
||||
// Set the bitpos to point to the next part to read
|
||||
m.SetPos(bit_pos_end + 1)
|
||||
return nil
|
||||
}
|
@ -1,194 +0,0 @@
|
||||
// Copyright 2017 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maindata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3/internal/bits"
|
||||
"github.com/hajimehoshi/go-mp3/internal/consts"
|
||||
"github.com/hajimehoshi/go-mp3/internal/frameheader"
|
||||
"github.com/hajimehoshi/go-mp3/internal/sideinfo"
|
||||
)
|
||||
|
||||
type FullReader interface {
|
||||
ReadFull([]byte) (int, error)
|
||||
}
|
||||
|
||||
// A MainData is MPEG1 Layer 3 Main Data.
|
||||
type MainData struct {
|
||||
ScalefacL [2][2][22]int // 0-4 bits
|
||||
ScalefacS [2][2][13][3]int // 0-4 bits
|
||||
Is [2][2][576]float32 // Huffman coded freq. lines
|
||||
}
|
||||
|
||||
var scalefacSizes = [16][2]int{
|
||||
{0, 0}, {0, 1}, {0, 2}, {0, 3}, {3, 0}, {1, 1}, {1, 2}, {1, 3},
|
||||
{2, 1}, {2, 2}, {2, 3}, {3, 1}, {3, 2}, {3, 3}, {4, 2}, {4, 3},
|
||||
}
|
||||
|
||||
func Read(source FullReader, prev *bits.Bits, header frameheader.FrameHeader, sideInfo *sideinfo.SideInfo) (*MainData, *bits.Bits, error) {
|
||||
nch := header.NumberOfChannels()
|
||||
// Calculate header audio data size
|
||||
framesize := header.FrameSize()
|
||||
if framesize > 2000 {
|
||||
return nil, nil, fmt.Errorf("mp3: framesize = %d", framesize)
|
||||
}
|
||||
// Sideinfo is 17 bytes for one channel and 32 bytes for two
|
||||
sideinfo_size := 32
|
||||
if nch == 1 {
|
||||
sideinfo_size = 17
|
||||
}
|
||||
// Main data size is the rest of the frame,including ancillary data
|
||||
main_data_size := framesize - sideinfo_size - 4 // sync+header
|
||||
// CRC is 2 bytes
|
||||
if header.ProtectionBit() == 0 {
|
||||
main_data_size -= 2
|
||||
}
|
||||
// Assemble main data buffer with data from this frame and the previous
|
||||
// two frames. main_data_begin indicates how many bytes from previous
|
||||
// frames that should be used. This buffer is later accessed by the
|
||||
// Bits function in the same way as the side info is.
|
||||
m, err := read(source, prev, main_data_size, sideInfo.MainDataBegin)
|
||||
if err != nil {
|
||||
// This could be due to not enough data in reservoir
|
||||
return nil, nil, err
|
||||
}
|
||||
md := &MainData{}
|
||||
for gr := 0; gr < 2; gr++ {
|
||||
for ch := 0; ch < nch; ch++ {
|
||||
part_2_start := m.BitPos()
|
||||
// Number of bits in the bitstream for the bands
|
||||
slen1 := scalefacSizes[sideInfo.ScalefacCompress[gr][ch]][0]
|
||||
slen2 := scalefacSizes[sideInfo.ScalefacCompress[gr][ch]][1]
|
||||
if sideInfo.WinSwitchFlag[gr][ch] == 1 && sideInfo.BlockType[gr][ch] == 2 {
|
||||
if sideInfo.MixedBlockFlag[gr][ch] != 0 {
|
||||
for sfb := 0; sfb < 8; sfb++ {
|
||||
md.ScalefacL[gr][ch][sfb] = m.Bits(slen1)
|
||||
}
|
||||
for sfb := 3; sfb < 12; sfb++ {
|
||||
//slen1 for band 3-5,slen2 for 6-11
|
||||
nbits := slen2
|
||||
if sfb < 6 {
|
||||
nbits = slen1
|
||||
}
|
||||
for win := 0; win < 3; win++ {
|
||||
md.ScalefacS[gr][ch][sfb][win] = m.Bits(nbits)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for sfb := 0; sfb < 12; sfb++ {
|
||||
//slen1 for band 3-5,slen2 for 6-11
|
||||
nbits := slen2
|
||||
if sfb < 6 {
|
||||
nbits = slen1
|
||||
}
|
||||
for win := 0; win < 3; win++ {
|
||||
md.ScalefacS[gr][ch][sfb][win] = m.Bits(nbits)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Scale factor bands 0-5
|
||||
if sideInfo.Scfsi[ch][0] == 0 || gr == 0 {
|
||||
for sfb := 0; sfb < 6; sfb++ {
|
||||
md.ScalefacL[gr][ch][sfb] = m.Bits(slen1)
|
||||
}
|
||||
} else if sideInfo.Scfsi[ch][0] == 1 && gr == 1 {
|
||||
// Copy scalefactors from granule 0 to granule 1
|
||||
// TODO: This is not listed on the spec.
|
||||
for sfb := 0; sfb < 6; sfb++ {
|
||||
md.ScalefacL[1][ch][sfb] = md.ScalefacL[0][ch][sfb]
|
||||
}
|
||||
}
|
||||
// Scale factor bands 6-10
|
||||
if sideInfo.Scfsi[ch][1] == 0 || gr == 0 {
|
||||
for sfb := 6; sfb < 11; sfb++ {
|
||||
md.ScalefacL[gr][ch][sfb] = m.Bits(slen1)
|
||||
}
|
||||
} else if sideInfo.Scfsi[ch][1] == 1 && gr == 1 {
|
||||
// Copy scalefactors from granule 0 to granule 1
|
||||
for sfb := 6; sfb < 11; sfb++ {
|
||||
md.ScalefacL[1][ch][sfb] = md.ScalefacL[0][ch][sfb]
|
||||
}
|
||||
}
|
||||
// Scale factor bands 11-15
|
||||
if sideInfo.Scfsi[ch][2] == 0 || gr == 0 {
|
||||
for sfb := 11; sfb < 16; sfb++ {
|
||||
md.ScalefacL[gr][ch][sfb] = m.Bits(slen2)
|
||||
}
|
||||
} else if sideInfo.Scfsi[ch][2] == 1 && gr == 1 {
|
||||
// Copy scalefactors from granule 0 to granule 1
|
||||
for sfb := 11; sfb < 16; sfb++ {
|
||||
md.ScalefacL[1][ch][sfb] = md.ScalefacL[0][ch][sfb]
|
||||
}
|
||||
}
|
||||
// Scale factor bands 16-20
|
||||
if sideInfo.Scfsi[ch][3] == 0 || gr == 0 {
|
||||
for sfb := 16; sfb < 21; sfb++ {
|
||||
md.ScalefacL[gr][ch][sfb] = m.Bits(slen2)
|
||||
}
|
||||
} else if sideInfo.Scfsi[ch][3] == 1 && gr == 1 {
|
||||
// Copy scalefactors from granule 0 to granule 1
|
||||
for sfb := 16; sfb < 21; sfb++ {
|
||||
md.ScalefacL[1][ch][sfb] = md.ScalefacL[0][ch][sfb]
|
||||
}
|
||||
}
|
||||
}
|
||||
// Read Huffman coded data. Skip stuffing bits.
|
||||
if err := readHuffman(m, header, sideInfo, md, part_2_start, gr, ch); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
// The ancillary data is stored here,but we ignore it.
|
||||
return md, m, nil
|
||||
}
|
||||
|
||||
func read(source FullReader, prev *bits.Bits, size int, offset int) (*bits.Bits, error) {
|
||||
if size > 1500 {
|
||||
return nil, fmt.Errorf("mp3: size = %d", size)
|
||||
}
|
||||
// Check that there's data available from previous frames if needed
|
||||
if prev != nil && offset > prev.LenInBytes() {
|
||||
// No, there is not, so we skip decoding this frame, but we have to
|
||||
// read the main_data bits from the bitstream in case they are needed
|
||||
// for decoding the next frame.
|
||||
buf := make([]byte, size)
|
||||
if n, err := source.ReadFull(buf); n < size {
|
||||
if err == io.EOF {
|
||||
return nil, &consts.UnexpectedEOF{"maindata.Read (1)"}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// TODO: Define a special error and enable to continue the next frame.
|
||||
return bits.Append(prev, buf), nil
|
||||
}
|
||||
// Copy data from previous frames
|
||||
vec := []byte{}
|
||||
if prev != nil {
|
||||
vec = prev.Tail(offset)
|
||||
}
|
||||
// Read the main_data from file
|
||||
buf := make([]byte, size)
|
||||
if n, err := source.ReadFull(buf); n < size {
|
||||
if err == io.EOF {
|
||||
return nil, &consts.UnexpectedEOF{"maindata.Read (2)"}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return bits.New(append(vec, buf...)), nil
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
// Copyright 2017 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sideinfo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3/internal/bits"
|
||||
"github.com/hajimehoshi/go-mp3/internal/consts"
|
||||
"github.com/hajimehoshi/go-mp3/internal/frameheader"
|
||||
)
|
||||
|
||||
type FullReader interface {
|
||||
ReadFull([]byte) (int, error)
|
||||
}
|
||||
|
||||
// A SideInfo is MPEG1 Layer 3 Side Information.
|
||||
// [2][2] means [gr][ch].
|
||||
type SideInfo struct {
|
||||
MainDataBegin int // 9 bits
|
||||
PrivateBits int // 3 bits in mono, 5 in stereo
|
||||
Scfsi [2][4]int // 1 bit
|
||||
Part2_3Length [2][2]int // 12 bits
|
||||
BigValues [2][2]int // 9 bits
|
||||
GlobalGain [2][2]int // 8 bits
|
||||
ScalefacCompress [2][2]int // 4 bits
|
||||
WinSwitchFlag [2][2]int // 1 bit
|
||||
|
||||
BlockType [2][2]int // 2 bits
|
||||
MixedBlockFlag [2][2]int // 1 bit
|
||||
TableSelect [2][2][3]int // 5 bits
|
||||
SubblockGain [2][2][3]int // 3 bits
|
||||
|
||||
Region0Count [2][2]int // 4 bits
|
||||
Region1Count [2][2]int // 3 bits
|
||||
|
||||
Preflag [2][2]int // 1 bit
|
||||
ScalefacScale [2][2]int // 1 bit
|
||||
Count1TableSelect [2][2]int // 1 bit
|
||||
Count1 [2][2]int // Not in file, calc by huffman decoder
|
||||
}
|
||||
|
||||
func Read(source FullReader, header frameheader.FrameHeader) (*SideInfo, error) {
|
||||
nch := header.NumberOfChannels()
|
||||
// Calculate header audio data size
|
||||
framesize := header.FrameSize()
|
||||
if framesize > 2000 {
|
||||
return nil, fmt.Errorf("mp3: framesize = %d\n", framesize)
|
||||
}
|
||||
// Sideinfo is 17 bytes for one channel and 32 bytes for two
|
||||
sideinfo_size := 32
|
||||
if nch == 1 {
|
||||
sideinfo_size = 17
|
||||
}
|
||||
// Main data size is the rest of the frame,including ancillary data
|
||||
main_data_size := framesize - sideinfo_size - 4 // sync+header
|
||||
// CRC is 2 bytes
|
||||
if header.ProtectionBit() == 0 {
|
||||
main_data_size -= 2
|
||||
}
|
||||
// Read sideinfo from bitstream into buffer used by Bits()
|
||||
buf := make([]byte, sideinfo_size)
|
||||
n, err := source.ReadFull(buf)
|
||||
if n < sideinfo_size {
|
||||
if err == io.EOF {
|
||||
return nil, &consts.UnexpectedEOF{"sideinfo.Read"}
|
||||
}
|
||||
return nil, fmt.Errorf("mp3: couldn't read sideinfo %d bytes: %v", sideinfo_size, err)
|
||||
}
|
||||
s := bits.New(buf)
|
||||
|
||||
// Parse audio data
|
||||
// Pointer to where we should start reading main data
|
||||
si := &SideInfo{}
|
||||
si.MainDataBegin = s.Bits(9)
|
||||
// Get private bits. Not used for anything.
|
||||
if header.Mode() == consts.ModeSingleChannel {
|
||||
si.PrivateBits = s.Bits(5)
|
||||
} else {
|
||||
si.PrivateBits = s.Bits(3)
|
||||
}
|
||||
// Get scale factor selection information
|
||||
for ch := 0; ch < nch; ch++ {
|
||||
for scfsi_band := 0; scfsi_band < 4; scfsi_band++ {
|
||||
si.Scfsi[ch][scfsi_band] = s.Bits(1)
|
||||
}
|
||||
}
|
||||
// Get the rest of the side information
|
||||
for gr := 0; gr < 2; gr++ {
|
||||
for ch := 0; ch < nch; ch++ {
|
||||
si.Part2_3Length[gr][ch] = s.Bits(12)
|
||||
si.BigValues[gr][ch] = s.Bits(9)
|
||||
si.GlobalGain[gr][ch] = s.Bits(8)
|
||||
si.ScalefacCompress[gr][ch] = s.Bits(4)
|
||||
si.WinSwitchFlag[gr][ch] = s.Bits(1)
|
||||
if si.WinSwitchFlag[gr][ch] == 1 {
|
||||
si.BlockType[gr][ch] = s.Bits(2)
|
||||
si.MixedBlockFlag[gr][ch] = s.Bits(1)
|
||||
for region := 0; region < 2; region++ {
|
||||
si.TableSelect[gr][ch][region] = s.Bits(5)
|
||||
}
|
||||
for window := 0; window < 3; window++ {
|
||||
si.SubblockGain[gr][ch][window] = s.Bits(3)
|
||||
}
|
||||
|
||||
// TODO: This is not listed on the spec. Is this correct??
|
||||
if si.BlockType[gr][ch] == 2 && si.MixedBlockFlag[gr][ch] == 0 {
|
||||
si.Region0Count[gr][ch] = 8 // Implicit
|
||||
} else {
|
||||
si.Region0Count[gr][ch] = 7 // Implicit
|
||||
}
|
||||
// The standard is wrong on this!!!
|
||||
// Implicit
|
||||
si.Region1Count[gr][ch] = 20 - si.Region0Count[gr][ch]
|
||||
} else {
|
||||
for region := 0; region < 3; region++ {
|
||||
si.TableSelect[gr][ch][region] = s.Bits(5)
|
||||
}
|
||||
si.Region0Count[gr][ch] = s.Bits(4)
|
||||
si.Region1Count[gr][ch] = s.Bits(3)
|
||||
si.BlockType[gr][ch] = 0 // Implicit
|
||||
}
|
||||
si.Preflag[gr][ch] = s.Bits(1)
|
||||
si.ScalefacScale[gr][ch] = s.Bits(1)
|
||||
si.Count1TableSelect[gr][ch] = s.Bits(1)
|
||||
}
|
||||
}
|
||||
return si, nil
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
// Copyright 2017 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mp3
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type source struct {
|
||||
reader io.ReadCloser
|
||||
buf []byte
|
||||
pos int64
|
||||
}
|
||||
|
||||
func (s *source) Seek(position int64, whence int) (int64, error) {
|
||||
seeker, ok := s.reader.(io.Seeker)
|
||||
if !ok {
|
||||
panic("mp3: source must be io.Seeker")
|
||||
}
|
||||
s.buf = nil
|
||||
n, err := seeker.Seek(position, whence)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
s.pos = n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (s *source) Close() error {
|
||||
s.buf = nil
|
||||
return s.reader.Close()
|
||||
}
|
||||
|
||||
func (s *source) skipTags() error {
|
||||
buf := make([]byte, 3)
|
||||
if _, err := s.ReadFull(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
switch string(buf) {
|
||||
case "TAG":
|
||||
buf := make([]byte, 125)
|
||||
if _, err := s.ReadFull(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case "ID3":
|
||||
// Skip version (2 bytes) and flag (1 byte)
|
||||
buf := make([]byte, 3)
|
||||
if _, err := s.ReadFull(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf = make([]byte, 4)
|
||||
n, err := s.ReadFull(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != 4 {
|
||||
return nil
|
||||
}
|
||||
size := (uint32(buf[0]) << 21) | (uint32(buf[1]) << 14) |
|
||||
(uint32(buf[2]) << 7) | uint32(buf[3])
|
||||
buf = make([]byte, size)
|
||||
if _, err := s.ReadFull(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
s.Unread(buf)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *source) rewind() error {
|
||||
if _, err := s.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
s.pos = 0
|
||||
s.buf = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *source) Unread(buf []byte) {
|
||||
s.buf = append(s.buf, buf...)
|
||||
s.pos -= int64(len(buf))
|
||||
}
|
||||
|
||||
func (s *source) ReadFull(buf []byte) (int, error) {
|
||||
read := 0
|
||||
if s.buf != nil {
|
||||
read = copy(buf, s.buf)
|
||||
if len(s.buf) > read {
|
||||
s.buf = s.buf[read:]
|
||||
} else {
|
||||
s.buf = nil
|
||||
}
|
||||
if len(buf) == read {
|
||||
return read, nil
|
||||
}
|
||||
}
|
||||
|
||||
n, err := io.ReadFull(s.reader, buf[read:])
|
||||
if err != nil {
|
||||
// Allow if all data can't be read. This is common.
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
err = io.EOF
|
||||
}
|
||||
}
|
||||
s.pos += int64(n)
|
||||
return n + read, err
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
.DS_Store
|
||||
*~
|
@ -1,5 +0,0 @@
|
||||
Christopher Cooper <chris@getfreebird.com>
|
||||
Hajime Hoshi <hajimehoshi@gmail.com>
|
||||
Medusalix <8124898+medusalix@users.noreply.github.com>
|
||||
Michal Štrba <faiface2202@gmail.com>
|
||||
Yosuke Akatsuka <yosuke.akatsuka@gmail.com>
|
@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -1,35 +0,0 @@
|
||||
# Oto (音)
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/hajimehoshi/oto?status.svg)](http://godoc.org/github.com/hajimehoshi/oto)
|
||||
|
||||
A low-level library to play sound. This package offers `io.WriteCloser` to play PCM sound.
|
||||
|
||||
## Platforms
|
||||
|
||||
* Windows
|
||||
* macOS
|
||||
* Linux
|
||||
* FreeBSD
|
||||
* Android
|
||||
* iOS
|
||||
* Web browsers ([GopherJS](https://github.com/gopherjs/gopherjs) and WebAssembly)
|
||||
|
||||
## Prerequisite
|
||||
|
||||
### Linux
|
||||
|
||||
libasound2-dev is required. On Ubuntu or Debian, run this command:
|
||||
|
||||
```sh
|
||||
apt install libasound2-dev
|
||||
```
|
||||
|
||||
In most cases this command must be run by root user or through `sudo` command.
|
||||
|
||||
### FreeBSD
|
||||
|
||||
OpenAL is required. Install openal-soft:
|
||||
|
||||
```sh
|
||||
pkg install openal-soft
|
||||
```
|
@ -1,164 +0,0 @@
|
||||
// Copyright 2019 The Oto Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/oto/internal/mux"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
driverWriter *driverWriter
|
||||
mux *mux.Mux
|
||||
errCh chan error
|
||||
}
|
||||
|
||||
var (
|
||||
theContext *Context
|
||||
contextM sync.Mutex
|
||||
)
|
||||
|
||||
var errClosed = errors.New("closed")
|
||||
|
||||
// NewContext creates a new context, that creates and holds ready-to-use Player objects.
|
||||
//
|
||||
// The sampleRate argument specifies the number of samples that should be played during one second.
|
||||
// Usual numbers are 44100 or 48000.
|
||||
//
|
||||
// The channelNum argument specifies the number of channels. One channel is mono playback. Two
|
||||
// channels are stereo playback. No other values are supported.
|
||||
//
|
||||
// The bitDepthInBytes argument specifies the number of bytes per sample per channel. The usual value
|
||||
// is 2. Only values 1 and 2 are supported.
|
||||
//
|
||||
// The bufferSizeInBytes argument specifies the size of the buffer of the Context. This means, how
|
||||
// many bytes can Context remember before actually playing them. Bigger buffer can reduce the number
|
||||
// of Player's Write calls, thus reducing CPU time. Smaller buffer enables more precise timing. The
|
||||
// longest delay between when samples were written and when they started playing is equal to the size
|
||||
// of the buffer.
|
||||
func NewContext(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes int) (*Context, error) {
|
||||
contextM.Lock()
|
||||
defer contextM.Unlock()
|
||||
|
||||
if theContext != nil {
|
||||
panic("oto: NewContext can be called only once")
|
||||
}
|
||||
|
||||
d, err := newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dw := &driverWriter{
|
||||
driver: d,
|
||||
bufferSize: bufferSizeInBytes,
|
||||
bytesPerSecond: sampleRate * channelNum * bitDepthInBytes,
|
||||
}
|
||||
c := &Context{
|
||||
driverWriter: dw,
|
||||
mux: mux.New(channelNum, bitDepthInBytes),
|
||||
errCh: make(chan error),
|
||||
}
|
||||
theContext = c
|
||||
go func() {
|
||||
if _, err := io.Copy(c.driverWriter, c.mux); err != nil {
|
||||
c.errCh <- err
|
||||
}
|
||||
close(c.errCh)
|
||||
}()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// NewPlayer is a short-hand of creating a Context by NewContext and a Player by the context's NewPlayer.
|
||||
func NewPlayer(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes int) (*Player, error) {
|
||||
c, err := NewContext(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.NewPlayer(), nil
|
||||
}
|
||||
|
||||
// NewPlayer creates a new, ready-to-use Player belonging to the Context.
|
||||
func (c *Context) NewPlayer() *Player {
|
||||
p := newPlayer(c)
|
||||
c.mux.AddSource(p.r)
|
||||
return p
|
||||
}
|
||||
|
||||
// Close closes the Context and its Players and frees any resources associated with it. The Context is no longer
|
||||
// usable after calling Close.
|
||||
func (c *Context) Close() error {
|
||||
contextM.Lock()
|
||||
theContext = nil
|
||||
contextM.Unlock()
|
||||
|
||||
if err := c.driverWriter.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.mux.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-c.errCh
|
||||
}
|
||||
|
||||
type driverWriter struct {
|
||||
driver *driver
|
||||
bufferSize int
|
||||
bytesPerSecond int
|
||||
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
func (d *driverWriter) Write(buf []byte) (int, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
|
||||
written := 0
|
||||
for len(buf) > 0 {
|
||||
if d.driver == nil {
|
||||
return written, errClosed
|
||||
}
|
||||
n, err := d.driver.TryWrite(buf)
|
||||
written += n
|
||||
if err != nil {
|
||||
return written, err
|
||||
}
|
||||
buf = buf[n:]
|
||||
// When not all buf is written, the underlying buffer is full.
|
||||
// Mitigate the busy loop by sleeping (#10).
|
||||
if len(buf) > 0 {
|
||||
t := time.Second * time.Duration(d.bufferSize) / time.Duration(d.bytesPerSecond) / 8
|
||||
time.Sleep(t)
|
||||
}
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
|
||||
func (d *driverWriter) Close() error {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
|
||||
// Close should be wait until the buffer data is consumed (#36).
|
||||
// This is the simplest (but ugly) fix.
|
||||
// TODO: Implement player's Close to wait the buffer played.
|
||||
time.Sleep(time.Second * time.Duration(d.bufferSize) / time.Duration(d.bytesPerSecond))
|
||||
if err := d.driver.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,291 +0,0 @@
|
||||
// Copyright 2016 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oto
|
||||
|
||||
/*
|
||||
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static jclass android_media_AudioFormat;
|
||||
static jclass android_media_AudioManager;
|
||||
static jclass android_media_AudioTrack;
|
||||
|
||||
static char* initAudioTrack(uintptr_t java_vm, uintptr_t jni_env,
|
||||
int sampleRate, int channelNum, int bitDepthInBytes, jobject* audioTrack, int bufferSize) {
|
||||
JavaVM* vm = (JavaVM*)java_vm;
|
||||
JNIEnv* env = (JNIEnv*)jni_env;
|
||||
|
||||
jclass local = (*env)->FindClass(env, "android/media/AudioFormat");
|
||||
android_media_AudioFormat = (*env)->NewGlobalRef(env, local);
|
||||
(*env)->DeleteLocalRef(env, local);
|
||||
|
||||
local = (*env)->FindClass(env, "android/media/AudioManager");
|
||||
android_media_AudioManager = (*env)->NewGlobalRef(env, local);
|
||||
(*env)->DeleteLocalRef(env, local);
|
||||
|
||||
local = (*env)->FindClass(env, "android/media/AudioTrack");
|
||||
android_media_AudioTrack = (*env)->NewGlobalRef(env, local);
|
||||
(*env)->DeleteLocalRef(env, local);
|
||||
|
||||
const jint android_media_AudioManager_STREAM_MUSIC =
|
||||
(*env)->GetStaticIntField(
|
||||
env, android_media_AudioManager,
|
||||
(*env)->GetStaticFieldID(env, android_media_AudioManager, "STREAM_MUSIC", "I"));
|
||||
const jint android_media_AudioTrack_MODE_STREAM =
|
||||
(*env)->GetStaticIntField(
|
||||
env, android_media_AudioTrack,
|
||||
(*env)->GetStaticFieldID(env, android_media_AudioTrack, "MODE_STREAM", "I"));
|
||||
const jint android_media_AudioFormat_CHANNEL_OUT_MONO =
|
||||
(*env)->GetStaticIntField(
|
||||
env, android_media_AudioFormat,
|
||||
(*env)->GetStaticFieldID(env, android_media_AudioFormat, "CHANNEL_OUT_MONO", "I"));
|
||||
const jint android_media_AudioFormat_CHANNEL_OUT_STEREO =
|
||||
(*env)->GetStaticIntField(
|
||||
env, android_media_AudioFormat,
|
||||
(*env)->GetStaticFieldID(env, android_media_AudioFormat, "CHANNEL_OUT_STEREO", "I"));
|
||||
const jint android_media_AudioFormat_ENCODING_PCM_8BIT =
|
||||
(*env)->GetStaticIntField(
|
||||
env, android_media_AudioFormat,
|
||||
(*env)->GetStaticFieldID(env, android_media_AudioFormat, "ENCODING_PCM_8BIT", "I"));
|
||||
const jint android_media_AudioFormat_ENCODING_PCM_16BIT =
|
||||
(*env)->GetStaticIntField(
|
||||
env, android_media_AudioFormat,
|
||||
(*env)->GetStaticFieldID(env, android_media_AudioFormat, "ENCODING_PCM_16BIT", "I"));
|
||||
|
||||
jint channel = android_media_AudioFormat_CHANNEL_OUT_MONO;
|
||||
switch (channelNum) {
|
||||
case 1:
|
||||
channel = android_media_AudioFormat_CHANNEL_OUT_MONO;
|
||||
break;
|
||||
case 2:
|
||||
channel = android_media_AudioFormat_CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
default:
|
||||
return "invalid channel";
|
||||
}
|
||||
|
||||
jint encoding = android_media_AudioFormat_ENCODING_PCM_8BIT;
|
||||
switch (bitDepthInBytes) {
|
||||
case 1:
|
||||
encoding = android_media_AudioFormat_ENCODING_PCM_8BIT;
|
||||
break;
|
||||
case 2:
|
||||
encoding = android_media_AudioFormat_ENCODING_PCM_16BIT;
|
||||
break;
|
||||
default:
|
||||
return "invalid bitDepthInBytes";
|
||||
}
|
||||
|
||||
const jobject tmpAudioTrack =
|
||||
(*env)->NewObject(
|
||||
env, android_media_AudioTrack,
|
||||
(*env)->GetMethodID(env, android_media_AudioTrack, "<init>", "(IIIIII)V"),
|
||||
android_media_AudioManager_STREAM_MUSIC,
|
||||
sampleRate, channel, encoding, bufferSize,
|
||||
android_media_AudioTrack_MODE_STREAM);
|
||||
*audioTrack = (*env)->NewGlobalRef(env, tmpAudioTrack);
|
||||
(*env)->DeleteLocalRef(env, tmpAudioTrack);
|
||||
|
||||
(*env)->CallVoidMethod(
|
||||
env, *audioTrack,
|
||||
(*env)->GetMethodID(env, android_media_AudioTrack, "play", "()V"));
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char* writeToAudioTrack(uintptr_t java_vm, uintptr_t jni_env,
|
||||
jobject audioTrack, int bitDepthInBytes, void* data, int length) {
|
||||
JavaVM* vm = (JavaVM*)java_vm;
|
||||
JNIEnv* env = (JNIEnv*)jni_env;
|
||||
|
||||
jbyteArray arrInBytes;
|
||||
jshortArray arrInShorts;
|
||||
switch (bitDepthInBytes) {
|
||||
case 1:
|
||||
arrInBytes = (*env)->NewByteArray(env, length);
|
||||
(*env)->SetByteArrayRegion(env, arrInBytes, 0, length, data);
|
||||
break;
|
||||
case 2:
|
||||
arrInShorts = (*env)->NewShortArray(env, length);
|
||||
(*env)->SetShortArrayRegion(env, arrInShorts, 0, length, data);
|
||||
break;
|
||||
}
|
||||
|
||||
jint result;
|
||||
static jmethodID write1 = NULL;
|
||||
static jmethodID write2 = NULL;
|
||||
if (!write1) {
|
||||
write1 = (*env)->GetMethodID(env, android_media_AudioTrack, "write", "([BII)I");
|
||||
}
|
||||
if (!write2) {
|
||||
write2 = (*env)->GetMethodID(env, android_media_AudioTrack, "write", "([SII)I");
|
||||
}
|
||||
switch (bitDepthInBytes) {
|
||||
case 1:
|
||||
result = (*env)->CallIntMethod(env, audioTrack, write1, arrInBytes, 0, length);
|
||||
(*env)->DeleteLocalRef(env, arrInBytes);
|
||||
break;
|
||||
case 2:
|
||||
result = (*env)->CallIntMethod(env, audioTrack, write2, arrInShorts, 0, length);
|
||||
(*env)->DeleteLocalRef(env, arrInShorts);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (result) {
|
||||
case -3: // ERROR_INVALID_OPERATION
|
||||
return "invalid operation";
|
||||
case -2: // ERROR_BAD_VALUE
|
||||
return "bad value";
|
||||
case -1: // ERROR
|
||||
return "error";
|
||||
}
|
||||
if (result < 0) {
|
||||
return "unknown error";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char* releaseAudioTrack(uintptr_t java_vm, uintptr_t jni_env,
|
||||
jobject audioTrack) {
|
||||
JavaVM* vm = (JavaVM*)java_vm;
|
||||
JNIEnv* env = (JNIEnv*)jni_env;
|
||||
|
||||
(*env)->CallVoidMethod(
|
||||
env, audioTrack,
|
||||
(*env)->GetMethodID(env, android_media_AudioTrack, "release", "()V"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/mobile/app"
|
||||
)
|
||||
|
||||
type driver struct {
|
||||
sampleRate int
|
||||
channelNum int
|
||||
bitDepthInBytes int
|
||||
audioTrack C.jobject
|
||||
chErr chan error
|
||||
chBuffer chan []byte
|
||||
tmp []byte
|
||||
bufferSize int
|
||||
}
|
||||
|
||||
func newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes int) (*driver, error) {
|
||||
p := &driver{
|
||||
sampleRate: sampleRate,
|
||||
channelNum: channelNum,
|
||||
bitDepthInBytes: bitDepthInBytes,
|
||||
chErr: make(chan error),
|
||||
chBuffer: make(chan []byte),
|
||||
}
|
||||
runtime.SetFinalizer(p, (*driver).Close)
|
||||
|
||||
if err := app.RunOnJVM(func(vm, env, ctx uintptr) error {
|
||||
audioTrack := C.jobject(0)
|
||||
bufferSize := C.int(bufferSizeInBytes)
|
||||
if msg := C.initAudioTrack(C.uintptr_t(vm), C.uintptr_t(env),
|
||||
C.int(sampleRate), C.int(channelNum), C.int(bitDepthInBytes),
|
||||
&audioTrack, bufferSize); msg != nil {
|
||||
return errors.New("oto: initAutioTrack failed: " + C.GoString(msg))
|
||||
}
|
||||
p.audioTrack = audioTrack
|
||||
p.bufferSize = int(bufferSize)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go p.loop()
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *driver) loop() {
|
||||
for bufInBytes := range p.chBuffer {
|
||||
var bufInShorts []int16
|
||||
if p.bitDepthInBytes == 2 {
|
||||
bufInShorts = make([]int16, len(bufInBytes)/2)
|
||||
for i := 0; i < len(bufInShorts); i++ {
|
||||
bufInShorts[i] = int16(bufInBytes[2*i]) | (int16(bufInBytes[2*i+1]) << 8)
|
||||
}
|
||||
}
|
||||
if err := app.RunOnJVM(func(vm, env, ctx uintptr) error {
|
||||
msg := (*C.char)(nil)
|
||||
switch p.bitDepthInBytes {
|
||||
case 1:
|
||||
msg = C.writeToAudioTrack(C.uintptr_t(vm), C.uintptr_t(env),
|
||||
p.audioTrack, C.int(p.bitDepthInBytes),
|
||||
unsafe.Pointer(&bufInBytes[0]), C.int(len(bufInBytes)))
|
||||
case 2:
|
||||
msg = C.writeToAudioTrack(C.uintptr_t(vm), C.uintptr_t(env),
|
||||
p.audioTrack, C.int(p.bitDepthInBytes),
|
||||
unsafe.Pointer(&bufInShorts[0]), C.int(len(bufInShorts)))
|
||||
default:
|
||||
panic("not reach")
|
||||
}
|
||||
if msg != nil {
|
||||
return errors.New("oto: loop failed: " + C.GoString(msg))
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
p.chErr <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *driver) TryWrite(data []byte) (int, error) {
|
||||
n := min(len(data), p.bufferSize-len(p.tmp))
|
||||
p.tmp = append(p.tmp, data[:n]...)
|
||||
|
||||
if len(p.tmp) < p.bufferSize {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
select {
|
||||
case p.chBuffer <- p.tmp:
|
||||
case err := <-p.chErr:
|
||||
return 0, err
|
||||
}
|
||||
|
||||
p.tmp = nil
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (p *driver) Close() error {
|
||||
if p.audioTrack == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
runtime.SetFinalizer(p, nil)
|
||||
err := app.RunOnJVM(func(vm, env, ctx uintptr) error {
|
||||
if msg := C.releaseAudioTrack(C.uintptr_t(vm), C.uintptr_t(env),
|
||||
p.audioTrack); msg != nil {
|
||||
return errors.New("oto: release failed: " + C.GoString(msg))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
p.audioTrack = 0
|
||||
return err
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
// Copyright 2015 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build js
|
||||
|
||||
package oto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/gopherjs/gopherwasm/js"
|
||||
)
|
||||
|
||||
type driver struct {
|
||||
sampleRate int
|
||||
channelNum int
|
||||
bitDepthInBytes int
|
||||
nextPos float64
|
||||
tmp []byte
|
||||
bufferSize int
|
||||
context js.Value
|
||||
lastTime float64
|
||||
lastAudioTime float64
|
||||
ready bool
|
||||
}
|
||||
|
||||
const audioBufferSamples = 3200
|
||||
|
||||
func newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSize int) (*driver, error) {
|
||||
class := js.Global().Get("AudioContext")
|
||||
if class == js.Undefined() {
|
||||
class = js.Global().Get("webkitAudioContext")
|
||||
}
|
||||
if class == js.Undefined() {
|
||||
return nil, errors.New("oto: audio couldn't be initialized")
|
||||
}
|
||||
p := &driver{
|
||||
sampleRate: sampleRate,
|
||||
channelNum: channelNum,
|
||||
bitDepthInBytes: bitDepthInBytes,
|
||||
context: class.New(),
|
||||
bufferSize: max(bufferSize, audioBufferSamples*channelNum*bitDepthInBytes),
|
||||
}
|
||||
|
||||
setCallback := func(event string) {
|
||||
var f js.Callback
|
||||
f = js.NewCallback(func(arguments []js.Value) {
|
||||
if !p.ready {
|
||||
p.context.Call("resume")
|
||||
p.ready = true
|
||||
}
|
||||
js.Global().Get("document").Call("removeEventListener", event, f)
|
||||
})
|
||||
js.Global().Get("document").Call("addEventListener", event, f)
|
||||
}
|
||||
|
||||
// Browsers require user interaction to start the audio.
|
||||
// https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#webaudio
|
||||
setCallback("touchend")
|
||||
setCallback("keyup")
|
||||
setCallback("mouseup")
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func toLR(data []byte) ([]float32, []float32) {
|
||||
const max = 1 << 15
|
||||
|
||||
l := make([]float32, len(data)/4)
|
||||
r := make([]float32, len(data)/4)
|
||||
for i := 0; i < len(data)/4; i++ {
|
||||
l[i] = float32(int16(data[4*i])|int16(data[4*i+1])<<8) / max
|
||||
r[i] = float32(int16(data[4*i+2])|int16(data[4*i+3])<<8) / max
|
||||
}
|
||||
return l, r
|
||||
}
|
||||
|
||||
func nowInSeconds() float64 {
|
||||
return js.Global().Get("performance").Call("now").Float() / 1000.0
|
||||
}
|
||||
|
||||
func (p *driver) TryWrite(data []byte) (int, error) {
|
||||
if !p.ready {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
n := min(len(data), max(0, p.bufferSize-len(p.tmp)))
|
||||
p.tmp = append(p.tmp, data[:n]...)
|
||||
|
||||
c := p.context.Get("currentTime").Float()
|
||||
now := nowInSeconds()
|
||||
|
||||
if p.lastTime != 0 && p.lastAudioTime != 0 && p.lastAudioTime >= c && p.lastTime != now {
|
||||
// Unfortunately, currentTime might not be precise enough on some devices
|
||||
// (e.g. Android Chrome). Adjust the audio time with OS clock.
|
||||
c = p.lastAudioTime + now - p.lastTime
|
||||
}
|
||||
|
||||
p.lastAudioTime = c
|
||||
p.lastTime = now
|
||||
|
||||
if p.nextPos < c {
|
||||
p.nextPos = c
|
||||
}
|
||||
|
||||
// It's too early to enqueue a buffer.
|
||||
// Highly likely, there are two playing buffers now.
|
||||
if c+float64(p.bufferSize/p.bitDepthInBytes/p.channelNum)/float64(p.sampleRate) < p.nextPos {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
le := audioBufferSamples * p.bitDepthInBytes * p.channelNum
|
||||
if len(p.tmp) < le {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
buf := p.context.Call("createBuffer", p.channelNum, audioBufferSamples, p.sampleRate)
|
||||
l, r := toLR(p.tmp[:le])
|
||||
tl := js.TypedArrayOf(l)
|
||||
tr := js.TypedArrayOf(r)
|
||||
if buf.Get("copyToChannel") != js.Undefined() {
|
||||
buf.Call("copyToChannel", tl, 0, 0)
|
||||
buf.Call("copyToChannel", tr, 1, 0)
|
||||
} else {
|
||||
// copyToChannel is not defined on Safari 11
|
||||
buf.Call("getChannelData", 0).Call("set", tl)
|
||||
buf.Call("getChannelData", 1).Call("set", tr)
|
||||
}
|
||||
tl.Release()
|
||||
tr.Release()
|
||||
|
||||
s := p.context.Call("createBufferSource")
|
||||
s.Set("buffer", buf)
|
||||
s.Call("connect", p.context.Get("destination"))
|
||||
s.Call("start", p.nextPos)
|
||||
p.nextPos += buf.Get("duration").Float()
|
||||
|
||||
p.tmp = p.tmp[le:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (p *driver) Close() error {
|
||||
return nil
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
// Copyright 2017 The Oto Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !js
|
||||
// +build !android
|
||||
// +build !ios
|
||||
|
||||
package oto
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lasound
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
static void check(int *err, int newErr) {
|
||||
if (*err) {
|
||||
return;
|
||||
}
|
||||
*err = newErr;
|
||||
}
|
||||
|
||||
static int ALSA_hw_params(
|
||||
snd_pcm_t *pcm,
|
||||
unsigned sampleRate,
|
||||
unsigned numChans,
|
||||
snd_pcm_format_t format,
|
||||
snd_pcm_uframes_t* buffer_size,
|
||||
snd_pcm_uframes_t* period_size) {
|
||||
snd_pcm_hw_params_t* params = NULL;
|
||||
int err = 0;
|
||||
snd_pcm_hw_params_alloca(¶ms);
|
||||
check(&err, snd_pcm_hw_params_any(pcm, params));
|
||||
|
||||
check(&err, snd_pcm_hw_params_set_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED));
|
||||
check(&err, snd_pcm_hw_params_set_format(pcm, params, format));
|
||||
check(&err, snd_pcm_hw_params_set_channels(pcm, params, numChans));
|
||||
check(&err, snd_pcm_hw_params_set_rate_resample(pcm, params, 1));
|
||||
check(&err, snd_pcm_hw_params_set_rate_near(pcm, params, &sampleRate, NULL));
|
||||
check(&err, snd_pcm_hw_params_set_buffer_size_near(pcm, params, buffer_size));
|
||||
check(&err, snd_pcm_hw_params_set_period_size_near(pcm, params, period_size, NULL));
|
||||
|
||||
check(&err, snd_pcm_hw_params(pcm, params));
|
||||
|
||||
return err;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type driver struct {
|
||||
handle *C.snd_pcm_t
|
||||
buf []byte
|
||||
bufSamples int
|
||||
numChans int
|
||||
bitDepthInBytes int
|
||||
}
|
||||
|
||||
func alsaError(err C.int) error {
|
||||
return fmt.Errorf("oto: ALSA error: %s", C.GoString(C.snd_strerror(err)))
|
||||
}
|
||||
|
||||
func newDriver(sampleRate, numChans, bitDepthInBytes, bufferSizeInBytes int) (*driver, error) {
|
||||
p := &driver{
|
||||
numChans: numChans,
|
||||
bitDepthInBytes: bitDepthInBytes,
|
||||
}
|
||||
|
||||
// open a default ALSA audio device for blocking stream playback
|
||||
if errCode := C.snd_pcm_open(&p.handle, C.CString("default"), C.SND_PCM_STREAM_PLAYBACK, 0); errCode < 0 {
|
||||
return nil, alsaError(errCode)
|
||||
}
|
||||
|
||||
// bufferSize is the total size of the main circular buffer fullness of this buffer
|
||||
// oscilates somewhere between bufferSize and bufferSize-periodSize
|
||||
bufferSize := C.snd_pcm_uframes_t(bufferSizeInBytes / (numChans * bitDepthInBytes))
|
||||
// periodSize is the number of samples that will be taken from the main circular
|
||||
// buffer at once, we leave this value to bufferSize, because ALSA will change that
|
||||
// to the maximum viable number, obviously lower than bufferSize
|
||||
periodSize := bufferSize
|
||||
|
||||
// choose the correct sample format according to bitDepthInBytes
|
||||
var format C.snd_pcm_format_t
|
||||
switch bitDepthInBytes {
|
||||
case 1:
|
||||
format = C.SND_PCM_FORMAT_S8
|
||||
case 2:
|
||||
format = C.SND_PCM_FORMAT_S16_LE
|
||||
default:
|
||||
panic(fmt.Errorf("oto: bitDepthInBytes must be 1 or 2, got %d", bitDepthInBytes))
|
||||
}
|
||||
|
||||
// set the device hardware parameters according to sampleRate, numChans, format, bufferSize
|
||||
// and periodSize
|
||||
//
|
||||
// bufferSize and periodSize are passed as pointers, because they may be changed according
|
||||
// to the wisdom of ALSA
|
||||
//
|
||||
// ALSA will try too keep them as close to what was requested as possible
|
||||
if errCode := C.ALSA_hw_params(p.handle, C.uint(sampleRate), C.uint(numChans), format, &bufferSize, &periodSize); errCode < 0 {
|
||||
p.Close()
|
||||
return nil, alsaError(errCode)
|
||||
}
|
||||
|
||||
// allocate the buffer of the size of the period, use the periodSize that we've got back
|
||||
// from ALSA after it's wise decision
|
||||
p.bufSamples = int(periodSize)
|
||||
p.buf = []byte{}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *driver) TryWrite(data []byte) (n int, err error) {
|
||||
bufSize := p.bufSamples * p.numChans * p.bitDepthInBytes
|
||||
for len(data) > 0 {
|
||||
toWrite := min(len(data), max(0, bufSize-len(p.buf)))
|
||||
p.buf = append(p.buf, data[:toWrite]...)
|
||||
data = data[toWrite:]
|
||||
n += toWrite
|
||||
|
||||
// our buffer is not full and we've used up all the data, we'll keep them and finish
|
||||
if len(p.buf) < bufSize {
|
||||
break
|
||||
}
|
||||
|
||||
// write samples to the main circular buffer
|
||||
wrote := C.snd_pcm_writei(p.handle, unsafe.Pointer(&p.buf[0]), C.snd_pcm_uframes_t(p.bufSamples))
|
||||
if wrote == -C.EPIPE {
|
||||
// Underrun!
|
||||
if errCode := C.snd_pcm_prepare(p.handle); errCode < 0 {
|
||||
return 0, alsaError(errCode)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if wrote < 0 {
|
||||
// an error occured while writing samples
|
||||
return 0, alsaError(C.int(wrote))
|
||||
}
|
||||
p.buf = p.buf[int(wrote)*p.numChans*p.bitDepthInBytes:]
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (p *driver) Close() error {
|
||||
// drop the remaining unprocessed samples in the main circular buffer
|
||||
if errCode := C.snd_pcm_drop(p.handle); errCode < 0 {
|
||||
return alsaError(errCode)
|
||||
}
|
||||
if errCode := C.snd_pcm_close(p.handle); errCode < 0 {
|
||||
return alsaError(errCode)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,258 +0,0 @@
|
||||
// Copyright 2015 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build darwin freebsd
|
||||
// +build !js
|
||||
// +build !android
|
||||
|
||||
package oto
|
||||
|
||||
// #cgo darwin LDFLAGS: -framework OpenAL
|
||||
// #cgo freebsd LDFLAGS: -lopenal
|
||||
//
|
||||
// #include <stdint.h>
|
||||
//
|
||||
// #ifdef __APPLE__
|
||||
// #include <OpenAL/al.h>
|
||||
// #include <OpenAL/alc.h>
|
||||
// #else
|
||||
// #include <AL/al.h>
|
||||
// #include <AL/alc.h>
|
||||
// #endif
|
||||
//
|
||||
// static uintptr_t _alcOpenDevice(const ALCchar* name) {
|
||||
// return (uintptr_t)alcOpenDevice(name);
|
||||
// }
|
||||
//
|
||||
// static ALCboolean _alcCloseDevice(uintptr_t device) {
|
||||
// return alcCloseDevice((void*)device);
|
||||
// }
|
||||
//
|
||||
// static uintptr_t _alcCreateContext(uintptr_t device, const ALCint* attrList) {
|
||||
// return (uintptr_t)alcCreateContext((void*)device, attrList);
|
||||
// }
|
||||
//
|
||||
// static ALCenum _alcGetError(uintptr_t device) {
|
||||
// return alcGetError((void*)device);
|
||||
// }
|
||||
//
|
||||
// static void _alcMakeContextCurrent(uintptr_t context) {
|
||||
// alcMakeContextCurrent((void*)context);
|
||||
// }
|
||||
//
|
||||
// static void _alcDestroyContext(uintptr_t context) {
|
||||
// alcDestroyContext((void*)context);
|
||||
// }
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// As x/mobile/exp/audio/al is broken on macOS (https://github.com/golang/go/issues/15075),
|
||||
// and that doesn't support FreeBSD, use OpenAL directly here.
|
||||
|
||||
type driver struct {
|
||||
// alContext represents a pointer to ALCcontext. The type is uintptr since the value
|
||||
// can be 0x18 on macOS, which is invalid as a pointer value, and this might cause
|
||||
// GC errors.
|
||||
alContext alContext
|
||||
alDevice alDevice
|
||||
alDeviceName string
|
||||
alSource C.ALuint
|
||||
sampleRate int
|
||||
isClosed bool
|
||||
alFormat C.ALenum
|
||||
|
||||
bufs []C.ALuint
|
||||
tmp []byte
|
||||
bufferSize int
|
||||
}
|
||||
|
||||
// alContext is a pointer to OpenAL context.
|
||||
// The value is not unsafe.Pointer for C.ALCcontext but uintptr,
|
||||
// because device pointer value can be an invalid value as a pointer on macOS,
|
||||
// and Cgo pointer checker complains (#65).
|
||||
type alContext uintptr
|
||||
|
||||
// alDevice is a pointer to OpenAL device.
|
||||
type alDevice uintptr
|
||||
|
||||
func (a alDevice) getError() error {
|
||||
switch c := C._alcGetError(C.uintptr_t(a)); c {
|
||||
case C.ALC_NO_ERROR:
|
||||
return nil
|
||||
case C.ALC_INVALID_DEVICE:
|
||||
return errors.New("OpenAL error: invalid device")
|
||||
case C.ALC_INVALID_CONTEXT:
|
||||
return errors.New("OpenAL error: invalid context")
|
||||
case C.ALC_INVALID_ENUM:
|
||||
return errors.New("OpenAL error: invalid enum")
|
||||
case C.ALC_INVALID_VALUE:
|
||||
return errors.New("OpenAL error: invalid value")
|
||||
case C.ALC_OUT_OF_MEMORY:
|
||||
return errors.New("OpenAL error: out of memory")
|
||||
default:
|
||||
return fmt.Errorf("OpenAL error: code %d", c)
|
||||
}
|
||||
}
|
||||
|
||||
func alFormat(channelNum, bitDepthInBytes int) C.ALenum {
|
||||
switch {
|
||||
case channelNum == 1 && bitDepthInBytes == 1:
|
||||
return C.AL_FORMAT_MONO8
|
||||
case channelNum == 1 && bitDepthInBytes == 2:
|
||||
return C.AL_FORMAT_MONO16
|
||||
case channelNum == 2 && bitDepthInBytes == 1:
|
||||
return C.AL_FORMAT_STEREO8
|
||||
case channelNum == 2 && bitDepthInBytes == 2:
|
||||
return C.AL_FORMAT_STEREO16
|
||||
}
|
||||
panic(fmt.Sprintf("oto: invalid channel num (%d) or bytes per sample (%d)", channelNum, bitDepthInBytes))
|
||||
}
|
||||
|
||||
const numBufs = 2
|
||||
|
||||
func newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes int) (*driver, error) {
|
||||
name := C.alGetString(C.ALC_DEFAULT_DEVICE_SPECIFIER)
|
||||
d := alDevice(C._alcOpenDevice((*C.ALCchar)(name)))
|
||||
if d == 0 {
|
||||
return nil, fmt.Errorf("oto: alcOpenDevice must not return null")
|
||||
}
|
||||
c := alContext(C._alcCreateContext(C.uintptr_t(d), nil))
|
||||
if c == 0 {
|
||||
return nil, fmt.Errorf("oto: alcCreateContext must not return null")
|
||||
}
|
||||
|
||||
// Don't check getError until making the current context is done.
|
||||
// Linux might fail this check even though it succeeds (hajimehoshi/ebiten#204).
|
||||
C._alcMakeContextCurrent(C.uintptr_t(c))
|
||||
if err := d.getError(); err != nil {
|
||||
return nil, fmt.Errorf("oto: Activate: %v", err)
|
||||
}
|
||||
|
||||
s := C.ALuint(0)
|
||||
C.alGenSources(1, &s)
|
||||
if err := d.getError(); err != nil {
|
||||
return nil, fmt.Errorf("oto: NewSource: %v", err)
|
||||
}
|
||||
|
||||
p := &driver{
|
||||
alContext: c,
|
||||
alDevice: d,
|
||||
alSource: s,
|
||||
alDeviceName: C.GoString((*C.char)(name)),
|
||||
sampleRate: sampleRate,
|
||||
alFormat: alFormat(channelNum, bitDepthInBytes),
|
||||
bufs: make([]C.ALuint, numBufs),
|
||||
bufferSize: bufferSizeInBytes,
|
||||
}
|
||||
runtime.SetFinalizer(p, (*driver).Close)
|
||||
C.alGenBuffers(C.ALsizei(numBufs), &p.bufs[0])
|
||||
C.alSourcePlay(p.alSource)
|
||||
|
||||
if err := d.getError(); err != nil {
|
||||
return nil, fmt.Errorf("oto: Play: %v", err)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *driver) TryWrite(data []byte) (int, error) {
|
||||
if err := p.alDevice.getError(); err != nil {
|
||||
return 0, fmt.Errorf("oto: starting Write: %v", err)
|
||||
}
|
||||
n := min(len(data), max(0, p.bufferSize-len(p.tmp)))
|
||||
p.tmp = append(p.tmp, data[:n]...)
|
||||
if len(p.tmp) < p.bufferSize {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
pn := C.ALint(0)
|
||||
C.alGetSourcei(p.alSource, C.AL_BUFFERS_PROCESSED, &pn)
|
||||
|
||||
if pn > 0 {
|
||||
bufs := make([]C.ALuint, pn)
|
||||
C.alSourceUnqueueBuffers(p.alSource, C.ALsizei(len(bufs)), &bufs[0])
|
||||
if err := p.alDevice.getError(); err != nil {
|
||||
return 0, fmt.Errorf("oto: UnqueueBuffers: %v", err)
|
||||
}
|
||||
p.bufs = append(p.bufs, bufs...)
|
||||
}
|
||||
|
||||
if len(p.bufs) == 0 {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
buf := p.bufs[0]
|
||||
p.bufs = p.bufs[1:]
|
||||
C.alBufferData(buf, p.alFormat, unsafe.Pointer(&p.tmp[0]), C.ALsizei(p.bufferSize), C.ALsizei(p.sampleRate))
|
||||
C.alSourceQueueBuffers(p.alSource, 1, &buf)
|
||||
if err := p.alDevice.getError(); err != nil {
|
||||
return 0, fmt.Errorf("oto: QueueBuffer: %v", err)
|
||||
}
|
||||
|
||||
state := C.ALint(0)
|
||||
C.alGetSourcei(p.alSource, C.AL_SOURCE_STATE, &state)
|
||||
if state == C.AL_STOPPED || state == C.AL_INITIAL {
|
||||
C.alSourceRewind(p.alSource)
|
||||
C.alSourcePlay(p.alSource)
|
||||
if err := p.alDevice.getError(); err != nil {
|
||||
return 0, fmt.Errorf("oto: Rewind or Play: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
p.tmp = nil
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (p *driver) Close() error {
|
||||
if err := p.alDevice.getError(); err != nil {
|
||||
return fmt.Errorf("oto: starting Close: %v", err)
|
||||
}
|
||||
if p.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
n := C.ALint(0)
|
||||
C.alGetSourcei(p.alSource, C.AL_BUFFERS_QUEUED, &n)
|
||||
if 0 < n {
|
||||
bs := make([]C.ALuint, n)
|
||||
C.alSourceUnqueueBuffers(p.alSource, C.ALsizei(len(bs)), &bs[0])
|
||||
p.bufs = append(p.bufs, bs...)
|
||||
}
|
||||
|
||||
C.alSourceStop(p.alSource)
|
||||
C.alDeleteSources(1, &p.alSource)
|
||||
if len(p.bufs) != 0 {
|
||||
C.alDeleteBuffers(C.ALsizei(numBufs), &p.bufs[0])
|
||||
}
|
||||
C._alcDestroyContext(C.uintptr_t(p.alContext))
|
||||
|
||||
if err := p.alDevice.getError(); err != nil {
|
||||
return fmt.Errorf("oto: CloseDevice: %v", err)
|
||||
}
|
||||
|
||||
b := C._alcCloseDevice(C.uintptr_t(p.alDevice))
|
||||
if b == C.ALC_FALSE {
|
||||
return fmt.Errorf("oto: CloseDevice: %s failed to close", p.alDeviceName)
|
||||
}
|
||||
|
||||
p.isClosed = true
|
||||
runtime.SetFinalizer(p, nil)
|
||||
return nil
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
// Copyright 2015 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !js
|
||||
|
||||
package oto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type header struct {
|
||||
buffer []byte
|
||||
waveHdr *wavehdr
|
||||
}
|
||||
|
||||
func newHeader(waveOut uintptr, bufferSize int) (*header, error) {
|
||||
h := &header{
|
||||
buffer: make([]byte, bufferSize),
|
||||
}
|
||||
h.waveHdr = &wavehdr{
|
||||
lpData: uintptr(unsafe.Pointer(&h.buffer[0])),
|
||||
dwBufferLength: uint32(bufferSize),
|
||||
}
|
||||
if err := waveOutPrepareHeader(waveOut, h.waveHdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h *header) Write(waveOut uintptr, data []byte) error {
|
||||
if len(data) != len(h.buffer) {
|
||||
return errors.New("oto: len(data) must equal to len(h.buffer)")
|
||||
}
|
||||
copy(h.buffer, data)
|
||||
if err := waveOutWrite(waveOut, h.waveHdr); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type driver struct {
|
||||
out uintptr
|
||||
headers []*header
|
||||
tmp []byte
|
||||
bufferSize int
|
||||
}
|
||||
|
||||
func newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes int) (*driver, error) {
|
||||
numBlockAlign := channelNum * bitDepthInBytes
|
||||
f := &waveformatex{
|
||||
wFormatTag: waveFormatPCM,
|
||||
nChannels: uint16(channelNum),
|
||||
nSamplesPerSec: uint32(sampleRate),
|
||||
nAvgBytesPerSec: uint32(sampleRate * numBlockAlign),
|
||||
wBitsPerSample: uint16(bitDepthInBytes * 8),
|
||||
nBlockAlign: uint16(numBlockAlign),
|
||||
}
|
||||
w, err := waveOutOpen(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
const numBufs = 2
|
||||
p := &driver{
|
||||
out: w,
|
||||
headers: make([]*header, numBufs),
|
||||
bufferSize: bufferSizeInBytes,
|
||||
}
|
||||
runtime.SetFinalizer(p, (*driver).Close)
|
||||
for i := range p.headers {
|
||||
var err error
|
||||
p.headers[i], err = newHeader(w, p.bufferSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *driver) TryWrite(data []byte) (int, error) {
|
||||
n := min(len(data), max(0, p.bufferSize-len(p.tmp)))
|
||||
p.tmp = append(p.tmp, data[:n]...)
|
||||
if len(p.tmp) < p.bufferSize {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
var headerToWrite *header
|
||||
for _, h := range p.headers {
|
||||
// TODO: Need to check WHDR_DONE?
|
||||
if h.waveHdr.dwFlags&whdrInqueue == 0 {
|
||||
headerToWrite = h
|
||||
break
|
||||
}
|
||||
}
|
||||
if headerToWrite == nil {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
if err := headerToWrite.Write(p.out, p.tmp); err != nil {
|
||||
// This error can happen when e.g. a new HDMI connection is detected (#51).
|
||||
const errorNotFound = 1168
|
||||
werr := err.(*winmmError)
|
||||
if werr.fname == "waveOutWrite" && werr.errno == errorNotFound {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
p.tmp = nil
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (p *driver) Close() error {
|
||||
runtime.SetFinalizer(p, nil)
|
||||
// TODO: Call waveOutUnprepareHeader here
|
||||
if err := waveOutClose(p.out); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
module github.com/hajimehoshi/oto
|
||||
|
||||
require (
|
||||
github.com/gopherjs/gopherwasm v1.0.0
|
||||
golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd // indirect
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 // indirect
|
||||
golang.org/x/mobile v0.0.0-20180806140643-507816974b79
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb
|
||||
)
|
@ -1,14 +0,0 @@
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherwasm v1.0.0 h1:32nge/RlujS1Im4HNCJPp0NbBOAeBXFuT1KonUuLl+Y=
|
||||
github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
|
||||
golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd h1:nLIcFw7GiqKXUS7HiChg6OAYWgASB2H97dZKd1GhDSs=
|
||||
golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/mobile v0.0.0-20180806140643-507816974b79 h1:t2JRgCWkY7Qaa1J2jal+wqC9OjbyHCHwIA9rVlRUSMo=
|
||||
golang.org/x/mobile v0.0.0-20180806140643-507816974b79/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/sys v0.0.0-20180806082429-34b17bdb4300 h1:eJa+6+7jje7fOYUrLnwKNR9kcpvLANj1Asw0Ou1pBiI=
|
||||
golang.org/x/sys v0.0.0-20180806082429-34b17bdb4300/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb h1:pf3XwC90UUdNPYWZdFjhGBE7DUFuK3Ct1zWmZ65QN30=
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
@ -1,167 +0,0 @@
|
||||
// Copyright 2019 The Oto Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Mux is a multiplexer for multiple io.Reader objects.
|
||||
type Mux struct {
|
||||
channelNum int
|
||||
bitDepthInBytes int
|
||||
readers map[io.Reader]*bufio.Reader
|
||||
closed bool
|
||||
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
func New(channelNum, bitDepthInBytes int) *Mux {
|
||||
m := &Mux{
|
||||
channelNum: channelNum,
|
||||
bitDepthInBytes: bitDepthInBytes,
|
||||
readers: map[io.Reader]*bufio.Reader{},
|
||||
}
|
||||
runtime.SetFinalizer(m, (*Mux).Close)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Mux) Read(buf []byte) (int, error) {
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
|
||||
if m.closed {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
if len(m.readers) == 0 {
|
||||
// When there is no reader, Read should return with 0s or Read caller can block forever.
|
||||
// See https://github.com/hajimehoshi/go-mp3/issues/28
|
||||
n := 256
|
||||
if len(buf) < 256 {
|
||||
n = len(buf)
|
||||
}
|
||||
copy(buf, make([]byte, n))
|
||||
return n, nil
|
||||
}
|
||||
|
||||
bs := m.channelNum * m.bitDepthInBytes
|
||||
l := len(buf)
|
||||
l = l / bs * bs // Adjust the length in order not to mix different channels.
|
||||
|
||||
bufs := map[*bufio.Reader][]byte{}
|
||||
for _, p := range m.readers {
|
||||
peeked, err := p.Peek(l)
|
||||
if err != nil && err != bufio.ErrBufferFull && err != io.EOF {
|
||||
return 0, err
|
||||
}
|
||||
if l > len(peeked) {
|
||||
l = len(peeked)
|
||||
l = l / bs * bs
|
||||
}
|
||||
bufs[p] = peeked[:l]
|
||||
}
|
||||
|
||||
if l == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
for _, p := range m.readers {
|
||||
if _, err := p.Discard(l); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
switch m.bitDepthInBytes {
|
||||
case 1:
|
||||
const (
|
||||
max = 127
|
||||
min = -128
|
||||
offset = 128
|
||||
)
|
||||
for i := 0; i < l; i++ {
|
||||
x := 0
|
||||
for _, b := range bufs {
|
||||
x += int(b[i]) - offset
|
||||
}
|
||||
if x > max {
|
||||
x = max
|
||||
}
|
||||
if x < min {
|
||||
x = min
|
||||
}
|
||||
buf[i] = byte(x + offset)
|
||||
}
|
||||
case 2:
|
||||
const (
|
||||
max = (1 << 15) - 1
|
||||
min = -(1 << 15)
|
||||
)
|
||||
for i := 0; i < l/2; i++ {
|
||||
x := 0
|
||||
for _, b := range bufs {
|
||||
x += int(int16(b[2*i]) | (int16(b[2*i+1]) << 8))
|
||||
}
|
||||
if x > max {
|
||||
x = max
|
||||
}
|
||||
if x < min {
|
||||
x = min
|
||||
}
|
||||
buf[2*i] = byte(x)
|
||||
buf[2*i+1] = byte(x >> 8)
|
||||
}
|
||||
default:
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (m *Mux) Close() error {
|
||||
m.m.Lock()
|
||||
runtime.SetFinalizer(m, nil)
|
||||
m.readers = nil
|
||||
m.closed = true
|
||||
m.m.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mux) AddSource(source io.Reader) {
|
||||
m.m.Lock()
|
||||
if m.closed {
|
||||
panic("mux: already closed")
|
||||
}
|
||||
if _, ok := m.readers[source]; ok {
|
||||
panic("mux: the io.Reader cannot be added multiple times")
|
||||
}
|
||||
m.readers[source] = bufio.NewReaderSize(source, 256)
|
||||
m.m.Unlock()
|
||||
}
|
||||
|
||||
func (m *Mux) RemoveSource(source io.Reader) {
|
||||
m.m.Lock()
|
||||
if m.closed {
|
||||
panic("mux: already closed")
|
||||
}
|
||||
if _, ok := m.readers[source]; !ok {
|
||||
panic("mux: the io.Reader is already removed")
|
||||
}
|
||||
delete(m.readers, source)
|
||||
m.m.Unlock()
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
// Copyright 2017 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package oto offers io.Writer to play sound on multiple platforms.
|
||||
package oto
|
||||
|
||||
import (
|
||||
"io"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Player is a PCM (pulse-code modulation) audio player.
|
||||
// Player implements io.WriteCloser.
|
||||
// Use Write method to play samples.
|
||||
type Player struct {
|
||||
context *Context
|
||||
r *io.PipeReader
|
||||
w *io.PipeWriter
|
||||
}
|
||||
|
||||
func newPlayer(context *Context) *Player {
|
||||
r, w := io.Pipe()
|
||||
p := &Player{
|
||||
context: context,
|
||||
r: r,
|
||||
w: w,
|
||||
}
|
||||
runtime.SetFinalizer(p, (*Player).Close)
|
||||
return p
|
||||
}
|
||||
|
||||
// Write writes PCM samples to the Player.
|
||||
//
|
||||
// The format is as follows:
|
||||
// [data] = [sample 1] [sample 2] [sample 3] ...
|
||||
// [sample *] = [channel 1] ...
|
||||
// [channel *] = [byte 1] [byte 2] ...
|
||||
// Byte ordering is little endian.
|
||||
//
|
||||
// The data is first put into the Player's buffer. Once the buffer is full, Player starts playing
|
||||
// the data and empties the buffer.
|
||||
//
|
||||
// If the supplied data doesn't fit into the Player's buffer, Write block until a sufficient amount
|
||||
// of data has been played (or at least started playing) and the remaining unplayed data fits into
|
||||
// the buffer.
|
||||
//
|
||||
// Note, that the Player won't start playing anything until the buffer is full.
|
||||
func (p *Player) Write(buf []byte) (int, error) {
|
||||
return p.w.Write(buf)
|
||||
}
|
||||
|
||||
// Close closes the Player and frees any resources associated with it. The Player is no longer
|
||||
// usable after calling Close.
|
||||
func (p *Player) Close() error {
|
||||
runtime.SetFinalizer(p, nil)
|
||||
|
||||
// Already closed
|
||||
if p.context == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close the pipe writer before RemoveSource, or Read-ing in the mux takes forever.
|
||||
if err := p.w.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.context.mux.RemoveSource(p.r)
|
||||
p.context = nil
|
||||
|
||||
// Close the pipe reader after RemoveSource, or ErrClosedPipe happens at Read-ing.
|
||||
if err := p.r.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a < b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
// Copyright 2017 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !js
|
||||
|
||||
package oto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
winmm = windows.NewLazySystemDLL("winmm")
|
||||
)
|
||||
|
||||
var (
|
||||
procWaveOutOpen = winmm.NewProc("waveOutOpen")
|
||||
procWaveOutClose = winmm.NewProc("waveOutClose")
|
||||
procWaveOutPrepareHeader = winmm.NewProc("waveOutPrepareHeader")
|
||||
procWaveOutWrite = winmm.NewProc("waveOutWrite")
|
||||
)
|
||||
|
||||
type wavehdr struct {
|
||||
lpData uintptr
|
||||
dwBufferLength uint32
|
||||
dwBytesRecorded uint32
|
||||
dwUser uintptr
|
||||
dwFlags uint32
|
||||
dwLoops uint32
|
||||
lpNext uintptr
|
||||
reserved uintptr
|
||||
}
|
||||
|
||||
type waveformatex struct {
|
||||
wFormatTag uint16
|
||||
nChannels uint16
|
||||
nSamplesPerSec uint32
|
||||
nAvgBytesPerSec uint32
|
||||
nBlockAlign uint16
|
||||
wBitsPerSample uint16
|
||||
cbSize uint16
|
||||
}
|
||||
|
||||
const (
|
||||
waveFormatPCM = 1
|
||||
whdrInqueue = 16
|
||||
)
|
||||
|
||||
type mmresult uint
|
||||
|
||||
const (
|
||||
mmsyserrNoerror mmresult = 0
|
||||
mmsyserrError mmresult = 1
|
||||
mmsyserrBaddeviceid mmresult = 2
|
||||
mmsyserrAllocated mmresult = 4
|
||||
mmsyserrInvalidhandle mmresult = 5
|
||||
mmsyserrNodriver mmresult = 6
|
||||
mmsyserrNomem mmresult = 7
|
||||
waveerrBadformat mmresult = 32
|
||||
waveerrStillplaying mmresult = 33
|
||||
waveerrUnprepared mmresult = 34
|
||||
waveerrSync mmresult = 35
|
||||
)
|
||||
|
||||
func (m mmresult) String() string {
|
||||
switch m {
|
||||
case mmsyserrNoerror:
|
||||
return "MMSYSERR_NOERROR"
|
||||
case mmsyserrError:
|
||||
return "MMSYSERR_ERROR"
|
||||
case mmsyserrBaddeviceid:
|
||||
return "MMSYSERR_BADDEVICEID"
|
||||
case mmsyserrAllocated:
|
||||
return "MMSYSERR_ALLOCATED"
|
||||
case mmsyserrInvalidhandle:
|
||||
return "MMSYSERR_INVALIDHANDLE"
|
||||
case mmsyserrNodriver:
|
||||
return "MMSYSERR_NODRIVER"
|
||||
case mmsyserrNomem:
|
||||
return "MMSYSERR_NOMEM"
|
||||
case waveerrBadformat:
|
||||
return "WAVEERR_BADFORMAT"
|
||||
case waveerrStillplaying:
|
||||
return "WAVEERR_STILLPLAYING"
|
||||
case waveerrUnprepared:
|
||||
return "WAVEERR_UNPREPARED"
|
||||
case waveerrSync:
|
||||
return "WAVEERR_SYNC"
|
||||
}
|
||||
return fmt.Sprintf("MMRESULT (%d)", m)
|
||||
}
|
||||
|
||||
type winmmError struct {
|
||||
fname string
|
||||
errno windows.Errno
|
||||
mmresult mmresult
|
||||
}
|
||||
|
||||
func (e *winmmError) Error() string {
|
||||
if e.errno != 0 {
|
||||
return fmt.Sprintf("winmm error at %s: Errno: %d", e.fname, e.errno)
|
||||
}
|
||||
if e.mmresult != mmsyserrNoerror {
|
||||
return fmt.Sprintf("winmm error at %s: %s", e.fname, e.mmresult)
|
||||
}
|
||||
return fmt.Sprintf("winmm error at %s", e.fname)
|
||||
}
|
||||
|
||||
func waveOutOpen(f *waveformatex) (uintptr, error) {
|
||||
const (
|
||||
waveMapper = 0xffffffff
|
||||
callbackNull = 0
|
||||
)
|
||||
var w uintptr
|
||||
r, _, e := procWaveOutOpen.Call(uintptr(unsafe.Pointer(&w)), waveMapper, uintptr(unsafe.Pointer(f)),
|
||||
0, 0, callbackNull)
|
||||
if e.(windows.Errno) != 0 {
|
||||
return 0, &winmmError{
|
||||
fname: "waveOutOpen",
|
||||
errno: e.(windows.Errno),
|
||||
}
|
||||
}
|
||||
if mmresult(r) != mmsyserrNoerror {
|
||||
return 0, &winmmError{
|
||||
fname: "waveOutOpen",
|
||||
mmresult: mmresult(r),
|
||||
}
|
||||
}
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func waveOutClose(hwo uintptr) error {
|
||||
r, _, e := procWaveOutClose.Call(hwo)
|
||||
if e.(windows.Errno) != 0 {
|
||||
return &winmmError{
|
||||
fname: "waveOutClose",
|
||||
errno: e.(windows.Errno),
|
||||
}
|
||||
}
|
||||
// WAVERR_STILLPLAYING is ignored.
|
||||
if mmresult(r) != mmsyserrNoerror && mmresult(r) != waveerrStillplaying {
|
||||
return &winmmError{
|
||||
fname: "waveOutClose",
|
||||
mmresult: mmresult(r),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func waveOutPrepareHeader(hwo uintptr, pwh *wavehdr) error {
|
||||
r, _, e := procWaveOutPrepareHeader.Call(hwo, uintptr(unsafe.Pointer(pwh)), unsafe.Sizeof(wavehdr{}))
|
||||
if e.(windows.Errno) != 0 {
|
||||
return &winmmError{
|
||||
fname: "waveOutPrepareHeader",
|
||||
errno: e.(windows.Errno),
|
||||
}
|
||||
}
|
||||
if mmresult(r) != mmsyserrNoerror {
|
||||
return &winmmError{
|
||||
fname: "waveOutPrepareHeader",
|
||||
mmresult: mmresult(r),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func waveOutWrite(hwo uintptr, pwh *wavehdr) error {
|
||||
r, _, e := procWaveOutWrite.Call(hwo, uintptr(unsafe.Pointer(pwh)), unsafe.Sizeof(wavehdr{}))
|
||||
if e.(windows.Errno) != 0 {
|
||||
return &winmmError{
|
||||
fname: "waveOutWrite",
|
||||
errno: e.(windows.Errno),
|
||||
}
|
||||
}
|
||||
if mmresult(r) != mmsyserrNoerror {
|
||||
return &winmmError{
|
||||
fname: "waveOutWrite",
|
||||
mmresult: mmresult(r),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
@ -1,15 +0,0 @@
|
||||
language: go
|
||||
go_import_path: github.com/pkg/errors
|
||||
go:
|
||||
- 1.4.x
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
@ -1,23 +0,0 @@
|
||||
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -1,52 +0,0 @@
|
||||
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge)
|
||||
|
||||
Package errors provides simple error handling primitives.
|
||||
|
||||
`go get github.com/pkg/errors`
|
||||
|
||||
The traditional error handling idiom in Go is roughly akin to
|
||||
```go
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
|
||||
|
||||
## Adding context to an error
|
||||
|
||||
The errors.Wrap function returns a new error that adds context to the original error. For example
|
||||
```go
|
||||
_, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read failed")
|
||||
}
|
||||
```
|
||||
## Retrieving the cause of an error
|
||||
|
||||
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
|
||||
```go
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
```
|
||||
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
|
||||
```go
|
||||
switch err := errors.Cause(err).(type) {
|
||||
case *MyError:
|
||||
// handle specifically
|
||||
default:
|
||||
// unknown error
|
||||
}
|
||||
```
|
||||
|
||||
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
|
||||
|
||||
Before proposing a change, please discuss your change by raising an issue.
|
||||
|
||||
## License
|
||||
|
||||
BSD-2-Clause
|
@ -1,32 +0,0 @@
|
||||
version: build-{build}.{branch}
|
||||
|
||||
clone_folder: C:\gopath\src\github.com\pkg\errors
|
||||
shallow_clone: true # for startup speed
|
||||
|
||||
environment:
|
||||
GOPATH: C:\gopath
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
# http://www.appveyor.com/docs/installed-software
|
||||
install:
|
||||
# some helpful output for debugging builds
|
||||
- go version
|
||||
- go env
|
||||
# pre-installed MinGW at C:\MinGW is 32bit only
|
||||
# but MSYS2 at C:\msys64 has mingw64
|
||||
- set PATH=C:\msys64\mingw64\bin;%PATH%
|
||||
- gcc --version
|
||||
- g++ --version
|
||||
|
||||
build_script:
|
||||
- go install -v ./...
|
||||
|
||||
test_script:
|
||||
- set PATH=C:\gopath\bin;%PATH%
|
||||
- go test -v ./...
|
||||
|
||||
#artifacts:
|
||||
# - path: '%GOPATH%\bin\*.exe'
|
||||
deploy: off
|
@ -1,282 +0,0 @@
|
||||
// Package errors provides simple error handling primitives.
|
||||
//
|
||||
// The traditional error handling idiom in Go is roughly akin to
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// which when applied recursively up the call stack results in error reports
|
||||
// without context or debugging information. The errors package allows
|
||||
// programmers to add context to the failure path in their code in a way
|
||||
// that does not destroy the original value of the error.
|
||||
//
|
||||
// Adding context to an error
|
||||
//
|
||||
// The errors.Wrap function returns a new error that adds context to the
|
||||
// original error by recording a stack trace at the point Wrap is called,
|
||||
// together with the supplied message. For example
|
||||
//
|
||||
// _, err := ioutil.ReadAll(r)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "read failed")
|
||||
// }
|
||||
//
|
||||
// If additional control is required, the errors.WithStack and
|
||||
// errors.WithMessage functions destructure errors.Wrap into its component
|
||||
// operations: annotating an error with a stack trace and with a message,
|
||||
// respectively.
|
||||
//
|
||||
// Retrieving the cause of an error
|
||||
//
|
||||
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||
// preceding error. Depending on the nature of the error it may be necessary
|
||||
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||
// for inspection. Any error value which implements this interface
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||
// the topmost error that does not implement causer, which is assumed to be
|
||||
// the original cause. For example:
|
||||
//
|
||||
// switch err := errors.Cause(err).(type) {
|
||||
// case *MyError:
|
||||
// // handle specifically
|
||||
// default:
|
||||
// // unknown error
|
||||
// }
|
||||
//
|
||||
// Although the causer interface is not exported by this package, it is
|
||||
// considered a part of its stable public interface.
|
||||
//
|
||||
// Formatted printing of errors
|
||||
//
|
||||
// All error values returned from this package implement fmt.Formatter and can
|
||||
// be formatted by the fmt package. The following verbs are supported:
|
||||
//
|
||||
// %s print the error. If the error has a Cause it will be
|
||||
// printed recursively.
|
||||
// %v see %s
|
||||
// %+v extended format. Each Frame of the error's StackTrace will
|
||||
// be printed in detail.
|
||||
//
|
||||
// Retrieving the stack trace of an error or wrapper
|
||||
//
|
||||
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||
// invoked. This information can be retrieved with the following interface:
|
||||
//
|
||||
// type stackTracer interface {
|
||||
// StackTrace() errors.StackTrace
|
||||
// }
|
||||
//
|
||||
// The returned errors.StackTrace type is defined as
|
||||
//
|
||||
// type StackTrace []Frame
|
||||
//
|
||||
// The Frame type represents a call site in the stack trace. Frame supports
|
||||
// the fmt.Formatter interface that can be used for printing information about
|
||||
// the stack trace of this error. For example:
|
||||
//
|
||||
// if err, ok := err.(stackTracer); ok {
|
||||
// for _, f := range err.StackTrace() {
|
||||
// fmt.Printf("%+s:%d", f)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Although the stackTracer interface is not exported by this package, it is
|
||||
// considered a part of its stable public interface.
|
||||
//
|
||||
// See the documentation for Frame.Format for more details.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// New returns an error with the supplied message.
|
||||
// New also records the stack trace at the point it was called.
|
||||
func New(message string) error {
|
||||
return &fundamental{
|
||||
msg: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf formats according to a format specifier and returns the string
|
||||
// as a value that satisfies error.
|
||||
// Errorf also records the stack trace at the point it was called.
|
||||
func Errorf(format string, args ...interface{}) error {
|
||||
return &fundamental{
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// fundamental is an error that has a message and a stack, but no caller.
|
||||
type fundamental struct {
|
||||
msg string
|
||||
*stack
|
||||
}
|
||||
|
||||
func (f *fundamental) Error() string { return f.msg }
|
||||
|
||||
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
io.WriteString(s, f.msg)
|
||||
f.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, f.msg)
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", f.msg)
|
||||
}
|
||||
}
|
||||
|
||||
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||
// If err is nil, WithStack returns nil.
|
||||
func WithStack(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
type withStack struct {
|
||||
error
|
||||
*stack
|
||||
}
|
||||
|
||||
func (w *withStack) Cause() error { return w.error }
|
||||
|
||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v", w.Cause())
|
||||
w.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, w.Error())
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap returns an error annotating err with a stack trace
|
||||
// at the point Wrap is called, and the supplied message.
|
||||
// If err is nil, Wrap returns nil.
|
||||
func Wrap(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf returns an error annotating err with a stack trace
|
||||
// at the point Wrapf is called, and the format specifier.
|
||||
// If err is nil, Wrapf returns nil.
|
||||
func Wrapf(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessage annotates err with a new message.
|
||||
// If err is nil, WithMessage returns nil.
|
||||
func WithMessage(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessagef annotates err with the format specifier.
|
||||
// If err is nil, WithMessagef returns nil.
|
||||
func WithMessagef(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
}
|
||||
|
||||
type withMessage struct {
|
||||
cause error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||
func (w *withMessage) Cause() error { return w.cause }
|
||||
|
||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||
io.WriteString(s, w.msg)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's', 'q':
|
||||
io.WriteString(s, w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Cause returns the underlying cause of the error, if possible.
|
||||
// An error value has a cause if it implements the following
|
||||
// interface:
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// If the error does not implement Cause, the original error will
|
||||
// be returned. If the error is nil, nil will be returned without further
|
||||
// investigation.
|
||||
func Cause(err error) error {
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
for err != nil {
|
||||
cause, ok := err.(causer)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
err = cause.Cause()
|
||||
}
|
||||
return err
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
// multiple frames may have the same PC value.
|
||||
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||
|
||||
// file returns the full path to the file that contains the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) file() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
file, _ := fn.FileLine(f.pc())
|
||||
return file
|
||||
}
|
||||
|
||||
// line returns the line number of source code of the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) line() int {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return 0
|
||||
}
|
||||
_, line := fn.FileLine(f.pc())
|
||||
return line
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
// %d source line
|
||||
// %n function name
|
||||
// %v equivalent to %s:%d
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+s function name and path of source file relative to the compile time
|
||||
// GOPATH separated by \n\t (<funcname>\n\t<path>)
|
||||
// %+v equivalent to %+s:%d
|
||||
func (f Frame) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
pc := f.pc()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
io.WriteString(s, "unknown")
|
||||
} else {
|
||||
file, _ := fn.FileLine(pc)
|
||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||
}
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
fmt.Fprintf(s, "%d", f.line())
|
||||
case 'n':
|
||||
name := runtime.FuncForPC(f.pc()).Name()
|
||||
io.WriteString(s, funcname(name))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
f.Format(s, 'd')
|
||||
}
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
// Format formats the stack of Frames according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s lists source files for each Frame in the stack
|
||||
// %v lists the source file and line number for each Frame in the stack
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+v Prints filename, function, and line number for each Frame in the stack.
|
||||
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
fmt.Fprintf(s, "\n%+v", f)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
fmt.Fprintf(s, "%v", []Frame(st))
|
||||
}
|
||||
case 's':
|
||||
fmt.Fprintf(s, "%s", []Frame(st))
|
||||
}
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
func (s *stack) Format(st fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case st.Flag('+'):
|
||||
for _, pc := range *s {
|
||||
f := Frame(pc)
|
||||
fmt.Fprintf(st, "\n%+v", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) StackTrace() StackTrace {
|
||||
f := make([]Frame, len(*s))
|
||||
for i := 0; i < len(f); i++ {
|
||||
f[i] = Frame((*s)[i])
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func callers() *stack {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(3, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return &st
|
||||
}
|
||||
|
||||
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||
func funcname(name string) string {
|
||||
i := strings.LastIndex(name, "/")
|
||||
name = name[i+1:]
|
||||
i = strings.Index(name, ".")
|
||||
return name[i+1:]
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
@ -1,3 +0,0 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
@ -1,27 +0,0 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -1,22 +0,0 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
@ -1,32 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gldriver
|
||||
|
||||
import "image"
|
||||
|
||||
type bufferImpl struct {
|
||||
// buf should always be equal to (i.e. the same ptr, len, cap as) rgba.Pix.
|
||||
// It is a separate, redundant field in order to detect modifications to
|
||||
// the rgba field that are invalid as per the screen.Buffer documentation.
|
||||
buf []byte
|
||||
rgba image.RGBA
|
||||
size image.Point
|
||||
}
|
||||
|
||||
func (b *bufferImpl) Release() {}
|
||||
func (b *bufferImpl) Size() image.Point { return b.size }
|
||||
func (b *bufferImpl) Bounds() image.Rectangle { return image.Rectangle{Max: b.size} }
|
||||
func (b *bufferImpl) RGBA() *image.RGBA { return &b.rgba }
|
||||
|
||||
func (b *bufferImpl) preUpload() {
|
||||
// Check that the program hasn't tried to modify the rgba field via the
|
||||
// pointer returned by the bufferImpl.RGBA method. This check doesn't catch
|
||||
// 100% of all cases; it simply tries to detect some invalid uses of a
|
||||
// screen.Buffer such as:
|
||||
// *buffer.RGBA() = anotherImageRGBA
|
||||
if len(b.buf) != 0 && len(b.rgba.Pix) != 0 && &b.buf[0] != &b.rgba.Pix[0] {
|
||||
panic("gldriver: invalid Buffer.RGBA modification")
|
||||
}
|
||||
}
|
@ -1,671 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin
|
||||
// +build 386 amd64
|
||||
// +build !ios
|
||||
|
||||
package gldriver
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Cocoa -framework OpenGL
|
||||
#include <OpenGL/gl3.h>
|
||||
#import <Carbon/Carbon.h> // for HIToolbox/Events.h
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void startDriver();
|
||||
void stopDriver();
|
||||
void makeCurrentContext(uintptr_t ctx);
|
||||
void flushContext(uintptr_t ctx);
|
||||
uintptr_t doNewWindow(int width, int height, char* title);
|
||||
void doShowWindow(uintptr_t id);
|
||||
void doCloseWindow(uintptr_t id);
|
||||
uint64_t threadID();
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/exp/shiny/driver/internal/lifecycler"
|
||||
"golang.org/x/exp/shiny/screen"
|
||||
"golang.org/x/mobile/event/key"
|
||||
"golang.org/x/mobile/event/mouse"
|
||||
"golang.org/x/mobile/event/paint"
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/geom"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
const useLifecycler = true
|
||||
|
||||
// TODO: change this to true, after manual testing on OS X.
|
||||
const handleSizeEventsAtChannelReceive = false
|
||||
|
||||
var initThreadID C.uint64_t
|
||||
|
||||
func init() {
|
||||
// Lock the goroutine responsible for initialization to an OS thread.
|
||||
// This means the goroutine running main (and calling startDriver below)
|
||||
// is locked to the OS thread that started the program. This is
|
||||
// necessary for the correct delivery of Cocoa events to the process.
|
||||
//
|
||||
// A discussion on this topic:
|
||||
// https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ
|
||||
runtime.LockOSThread()
|
||||
initThreadID = C.threadID()
|
||||
}
|
||||
|
||||
func newWindow(opts *screen.NewWindowOptions) (uintptr, error) {
|
||||
width, height := optsSize(opts)
|
||||
|
||||
title := C.CString(opts.GetTitle())
|
||||
defer C.free(unsafe.Pointer(title))
|
||||
|
||||
return uintptr(C.doNewWindow(C.int(width), C.int(height), title)), nil
|
||||
}
|
||||
|
||||
func initWindow(w *windowImpl) {
|
||||
w.glctx, w.worker = gl.NewContext()
|
||||
}
|
||||
|
||||
func showWindow(w *windowImpl) {
|
||||
C.doShowWindow(C.uintptr_t(w.id))
|
||||
}
|
||||
|
||||
//export preparedOpenGL
|
||||
func preparedOpenGL(id, ctx, vba uintptr) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[id]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
w.ctx = ctx
|
||||
go drawLoop(w, vba)
|
||||
}
|
||||
|
||||
func closeWindow(id uintptr) {
|
||||
C.doCloseWindow(C.uintptr_t(id))
|
||||
}
|
||||
|
||||
var mainCallback func(screen.Screen)
|
||||
|
||||
func main(f func(screen.Screen)) error {
|
||||
if tid := C.threadID(); tid != initThreadID {
|
||||
log.Fatalf("gldriver.Main called on thread %d, but gldriver.init ran on %d", tid, initThreadID)
|
||||
}
|
||||
|
||||
mainCallback = f
|
||||
C.startDriver()
|
||||
return nil
|
||||
}
|
||||
|
||||
//export driverStarted
|
||||
func driverStarted() {
|
||||
go func() {
|
||||
mainCallback(theScreen)
|
||||
C.stopDriver()
|
||||
}()
|
||||
}
|
||||
|
||||
//export drawgl
|
||||
func drawgl(id uintptr) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[id]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
if w == nil {
|
||||
return // closing window
|
||||
}
|
||||
|
||||
// TODO: is this necessary?
|
||||
w.lifecycler.SetVisible(true)
|
||||
w.lifecycler.SendEvent(w, w.glctx)
|
||||
|
||||
w.Send(paint.Event{External: true})
|
||||
<-w.drawDone
|
||||
}
|
||||
|
||||
// drawLoop is the primary drawing loop.
|
||||
//
|
||||
// After Cocoa has created an NSWindow and called prepareOpenGL,
|
||||
// it starts drawLoop on a locked goroutine to handle OpenGL calls.
|
||||
//
|
||||
// The screen is drawn every time a paint.Event is received, which can be
|
||||
// triggered either by the user or by Cocoa via drawgl (for example, when
|
||||
// the window is resized).
|
||||
func drawLoop(w *windowImpl, vba uintptr) {
|
||||
runtime.LockOSThread()
|
||||
C.makeCurrentContext(C.uintptr_t(w.ctx.(uintptr)))
|
||||
|
||||
// Starting in OS X 10.11 (El Capitan), the vertex array is
|
||||
// occasionally getting unbound when the context changes threads.
|
||||
//
|
||||
// Avoid this by binding it again.
|
||||
C.glBindVertexArray(C.GLuint(vba))
|
||||
if errno := C.glGetError(); errno != 0 {
|
||||
panic(fmt.Sprintf("gldriver: glBindVertexArray failed: %d", errno))
|
||||
}
|
||||
|
||||
workAvailable := w.worker.WorkAvailable()
|
||||
|
||||
// TODO(crawshaw): exit this goroutine on Release.
|
||||
for {
|
||||
select {
|
||||
case <-workAvailable:
|
||||
w.worker.DoWork()
|
||||
case <-w.publish:
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-workAvailable:
|
||||
w.worker.DoWork()
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
C.flushContext(C.uintptr_t(w.ctx.(uintptr)))
|
||||
w.publishDone <- screen.PublishResult{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//export setGeom
|
||||
func setGeom(id uintptr, ppp float32, widthPx, heightPx int) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[id]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
if w == nil {
|
||||
return // closing window
|
||||
}
|
||||
|
||||
sz := size.Event{
|
||||
WidthPx: widthPx,
|
||||
HeightPx: heightPx,
|
||||
WidthPt: geom.Pt(float32(widthPx) / ppp),
|
||||
HeightPt: geom.Pt(float32(heightPx) / ppp),
|
||||
PixelsPerPt: ppp,
|
||||
}
|
||||
|
||||
if !handleSizeEventsAtChannelReceive {
|
||||
w.szMu.Lock()
|
||||
w.sz = sz
|
||||
w.szMu.Unlock()
|
||||
}
|
||||
|
||||
w.Send(sz)
|
||||
}
|
||||
|
||||
//export windowClosing
|
||||
func windowClosing(id uintptr) {
|
||||
sendLifecycle(id, (*lifecycler.State).SetDead, true)
|
||||
}
|
||||
|
||||
func sendWindowEvent(id uintptr, e interface{}) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[id]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
if w == nil {
|
||||
return // closing window
|
||||
}
|
||||
w.Send(e)
|
||||
}
|
||||
|
||||
var mods = [...]struct {
|
||||
flags uint32
|
||||
code uint16
|
||||
mod key.Modifiers
|
||||
}{
|
||||
// Left and right variants of modifier keys have their own masks,
|
||||
// but they are not documented. These were determined empirically.
|
||||
{1<<17 | 0x102, C.kVK_Shift, key.ModShift},
|
||||
{1<<17 | 0x104, C.kVK_RightShift, key.ModShift},
|
||||
{1<<18 | 0x101, C.kVK_Control, key.ModControl},
|
||||
// TODO key.ControlRight
|
||||
{1<<19 | 0x120, C.kVK_Option, key.ModAlt},
|
||||
{1<<19 | 0x140, C.kVK_RightOption, key.ModAlt},
|
||||
{1<<20 | 0x108, C.kVK_Command, key.ModMeta},
|
||||
{1<<20 | 0x110, C.kVK_Command, key.ModMeta}, // TODO: missing kVK_RightCommand
|
||||
}
|
||||
|
||||
func cocoaMods(flags uint32) (m key.Modifiers) {
|
||||
for _, mod := range mods {
|
||||
if flags&mod.flags == mod.flags {
|
||||
m |= mod.mod
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func cocoaMouseDir(ty int32) mouse.Direction {
|
||||
switch ty {
|
||||
case C.NSLeftMouseDown, C.NSRightMouseDown, C.NSOtherMouseDown:
|
||||
return mouse.DirPress
|
||||
case C.NSLeftMouseUp, C.NSRightMouseUp, C.NSOtherMouseUp:
|
||||
return mouse.DirRelease
|
||||
default: // dragged
|
||||
return mouse.DirNone
|
||||
}
|
||||
}
|
||||
|
||||
func cocoaMouseButton(button int32) mouse.Button {
|
||||
switch button {
|
||||
case 0:
|
||||
return mouse.ButtonLeft
|
||||
case 1:
|
||||
return mouse.ButtonRight
|
||||
case 2:
|
||||
return mouse.ButtonMiddle
|
||||
default:
|
||||
return mouse.ButtonNone
|
||||
}
|
||||
}
|
||||
|
||||
//export mouseEvent
|
||||
func mouseEvent(id uintptr, x, y, dx, dy float32, ty, button int32, flags uint32) {
|
||||
cmButton := mouse.ButtonNone
|
||||
switch ty {
|
||||
default:
|
||||
cmButton = cocoaMouseButton(button)
|
||||
case C.NSMouseMoved, C.NSLeftMouseDragged, C.NSRightMouseDragged, C.NSOtherMouseDragged:
|
||||
// No-op.
|
||||
case C.NSScrollWheel:
|
||||
// Note that the direction of scrolling is inverted by default
|
||||
// on OS X by the "natural scrolling" setting. At the Cocoa
|
||||
// level this inversion is applied to trackpads and mice behind
|
||||
// the scenes, and the value of dy goes in the direction the OS
|
||||
// wants scrolling to go.
|
||||
//
|
||||
// This means the same trackpad/mouse motion on OS X and Linux
|
||||
// can produce wheel events in opposite directions, but the
|
||||
// direction matches what other programs on the OS do.
|
||||
//
|
||||
// If we wanted to expose the phsyical device motion in the
|
||||
// event we could use [NSEvent isDirectionInvertedFromDevice]
|
||||
// to know if "natural scrolling" is enabled.
|
||||
//
|
||||
// TODO: On a trackpad, a scroll can be a drawn-out affair with a
|
||||
// distinct beginning and end. Should the intermediate events be
|
||||
// DirNone?
|
||||
//
|
||||
// TODO: handle horizontal scrolling
|
||||
button := mouse.ButtonWheelUp
|
||||
if dy < 0 {
|
||||
dy = -dy
|
||||
button = mouse.ButtonWheelDown
|
||||
}
|
||||
e := mouse.Event{
|
||||
X: x,
|
||||
Y: y,
|
||||
Button: button,
|
||||
Direction: mouse.DirStep,
|
||||
Modifiers: cocoaMods(flags),
|
||||
}
|
||||
for delta := int(dy); delta != 0; delta-- {
|
||||
sendWindowEvent(id, e)
|
||||
}
|
||||
return
|
||||
}
|
||||
sendWindowEvent(id, mouse.Event{
|
||||
X: x,
|
||||
Y: y,
|
||||
Button: cmButton,
|
||||
Direction: cocoaMouseDir(ty),
|
||||
Modifiers: cocoaMods(flags),
|
||||
})
|
||||
}
|
||||
|
||||
//export keyEvent
|
||||
func keyEvent(id uintptr, runeVal rune, dir uint8, code uint16, flags uint32) {
|
||||
sendWindowEvent(id, key.Event{
|
||||
Rune: cocoaRune(runeVal),
|
||||
Direction: key.Direction(dir),
|
||||
Code: cocoaKeyCode(code),
|
||||
Modifiers: cocoaMods(flags),
|
||||
})
|
||||
}
|
||||
|
||||
//export flagEvent
|
||||
func flagEvent(id uintptr, flags uint32) {
|
||||
for _, mod := range mods {
|
||||
if flags&mod.flags == mod.flags && lastFlags&mod.flags != mod.flags {
|
||||
keyEvent(id, -1, C.NSKeyDown, mod.code, flags)
|
||||
}
|
||||
if lastFlags&mod.flags == mod.flags && flags&mod.flags != mod.flags {
|
||||
keyEvent(id, -1, C.NSKeyUp, mod.code, flags)
|
||||
}
|
||||
}
|
||||
lastFlags = flags
|
||||
}
|
||||
|
||||
var lastFlags uint32
|
||||
|
||||
func sendLifecycle(id uintptr, setter func(*lifecycler.State, bool), val bool) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[id]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
if w == nil {
|
||||
return
|
||||
}
|
||||
setter(&w.lifecycler, val)
|
||||
w.lifecycler.SendEvent(w, w.glctx)
|
||||
}
|
||||
|
||||
func sendLifecycleAll(dead bool) {
|
||||
windows := []*windowImpl{}
|
||||
|
||||
theScreen.mu.Lock()
|
||||
for _, w := range theScreen.windows {
|
||||
windows = append(windows, w)
|
||||
}
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
for _, w := range windows {
|
||||
w.lifecycler.SetFocused(false)
|
||||
w.lifecycler.SetVisible(false)
|
||||
if dead {
|
||||
w.lifecycler.SetDead(true)
|
||||
}
|
||||
w.lifecycler.SendEvent(w, w.glctx)
|
||||
}
|
||||
}
|
||||
|
||||
//export lifecycleDeadAll
|
||||
func lifecycleDeadAll() { sendLifecycleAll(true) }
|
||||
|
||||
//export lifecycleHideAll
|
||||
func lifecycleHideAll() { sendLifecycleAll(false) }
|
||||
|
||||
//export lifecycleVisible
|
||||
func lifecycleVisible(id uintptr, val bool) {
|
||||
sendLifecycle(id, (*lifecycler.State).SetVisible, val)
|
||||
}
|
||||
|
||||
//export lifecycleFocused
|
||||
func lifecycleFocused(id uintptr, val bool) {
|
||||
sendLifecycle(id, (*lifecycler.State).SetFocused, val)
|
||||
}
|
||||
|
||||
// cocoaRune marks the Carbon/Cocoa private-range unicode rune representing
|
||||
// a non-unicode key event to -1, used for Rune in the key package.
|
||||
//
|
||||
// http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT
|
||||
func cocoaRune(r rune) rune {
|
||||
if '\uE000' <= r && r <= '\uF8FF' {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// cocoaKeyCode converts a Carbon/Cocoa virtual key code number
|
||||
// into the standard keycodes used by the key package.
|
||||
//
|
||||
// To get a sense of the key map, see the diagram on
|
||||
// http://boredzo.org/blog/archives/2007-05-22/virtual-key-codes
|
||||
func cocoaKeyCode(vkcode uint16) key.Code {
|
||||
switch vkcode {
|
||||
case C.kVK_ANSI_A:
|
||||
return key.CodeA
|
||||
case C.kVK_ANSI_B:
|
||||
return key.CodeB
|
||||
case C.kVK_ANSI_C:
|
||||
return key.CodeC
|
||||
case C.kVK_ANSI_D:
|
||||
return key.CodeD
|
||||
case C.kVK_ANSI_E:
|
||||
return key.CodeE
|
||||
case C.kVK_ANSI_F:
|
||||
return key.CodeF
|
||||
case C.kVK_ANSI_G:
|
||||
return key.CodeG
|
||||
case C.kVK_ANSI_H:
|
||||
return key.CodeH
|
||||
case C.kVK_ANSI_I:
|
||||
return key.CodeI
|
||||
case C.kVK_ANSI_J:
|
||||
return key.CodeJ
|
||||
case C.kVK_ANSI_K:
|
||||
return key.CodeK
|
||||
case C.kVK_ANSI_L:
|
||||
return key.CodeL
|
||||
case C.kVK_ANSI_M:
|
||||
return key.CodeM
|
||||
case C.kVK_ANSI_N:
|
||||
return key.CodeN
|
||||
case C.kVK_ANSI_O:
|
||||
return key.CodeO
|
||||
case C.kVK_ANSI_P:
|
||||
return key.CodeP
|
||||
case C.kVK_ANSI_Q:
|
||||
return key.CodeQ
|
||||
case C.kVK_ANSI_R:
|
||||
return key.CodeR
|
||||
case C.kVK_ANSI_S:
|
||||
return key.CodeS
|
||||
case C.kVK_ANSI_T:
|
||||
return key.CodeT
|
||||
case C.kVK_ANSI_U:
|
||||
return key.CodeU
|
||||
case C.kVK_ANSI_V:
|
||||
return key.CodeV
|
||||
case C.kVK_ANSI_W:
|
||||
return key.CodeW
|
||||
case C.kVK_ANSI_X:
|
||||
return key.CodeX
|
||||
case C.kVK_ANSI_Y:
|
||||
return key.CodeY
|
||||
case C.kVK_ANSI_Z:
|
||||
return key.CodeZ
|
||||
case C.kVK_ANSI_1:
|
||||
return key.Code1
|
||||
case C.kVK_ANSI_2:
|
||||
return key.Code2
|
||||
case C.kVK_ANSI_3:
|
||||
return key.Code3
|
||||
case C.kVK_ANSI_4:
|
||||
return key.Code4
|
||||
case C.kVK_ANSI_5:
|
||||
return key.Code5
|
||||
case C.kVK_ANSI_6:
|
||||
return key.Code6
|
||||
case C.kVK_ANSI_7:
|
||||
return key.Code7
|
||||
case C.kVK_ANSI_8:
|
||||
return key.Code8
|
||||
case C.kVK_ANSI_9:
|
||||
return key.Code9
|
||||
case C.kVK_ANSI_0:
|
||||
return key.Code0
|
||||
// TODO: move the rest of these codes to constants in key.go
|
||||
// if we are happy with them.
|
||||
case C.kVK_Return:
|
||||
return key.CodeReturnEnter
|
||||
case C.kVK_Escape:
|
||||
return key.CodeEscape
|
||||
case C.kVK_Delete:
|
||||
return key.CodeDeleteBackspace
|
||||
case C.kVK_Tab:
|
||||
return key.CodeTab
|
||||
case C.kVK_Space:
|
||||
return key.CodeSpacebar
|
||||
case C.kVK_ANSI_Minus:
|
||||
return key.CodeHyphenMinus
|
||||
case C.kVK_ANSI_Equal:
|
||||
return key.CodeEqualSign
|
||||
case C.kVK_ANSI_LeftBracket:
|
||||
return key.CodeLeftSquareBracket
|
||||
case C.kVK_ANSI_RightBracket:
|
||||
return key.CodeRightSquareBracket
|
||||
case C.kVK_ANSI_Backslash:
|
||||
return key.CodeBackslash
|
||||
// 50: Keyboard Non-US "#" and ~
|
||||
case C.kVK_ANSI_Semicolon:
|
||||
return key.CodeSemicolon
|
||||
case C.kVK_ANSI_Quote:
|
||||
return key.CodeApostrophe
|
||||
case C.kVK_ANSI_Grave:
|
||||
return key.CodeGraveAccent
|
||||
case C.kVK_ANSI_Comma:
|
||||
return key.CodeComma
|
||||
case C.kVK_ANSI_Period:
|
||||
return key.CodeFullStop
|
||||
case C.kVK_ANSI_Slash:
|
||||
return key.CodeSlash
|
||||
case C.kVK_CapsLock:
|
||||
return key.CodeCapsLock
|
||||
case C.kVK_F1:
|
||||
return key.CodeF1
|
||||
case C.kVK_F2:
|
||||
return key.CodeF2
|
||||
case C.kVK_F3:
|
||||
return key.CodeF3
|
||||
case C.kVK_F4:
|
||||
return key.CodeF4
|
||||
case C.kVK_F5:
|
||||
return key.CodeF5
|
||||
case C.kVK_F6:
|
||||
return key.CodeF6
|
||||
case C.kVK_F7:
|
||||
return key.CodeF7
|
||||
case C.kVK_F8:
|
||||
return key.CodeF8
|
||||
case C.kVK_F9:
|
||||
return key.CodeF9
|
||||
case C.kVK_F10:
|
||||
return key.CodeF10
|
||||
case C.kVK_F11:
|
||||
return key.CodeF11
|
||||
case C.kVK_F12:
|
||||
return key.CodeF12
|
||||
// 70: PrintScreen
|
||||
// 71: Scroll Lock
|
||||
// 72: Pause
|
||||
// 73: Insert
|
||||
case C.kVK_Home:
|
||||
return key.CodeHome
|
||||
case C.kVK_PageUp:
|
||||
return key.CodePageUp
|
||||
case C.kVK_ForwardDelete:
|
||||
return key.CodeDeleteForward
|
||||
case C.kVK_End:
|
||||
return key.CodeEnd
|
||||
case C.kVK_PageDown:
|
||||
return key.CodePageDown
|
||||
case C.kVK_RightArrow:
|
||||
return key.CodeRightArrow
|
||||
case C.kVK_LeftArrow:
|
||||
return key.CodeLeftArrow
|
||||
case C.kVK_DownArrow:
|
||||
return key.CodeDownArrow
|
||||
case C.kVK_UpArrow:
|
||||
return key.CodeUpArrow
|
||||
case C.kVK_ANSI_KeypadClear:
|
||||
return key.CodeKeypadNumLock
|
||||
case C.kVK_ANSI_KeypadDivide:
|
||||
return key.CodeKeypadSlash
|
||||
case C.kVK_ANSI_KeypadMultiply:
|
||||
return key.CodeKeypadAsterisk
|
||||
case C.kVK_ANSI_KeypadMinus:
|
||||
return key.CodeKeypadHyphenMinus
|
||||
case C.kVK_ANSI_KeypadPlus:
|
||||
return key.CodeKeypadPlusSign
|
||||
case C.kVK_ANSI_KeypadEnter:
|
||||
return key.CodeKeypadEnter
|
||||
case C.kVK_ANSI_Keypad1:
|
||||
return key.CodeKeypad1
|
||||
case C.kVK_ANSI_Keypad2:
|
||||
return key.CodeKeypad2
|
||||
case C.kVK_ANSI_Keypad3:
|
||||
return key.CodeKeypad3
|
||||
case C.kVK_ANSI_Keypad4:
|
||||
return key.CodeKeypad4
|
||||
case C.kVK_ANSI_Keypad5:
|
||||
return key.CodeKeypad5
|
||||
case C.kVK_ANSI_Keypad6:
|
||||
return key.CodeKeypad6
|
||||
case C.kVK_ANSI_Keypad7:
|
||||
return key.CodeKeypad7
|
||||
case C.kVK_ANSI_Keypad8:
|
||||
return key.CodeKeypad8
|
||||
case C.kVK_ANSI_Keypad9:
|
||||
return key.CodeKeypad9
|
||||
case C.kVK_ANSI_Keypad0:
|
||||
return key.CodeKeypad0
|
||||
case C.kVK_ANSI_KeypadDecimal:
|
||||
return key.CodeKeypadFullStop
|
||||
case C.kVK_ANSI_KeypadEquals:
|
||||
return key.CodeKeypadEqualSign
|
||||
case C.kVK_F13:
|
||||
return key.CodeF13
|
||||
case C.kVK_F14:
|
||||
return key.CodeF14
|
||||
case C.kVK_F15:
|
||||
return key.CodeF15
|
||||
case C.kVK_F16:
|
||||
return key.CodeF16
|
||||
case C.kVK_F17:
|
||||
return key.CodeF17
|
||||
case C.kVK_F18:
|
||||
return key.CodeF18
|
||||
case C.kVK_F19:
|
||||
return key.CodeF19
|
||||
case C.kVK_F20:
|
||||
return key.CodeF20
|
||||
// 116: Keyboard Execute
|
||||
case C.kVK_Help:
|
||||
return key.CodeHelp
|
||||
// 118: Keyboard Menu
|
||||
// 119: Keyboard Select
|
||||
// 120: Keyboard Stop
|
||||
// 121: Keyboard Again
|
||||
// 122: Keyboard Undo
|
||||
// 123: Keyboard Cut
|
||||
// 124: Keyboard Copy
|
||||
// 125: Keyboard Paste
|
||||
// 126: Keyboard Find
|
||||
case C.kVK_Mute:
|
||||
return key.CodeMute
|
||||
case C.kVK_VolumeUp:
|
||||
return key.CodeVolumeUp
|
||||
case C.kVK_VolumeDown:
|
||||
return key.CodeVolumeDown
|
||||
// 130: Keyboard Locking Caps Lock
|
||||
// 131: Keyboard Locking Num Lock
|
||||
// 132: Keyboard Locking Scroll Lock
|
||||
// 133: Keyboard Comma
|
||||
// 134: Keyboard Equal Sign
|
||||
// ...: Bunch of stuff
|
||||
case C.kVK_Control:
|
||||
return key.CodeLeftControl
|
||||
case C.kVK_Shift:
|
||||
return key.CodeLeftShift
|
||||
case C.kVK_Option:
|
||||
return key.CodeLeftAlt
|
||||
case C.kVK_Command:
|
||||
return key.CodeLeftGUI
|
||||
case C.kVK_RightControl:
|
||||
return key.CodeRightControl
|
||||
case C.kVK_RightShift:
|
||||
return key.CodeRightShift
|
||||
case C.kVK_RightOption:
|
||||
return key.CodeRightAlt
|
||||
// TODO key.CodeRightGUI
|
||||
default:
|
||||
return key.CodeUnknown
|
||||
}
|
||||
}
|
||||
|
||||
func surfaceCreate() error {
|
||||
return errors.New("gldriver: surface creation not implemented on darwin")
|
||||
}
|
@ -1,327 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin
|
||||
// +build 386 amd64
|
||||
// +build !ios
|
||||
|
||||
#include "_cgo_export.h"
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <OpenGL/gl3.h>
|
||||
|
||||
// The variables did not exist on older OS X releases,
|
||||
// we use the old variables deprecated on macOS to define them.
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101200
|
||||
enum
|
||||
{
|
||||
NSEventTypeScrollWheel = NSScrollWheel,
|
||||
NSEventTypeKeyDown = NSKeyDown
|
||||
};
|
||||
enum
|
||||
{
|
||||
NSWindowStyleMaskTitled = NSTitledWindowMask,
|
||||
NSWindowStyleMaskResizable = NSResizableWindowMask,
|
||||
NSWindowStyleMaskMiniaturizable = NSMiniaturizableWindowMask,
|
||||
NSWindowStyleMaskClosable = NSClosableWindowMask
|
||||
};
|
||||
#endif
|
||||
|
||||
void makeCurrentContext(uintptr_t context) {
|
||||
NSOpenGLContext* ctx = (NSOpenGLContext*)context;
|
||||
[ctx makeCurrentContext];
|
||||
}
|
||||
|
||||
void flushContext(uintptr_t context) {
|
||||
NSOpenGLContext* ctx = (NSOpenGLContext*)context;
|
||||
[ctx flushBuffer];
|
||||
}
|
||||
|
||||
uint64 threadID() {
|
||||
uint64 id;
|
||||
if (pthread_threadid_np(pthread_self(), &id)) {
|
||||
abort();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
@interface ScreenGLView : NSOpenGLView<NSWindowDelegate>
|
||||
{
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation ScreenGLView
|
||||
- (void)prepareOpenGL {
|
||||
[self setWantsBestResolutionOpenGLSurface:YES];
|
||||
GLint swapInt = 1;
|
||||
NSOpenGLContext *ctx = [self openGLContext];
|
||||
[ctx setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
|
||||
|
||||
// Using attribute arrays in OpenGL 3.3 requires the use of a VBA.
|
||||
// But VBAs don't exist in ES 2. So we bind a default one.
|
||||
GLuint vba;
|
||||
glGenVertexArrays(1, &vba);
|
||||
glBindVertexArray(vba);
|
||||
|
||||
preparedOpenGL((GoUintptr)self, (GoUintptr)ctx, (GoUintptr)vba);
|
||||
}
|
||||
|
||||
- (void)callSetGeom {
|
||||
// Calculate screen PPI.
|
||||
//
|
||||
// Note that the backingScaleFactor converts from logical
|
||||
// pixels to actual pixels, but both of these units vary
|
||||
// independently from real world size. E.g.
|
||||
//
|
||||
// 13" Retina Macbook Pro, 2560x1600, 227ppi, backingScaleFactor=2, scale=3.15
|
||||
// 15" Retina Macbook Pro, 2880x1800, 220ppi, backingScaleFactor=2, scale=3.06
|
||||
// 27" iMac, 2560x1440, 109ppi, backingScaleFactor=1, scale=1.51
|
||||
// 27" Retina iMac, 5120x2880, 218ppi, backingScaleFactor=2, scale=3.03
|
||||
NSScreen *screen = self.window.screen;
|
||||
double screenPixW = [screen frame].size.width * [screen backingScaleFactor];
|
||||
|
||||
CGDirectDisplayID display = (CGDirectDisplayID)[[[screen deviceDescription] valueForKey:@"NSScreenNumber"] intValue];
|
||||
CGSize screenSizeMM = CGDisplayScreenSize(display); // in millimeters
|
||||
float ppi = 25.4 * screenPixW / screenSizeMM.width;
|
||||
float pixelsPerPt = ppi/72.0;
|
||||
|
||||
// The width and height reported to the geom package are the
|
||||
// bounds of the OpenGL view. Several steps are necessary.
|
||||
// First, [self bounds] gives us the number of logical pixels
|
||||
// in the view. Multiplying this by the backingScaleFactor
|
||||
// gives us the number of actual pixels.
|
||||
NSRect r = [self bounds];
|
||||
int w = r.size.width * [screen backingScaleFactor];
|
||||
int h = r.size.height * [screen backingScaleFactor];
|
||||
|
||||
setGeom((GoUintptr)self, pixelsPerPt, w, h);
|
||||
}
|
||||
|
||||
- (void)reshape {
|
||||
[super reshape];
|
||||
[self callSetGeom];
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)theRect {
|
||||
// Called during resize. Do an extra draw if we are visible.
|
||||
// This gets rid of flicker when resizing.
|
||||
drawgl((GoUintptr)self);
|
||||
}
|
||||
|
||||
- (void)mouseEventNS:(NSEvent *)theEvent {
|
||||
NSPoint p = [theEvent locationInWindow];
|
||||
double h = self.frame.size.height;
|
||||
|
||||
// Both h and p are measured in Cocoa pixels, which are a fraction of
|
||||
// physical pixels, so we multiply by backingScaleFactor.
|
||||
double scale = [self.window.screen backingScaleFactor];
|
||||
|
||||
double x = p.x * scale;
|
||||
double y = (h - p.y) * scale - 1; // flip origin from bottom-left to top-left.
|
||||
|
||||
double dx, dy;
|
||||
if (theEvent.type == NSEventTypeScrollWheel) {
|
||||
dx = theEvent.scrollingDeltaX;
|
||||
dy = theEvent.scrollingDeltaY;
|
||||
}
|
||||
|
||||
mouseEvent((GoUintptr)self, x, y, dx, dy, theEvent.type, theEvent.buttonNumber, theEvent.modifierFlags);
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
|
||||
- (void)mouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
|
||||
- (void)mouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
|
||||
- (void)mouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
|
||||
- (void)rightMouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
|
||||
- (void)rightMouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
|
||||
- (void)rightMouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
|
||||
- (void)otherMouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
|
||||
- (void)otherMouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
|
||||
- (void)otherMouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
|
||||
- (void)scrollWheel:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
|
||||
|
||||
// raw modifier key presses
|
||||
- (void)flagsChanged:(NSEvent *)theEvent {
|
||||
flagEvent((GoUintptr)self, theEvent.modifierFlags);
|
||||
}
|
||||
|
||||
// overrides special handling of escape and tab
|
||||
- (BOOL)performKeyEquivalent:(NSEvent *)theEvent {
|
||||
[self key:theEvent];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)keyDown:(NSEvent *)theEvent { [self key:theEvent]; }
|
||||
- (void)keyUp:(NSEvent *)theEvent { [self key:theEvent]; }
|
||||
|
||||
- (void)key:(NSEvent *)theEvent {
|
||||
NSRange range = [theEvent.characters rangeOfComposedCharacterSequenceAtIndex:0];
|
||||
|
||||
uint8_t buf[4] = {0, 0, 0, 0};
|
||||
if (![theEvent.characters getBytes:buf
|
||||
maxLength:4
|
||||
usedLength:nil
|
||||
encoding:NSUTF32LittleEndianStringEncoding
|
||||
options:NSStringEncodingConversionAllowLossy
|
||||
range:range
|
||||
remainingRange:nil]) {
|
||||
NSLog(@"failed to read key event %@", theEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t rune = (uint32_t)buf[0]<<0 | (uint32_t)buf[1]<<8 | (uint32_t)buf[2]<<16 | (uint32_t)buf[3]<<24;
|
||||
|
||||
uint8_t direction;
|
||||
if ([theEvent isARepeat]) {
|
||||
direction = 0;
|
||||
} else if (theEvent.type == NSEventTypeKeyDown) {
|
||||
direction = 1;
|
||||
} else {
|
||||
direction = 2;
|
||||
}
|
||||
keyEvent((GoUintptr)self, (int32_t)rune, direction, theEvent.keyCode, theEvent.modifierFlags);
|
||||
}
|
||||
|
||||
- (void)windowDidChangeScreenProfile:(NSNotification *)notification {
|
||||
[self callSetGeom];
|
||||
}
|
||||
|
||||
// TODO: catch windowDidMiniaturize?
|
||||
|
||||
- (void)windowDidExpose:(NSNotification *)notification {
|
||||
lifecycleVisible((GoUintptr)self, true);
|
||||
}
|
||||
|
||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||
lifecycleFocused((GoUintptr)self, true);
|
||||
}
|
||||
|
||||
- (void)windowDidResignKey:(NSNotification *)notification {
|
||||
lifecycleFocused((GoUintptr)self, false);
|
||||
if ([NSApp isHidden]) {
|
||||
lifecycleVisible((GoUintptr)self, false);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)windowWillClose:(NSNotification *)notification {
|
||||
// TODO: is this right? Closing a window via the top-left red button
|
||||
// seems to return early without ever calling windowClosing.
|
||||
if (self.window.nextResponder == NULL) {
|
||||
return; // already called close
|
||||
}
|
||||
|
||||
windowClosing((GoUintptr)self);
|
||||
[self.window.nextResponder release];
|
||||
self.window.nextResponder = NULL;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface AppDelegate : NSObject<NSApplicationDelegate>
|
||||
{
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation AppDelegate
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
driverStarted();
|
||||
[[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(NSNotification *)aNotification {
|
||||
lifecycleDeadAll();
|
||||
}
|
||||
|
||||
- (void)applicationWillHide:(NSNotification *)aNotification {
|
||||
lifecycleHideAll();
|
||||
}
|
||||
@end
|
||||
|
||||
uintptr_t doNewWindow(int width, int height, char* title) {
|
||||
NSScreen *screen = [NSScreen mainScreen];
|
||||
double w = (double)width / [screen backingScaleFactor];
|
||||
double h = (double)height / [screen backingScaleFactor];
|
||||
__block ScreenGLView* view = NULL;
|
||||
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
id menuBar = [NSMenu new];
|
||||
id menuItem = [NSMenuItem new];
|
||||
[menuBar addItem:menuItem];
|
||||
[NSApp setMainMenu:menuBar];
|
||||
|
||||
id menu = [NSMenu new];
|
||||
NSString* name = [[NSString alloc] initWithUTF8String:title];
|
||||
|
||||
id hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide"
|
||||
action:@selector(hide:) keyEquivalent:@"h"];
|
||||
[menu addItem:hideMenuItem];
|
||||
|
||||
id quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
|
||||
action:@selector(terminate:) keyEquivalent:@"q"];
|
||||
[menu addItem:quitMenuItem];
|
||||
[menuItem setSubmenu:menu];
|
||||
|
||||
NSRect rect = NSMakeRect(0, 0, w, h);
|
||||
|
||||
NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
|
||||
styleMask:NSWindowStyleMaskTitled
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
window.styleMask |= NSWindowStyleMaskResizable;
|
||||
window.styleMask |= NSWindowStyleMaskMiniaturizable;
|
||||
window.styleMask |= NSWindowStyleMaskClosable;
|
||||
window.title = name;
|
||||
window.displaysWhenScreenProfileChanges = YES;
|
||||
[window cascadeTopLeftFromPoint:NSMakePoint(20,20)];
|
||||
[window setAcceptsMouseMovedEvents:YES];
|
||||
|
||||
NSOpenGLPixelFormatAttribute attr[] = {
|
||||
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
|
||||
NSOpenGLPFAColorSize, 24,
|
||||
NSOpenGLPFAAlphaSize, 8,
|
||||
NSOpenGLPFADepthSize, 16,
|
||||
NSOpenGLPFADoubleBuffer,
|
||||
NSOpenGLPFAAllowOfflineRenderers,
|
||||
0
|
||||
};
|
||||
id pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
|
||||
view = [[ScreenGLView alloc] initWithFrame:rect pixelFormat:pixFormat];
|
||||
[window setContentView:view];
|
||||
[window setDelegate:view];
|
||||
[window makeFirstResponder:view];
|
||||
});
|
||||
|
||||
return (uintptr_t)view;
|
||||
}
|
||||
|
||||
void doShowWindow(uintptr_t viewID) {
|
||||
ScreenGLView* view = (ScreenGLView*)viewID;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[view.window makeKeyAndOrderFront:view.window];
|
||||
});
|
||||
}
|
||||
|
||||
void doCloseWindow(uintptr_t viewID) {
|
||||
ScreenGLView* view = (ScreenGLView*)viewID;
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
[view.window performClose:view];
|
||||
});
|
||||
}
|
||||
|
||||
void startDriver() {
|
||||
[NSAutoreleasePool new];
|
||||
[NSApplication sharedApplication];
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
AppDelegate* delegate = [[AppDelegate alloc] init];
|
||||
[NSApp setDelegate:delegate];
|
||||
[NSApp run];
|
||||
}
|
||||
|
||||
void stopDriver() {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[NSApp terminate:nil];
|
||||
});
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gldriver
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
// NewContext creates an OpenGL ES context with a dedicated processing thread.
|
||||
func NewContext() (gl.Context, error) {
|
||||
glctx, worker := gl.NewContext()
|
||||
|
||||
errCh := make(chan error)
|
||||
workAvailable := worker.WorkAvailable()
|
||||
go func() {
|
||||
runtime.LockOSThread()
|
||||
err := surfaceCreate()
|
||||
errCh <- err
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for range workAvailable {
|
||||
worker.DoWork()
|
||||
}
|
||||
}()
|
||||
if err := <-errCh; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return glctx, nil
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gldriver
|
||||
|
||||
// These constants match the values found in the EGL 1.4 headers,
|
||||
// egl.h, eglext.h, and eglplatform.h.
|
||||
const (
|
||||
_EGL_DONT_CARE = -1
|
||||
|
||||
_EGL_NO_SURFACE = 0
|
||||
_EGL_NO_CONTEXT = 0
|
||||
_EGL_NO_DISPLAY = 0
|
||||
|
||||
_EGL_OPENGL_ES2_BIT = 0x04 // EGL_RENDERABLE_TYPE mask
|
||||
_EGL_WINDOW_BIT = 0x04 // EGL_SURFACE_TYPE mask
|
||||
|
||||
_EGL_OPENGL_ES_API = 0x30A0
|
||||
_EGL_RENDERABLE_TYPE = 0x3040
|
||||
_EGL_SURFACE_TYPE = 0x3033
|
||||
_EGL_BUFFER_SIZE = 0x3020
|
||||
_EGL_ALPHA_SIZE = 0x3021
|
||||
_EGL_BLUE_SIZE = 0x3022
|
||||
_EGL_GREEN_SIZE = 0x3023
|
||||
_EGL_RED_SIZE = 0x3024
|
||||
_EGL_DEPTH_SIZE = 0x3025
|
||||
_EGL_STENCIL_SIZE = 0x3026
|
||||
_EGL_SAMPLE_BUFFERS = 0x3032
|
||||
_EGL_CONFIG_CAVEAT = 0x3027
|
||||
_EGL_NONE = 0x3038
|
||||
|
||||
_EGL_CONTEXT_CLIENT_VERSION = 0x3098
|
||||
)
|
||||
|
||||
// ANGLE specific options found in eglext.h
|
||||
const (
|
||||
_EGL_PLATFORM_ANGLE_ANGLE = 0x3202
|
||||
_EGL_PLATFORM_ANGLE_TYPE_ANGLE = 0x3203
|
||||
_EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE = 0x3204
|
||||
_EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE = 0x3205
|
||||
_EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE = 0x3206
|
||||
|
||||
_EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE = 0x3207
|
||||
_EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE = 0x3208
|
||||
_EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE = 0x3209
|
||||
_EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE = 0x320A
|
||||
_EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE = 0x320B
|
||||
|
||||
_EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE = 0x320D
|
||||
_EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE = 0x320E
|
||||
)
|
||||
|
||||
const (
|
||||
_EGL_SUCCESS = 0x3000
|
||||
_EGL_NOT_INITIALIZED = 0x3001
|
||||
_EGL_BAD_ACCESS = 0x3002
|
||||
_EGL_BAD_ALLOC = 0x3003
|
||||
_EGL_BAD_ATTRIBUTE = 0x3004
|
||||
_EGL_BAD_CONFIG = 0x3005
|
||||
_EGL_BAD_CONTEXT = 0x3006
|
||||
_EGL_BAD_CURRENT_SURFACE = 0x3007
|
||||
_EGL_BAD_DISPLAY = 0x3008
|
||||
_EGL_BAD_MATCH = 0x3009
|
||||
_EGL_BAD_NATIVE_PIXMAP = 0x300A
|
||||
_EGL_BAD_NATIVE_WINDOW = 0x300B
|
||||
_EGL_BAD_PARAMETER = 0x300C
|
||||
_EGL_BAD_SURFACE = 0x300D
|
||||
_EGL_CONTEXT_LOST = 0x300E
|
||||
)
|
||||
|
||||
func eglErrString(errno uintptr) string {
|
||||
switch errno {
|
||||
case _EGL_SUCCESS:
|
||||
return "EGL_SUCCESS"
|
||||
case _EGL_NOT_INITIALIZED:
|
||||
return "EGL_NOT_INITIALIZED"
|
||||
case _EGL_BAD_ACCESS:
|
||||
return "EGL_BAD_ACCESS"
|
||||
case _EGL_BAD_ALLOC:
|
||||
return "EGL_BAD_ALLOC"
|
||||
case _EGL_BAD_ATTRIBUTE:
|
||||
return "EGL_BAD_ATTRIBUTE"
|
||||
case _EGL_BAD_CONFIG:
|
||||
return "EGL_BAD_CONFIG"
|
||||
case _EGL_BAD_CONTEXT:
|
||||
return "EGL_BAD_CONTEXT"
|
||||
case _EGL_BAD_CURRENT_SURFACE:
|
||||
return "EGL_BAD_CURRENT_SURFACE"
|
||||
case _EGL_BAD_DISPLAY:
|
||||
return "EGL_BAD_DISPLAY"
|
||||
case _EGL_BAD_MATCH:
|
||||
return "EGL_BAD_MATCH"
|
||||
case _EGL_BAD_NATIVE_PIXMAP:
|
||||
return "EGL_BAD_NATIVE_PIXMAP"
|
||||
case _EGL_BAD_NATIVE_WINDOW:
|
||||
return "EGL_BAD_NATIVE_WINDOW"
|
||||
case _EGL_BAD_PARAMETER:
|
||||
return "EGL_BAD_PARAMETER"
|
||||
case _EGL_BAD_SURFACE:
|
||||
return "EGL_BAD_SURFACE"
|
||||
case _EGL_CONTEXT_LOST:
|
||||
return "EGL_CONTEXT_LOST"
|
||||
}
|
||||
return "EGL: unknown error"
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package gldriver provides an OpenGL driver for accessing a screen.
|
||||
package gldriver // import "golang.org/x/exp/shiny/driver/gldriver"
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"golang.org/x/exp/shiny/driver/internal/errscreen"
|
||||
"golang.org/x/exp/shiny/screen"
|
||||
"golang.org/x/image/math/f64"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
// Main is called by the program's main function to run the graphical
|
||||
// application.
|
||||
//
|
||||
// It calls f on the Screen, possibly in a separate goroutine, as some OS-
|
||||
// specific libraries require being on 'the main thread'. It returns when f
|
||||
// returns.
|
||||
func Main(f func(screen.Screen)) {
|
||||
if err := main(f); err != nil {
|
||||
f(errscreen.Stub(err))
|
||||
}
|
||||
}
|
||||
|
||||
func mul(a, b f64.Aff3) f64.Aff3 {
|
||||
return f64.Aff3{
|
||||
a[0]*b[0] + a[1]*b[3],
|
||||
a[0]*b[1] + a[1]*b[4],
|
||||
a[0]*b[2] + a[1]*b[5] + a[2],
|
||||
|
||||
a[3]*b[0] + a[4]*b[3],
|
||||
a[3]*b[1] + a[4]*b[4],
|
||||
a[3]*b[2] + a[4]*b[5] + a[5],
|
||||
}
|
||||
}
|
||||
|
||||
// writeAff3 must only be called while holding windowImpl.glctxMu.
|
||||
func writeAff3(glctx gl.Context, u gl.Uniform, a f64.Aff3) {
|
||||
var m [9]float32
|
||||
m[0*3+0] = float32(a[0*3+0])
|
||||
m[0*3+1] = float32(a[1*3+0])
|
||||
m[0*3+2] = 0
|
||||
m[1*3+0] = float32(a[0*3+1])
|
||||
m[1*3+1] = float32(a[1*3+1])
|
||||
m[1*3+2] = 0
|
||||
m[2*3+0] = float32(a[0*3+2])
|
||||
m[2*3+1] = float32(a[1*3+2])
|
||||
m[2*3+2] = 1
|
||||
glctx.UniformMatrix3fv(u, m[:])
|
||||
}
|
||||
|
||||
// f32Bytes returns the byte representation of float32 values in the given byte
|
||||
// order. byteOrder must be either binary.BigEndian or binary.LittleEndian.
|
||||
func f32Bytes(byteOrder binary.ByteOrder, values ...float32) []byte {
|
||||
le := false
|
||||
switch byteOrder {
|
||||
case binary.BigEndian:
|
||||
case binary.LittleEndian:
|
||||
le = true
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid byte order %v", byteOrder))
|
||||
}
|
||||
|
||||
b := make([]byte, 4*len(values))
|
||||
for i, v := range values {
|
||||
u := math.Float32bits(v)
|
||||
if le {
|
||||
b[4*i+0] = byte(u >> 0)
|
||||
b[4*i+1] = byte(u >> 8)
|
||||
b[4*i+2] = byte(u >> 16)
|
||||
b[4*i+3] = byte(u >> 24)
|
||||
} else {
|
||||
b[4*i+0] = byte(u >> 24)
|
||||
b[4*i+1] = byte(u >> 16)
|
||||
b[4*i+2] = byte(u >> 8)
|
||||
b[4*i+3] = byte(u >> 0)
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// compileProgram must only be called while holding windowImpl.glctxMu.
|
||||
func compileProgram(glctx gl.Context, vSrc, fSrc string) (gl.Program, error) {
|
||||
program := glctx.CreateProgram()
|
||||
if program.Value == 0 {
|
||||
return gl.Program{}, fmt.Errorf("gldriver: no programs available")
|
||||
}
|
||||
|
||||
vertexShader, err := compileShader(glctx, gl.VERTEX_SHADER, vSrc)
|
||||
if err != nil {
|
||||
return gl.Program{}, err
|
||||
}
|
||||
fragmentShader, err := compileShader(glctx, gl.FRAGMENT_SHADER, fSrc)
|
||||
if err != nil {
|
||||
glctx.DeleteShader(vertexShader)
|
||||
return gl.Program{}, err
|
||||
}
|
||||
|
||||
glctx.AttachShader(program, vertexShader)
|
||||
glctx.AttachShader(program, fragmentShader)
|
||||
glctx.LinkProgram(program)
|
||||
|
||||
// Flag shaders for deletion when program is unlinked.
|
||||
glctx.DeleteShader(vertexShader)
|
||||
glctx.DeleteShader(fragmentShader)
|
||||
|
||||
if glctx.GetProgrami(program, gl.LINK_STATUS) == 0 {
|
||||
defer glctx.DeleteProgram(program)
|
||||
return gl.Program{}, fmt.Errorf("gldriver: program compile: %s", glctx.GetProgramInfoLog(program))
|
||||
}
|
||||
return program, nil
|
||||
}
|
||||
|
||||
// compileShader must only be called while holding windowImpl.glctxMu.
|
||||
func compileShader(glctx gl.Context, shaderType gl.Enum, src string) (gl.Shader, error) {
|
||||
shader := glctx.CreateShader(shaderType)
|
||||
if shader.Value == 0 {
|
||||
return gl.Shader{}, fmt.Errorf("gldriver: could not create shader (type %v)", shaderType)
|
||||
}
|
||||
glctx.ShaderSource(shader, src)
|
||||
glctx.CompileShader(shader)
|
||||
if glctx.GetShaderi(shader, gl.COMPILE_STATUS) == 0 {
|
||||
defer glctx.DeleteShader(shader)
|
||||
return gl.Shader{}, fmt.Errorf("gldriver: shader compile: %s", glctx.GetShaderInfoLog(shader))
|
||||
}
|
||||
return shader, nil
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !darwin !386,!amd64 ios
|
||||
// +build !linux android
|
||||
// +build !windows
|
||||
// +build !openbsd
|
||||
|
||||
package gldriver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/exp/shiny/screen"
|
||||
)
|
||||
|
||||
func newWindow(opts *screen.NewWindowOptions) (uintptr, error) { return 0, nil }
|
||||
|
||||
func initWindow(id *windowImpl) {}
|
||||
func showWindow(id *windowImpl) {}
|
||||
func closeWindow(id uintptr) {}
|
||||
func drawLoop(w *windowImpl) {}
|
||||
|
||||
func main(f func(screen.Screen)) error {
|
||||
return fmt.Errorf("gldriver: unsupported GOOS/GOARCH %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gldriver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/exp/shiny/screen"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
var theScreen = &screenImpl{
|
||||
windows: make(map[uintptr]*windowImpl),
|
||||
}
|
||||
|
||||
type screenImpl struct {
|
||||
texture struct {
|
||||
program gl.Program
|
||||
pos gl.Attrib
|
||||
mvp gl.Uniform
|
||||
uvp gl.Uniform
|
||||
inUV gl.Attrib
|
||||
sample gl.Uniform
|
||||
quad gl.Buffer
|
||||
}
|
||||
fill struct {
|
||||
program gl.Program
|
||||
pos gl.Attrib
|
||||
mvp gl.Uniform
|
||||
color gl.Uniform
|
||||
quad gl.Buffer
|
||||
}
|
||||
|
||||
mu sync.Mutex
|
||||
windows map[uintptr]*windowImpl
|
||||
}
|
||||
|
||||
func (s *screenImpl) NewBuffer(size image.Point) (retBuf screen.Buffer, retErr error) {
|
||||
m := image.NewRGBA(image.Rectangle{Max: size})
|
||||
return &bufferImpl{
|
||||
buf: m.Pix,
|
||||
rgba: *m,
|
||||
size: size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *screenImpl) NewTexture(size image.Point) (screen.Texture, error) {
|
||||
// TODO: can we compile these programs eagerly instead of lazily?
|
||||
|
||||
// Find a GL context for this texture.
|
||||
// TODO: this might be correct. Some GL objects can be shared
|
||||
// across contexts. But this needs a review of the spec to make
|
||||
// sure it's correct, and some testing would be nice.
|
||||
var w *windowImpl
|
||||
|
||||
s.mu.Lock()
|
||||
for _, window := range s.windows {
|
||||
w = window
|
||||
break
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
if w == nil {
|
||||
return nil, fmt.Errorf("gldriver: no window available")
|
||||
}
|
||||
|
||||
w.glctxMu.Lock()
|
||||
defer w.glctxMu.Unlock()
|
||||
glctx := w.glctx
|
||||
if glctx == nil {
|
||||
return nil, fmt.Errorf("gldriver: no GL context available")
|
||||
}
|
||||
|
||||
if !glctx.IsProgram(s.texture.program) {
|
||||
p, err := compileProgram(glctx, textureVertexSrc, textureFragmentSrc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.texture.program = p
|
||||
s.texture.pos = glctx.GetAttribLocation(p, "pos")
|
||||
s.texture.mvp = glctx.GetUniformLocation(p, "mvp")
|
||||
s.texture.uvp = glctx.GetUniformLocation(p, "uvp")
|
||||
s.texture.inUV = glctx.GetAttribLocation(p, "inUV")
|
||||
s.texture.sample = glctx.GetUniformLocation(p, "sample")
|
||||
s.texture.quad = glctx.CreateBuffer()
|
||||
|
||||
glctx.BindBuffer(gl.ARRAY_BUFFER, s.texture.quad)
|
||||
glctx.BufferData(gl.ARRAY_BUFFER, quadCoords, gl.STATIC_DRAW)
|
||||
}
|
||||
|
||||
t := &textureImpl{
|
||||
w: w,
|
||||
id: glctx.CreateTexture(),
|
||||
size: size,
|
||||
}
|
||||
|
||||
glctx.BindTexture(gl.TEXTURE_2D, t.id)
|
||||
glctx.TexImage2D(gl.TEXTURE_2D, 0, size.X, size.Y, gl.RGBA, gl.UNSIGNED_BYTE, nil)
|
||||
glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||
glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
|
||||
glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||
glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func optsSize(opts *screen.NewWindowOptions) (width, height int) {
|
||||
width, height = 1024, 768
|
||||
if opts != nil {
|
||||
if opts.Width > 0 {
|
||||
width = opts.Width
|
||||
}
|
||||
if opts.Height > 0 {
|
||||
height = opts.Height
|
||||
}
|
||||
}
|
||||
return width, height
|
||||
}
|
||||
|
||||
func (s *screenImpl) NewWindow(opts *screen.NewWindowOptions) (screen.Window, error) {
|
||||
id, err := newWindow(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w := &windowImpl{
|
||||
s: s,
|
||||
id: id,
|
||||
publish: make(chan struct{}),
|
||||
publishDone: make(chan screen.PublishResult),
|
||||
drawDone: make(chan struct{}),
|
||||
}
|
||||
initWindow(w)
|
||||
|
||||
s.mu.Lock()
|
||||
s.windows[id] = w
|
||||
s.mu.Unlock()
|
||||
|
||||
if useLifecycler {
|
||||
w.lifecycler.SendEvent(w, nil)
|
||||
}
|
||||
|
||||
showWindow(w)
|
||||
|
||||
return w, nil
|
||||
}
|
@ -1,160 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gldriver
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
|
||||
"golang.org/x/exp/shiny/screen"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
type textureImpl struct {
|
||||
w *windowImpl
|
||||
id gl.Texture
|
||||
fb gl.Framebuffer
|
||||
size image.Point
|
||||
}
|
||||
|
||||
func (t *textureImpl) Size() image.Point { return t.size }
|
||||
func (t *textureImpl) Bounds() image.Rectangle { return image.Rectangle{Max: t.size} }
|
||||
|
||||
func (t *textureImpl) Release() {
|
||||
t.w.glctxMu.Lock()
|
||||
defer t.w.glctxMu.Unlock()
|
||||
|
||||
if t.fb.Value != 0 {
|
||||
t.w.glctx.DeleteFramebuffer(t.fb)
|
||||
t.fb = gl.Framebuffer{}
|
||||
}
|
||||
t.w.glctx.DeleteTexture(t.id)
|
||||
t.id = gl.Texture{}
|
||||
}
|
||||
|
||||
func (t *textureImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
|
||||
buf := src.(*bufferImpl)
|
||||
buf.preUpload()
|
||||
|
||||
// src2dst is added to convert from the src coordinate space to the dst
|
||||
// coordinate space. It is subtracted to convert the other way.
|
||||
src2dst := dp.Sub(sr.Min)
|
||||
|
||||
// Clip to the source.
|
||||
sr = sr.Intersect(buf.Bounds())
|
||||
|
||||
// Clip to the destination.
|
||||
dr := sr.Add(src2dst)
|
||||
dr = dr.Intersect(t.Bounds())
|
||||
if dr.Empty() {
|
||||
return
|
||||
}
|
||||
|
||||
// Bring dr.Min in dst-space back to src-space to get the pixel buffer offset.
|
||||
pix := buf.rgba.Pix[buf.rgba.PixOffset(dr.Min.X-src2dst.X, dr.Min.Y-src2dst.Y):]
|
||||
|
||||
t.w.glctxMu.Lock()
|
||||
defer t.w.glctxMu.Unlock()
|
||||
|
||||
t.w.glctx.BindTexture(gl.TEXTURE_2D, t.id)
|
||||
|
||||
width := dr.Dx()
|
||||
if width*4 == buf.rgba.Stride {
|
||||
t.w.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, dr.Min.X, dr.Min.Y, width, dr.Dy(), gl.RGBA, gl.UNSIGNED_BYTE, pix)
|
||||
return
|
||||
}
|
||||
// TODO: can we use GL_UNPACK_ROW_LENGTH with glPixelStorei for stride in
|
||||
// ES 3.0, instead of uploading the pixels row-by-row?
|
||||
for y, p := dr.Min.Y, 0; y < dr.Max.Y; y++ {
|
||||
t.w.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, dr.Min.X, y, width, 1, gl.RGBA, gl.UNSIGNED_BYTE, pix[p:])
|
||||
p += buf.rgba.Stride
|
||||
}
|
||||
}
|
||||
|
||||
func (t *textureImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
|
||||
minX := float64(dr.Min.X)
|
||||
minY := float64(dr.Min.Y)
|
||||
maxX := float64(dr.Max.X)
|
||||
maxY := float64(dr.Max.Y)
|
||||
mvp := calcMVP(
|
||||
t.size.X, t.size.Y,
|
||||
minX, minY,
|
||||
maxX, minY,
|
||||
minX, maxY,
|
||||
)
|
||||
|
||||
glctx := t.w.glctx
|
||||
|
||||
t.w.glctxMu.Lock()
|
||||
defer t.w.glctxMu.Unlock()
|
||||
|
||||
create := t.fb.Value == 0
|
||||
if create {
|
||||
t.fb = glctx.CreateFramebuffer()
|
||||
}
|
||||
glctx.BindFramebuffer(gl.FRAMEBUFFER, t.fb)
|
||||
if create {
|
||||
glctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, t.id, 0)
|
||||
}
|
||||
|
||||
glctx.Viewport(0, 0, t.size.X, t.size.Y)
|
||||
doFill(t.w.s, t.w.glctx, mvp, src, op)
|
||||
|
||||
// We can't restore the GL state (i.e. bind the back buffer, also known as
|
||||
// gl.Framebuffer{Value: 0}) right away, since we don't necessarily know
|
||||
// the right viewport size yet. It is valid to call textureImpl.Fill before
|
||||
// we've gotten our first size.Event. We bind it lazily instead.
|
||||
t.w.backBufferBound = false
|
||||
}
|
||||
|
||||
var quadCoords = f32Bytes(binary.LittleEndian,
|
||||
0, 0, // top left
|
||||
1, 0, // top right
|
||||
0, 1, // bottom left
|
||||
1, 1, // bottom right
|
||||
)
|
||||
|
||||
const textureVertexSrc = `#version 100
|
||||
uniform mat3 mvp;
|
||||
uniform mat3 uvp;
|
||||
attribute vec3 pos;
|
||||
attribute vec2 inUV;
|
||||
varying vec2 uv;
|
||||
void main() {
|
||||
vec3 p = pos;
|
||||
p.z = 1.0;
|
||||
gl_Position = vec4(mvp * p, 1);
|
||||
uv = (uvp * vec3(inUV, 1)).xy;
|
||||
}
|
||||
`
|
||||
|
||||
const textureFragmentSrc = `#version 100
|
||||
precision mediump float;
|
||||
varying vec2 uv;
|
||||
uniform sampler2D sample;
|
||||
void main() {
|
||||
gl_FragColor = texture2D(sample, uv);
|
||||
}
|
||||
`
|
||||
|
||||
const fillVertexSrc = `#version 100
|
||||
uniform mat3 mvp;
|
||||
attribute vec3 pos;
|
||||
void main() {
|
||||
vec3 p = pos;
|
||||
p.z = 1.0;
|
||||
gl_Position = vec4(mvp * p, 1);
|
||||
}
|
||||
`
|
||||
|
||||
const fillFragmentSrc = `#version 100
|
||||
precision mediump float;
|
||||
uniform vec4 color;
|
||||
void main() {
|
||||
gl_FragColor = color;
|
||||
}
|
||||
`
|
@ -1,357 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
package gldriver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/exp/shiny/driver/internal/win32"
|
||||
"golang.org/x/exp/shiny/screen"
|
||||
"golang.org/x/mobile/event/key"
|
||||
"golang.org/x/mobile/event/lifecycle"
|
||||
"golang.org/x/mobile/event/mouse"
|
||||
"golang.org/x/mobile/event/paint"
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
// TODO: change this to true, after manual testing on Win32.
|
||||
const useLifecycler = false
|
||||
|
||||
// TODO: change this to true, after manual testing on Win32.
|
||||
const handleSizeEventsAtChannelReceive = false
|
||||
|
||||
func main(f func(screen.Screen)) error {
|
||||
return win32.Main(func() { f(theScreen) })
|
||||
}
|
||||
|
||||
var (
|
||||
eglGetPlatformDisplayEXT = gl.LibEGL.NewProc("eglGetPlatformDisplayEXT")
|
||||
eglInitialize = gl.LibEGL.NewProc("eglInitialize")
|
||||
eglChooseConfig = gl.LibEGL.NewProc("eglChooseConfig")
|
||||
eglGetError = gl.LibEGL.NewProc("eglGetError")
|
||||
eglBindAPI = gl.LibEGL.NewProc("eglBindAPI")
|
||||
eglCreateWindowSurface = gl.LibEGL.NewProc("eglCreateWindowSurface")
|
||||
eglCreateContext = gl.LibEGL.NewProc("eglCreateContext")
|
||||
eglMakeCurrent = gl.LibEGL.NewProc("eglMakeCurrent")
|
||||
eglSwapInterval = gl.LibEGL.NewProc("eglSwapInterval")
|
||||
eglDestroySurface = gl.LibEGL.NewProc("eglDestroySurface")
|
||||
eglSwapBuffers = gl.LibEGL.NewProc("eglSwapBuffers")
|
||||
)
|
||||
|
||||
type eglConfig uintptr // void*
|
||||
|
||||
type eglInt int32
|
||||
|
||||
var rgb888 = [...]eglInt{
|
||||
_EGL_RENDERABLE_TYPE, _EGL_OPENGL_ES2_BIT,
|
||||
_EGL_SURFACE_TYPE, _EGL_WINDOW_BIT,
|
||||
_EGL_BLUE_SIZE, 8,
|
||||
_EGL_GREEN_SIZE, 8,
|
||||
_EGL_RED_SIZE, 8,
|
||||
_EGL_DEPTH_SIZE, 16,
|
||||
_EGL_STENCIL_SIZE, 8,
|
||||
_EGL_NONE,
|
||||
}
|
||||
|
||||
type ctxWin32 struct {
|
||||
ctx uintptr
|
||||
display uintptr // EGLDisplay
|
||||
surface uintptr // EGLSurface
|
||||
}
|
||||
|
||||
func newWindow(opts *screen.NewWindowOptions) (uintptr, error) {
|
||||
w, err := win32.NewWindow(opts)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uintptr(w), nil
|
||||
}
|
||||
|
||||
func initWindow(w *windowImpl) {
|
||||
w.glctx, w.worker = gl.NewContext()
|
||||
}
|
||||
|
||||
func showWindow(w *windowImpl) {
|
||||
// Show makes an initial call to sizeEvent (via win32.SizeEvent), where
|
||||
// we setup the EGL surface and GL context.
|
||||
win32.Show(syscall.Handle(w.id))
|
||||
}
|
||||
|
||||
func closeWindow(id uintptr) {} // TODO
|
||||
|
||||
func drawLoop(w *windowImpl) {
|
||||
runtime.LockOSThread()
|
||||
|
||||
display := w.ctx.(ctxWin32).display
|
||||
surface := w.ctx.(ctxWin32).surface
|
||||
ctx := w.ctx.(ctxWin32).ctx
|
||||
|
||||
if ret, _, _ := eglMakeCurrent.Call(display, surface, surface, ctx); ret == 0 {
|
||||
panic(fmt.Sprintf("eglMakeCurrent failed: %v", eglErr()))
|
||||
}
|
||||
|
||||
// TODO(crawshaw): exit this goroutine on Release.
|
||||
workAvailable := w.worker.WorkAvailable()
|
||||
for {
|
||||
select {
|
||||
case <-workAvailable:
|
||||
w.worker.DoWork()
|
||||
case <-w.publish:
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-workAvailable:
|
||||
w.worker.DoWork()
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
if ret, _, _ := eglSwapBuffers.Call(display, surface); ret == 0 {
|
||||
panic(fmt.Sprintf("eglSwapBuffers failed: %v", eglErr()))
|
||||
}
|
||||
w.publishDone <- screen.PublishResult{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
win32.SizeEvent = sizeEvent
|
||||
win32.PaintEvent = paintEvent
|
||||
win32.MouseEvent = mouseEvent
|
||||
win32.KeyEvent = keyEvent
|
||||
win32.LifecycleEvent = lifecycleEvent
|
||||
}
|
||||
|
||||
func lifecycleEvent(hwnd syscall.Handle, to lifecycle.Stage) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[uintptr(hwnd)]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
if w.lifecycleStage == to {
|
||||
return
|
||||
}
|
||||
w.Send(lifecycle.Event{
|
||||
From: w.lifecycleStage,
|
||||
To: to,
|
||||
DrawContext: w.glctx,
|
||||
})
|
||||
w.lifecycleStage = to
|
||||
}
|
||||
|
||||
func mouseEvent(hwnd syscall.Handle, e mouse.Event) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[uintptr(hwnd)]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
w.Send(e)
|
||||
}
|
||||
|
||||
func keyEvent(hwnd syscall.Handle, e key.Event) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[uintptr(hwnd)]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
w.Send(e)
|
||||
}
|
||||
|
||||
func paintEvent(hwnd syscall.Handle, e paint.Event) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[uintptr(hwnd)]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
if w.ctx == nil {
|
||||
// Sometimes a paint event comes in before initial
|
||||
// window size is set. Ignore it.
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: the paint.Event should have External: true.
|
||||
w.Send(paint.Event{})
|
||||
}
|
||||
|
||||
func sizeEvent(hwnd syscall.Handle, e size.Event) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[uintptr(hwnd)]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
if w.ctx == nil {
|
||||
// This is the initial size event on window creation.
|
||||
// Create an EGL surface and spin up a GL context.
|
||||
if err := createEGLSurface(hwnd, w); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go drawLoop(w)
|
||||
}
|
||||
|
||||
if !handleSizeEventsAtChannelReceive {
|
||||
w.szMu.Lock()
|
||||
w.sz = e
|
||||
w.szMu.Unlock()
|
||||
}
|
||||
|
||||
w.Send(e)
|
||||
|
||||
if handleSizeEventsAtChannelReceive {
|
||||
return
|
||||
}
|
||||
|
||||
// Screen is dirty, generate a paint event.
|
||||
//
|
||||
// The sizeEvent function is called on the goroutine responsible for
|
||||
// calling the GL worker.DoWork. When compiling with -tags gldebug,
|
||||
// these GL calls are blocking (so we can read the error message), so
|
||||
// to make progress they need to happen on another goroutine.
|
||||
go func() {
|
||||
// TODO: this call to Viewport is not right, but is very hard to
|
||||
// do correctly with our async events channel model. We want
|
||||
// the call to Viewport to be made the instant before the
|
||||
// paint.Event is received.
|
||||
w.glctxMu.Lock()
|
||||
w.glctx.Viewport(0, 0, e.WidthPx, e.HeightPx)
|
||||
w.glctx.ClearColor(0, 0, 0, 1)
|
||||
w.glctx.Clear(gl.COLOR_BUFFER_BIT)
|
||||
w.glctxMu.Unlock()
|
||||
|
||||
w.Send(paint.Event{})
|
||||
}()
|
||||
}
|
||||
|
||||
func eglErr() error {
|
||||
if ret, _, _ := eglGetError.Call(); ret != _EGL_SUCCESS {
|
||||
return errors.New(eglErrString(ret))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createEGLSurface(hwnd syscall.Handle, w *windowImpl) error {
|
||||
var displayAttribPlatforms = [][]eglInt{
|
||||
// Default
|
||||
[]eglInt{
|
||||
_EGL_PLATFORM_ANGLE_TYPE_ANGLE,
|
||||
_EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE,
|
||||
_EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
|
||||
_EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
|
||||
_EGL_NONE,
|
||||
},
|
||||
// Direct3D 11
|
||||
[]eglInt{
|
||||
_EGL_PLATFORM_ANGLE_TYPE_ANGLE,
|
||||
_EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
|
||||
_EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
|
||||
_EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
|
||||
_EGL_NONE,
|
||||
},
|
||||
// Direct3D 9
|
||||
[]eglInt{
|
||||
_EGL_PLATFORM_ANGLE_TYPE_ANGLE,
|
||||
_EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE,
|
||||
_EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
|
||||
_EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
|
||||
_EGL_NONE,
|
||||
},
|
||||
// Direct3D 11 with WARP
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/gg615082.aspx
|
||||
[]eglInt{
|
||||
_EGL_PLATFORM_ANGLE_TYPE_ANGLE,
|
||||
_EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
|
||||
_EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE,
|
||||
_EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE,
|
||||
_EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
|
||||
_EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
|
||||
_EGL_NONE,
|
||||
},
|
||||
}
|
||||
|
||||
dc, err := win32.GetDC(hwnd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("win32.GetDC failed: %v", err)
|
||||
}
|
||||
|
||||
var display uintptr = _EGL_NO_DISPLAY
|
||||
for i, displayAttrib := range displayAttribPlatforms {
|
||||
lastTry := i == len(displayAttribPlatforms)-1
|
||||
|
||||
display, _, _ = eglGetPlatformDisplayEXT.Call(
|
||||
_EGL_PLATFORM_ANGLE_ANGLE,
|
||||
uintptr(dc),
|
||||
uintptr(unsafe.Pointer(&displayAttrib[0])),
|
||||
)
|
||||
|
||||
if display == _EGL_NO_DISPLAY {
|
||||
if !lastTry {
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("eglGetPlatformDisplayEXT failed: %v", eglErr())
|
||||
}
|
||||
|
||||
if ret, _, _ := eglInitialize.Call(display, 0, 0); ret == 0 {
|
||||
if !lastTry {
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("eglInitialize failed: %v", eglErr())
|
||||
}
|
||||
}
|
||||
|
||||
eglBindAPI.Call(_EGL_OPENGL_ES_API)
|
||||
if err := eglErr(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var numConfigs eglInt
|
||||
var config eglConfig
|
||||
ret, _, _ := eglChooseConfig.Call(
|
||||
display,
|
||||
uintptr(unsafe.Pointer(&rgb888[0])),
|
||||
uintptr(unsafe.Pointer(&config)),
|
||||
1,
|
||||
uintptr(unsafe.Pointer(&numConfigs)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return fmt.Errorf("eglChooseConfig failed: %v", eglErr())
|
||||
}
|
||||
if numConfigs <= 0 {
|
||||
return errors.New("eglChooseConfig found no valid config")
|
||||
}
|
||||
|
||||
surface, _, _ := eglCreateWindowSurface.Call(display, uintptr(config), uintptr(hwnd), 0, 0)
|
||||
if surface == _EGL_NO_SURFACE {
|
||||
return fmt.Errorf("eglCreateWindowSurface failed: %v", eglErr())
|
||||
}
|
||||
|
||||
contextAttribs := [...]eglInt{
|
||||
_EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
_EGL_NONE,
|
||||
}
|
||||
context, _, _ := eglCreateContext.Call(
|
||||
display,
|
||||
uintptr(config),
|
||||
_EGL_NO_CONTEXT,
|
||||
uintptr(unsafe.Pointer(&contextAttribs[0])),
|
||||
)
|
||||
if context == _EGL_NO_CONTEXT {
|
||||
return fmt.Errorf("eglCreateContext failed: %v", eglErr())
|
||||
}
|
||||
|
||||
eglSwapInterval.Call(display, 1)
|
||||
|
||||
w.ctx = ctxWin32{
|
||||
ctx: context,
|
||||
display: display,
|
||||
surface: surface,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func surfaceCreate() error {
|
||||
return errors.New("gldriver: surface creation not implemented on windows")
|
||||
}
|
@ -1,389 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gldriver
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/exp/shiny/driver/internal/drawer"
|
||||
"golang.org/x/exp/shiny/driver/internal/event"
|
||||
"golang.org/x/exp/shiny/driver/internal/lifecycler"
|
||||
"golang.org/x/exp/shiny/screen"
|
||||
"golang.org/x/image/math/f64"
|
||||
"golang.org/x/mobile/event/lifecycle"
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
type windowImpl struct {
|
||||
s *screenImpl
|
||||
|
||||
// id is an OS-specific data structure for the window.
|
||||
// - Cocoa: ScreenGLView*
|
||||
// - X11: Window
|
||||
// - Windows: win32.HWND
|
||||
id uintptr
|
||||
|
||||
// ctx is a C data structure for the GL context.
|
||||
// - Cocoa: uintptr holding a NSOpenGLContext*.
|
||||
// - X11: uintptr holding an EGLSurface.
|
||||
// - Windows: ctxWin32
|
||||
ctx interface{}
|
||||
|
||||
lifecycler lifecycler.State
|
||||
// TODO: Delete the field below (and the useLifecycler constant), and use
|
||||
// the field above for cocoa and win32.
|
||||
lifecycleStage lifecycle.Stage // current stage
|
||||
|
||||
event.Deque
|
||||
publish chan struct{}
|
||||
publishDone chan screen.PublishResult
|
||||
drawDone chan struct{}
|
||||
|
||||
// glctxMu is a mutex that enforces the atomicity of methods like
|
||||
// Texture.Upload or Window.Draw that are conceptually one operation
|
||||
// but are implemented by multiple OpenGL calls. OpenGL is a stateful
|
||||
// API, so interleaving OpenGL calls from separate higher-level
|
||||
// operations causes inconsistencies.
|
||||
glctxMu sync.Mutex
|
||||
glctx gl.Context
|
||||
worker gl.Worker
|
||||
// backBufferBound is whether the default Framebuffer, with ID 0, also
|
||||
// known as the back buffer or the window's Framebuffer, is bound and its
|
||||
// viewport is known to equal the window size. It can become false when we
|
||||
// bind to a texture's Framebuffer or when the window size changes.
|
||||
backBufferBound bool
|
||||
|
||||
// szMu protects only sz. If you need to hold both glctxMu and szMu, the
|
||||
// lock ordering is to lock glctxMu first (and unlock it last).
|
||||
szMu sync.Mutex
|
||||
sz size.Event
|
||||
}
|
||||
|
||||
// NextEvent implements the screen.EventDeque interface.
|
||||
func (w *windowImpl) NextEvent() interface{} {
|
||||
e := w.Deque.NextEvent()
|
||||
if handleSizeEventsAtChannelReceive {
|
||||
if sz, ok := e.(size.Event); ok {
|
||||
w.glctxMu.Lock()
|
||||
w.backBufferBound = false
|
||||
w.szMu.Lock()
|
||||
w.sz = sz
|
||||
w.szMu.Unlock()
|
||||
w.glctxMu.Unlock()
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (w *windowImpl) Release() {
|
||||
// There are two ways a window can be closed: the Operating System or
|
||||
// Desktop Environment can initiate (e.g. in response to a user clicking a
|
||||
// red button), or the Go app can programatically close the window (by
|
||||
// calling Window.Release).
|
||||
//
|
||||
// When the OS closes a window:
|
||||
// - Cocoa: Obj-C's windowWillClose calls Go's windowClosing.
|
||||
// - X11: the X11 server sends a WM_DELETE_WINDOW message.
|
||||
// - Windows: TODO: implement and document this.
|
||||
//
|
||||
// This should send a lifecycle event (To: StageDead) to the Go app's event
|
||||
// loop, which should respond by calling Window.Release (this method).
|
||||
// Window.Release is where system resources are actually cleaned up.
|
||||
//
|
||||
// When Window.Release is called, the closeWindow call below:
|
||||
// - Cocoa: calls Obj-C's performClose, which emulates the red button
|
||||
// being clicked. (TODO: document how this actually cleans up
|
||||
// resources??)
|
||||
// - X11: calls C's XDestroyWindow.
|
||||
// - Windows: TODO: implement and document this.
|
||||
//
|
||||
// On Cocoa, if these two approaches race, experiments suggest that the
|
||||
// race is won by performClose (which is called serially on the main
|
||||
// thread). Even if that isn't true, the windowWillClose handler is
|
||||
// idempotent.
|
||||
|
||||
theScreen.mu.Lock()
|
||||
delete(theScreen.windows, w.id)
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
closeWindow(w.id)
|
||||
}
|
||||
|
||||
func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
|
||||
originalSRMin := sr.Min
|
||||
sr = sr.Intersect(src.Bounds())
|
||||
if sr.Empty() {
|
||||
return
|
||||
}
|
||||
dp = dp.Add(sr.Min.Sub(originalSRMin))
|
||||
// TODO: keep a texture around for this purpose?
|
||||
t, err := w.s.NewTexture(sr.Size())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
t.Upload(image.Point{}, src, sr)
|
||||
w.Draw(f64.Aff3{
|
||||
1, 0, float64(dp.X),
|
||||
0, 1, float64(dp.Y),
|
||||
}, t, t.Bounds(), draw.Src, nil)
|
||||
t.Release()
|
||||
}
|
||||
|
||||
func useOp(glctx gl.Context, op draw.Op) {
|
||||
if op == draw.Over {
|
||||
glctx.Enable(gl.BLEND)
|
||||
glctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
||||
} else {
|
||||
glctx.Disable(gl.BLEND)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *windowImpl) bindBackBuffer() {
|
||||
w.szMu.Lock()
|
||||
sz := w.sz
|
||||
w.szMu.Unlock()
|
||||
|
||||
w.backBufferBound = true
|
||||
w.glctx.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{Value: 0})
|
||||
w.glctx.Viewport(0, 0, sz.WidthPx, sz.HeightPx)
|
||||
}
|
||||
|
||||
func (w *windowImpl) fill(mvp f64.Aff3, src color.Color, op draw.Op) {
|
||||
w.glctxMu.Lock()
|
||||
defer w.glctxMu.Unlock()
|
||||
|
||||
if !w.backBufferBound {
|
||||
w.bindBackBuffer()
|
||||
}
|
||||
|
||||
doFill(w.s, w.glctx, mvp, src, op)
|
||||
}
|
||||
|
||||
func doFill(s *screenImpl, glctx gl.Context, mvp f64.Aff3, src color.Color, op draw.Op) {
|
||||
useOp(glctx, op)
|
||||
if !glctx.IsProgram(s.fill.program) {
|
||||
p, err := compileProgram(glctx, fillVertexSrc, fillFragmentSrc)
|
||||
if err != nil {
|
||||
// TODO: initialize this somewhere else we can better handle the error.
|
||||
panic(err.Error())
|
||||
}
|
||||
s.fill.program = p
|
||||
s.fill.pos = glctx.GetAttribLocation(p, "pos")
|
||||
s.fill.mvp = glctx.GetUniformLocation(p, "mvp")
|
||||
s.fill.color = glctx.GetUniformLocation(p, "color")
|
||||
s.fill.quad = glctx.CreateBuffer()
|
||||
|
||||
glctx.BindBuffer(gl.ARRAY_BUFFER, s.fill.quad)
|
||||
glctx.BufferData(gl.ARRAY_BUFFER, quadCoords, gl.STATIC_DRAW)
|
||||
}
|
||||
glctx.UseProgram(s.fill.program)
|
||||
|
||||
writeAff3(glctx, s.fill.mvp, mvp)
|
||||
|
||||
r, g, b, a := src.RGBA()
|
||||
glctx.Uniform4f(
|
||||
s.fill.color,
|
||||
float32(r)/65535,
|
||||
float32(g)/65535,
|
||||
float32(b)/65535,
|
||||
float32(a)/65535,
|
||||
)
|
||||
|
||||
glctx.BindBuffer(gl.ARRAY_BUFFER, s.fill.quad)
|
||||
glctx.EnableVertexAttribArray(s.fill.pos)
|
||||
glctx.VertexAttribPointer(s.fill.pos, 2, gl.FLOAT, false, 0, 0)
|
||||
|
||||
glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
|
||||
|
||||
glctx.DisableVertexAttribArray(s.fill.pos)
|
||||
}
|
||||
|
||||
func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
|
||||
minX := float64(dr.Min.X)
|
||||
minY := float64(dr.Min.Y)
|
||||
maxX := float64(dr.Max.X)
|
||||
maxY := float64(dr.Max.Y)
|
||||
w.fill(w.mvp(
|
||||
minX, minY,
|
||||
maxX, minY,
|
||||
minX, maxY,
|
||||
), src, op)
|
||||
}
|
||||
|
||||
func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
|
||||
minX := float64(sr.Min.X)
|
||||
minY := float64(sr.Min.Y)
|
||||
maxX := float64(sr.Max.X)
|
||||
maxY := float64(sr.Max.Y)
|
||||
w.fill(w.mvp(
|
||||
src2dst[0]*minX+src2dst[1]*minY+src2dst[2],
|
||||
src2dst[3]*minX+src2dst[4]*minY+src2dst[5],
|
||||
src2dst[0]*maxX+src2dst[1]*minY+src2dst[2],
|
||||
src2dst[3]*maxX+src2dst[4]*minY+src2dst[5],
|
||||
src2dst[0]*minX+src2dst[1]*maxY+src2dst[2],
|
||||
src2dst[3]*minX+src2dst[4]*maxY+src2dst[5],
|
||||
), src, op)
|
||||
}
|
||||
|
||||
func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
|
||||
t := src.(*textureImpl)
|
||||
sr = sr.Intersect(t.Bounds())
|
||||
if sr.Empty() {
|
||||
return
|
||||
}
|
||||
|
||||
w.glctxMu.Lock()
|
||||
defer w.glctxMu.Unlock()
|
||||
|
||||
if !w.backBufferBound {
|
||||
w.bindBackBuffer()
|
||||
}
|
||||
|
||||
useOp(w.glctx, op)
|
||||
w.glctx.UseProgram(w.s.texture.program)
|
||||
|
||||
// Start with src-space left, top, right and bottom.
|
||||
srcL := float64(sr.Min.X)
|
||||
srcT := float64(sr.Min.Y)
|
||||
srcR := float64(sr.Max.X)
|
||||
srcB := float64(sr.Max.Y)
|
||||
// Transform to dst-space via the src2dst matrix, then to a MVP matrix.
|
||||
writeAff3(w.glctx, w.s.texture.mvp, w.mvp(
|
||||
src2dst[0]*srcL+src2dst[1]*srcT+src2dst[2],
|
||||
src2dst[3]*srcL+src2dst[4]*srcT+src2dst[5],
|
||||
src2dst[0]*srcR+src2dst[1]*srcT+src2dst[2],
|
||||
src2dst[3]*srcR+src2dst[4]*srcT+src2dst[5],
|
||||
src2dst[0]*srcL+src2dst[1]*srcB+src2dst[2],
|
||||
src2dst[3]*srcL+src2dst[4]*srcB+src2dst[5],
|
||||
))
|
||||
|
||||
// OpenGL's fragment shaders' UV coordinates run from (0,0)-(1,1),
|
||||
// unlike vertex shaders' XY coordinates running from (-1,+1)-(+1,-1).
|
||||
//
|
||||
// We are drawing a rectangle PQRS, defined by two of its
|
||||
// corners, onto the entire texture. The two quads may actually
|
||||
// be equal, but in the general case, PQRS can be smaller.
|
||||
//
|
||||
// (0,0) +---------------+ (1,0)
|
||||
// | P +-----+ Q |
|
||||
// | | | |
|
||||
// | S +-----+ R |
|
||||
// (0,1) +---------------+ (1,1)
|
||||
//
|
||||
// The PQRS quad is always axis-aligned. First of all, convert
|
||||
// from pixel space to texture space.
|
||||
tw := float64(t.size.X)
|
||||
th := float64(t.size.Y)
|
||||
px := float64(sr.Min.X-0) / tw
|
||||
py := float64(sr.Min.Y-0) / th
|
||||
qx := float64(sr.Max.X-0) / tw
|
||||
sy := float64(sr.Max.Y-0) / th
|
||||
// Due to axis alignment, qy = py and sx = px.
|
||||
//
|
||||
// The simultaneous equations are:
|
||||
// 0 + 0 + a02 = px
|
||||
// 0 + 0 + a12 = py
|
||||
// a00 + 0 + a02 = qx
|
||||
// a10 + 0 + a12 = qy = py
|
||||
// 0 + a01 + a02 = sx = px
|
||||
// 0 + a11 + a12 = sy
|
||||
writeAff3(w.glctx, w.s.texture.uvp, f64.Aff3{
|
||||
qx - px, 0, px,
|
||||
0, sy - py, py,
|
||||
})
|
||||
|
||||
w.glctx.ActiveTexture(gl.TEXTURE0)
|
||||
w.glctx.BindTexture(gl.TEXTURE_2D, t.id)
|
||||
w.glctx.Uniform1i(w.s.texture.sample, 0)
|
||||
|
||||
w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quad)
|
||||
w.glctx.EnableVertexAttribArray(w.s.texture.pos)
|
||||
w.glctx.VertexAttribPointer(w.s.texture.pos, 2, gl.FLOAT, false, 0, 0)
|
||||
|
||||
w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quad)
|
||||
w.glctx.EnableVertexAttribArray(w.s.texture.inUV)
|
||||
w.glctx.VertexAttribPointer(w.s.texture.inUV, 2, gl.FLOAT, false, 0, 0)
|
||||
|
||||
w.glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
|
||||
|
||||
w.glctx.DisableVertexAttribArray(w.s.texture.pos)
|
||||
w.glctx.DisableVertexAttribArray(w.s.texture.inUV)
|
||||
}
|
||||
|
||||
func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
|
||||
drawer.Copy(w, dp, src, sr, op, opts)
|
||||
}
|
||||
|
||||
func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
|
||||
drawer.Scale(w, dr, src, sr, op, opts)
|
||||
}
|
||||
|
||||
func (w *windowImpl) mvp(tlx, tly, trx, try, blx, bly float64) f64.Aff3 {
|
||||
w.szMu.Lock()
|
||||
sz := w.sz
|
||||
w.szMu.Unlock()
|
||||
|
||||
return calcMVP(sz.WidthPx, sz.HeightPx, tlx, tly, trx, try, blx, bly)
|
||||
}
|
||||
|
||||
// calcMVP returns the Model View Projection matrix that maps the quadCoords
|
||||
// unit square, (0, 0) to (1, 1), to a quad QV, such that QV in vertex shader
|
||||
// space corresponds to the quad QP in pixel space, where QP is defined by
|
||||
// three of its four corners - the arguments to this function. The three
|
||||
// corners are nominally the top-left, top-right and bottom-left, but there is
|
||||
// no constraint that e.g. tlx < trx.
|
||||
//
|
||||
// In pixel space, the window ranges from (0, 0) to (widthPx, heightPx). The
|
||||
// Y-axis points downwards.
|
||||
//
|
||||
// In vertex shader space, the window ranges from (-1, +1) to (+1, -1), which
|
||||
// is a 2-unit by 2-unit square. The Y-axis points upwards.
|
||||
func calcMVP(widthPx, heightPx int, tlx, tly, trx, try, blx, bly float64) f64.Aff3 {
|
||||
// Convert from pixel coords to vertex shader coords.
|
||||
invHalfWidth := +2 / float64(widthPx)
|
||||
invHalfHeight := -2 / float64(heightPx)
|
||||
tlx = tlx*invHalfWidth - 1
|
||||
tly = tly*invHalfHeight + 1
|
||||
trx = trx*invHalfWidth - 1
|
||||
try = try*invHalfHeight + 1
|
||||
blx = blx*invHalfWidth - 1
|
||||
bly = bly*invHalfHeight + 1
|
||||
|
||||
// The resultant affine matrix:
|
||||
// - maps (0, 0) to (tlx, tly).
|
||||
// - maps (1, 0) to (trx, try).
|
||||
// - maps (0, 1) to (blx, bly).
|
||||
return f64.Aff3{
|
||||
trx - tlx, blx - tlx, tlx,
|
||||
try - tly, bly - tly, tly,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *windowImpl) Publish() screen.PublishResult {
|
||||
// gl.Flush is a lightweight (on modern GL drivers) blocking call
|
||||
// that ensures all GL functions pending in the gl package have
|
||||
// been passed onto the GL driver before the app package attempts
|
||||
// to swap the screen buffer.
|
||||
//
|
||||
// This enforces that the final receive (for this paint cycle) on
|
||||
// gl.WorkAvailable happens before the send on publish.
|
||||
w.glctxMu.Lock()
|
||||
w.glctx.Flush()
|
||||
w.glctxMu.Unlock()
|
||||
|
||||
w.publish <- struct{}{}
|
||||
res := <-w.publishDone
|
||||
|
||||
select {
|
||||
case w.drawDone <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
@ -1,327 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux,!android openbsd
|
||||
|
||||
#include "_cgo_export.h"
|
||||
#include <EGL/egl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
Atom net_wm_name;
|
||||
Atom utf8_string;
|
||||
Atom wm_delete_window;
|
||||
Atom wm_protocols;
|
||||
Atom wm_take_focus;
|
||||
|
||||
EGLConfig e_config;
|
||||
EGLContext e_ctx;
|
||||
EGLDisplay e_dpy;
|
||||
Colormap x_colormap;
|
||||
Display *x_dpy;
|
||||
XVisualInfo *x_visual_info;
|
||||
Window x_root;
|
||||
|
||||
// TODO: share code with eglErrString
|
||||
char *
|
||||
eglGetErrorStr() {
|
||||
switch (eglGetError()) {
|
||||
case EGL_SUCCESS:
|
||||
return "EGL_SUCCESS";
|
||||
case EGL_NOT_INITIALIZED:
|
||||
return "EGL_NOT_INITIALIZED";
|
||||
case EGL_BAD_ACCESS:
|
||||
return "EGL_BAD_ACCESS";
|
||||
case EGL_BAD_ALLOC:
|
||||
return "EGL_BAD_ALLOC";
|
||||
case EGL_BAD_ATTRIBUTE:
|
||||
return "EGL_BAD_ATTRIBUTE";
|
||||
case EGL_BAD_CONFIG:
|
||||
return "EGL_BAD_CONFIG";
|
||||
case EGL_BAD_CONTEXT:
|
||||
return "EGL_BAD_CONTEXT";
|
||||
case EGL_BAD_CURRENT_SURFACE:
|
||||
return "EGL_BAD_CURRENT_SURFACE";
|
||||
case EGL_BAD_DISPLAY:
|
||||
return "EGL_BAD_DISPLAY";
|
||||
case EGL_BAD_MATCH:
|
||||
return "EGL_BAD_MATCH";
|
||||
case EGL_BAD_NATIVE_PIXMAP:
|
||||
return "EGL_BAD_NATIVE_PIXMAP";
|
||||
case EGL_BAD_NATIVE_WINDOW:
|
||||
return "EGL_BAD_NATIVE_WINDOW";
|
||||
case EGL_BAD_PARAMETER:
|
||||
return "EGL_BAD_PARAMETER";
|
||||
case EGL_BAD_SURFACE:
|
||||
return "EGL_BAD_SURFACE";
|
||||
case EGL_CONTEXT_LOST:
|
||||
return "EGL_CONTEXT_LOST";
|
||||
}
|
||||
return "unknown EGL error";
|
||||
}
|
||||
|
||||
void
|
||||
startDriver() {
|
||||
x_dpy = XOpenDisplay(NULL);
|
||||
if (!x_dpy) {
|
||||
fprintf(stderr, "XOpenDisplay failed\n");
|
||||
exit(1);
|
||||
}
|
||||
e_dpy = eglGetDisplay(x_dpy);
|
||||
if (!e_dpy) {
|
||||
fprintf(stderr, "eglGetDisplay failed: %s\n", eglGetErrorStr());
|
||||
exit(1);
|
||||
}
|
||||
EGLint e_major, e_minor;
|
||||
if (!eglInitialize(e_dpy, &e_major, &e_minor)) {
|
||||
fprintf(stderr, "eglInitialize failed: %s\n", eglGetErrorStr());
|
||||
exit(1);
|
||||
}
|
||||
if (!eglBindAPI(EGL_OPENGL_ES_API)) {
|
||||
fprintf(stderr, "eglBindAPI failed: %s\n", eglGetErrorStr());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static const EGLint attribs[] = {
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_DEPTH_SIZE, 16,
|
||||
EGL_CONFIG_CAVEAT, EGL_NONE,
|
||||
EGL_NONE
|
||||
};
|
||||
EGLint num_configs;
|
||||
if (!eglChooseConfig(e_dpy, attribs, &e_config, 1, &num_configs)) {
|
||||
fprintf(stderr, "eglChooseConfig failed: %s\n", eglGetErrorStr());
|
||||
exit(1);
|
||||
}
|
||||
EGLint vid;
|
||||
if (!eglGetConfigAttrib(e_dpy, e_config, EGL_NATIVE_VISUAL_ID, &vid)) {
|
||||
fprintf(stderr, "eglGetConfigAttrib failed: %s\n", eglGetErrorStr());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
XVisualInfo visTemplate;
|
||||
visTemplate.visualid = vid;
|
||||
int num_visuals;
|
||||
x_visual_info = XGetVisualInfo(x_dpy, VisualIDMask, &visTemplate, &num_visuals);
|
||||
if (!x_visual_info) {
|
||||
fprintf(stderr, "XGetVisualInfo failed\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
x_root = RootWindow(x_dpy, DefaultScreen(x_dpy));
|
||||
x_colormap = XCreateColormap(x_dpy, x_root, x_visual_info->visual, AllocNone);
|
||||
if (!x_colormap) {
|
||||
fprintf(stderr, "XCreateColormap failed\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static const EGLint ctx_attribs[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 3,
|
||||
EGL_NONE
|
||||
};
|
||||
e_ctx = eglCreateContext(e_dpy, e_config, EGL_NO_CONTEXT, ctx_attribs);
|
||||
if (!e_ctx) {
|
||||
fprintf(stderr, "eglCreateContext failed: %s\n", eglGetErrorStr());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
net_wm_name = XInternAtom(x_dpy, "_NET_WM_NAME", False);
|
||||
utf8_string = XInternAtom(x_dpy, "UTF8_STRING", False);
|
||||
wm_delete_window = XInternAtom(x_dpy, "WM_DELETE_WINDOW", False);
|
||||
wm_protocols = XInternAtom(x_dpy, "WM_PROTOCOLS", False);
|
||||
wm_take_focus = XInternAtom(x_dpy, "WM_TAKE_FOCUS", False);
|
||||
|
||||
const int key_lo = 8;
|
||||
const int key_hi = 255;
|
||||
int keysyms_per_keycode;
|
||||
KeySym *keysyms = XGetKeyboardMapping(x_dpy, key_lo, key_hi-key_lo+1, &keysyms_per_keycode);
|
||||
if (keysyms_per_keycode < 2) {
|
||||
fprintf(stderr, "XGetKeyboardMapping returned too few keysyms per keycode: %d\n", keysyms_per_keycode);
|
||||
exit(1);
|
||||
}
|
||||
int k;
|
||||
for (k = key_lo; k <= key_hi; k++) {
|
||||
onKeysym(k,
|
||||
keysyms[(k-key_lo)*keysyms_per_keycode + 0],
|
||||
keysyms[(k-key_lo)*keysyms_per_keycode + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
processEvents() {
|
||||
while (XPending(x_dpy)) {
|
||||
XEvent ev;
|
||||
XNextEvent(x_dpy, &ev);
|
||||
switch (ev.type) {
|
||||
case KeyPress:
|
||||
case KeyRelease:
|
||||
onKey(ev.xkey.window, ev.xkey.state, ev.xkey.keycode, ev.type == KeyPress ? 1 : 2);
|
||||
break;
|
||||
case ButtonPress:
|
||||
case ButtonRelease:
|
||||
onMouse(ev.xbutton.window, ev.xbutton.x, ev.xbutton.y, ev.xbutton.state, ev.xbutton.button,
|
||||
ev.type == ButtonPress ? 1 : 2);
|
||||
break;
|
||||
case MotionNotify:
|
||||
onMouse(ev.xmotion.window, ev.xmotion.x, ev.xmotion.y, ev.xmotion.state, 0, 0);
|
||||
break;
|
||||
case FocusIn:
|
||||
case FocusOut:
|
||||
onFocus(ev.xmotion.window, ev.type == FocusIn);
|
||||
break;
|
||||
case Expose:
|
||||
// A non-zero Count means that there are more expose events coming. For
|
||||
// example, a non-rectangular exposure (e.g. from a partially overlapped
|
||||
// window) will result in multiple expose events whose dirty rectangles
|
||||
// combine to define the dirty region. Go's paint events do not provide
|
||||
// dirty regions, so we only pass on the final X11 expose event.
|
||||
if (ev.xexpose.count == 0) {
|
||||
onExpose(ev.xexpose.window);
|
||||
}
|
||||
break;
|
||||
case ConfigureNotify:
|
||||
onConfigure(ev.xconfigure.window, ev.xconfigure.x, ev.xconfigure.y,
|
||||
ev.xconfigure.width, ev.xconfigure.height,
|
||||
DisplayWidth(x_dpy, DefaultScreen(x_dpy)),
|
||||
DisplayWidthMM(x_dpy, DefaultScreen(x_dpy)));
|
||||
break;
|
||||
case ClientMessage:
|
||||
if ((ev.xclient.message_type != wm_protocols) || (ev.xclient.format != 32)) {
|
||||
break;
|
||||
}
|
||||
Atom a = ev.xclient.data.l[0];
|
||||
if (a == wm_delete_window) {
|
||||
onDeleteWindow(ev.xclient.window);
|
||||
} else if (a == wm_take_focus) {
|
||||
XSetInputFocus(x_dpy, ev.xclient.window, RevertToParent, ev.xclient.data.l[1]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
makeCurrent(uintptr_t surface) {
|
||||
EGLSurface surf = (EGLSurface)(surface);
|
||||
if (!eglMakeCurrent(e_dpy, surf, surf, e_ctx)) {
|
||||
fprintf(stderr, "eglMakeCurrent failed: %s\n", eglGetErrorStr());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
swapBuffers(uintptr_t surface) {
|
||||
EGLSurface surf = (EGLSurface)(surface);
|
||||
if (!eglSwapBuffers(e_dpy, surf)) {
|
||||
fprintf(stderr, "eglSwapBuffers failed: %s\n", eglGetErrorStr());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
doCloseWindow(uintptr_t id) {
|
||||
Window win = (Window)(id);
|
||||
XDestroyWindow(x_dpy, win);
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
doNewWindow(int width, int height, char* title, int title_len) {
|
||||
XSetWindowAttributes attr;
|
||||
attr.colormap = x_colormap;
|
||||
attr.event_mask =
|
||||
KeyPressMask |
|
||||
KeyReleaseMask |
|
||||
ButtonPressMask |
|
||||
ButtonReleaseMask |
|
||||
PointerMotionMask |
|
||||
ExposureMask |
|
||||
StructureNotifyMask |
|
||||
FocusChangeMask;
|
||||
|
||||
Window win = XCreateWindow(
|
||||
x_dpy, x_root, 0, 0, width, height, 0, x_visual_info->depth, InputOutput,
|
||||
x_visual_info->visual, CWColormap | CWEventMask, &attr);
|
||||
|
||||
XSizeHints sizehints;
|
||||
sizehints.width = width;
|
||||
sizehints.height = height;
|
||||
sizehints.flags = USSize;
|
||||
XSetNormalHints(x_dpy, win, &sizehints);
|
||||
|
||||
Atom atoms[2];
|
||||
atoms[0] = wm_delete_window;
|
||||
atoms[1] = wm_take_focus;
|
||||
XSetWMProtocols(x_dpy, win, atoms, 2);
|
||||
|
||||
XSetStandardProperties(x_dpy, win, "", "App", None, (char **)NULL, 0, &sizehints);
|
||||
XChangeProperty(x_dpy, win, net_wm_name, utf8_string, 8, PropModeReplace, title, title_len);
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
doShowWindow(uintptr_t id) {
|
||||
Window win = (Window)(id);
|
||||
XMapWindow(x_dpy, win);
|
||||
EGLSurface surf = eglCreateWindowSurface(e_dpy, e_config, win, NULL);
|
||||
if (!surf) {
|
||||
fprintf(stderr, "eglCreateWindowSurface failed: %s\n", eglGetErrorStr());
|
||||
exit(1);
|
||||
}
|
||||
return (uintptr_t)(surf);
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
surfaceCreate() {
|
||||
static const EGLint ctx_attribs[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 3,
|
||||
EGL_NONE
|
||||
};
|
||||
EGLContext ctx = eglCreateContext(e_dpy, e_config, EGL_NO_CONTEXT, ctx_attribs);
|
||||
if (!ctx) {
|
||||
fprintf(stderr, "surface eglCreateContext failed: %s\n", eglGetErrorStr());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const EGLint cfg_attribs[] = {
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_DEPTH_SIZE, 16,
|
||||
EGL_CONFIG_CAVEAT, EGL_NONE,
|
||||
EGL_NONE
|
||||
};
|
||||
EGLConfig cfg;
|
||||
EGLint num_configs;
|
||||
if (!eglChooseConfig(e_dpy, cfg_attribs, &cfg, 1, &num_configs)) {
|
||||
fprintf(stderr, "gldriver: surface eglChooseConfig failed: %s\n", eglGetErrorStr());
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: use the size of the monitor as a bound for texture size.
|
||||
static const EGLint attribs[] = {
|
||||
EGL_WIDTH, 4096,
|
||||
EGL_HEIGHT, 3072,
|
||||
EGL_NONE
|
||||
};
|
||||
EGLSurface surface = eglCreatePbufferSurface(e_dpy, cfg, attribs);
|
||||
if (!surface) {
|
||||
fprintf(stderr, "gldriver: surface eglCreatePbufferSurface failed: %s\n", eglGetErrorStr());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!eglMakeCurrent(e_dpy, surface, surface, ctx)) {
|
||||
fprintf(stderr, "gldriver: surface eglMakeCurrent failed: %s\n", eglGetErrorStr());
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (uintptr_t)surface;
|
||||
}
|
@ -1,318 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux,!android openbsd
|
||||
|
||||
package gldriver
|
||||
|
||||
/*
|
||||
#cgo linux LDFLAGS: -lEGL -lGLESv2 -lX11
|
||||
#cgo openbsd LDFLAGS: -L/usr/X11R6/lib/ -lEGL -lGLESv2 -lX11
|
||||
|
||||
#cgo openbsd CFLAGS: -I/usr/X11R6/include/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
char *eglGetErrorStr();
|
||||
void startDriver();
|
||||
void processEvents();
|
||||
void makeCurrent(uintptr_t ctx);
|
||||
void swapBuffers(uintptr_t ctx);
|
||||
void doCloseWindow(uintptr_t id);
|
||||
uintptr_t doNewWindow(int width, int height, char* title, int title_len);
|
||||
uintptr_t doShowWindow(uintptr_t id);
|
||||
uintptr_t surfaceCreate();
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/exp/shiny/driver/internal/x11key"
|
||||
"golang.org/x/exp/shiny/screen"
|
||||
"golang.org/x/mobile/event/key"
|
||||
"golang.org/x/mobile/event/mouse"
|
||||
"golang.org/x/mobile/event/paint"
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/geom"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
const useLifecycler = true
|
||||
|
||||
const handleSizeEventsAtChannelReceive = true
|
||||
|
||||
var theKeysyms x11key.KeysymTable
|
||||
|
||||
func init() {
|
||||
// It might not be necessary, but it probably doesn't hurt to try to make
|
||||
// 'the main thread' be 'the X11 / OpenGL thread'.
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
func newWindow(opts *screen.NewWindowOptions) (uintptr, error) {
|
||||
width, height := optsSize(opts)
|
||||
|
||||
title := opts.GetTitle()
|
||||
ctitle := C.CString(title)
|
||||
defer C.free(unsafe.Pointer(ctitle))
|
||||
|
||||
retc := make(chan uintptr)
|
||||
uic <- uiClosure{
|
||||
f: func() uintptr {
|
||||
return uintptr(C.doNewWindow(C.int(width), C.int(height), ctitle, C.int(len(title))))
|
||||
},
|
||||
retc: retc,
|
||||
}
|
||||
return <-retc, nil
|
||||
}
|
||||
|
||||
func initWindow(w *windowImpl) {
|
||||
w.glctx, w.worker = glctx, worker
|
||||
}
|
||||
|
||||
func showWindow(w *windowImpl) {
|
||||
retc := make(chan uintptr)
|
||||
uic <- uiClosure{
|
||||
f: func() uintptr {
|
||||
return uintptr(C.doShowWindow(C.uintptr_t(w.id)))
|
||||
},
|
||||
retc: retc,
|
||||
}
|
||||
w.ctx = <-retc
|
||||
go drawLoop(w)
|
||||
}
|
||||
|
||||
func closeWindow(id uintptr) {
|
||||
uic <- uiClosure{
|
||||
f: func() uintptr {
|
||||
C.doCloseWindow(C.uintptr_t(id))
|
||||
return 0
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func drawLoop(w *windowImpl) {
|
||||
glcontextc <- w.ctx.(uintptr)
|
||||
go func() {
|
||||
for range w.publish {
|
||||
publishc <- w
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var (
|
||||
glcontextc = make(chan uintptr)
|
||||
publishc = make(chan *windowImpl)
|
||||
uic = make(chan uiClosure)
|
||||
|
||||
// TODO: don't assume that there is only one window, and hence only
|
||||
// one (global) GL context.
|
||||
//
|
||||
// TODO: should we be able to make a shiny.Texture before having a
|
||||
// shiny.Window's GL context? Should something like gl.IsProgram be a
|
||||
// method instead of a function, and have each shiny.Window have its own
|
||||
// gl.Context?
|
||||
glctx gl.Context
|
||||
worker gl.Worker
|
||||
)
|
||||
|
||||
// uiClosure is a closure to be run on C's UI thread.
|
||||
type uiClosure struct {
|
||||
f func() uintptr
|
||||
retc chan uintptr
|
||||
}
|
||||
|
||||
func main(f func(screen.Screen)) error {
|
||||
if gl.Version() == "GL_ES_2_0" {
|
||||
return errors.New("gldriver: ES 3 required on X11")
|
||||
}
|
||||
C.startDriver()
|
||||
glctx, worker = gl.NewContext()
|
||||
|
||||
closec := make(chan struct{})
|
||||
go func() {
|
||||
f(theScreen)
|
||||
close(closec)
|
||||
}()
|
||||
|
||||
// heartbeat is a channel that, at regular intervals, directs the select
|
||||
// below to also consider X11 events, not just Go events (channel
|
||||
// communications).
|
||||
//
|
||||
// TODO: select instead of poll. Note that knowing whether to call
|
||||
// C.processEvents needs to select on a file descriptor, and the other
|
||||
// cases below select on Go channels.
|
||||
heartbeat := time.NewTicker(time.Second / 60)
|
||||
workAvailable := worker.WorkAvailable()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-closec:
|
||||
return nil
|
||||
case ctx := <-glcontextc:
|
||||
// TODO: do we need to synchronize with seeing a size event for
|
||||
// this window's context before or after calling makeCurrent?
|
||||
// Otherwise, are we racing with the gl.Viewport call? I've
|
||||
// occasionally seen a stale viewport, if the window manager sets
|
||||
// the window width and height to something other than that
|
||||
// requested by XCreateWindow, but it's not easily reproducible.
|
||||
C.makeCurrent(C.uintptr_t(ctx))
|
||||
case w := <-publishc:
|
||||
C.swapBuffers(C.uintptr_t(w.ctx.(uintptr)))
|
||||
w.publishDone <- screen.PublishResult{}
|
||||
case req := <-uic:
|
||||
ret := req.f()
|
||||
if req.retc != nil {
|
||||
req.retc <- ret
|
||||
}
|
||||
case <-heartbeat.C:
|
||||
C.processEvents()
|
||||
case <-workAvailable:
|
||||
worker.DoWork()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//export onExpose
|
||||
func onExpose(id uintptr) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[id]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
if w == nil {
|
||||
return
|
||||
}
|
||||
|
||||
w.Send(paint.Event{External: true})
|
||||
}
|
||||
|
||||
//export onKeysym
|
||||
func onKeysym(k, unshifted, shifted uint32) {
|
||||
theKeysyms[k][0] = unshifted
|
||||
theKeysyms[k][1] = shifted
|
||||
}
|
||||
|
||||
//export onKey
|
||||
func onKey(id uintptr, state uint16, detail, dir uint8) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[id]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
if w == nil {
|
||||
return
|
||||
}
|
||||
|
||||
r, c := theKeysyms.Lookup(detail, state)
|
||||
w.Send(key.Event{
|
||||
Rune: r,
|
||||
Code: c,
|
||||
Modifiers: x11key.KeyModifiers(state),
|
||||
Direction: key.Direction(dir),
|
||||
})
|
||||
}
|
||||
|
||||
//export onMouse
|
||||
func onMouse(id uintptr, x, y int32, state uint16, button, dir uint8) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[id]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
if w == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: should a mouse.Event have a separate MouseModifiers field, for
|
||||
// which buttons are pressed during a mouse move?
|
||||
btn := mouse.Button(button)
|
||||
switch btn {
|
||||
case 4:
|
||||
btn = mouse.ButtonWheelUp
|
||||
case 5:
|
||||
btn = mouse.ButtonWheelDown
|
||||
case 6:
|
||||
btn = mouse.ButtonWheelLeft
|
||||
case 7:
|
||||
btn = mouse.ButtonWheelRight
|
||||
}
|
||||
if btn.IsWheel() {
|
||||
if dir != uint8(mouse.DirPress) {
|
||||
return
|
||||
}
|
||||
dir = uint8(mouse.DirStep)
|
||||
}
|
||||
w.Send(mouse.Event{
|
||||
X: float32(x),
|
||||
Y: float32(y),
|
||||
Button: btn,
|
||||
Modifiers: x11key.KeyModifiers(state),
|
||||
Direction: mouse.Direction(dir),
|
||||
})
|
||||
}
|
||||
|
||||
//export onFocus
|
||||
func onFocus(id uintptr, focused bool) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[id]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
if w == nil {
|
||||
return
|
||||
}
|
||||
|
||||
w.lifecycler.SetFocused(focused)
|
||||
w.lifecycler.SendEvent(w, w.glctx)
|
||||
}
|
||||
|
||||
//export onConfigure
|
||||
func onConfigure(id uintptr, x, y, width, height, displayWidth, displayWidthMM int32) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[id]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
if w == nil {
|
||||
return
|
||||
}
|
||||
|
||||
w.lifecycler.SetVisible(x+width > 0 && y+height > 0)
|
||||
w.lifecycler.SendEvent(w, w.glctx)
|
||||
|
||||
const (
|
||||
mmPerInch = 25.4
|
||||
ptPerInch = 72
|
||||
)
|
||||
pixelsPerMM := float32(displayWidth) / float32(displayWidthMM)
|
||||
w.Send(size.Event{
|
||||
WidthPx: int(width),
|
||||
HeightPx: int(height),
|
||||
WidthPt: geom.Pt(width),
|
||||
HeightPt: geom.Pt(height),
|
||||
PixelsPerPt: pixelsPerMM * mmPerInch / ptPerInch,
|
||||
})
|
||||
}
|
||||
|
||||
//export onDeleteWindow
|
||||
func onDeleteWindow(id uintptr) {
|
||||
theScreen.mu.Lock()
|
||||
w := theScreen.windows[id]
|
||||
theScreen.mu.Unlock()
|
||||
|
||||
if w == nil {
|
||||
return
|
||||
}
|
||||
|
||||
w.lifecycler.SetDead(true)
|
||||
w.lifecycler.SendEvent(w, w.glctx)
|
||||
}
|
||||
|
||||
func surfaceCreate() error {
|
||||
if C.surfaceCreate() == 0 {
|
||||
return errors.New("gldriver: surface creation failed")
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package drawer provides functions that help implement screen.Drawer methods.
|
||||
package drawer // import "golang.org/x/exp/shiny/driver/internal/drawer"
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
|
||||
"golang.org/x/exp/shiny/screen"
|
||||
"golang.org/x/image/math/f64"
|
||||
)
|
||||
|
||||
// Copy implements the Copy method of the screen.Drawer interface by calling
|
||||
// the Draw method of that same interface.
|
||||
func Copy(dst screen.Drawer, dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
|
||||
dst.Draw(f64.Aff3{
|
||||
1, 0, float64(dp.X - sr.Min.X),
|
||||
0, 1, float64(dp.Y - sr.Min.Y),
|
||||
}, src, sr, op, opts)
|
||||
}
|
||||
|
||||
// Scale implements the Scale method of the screen.Drawer interface by calling
|
||||
// the Draw method of that same interface.
|
||||
func Scale(dst screen.Drawer, dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
|
||||
rx := float64(dr.Dx()) / float64(sr.Dx())
|
||||
ry := float64(dr.Dy()) / float64(sr.Dy())
|
||||
dst.Draw(f64.Aff3{
|
||||
rx, 0, float64(dr.Min.X) - rx*float64(sr.Min.X),
|
||||
0, ry, float64(dr.Min.Y) - ry*float64(sr.Min.Y),
|
||||
}, src, sr, op, opts)
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package errscreen provides a stub Screen implementation.
|
||||
package errscreen // import "golang.org/x/exp/shiny/driver/internal/errscreen"
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"golang.org/x/exp/shiny/screen"
|
||||
)
|
||||
|
||||
// Stub returns a Screen whose methods all return the given error.
|
||||
func Stub(err error) screen.Screen {
|
||||
return stub{err}
|
||||
}
|
||||
|
||||
type stub struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (s stub) NewBuffer(size image.Point) (screen.Buffer, error) { return nil, s.err }
|
||||
func (s stub) NewTexture(size image.Point) (screen.Texture, error) { return nil, s.err }
|
||||
func (s stub) NewWindow(opts *screen.NewWindowOptions) (screen.Window, error) { return nil, s.err }
|
@ -1,68 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package event provides an infinitely buffered double-ended queue of events.
|
||||
package event // import "golang.org/x/exp/shiny/driver/internal/event"
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Deque is an infinitely buffered double-ended queue of events. The zero value
|
||||
// is usable, but a Deque value must not be copied.
|
||||
type Deque struct {
|
||||
mu sync.Mutex
|
||||
cond sync.Cond // cond.L is lazily initialized to &Deque.mu.
|
||||
back []interface{} // FIFO.
|
||||
front []interface{} // LIFO.
|
||||
}
|
||||
|
||||
func (q *Deque) lockAndInit() {
|
||||
q.mu.Lock()
|
||||
if q.cond.L == nil {
|
||||
q.cond.L = &q.mu
|
||||
}
|
||||
}
|
||||
|
||||
// NextEvent implements the screen.EventDeque interface.
|
||||
func (q *Deque) NextEvent() interface{} {
|
||||
q.lockAndInit()
|
||||
defer q.mu.Unlock()
|
||||
|
||||
for {
|
||||
if n := len(q.front); n > 0 {
|
||||
e := q.front[n-1]
|
||||
q.front[n-1] = nil
|
||||
q.front = q.front[:n-1]
|
||||
return e
|
||||
}
|
||||
|
||||
if n := len(q.back); n > 0 {
|
||||
e := q.back[0]
|
||||
q.back[0] = nil
|
||||
q.back = q.back[1:]
|
||||
return e
|
||||
}
|
||||
|
||||
q.cond.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
// Send implements the screen.EventDeque interface.
|
||||
func (q *Deque) Send(event interface{}) {
|
||||
q.lockAndInit()
|
||||
defer q.mu.Unlock()
|
||||
|
||||
q.back = append(q.back, event)
|
||||
q.cond.Signal()
|
||||
}
|
||||
|
||||
// SendFirst implements the screen.EventDeque interface.
|
||||
func (q *Deque) SendFirst(event interface{}) {
|
||||
q.lockAndInit()
|
||||
defer q.mu.Unlock()
|
||||
|
||||
q.front = append(q.front, event)
|
||||
q.cond.Signal()
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package lifecycler tracks a window's lifecycle state.
|
||||
//
|
||||
// It eliminates sending redundant lifecycle events, ones where the From and To
|
||||
// stages are equal. For example, moving a window from one part of the screen
|
||||
// to another should not send multiple events from StageVisible to
|
||||
// StageVisible, even though the underlying window system's message might only
|
||||
// hold the new position, and not whether the window was previously visible.
|
||||
package lifecycler // import "golang.org/x/exp/shiny/driver/internal/lifecycler"
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"golang.org/x/mobile/event/lifecycle"
|
||||
)
|
||||
|
||||
// State is a window's lifecycle state.
|
||||
type State struct {
|
||||
mu sync.Mutex
|
||||
stage lifecycle.Stage
|
||||
dead bool
|
||||
focused bool
|
||||
visible bool
|
||||
}
|
||||
|
||||
func (s *State) SetDead(b bool) {
|
||||
s.mu.Lock()
|
||||
s.dead = b
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *State) SetFocused(b bool) {
|
||||
s.mu.Lock()
|
||||
s.focused = b
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *State) SetVisible(b bool) {
|
||||
s.mu.Lock()
|
||||
s.visible = b
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *State) SendEvent(r Sender, drawContext interface{}) {
|
||||
s.mu.Lock()
|
||||
from, to := s.stage, lifecycle.StageAlive
|
||||
// The order of these if's is important. For example, once a window becomes
|
||||
// StageDead, it should never change stage again.
|
||||
//
|
||||
// Similarly, focused trumps visible. It's hard to imagine a situation
|
||||
// where a window is focused and not visible on screen, but in that
|
||||
// unlikely case, StageFocused seems the most appropriate stage.
|
||||
if s.dead {
|
||||
to = lifecycle.StageDead
|
||||
} else if s.focused {
|
||||
to = lifecycle.StageFocused
|
||||
} else if s.visible {
|
||||
to = lifecycle.StageVisible
|
||||
}
|
||||
s.stage = to
|
||||
s.mu.Unlock()
|
||||
|
||||
if from != to {
|
||||
r.Send(lifecycle.Event{
|
||||
From: from,
|
||||
To: to,
|
||||
|
||||
// TODO: does shiny use this at all?
|
||||
DrawContext: drawContext,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sender is who to send the lifecycle event to.
|
||||
type Sender interface {
|
||||
Send(event interface{})
|
||||
}
|
@ -1,350 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
|
||||
"golang.org/x/mobile/event/key"
|
||||
)
|
||||
|
||||
// convVirtualKeyCode converts a Win32 virtual key code number
|
||||
// into the standard keycodes used by the key package.
|
||||
func convVirtualKeyCode(vKey uint32) key.Code {
|
||||
switch vKey {
|
||||
case 0x01: // VK_LBUTTON left mouse button
|
||||
case 0x02: // VK_RBUTTON right mouse button
|
||||
case 0x03: // VK_CANCEL control-break processing
|
||||
case 0x04: // VK_MBUTTON middle mouse button
|
||||
case 0x05: // VK_XBUTTON1 X1 mouse button
|
||||
case 0x06: // VK_XBUTTON2 X2 mouse button
|
||||
case 0x08: // VK_BACK
|
||||
return key.CodeDeleteBackspace
|
||||
case 0x09: // VK_TAB
|
||||
return key.CodeTab
|
||||
case 0x0C: // VK_CLEAR
|
||||
case 0x0D: // VK_RETURN
|
||||
return key.CodeReturnEnter
|
||||
case 0x10: // VK_SHIFT
|
||||
return key.CodeLeftShift
|
||||
case 0x11: // VK_CONTROL
|
||||
return key.CodeLeftControl
|
||||
case 0x12: // VK_MENU
|
||||
return key.CodeLeftAlt
|
||||
case 0x13: // VK_PAUSE
|
||||
case 0x14: // VK_CAPITAL
|
||||
return key.CodeCapsLock
|
||||
case 0x15: // VK_KANA, VK_HANGUEL, VK_HANGUL
|
||||
case 0x17: // VK_JUNJA
|
||||
case 0x18: // VK_FINA, L
|
||||
case 0x19: // VK_HANJA, VK_KANJI
|
||||
case 0x1B: // VK_ESCAPE
|
||||
return key.CodeEscape
|
||||
case 0x1C: // VK_CONVERT
|
||||
case 0x1D: // VK_NONCONVERT
|
||||
case 0x1E: // VK_ACCEPT
|
||||
case 0x1F: // VK_MODECHANGE
|
||||
case 0x20: // VK_SPACE
|
||||
return key.CodeSpacebar
|
||||
case 0x21: // VK_PRIOR
|
||||
return key.CodePageUp
|
||||
case 0x22: // VK_NEXT
|
||||
return key.CodePageDown
|
||||
case 0x23: // VK_END
|
||||
return key.CodeEnd
|
||||
case 0x24: // VK_HOME
|
||||
return key.CodeHome
|
||||
case 0x25: // VK_LEFT
|
||||
return key.CodeLeftArrow
|
||||
case 0x26: // VK_UP
|
||||
return key.CodeUpArrow
|
||||
case 0x27: // VK_RIGHT
|
||||
return key.CodeRightArrow
|
||||
case 0x28: // VK_DOWN
|
||||
return key.CodeDownArrow
|
||||
case 0x29: // VK_SELECT
|
||||
case 0x2A: // VK_PRINT
|
||||
case 0x2B: // VK_EXECUTE
|
||||
case 0x2C: // VK_SNAPSHOT
|
||||
case 0x2D: // VK_INSERT
|
||||
case 0x2E: // VK_DELETE
|
||||
return key.CodeDeleteForward
|
||||
case 0x2F: // VK_HELP
|
||||
return key.CodeHelp
|
||||
case 0x30:
|
||||
return key.Code0
|
||||
case 0x31:
|
||||
return key.Code1
|
||||
case 0x32:
|
||||
return key.Code2
|
||||
case 0x33:
|
||||
return key.Code3
|
||||
case 0x34:
|
||||
return key.Code4
|
||||
case 0x35:
|
||||
return key.Code5
|
||||
case 0x36:
|
||||
return key.Code6
|
||||
case 0x37:
|
||||
return key.Code7
|
||||
case 0x38:
|
||||
return key.Code8
|
||||
case 0x39:
|
||||
return key.Code9
|
||||
case 0x41:
|
||||
return key.CodeA
|
||||
case 0x42:
|
||||
return key.CodeB
|
||||
case 0x43:
|
||||
return key.CodeC
|
||||
case 0x44:
|
||||
return key.CodeD
|
||||
case 0x45:
|
||||
return key.CodeE
|
||||
case 0x46:
|
||||
return key.CodeF
|
||||
case 0x47:
|
||||
return key.CodeG
|
||||
case 0x48:
|
||||
return key.CodeH
|
||||
case 0x49:
|
||||
return key.CodeI
|
||||
case 0x4A:
|
||||
return key.CodeJ
|
||||
case 0x4B:
|
||||
return key.CodeK
|
||||
case 0x4C:
|
||||
return key.CodeL
|
||||
case 0x4D:
|
||||
return key.CodeM
|
||||
case 0x4E:
|
||||
return key.CodeN
|
||||
case 0x4F:
|
||||
return key.CodeO
|
||||
case 0x50:
|
||||
return key.CodeP
|
||||
case 0x51:
|
||||
return key.CodeQ
|
||||
case 0x52:
|
||||
return key.CodeR
|
||||
case 0x53:
|
||||
return key.CodeS
|
||||
case 0x54:
|
||||
return key.CodeT
|
||||
case 0x55:
|
||||
return key.CodeU
|
||||
case 0x56:
|
||||
return key.CodeV
|
||||
case 0x57:
|
||||
return key.CodeW
|
||||
case 0x58:
|
||||
return key.CodeX
|
||||
case 0x59:
|
||||
return key.CodeY
|
||||
case 0x5A:
|
||||
return key.CodeZ
|
||||
case 0x5B: // VK_LWIN
|
||||
return key.CodeLeftGUI
|
||||
case 0x5C: // VK_RWIN
|
||||
return key.CodeRightGUI
|
||||
case 0x5D: // VK_APPS
|
||||
case 0x5F: // VK_SLEEP
|
||||
case 0x60: // VK_NUMPAD0
|
||||
return key.CodeKeypad0
|
||||
case 0x61: // VK_NUMPAD1
|
||||
return key.CodeKeypad1
|
||||
case 0x62: // VK_NUMPAD2
|
||||
return key.CodeKeypad2
|
||||
case 0x63: // VK_NUMPAD3
|
||||
return key.CodeKeypad3
|
||||
case 0x64: // VK_NUMPAD4
|
||||
return key.CodeKeypad4
|
||||
case 0x65: // VK_NUMPAD5
|
||||
return key.CodeKeypad5
|
||||
case 0x66: // VK_NUMPAD6
|
||||
return key.CodeKeypad6
|
||||
case 0x67: // VK_NUMPAD7
|
||||
return key.CodeKeypad7
|
||||
case 0x68: // VK_NUMPAD8
|
||||
return key.CodeKeypad8
|
||||
case 0x69: // VK_NUMPAD9
|
||||
return key.CodeKeypad9
|
||||
case 0x6A: // VK_MULTIPLY
|
||||
return key.CodeKeypadAsterisk
|
||||
case 0x6B: // VK_ADD
|
||||
return key.CodeKeypadPlusSign
|
||||
case 0x6C: // VK_SEPARATOR
|
||||
case 0x6D: // VK_SUBTRACT
|
||||
return key.CodeKeypadHyphenMinus
|
||||
case 0x6E: // VK_DECIMAL
|
||||
return key.CodeFullStop
|
||||
case 0x6F: // VK_DIVIDE
|
||||
return key.CodeKeypadSlash
|
||||
case 0x70: // VK_F1
|
||||
return key.CodeF1
|
||||
case 0x71: // VK_F2
|
||||
return key.CodeF2
|
||||
case 0x72: // VK_F3
|
||||
return key.CodeF3
|
||||
case 0x73: // VK_F4
|
||||
return key.CodeF4
|
||||
case 0x74: // VK_F5
|
||||
return key.CodeF5
|
||||
case 0x75: // VK_F6
|
||||
return key.CodeF6
|
||||
case 0x76: // VK_F7
|
||||
return key.CodeF7
|
||||
case 0x77: // VK_F8
|
||||
return key.CodeF8
|
||||
case 0x78: // VK_F9
|
||||
return key.CodeF9
|
||||
case 0x79: // VK_F10
|
||||
return key.CodeF10
|
||||
case 0x7A: // VK_F11
|
||||
return key.CodeF11
|
||||
case 0x7B: // VK_F12
|
||||
return key.CodeF12
|
||||
case 0x7C: // VK_F13
|
||||
return key.CodeF13
|
||||
case 0x7D: // VK_F14
|
||||
return key.CodeF14
|
||||
case 0x7E: // VK_F15
|
||||
return key.CodeF15
|
||||
case 0x7F: // VK_F16
|
||||
return key.CodeF16
|
||||
case 0x80: // VK_F17
|
||||
return key.CodeF17
|
||||
case 0x81: // VK_F18
|
||||
return key.CodeF18
|
||||
case 0x82: // VK_F19
|
||||
return key.CodeF19
|
||||
case 0x83: // VK_F20
|
||||
return key.CodeF20
|
||||
case 0x84: // VK_F21
|
||||
return key.CodeF21
|
||||
case 0x85: // VK_F22
|
||||
return key.CodeF22
|
||||
case 0x86: // VK_F23
|
||||
return key.CodeF23
|
||||
case 0x87: // VK_F24
|
||||
return key.CodeF24
|
||||
case 0x90: // VK_NUMLOCK
|
||||
return key.CodeKeypadNumLock
|
||||
case 0x91: // VK_SCROLL
|
||||
case 0xA0: // VK_LSHIFT
|
||||
return key.CodeLeftShift
|
||||
case 0xA1: // VK_RSHIFT
|
||||
return key.CodeRightShift
|
||||
case 0xA2: // VK_LCONTROL
|
||||
return key.CodeLeftControl
|
||||
case 0xA3: // VK_RCONTROL
|
||||
return key.CodeRightControl
|
||||
case 0xA4: // VK_LMENU
|
||||
case 0xA5: // VK_RMENU
|
||||
case 0xA6: // VK_BROWSER_BACK
|
||||
case 0xA7: // VK_BROWSER_FORWARD
|
||||
case 0xA8: // VK_BROWSER_REFRESH
|
||||
case 0xA9: // VK_BROWSER_STOP
|
||||
case 0xAA: // VK_BROWSER_SEARCH
|
||||
case 0xAB: // VK_BROWSER_FAVORITES
|
||||
case 0xAC: // VK_BROWSER_HOME
|
||||
case 0xAD: // VK_VOLUME_MUTE
|
||||
return key.CodeMute
|
||||
case 0xAE: // VK_VOLUME_DOWN
|
||||
return key.CodeVolumeDown
|
||||
case 0xAF: // VK_VOLUME_UP
|
||||
return key.CodeVolumeUp
|
||||
case 0xB0: // VK_MEDIA_NEXT_TRACK
|
||||
case 0xB1: // VK_MEDIA_PREV_TRACK
|
||||
case 0xB2: // VK_MEDIA_STOP
|
||||
case 0xB3: // VK_MEDIA_PLAY_PAUSE
|
||||
case 0xB4: // VK_LAUNCH_MAIL
|
||||
case 0xB5: // VK_LAUNCH_MEDIA_SELECT
|
||||
case 0xB6: // VK_LAUNCH_APP1
|
||||
case 0xB7: // VK_LAUNCH_APP2
|
||||
case 0xBA: // VK_OEM_1 ';:'
|
||||
return key.CodeSemicolon
|
||||
case 0xBB: // VK_OEM_PLUS '+'
|
||||
return key.CodeEqualSign
|
||||
case 0xBC: // VK_OEM_COMMA ','
|
||||
return key.CodeComma
|
||||
case 0xBD: // VK_OEM_MINUS '-'
|
||||
return key.CodeHyphenMinus
|
||||
case 0xBE: // VK_OEM_PERIOD '.'
|
||||
return key.CodeFullStop
|
||||
case 0xBF: // VK_OEM_2 '/?'
|
||||
return key.CodeSlash
|
||||
case 0xC0: // VK_OEM_3 '`~'
|
||||
return key.CodeGraveAccent
|
||||
case 0xDB: // VK_OEM_4 '[{'
|
||||
return key.CodeLeftSquareBracket
|
||||
case 0xDC: // VK_OEM_5 '\|'
|
||||
return key.CodeBackslash
|
||||
case 0xDD: // VK_OEM_6 ']}'
|
||||
return key.CodeRightSquareBracket
|
||||
case 0xDE: // VK_OEM_7 'single-quote/double-quote'
|
||||
return key.CodeApostrophe
|
||||
case 0xDF: // VK_OEM_8
|
||||
return key.CodeUnknown
|
||||
case 0xE2: // VK_OEM_102
|
||||
case 0xE5: // VK_PROCESSKEY
|
||||
case 0xE7: // VK_PACKET
|
||||
case 0xF6: // VK_ATTN
|
||||
case 0xF7: // VK_CRSEL
|
||||
case 0xF8: // VK_EXSEL
|
||||
case 0xF9: // VK_EREOF
|
||||
case 0xFA: // VK_PLAY
|
||||
case 0xFB: // VK_ZOOM
|
||||
case 0xFC: // VK_NONAME
|
||||
case 0xFD: // VK_PA1
|
||||
case 0xFE: // VK_OEM_CLEAR
|
||||
}
|
||||
return key.CodeUnknown
|
||||
}
|
||||
|
||||
func readRune(vKey uint32, scanCode uint8) rune {
|
||||
var (
|
||||
keystate [256]byte
|
||||
buf [4]uint16
|
||||
)
|
||||
if err := _GetKeyboardState(&keystate[0]); err != nil {
|
||||
panic(fmt.Sprintf("win32: %v", err))
|
||||
}
|
||||
// TODO: cache GetKeyboardLayout result, update on WM_INPUTLANGCHANGE
|
||||
layout := _GetKeyboardLayout(0)
|
||||
ret := _ToUnicodeEx(vKey, uint32(scanCode), &keystate[0], &buf[0], int32(len(buf)), 0, layout)
|
||||
if ret < 1 {
|
||||
return -1
|
||||
}
|
||||
return utf16.Decode(buf[:ret])[0]
|
||||
}
|
||||
|
||||
func sendKeyEvent(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
|
||||
e := key.Event{
|
||||
Rune: readRune(uint32(wParam), uint8(lParam>>16)),
|
||||
Code: convVirtualKeyCode(uint32(wParam)),
|
||||
Modifiers: keyModifiers(),
|
||||
}
|
||||
switch uMsg {
|
||||
case _WM_KEYDOWN:
|
||||
const prevMask = 1 << 30
|
||||
if repeat := lParam&prevMask == prevMask; repeat {
|
||||
e.Direction = key.DirNone
|
||||
} else {
|
||||
e.Direction = key.DirPress
|
||||
}
|
||||
case _WM_KEYUP:
|
||||
e.Direction = key.DirRelease
|
||||
default:
|
||||
panic(fmt.Sprintf("win32: unexpected key message: %d", uMsg))
|
||||
}
|
||||
|
||||
KeyEvent(hwnd, e)
|
||||
return 0
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go
|
||||
|
||||
package win32
|
@ -1,185 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type _COLORREF uint32
|
||||
|
||||
func _RGB(r, g, b byte) _COLORREF {
|
||||
return _COLORREF(r) | _COLORREF(g)<<8 | _COLORREF(b)<<16
|
||||
}
|
||||
|
||||
type _POINT struct {
|
||||
X int32
|
||||
Y int32
|
||||
}
|
||||
|
||||
type _RECT struct {
|
||||
Left int32
|
||||
Top int32
|
||||
Right int32
|
||||
Bottom int32
|
||||
}
|
||||
|
||||
type _MSG struct {
|
||||
HWND syscall.Handle
|
||||
Message uint32
|
||||
Wparam uintptr
|
||||
Lparam uintptr
|
||||
Time uint32
|
||||
Pt _POINT
|
||||
}
|
||||
|
||||
type _WNDCLASS struct {
|
||||
Style uint32
|
||||
LpfnWndProc uintptr
|
||||
CbClsExtra int32
|
||||
CbWndExtra int32
|
||||
HInstance syscall.Handle
|
||||
HIcon syscall.Handle
|
||||
HCursor syscall.Handle
|
||||
HbrBackground syscall.Handle
|
||||
LpszMenuName *uint16
|
||||
LpszClassName *uint16
|
||||
}
|
||||
|
||||
type _WINDOWPOS struct {
|
||||
HWND syscall.Handle
|
||||
HWNDInsertAfter syscall.Handle
|
||||
X int32
|
||||
Y int32
|
||||
Cx int32
|
||||
Cy int32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
const (
|
||||
_WM_SETFOCUS = 7
|
||||
_WM_KILLFOCUS = 8
|
||||
_WM_PAINT = 15
|
||||
_WM_CLOSE = 16
|
||||
_WM_WINDOWPOSCHANGED = 71
|
||||
_WM_KEYDOWN = 256
|
||||
_WM_KEYUP = 257
|
||||
_WM_SYSKEYDOWN = 260
|
||||
_WM_SYSKEYUP = 261
|
||||
_WM_MOUSEMOVE = 512
|
||||
_WM_MOUSEWHEEL = 522
|
||||
_WM_LBUTTONDOWN = 513
|
||||
_WM_LBUTTONUP = 514
|
||||
_WM_RBUTTONDOWN = 516
|
||||
_WM_RBUTTONUP = 517
|
||||
_WM_MBUTTONDOWN = 519
|
||||
_WM_MBUTTONUP = 520
|
||||
_WM_USER = 0x0400
|
||||
)
|
||||
|
||||
const (
|
||||
_WS_OVERLAPPED = 0x00000000
|
||||
_WS_CAPTION = 0x00C00000
|
||||
_WS_SYSMENU = 0x00080000
|
||||
_WS_THICKFRAME = 0x00040000
|
||||
_WS_MINIMIZEBOX = 0x00020000
|
||||
_WS_MAXIMIZEBOX = 0x00010000
|
||||
_WS_OVERLAPPEDWINDOW = _WS_OVERLAPPED | _WS_CAPTION | _WS_SYSMENU | _WS_THICKFRAME | _WS_MINIMIZEBOX | _WS_MAXIMIZEBOX
|
||||
)
|
||||
|
||||
const (
|
||||
_VK_SHIFT = 16
|
||||
_VK_CONTROL = 17
|
||||
_VK_MENU = 18
|
||||
_VK_LWIN = 0x5B
|
||||
_VK_RWIN = 0x5C
|
||||
)
|
||||
|
||||
const (
|
||||
_MK_LBUTTON = 0x0001
|
||||
_MK_MBUTTON = 0x0010
|
||||
_MK_RBUTTON = 0x0002
|
||||
)
|
||||
|
||||
const (
|
||||
_COLOR_BTNFACE = 15
|
||||
)
|
||||
|
||||
const (
|
||||
_IDI_APPLICATION = 32512
|
||||
_IDC_ARROW = 32512
|
||||
)
|
||||
|
||||
const (
|
||||
_CW_USEDEFAULT = 0x80000000 - 0x100000000
|
||||
|
||||
_SW_SHOWDEFAULT = 10
|
||||
|
||||
_HWND_MESSAGE = syscall.Handle(^uintptr(2)) // -3
|
||||
|
||||
_SWP_NOSIZE = 0x0001
|
||||
)
|
||||
|
||||
const (
|
||||
_BI_RGB = 0
|
||||
_DIB_RGB_COLORS = 0
|
||||
|
||||
_AC_SRC_OVER = 0x00
|
||||
_AC_SRC_ALPHA = 0x01
|
||||
|
||||
_SRCCOPY = 0x00cc0020
|
||||
|
||||
_WHEEL_DELTA = 120
|
||||
)
|
||||
|
||||
func _GET_X_LPARAM(lp uintptr) int32 {
|
||||
return int32(_LOWORD(lp))
|
||||
}
|
||||
|
||||
func _GET_Y_LPARAM(lp uintptr) int32 {
|
||||
return int32(_HIWORD(lp))
|
||||
}
|
||||
|
||||
func _GET_WHEEL_DELTA_WPARAM(lp uintptr) int16 {
|
||||
return int16(_HIWORD(lp))
|
||||
}
|
||||
|
||||
func _LOWORD(l uintptr) uint16 {
|
||||
return uint16(uint32(l))
|
||||
}
|
||||
|
||||
func _HIWORD(l uintptr) uint16 {
|
||||
return uint16(uint32(l >> 16))
|
||||
}
|
||||
|
||||
// notes to self
|
||||
// UINT = uint32
|
||||
// callbacks = uintptr
|
||||
// strings = *uint16
|
||||
|
||||
//sys GetDC(hwnd syscall.Handle) (dc syscall.Handle, err error) = user32.GetDC
|
||||
//sys ReleaseDC(hwnd syscall.Handle, dc syscall.Handle) (err error) = user32.ReleaseDC
|
||||
//sys sendMessage(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) = user32.SendMessageW
|
||||
|
||||
//sys _CreateWindowEx(exstyle uint32, className *uint16, windowText *uint16, style uint32, x int32, y int32, width int32, height int32, parent syscall.Handle, menu syscall.Handle, hInstance syscall.Handle, lpParam uintptr) (hwnd syscall.Handle, err error) = user32.CreateWindowExW
|
||||
//sys _DefWindowProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) = user32.DefWindowProcW
|
||||
//sys _DestroyWindow(hwnd syscall.Handle) (err error) = user32.DestroyWindow
|
||||
//sys _DispatchMessage(msg *_MSG) (ret int32) = user32.DispatchMessageW
|
||||
//sys _GetClientRect(hwnd syscall.Handle, rect *_RECT) (err error) = user32.GetClientRect
|
||||
//sys _GetWindowRect(hwnd syscall.Handle, rect *_RECT) (err error) = user32.GetWindowRect
|
||||
//sys _GetKeyboardLayout(threadID uint32) (locale syscall.Handle) = user32.GetKeyboardLayout
|
||||
//sys _GetKeyboardState(lpKeyState *byte) (err error) = user32.GetKeyboardState
|
||||
//sys _GetKeyState(virtkey int32) (keystatus int16) = user32.GetKeyState
|
||||
//sys _GetMessage(msg *_MSG, hwnd syscall.Handle, msgfiltermin uint32, msgfiltermax uint32) (ret int32, err error) [failretval==-1] = user32.GetMessageW
|
||||
//sys _LoadCursor(hInstance syscall.Handle, cursorName uintptr) (cursor syscall.Handle, err error) = user32.LoadCursorW
|
||||
//sys _LoadIcon(hInstance syscall.Handle, iconName uintptr) (icon syscall.Handle, err error) = user32.LoadIconW
|
||||
//sys _MoveWindow(hwnd syscall.Handle, x int32, y int32, w int32, h int32, repaint bool) (err error) = user32.MoveWindow
|
||||
//sys _PostMessage(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult bool) = user32.PostMessageW
|
||||
//sys _PostQuitMessage(exitCode int32) = user32.PostQuitMessage
|
||||
//sys _RegisterClass(wc *_WNDCLASS) (atom uint16, err error) = user32.RegisterClassW
|
||||
//sys _ShowWindow(hwnd syscall.Handle, cmdshow int32) (wasvisible bool) = user32.ShowWindow
|
||||
//sys _ScreenToClient(hwnd syscall.Handle, lpPoint *_POINT) (ok bool) = user32.ScreenToClient
|
||||
//sys _ToUnicodeEx(wVirtKey uint32, wScanCode uint32, lpKeyState *byte, pwszBuff *uint16, cchBuff int32, wFlags uint32, dwhkl syscall.Handle) (ret int32) = user32.ToUnicodeEx
|
||||
//sys _TranslateMessage(msg *_MSG) (done bool) = user32.TranslateMessage
|
@ -1,491 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
// Package win32 implements a partial shiny screen driver using the Win32 API.
|
||||
// It provides window, lifecycle, key, and mouse management, but no drawing.
|
||||
// That is left to windriver (using GDI) or gldriver (using DirectX via ANGLE).
|
||||
package win32 // import "golang.org/x/exp/shiny/driver/internal/win32"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/exp/shiny/screen"
|
||||
"golang.org/x/mobile/event/key"
|
||||
"golang.org/x/mobile/event/lifecycle"
|
||||
"golang.org/x/mobile/event/mouse"
|
||||
"golang.org/x/mobile/event/paint"
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/geom"
|
||||
)
|
||||
|
||||
// screenHWND is the handle to the "Screen window".
|
||||
// The Screen window encapsulates all screen.Screen operations
|
||||
// in an actual Windows window so they all run on the main thread.
|
||||
// Since any messages sent to a window will be executed on the
|
||||
// main thread, we can safely use the messages below.
|
||||
var screenHWND syscall.Handle
|
||||
|
||||
const (
|
||||
msgCreateWindow = _WM_USER + iota
|
||||
msgMainCallback
|
||||
msgShow
|
||||
msgQuit
|
||||
msgLast
|
||||
)
|
||||
|
||||
// userWM is used to generate private (WM_USER and above) window message IDs
|
||||
// for use by screenWindowWndProc and windowWndProc.
|
||||
type userWM struct {
|
||||
sync.Mutex
|
||||
id uint32
|
||||
}
|
||||
|
||||
func (m *userWM) next() uint32 {
|
||||
m.Lock()
|
||||
if m.id == 0 {
|
||||
m.id = msgLast
|
||||
}
|
||||
r := m.id
|
||||
m.id++
|
||||
m.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
var currentUserWM userWM
|
||||
|
||||
func newWindow(opts *screen.NewWindowOptions) (syscall.Handle, error) {
|
||||
// TODO(brainman): convert windowClass to *uint16 once (in initWindowClass)
|
||||
wcname, err := syscall.UTF16PtrFromString(windowClass)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
title, err := syscall.UTF16PtrFromString(opts.GetTitle())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
hwnd, err := _CreateWindowEx(0,
|
||||
wcname, title,
|
||||
_WS_OVERLAPPEDWINDOW,
|
||||
_CW_USEDEFAULT, _CW_USEDEFAULT,
|
||||
_CW_USEDEFAULT, _CW_USEDEFAULT,
|
||||
0, 0, hThisInstance, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// TODO(andlabs): use proper nCmdShow
|
||||
// TODO(andlabs): call UpdateWindow()
|
||||
|
||||
return hwnd, nil
|
||||
}
|
||||
|
||||
// ResizeClientRect makes hwnd client rectangle opts.Width by opts.Height in size.
|
||||
func ResizeClientRect(hwnd syscall.Handle, opts *screen.NewWindowOptions) error {
|
||||
if opts == nil || opts.Width <= 0 || opts.Height <= 0 {
|
||||
return nil
|
||||
}
|
||||
var cr, wr _RECT
|
||||
err := _GetClientRect(hwnd, &cr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = _GetWindowRect(hwnd, &wr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w := (wr.Right - wr.Left) - (cr.Right - int32(opts.Width))
|
||||
h := (wr.Bottom - wr.Top) - (cr.Bottom - int32(opts.Height))
|
||||
return _MoveWindow(hwnd, wr.Left, wr.Top, w, h, false)
|
||||
}
|
||||
|
||||
// Show shows a newly created window.
|
||||
// It sends the appropriate lifecycle events, makes the window appear
|
||||
// on the screen, and sends an initial size event.
|
||||
//
|
||||
// This is a separate step from NewWindow to give the driver a chance
|
||||
// to setup its internal state for a window before events start being
|
||||
// delivered.
|
||||
func Show(hwnd syscall.Handle) {
|
||||
SendMessage(hwnd, msgShow, 0, 0)
|
||||
}
|
||||
|
||||
func Release(hwnd syscall.Handle) {
|
||||
SendMessage(hwnd, _WM_CLOSE, 0, 0)
|
||||
}
|
||||
|
||||
func sendFocus(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
|
||||
switch uMsg {
|
||||
case _WM_SETFOCUS:
|
||||
LifecycleEvent(hwnd, lifecycle.StageFocused)
|
||||
case _WM_KILLFOCUS:
|
||||
LifecycleEvent(hwnd, lifecycle.StageVisible)
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected focus message: %d", uMsg))
|
||||
}
|
||||
return _DefWindowProc(hwnd, uMsg, wParam, lParam)
|
||||
}
|
||||
|
||||
func sendShow(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
|
||||
LifecycleEvent(hwnd, lifecycle.StageVisible)
|
||||
_ShowWindow(hwnd, _SW_SHOWDEFAULT)
|
||||
sendSize(hwnd)
|
||||
return 0
|
||||
}
|
||||
|
||||
func sendSizeEvent(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
|
||||
wp := (*_WINDOWPOS)(unsafe.Pointer(lParam))
|
||||
if wp.Flags&_SWP_NOSIZE != 0 {
|
||||
return 0
|
||||
}
|
||||
sendSize(hwnd)
|
||||
return 0
|
||||
}
|
||||
|
||||
func sendSize(hwnd syscall.Handle) {
|
||||
var r _RECT
|
||||
if err := _GetClientRect(hwnd, &r); err != nil {
|
||||
panic(err) // TODO(andlabs)
|
||||
}
|
||||
|
||||
width := int(r.Right - r.Left)
|
||||
height := int(r.Bottom - r.Top)
|
||||
|
||||
// TODO(andlabs): don't assume that PixelsPerPt == 1
|
||||
SizeEvent(hwnd, size.Event{
|
||||
WidthPx: width,
|
||||
HeightPx: height,
|
||||
WidthPt: geom.Pt(width),
|
||||
HeightPt: geom.Pt(height),
|
||||
PixelsPerPt: 1,
|
||||
})
|
||||
}
|
||||
|
||||
func sendClose(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
|
||||
// TODO(ktye): DefWindowProc calls DestroyWindow by default.
|
||||
// To intercept destruction of the window, return 0 and call
|
||||
// DestroyWindow when appropriate.
|
||||
LifecycleEvent(hwnd, lifecycle.StageDead)
|
||||
return _DefWindowProc(hwnd, uMsg, wParam, lParam)
|
||||
}
|
||||
|
||||
func sendMouseEvent(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
|
||||
e := mouse.Event{
|
||||
X: float32(_GET_X_LPARAM(lParam)),
|
||||
Y: float32(_GET_Y_LPARAM(lParam)),
|
||||
Modifiers: keyModifiers(),
|
||||
}
|
||||
|
||||
switch uMsg {
|
||||
case _WM_MOUSEMOVE:
|
||||
e.Direction = mouse.DirNone
|
||||
case _WM_LBUTTONDOWN, _WM_MBUTTONDOWN, _WM_RBUTTONDOWN:
|
||||
e.Direction = mouse.DirPress
|
||||
case _WM_LBUTTONUP, _WM_MBUTTONUP, _WM_RBUTTONUP:
|
||||
e.Direction = mouse.DirRelease
|
||||
case _WM_MOUSEWHEEL:
|
||||
// TODO: On a trackpad, a scroll can be a drawn-out affair with a
|
||||
// distinct beginning and end. Should the intermediate events be
|
||||
// DirNone?
|
||||
e.Direction = mouse.DirStep
|
||||
|
||||
// Convert from screen to window coordinates.
|
||||
p := _POINT{
|
||||
int32(e.X),
|
||||
int32(e.Y),
|
||||
}
|
||||
_ScreenToClient(hwnd, &p)
|
||||
e.X = float32(p.X)
|
||||
e.Y = float32(p.Y)
|
||||
default:
|
||||
panic("sendMouseEvent() called on non-mouse message")
|
||||
}
|
||||
|
||||
switch uMsg {
|
||||
case _WM_MOUSEMOVE:
|
||||
// No-op.
|
||||
case _WM_LBUTTONDOWN, _WM_LBUTTONUP:
|
||||
e.Button = mouse.ButtonLeft
|
||||
case _WM_MBUTTONDOWN, _WM_MBUTTONUP:
|
||||
e.Button = mouse.ButtonMiddle
|
||||
case _WM_RBUTTONDOWN, _WM_RBUTTONUP:
|
||||
e.Button = mouse.ButtonRight
|
||||
case _WM_MOUSEWHEEL:
|
||||
// TODO: handle horizontal scrolling
|
||||
delta := _GET_WHEEL_DELTA_WPARAM(wParam) / _WHEEL_DELTA
|
||||
switch {
|
||||
case delta > 0:
|
||||
e.Button = mouse.ButtonWheelUp
|
||||
case delta < 0:
|
||||
e.Button = mouse.ButtonWheelDown
|
||||
delta = -delta
|
||||
default:
|
||||
return
|
||||
}
|
||||
for delta > 0 {
|
||||
MouseEvent(hwnd, e)
|
||||
delta--
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
MouseEvent(hwnd, e)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// Precondition: this is called in immediate response to the message that triggered the event (so not after w.Send).
|
||||
func keyModifiers() (m key.Modifiers) {
|
||||
down := func(x int32) bool {
|
||||
// GetKeyState gets the key state at the time of the message, so this is what we want.
|
||||
return _GetKeyState(x)&0x80 != 0
|
||||
}
|
||||
|
||||
if down(_VK_CONTROL) {
|
||||
m |= key.ModControl
|
||||
}
|
||||
if down(_VK_MENU) {
|
||||
m |= key.ModAlt
|
||||
}
|
||||
if down(_VK_SHIFT) {
|
||||
m |= key.ModShift
|
||||
}
|
||||
if down(_VK_LWIN) || down(_VK_RWIN) {
|
||||
m |= key.ModMeta
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
var (
|
||||
MouseEvent func(hwnd syscall.Handle, e mouse.Event)
|
||||
PaintEvent func(hwnd syscall.Handle, e paint.Event)
|
||||
SizeEvent func(hwnd syscall.Handle, e size.Event)
|
||||
KeyEvent func(hwnd syscall.Handle, e key.Event)
|
||||
LifecycleEvent func(hwnd syscall.Handle, e lifecycle.Stage)
|
||||
|
||||
// TODO: use the golang.org/x/exp/shiny/driver/internal/lifecycler package
|
||||
// instead of or together with the LifecycleEvent callback?
|
||||
)
|
||||
|
||||
func sendPaint(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
|
||||
PaintEvent(hwnd, paint.Event{})
|
||||
return _DefWindowProc(hwnd, uMsg, wParam, lParam)
|
||||
}
|
||||
|
||||
var screenMsgs = map[uint32]func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr){}
|
||||
|
||||
func AddScreenMsg(fn func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr)) uint32 {
|
||||
uMsg := currentUserWM.next()
|
||||
screenMsgs[uMsg] = func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) uintptr {
|
||||
fn(hwnd, uMsg, wParam, lParam)
|
||||
return 0
|
||||
}
|
||||
return uMsg
|
||||
}
|
||||
|
||||
func screenWindowWndProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
|
||||
switch uMsg {
|
||||
case msgCreateWindow:
|
||||
p := (*newWindowParams)(unsafe.Pointer(lParam))
|
||||
p.w, p.err = newWindow(p.opts)
|
||||
case msgMainCallback:
|
||||
go func() {
|
||||
mainCallback()
|
||||
SendScreenMessage(msgQuit, 0, 0)
|
||||
}()
|
||||
case msgQuit:
|
||||
_PostQuitMessage(0)
|
||||
}
|
||||
fn := screenMsgs[uMsg]
|
||||
if fn != nil {
|
||||
return fn(hwnd, uMsg, wParam, lParam)
|
||||
}
|
||||
return _DefWindowProc(hwnd, uMsg, wParam, lParam)
|
||||
}
|
||||
|
||||
//go:uintptrescapes
|
||||
|
||||
func SendScreenMessage(uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
|
||||
return SendMessage(screenHWND, uMsg, wParam, lParam)
|
||||
}
|
||||
|
||||
var windowMsgs = map[uint32]func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr){
|
||||
_WM_SETFOCUS: sendFocus,
|
||||
_WM_KILLFOCUS: sendFocus,
|
||||
_WM_PAINT: sendPaint,
|
||||
msgShow: sendShow,
|
||||
_WM_WINDOWPOSCHANGED: sendSizeEvent,
|
||||
_WM_CLOSE: sendClose,
|
||||
|
||||
_WM_LBUTTONDOWN: sendMouseEvent,
|
||||
_WM_LBUTTONUP: sendMouseEvent,
|
||||
_WM_MBUTTONDOWN: sendMouseEvent,
|
||||
_WM_MBUTTONUP: sendMouseEvent,
|
||||
_WM_RBUTTONDOWN: sendMouseEvent,
|
||||
_WM_RBUTTONUP: sendMouseEvent,
|
||||
_WM_MOUSEMOVE: sendMouseEvent,
|
||||
_WM_MOUSEWHEEL: sendMouseEvent,
|
||||
|
||||
_WM_KEYDOWN: sendKeyEvent,
|
||||
_WM_KEYUP: sendKeyEvent,
|
||||
// TODO case _WM_SYSKEYDOWN, _WM_SYSKEYUP:
|
||||
}
|
||||
|
||||
func AddWindowMsg(fn func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr)) uint32 {
|
||||
uMsg := currentUserWM.next()
|
||||
windowMsgs[uMsg] = func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) uintptr {
|
||||
fn(hwnd, uMsg, wParam, lParam)
|
||||
return 0
|
||||
}
|
||||
return uMsg
|
||||
}
|
||||
|
||||
func windowWndProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
|
||||
fn := windowMsgs[uMsg]
|
||||
if fn != nil {
|
||||
return fn(hwnd, uMsg, wParam, lParam)
|
||||
}
|
||||
return _DefWindowProc(hwnd, uMsg, wParam, lParam)
|
||||
}
|
||||
|
||||
type newWindowParams struct {
|
||||
opts *screen.NewWindowOptions
|
||||
w syscall.Handle
|
||||
err error
|
||||
}
|
||||
|
||||
func NewWindow(opts *screen.NewWindowOptions) (syscall.Handle, error) {
|
||||
var p newWindowParams
|
||||
p.opts = opts
|
||||
SendScreenMessage(msgCreateWindow, 0, uintptr(unsafe.Pointer(&p)))
|
||||
return p.w, p.err
|
||||
}
|
||||
|
||||
const windowClass = "shiny_Window"
|
||||
|
||||
func initWindowClass() (err error) {
|
||||
wcname, err := syscall.UTF16PtrFromString(windowClass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = _RegisterClass(&_WNDCLASS{
|
||||
LpszClassName: wcname,
|
||||
LpfnWndProc: syscall.NewCallback(windowWndProc),
|
||||
HIcon: hDefaultIcon,
|
||||
HCursor: hDefaultCursor,
|
||||
HInstance: hThisInstance,
|
||||
// TODO(andlabs): change this to something else? NULL? the hollow brush?
|
||||
HbrBackground: syscall.Handle(_COLOR_BTNFACE + 1),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func initScreenWindow() (err error) {
|
||||
const screenWindowClass = "shiny_ScreenWindow"
|
||||
swc, err := syscall.UTF16PtrFromString(screenWindowClass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
emptyString, err := syscall.UTF16PtrFromString("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wc := _WNDCLASS{
|
||||
LpszClassName: swc,
|
||||
LpfnWndProc: syscall.NewCallback(screenWindowWndProc),
|
||||
HIcon: hDefaultIcon,
|
||||
HCursor: hDefaultCursor,
|
||||
HInstance: hThisInstance,
|
||||
HbrBackground: syscall.Handle(_COLOR_BTNFACE + 1),
|
||||
}
|
||||
_, err = _RegisterClass(&wc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
screenHWND, err = _CreateWindowEx(0,
|
||||
swc, emptyString,
|
||||
_WS_OVERLAPPEDWINDOW,
|
||||
_CW_USEDEFAULT, _CW_USEDEFAULT,
|
||||
_CW_USEDEFAULT, _CW_USEDEFAULT,
|
||||
_HWND_MESSAGE, 0, hThisInstance, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
hDefaultIcon syscall.Handle
|
||||
hDefaultCursor syscall.Handle
|
||||
hThisInstance syscall.Handle
|
||||
)
|
||||
|
||||
func initCommon() (err error) {
|
||||
hDefaultIcon, err = _LoadIcon(0, _IDI_APPLICATION)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hDefaultCursor, err = _LoadCursor(0, _IDC_ARROW)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(andlabs) hThisInstance
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:uintptrescapes
|
||||
|
||||
func SendMessage(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
|
||||
return sendMessage(hwnd, uMsg, wParam, lParam)
|
||||
}
|
||||
|
||||
var mainCallback func()
|
||||
|
||||
func Main(f func()) (retErr error) {
|
||||
// It does not matter which OS thread we are on.
|
||||
// All that matters is that we confine all UI operations
|
||||
// to the thread that created the respective window.
|
||||
runtime.LockOSThread()
|
||||
|
||||
if err := initCommon(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := initScreenWindow(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
// TODO(andlabs): log an error if this fails?
|
||||
_DestroyWindow(screenHWND)
|
||||
// TODO(andlabs): unregister window class
|
||||
}()
|
||||
|
||||
if err := initWindowClass(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Prime the pump.
|
||||
mainCallback = f
|
||||
_PostMessage(screenHWND, msgMainCallback, 0, 0)
|
||||
|
||||
// Main message pump.
|
||||
var m _MSG
|
||||
for {
|
||||
done, err := _GetMessage(&m, 0, 0, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("win32 GetMessage failed: %v", err)
|
||||
}
|
||||
if done == 0 { // WM_QUIT
|
||||
break
|
||||
}
|
||||
_TranslateMessage(&m)
|
||||
_DispatchMessage(&m)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,286 +0,0 @@
|
||||
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
|
||||
|
||||
package win32
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return nil
|
||||
case errnoERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
// TODO: add more here, after collecting data on the common
|
||||
// error values see on Windows. (perhaps when running
|
||||
// all.bat?)
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
moduser32 = windows.NewLazySystemDLL("user32.dll")
|
||||
|
||||
procGetDC = moduser32.NewProc("GetDC")
|
||||
procReleaseDC = moduser32.NewProc("ReleaseDC")
|
||||
procSendMessageW = moduser32.NewProc("SendMessageW")
|
||||
procCreateWindowExW = moduser32.NewProc("CreateWindowExW")
|
||||
procDefWindowProcW = moduser32.NewProc("DefWindowProcW")
|
||||
procDestroyWindow = moduser32.NewProc("DestroyWindow")
|
||||
procDispatchMessageW = moduser32.NewProc("DispatchMessageW")
|
||||
procGetClientRect = moduser32.NewProc("GetClientRect")
|
||||
procGetWindowRect = moduser32.NewProc("GetWindowRect")
|
||||
procGetKeyboardLayout = moduser32.NewProc("GetKeyboardLayout")
|
||||
procGetKeyboardState = moduser32.NewProc("GetKeyboardState")
|
||||
procGetKeyState = moduser32.NewProc("GetKeyState")
|
||||
procGetMessageW = moduser32.NewProc("GetMessageW")
|
||||
procLoadCursorW = moduser32.NewProc("LoadCursorW")
|
||||
procLoadIconW = moduser32.NewProc("LoadIconW")
|
||||
procMoveWindow = moduser32.NewProc("MoveWindow")
|
||||
procPostMessageW = moduser32.NewProc("PostMessageW")
|
||||
procPostQuitMessage = moduser32.NewProc("PostQuitMessage")
|
||||
procRegisterClassW = moduser32.NewProc("RegisterClassW")
|
||||
procShowWindow = moduser32.NewProc("ShowWindow")
|
||||
procScreenToClient = moduser32.NewProc("ScreenToClient")
|
||||
procToUnicodeEx = moduser32.NewProc("ToUnicodeEx")
|
||||
procTranslateMessage = moduser32.NewProc("TranslateMessage")
|
||||
)
|
||||
|
||||
func GetDC(hwnd syscall.Handle) (dc syscall.Handle, err error) {
|
||||
r0, _, e1 := syscall.Syscall(procGetDC.Addr(), 1, uintptr(hwnd), 0, 0)
|
||||
dc = syscall.Handle(r0)
|
||||
if dc == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReleaseDC(hwnd syscall.Handle, dc syscall.Handle) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procReleaseDC.Addr(), 2, uintptr(hwnd), uintptr(dc), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func sendMessage(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
|
||||
r0, _, _ := syscall.Syscall6(procSendMessageW.Addr(), 4, uintptr(hwnd), uintptr(uMsg), uintptr(wParam), uintptr(lParam), 0, 0)
|
||||
lResult = uintptr(r0)
|
||||
return
|
||||
}
|
||||
|
||||
func _CreateWindowEx(exstyle uint32, className *uint16, windowText *uint16, style uint32, x int32, y int32, width int32, height int32, parent syscall.Handle, menu syscall.Handle, hInstance syscall.Handle, lpParam uintptr) (hwnd syscall.Handle, err error) {
|
||||
r0, _, e1 := syscall.Syscall12(procCreateWindowExW.Addr(), 12, uintptr(exstyle), uintptr(unsafe.Pointer(className)), uintptr(unsafe.Pointer(windowText)), uintptr(style), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(parent), uintptr(menu), uintptr(hInstance), uintptr(lpParam))
|
||||
hwnd = syscall.Handle(r0)
|
||||
if hwnd == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func _DefWindowProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
|
||||
r0, _, _ := syscall.Syscall6(procDefWindowProcW.Addr(), 4, uintptr(hwnd), uintptr(uMsg), uintptr(wParam), uintptr(lParam), 0, 0)
|
||||
lResult = uintptr(r0)
|
||||
return
|
||||
}
|
||||
|
||||
func _DestroyWindow(hwnd syscall.Handle) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procDestroyWindow.Addr(), 1, uintptr(hwnd), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func _DispatchMessage(msg *_MSG) (ret int32) {
|
||||
r0, _, _ := syscall.Syscall(procDispatchMessageW.Addr(), 1, uintptr(unsafe.Pointer(msg)), 0, 0)
|
||||
ret = int32(r0)
|
||||
return
|
||||
}
|
||||
|
||||
func _GetClientRect(hwnd syscall.Handle, rect *_RECT) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procGetClientRect.Addr(), 2, uintptr(hwnd), uintptr(unsafe.Pointer(rect)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func _GetWindowRect(hwnd syscall.Handle, rect *_RECT) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procGetWindowRect.Addr(), 2, uintptr(hwnd), uintptr(unsafe.Pointer(rect)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func _GetKeyboardLayout(threadID uint32) (locale syscall.Handle) {
|
||||
r0, _, _ := syscall.Syscall(procGetKeyboardLayout.Addr(), 1, uintptr(threadID), 0, 0)
|
||||
locale = syscall.Handle(r0)
|
||||
return
|
||||
}
|
||||
|
||||
func _GetKeyboardState(lpKeyState *byte) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procGetKeyboardState.Addr(), 1, uintptr(unsafe.Pointer(lpKeyState)), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func _GetKeyState(virtkey int32) (keystatus int16) {
|
||||
r0, _, _ := syscall.Syscall(procGetKeyState.Addr(), 1, uintptr(virtkey), 0, 0)
|
||||
keystatus = int16(r0)
|
||||
return
|
||||
}
|
||||
|
||||
func _GetMessage(msg *_MSG, hwnd syscall.Handle, msgfiltermin uint32, msgfiltermax uint32) (ret int32, err error) {
|
||||
r0, _, e1 := syscall.Syscall6(procGetMessageW.Addr(), 4, uintptr(unsafe.Pointer(msg)), uintptr(hwnd), uintptr(msgfiltermin), uintptr(msgfiltermax), 0, 0)
|
||||
ret = int32(r0)
|
||||
if ret == -1 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func _LoadCursor(hInstance syscall.Handle, cursorName uintptr) (cursor syscall.Handle, err error) {
|
||||
r0, _, e1 := syscall.Syscall(procLoadCursorW.Addr(), 2, uintptr(hInstance), uintptr(cursorName), 0)
|
||||
cursor = syscall.Handle(r0)
|
||||
if cursor == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func _LoadIcon(hInstance syscall.Handle, iconName uintptr) (icon syscall.Handle, err error) {
|
||||
r0, _, e1 := syscall.Syscall(procLoadIconW.Addr(), 2, uintptr(hInstance), uintptr(iconName), 0)
|
||||
icon = syscall.Handle(r0)
|
||||
if icon == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func _MoveWindow(hwnd syscall.Handle, x int32, y int32, w int32, h int32, repaint bool) (err error) {
|
||||
var _p0 uint32
|
||||
if repaint {
|
||||
_p0 = 1
|
||||
} else {
|
||||
_p0 = 0
|
||||
}
|
||||
r1, _, e1 := syscall.Syscall6(procMoveWindow.Addr(), 6, uintptr(hwnd), uintptr(x), uintptr(y), uintptr(w), uintptr(h), uintptr(_p0))
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func _PostMessage(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult bool) {
|
||||
r0, _, _ := syscall.Syscall6(procPostMessageW.Addr(), 4, uintptr(hwnd), uintptr(uMsg), uintptr(wParam), uintptr(lParam), 0, 0)
|
||||
lResult = r0 != 0
|
||||
return
|
||||
}
|
||||
|
||||
func _PostQuitMessage(exitCode int32) {
|
||||
syscall.Syscall(procPostQuitMessage.Addr(), 1, uintptr(exitCode), 0, 0)
|
||||
return
|
||||
}
|
||||
|
||||
func _RegisterClass(wc *_WNDCLASS) (atom uint16, err error) {
|
||||
r0, _, e1 := syscall.Syscall(procRegisterClassW.Addr(), 1, uintptr(unsafe.Pointer(wc)), 0, 0)
|
||||
atom = uint16(r0)
|
||||
if atom == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func _ShowWindow(hwnd syscall.Handle, cmdshow int32) (wasvisible bool) {
|
||||
r0, _, _ := syscall.Syscall(procShowWindow.Addr(), 2, uintptr(hwnd), uintptr(cmdshow), 0)
|
||||
wasvisible = r0 != 0
|
||||
return
|
||||
}
|
||||
|
||||
func _ScreenToClient(hwnd syscall.Handle, lpPoint *_POINT) (ok bool) {
|
||||
r0, _, _ := syscall.Syscall(procScreenToClient.Addr(), 2, uintptr(hwnd), uintptr(unsafe.Pointer(lpPoint)), 0)
|
||||
ok = r0 != 0
|
||||
return
|
||||
}
|
||||
|
||||
func _ToUnicodeEx(wVirtKey uint32, wScanCode uint32, lpKeyState *byte, pwszBuff *uint16, cchBuff int32, wFlags uint32, dwhkl syscall.Handle) (ret int32) {
|
||||
r0, _, _ := syscall.Syscall9(procToUnicodeEx.Addr(), 7, uintptr(wVirtKey), uintptr(wScanCode), uintptr(unsafe.Pointer(lpKeyState)), uintptr(unsafe.Pointer(pwszBuff)), uintptr(cchBuff), uintptr(wFlags), uintptr(dwhkl), 0, 0)
|
||||
ret = int32(r0)
|
||||
return
|
||||
}
|
||||
|
||||
func _TranslateMessage(msg *_MSG) (done bool) {
|
||||
r0, _, _ := syscall.Syscall(procTranslateMessage.Addr(), 1, uintptr(unsafe.Pointer(msg)), 0, 0)
|
||||
done = r0 != 0
|
||||
return
|
||||
}
|
@ -1,224 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// x11key contains X11 numeric codes for the keyboard and mouse.
|
||||
package x11key // import "golang.org/x/exp/shiny/driver/internal/x11key"
|
||||
|
||||
import (
|
||||
"golang.org/x/mobile/event/key"
|
||||
)
|
||||
|
||||
// These constants come from /usr/include/X11/X.h
|
||||
const (
|
||||
ShiftMask = 1 << 0
|
||||
LockMask = 1 << 1
|
||||
ControlMask = 1 << 2
|
||||
Mod1Mask = 1 << 3
|
||||
Mod2Mask = 1 << 4
|
||||
Mod3Mask = 1 << 5
|
||||
Mod4Mask = 1 << 6
|
||||
Mod5Mask = 1 << 7
|
||||
Button1Mask = 1 << 8
|
||||
Button2Mask = 1 << 9
|
||||
Button3Mask = 1 << 10
|
||||
Button4Mask = 1 << 11
|
||||
Button5Mask = 1 << 12
|
||||
)
|
||||
|
||||
type KeysymTable [256][2]uint32
|
||||
|
||||
func (t *KeysymTable) Lookup(detail uint8, state uint16) (rune, key.Code) {
|
||||
// The key event's rune depends on whether the shift key is down.
|
||||
unshifted := rune(t[detail][0])
|
||||
r := unshifted
|
||||
if state&ShiftMask != 0 {
|
||||
r = rune(t[detail][1])
|
||||
// In X11, a zero keysym when shift is down means to use what the
|
||||
// keysym is when shift is up.
|
||||
if r == 0 {
|
||||
r = unshifted
|
||||
}
|
||||
}
|
||||
|
||||
// The key event's code is independent of whether the shift key is down.
|
||||
var c key.Code
|
||||
if 0 <= unshifted && unshifted < 0x80 {
|
||||
// TODO: distinguish the regular '2' key and number-pad '2' key (with
|
||||
// Num-Lock).
|
||||
c = asciiKeycodes[unshifted]
|
||||
} else {
|
||||
r, c = -1, nonUnicodeKeycodes[unshifted]
|
||||
}
|
||||
|
||||
// TODO: Unicode-but-not-ASCII keysyms like the Swiss keyboard's 'ö'.
|
||||
return r, c
|
||||
}
|
||||
|
||||
func KeyModifiers(state uint16) (m key.Modifiers) {
|
||||
if state&ShiftMask != 0 {
|
||||
m |= key.ModShift
|
||||
}
|
||||
if state&ControlMask != 0 {
|
||||
m |= key.ModControl
|
||||
}
|
||||
if state&Mod1Mask != 0 {
|
||||
m |= key.ModAlt
|
||||
}
|
||||
if state&Mod4Mask != 0 {
|
||||
m |= key.ModMeta
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// These constants come from /usr/include/X11/{keysymdef,XF86keysym}.h
|
||||
const (
|
||||
xkISOLeftTab = 0xfe20
|
||||
xkBackSpace = 0xff08
|
||||
xkTab = 0xff09
|
||||
xkReturn = 0xff0d
|
||||
xkEscape = 0xff1b
|
||||
xkMultiKey = 0xff20
|
||||
xkHome = 0xff50
|
||||
xkLeft = 0xff51
|
||||
xkUp = 0xff52
|
||||
xkRight = 0xff53
|
||||
xkDown = 0xff54
|
||||
xkPageUp = 0xff55
|
||||
xkPageDown = 0xff56
|
||||
xkEnd = 0xff57
|
||||
xkInsert = 0xff63
|
||||
xkMenu = 0xff67
|
||||
xkF1 = 0xffbe
|
||||
xkF2 = 0xffbf
|
||||
xkF3 = 0xffc0
|
||||
xkF4 = 0xffc1
|
||||
xkF5 = 0xffc2
|
||||
xkF6 = 0xffc3
|
||||
xkF7 = 0xffc4
|
||||
xkF8 = 0xffc5
|
||||
xkF9 = 0xffc6
|
||||
xkF10 = 0xffc7
|
||||
xkF11 = 0xffc8
|
||||
xkF12 = 0xffc9
|
||||
xkShiftL = 0xffe1
|
||||
xkShiftR = 0xffe2
|
||||
xkControlL = 0xffe3
|
||||
xkControlR = 0xffe4
|
||||
xkAltL = 0xffe9
|
||||
xkAltR = 0xffea
|
||||
xkSuperL = 0xffeb
|
||||
xkSuperR = 0xffec
|
||||
xkDelete = 0xffff
|
||||
|
||||
xf86xkAudioLowerVolume = 0x1008ff11
|
||||
xf86xkAudioMute = 0x1008ff12
|
||||
xf86xkAudioRaiseVolume = 0x1008ff13
|
||||
)
|
||||
|
||||
// nonUnicodeKeycodes maps from those xproto.Keysym values (converted to runes)
|
||||
// that do not correspond to a Unicode code point, such as "Page Up", "F1" or
|
||||
// "Left Shift", to key.Code values.
|
||||
var nonUnicodeKeycodes = map[rune]key.Code{
|
||||
xkISOLeftTab: key.CodeTab,
|
||||
xkBackSpace: key.CodeDeleteBackspace,
|
||||
xkTab: key.CodeTab,
|
||||
xkReturn: key.CodeReturnEnter,
|
||||
xkEscape: key.CodeEscape,
|
||||
xkHome: key.CodeHome,
|
||||
xkLeft: key.CodeLeftArrow,
|
||||
xkUp: key.CodeUpArrow,
|
||||
xkRight: key.CodeRightArrow,
|
||||
xkDown: key.CodeDownArrow,
|
||||
xkPageUp: key.CodePageUp,
|
||||
xkPageDown: key.CodePageDown,
|
||||
xkEnd: key.CodeEnd,
|
||||
xkInsert: key.CodeInsert,
|
||||
xkMenu: key.CodeRightGUI, // TODO: CodeRightGUI or CodeMenu??
|
||||
xkMultiKey: key.CodeCompose,
|
||||
|
||||
xkF1: key.CodeF1,
|
||||
xkF2: key.CodeF2,
|
||||
xkF3: key.CodeF3,
|
||||
xkF4: key.CodeF4,
|
||||
xkF5: key.CodeF5,
|
||||
xkF6: key.CodeF6,
|
||||
xkF7: key.CodeF7,
|
||||
xkF8: key.CodeF8,
|
||||
xkF9: key.CodeF9,
|
||||
xkF10: key.CodeF10,
|
||||
xkF11: key.CodeF11,
|
||||
xkF12: key.CodeF12,
|
||||
|
||||
xkShiftL: key.CodeLeftShift,
|
||||
xkShiftR: key.CodeRightShift,
|
||||
xkControlL: key.CodeLeftControl,
|
||||
xkControlR: key.CodeRightControl,
|
||||
xkAltL: key.CodeLeftAlt,
|
||||
xkAltR: key.CodeRightAlt,
|
||||
xkSuperL: key.CodeLeftGUI,
|
||||
xkSuperR: key.CodeRightGUI,
|
||||
|
||||
xkDelete: key.CodeDeleteForward,
|
||||
|
||||
xf86xkAudioRaiseVolume: key.CodeVolumeUp,
|
||||
xf86xkAudioLowerVolume: key.CodeVolumeDown,
|
||||
xf86xkAudioMute: key.CodeMute,
|
||||
}
|
||||
|
||||
// asciiKeycodes maps lower-case ASCII runes to key.Code values.
|
||||
var asciiKeycodes = [0x80]key.Code{
|
||||
'a': key.CodeA,
|
||||
'b': key.CodeB,
|
||||
'c': key.CodeC,
|
||||
'd': key.CodeD,
|
||||
'e': key.CodeE,
|
||||
'f': key.CodeF,
|
||||
'g': key.CodeG,
|
||||
'h': key.CodeH,
|
||||
'i': key.CodeI,
|
||||
'j': key.CodeJ,
|
||||
'k': key.CodeK,
|
||||
'l': key.CodeL,
|
||||
'm': key.CodeM,
|
||||
'n': key.CodeN,
|
||||
'o': key.CodeO,
|
||||
'p': key.CodeP,
|
||||
'q': key.CodeQ,
|
||||
'r': key.CodeR,
|
||||
's': key.CodeS,
|
||||
't': key.CodeT,
|
||||
'u': key.CodeU,
|
||||
'v': key.CodeV,
|
||||
'w': key.CodeW,
|
||||
'x': key.CodeX,
|
||||
'y': key.CodeY,
|
||||
'z': key.CodeZ,
|
||||
|
||||
'1': key.Code1,
|
||||
'2': key.Code2,
|
||||
'3': key.Code3,
|
||||
'4': key.Code4,
|
||||
'5': key.Code5,
|
||||
'6': key.Code6,
|
||||
'7': key.Code7,
|
||||
'8': key.Code8,
|
||||
'9': key.Code9,
|
||||
'0': key.Code0,
|
||||
|
||||
' ': key.CodeSpacebar,
|
||||
'-': key.CodeHyphenMinus,
|
||||
'=': key.CodeEqualSign,
|
||||
'[': key.CodeLeftSquareBracket,
|
||||
']': key.CodeRightSquareBracket,
|
||||
'\\': key.CodeBackslash,
|
||||
';': key.CodeSemicolon,
|
||||
'\'': key.CodeApostrophe,
|
||||
'`': key.CodeGraveAccent,
|
||||
',': key.CodeComma,
|
||||
'.': key.CodeFullStop,
|
||||
'/': key.CodeSlash,
|
||||
|
||||
// TODO: distinguish CodeKeypadSlash vs CodeSlash, and similarly for other
|
||||
// keypad codes.
|
||||
}
|
@ -1,354 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package screen provides interfaces for portable two-dimensional graphics and
|
||||
// input events.
|
||||
//
|
||||
// Screens are not created directly. Instead, driver packages provide access to
|
||||
// the screen through a Main function that is designed to be called by the
|
||||
// program's main function. The golang.org/x/exp/shiny/driver package provides
|
||||
// the default driver for the system, such as the X11 driver for desktop Linux,
|
||||
// but other drivers, such as the OpenGL driver, can be explicitly invoked by
|
||||
// calling that driver's Main function. To use the default driver:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "golang.org/x/exp/shiny/driver"
|
||||
// "golang.org/x/exp/shiny/screen"
|
||||
// "golang.org/x/mobile/event/lifecycle"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// driver.Main(func(s screen.Screen) {
|
||||
// w, err := s.NewWindow(nil)
|
||||
// if err != nil {
|
||||
// handleError(err)
|
||||
// return
|
||||
// }
|
||||
// defer w.Release()
|
||||
//
|
||||
// for {
|
||||
// switch e := w.NextEvent().(type) {
|
||||
// case lifecycle.Event:
|
||||
// if e.To == lifecycle.StageDead {
|
||||
// return
|
||||
// }
|
||||
// etc
|
||||
// case etc:
|
||||
// etc
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// Complete examples can be found in the shiny/example directory.
|
||||
//
|
||||
// Each driver package provides Screen, Buffer, Texture and Window
|
||||
// implementations that work together. Such types are interface types because
|
||||
// this package is driver-independent, but those interfaces aren't expected to
|
||||
// be implemented outside of drivers. For example, a driver's Window
|
||||
// implementation will generally work only with that driver's Buffer
|
||||
// implementation, and will not work with an arbitrary type that happens to
|
||||
// implement the Buffer methods.
|
||||
package screen // import "golang.org/x/exp/shiny/screen"
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/image/math/f64"
|
||||
)
|
||||
|
||||
// TODO: specify image format (Alpha or Gray, not just RGBA) for NewBuffer
|
||||
// and/or NewTexture?
|
||||
|
||||
// Screen creates Buffers, Textures and Windows.
|
||||
type Screen interface {
|
||||
// NewBuffer returns a new Buffer for this screen.
|
||||
NewBuffer(size image.Point) (Buffer, error)
|
||||
|
||||
// NewTexture returns a new Texture for this screen.
|
||||
NewTexture(size image.Point) (Texture, error)
|
||||
|
||||
// NewWindow returns a new Window for this screen.
|
||||
//
|
||||
// A nil opts is valid and means to use the default option values.
|
||||
NewWindow(opts *NewWindowOptions) (Window, error)
|
||||
}
|
||||
|
||||
// TODO: rename Buffer to Image, to be less confusing with a Window's back and
|
||||
// front buffers.
|
||||
|
||||
// Buffer is an in-memory pixel buffer. Its pixels can be modified by any Go
|
||||
// code that takes an *image.RGBA, such as the standard library's image/draw
|
||||
// package. A Buffer is essentially an *image.RGBA, but not all *image.RGBA
|
||||
// values (including those returned by image.NewRGBA) are valid Buffers, as a
|
||||
// driver may assume that the memory backing a Buffer's pixels are specially
|
||||
// allocated.
|
||||
//
|
||||
// To see a Buffer's contents on a screen, upload it to a Texture (and then
|
||||
// draw the Texture on a Window) or upload it directly to a Window.
|
||||
//
|
||||
// When specifying a sub-Buffer via Upload, a Buffer's top-left pixel is always
|
||||
// (0, 0) in its own coordinate space.
|
||||
type Buffer interface {
|
||||
// Release releases the Buffer's resources, after all pending uploads and
|
||||
// draws resolve.
|
||||
//
|
||||
// The behavior of the Buffer after Release, whether calling its methods or
|
||||
// passing it as an argument, is undefined.
|
||||
Release()
|
||||
|
||||
// Size returns the size of the Buffer's image.
|
||||
Size() image.Point
|
||||
|
||||
// Bounds returns the bounds of the Buffer's image. It is equal to
|
||||
// image.Rectangle{Max: b.Size()}.
|
||||
Bounds() image.Rectangle
|
||||
|
||||
// RGBA returns the pixel buffer as an *image.RGBA.
|
||||
//
|
||||
// Its contents should not be accessed while the Buffer is uploading.
|
||||
//
|
||||
// The contents of the returned *image.RGBA's Pix field (of type []byte)
|
||||
// can be modified at other times, but that Pix slice itself (i.e. its
|
||||
// underlying pointer, length and capacity) should not be modified at any
|
||||
// time.
|
||||
//
|
||||
// The following is valid:
|
||||
// m := buffer.RGBA()
|
||||
// if len(m.Pix) >= 4 {
|
||||
// m.Pix[0] = 0xff
|
||||
// m.Pix[1] = 0x00
|
||||
// m.Pix[2] = 0x00
|
||||
// m.Pix[3] = 0xff
|
||||
// }
|
||||
// or, equivalently:
|
||||
// m := buffer.RGBA()
|
||||
// m.SetRGBA(m.Rect.Min.X, m.Rect.Min.Y, color.RGBA{0xff, 0x00, 0x00, 0xff})
|
||||
// and using the standard library's image/draw package is also valid:
|
||||
// dst := buffer.RGBA()
|
||||
// draw.Draw(dst, dst.Bounds(), etc)
|
||||
// but the following is invalid:
|
||||
// m := buffer.RGBA()
|
||||
// m.Pix = anotherByteSlice
|
||||
// and so is this:
|
||||
// *buffer.RGBA() = anotherImageRGBA
|
||||
RGBA() *image.RGBA
|
||||
}
|
||||
|
||||
// Texture is a pixel buffer, but not one that is directly accessible as a
|
||||
// []byte. Conceptually, it could live on a GPU, in another process or even be
|
||||
// across a network, instead of on a CPU in this process.
|
||||
//
|
||||
// Buffers can be uploaded to Textures, and Textures can be drawn on Windows.
|
||||
//
|
||||
// When specifying a sub-Texture via Draw, a Texture's top-left pixel is always
|
||||
// (0, 0) in its own coordinate space.
|
||||
type Texture interface {
|
||||
// Release releases the Texture's resources, after all pending uploads and
|
||||
// draws resolve.
|
||||
//
|
||||
// The behavior of the Texture after Release, whether calling its methods
|
||||
// or passing it as an argument, is undefined.
|
||||
Release()
|
||||
|
||||
// Size returns the size of the Texture's image.
|
||||
Size() image.Point
|
||||
|
||||
// Bounds returns the bounds of the Texture's image. It is equal to
|
||||
// image.Rectangle{Max: t.Size()}.
|
||||
Bounds() image.Rectangle
|
||||
|
||||
Uploader
|
||||
|
||||
// TODO: also implement Drawer? If so, merge the Uploader and Drawer
|
||||
// interfaces??
|
||||
}
|
||||
|
||||
// EventDeque is an infinitely buffered double-ended queue of events.
|
||||
type EventDeque interface {
|
||||
// Send adds an event to the end of the deque. They are returned by
|
||||
// NextEvent in FIFO order.
|
||||
Send(event interface{})
|
||||
|
||||
// SendFirst adds an event to the start of the deque. They are returned by
|
||||
// NextEvent in LIFO order, and have priority over events sent via Send.
|
||||
SendFirst(event interface{})
|
||||
|
||||
// NextEvent returns the next event in the deque. It blocks until such an
|
||||
// event has been sent.
|
||||
//
|
||||
// Typical event types include:
|
||||
// - lifecycle.Event
|
||||
// - size.Event
|
||||
// - paint.Event
|
||||
// - key.Event
|
||||
// - mouse.Event
|
||||
// - touch.Event
|
||||
// from the golang.org/x/mobile/event/... packages. Other packages may send
|
||||
// events, of those types above or of other types, via Send or SendFirst.
|
||||
NextEvent() interface{}
|
||||
|
||||
// TODO: LatestLifecycleEvent? Is that still worth it if the
|
||||
// lifecycle.Event struct type loses its DrawContext field?
|
||||
|
||||
// TODO: LatestSizeEvent?
|
||||
}
|
||||
|
||||
// Window is a top-level, double-buffered GUI window.
|
||||
type Window interface {
|
||||
// Release closes the window.
|
||||
//
|
||||
// The behavior of the Window after Release, whether calling its methods or
|
||||
// passing it as an argument, is undefined.
|
||||
Release()
|
||||
|
||||
EventDeque
|
||||
|
||||
Uploader
|
||||
|
||||
Drawer
|
||||
|
||||
// Publish flushes any pending Upload and Draw calls to the window, and
|
||||
// swaps the back buffer to the front.
|
||||
Publish() PublishResult
|
||||
}
|
||||
|
||||
// PublishResult is the result of an Window.Publish call.
|
||||
type PublishResult struct {
|
||||
// BackBufferPreserved is whether the contents of the back buffer was
|
||||
// preserved. If false, the contents are undefined.
|
||||
BackBufferPreserved bool
|
||||
}
|
||||
|
||||
// NewWindowOptions are optional arguments to NewWindow.
|
||||
type NewWindowOptions struct {
|
||||
// Width and Height specify the dimensions of the new window. If Width
|
||||
// or Height are zero, a driver-dependent default will be used for each
|
||||
// zero value dimension.
|
||||
Width, Height int
|
||||
|
||||
// Title specifies the window title.
|
||||
Title string
|
||||
|
||||
// TODO: fullscreen, icon, cursorHidden?
|
||||
}
|
||||
|
||||
// GetTitle returns a sanitized form of o.Title. In particular, its length will
|
||||
// not exceed 4096, and it may be further truncated so that it is valid UTF-8
|
||||
// and will not contain the NUL byte.
|
||||
//
|
||||
// o may be nil, in which case "" is returned.
|
||||
func (o *NewWindowOptions) GetTitle() string {
|
||||
if o == nil {
|
||||
return ""
|
||||
}
|
||||
return sanitizeUTF8(o.Title, 4096)
|
||||
}
|
||||
|
||||
func sanitizeUTF8(s string, n int) string {
|
||||
if n < len(s) {
|
||||
s = s[:n]
|
||||
}
|
||||
i := 0
|
||||
for i < len(s) {
|
||||
r, n := utf8.DecodeRuneInString(s[i:])
|
||||
if r == 0 || (r == utf8.RuneError && n == 1) {
|
||||
break
|
||||
}
|
||||
i += n
|
||||
}
|
||||
return s[:i]
|
||||
}
|
||||
|
||||
// Uploader is something you can upload a Buffer to.
|
||||
type Uploader interface {
|
||||
// Upload uploads the sub-Buffer defined by src and sr to the destination
|
||||
// (the method receiver), such that sr.Min in src-space aligns with dp in
|
||||
// dst-space. The destination's contents are overwritten; the draw operator
|
||||
// is implicitly draw.Src.
|
||||
//
|
||||
// It is valid to upload a Buffer while another upload of the same Buffer
|
||||
// is in progress, but a Buffer's image.RGBA pixel contents should not be
|
||||
// accessed while it is uploading. A Buffer is re-usable, in that its pixel
|
||||
// contents can be further modified, once all outstanding calls to Upload
|
||||
// have returned.
|
||||
//
|
||||
// TODO: make it optional that a Buffer's contents is preserved after
|
||||
// Upload? Undoing a swizzle is a non-trivial amount of work, and can be
|
||||
// redundant if the next paint cycle starts by clearing the buffer.
|
||||
//
|
||||
// When uploading to a Window, there will not be any visible effect until
|
||||
// Publish is called.
|
||||
Upload(dp image.Point, src Buffer, sr image.Rectangle)
|
||||
|
||||
// Fill fills that part of the destination (the method receiver) defined by
|
||||
// dr with the given color.
|
||||
//
|
||||
// When filling a Window, there will not be any visible effect until
|
||||
// Publish is called.
|
||||
Fill(dr image.Rectangle, src color.Color, op draw.Op)
|
||||
}
|
||||
|
||||
// TODO: have a Downloader interface? Not every graphical app needs to be
|
||||
// interactive or involve a window. You could use the GPU for hardware-
|
||||
// accelerated image manipulation: upload a buffer, do some texture ops, then
|
||||
// download the result.
|
||||
|
||||
// Drawer is something you can draw Textures on.
|
||||
//
|
||||
// Draw is the most general purpose of this interface's methods. It supports
|
||||
// arbitrary affine transformations, such as translations, scales and
|
||||
// rotations.
|
||||
//
|
||||
// Copy and Scale are more specific versions of Draw. The affected dst pixels
|
||||
// are an axis-aligned rectangle, quantized to the pixel grid. Copy copies
|
||||
// pixels in a 1:1 manner, Scale is more general. They have simpler parameters
|
||||
// than Draw, using ints instead of float64s.
|
||||
//
|
||||
// When drawing on a Window, there will not be any visible effect until Publish
|
||||
// is called.
|
||||
type Drawer interface {
|
||||
// Draw draws the sub-Texture defined by src and sr to the destination (the
|
||||
// method receiver). src2dst defines how to transform src coordinates to
|
||||
// dst coordinates. For example, if src2dst is the matrix
|
||||
//
|
||||
// m00 m01 m02
|
||||
// m10 m11 m12
|
||||
//
|
||||
// then the src-space point (sx, sy) maps to the dst-space point
|
||||
// (m00*sx + m01*sy + m02, m10*sx + m11*sy + m12).
|
||||
Draw(src2dst f64.Aff3, src Texture, sr image.Rectangle, op draw.Op, opts *DrawOptions)
|
||||
|
||||
// DrawUniform is like Draw except that the src is a uniform color instead
|
||||
// of a Texture.
|
||||
DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *DrawOptions)
|
||||
|
||||
// Copy copies the sub-Texture defined by src and sr to the destination
|
||||
// (the method receiver), such that sr.Min in src-space aligns with dp in
|
||||
// dst-space.
|
||||
Copy(dp image.Point, src Texture, sr image.Rectangle, op draw.Op, opts *DrawOptions)
|
||||
|
||||
// Scale scales the sub-Texture defined by src and sr to the destination
|
||||
// (the method receiver), such that sr in src-space is mapped to dr in
|
||||
// dst-space.
|
||||
Scale(dr image.Rectangle, src Texture, sr image.Rectangle, op draw.Op, opts *DrawOptions)
|
||||
}
|
||||
|
||||
// These draw.Op constants are provided so that users of this package don't
|
||||
// have to explicitly import "image/draw".
|
||||
const (
|
||||
Over = draw.Over
|
||||
Src = draw.Src
|
||||
)
|
||||
|
||||
// DrawOptions are optional arguments to Draw.
|
||||
type DrawOptions struct {
|
||||
// TODO: transparency in [0x0000, 0xffff]?
|
||||
// TODO: scaler (nearest neighbor vs linear)?
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
@ -1,3 +0,0 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
@ -1,27 +0,0 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -1,22 +0,0 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
@ -1,37 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package f64 implements float64 vector and matrix types.
|
||||
package f64 // import "golang.org/x/image/math/f64"
|
||||
|
||||
// Vec2 is a 2-element vector.
|
||||
type Vec2 [2]float64
|
||||
|
||||
// Vec3 is a 3-element vector.
|
||||
type Vec3 [3]float64
|
||||
|
||||
// Vec4 is a 4-element vector.
|
||||
type Vec4 [4]float64
|
||||
|
||||
// Mat3 is a 3x3 matrix in row major order.
|
||||
//
|
||||
// m[3*r + c] is the element in the r'th row and c'th column.
|
||||
type Mat3 [9]float64
|
||||
|
||||
// Mat4 is a 4x4 matrix in row major order.
|
||||
//
|
||||
// m[4*r + c] is the element in the r'th row and c'th column.
|
||||
type Mat4 [16]float64
|
||||
|
||||
// Aff3 is a 3x3 affine transformation matrix in row major order, where the
|
||||
// bottom row is implicitly [0 0 1].
|
||||
//
|
||||
// m[3*r + c] is the element in the r'th row and c'th column.
|
||||
type Aff3 [6]float64
|
||||
|
||||
// Aff4 is a 4x4 affine transformation matrix in row major order, where the
|
||||
// bottom row is implicitly [0 0 0 1].
|
||||
//
|
||||
// m[4*r + c] is the element in the r'th row and c'th column.
|
||||
type Aff4 [12]float64
|
@ -1,3 +0,0 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
@ -1,3 +0,0 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
@ -1,27 +0,0 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -1,22 +0,0 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
@ -1,67 +0,0 @@
|
||||
package org.golang.app;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.NativeActivity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.KeyCharacterMap;
|
||||
|
||||
public class GoNativeActivity extends NativeActivity {
|
||||
private static GoNativeActivity goNativeActivity;
|
||||
|
||||
public GoNativeActivity() {
|
||||
super();
|
||||
goNativeActivity = this;
|
||||
}
|
||||
|
||||
String getTmpdir() {
|
||||
return getCacheDir().getAbsolutePath();
|
||||
}
|
||||
|
||||
int getRune(int deviceId, int keyCode, int metaState) {
|
||||
try {
|
||||
int rune = KeyCharacterMap.load(deviceId).get(keyCode, metaState);
|
||||
if (rune == 0) {
|
||||
return -1;
|
||||
}
|
||||
return rune;
|
||||
} catch (KeyCharacterMap.UnavailableException e) {
|
||||
return -1;
|
||||
} catch (Exception e) {
|
||||
Log.e("Go", "exception reading KeyCharacterMap", e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void load() {
|
||||
// Interestingly, NativeActivity uses a different method
|
||||
// to find native code to execute, avoiding
|
||||
// System.loadLibrary. The result is Java methods
|
||||
// implemented in C with JNIEXPORT (and JNI_OnLoad) are not
|
||||
// available unless an explicit call to System.loadLibrary
|
||||
// is done. So we do it here, borrowing the name of the
|
||||
// library from the same AndroidManifest.xml metadata used
|
||||
// by NativeActivity.
|
||||
try {
|
||||
ActivityInfo ai = getPackageManager().getActivityInfo(
|
||||
getIntent().getComponent(), PackageManager.GET_META_DATA);
|
||||
if (ai.metaData == null) {
|
||||
Log.e("Go", "loadLibrary: no manifest metadata found");
|
||||
return;
|
||||
}
|
||||
String libName = ai.metaData.getString("android.app.lib_name");
|
||||
System.loadLibrary(libName);
|
||||
} catch (Exception e) {
|
||||
Log.e("Go", "loadLibrary failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
load();
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
}
|
@ -1,191 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build android
|
||||
|
||||
#include <android/log.h>
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
#define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, "Go", __VA_ARGS__)
|
||||
#define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "Go", __VA_ARGS__)
|
||||
|
||||
static jobject current_ctx;
|
||||
|
||||
static jclass find_class(JNIEnv *env, const char *class_name) {
|
||||
jclass clazz = (*env)->FindClass(env, class_name);
|
||||
if (clazz == NULL) {
|
||||
(*env)->ExceptionClear(env);
|
||||
LOG_FATAL("cannot find %s", class_name);
|
||||
return NULL;
|
||||
}
|
||||
return clazz;
|
||||
}
|
||||
|
||||
static jmethodID find_method(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
|
||||
jmethodID m = (*env)->GetMethodID(env, clazz, name, sig);
|
||||
if (m == 0) {
|
||||
(*env)->ExceptionClear(env);
|
||||
LOG_FATAL("cannot find method %s %s", name, sig);
|
||||
return 0;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
static jmethodID key_rune_method;
|
||||
|
||||
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
JNIEnv* env;
|
||||
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
static int main_running = 0;
|
||||
|
||||
// Entry point from our subclassed NativeActivity.
|
||||
//
|
||||
// By here, the Go runtime has been initialized (as we are running in
|
||||
// -buildmode=c-shared) but the first time it is called, Go's main.main
|
||||
// hasn't been called yet.
|
||||
//
|
||||
// The Activity may be created and destroyed multiple times throughout
|
||||
// the life of a single process. Each time, onCreate is called.
|
||||
void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_t savedStateSize) {
|
||||
if (!main_running) {
|
||||
JNIEnv* env = activity->env;
|
||||
|
||||
// Note that activity->clazz is mis-named.
|
||||
current_ctx = activity->clazz;
|
||||
|
||||
jclass clazz = (*env)->GetObjectClass(env, current_ctx);
|
||||
key_rune_method = find_method(env, clazz, "getRune", "(III)I");
|
||||
|
||||
setCurrentContext(activity->vm, (*env)->NewGlobalRef(env, current_ctx));
|
||||
|
||||
// Set TMPDIR.
|
||||
jmethodID gettmpdir = find_method(env, clazz, "getTmpdir", "()Ljava/lang/String;");
|
||||
jstring jpath = (jstring)(*env)->CallObjectMethod(env, current_ctx, gettmpdir, NULL);
|
||||
const char* tmpdir = (*env)->GetStringUTFChars(env, jpath, NULL);
|
||||
if (setenv("TMPDIR", tmpdir, 1) != 0) {
|
||||
LOG_INFO("setenv(\"TMPDIR\", \"%s\", 1) failed: %d", tmpdir, errno);
|
||||
}
|
||||
(*env)->ReleaseStringUTFChars(env, jpath, tmpdir);
|
||||
|
||||
// Call the Go main.main.
|
||||
uintptr_t mainPC = (uintptr_t)dlsym(RTLD_DEFAULT, "main.main");
|
||||
if (!mainPC) {
|
||||
LOG_FATAL("missing main.main");
|
||||
}
|
||||
callMain(mainPC);
|
||||
main_running = 1;
|
||||
}
|
||||
|
||||
// These functions match the methods on Activity, described at
|
||||
// http://developer.android.com/reference/android/app/Activity.html
|
||||
//
|
||||
// Note that onNativeWindowResized is not called on resize. Avoid it.
|
||||
// https://code.google.com/p/android/issues/detail?id=180645
|
||||
activity->callbacks->onStart = onStart;
|
||||
activity->callbacks->onResume = onResume;
|
||||
activity->callbacks->onSaveInstanceState = onSaveInstanceState;
|
||||
activity->callbacks->onPause = onPause;
|
||||
activity->callbacks->onStop = onStop;
|
||||
activity->callbacks->onDestroy = onDestroy;
|
||||
activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
|
||||
activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
|
||||
activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded;
|
||||
activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
|
||||
activity->callbacks->onInputQueueCreated = onInputQueueCreated;
|
||||
activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
|
||||
activity->callbacks->onConfigurationChanged = onConfigurationChanged;
|
||||
activity->callbacks->onLowMemory = onLowMemory;
|
||||
|
||||
onCreate(activity);
|
||||
}
|
||||
|
||||
// TODO(crawshaw): Test configuration on more devices.
|
||||
static const EGLint RGB_888[] = {
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_DEPTH_SIZE, 16,
|
||||
EGL_CONFIG_CAVEAT, EGL_NONE,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
EGLDisplay display = NULL;
|
||||
EGLSurface surface = NULL;
|
||||
|
||||
static char* initEGLDisplay() {
|
||||
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
if (!eglInitialize(display, 0, 0)) {
|
||||
return "EGL initialize failed";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* createEGLSurface(ANativeWindow* window) {
|
||||
char* err;
|
||||
EGLint numConfigs, format;
|
||||
EGLConfig config;
|
||||
EGLContext context;
|
||||
|
||||
if (display == 0) {
|
||||
if ((err = initEGLDisplay()) != NULL) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!eglChooseConfig(display, RGB_888, &config, 1, &numConfigs)) {
|
||||
return "EGL choose RGB_888 config failed";
|
||||
}
|
||||
if (numConfigs <= 0) {
|
||||
return "EGL no config found";
|
||||
}
|
||||
|
||||
eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
|
||||
if (ANativeWindow_setBuffersGeometry(window, 0, 0, format) != 0) {
|
||||
return "EGL set buffers geometry failed";
|
||||
}
|
||||
|
||||
surface = eglCreateWindowSurface(display, config, window, NULL);
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
return "EGL create surface failed";
|
||||
}
|
||||
|
||||
const EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
|
||||
context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
|
||||
|
||||
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
|
||||
return "eglMakeCurrent failed";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* destroyEGLSurface() {
|
||||
if (!eglDestroySurface(display, surface)) {
|
||||
return "EGL destroy surface failed";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int32_t getKeyRune(JNIEnv* env, AInputEvent* e) {
|
||||
return (int32_t)(*env)->CallIntMethod(
|
||||
env,
|
||||
current_ctx,
|
||||
key_rune_method,
|
||||
AInputEvent_getDeviceId(e),
|
||||
AKeyEvent_getKeyCode(e),
|
||||
AKeyEvent_getMetaState(e)
|
||||
);
|
||||
}
|
@ -1,819 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build android
|
||||
|
||||
/*
|
||||
Android Apps are built with -buildmode=c-shared. They are loaded by a
|
||||
running Java process.
|
||||
|
||||
Before any entry point is reached, a global constructor initializes the
|
||||
Go runtime, calling all Go init functions. All cgo calls will block
|
||||
until this is complete. Next JNI_OnLoad is called. When that is
|
||||
complete, one of two entry points is called.
|
||||
|
||||
All-Go apps built using NativeActivity enter at ANativeActivity_onCreate.
|
||||
|
||||
Go libraries (for example, those built with gomobile bind) do not use
|
||||
the app package initialization.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -landroid -llog -lEGL -lGLESv2
|
||||
|
||||
#include <android/configuration.h>
|
||||
#include <android/input.h>
|
||||
#include <android/keycodes.h>
|
||||
#include <android/looper.h>
|
||||
#include <android/native_activity.h>
|
||||
#include <android/native_window.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <jni.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
EGLDisplay display;
|
||||
EGLSurface surface;
|
||||
|
||||
char* createEGLSurface(ANativeWindow* window);
|
||||
char* destroyEGLSurface();
|
||||
int32_t getKeyRune(JNIEnv* env, AInputEvent* e);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/mobile/app/internal/callfn"
|
||||
"golang.org/x/mobile/event/key"
|
||||
"golang.org/x/mobile/event/lifecycle"
|
||||
"golang.org/x/mobile/event/paint"
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/event/touch"
|
||||
"golang.org/x/mobile/geom"
|
||||
"golang.org/x/mobile/internal/mobileinit"
|
||||
)
|
||||
|
||||
// RunOnJVM runs fn on a new goroutine locked to an OS thread with a JNIEnv.
|
||||
//
|
||||
// RunOnJVM blocks until the call to fn is complete. Any Java
|
||||
// exception or failure to attach to the JVM is returned as an error.
|
||||
//
|
||||
// The function fn takes vm, the current JavaVM*,
|
||||
// env, the current JNIEnv*, and
|
||||
// ctx, a jobject representing the global android.context.Context.
|
||||
func RunOnJVM(fn func(vm, jniEnv, ctx uintptr) error) error {
|
||||
return mobileinit.RunOnJVM(fn)
|
||||
}
|
||||
|
||||
//export setCurrentContext
|
||||
func setCurrentContext(vm *C.JavaVM, ctx C.jobject) {
|
||||
mobileinit.SetCurrentContext(unsafe.Pointer(vm), uintptr(ctx))
|
||||
}
|
||||
|
||||
//export callMain
|
||||
func callMain(mainPC uintptr) {
|
||||
for _, name := range []string{"TMPDIR", "PATH", "LD_LIBRARY_PATH"} {
|
||||
n := C.CString(name)
|
||||
os.Setenv(name, C.GoString(C.getenv(n)))
|
||||
C.free(unsafe.Pointer(n))
|
||||
}
|
||||
|
||||
// Set timezone.
|
||||
//
|
||||
// Note that Android zoneinfo is stored in /system/usr/share/zoneinfo,
|
||||
// but it is in some kind of packed TZiff file that we do not support
|
||||
// yet. As a stopgap, we build a fixed zone using the tm_zone name.
|
||||
var curtime C.time_t
|
||||
var curtm C.struct_tm
|
||||
C.time(&curtime)
|
||||
C.localtime_r(&curtime, &curtm)
|
||||
tzOffset := int(curtm.tm_gmtoff)
|
||||
tz := C.GoString(curtm.tm_zone)
|
||||
time.Local = time.FixedZone(tz, tzOffset)
|
||||
|
||||
go callfn.CallFn(mainPC)
|
||||
}
|
||||
|
||||
//export onStart
|
||||
func onStart(activity *C.ANativeActivity) {
|
||||
}
|
||||
|
||||
//export onResume
|
||||
func onResume(activity *C.ANativeActivity) {
|
||||
}
|
||||
|
||||
//export onSaveInstanceState
|
||||
func onSaveInstanceState(activity *C.ANativeActivity, outSize *C.size_t) unsafe.Pointer {
|
||||
return nil
|
||||
}
|
||||
|
||||
//export onPause
|
||||
func onPause(activity *C.ANativeActivity) {
|
||||
}
|
||||
|
||||
//export onStop
|
||||
func onStop(activity *C.ANativeActivity) {
|
||||
}
|
||||
|
||||
//export onCreate
|
||||
func onCreate(activity *C.ANativeActivity) {
|
||||
// Set the initial configuration.
|
||||
//
|
||||
// Note we use unbuffered channels to talk to the activity loop, and
|
||||
// NativeActivity calls these callbacks sequentially, so configuration
|
||||
// will be set before <-windowRedrawNeeded is processed.
|
||||
windowConfigChange <- windowConfigRead(activity)
|
||||
}
|
||||
|
||||
//export onDestroy
|
||||
func onDestroy(activity *C.ANativeActivity) {
|
||||
}
|
||||
|
||||
//export onWindowFocusChanged
|
||||
func onWindowFocusChanged(activity *C.ANativeActivity, hasFocus int) {
|
||||
}
|
||||
|
||||
//export onNativeWindowCreated
|
||||
func onNativeWindowCreated(activity *C.ANativeActivity, window *C.ANativeWindow) {
|
||||
}
|
||||
|
||||
//export onNativeWindowRedrawNeeded
|
||||
func onNativeWindowRedrawNeeded(activity *C.ANativeActivity, window *C.ANativeWindow) {
|
||||
// Called on orientation change and window resize.
|
||||
// Send a request for redraw, and block this function
|
||||
// until a complete draw and buffer swap is completed.
|
||||
// This is required by the redraw documentation to
|
||||
// avoid bad draws.
|
||||
windowRedrawNeeded <- window
|
||||
<-windowRedrawDone
|
||||
}
|
||||
|
||||
//export onNativeWindowDestroyed
|
||||
func onNativeWindowDestroyed(activity *C.ANativeActivity, window *C.ANativeWindow) {
|
||||
windowDestroyed <- window
|
||||
}
|
||||
|
||||
//export onInputQueueCreated
|
||||
func onInputQueueCreated(activity *C.ANativeActivity, q *C.AInputQueue) {
|
||||
inputQueue <- q
|
||||
<-inputQueueDone
|
||||
}
|
||||
|
||||
//export onInputQueueDestroyed
|
||||
func onInputQueueDestroyed(activity *C.ANativeActivity, q *C.AInputQueue) {
|
||||
inputQueue <- nil
|
||||
<-inputQueueDone
|
||||
}
|
||||
|
||||
//export onContentRectChanged
|
||||
func onContentRectChanged(activity *C.ANativeActivity, rect *C.ARect) {
|
||||
}
|
||||
|
||||
type windowConfig struct {
|
||||
orientation size.Orientation
|
||||
pixelsPerPt float32
|
||||
}
|
||||
|
||||
func windowConfigRead(activity *C.ANativeActivity) windowConfig {
|
||||
aconfig := C.AConfiguration_new()
|
||||
C.AConfiguration_fromAssetManager(aconfig, activity.assetManager)
|
||||
orient := C.AConfiguration_getOrientation(aconfig)
|
||||
density := C.AConfiguration_getDensity(aconfig)
|
||||
C.AConfiguration_delete(aconfig)
|
||||
|
||||
// Calculate the screen resolution. This value is approximate. For example,
|
||||
// a physical resolution of 200 DPI may be quantized to one of the
|
||||
// ACONFIGURATION_DENSITY_XXX values such as 160 or 240.
|
||||
//
|
||||
// A more accurate DPI could possibly be calculated from
|
||||
// https://developer.android.com/reference/android/util/DisplayMetrics.html#xdpi
|
||||
// but this does not appear to be accessible via the NDK. In any case, the
|
||||
// hardware might not even provide a more accurate number, as the system
|
||||
// does not apparently use the reported value. See golang.org/issue/13366
|
||||
// for a discussion.
|
||||
var dpi int
|
||||
switch density {
|
||||
case C.ACONFIGURATION_DENSITY_DEFAULT:
|
||||
dpi = 160
|
||||
case C.ACONFIGURATION_DENSITY_LOW,
|
||||
C.ACONFIGURATION_DENSITY_MEDIUM,
|
||||
213, // C.ACONFIGURATION_DENSITY_TV
|
||||
C.ACONFIGURATION_DENSITY_HIGH,
|
||||
320, // ACONFIGURATION_DENSITY_XHIGH
|
||||
480, // ACONFIGURATION_DENSITY_XXHIGH
|
||||
640: // ACONFIGURATION_DENSITY_XXXHIGH
|
||||
dpi = int(density)
|
||||
case C.ACONFIGURATION_DENSITY_NONE:
|
||||
log.Print("android device reports no screen density")
|
||||
dpi = 72
|
||||
default:
|
||||
log.Printf("android device reports unknown density: %d", density)
|
||||
// All we can do is guess.
|
||||
if density > 0 {
|
||||
dpi = int(density)
|
||||
} else {
|
||||
dpi = 72
|
||||
}
|
||||
}
|
||||
|
||||
o := size.OrientationUnknown
|
||||
switch orient {
|
||||
case C.ACONFIGURATION_ORIENTATION_PORT:
|
||||
o = size.OrientationPortrait
|
||||
case C.ACONFIGURATION_ORIENTATION_LAND:
|
||||
o = size.OrientationLandscape
|
||||
}
|
||||
|
||||
return windowConfig{
|
||||
orientation: o,
|
||||
pixelsPerPt: float32(dpi) / 72,
|
||||
}
|
||||
}
|
||||
|
||||
//export onConfigurationChanged
|
||||
func onConfigurationChanged(activity *C.ANativeActivity) {
|
||||
// A rotation event first triggers onConfigurationChanged, then
|
||||
// calls onNativeWindowRedrawNeeded. We extract the orientation
|
||||
// here and save it for the redraw event.
|
||||
windowConfigChange <- windowConfigRead(activity)
|
||||
}
|
||||
|
||||
//export onLowMemory
|
||||
func onLowMemory(activity *C.ANativeActivity) {
|
||||
}
|
||||
|
||||
var (
|
||||
inputQueue = make(chan *C.AInputQueue)
|
||||
inputQueueDone = make(chan struct{})
|
||||
windowDestroyed = make(chan *C.ANativeWindow)
|
||||
windowRedrawNeeded = make(chan *C.ANativeWindow)
|
||||
windowRedrawDone = make(chan struct{})
|
||||
windowConfigChange = make(chan windowConfig)
|
||||
)
|
||||
|
||||
func init() {
|
||||
theApp.registerGLViewportFilter()
|
||||
}
|
||||
|
||||
func main(f func(App)) {
|
||||
mainUserFn = f
|
||||
// TODO: merge the runInputQueue and mainUI functions?
|
||||
go func() {
|
||||
if err := mobileinit.RunOnJVM(runInputQueue); err != nil {
|
||||
log.Fatalf("app: %v", err)
|
||||
}
|
||||
}()
|
||||
// Preserve this OS thread for:
|
||||
// 1. the attached JNI thread
|
||||
// 2. the GL context
|
||||
if err := mobileinit.RunOnJVM(mainUI); err != nil {
|
||||
log.Fatalf("app: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var mainUserFn func(App)
|
||||
|
||||
func mainUI(vm, jniEnv, ctx uintptr) error {
|
||||
workAvailable := theApp.worker.WorkAvailable()
|
||||
|
||||
donec := make(chan struct{})
|
||||
go func() {
|
||||
mainUserFn(theApp)
|
||||
close(donec)
|
||||
}()
|
||||
|
||||
var pixelsPerPt float32
|
||||
var orientation size.Orientation
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-donec:
|
||||
return nil
|
||||
case cfg := <-windowConfigChange:
|
||||
pixelsPerPt = cfg.pixelsPerPt
|
||||
orientation = cfg.orientation
|
||||
case w := <-windowRedrawNeeded:
|
||||
if C.surface == nil {
|
||||
if errStr := C.createEGLSurface(w); errStr != nil {
|
||||
return fmt.Errorf("%s (%s)", C.GoString(errStr), eglGetError())
|
||||
}
|
||||
}
|
||||
theApp.sendLifecycle(lifecycle.StageFocused)
|
||||
widthPx := int(C.ANativeWindow_getWidth(w))
|
||||
heightPx := int(C.ANativeWindow_getHeight(w))
|
||||
theApp.eventsIn <- size.Event{
|
||||
WidthPx: widthPx,
|
||||
HeightPx: heightPx,
|
||||
WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt),
|
||||
HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt),
|
||||
PixelsPerPt: pixelsPerPt,
|
||||
Orientation: orientation,
|
||||
}
|
||||
theApp.eventsIn <- paint.Event{External: true}
|
||||
case <-windowDestroyed:
|
||||
if C.surface != nil {
|
||||
if errStr := C.destroyEGLSurface(); errStr != nil {
|
||||
return fmt.Errorf("%s (%s)", C.GoString(errStr), eglGetError())
|
||||
}
|
||||
}
|
||||
C.surface = nil
|
||||
theApp.sendLifecycle(lifecycle.StageAlive)
|
||||
case <-workAvailable:
|
||||
theApp.worker.DoWork()
|
||||
case <-theApp.publish:
|
||||
// TODO: compare a generation number to redrawGen for stale paints?
|
||||
if C.surface != nil {
|
||||
// eglSwapBuffers blocks until vsync.
|
||||
if C.eglSwapBuffers(C.display, C.surface) == C.EGL_FALSE {
|
||||
log.Printf("app: failed to swap buffers (%s)", eglGetError())
|
||||
}
|
||||
}
|
||||
select {
|
||||
case windowRedrawDone <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
theApp.publishResult <- PublishResult{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runInputQueue(vm, jniEnv, ctx uintptr) error {
|
||||
env := (*C.JNIEnv)(unsafe.Pointer(jniEnv)) // not a Go heap pointer
|
||||
|
||||
// Android loopers select on OS file descriptors, not Go channels, so we
|
||||
// translate the inputQueue channel to an ALooper_wake call.
|
||||
l := C.ALooper_prepare(C.ALOOPER_PREPARE_ALLOW_NON_CALLBACKS)
|
||||
pending := make(chan *C.AInputQueue, 1)
|
||||
go func() {
|
||||
for q := range inputQueue {
|
||||
pending <- q
|
||||
C.ALooper_wake(l)
|
||||
}
|
||||
}()
|
||||
|
||||
var q *C.AInputQueue
|
||||
for {
|
||||
if C.ALooper_pollAll(-1, nil, nil, nil) == C.ALOOPER_POLL_WAKE {
|
||||
select {
|
||||
default:
|
||||
case p := <-pending:
|
||||
if q != nil {
|
||||
processEvents(env, q)
|
||||
C.AInputQueue_detachLooper(q)
|
||||
}
|
||||
q = p
|
||||
if q != nil {
|
||||
C.AInputQueue_attachLooper(q, l, 0, nil, nil)
|
||||
}
|
||||
inputQueueDone <- struct{}{}
|
||||
}
|
||||
}
|
||||
if q != nil {
|
||||
processEvents(env, q)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processEvents(env *C.JNIEnv, q *C.AInputQueue) {
|
||||
var e *C.AInputEvent
|
||||
for C.AInputQueue_getEvent(q, &e) >= 0 {
|
||||
if C.AInputQueue_preDispatchEvent(q, e) != 0 {
|
||||
continue
|
||||
}
|
||||
processEvent(env, e)
|
||||
C.AInputQueue_finishEvent(q, e, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func processEvent(env *C.JNIEnv, e *C.AInputEvent) {
|
||||
switch C.AInputEvent_getType(e) {
|
||||
case C.AINPUT_EVENT_TYPE_KEY:
|
||||
processKey(env, e)
|
||||
case C.AINPUT_EVENT_TYPE_MOTION:
|
||||
// At most one of the events in this batch is an up or down event; get its index and change.
|
||||
upDownIndex := C.size_t(C.AMotionEvent_getAction(e)&C.AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> C.AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT
|
||||
upDownType := touch.TypeMove
|
||||
switch C.AMotionEvent_getAction(e) & C.AMOTION_EVENT_ACTION_MASK {
|
||||
case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
|
||||
upDownType = touch.TypeBegin
|
||||
case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
|
||||
upDownType = touch.TypeEnd
|
||||
}
|
||||
|
||||
for i, n := C.size_t(0), C.AMotionEvent_getPointerCount(e); i < n; i++ {
|
||||
t := touch.TypeMove
|
||||
if i == upDownIndex {
|
||||
t = upDownType
|
||||
}
|
||||
theApp.eventsIn <- touch.Event{
|
||||
X: float32(C.AMotionEvent_getX(e, i)),
|
||||
Y: float32(C.AMotionEvent_getY(e, i)),
|
||||
Sequence: touch.Sequence(C.AMotionEvent_getPointerId(e, i)),
|
||||
Type: t,
|
||||
}
|
||||
}
|
||||
default:
|
||||
log.Printf("unknown input event, type=%d", C.AInputEvent_getType(e))
|
||||
}
|
||||
}
|
||||
|
||||
func processKey(env *C.JNIEnv, e *C.AInputEvent) {
|
||||
deviceID := C.AInputEvent_getDeviceId(e)
|
||||
if deviceID == 0 {
|
||||
// Software keyboard input, leaving for scribe/IME.
|
||||
return
|
||||
}
|
||||
|
||||
k := key.Event{
|
||||
Rune: rune(C.getKeyRune(env, e)),
|
||||
Code: convAndroidKeyCode(int32(C.AKeyEvent_getKeyCode(e))),
|
||||
}
|
||||
switch C.AKeyEvent_getAction(e) {
|
||||
case C.AKEY_STATE_DOWN:
|
||||
k.Direction = key.DirPress
|
||||
case C.AKEY_STATE_UP:
|
||||
k.Direction = key.DirRelease
|
||||
default:
|
||||
k.Direction = key.DirNone
|
||||
}
|
||||
// TODO(crawshaw): set Modifiers.
|
||||
theApp.eventsIn <- k
|
||||
}
|
||||
|
||||
func eglGetError() string {
|
||||
switch errNum := C.eglGetError(); errNum {
|
||||
case C.EGL_SUCCESS:
|
||||
return "EGL_SUCCESS"
|
||||
case C.EGL_NOT_INITIALIZED:
|
||||
return "EGL_NOT_INITIALIZED"
|
||||
case C.EGL_BAD_ACCESS:
|
||||
return "EGL_BAD_ACCESS"
|
||||
case C.EGL_BAD_ALLOC:
|
||||
return "EGL_BAD_ALLOC"
|
||||
case C.EGL_BAD_ATTRIBUTE:
|
||||
return "EGL_BAD_ATTRIBUTE"
|
||||
case C.EGL_BAD_CONTEXT:
|
||||
return "EGL_BAD_CONTEXT"
|
||||
case C.EGL_BAD_CONFIG:
|
||||
return "EGL_BAD_CONFIG"
|
||||
case C.EGL_BAD_CURRENT_SURFACE:
|
||||
return "EGL_BAD_CURRENT_SURFACE"
|
||||
case C.EGL_BAD_DISPLAY:
|
||||
return "EGL_BAD_DISPLAY"
|
||||
case C.EGL_BAD_SURFACE:
|
||||
return "EGL_BAD_SURFACE"
|
||||
case C.EGL_BAD_MATCH:
|
||||
return "EGL_BAD_MATCH"
|
||||
case C.EGL_BAD_PARAMETER:
|
||||
return "EGL_BAD_PARAMETER"
|
||||
case C.EGL_BAD_NATIVE_PIXMAP:
|
||||
return "EGL_BAD_NATIVE_PIXMAP"
|
||||
case C.EGL_BAD_NATIVE_WINDOW:
|
||||
return "EGL_BAD_NATIVE_WINDOW"
|
||||
case C.EGL_CONTEXT_LOST:
|
||||
return "EGL_CONTEXT_LOST"
|
||||
default:
|
||||
return fmt.Sprintf("Unknown EGL err: %d", errNum)
|
||||
}
|
||||
}
|
||||
|
||||
func convAndroidKeyCode(aKeyCode int32) key.Code {
|
||||
// Many Android key codes do not map into USB HID codes.
|
||||
// For those, key.CodeUnknown is returned. This switch has all
|
||||
// cases, even the unknown ones, to serve as a documentation
|
||||
// and search aid.
|
||||
switch aKeyCode {
|
||||
case C.AKEYCODE_UNKNOWN:
|
||||
case C.AKEYCODE_SOFT_LEFT:
|
||||
case C.AKEYCODE_SOFT_RIGHT:
|
||||
case C.AKEYCODE_HOME:
|
||||
return key.CodeHome
|
||||
case C.AKEYCODE_BACK:
|
||||
case C.AKEYCODE_CALL:
|
||||
case C.AKEYCODE_ENDCALL:
|
||||
case C.AKEYCODE_0:
|
||||
return key.Code0
|
||||
case C.AKEYCODE_1:
|
||||
return key.Code1
|
||||
case C.AKEYCODE_2:
|
||||
return key.Code2
|
||||
case C.AKEYCODE_3:
|
||||
return key.Code3
|
||||
case C.AKEYCODE_4:
|
||||
return key.Code4
|
||||
case C.AKEYCODE_5:
|
||||
return key.Code5
|
||||
case C.AKEYCODE_6:
|
||||
return key.Code6
|
||||
case C.AKEYCODE_7:
|
||||
return key.Code7
|
||||
case C.AKEYCODE_8:
|
||||
return key.Code8
|
||||
case C.AKEYCODE_9:
|
||||
return key.Code9
|
||||
case C.AKEYCODE_STAR:
|
||||
case C.AKEYCODE_POUND:
|
||||
case C.AKEYCODE_DPAD_UP:
|
||||
case C.AKEYCODE_DPAD_DOWN:
|
||||
case C.AKEYCODE_DPAD_LEFT:
|
||||
case C.AKEYCODE_DPAD_RIGHT:
|
||||
case C.AKEYCODE_DPAD_CENTER:
|
||||
case C.AKEYCODE_VOLUME_UP:
|
||||
return key.CodeVolumeUp
|
||||
case C.AKEYCODE_VOLUME_DOWN:
|
||||
return key.CodeVolumeDown
|
||||
case C.AKEYCODE_POWER:
|
||||
case C.AKEYCODE_CAMERA:
|
||||
case C.AKEYCODE_CLEAR:
|
||||
case C.AKEYCODE_A:
|
||||
return key.CodeA
|
||||
case C.AKEYCODE_B:
|
||||
return key.CodeB
|
||||
case C.AKEYCODE_C:
|
||||
return key.CodeC
|
||||
case C.AKEYCODE_D:
|
||||
return key.CodeD
|
||||
case C.AKEYCODE_E:
|
||||
return key.CodeE
|
||||
case C.AKEYCODE_F:
|
||||
return key.CodeF
|
||||
case C.AKEYCODE_G:
|
||||
return key.CodeG
|
||||
case C.AKEYCODE_H:
|
||||
return key.CodeH
|
||||
case C.AKEYCODE_I:
|
||||
return key.CodeI
|
||||
case C.AKEYCODE_J:
|
||||
return key.CodeJ
|
||||
case C.AKEYCODE_K:
|
||||
return key.CodeK
|
||||
case C.AKEYCODE_L:
|
||||
return key.CodeL
|
||||
case C.AKEYCODE_M:
|
||||
return key.CodeM
|
||||
case C.AKEYCODE_N:
|
||||
return key.CodeN
|
||||
case C.AKEYCODE_O:
|
||||
return key.CodeO
|
||||
case C.AKEYCODE_P:
|
||||
return key.CodeP
|
||||
case C.AKEYCODE_Q:
|
||||
return key.CodeQ
|
||||
case C.AKEYCODE_R:
|
||||
return key.CodeR
|
||||
case C.AKEYCODE_S:
|
||||
return key.CodeS
|
||||
case C.AKEYCODE_T:
|
||||
return key.CodeT
|
||||
case C.AKEYCODE_U:
|
||||
return key.CodeU
|
||||
case C.AKEYCODE_V:
|
||||
return key.CodeV
|
||||
case C.AKEYCODE_W:
|
||||
return key.CodeW
|
||||
case C.AKEYCODE_X:
|
||||
return key.CodeX
|
||||
case C.AKEYCODE_Y:
|
||||
return key.CodeY
|
||||
case C.AKEYCODE_Z:
|
||||
return key.CodeZ
|
||||
case C.AKEYCODE_COMMA:
|
||||
return key.CodeComma
|
||||
case C.AKEYCODE_PERIOD:
|
||||
return key.CodeFullStop
|
||||
case C.AKEYCODE_ALT_LEFT:
|
||||
return key.CodeLeftAlt
|
||||
case C.AKEYCODE_ALT_RIGHT:
|
||||
return key.CodeRightAlt
|
||||
case C.AKEYCODE_SHIFT_LEFT:
|
||||
return key.CodeLeftShift
|
||||
case C.AKEYCODE_SHIFT_RIGHT:
|
||||
return key.CodeRightShift
|
||||
case C.AKEYCODE_TAB:
|
||||
return key.CodeTab
|
||||
case C.AKEYCODE_SPACE:
|
||||
return key.CodeSpacebar
|
||||
case C.AKEYCODE_SYM:
|
||||
case C.AKEYCODE_EXPLORER:
|
||||
case C.AKEYCODE_ENVELOPE:
|
||||
case C.AKEYCODE_ENTER:
|
||||
return key.CodeReturnEnter
|
||||
case C.AKEYCODE_DEL:
|
||||
return key.CodeDeleteBackspace
|
||||
case C.AKEYCODE_GRAVE:
|
||||
return key.CodeGraveAccent
|
||||
case C.AKEYCODE_MINUS:
|
||||
return key.CodeHyphenMinus
|
||||
case C.AKEYCODE_EQUALS:
|
||||
return key.CodeEqualSign
|
||||
case C.AKEYCODE_LEFT_BRACKET:
|
||||
return key.CodeLeftSquareBracket
|
||||
case C.AKEYCODE_RIGHT_BRACKET:
|
||||
return key.CodeRightSquareBracket
|
||||
case C.AKEYCODE_BACKSLASH:
|
||||
return key.CodeBackslash
|
||||
case C.AKEYCODE_SEMICOLON:
|
||||
return key.CodeSemicolon
|
||||
case C.AKEYCODE_APOSTROPHE:
|
||||
return key.CodeApostrophe
|
||||
case C.AKEYCODE_SLASH:
|
||||
return key.CodeSlash
|
||||
case C.AKEYCODE_AT:
|
||||
case C.AKEYCODE_NUM:
|
||||
case C.AKEYCODE_HEADSETHOOK:
|
||||
case C.AKEYCODE_FOCUS:
|
||||
case C.AKEYCODE_PLUS:
|
||||
case C.AKEYCODE_MENU:
|
||||
case C.AKEYCODE_NOTIFICATION:
|
||||
case C.AKEYCODE_SEARCH:
|
||||
case C.AKEYCODE_MEDIA_PLAY_PAUSE:
|
||||
case C.AKEYCODE_MEDIA_STOP:
|
||||
case C.AKEYCODE_MEDIA_NEXT:
|
||||
case C.AKEYCODE_MEDIA_PREVIOUS:
|
||||
case C.AKEYCODE_MEDIA_REWIND:
|
||||
case C.AKEYCODE_MEDIA_FAST_FORWARD:
|
||||
case C.AKEYCODE_MUTE:
|
||||
case C.AKEYCODE_PAGE_UP:
|
||||
return key.CodePageUp
|
||||
case C.AKEYCODE_PAGE_DOWN:
|
||||
return key.CodePageDown
|
||||
case C.AKEYCODE_PICTSYMBOLS:
|
||||
case C.AKEYCODE_SWITCH_CHARSET:
|
||||
case C.AKEYCODE_BUTTON_A:
|
||||
case C.AKEYCODE_BUTTON_B:
|
||||
case C.AKEYCODE_BUTTON_C:
|
||||
case C.AKEYCODE_BUTTON_X:
|
||||
case C.AKEYCODE_BUTTON_Y:
|
||||
case C.AKEYCODE_BUTTON_Z:
|
||||
case C.AKEYCODE_BUTTON_L1:
|
||||
case C.AKEYCODE_BUTTON_R1:
|
||||
case C.AKEYCODE_BUTTON_L2:
|
||||
case C.AKEYCODE_BUTTON_R2:
|
||||
case C.AKEYCODE_BUTTON_THUMBL:
|
||||
case C.AKEYCODE_BUTTON_THUMBR:
|
||||
case C.AKEYCODE_BUTTON_START:
|
||||
case C.AKEYCODE_BUTTON_SELECT:
|
||||
case C.AKEYCODE_BUTTON_MODE:
|
||||
case C.AKEYCODE_ESCAPE:
|
||||
return key.CodeEscape
|
||||
case C.AKEYCODE_FORWARD_DEL:
|
||||
return key.CodeDeleteForward
|
||||
case C.AKEYCODE_CTRL_LEFT:
|
||||
return key.CodeLeftControl
|
||||
case C.AKEYCODE_CTRL_RIGHT:
|
||||
return key.CodeRightControl
|
||||
case C.AKEYCODE_CAPS_LOCK:
|
||||
return key.CodeCapsLock
|
||||
case C.AKEYCODE_SCROLL_LOCK:
|
||||
case C.AKEYCODE_META_LEFT:
|
||||
return key.CodeLeftGUI
|
||||
case C.AKEYCODE_META_RIGHT:
|
||||
return key.CodeRightGUI
|
||||
case C.AKEYCODE_FUNCTION:
|
||||
case C.AKEYCODE_SYSRQ:
|
||||
case C.AKEYCODE_BREAK:
|
||||
case C.AKEYCODE_MOVE_HOME:
|
||||
case C.AKEYCODE_MOVE_END:
|
||||
case C.AKEYCODE_INSERT:
|
||||
return key.CodeInsert
|
||||
case C.AKEYCODE_FORWARD:
|
||||
case C.AKEYCODE_MEDIA_PLAY:
|
||||
case C.AKEYCODE_MEDIA_PAUSE:
|
||||
case C.AKEYCODE_MEDIA_CLOSE:
|
||||
case C.AKEYCODE_MEDIA_EJECT:
|
||||
case C.AKEYCODE_MEDIA_RECORD:
|
||||
case C.AKEYCODE_F1:
|
||||
return key.CodeF1
|
||||
case C.AKEYCODE_F2:
|
||||
return key.CodeF2
|
||||
case C.AKEYCODE_F3:
|
||||
return key.CodeF3
|
||||
case C.AKEYCODE_F4:
|
||||
return key.CodeF4
|
||||
case C.AKEYCODE_F5:
|
||||
return key.CodeF5
|
||||
case C.AKEYCODE_F6:
|
||||
return key.CodeF6
|
||||
case C.AKEYCODE_F7:
|
||||
return key.CodeF7
|
||||
case C.AKEYCODE_F8:
|
||||
return key.CodeF8
|
||||
case C.AKEYCODE_F9:
|
||||
return key.CodeF9
|
||||
case C.AKEYCODE_F10:
|
||||
return key.CodeF10
|
||||
case C.AKEYCODE_F11:
|
||||
return key.CodeF11
|
||||
case C.AKEYCODE_F12:
|
||||
return key.CodeF12
|
||||
case C.AKEYCODE_NUM_LOCK:
|
||||
return key.CodeKeypadNumLock
|
||||
case C.AKEYCODE_NUMPAD_0:
|
||||
return key.CodeKeypad0
|
||||
case C.AKEYCODE_NUMPAD_1:
|
||||
return key.CodeKeypad1
|
||||
case C.AKEYCODE_NUMPAD_2:
|
||||
return key.CodeKeypad2
|
||||
case C.AKEYCODE_NUMPAD_3:
|
||||
return key.CodeKeypad3
|
||||
case C.AKEYCODE_NUMPAD_4:
|
||||
return key.CodeKeypad4
|
||||
case C.AKEYCODE_NUMPAD_5:
|
||||
return key.CodeKeypad5
|
||||
case C.AKEYCODE_NUMPAD_6:
|
||||
return key.CodeKeypad6
|
||||
case C.AKEYCODE_NUMPAD_7:
|
||||
return key.CodeKeypad7
|
||||
case C.AKEYCODE_NUMPAD_8:
|
||||
return key.CodeKeypad8
|
||||
case C.AKEYCODE_NUMPAD_9:
|
||||
return key.CodeKeypad9
|
||||
case C.AKEYCODE_NUMPAD_DIVIDE:
|
||||
return key.CodeKeypadSlash
|
||||
case C.AKEYCODE_NUMPAD_MULTIPLY:
|
||||
return key.CodeKeypadAsterisk
|
||||
case C.AKEYCODE_NUMPAD_SUBTRACT:
|
||||
return key.CodeKeypadHyphenMinus
|
||||
case C.AKEYCODE_NUMPAD_ADD:
|
||||
return key.CodeKeypadPlusSign
|
||||
case C.AKEYCODE_NUMPAD_DOT:
|
||||
return key.CodeKeypadFullStop
|
||||
case C.AKEYCODE_NUMPAD_COMMA:
|
||||
case C.AKEYCODE_NUMPAD_ENTER:
|
||||
return key.CodeKeypadEnter
|
||||
case C.AKEYCODE_NUMPAD_EQUALS:
|
||||
return key.CodeKeypadEqualSign
|
||||
case C.AKEYCODE_NUMPAD_LEFT_PAREN:
|
||||
case C.AKEYCODE_NUMPAD_RIGHT_PAREN:
|
||||
case C.AKEYCODE_VOLUME_MUTE:
|
||||
return key.CodeMute
|
||||
case C.AKEYCODE_INFO:
|
||||
case C.AKEYCODE_CHANNEL_UP:
|
||||
case C.AKEYCODE_CHANNEL_DOWN:
|
||||
case C.AKEYCODE_ZOOM_IN:
|
||||
case C.AKEYCODE_ZOOM_OUT:
|
||||
case C.AKEYCODE_TV:
|
||||
case C.AKEYCODE_WINDOW:
|
||||
case C.AKEYCODE_GUIDE:
|
||||
case C.AKEYCODE_DVR:
|
||||
case C.AKEYCODE_BOOKMARK:
|
||||
case C.AKEYCODE_CAPTIONS:
|
||||
case C.AKEYCODE_SETTINGS:
|
||||
case C.AKEYCODE_TV_POWER:
|
||||
case C.AKEYCODE_TV_INPUT:
|
||||
case C.AKEYCODE_STB_POWER:
|
||||
case C.AKEYCODE_STB_INPUT:
|
||||
case C.AKEYCODE_AVR_POWER:
|
||||
case C.AKEYCODE_AVR_INPUT:
|
||||
case C.AKEYCODE_PROG_RED:
|
||||
case C.AKEYCODE_PROG_GREEN:
|
||||
case C.AKEYCODE_PROG_YELLOW:
|
||||
case C.AKEYCODE_PROG_BLUE:
|
||||
case C.AKEYCODE_APP_SWITCH:
|
||||
case C.AKEYCODE_BUTTON_1:
|
||||
case C.AKEYCODE_BUTTON_2:
|
||||
case C.AKEYCODE_BUTTON_3:
|
||||
case C.AKEYCODE_BUTTON_4:
|
||||
case C.AKEYCODE_BUTTON_5:
|
||||
case C.AKEYCODE_BUTTON_6:
|
||||
case C.AKEYCODE_BUTTON_7:
|
||||
case C.AKEYCODE_BUTTON_8:
|
||||
case C.AKEYCODE_BUTTON_9:
|
||||
case C.AKEYCODE_BUTTON_10:
|
||||
case C.AKEYCODE_BUTTON_11:
|
||||
case C.AKEYCODE_BUTTON_12:
|
||||
case C.AKEYCODE_BUTTON_13:
|
||||
case C.AKEYCODE_BUTTON_14:
|
||||
case C.AKEYCODE_BUTTON_15:
|
||||
case C.AKEYCODE_BUTTON_16:
|
||||
case C.AKEYCODE_LANGUAGE_SWITCH:
|
||||
case C.AKEYCODE_MANNER_MODE:
|
||||
case C.AKEYCODE_3D_MODE:
|
||||
case C.AKEYCODE_CONTACTS:
|
||||
case C.AKEYCODE_CALENDAR:
|
||||
case C.AKEYCODE_MUSIC:
|
||||
case C.AKEYCODE_CALCULATOR:
|
||||
}
|
||||
/* Defined in an NDK API version beyond what we use today:
|
||||
C.AKEYCODE_ASSIST
|
||||
C.AKEYCODE_BRIGHTNESS_DOWN
|
||||
C.AKEYCODE_BRIGHTNESS_UP
|
||||
C.AKEYCODE_EISU
|
||||
C.AKEYCODE_HENKAN
|
||||
C.AKEYCODE_KANA
|
||||
C.AKEYCODE_KATAKANA_HIRAGANA
|
||||
C.AKEYCODE_MEDIA_AUDIO_TRACK
|
||||
C.AKEYCODE_MUHENKAN
|
||||
C.AKEYCODE_RO
|
||||
C.AKEYCODE_YEN
|
||||
C.AKEYCODE_ZENKAKU_HANKAKU
|
||||
*/
|
||||
return key.CodeUnknown
|
||||
}
|
@ -1,208 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux darwin windows
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"golang.org/x/mobile/event/lifecycle"
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/gl"
|
||||
_ "golang.org/x/mobile/internal/mobileinit"
|
||||
)
|
||||
|
||||
// Main is called by the main.main function to run the mobile application.
|
||||
//
|
||||
// It calls f on the App, in a separate goroutine, as some OS-specific
|
||||
// libraries require being on 'the main thread'.
|
||||
func Main(f func(App)) {
|
||||
main(f)
|
||||
}
|
||||
|
||||
// App is how a GUI mobile application interacts with the OS.
|
||||
type App interface {
|
||||
// Events returns the events channel. It carries events from the system to
|
||||
// the app. The type of such events include:
|
||||
// - lifecycle.Event
|
||||
// - mouse.Event
|
||||
// - paint.Event
|
||||
// - size.Event
|
||||
// - touch.Event
|
||||
// from the golang.org/x/mobile/event/etc packages. Other packages may
|
||||
// define other event types that are carried on this channel.
|
||||
Events() <-chan interface{}
|
||||
|
||||
// Send sends an event on the events channel. It does not block.
|
||||
Send(event interface{})
|
||||
|
||||
// Publish flushes any pending drawing commands, such as OpenGL calls, and
|
||||
// swaps the back buffer to the screen.
|
||||
Publish() PublishResult
|
||||
|
||||
// TODO: replace filters (and the Events channel) with a NextEvent method?
|
||||
|
||||
// Filter calls each registered event filter function in sequence.
|
||||
Filter(event interface{}) interface{}
|
||||
|
||||
// RegisterFilter registers a event filter function to be called by Filter. The
|
||||
// function can return a different event, or return nil to consume the event,
|
||||
// but the function can also return its argument unchanged, where its purpose
|
||||
// is to trigger a side effect rather than modify the event.
|
||||
RegisterFilter(f func(interface{}) interface{})
|
||||
}
|
||||
|
||||
// PublishResult is the result of an App.Publish call.
|
||||
type PublishResult struct {
|
||||
// BackBufferPreserved is whether the contents of the back buffer was
|
||||
// preserved. If false, the contents are undefined.
|
||||
BackBufferPreserved bool
|
||||
}
|
||||
|
||||
var theApp = &app{
|
||||
eventsOut: make(chan interface{}),
|
||||
lifecycleStage: lifecycle.StageDead,
|
||||
publish: make(chan struct{}),
|
||||
publishResult: make(chan PublishResult),
|
||||
}
|
||||
|
||||
func init() {
|
||||
theApp.eventsIn = pump(theApp.eventsOut)
|
||||
theApp.glctx, theApp.worker = gl.NewContext()
|
||||
}
|
||||
|
||||
func (a *app) sendLifecycle(to lifecycle.Stage) {
|
||||
if a.lifecycleStage == to {
|
||||
return
|
||||
}
|
||||
a.eventsIn <- lifecycle.Event{
|
||||
From: a.lifecycleStage,
|
||||
To: to,
|
||||
DrawContext: a.glctx,
|
||||
}
|
||||
a.lifecycleStage = to
|
||||
}
|
||||
|
||||
type app struct {
|
||||
filters []func(interface{}) interface{}
|
||||
|
||||
eventsOut chan interface{}
|
||||
eventsIn chan interface{}
|
||||
lifecycleStage lifecycle.Stage
|
||||
publish chan struct{}
|
||||
publishResult chan PublishResult
|
||||
|
||||
glctx gl.Context
|
||||
worker gl.Worker
|
||||
}
|
||||
|
||||
func (a *app) Events() <-chan interface{} {
|
||||
return a.eventsOut
|
||||
}
|
||||
|
||||
func (a *app) Send(event interface{}) {
|
||||
a.eventsIn <- event
|
||||
}
|
||||
|
||||
func (a *app) Publish() PublishResult {
|
||||
// gl.Flush is a lightweight (on modern GL drivers) blocking call
|
||||
// that ensures all GL functions pending in the gl package have
|
||||
// been passed onto the GL driver before the app package attempts
|
||||
// to swap the screen buffer.
|
||||
//
|
||||
// This enforces that the final receive (for this paint cycle) on
|
||||
// gl.WorkAvailable happens before the send on endPaint.
|
||||
a.glctx.Flush()
|
||||
a.publish <- struct{}{}
|
||||
return <-a.publishResult
|
||||
}
|
||||
|
||||
func (a *app) Filter(event interface{}) interface{} {
|
||||
for _, f := range a.filters {
|
||||
event = f(event)
|
||||
}
|
||||
return event
|
||||
}
|
||||
|
||||
func (a *app) RegisterFilter(f func(interface{}) interface{}) {
|
||||
a.filters = append(a.filters, f)
|
||||
}
|
||||
|
||||
type stopPumping struct{}
|
||||
|
||||
// pump returns a channel src such that sending on src will eventually send on
|
||||
// dst, in order, but that src will always be ready to send/receive soon, even
|
||||
// if dst currently isn't. It is effectively an infinitely buffered channel.
|
||||
//
|
||||
// In particular, goroutine A sending on src will not deadlock even if goroutine
|
||||
// B that's responsible for receiving on dst is currently blocked trying to
|
||||
// send to A on a separate channel.
|
||||
//
|
||||
// Send a stopPumping on the src channel to close the dst channel after all queued
|
||||
// events are sent on dst. After that, other goroutines can still send to src,
|
||||
// so that such sends won't block forever, but such events will be ignored.
|
||||
func pump(dst chan interface{}) (src chan interface{}) {
|
||||
src = make(chan interface{})
|
||||
go func() {
|
||||
// initialSize is the initial size of the circular buffer. It must be a
|
||||
// power of 2.
|
||||
const initialSize = 16
|
||||
i, j, buf, mask := 0, 0, make([]interface{}, initialSize), initialSize-1
|
||||
|
||||
maybeSrc := src
|
||||
for {
|
||||
maybeDst := dst
|
||||
if i == j {
|
||||
maybeDst = nil
|
||||
}
|
||||
if maybeDst == nil && maybeSrc == nil {
|
||||
break
|
||||
}
|
||||
|
||||
select {
|
||||
case maybeDst <- buf[i&mask]:
|
||||
buf[i&mask] = nil
|
||||
i++
|
||||
|
||||
case e := <-maybeSrc:
|
||||
if _, ok := e.(stopPumping); ok {
|
||||
maybeSrc = nil
|
||||
continue
|
||||
}
|
||||
|
||||
// Allocate a bigger buffer if necessary.
|
||||
if i+len(buf) == j {
|
||||
b := make([]interface{}, 2*len(buf))
|
||||
n := copy(b, buf[j&mask:])
|
||||
copy(b[n:], buf[:j&mask])
|
||||
i, j = 0, len(buf)
|
||||
buf, mask = b, len(b)-1
|
||||
}
|
||||
|
||||
buf[j&mask] = e
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
close(dst)
|
||||
// Block forever.
|
||||
for range src {
|
||||
}
|
||||
}()
|
||||
return src
|
||||
}
|
||||
|
||||
// TODO: do this for all build targets, not just linux (x11 and Android)? If
|
||||
// so, should package gl instead of this package call RegisterFilter??
|
||||
//
|
||||
// TODO: does Android need this?? It seems to work without it (Nexus 7,
|
||||
// KitKat). If only x11 needs this, should we move this to x11.go??
|
||||
func (a *app) registerGLViewportFilter() {
|
||||
a.RegisterFilter(func(e interface{}) interface{} {
|
||||
if e, ok := e.(size.Event); ok {
|
||||
a.glctx.Viewport(0, 0, e.WidthPx, e.HeightPx)
|
||||
}
|
||||
return e
|
||||
})
|
||||
}
|
@ -1,496 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin
|
||||
// +build !ios
|
||||
|
||||
package app
|
||||
|
||||
// Simple on-screen app debugging for OS X. Not an officially supported
|
||||
// development target for apps, as screens with mice are very different
|
||||
// than screens with touch panels.
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Cocoa -framework OpenGL
|
||||
#import <Carbon/Carbon.h> // for HIToolbox/Events.h
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <pthread.h>
|
||||
|
||||
void runApp(void);
|
||||
void stopApp(void);
|
||||
void makeCurrentContext(GLintptr);
|
||||
uint64 threadID();
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/mobile/event/key"
|
||||
"golang.org/x/mobile/event/lifecycle"
|
||||
"golang.org/x/mobile/event/paint"
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/event/touch"
|
||||
"golang.org/x/mobile/geom"
|
||||
)
|
||||
|
||||
var initThreadID uint64
|
||||
|
||||
func init() {
|
||||
// Lock the goroutine responsible for initialization to an OS thread.
|
||||
// This means the goroutine running main (and calling runApp below)
|
||||
// is locked to the OS thread that started the program. This is
|
||||
// necessary for the correct delivery of Cocoa events to the process.
|
||||
//
|
||||
// A discussion on this topic:
|
||||
// https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ
|
||||
runtime.LockOSThread()
|
||||
initThreadID = uint64(C.threadID())
|
||||
}
|
||||
|
||||
func main(f func(App)) {
|
||||
if tid := uint64(C.threadID()); tid != initThreadID {
|
||||
log.Fatalf("app.Main called on thread %d, but app.init ran on %d", tid, initThreadID)
|
||||
}
|
||||
|
||||
go func() {
|
||||
f(theApp)
|
||||
C.stopApp()
|
||||
// TODO(crawshaw): trigger runApp to return
|
||||
}()
|
||||
|
||||
C.runApp()
|
||||
}
|
||||
|
||||
// loop is the primary drawing loop.
|
||||
//
|
||||
// After Cocoa has captured the initial OS thread for processing Cocoa
|
||||
// events in runApp, it starts loop on another goroutine. It is locked
|
||||
// to an OS thread for its OpenGL context.
|
||||
//
|
||||
// The loop processes GL calls until a publish event appears.
|
||||
// Then it runs any remaining GL calls and flushes the screen.
|
||||
//
|
||||
// As NSOpenGLCPSwapInterval is set to 1, the call to CGLFlushDrawable
|
||||
// blocks until the screen refresh.
|
||||
func (a *app) loop(ctx C.GLintptr) {
|
||||
runtime.LockOSThread()
|
||||
C.makeCurrentContext(ctx)
|
||||
|
||||
workAvailable := a.worker.WorkAvailable()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-workAvailable:
|
||||
a.worker.DoWork()
|
||||
case <-theApp.publish:
|
||||
loop1:
|
||||
for {
|
||||
select {
|
||||
case <-workAvailable:
|
||||
a.worker.DoWork()
|
||||
default:
|
||||
break loop1
|
||||
}
|
||||
}
|
||||
C.CGLFlushDrawable(C.CGLGetCurrentContext())
|
||||
theApp.publishResult <- PublishResult{}
|
||||
select {
|
||||
case drawDone <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var drawDone = make(chan struct{})
|
||||
|
||||
// drawgl is used by Cocoa to occasionally request screen updates.
|
||||
//
|
||||
//export drawgl
|
||||
func drawgl() {
|
||||
switch theApp.lifecycleStage {
|
||||
case lifecycle.StageFocused, lifecycle.StageVisible:
|
||||
theApp.Send(paint.Event{
|
||||
External: true,
|
||||
})
|
||||
<-drawDone
|
||||
}
|
||||
}
|
||||
|
||||
//export startloop
|
||||
func startloop(ctx C.GLintptr) {
|
||||
go theApp.loop(ctx)
|
||||
}
|
||||
|
||||
var windowHeightPx float32
|
||||
|
||||
//export setGeom
|
||||
func setGeom(pixelsPerPt float32, widthPx, heightPx int) {
|
||||
windowHeightPx = float32(heightPx)
|
||||
theApp.eventsIn <- size.Event{
|
||||
WidthPx: widthPx,
|
||||
HeightPx: heightPx,
|
||||
WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt),
|
||||
HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt),
|
||||
PixelsPerPt: pixelsPerPt,
|
||||
}
|
||||
}
|
||||
|
||||
var touchEvents struct {
|
||||
sync.Mutex
|
||||
pending []touch.Event
|
||||
}
|
||||
|
||||
func sendTouch(t touch.Type, x, y float32) {
|
||||
theApp.eventsIn <- touch.Event{
|
||||
X: x,
|
||||
Y: windowHeightPx - y,
|
||||
Sequence: 0,
|
||||
Type: t,
|
||||
}
|
||||
}
|
||||
|
||||
//export eventMouseDown
|
||||
func eventMouseDown(x, y float32) { sendTouch(touch.TypeBegin, x, y) }
|
||||
|
||||
//export eventMouseDragged
|
||||
func eventMouseDragged(x, y float32) { sendTouch(touch.TypeMove, x, y) }
|
||||
|
||||
//export eventMouseEnd
|
||||
func eventMouseEnd(x, y float32) { sendTouch(touch.TypeEnd, x, y) }
|
||||
|
||||
//export lifecycleDead
|
||||
func lifecycleDead() { theApp.sendLifecycle(lifecycle.StageDead) }
|
||||
|
||||
//export eventKey
|
||||
func eventKey(runeVal int32, direction uint8, code uint16, flags uint32) {
|
||||
var modifiers key.Modifiers
|
||||
for _, mod := range mods {
|
||||
if flags&mod.flags == mod.flags {
|
||||
modifiers |= mod.mod
|
||||
}
|
||||
}
|
||||
|
||||
theApp.eventsIn <- key.Event{
|
||||
Rune: convRune(rune(runeVal)),
|
||||
Code: convVirtualKeyCode(code),
|
||||
Modifiers: modifiers,
|
||||
Direction: key.Direction(direction),
|
||||
}
|
||||
}
|
||||
|
||||
//export eventFlags
|
||||
func eventFlags(flags uint32) {
|
||||
for _, mod := range mods {
|
||||
if flags&mod.flags == mod.flags && lastFlags&mod.flags != mod.flags {
|
||||
eventKey(-1, uint8(key.DirPress), mod.code, flags)
|
||||
}
|
||||
if lastFlags&mod.flags == mod.flags && flags&mod.flags != mod.flags {
|
||||
eventKey(-1, uint8(key.DirRelease), mod.code, flags)
|
||||
}
|
||||
}
|
||||
lastFlags = flags
|
||||
}
|
||||
|
||||
var lastFlags uint32
|
||||
|
||||
var mods = [...]struct {
|
||||
flags uint32
|
||||
code uint16
|
||||
mod key.Modifiers
|
||||
}{
|
||||
// Left and right variants of modifier keys have their own masks,
|
||||
// but they are not documented. These were determined empirically.
|
||||
{1<<17 | 0x102, C.kVK_Shift, key.ModShift},
|
||||
{1<<17 | 0x104, C.kVK_RightShift, key.ModShift},
|
||||
{1<<18 | 0x101, C.kVK_Control, key.ModControl},
|
||||
// TODO key.ControlRight
|
||||
{1<<19 | 0x120, C.kVK_Option, key.ModAlt},
|
||||
{1<<19 | 0x140, C.kVK_RightOption, key.ModAlt},
|
||||
{1<<20 | 0x108, C.kVK_Command, key.ModMeta},
|
||||
{1<<20 | 0x110, C.kVK_Command, key.ModMeta}, // TODO: missing kVK_RightCommand
|
||||
}
|
||||
|
||||
//export lifecycleAlive
|
||||
func lifecycleAlive() { theApp.sendLifecycle(lifecycle.StageAlive) }
|
||||
|
||||
//export lifecycleVisible
|
||||
func lifecycleVisible() {
|
||||
theApp.sendLifecycle(lifecycle.StageVisible)
|
||||
}
|
||||
|
||||
//export lifecycleFocused
|
||||
func lifecycleFocused() { theApp.sendLifecycle(lifecycle.StageFocused) }
|
||||
|
||||
// convRune marks the Carbon/Cocoa private-range unicode rune representing
|
||||
// a non-unicode key event to -1, used for Rune in the key package.
|
||||
//
|
||||
// http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT
|
||||
func convRune(r rune) rune {
|
||||
if '\uE000' <= r && r <= '\uF8FF' {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// convVirtualKeyCode converts a Carbon/Cocoa virtual key code number
|
||||
// into the standard keycodes used by the key package.
|
||||
//
|
||||
// To get a sense of the key map, see the diagram on
|
||||
// http://boredzo.org/blog/archives/2007-05-22/virtual-key-codes
|
||||
func convVirtualKeyCode(vkcode uint16) key.Code {
|
||||
switch vkcode {
|
||||
case C.kVK_ANSI_A:
|
||||
return key.CodeA
|
||||
case C.kVK_ANSI_B:
|
||||
return key.CodeB
|
||||
case C.kVK_ANSI_C:
|
||||
return key.CodeC
|
||||
case C.kVK_ANSI_D:
|
||||
return key.CodeD
|
||||
case C.kVK_ANSI_E:
|
||||
return key.CodeE
|
||||
case C.kVK_ANSI_F:
|
||||
return key.CodeF
|
||||
case C.kVK_ANSI_G:
|
||||
return key.CodeG
|
||||
case C.kVK_ANSI_H:
|
||||
return key.CodeH
|
||||
case C.kVK_ANSI_I:
|
||||
return key.CodeI
|
||||
case C.kVK_ANSI_J:
|
||||
return key.CodeJ
|
||||
case C.kVK_ANSI_K:
|
||||
return key.CodeK
|
||||
case C.kVK_ANSI_L:
|
||||
return key.CodeL
|
||||
case C.kVK_ANSI_M:
|
||||
return key.CodeM
|
||||
case C.kVK_ANSI_N:
|
||||
return key.CodeN
|
||||
case C.kVK_ANSI_O:
|
||||
return key.CodeO
|
||||
case C.kVK_ANSI_P:
|
||||
return key.CodeP
|
||||
case C.kVK_ANSI_Q:
|
||||
return key.CodeQ
|
||||
case C.kVK_ANSI_R:
|
||||
return key.CodeR
|
||||
case C.kVK_ANSI_S:
|
||||
return key.CodeS
|
||||
case C.kVK_ANSI_T:
|
||||
return key.CodeT
|
||||
case C.kVK_ANSI_U:
|
||||
return key.CodeU
|
||||
case C.kVK_ANSI_V:
|
||||
return key.CodeV
|
||||
case C.kVK_ANSI_W:
|
||||
return key.CodeW
|
||||
case C.kVK_ANSI_X:
|
||||
return key.CodeX
|
||||
case C.kVK_ANSI_Y:
|
||||
return key.CodeY
|
||||
case C.kVK_ANSI_Z:
|
||||
return key.CodeZ
|
||||
case C.kVK_ANSI_1:
|
||||
return key.Code1
|
||||
case C.kVK_ANSI_2:
|
||||
return key.Code2
|
||||
case C.kVK_ANSI_3:
|
||||
return key.Code3
|
||||
case C.kVK_ANSI_4:
|
||||
return key.Code4
|
||||
case C.kVK_ANSI_5:
|
||||
return key.Code5
|
||||
case C.kVK_ANSI_6:
|
||||
return key.Code6
|
||||
case C.kVK_ANSI_7:
|
||||
return key.Code7
|
||||
case C.kVK_ANSI_8:
|
||||
return key.Code8
|
||||
case C.kVK_ANSI_9:
|
||||
return key.Code9
|
||||
case C.kVK_ANSI_0:
|
||||
return key.Code0
|
||||
// TODO: move the rest of these codes to constants in key.go
|
||||
// if we are happy with them.
|
||||
case C.kVK_Return:
|
||||
return key.CodeReturnEnter
|
||||
case C.kVK_Escape:
|
||||
return key.CodeEscape
|
||||
case C.kVK_Delete:
|
||||
return key.CodeDeleteBackspace
|
||||
case C.kVK_Tab:
|
||||
return key.CodeTab
|
||||
case C.kVK_Space:
|
||||
return key.CodeSpacebar
|
||||
case C.kVK_ANSI_Minus:
|
||||
return key.CodeHyphenMinus
|
||||
case C.kVK_ANSI_Equal:
|
||||
return key.CodeEqualSign
|
||||
case C.kVK_ANSI_LeftBracket:
|
||||
return key.CodeLeftSquareBracket
|
||||
case C.kVK_ANSI_RightBracket:
|
||||
return key.CodeRightSquareBracket
|
||||
case C.kVK_ANSI_Backslash:
|
||||
return key.CodeBackslash
|
||||
// 50: Keyboard Non-US "#" and ~
|
||||
case C.kVK_ANSI_Semicolon:
|
||||
return key.CodeSemicolon
|
||||
case C.kVK_ANSI_Quote:
|
||||
return key.CodeApostrophe
|
||||
case C.kVK_ANSI_Grave:
|
||||
return key.CodeGraveAccent
|
||||
case C.kVK_ANSI_Comma:
|
||||
return key.CodeComma
|
||||
case C.kVK_ANSI_Period:
|
||||
return key.CodeFullStop
|
||||
case C.kVK_ANSI_Slash:
|
||||
return key.CodeSlash
|
||||
case C.kVK_CapsLock:
|
||||
return key.CodeCapsLock
|
||||
case C.kVK_F1:
|
||||
return key.CodeF1
|
||||
case C.kVK_F2:
|
||||
return key.CodeF2
|
||||
case C.kVK_F3:
|
||||
return key.CodeF3
|
||||
case C.kVK_F4:
|
||||
return key.CodeF4
|
||||
case C.kVK_F5:
|
||||
return key.CodeF5
|
||||
case C.kVK_F6:
|
||||
return key.CodeF6
|
||||
case C.kVK_F7:
|
||||
return key.CodeF7
|
||||
case C.kVK_F8:
|
||||
return key.CodeF8
|
||||
case C.kVK_F9:
|
||||
return key.CodeF9
|
||||
case C.kVK_F10:
|
||||
return key.CodeF10
|
||||
case C.kVK_F11:
|
||||
return key.CodeF11
|
||||
case C.kVK_F12:
|
||||
return key.CodeF12
|
||||
// 70: PrintScreen
|
||||
// 71: Scroll Lock
|
||||
// 72: Pause
|
||||
// 73: Insert
|
||||
case C.kVK_Home:
|
||||
return key.CodeHome
|
||||
case C.kVK_PageUp:
|
||||
return key.CodePageUp
|
||||
case C.kVK_ForwardDelete:
|
||||
return key.CodeDeleteForward
|
||||
case C.kVK_End:
|
||||
return key.CodeEnd
|
||||
case C.kVK_PageDown:
|
||||
return key.CodePageDown
|
||||
case C.kVK_RightArrow:
|
||||
return key.CodeRightArrow
|
||||
case C.kVK_LeftArrow:
|
||||
return key.CodeLeftArrow
|
||||
case C.kVK_DownArrow:
|
||||
return key.CodeDownArrow
|
||||
case C.kVK_UpArrow:
|
||||
return key.CodeUpArrow
|
||||
case C.kVK_ANSI_KeypadClear:
|
||||
return key.CodeKeypadNumLock
|
||||
case C.kVK_ANSI_KeypadDivide:
|
||||
return key.CodeKeypadSlash
|
||||
case C.kVK_ANSI_KeypadMultiply:
|
||||
return key.CodeKeypadAsterisk
|
||||
case C.kVK_ANSI_KeypadMinus:
|
||||
return key.CodeKeypadHyphenMinus
|
||||
case C.kVK_ANSI_KeypadPlus:
|
||||
return key.CodeKeypadPlusSign
|
||||
case C.kVK_ANSI_KeypadEnter:
|
||||
return key.CodeKeypadEnter
|
||||
case C.kVK_ANSI_Keypad1:
|
||||
return key.CodeKeypad1
|
||||
case C.kVK_ANSI_Keypad2:
|
||||
return key.CodeKeypad2
|
||||
case C.kVK_ANSI_Keypad3:
|
||||
return key.CodeKeypad3
|
||||
case C.kVK_ANSI_Keypad4:
|
||||
return key.CodeKeypad4
|
||||
case C.kVK_ANSI_Keypad5:
|
||||
return key.CodeKeypad5
|
||||
case C.kVK_ANSI_Keypad6:
|
||||
return key.CodeKeypad6
|
||||
case C.kVK_ANSI_Keypad7:
|
||||
return key.CodeKeypad7
|
||||
case C.kVK_ANSI_Keypad8:
|
||||
return key.CodeKeypad8
|
||||
case C.kVK_ANSI_Keypad9:
|
||||
return key.CodeKeypad9
|
||||
case C.kVK_ANSI_Keypad0:
|
||||
return key.CodeKeypad0
|
||||
case C.kVK_ANSI_KeypadDecimal:
|
||||
return key.CodeKeypadFullStop
|
||||
case C.kVK_ANSI_KeypadEquals:
|
||||
return key.CodeKeypadEqualSign
|
||||
case C.kVK_F13:
|
||||
return key.CodeF13
|
||||
case C.kVK_F14:
|
||||
return key.CodeF14
|
||||
case C.kVK_F15:
|
||||
return key.CodeF15
|
||||
case C.kVK_F16:
|
||||
return key.CodeF16
|
||||
case C.kVK_F17:
|
||||
return key.CodeF17
|
||||
case C.kVK_F18:
|
||||
return key.CodeF18
|
||||
case C.kVK_F19:
|
||||
return key.CodeF19
|
||||
case C.kVK_F20:
|
||||
return key.CodeF20
|
||||
// 116: Keyboard Execute
|
||||
case C.kVK_Help:
|
||||
return key.CodeHelp
|
||||
// 118: Keyboard Menu
|
||||
// 119: Keyboard Select
|
||||
// 120: Keyboard Stop
|
||||
// 121: Keyboard Again
|
||||
// 122: Keyboard Undo
|
||||
// 123: Keyboard Cut
|
||||
// 124: Keyboard Copy
|
||||
// 125: Keyboard Paste
|
||||
// 126: Keyboard Find
|
||||
case C.kVK_Mute:
|
||||
return key.CodeMute
|
||||
case C.kVK_VolumeUp:
|
||||
return key.CodeVolumeUp
|
||||
case C.kVK_VolumeDown:
|
||||
return key.CodeVolumeDown
|
||||
// 130: Keyboard Locking Caps Lock
|
||||
// 131: Keyboard Locking Num Lock
|
||||
// 132: Keyboard Locking Scroll Lock
|
||||
// 133: Keyboard Comma
|
||||
// 134: Keyboard Equal Sign
|
||||
// ...: Bunch of stuff
|
||||
case C.kVK_Control:
|
||||
return key.CodeLeftControl
|
||||
case C.kVK_Shift:
|
||||
return key.CodeLeftShift
|
||||
case C.kVK_Option:
|
||||
return key.CodeLeftAlt
|
||||
case C.kVK_Command:
|
||||
return key.CodeLeftGUI
|
||||
case C.kVK_RightControl:
|
||||
return key.CodeRightControl
|
||||
case C.kVK_RightShift:
|
||||
return key.CodeRightShift
|
||||
case C.kVK_RightOption:
|
||||
return key.CodeRightAlt
|
||||
// TODO key.CodeRightGUI
|
||||
default:
|
||||
return key.CodeUnknown
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue