Undo notification sound

pull/94/head
Miguel Mota 3 years ago
parent c36f3de781
commit c6744f173d

@ -276,7 +276,7 @@ func (ct *Cointop) configToToml() ([]byte, error) {
}
priceAlertsMapIfc := map[string]interface{}{
"alerts": priceAlertsIfc,
"sound": ct.State.priceAlerts.SoundEnabled,
//"sound": ct.State.priceAlerts.SoundEnabled,
}
var coinsTableColumnsIfc interface{} = ct.State.coinsTableColumns

@ -182,7 +182,7 @@ func (ct *Cointop) CheckPriceAlert(alert *PriceAlert) error {
if msg != "" {
if ct.State.priceAlerts.SoundEnabled {
notifier.NotifyWithSound(title, msg)
notifier.Notify(title, msg)
} else {
notifier.Notify(title, msg)
}

@ -5,7 +5,6 @@ require (
github.com/anaskhan96/soup v1.1.1 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/creack/pty v1.1.11
github.com/faiface/beep v1.0.2
github.com/fatih/color v1.9.0
github.com/gen2brain/beeep v0.0.0-20200526185328-e9c15c258e28
github.com/gliderlabs/ssh v0.3.0

@ -26,14 +26,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/faiface/beep v1.0.2 h1:UB5DiRNmA4erfUYnHbgU4UB6DlBOrsdEFRtcc8sCkdQ=
github.com/faiface/beep v1.0.2/go.mod h1:1yLb5yRdHMsovYYWVqYLioXkVuziCSITW1oarTeduQM=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.1.1/go.mod h1:K1udHkiR3cOtlpKG5tZPD5XxrF7v2y7lDq7Whcj+xkQ=
github.com/gen2brain/beeep v0.0.0-20200526185328-e9c15c258e28 h1:M2Zt3G2w6Q57GZndOYk42p7RvMeO8izO8yKTfIxGqxA=
github.com/gen2brain/beeep v0.0.0-20200526185328-e9c15c258e28/go.mod h1:ElSskYZe3oM8kThaHGJ+kiN2yyUMVXMZ7WxF9QqLDS8=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -58,27 +54,17 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
github.com/gopherjs/gopherwasm v1.1.0 h1:fA2uLoctU5+T3OhOn2vYP0DVT6pxc7xhTlBB1paATqQ=
github.com/gopherjs/gopherwasm v1.1.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM=
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@ -89,7 +75,6 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/maruel/panicparse v1.5.0 h1:etK4QAf/Spw8eyowKbOHRkOfhblp/kahGUy96RvbMjI=
github.com/maruel/panicparse v1.5.0/go.mod h1:aOutY/MUjdj80R0AEVI9qE2zHqig+67t2ffUDDiLzAM=
@ -102,13 +87,11 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mewkiz/flac v1.0.5/go.mod h1:EHZNU32dMF6alpurYyKHDLYpW1lYpBZ5WrXi/VuNIGs=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miguelmota/go-coinmarketcap v0.1.6 h1:YIe+VdFhEgyGESfmkL7BHRDIdf6CUOAjJisml01AFqs=
github.com/miguelmota/go-coinmarketcap v0.1.6/go.mod h1:Jdv/kqtKclIElmoNAZMMJn0DSQv+j7p/H1te/GGnxhA=
@ -130,8 +113,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@ -178,14 +159,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
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/net v0.0.0-20180215212450-dc948dff8834/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -203,7 +178,6 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -228,7 +202,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

File diff suppressed because one or more lines are too long

@ -1,16 +1,6 @@
package notifier
import (
"bytes"
"encoding/hex"
"io"
"io/ioutil"
"strings"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
notifylib "github.com/gen2brain/beeep"
)
@ -18,51 +8,3 @@ import (
func Notify(title string, msg string) error {
return notifylib.Notify(title, msg, "")
}
// NotifyWithSound ...
func NotifyWithSound(title string, msg string) error {
err := Notify(title, msg)
if err != nil {
return err
}
err = PlaySound()
if err != nil {
return err
}
return nil
}
// PlaySound ...
func PlaySound() error {
f, err := mp3File()
if err != nil {
return err
}
streamer, format, err := mp3.Decode(f)
if err != nil {
return err
}
defer streamer.Close()
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
done := make(chan bool)
speaker.Play(beep.Seq(streamer, beep.Callback(func() {
done <- true
})))
<-done
return nil
}
func mp3File() (io.ReadCloser, error) {
r := strings.TrimRight(strings.TrimLeft(Mp3(), "\r\n"), "\r\n")
mp3Bytes, err := hex.DecodeString(r)
if err != nil {
return nil, err
}
f := ioutil.NopCloser(bytes.NewReader(mp3Bytes))
return f, nil
}

@ -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(&params);
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:]
}

3
vendor/golang.org/x/exp/AUTHORS generated vendored

@ -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.

27
vendor/golang.org/x/exp/LICENSE generated vendored

@ -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.

22
vendor/golang.org/x/exp/PATENTS generated vendored

@ -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.

27
vendor/golang.org/x/image/LICENSE generated vendored

@ -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.

22
vendor/golang.org/x/image/PATENTS generated vendored

@ -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…
Cancel
Save